Существует сильная разница между встроенным и ключевым словом в том, как 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
Как вы думаете, что происходит?
Действительно, встроенная команда похожа на команду, за исключением того, что она встроена в оболочку, а ключевое слово - это то, что допускает сложное поведение! мы можем сказать, что это часть грамматики оболочки.