Running multiple commands if an || condition is false - bash

So I'm having a bit of a problem when running this command in bash;
echo "$usr_age" | grep "^[0-9]*$" > $null || echo "Please only use numbers in the Age field." || exit 1
When running it, if the "$usr_age" variable has anything that is not a number, it warns the user, but it doesn't exit the script. I also tried changing the last || to && but if I do so it will just exit the script even if the variable is all numbers.
Note: the "$null" variable is just "/dev/null"
Thank you.

Use a grouping operator to combine the echo and the exit. a || b runs b only if a fails, whereas you want to run exit whether or not echo succeeds.
grep -q "^[0-9]*$" <<<"$usr_age" || { echo "Only use numbers in the Age field."; exit 1; }
By the way -- grep, as an external command, is quite slow to start up compared to using a shell builtin. Consider instead bash's built-in regex support:
[[ $usr_age =~ ^[0-9]*$ ]] || { echo "Only use numbers in the Age field."; exit 1; }

Related

combining || and && in bash [duplicate]

This question already has answers here:
How does AND and OR operators work in Bash?
(6 answers)
Closed 2 years ago.
When I execute this in the following line in my shell
echo this || echo that && echo other
The output is:
this
other
Now I don't understand how echo other is executed, because echo that does not return a successful exit status because it is not executed. The && operator only executes the right command when the left command exits successfully.
Does this mean the && operator will execute the righthand command if anything on the left side exits successfully?
You can rewrite that example as:
(echo this || echo that) && echo other
You say echo that does not return a successful exit status, but rather, it doesn't return anything at all - it is not executed. So the expression (echo this || echo that) has a successful result (the return of echo this), which makes echo other be executed.
A good example for this situation is just running echo this || echo that - it has a return value of 0, as such, not executing echo that does not turn it into a failure.
Using both || and && in the same line of code, is not a good practice, while it might work on some cases/situations, that does not mean it will work on all cases.
see http://mywiki.wooledge.org/BashPitfalls#cmd1_.26.26_cmd2_.7C.7C_cmd3
Always use an if statement if you feel like doing a short circuit.
Command grouping is a work around if you really need to do that, using the { }
echo this || { echo that && echo other; }
&& and || have the same order of precedence.
So echo this || echo that occurs first and then && echo other.
The effect of:
command1 || command2
is as follows:
If command1 returns zero: command2 is not executed, and overall return value is 0.
If command1 returns non-zero: command2 is executed, and overall return value is that of command2.
It is somewhat analogous to short-circuit evaluation of an "or" operator in various programming languages (e.g. a || b in C) except that a "true" value is a zero (i.e. successful) exit status rather than a non-zero value of an expression.
Given that the echo statements return 0, this means that the effect of
echo this || echo that
is to execute echo this but not echo that, and to have an overall exit status of 0.
Given this, and that the || and && are treated with equal precedence (so are evaluated from left to right), the command sequence:
echo this || echo that && echo other
will also cause echo other to be executed (because foo && bar will execute bar if foo returned 0).
So, command1 || command2 && command3 is valid, but if should not be read as meaning:
(a) Run command1. Then if command1 failed then run command2 but if command1 succeeded then run command3.
Instead, it should be read as:
(b) Run command1. If command1 failed then run command2. If either (command1 succeeded) or (command1 failed but command2 succeeded), then run command3.
(where "succeeded" means exited with zero status).
If the intention is as described in (a), then this should instead be implemented using:
if ! command1 ; then command2 ; else command3 ; fi
or
if command1 ; then command3 ; else command2 ; fi

A shell command to run 'x' number of times while a command keeps failing

I'm trying to run a shell command (currently either sh or bash) which connects to a database. Because the database is still 'warming up', the command fails.
So I was trying to do a loop (let's say ... 100 tries) and each time the command fails, wait 1 second and retry.
If there's an error, this is the start of the string that is dumped to stdout: Sqlcmd: Error: <snipped>
Here's what I've been trying:
for i in $(seq 1 100)
do
X='/opt/mssql-tools/bin/sqlcmd -E -S localhost -Q "<some sql statement> "'
if [[ $X == Sqlcmd: Error:* ]]
echo "."
then
break
fi
done
It's not working as I figure out the string comparison stuff with shell/bash ... but was more making sure if I was on the right track etc.
You could try something like:
while true ; do
if Sqlcmd xxx xxx xxx ; then break ; fi
# or:
Sqlcmd xx xxx xxx && break
sleep 1
done
You can also add a counter:
for ((n=100;n>0;n--)) ; do
Sqlcmd xxx xxx xxx
if [[ $? == 0 ]] ; then
break
fi
sleep 1
done
[[ $n == 0 ]] && echo Timeout && exit 1
I'm showing two different ways of testing the return value here, but the first one is preferred (if cmd ; then ... ; fi).
$? is the return value from the last command, which is 0 when it completed successfully. If it returns 0 even in case of error (which can happen for malformed programs), you can test the output with grep:
Sqlcmd xxx xxx 2>&1 | grep <error pattern> > /dev/null
if [[ $? != 0 ]] ; then break ; fi
Here we test $? != 0 because grep will return 0 when the error pattern has been found.
If you want to get the output result into a variable, run the command with X=$(Sqlcmd xxx xxx). Then you can use bash string comparison:
X=$(Sqlcmd xxx xxx)
if [[ "$X" =~ .*error.* ]] ; then
<handle error here>
fi
Note bash can match regexp, which makes it really handy at checking error types.
You can also use a switch/case construct:
case "$X" in
*Error:*) echo " Error detected " ;;
*) break ;;
esac
(Note the double ;;)
I ended up learning all the clues from #matthieu's post. This is what I ended up doing:
for i in $(seq 1 30)
do
/opt/mssql-tools/bin/sqlcmd -U sa -P <snip> -S localhost -Q "USE Master" 2>&1
if [[ $? != 0 ]]
then
# Failed
echo "."
sleep 1s
else
# worked!
break
fi
done
breakdown for those learning (like me)
execute a sql query using the sqlcmd command. Any errors via stderr (that's the 2 in 2>&1) will be redirected to the console stdout (that's the $1). REF: 2>&1 shell idiom.
the result status code is sent to $? (REF: what is bash dollar questionmark ?)
if it failed(any value that is NOT a zero), then sleep 1 sec and we'll try. Only re-try 30 times, though.
if we worked (value is zero), then stop trying and go on....
So there we have it! shell/bash shell 101 stuff. good luck!

SHELL - AND operation within IF statement

Assuming thoses functions :
return_0() {
return 0
}
return_1() {
return 1
}
Then the following code :
if return_0; then
echo "we're in" # this will be displayed
fi
if return_1; then
echo "we aren't" # this won't be displayed
fi
if return_0 -a return_1; then
echo "and here we're in again" # will be displayed - Why ?
fi
Why I am getting into the last ifstatement ?
Aren't we supposed to be out of the condition with those 0 and 1 ?
-a is one of the options of the test command (which is also implemented by [ and [[). So you can't just use -a by itself. You probably want to use &&, which is a control operator token for an AND list.
if return_0 && return_1; then ...
You can use -a to tell test to "and" two different test expressions, like
if test -r /file -a -x /file; then
echo 'file is readable and executable'
fi
But this is equivalent to
if [ -r /file -a -x /file ]; then ...
which may be more readable because the brackets make the test part of the expression clearer.
See the Bash Reference Manual for further information on...
&&, see lists
if statements and the various test commands and keywords, see conditional constructs
When you execute
if return_0 -a return_1; then
echo "and here we're in again" # will be displayed - Why ?
fi
You execute the line return_0 -a return_1. This actually means that you pass -a and return_1 as arguments to return_0. If you want to have an and operation, you should make use of the && syntax.
if return_0 && return_1; then
echo "and here we're in again" # will be displayed - Why ?
fi
The useful information to understand this is:
AND and OR lists are sequences of one of more pipelines separated by the && and || control operators, respectively. AND and OR lists are executed with left associativity. An AND list has the form
command1 && command2
command2 is executed if, and only if, command1 returns an exit status of zero.
An OR list has the form
command1 || command2
command2 is executed if and only if command1 returns a non-zero exit status. The return status of AND and OR lists is the exit status of the last command executed in the list.

Comparing variable to grep statement

I am trying to make the statement pass as true, if and only if the user input through stdin is within the guidelines of
[a-z_][a-z0-9_-]*
so if the user were to input a % or $ in their argument passed then it would return false. How could i go about that?
This reads from stdin and reports on true or false status (and exits if it is false):
grep -q '^[a-z_][a-z0-9_-]*$' && echo true || { echo false; exit 1 ; }
If grep finds a match to your regex, it sets its exit code to true (0) in which case the "and" (&&) clause is executed and "true" is returned. If grep fails to find a match, the "or" (||) clause is executed and "false" is returned. The -q flag to grep tells grep to be quiet.
If one were to use this in a script, one would probably want to capture the user input into a shell variable and then test it. That might look like:
read -p "Enter a name: " var
echo "$var" | grep -q '^[a-z_][a-z0-9_-]*$' && echo true || { echo false; exit 1 ; }
To make it easy to add more statements to execute if the result is "true", we can write it out in a longer form with a place marked to add more statements:
read -p "Enter a name: " var
if echo "$var" | grep -q '^[a-z_][a-z0-9_-]*$'
then
echo true
# other statements to execute if true go here
else
echo false
exit 1
fi
The answer depends on what you mean by return. If by return you mean literal false, well, you have a small problem: UNIX scripts can only return an integer in the range 0-255. Normally in UNIX when a program returns it returns an exit status that indicates (among other things) the success or failure of the program. So you could just write:
grep -q ''^[a-z_][a-z0-9_-]*' || exit
At the top of your script. Since a shell script exits with the last value of $? anyway, that would only exit if the grep fails, and would exit with the same exit code as the grep statement itself. Technically this would mean returning 1, but in UNIX that would be akin to false. If the grep succeeds, the script would continue, not returning anything until completion or another error condition occurs.
If what you really want is to print the string "false", then see John1024's answer.

retrieve error code from a command launched within a bash script

Ok I'm kind of new to bash scripting [the advanced stuff] and I need a little help. I don't even know exactly how to phrase this so I'll just explain what I am doing and what I need to know about it.
in my script I run a ./configure and I need to be able to catch if there was an error in the configure and react accordingly within the bash script.
the code is:
function dobuild {
echo -e "\e[1;35;40mExecuting Bootstrap and Configure\e[0m"
cd /devel/xbmc
if [ $Debug = "1" ];
then
#either outputs to screen or nulls output
./bootstrap >/dev/null
/usr/bin/auto-apt run ./configure --prefix=/usr --enable-gl --enable-vdpau --enable-crystalhd --enable-rtmp --enable-libbluray >/dev/null
else
./bootstrap
/usr/bin/auto-apt run ./configure --prefix=/usr --enable-gl --enable-vdpau --enable-crystalhd --enable-rtmp --enable-libbluray
fi
}
and say the configure returns an error 1 or 2 how do I trap that and act on it?
TIA
After the execution of every shell command it's return value, a number between 0 and 255, is available in the shell variable ?. You can get the value of this variable by prefixing it with the $ operator.
You have to be a little careful with ?, because it is reset by every command, even a test. For example:
some_command
if (( $? != 0 ))
then
echo "Error detected! $?" >&2
fi
Gives: Error detected! 0 because ? was reset by the test condition. It is probably best to store ? in another variable if you are going to use it later, which includes doing more than one test on it.
To do a numeric test in bash use the (( ... )) numeric test construct:
some_command
result=$?
if (( $result == 0 ))
then
echo "it worked!"
elif (( $result == 1 ))
then
echo "Error 1 detected!" >&2
elif (( $result == 2 ))
then
echo "Error 2 detected!" >&2
else
echo "Some other error was detected: $result" >&2
fi
Alternatively use a case statement.
After the execution of a command, the returned value is stored in the shell variable $?. So you would have to match that with the return values of success and failure
if [ $? == 1 ]
then
#do something
else
#do something else
fi
The other answers about $? are great (though be careful about assuming values other than 0 and not-0 - different commands. or different versions of the same command may fail with different values), but if you just need to act on success or failure immediately, you can simplify things:
if command ; then
# success code here
else
# failure code here
fi
Or if you only want to act on failure, here's a hack for older shells (the colon is a null command but it satisfies the then clause):
if command ; then :
else
# failure code here
fi
But in modern shells like bash this is better:
if ! command ; then # use the ! (not) operator
# failure code here
fi
And, if you only need to do simple things, you can use the "short circuit" operators:
command1 && command2_if_command1_succeeds
command1 || command2_if_command1_fails
Those only work for single commands, stringing more && and || on them doesn't do what you might think in most cases so most people avoid that. However, you can do multiple commands if you group them:
command1 && { command2; command3; command4; }
That can get hard to read so it's best to keep it simple if you use it all:
command1 || { echo "Error, command1 failed!" >&2; exit 1; }

Resources