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.
Related
I am trying to test if certain variables are set and if ping exits with 0. When I do
var=1 #set for later
#"If" checks exit status correctly
if ping -c 1 an-inaccessible-thing
then
echo T
else
echo F
fi
#returns F
#"if" does not like the program in the [[ ]]
if [[ -n $var && ping -c 1 an-inaccessible-thing ]]
then
echo T
else
echo F
fi
#returns this error for obvious reasons
-bash: conditional binary operator expected
-bash: syntax error near `-c'
#if runs its test on the output of the shell, not its exit code.
if [[ -n $var && $(ping -c 1 an-inaccessible-thing) ]]
then
echo T
else
echo F
fi
#returns T, probably because it's being evaluated with -n and no the exit code
how can I test programs exit code inside the double square brackets?
Because [[ is a separate (bash builtin) program, like ping or any other program.
Do && outside of [[ ]].
[[ -n $var ]] && ping -c 1 an-inaccessible-thing
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
I would like to write a loop in bash which executes until a certain command stops failing (returning non-zero exit code), like so:
while ! my_command; do
# do something
done
But inside this loop I need to check which exit code my_command returned, so I tried this:
while ! my_command; do
if [ $? -eq 5 ]; then
echo "Error was 5"
else
echo "Error was not 5"
fi
# potentially, other code follows...
done
But then the special variable ? becomes 0 inside the loop body.
The obvious solution is:
while true; do
my_command
EC=$?
if [ $EC -eq 0 ]; then
break
fi
some_code_dependent_on_exit_code $EC
done
How can I check the exit code of my_command (called in loop header) inside loop body without rewriting this example using a while true loop with a break condition as shown above?
In addition to the well-known while loop, POSIX provides an until loop that eliminates the need to negate the exit status of my_command.
# To demonstrate
my_command () { read number; return $number; }
until my_command; do
if [ $? -eq 5 ]; then
echo "Error was 5"
else
echo "Error was not 5"
fi
# potentially, other code follows...
done
If true command hurt your sensibility, you could write:
while my_command ; ret=$? ; [ $ret -ne 0 ];do
echo do something with $ret
done
This could be simplified:
while my_command ; ((ret=$?)) ;do
echo do something with $ret
done
But if you don't need ResultCode, you could simply:
while my_command ; [ $? -ne 0 ];do
echo Loop on my_command
done
or
while my_command ; (($?)) ;do
echo Loop on my_command
done
And maybe, why not?
while ! my_command ;do
echo Loop on my_command
done
But from there you could better use until as chepner suggest
You can get the status of a negated command from the PIPESTATUS built-in variable:
while ! my_command ; do
some_code_dependent_on_exit_code "${PIPESTATUS[0]}"
done
chepner's solution is better in this case, but PIPESTATUS is sometimes useful for similar problems.
So in my case I also need to ignore some exit codes and want to provide some useful output to the user so I wrote this up:
retrycmd(){
MSG=$1
IGNORE=$2
shift 2
local SLEEP_T=5
local L_CNT=5
local C_CNT=0
while ((C_CNT++ < ${L_CNT})) && ! $#;do
RET=${PIPESTATUS[0]}
#echo "RET: ${RET}"
for I in ${IGNORE//,/ };do # bashism: replace(/) all(/) match(,) with(/) value(<space>)
if ((${RET} == ${I}));then
#echo "${RET} = ${I}"
break 2
fi
done
echo "${MSG} failure ${C_CNT}"
sleep ${SLEEP_T}
done
if ((${C_CNT} > ${L_CNT}));then
echo "${MSG} failed"
poweroff
fi
}
#retrycmd "Doing task" "IGNORE,CSV" <CMD>
retrycmd "Ping google" "0" ping www.google.com
I have created a simple BASH script that checks every hour for the presence of a file on a remote server. It worked error-free until I was asked to move it to a server that runs KSH.
The portion of code that errors-out is this one:
connect_string=$UID#$SERVER:$srcdir/$EVENTFILE
result=`sftp -b "$connect_string" 2>&1`
if [ echo "$result" | grep "not found" ]; then
echo "not found"
else
echo "found"
fi
These are the errors it throws:
-ksh: .[51]: [: ']' missing
grep: ]: No such file or directory
found
It still runs though and confirms that the file I am polling for is there but I need to fix this. I changed the if statement like so
if [[ echo "$result" | grep "not found" ]]; then
but it fails right away with this error
-ksh: .: syntax error: `"$result"' unexpected
What am I missing?
Your basic syntax assumptions for if are incorrect. The old [...] syntax, calls the test builtin, and [[...]] is for textual pattern matching.
As #shelter's comment, the correct syntax is:
connect_string="$UID#$SERVER:$srcdir/$EVENTFILE"
result=`sftp -b "$connect_string" 2>&1`
if echo "$result" | grep "not found" ; then
echo "not found"
else
echo "found"
fi
But this is an unnecessary use of the external grep program, you can use shell text comparison:
if [[ $result == *not\ found* ]] ; then
echo "not found"
else
echo "found"
fi
(tested with bash and ksh)
Your solution:
EXIT=`echo $?`
if [ $EXIT != 0 ]
then
...
fi
Can be improved. First, if you are going to do an arithmetic comparison, then use ((...)), not test, and I can't figure out why you have the EXIT variable:
if (( $? != 0 ))
then
...
fi
But to go full circle, you actually only need:
if sftp -b "$connect_string" 2>&1
then
...
fi
echo "$result" | grep "not found"
#capture exit status code from previous command ie grep.
if [[ $? == 0 ]]
than
echo "not found"
else
echo "found"
fi
It appears you're struggling with a basic tenet of bash/ksh control structures.
Between the if and the then keywords, the shell expects one or more commands, with
the last command in the series deciding how the if statement is processed.
The square brackets are only needed if you actually need to perform a comparison. Internally they are equivalent to the test command - if the comparison succeeds, it
results in an exit status of 0.
Example:
$ [ a == a ]
$ echo $?
0
$ [ a == b ]
$ echo $?
1
Which is equivalent to:
$ test a == a
$ echo $?
0
$ test a == b
$ echo $?
1
I changed my approach to this.
connect_string=$UID#$SERVER:$srcdir/$EVENTFILE
result=`sftp "$connect_string" 2>&1`
EXIT=`echo $?`
if [ $EXIT != 0 ]
then
echo "file not found"
exit 1
else
echo "file found"
exit 0
fi
It takes care of my problem. Thanks to all.
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