Why does if [ ...something... ]; then echo "Exit status is $?" always emit 0? - bash

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.

Related

Bash exit code of the test part should not be 0

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.

How to determine one program output to pipe as next program input in shell using ${PIPESTATUS[*]}

I am new to shell script. I have a scenario where i need to get the exit status of one shell script and pass its value as input(if exit code is zero/otherwise exit the main shell script file) to the next shell script and continue until it executes all the scripts. Below is the code i tried.but its not working as expected.
status=`run1.sh`
if [[ status -eq 0 ]]; then
status=`run2.sh`
else
exit 1
fi
if [[ status -eq 0 ]];then
status=`run3.sh`
else
exit 2
fi
Its running successfully for first 2 scripts. It's failing on second if block, even though the output value of run2.sh is 0. I searched in google, its suggesting to use {PIPESTATUS[#]}, I tried it by replacing exit 1 with pipestatus and pass pipestatus in the second if block like below.
status=`run1.sh`
if [[ status -eq 0 ]]; then
status=`run2.sh`
else
exit ${PIPESTATUS[1]} ## pipestatus[1]- is run2.sh output value
fi
if [[ ${PIPESTATUS[1]} -eq 0 ]];then
status=`run3.sh`
else
exit 2
fi
I think i am not clear on how to use pipestatus. I would appreciate if anyone can provide me some example to my scenario.
status is a static string; you are not examining the variable you created at all.
There is no pipe here so PIPESTATUS does not come into play. It's for code like
one | two
where traditionally the exit status of one wasn't available to the shell; Bash changed that by exposing results from every process in a pipeline.
Having your scripts print the number zero to signal success is not how it's usually done. A command produces both output and a status code (the number you give as the argument to exit, or in a function, to return), and code would usually examine the latter. It is exposed in $? but the shell's control structures implicitly check it under the hood, so your code would look like
if run1.sh; then
if run2.sh; then
if run3.sh; then
...
provided you change them to produce a useful exit code. Though this could be further simplified to
run1.sh || exit
run2.sh || exit
run3.sh || exit
or even to
set -e
run1.sh
run2.sh
run3.sh

KSH: IF condition issue: if test .... then vs if ... then

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

How do I check the exit status of a Makefile shell invocation?

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

In a Bash script, how can I exit the entire script if a certain condition occurs?

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

Resources