Linux中对管道命令中的任意子命令进行返回码校验

2023-08-03,,

~~ linux return code with pipeline~~

~~ linux 管道命令中的返回码~~

BASH SHELL中,通常使用 $? 来获取上一条命令的返回码。

Shell Scripting Tutorial - Checking the exit status of ANY command in a pipeline

对于管道中的命令,使用$?只能获取管道中最后一条命令的返回码,例如:

${RUN_COMMAND} 2> "${CUR_DIR}"/weiflow-from-weiclient.log | ${WEIBOX_UTIL_HOME}/rotatelogs.py -n 3 "${CUR_DIR}"/weiflow-from-weiclient.log 500M

使用 $PIPESTATUS来获取管道中每个命令的返回码。

注意:

    PIPESTATUS 是一个数组,第一条命令的返回码存储在${PIPESTATUS[0]},以此类推。

    如果前一条命令不是一个管道,而是一个单独的命令,命令的返回码存储为\({PIPESTATUS[0]},此时\){PIPESTATUS[0]}同\(?值相同(事实上,PIPESTATUS最后一个元素的值总是与\)?的值相同)

    每执行一条命令,切记PIPESTATUS都会更新其值为上一条命令的返回码,

cat /not/a/valid/filename|cat

      if [ \({PIPESTATUS[0]} -ne 0 ]; then echo \){PIPESTATUS[@]}; fi

上例中执行完管道后,\({PIPESTATUS[0]}值为1,\){PIPESTATUS[1]}值为0

但是上面的脚本执行完成后,输出为0,这是因为if 分支的测试命令值为真,然后 PIPESTATUS[0]的值此时被置为0。应当在命令执行完成后立即在同一个测试命令中对所有值进行测试,例如

if [ \({PIPESTATUS[0]} -eq 1 -a \){PIPESTATUS[1]} -eq 0 ] ; then echo something; fi

或者先将$PIPESTATUS数组保存下来,以后再处理,例如

ret=${PIPESTATUS[@]};

RESULT

${RUN_COMMAND} 2> "${CUR_DIR}"/weiflow-from-weiclient.log | ${WEIBOX_UTIL_HOME}/rotatelogs.py -n 3 "${CUR_DIR}"/weiflow-from-weiclient.log 500M
exit \${PIPESTATUS[0]}

Checking the exit status of ANY command in a pipeline

It's a pretty common thing in a shell script to want to check the exit status of the previous command. You can do this with the $? variable, as is widely known:

#!/bin/bash
grep some.machine.example.com /etc/hosts
if [ "$?" -ne "0" ]; then
# The grep command failed to find "some.machine.example.com" in /etc/hosts file
echo "I don't know the IP address of some.machine.example.com"
exit 2
fi

What gets difficult is when you execute a pipeline: (see pipelines for more information on the Unix Pipeline)

#!/bin/bash
grep some.machine.example.com /etc/hosts 2>&1 | tee /tmp/hosts-results.txt
if [ "$?" -ne "0" ]; then
# Ah - what we get here is the status of the "tee" command,
# not the status of the "grep" command :-(

What you get is the result of the tee command, which writes the results to the display as well as to the /tmp/hosts-results.txt file.

To find out what grep returned, $? is of no use.

Instead, use the ${PIPESTATUS[]} array variable. ${PIPESTATUS[0]} tells us what grep returned, while ${PIPESTATUS[1]} tells us what tee returned.

So, to see what grep found, we can write our script like this:

#!/bin/bash
grep some.machine.example.com /etc/hosts 2>&1 | tee /tmp/hosts-results.txt
if [ "${PIPESTATUS[0]}" -ne "0" ]; then
# The grep command failed to find "some.machine.example.com" in /etc/hosts file
echo "I don't know the IP address of some.machine.example.com"
exit 2
fi

Here's The Rub

The downside is, that any command you use to access ${PIPESTATUS[]}, will automatically replace the current state of the array with the return code of the command you have just run:

Pipeline (command) PIPESTATUS shows status of:
grep ... tee ...
echo "Grep returned ${PIPESTATUS[0]}" echo "Grep ...
echo "Maybe PIPESTATUS isn't so useful after all" echo "Maybe ...

So as soon as we use echo to tell us about the return code of grep, the ${PIPESTATUS[]} array now tells us about the return code of the echo statement itself, which is pretty likely to be zero, as not much can cause echo to fail!

The Fix

Because ${PIPESTATUS[]} is a special variable, it changes all the time. However, we can copy this array into another array, which is just a regular array, which will not be changed at all just by running some more commands. Copying an array requires a slightly different syntax to simply copying contents of a variable into another:

RC=( "${PIPESTATUS[@]}" )

Where RC stands for Return Code. We can then investigate the status of RC at our leisure. For testing purposes, the program true always returns zero, and false always returns 1:

#!/bin/bash
echo "tftf"
true | false | true | false
RC=( "${PIPESTATUS[@]}" )
echo "RC[0] = ${RC[0]}" # true = 0
echo "RC[1] = ${RC[1]}" # false = 1
echo "RC[2] = ${RC[2]}" # true = 0
echo "RC[3] = ${RC[3]}" # false = 1 echo "ftft"
false | true | false | true
RC=( "${PIPESTATUS[@]}" )
echo "RC[0] = ${RC[0]}" # false = 1
echo "RC[1] = ${RC[1]}" # true = 0
echo "RC[2] = ${RC[2]}" # false = 1
echo "RC[3] = ${RC[3]}" # true = 0 echo "fftt"
false | false | true | true
RC=( "${PIPESTATUS[@]}" )
echo "RC[0] = ${RC[0]}" # false = 1
echo "RC[1] = ${RC[1]}" # false = 1
echo "RC[2] = ${RC[2]}" # true = 0
echo "RC[3] = ${RC[3]}" # true = 0

Linux中对管道命令中的任意子命令进行返回码校验的相关教程结束。

《Linux中对管道命令中的任意子命令进行返回码校验.doc》

下载本文的Word格式文档,以方便收藏与打印。