In a bash script, using the conditional "or" in an "if" statement

  • 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?

    In bash, 'or' operator is '||' (C style).

    You can also use `-o` within the same `[ ]`.

    @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 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 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.

    `The XSI extensions specifying the -a and -o binary primaries and the '(' and ')' operators have been marked obsolescent.` and should not be used.

    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.

  • bahamat

    bahamat Correct answer

    8 years ago

    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.)

    Furthermore, `||` doesn't do a standard logic "OR" - it short-circuits, and the second command is run only if the first fails.

    @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.

    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

    This is what the `test` man page recommends too

    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 `[[ ]]`).

    @bahamat, you should use `=` instead of `==` in `[...]`, it's the standard comparison operator. Bash does support `==` too, but not all shells do.

    There is no need to wrap every expression in brackets which is slower in addition to adding more clutter.

    It's worth mentioning that for AND use `&&` instead of `||`.

License under CC-BY-SA with attribution


Content dated before 6/26/2020 9:53 AM