在 Ubuntu 16.04 下使用#!/bin/bash
我想要一个 eval'ed 命令...
- 提供直接输出
- 将输出获取到变量
- 检查评估结果
# valid command
foo=$(eval "ls" | tee /dev/tty);
echo ${PIPESTATUS[@]}
echo $foo
# invalid command
foo=$(eval "ls -ßnonsense" | tee /dev/tty);
echo ${PIPESTATUS[@]}
echo $foo
PIPESTATUS
我的问题是,当我得到一个有意义的
- 我完全删除了
tee /dev/tty
(否则,是的,它掩盖了作为第一个管道命令一部分的任何评估失败) - 或者将其放在括号之外
foo=$(eval "ls" ) | tee /dev/tty;
- ...但在这种情况下,我不再得到直接输出或 $foo 输出。
答案1
改用zsh
:
shell_code='ls' # or ls -ßnonsense...
{ foo=$(eval " $shell_code" >&1 >&3 3>&-); } 3>&1
print -r status=$? output=$foo
在这里,我们将原始 stdout (如果该脚本从终端未重定向地运行,则仅是控制 tty)复制到 fd 3,并将eval
的输出重定向到命令替换和 fd 3 (使用 zsh 自己的tee
ing当 fd 多次重定向以进行输出时的机制)。
在带有 的系统上使用和其他支持(但如果在 Linux/Cygwin 上则不支持 ksh93)bash
的 shell ,您可以执行类似的操作:pipefail
/dev/fd/<n>
shell_code='ls' # or ls -ßnonsense...
{
foo=$(
set -o pipefail
eval " $shell_code" 3>&- |
tee 4>&1 >&3 3>&- /dev/fd/4
)
} 3>&1
printf 'status=%s output=%s\n' "$?" "$foo"
打开pipefail
时,管道的退出状态是管道中失败的最右边命令的退出状态。所以这里将是eval
除非tee
失败。
eval
要无条件获取退出状态,bash
可以使用 :
shell_code='ls' # or ls -ßnonsense...
{
foo=$(
eval " $shell_code" 3>&- |
tee 4>&1 >&3 3>&- /dev/fd/4
exit "$PIPESTATUS"
)
} 3>&1
printf 'status=%s output=%s\n' "$?" "$foo"
在这些方法中,tee
的 stdout 是原始的 stdout,而命令替换管道通过 提供/dev/fd/4
。我们正在这样做,而不是相反(tee
将 stdout 发送到命令替换管道,通过某些命令写入原始 stdout /dev/fd/<n>
),尽管这会使代码变得更加复杂,但要解决后者不起作用的事实在 Linux 或 Cygwin 系统上,打开/dev/fd/<n>
与复制 fd 不同<n>
(但对于指向管道写入端的 fd 执行此操作,就像命令替换一样(不是使用套接字对的 ksh93)在功能上是等效的)
答案2
这是一个可移植的解决方案,应该适用于大多数(如果不是全部)Bourne 语法 shell;使用bash
、dash
、ksh93
、ksh88
、ash
、 (Bourne)sh
和进行测试zsh
:
for command in "ls -ßnonsense" "ls -ld /tmp"; do
foo=`{ eval "$command"; echo $? > /tmp/ps.$$; } | tee /dev/tty;`
st=`cat /tmp/ps.$$;rm /tmp/ps.$$`
echo "foo=$foo status=$st"
done