Bash ternary operation gives same output on either Boolean condition - bash

So in my bash script, I output status report to terminal as well as write it to the log file. I wanted to use a bash ternary operator that will output to terminal as well as write a log file if variable LOG_TO_TERMINAL is true, and if that is set to false, just write to a log file without outputting status to the terminal.
My sample code looks like this:
[[ $LOG_TO_TERMINAL ]] && echo "error message" >> $LOG_FILE || echo "error message" | tee -a $LOG_FILE
which just logs the file instead of echoing to the terminal no matter whether I set LOG_TO_TERMINAL to true or false.
To isolate the problem, I tried simplifying the code to:
[[ $LOG_TO_TERMINAL ]] && echo "log to terminal" || echo "don't log to terminal"
But this code snippet also echoes "log to terminal" no matter what its value is.

The test [[ $LOG_TO_TERMINAL ]] tests whether LOG_TO_TERMINAL has a value or not. Nothing else. The shell doesn't treat false (or 0 or null etc.) as special false-y values.
If you want some other test you need to test specifically for that.
[[ $LOG_TO_TERMINAL = true ]]
or
[[ $LOG_TO_TERMINAL != false ]]
or
[[ $LOG_TO_TERMINAL = 1 ]]
etc.
If you were expecting to use the return code from the true and/or false commands then you need $LOG_TO_TERMINAL && Y || Z or similar to run the command stored in the variable (though I wouldn't recommend this version of this test).
Also note that X && Y || Z is not a ternary operation in the shell. See the Shellcheck wiki for warning SC2015 for more about this.

You want this:
[[ $LOG_TO_TERMINAL = 1 ]] && echo "log to terminal" || echo "don't log to terminal"

Related

Running multiple commands if an || condition is false

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

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.

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

What is wrong with my shell script's if condition?

I have a secondary list of start up programs that I would like to start in case I am working. So I have added this shell script to the start up of my system running on Ubuntu.
echo "Do you want to start the start up applications[Y/n]?"
while read inputline
do
what="$inputline"
break
done
if [ "$what" == "Y" -o "$what" == "y" ]
then
. ~/bin/webstorm.sh &
workrave &
firefox &
. ~/bin/AptanaStudio3
fi
I keep getting this error that says something like [: Y: unexpected operator all the time and never starts the programs.
Disclaimer: I have no idea how to write shell scripts.
Try changing:
if [ "$what" == "Y" -o "$what" == "y" ]
to
if [[ "$what" == "Y" ]] || [[ "$what" == "y" ]]
What is your shebang line? #!/bin/bash or #!/bin/sh?
If you are intending to use bash as script interpreter, don't forget shebang line.
(Your code is working with bash on my system at least..)

bash one-line conditional fails when using set -e

I started using set -e in my bash scripts,
and discovered that short form of conditional expression breaks the script execution.
For example the following line should check that $var is not empty:
[ -z "$var" ] && die "result is empty"
But causes silent exit from script when $var has non-zero length.
I used this form of conditional expression in many places...
What should I do to make it run correctly? Rewrite everything with "if" construction (which would be ugly)? Or abandon "set -e"?
Edit: Everybody is asking for the code. Here is full [non]working example:
#!/bin/bash
set -e
function check_me()
{
ws="smth"
[ -z "$ws" ] && echo " fail" && exit 1
}
echo "checking wrong thing"
check_me
echo "check finished"
I'd expect it to print both echoes before and after function call.
But it silently fails in the check_me function. Output is:
checking wrong thing
Use
[ -n "$var" ] || die "result is empty"
This way, the return value of the entire statement is true if $var is non-empty, so the ERR trap is not triggered.
I'm afraid you will have to rewrite everything so no false statements occur.
The definition of set -e is clear:
-e Exit immediately if a simple command (see SHELL GRAMMAR above) exits with a non-zero status. The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in an if statement, part of a && or || list, or if the command's return value is being inverted via !. A trap on ERR, if set, is executed before the shell exits.
You are using the "optimization" system of Bash: because a false statement will cause an AND (&&) statement never to be true, bash knows it doesn't have to execute the second part of the line. However, this is a clever "abuse" of the system, not intended behaviour and therefore incompatible with set -e. You will have to rewrite everything so it is using proper ifs.
You should write your script such that no command ever exits with non-zero status.
In your command [ -z "$var" ] can be true, in which case you call die, or false in which case -e does it's thing.
Either write it with if, as you say, or use something like this:
[ -z "$var" ] && die "result is empty" || true
I'd recommend if though.
What the bash help isn't very clear on is that only the last statement in an && or || chain is subject to causing an exit under set -e. foo && bar will exit if bar returns false, but not if foo returns false.
So your script should work... but it doesn't. Why?
It's not because of the failed -z test. It's because that failure makes the function return a non-zero status:
#!/bin/bash
set -e
function check_me()
{
ws="smth"
[ -z "$ws" ] && echo " fail" && exit 1
# The line above fails, setting $? to 1
# The function now returns, returning 1!
}
echo "checking wrong thing"
check_me # function returns 1, causing exit here
echo "check finished"
So there are multiple ways to fix this. You could add ||true to the conditional inside the function, or to the line that calls check_me. But as others have pointed out, using ||true has its own problems.
In this specific scenario, where the desired postcondition of check_me is "either this thing is valid or the script has exited", the straightforward thing to do is to write it like that, i.e. [[ -n "$ws" ]] || die "whatever".
But using && conditions will actually work fine with set -e in general, as long as you don't use such a conditional as the last thing in a function. You need to add an explicit true or return 0 or even : as a statement following such a conditional, unless you intend the function to return false when the condition fails.

Resources