Using Set -e , how to make exception for one command - shell

Using set -e in the shell script can let the process
exit immediately if one of the commands exits with a non-zero status
However, if I want to exclude one command, so that if that specific command exits with non-zero value, the process continues.

First rule, don't use set -e. Second rule, see rule 1. The implementation of set -e has strange behavior in edge cases, and has changed through various versions of shells. It is safest not to use it. However, you can suppress it simply by making your command part of a compound command. That is, instead of
cmd_which_may_fail
you can simply do:
cmd_which_may_fail || true
Now, if cmd_which_may_fail does fail, the script will not exit.
Note that is is sometimes tempting (and you will often see code that does this) to do something like cmd_which_may_fail || echo 'command failed!' >&2, but you really should not do that. Instead, allow cmd_which_may_fail to emit a reasonable error message (most common commands do, and if you have a command that doesn't emit good error messages you might want to reconsider using it at all), since that process will have context and can write a useful error message. Command failed is generally considered a useless error message.

I usually do
command ||:
the return status of : is zero. If command returns nonzero status, then : is executed, which returns zero status. If command returns a zero status, then : is not executed. In both cases - the return status is zero, ie. success.
I've seen:
command |:
Well... it's one character shorter, but binding stdout to : process seems like a waste of cpu time for me. The return status of the pipe is the return status of the last executed command, and it's :. But this can fail when set -o pipefail, so I wouldn't recommend it.
You can run a command in a subshell:
( set +e; command; )
this allows for more flexibility, but you need to add ( before the command and is far more typing.

( set +e; command; ) do not work
( set +e; command; true ) is the solution
Example:
#!/usr/bin/bash
set -e
( set +e ; killall DUMMY_PROCESS_ABC )
echo happy end
This will work:
#!/usr/bin/bash
set -e
echo $( set +e ; killall DUMMY_PROCESS_ABC )
echo happy end
this also work:
#!/usr/bin/bash
set -e
(set +e ; killall DUMMY_PROCESS_ABC ; true)
echo happy end

Related

How to make exception for a bash script with set -ex

I have a bash script that has set -ex, which means the running script will exit once any command in it hits an error.
My use case is that there's a subcommand in this script for which I want to catch its error, instead of making the running script shutdown.
E.g., here's myscript.sh
#!/bin/bash
set -ex
# sudo code here
error = $( some command )
if [[ -n $error ]] ; then
#do something
fi
Any idea how I can achieve this?
You can override the output of a single command
this_will_fail || true
Or for an entire block of code
set +e
this_will_fail
set -e
Beware, however, that if you decide you don't want to use set -e in the script anymore this won't work.
If you want to handle a particular command's error status yourself, you can use as the condition in an if statement:
if ! some command; then
echo 'An error occurred!' >&2
# handle error here
fi
Since the command is part of a condition, it won't exit on error. Note that other than the ! (which negates it, so the then clause will run if the command fails rather than it succeeds), you just include the command directly in the if statement (no brackets, parentheses, etc).
BTW, in your pseudocode example, you seem to be treating it as an error if the command produces any output; usually that's not what you want, and I'm assuming you actually want to test the exit status to detect errors.

Bash control flow using || on function, with set -e

If I put set -e in a Bash script, the script will exit on future errors. I'm confused about how this works with functions. Consider the following, which will only print one to standard out:
set -e # Exit on error
fun(){
echo one
non_existing_command
echo two
}
fun
Clearly, the non_existing_command is an error and so the script exits before the second echo. Usually one can use the or operator || to run another command if and only if the first command fails. That is, I would suspect the following to print out both one and three, but not two:
set -e # Exit on error
fun(){
echo one
non_existing_command
echo two
}
fun || echo three
What I get however is one and two. That is, the || operator prevents the exit (as it should) but it chooses to continue with the function body and disregard the right-hand command.
Any explanation?
It appears to be documented in the set builtin command
If a compound command or shell function executes in a context where -e is being ignored [such as on the left-hand of a ||], 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.
Emphasis and comment are mine.
Also, if you try to set -e within the function, don't bother: the next sentence:
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.

How to use detect failure in command substitution

As you know set -e is very useful to discover any failure when command is executed. However I found it to be delicate to use and I don't know how to use it in the following scenarios:
==============First example================================
set -e
function main() {
local X1="$(exp::notdefined)"
echo "Will reach here : ${LINENO}"
X2="$(exp::notdefined)"
echo "Will not reach here : ${LINENO}"
}
main
==============Second example================================
set -e
function exp::tmp() {
echo "Now '$-' is "$-" : ${LINENO}"
false
return 0
}
function main() {
X1="$(exp::tmp)"
echo "Will reach here : ${LINENO}. '\$X1' : ${X1}"
X2="$(set -e ; exp::tmp)"
echo "Will not reach here : ${LINENO}"
}
main
===============================
The first example shows that, if we use command substitution on a local variable, then it will not fail even if the command substituted is not found. I don't know how to detect these kinds of failures.
The second example shows that, if the bash options (-e) will not propagate unless we call set -e inside the command braces. Is there any better way to do this?
You request immediate exit on pipeline failure with -e, e.g.:
-e Exit immediately if a pipeline (which may consistof a single simple
command), a list, or a compound command (see SHELL GRAMMAR above),
exits with a non-zero status.
The reason the bad command substitution does not cause failure within the function is because local provides its own return status.
local [option] [name[=value] ...]
... The return status is 0 unless local is used outside a function, an
invalid name is supplied, or name is a readonly variable.
The assignment of a failed command substitution does not cause local to return non-zero. Therefore, no immediate-exit is triggered.
As far as checking for a failure of command substitution following local, since the output is assigned to the variable, and the return will not be non-zero in the event of command substitution failure, you would have to validate by checking the variable contents itself for expected values for success/failure.
From the bash manual:
Subshells spawned to execute 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.
Example 2 behaves differently when bash runs with --posix; however for example 1 I can't find any documentation explaining why local would cause this.

Why do bash "set -e" cause exit after a seeming error-free command?

Without using set -e, the script runs as expected, with all results correctly generated.
After adding set -e, it exits after this command:
./NameOfATool > result.txt
When I wrap set +e and set -e around that command, then the script terminates as expected.
Why would it exit, or what might be wrong with the command?
p.s. NameOfAToolis an executable compiled from C code. When I manually type that command, it runs ok without giving an error.
set -e will cause the script to exit if any command returns a non-zero exit status. (Well, there are a bunch of exceptions, but that's the general rule.) So, ./NameOfATool apparently returns a non-zero exit status. This might mean that it actually thinks there's an error, or it might mean that the program was poorly written and doesn't report an appropriate exit status for success, or it might mean that it uses special exit-status values to report specific things (much like the standard utility diff, which returns 0 for "same", 1 for "different", and 2 for "error").
Try set +e in your trap:
set -e;
trap 'x=$?; set +e; echo Hello; false; echo World; exit 22;' ERR
echo Testing
false
echo Never See This
Omit the set +e and you don't see the "World" as the non-zero exit code in the trap exits before the trap is completed.
As #ruakh said, this indicates that the tool is exiting with a nonzero (=error) status. You can prevent this from exiting the script by putting it in a compound command that always succeeds:
./NameOfATool > result.txt || true
If the tool exits with a nonzero status, it runs true, which succeeds; hence, the entire compound command is considered to have succeeded. If the command's exit status is significant (i.e. you need to be able to tell if it exited with status 0, 1, or 2), you can either record it for later use:
./NameOfATool > result.txt && toolStatus=0 || toolStatus=$?
...or use the status directly:
if ./NameOfATool > result.txt; then
# do things appropriate for exit status = 0
else
toolStatus=$?
# do things appropriate for exit status != 0
fi

Getting the return value in "sh -e"

I'm writing a shell script with #!/bin/sh as the first line so that the script exits on the first error. There are a few lines in the file that are in the form of command || true so that the script doesn't exit right there if the command fails. However, I still want to know know the exit code of the command. How would I get the exit code without having to use set +e to temporarily disable that behavior?
Your question appears to imply set -e.
Assuming set -e:
Instead of command || true you can use command || exitCode=$?. The script will continue and the exit status of command is captured in exitCode.
$? is an internal variable that keeps the exit code of the last command.
Since || short-circuits if command succeeds, set exitCode=0 between tests or instead use: command && exitCode=0 || exitCode=$?.
But prefer to avoid set -e style scripting altogether, and instead add explicit error handling to each command in your script.
If you want to know the status of the command, then presumably you take different actions depending on its value. In which case your code should look something like:
if command; then
# do something when command succeeds
else
# do something when command fails
fi
In that case you don't need to do anything special, since the shell will not abort when command fails.
The only reasons set -e would give you any problems is if you write your code as:
command
if test $? = 1; ...
So don't do that.

Resources