Score:3

set -e subshell doesn't exit on the first command that fails if I capture the error code of the compound command using "||"

it flag

I have a script.sh and I have to correctly diagnose any errors inside it, so the prova function in case of an error shouldn't stop midway but should continue to the end reporting its error message. To do this I have to capture any error code from a subshell using the OR operator || but doing so the subshell doesn't exit on the first command that fails, and that's not what I want.

Example:

Assuming I'm in the directory containing the script, from a shell I run the command:

./script.sh

If I don't use the OR || operator, the function doesn't continue to the end and therefore doesn't produce the error message:

#!/bin/bash -e
...
prova () {
    local function_name="${FUNCNAME[0]}"
    local exit_code=0
    (
        set -e
        prova1
        prova2
        prova3
    )
    echo "$function_name: An error has occurred." >&2
    return $exit_code
}
prova
...

StdErr:

./script.sh: line 7: prova1: command not found

if I use the OR || operator, it executes all 3 commands when it should still stop at the first one capturing its exit code:

#!/bin/bash -e
...
prova () {
    local function_name="${FUNCNAME[0]}"
    local exit_code=0
    (
        set -e
        prova1
        prova2
        prova3
    ) || exit_code=$?
    if [[ $exit_code -ne 0 ]]; then
        echo "$function_name: An error has occurred." >&2
    fi
    return $exit_code
}
prova
...

StdErr:

./script.sh: line 7: prova1: command not found
./script.sh: line 8: prova2: command not found
./script.sh: line 9: prova3: command not found
prova: An error has occurred.

I want this StdErr instead:

./script.sh: line 7: prova1: command not found
prova: An error has occurred.

How do I get it?

Score:2
es flag

Because the compound command is part of a || list, and not the last in that list, -e option does not have an effect in that compound command. See the documentation of the set builtin in bash's manual:

-e    Exit  immediately  if  a  pipeline  (which  may  consist of a single simple
      command), a list, or a compound command (see SHELL  GRAMMAR  above),  exits
      with  a non-zero status.  The shell does not exit if the command that fails
      is part of the command list immediately following a while or until keyword,
      part  of  the  test  following  the  if or elif reserved words, part of any
      command executed in a && or || list except the command following the  final
      &&  or  ||,  any  command  in  a pipeline but the last, or if the command's
      return value is being inverted with !.  If a compound command other than  a
      subshell  returns  a  non-zero status because a command failed while -e was
      being ignored, the shell does not exit.  A trap on ERR, if set, is executed
      before  the  shell exits.  This option applies to the shell environment and
      each subshell environment separately  (see  COMMAND  EXECUTION  ENVIRONMENT
      above),  and  may cause subshells to exit before executing all the commands
      in the subshell.

      If a compound command or shell function executes in a context where  -e  is
      being ignored, none of the commands executed within the compound command or
      function body will be affected by the -e setting, even if -e is set  and  a
      command  returns a failure status.  If a compound command or shell function
      sets -e while executing in a context where -e is ignored, that setting will
      not  have  any  effect until the compound command or the command containing
      the function call completes.

To exit the subshell after the first failing command you could use code like this:

prova () {
    local exit_code=0
    (
        prova1 &&
        prova2 &&
        prova3
    ) || exit_code=$?
    return $exit_code
}

You might not even need to use a subshell in the function.

If you need to use -e in the compound command in the function, you could use something like this:

#!/bin/bash

prova() {
    (
    set -e
    prova1
    prova2
    prova3
    )
    
    local exit_code=$?
    if [[ $exit_code -ne 0 ]]; then
        echo "${FUNCNAME[0]}: An error has occurred." >&2
    fi
    return $exit_code
}

prova
terdon avatar
cn flag
Looks great, thanks @muru!
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.