在bash中的复合命令中设置变量失败(命令组)

在bash中的复合命令中设置变量失败(命令组),bash,Bash,group命令{list;}应该在当前shell环境中执行list 这允许变量赋值等内容在命令组()之外可见 我使用它将输出发送到日志文件以及终端: { { echo "Result is 13"; echo "ERROR: division by 0" 1>&2; } | tee -a stdout.txt; } 3>&1 1>&2 2>&3 | tee -a stderr.txt; 关于主题“在shell脚本中将stdout和stde

group命令
{list;}
应该在当前shell环境中执行list

这允许变量赋值等内容在命令组()之外可见

我使用它将输出发送到日志文件以及终端:

{ { echo "Result is 13"; echo "ERROR: division by 0" 1>&2; } | tee -a stdout.txt; } 3>&1 1>&2 2>&3 | tee -a stderr.txt;
关于主题“在shell脚本中将stdout和stderr管道连接到两个不同的进程?”请阅读以下内容:

模拟输出到stdout和stderr的命令

我还想评估退出状态
/bin/true
/bin/false
模拟可能成功或失败的命令。因此,我尝试将
$?
保存到变量
r

~$ r=init; { /bin/true; r=$?; } | cat; echo $r;
init
~$ r=init; { /bin/true; r=$?; } 2>/dev/null; echo $r;
0
正如您所看到的,上面的管道构造没有设置变量
r
,而第二个命令行导致了预期的结果。是虫子还是我的错?谢谢

我用以下版本的bash测试了Ubuntu 12.04.2 LTS(
~$
)和Debian GNU/Linux 7.0(wheezy)(
~#
):

~$ echo $BASH_VERSION
4.2.25(1)-release

~# echo $BASH_VERSION
4.2.37(1)-release

我想,您错过了/bin/true返回0和/bin/false返回1

$r='res:';{/bin/true;r+=$?;}2>/dev/null;echo$r

res:0

$r='res:';{/bin/false;r+=$?;}2>/dev/null;echo$r

决议:1


我尝试了一个测试程序:

x=0
{ x=$$ ; echo "$$ $BASHPID $x" ; }
echo $x

x=0
{ x=$$ ; echo "$$ $BASHPID $x" ; } | cat
echo $x
事实上,管道似乎将先前的代码强制进入另一个进程,但没有重新初始化bash,因此
$BASHPID
发生了变化,但
$
发生了变化

有关
$
$BASHPID
之间差异的更多详细信息,请参阅


同时输出
$BASH_SUBSHELL
显示第二位在子shell(级别1)中运行,第一位在级别0处。

BASH将管道的所有元素作为子进程执行;如果它们是shell内置或命令组,这意味着它们在子shell中执行,因此它们设置的任何变量都不会传播到父shell。一般来说,这可能很难解决,但如果您只需要命令组的退出状态,则可以使用$PIPESTATUS数组来获取它:

$ { false; } | cat; echo "${PIPESTATUS[@]}"
1 0
$ { false; } | cat; r=${PIPESTATUS[0]}; echo $r
1
$ { true; } | cat; r=${PIPESTATUS[0]}; echo $r
0
请注意,这仅适用于获取组中最后一个命令的退出状态:

$ { false; true; false; uselessvar=$?; } | cat; r=${PIPESTATUS[0]}; echo $r
0

。。。因为
uselessvar=$?
成功。

使用变量保持退出状态对于管道来说不是合适的方法:

~$ r=init; { /bin/true; r=$?; } | cat; echo $r; init 。。。因此,我应用了一些缩进:

{ { : # no operation r=$( { { { echo "Result is 13" echo "ERROR: division by 0" 1>&2 /bin/false; echo $? 1>&4 } | tee stdout.txt; } 3>&1 1>&2 2>&3 | tee stderr.txt; } 4>&1 1>&2 2>&3 ); } 3>&1; } 1>stdout.term 2>stderr.term; echo r=$r 让我解释一下:

  • 下面的group命令模拟生成错误代码1以及一些错误消息的某个命令。文件描述符4在步骤3中声明:

    { echo "Result is 13" echo "ERROR: division by 0" 1>&2 /bin/false; echo $? 1>&4 } | tee stdout.txt;
  • 退出状态已在步骤1中发送到文件描述符4。它现在被重定向到文件描述符1,它定义了变量r的值。错误消息被重定向到文件描述符2,而正常输出(“结果为13”)被附加到文件描述符3:

    r=$( { ... } 4>&1 1>&2 2>&3 );
  • r=$({ ... } 4>&1 1>&2 2>&3 );
  • 最后,文件描述符3被重定向到文件描述符1。这控制输出“结果为13”: { ... } 3>&1;
  • 最外面的大括号仅显示命令的行为

    Gordon Davidson建议利用数组变量PIPESTATUS,该变量包含最近执行的前台管道中进程的退出状态值列表。这可能是一种很有前途的方法,但会导致一个问题,即如何将其价值移交给封闭的管道

    ~# r=init; { { echo "Result is 13"; echo "ERROR: division by 0" 1>&2; } | tee -a stdout.txt; r=${PIPESTATUS[0]}; } 3>&1 1>&2 2>&3 | tee -a stderr.txt; echo "Can you tell me the exit status? $r" ERROR: division by 0 Result is 13 Can you tell me the exit status? init ~#r=init;{{echo“结果是13;echo”错误:除以0“1>&2;}tee-a stdout.txt;r=${PIPESTATUS[0]};}3>&1>&2>&3 | tee-a stderr.txt;echo“你能告诉我退出状态吗?$r” 错误:被0除 结果是13 你能告诉我退出的情况吗?初始化
    虽然
    {…}
    确实意味着在当前的shell环境中运行一个简单的命令,
    {/code>(以及某些其他构造)意味着在子shell中运行一个或多个命令,而不管它是否由
    {…}
    (…)
    包围。我理解。Manpage正确地指出:“管道中的每个命令都作为一个单独的进程执行(即,在子shell中)。”这就是答案。感谢Douglas Leeder,他指出了如何分析关于子壳的混淆。 root@voipterm1:~# cat stdout.txt Result is 13 root@voipterm1:~# cat stderr.txt ERROR: division by 0 root@voipterm1:~# cat stdout.term Result is 13 root@voipterm1:~# cat stderr.term ERROR: division by 0 { echo "Result is 13" echo "ERROR: division by 0" 1>&2 /bin/false; echo $? 1>&4 } | tee stdout.txt; { ... } 3>&1 1>&2 2>&3 | tee stderr.txt; r=$( { ... } 4>&1 1>&2 2>&3 ); { ... } 3>&1; ~# r=init; { { echo "Result is 13"; echo "ERROR: division by 0" 1>&2; } | tee -a stdout.txt; r=${PIPESTATUS[0]}; } 3>&1 1>&2 2>&3 | tee -a stderr.txt; echo "Can you tell me the exit status? $r" ERROR: division by 0 Result is 13 Can you tell me the exit status? init