如何结合 bash 命令分组和管道状态

use*_*487 6 bash

如何结合 bash 命令分组和管道状态?

这是一个示例命令组:

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2; } 3>&1 | gzip --rsyncable > my_file.tar.gz
Run Code Online (Sandbox Code Playgroud)


这是一个示例管道状态读数,以配合上述内容:

[[ ${PIPESTATUS[*]} =~ [1-9] ]] && rm my_file.tar.gz
Run Code Online (Sandbox Code Playgroud)


在这个例子中,命令组使邮件假脱机没有关于“从 tar 中删除前导 /”的警告,通过 cron 传送,因为它们落在 stderr(Unices 缺少 stdwarn 并且 tar 缺少安静选项),同时让真正的错误通过.

管道状态读出确保立即删除损坏的备份文件,以防止使用标准 FIFO 算法在以后清除旧的有效文件。

但是这个例子不起作用。上面的管道状态包含[1 0],即grep和gzip的退出码,但不包含tar。

我尝试过的一种尝试是:

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2; GROUPSTATUS=${PIPESTATUS[*]}; } 3>&1 | gzip --rsyncable > my_file.tar.gz
[[ $GROUPSTATUS =~ [1-9] ]] && rm my_file.tar.gz
Run Code Online (Sandbox Code Playgroud)

但离开组时 GROUPSTATUS 为空。

(请注意,通过将 GROUPSTATUS 设置为管道状态以外的任何内容,例如某种类型的文字,可以验证该变量在正常情况下实际上确实脱离了命令分组范围。)

我也试过return从组内是否可以将第一个管道组件的退出代码传递到外部,但在命令组内返回只会产生来自 bash 的错误消息。

Gil*_*il' 5

执行管道时,每个管道分隔元素都在其自己的进程中执行。变量赋值只在它们自己的进程中生效。在ksh和zsh下,管道的最后一个元素在原shell中执行;在 bash 等其他 shell 下,每个管道元素都在其自己的子 shell 中执行,而原始 shell 只是等待它们全部结束。

$ bash -c 'GROUPSTATUS=foo; echo GROUPSTATUS is $GROUPSTATUS'
GROUPSTATUS is foo
$ bash -c 'GROUPSTATUS=foo | :; echo GROUPSTATUS is $GROUPSTATUS'
GROUPSTATUS is 
Run Code Online (Sandbox Code Playgroud)

在您的情况下,由于您只关心所有成功的命令,您可以使状态代码向上流动。

{ tar -cf - my_folder 2>&1 1>&3 | grep -v "Removing leading" 1>&2;
  ! ((PIPESTATUS[0])); } 3>&1 |
gzip --rsyncable > my_file.tar.gz;
if ((PIPESTATUS[0] || PIPESTATUS[1])); then rm my_file.tar.gz; fi
Run Code Online (Sandbox Code Playgroud)

如果您想从管道左侧获取超过 8 位的信息,您可以写入另一个文件描述符。这是一个原理验证示例:

{ { tar …; echo $? >&4; } | …; } | { gzip …; echo $? >&4; } \
  4>&1 | ! grep -vxc '0'
Run Code Online (Sandbox Code Playgroud)

一旦获得标准输出上的数据,就可以使用命令替换将其输入到 shell 变量中,即$(…). 命令替换从命令的标准输出中读取,因此如果您还打算将内容打印到脚本的标准输出,则它们需要临时通过另一个文件描述符。以下代码段将 fd 3 用于最终进入脚本标准输出的事物,而 fd 4 用于捕获到$statuses.

statuses=$({ { tar -v … >&3; echo tar $? >&4; } | …; } |
           { gzip …; echo gzip $? >&4; } 4>&1) 3>&1
Run Code Online (Sandbox Code Playgroud)

如果您需要将不同命令的输出捕获到不同的变量中,我认为即使在 bash、ksh 或 zsh 等“高级”shell 中也没有直接的方法。以下是一些解决方法:

  • 使用临时文件。
  • 使用单个输出流,例如每行带有前缀以指示其来源,并在顶层进行过滤。
  • 使用更高级的语言,例如 Perl 或 Python。


小智 1

一个简单的方法是绕过这个问题,即将 my_folder 放入 shell 变量中并从中删除前导斜杠:

tar -cf - ${my_folder##/} | gzip --rsyncable > my_file.tar.gz
Run Code Online (Sandbox Code Playgroud)

否则,您可以检查块内的 ${PIPESTATUS[0]} ,如果它不为零,则使用 false 向外部进程发出错误信号:

{ tar -cf - my_folder 2>&1 1>&3 | 
    grep -v "Removing leading" 1>&2; 
    [ ${PIPESTATUS[0]} -eq 0 ] && true || false; } 3>&1 | 
    gzip --rsyncable > my_file.tar.gz
Run Code Online (Sandbox Code Playgroud)