Являются ли оператор if else логическим и && или || и где я должен предпочесть один другому?

27

Я узнаю о структурах принятия решений, и я столкнулся с этими кодами:

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


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

Они оба ведут себя одинаково. Есть ли какие-то преимущества в использовании одного способа из другого?

Субхаа Чандар
источник
stackoverflow.com/questions/4510640/…
Джордж Василиу
3
Они не эквивалентны. Смотрите отличный ответ от Икара. Например, рассмотрим случай, когда ./myfile существует, но не читается.
AlexP

Ответы:

26

Нет, конструкция if A; then B; else C; fiи A && B || Cявляется не эквивалентна .

С помощью if A; then B; else C; fiкоманда Aвсегда оценивается и выполняется (по крайней мере, делается попытка выполнить ее), а затем оценивается и выполняется либо команда, Bлибо команда C.

С A && B || C, это же для команд Aи , Bно разные для C: команды Cвычисляется и выполняется , если либо A не удается или B не удается.

В вашем примере, предположим, что вы chmod u-r ./myfile, несмотря на [ -f ./myfile ]успехи, вы будетеcat /home/user/myfile

Мой совет: используйте A && Bили A || Bвсе, что вы хотите, это остается легко читать и понимать, и ловушки нет. Но если вы имеете в виду, если ... тогда ... еще ... тогда используйте if A; then B; else C; fi.

xhienne
источник
29

Большинству людей легче понять if... then... else... fiформу.

Для a && b || c, вы должны быть уверены, что bвозвращает истину. Это является причиной незначительных ошибок и является хорошей причиной, чтобы избежать этого стиля. Если b не возвращает true, это не то же самое.

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

Для очень коротких тестов и действий, у которых нет условия else, укороченная длина является привлекательной, например

 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

&&и ||это short circuiting operators, как только результат известен дальнейшие ненужные тесты пропускаются. a && b || cсгруппирован как (a && b) || c. Первый a- это бег. Если он failsопределен как не возвращающий состояние выхода 0, то группа (a && b)известна failи bзапускать ее не нужно. ||Не знает результат выражения так нужно выполнить c. Если aуспешно (возвращает ноль), то &&оператор еще не знает результат, a && bпоэтому должен бежать, bчтобы узнать. Если bуспешно, то a && bуспешно и ||знает, что общий результат - успех, поэтому запускать не нужно c. Если bне получится||все еще не знает значение выражения, поэтому нужно запустить c.

Икар
источник
7

Оператор && выполняет следующую команду, если предыдущая команда прошла успешно (возвращенный код выхода ($?) 0 = логическое истина).

В форме A && B || Cкоманда (или условие) A оценивается, и если A возвращает true (успех, код выхода 0), то команда B выполняется. Если A потерпит неудачу (таким образом, вернет false - код выхода, отличный от 0), и / или B завершится неудачно ( вернет false ), тогда будет выполнена команда C.

Также &&оператор используется как AND в проверках условий, а оператор ||работает как OR в проверках условий.

В зависимости от того, что вы хотите сделать со своим сценарием, форму A && B || Cможно использовать для проверки условий, как в вашем примере, или можно использовать для цепочки команд и обеспечения выполнения серии команд, если предыдущие команды имели успешный код завершения 0 .
Именно поэтому часто можно увидеть такие команды , как:
do_something && do_something_else_that_depended_on_something.

Примеры:
apt-get update && apt-get upgrade если обновление завершится неудачно, то обновление не будет выполнено (имеет смысл в реальном мире ...).

mkdir test && echo "Something" > test/file
Эта часть echo "Something"будет выполнена, только если она mkdir testпрошла успешно и операция вернула код завершения 0 .

./configure --prefix=/usr && make && sudo make install
Обычно встречается при компиляции заданий для объединения необходимых зависимых команд.

Если вы попытаетесь реализовать вышеупомянутые «цепочки» с помощью if - then - иначе вам понадобится гораздо больше команд и проверок (и, следовательно, больше кода для написания - больше ошибок) для простой задачи.

Также имейте в виду, что связанные команды с && и || читаются оболочкой слева направо. Вам может потребоваться сгруппировать команды и проверки условий в скобках, чтобы следующий шаг зависел от успешного вывода некоторых предыдущих команд. Например, посмотрите это:

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

Или пример из реальной жизни:

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

Имейте в виду, что некоторые команды возвращают разные коды выхода в зависимости от выполняемого процесса или возвращают разные коды в зависимости от их действий (например, команда GNU diffвозвращает 1, если два файла различаются, и 0, если их нет). К таким командам нужно относиться с осторожностью в && и || ,

Также просто, чтобы собрать всю головоломку, помните о конкатенации команд с помощью ;оператора. В формате A;B;Cвсе команды будут выполняться последовательно, независимо от того, какой был код выхода команды Aи B.

Георгий Василиу
источник
1

Большая часть путаницы по этому поводу может быть связана с документацией bash, вызывающей эти списки AND и OR . Хотя логически похожи на &&и ||нашли внутри квадратные скобки, они действуют по- разному.

Некоторые примеры могут проиллюстрировать это лучше всего ...

ПРИМЕЧАНИЕ. Одинарные и двойные квадратные скобки ( [ ... ]и [[ ... ]]) являются собственными командами, которые выполняют сравнение и возвращают код выхода. Им на самом деле не нужно if.

cmda  && cmdb  || cmdc

Если cmdaвыход истина, cmdbвыполняется.
Если cmdaвыходит false, cmdbНЕ выполняется, но cmdcесть.

cmda; cmdb  && cmdc  || cmdd

Как cmdaвыходы игнорируются.
Если cmdbвыход истина, cmdcвыполняется.
Если cmdbвыходит false, cmdcНЕ выполняется и cmddесть.

cmda  && cmdb; cmdc

Если cmdaвыход true, cmdbвыполняется, а затем cmdc.
Если cmdaвыходит false, cmdbНЕ выполняется, но cmdcесть.

А? Почему cmdcвыполняется?
Потому что для интерпретатора точка с запятой ( ;) и символ новой строки означают одно и то же. Баш видит эту строку кода как ...

cmda  && cmdb
cmdc  

Чтобы достичь ожидаемого, мы должны заключить cmdb; cmdcв фигурные скобки, чтобы сделать их составной командой (групповой командой) . Дополнительная завершающая точка с запятой является лишь требованием { ...; }синтаксиса. Итак, мы получаем ...

cmda && { cmdb; cmdc; }
Если cmdaвыход true, cmdbвыполняется, а затем cmdc.
Если cmdaвыходит ложная, ни cmdbили cmdcвыполняется.
Выполнение продолжается со следующей строки.

использование

Условные списки команд наиболее полезны для скорейшего возврата из функций и, таким образом, для избежания интерпретации и выполнения большого количества ненужного кода. Многократные возвраты функций означают, однако, что нужно быть одержимым короткими функциями, чтобы легче было охватить все возможные условия.

Вот пример из бегущего кода ...

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
источник