在Bash中,tee会将函数变量变为局部变量,我该如何避免这种情况?

3

我被一段应该既输出到标准输出又写入文件的bash脚本难住了。我在函数中使用了一些变量。每当我尝试使用tee将函数重定向到一个文件并打印到屏幕上时,我无法使用在函数中使用的变量,它们似乎变成了局部变量。

这里是一个简单的示例:

#!/bin/bash
LOGV=/root/log

function var()
{
echo -e "Please, insert VAR value:\n"
read -re VAR
}
var 2>&1 | tee $LOGV
echo "This is VAR:$VAR"

输出:

[root@testbox ~]# ./var.sh   
Please, insert VAR value:

foo
This is VAR:
[root@testbox ~]#

提前致谢!

编辑: 响应@Etan Reisner的建议,使用 var 2>&1 > >(tee $LOGV)

这种构造的唯一问题是日志文件没有接收到全部内容...

[root@testbox~]# ./var.sh
Please, insert VAR value: 

foo 
This is VAR:foo
[root@testbox ~]# cat log 
Please, insert VAR value:

2
我认为你的问题在于,由于你正在使用管道(进入tee),因此对var的初始调用发生在子shell中。因此,你在该子进程中设置了一个环境变量,但并不会影响主(父)进程的环境。 - Steve Summit
你想要在输出文件中加入提示信息“请插入...”吗? - William Pursell
谢谢,是的,我想这是子shell的问题,但我还无法解决。 William Pursell,是的,我知道! - LinenG
1
你的最终目标是什么?如果你想让子shell影响主shell,就需要避免在子shell中进行变量赋值。如果你的最终目标是后置代码片段,那么你唯一的解决方案(据我所知)就是用类似于> >(tee $LOGV)这样的进程替换和输出重定向来替换管道。 - Etan Reisner
回复:“日志没有接收到所有内容”--它没有接收到用户键入的本地回显,因为那是本地回显--它不在stdout或stderr上,而纯粹是终端构造。 - Charles Duffy
显示剩余4条评论
1个回答

7

这是BashFAQ#24的变体。

var 2>&1 | tee $LOGV

像任何shell管道一样,都有选项在子进程中运行var函数--在实践中,在bash中表现出这种方式。(POSIX sh规范未定义哪些管道组件(如果有)在父shell中运行的详细信息)。


避免这种情况就像不使用管道一样简单。

var > >(tee "$LOGV") 2>&1

使用进程替换(一种ksh扩展,被bash采用,不存在于POSIX sh中)来通过文件名(在现代Linux中的形式为/dev/fd/##)表示tee子进程,可以将输出重定向到该文件名而不必将函数移入管道中。


如果您想确保在运行其他命令之前退出tee,请使用锁:

#!/bin/bash
logv=/tmp/log

collect_var() {
        echo "value for var:"
        read -re var
}
collect_var > >(logv="$logv" flock "$logv" -c 'exec tee "$logv"') 2>&1
flock "$logv" -c true # wait for tee to exit

echo "This is var: $var"

顺带一提,如果你想运行多个命令并将它们的输出以此方式进行流水处理,你应该只调用tee一次,并根据需要进行输入:

#!/bin/bash
logv=/tmp/log
collect_var() { echo "value for var:"; read -re var; }

exec 3> >(logv="$logv" flock "$logv" -c 'exec tee "$logv"') # open output to log
collect_var >&3 2>&3         # run function, sending stdout/stderr to log
echo "This is var: $var" >&3 # ...and optionally run other commands the same way
exec 3>&-                    # close output
flock "$logv" -c true        # ...and wait for tee to finish flushing and exit.

我觉得我没有完全理解你的目标,使得上面的反对意义不明。你能详细描述一下你期望在运行命令后日志文件中包含的内容,以及导致你认为脚本没有退出的确切行为或输出吗?(至于第一个问题,你最初的基于管道的代码也没有将日志追加到logv,所以我不明白这是个问题)。 - Charles Duffy
Charles Duffy,最终目标是创建一个带有定义的全局变量的函数,该函数可以在重定向脚本执行的stdout/stderr到文件和屏幕的同时在函数外部使用。 P.S. 你说得对,我的错,我没有提到追加,抱歉。 - LinenG
现在,如果你想要提供多个“命令”来输入到 tee 中,那么你需要使用前面的 exec 格式。我将进行更新以展示结合了锁定。 - Charles Duffy
顺便说一下,如果您的 echo "This is var: $var" 也被馈送到了 tee 中,即在多行形式关闭 FD 之前位于 >&3 部分,则不会出现不同步的问题。 - Charles Duffy
Charles,这正是我想要实现的!感谢您提供的所有答案和详细示例! - LinenG
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接