3

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 管道传输到 shell 脚本中的两个不同进程?.

{ 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

4 回答 4

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

于 2013-08-05T18:44:13.980 回答
0

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

〜$ 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键盘快捷键将所有代码缩进 4 个空格。对于更多编辑帮助,请单击 [?] 工具栏图标。”

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

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
你能告诉我退出状态吗?在里面
于 2013-08-06T12:18:29.193 回答
0

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

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

资源:0

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

资源:1

于 2013-08-05T17:52:29.403 回答
0

我尝试了一个测试程序:

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

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

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

有关和 之间不同的更多详细信息,请参阅bash pid 和 $$$$之间的差异$BASHPID

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

于 2013-08-05T17:56:38.557 回答