~~ 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