Ответ 1
Оболочка - это интерфейс для операционной системы. Это, как правило, более или менее надежный язык программирования, но с функциями, предназначенными для упрощения взаимодействия с операционной системой и файловой системой. Оболочка POSIX (в дальнейшем именуемая так же, как "оболочка" ) - это немного мута, объединяющая некоторые функции LISP (s-выражения имеют много общего с оболочкой разбиение слова) и C (большая часть оболочки арифметический синтаксис семантика происходит от C).
Другой корень синтаксиса оболочки происходит от его воспитания как мишмара отдельных утилит UNIX. Большинство из которых часто встроены в оболочку, фактически могут быть реализованы как внешние команды. Он бросает много неофитов оболочки для цикла, когда они понимают, что /bin/[
существует во многих системах.
$ if '/bin/[' -f '/bin/['; then echo t; fi # Tested as-is on OS X, without the `]`
t
ваты?
Это имеет гораздо больший смысл, если вы посмотрите, как оболочка реализована. Вот реализация, которую я сделал как упражнение. Это на 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:
# We're in a child process
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 настолько вездесущ, что некоторые авторы script идут годами, не понимая, что script может фактически не работать в строгой системе POSIXly. (Мне также интересно, как люди могут так много заботиться о семантике и стиле одного языка программирования и так мало для семантики и стиля оболочки, но я расходясь.)
Порядок оценки
Это немного сложный вопрос: bash интерпретирует выражения в своем основном синтаксисе слева направо, но в его арифметическом синтаксисе он следует за приоритетом C. Однако выражения отличаются от разложений. В разделе EXPANSION
руководства bash:
Порядок разложений: расширение скобки; расширение тильды, параметр и переменное расширение, арифметическое расширение и подстановка команд (сделано в порядке слева направо); расщепление слов; и расширение имени пути.
Если вы понимаете перевод слов, расширение пути и расширение параметров, вы хорошо понимаете, что делает bash. Обратите внимание, что расширение имени пути, идущее после словаря, имеет решающее значение, поскольку оно гарантирует, что файл с пробелом в его имени все еще может быть сопоставлен glob. Вот почему хорошее использование расширений glob лучше, чем команды синтаксического анализа в целом.
Масштаб
Область действия
Как старый ECMAscript, оболочка имеет динамическую область действия, если вы явно не объявляете имена внутри функции.
$ foo() { echo $x; }
$ bar() { local x; echo $x; }
$ foo
$ bar
$ x=123
$ foo
123
$ bar
$ …
Окружающая среда и область процесса
Subshells наследуют переменные своих родительских оболочек, но другие виды процессов не наследуют невыполненные имена.
$ x=123
$ ( echo $x )
123
$ bash -c 'echo $x'
$ export x
$ bash -c 'echo $x'
123
$ y=123 bash -c 'echo $y' # another way to transiently export a name
123
Вы можете комбинировать эти правила:
$ foo() {
> local -x bar=123 # Export foo, but only in this scope
> bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar
$
Вводная дисциплина
Um, типы. Да. bash действительно не имеет типов, и все расширяется до строки (или, возможно, слово было бы более уместным). Но рассмотрим различные типы расширений.
Строки
Довольно многое можно рассматривать как строку. Брейды в bash являются строками, смысл которых полностью зависит от применяемого к нему расширения.
Без расширенияВозможно, стоит продемонстрировать, что голое слово действительно просто слово, и что цитаты ничего не меняют об этом.
$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
Расширение подстроки
$ fail='echoes'
$ set -x # So we can see what going on
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World
Подробнее о расширениях читайте в разделе Parameter Expansion
руководства. Это довольно мощный.
Целые числа и арифметические выражения
Вы можете заливать имена с помощью атрибута integer, чтобы сообщить оболочке обрабатывать правую часть выражений присваивания как арифметику. Затем, когда параметр расширяется, он будет вычисляться как целочисленная математика до того, как будет расширяться до... строки.
$ foo=10+10
$ echo $foo
10+10
$ declare -i foo
$ foo=$foo # Must re-evaluate the assignment
$ echo $foo
20
$ echo "${foo:0:1}" # Still just a string
2
Массивы
Аргументы и позиционные параметрыПрежде чем говорить о массивах, возможно, стоит обсудить позиционные параметры. Аргументы для оболочки script можно получить с помощью нумерованных параметров $1
, $2
, $3
и т.д. Вы можете получить доступ ко всем этим параметрам сразу, используя "[email protected]"
, расширение которого имеет много общего с массивы. Вы можете устанавливать и изменять позиционные параметры с помощью встроенных функций 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 "[email protected]"
> }
$ showshift foo bar baz biz quux xyzzy
$1 => biz
$2 => quux
$3 => xyzzy
Руководство по bash также иногда ссылается на $0
как позиционный параметр. Я нахожу это запутанным, потому что он не включает его в аргумент count $#
, но это пронумерованный параметр, поэтому meh. $0
- это имя оболочки или текущей оболочки script.
Синтаксис массивов моделируется после позиционных параметров, поэтому в большинстве случаев полезно думать о массивах как о названном виде "внешних позиционных параметров", если хотите. Массивы могут быть объявлены с использованием следующих подходов:
$ 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" # Even if the zeroth index isn't set
$ …
Если вы используете кавычки или обратную косую черту, чтобы предотвратить создание слов, массив будет поддерживать заданный набор слов:
$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2
Основное различие между массивами и позиционными параметрами:
- Позиционные параметры не являются разреженными. Если
$12
установлен, вы можете быть уверены, что также установлен$11
. (Он может быть установлен в пустую строку, но$#
не будет меньше 12.) Если установлено"${arr[12]}"
, нет гарантии, что"${arr[11]}"
установлен, а длина массива может быть такой же малой, как 1. - Нулевой элемент массива однозначно является нулевым элементом этого массива. В позиционных параметрах нулевой элемент не является первым аргументом, а именем оболочки или оболочки script.
- В
shift
массив вам нужно нарезать и переназначить его, напримерarr=( "${arr[@]:1}" )
. Вы также можете сделатьunset arr[0]
, но это сделает первый элемент в индексе 1. - Массивы могут быть разделены неявно между функциями оболочки как глобальными, но вы должны явно передать позиционные параметры функции оболочки, чтобы увидеть их.
Часто бывает удобно использовать расширения pathname для создания массивов имен файлов:
$ dirs=( */ )
Команды
Команды являются ключевыми, но они также покрыты большей глубиной, чем я могу в руководстве. Прочтите раздел SHELL GRAMMAR
. Различные команды:
- Простые команды (например,
$ startx
) - Трубопроводы (например,
$ yes | make config
) (lol) - Списки (например,
$ grep -qF foo file && sed 's/foo/bar/' file > newfile
) - Составные команды (например,
$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
) - Сопроцессы (сложный, без примера)
- Функции (Именованная составная команда, которую можно рассматривать как простую команду)
Модель выполнения
Конечно, модель исполнения включает кучу и стек. Это эндемично для всех программ UNIX. bash также имеет стек вызовов для функций оболочки, видимый через вложенное использование встроенного caller
.
Литература:
- Раздел
SHELL GRAMMAR
руководства bash - Документация XCU Shell Command
- Bash Руководство в wiki Greycat.
- Расширенное программирование в среде UNIX
Просьба высказать свои замечания, если вы хотите, чтобы я расширился дальше в определенном направлении.