Семантика для скриптов Bash?

Больше, чем любой другой язык, который я знаю, я "выучил" Bash с помощью Google, каждый раз, когда мне нужно что-то маленькое. Следовательно, я могу комбинировать маленькие сценарии, которые, кажется, работают. Тем не менее, я не знаю, что происходит, и я надеялся на более формальное введение в Bash как язык программирования. Например: что такое порядок оценки? каковы правила определения объема? Что такое дисциплина ввода, например, все ли это строка? Каково состояние программы - это присвоение ключей по строкам именам переменных; есть ли что-то большее, например, стек? Есть ли куча? И так далее.

Я думал обратиться к руководству по GNU Bash для такого понимания, но, похоже, это не то, чего я хочу; это скорее список синтаксического сахара, а не объяснение основной семантической модели. Миллион и один "учебник по bash" онлайн только хуже. Возможно, я должен сначала учитьсяshи понимать Bash как синтаксический сахар на вершине этого? Я не знаю, если это точная модель, хотя.

Какие-либо предложения?

РЕДАКТИРОВАТЬ: меня попросили привести примеры того, что в идеале я ищу. Довольно экстремальный пример того, что я бы назвал "формальной семантикой", - это статья о "сущности JavaScript". Возможно, чуть менее формальным примером является доклад на Haskell 2010.

3 ответа

Решение

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

Массивы

Синтаксис массивов моделируется после позиционных параметров, поэтому в большинстве случаев полезно рассматривать массивы как именованный вид "внешних позиционных параметров", если хотите. Массивы могут быть объявлены с использованием следующих подходов:

$ 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

Основное различие между массивами и позиционными параметрами:

  1. Позиционные параметры не редки. Если $12 установлен, вы можете быть уверены $11 тоже установлен. (Можно установить пустую строку, но $# не будет меньше 12.) Если "${arr[12]}" установлено, нет никакой гарантии, что "${arr[11]}" установлен, и длина массива может быть как 1.
  2. Нулевой элемент массива однозначно является нулевым элементом этого массива. В позиционных параметрах нулевой элемент является не первым аргументом, а именем оболочки или сценария оболочки.
  3. к shift массив, вы должны нарезать и переназначить его, как arr=( "${arr[@]:1}" ), Вы могли бы также сделать unset arr[0], но это сделало бы первый элемент с индексом 1.
  4. Массивы могут быть неявно распределены между функциями оболочки как глобальные, но вы должны явно передавать позиционные параметры в функцию оболочки, чтобы они могли их видеть.

Часто удобно использовать расширения пути для создания массивов имен файлов:

$ dirs=( */ )

команды

Команды являются ключевыми, но они также описаны лучше, чем я могу в руководстве. Прочитайте SHELL GRAMMAR раздел. Различные виды команд:

  1. Простые команды (например, $ startx)
  2. Трубопроводы (например, $ yes | make config) (лол)
  3. Списки (например, $ grep -qF foo file && sed 's/foo/bar/' file > newfile)
  4. Составные команды (например, $ ( cd -P /var/www/webroot && echo "webroot is $PWD" ))
  5. Копроцессы (сложные, без примера)
  6. Функции (именованная составная команда, которую можно рассматривать как простую команду)

Модель исполнения

Конечно, модель выполнения включает в себя как кучу, так и стек. Это характерно для всех программ UNIX. Bash также имеет стек вызовов для функций оболочки, видимый через вложенное использование caller встроенный.

Рекомендации:

  1. SHELL GRAMMAR раздел руководства по bash
  2. Документация по языку командной консоли XCU
  3. Руководство по Bash на вики Greycat.
  4. Расширенное программирование в среде UNIX

Пожалуйста, оставляйте комментарии, если хотите, чтобы я продолжал расширяться в определенном направлении.

Ответ на ваш вопрос "Что такое дисциплина ввода, например, является ли все строкой?" Переменные 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

Объявить варианты значений:

  • -a Переменная является массивом.
  • -f Использовать только имена функций.
  • -i Переменная должна рассматриваться как целое число; арифметическая оценка выполняется, когда переменной присваивается значение.
  • -p Показать атрибуты и значения каждой переменной. При использовании -p дополнительные параметры игнорируются.
  • -r Сделать переменные только для чтения. Этим переменным затем нельзя присвоить значения с помощью последующих операторов присваивания, а также они не могут быть сброшены.
  • -t Дайте каждой переменной атрибут трассировки.
  • -x Отметить каждую переменную для экспорта в последующие команды через окружение.

Man-страница Bash содержит немного больше информации, чем большинство страниц, и включает в себя то, что вы просите. Мое предположение после более чем десятилетнего написания сценариев bash состоит в том, что из-за его истории как расширения sh он имеет какой-то причудливый синтаксис (для обеспечения обратной совместимости с sh).

FWIW, мой опыт был похож на ваш; Хотя различные книги (например, "Изучение оболочки Bash" О'Рейли и т. п.) действительно помогают с синтаксисом, существует множество странных способов решения различных проблем, и некоторые из них отсутствуют в книге и должны быть найдены.

Другие вопросы по тегам