Яка різниця між вбудованою оболонкою та ключовим словом оболонки?


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

Це добре працює, і Баш із задоволенням виведе

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

Ой. Це тому, що коли Баш бачить цю лінію, він не бачить нічого особливого, а значить, розширює всі змінні, і врешті-решт бачить:

[[ -d . ]]

Наразі розширення псевдонімів і сканування ключових слів уже давно виконуються і більше не виконуватимуться, тому Баш намагається знайти команду, яку називають [[, не знаходить її, і скаржиться.

За тими ж лініями:

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

і

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

Розширення псевдонімів - це теж щось особливе. Ви хоч раз зробили таке:

$ 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. Дякую. Один , можливо , не пов'язаний питання: просто заради цікавості я намагаюся знайти в документацію по «тимчасово відключити псевдонім» функціональність з попередньої команди з =\'допомоги man, aproposі helpта я не віз. Будь-яка ідея, куди б я пішов про пошук цієї інформації? Переважно так, що в майбутньому я бачу, що ще там є, бо я думаю, що мені не вистачає довідкового джерела.
нк.

@nc: ви не знайдете це явно задокументовано. Причина цього працює пояснюється у цій відповіді. Найближчий ви знайдете в посібнику з експлуатації в розділі " Обстріли" . Ви побачите, що розширення псевдоніму робиться дуже рано (те, що я намагався підкреслити у цій відповіді), на кроці 2. Видалення цитат, розширення параметрів, глобалізація тощо виконуються пізніше. Отже, щоб відключити псевдонім, ви можете використовувати якесь цитування, щоб заборонити оболонці розуміти маркер як псевдонім, наприклад \ll, "ll"або 'll'.
gniourf_gniourf

1
Насправді я дістав замовлення, спочатку псевдоніми та ключові слова звільняються. Оскільки ми цитуємо [[врожайність, \[[вона не розбирається як псевдонім. Правильно поки що? Там, де я загубився, не розуміючи, що зворотна косої риски є цитуючою справою в Bash, і я спробував роздивитись її під псевдонімами та ключовими словами і повністю загубився ... Все добре зараз. У розділі Цитування:> Нецитується зворотна косої риски () - символ втечі.
нк.

1
Таким чином, ми можемо вважати (2) у цьому списку Op як Токенізація, і \[[це єдиний маркер, що типу LiteralQuoteToken, на відміну від [[якого буде OpenTestKeywordToken, і що вимагає закриття ]]CloseTestKeywordToken для компіляції або правильного синтаксису. Пізніше, у (4), LiteralQuoteToken буде оцінено [[як ім'я bulitin / команди для виконання, а в (6) Bash буде barf, оскільки немає [[вбудованої або команди. Приємно. Я можу забути точні деталі з часом, але в цей момент спосіб, яким Баш веде речі, для мене набагато зрозуміліший; Дякую тобі.
нк.

9

man bashназиває їх SHELL BUILTIN COMMANDS. Так, "вбудована оболонка" - це як звичайна команда, наприклад grep, тощо, але замість того, щоб міститися в окремому файлі, вона вбудована в сам bash . Це змушує їх виконувати ефективніше, ніж зовнішні команди.

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


1
Цікавий факт: [[ is a shell keywordале [ is a shell builtin. Я поняття не маю, чому.
Sparhawk

1
Можливо, з історичних причин та стандарту POSIX максимально наближено відповідати старій оболонці Борна, оскільки [існували як окрема команда ще в той день. [[не визначено стандартом, тому розробники можуть вибрати, як включити це як ключове слово або як вбудований.
Сергій Колодяжний

1

Посібник з командного рядка, що постачається з Ubuntu, не дає визначення ключових слів, однак онлайн-інструкція (див. Sidenote) та стандартні специфікації мови POSIX Shell Command Language , посилаються на них як "Зарезервовані слова", і обидва надають їх списки. Із стандарту POSIX:

Це розпізнавання відбувається лише тоді, коли жоден із символів не цитується і коли слово використовується як:

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

  • Перше слово, що слідує за одним із зарезервованих слів, окрім регістру, для або в

  • Третє слово в команді case (тільки в цьому випадку дійсне)

  • Третє слово в команді a для команди (лише в і дійсно в цьому випадку)

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

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

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

Sidenote

Індекс GNU відображається [як зарезервоване слово, однак це фактично вбудована команда. [[на противагу - застережене слово.

Дивіться також: Відмінності між ключовим словом, зарезервованим словом та вбудованим?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.