Является ли тест или [или [[более переносимым как между оболочками Bash, так и между другими оболочками?

44

Я вижу, я могу сделать

$ [ -w /home/durrantm ] && echo "writable"
writable

или

$ test -w /home/durrantm && echo "writable"
writable

или

$ [[ -w /home/durrantm ]] && echo "writable"
writable

Мне нравится использовать третий синтаксис. Они эквивалентны во всех отношениях и для всех отрицательных и краевых случаев? Существуют ли различия в переносимости, например, между bash в Ubuntu и в OS X или более старыми / более новыми версиями bash, например, до / после 4.0, и они оба расширяют выражения одинаково?

Майкл Даррант
источник
2
Для [ … ]против [[ … ]]против test …, есть более полные ответы в этом основном дубликата вопроса .
Жиль "ТАК - перестань быть злым"
Конкретный вопрос проверки возможности записи файла см. Также Как неинвазивно проверять доступ на запись в файл?
G-Man говорит: «Восстановите Монику»
1
Существует поговорка: «нет переносимого кода, только код, который был перенесен». Мой совет по этому поводу: используйте наиболее читаемую форму (вероятно, [[...]]) и попробуйте ее на всех платформах, которые вы хотите поддерживать. Нет смысла скрывать ваши сценарии, поэтому они работают на древних платформах, которые не используются ни вами, ни вашей целевой аудиторией. Это просто сделает ваш код трудным для чтения, внесет ненужные ошибки и, возможно, даже проблемы с безопасностью (как это было для openssl).
stefan.schwetschke

Ответы:

31

[является синонимом testкоманды и является одновременно встроенной командой bash и отдельной командой. Но [[является ключевым словом bash и работает только в некоторых версиях. Так что из соображений портативности вам лучше использовать один []илиtest

[ -w "/home/durrantm" ] && echo "writable"
Костас
источник
2
[встроен POSIX или Bash?
Сандбург
@Sandburg POSIX - это стандарт, поэтому он не может иметь встроенных модулей. Он не определяет, должно ли что-то быть встроено в интерпретатор или нет, просто то, что он должен делать. Эти правила применяются как к формам, так testи к ним [. Если оболочка может выполнять оценку самостоятельно, это экономит процесс и делает его несколько быстрее, но результат должен быть таким же.
Бахсау
@Bachsau, который до сих пор не отвечает на вопрос Сэндбурга относительно того [, определяется ли POSIX поведение или нет, и поэтому максимально переносимо (что, по-моему, и есть смысл в этом вопросе, если я не ошибаюсь)
JamesTheAwesomeDude
@Sandburg (и кому-либо еще, сталкивающемуся с этим): Да, похоже, и то, [и другое test - POSIX . Кажется, не существует ни одного «рекомендованного», хотя единственное различие (?), Которое я могу заметить между ними, заключается в том, что testаргумент «не должен распознавать» - «[как разделитель, указывающий конец параметров]» (? - подразумевает, что » -» будет признан истекшим аргументов в пользу [, которую я не могу на самом деле придумать хороший тестовый пример для так или иначе но я отвлекся использование которого будет IMO,... [выглядит элегантно, но testisclearly команда)
JamesTheAwesomeDude
35

Да, есть различия. Наиболее портативными являются testили [ ]. Они оба являются частью спецификации POSIXtest .

if ... fiКонструкция также определяется POSIX и должен быть полностью портативным.

[[ ]]Это kshфункция , которая также присутствует в некоторых версиях bash( все современные ), в zshи , возможно , в других , но не присутствует в shили dashили различных других простых оболочках.

Таким образом, чтобы сделать ваши скрипты портативными, использовать [ ], testили if ... fi.

Тердон
источник
10
Просто заметьте, bashи zshподдерживали [[в течение очень долгого времени ( bashдобавили его в конце 90-х, zshне позднее 2000 года, и я был бы удивлен, если бы в нем вообще не было поддержки), так что вы вряд ли встретите какую-либо версию без нее [[. Обнаружение другой POSIX-совместимой оболочки (например, dash) гораздо более вероятно.
chepner
1
Одна интересная особенность [[заключается в том, что расширения параметров не нужно заключать в кавычки: [[-f $file]]событие работает, если $fileсодержит пробельные символы.
helpermethod
2
@helpermethod также, встроенные регулярные выражения[[ $a =~ ^reg.*exp.*$' ]]
GnP
20

Обратите внимание, что [] && cmdэто не то же самое, что if .. fiстроительство.

Иногда его поведение довольно похоже, и вы можете использовать [] && cmdвместо if .. fi. Но только иногда. Если у вас есть более одной команды для выполнения, если условие или вам нужно if .. else .. fiбыть осторожным и прорабатывать логику.

Пара примеров:

[ -z "$VAR" ] && ls file || echo wiiii

Это не то же самое, что

if [ -z $VAR ] ; then
  ls file
else
  echo wiii
fi

потому что, если lsне удастся, echoбудет выполнено, что не произойдет с if.

Другой пример:

[ -z "$VAR" ] && ls file && echo wiii

это не то же самое, что

if [ -z "$VAR" ] ; then
   ls file
   echo $wiii
fi

хотя эта конструкция будет действовать так же

[ -z "$VAR" ] && { ls file ; echo wiii ; }

пожалуйста, обратите внимание, ;после того, как эхо важно и должно быть там.

Таким образом, возобновив утверждение выше, мы можем сказать,

[] && cmd == если первая команда успешна, выполнить следующую

if .. fi == если условие (которое может быть и тестовой командой), затем выполнить команду (ы)

Так что для переносимости [и [[использования [только.

ifсовместим с POSIX. Так что, если вам придется выбирать между [и ifсмотреть на вашу задачу и ожидаемое поведение.

порыв
источник
Я, вероятно, просто плотный, но я не вижу, в чем разница ... не могли бы вы привести пример, где эти две конструкции дали бы разные результаты?
evilsoup
1
@evilsoup, обновлено. Может быть, я не лучший объяснитель, хотя я надеюсь, что это будет ясно сейчас.
Раш
1
Очень хорошие моменты, спешка. Я полагаю , безопасная форма вашего 1 - го примера будет: [ -z "$VAR" ] && { ls file; true; } || echo wiiii. Это немного более многословно, но все же короче, чем if...fiконструкция.
PM 2Ring
Сделал вопрос о [vs [[vs test, а не о том, если ... fi vs &&, чтобы сделать его ОДНЫМ вопросом. К сожалению, из-за этого ответ кажется неуместным. Извините за то, что вопрос не был сфокусирован изначально, что привело к этому. Живи и (старайся) учиться. :)
Майкл Даррант
Я понизил голос, потому что а) это в основном хороший ответ, но это ответ на другой вопрос. б) вы представляете [и ifкак субтитры, но это не так. Это на самом деле, &&что заменяет if. [выполняет некоторый код и возвращает статус, очень как lsи grepбудет. ifвыполнение ветвлений зависит от состояния возврата команды (оператора), заданного после if, это может быть любая команда (оператор). &&выполняет следующий оператор, только если предыдущий вернул 0, очень похоже на простой if..then..fi.
GnP
9

На самом деле &&это заменяет оператор а if, а не test: ifоператор в сценариях оболочки проверяет, вернула ли команда «успешный» (нулевой) статус выхода; в вашем примере команда есть [.

Итак, на самом деле есть две вещи, которые вы меняете здесь: команда, используемая для запуска теста, и синтаксис, используемый для выполнения кода, основанного на результате этого теста.

Тестовые команды:

  • testявляется стандартизированной командой для оценки свойств строк и файлов; в вашем примере вы запускаете командуtest -w /home/durrantm
  • [псевдоним этой команды, одинаково стандартизированный, который имеет обязательный последний аргумент ], чтобы выглядеть как выражение в скобках; не обманывайте себя, это все же просто команда (вы можете даже обнаружить, что в вашей системе есть файл с именем /bin/[)
  • [[является расширенной версией тестовой команды, встроенной в некоторые оболочки, но не являющейся частью того же стандарта POSIX; он включает в себя дополнительные опции, которые вы не используете здесь

Условные выражения:

  • &&Оператор ( стандартизован здесь ) выполняет логическую операцию, путем оценки двух команд и возврата 0 (который представляет собой истинно) , если они оба возвращают 0; она будет оценивать вторую команду только в том случае, если первая вернула ноль, поэтому ее можно использовать как простую условную
  • if ... then ... fiКонструкция ( стандартизовано здесь ) использует тот же метод судить «правду», но позволяет получить список соединений операторов в thenпредложении, а не одной команды , обеспечиваемой с помощью &&короткого замыкания, а также обеспечивает elifи elseположение, которые трудно писать используя только &&и ||. Обратите внимание, что в ifвыражении нет скобок вокруг условия .

Итак, нижеприведенные примеры одинаково переносимы и полностью эквивалентны визуализации вашего примера:

  • test -w /home/durrantm && echo "writable"
  • [ -w /home/durrantm ] && echo "writable"
  • if test -w /home/durrantm; then echo "writable"; fi
  • if [ -w /home/durrantm ]; then echo "writable"; fi

Хотя следующие также эквивалентны, но менее переносимы из-за нестандартного характера [[:

  • [[ -w /home/durrantm ]] && echo "writable"
  • if [[ -w /home/durrantm ]]; then echo "writable"; fi
IMSoP
источник
Да, я удалил часть if .... fi, чтобы это было 1 вопрос.
Майкл Даррант
7

Для переносимости используйте test/ [. Но если вам не нужна переносимость, ради здравомыслия вы и другие, читающие ваш сценарий, используете [[. :)

Также смотрите What is the difference between test, [ and [[ ?в BashFAQ .

PM 2Ring
источник
7

Если вы хотите переносимости за пределы мира, похожего на Борн, то:

test -w /home/durrantm && echo writable

самый портативный. Работает как в снарядах Борна, так cshи в rcсемьях.

test -w /home/durrantm && echo "writable"

результат будет "writable"вместо writableв оболочках rcсемьи ( rc, es, akanga, где "не специальный).

[ -w /home/durrantm ] && echo writable

не будет работать в оболочках cshили rcсемейств в системах, в которых нет [команды $PATH(известно, что некоторые имеют, testно не ее [псевдоним).

if [ -w /home/durrantm ]; then echo writabe; fi

работает только в оболочках семьи Борн.

[[ -w /home/durrantm ]] && echo writable

работает только в ksh(где он возник) zshи bash(все 3 в семье Борн).

Никто не будет работать в fishоболочке, где вам нужно:

[ -w /home/durrantm ]; and echo writable

или:

if [ -w /home/durrantm ]; echo writable; end
Стефан Шазелас
источник
0

Моя самая важная причина для выбора либо if foo; then bar; fiили foo && barявляется ли статус завершения всей команды важно.

Для сравнения:

#!/bin/sh
set -e
foo && bar
do_baz

с:

#!/bin/sh
set -e
if foo; then bar; fi
do_baz

Вы можете подумать, что они делают то же самое; однако если fooпроизойдет сбой (или будет ложным, в зависимости от вашей точки зрения), то в первом примере do_baz не будет выполнен, так как сценарий завершится ... Команда set -eдает команду оболочке немедленно выйти, если какая-либо команда вернет ложное состояние. Очень полезно, если вы делаете такие вещи, как:

cd /some/directory
rm -rf *

Вы не хотите, чтобы скрипт продолжал работать, если cdпо какой-либо причине произошел сбой.

wurtel
источник
1
Нет сбой fooне прервет сценарий в любом случае. Это особый случай для set -e(когда команда оценивается как условие (слева от некоторых && / || или в условиях if / while / before / elsif ...). В barобоих случаях сбой приведет к выходу из оболочки.
Стефан Шазелас
2
Вы хотите cd /some/directory && rm -rf -- *или cd /some/directory || exit; rm -rf -- *(все еще не удаляет скрытые файлы). Мне лично не нравится идея использовать set -eв качестве предлога, чтобы не пытаться написать правильный код.
Стефан Шазелас