Capturing exit status of a failed command in a shell script - bash

I have a shell script with multiple commands as below
cmd-1
cmd-2
cmd-3
....
cmd-n
I want the shell script execution to continue even if there is a failure in the middle e.g cmd-3 or cmd-7. To achieve this I used set +e. This allows me to continue the execution, but I am unable to capture the exit status of the failed command (since exit status of the script is always based on the last command). Is there any way to set the status of the complete script based on last failed command.

You can achieve it like that. Not too clean way, but quite easy.
exit_code=0
cmd-1 || exit_code=$?
cmd-2 || exit_code=$?
cmd-3 || exit_code=$?
....
cmd-n || exit_code=$?
exit $exit_code
The clean way would be to split your code to functions and check result of you commands there.

Related

Jenkins - Continue script on failure

I have a freestyle Jenkins job that has a simple bash script for a build.
In the bash script, there is a for loop which sometimes returns a non-zero return code.
Thing is, Jenkins quits immediately when it happens. However, if during the run it gets a non-zero return code, the job needs to continue, and in the end mark the job as failed. (In other words, don't stop on failure but show the job failed when it's finished). (that's why I can't just append || true)
Is it possible to do? Thanks ahead!
If you set the bash option -e, (ie: #!/bin/bash -e) then the shell script will exit upon error. This is typically desired to avoid lots of testing for failures and still catching them, so many scripts have that configuration. Also, typically, this is NOT applicable inside loops if it-then-else constructs, so you should investigate what inner command you are invoking and what it is doing .. it may have an exit $? that is propagating.
If running a Jenkins execute shell step, you can add the first line #!/bin/bash +e and that may override the fail. Otherwise, try:
#!/bin/bash
FAILURE=false
set +e
for ENTRY in 1 2 3 4 5 .. N
do
command ${ENTRY}
[[ $? == 0 ]] || FAILURE=true
done
set -e
[[ ${FAILURE} == 'true' ]] && return 1
return 0

Bash - how to retrieve exit status of first command in an 'or' statement

In a script in gitlab I have the following statement:
locust || true
This is because I don't want gitlab CI to stop the execution of the stage if the locust command fails with some exit code. But how can I nevertheless retrieve the exit code of the locust statement
If you place the command in an if statement instead of using || true, failure will not cause the script to exit and you'll be able to save the exit code.
if locust; then
rc=0
else
rc=$?
fi
echo locust exited with rc=$rc
PS: you can't just do if ! locust; then... because in that case, the ! reverses the logic of the exit code. You actually need the else block for the rc=$? to work the way you want.
PPS: Yeah, I like that other answer better, rc=0; locust || rc=$?... I should have thought of that!
You could replace true with a variable assignment:
rc=0; locust || rc=$?
If like in a context with errexit set you want to make sure that the overall return code is always 0 even though the assignment miraculously fails somehow, just re-attach || true:
rc=0; locust || rc=$? || true
Going further:
If instead of the literal true you want some next command to be executed if the first one fails, then negate ! the variable assignment to make it fail in order to proceed to the evaluation of the second ||.
# For personal use only!
rc=0; first-cmd || ! rc=$? || next-cmd
But be cautious here (as always when connecting commands logically): Don't use this shortcut in a production context! Rather perform separate checks to see if all preconditions have been met to execute that command.
Answering the ”Locust side” of the question: you can pass —exit-code-on-error 0 to only give a non-zero exit code if the run failed completely, not on failed responses.
(idk why this is not the default but changing it now would break stuff, so I probably won’t)

Concise way to run command if previous command completes successfully, when previous command is already running

I use the following pattern often:
some_long_running_command && echo success
However, sometimes I forget to attach the && success, so I write out the status check in the next line:
some_long_running_command
[ $? -eq 0 ] && echo success
As you can see, the status check in the second example ([ $? -eq 0 ] &&) is much more verbose than the check from the first example (&&).
Is there a more concise way to run a command only if the previous command completes successfully, when the previous command is already running?
exit exits its shell using the same exit status as the most recently completed command. This is true even if that command runs in the parent of the shell that executes exit: you can run exit in a subshell.
some_long_running_command
(exit) && echo success
Depending on the command, you can also suspend the job, then resume it with fg, which will have the same exit status as the command being resumed. This has the benefit of not having to wait for the command to complete before adding the new && list.
some_long_running_command
# type ctrl-z here
fg && echo success
Generally, this will work as long as some_long_running_command doesn't have to keep track of some resource that is changing in real-time, so that you don't lose data that came and went while the job was (briefly) suspended.

Best Option for resumable script

I am writing a script that executes around 10 back-end processes in sequence, depending on if the previous process was executed without any errors.
Now let's assume the scenario, in which lets say 5th process failed and script came out. But I want to code it in a way such that, when next time user runs it(after removing the error because of which script exited last time), he should be able to run from 5th process onwards and not again from 1st process.
To be more specific, assume following is the script:
Script Starts
Process1
if [ $? -eq 0 ] then
Process2
if [ $? -eq 0 ] then
Process3
if [ $? -eq 0 ] then
..
..
..
..
if [ $? -eq 0 ] then
Process10
else
exit
So here the script will exit anytime if any one of the process fails to complete with status 0. So again, if process5 fails, and user corrects the problem and restarts script, the script should start with process5 again and not process1 or at least there should be an option to user if he wants to resume the script or start it back from beginning i.e. process1.
What all possible ways we can code this kind of script, also please bear in mind, I am not allowed to use a temporary db, where I can store the status of each process.
I need to code in sh (shell script) in unix.
A simple solution would be to write stamp files:
#/bin/sh
set -e # Automatically abort if any simple command fails
if ! test -f cmd1-stamp; cmd1; fi
touch cmd1-stamp
if ! test -f cmd2-stamp; cmd2; fi
touch cmd2-stamp
When the script executes, if cmd1-stamp exists, cmd1 is not executed. Otherwise, cmd1 is executed. The script will abort if it fails. Note that it is very tempting to write test -f cmd1-stamp || cmd1, and this seems to work ( in bash ) but the shell specs state that the shell shall abort if the simple command that fails is not a part of an AND or OR list, and I suspect this is (yet another) instance of bash not conforming to the spec. (Although it doesn't seem to specify that the shell shall not abort if the failing command is part of an AND or OR list.)

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