【问题标题】:setting variables inside a compound command in bash fails (command group)在 bash 中的复合命令中设置变量失败(命令组)
【发布时间】:2013-08-06 12:36:43
【问题描述】:

group命令{ 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;

关于“在 shell 脚本中将 stdout 和 stderr 传递给两个不同的进程?”主题?在这里阅读: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

【问题讨论】:

  • { ... } 确实意味着在当前 shell 环境中运行一个简单的命令,|(和某些其他结构)意味着在子 shell 中运行一个或多个命令,无论它是否被 @ 包围987654337@或( ... )或不。
  • 我明白了。手册页正确地指出:“管道中的每个命令都作为单独的进程执行(即在子外壳中)。”这就是答案。感谢 Douglas Leeder 指出了如何分析子 shell 的混淆。

标签: bash


【解决方案1】:

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

$ r='res:'; { /bin/true; r+=$?; } 2>/开发/空;回声$r;

分辨率:0

$ r='res:'; { /bin/false; r+=$?; } 2>/开发/空;回声$r;

分辨率:1

【讨论】:

    【解决方案2】:

    我尝试了一个测试程序:

    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。

    【讨论】:

      【解决方案3】:

      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=$? 成功了。

      【讨论】:

        【解决方案4】:

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

        〜$ r =初始化; { /bin/true; r=$?; } |猫;回声 $r; 在里面

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

        所以我想将我的解决方案添加到原始挑战中,以将输出发送到日志文件以及终端,同时跟踪退出状态。我决定使用另一个文件描述符。单行格式可能有点混乱...

        { { r=$( { { { echo "Result is 13"; echo "ERROR: 除以 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;回声 r=$r

        ...所以我应用了一些缩进:

        { { : # 无操作 r=$({ { { echo "结果是 13" echo "ERROR: 除以 0" 1>&2 /bin/假;回声$? 1>&4 } |三通标准输出.txt; 3>&1 1>&2 2>&3 |三通标准错误.txt; 4>&1 1>&2 2>&3 ); 3>&1; 1>stdout.term 2>stderr.term;回声 r=$r

        不要介意“不操作”这句话。它表明论坛的格式检查器依赖于它,否则会坚持:“您的帖子似乎包含未正确格式化为代码的代码。请使用代码工具栏按钮或 CTRL+K 键盘快捷键。有关更多编辑帮助,请单击 [?] 工具栏图标。"

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

        r=1

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

        root@voipterm1:~# cat stdout.txt 结果是 13 root@voipterm1:~# cat stderr.txt 错误:除以 0 root@voipterm1:~# cat stdout.term 结果是 13 root@voipterm1:~# cat stderr.term 错误:除以 0

        让我解释一下:

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

          { echo "结果是 13" echo "ERROR: 除以 0" 1>&2 /bin/假;回声$? 1>&4 } |三通标准输出.txt;
        1. 通过以下代码,stdout 和 stderr 流使用文件描述符 3 作为虚拟对象进行交换。这样错误消息就会发送到文件stderr.txt

          { ... 3>&1 1>&2 2>&3 |三通标准错误.txt;
        1. 退出状态已在步骤 1 中发送到文件描述符 4。它现在被重定向到定义变量 r 值的文件描述符 1。错误消息被重定向到文件描述符 2,而正常输出(“结果为 13”)附加到文件描述符 3:

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

        最外面的花括号只是显示命令的行为方式。

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

        ~# r=init; { { echo "结果为 13"; echo "ERROR: 除以 0" 1>&2; } | tee -a 标准输出.txt; r=${PIPESTATUS[0]}; 3>&1 1>&2 2>&3 | tee -a stderr.txt; echo "你能告诉我退出状态吗?$r" 错误:除以 0 结果是 13 你能告诉我退出状态吗?在里面

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-24
          • 1970-01-01
          • 2012-07-22
          • 2012-03-02
          相关资源
          最近更新 更多