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

~~ 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来获取管道中每个命令的返回码。

注意:

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

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

  3. 每执行一条命令,切记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
posted @ 2020-03-04 00:11  苏轶然  阅读(731)  评论(0编辑  收藏  举报