考虑:
numbers="1 111 5 23 56 211 63"
max=0
for num in ${numbers[@]}; do
[ $num -gt $max ]\
&& echo "old -> new max = $max -> $num"\
&& max=$num
done | tee logfile
echo "Max= $max"
如果我删除| tee logfile
max 变量,则会正确打印为 211,但如果我将其保留,则会得到Max= 0
.
到底是怎么回事?
答案1
管道的每一侧都在子 shell 中执行。子 shell 是原始 shell 进程的副本,它以相同的状态启动,然后独立演化。子 shell 中设置的变量无法转义回父 shell。
在 bash 中,您可以使用而不是使用管道流程替代。这与管道的行为几乎相同,只是循环在原始 shell 中执行并且仅tee
在子 shell 中执行。
for num in "${numbers[@]}"; do
if ((num > max)); then
echo "old -> new max = $max -> $num"
max=$num
fi
done > >(tee logfile)
echo "Max= $max"
当我在做的时候,我改变了你的脚本中的一些内容:
- 始终在变量替换周围使用双引号,除非您知道为什么需要将它们省略。
&&
当你的意思是时不要使用if
。如果使用命令的返回状态,它的可读性较差,并且不具有相同的行为。- 由于我使用的是 bash 特定的构造,所以我也可以使用算术语法。
请注意,管道解决方案和子 shell 解决方案之间存在细微差别:管道命令在两个命令都退出时完成,而具有进程替换的命令在主命令退出时完成,而不等待进程替换中的进程。这意味着当您的脚本完成时,日志文件可能尚未完全写入。
另一种方法是使用其他通道从子 shell 到原始 shell 进程进行通信。你可以使用临时文件(灵活,但做起来更难——将临时文件写入适当的目录,避免名称冲突(使用mktemp
),即使出现错误也将其删除)。或者你可以使用另一个管道传达结果并在命令替换中获取结果。
max=$({ { for … done;
echo "$max" >&4
} | tee logfile >&3; } 4>&1) 3&>1
的输出tee
转到文件描述符 3,该文件描述符被重定向到脚本的标准输出。的输出echo "$max"
转到文件描述符 4,该描述符被重定向到命令替换。
答案2
一旦将 for 循环放入管道中,它就会在子 shell 中运行,该子 shell 不会将其变量传递给 supershell。
答案3
它无法在管道中幸存,但这是有效的:
numbers="1 111 5 23 56 211 63"
max=0
echo $max>maxfile
for num in ${numbers[@]}; do
[ $num -gt $max ]\
&& echo "old -> new max = $max -> $num"\
&& max=$num\
&& echo $max>maxfile
done | tee logfile
read max<maxfile
echo "Max= $max"