Bash如何不使用管道实现stdout同时输出到终端与文件
问题原因
管道|在Bash中会为两侧的命令创建独立子shell,你写的for循环属于管道左侧执行块,循环内部对myVar1的修改仅在子shell环境中生效,循环结束回到父shell后,父shell内的myVar1仍保留原值,就出现了你看到的变量值不一致问题。
解决方案
你查到的基于exec操作文件描述符+进程替换的思路是正确的,这种实现不会触发子shell,循环内的变量修改可以正常在当前shell环境生效,同时实现输出同时打印到stdout和写入日志文件。
方案1:整段代码输出双写
适合需要把一段连续逻辑的所有输出同时打印到终端、追加写入日志的场景,核心逻辑是先备份原stdout到临时文件描述符,重定向当前shell的stdout到tee进程,逻辑执行完成后恢复原stdout、关闭临时文件描述符。
完整脚本示例:
#!/usr/bin/env bash myVar1=X.Y.Z.T # 备份stdout到文件描述符3,将stdout重定向到tee实现输出双写 exec 3>&1 1> >(tee -a myFile.log) echo "=> BEFORE THE LOOP : myVar1 = $myVar1" for IP;do myVar1="$IP" echo "=> INSIDE THE LOOP : myVar1 = $myVar1" done # 恢复原stdout,关闭临时文件描述符3 exec 1>&3 3>&- echo "=> AFTER THE LOOP : myVar1 = $myVar1"
执行效果:
$ ./myScript.sh 1.2.3.4 9.8.7.6 => BEFORE THE LOOP : myVar1 = X.Y.Z.T => INSIDE THE LOOP : myVar1 = 1.2.3.4 => INSIDE THE LOOP : myVar1 = 9.8.7.6 => AFTER THE LOOP : myVar1 = 9.8.7.6
所有输出会正常打印到终端,同时追加写入myFile.log,循环内对myVar1的修改在循环外正常生效。
方案2:仅循环块输出双写
如果只需要把for循环内的输出双写,循环外的输出不需要写入日志,可以直接给循环块加重定向,不需要修改全局文件描述符:
#!/usr/bin/env bash myVar1=X.Y.Z.T echo "=> BEFORE THE LOOP : myVar1 = $myVar1" for IP;do myVar1="$IP" echo "=> INSIDE THE LOOP : myVar1 = $myVar1" done > >(tee -a myFile.log) 2>&1 echo "=> AFTER THE LOOP : myVar1 = $myVar1"
注意事项:
- 进程替换
>(...)是Bash专属特性,脚本必须用bash执行,不能用sh解释运行- 使用
exec修改全局文件描述符后,一定要记得恢复原stdout、关闭临时文件描述符,否则后续脚本输出会持续被tee捕获,无法正常打印到终端
内容的提问来源于stack exchange,提问作者SebMa
相关产品推荐
相关产品推荐

