Больше, чем какой-либо другой язык, который я знаю, я «выучил» Bash с помощью Google каждый раз, когда мне что-то нужно. Следовательно, я могу собирать вместе небольшие скрипты, которые, кажется, работают. Тем не менее, я не очень знаю , что происходит, и я надеялся на более формальное введение в Bash в качестве языка программирования. Например: каков порядок оценки? каковы правила определения объема работ? Какова дисциплина набора текста, например, все ли является строкой? Каково состояние программы - присвоение значения ключом строк именам переменных; есть еще что-то, например стек? Есть куча? И так далее.
Я подумал обратиться к руководству по GNU Bash для такого понимания, но, похоже, это не то, что мне нужно; это скорее подробный перечень синтаксического сахара, чем объяснение основной семантической модели. Миллион и один "учебник по bash" в сети только хуже. Возможно, мне следует сначала изучить sh
и понять Bash как синтаксический сахар поверх всего этого? Однако я не знаю, точна ли это модель.
Какие-либо предложения?
РЕДАКТИРОВАТЬ: Меня попросили привести примеры того, что в идеале я ищу. Довольно крайним примером того, что я бы назвал «формальной семантикой», является статья о «сути JavaScript» . Возможно, чуть менее формальным примером является отчет Haskell 2010 .
источник
Ответы:
Оболочка - это интерфейс для операционной системы. Обычно это более или менее надежный сам по себе язык программирования, но с функциями, предназначенными для упрощения взаимодействия с операционной системой и файловой системой. Семантика оболочки POSIX (в дальнейшем именуемая просто «оболочка») представляет собой что-то вроде путаницы, объединяя некоторые функции LISP (s-выражения имеют много общего с разделением слов оболочки ) и C (большая часть арифметического синтаксиса оболочки семантика исходит из C).
Другой корень синтаксиса оболочки возник из-за того, что она выросла из мешанины отдельных утилит UNIX. Большинство из того, что часто является встроенным в оболочку, на самом деле может быть реализовано как внешние команды. Многие новички в области оболочки запутаются, когда осознают, что это
/bin/[
существует во многих системах.$ if '/bin/[' -f '/bin/['; then echo t; fi # Tested as-is on OS X, without the `]` 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: # 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 настолько распространен, что некоторые авторы скриптов годами не осознают, что скрипт на самом деле может не работать в системе, строгой по 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' # 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 $
Печатная дисциплина
Гм, типы. Да уж. В Bash действительно нет типов, и все раскрывается в строку (или, возможно, было бы более подходящим слово ). Но давайте рассмотрим различные типы расширений.
Струны
Практически все можно рассматривать как строку. Голые слова в bash - это строки, значение которых полностью зависит от применяемого к нему расширения.
Нет расширенияВозможно, стоит продемонстрировать, что простое слово на самом деле просто слово, и что кавычки ничего в этом не меняют.
Расширение подстроки$ echo foo foo $ 'echo' foo foo $ "echo" foo foo
$ fail='echoes' $ set -x # So we can see what's going on $ "${fail:0:-2}" Hello World + echo Hello World Hello World
Подробнее о расширениях читайте в
Parameter Expansion
разделе руководства. Это довольно мощно.Целые числа и арифметические выражения
Вы можете наполнить имена целочисленным атрибутом, чтобы указать оболочке обрабатывать правую часть выражений присваивания как арифметические. Затем, когда параметр расширяется, он будет вычислен как целочисленное математическое значение перед расширением до… строки.
$ 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
Массивы
Аргументы и позиционные параметрыПрежде чем говорить о массивах, возможно, стоит обсудить позиционные параметры. Аргументы сценария оболочки можно получить с помощью пронумерованных параметров,
$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
это имя оболочки или текущего сценария оболочки.Синтаксис массивов смоделирован на основе позиционных параметров, поэтому в большинстве случаев полезно думать о массивах как о названном виде «внешних позиционных параметров», если хотите. Массивы можно объявлять следующими способами:
Вы можете получить доступ к элементам массива по индексу:
$ 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.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
Раздел руководства БашаПожалуйста, прокомментируйте, если вы хотите, чтобы я развился дальше в определенном направлении.
источник
yes | make config
;-) А если серьезно, то очень хорошее описание./bin/[
и/bin/test
часто это одно и то же приложение. 2) «Предположите, что первое слово - это команда». - ожидайте, когда вы выполните задание ...execle
и вставить первые слова в окружение, но это все равно немного усложнит задачу.a = 1
не работают).Ответ на ваш вопрос «Какова дисциплина печати, например, все ли является строкой». Переменные Bash представляют собой символьные строки. Но Bash разрешает арифметические операции и сравнения переменных, когда переменные являются целыми числами. Исключением из правила Bash-переменные являются символьные строки, когда указанные переменные набраны или объявлены иначе.
$ A=10/2 $ echo "A = $A" # Variable A acting like a String. A = 10/2 $ B=1 $ let B="$B+1" # Let is internal to bash. $ echo "B = $B" # One is added to B was Behaving as an integer. B = 2 $ A=1024 # A Defaults to string $ B=${A/24/STRING01} # Substitute "24" with "STRING01". $ echo "B = $B" # $B STRING is a string B = 10STRING01 $ B=${A/24/STRING01} # Substitute "24" with "STRING01". $ declare -i B $ echo "B = $B" # Declaring a variable with non-integers in it doesn't change the contents. B = 10STRING01 $ B=${B/STRING01/24} # Substitute "STRING01" with "24". $ echo "B = $B" B = 1024 $ declare -i B=10/2 # Declare B and assigning it an integer value $ echo "B = $B" # Variable B behaving as an Integer B = 5
Объявите значения опций:
источник
Справочная страница bash содержит немного больше информации, чем большинство справочных страниц, и включает кое-что из того, что вы просите. Мое предположение после более чем десятилетнего использования сценариев bash состоит в том, что из-за своей истории как расширения sh, он имеет некоторый фанковый синтаксис (для обеспечения обратной совместимости с sh).
FWIW, мой опыт был похож на ваш; хотя различные книги (например, O'Reilly «Learning the Bash Shell» и подобные) действительно помогают с синтаксисом, существует множество странных способов решения различных проблем, и некоторых из них нет в книге, и их следует искать в Google.
источник