How to catch and handle nonzero exit status within a Bash function?

11,675

Under set -e, the non-existence of failfailfail causes the whole script to exit (or the subshell, if the function is executed in a subshell).

If you don't need to modify the shell state from the function, you can run it in a subshell.

myfunc() (
    set -e
    ls
    failfailfail
    uptime
)

Another approach in bash is to set an ERR trap to execute return. If you want to make it a local setting, you need to restore the old trap value, which is a little cumbersome.

myfunc() {
    local old_ERR_trap=$(trap -p ERR)
    if [[ -z $old_ERR_trap ]]; then old_ERR_trap="trap - ERR"; fi
    trap 'local ret=$?; eval "$old_ERR_trap"; return $ret' ERR
    ls
    failfailfail
    uptime
}
Share:
11,675

Related videos on Youtube

smitelli
Author by

smitelli

Greetings! My name is Scott, I reside in the Raleigh-Durham, North Carolina area, and I do a little bit of everything. I’m a web developer professionally, and a sound mixer/editor in my spare time. I also have a wide range of other hobbies — from writing to time-lapse and still photography to drawing a webcomic.

Updated on September 18, 2022

Comments

  • smitelli
    smitelli over 1 year

    Say I have the following (pointless) Bash function:

    myfunc() {
        ls
        failfailfail
        uptime
    }
    

    I run it as follows:

    myfunc || echo "Something is wrong."
    

    What I want to happen is ls runs (as it should), failfailfail does not work (since it doesn't exist), and uptime doesn't run. The return value of the function would be nonzero, and the error message would be shown. The return value doesn't have to be the exact exit status of the failed command, it just shouldn't be zero.

    What actually happens is I get the output of ls, followed by "-bash: failfailfail: command not found", followed by the output of uptime. The error message is not shown, because the failed exit status is getting eaten.

    set -e has no useful effect, either within the function or in the scope where the function is called. The only way I can get this to work the way I want is:

    myfunc() {
        ls || return $?
        failfailfail || return $?
        uptime || return $?
    }
    

    But this seems awfully repetitive and ugly. Is there another way to clean this up?

  • Wildcard
    Wildcard about 8 years
    You can put line breaks after &&, which would make this easier to maintain IMO. You don't need backslashes for that, either.
  • Julie Pelletier
    Julie Pelletier about 8 years
    It's interesting but I think it may cause more confusion that way.
  • smitelli
    smitelli about 8 years
    This feels... devious.
  • MAChitgarha
    MAChitgarha over 4 years
    Instead of posting a new answer, you should edit the existing one. Your answer doesn't need to be a separate answer from the previous one.
  • Wildcard
    Wildcard over 4 years
    @MAChitgarha this is from three years ago.
  • MAChitgarha
    MAChitgarha over 4 years
    @Wildcard I know, but some people may search for a solution, now. Editing the previous answer is more helpful than adding an answer, though.