我在约 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/