Posix Shell test non zero exit code script termination when set -e - shell

Reading the manual of the test shell command:
man test
Description states:
Exit with the status determined by EXPRESSION.
But this conflicts with my POSIX sh test script examples, where I use set -eu that should terminate a script if a command's exit status is not zero:
#!/bin/sh
set -eu
status=1
test "$status" -ne 0 && echo "Status not eq 0"
echo "(A) Exit code for "$status" = $?"
echo ""
status=0
test "$status" -ne 0 && echo "Status not eq 0"
echo "(B) Exit code for "$status" = $?"
echo ""
status=1
test "$status" -ne 0
echo "(C) Exit code for "$status" = $?"
echo ""
status=0
test "$status" -ne 0
echo "(D) Exit code for "$status" = $?"
running that script provides the output:
Status not eq 0
(A) Exit code for 1 = 0
(B) Exit code for 0 = 1
(C) Exit code for 1 = 0
This is how I understand flow of a execution for:
status=1
test "$status" -ne 0 && echo "Status not eq 0"
echo "(A) Exit code for "$status" = $?"
with output:
Status not eq 0
(A) Exit code for 1 = 0
test "$status" -ne 0 evaluates to true so its exit code is zero therefore an expression after boolean && is executed and since it is just an echo it returns also zero exit code so the script does not brake and echoes next line (A) Exit code for 1 = 0
but for the
status=0
test "$status" -ne 0 && echo "Status not eq 0"
echo "(B) Exit code for "$status" = $?"
with the output:
(B) Exit code for 0 = 1
Following previous reasoning the test should return non zero therefore an expression after && should not be executed (and it is not) but the script executes even the test returned non zero exit code (B) Exit code for 0 = 1.
Why does the script continue execution? It should brake due to non zero exit status.
There is no output for the case:
status=0
test "$status" -ne 0
echo "(D) Exit code for "$status" = $?"
that suggests to me the script execution was terminated at the line test "$status" -ne 0 and if you run echo $? after running the script you'll get in fact 1.
But how come the script is terminated when the test returns non zero exit status for the example (D) but it is not for the example (B)?
The only difference between (D) and (B) is that (B) has && echo "Status not eq 0" after the test but that is not executed and the exit status is 1 so in case of (B) the script should be terminated but it wasn't and if an exit status of a test is treated somehow so specially so it does not terminate a script having a set -e then why it terminates script for the (D) example?
EDIT
Similarly to test behaves ls
for the script:
#!/bin/sh
set -eu
ls notexitingfile && echo "File exists"
echo "Exit code for ls = $?"
ls notexitingfile
echo "Exit code for ls = $?"
the output is:
ls: cannot access 'notexitingfile': No such file or directory
Exit code for ls = 2
ls: cannot access 'notexitingfile': No such file or directory
note Exit code for ls = 2 for the first example and lack of it for the second.
I think the cause of the unexpected behavior may be my misunderstanding of the script termination (set -e) due to non zero exit code when using && operator.

The set -e option applies only to a single command.
It does not apply for commands combined with || or &&.
man bash says:
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 !.

Related

Only run code if git tag exists for current commit in BASH [duplicate]

What would be the best way to check the exit status in an if statement in order to echo a specific output?
I'm thinking of it being:
if [ $? -eq 1 ]
then
echo "blah blah blah"
fi
The issue I am also having is that the exit statement is before the if statement simply because it has to have that exit code. Also, I know I'm doing something wrong since the exit would obviously exit the program.
Every command that runs has an exit status.
That check is looking at the exit status of the command that finished most recently before that line runs.
If you want your script to exit when that test returns true (the previous command failed) then you put exit 1 (or whatever) inside that if block after the echo.
That being said, if you are running the command and are wanting to test its output, using the following is often more straightforward.
if some_command; then
echo command returned true
else
echo command returned some error
fi
Or to turn that around use ! for negation
if ! some_command; then
echo command returned some error
else
echo command returned true
fi
Note though that neither of those cares what the error code is. If you know you only care about a specific error code then you need to check $? manually.
Note that exit codes != 0 are used to report errors. So, it's better to do:
retVal=$?
if [ $retVal -ne 0 ]; then
echo "Error"
fi
exit $retVal
instead of
# will fail for error codes == 1
retVal=$?
if [ $retVal -eq 1 ]; then
echo "Error"
fi
exit $retVal
An alternative to an explicit if statement
Minimally:
test $? -eq 0 || echo "something bad happened"
Complete:
EXITCODE=$?
test $EXITCODE -eq 0 && echo "something good happened" || echo "something bad happened";
exit $EXITCODE
$? is a parameter like any other. You can save its value to use before ultimately calling exit.
exit_status=$?
if [ $exit_status -eq 1 ]; then
echo "blah blah blah"
fi
exit $exit_status
For the record, if the script is run with set -e (or #!/bin/bash -e) and you therefore cannot check $? directly (since the script would terminate on any return code other than zero), but want to handle a specific code, #gboffis comment is great:
/some/command || error_code=$?
if [ "${error_code}" -eq 2 ]; then
...
Just to add to the helpful and detailed answer:
If you have to check the exit code explicitly, it is better to use the arithmetic operator, (( ... )), this way:
run_some_command
(($? != 0)) && { printf '%s\n' "Command exited with non-zero"; exit 1; }
Or, use a case statement:
run_some_command; ec=$? # grab the exit code into a variable so that it can
# be reused later, without the fear of being overwritten
case $ec in
0) ;;
1) printf '%s\n' "Command exited with non-zero"; exit 1;;
*) do_something_else;;
esac
Related answer about error handling in Bash:
Raise error in a Bash script
If you are writing a function – which is always preferred – you can propagate the error like this:
function()
{
if <command>; then
echo worked
else
return
fi
}
Now, the caller can do things like function && next as expected! This is useful if you have a lot of things to do in the if block, etc. (otherwise there are one-liners for this). It can easily be tested using the false command.
Using Z shell (zsh) you can simply use:
if [[ $(false)? -eq 1 ]]; then echo "yes" ;fi
When using Bash and set -e is on, you can use:
false || exit_code=$?
if [[ ${exit_code} -ne 0 ]]; then echo ${exit_code}; fi
This might only be useful in a limited set of use-cases, I use this specifically when I need to capture the output from a command and write it to a log file if the exit code reports that something went wrong.
RESULT=$(my_command_that_might_fail)
if (exit $?)
then
echo "everything went fine."
else
echo "ERROR: $RESULT" >> my_logfile.txt
fi
you can just add this if statement:
if [ $? -ne 0 ];
then
echo 'The previous command was not executed successfully';
fi
Below test scripts below work for
simple bash test commands
multiple test commands
bash test commands with pipe included:
if [[ $(echo -en "abc\n def" |grep -e "^abc") && ! $(echo -en "abc\n def" |grep -e "^def") ]] ; then
echo "pipe true"
else
echo "pipe false"
fi
if [[ $(echo -en "abc\n def" |grep -e "^abc") && $(echo -en "abc\n def" |grep -e "^def") ]] ; then
echo "pipe true"
else
echo "pipe false"
fi
The output is:
pipe true
pipe false

Exit code as variable? $? is always zero

I noticed something strange (at least for me it's strange). I am writing a script and I need 0 or 1 as exit codes. So far so good. I have them in a simple if-else with echo $? above each condition but when I make the echo $? as a variable to call it's always showing 0 as exit code.
#!/bin/bash
exit=`echo $?`
DIRECTORY="/some/dir"
if [[ $(stat -c "%a" "$DIRECTORY") == "777" ]]
then
echo $?
#echo "The exit code is: $exit"
else
echo $?
#echo "The exit code is: $exit"
fi
#EOF
If use just "echo $?" it's all good. I receive 0 or 1. But in the commented part I always receive 0.
The $? construct contains the status code of the last command executed - exactly the last command.
The [[ is a command (bash test). Hence, you are testing the result of test.
To fix it, save the result first. For example:
result=$(stat -c "%a" "$DIRECTORY")
status=$?
... do stuff with status and result
You set the value of exit at the top of the code. This remains unchanged throughout the script. This is why you always get the same value.
Instead:
#!/bin/bash
dir='/some/dir'
if [[ "$(stat -c "%a" "$dir")" == "777" ]]; then
status=0
printf 'Permissions are 777 (status=%d)\n' "$status"
else
status=1
printf 'Permissions are not 777 (status=%d)\n' "$status"
fi
or
#!/bin/sh
dir='/some/dir'
case "$(stat -c "%a" "$dir")" in
777) echo 'Permissions are 777'; status=0 ;;
*) echo 'Permissions are not 777'; status=1 ;;
esac
Note that there is actually no need to investigate $? here. If the test succeeds, you know that it's going to be 0, otherwise non-zero.

exit status code for shell script execution

I am trying to run a shell script(A) from another shell script(B). For testing purpose, the shell script(A) will always throw exception/error (just for testing). I am trying to get the exit status code immediately after running that script.
code:
case 1:
errormsg=$(sample.sh 2>&1)
if [ $? -ne 0 ]
then
echo $?
echo "Successful."
else
echo $?
echo "Error."
output:
0
Successful
case 2 :
errormsg=$(sample.sh 2>&1)
echo $?
if [ $? -ne 0 ]
then
echo $?
echo "Successful."
else
echo $?
echo "Error."
output:
1
1
Error
Why there is difference in output? Shouldn't the output be 'Error' in the case 1 too?
$? always give the result of the last executed command.
In case 1 this is your external script, but in case 2 it is the echo $?
That's why you get different results

How to tell a script to stop when a execution of script that it calls ends with specific condition

I have a master.shscript that calls 2 other scripts
#!/bin/bash
/home/script1
/home/script2
script1 has a following condition:
if [ $len -lt 10 ]; then
echo "mask is not 10"
exit
fi
How to i tell master.sh not to proceed to script2 if script one makes that exit condition? In other words, stop executing subsequent scripts if $len is not 10
Standard way is to exit with a non zero exit status on error:
if (( len < 10 )) ; then
echo Mask is not at least 10. >&2
exit 1
fi
You can then simply check the exit code:
/home/script1 && /home/script2
If there are several things that can go wrong and you want to react to a failure, you can exit with various exit codes and examine them in the parent with $?:
/home/script1
case $? in
0) /home/script2 ;;
1) echo 'Fatal problem' ;;
2) echo 'Missing file' ;;
# etc.
esac
For the non-standard situation, I'd capture the output:
[[ $(/home/script1) == 'mask is not 10' ]] && exit
/home/script2

What causes the exit status to change for the same command even when the output is identical?

In the below code, I am trying to check if the command within the if condition completed successfully and that the data was pushed into the target file temp.txt.
Consider:
#!/usr/bin/ksh
A=4
B=1
$(tail -n $(( $A - $B )) sample.txt > temp.txt)
echo "1. Exit status:"$?
if [[ $( tail -n $(( $A - $B )) sample.txt > temp.txt ) ]]; then
echo "2. Exit status:"$?
echo "Command completed successfully"
else
echo "3. Exit status:"$?
echo "Command was unsuccessfully"
fi
Output:
$ sh sample.sh
1. Exit status:0
3. Exit status:1
Now I can't get why the exit status changes above.. when the output of both the instances of the tail commands are identical. Where am I going wrong here..?
In the first case, you're getting the exit status of a call to the tail command (the subshell you spawned with $() preserves the last exit status)
In the second case, you're getting the exit status of a call to the [[ ]] Bash built-in. But this is actually testing the output of your tail command, which is a completely different operation. And since that output is empty, the test fails.
Consider :
$ [[ "" ]] # Testing an empty string
$ echo $? # exit status 1, because empty strings are considered equivalent to FALSE
1
$ echo # Empty output
$ echo $? # exit status 0, the echo command executed without errors
0
$ [[ $(echo) ]] # Testing the output of the echo command
$ echo $? # exit status 1, just like the first example.
1
$ echo foo
foo
$ echo $? # No errors here either
0
$ [[ $(echo foo) ]]
$ echo $? # Exit status 0, but this is **NOT** the exit status of the echo command.
0

Resources