В чем разница между встроенной оболочкой и ключевым словом оболочки?


33

Когда я запускаю эти две команды, я получаю

$ type cd
cd is a shell builtin
$ type if
if is a shell keyword

Хорошо видно, что cdэто встроенная ifоболочка и ключевое слово оболочки. Так в чем же разница между встроенной оболочкой и ключевым словом?


Ответы:


45

Существует сильная разница между встроенным и ключевым словом в том, как Bash анализирует ваш код. Прежде чем говорить о разнице, давайте перечислим все ключевые слова и встроенные функции:

Встроенные команды:

$ compgen -b
.         :         [         alias     bg        bind      break     
builtin   caller    cd        command   compgen   complete  compopt   
continue  declare   dirs      disown    echo      enable    eval      
exec      exit      export    false     fc        fg        getopts   
hash      help      history   jobs      kill      let       local     
logout    mapfile   popd      printf    pushd     pwd       read      
readarray readonly  return    set       shift     shopt     source    
suspend   test      times     trap      true      type      typeset   
ulimit    umask     unalias   unset     wait                          

Ключевые слова:

$ compgen -k
if        then      else      elif      fi        case      
esac      for       select    while     until     do        
done      in        function  time      {         }         
!         [[        ]]        coproc              

Обратите внимание, что, например [, это встроенная функция, а [[это ключевое слово. Я буду использовать эти два, чтобы проиллюстрировать разницу ниже, так как они являются хорошо известными операторами: все их знают и регулярно их используют (или должны).

Ключевое слово сканируется и понимается Bash на очень раннем этапе его анализа. Это позволяет, например, следующее:

string_with_spaces='some spaces here'
if [[ -n $string_with_spaces ]]; then
    echo "The string is non-empty"
fi

Это отлично работает, и Bash с удовольствием выведет

The string is non-empty

Обратите внимание, что я не цитировал $string_with_spaces. Принимая во внимание следующее:

string_with_spaces='some spaces here'
if [ -n $string_with_spaces ]; then
    echo "The string is non-empty"
fi

показывает, что Баш не доволен:

bash: [: too many arguments

Почему он работает с ключевыми словами, а не со встроенными? потому что, когда Bash анализирует код, он видит [[ключевое слово и очень рано понимает, что он особенный. Поэтому он будет искать закрытие ]]и по-особому будет относиться к внутренней части. Встроенная (или команда) рассматривается как фактическая команда, которая будет вызываться с аргументами. В последнем примере bash понимает, что он должен выполнить команду [с аргументами (по одному в строке):

-n
some
spaces
here
]

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

На практике вы видите, что это различие допускает сложное поведение, которое было бы невозможно с помощью встроенных команд (или команд).

До сих пор на практике, как вы можете отличить встроенный от ключевого слова? это забавный эксперимент для выполнения:

$ a='['
$ $a -d . ]
$ echo $?
0

Когда Bash анализирует строку $a -d . ], он не видит ничего особенного (то есть ни псевдонимов, ни перенаправлений, ни ключевых слов), поэтому он просто выполняет расширение переменных. После раскрытия переменных он видит:

[ -d . ]

поэтому выполняет команду (встроенную) [с аргументами -d, .и ], что, конечно, верно (это только проверяет, .является ли каталог).

Теперь смотри:

$ a='[['
$ $a -d . ]]
bash: [[: command not found

Ой. Это потому, что когда Bash видит эту строку, он не видит ничего особенного, и, следовательно, раскрывает все переменные и в конечном итоге видит:

[[ -d . ]]

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

В том же духе:

$ '[' -d . ]
$ echo $?
0
$ '[[' -d . ]]
bash: [[: command not found

а также

$ \[ -d . ]
$ echo $?
0
$ \[[ -d . ]]
bash: [[: command not found

Расширение Alias ​​также является чем-то особенным. Вы все сделали следующее по крайней мере один раз:

$ alias ll='ls -l'
$ ll
.... <list of files in long format> ....
$ \ll
bash: ll: command not found
$ 'll'
bash: ll: command not found

Причина та же: расширение псевдонима происходит задолго до раскрытия переменной и удаления кавычек.


Ключевое слово против псевдонима

Как вы думаете, что произойдет, если мы определим псевдоним как ключевое слово?

$ alias mytest='[['
$ mytest -d . ]]
$ echo $?
0

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


Вывод: встроенные функции действительно ведут себя как команды: они соответствуют действию, выполняемому с аргументами, которые подвергаются прямому расширению переменных, а также расщеплению и разбивке слов. Это действительно так же, как если бы где-то была внешняя команда /binили /usr/binона вызывалась с аргументами, заданными после раскрытия переменной и т. Д. Обратите внимание, что когда я говорю, что это действительно как внешняя команда, я имею в виду только аргументы, расщепление слов, глобирование, расширение переменной и т. д. Встроенный модуль может изменять внутреннее состояние оболочки!

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

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


!это ключевое слово. Кажется, можно было бы подражать его поведению с помощью функции:

not() {
    if "$@"; then
        return false
    else
        return true
    fi
}

но это запретило бы такие конструкции, как:

$ ! ! true
$ echo $?
0

или

$ ! { true; }
echo $?
1

То же самое для time: более эффективно иметь ключевое слово, чтобы можно было рассчитывать сложные составные команды и конвейеры с перенаправлениями:

$ time grep '^#' ~/.bashrc | { i=0; while read -r; do printf '%4d %s\n' "$((++i))" "$REPLY"; done; } > bashrc_numbered 2>/dev/null

Если бы timeбыла простая команда (даже встроенная), она бы увидела только аргументы grep, ^#и /home/gniourf/.bashrc, раз это, а затем ее выходные данные прошли бы через оставшиеся части конвейера. Но с ключевым словом Bash может справиться со всем! он может timeзавершить конвейер, включая перенаправления! Если бы мы timeбыли просто командой, мы бы не смогли:

$ time { printf 'hello '; echo world; }

Попытайся:

$ \time { printf 'hello '; echo world; }
bash: syntax error near unexpected token `}'

Попробуйте исправить (?) Это:

$ \time { printf 'hello '; echo world;
time: cannot run {: No such file or directory

Безнадежный.


Ключевое слово против псевдонима?

$ alias mytime=time
$ alias myls=ls
$ mytime myls

Как вы думаете, что происходит?


Действительно, встроенная команда похожа на команду, за исключением того, что она встроена в оболочку, а ключевое слово - это то, что допускает сложное поведение! мы можем сказать, что это часть грамматики оболочки.


2
Согласившись с @JohnyTex, это один из наиболее полных и подходящих ответов, которые я видел на сайтах Stack. Спасибо. Один, возможно, не связанный с этим вопрос: просто ради любопытства я пытаюсь найти документацию для функции «временно отключить псевдоним», предшествующую команде с =\помощью « manapropos, helpи мне не повезло. Есть идеи, где мне найти эту информацию? Главным образом, чтобы в будущем я мог видеть, что еще там, потому что я думаю, что мне не хватает справочного источника.
нк.

@nc: вы не найдете документально оформленную документацию. Причина, по которой это работает, объясняется в этом ответе. Самое близкое, что вы найдете в справочном руководстве в разделе Работа с оболочкой . Вы увидите, что расширение псевдонима выполняется очень рано (что я постарался подчеркнуть в этом ответе), на шаге 2. Удаление кавычек, расширение параметров, глобализация и т. Д. Будут выполнены позже. Таким образом, чтобы отключить псевдоним, вы можете использовать некое цитирование, чтобы запретить оболочке понимать токен как псевдоним, например \ll, "ll"или 'll'.
gniourf_gniourf

1
Я действительно получил порядок, псевдонимы и ключевые слова увольняются первыми Поскольку мы цитируем [[результат, \[[он не анализируется как псевдоним. Прямо сейчас? Там, где я заблудился, я не понимал, что обратная косая черта - это цитата в Bash, и я попытался найти ее под псевдонимами и ключевыми словами и совершенно потерял… Теперь все хорошо. В разделе «Цитирование»:> Обратная косая черта без кавычек () - это escape-символ.
нк.

1
Таким образом, мы можем думать о (2) в этом списке операций как о Tokenization, и \[[это единственный токен типа LiteralQuoteToken, в отличие от [[которого будет OpenTestKeywordToken, и для которого требуется закрывающий ]]CloseTestKeywordToken для компиляции / правильного синтаксиса oroper. Позже, в (4) LiteralQuoteToken будет оцениваться [[как имя команды / команды, которую нужно выполнить, а в (6) Bash запретит, потому что нет [[встроенной команды или команды. Ницца. Я могу забыть точные детали со временем, но в данный момент способ, которым Bash управляет вещами, намного яснее для меня; Спасибо.
нк.

9

man bashназывает их SHELL BUILTIN COMMANDS. Итак, «встроенная оболочка» похожа на обычную команду, например grep, и т. Д., Но вместо того, чтобы содержаться в отдельном файле, она встроена в сам bash . Это заставляет их выполнять более эффективно, чем внешние команды.

Ключевое слово также «жестко зашиты в Bash, но в отличие от встроенных команд, ключевое слово не является само по себе команда, но субъединица командной конструкции.» Я понимаю, что это означает, что ключевые слова не имеют функции в одиночку, но требуют, чтобы команды делали что-либо. (Из ссылки, другие примеры for, while, doи !, и есть больше в моем ответе на ваш другой вопрос.)


1
Интересный факт: [[ is a shell keywordно [ is a shell builtin. Понятия не имею почему.
Sparhawk

1
Вероятно, по историческим причинам и стандарту POSIX, чтобы соответствовать старой оболочке Bourne настолько близко, насколько это возможно, так [как существовал как отдельная команда в прошлом. [[не указано в стандарте, поэтому разработчики могут включить его в качестве ключевого слова или в качестве встроенного.
Сергей Колодяжный

1

Руководство по командной строке, поставляемое с Ubuntu, не дает определения ключевых слов, однако в онлайн-руководстве (см. Sidenote) и в стандартных спецификациях POSIX Shell Command Language они называются «зарезервированными словами», и оба предоставляют их списки. Из стандарта POSIX:

Это распознавание должно происходить только тогда, когда ни один из символов не указан в кавычках и когда слово используется как:

  • Первое слово команды

  • Первое слово, следующее за одним из зарезервированных слов, кроме case, for или in

  • Третье слово в команде case (в этом случае действует только in)

  • Третье слово в команде for (только в и do действительны в этом случае)

Ключевым моментом здесь является то, что ключевые слова / зарезервированные слова имеют особое значение, поскольку они облегчают синтаксис оболочки, служат для сигнализации определенных блоков кода, таких как циклы, составные команды, операторы ветвления (if / case) и т. Д. Они позволяют формировать операторы команд, но сами по себе - ничего не делать, а на самом деле , если вы вводите ключевые слова , такие как for, until, case- оболочка будет ожидать полного заявления, в противном случае - ошибка синтаксиса:

$ for
bash: syntax error near unexpected token `newline'
$  

На уровне исходного кода зарезервированные слова для bash определены в parese.y , в то время как встроенные модули имеют целый каталог, выделенный для них.

Примечание

Индекс GNU отображается [как зарезервированное слово, однако это встроенная команда. [[напротив, зарезервированное слово.

Смотрите также: Различия между ключевым словом, зарезервированным словом и встроенным?

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.