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

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

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

Любые предложения?

EDIT: Меня попросили привести примеры того, что я в идеале ищу. Довольно экстремальным примером того, что я считаю "формальной семантикой", является этот документ "Суть JavaScript" . Возможно, немного менее формальным примером является отчет Haskell 2010.

Ответ 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.

Литература:

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

Ответ 2

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

Ответ 3

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

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