补充CodeGnome's helpful answer,解释你的命令是如何实际工作的,以及为什么它没有按照你的意愿运行:
在 Bash 的语法中,输入重定向(例如 < data.txt)是 单个 命令的一部分,而 |(管道符号)链接 多个 命令,来自从左到右,形成一个管道。
从技术上讲,while ... done ... < data.txt | grep Iteration 是由 2 个命令组成的单个管道:
换句话说:
相比之下,听起来您想将grep 应用到data.txt首先,并且只将匹配的行发送到while loop。 p>
您有以下选项用于将命令的输出发送到另一个命令:
注意:为简洁起见,以下解决方案使用简化的 while 循环 - while 命令是单行还是跨多行无关紧要。
此外,data.txt 不是使用输入重定向 (< data.txt) 将文件内容传递给 grep,而是作为 文件名参数 传递。
选项 1:将输出发送到while 循环的命令首先放在管道中:
grep 'Iteration' data.txt | while read -r a; do echo "$a"; done
这种方法的缺点是您的 while 循环随后会在 subshell 中运行(就像管道的所有段默认情况下一样),这意味着在您的while 命令中定义或修改的变量对于当前 shell 是不可见的。
在 Bash v4.2+ 中,您可以通过运行 shopt -s lastpipe 解决此问题,这会告诉 Bash 运行 last 管道段 - while 命令这种情况 - 在 current shell 中。
请注意,lastpipe 是 POSIX 标准的非标准 bash 扩展。
(要在 交互式 shell 中尝试此操作,您必须先使用 set +m 关闭作业控制。)
选项 2:使用process substitution:
简单地说,进程替换 <(...) 允许您将命令输出显示为临时文件的内容,该文件会自行清理。
由于<(...) 扩展为临时文件(FIFO's)的path,并且while 循环中的read 只接受stdin 输入,因此也必须应用输入重定向:< <(...):
while read -r a; do echo "$a"; done < <(grep 'Iteration' data.txt)
这种方法的优点是while 循环在current 子shell 中运行,因此在命令完成后任何变量定义或修改都保留在范围内。
这种方法的潜在缺点是进程替换是对 POSIX 标准的非标准 bash 扩展(尽管 ksh 和 zsh 也支持它们)。
选项 3:在此处文档中使用命令替换:
首先在管道中使用命令(选项 1)是符合 POSIX 的方法,但不允许您修改 current shell 中的变量(并且 Bash 的 lastpipe 选项不是POSIX 兼容)。
将命令输出发送到在 当前 shell 中运行的命令的唯一 POSIX 兼容方法是在双引号here-document:
while read -r a; do echo "$a"; done <<EOF
$(grep 'Iteration' data.txt)
EOF
简化您的代码并使其更加健壮:
您的代码的其余部分存在一些值得解决的不明显缺陷:
双引号引用您的变量引用(例如,echo "$a" 而不是 echo $a),除非您特别希望将分词和通配符(文件名扩展)应用于值;分词和通配符是shell expansions的两种。
同样,不要使用for 来迭代(必须不带引号的)变量引用(在你的情况下不要使用for word in $a),除非你想要globbing 应用于单个单词 - 看看运行 $a='one *'; for word in $a; do echo "$word"; done 时会发生什么
您可以事先关闭通配符 (set -f),然后再打开 (set +f),但最好先使用 read -ra words ... 将单词读入 array,然后安全地迭代带有for word in "${words[@]}"; ... 的数组元素——注意数组变量引用周围的"..."。
始终使用-r 和read;没有它,很少使用 \-preprocessing 被应用,它会“吃掉”嵌入的 \ 字符。
如果我们听从上面的建议,应用一些额外的调整,并使用进程替换将grep 的输出提供给while 循环,我们得到:
count=1
while read -r a; do # Note the -r
if (( ++count <= 2 )); then
echo "$a"
# Split $a safely into words and store the words in
# array variable ${words[@]}.
read -ra words <<<"$a" # Note the -a to read into an *array*.
# Loop over the words (elements of the array).
# Note: To simply print the words, you could use
# `printf '%s\n' "${words[@]}"`` instead of the loop.
for word in "${words[@]}"; do
echo "$word"
done
fi
done < <(grep 'Iteration' data.txt)
注意:正如所写,您根本不需要 循环,因为您总是在第一次迭代后退出。
最后,作为较大输入集的一般替代方案,请考虑Ed Morton's helpful answer,由于使用awk 来处理您的输入文件,它要快得多,而在shell 代码中循环通常很慢。