Разница между [[expr1 || expr2]] и [[expr1]] || [[expr2]]

7

Рассмотрим два условных выражения expr1и expr2, например, $i -eq $jи $k -eq $l. Мы можем написать это bashнесколькими способами. Вот две возможности

[[ expr1 || expr2 ]]

[[ expr1 ]] || [[ expr2 ]]

Я вполне уверен, что видел здесь рекомендации о том, что второе предпочтительнее, но я не могу найти доказательств, подтверждающих это.

Вот пример сценария, который, кажется, демонстрирует, что нет никакой разницы:

for i in 0 1
do
  for j in 0 1
  do
    for k in 0 1
    do
      for l in 0 1
      do
        if [[ $i -eq $j || $k -eq $l ]]; then printf "1-yes\t"; else printf "1-no\t"; fi
        if [[ $i -eq $j ]] || [[ $k -eq $l ]]; then printf "2-yes\n"; else printf "2-no\n"; fi
      done
    done
  done
done

и вывод, показывающий, что обе условные конструкции дают одинаковый результат:

1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-no    2-no
1-no    2-no
1-yes   2-yes
1-yes   2-yes
1-no    2-no
1-no    2-no
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes

Есть ли польза от использования одной конструкции над другой?

Для бонусных баллов тот же вопрос, но обобщающий несколько условий, используя ||и &&. Например, [[ expr1 && expr2 || expr3 ]].

roaima
источник
Относительно того, почему рекомендации такого типа часто даются в отношении [или test( не [[ ), найдите OBтеги (связанные с «устаревшим» определением) в спецификации POSIXtest . В test, есть некоторые патологические случаи , когда это может быть невозможно сказать , если (или )предназначается , чтобы быть синтаксисом смысл команды испытаний или строки , которые будут проверены, так что люди , которые игнорируют маркеры устаревания и использовать этот синтаксис может фактически нужен otherwise- устаревшая "x$foo"практика; с [[, это не проблема.
Чарльз Даффи

Ответы:

8

Я думаю, что рекомендация, которую вы видели, была для POSIX sh и / или testкоманды, которая удваивается как [команда, а не как [[конструкция, которая появилась в ksh (спасибо Stéphane Chazelas за подсказку), а также используется, например, в bash, zsh и некоторых других снаряды.

В большинстве языков, таких как C, когда условие уже известно как истинное или ложное, нет необходимости оценивать оставшиеся части в зависимости от операции: если true не после логической или следующей за ней, если false не после логической и , и т.д. Это, конечно, позволяет, например, остановиться, когда указатель равен NULL, и не пытаться разыменовать его в следующем предложении.

Но ш «s [ expr1 -o expr2 ]конструкция (включая реализацию в Bash) не делает этого: он всегда оценивает обе стороны, когда один хотел бы только выражение1 быть оценены. Это могло быть сделано для совместимости с testреализацией команды. С другой стороны, sh ||и &&действительно следуют обычному принципу: не оценивается, если это не изменит результат.

Таким образом, разница в примечании будет:

: > /tmp/effect #clear effect
if [ 1 -eq 1 -o $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]; then
    echo true;
fi
cat /tmp/effect

что дает:

true
or-was-evaluated

Выше каждый [мог быть заменен /usr/bin/[на псевдоним testкоманды, который использовался до того, как [был встроен в оболочку.

Пока две следующие конструкции:

: > /tmp/effect #clear effect
if [ 1 -eq 1 ] || [ $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]; then
    echo true;
fi
cat /tmp/effect

или

: > /tmp/effect #clear effect
if [[ 1 -eq 1 || $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]]; then
    echo true;
fi
cat /tmp/effect

Будет только уступать trueи оставлять effectпустым: ||ведет себя правильно, и [[исправил эту проблему тоже.


ОБНОВИТЬ:

Как прокомментировал @ StéphaneChazelas, я пропустил несколько различий, связанных с первоначальным вопросом. Я просто поставлю здесь самый важный (по крайней мере для меня): приоритет операторов.

Пока оболочка не будет учитывать приоритет:

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

Выход (потому что нет приоритета и, следовательно, сначала true || trueоценивается, а затем && false):

false

внутри [[ ]]на &&оператора имеет приоритет над ||:

if [[ 1 -eq 1 || 1 -eq 1 && 1 -eq 0 ]]; then
    echo true
else
    echo false
fi

Урожай (потому что 1 -eq 1 && 1 -eq 0он сгруппирован и, следовательно, является вторым членом ||):

true

по крайней мере для кш , баш , зш .

Так [[ ]]улучшилось поведение как [ ]логических операторов прямой, так и логической оболочки.

AB
источник
1
[ x -o y ]не нужно оценивать обе стороны. В то время как GNU testили [встроенный в bashdo, вы обнаружите, strace zsh -c '[ x -o -f /x ]'что [не пытается stat () /x. То же самое с mksh. Но это правда , что -oи -aсерьезно нарушена, не может быть использована надежно и устаревшим POSIX.
Стефан
1
Одно отличие состоит в том [[...]], что внутри , &&имеет более высокий приоритет, чем ||, в то время как снаружи они имеют равный приоритет. Сравните ksh -c '[[ x || x && "" ]]'противsh -c 'true || true && false'
Стефан
1
Обратите внимание, что стандарт [/ testутилита не имеет ==оператора. Оператор равенства =(обратите внимание , что внутри КШ это [[...]], =/ ==не операторы равенства, но согласующий шаблон из них).
Стефан
1
Это наоборот. &&имеет приоритет над ||внутренним [[...]](или ((...))) , но не &&и ||оболочками операторы. [[ x || y && z ]]is x || (y && z)while x || y && zis (x || y) && z(операторы вычисляются слева направо без приоритета). Подобно тому, как *имеет приоритет над +( 1 + 2 * 3есть 1 + (2 * 3)), но +и -имеет такой же приоритет.
Стефан
1
Если &&имеет приоритет над ||, то это должно быть true || (true && false), вместо(true || true) && false
Prvt_Yadav