bash, logic of: a && b && c - bash

One thing I don't get with this operators is when I use two of them in sequence.
What I mean by that:
% true && echo "problem"
problem
% echo $?
0
So far, so good. true returns "error" (exit status 1) and echo "problem" returns 0, so logical AND operation result must be 0.
% true && echo "problem" || echo "exit"
problem
OK, that's a surprise: since true && echo "problem" results in 0, || should also evaluate echo "exit", since after all right-hand operand of || might be true and so the result of this logical OR might be true.
Now:
% true && echo "problem" && echo "exit"
problem
exit
This is also surprising: after all since true && echo "problem" returns zero, the lazy && operator should not evaluate echo "exit" since the result of logical AND must be zero anyway.
Why is the behavior of last two examples opposite to what I intuitively expect?
P.S. This is opposite of Python behavior:
% python
>>> def pp():
... print "problem"
...
>>> def pe():
... print "exit"
>>> True and pp() and pe()
problem
>>> True and pp() or pe()
problem
exit

You're misunderstanding success/failure statuses and also what && and || mean in this context in bash.
An exit status of success is 0, while failure is non-zero so when you say true returns "error" (exit status 1), no it doesn't. Also, nothing in shell "returns" anything. Scripts and functions produce output and have an exit status - using the word "return" leads to confusion over which of those 2 separate things you mean. For example if we define this function:
foo() {
echo "hello"
return 7
}
and then use it to populate a variable:
var=$(foo)
$ echo "$var"
hello
did foo() "return" hello or did foo() "return" 7? The best answer is no, it didn't "return" either - it output hello and exited with status 7.
Although there's a poorly-named "return" keyword there what that REALLY is producing is an exit status for the function, the same as if you had a shell script that was just:
echo "hello"
exit 7
and you can see that if you test it ($? always holds the exit status of the most recently run command):
foo() {
echo "hello"
return 7
}
$ foo
hello
$ echo "$?"
7
I assume the shell creators chose "return" for the function keyword because "exit" already meant "exit from the running process" but IMHO that made things confusing, though I don't have a better suggestion and even if I did that ship has sailed long ago. If you read return 7 as set the exit status to 7 then return from the function without assuming the function is actually "returning" anything then you'd be right.
Note also that the function is outputting "hello" - that's also not a "return" but if you use it as var=$(foo) then var ends up containing hello so then some people incorrectly refer to that as a "return" too since in other languages like C if you wrote var=foo():
char *foo() {
return "hello"
}
var=foo()
then var would contain the argument that was given to return in the function but that is just not the same semantics as shell where, unlike the similar C code above, var=$(foo) sets var to the output from foo(), not the "return" (actually exit status) from foo().
So - the function above doesn't actually "return" anything, it outputs hello and exits with status 7.
So here's what your command line true && echo "problem" || echo "exit" actually does:
true = output nothing and exit with status 0 (success)
echo "problem" - output problem and exit with status 0 (success)
echo "exit" - output exit and exit with status 0 (success)
Now, what do && and || mean? What they really are is shorthand for if statements:
&& foo = if the previously run command exited success then execute foo
|| foo = if the previously run command exited failure then execute foo
So a command line like:
cmdA && cmdB || cmdC
in terms of success/fail status should be read as:
cmdA
ret=$?
if (( ret == 0 )); then
cmdB
ret=$?
fi
if (( ret != 0 )); then
cmdC
ret=$?
fi
( exit "$ret" )
We need the ret temp variable because the if itself has an exit status that'd overwrite $?. So cmdC will get called if cmdA exits with a failure status, but it'll also get called if cmdA succeeded and then cmdB exited with a failure status. At the end of cmdA && cmdB || cmdC the exit status as stored in $? will simply be the exit status of whichever command ran last, it will not, for example, be the product of boolean arithmetic on all of the exit statuses of all the commands that ran as apparently suggested in the question might be the case.
Note also that what that should NOT be read as is what you may intuitively have expected if you thought of && ... || ... as a ternary expression, which trips many people up, especially since that || is typically an error leg whose incorrect placement may escape your code inspectors/testers notice:
cmdA
if (( $? == 0 )); then
cmdB
else
cmdC
fi
Given the above, here's what your command lines actually mean:
true && echo "problem" || echo "exit"
true
ret=$?
if (( ret == 0 )); then
echo "problem"
ret=$?
fi
if (( ret != 0 )); then
echo "exit"
ret=$?
fi
( exit "$ret" )
true && echo "problem" && echo "exit"
true
ret=$?
if (( ret == 0 )); then
echo "problem"
ret=$?
fi
if (( ret == 0 )); then
echo "exit"
ret=$?
fi
( exit "$ret" )
and if what you WANTED to have happen instead of "2" above was actually:
true
if (( $? == 0 )); then
echo "problem"
else
echo "exit"
fi
then you should write that code or similar instead of using &&s and ||s (e.g. as 1 line you could write if true; then echo "problem"; else echo "exit"; fi) so you don't get unexpected output if you reach the echo "problem" leg and it fails for some reason thereby causing you to afterwards fall into the echo "exit" leg (unlikely with just echo but very possible with other commands).

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

How to exit a programm with an error message and error code in bash?

I'd like to exit a script when an operation could not be executed.
non-existing || exit 1;
works fine and exits, while
ls || exit 1;
does not exit. Now, I'd also like to add an error message before exiting the program.
non-existing || echo "Having trouble" && exit 1;
exits as expected, but
ls || echo "Having trouble" && exit 1;
also exits, although the echo command (and thus the exit) should not be executed.
I first thought that using brackets might help:
ls || (echo "Having trouble" && exit 1;)
Does not exit. But when I invoke a non-existing program, the exit-command only exits from the sub-shell that I opened using the brackets:
non-existing || (echo "Having trouble" && exit 1;)
echo "Still executed."
How can I exit a program with an error message in bash?
Why does ls || echo "Having trouble" && exit 1; exit?
You can use Use { ...; } instead of (...) to avoid the sub-shell and the problem that causes and to get the result that you want.
Note that a space after the opening { and a semicolon/etc. and a space before the closing } are required for this (where they are optional with ()).
As to why ls || echo && exit doesn't do what you expect the answer is because the || and && operators have the same precedence and are left-associative (see the POSIX spec).
So when the shell sees
ls || echo "Having trouble" && exit 1;
you think it is
ls || { echo "Having trouble" && exit 1; }
but the shell parses it as
{ ls || echo "Having trouble"; } && exit 1;
Or, as the spec puts it:
The operators "&&" and "||" shall have equal precedence and shall be evaluated with left associativity. For example, both of the following commands write solely bar to standard output:
false && echo foo || echo bar
true || echo foo && echo bar

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; }

Bash ignore error and get return code

I am using set -e to abort on errors.
But for particular one function I want to ignore error and on error I want return code of the function.
Example:
do_work || true
if [ $? -ne 0 ]
then
echo "Error"
fi
But it is not work return code is always true due || true
How to get return code on do_work on error ?
do_work || {
status=$?
echo "Error"
}
Several of the answers given here are not correct, because they result in a test against a variable that will be un-defined if do_work succeeds.
We need to cover the successful case as well, so the answer is:
set -eu
do_work && status=0 || status=1
The poster's question is a little ambiguous because it says in the text "on error I want return code" but then the code implies "I always want the return code"
To illustrate, here is problematic code:
set -e
do_work() {
return 0
}
status=123
do_work || status=$?
echo $status
In this code the value printed is 123, and not 0 as we might hope for.
You could use a subshell shortcut:
( set +e; do_work )
if [ $? -ne 0 ]
then
echo "Error"
fi
Hope this helps =)
One way is to use a pipe, -e only looks at the right-most result of a pipe:
set -e
do_work | true
retn=${PIPESTATUS[0]}
if (( $retn != 0 ))
then
echo "Error $retn"
fi
echo Ending
I wrote a simple do_work which just did exit 42 and got the following output:
Error 42
Ending
The PIPESTATUS array is maintained by Bash, with each element giving the return code of each part of the pipeline. We need to capture it at once (hence $retn) since it is overwritten at each command.
Of course this might be problematic if your do_work includes a pipe itself.
do_work || status=$?
if [ $status -ne 0 ]
then
echo "Oh no - Fail whale $status has arrived"
fi

Returning a boolean from a Bash function

I want to write a bash function that check if a file has certain properties and returns true or false. Then I can use it in my scripts in the "if". But what should I return?
function myfun(){ ... return 0; else return 1; fi;}
then I use it like this:
if myfun filename.txt; then ...
of course this doesn't work. How can this be accomplished?
Use 0 for true and 1 for false.
Sample:
#!/bin/bash
isdirectory() {
if [ -d "$1" ]
then
# 0 = true
return 0
else
# 1 = false
return 1
fi
}
if isdirectory $1; then echo "is directory"; else echo "nopes"; fi
Edit
From #amichair's comment, these are also possible
isdirectory() {
if [ -d "$1" ]
then
true
else
false
fi
}
isdirectory() {
[ -d "$1" ]
}
Why you should care what I say in spite of there being a 350+ upvote answer
It's not that 0 = true and 1 = false. It is: zero means no failure (success) and non-zero means failure (of type N).
While the selected answer is technically "true" please do not put return 1** in your code for false. It will have several unfortunate side effects.
Experienced developers will spot you as an amateur (for the reason below).
Experienced developers don't do this (for all the reasons below).
It is error prone.
Even experienced developers can mistake 0 and 1 as false and true respectively (for the reason above).
It requires (or will encourage) extraneous and ridiculous comments.
It's actually less helpful than implicit return statuses.
Learn some bash
The bash manual says (emphasis mine)
return [n]
Cause a shell function to stop executing and return the value n to its caller. If n is not supplied, the return value is the exit status of the last command executed in the function.
Therefore, we don't have to EVER use 0 and 1 to indicate True and False. The fact that they do so is essentially trivial knowledge useful only for debugging code, interview questions, and blowing the minds of newbies.
The bash manual also says
otherwise the function’s return status is the exit status of the last command executed
The bash manual also says
($?) Expands to the exit status of the most recently executed foreground pipeline.
Whoa, wait. Pipeline? Let's turn to the bash manual one more time.
A pipeline is a sequence of one or more commands separated by one of the control operators ‘|’ or ‘|&’.
Yes. They said 1 command is a pipeline. Therefore, all 3 of those quotes are saying the same thing.
$? tells you what happened last.
It bubbles up.
My answer
While #Kambus demonstrated that with such a simple function, no return is needed at all. I think it
was unrealistically simple compared to the needs of most people who will read this.
Why return?
If a function is going to return its last command's exit status, why use return at all? Because it causes a function to stop executing.
Stop execution under multiple conditions
01 function i_should(){
02 uname="$(uname -a)"
03
04 [[ "$uname" =~ Darwin ]] && return
05
06 if [[ "$uname" =~ Ubuntu ]]; then
07 release="$(lsb_release -a)"
08 [[ "$release" =~ LTS ]]
09 return
10 fi
11
12 false
13 }
14
15 function do_it(){
16 echo "Hello, old friend."
17 }
18
19 if i_should; then
20 do_it
21 fi
What we have here is...
Line 04 is an explicit[-ish] return true because the RHS of && only gets executed if the LHS was true
Line 09 returns either true or false matching the status of line 08
Line 13 returns false because of line 12
(Yes, this can be golfed down, but the entire example is contrived.)
Another common pattern
# Instead of doing this...
some_command
if [[ $? -eq 1 ]]; then
echo "some_command failed"
fi
# Do this...
some_command
status=$?
if ! (exit $status); then
echo "some_command failed"
fi
Notice how setting a status variable demystifies the meaning of $?. (Of course you know what $? means, but someone less knowledgeable than you will have to Google it some day. Unless your code is doing high frequency trading, show some love, set the variable.) But the real take-away is that "if not exit status" or conversely "if exit status" can be read out loud and explain their meaning. However, that last one may be a bit too ambitious because seeing the word exit might make you think it is exiting the script, when in reality it is exiting the (...) command group subshell.
** If you absolutely insist on using return 1 for false, I suggest you at least use return 255 instead. This will cause your future self, or any other developer who must maintain your code to question "why is that 255?" Then they will at least be paying attention and have a better chance of avoiding a mistake.
myfun(){
[ -d "$1" ]
}
if myfun "path"; then
echo yes
fi
# or
myfun "path" && echo yes
Be careful when checking directory only with option -d !
if variable $1 is empty the check will still be successfull. To be sure, check also that the variable is not empty.
#! /bin/bash
is_directory(){
if [[ -d $1 ]] && [[ -n $1 ]] ; then
return 0
else
return 1
fi
}
#Test
if is_directory $1 ; then
echo "Directory exist"
else
echo "Directory does not exist!"
fi
Use the true or false commands immediately before your return, then return with no parameters. The return will automatically use the value of your last command.
Providing arguments to return is inconsistent, type specific and prone to error if you are not using 1 or 0. And as previous comments have stated, using 1 or 0 here is not the right way to approach this function.
#!/bin/bash
function test_for_cat {
if [ "$1" = "cat" ];
then
true
return
else
false
return
fi
}
for i in cat hat;
do
echo "${i}:"
if test_for_cat "${i}";
then
echo "- True"
else
echo "- False"
fi
done
Output:
$ bash bash_return.sh
cat:
- True
hat:
- False
For code readability reasons I believe returning true/false should:
be on one line
be one command
be easy to remember
mention the keyword return followed by another keyword (true or false)
My solution is return $(true) or return $(false) as shown:
is_directory()
{
if [ -d "${1}" ]; then
return $(true)
else
return $(false)
fi
}
I encountered a point (not explictly yet mentioned?) which I was stumbling over. That is, not how to return the boolean, but rather how to correctly evaluate it!
I was trying to say if [ myfunc ]; then ..., but that's simply wrong. You must not use the brackets! if myfunc; then ... is the way to do it.
As at #Bruno and others reiterated, true and false are commands, not values! That's very important to understanding booleans in shell scripts.
In this post, I explained and demoed using boolean variables: https://stackoverflow.com/a/55174008/3220983 . I strongly suggest checking that out, because it's so closely related.
Here, I'll provide some examples of returning and evaluating booleans from functions:
This:
test(){ false; }
if test; then echo "it is"; fi
Produces no echo output. (i.e. false returns false)
test(){ true; }
if test; then echo "it is"; fi
Produces:
it is
(i.e. true returns true)
And
test(){ x=1; }
if test; then echo "it is"; fi
Produces:
it is
Because 0 (i.e. true) was returned implicitly.
Now, this is what was screwing me up...
test(){ true; }
if [ test ]; then echo "it is"; fi
Produces:
it is
AND
test(){ false; }
if [ test ]; then echo "it is"; fi
ALSO produces:
it is
Using the brackets here produced a false positive! (I infer the "outer" command result is 0.)
The major take away from my post is: don't use brackets to evaluate a boolean function (or variable) like you would for a typical equality check e.g. if [ x -eq 1 ]; then... !
Following up on #Bruno Bronosky and #mrteatime, I offer the suggestion that you just write your boolean return "backwards". This is what I mean:
foo()
{
if [ "$1" == "bar" ]; then
true; return
else
false; return
fi;
}
That eliminates the ugly two line requirement for every return statement.
I found the shortest form to test the function output is simply
do_something() {
[[ -e $1 ]] # e.g. test file exists
}
do_something "myfile.txt" || { echo "File doesn't exist!"; exit 1; }
It might work if you rewrite this
function myfun(){ ... return 0; else return 1; fi;} as this function myfun(){ ... return; else false; fi;}. That is if false is the last instruction in the function you get false result for whole function but return interrupts function with true result anyway. I believe it's true for my bash interpreter at least.
Here are some good examples with test cases to experiment with.
For basic functions, call true/false in the last line of the method, or use true; return to exit early.
function is_true() { true; }
if is_true; then echo 'true is true'; fi
if ! is_true; then exit; else echo '! true is ! true'; fi
function is_false() { false; }
if ! is_false; then echo 'false is false'; fi
if is_false; then exit; else echo '! false is ! false'; fi
If you can not return immediately, store the return value in variable. Use (true; echo $?) while setting the variable. This also works for nested functions (see next section).
function from_var() {
local input=$1
local my_var
if ((input == 1)); then
my_var=$(true; echo $?)
else
my_var=$(false; echo $?)
fi
echo 'ignore this line'
(exit $my_var)
}
if from_var 1; then echo "return true is true"; else exit; fi
if from_var 0; then exit; else echo "return false is false"; fi
If you need to store the result of the function call that returns a bool, use the same technique, but pipe the output of the call to /dev/null or the result may also contain strings from echo or other commands. Notice the (exit $rval) in the if statement lets you correctly interpret the return value. (Other methods like if (($rval)) or if [ $rval ] will not work as expected.
# Return a truthy result
rval=$(from_var 1 >/dev/null; echo $?)
if (exit $rval); then echo "return true as variable is true"; else exit; fi
# Return a falsy result
rval=$(from_var 0 >/dev/null; echo $?)
if (exit $rval); then exit; else echo "return false as variable is false"; fi
The full output of this code is:
true is true
! true is ! true
false is false
! false is ! false
ignore this line
return true is true
ignore this line
return false is false
return true as variable is true
return false as variable is false
If you don't want to suppress the output from within the function using > /dev/null, then rewrite to call the function first.
from_var 0; rval="$?"

Resources