331

I have a couple of variables and I want to check the following condition (written out in words, then my failed attempt at bash scripting):

if varA EQUALS 1 AND ( varB EQUALS "t1" OR varB EQUALS "t2" ) then 

do something

done.

And in my failed attempt, I came up with:

if (($varA == 1)) && ( (($varB == "t1")) || (($varC == "t2")) ); 
  then
    scale=0.05
  fi
codeforester
  • 39,467
  • 16
  • 112
  • 140
Amit
  • 7,688
  • 17
  • 53
  • 68

5 Answers5

838

What you've written actually almost works (it would work if all the variables were numbers), but it's not an idiomatic way at all.

  • (…) parentheses indicate a subshell. What's inside them isn't an expression like in many other languages. It's a list of commands (just like outside parentheses). These commands are executed in a separate subprocess, so any redirection, assignment, etc. performed inside the parentheses has no effect outside the parentheses.
    • With a leading dollar sign, $(…) is a command substitution: there is a command inside the parentheses, and the output from the command is used as part of the command line (after extra expansions unless the substitution is between double quotes, but that's another story).
  • { … } braces are like parentheses in that they group commands, but they only influence parsing, not grouping. The program x=2; { x=4; }; echo $x prints 4, whereas x=2; (x=4); echo $x prints 2. (Also braces require spaces around them and a semicolon before closing, whereas parentheses don't. That's just a syntax quirk.)
    • With a leading dollar sign, ${VAR} is a parameter expansion, expanding to the value of a variable, with possible extra transformations.
  • ((…)) double parentheses surround an arithmetic instruction, that is, a computation on integers, with a syntax resembling other programming languages. This syntax is mostly used for assignments and in conditionals.
    • The same syntax is used in arithmetic expressions $((…)), which expand to the integer value of the expression.
  • [[ … ]] double brackets surround conditional expressions. Conditional expressions are mostly built on operators such as -n $variable to test if a variable is empty and -e $file to test if a file exists. There are also string equality operators: "$string1" == "$string2" (beware that the right-hand side is a pattern, e.g. [[ $foo == a* ]] tests if $foo starts with a while [[ $foo == "a*" ]] tests if $foo is exactly a*), and the familiar !, && and || operators for negation, conjunction and disjunction as well as parentheses for grouping. Note that you need a space around each operator (e.g. [[ "$x" == "$y" ]], not [[ "$x"=="$y" ]]), and a space or a character like ; both inside and outside the brackets (e.g. [[ -n $foo ]], not [[-n $foo]]).
  • [ … ] single brackets are an alternate form of conditional expressions with more quirks (but older and more portable). Don't write any for now; start worrying about them when you find scripts that contain them.

This is the idiomatic way to write your test in bash:

if [[ $varA == 1 && ($varB == "t1" || $varC == "t2") ]]; then

If you need portability to other shells, this would be the way (note the additional quoting and the separate sets of brackets around each individual test, and the use of the traditional = operator rather than the ksh/bash/zsh == variant):

if [ "$varA" = 1 ] && { [ "$varB" = "t1" ] || [ "$varC" = "t2" ]; }; then
Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
  • 16
    It's better to use `==` to differentiate the comparison from assigning a variable (which is also `=`) – Will Sheppard Jun 19 '14 at 11:07
  • +1 @WillSheppard for yr reminder of proper style. Gilles, dont you need a semicolon after yr closing curly bracket and before "then" ? I always thought `if`, `then`, `else` and `fi` could not be on the same line... As in: `if [ "$varA" = 1 ] && { [ "$varB" = "t1" ] || [ "$varC" = "t2" ]; }; then` – Cbhihe Apr 03 '16 at 08:05
  • Backquotes (`\`…\``) are old-style form of command substitution, with some differences: in this form, backslash retains its literal meaning except when followed by `$`, `\``, or ```\```, and the first backquote not preceded by a backslash terminates the command substitution; whereas in the `$(…)` form, all characters between the parentheses make up the command, none are treated specially. – Rockallite Jan 19 '17 at 02:41
  • 3
    You could emphasize that single brackets have completely different semantics inside and outside of double brackets. (Because you start with explicitly pointing out the subshell semantics but then only as an aside mention the grouping semantics as part of conditional expressions. Was confusing to me for a second when I looked at your idiomatic example.) – Peter - Reinstate Monica Aug 28 '17 at 13:16
  • @PeterA.Schneider I don't understand your comment. What semantics of single brackets are you referring to? The use to delimit character sets in glob patterns? Why is it relevant? – Gilles 'SO- stop being evil' Aug 28 '17 at 14:15
  • 6
    Oh, I meant single (round) parentheses, sorry for the confusion. The ones in `[[ $varA = 1 && ($varB = "t1" || $varC = "t2") ]]` do not start a sub process although the first bullet point explicitly says: "What's inside [parentheses] **isn't an expression** like in many other languages" -- but it certainly is here! That is probably obvious to the experienced bash wiz, but not even to me, immediately. The confusion can arise because single parentheses *can be* used in an `if` statement, just not in expressions inside double brackets. – Peter - Reinstate Monica Aug 30 '17 at 13:41
  • 7
    @protagonist `==` is not really “more idiomatic” than `=`. They have the same meaning, but `==` is a ksh variant also available in bash and zsh, whereas `=` is portable. There isn't really any advantage to using `==`, and it doesn't work in plain sh. – Gilles 'SO- stop being evil' Sep 14 '19 at 07:03
  • 3
    @WillSheppard There are already many other differences between comparison and assignment: comparison is inside brackets, assignment is not; comparison has spaces around the operator, assignment doesn't; assignment has a variable name on the left, comparison only rarely has something that looks like a variable name on the left and you can and should put quotes anyway. You can write `==` inside `[[ … ]]`, if you want but `=` has the advantage of also working in `[ … ]`, so I recommend not getting into the habit of using `==` at all. – Gilles 'SO- stop being evil' Sep 14 '19 at 07:06
  • I have a question like in this statement for eg: if [[ $varA == 1 && ($varB == "t1" || $varC == "t2") ]]; then doesn't the result of this statement ($varB == "t1" || $varC == "t2"), will be run in subshell and doesn't the expression that run in the subshell, will not be related to the statements running in this shell, so how will the result will be evaluated? – nakli_batman Oct 27 '22 at 02:13
  • 2
    @nakli_batman No, parentheses inside `[[ … ]]` don't mean a subshell. They mean operator prioritization. Note also that even if you ran the whole condition in a subshell (`if ( [[ $varA … ]] ); then …`) it would still work: the subshell has its own copy of all the variables, so it can make the same calculation, the fact that it's a subshell only matters if you change variables inside as the changes won't be seen in the parent. – Gilles 'SO- stop being evil' Oct 27 '22 at 06:48
45

very close

if [[ $varA -eq 1 ]] && [[ $varB == 't1' || $varC == 't2' ]]; 
  then 
    scale=0.05
  fi

should work.

breaking it down

[[ $varA -eq 1 ]] 

is an integer comparison where as

$varB == 't1'

is a string comparison. otherwise, I am just grouping the comparisons correctly.

Double square brackets delimit a Conditional Expression. And, I find the following to be a good reading on the subject: "(IBM) Demystify test, [, [[, ((, and if-then-else"

matchew
  • 19,195
  • 5
  • 44
  • 48
  • Just to be sure: The quoting in `'t1'` is unnecessary, right? Because as opposed to arithmetic instructions in double parentheses, where `t1` would be a variable, `t1` in a conditional expression in double *brackets* is just a literal string. I.e., `[[ $varB == 't1' ]]` is exactly the same as `[[ $varB == t1 ]]`, right? – Peter - Reinstate Monica Aug 28 '17 at 13:21
12

A very portable version (even to legacy bourne shell):

if [ "$varA" = 1 -a \( "$varB" = "t1" -o "$varB" = "t2" \) ]
then    do-something
fi

This has the additional quality of running only one subprocess at most (which is the process [), whatever the shell flavor.

Replace = with -eq if variables contain numeric values, e.g.

  • 3 -eq 03 is true, but
  • 3 = 03 is false. (string comparison)
NAND
  • 663
  • 8
  • 22
J.P. Tosoni
  • 549
  • 5
  • 15
  • Honest question: why is portability to the legacy Bourne shell important? Which are the use cases for `sh` where `bash` does not fill the bill? – András Aszódi Nov 30 '22 at 14:20
  • 1
    @András: there are many flavors of the shell, all except csh inherit from Bourne shell, few are bash compatible. E. g. Openwrt shell is "ash" which misses many bash characteristics. ksh, dash are used a lot but are not fully bash compatible. Compatibility is an issue for the programmer, to avoid learning a new shell specifics every time they move to another job. Just my 2 cents though. – J.P. Tosoni Dec 02 '22 at 18:43
9

Here is the code for the short version of if-then-else statement:

( [ $a -eq 1 ] || [ $b -eq 2 ] ) && echo "ok" || echo "nok"

Pay attention to the following:

  1. || and && operands inside if condition (i.e. between round parentheses) are logical operands (or/and)

  2. || and && operands outside if condition mean then/else

Practically the statement says:

if (a=1 or b=2) then "ok" else "nok"

Dadep
  • 2,796
  • 5
  • 27
  • 40
tlc
  • 91
  • 1
  • 1
  • Parenthesis `( ... )` creates a subshell. May want to use braces `{ ... }` instead. Any state created in a subshell won't be visible in the caller. – Clint Pachl Mar 28 '18 at 05:58
-1
if ([ $NUM1 == 1 ] || [ $NUM2 == 1 ]) && [ -z "$STR" ]
then
    echo STR is empty but should have a value.
fi
AlikElzin-kilaka
  • 34,335
  • 35
  • 194
  • 277