Bash – Having problems with bash array $PIPESTATUS

awkbashio-redirectionosxpipe

I'm working on a script (that will run on OSX, and probably nothing else), that basically just parses the /var/log/accountpolicy.log* logs for authentication times/counts. The initial command is a zgrep executed via sudo, which is piped to awk, executing an awk script. After the command is ran, I use ${PIPESTATUS[@]} to determine if anything failed, and if so, which part.

Here's the awk script in its current state:

#! /usr/local/bin/awk -f

BEGIN {
  return_code = 0
  if ( length( username ) == 0 ){
    return_code = 2
    exit return_code
  }

  rows = 0
}
{ 
  if ( $8 != sprintf("\"%s\",", username ) ) next

  rows = rows+1
  print $0
} 
END {
  if ( return_code > 0 ) exit return_code
  if ( rows == 0 ) exit 3
}

The awk script has some custom value validation and exit codes. The return codes 1, 2 and 3 mean:

  1. Awk failed (for some reason related to awk)
  2. No username was specified for awk to parse for
  3. The username was specified, but no values were found

Test #1 (working properly)

An example execution (hiding the output from the awk script, since this question is related to return codes specifically):

$ sudo -n zgrep -h AuthenticationAllowed /var/log/accountpolicy.log* 2>/dev/null | awk -v username="${USER}" -f ./parse-accountpoliocy.awk &>/dev/null
$ echo ${PIPESTATUS[@]}
0 0

You can see that ${PIPESTATUS[@]} shows that both sudo and awk were successful, which is expected, as I know I have sudo access, and the awk variable username was set and has log entries.

Test #2 (working properly)

Now if we change the awk variable username to an account that does not exist, then the awk script should exit with a return code of 3:

$ sudo -n zgrep -h AuthenticationAllowed /var/log/accountpolicy.log* 2>/dev/null | awk -v username="fakeuser" -f ./parse-accountpoliocy.awk &>/dev/null
$ echo ${PIPESTATUS[@]}
0 3

Perfect!

Test #3 (Problem..)

If I execute the command above, but neglect to define the username awk variable, then the awk script should exit with a return code of 2

$ sudo -n zgrep -h AuthenticationAllowed /var/log/accountpolicy.log* 2>/dev/null | awk -f ./parse-accountpoliocy.awk &>/dev/null
$ echo ${PIPESTATUS[@]}
141 2

As you can see, the awk script does return 2, but now for some reason, the sudo/zgrep returns 141, despite the fact that that part of the command wasn't altered at all… and this is the problem I’m running into.

I even tried to execute the command without hiding the output (of either STDOUT or STDERR), and the result was the same, but no error was displayed:

$ sudo -n zgrep -h AuthenticationAllowed /var/log/accountpolicy.log*  | awk -f ./parse-accountpoliocy.awk
$ echo ${PIPESTATUS[@]}
141 2

Question

How can the exit code of the awk script change the exit code for the sudo/zgrep command stored within ${PIPESTATUS[@]}?


Environmental Information

  • OSx Version: 10.11.6 (El Capitan)
  • Bash Version: 4.4.12
  • AWK Version: GNU Awk 4.1.4

Best Answer

$ echo $((141-128))
13
$ kill -l | grep 13
13   PIPE Broken pipe                   29   INFO Information request
$ 

So that 141 is how the shell kluges the 16-bit exit status word (see wait(2)) containing a PIPE signal into a single number; this does not happen for the exit 3 case as that happens after the data being piped has been done processed by awk (and zgrep has nothing more to write to awk via the pipe). The exit 2 instead happens very early while zgrep still has data it wants to pipe to awk, so zgrep gets whacked with a PIPE when it tries to write to the awk that has gone away.

Related Question