在 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
错误?我怎样才能简单地PIPESTATUS
从eval
子shell中导出?
编辑2:感谢下面标记的出色答案,我的最终决定是针对我动态组装包含重定向的命令字符串的情况(这就是我需要 eval 的原因),我将使用set -o pipefail
然后在执行后执行 do set +o pipefail
。
我还在重新研究执行动态构建命令的最佳实践,因为自上次以来我已经有了几年的 bash 经验。我的用例eval
是:
- 命令可能包含
bash -c
,所以我不能用来bash -c
执行它们 - 可能包含重定向,例如
>> log
- 动态命令还可以添加参数以用于调试目的
据我所知,我可能可以对 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 ))
。
false
or(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-get
或tee
失败时返回失败。请注意,函数体是一个子 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 pipefail
apt-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 $?
我觉得可以做一些事情来将输出输入awk
以readarray
捕获不同的管道状态,但这将非常混乱(除了已经混乱之外)。
当事情变得如此丑陋时,你必须开始怀疑整个方法是否错误。
也许如果你更详细地阐述你的需求,可能会出现一种不同的方式来实现它。