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

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.

Related

How to detect "Cannot fork" errors in bash?

For currently unknown reasons, one of our bash-scripts produces "Cannot fork" errors when running a simple line like:
myvar=`mycmd || echo "error"; exit 2`
Obviously the problem is that no new process can be created (forked) so that command fails.
However bash just ignores the error and continues in the script which caused unexpected problems.
As you can see, we already check for errors in the command itself, but the "Cannot fork" error appears before the command is even run.
Is there a way to catch that error and stop bash from execution?
There are actually several problems with this error check, that'll prevent it from properly handing any error, not just "Cannot fork" errors.
The first problem is that || has higher precedence than ;, so mycmd || echo "error"; exit 2 will run echo "error" only if mycmd fails, but it'll run exit 2 unconditionally, whether or not mycmd succeeds or fails.
In order to fix this, you should group the error handling commands with { }. Something like: mycmd || { echo "error"; exit 2; }. Note that a ; or line break before } is required, or the } will be treated as an argument to exit.
(BTW, I sometimes see this shorthanded as mycmd || echo "error" && exit 2. Don't do this. If the echo fails for some weird reason, it won't run the exit.)
Also all of this, the echo and the exit, is run in the subshell created by the backticks (or would be, if that subshell had forked successfully). That means the error message will get saved in myvar rather than printed (error messages should generally be sent to standard error, e.g. echo "error" >&2); and more importantly it'll be the subshell that exits, not the shell that's running the script. The main script will note that the subshell exited with an error... and blithely keep running. (Well, unless you have -e set, but that's a whole other ball of potential bugs.)
The solution to that is to put the || stuff outside the backticks (or `$( ), since that's generally preferred over backticks). That way it happens in the main shell, that's what prints the error, that's what exits if there's an error, etc. This should also solve the "Cannot fork" problem, although I haven't tested it.
So, with all these corrections, it should look something like this:
myvar=$(mycmd) || {
echo "error" >&2
exit 2
}
Oh, and as Charles Duffy pointed out in a comment, if you use something like local myvar=$(mycmd) or export myvar=$(mycmd), the local/export/whatever command will override the exit status from mycmd. If you need to do that, set the variable's properties separately from its value:
local myvar
myvar=$(mycmd) || {
...

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

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

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.

error handling in shell script to stop flow execution on next line

I would like to setup a error handling on my shell script where is my invocation of script fail with error, I should be able to stop the executin and flow should nt go on next line.
Like In my main script, I am making call to below script
sh /usr/oracle/StopServer.sh
if this script fail with error, my next script on this main file should not execute.. pls help.
You can check the return value of the command execution, one way to do this is:
sh /usr/oracle/StopServer.sh
if [ $? -ne 0 ]; then
# exit or take action
fi
it should do the trick
Here you go.
sh /usr/oracle/StopServer.sh && sh my_next_line_that_only_happens_if_Stop_server_exits_without_error
For more information, the && operator in bash (and most languages) exhibits McCarthy evaluation which is basically just lazy evaluation for boolean conditionals. what this means is that for an and (&&) then the second term of the and expression will only be evaluated if the first part is true (because otherwise the result is garuanteed to be false. Similarly if we did A || B (or) B would only be executed if A were false, which means it returned with an exit code of 0 and thus failed. If you had a program that you wanted to execute if the program exited normally and another that you want to execute if it exited with a failed state (I'm going to call them normal and fail) then you could execute them like so:
sh condition.sh && sh normal.sh || sh fail.sh
EDIT:
if [ sh condition.sh ]; then
# do whatever
else
#this is what to do if it failed
fi
EDIT #2:
If you want to see what happens try running this:
if [ ls -badoption ] ; then
echo "passed "
else
echo "failed"
fi
# result follows
# zsh: parse error: condition expected: ls
# failed
it will fail as there is a bad option and failed will be run, if you had just put ls it would have echoed passed, this is where you can either exit or run the script depending on what path you take.

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