翻译自http://mywiki.wooledge.org/BashFAQ/002

如何保存命令的返回值到一个变量中, 这个取决于你是想保存命令的输出,还是他的返回码(0到255, 一般来说0代表成功).

如果是想捕获输出, 可以用command substitution

output=$(command)      # stdout only; stderr remains uncaptured
output=$(command 2>&1) # both stdout and stderr will be captured

如果是想要返回码, 应该在运行命令之后, 用特殊参数 $?

command
status=$?

如果两者都需要:

output=$(command)
status=$?

如果不是想要返回码, 而只是想知道命令成功还是失败, 可以如下这样

if command; then
    printf "it succeeded\n"
else
    printf "it failed\n"
fi

如果要根据成功/失败执行下一步操作, 但不想知道返回码, 又要取输出内容:

if output=$(command); then
    printf "it succeeded\n"
    ...

如果想从一个pippline里面获取其中一个command的返回码? 最后一个的话, 就是 $? . 如果是中间某个呢? 用PIPESTATUS数组(只在bash中有效)

grep foo somelogfile | head -5
status=${PIPESTATUS[0]}

bash3.0 又添加了一个pipefail选项, 如果你想grep失败的时候执行下一步:

set -o pipefail
if ! grep foo somelogfile | head -5; then
    printf "uh oh\n"
fi

好, 现在来看一些更复杂的问题: 如果只想要错误输出, 而不想要标准输出. 首先, 你需要决定把标准输出指向哪里去.

output=$(command 2>&1 >/dev/null)  # Save stderr, discard stdout.
output=$(command 2>&1 >/dev/tty)   # Save stderr, send stdout to the terminal.
output=$(command 3>&2 2>&1 1>&3-)  # Save stderr, send stdout to script's stderr.

最后一个有些难以理解. 首先要了解 1>&3- 等价于1>&3 3>&-. 然后按下表中的顺序理一下

Redirection fd 0 (stdin) fd 1 (stdout) fd 2 (stderr) fd 3 Description
initial /dev/tty /dev/tty /dev/tty   假设命令是跑在一个终端. stdin stdout stder全部都是初始化为指向终端(tty)
$(…) /dev/tty pipe /dev/tty   标准输出被管道捕获
3>&2 /dev/tty pipe /dev/tty /dev/tty 把描述符2复制到新建的一个描述3, 这时候描述符3指向标准错误输出
2>&1 /dev/tty pipe pipe /dev/tty 描述符2指向1当前的指向, 也就是说2和1一起都是被捕获
1>&3 /dev/tty /dev/tty pipe /dev/tty 复制3到1, 也就是说描述符1指向了标准错误. 到现在为止, 我们已经交换了1和2
3>&- /dev/tty /dev/tty pipe   最后关闭3, 已经不需要了

n>&m- 有时候称之为 将m 重命名为n.

来个更复杂的! 我们把stder保存下来, stdout还是往之前应该去的地方去, 就好像stdout没有做过任何的重定向.

有两种方式

exec 3>&1                    # Save the place that stdout (1) points to.
output=$(command 2>&1 1>&3)  # Run command.  stderr is captured.
exec 3>&-                    # Close FD #3.

# Or this alternative, which captures stderr, letting stdout through:
{ output=$(command 2>&1 1>&3-) ;} 3>&1

我觉得有必要说明一下带重定向的命令的执行方式. command 2>&1 1>&3 是先把2指向1, 然后把1指向3

第一种方式应该还比较好懂. 先创建FD3,并把1复制到3, 然后执行命令 $(command 2>&1 1>&3) , 把FD1的输出管道给output, 然后把3关闭.

第二种方式, 其实就是把三行合成为1行了.

如果想分别保存stdout, stderr到2个变量中, 只用FD是做不到的. 需要用到一个临时文件, 或者是命名的管道.

一个很糟糕的实现如下:

result=$(
    { stdout=$(cmd) ; } 2>&1
        printf "this line is the separator\n"
            printf "%s\n" "$stdout"
            )
var_out=${result#*this line is the separator$'\n'}
var_err=${result%$'\n'this line is the separator*}

如果还想保存返回码的话

cmd() { curl -s -v http://www.google.fr; }

result=$(
    { stdout=$(cmd); returncode=$?; } 2>&1
        printf "this is the separator"
            printf "%s\n" "$stdout"
                exit "$returncode"
                )
returncode=$?

var_out=${result#*this is the separator}
var_err=${result%this is the separator*}

Done.