Bash循环遍历文件过早结束

问题描述

我无法在约2万行的文本文件中进行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."

输入文件是以下格式的约2万行的列表:

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行之后过早结束,而输入文件的长度约为2万行。

解决方法

是的,创建稳定的文件副本是最好的开始。
学习awk和/或perl仍然很值得。它并不像看起来那么难。 :)

除此之外,还进行了一些优化-尽量避免在循环内运行任何程序。对于2万行文件,则为2万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}"

(这将对行进行 sort 的重新构建,但是如果有制表符或多余的空格等,它将仅填充$IFS的第一个字符,即默认值。在这里无关紧要。)

awk可以简化此工作,并且可以更快地使用内置的更好的工具。

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

运行一些时间比较-在有和没有sed的情况下,将一个read与两个进行比较,然后将每个与awk进行比较。 :)

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