Bash 循环遍历文件过早结束

标签 bash while-loop

我在约 20k 行的文本文件中循环 Bash 时遇到问题。

这是我的(最小化的)代码:

LINE_NB=0
while IFS= read -r LINE; do
    LINE_NB=$((LINE_NB+1))
    CMD=$(sed "s/\([^ ]*\) .*/\1/" <<< ${LINE})
    echo "[${LINE_NB}] ${LINE}: CMD='${CMD}'"   
done <"${FILE}"

while 循环在数百次迭代后过早结束。但是,如果我删除 CMD=$(sed...) 部分,循环将正常工作。因此,很明显,存在一些我无法发现的干扰。

我准备好了here ,我也试过:

LINE_NB=0
while IFS= read -r -u4 LINE; do
    LINE_NB=$((LINE_NB+1))
    CMD=$(sed "s/\([^ ]*\) .*/\1/" <<< ${LINE})
    echo "[${LINE_NB}] ${LINE}: CMD='${CMD}'"
done 4<"${FILE}"

但没有任何变化。对此行为的任何解释以及如何解决它的帮助?

谢谢!

为了澄清 user1934428 的情况(感谢您的关注!),我现在创建了一个最小脚本并添加了“set -x”。完整脚本如下:

#!/usr/bin/env bash
set -x
FILE="$1"
LINE_NB=0

while IFS= read -u "$file_fd" -r LINE; do
  LINE_NB=$((LINE_NB+1))
  CMD=$(sed "s/\([^ ]*\) .*/\1/" <<< "${LINE}")
  echo "[${LINE_NB}] ${LINE}: CMD='${CMD}'" #, TIME='${TIME}' "

done {file_fd}<"${FILE}"

echo "Done."

输入文件是一个约 20k 行的列表:

S1 0.018206
L1 0.018966
F1 0.006833
S2 0.004212
L2 0.008005
I8R190 18.3791
I4R349 18.5935
...

while 循环在(看似)随机点处过早结束。一种可能的输出是:

+ FILE=20k/ir-collapsed.txt
+ LINE_NB=0
+ IFS=
+ read -u 10 -r LINE
+ LINE_NB=1
++ sed 's/\([^ ]*\) .*/\1/'
+ CMD=S1
+ echo '[1] S1 0.018206: CMD='\''S1'\'''
[1] S1 0.018206: CMD='S1'
+ echo '[6510] S1514 0.185504: CMD='\''S1514'\'''
...[snip]...
[6510] S1514 0.185504: CMD='S1514'
+ IFS=
+ read -u 10 -r LINE
+ echo Done.
Done.

如您所见,循环在第 6510 行之后提前结束,而输入文件的长度约为 20k 行。

最佳答案

是的,制作稳定的文件副本是最好的开始。
学习 awk 和/或 perl 仍然非常值得您花时间。它并不像看起来那么难。 :)

除此之外,还有一些优化 - 如果可以避免,尽量不要在循环内运行任何程序。对于一个 20k 行的文件,那是 20k sed,这实际上是不必要的。相反,您可以为此使用参数解析。

# don't use all caps.
# cmd=$(sed "s/\([^ ]*\) .*/\1/" <<< "${line}") becomes
cmd="${cmd%% *}" # strip everything from the first space

使用 read 来处理这个问题会更好,因为您已经在使用它了,但是如果可以避免的话就不要生成另一个。尽管我很喜欢它,但 read 效率很低;它必须进行大量操作才能处理所有选项。

while IFS= read -u "$file_fd" cmd timeval; do
  echo "[$((++line_nb))] CMD='${CMD}' TIME='${timeval}'"
done {file_fd}<"${file}"

while IFS= read -u "$file_fd" -r -a tok; do
  echo "[$((++line_nb))] LINE='${tok[@]}' CMD='${tok[0]}' TIME='${tok[1]}'"
done {file_fd}<"${file}"

(这将排序重建行,但如果有制表符或额外的空格等,它只会填充 $IFS 的第一个字符,这默认情况下是一个空格。在这里应该无关紧要。)

不过,

awk 会很快完成这项工作,而且速度会快很多,而且已经内置了更好的工具。

awk '{printf "NR=[%d] LINE=[%s] CMD=[%s] TIME=[%s]\n",NR,$0,$1,$2 }' 20k/ir-collapsed.txt

运行一些时间比较 - 使用和不使用 sed,一次 read 与两次,然后将每个与 awk 进行比较。 :)

每一行要做的事情越多,文件中的行越多,它就越重要。养成尽可能整齐地做小事的习惯 - 从长远来看,它会带来丰厚的返回。

关于Bash 循环遍历文件过早结束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63374701/

相关文章:

python - 我如何确保 Python while 循环需要特定的时间来运行?

c - C 中 do-while 循环的意外行为?

java - 输入数字直到0,然后计算平均值

bash - AWK 中的条件

linux - 不重复的排列列

linux - 自定义 bash 通配符扩展

c++ - 是否可以在没有用户输入的情况下跳过 getline()?

php - 调用资源上的成员函数 fetch_object()

python - 如何自动更新 python/bash 脚本?

arrays - Bash 将文本文件格式化为列