Log exit code of command, similar to time command

cmd && echo "$?" wouldn't work since it would by necessity only print zeroes (the echo would only execute on successful completion of the preceding command).

Here's a short shell function for you:

tellexit () {
    "$@"

    local err="$?"
    printf 'exit code\t%d\n' "$err" >/dev/tty
    return "$err"
}

This prints the exit code of the given command in a similar manner as the time command does.

$ tellexit echo "hello world"
hello world
exit code       0

$ tellexit false
exit code       1

By redirecting the printf to /dev/tty in the function, we may still use tellexit with redirections without getting junk in our standard output or error streams:

$ tellexit bash -c 'echo hello; echo world >&2' >out 2>err
exit code       0
$ cat out
hello
$ cat err
world

By saving the exit code in a variable we are able to return it to the caller:

$ tellexit false || echo 'failed'
exit code       1
failed

A fancier version of the same function also prints the signal that killed the command if the exit code is greater than 128 (which means it terminated due to a signal):

tellexit () {
    "$@"

    local err="$?"

    if [ "$err" -gt 128 ]; then
        printf 'exit code\t%d (%s)\n' "$err" "$(kill -l "$err")" >/dev/tty
    else
        printf 'exit code\t%d\n' "$err" >/dev/tty
    fi

    return "$err"
}

Testing:

$ tellexit sh -c 'kill $$'
exit code       143 (TERM)

$ tellexit sh -c 'kill -9 $$'
Killed
exit code       137 (KILL)

(The local thing requires ash/pdksh/bash/zsh, or you can change it to typeset which a few other shells also understand.)


Use a shell wrapper function thingy. Probably with a different name.

$ exito() { "$@"; echo $?; }
$ exito true
0
$ exito false
1
$ exito echo "test test"      
test test
0
$ 

(This of course will corrupt standard output, so either use the tty as shown by @Kusalananda or do not use it outside of interactive contexts.)

Veering off into unportable territory, some shells can report on the status of all the commands in a pipeline, not just the last one, e.g. in ZSH if you want failures reported from an entire pipeline:

% TRAPZERR() { print >/dev/tty $pipestatus }
% perl -e 'exit 42' | perl -e 'exit 99'
42 99
% false | perl -e 'exit 42' | perl -e 'exit 99'
1 42 99
% perl -e 'exit 42' | perl -e 'exit 99' | true
% 

TRAPZERR otherwise does not fire when there is no error (on the "no news is good news" principal).


GNU time has an option for this:

time -f %x sleep 1
0

Passes through the exit code1, unless killed by a signal2:

$ /usr/bin/time -f %x sleep 1; echo $?
0
0

$ /usr/bin/time -f %x sleep 2x; echo $?
sleep: invalid time interval ‘2x’
Try 'sleep --help' for more information.
1
1

$ /usr/bin/time -f %x sleep 60s; echo $? # Press ^C before a minute elapses
0
2

If you want to know/handle the signal kill situation, pass -v and grep the stderr for the string Command terminated by signal.


1Thanks to Olivier Dulac for noting the exit code passes through.
2Also, thank you Stéphane Chazelas for pointing out kill signal exit code does not pass through.