31

I'm learning about decision making structures and I came across these codes:

if [ -f ./myfile ]
then
     cat ./myfile
else
     cat /home/user/myfile
fi


[ -f ./myfile ] &&
cat ./myfile ||
cat /home/user/myfile

Both of them behave the same. Are there any advantages to using one way from the other?

Subhaa Chandar
  • 343
  • 3
  • 4

4 Answers4

32

Most people find it easier to comprehend the if ... then ... else ... fi form.

For the a && b || c, you have to be sure that b returns true. This is a cause of subtle bugs and is a good reason to avoid this style. If b doesn't return true these are not the same.

 $ if true; then false ; else echo boom ; fi
 $ true && false || echo boom
 boom

For very short tests and actions which don't have an else clause, the shortened length is attractive, e.g.

 die(){ printf "%s: %s\n" "$0" "$*" >&2 ; exit 1; }

 [ "$#" -eq 2] || die "Needs 2 arguments, input and output"

 if [ "$#" -ne 2 ] ; then
     die "Needs 2 arguments, input and output"
 fi

&& and || are short circuiting operators, as soon as the result is known further unneeded tests are skipped. a && b || c is grouped as (a && b) || c. First a is run. If it fails which is defined as not returning an exit status of 0, then the group (a && b) is known to fail and b does not need to be run. The || does not know the result of the expression so needs to execute c. If a succeeds (returns zero) then the && operator doesn't yet know the result of a && b so has to run b to find out. If b succeeds then a && b succeeds and the || knows the overall result is success, so doesn't need to run c. If b fails then || still does not know the value of the expression, so does need to run c.

jrw32982
  • 723
icarus
  • 17,920
31

No, constructions if A; then B; else C; fi and A && B || C are not equivalent.

With if A; then B; else C; fi, command A is always evaluated and executed (at least an attempt to execute it is made) and then either command B or command C are evaluated and executed.

With A && B || C, it's the same for commands A and B but different for C: command C is evaluated and executed if either A fails or B fails.

In your example, suppose you chmod u-r ./myfile, then, despite [ -f ./myfile ] succeeds, you will cat /home/user/myfile

My advice: use A && B or A || B all you want, this remains easy to read and understand and there is no trap. But if you mean if...then...else... then use if A; then B; else C; fi.

xhienne
  • 17,793
  • 2
  • 53
  • 69
7

Operator && executes the next command the if previous command had a successful execution, (returned exit code ($?) 0 = logical true).

In form A && B || C , command (or condition) A is evaluated and if A returns true (success, exit code 0) then command B is executed. If A fails (thus will return false - exit code other than 0) and/or B fails (returning false) then command C will be executed.

Also && operator is used as an AND in condition checks and operator || works like OR in condition checks.

Depending on what you want to do with your script, form A && B || C can be used for condition checks like your example or can be used to chain commands and ensure a series of commands to be executed if previous commands had a successful exit code 0.
This is why it is common to see commands like:
do_something && do_something_else_that_depended_on_something.

Examples:
apt-get update && apt-get upgrade If update fails then upgrade is not executed, (makes sense in the real world...).

mkdir test && echo "Something" > test/file
The part echo "Something" will be executed only if mkdir test was successful and operation returned exit code 0.

./configure --prefix=/usr && make && sudo make install
Usually found on compiling jobs to chain necessary dependent commands together.

If you try to implement above "chains" with if-then-else you will need much more commands and checks (and thus more code to write - more things to go wrong) for a simple task.

Also, keep in mind that chained commands with && and || are read by shell left to right. You might need to group commands and condition checks with brackets to depend the next step on the successful output of some previous commands . For example see this:

root@debian:$ true || true && false;echo $?
1 
#read from left to right
#true OR true=true AND false = false = exit code 1=not success

root@debian:$ true || (true && false);echo $?
0 
# true OR (true AND false)=true OR false = true = exit code 0 = success

Or a real life example:

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 ]] && [[ $c -eq 2 ]];echo $?
1 
#condition $a = true OR condition b = true AND condition $c = false
#=> yields false as read from left to right, thus exit code=1 = not ok

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 && $c -eq 2 ]];echo $?
0 
#vars b and c are checked in a group which returns false, 
#condition check of var a returns true, thus true OR false yields true = exit code 0

Keep in mind that some commands return different exit codes depending on the process executed, or return different codes depending on their actions, (for example command GNU diff, returns 1 if two files differ, and 0 if they don't). Such commands need to be treated with care in && and ||.

Also just to have all the puzzle together, mind the concatenation of commands using ; operator . With a format A;B;C all commands will be executed in series no matter what was the exit code of command A and B.

agc
  • 7,223
1

Much of the confusion about this may be due to the bash documentation calling these AND and OR lists. While logically similar to the && and || found inside square brackets, they function differently.

Some examples may illustrate this best...

NOTE: Single and double square brackets ([ ... ] and [[ ... ]]) are commands in their own right that do a comparison and return an exit code. They do not actually need the if.

cmda  && cmdb  || cmdc

If cmda exits true, cmdb is executed.
If cmda exits false, cmdb is NOT executed, but cmdc is.

cmda; cmdb  && cmdc  || cmdd

How cmda exits is ignored.
If cmdb exits true, cmdc is executed.
If cmdb exits false, cmdc is NOT executed and cmdd is.

cmda  && cmdb; cmdc

If cmda exits true, cmdb is executed, followed by cmdc.
If cmda exits false, cmdb is NOT executed but cmdc is.

Huh? Why is cmdc executed?
Because to the interpretor, a semicolon(;) and a newline mean exactly the same thing. Bash sees that line of code as...

cmda  && cmdb
cmdc  

To achieve what is expected, we must enclose cmdb; cmdc inside curly braces to make them a Compound Command (group command). The additional terminating semicolon is just a requirement of the { ...; } syntax. So we get...

cmda && { cmdb; cmdc; }
If cmda exits true, cmdb is executed, followed by cmdc.
If cmda exits false, neither cmdb or cmdc is executed.
Execution continues with the next line.

Usage

Conditional command lists are most useful for returning as soon as possible from functions and thus avoiding interpreting and executing a lot of unnecessary code. Multiple function returns mean though, that one must be obsessive about keeping the functions short so it's easier to insure that all possible conditions are covered.

Here is an example from some running code...

fnInit () {
  :
  _fn="$1"
  ### fnInit "${FUNCNAME}" ...
  ### first argument MUST be name of the calling function
  #
  [[ "$2" == "--help-all" ]]  && { helpAll                      ; return 0; }
  ### pick from list of functions
  #
  [[ "$2" == "--note-all" ]]  && { noteAll                      ; return 0; }
  ### pick from notes in METAFILE
  #
  [[ "$2" == "--version"  ]]  && { versionShow "${_fn}" "${@:3}"; return 0; }
  #
  [[ "$2" == "--function" ]]  && {
    isFnLoaded "$3"           && { "${@:3}"                     ; return 0; }
    #
    errorShow functionnotfound "Unknown function:  $3"
    return 0
  }
  ### call any loaded function
  #
  [[ "$2" == "--help" || "$2" == "-h" ]]  && { noteShow "$_fn" "${@:3}"; return 0; }
  ### fnInit "${FUNCNAME}" --help or -h
  #
  return 1
}
DocSalvager
  • 2,152