评估:$?与 ${PIPESTATUS[@]} (bash)

评估:$?与 ${PIPESTATUS[@]} (bash)

在 bash 5.0 中,我希望捕获${PIPESTATUS[@]}通过eval.但是,eval似乎 mask ${PIPESTATUS[@]},但不 mask$?相当于${PIPESTATUS[-1]}。有没有办法${PIPESTATUS[@]}从结果中提取eval?在下面的命令字符串中添加一些内容似乎&& array=( ${PIPESTATUS[@]} ) && export array不起作用。

我的假设是否正确,这$?并不简单${PIPESTATUS[-1]}

我的示例代码(以 root 身份运行):

#!/usr/bin/env bash

#without eval, ${PIPESTATUS[@]} has two entries as it should.
apt-get install -y java-17-openjdk-amd64 2>&1 | tee -a ~/log 
commandsPipestatus=( ${PIPESTATUS[@]} )
for status in ${commandsPipestatus[@]}; do
    echo $status
done

echo ""
echo ""

#with eval, ${PIPESTATUS[@]} has one entry and is equal to $?
commandstring="apt-get install -y java-17-openjdk-amd64 2>&1 | tee -a ~/log"
eval "$commandstring"
commandsPipestatus=( ${PIPESTATUS[@]} )
for status in ${commandsPipestatus[@]}; do
    echo $status
done

编辑:修复了我关于 PIPESTATUS 的原始陈述中的一个小技术更正。此外,根据以下答案,以下是一些澄清:

  • 我使用它是eval因为我正在以编程方式构建命令字符串,其中一些可能包含bash -c...以不同用户身份运行命令。
  • 我查看了设置pipefail,有时可能会起作用,但并不总是有效,因为有时我需要知道管道中多个步骤的状态。如果我可以设置set -o pipefail然后取消设置它,那么就可以工作,但我不知道如何取消设置pipefail,并且我不能简单地退出子 shell,然后继续进入一个pipefail未设置的新子 shell。如何取消设置 shell 选项,例如pipefail
  • 我上面对如何导出包含的数组的理解是否PIPESTATUS错误?我怎样才能简单地PIPESTATUSeval子shell中导出?

编辑2:感谢下面标记的出色答案,我的最终决定是针对我动态组装包含重定向的命令字符串的情况(这就是我需要 eval 的原因),我将使用set -o pipefail然后在执行后执行 do set +o pipefail

我还在重新研究执行动态构建命令的最佳实践,因为自上次以来我已经有了几年的 bash 经验。我的用例eval是:

  1. 命令可能包含bash -c,所以我不能用来bash -c执行它们
  2. 可能包含重定向,例如>> log
  3. 动态命令还可以添加参数以用于调试目的

据我所知,我可能可以对 1 做不同的事情,而对于 3 我应该使用像set -x [command]andtrap之类的东西。我还没有找到2的解决方案。

答案1

$?包含最后运行的命令的最右侧组件的状态(或最右侧失败组件,如果pipefail已设置),尽管可能由!前缀关键字修改。

的元素$PIPESTATUS是前一个管道的每个组件的退出状态,!对值没有影响。

之后(exit 1) | (exit 2) | (exit 3),$PIPESTATUS将是 (1 2 3) 并且$?将是 3。之后! (exit 1) | (exit 2) | (exit 3),$PIPESTATUS将相同,但$?将是 0,与 相同$(( ! 3 ))

falseor(exit 1)仍然是一个带有一个组件的管道,在第一种情况下是一个简单的命令,在第二种情况下是一个子 shell,所以你得到PIPESTATUS=(1)and $?= 1。

这也是一样的,eval 'any shell code'只是一个简单的命令。

之后eval 'A | B' | C$PIPESTATUS将包含 的退出状态eval(这将是它运行的最后一个命令的退出状态:)B和 的退出状态C(A | B) | C顺便说一句,同样如此。

你可以这样做:

eval '
  A | B
  saved_STATUS=$? saved_PIPESTATUS=( "${PIPESTATUS[@]}" )
'

要保留管道组件的状态(如 调用的解释器所见)eval,因此在您的情况下:

preserve_status='
  saved_STATUS=$? saved_PIPESTATUS=( "${PIPESTATUS[@]}" )
'
eval "$commandstring $preserve_status"
commandsPipestatus=( "${saved_PIPESTATUS[@]}" )
for status in "${commandsPipestatus[@]}"; do
    echo "$status"
done

然而在这里,在我看来你宁愿想要:

install_java() (
  set -o pipefail
  apt-get install -y java-17-openjdk-amd64 2>&1 | tee -a ~/log
)

那是:

  • 将代码存储在函数中,而不是变量中
  • 使用该pipefail选项,以便该函数在apt-gettee失败时返回失败。请注意,函数体是一个子 shell,而不是更常见的指挥组命令 ( { ...; }) 表示该选项仅在本地设置。使用最新版本的bash,您还可以使用local -; set -o pipefail在函数中本地设置的选项,而不需要子 shell。

但请注意,正常输出和错误都apt-get将发送到 stdout。

如果使用zsh,你可以这样做:

exec {log}>> ~/log # at the beginning of the script for instance.
apt-get install -y java-17-openjdk-amd64 >&1 >&$log 2>&2 2>&$log

将 stderr 的 stdout 发送apt-get到日志及其各自的原始目的地。 ing在内部完成,并保留tee退出状态。apt-get

也许可以为此创建一个辅助函数,例如:

#! /bin/zsh -
die() { print -ru2 -C1 -- "$@"; exit 1; }
exec {log}>> ~/log
with_log() { "$@" >&1 >&$log 2>&2 2>&$log; }

with_log apt-get ... || die "Aborting..."

答案2

使用函数代替eval可能是更好的解决方案。

所以你可以这样做,如果需要的话允许安装多个包:

install() {
  apt-get install -y "${1}" 2>&1 | tee -a ~/log
}
packages=("java-17-openjdk-amd64")
for pkg in "${packages[@]}" ; do
  install pkg
  # replace below with actions based on status
  printf "%s\n" "${PIPESTATUS[@]}"
done

set -o pipefailapt-get是一种无需检查所有命令即可从命令中冒出错误的方法,但是如果您无法写入其日志PIPESTATUS,事情可能会变得有点模糊。tee

编辑:基于附加信息

我猜测您遇到的问题eval是因为它在子 shell 中运行您的命令(或者至少以类似的方式运行)。感觉就像你只会得到一个退出代码(很高兴这个假设是错误的)。

您可以在命令中打印管道状态,但获取它会很棘手。

这种怪物是你可以处理它的一种方法,但它非常复杂,并且在文本处理方面需要对你的命令进行一些定制,并且最后仍然只能得到一个退出代码

# just some stuff to print some text and exit with a non zero
# before the next pipeline step masks the exit code

foo='bash -c "echo hello;false" | tee log;echo PIPE ${PIPESTATUS[@]}'
# awk grabs the line with the pipe status text, pulls one of
# the exit codes from it and exits with it, otherwise prints
# the output as seen

eval "$foo" | awk '
  {if ($1=="PIPE"){print "just showing you I saw all the codes "$0;exit $2}; print $0}'
echo $?

我觉得可以做一些事情来将输出输入awkreadarray捕获不同的管道状态,但这将非常混乱(除了已经混乱之外)。

当事情变得如此丑陋时,你必须开始怀疑整个方法是否错误。

也许如果你更详细地阐述你的需求,可能会出现一种不同的方式来实现它。

相关内容