Two styles of check return value in ksh - shell

In ksh shell, I wanna to check the return value after running a command, I've wrote two styles:
if [ $? -ne 0 ] ; then
echo "failed!"
exit 1
else
exit 0
fi
[ $? -ne 0 ] && echo "failed!" && exit 1
Are they equivalent? If not, what could I do if I wanna to write it in one line?

They're close, but not the same. First, the if will execute the exit 1 even if the echo failed for some reason; the chained expression won't. Also, the chained version lacks an equivalent of the else exit 0.
A better equivalent would be this:
[ $? -ne 0 ] && { echo "failed!"; exit 1; } || exit 0
This is tagged ksh, so you might find the numeric expression syntax cleaner:
(( $? )) && { echo "failed!"; exit 1; } || exit 0
But you can also write an if on one line, if you like:
if (( $? )); then echo "failed!"; exit 1; else exit 0; fi
If the command that you just ran above this expression in order to set $? is short, you may want to just use it directly as the if expression - with reversed clauses, since exit code 0 is true:
if grep -q "pattern" /some/filename; then exit 0; else echo "failed!"; exit 1; fi
It doesn't matter for this simple case, but in general you probably want to avoid echo. Instead, use printf - or if you don't mind being ksh-only, you can use print. The problem with echo is that it doesn't provide a portable way to deal with weird strings in variables:
$ x=-n
$ echo "$x"
$
While both printf and print do:
$ printf '%s\n' "$x"
-n
$ print - "$x"
-n
Again, not a problem here, or any time you're just printing out a literal string, but I found it was easier to train myself out of the echo habit entirely.

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 always called - variable precedence

I'm new to bash and having an issue where exit is always called in my script. Consider this simple code:
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
/export/home/scripts/script1.sh \
"$x" \
|| echo "Error.. something went wrong." && exit 1
fi
How can I handle errors, considering && takes precedence over || ?
Using GNU bash, version 3.2.51(1).
Thanks
You can do it like this :
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
/export/home/scripts/script1.sh \
"$x" \
|| { echo "Error.. something went wrong." && exit 1 ; }
fi
Note : I used { ; }, instead of (), because () will open your command in a subshell, so it will not exit.
&& and || have the same precedence in shell; the implicit parenthesization is (a || b) && c, not a || (b && c). Mixing || and && in the same list is rarely a good idea; use an explicit if statement.
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
if ! /export/home/scripts/script1.sh "$x"; then
echo "Error.. something went wrong"
exit 1
fi
fi
For arithmetic comparisons, prefer the arithemetic command ((...)) over [[ ... ]] for readability.
if (( x >= 1 && x <= 4 )); then
You can use braces to regroup commands without creating a new subshell :
{ true || false; } && echo true || echo false # echoes true
{ false || false; } && echo true || echo false # echoes false
Its syntax is pretty annoying : the opening brace must be followed by a space (or another character of $IFS, such as a linefeed or a tab), and the closing brace must be preceded by a linefeed or a ;, denoting the end of the last command of the block.
Parenthesis don't have those difficulties, but they will execute their instructions in a subshell, which has multiple other effects :
calling exit will only exit the subshell, not the shell running your script : (exit) is a no-op
updating variables will only apply to the subshell and will have no effect on the values known to your script : a=0;( (( a++ )) ; echo $a) ; echo $a will echo 1 from the subshell, then 0 from the outer shell.
I prefer doing explicit tests on scripts using if so that I can clean up after myself if things go pear shaped. Helps keep the code looking cleaner, too.
if [[ "$x" -ge 1 && "$x" -le 4 ]]; then
if ! /export/home/scripts/script1.sh "$x"; then
err="Error.. something went wrong."
test -t 0 && echo "$err" >&2 # send errors to stderr if on terminal
logger -p local0.critical -t $(hostname -s) "$err" # send to syslog
# You could even add some code here to clean up after script1.sh.
exit 1
fi
fi

Bash loop until a certain command stops failing

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

Counting down in a loop to zero by the number being given

I am trying to write a while loop to determine the number is being given to count down to 0. Also, if there's no argument given, must display "no parameters given.
Now I have it counting down but the last number is not being 0 and as it is counting down it starts with the number 1. I mush use a while loop.
My NEW SCRIPT.
if [ $# -eq "0" ] ;then
echo "No paramters given"
else
echo $#
fi
COUNT=$1
while [ $COUNT -gt 0 ] ;do
echo $COUNT
let COUNT=COUNT-1
done
echo Finished!
This is what outputs for me.
sh countdown.sh 5
1
5
4
3
2
1
Finished!
I need it to reach to 0
#Slizzered has already spotted your problem in a comment:
You need operator -ge (greater than or equal) rather than -gt (greater than) in order to count down to 0.
As for why 1 is printed first: that's simply due to the echo $# statement before the while loop.
If you're using bash, you could also consider simplifying your code with this idiomatic reformulation:
#!/usr/bin/env bash
# Count is passed as the 1st argument.
# Abort with error message, if not given.
count=${1?No parameters given}
# Count down to 0 using a C-style arithmetic expression inside `((...))`.
# Note: Increment the count first so as to simplify the `while` loop.
(( ++count ))
while (( --count >= 0 )); do
echo $count
done
echo 'Finished!'
${1?No parameters given} is an instance of shell parameter expansion
bash shell arithmetic is documented here.
You should also validate the variable before using it in an arithmetic context. Otherwise, a user can construct an argument that will cause the script to run in an infinite loop or hit the recursion limit and segfault.
Also, don't use uppercase variable names since you risk overriding special shell variables and environment variables. And don't use [ in bash; prefer the superior [[ and (( constructs.
#!/usr/bin/env bash
shopt -s extglob # enables extended globs
if (( $# != 1 )); then
printf >&2 'Missing argument\n'
exit 1
elif [[ $1 != +([0-9]) ]]; then
printf >&2 'Not an acceptable number\n'
exit 2
fi
for (( i = $1; i >= 0; i-- )); do
printf '%d\n' "$i"
done
# or if you insist on using while
#i=$1
#while (( i >= 0 )); do
# printf '%d\n' "$((i--))"
#done
Your code is far from being able to run. So, I don't know where to start to explain. Let's take this small script:
#!/bin/sh
die() {
echo $1 >&2
exit 1;
}
test -z "$1" && die "no parameters given"
for i in $(seq $1 -1 0); do
echo "$i"
done
The main part is the routine seq which does what you need: counting from start value to end value (with increment in between). The start value is $1, the parameter to our script, the increment is -1.
The test line tests whether there is a parameter on the command line - if not, the script ends via the subroutine die.
Hth.
There are a number of ways to do this, but the general approach is to loop from the number given to an ending number decrementing the loop count with each iteration. A C-style for loop works as well as anything. You will adjust the sleep value to get the timing you like. You should also validate the required number and type of input your script takes. One such approach would be:
#!/bin/bash
[ -n "$1" ] || {
printf " error: insufficient input. usage: %s number (for countdown)\n" "${0//*\//}"
exit 1
}
[ "$1" -eq "$1" >/dev/null 2>&1 ] || {
printf " error: invalid input. number '%s' is not an integer\n" "$1"
exit 1
}
declare -i cnt=$(($1))
printf "\nLaunch will occur in:\n\n"
for ((i = cnt; i > 0; i--)); do
printf " %2s\n" "$i"
sleep .5
done
printf "\nFinished -- blastoff!\n\n"
exit 0
Output
$ bash ./scr/tmp/stack/countdown.sh 10
Launch will occur in:
10
9
8
7
6
5
4
3
2
1
Finished -- blastoff!
Your Approach
Your approach is fine, but you need to use the value of COUNT $COUNT in your expression. You also should declare -i COUNT=$1 to tell the shell to treat it as an integer:
#!/bin/bash
if [ $# -eq "0" ] ;then
echo "No paramters given"
else
echo -e "\nNumber of arguments: $#\n\n"
fi
declare -i COUNT=$1
while [ $COUNT -gt 0 ] ;do
echo $COUNT
let COUNT=$COUNT-1
done
echo -e "\nFinished!\n"

Shell Script: Exit call not working on using back quotes

I am using exit 1 to stop a shell script execution when error occured.
Shell Script
test() {
mod=$(($1 % 10))
if [ "$mod" = "0" ]
then
echo "$i";
exit 1;
fi
}
for i in `seq 100`
do
val=`test "$i"`
echo "$val"
done
echo "It's still running"
Why it's not working?. How can I stop the shell script execution?
The shell that exit is exiting is the one started by the command substitution, not the shell that starts the command substitution.
Try this:
for i in `seq 100`
do
val=`test "$i"` || exit
echo "$val"
done
echo "It's still running"
You need to explicitly check the exit code of the command substitution (which is passed through by the variable assignment) and call exit again if it is non-zero.
Incidentally, you probably want to use return in a function rather than exit. Let the function caller decide what to do, unless the error is so severe that there is no logical alternative to exiting the shell:
test () {
if (( $1 % 10 == 0 )); then
echo "$i"
return 1
fi
}
The exit command terminates only the (sub)shell in which it is executed.
If you want to terminate the entire script, you have to check the exit status
($?) of the function and react accordingly:
#!/bin/bash
test() {
mod=$(($1 % 10))
if [ "$mod" -eq "0" ]
then
echo "$i";
exit 1;
fi
}
for i in `seq 100`
do
val=`test "$i"`
if [[ $? -eq 1 ]]
then
exit 1;
fi
echo "$val"
done
echo "It's still running"

Resources