Search code examples
awkposixgetline

How do I get the exit status of a command in a getline pipeline?


In POSIX awk, how do I get the exit status (return code) from command after processing its output via command | getline var? I want my awk script to exit 1 if command exited with a non-zero exit status.

For example, suppose I had an awk script named foo.awk that looks like this:

function close_and_get_exit_status(cmd) {
    # magic goes here...
}
BEGIN {
    cmd = "echo foo; echo bar; echo baz; false"
    while ((cmd | getline line) > 0)
        print "got a line of text: " line
    if (close_and_get_exit_status(cmd) != 0) {
        print "ERROR: command '" cmd "' failed" | "cat >&2"
        exit 1
    }
    print "command '" cmd "' was successful"
}

then I want the following to happen:

$ awk -f foo.awk
got a line of text: foo
got a line of text: bar
got a line of text: baz
ERROR: command 'echo foo; echo bar; echo baz; false' failed
$ echo $?
1

According to the POSIX specification for awk, command | getline returns 1 for successful input, zero for end-of-file, and -1 for an error. It's not an error if command exits with a non-zero exit status, so this can't be used to see if command is done and has failed.

Similarly, close() can't be used for this purpose: close() returns non-zero only if the close fails, not if the associated command returns a non-zero exit status. (In gawk, close(command) returns the exit status of command. This is the behavior I'd like, but I think it violates the POSIX spec and not all implementations of awk behave this way.)

The awk system() function returns the exit status of the command, but as far as I can tell there's no way to use getline with it.


Solution

  • The simplest thing to do is just echo the exit status from shell after the command executes and then read that with getline. e.g.

    $ cat tst.awk    
    BEGIN {
        cmd = "echo foo; echo bar; echo baz; false"
    
        mod = cmd "; echo \"$?\""
        while ((mod | getline line) > 0) {
            if (numLines++)
                print "got a line of text: " prev
            prev = line
        }
        status = line
        close(mod)
    
        if (status != 0) {
            print "ERROR: command '" cmd "' failed" | "cat >&2"
            exit 1
        }
        print "command '" cmd "' was successful"
    }
    
    $ awk -f tst.awk
    got a line of text: foo
    got a line of text: bar
    got a line of text: baz
    ERROR: command 'echo foo; echo bar; echo baz; false' failed
    $ echo $?
    1
    

    In case anyone's reading this and considering using getline, make sure you read http://awk.freeshell.org/AllAboutGetline and FULLY understand all the caveats and implications of doing so first.