Оболочка - это интерфейс для операционной системы. Обычно это более или менее надежный сам по себе язык программирования, но с функциями, предназначенными для упрощения взаимодействия с операционной системой и файловой системой. Семантика оболочки POSIX (в дальнейшем именуемая просто «оболочка») представляет собой что-то вроде путаницы, объединяя некоторые функции LISP (s-выражения имеют много общего с разделением слов оболочки ) и C (большая часть арифметического синтаксиса оболочки семантика исходит из C).
Другой корень синтаксиса оболочки возник из-за того, что она выросла из мешанины отдельных утилит UNIX. Большинство из того, что часто является встроенным в оболочку, на самом деле может быть реализовано как внешние команды. Многие новички в области оболочки запутаются, когда осознают, что это /bin/[
существует во многих системах.
$ if '/bin/[' -f '/bin/['; then echo t; fi
t
Wat?
В этом будет больше смысла, если вы посмотрите, как реализована оболочка. Вот реализация, которую я сделал в качестве упражнения. Это на Python, но я надеюсь, что это ни для кого не помешает. Это не очень надежно, но поучительно:
#!/usr/bin/env python
from __future__ import print_function
import os, sys
'''Hacky barebones shell.'''
try:
input=raw_input
except NameError:
pass
def main():
while True:
cmd = input('prompt> ')
args = cmd.split()
if not args:
continue
cpid = os.fork()
if cpid == 0:
os.execl(args[0], *args)
else:
os.waitpid(cpid, 0)
if __name__ == '__main__':
main()
Я надеюсь, что из приведенного выше ясно видно, что модель выполнения оболочки в значительной степени:
1. Expand words.
2. Assume the first word is a command.
3. Execute that command with the following words as arguments.
Расширение, разрешение команд, исполнение. Вся семантика оболочки связана с одной из этих трех вещей, хотя она намного богаче, чем реализация, которую я написал выше.
Не все команды fork
. Фактически, есть несколько команд, которые не имеют особого смысла реализованы как внешние (так что они должны были бы это делать fork
), но даже они часто доступны как внешние для строгого соответствия POSIX.
Bash опирается на эту основу, добавляя новые функции и ключевые слова для улучшения оболочки POSIX. Он почти совместим с sh, а bash настолько распространен, что некоторые авторы скриптов годами не осознают, что скрипт на самом деле может не работать в системе, строгой по POSIX. (Мне также интересно, как люди могут так сильно заботиться о семантике и стиле одного языка программирования и так мало о семантике и стиле оболочки, но я расходлюсь.)
Порядок оценки
Это немного хитрый вопрос: Bash интерпретирует выражения в своем основном синтаксисе слева направо, но в своем арифметическом синтаксисе он следует приоритету C. Однако выражения отличаются от расширений . Из EXPANSION
раздела руководства bash:
Порядок раскрытий: раскрытие скобок; раскрытие тильды, раскрытие параметров и переменных, арифметическое раскрытие и подстановка команд (выполняется слева направо); разбиение слов; и расширение имени пути.
Если вы разбираетесь в разделении слов, расширении имени пути и раскрытии параметров, вы хорошо на своем пути к пониманию большей части того, что делает bash. Обратите внимание, что расширение имени пути после разделения слов имеет решающее значение, поскольку оно гарантирует, что файл с пробелом в имени все еще может быть сопоставлен глобусом. Вот почему хорошее использование расширений glob лучше, чем команд синтаксического анализа в целом.
Объем
Объем функции
Как и старый ECMAscript, оболочка имеет динамическую область видимости, если вы явно не объявляете имена внутри функции.
$ foo() { echo $x; }
$ bar() { local x; echo $x; }
$ foo
$ bar
$ x=123
$ foo
123
$ bar
$ …
«Объем» среды и процесса
Подоболочки наследуют переменные своих родительских оболочек, но другие виды процессов не наследуют неэкспортированные имена.
$ x=123
$ ( echo $x )
123
$ bash -c 'echo $x'
$ export x
$ bash -c 'echo $x'
123
$ y=123 bash -c 'echo $y'
123
Вы можете комбинировать эти правила определения объема:
$ foo() {
> local -x bar=123
> bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar
$
Печатная дисциплина
Гм, типы. Да уж. В Bash действительно нет типов, и все раскрывается в строку (или, возможно, было бы более подходящим слово ). Но давайте рассмотрим различные типы расширений.
Струны
Практически все можно рассматривать как строку. Голые слова в bash - это строки, значение которых полностью зависит от применяемого к нему расширения.
Нет расширения
Возможно, стоит продемонстрировать, что простое слово на самом деле просто слово, и что кавычки ничего в этом не меняют.
$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
Расширение подстроки
$ fail='echoes'
$ set -x
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World
Подробнее о расширениях читайте в Parameter Expansion
разделе руководства. Это довольно мощно.
Целые числа и арифметические выражения
Вы можете наполнить имена целочисленным атрибутом, чтобы указать оболочке обрабатывать правую часть выражений присваивания как арифметические. Затем, когда параметр расширяется, он будет вычислен как целочисленное математическое значение перед расширением до… строки.
$ foo=10+10
$ echo $foo
10+10
$ declare -i foo
$ foo=$foo
$ echo $foo
20
$ echo "${foo:0:1}"
2
Массивы
Аргументы и позиционные параметры
Прежде чем говорить о массивах, возможно, стоит обсудить позиционные параметры. Аргументы сценария оболочки можно получить с помощью пронумерованных параметров, $1
, $2
, $3
и т.д. Вы можете получить доступ ко всем этим параметрам одновременно , используя "$@"
, что разложение имеет много общего с массивами. Вы можете установить и изменить позиционные параметры с помощью встроенных функций set
или shift
или просто вызвав оболочку или функцию оболочки с этими параметрами:
$ bash -c 'for ((i=1;i<=$#;i++)); do
> printf "\$%d => %s\n" "$i" "${@:i:1}"
> done' -- foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showpp() {
> local i
> for ((i=1;i<=$#;i++)); do
> printf '$%d => %s\n' "$i" "${@:i:1}"
> done
> }
$ showpp foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showshift() {
> shift 3
> showpp "$@"
> }
$ showshift foo bar baz biz quux xyzzy
$1 => biz
$2 => quux
$3 => xyzzy
В руководстве по bash также иногда упоминается $0
как позиционный параметр. Я нахожу это запутанным, потому что он не включает его в счетчик аргументов $#
, но это нумерованный параметр, так что я. $0
это имя оболочки или текущего сценария оболочки.
Массивы
Синтаксис массивов смоделирован на основе позиционных параметров, поэтому в большинстве случаев полезно думать о массивах как о названном виде «внешних позиционных параметров», если хотите. Массивы можно объявлять следующими способами:
$ foo=( element0 element1 element2 )
$ bar[3]=element3
$ baz=( [12]=element12 [0]=element0 )
Вы можете получить доступ к элементам массива по индексу:
$ echo "${foo[1]}"
element1
Вы можете разрезать массивы:
$ printf '"%s"\n' "${foo[@]:1}"
"element1"
"element2"
Если рассматривать массив как нормальный параметр, вы получите нулевой индекс.
$ echo "$baz"
element0
$ echo "$bar"
$ …
Если вы используете кавычки или обратную косую черту для предотвращения разделения слов, массив сохранит указанное разделение слов:
$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2
Основное различие между массивами и позиционными параметрами:
- Позиционные параметры не редкость. Если
$12
установлен, вы можете быть уверены $11
, что он тоже установлен. (Это может быть пустая строка, но $#
не меньше 12.) Если "${arr[12]}"
установлено, нет гарантии, что "${arr[11]}"
она установлена, и длина массива может быть меньше 1.
- Нулевой элемент массива однозначно является нулевым элементом этого массива. В позиционных параметрах нулевой элемент - это не первый аргумент , а имя оболочки или сценария оболочки.
- Для
shift
массива вы должны нарезать и переназначить его, например arr=( "${arr[@]:1}" )
. Вы также можете это сделать unset arr[0]
, но это сделает первый элемент с индексом 1.
- Массивы могут неявно совместно использоваться функциями оболочки как глобальные объекты, но вы должны явно передать позиционные параметры функции оболочки, чтобы она их увидела.
Часто бывает удобно использовать расширения имен файлов для создания массивов имен файлов:
$ dirs=( */ )
Команды
Команды являются ключевыми, но они также описаны лучше, чем я могу в руководстве. Прочтите SHELL GRAMMAR
раздел. Различные виды команд:
- Простые команды (например
$ startx
)
- Трубопроводы (например
$ yes | make config
) (смеется)
- Списки (например
$ grep -qF foo file && sed 's/foo/bar/' file > newfile
)
- Составные команды (например
$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
)
- Сопроцессы (сложные, без примера)
- Функции (именованная составная команда, которую можно рассматривать как простую команду)
Модель исполнения
Модель выполнения, конечно же, включает в себя как кучу, так и стек. Это характерно для всех программ UNIX. Bash также имеет стек вызовов для функций оболочки, видимый через вложенное использование caller
встроенного.
Ссылки:
SHELL GRAMMAR
Раздел руководства Баша
- XCU Shell Command Language документация
- Руководство Bash на вики Greycat в.
- Расширенное программирование в среде UNIX
Пожалуйста, прокомментируйте, если вы хотите, чтобы я развился дальше в определенном направлении.