Why is this bash script returning an exit code of 0?
$ if [ $NonExistent != "something" ]; then echo good; fi; echo $?
-bash: [: !=: unary operator expected
0
I've tried putting the script in a file and adding
set -e
set -o pipefail
but it still returns 0. Assuming I can't modify this script and can only invoke it, is there a way I could know that the script failed?
It's returning 0 because
https://www.gnu.org/software/bash/manual/bashref.html#Conditional-Constructs
if
[...]
The return status is the exit status of the last command executed, or zero if no condition tested true.
As for knowing that the script failed: What exactly do you mean by "failed"? It runs through successfully.
You could test whether the output starts with good if you want to know whether the condition was true or not.
Related
What is the correct way to output an exit status in bash? As far as I know, the exit status called by $? corresponds to the status of the last command executed.
The script being worked on has a few conditional checks on the files fed as arguments, for example, a check on whether any files were named at all or if a file exists or not.
So I have conditional statements like this:
if [ $# -eq 0 ] ; then
echo "No file name(s) given! \nExit status=$?"
exit
if [ ! -e "$fName" ] ; then
echo "$fName does not exist! \nExit status=$?"
exit
But these return an exit status of 0. I'm not entirely sure even what exit codes would be appropriate for each of these situations, but I think both 1 would work for both based on this. Should I just hard-code the 1 into the conditional statement or change the logic so unix outputs an error code? Also, what command would the 0 that I get for the above example be the exit code for?
In both of these cases, the last command executed was [, which exited with 0 status ("success") to land you in the "true" block.
Yes, you need to specify a non-zero exit code for the exit command.
Pedantically "unix" does not output the error code: it's the shell running your script that exits with the specified status.
what is the difference if I put test or without test in a .ksh file?
if test $1 -ne 0 ; then ....
and
if $1 -ne 0 ; then ....
Many Thanks
I actually think this is an important question, as it highlights some important rules in shell programming:
Every command in the shell returns an true (0) or false exit code
Control structures in the shell do not require comparisons
Every command in the shell returns an exit code
Any properly coded command in the shell will return 0 for success,
and non-zero for failure. While there is only one way to succeed, but always more than way to fail.
Example:
$ no-such-command || echo no $?
ksh[1]: no-such-command: not found [No such file or directory]
no 127
$
The exit status of a command is caught in the pseudo variable $? and is available until you complete another command.
This exit status is used in control structures like if ... then ... fi
or until ... do ... done.
failing(){ return 2; }
failing &&
echo "It works" ||
echo "It failed with exit code $?"
results in
It failed with exit code 2
Control structures in the shell do not require comparisons
Let's start with the simplest definition
of the if command:
if compound-list
then
compound-list
fi
For the full syntax, see Section 2.9.4 Compound Commands of Shell Command Language of The Open Group Base Specifications.
Between the keywords, if, then, and fi there are two sections of
code, named compound-list.
This is shorthand for any sequence of code that would be valid in a script. The exit status of the list will be equal to the exit status of the last command in the list.
The important difference for the two lists is that the firts will determine the flow of control, while the second determines the exit status of the entire expression, when executed.
Conclusion
Any command can be used as the test in an if/then/else/fi construct.
Because we often want to test things explicitly, we often use the actual test command or its derivatives [ ... ] and [[ ... ]].
if [[ -n $1 ]]; then
echo "'$1' is a non-empty string"
fi
For complex expressions it is always preferred to wrap them in a
function to apply some abstraction.
One more trivial example:
non_empty_files_present(){
(path=${1:?directory expected}
(find ${path} -type f -size +0 | read line) 2> /dev/null
)
}
if non_empty_files_present /var/tmp; then
echo "Some files have content"
fi
Editing this post, original is at bottom beneath the "Thanks!"
command='a.out arg1 arg2 &'
eval ${command}
if [ $? -ne 0 ]; then
printf "Command \'${command}\' failed\n"
exit 1
fi
wait
Here is a test script that demonstrates the problem, which I oversimplified
in the original post. Notice the ampersand in line 2 and the wait command.
These more faithfully represent my script. In case it matters, the ampersand
is sometimes there and sometimes not, its presence is determined by a user-
specified flag that indicates whether or not to background a long arithmetic
calculation. And, also in case it matters, I'm actually backgrounding many
(twelve) processes, i.e., ${command[0..11]}. I want the script to die if any
fail. I use 'wait' to synchronize the successful return of all processes.
Happy (sort of) to use another approach but this almost works.
The ampersand (for backgrounding) seems to cause the problem.
When ${command} omits the ampersand, the script runs as expected:
The executable a.out is not found, a complaint to that effect is issued,
and $? is non-zero so the host script exits. When ${command} includes
the ampersand, the same complaint is issued but $? is zero so the
script continues to execute. I want the script to die immediately when
a.out fails but how do I obtain the non-zero return value from a
backgrounded process?
Thanks!
(original post):
I have a bash script that uses commands of the form
eval ${command}
if [ $? -ne 0 ]; then
printf "Command ${command} failed"
exit 1
fi
where ${command} is a string of words, e.g., "a.out arg1 ... argn".
The problem is that the return code from eval (i.e., $?) is always
zero even when ${command} fails. Removing the "eval" from the above
snippet allows the correct return code ($?) to be returned and thus
halt the script. I need to keep my command string in a variable
(${command}) in order to manipulate it elsewhere, and simply running
${command} without the eval doesn't work well for other reasons. How do I catch the
correct return code when using eval?
Thanks!
Charlie
The ampersand (for backgrounding) seems to cause the problem.
That is correct.
The shell cannot know a command's exit code until the command completes. When you put a command in background, the shell does not wait for completion. Hence, it cannot know the (future) return status of the command in background.
This is documented in man bash:
If a command is terminated by the control operator &, the shell
executes the command in the background in a subshell. The shell does
not wait for the command to finish, and the return status is 0.
In other words, the return code after putting a command in background is always 0 because the shell cannot predict the future return code of a command that has not yet completed.
If you want to find the return status of commands in the background, you need to use the wait command.
Examples
The command false always sets a return status of 1:
$ false ; echo status=$?
status=1
Observe, though, what happens if we background it:
$ false & echo status=$?
[1] 4051
status=0
The status is 0 because the command was put in background and the shell cannot predict its future exit code. If we wait a few moments, we will see:
$
[1]+ Exit 1 false
Here, the shell is notifying us that the brackground task completed and its return status was just as it should be: 1.
In the above, we did not use eval. If we do, nothing changes:
$ eval 'false &' ; echo status=$?
[1] 4094
status=0
$
[1]+ Exit 1 false
If you do want the return status of a backgrounded command, use wait. For example, this shows how to capture the return status of false:
$ false & wait $!; echo status=$?
[1] 4613
[1]+ Exit 1 false
status=1
From the man page on my system:
eval [arg ...] The args are read and concatenated together into a single command. This command is then read and executed by the shell, and its exit status is returned as the value of eval. If there are no args, or only null arguments, eval returns 0.
If your system documentation is 'the same', then, most likely, whatever commands you are running are the problem, i.e. 'a.out' is returning '0' on exit instead of a non-zero value. Add appropriate 'exit return code' to your compiled program.
You might also try using $() which will 'run' your binary instead of 'evaluating' it..., i.e.
STATUS=$(a.out var var var)
As long on only one 'command' is in the stream, then the value of $? is the 'exit code'; otherwise, $? is the return code for the last command in a multi-command 'pipe'...
:)
Dale
I have a Makefile which runs a program which on success return a non-zero value, and on failure return another non-zero value. I know that I can ignore the exit status by prefixing the command with -, but that does not work because I need to know if the command succeeded.
You can test the returned value on a second command on the same Makefile line, using the shell $? variable that contains the last returned value.
For example with the false command that would obviously stop the compilation:
test:
/bin/false ; /usr/bin/test "$$?" -eq 1 # <-- make does not stop here
/bin/echo "Continues ..."
/bin/false # <-- make stops here
Use
command || [ $$? -eq v ]
as your command, substituting command with the command, and v with the value returned on success.
(This is just a more compact version of Didier Trosset's answer.)
Depending on how the tool behaves on fail, you could just check for the existence of the output file. something like:
#if test ! -f $(FILE); then exit 2; fi
I'm writing a script in Bash to test some code. However, it seems silly to run the tests if compiling the code fails in the first place, in which case I'll just abort the tests.
Is there a way I can do this without wrapping the entire script inside of a while loop and using breaks? Something like a dun dun dun goto?
Try this statement:
exit 1
Replace 1 with appropriate error codes. See also Exit Codes With Special Meanings.
Use set -e
#!/bin/bash
set -e
/bin/command-that-fails
/bin/command-that-fails2
The script will terminate after the first line that fails (returns nonzero exit code). In this case, command-that-fails2 will not run.
If you were to check the return status of every single command, your script would look like this:
#!/bin/bash
# I'm assuming you're using make
cd /project-dir
make
if [[ $? -ne 0 ]] ; then
exit 1
fi
cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
exit 1
fi
With set -e it would look like:
#!/bin/bash
set -e
cd /project-dir
make
cd /project-dir2
make
Any command that fails will cause the entire script to fail and return an exit status you can check with $?. If your script is very long or you're building a lot of stuff it's going to get pretty ugly if you add return status checks everywhere.
A SysOps guy once taught me the Three-Fingered Claw technique:
yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$#" || die "cannot $*"; }
These functions are *NIX OS and shell flavor-robust. Put them at the beginning of your script (bash or otherwise), try() your statement and code on.
Explanation
(based on flying sheep comment).
yell: print the script name and all arguments to stderr:
$0 is the path to the script ;
$* are all arguments.
>&2 means > redirect stdout to & pipe 2. pipe 1 would be stdout itself.
die does the same as yell, but exits with a non-0 exit status, which means “fail”.
try uses the || (boolean OR), which only evaluates the right side if the left one failed.
$# is all arguments again, but different.
If you will invoke the script with source, you can use return <x> where <x> will be the script exit status (use a non-zero value for error or false). But if you invoke an executable script (i.e., directly with its filename), the return statement will result in a complain (error message "return: can only `return' from a function or sourced script").
If exit <x> is used instead, when the script is invoked with source, it will result in exiting the shell that started the script, but an executable script will just terminate, as expected.
To handle either case in the same script, you can use
return <x> 2> /dev/null || exit <x>
This will handle whichever invocation may be suitable. That is assuming you will use this statement at the script's top level. I would advise against directly exiting the script from within a function.
Note: <x> is supposed to be just a number.
I often include a function called run() to handle errors. Every call I want to make is passed to this function so the entire script exits when a failure is hit. The advantage of this over the set -e solution is that the script doesn't exit silently when a line fails, and can tell you what the problem is. In the following example, the 3rd line is not executed because the script exits at the call to false.
function run() {
cmd_output=$(eval $1)
return_value=$?
if [ $return_value != 0 ]; then
echo "Command $1 failed"
exit -1
else
echo "output: $cmd_output"
echo "Command succeeded."
fi
return $return_value
}
run "date"
run "false"
run "date"
Instead of if construct, you can leverage the short-circuit evaluation:
#!/usr/bin/env bash
echo $[1+1]
echo $[2/0] # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]
Note the pair of parentheses which is necessary because of priority of alternation operator. $? is a special variable set to exit code of most recently called command.
I have the same question but cannot ask it because it would be a duplicate.
The accepted answer, using exit, does not work when the script is a bit more complicated. If you use a background process to check for the condition, exit only exits that process, as it runs in a sub-shell. To kill the script, you have to explicitly kill it (at least that is the only way I know).
Here is a little script on how to do it:
#!/bin/bash
boom() {
while true; do sleep 1.2; echo boom; done
}
f() {
echo Hello
N=0
while
((N++ <10))
do
sleep 1
echo $N
# ((N > 5)) && exit 4 # does not work
((N > 5)) && { kill -9 $$; exit 5; } # works
done
}
boom &
f &
while true; do sleep 0.5; echo beep; done
This is a better answer but still incomplete a I really don't know how to get rid of the boom part.
You can close your program by program name on follow way:
for soft exit do
pkill -9 -x programname # Replace "programmname" by your programme
for hard exit do
pkill -15 -x programname # Replace "programmname" by your programme
If you like to know how to evaluate condition for closing a program, you need to customize your question.
#!/bin/bash -x
# exit and report the failure if any command fails
exit_trap () { # ---- (1)
local lc="$BASH_COMMAND" rc=$?
echo "Command [$lc] exited with code [$rc]"
}
trap exit_trap EXIT # ---- (2)
set -e # ---- (3)
Explanation:
This question is also about how to write clean code. Let's divide the above script into multiple parts:
Part - 1:
exit_trap is a function that gets called when any step failed and captures the last executed step using $BASH_COMMAND and captures the return code of that step. This is the function that can be used for any clean-up, similar to shutdownhooks
The command currently being executed or about to be executed, unless the shell is executing a command as the result of a trap, in which case it is the command executing at the time of the trap.
Doc.
Part - 2:
trap [action] [signal]
Register the trap action (here exit_trap function) in case of EXIT signal.
Part - 3:
Exit immediately if a sequence of one or more commands returns a non-zero status. 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 in an if statement, 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 status is being inverted with !. If a compound command other than a subshell returns a non-zero status because a command failed while -e was being ignored, the shell does not exit. A trap on ERR, if set, is executed before the shell exits.
Doc.
Part - 4:
You can create a common.sh file and source it in all of your scripts.
source common.sh