561

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?

Andrew
  • 16,855
  • 8
    In bash, 'or' operator is '||' (C style). – Marius Cotofana Sep 08 '12 at 20:47
  • 7
    You can also use -o within the same [ ]. – Thor Sep 08 '12 at 20:53
  • 11
    @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 09 '12 at 00:46
  • 3
    @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 09 '12 at 17:14
  • 11
    @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 02:06
  • The XSI extensions specifying the -a and -o binary primaries and the '(' and ')' operators have been marked obsolescent. and should not be used. – William Pursell Jan 04 '20 at 16:50
  • 1
    One huge disadvantage of [[ (IMO, a complete show stopper) is that it does not produce reasonable error messages, but happily returns truthiness with no error message to statements like [[ $a -eq 0 ]] when $a is not an integer value. In other words, if the target language is bash, no one should be using [[ at all. – William Pursell Jan 04 '20 at 16:53
  • Related (but not a duplicate!): https://stackoverflow.com/questions/13408493/ – Attila Csipak Jul 14 '23 at 06:18

4 Answers4

868

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.

bahamat
  • 39,666
  • 4
  • 75
  • 104
  • 19
    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
  • 41
    @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
  • 15
    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: https://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
  • 9
    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 05:45
  • @bahamat, you should use = instead of == in [...], it's the standard comparison operator. Bash does support == too, but not all shells do. – ilkkachu Oct 28 '19 at 21:31
  • There is no need to wrap every expression in brackets which is slower in addition to adding more clutter. – Rick O'Shea Dec 26 '19 at 21:11
  • It's worth mentioning that for AND use && instead of ||. – Gabriel Staples Apr 15 '20 at 20:14
  • I think @AnandShanbhag answer is better than this: https://unix.stackexchange.com/questions/47584/in-a-bash-script-using-the-conditional-or-in-an-if-statement/586897#586897 – Massimo May 27 '20 at 15:08
  • 3
    @Massimo Since [ is the same thing as test, it's literally the same answer. – bahamat Jun 03 '20 at 18:43
  • @bahamat 1) not all the readers know 2) the simplest form to read and remember is test – Massimo Jun 03 '20 at 19:46
  • 3
    I disagree with your assertion, but it's beside the point anyway. The question was why | doesn't work. The answer is because it's supposed to be ||. Using test vs [ is entirely irrelevant to the question. – bahamat Jun 12 '20 at 23:35
  • 2
    Completely disagree with Massimo too, test is horribly UNintuitive and brackets read much easier. Also easier when switching between programming languages. – MS Berends Jul 21 '21 at 11:46
  • How to correctly negate it? Is if ! [ "$fname" = "a.txt" ] || [ "$fname" = "c.txt" ] correct? – pmor Oct 31 '22 at 13:43
  • @pmor Yes, but you need to negate each expression independently. Your expression would be "if $fname is anything other than a.txt, or is c.txt", which doesn't make much sense. If you want not A and not B then it's if ! [[ A ]] && ! [[ B ]]; then .... And remember that "not A OR not B" is not the same thing as "not A AND not B". – bahamat Nov 01 '22 at 18:44
  • does if [ -n "$NEW_FLAGNAME" ] || [ -n "$LEGACY_FLAGNAME"] work? – quant2016 Nov 24 '22 at 10:13
  • I forget space after "$LEGACY_FLAGNAME". if [ -n "$NEW_FLAGNAME" ] || [ -n "$LEGACY_FLAGNAME" ]; then echo enabled; else echo disabled; fi works as needed(; can be replaced with newlines) – quant2016 Nov 24 '22 at 10:29
278

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.

jesse_b
  • 37,005
  • 13
    +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". http://www.tldp.org/LDP/abs/html/testconstructs.html#DBLBRACKETS – michael May 19 '20 at 23:30
  • 1
    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 03 '20 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 '20 at 04:05
  • 2
    @jbobbins: If expression1 is false there is no need to evaluate expression2. – jesse_b Sep 25 '20 at 11:20
  • 4
    Wow, that's embarrassing... I guess my brain was locked into true mode. ;( Maybe it's time to retire :D – jbobbins Sep 25 '20 at 15:10
8

You can also use OR condition like this

if test "$fname" = "a.txt" || test "$fname" = "c.txt"
AnandShanbhag
  • 181
  • 1
  • 1
  • 7
    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 '20 at 21:20
  • It is easier to read test than brackets. Why make the code more cryptic than necessary? – Massimo May 27 '20 at 15:12
  • 3
    That is your subjective opinion. Mine is that brackets are easier to read. None of our opinions is "correct", just saying. – Martin Melka Mar 02 '21 at 09:22
  • 4
    Massimo, you’re replying under every answer stating that you think test is more convenient, yet nobody is upvoting these comments. How much cue do you need? You might think your way is best, but I think most people disagree. Sorry :) – MS Berends Jul 21 '21 at 11:56
  • @Massimo "more readable" is a subjective opinion. But [] and [[]] are clearly more idiomatic. – Barmar Jan 26 '22 at 19:23
  • This is educational for those who are not aware that [ (and [[ ) is a shortcut alias for /bin/test . – cgseller Jan 24 '24 at 18:21
2

You can use or condition like this

 if [ "$fname" = "a.txt" -o "$fname" = "c.txt" ]