Почему `[` является встроенной оболочкой, а `[[` - ключевым словом оболочки?

64

Насколько я знаю, [[это расширенная версия [, но я смущен, когда вижу [[в качестве ключевого слова и [отображается как встроенный.

[root@server ~]# type [
[ is a shell builtin
[root@server ~]# type [[
[[ is a shell keyword

TLDP говорит

Встроенный может быть синонимом системной команды с тем же именем, но Bash реализует ее внутренне. Например, команда Bash echo отличается от команды / bin / echo, хотя их поведение практически идентично.

а также

Ключевое слово - это зарезервированное слово, токен или оператор. Ключевые слова имеют особое значение для оболочки и действительно являются строительными блоками синтаксиса оболочки. В качестве примеров, пока, делай, и! являются ключевыми словами. Подобно встроенному, ключевое слово жестко запрограммировано в Bash, но в отличие от встроенного, ключевое слово само по себе не является командой, а представляет собой субъединицу командной конструкции. [2]

Разве это не должно быть как [и [[ключевое слово? Есть ли что-то, что мне здесь не хватает? Кроме того, эта ссылка подтверждает, что оба [и [[должны принадлежать к одному и тому же виду.

Sree
источник
2
См. Unix.stackexchange.com/a/56674
Стефан
9
/ bin / [существует на моей машине.
Джошуа
2
В качестве простой демонстрации одной разницы между ними: if "[" $x -eq 3 ]работает , как ожидалось (поскольку Bash выглядит для команды под названием [, и это есть), но if "[[" $x -eq 3 ]]ничего не работает (потому что еще раз Bash ищет команду соответствующего имени, но нет [[команда).
Кайл Стрэнд
1
@ Джошуа Так и делает /usr/bin/echo, но это не значит, что это не встроенная функция .
Джонатон Рейнхарт
Все существующие булитины также анализируют аргументы, если они не были встроенными.
Джошуа

Ответы:

80

Разница между [и [[является довольно фундаментальной.

  • [это команда. Его аргументы обрабатываются так же, как и любые другие аргументы команд. Например, рассмотрим:

    [ -z $name ]

    Оболочка развернется $nameи выполнит как разбиение по словам, так и генерацию имени файла для результата, как и для любой другой команды.

    Например, следующее не удастся:

    $ name="here and there"
    $ [ -n $name ] && echo not empty
    bash: [: too many arguments

    Для правильной работы цитаты необходимы:

    $ [ -n "$name" ] && echo not empty
    not empty
  • [[является ключевым словом оболочки и его аргументы обрабатываются в соответствии со специальными правилами. Например, рассмотрим:

    [[ -z $name ]]

    Оболочка будет расширяться, $nameно, в отличие от любой другой команды, она не будет выполнять ни разбиение слов, ни генерацию имени файла для результата. Например, следующее будет выполнено успешно, несмотря на наличие пробелов в name:

    $ name="here and there"
    $ [[ -n $name ]] && echo not empty
    not empty

Резюме

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

Поскольку [[это ключевое слово, а не команда, оболочка обрабатывает его специально и работает по совершенно другим правилам.

John1024
источник
+1. Благодарю. Не могли бы вы указать источник (ссылку), по каким правилам действуют команда и ключевое слово?
Тим
@Tim Правила, по которым работают команды , подробно описаны в man bash. См., В частности, разделы, озаглавленные «ПРОСТОЕ РАСПРОСТРАНЕНИЕ КОМАНД» и «КОМАНДНОЕ ИСПОЛНЕНИЕ». В дополнение к [[, другие Баш ключевые слова включают if, then, whileи «дело». Для ключевых слов нет общих правил: каждое ключевое слово является особым случаем. man bash включает в себя детали для каждого.
John1024
62

В V7 Unix - где дебютировала оболочка Bourne - [назывался testи существовал только как /bin/test. Итак, код, который вы написали бы сегодня как:

if [ "$foo" = "bar" ] ; then ...

вы бы написали вместо этого как

if test "$foo" = "bar" ; then ...

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

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

[и testможет быть реализовано с использованием того же кода. Это относится /bin/[и /bin/testк OS X, где это жесткие ссылки на один и тот же исполняемый файл. В результате реализация полностью игнорирует завершающий элемент ]: она не требуется, если вы вызываете ее как /bin/[, и не жалуется если вы делаете предоставить его /bin/test.⁴

Ничто из этой истории не влияет [[, потому что никогда не было изначальной программы под названием [[. Он существует исключительно внутри тех оболочек, которые реализуют его как расширение оболочки POSIX .

Часть различия между «встроенным» и «ключевым словом» обусловлена ​​этой историей. Это также отражает тот факт, что синтаксические правила для синтаксического анализа [[выражений отличаются, как указано в ответе Джона 1024.⁵


Примечания:

  1. Когда вы смотрите на это таким образом, становится ясно, почему вы должны помещать пробелы [в сценарии оболочки, в отличие от того, как скобки и скобки работают в большинстве других языков программирования. Если синтаксический анализатор команд оболочки разрешил if["$x"..., он также должен был бы разрешитьiftest"$x"...

  2. Это произошло около 1980 года. /bin/[В моей копии Ancient Unix V7 от 1979 года не существует, и не man testдокументирует ее как псевдоним. В соответствующей записи мужчина страницы я имею в пре-релиз копию системы III руководства с 1980 года, он будет в списке.

  3. ls -i /bin/[ /bin/test

  4. Но не рассчитывайте на это поведение. Встроенная версия Bash [требует закрытия ], и ее встроенная testреализация будет жаловаться, если вы ее предоставите.

  5. Различие между встроенными и внешними командами также может иметь значение по другой причине: две реализации могут вести себя по-разному. Это относится ко echoмногим системам. Поскольку существует только одна реализация, нет необходимости проводить такое различие для ключевого слова.

Уоррен Янг
источник
Спасибо @Warren Young, но почему builtinи keywordразличие между [и , [[когда оба обеспечивает те же функциональные возможности ( за исключением того , что [[приходит с большим количеством функций , чем [)?
Шри
2
@sree, [[будучи ключевым словом, позволяет bash делать то, что невозможно, [например, цитирование не нужно много времени, потому что оболочка знает, что это переменная. То есть на обработку командной строки влияет использование ключевого слова, а не использование встроенного - это происходит намного позже.
Муру
1
Обратите внимание, что код для оболочки V7 Bourne показывает [встроенную функцию , но код закомментирован.
Стефан Шазелас
cdявляется встроенным, но он ничего не скрывает ( cdне может быть реализован как внешняя программа).
Паŭло Эберманн
2
Это превосходное историческое резюме, но оно не учитывает важную деталь (IMO), указанную Муру выше и Джоном 1024 в своем ответе, что создание [[ключевого слова позволяет оболочке использовать специальные правила синтаксического анализа для своих аргументов. Таким образом, увы, мой upvote идет к John1024.
Ильмари Каронен,
3

[изначально была просто внешней командой, другое название для /bin/test. Но некоторые команды, такие как [и echo, используются так часто в сценариях оболочки, что разработчики оболочки решили скопировать код непосредственно в саму оболочку, а не запускать другой процесс каждый раз, когда они используются. Это превратило эти команды в «встроенные», хотя вы все равно можете вызывать внешнюю программу через ее полный путь.

[[пришел намного позже Хотя встроенная функция реализована внутри оболочки, она анализируется как внешние команды. Как объясняется в ответе Джона 1024, это означает, что для переменных без кавычек будет выполнено разбиение по словам, а токены как >и <обрабатываются как перенаправление ввода / вывода. Это делало написание сложных выражений сравнения неудобным. [[был создан как синтаксис оболочки, так что он мог быть проанализирован идеосинкразически. Внутри [[переменных отсутствует разделение слов, <и они >могут использоваться как операторы сравнения, =могут вести себя по-разному в зависимости от того, указан ли следующий параметр в кавычках или нет, и т. Д. Это все удобства, которые [[проще использовать, чем традиционная [команда / встроенная команда.

Они не могли просто перекодировать в [такой синтаксис, потому что это было бы несовместимым изменением для миллионов скриптов. Используя новый [[синтаксис, который ранее не существовал, они могли бы полностью изменить способ, которым он используется, с совместимостью вверх.

Это похоже на эволюцию, которая привела к $((...))синтаксису для арифметических выражений, который в основном заменил традиционную exprкоманду.

Barmar
источник
0

Поздние [[в bashэто оптимизация в [.

У классики [есть один большой недостаток, когда он часто используется для выполнения тривиальной операции: он будет порождать новый процесс каждый раз:
(Он создает новое адресное пространство только для сравнения 0и 1! Каждый раз!)

Я думаю, что основным моментом добавления [[было сделать так, чтобы выражение выражения внутри [не порождало лишний процесс. Но то, как [работает, не может быть изменено - это создаст много путаницы и проблем. Итак, оптимизация была реализована с новым именем, более эффективным способом, а именно командой встроенной оболочки.
Это стало ключевым словом в синтаксисе оболочки как побочный эффект.

В то время, когда время [использовалось первым, это был правильный способ сделать это с помощью внешнего процесса.

Volker Siegel
источник
5
Обратите внимание, что хотя [изначально это была внешняя команда, она была добавлена ​​в качестве встроенной в оболочку довольно рано, вероятно, к моменту выпуска Unix System III и определенно до выпуска Unix System V. Таким образом, «дополнительный процесс» не был проблемой целую вечность. Однако синтаксис остался неизменным - он обрабатывался так, как если бы он был внешней командой.
Джонатан Леффлер
@JonathanLeffler О, спасибо, я пропустил, и то, [и другое - это означает некоторые изменения ...
Volker Siegel