Bash semantics subshell errexit

I have errexit (and pipefail) for my shell script because this is the behavior that I usually want. However, sometimes I want to fix errors and handle them in a certain way.

I know that errexit is disabled for commands that contain logical operators or should be used as a condition (if, while, etc.)

eg.

git push && true echo "Pushed: $?" 

there will be an echo of "Pushed: 0" on success, or "Pushed: something else" on error.

However, if I want the subtitle to have errexit enabled, but then I want to capture the exit code of this subshell?

For instance:

 #!/usr/bin/env bash set -o errexit ( git push echo "Hai" ) && true echo "Did it work: $?" 

The problem is that bash sees && boolean and disables errexit for the subshell. This means that “High” is always an echo. This is undesirable.

How do I enable errexit in this subshell and capture the subshell status code without allowing this exit code to complete the outer shell without constantly turning errexit on and off all over the place ?

Update

I have a strong feeling that the solution is to use traps and capture the output signal. Feel free to give an answer before I answer.

+9
source share
5 answers

It seems I stumbled upon a point of disagreement for many shell fans:

http://austingroupbugs.net/view.php?id=537#bugnotes

Basically, the standard said something, the interpreters ignored it because the standard seemed illogical, but now interpreters like Bash really confuse the semantics, and no one wants to correct it.

Unfortunately, trap <blah> EXIT cannot be used to accomplish what I want, because trap is basically just an interrupt handler for the signal, there is no way to continue the script at the given point (as you would use a try..finally block in other languages).

Everything is terrible

In fact, as far as I know, there is no absolutely reasonable way to handle errors. Your options:

 #!/usr/bin/env bash set -e # Some other code set +e ( git push || exit $? echo "Hai" ) echo "Did it work: $?" set -e 

or

 #!/usr/bin/env bash set -e ( git push && echo "Hai" || exit $? ) && true echo "Did it work: $?" 

First of all, you wonder why you were worried about set -e !

+10
source

Using the Bash set command changes the parameters of the current shell. Changes to shell parameters are NOT inherited by the subshell. The reason is that the script writer might want to change the environment settings for the subshell!

These examples go to the parent shell without printing 'hai'

 ( set -e; git push; echo 'hai' ) 

Same:

 ( set -e; git push; printf '\nhai' ) 

Same:

 ( set -e git push printf '\nhai' ) 

Creating a Compond Team with '&& or ||' Operators following a subshell keeps the subshell open until all commands are resolved.

Use these two commands to find the quoted section of the bash manual:

 man bash /errexit 

Exit immediately if the pipeline (which may consist of one simple command), list, or compound command (see Wrapper SAMPLE) terminates with a nonzero state. The shell does not finish if the failed command is part of the list of commands immediately following the keyword some time before, part of the test after the reserved words if or elif, part of any command executed in the characters && or || a list, with the exception of the command following the last character && or ||, any command in the pipeline except the last, or if the return value of the command is inverted with !. If a compound command other than a subshell returns a nonzero state due to a command failure, when -e is ignored, the shell does not terminate. The ERR trap, if installed, runs before exiting the shell. This parameter is applied to the shell environment and each subshell environment separately (see above, the EXECUTION EXECUTION COMMAND) and may cause the subshells to exit after all the commands in the subshell have been executed.

This part of the bash manual mentions this again:

The ERR trap is not executed if the failed command is part of the list of commands immediately following the keyword for a while or before, part of the test in the if statement, part of the command executed in && or || a list, with the exception of the command following the last character && or ||, any command in the pipeline except the last, or if the return value of the command is inverted with !. These are the same conditions that the errexit (-e) option obeys.

Finally, this section of the Bash manual indicates that set -e inherited by the subshells of set -e compatible Bash:

The subshells generated to perform command substitutions inherit the value of the -e option from the parent shell. When not in posix mode, bash clears the -e option in such subshells.

+1
source

You can hack output parsing a bit. Command substitution does not inherit errexit (except for Bash 4.4 with inherit_errexit ), but it inherits an ERR trap with errtrace . Thus, you can use the trap to exit the subshell by mistake and use local or some other methods to avoid exiting the parent shell.

 handle_error() { local exit_code=$1 && shift echo -e "\nHANDLE_ERROR\t$exit_code" exit $exit_code } return_code() { # need to modify if not GNU head/tail local output="$(echo "$1" | head -n -1)" local result="$(echo "$1" | tail -1)" if [[ $result =~ HANDLE_ERROR\ [0-9]+ ]]; then echo "$output" return $(echo "$result" | cut -f2) else echo "$1" return 0 fi } set -o errtrace trap 'handle_error $?' ERR main() { local output="$(echo "output before"; echo "running command"; false; echo "Hai")" return_code "$output" && true echo "Did it work: $?" } main 

Unfortunately, in my tests using && true with command substitution, the trap is prevented from working (even with grouping of commands), so you cannot collapse it into one line. If you want to do this, you can make handle_error set a global variable instead of returning an exit status. Then you get:

  return_code "$(echo "output before"; echo "running command"; false; echo "Hai")" echo "Did it work: $global_last_error" 

Note that command substitution swallows the trailing newline characters , so this code will now add a newline to the output of the subshell, if not originally present.

This may not be 100% reliable, but may be acceptable in order to free you from re-enabling the errexit flag. Maybe there is a way to use the same template without parsing?

+1
source

Perhaps a mistake: https://groups.google.com/forum/?fromgroups=#!topic/gnu.bash.bug/NCK_0GmIv2M This suggests that set -e in (subshells) is independent of the surrounding context. The manual page says: "[set -e] is applied to the shell environment and each subshell environment is separate," but in fact set -e cannot work in the (subshell) if it is disabled in the surrounding context.

0
source

I am using this code

 function runWithOwnErrorHandling { functionName="$1" shift set +o errexit OLDTRAP="$(trap -p ERR)" trap - ERR ( set -o errexit $functionName " $@ " ) FUNCEXIT=$? set -o errexit $OLDTRAP } 

Name him

 function someFunc { echo "c1"; false echo "c2"; } runWithOwnErrorHandling someFunc [[ ${FUNCEXIT} -ne 0 ]] && echo someFunc failed 
0
source

Source: https://habr.com/ru/post/984885/


All Articles