Являются ли двойные квадратные скобки [[]] предпочтительнее одиночных квадратных скобок [] в Bash?

575

Сотрудник недавно заявил в обзоре кода, что [[ ]]конструкция должна быть более предпочтительной, чем [ ]конструкции, подобные

if [ "`id -nu`" = "$someuser" ] ; then 
     echo "I love you madly, $someuser"
fi

Он не мог предоставить обоснование. Есть один?

Леонард
источник
19
Будьте гибкими, иногда позволяет себе слушать совет , не требуя его глубокое объяснение :) Что касается [[с ним код хорошо и понятно, но помните тот день , когда вы будете порт ваших scriptworks по системе с оболочкой по умолчанию , который не является bashили ksh, и т. д. [уродливее, громоздче, но работает как AK-47в любой ситуации.
Ладья
178
@rook Вы можете выслушать совет без глубокого объяснения, конечно. Но когда вы запрашиваете объяснение и не получаете его, обычно это красный флаг. «Доверяй, но проверяй» и все такое.
Иосип Роден
6
Также смотрите: В чем разница между операторами Bash [[vs [vs (vs ((?) В Unix & Linux SE.
codeforester
1
@ rook другими словами «делай то, что тебе говорят, и не задавай вопросов»
глиф
@rook Это не имело смысла.
Ракиб

Ответы:

609

[[имеет меньше сюрпризов и, как правило, безопаснее в использовании. Но он не переносим - POSIX не указывает, что он делает, и только некоторые оболочки поддерживают его (кроме bash, я слышал, что ksh тоже это поддерживает). Например, вы можете сделать

[[ -e $b ]]

проверить, существует ли файл. Но с [, вы должны цитировать $b, потому что это разделяет аргумент и расширяет такие вещи, как "a*"(где [[буквально). Это также связано с тем, как [может быть внешняя программа и получать ее аргумент, как обычно, как и любая другая программа (хотя она также может быть встроенной, но тогда она по-прежнему не имеет этой специальной обработки).

[[также имеет некоторые другие полезные функции, такие как сопоставление регулярных выражений с =~операторами, как они известны в C-подобных языках. Вот хорошая страница об этом: В чем разница между тестом [и [[? и Баш тесты

Йоханнес Шауб - Литб
источник
21
Учитывая, что bash повсюду в наши дни, я склонен думать, что он чертовски портативен. Единственное распространенное для меня исключение - на платформах busybox - но вы все равно, возможно, захотите приложить для этого особые усилия, учитывая аппаратное обеспечение, на котором он работает.
оружие
14
@guns: Действительно. Я бы предположил, что ваше второе предложение опровергает ваше первое; Если рассматривать разработку программного обеспечения в целом, то «bash iswhere» и «исключение - платформы busybox» полностью несовместимы. busybox широко распространен для встраиваемых разработок.
Гонки
11
@guns: Даже если bash установлен на моем компьютере, сценарий может не выполняться (возможно, я / symlinked на какой-то вариант dash, как это стандартно в FreeBSD и Ubuntu). В Busybox есть и дополнительные странности: в настоящее время со стандартными опциями компиляции он анализирует, [[ ]]но интерпретирует его так же, как и [ ].
dubiousjim
59
@dubiousjim: Если вы используете конструкции только для bash (как эта), вы должны иметь #! / bin / bash, а не #! / bin / sh.
Ник Маттео
16
Вот почему я использую свои сценарии, #!/bin/shно затем переключаю их на использование, #!/bin/bashкак только полагаюсь на какую-то особенность BASH, чтобы обозначить, что она больше не является переносимой оболочкой Bourne.
Энтони
151

Различия в поведении

Некоторые отличия в Bash 4.3.11:

  • Расширение POSIX против Bash:

  • обычная команда против магии

    • [ это обычная команда со странным именем.

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

      Ubuntu 16.04 на самом деле имеет исполняемый файл для него, /usr/bin/[предоставляемый coreutils , но встроенная версия bash имеет преимущество.

      Ничто не изменяется в том, как Bash анализирует команду.

      В частности, <происходит перенаправление &&и ||объединение нескольких команд, ( )генерируются подоболочки, если не выполняется экранирование \, и расширение слов происходит как обычно.

    • [[ X ]]это единственная конструкция, которая делает Xбыть разобранным магическим образом. <, &&, ||И ()рассматриваются специально, и правила разбиения на слова различны.

      Есть и другие отличия, как =и =~.

      В Bashese: [это встроенная команда и [[ключевое слово: /ubuntu/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • && а также ||

    • [[ a = a && b = b ]]: правда, логично и
    • [ a = a && b = b ]: синтаксическая ошибка, &&проанализированная как разделитель команд ANDcmd1 && cmd2
    • [ a = a -a b = b ]: эквивалентно, но не рекомендуется POSIX³
    • [ a = a ] && [ b = b ]: POSIX и надежный аналог
  • (

    • [[ (a = a || a = b) && a = b ]]: ложный
    • [ ( a = a ) ]: синтаксическая ошибка, ()интерпретируется как подоболочка
    • [ \( a = a -o a = b \) -a a = b ]: эквивалентно, но ()не поддерживается POSIX
    • { [ a = a ] || [ a = b ]; } && [ a = b ] POSIX эквивалент⁵
  • разделение слов и генерация имени файла при расширениях (split + glob)

    • x='a b'; [[ $x = 'a b' ]]: правда, цитаты не нужны
    • x='a b'; [ $x = 'a b' ]: синтаксическая ошибка, расширяется до [ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: синтаксическая ошибка, если в текущем каталоге более одного файла.
    • x='a b'; [ "$x" = 'a b' ]: POSIX эквивалент
  • =

    • [[ ab = a? ]]: true, потому что он выполняет сопоставление с образцом ( * ? [это волшебство). Не расширяется до файлов в текущем каталоге.
    • [ ab = a? ]: a?glob расширяется. Так может быть истина или ложь в зависимости от файлов в текущем каталоге.
    • [ ab = a\? ]: false, не глобальное расширение
    • =и ==одинаковы в обоих [и [[, но ==это расширение Bash.
    • case ab in (a?) echo match; esac: POSIX эквивалент
    • [[ ab =~ 'ab?' ]]: false⁴, теряет магию с ''
    • [[ ab? =~ 'ab?' ]]: правда
  • =~

    • [[ ab =~ ab? ]]: true, POSIX расширенное совпадение с регулярным выражением , ?не расширяется
    • [ a =~ a ]: ошибка синтаксиса. Нет эквивалента Bash.
    • printf 'ab\n' | grep -Eq 'ab?': Эквивалент POSIX (только однострочные данные)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': POSIX эквивалент.

Рекомендация : всегда используйте [].

Есть POSIX-эквиваленты для каждой [[ ]]конструкции, которую я видел.

Если вы используете [[ ]]вас:

  • потерять мобильность
  • Заставьте читателя изучить тонкости другого расширения bash. [это обычная команда со странным именем, никакой особой семантики не требуется.

¹ Вдохновленный эквивалентной [[...]]конструкции в оболочке Korn

² но терпит неудачу для некоторых значений aили b(например, +или index) и выполняет числовое сравнение, если aи bвыглядит как десятичные целые числа. expr "x$a" '<' "x$b"работает вокруг обоих.

³, а также терпит неудачу для некоторых значений aили bкак !или (.

⁴ в bash 3.2 и выше и при условии, что совместимость с bash 3.1 не включена (как с BASH_COMPAT=3.1)

⁵ хотя группировка (здесь с {...;}командой группой вместо (...)которой будет работать ненужную подоболочку) не является необходимым , как ||и &&оболочками операторов (в отличие от ||и && [[...]]операторов или -o/ -a [операторов) имеет одинаковый приоритет. Так [ a = a ] || [ a = b ] && [ a = b ]было бы эквивалентно.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
4
Хорошие объяснения. Я пользуюсь [по тем же причинам.
Гордон
2
В Башезе :) ха. Приятное уточнение различий и причины придерживаться POSIX.
Джонатан Комар
56

[[ ]]имеет больше возможностей - я предлагаю вам взглянуть на Advanced Bash Scripting Guide для получения дополнительной информации, в частности, на расширенный раздел команды test в Главе 7. Тесты .

Кстати, как отмечается в руководстве, он [[ ]]был введен в ksh88 (версия оболочки Korn 1988 года).

Нильс фон Барт
источник
5
Это далеко не башизм, он впервые был введен в оболочку Корна.
Хенк Лангевелд
6
@Thomas, ABS на самом деле считается очень плохой ссылкой во многих кругах; в то время как у него есть много точной информации, он, как правило, очень мало заботится о том, чтобы избежать демонстрации плохих практик в своих примерах, и большую часть своей жизни провел без всякой поддержки.
Чарльз Даффи
@CharlesDuffy спасибо за ваш комментарий, вы можете назвать хорошую альтернативу. Я не специалист по написанию сценариев оболочки, я ищу руководство, которое могу посоветовать для написания сценария примерно раз в полгода.
Томас,
9
@Thomas, Wooledge BashFAQ и связанные с ним вики - вот что я использую; они активно поддерживаются обитателями канала Freenode #bash (которые, хотя иногда и колючие, имеют тенденцию глубоко заботиться о правильности). BashFAQ # 31, mywiki.wooledge.org/BashFAQ/031 , является страницей, имеющей непосредственное отношение к этому вопросу.
Чарльз Даффи
13

От Which компаратора, тест, кронштейн или двойной кронштейн, является самым быстрым? ( http://bashcurescancer.com )

Двойная скобка - это «составная команда», где в качестве теста и одиночная скобка являются встроенными в оболочку (и на самом деле это одна и та же команда). Таким образом, одинарные и двойные скобки выполняют разные коды.

Тест и одиночная скобка являются наиболее переносимыми, поскольку существуют как отдельные, так и внешние команды. Однако, если вы используете какую-либо удаленно современную версию BASH, двойная скобка поддерживается.

f3lix
источник
7
Что за одержимость самых быстрых скриптов? Я хочу, чтобы он был наиболее портативным и не заботился о том, что [[может принести улучшение . Но тогда я старый пердун старой школы :-)
Jens
1
Я думаю , что многие снаряды доказать встроенную версию [и testдаже хотя внешние варианты существуют.
dubiousjim
4
@ Дженс, в общем, я согласен: основная цель сценариев - это (была?) Переносимость (в противном случае мы будем кодировать и компилировать, а не сценарии) ... я могу придумать два исключения: (1) завершение табуляции (где сценарии завершения могут быть очень длинными с большим количеством условной логики); и (2) супер-подсказки ( PS1=...crazy stuff...и / или $PROMPT_COMMAND); для них я не хочу заметной задержки в выполнении сценария.
Майкл
1
Некоторые из одержимости самым быстрым - это просто стиль. При прочих равных условиях, почему бы не включить более эффективную конструкцию кода в стиль по умолчанию, особенно если эта конструкция также обеспечивает большую читабельность? Что касается переносимости, многие задачи, для которых подходит bash, по своей сути непереносимы. Например, мне нужно бежать, apt-get updateесли прошло больше X часов с момента последнего запуска. Это большое облегчение, когда можно оставить переносимость в уже слишком длинном списке ограничений для кода.
Рон Берк
5

Если вам нравится следующее руководство по стилю Google :

Тест [и[[

[[ ... ]]уменьшает ошибки, так как между [[и не происходит раскрытие имени пути или разбиение слов ]], и [[ ... ]]позволяет сопоставлять регулярные выражения там, где [ ... ]этого не происходит.

# This ensures the string on the left is made up of characters in the
# alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi

# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi
crizCraig
источник
1
Google написал: " расширение пути не происходит ... происходит ", но [[ -d ~ ]]возвращает true (что подразумевает ~расширение до /home/user). Я думаю, что Google должен был быть более точным в своем письме.
JamesThomasMoon1979
2

Типичная ситуация, когда вы не можете использовать [[это в скрипте autotools configure.ac, где скобки имеют особое и другое значение, поэтому вам придется использовать testвместо [или [[- Обратите внимание, что тестируют и [являются одной и той же программой.

Висенте Болея
источник
2
Учитывая, что автоинструменты не являются оболочкой POSIX, почему вы ожидаете, [что их определят как функцию оболочки POSIX?
Гордон
Потому что сценарий autoconf выглядит как сценарий оболочки, и он создает сценарий оболочки, и внутри него работает большинство команд оболочки.
vy32
-1

[[]] двойные скобки не поддерживаются в определенной версии SunOS и полностью не поддерживаются внутри объявлений функций: GNU bash, версия 2.02.0 (1) -релиз (sparc-sun-solaris2.6)

мусорщик
источник
1
очень верно, и совсем не несущественно. Переносимость bash между старыми версиями должна быть рассмотрена. Люди говорят: «bash является вездесущим и переносимым, за исключением, может быть, (вставьте здесь эзотерическую ОС) », - но по моему опыту, Solaris - одна из тех платформ, где особое внимание должно быть уделено переносимости: не только для рассмотрения более старых версий bash на более новая ОС, проблемы / ошибки с массивами, функциями и т.д .; но даже утилиты (используемые в скриптах), такие как tr, sed, awk, tar имеют странности и особенности на солярисе, которые вам нужно обойти.
Майкл
2
вы так правы ... так много утилит не POSIX в Solaris, просто посмотрите на вывод "df" и аргументы ... Позор Sun. Надеюсь, он постепенно исчезает (за исключением Канады).
мусорщик
7
Солярис 2.6 серьезно? Он был выпущен в 1997 году и прекратил поддержку в 2006 году. Я думаю, если вы все еще используете это, у вас есть другие проблемы !. Кстати, он использовал Bash v2.02, в котором были введены двойные скобки, поэтому он должен работать даже на чем-то старом. Solaris 10 от 2005 года использовал Bash 3.2.51, а Solaris 11 от 2011 года использовал Bash 4.1.11.
Петр
1
Re Script переносимость. В системах Linux обычно есть только редакция каждого инструмента, и это редакция GNU. В Solaris у вас обычно есть выбор между версией Solaris и GNU (скажем, Solaris tar против GNU tar). Если вы зависите от конкретных расширений GNU, вы должны указать это в своем скрипте, чтобы он был переносимым. В Solaris вы делаете это с помощью префикса «g», например, «ggrep», если вы хотите использовать GNU grep.
Петер
-2

Короче говоря, [[лучше, потому что он не разворачивает другой процесс. Никакие скобки или одиночные скобки медленнее, чем двойные, потому что они разветвляются на другой процесс.

unix4linux
источник
8
Test и [являются именами одной и той же встроенной команды в bash. Попробуйте использовать, type [чтобы увидеть это.
AB
1
@alberge, это правда, но [[, в отличие от [синтаксиса, интерпретируется интерпретатором командной строки bash. В bash попробуйте набрать type [[. Unix4linux правильно, что хотя классические [тесты Bourne-shell запускают новый процесс для определения значения истинности, [[синтаксис (заимствованный из ksh посредством bash, zsh и т. д.) - нет.
Тим Гилберт
2
@ Тим, я не уверен, о какой оболочке Bourne вы говорите, но [она встроена в Bash и Dash ( /bin/shво всех дистрибутивах Linux, производных от Debian).
AB
1
О, я понимаю, что вы имеете в виду, это правда. Я думал о чем-то вроде, скажем, / bin / sh в старых системах Solaris или HP / UX, но, конечно, если вам нужно было быть совместимым с теми, которые вы не использовали бы [[либо.
Тим Гилберт
1
@alberge Bourne shell не является Bash (он же Bourne Again SHell ).
kiamlaluno