Logic Operator Evaluation in Bash [duplicate] - bash

This question already has answers here:
why shell uses 0 as success?
(2 answers)
Closed 1 year ago.
Logic expressions seem to evaluate in a backwards manner in Bash as opposed to a programming language like C.
For example, echo will return 0 upon success so I would expect:
echo hi || echo back4more
would print hi and then back4more to stdout since 0 is returned first and the second statement needs to be checked to determine the truth of the whole logical OR expression. However, it only prints hi.
Similarly, this can be seen from:
echo hi && echo back4more
Which I would expect to print hi and that's it since echo first returns 0 and thus 0 && (logic) would always be false, so no need to evaluate (logic). But what really happens is hi and back4more get printed.
Why is this so?

Success is not truth.
Zero exit status means success. Non-zero exit status means failure.
Zero arithmetic value is logically boolean false. Non-zero arithmetic value is logically boolean true.
Why is this so?
The || checks for failure. Because echo exits with 0 exit status, it means success, the right side of || is not executed.

Related

Exit Code when called with No Arguments

I have a script that requires at least one argument. If the arguments are incorrect I will obviously return a non-zero exit code.
I have decided to print the usage instructions when called with no arguments, but I'm uncertain of the exit code. Should it be
0 (success)
one of the values [1-125] (error)
An argument could be made either way. Is some option recommended?
It depends on whether you want the callers of your script to know programatically why it failed.
If, for example, it will only ever be run by a human, you could just return 1 and rely on the error text to inform them.
You could even return zero regardless if you only ever want a human to easily know (via the output) if it failed but I would consider this very bad form - Microsoft have made this mistake in the past with some of their commands not setting %errorlevel%, making it very difficult for a script to figure out whether it's worked or not.
Even if you decide not to indicate why it failed, you should at least indicate that it failed.
If you want a program to easily figure out why it failed, return a different code for each discernible error type.
Any Unix command when it returns control to its parent process returns a code, number between 0 and 255.
Success is traditionally represented with exit 0; failure is normally indicated with a non-zero exit-code. This value can indicate different reasons for failure.
So what you have also is another case of unsuccessful completion of your program which should be treated as returning a proper error code and not 0.
Also note that traditional Unix system calls don't signal an error code for empty arguments, they have to be caught explicitly. There are ones for argument list too long (E2BIG) and invalid arguments (EINVAL).
This is what I decided on using, based mostly on Inians answer and the reserved exit codes presented here.
I decided on somewhat reusing the exit codes defined in /usr/include/asm-generic/errno-base.h1. There was nothing suitable for "too little arguments", so I picked the first one from the 64-113 range.
# Error codes
E2BIG=7
EINVAL=22
E2SMALL=64
NAME="my-script"
USAGE=$(cat << END
the help text
RETURN CODES
$NAME has the following return codes:
0 success
$E2BIG Argument list too long
$EINVAL Invalid argument
$E2SMALL Argument list too short
END
)
# This script requires exactly one argument. If less are provided it prints
# the USAGE instructions
if [ ${#} -lt 1 ] ; then
printf '%s\n' "${USAGE}"
exit $E2SMALL
fi
if [ "${1}" = "-h" ] || [ "${1}" = "--help" ] ; then
printf '%s\n' "${USAGE}"
exit 0
fi
if [ ${#} -gt 1 ] ; then
printf '%s\n' "${USAGE}"
exit $E2BIG
fi
It's a bit verbose, but at least there's a properly defined error code if the script is called improperly.
1 This may be debatable, but since the codes were already defined...

Why AND && logic is used in chained commands in most shells, not OR || logic

I am curious about the logic behind && in continuous commands execution in shell.
Lets see an example: command_a && command_b && command_c
In shell, this means that once a command fails, the consequent commands will not execute and the chain should stop.
If we replace each command by its exit code, the chain becomes, for example this expression 0 && 1 && 0. (command_a succeed, command_b failed, command_c succeed)
If we try evaluating this expression, the evaluation should stop right after the first command, 0 value.
If && logic is replaced by ||, the expression would be more fit to the meaning of original chained command.
0 || 1 || 0.
Expression's evaluation stops after command_b execution
There's a difference between the semantics of a successful command and the representation of success via the numeric value of the exit code. If you consider the abstract "is a command successful", && makes more sense, since the "success" abstract boolean is true. That's why && is used. You need A to run, AND you need B to run.
But, due to the fact that there's usually only one status for success, but many different types of errors, the value 0 has been defined long ago to be used for exit status to indicate success.
So, the exit status of a command can simply not replace the command itself in such an expression. The semantics and the representation are just different.
Check out this post.
"The right side of && will only be evaluated if the exit status of the left side is zero. || is the opposite: it will evaluate the right side only if the left side exit status is nonzero..."
$ false && echo howdy!
$ true && echo howdy!
howdy!
$ true || echo howdy!
$ false || echo howdy!
howdy!
I have seen || used in some shell script too. I think it depends on the occasions. You may use command-a && command-b when you want command-b to be executed only after command-a success.
Likewise, you may use command-a || command-b to deal with the situation when command-a fails.
Update: After reading your question three times I now understand what puzzles you: That 0 represents success, and/or (sic) that logical operators treat it as true. Yes, that can be confusing, coming from C. The other Peter's answer explains that well. I let my original answer stand anyway because it is not "wrong".
It is what logic dictates if commands are impossible to perform or don't make sense unless their predecessors succeeded. That happens quite often: "Retrieve a HTML document from the web, verify the time stamp, parse a value out of it, and write that value as an updated data somewhere." Each step depends on the success of all preceding steps.
If, instead, you have something "retrieve a document from the web, or from the cache, or from disk, or take a default here document, in this order of preference", then the appropriate way to write that is indeed with logical ORs. This happens but is less common.
ORing is a common idiom though for error handling, because subsequent commands are exactly performed if the pervious failed. Consider Perl's idiomatic cmd() || die();. (You have more lives. Phew.)

If ack finds results do this; else do that [duplicate]

This question already has answers here:
How to use double or single brackets, parentheses, curly braces
(9 answers)
Closed 8 years ago.
I'm trying to write out a script that will automate one of my more tedious tasks. I've pretty much got everything down and can get the individual components of code to do what I want them to, but I'm running into trouble trying to get the if statement to work. Here's what I'm trying to do:
if [ ack --ignore-case 'foo' ]; then
Do this action
else
Do that action
fi
As of now it always does the else action, even if the ack parameter is true.
Thanks!
Just drop the square brackets
if ack --ignore-case 'foo' ; then
Do this action
else
Do that action
fi
You don't want to check that the string ack --ignore-case 'foo' is non-empty, you want to check ack's exit status.
You should have recieved a warning telling you so:
-bash: [: --ignore-spaces: binary operator expected
Remove the [ - you want to check the exit status of ack:
if ack --ignore-case 'foo'; then
ack returns a 0 (success) exit status if a match is encountered.
Although it may look like a syntactical construct, [ is in fact a function (synonymous with test), which returns 0 if the test passes. if is looking at the return code of this function. In this case, ack gives you the return code that you need instead, so you shouldn't also be using [.

script in bash not understood using terminal in Ubuntu 12.04

Can anyone tells me what does this script means found in a .sh file:
[ ! -n "$T_R" ] && echo "Message Appear" && exit 1;
Edit: Correcting for misinformation pointed out by tripleee
The brackets [ ]
are an alias for 'test', which tests whether a condition is met. Not to complicate matters, but do note that this is discrete from the the bash shell keyword [[ ]] (Thanks, tripleee for clearing that up!). See This post for further details. These days, most people seem to use the latter due to its more robust feature set.
Between the brackets, the script is testing to determine whether the variable "$T_R" is an empty string.
The -n operator returns true if the length of the string passed to it as an argument is non-empty.
The ! inverts the case (the test succeeds if the result is not
true). So in this case, test suceeds (returns 0) if the length of
the string variable "$T_R" is **not non-zero ** (i.e. if the
variable is an empty-string, or is non-existant).
The double-ampersand, && operator means only execute the subsequent code in the event of success, so the message "Message Appear" will only be echoed in the event the test succeeds (again, if "$T_R" is empty or unset).
Finally, the && exit 1 says to exit returning status 1 after successfully echoing the Message Appear message.
The bash and test man pages are extremely helpful on all of these topics and should be consulted for further details.
The chained && is a common short-circuit idiom.
Instead of writing
if true; then
if true; then
echo moo
fi
fi
you can abbreviate to just true && true && echo moo.
echo will usually succeed so true && echo moo && exit 1 will execute both the echo and the exit if true succeeds (which obviously it always will).
(There are probably extreme corner cases where echo could fail, but if that happens, you are toast anyways so I don't think it makes sense to try to guard against those.)
The [ is an alias for test which is a general comparison helper for shell scripts (in Bash, it's even a built-in). test -n checks whether a string is non-empty.
! is the general negation operator, so it inverts the test to checking for an empty string.
(This is slightly unidiomatic, because there is a separate test -z "$T_R" which checks specifically for the string being empty.)

How to evaluate a boolean variable in an if block in bash? [duplicate]

This question already has answers here:
How can I declare and use Boolean variables in a shell script?
(25 answers)
Checking the success of a command in a bash `if [ .. ]` statement
(1 answer)
Closed 5 years ago.
I have defined the following variable:
myVar=true
now I'd like to run something along the lines of this:
if [ myVar ]
then
echo "true"
else
echo "false"
fi
The above code does work, but if I try to set
myVar=false
it will still output true.
What might be the problem?
edit: I know I can do something of the form
if [ "$myVar" = "true" ]; then ...
but it is kinda awkward.
Thanks
bash doesn't know boolean variables, nor does test (which is what gets called when you use [).
A solution would be:
if $myVar ; then ... ; fi
because true and false are commands that return 0 or 1 respectively which is what if expects.
Note that the values are "swapped". The command after if must return 0 on success while 0 means "false" in most programming languages.
SECURITY WARNING: This works because BASH expands the variable, then tries to execute the result as a command! Make sure the variable can't contain malicious code like rm -rf /
Note that the if $myVar; then ... ;fi construct has a security problem you might want to avoid with
case $myvar in
(true) echo "is true";;
(false) echo "is false";;
(rm -rf*) echo "I just dodged a bullet";;
esac
You might also want to rethink why if [ "$myvar" = "true" ] appears awkward to you. It's a shell string comparison that beats possibly forking a process just to obtain an exit status. A fork is a heavy and expensive operation, while a string comparison is dead cheap. Think a few CPU cycles versus several thousand. My case solution is also handled without forks.

Resources