Properly nesting conditionals in if statements - bash

I had a set of conditionals working perfectly. However, I needed to tweak the algorithm (making it more complicated), which involved nested conditionals in bash.
For instance, here is one such block:
a1=C
a2=P
a3=E
a4=1
a5=7
a6=0
a7=4
a8=T
a9=K
a10=S
letters=$(echo {A..Z} | tr -d ' ')
if [ "$c3" != "$a3" ]
then
c3=${letters:$((RANDOM%26+1)):1}
if [ "$c3" == "$a3" ] && (( $count % $difficulty3 != 0 ))
then
c3=${letters:$((RANDOM%26+1)):1}
if [ "$c3" == "$a3" ] && [ [ "$c1" != "$a1" ] || [ "$c2" != "$a2" ] ]
then
c3=${letters:$((RANDOM%26+1)):1}
fi
fi
fi
The innermost if statement essentially checks, if c3 and a3 are equal, to make sure that c1 and a1 are also equal and that c2 and a2 are also equal. If not, it reassigns the variable c3 once more.
My thought is to do an if (true AND ( true OR true)) type logic, but this doesn't seem to work here. I get:
line 367: [: too many arguments
All the c and a variables are a single letter or numerical digit, so strings with spaces as I've seen in related SO posts on this subject are not causing the issue.
When I try doing double brackets instead of single around the whole thing, the script fails to run at all. Parentheses won't work since these aren't arithmetic operations... so I'm scratching my head now. It seems this is the proper way to do nested conditionals... what am I missing?

Square brackets look like special shell syntax but they're not at all. [ is just another name for test, and the closing ] is merely an argument that's ignored.
You can use curly braces, which are special syntax:
if [ "$c3" == "$a3" ] && { [ "$c1" != "$a1" ] || [ "$c2" != "$a2" ]; }
Parentheses also work. The extra ; isn't needed, but they create an unnecessary sub-shell, so I recommend curly braces:
if [ "$c3" == "$a3" ] && ( [ "$c1" != "$a1" ] || [ "$c2" != "$a2" ] )

Related

How to write a one liner test containing "||"s and "&&"s [duplicate]

I would like to recreate something like this
if ( arg1 || arg2 || arg 3) {}
And I did got so far, but I get the following error:
line 11: [.: command not found
if [ $char == $';' -o $char == $'\\' -o $char == $'\'' ]
then ...
I tried different ways, but none seemed to work. Some of the ones I tried.
For Bash, you can use the [[ ]] form rather than [ ], which allows && and || internally:
if [[ foo || bar || baz ]] ; then
...
fi
Otherwise, you can use the usual Boolean logic operators externally:
[ foo ] || [ bar ] || [ baz ]
...or use operators specific to the test command (though modern versions of the POSIX specification describe this XSI extension as deprecated -- see the APPLICATION USAGE section):
[ foo -o bar -o baz ]
...which is a differently written form of the following, which is similarly deprecated:
test foo -o bar -o baz
Bash's [[ ]] and (( )) are more powerful and flexible than [ ].
[[ ]] is for strings and (( )) is for integer logic and arithmetic
&& and || operators can be used inside [[ ]] and (( )), and () can be used for grouping
No need to quote variable expansions inside [[ ]] or (( )) - Bash doesn't do word splitting or globbing in these contexts
Inside (( )), there is no need for a $ behind variable names to expand them
[[ ]] and (( )) can span multiple lines, without the need for a line continuation with \
Using these, we can write clean, readable, and more reliable code.
Examples
Compound statements with integers
a=1 b=2 c=3
((a == 2 || (b == 2 && c == 3))) && echo yes # yields yes
Compound statements with strings
x=apple y=mango z=pear
[[ $x == orange || ($y == mango && $z == pear) ]] && echo yes # yields yes
[ equivalents for the above statements, using {}
[ "$a" -eq 2 ] || { [ "$b" -eq 2 ] && [ "$c" -eq 3 ]; }
[ "$x" == orange ] || { [ $y == mango ] && [ "$z" == pear ]; }
Related
Is double square brackets [[ ]] preferable over single square brackets [ ] in Bash?
How to use double or single brackets, parentheses, curly braces
Comparing integers: arithmetic expression or conditional expression on Unix & Linux
Test for non-zero length string in Bash: [ -n “$var” ] or [ “$var” ]
Conditional Expressions - Bash Manual
Charles' answer is correct in that it shows you how to do logical operations on commands within (and without, for that matter) an if statement, but it looks more like you want to use case here:
case $char in
\;|\\|\') echo found;;
*) echo 'not found';;
esac
In bash, to group conditionals using the [ ] construct you must surround each group using parenthesis. Each opening/closing parenthesis must be escaped and preceded/succeeded with a white space. See below:
if [ \( "$char" = "$;" \) -o \( "$char" = "$\\" \) -o \( "$char" = "$\" \) ]
As such, it's definitely best to follow everyone elses advice and use bash's newer [[ ]] construct. Lastly, as I understand it == is a relational operator intended to be used with-in arithmetic expressions. i.e. -
$((3==4))
Cameron Newham and Bill Rosenblatt, "Learning the bash Shell" Jan.1998

bash if clause with and or combined

I'm trying to be smart but it doesn't work. Can anybody help me to do this a bit simpler?
if [[ "${DATUM}" == "${today}" && "${STUNDE}" == "${HH24}" ]] || [[ "${DATUM}" == "${today}" && "${STUNDE}" == "20" ]] ||
[[ "${DATUM}" == "${today}" && "${STUNDE}" == "" && "20" == "${HH24}" ]]; then
Is there a way to combine it?
Your code can be translated to:
(C1 and C2) or (C1 and C3) or (C1 and C4 and C5)
Applying boolean arithmetics you can simplify it as:
C1 and (C2 or C3 or (C4 and C5))
This said, you can add a nested if statement to, first, check the C1 condition and, second, check the other conditions. It does not simplify the code a lot but here it is:
if [ "${DATUM}" = "${today}" ]; then
if [ "${STUNDE}" = "${HH24}" ] || [ "${STUNDE}" = "20" ] || { [ "${STUNDE}" = "" ] && [ "${HH24}" = "20" ]; }; then
# Insert the code to execute when the conditions are satisfied
fi
fi
As others have noted, your boolean expression can be simplified applying the law of distributivity of conjunction (⋀, *, AND) over disjunction (⋁, +, OR):
(a ⋀ b) ⋁ (a ⋀ c) = a ⋀ (b ⋁ c)
But to simplify it further, note you can, in bash, use && and || inside the (bash-specific) [[ .. ]] command:
[[ $a == 1 && $b == 2 ]]
Also, when using [[ .. ]] compound command (over POSIX [ .. ]) you don't have to quote variables. And to test for null-strings, you can use the shorter -z $var form over $var == "".
All this together yields:
if [[ $DATUM == $today ]] && [[ $STUNDE == $HH24 || $STUNDE == 20 || -z $STUNDE && $HH24 == 20 ]]; then
# ...
fi
To further simplify it, we would need to have more details on your application logic, possible values, etc.

What is the difference between IF [ condition ] & [[ condition ]] , -eq and == in bash

Is there a difference between the two code snippets below
if [[ $a == "1" ]];then
echo $a
and
if [ $a == "1" ];then
echo $a
Also, is there a difference when I use -eq in place of == in the above snippet?
As for your main question: it is a duplicate of: Is [[ ]] preferable over [ ] in bash scripts?
You can also find a (hopefully) comprehensive discussion of the differences between [ ... ] and [[ ... ]] in this answer of mine.
In short: [[ ... ]] is parsed more like you'd expect in a regular programming language, and it implements many useful extensions, but it is not POSIX-compliant.
As for "is there a difference when I use -eq in place of ==?":
= and ==, its Bash alternative, perform string comparison.
Additionally, inside [[ ... ]] only, if the RHS of = or == is unquoted, it is interpreted as a glob-style pattern to match the LHS against; contrast [[ 'a' == '*' ]] && echo match with [[ 'a' == * ]] && echo match
Note that if you use [ ... ] (rather than [[ ... ]]) for POSIX compliance (portable use with /bin/sh), you should only use =, not ==; while Bash accepts == inside [ ... ] too, other shells don't.
-eq performs integer comparison
Other string/numeric operator pairs exist (e.g., -lt for numeric less-than vs. < for alphabetical (string) less-than, based on textual sort order).
Bash Conditional Expressions lists all operators you can use inside [ ... ] and [[ ... ]] (and also with test, which is effectively an alias of [ ... ]).
Additionally, inside [[ ... ]], regular expression-matching operator =~ is available - see Bash Conditional Constructs
In bash, numeric comparison is handled differently than string comparison
For numbers,
$var1 -eq $var2 // =
$var1 -gt $var2 // >
$var1 -ge $var2 // >=
$var1 -lt $var2 // <
$var1 -le $var2 // <=
$var1 -ne $var2 // !=
For strings
$str1 = $str2 // they are equal
str1 != str2 // not equal
str // Returns True if str is not null.
-n str // Returns True if the length of str is greater than zero.
-z str // Returns True if the length of str is equal to zero.
Note that == is the same as =
Also note that the == operates differently in a double bracket comparison (this is where your [ condition ] vs [[ condition ]] question comes in) when doing pattern matching. These comparisons/operators all all explained at http://www.tldp.org/LDP/abs/html/comparison-ops.html

Shellscript missing ]

I have a shellscript that tells me missing ] in the line
if [ $status != "2" && $status != "3" && `echo "$temp1 > $upperLimit" | bc` = "1" ]
and also missing ] in the line
if [ $status = "2" && `cat motionsensordate` \> `date +%s` ]
Why is that?
The single [ doesn't support logical operators inside the brackets. You have to use them outside
if [ "$status" != 2 ] && [ "$status" != 3 ] ...
Use double quotes for variables in single brackets to prevent unary operator expected error when the variable is empty.
Or, switch to double brackets:
if [[ $status != 2 && $status != 3 ... ]]
Also, status different to 2 and 3 can be expressed by a pattern:
if [[ $status != [23] && ... ]]
And if you would like to (in addition to the answers here) group together conditions:
if [[ ( COND1 || COND2 ) && COND3 ]]
then
echo "$cmd"
break
fi

Checking for null or character values in an if condition in shell script

I am trying to combine two conditions that checks for character values or a null value. For some reason they only work separately, when I try to combine them together in an "or" it gives me errors. Am I missing something?
RETENTION= #either a character value or null value
if [[ "$RETENTION" == +([a-zA-Z]) ]] || [ -z "$RETENTION"] ; then
#bad input
#exit 1
using an elif to do it separately will not work either. Only one condition works.
You have a typo in your condition:
if [[ "$RETENTION" == +([a-zA-Z]) ]] || [ -z "$RETENTION" ] ; then
Should be:
if [[ "$RETENTION" == +([a-zA-Z]) ]] || [ -z "$RETENTION" ] ; then
You had the double square bracket closed before your OR conditio
UPDATE
Actual resolution is to swap the order of the conditions:
if [ -z "$RETENTION" ] || [[ "$RETENTION" == +([a-zA-Z]) ]] ; then

Resources