5

I have a problem checking whether a certain command in a multi-pipe command chain did throw an error. Usually this is not hard to check but neither set -o pipefail nor checking ${PIPESTATUS[@]} works in my case. The setup is like this:

cmd="$snmpcmd $snmpargs $agent $oid | grep <grepoptions> for_stuff | cut -d',' f$fields | sed 's/ubstitute/some_other_stuff/g'"

Note-1: The command was tested thoroughly and works perfectly.

Now, I want to store the output of that command in an array called procdata. Thus, I did:

declare -a procdata
procdata=( $(eval $cmd) )

Note-2: eval is necessary because otherwise $snmpcmd throws up with an invalid option -- <grepoption> error which makes no sense because <grepoption> is not an $snmpcmd option obviously. At this stage I consider this a bug with $snmpcmd but that's another show...

If an error occurres, procdata will be empty. However, it might be empty for two different reasons: either because an error occurred while executing the $snmpcmd (e.g. timeout) or because grep couldn't find what it was looking for. The problem is, I need to be able to distinguish between these two cases and handle them separately.

Thus, set -o pipefail is not an option since it will propagate any error and I can't distinguish which part of the pipe failed. On the other hand echo ${PIPESTATUS[@]} is always 0 after procdata=( $(eval $cmd) ) even though I have many pipes!?. Yet if I execute the whole command directly at the prompt and call echo ${PIPESTATUS[@]} immediately after, it returns the exit status of all the pipes correctly.

I know I could bind the err stream to stdout but I would have to use heuristic methods to check whether the elements in procdata are valid or error messages and I run the risk of getting false positives. I could also pipe stdout to /dev/null and capture only the error stream and check whether ${#procdata[@]} -eq 0. But I'd have to repeat the call to get the actual data and the whole command is time costly (ca. 3-5s). I wouldn't want to call it twice. Or I could use a temporary file to write errors to but I'd rather do it without the overhead of creating/deleting files.

Any ideas how I can make this work in bash?

Thanks

P.S.:

$ echo $BASH_VERSION
4.2.37(1)-release
user3040975
  • 615
  • 1
  • 6
  • 10

1 Answers1

3

A number of things here:

(1) When you say eval $cmd and attempt to get the exit values of the processes in the pipeline contained in the command $cmd, echo "${PIPESTATUS[@]}" would contain only the exit status for eval. Instead of eval, you'd need to supply the complete command line.

(2) You need to get the PIPESTATUS while assigning the output of the pipeline to the variable. Attempting to do that later wouldn't work.


As an example, you can say:

foo=$(command | grep something | command2; echo "${PIPESTATUS[@]})"

This captures the output of the pipeline and the PIPESTATUS array into the variable foo.

You could get the command output into an array by saying:

result=($(head -n -1 <<< "$foo"))

and the PIPESTATUS array by saying

tail -1 <<< "$foo"
devnull
  • 118,548
  • 33
  • 236
  • 227
  • I had the same thought. Unfortunately it doesn't work either but I'm not sure why. I post the exact commands because I might not see the obvious: – user3040975 Dec 13 '13 at 12:10
  • `local cmdargs="-CHf , -m$mibs -v$snmpver -c$community $agent"; local procdatacmd="$tblcmd $cmdargs $proc_table "; procdatacmd+="| cut -d',' -f$fields | grep -w e -e | sort | uniq -c | sed 's/^ *\|\"//g;s/ /,/g' ; echo ${PIPESTATUS[@]}"`. Then I do: `declare -a procdata=( $(head -n -1 <<< $procdatacmd) )`. Output is empty ... just nothing. and are up and running on the agent. What the ... – user3040975 Dec 13 '13 at 12:16
  • @user3040975 You don't seem to be _running_ any command in the line above. – devnull Dec 13 '13 at 12:18
  • Right, sorry. But even `foo=$($procdatacmd); procdata=($(head -n -1 << $foo))` does not work: " /usr/bin/snmptable: invalid option -- '''". I have a "-x" switch to show the acutal command. Pasting and executing it on the prompt works flawlessly... – user3040975 Dec 13 '13 at 12:25
  • @user3040975 I assume that you have the command instead of a variable when you say `foo=$($procdatacmd);`. To be even more clear, you need to put in the complete command instead of a variable in place of `$procdatacmd`. – devnull Dec 13 '13 at 12:27
  • Yeah I meant the variable. `temp=$($tblcmd $cmdargs $proc_table | cut -d',' -f$fields | $( get_pgreplist ) | sort | uniq -c | sed 's/^ *\|\"/ /g;s/ /,/g' ; echo ${PIPESTATUS[@]})`. Now `procdata=( $(head -n -1 <<< $temp) )` does something, outputs nothing, although `procdata=( $(head <<< $temp) )` outputs the right values (line by line)?! I'm confused ... – user3040975 Dec 13 '13 at 12:36
  • @user3040975 You seem to be capturing the command output and exit status output into the same variable. Simply say: `head -n -1 <<< "$temp"` and `tail -1 <<< "$temp"` first to see the result. And please __quote__ the variables as indicated. – devnull Dec 13 '13 at 12:40
  • Yes, I'm trying to capture both in `temp` as you did with `foo` and try to read it into `procdata` as you read it into `result`. Simply saying `head -n -1 <<< "$temp"` and `tail -1 <<< "$temp"` after the above `temp` assignment give the following errors: "head: cannot open '1 0 1 0 0 0' for reading: No such file or directory" and "tail: cannot open '1 0 1 0 0 0' for reading: No such file or directory" ... – user3040975 Dec 13 '13 at 12:52
  • @user3040975 You seem to be doing something wrong. `1 0 1 0 0 0` indicate that the exit codes have been captured. Simply say `echo "${temp}"` to see what it returns. – devnull Dec 13 '13 at 12:56
  • 1
    It works as you said. I don't know what I did wrong above. If I do as you said in your answer it works. Thanks for your time and effort. Just for completeness: `echo "${temp}"` outputs `10,httpd,runnable 64,nfsd,runnable 0 0 0 0 0 0`. Searching for `http` outputs `0 0 1 0 0 0` indicating failure to grep while a bogus IP will give me `1 0 1 0 0 0` as intended. Thanks again! Sadly I can't vote this up for lack of reputation ... – user3040975 Dec 13 '13 at 13:13