247

This question is a sequel of sorts to my earlier question. The users on this site kindly helped me determine how to write a bash for loop that iterates over string values. For example, suppose that a loop control variable fname iterates over the strings "a.txt" "b.txt" "c.txt". I would like to echo "yes!" when fname has the value "a.txt" or "c.txt", and echo "no!" otherwise. I have tried the following bash shell script:

#!/bin/bash

for fname in "a.txt" "b.txt" "c.txt"
do
  echo $fname
  if [ "$fname" = "a.txt" ] | [ "$fname" = "c.txt" ]; then
    echo "yes!"
  else
    echo "no!"
  fi
done

I obtain the output:

a.txt

no!

b.txt

no!

c.txt

yes!

Why does the if statement apparently yield true when fname has the value "a.txt"? Have I used | incorrectly?

  • 4
    In bash, 'or' operator is '||' (C style). – Marius Cotofana Sep 8 '12 at 20:47
  • 4
    You can also use -o within the same [ ]. – Thor Sep 8 '12 at 20:53
  • 8
    @Thor I'd prefer || and separate [ ] over -o for portability simply because [ is not guaranteed to support more than 4 arguments. Of course if the target language is bash, no one should be using [ anyways because bash's [[ is superior in many ways. – jw013 Sep 9 '12 at 0:46
  • 2
    @jw013 Thanks. Does this mean that I should be using if [[ "$fname" = "a.txt" ]] || [[ "$fname" = "c.txt" ]] rather than if [ "$fname" = "a.txt" ] || [ "$fname" = "c.txt" ]? – Andrew Sep 9 '12 at 17:14
  • 6
    @Andrew That is correct, if as you are declaring the shebang as bash, as you are already doing. One advantage of [[ is that it doesn't do word splitting (special case) so [[ $unquoted_var = string ]] is safe. – jw013 Sep 10 '12 at 2:06
422

If you want to say OR use double pipe (||).

if [ "$fname" = "a.txt" ] || [ "$fname" = "c.txt" ]

(The original OP code using | was simply piping the output of the left side to the right side, in the same way any ordinary pipe works.)


After many years of comments and misunderstanding, allow me to clarify.

To do OR you use ||.

Whether you use [ or [[ or test or (( all depends on what you need on a case by case basis. It's wrong to say that one of those is preferred in all cases. Sometimes [ is right and [[ is wrong. But that's not what the question was. OP asked why | didn't work. The answer is because it should be || instead.

| improve this answer | |
  • 7
    Furthermore, || doesn't do a standard logic "OR" - it short-circuits, and the second command is run only if the first fails. – holdenweb Dec 12 '16 at 19:33
  • 24
    @holdenweb I'm pretty sure most modern optimized languages work the same way. No need to spend CPU cycles evaluating the second condition of OR if the first condition evaluates true. – bahamat Dec 12 '16 at 20:50
  • 4
    I thought bash liked == but after seeing this answer, I decided to look it up. Apparently, "it can be used but isn't standard". I thought I'd put this here for others if your curious: stackoverflow.com/a/2237103 – harperville Sep 17 '18 at 15:55
  • This is what the test man page recommends too – cdosborn Apr 21 '19 at 18:13
  • 4
    You can also use double bracket tests - if [[ "$fname" = "a.txt" ]] || [[ "$fname" = "c.txt" ]] (If you want or need to have the extra functionality associated with [[ ]]). – HankCa Apr 24 '19 at 5:45
63

The accepted answer is good but since you're using bash I'll add the bash solution:

if [[ "$fname" == "a.txt" || "$fname" == "c.txt" ]]; then

This is documented in the bash reference manual 3.2.4.2 Conditional Constructs

expression1 && expression2

True if both expression1 and expression2 are true.

expression1 || expression2

True if either expression1 or expression2 is true.

The && and || operators do not evaluate expression2 if the value of expression1 is sufficient to determine the return value of the entire conditional expression.

| improve this answer | |
  • 2
    This is the safest option, IMO. The accepted answer, didn't worked for me. – ivanleoncz Apr 24 at 17:29
  • 1
    +1 Agree this is the better answer (vs the "accepted" answer). Careful readers should note that the || (or &&) are inside the conditional, which in this case necessarily uses [[...]] and not [...] (i.e., it's not the shell's "or"/"and"). I'll only add that [[ ... ]] is not only more useful, supports more options (eg ||, &&, <, >) but also "safer" than older [ ...] ; the old -a and -o from the single-bracket syntax is supported, too, and as expected, operators "and" take precedence over "or". tldp.org/LDP/abs/html/testconstructs.html#DBLBRACKETS – michael May 19 at 23:30
  • I think @AnandShanbhag answer is better than this. Why spend neurons to bother about single or double brackets? C'mon, let do a favor to your readers: use test – Massimo Jun 3 at 19:49
  • For the && operator is this true? "The && and || operators do not evaluate expression2 if the value of expression1 is sufficient to determine the return value of the entire conditional expression." I understand the || case, but does anyone know of an example with the && case where you wouldn't need to evaluate expression2? It doesn't seem logically possible, but I'm probably missing or misunderstanding something. – jbobbins Sep 25 at 4:05
  • @jbobbins: If expression1 is false there is no need to evaluate expression2. – jesse_b Sep 25 at 11:20
1

You can also use OR condition like this

if test "$fname" = "a.txt" || test "$fname" = "c.txt"
| improve this answer | |
  • 1
    This is fundamentally the same as the accepted answer (and several comments) but more characters and (IMHO) less readable. – G-Man Says 'Reinstate Monica' May 15 at 21:20
  • It is easier to read test than brackets. Why make the code more cryptic than necessary? – Massimo May 27 at 15:12
0

You can use or condition like this

 if [ "$fname" = "a.txt" -o "$fname" = "c.txt" ]
| improve this answer | |
  • 9
    Yes you can, but using -o for "or" is marked as obsolete in the POSIX standard, as is using -a for "and". New code should not use -o or -a. – Kusalananda Jan 4 at 14:19
  • It also makes for unreliable code as the [ command is not always able to parse its arguments correctly when given more than 4 arguments (beside [ and ]). Try for instance with fname='!' with the [ builtin of the bash shell. – Stéphane Chazelas Jul 20 at 12:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.