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

时间:2013-08-05 17:44:35

标签: bash

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

这允许在命令组(http://mywiki.wooledge.org/BashGuide/CompoundCommands)之外显示变量赋值等内容。

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

{ { 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;

关于“将stdout和stderr管道传递给shell脚本中的两个不同进程?”在这里阅读:pipe stdout and stderr to two different processes in shell script?

{ echo "Result is 13"; echo "ERROR: division by 0" 1>&2; }

模拟输出到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

4 个答案:

答案 0 :(得分:0)

我想,你错过/ bin / true returs 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;

         

RES:1

  

答案 1 :(得分:0)

我尝试了一个测试程序:

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

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

确实 - 看起来管道将先前的代码强制转换到另一个进程,但没有重新初始化bash - 所以$BASHPID更改但$$更改。

有关$$$BASHPID之间差异的详细信息,请参阅Difference between bash pid and $$

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

答案 2 :(得分: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

请注意,这仅适用于获取组中 last 命令的退出状态:

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

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

答案 3 :(得分:0)

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

~$ r=init; { /bin/true; r=$?; } | cat; echo $r;
init

管道创建一个子shell。在管道中,退出状态被分配给其值被删除的变量r的(本地)副本。

所以我想将我的解决方案添加到orginating challenge中,以便在跟踪退出状态的同时将输出发送到日志文件和终端。我决定使用另一个文件描述符。单行格式化可能有点令人困惑......

{ { 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

...所以我申请了一些缩进:

{
    {
        :   # 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

不介意行"没有操作"。它显示论坛的格式检查器依赖于它,否则会坚持:"您的帖子似乎包含未正确格式化为代码的代码。请使用代码工具栏按钮或CTRL + K键盘快捷键将所有代码缩进4个空格。如需更多编辑帮助,请单击[?]工具栏图标。"

如果执行它会产生以下输出:

r=1

出于演示目的,我将终端输出重定向到文件stdout.termstderr.term

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

让我解释一下:

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

                {
                    echo "Result is 13"
                    echo "ERROR: division by 0" 1>&2
                    /bin/false; echo $? 1>&4
                } | tee stdout.txt;
    
  2. 通过以下代码,使用文件描述符3作为虚拟交换stdout和stderr流。这样就会将错误消息发送到文件stderr.txt

            {
                ...
            } 3>&1 1>&2 2>&3 | tee stderr.txt;
    
  3. 退出状态已在步骤1中发送到文件描述符4.现在将其重定向到文件描述符1,后者定义变量r的值。错误消息被重定向到文件描述符2,而正常输出("结果为13")附加到文件描述符3:

        r=$( { 
            ...
        } 4>&1 1>&2 2>&3 );
    
  4. 最后文件描述符3被重定向到文件描述符1.这控制输出"结果是13":

    {
        ...
    } 3>&1;
    
  5. 最外面的花括号只显示命令的行为。

    Gordon Davisson建议利用数组变量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