Why does not bash short-circuit work in this case? - bash

Suppose I am writing the following in a bash script:
if [ -z $a ] || [ -z $b ] ; then
usage
fi
It works but I would like to write it with short-circuiting as follows:
[ -z $a ] || [ -z $b ] || usage
Unfortunately it does not work. What am I missing ?

You want to execute usage in case either 1st or 2nd condition are accomplished. For that, you can do:
[ -z $a ] || [ -z $b ] && usage
Test:
$ [ -z "$a" ] || [ -z "$b" ] && echo "yes"
yes
$ b="a"
$ [ -z "$a" ] || [ -z "$b" ] && echo "yes"
yes
$ a="a"
$ [ -z "$a" ] || [ -z "$b" ] && echo "yes"
$

You could make use of the following form:
[[ expression ]]
and say:
[[ -z "$a" || -z "$b" ]] && usage
This would execute usage if either a or b is empty.
Always quote your variables. Saying
[ -z $a ]
if the variable a is set to foo bar would return an error:
bash: [: foo: binary operator expected

Related

Nested condition of if-statement in bash [duplicate]

This question already has answers here:
Meaning of "[: too many arguments" error from if [] (square brackets)
(6 answers)
Closed 4 years ago.
I want to check that of two variables both or neither are set. I've tried multiple options, and this is the most clean solution I've come up with:
if [ ! -z $A -a -z $B ] || [ -z $A -a ! -z $B ]; then
#error
fi
#success
When I run the script with both A and B set - it runs fine. But when I run it with A missing I get:
./test.sh: line 3: [: too many arguments
./test.sh: line 3: [: too many arguments
line 3 being the condition statement.
When I run it with B missing, I get:
./test.sh: line 3: [: argument expected
./test.sh: line 3: [: argument expected
Is it my condition that has wrong syntax or am I missing smth else?
You should try to avoid -a; it's non-standard and considered obsolete by the POSIX standard. Since || and && have equal precedence, you need to use { ... } to properly group the individual tests.
(This is in addition to the immediate need to quote your parameter expansions.)
if { [ ! -z "$A" ] && [ -z "$B" ]; } || { [ -z "$A" ] && [ ! -z "$B" ]; }; then
However, a simpler expression might be
if [ -z "$A$B" ] || { [ "$A" ] && [ "$B" ]; }; then
The concatenation of two strings is empty if and only if both strings are also empty.
[ "$A" ] is short for [ -n "$A" ], which is equivalent to [ ! -z "$A" ].
Using bash's [[ ... ]] command, you can write the more natural
if [[ -z $A && -n $B || -n $A && -z $B ]];
Quotes are optional in this case, and || and && are usable inside [[ ... ]] with the precedence you expect.
Quote your variables:
if [ ! -z "$A" -a -z "$B" ] || [ -z "$A" -a ! -z "$B" ]; then
If the variables are unquoted and unset, they are replaced with nothing, meaning that the command essentially becomes:
if [ ! -z -a -z ] || [ -z -a ! -z ]; then
resulting in the error you see.
You forgot to use quotation marks around your vars:
if [ ! -z "$A" -a -z "$B" ] || [ -z "$A" -a ! -z "$B" ]; then
echo "error"
fi
Bash will replace your vars in your script with the values, so when A=5 and B is unset, your version will read:
if [ ! -z 5 -a -z ] || [ -z 5 -a ! -z ]; then
You see that the syntax is wrong, as -z expects an argument. When using quotes, is reads:
if [ ! -z "5" -a -z "" ] || [ -z "5" -a ! -z "" ]; then
AS you can see, now the argument for B is an empty string, which is valid.
Also your version would have failed when setting A="string with spaces" when unquoted.

How to use multiple condition in if statement in bash?

Actually I am a new bash learner. I can use one condition in bash command. But how to use multiple condition in bash? I can use if statement like this:
read a
if [ $a = "y" ] ; then
echo "YES"
elif [ $a = "Y" ] ; then
echo "YES"
else
echo "NO"
fi
I am finding something like this:
read a b c
if [ $a -eq $b and $b -eq $c ] ; then
echo "EQUILATERAL"
elif [ $a -eq $b or $b -eq $c ] ; then
echo "ISOSCELES"
else
echo "SCALENE"
fi
I just want to know, what to use instead of and and or?
Use && for and (|| for or)
read a b c
if [ "$a" == "$b" ] && [ "$b" == "$c" ] ; then
echo "EQUILATERAL"
elif [ "$a" == "$b" ] || [ "$b" == "$c" ] ; then
echo "ISOSCELES"
else
echo "SCALENE"
fi
Use && and || to have multiple conditions. Additionally, change the square brackets to parentheses. Additionally change the -eq to == since you're comparing numbers and not strings. This works:
#!/bin/bash
read a b c
if (( $a == $b )) && (( $b == $c )); then
echo "EQUILATERAL"
elif (( $a == $b )) || (( $b == $c )) ; then
echo "ISOSCELES"
else
echo "SCALENE"
fi
In addition to the prior answers, the correct way to use compound expression in a single [ or test (they are the same) clause is to use -a (for and) and -o (for or).
(e.g. testing if both file1 and file2 are readable):
if [ -r "$file1" -a -r "$file2 ]
then
# do something with the files
fi
Using test itself:
if test -r "$file1" -a -r "$file2
then
# do something with the files
fi
The portable way of doing this inside test brackets is to use -a and -o. Beware however that -eq is a numeric comparison, so you need to make sure your variables are numeric before comparing them. Something like this:
#! /bin/sh
read a b c
expr "$a" : '[0-9][0-9]*$' \& "$b" : '[0-9][0-9]*$' \& "$c" : '[0-9][0-9]*$' >/dev/null || exit
if [ $a -eq $b -a $b -eq $c ] ; then
echo "EQUILATERAL"
elif [ $a -eq $b -o $b -eq $c ] ; then
echo "ISOSCELES"
else
echo "SCALENE"
fi

"[:too many arguments" error in BASH

I'm learning BASH through HackerRank.There's an exercise in which the lengths of the triangle is given and then you need to find whether the triangle is isosceles,scalene or equilateral.I wrote the following code:
read a
read b
read c
if [ [ "$a" -eq "$b" ] && [ "$b" -eq "$c" ] ]
then
echo "EQUILATERAL"
elif [ [ "$a" -eq "$b" ] || [ "$b" -eq "$c" ] ]
then
echo "ISOSCELES"
else
echo "SCALENE"
fi
But then I get the following error
solution.sh: line 4: [: too many arguments
solution.sh: line 7: [: too many arguments
solution.sh: line 7: [: too many arguments
Why is this happening? I tried long and hard to rectify it but nothing worked out
You can combine conditions either ommiting the surrounding brackets like this
if [ "$a" -eq "$b" ] && [ "$b" -eq "$c" ]
or by combining the conditions with -a/-o like this
if [ "$a" -eq "$b" -a "$b" -eq "$c" ]
see http://wiki.bash-hackers.org/commands/classictest#and_and_or
&& and || are Bash list operators. In a chain of commands, the next command is executed only if the previous command returned 0 (&&) or nonzero (||).
[ is an alias for the Bash internal test command and has arguments such as -eq or -ne. ] ends its command line. Type help test for more information.
So if you write a conditional expression, you do not put the list operators inside brackets.
Try, for example, this instead of the respective line in your code:
if [ "$a" -eq "$b" ] && [ "$b" -eq "$c" ]
then
[ isn't a grouping operator in bash, you can't use it to group tests.
there are a number of different ways to express the tests you want to make, numeric evaluation mode is probably easiest to read
if (( a == b && b == c ))
if (( a == b || b == c || c == a ))
This is going to break if you have decimal fractions, but will work fine for integers.
[ is a conditional command, like an alias for sh's test built-in command.
[[ is the same for bash which has more test options.
So make a choice between [ and [[ but not [ [ which means two command.
Example:
# [ [ -n 'test' ] ]
bash: [: too many arguments
# [ -n 'test' ] && echo $?
0
# [[ -n 'test' ]] && echo $?
0

Chosing "input" in an AND (&&) and OR (||) list of commands

I have to find a way to have my script read from one of these three options:
a file argument
standard input
a previously established environment variable
Here's what I currently have:
#!/bin/bash
key=$1
[ $# -ge 1 -a -f "$2" ] && input="$2" || [ -f "$INPUT" ] && input="$INPUT" || input="-"
echo $input
Only the environment variable refuses to work, the rest works fine.
I've tried using the export INPUT="pathnametofile" before but it doesn't make any difference, I end up with the shell asking me to enter info as if I called on cat.
The problem in your script
Your attemp is not working due to the way the shell processes a Lists of Commands:
‘&&’ and ‘||’ have equal precedence.
AND and OR lists are executed with left associativity.
Your sentence:
[ $# -ge 1 -a -f "$2" ] && input="$2" || [ -f "$INPUT" ] && input="$INPUT" || input="-"
does the same as follows:
[ $# -ge 1 -a -f "$2" ] && input="$2"
[ $? -eq 0 ] || [ -f "$INPUT" ]
[ $? -eq 0 ] && input="$INPUT"
[ $? -eq 0 ] || input="-"
Now yo may see why your unexpected behaviour.
A better attempt grouping commands
{ [ $# -ge 1 -a -f "$2" ] && input="$2"; } || { [ -f "$INPUT" ] && input="$INPUT"; } || input="-"
Now, due to precedence, the first group is not needed at all:
[ $# -ge 1 -a -f "$2" ] && input="$2" || { [ -f "$INPUT" ] && input="$INPUT"; } || input="-"
Furthermore, unless you have set the positional parameters by hand, you can remove the first check (after all, if $2 is emtpy, -f "" fails the same).
[ -f "$2" ] && input="$2" || { [ -f "$INPUT" ] && input="$INPUT"; } || input="-"
An alternative with the if conditional construct
if [ -f "$2" ]; then
input=$2
elif [ -f "$INPUT" ]; then
input=$INPUT
fi
echo "${input:=-}"
untested, but you'll probably have better luck with if commands, and test that the variable is not empty:
if [ $# -ge 1 -a -f "$2" ]; then
input="$2"
elif [ -n "$INPUT" -a -f "$INPUT" ]; then
input="$INPUT"
else
input="-"
fi

How to use IF loop in unix for multiple conditions

I need a logic to implement the following logic in unix
if ( $a !="xyz" || $d !="abc" ) && ( $b= $c))
then
echo "YES WORKING"
fi
I tried below code not working
if [ [ [ $a != "xyz" ] -o [ $d != "abc" ] ] -a [ "$b" = "$c" ] ]
then
echo "YES WORKING"
fi
getting error as
:[ :] unexpected operator/operand
You can do something like this:
[ $a != "xyz" -o $d != "abc" ] && [ "$b" = "$c" ] && echo "YES WORKING"
Your logic should work easy in shells supporting [[ ]]:
if [[ ($a != "xyz" || $d != "abc") && $b = "$c" ]]; then
echo "YES WORKING"
fi
Although there's a way for those that doesn't:
if ([ ! "$a" = "xyz" ] || [ ! "$d" = "abc" ]) && [ "$b" = "$c" ]; then
echo "YES WORKING"
fi
But that's still inefficient since you'd be summoning subshells, so use { } but the syntax is a little ugly:
if { [ ! "$a" = "xyz" ] || [ ! "$d" = "abc" ]; } && [ "$b" = "$c" ]; then
echo "YES WORKING"
fi

Resources