Семантика для сценаріїв Bash?


87

Більше, ніж будь-яку іншу мову, яку я знаю, я "вивчав" Bash, гуглюючи щоразу, коли мені потрібна якась дрібниця. Отже, я можу поєднати невеликі сценарії, які, здається, працюють. Однак я насправді не знаю, що відбувається, і я сподівався на більш офіційне введення в Bash як мову програмування. Наприклад: Який порядок оцінки? які правила масштабування? Що таке дисципліна друкування, наприклад, чи все це рядок? Який стан програми - це призначення ключа-значення рядків іменам змінних; чи є щось більше, наприклад, стек? Чи є купа? І так далі.

Я думав проконсультуватися з посібником GNU Bash для такого розуміння, але, схоже, це не те, що я хочу; це скоріше пральний список синтаксичного цукру, а не пояснення основної семантичної моделі. Мільйон-і-один "баш-підручники" в Інтернеті лише гірший. Можливо, мені слід спочатку вивчити shта зрозуміти Баша як синтаксичний цукор? Але я не знаю, чи це точна модель.

Будь-які пропозиції?

EDIT: Мене попросили навести приклади того, що в ідеалі я шукаю. Досить екстремальним прикладом того, що я вважав би "формальною семантикою", є цей документ про "суть JavaScript" . Можливо, трохи менш офіційним прикладом є звіт Haskell 2010 .


3
Чи є Advanced Bash Scripting Guide одним із "мільйонів і одного"?
choroba

2
Я не впевнений, що bash має "основну семантичну модель" (ну, можливо, "майже все - це рядок"); я думаю, що це справді синтаксичний цукор аж донизу.
Гордон Девіссон,

4
Те, що ви називаєте "пральним списком синтаксичного цукру", насправді є семантичною моделлю розширення - надзвичайно важливою частиною виконання. 90% помилок та плутанини пов’язані з нерозумінням моделі розширення.
той інший хлопець

4
Я розумію, чому хтось може подумати, що це широке запитання, якщо ви прочитаєте це, як, як мені написати сценарій оболонки ? Але справжнє питання полягає в тому, яка формальна семантика та основа для мови оболонки та bash зокрема? , і це гарне запитання з однією послідовною відповіддю. Голосування за відновлення.
Коджіро

1
Я досить багато дізнався на linuxcommand.org, і є навіть безкоштовний pdf більш поглибленої книги з командного рядка та написання сценаріїв оболонки
samrap

Відповіді:


107

Оболонка - це інтерфейс для операційної системи. Зазвичай це сама по собі більш-менш надійна мова програмування, але з функціями, розробленими для спрощення конкретного взаємодії з операційною системою та файловою системою. Семантика оболонки POSIX (надалі називається просто "оболонка") трохи сумбурна, поєднуючи деякі особливості LISP (s-вирази мають багато спільного з розбиванням слів оболонки ) та 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 настільки всюдисущий, що деякі автори сценаріїв проходять роки, не розуміючи, що сценарій насправді не може працювати в строго POSIX-системі. (Мені також цікаво, як люди можуть так піклуватися про семантику та стиль однієї мови програмування, і так мало про семантику та стиль оболонки, але я розходяться.)

Порядок оцінки

Це трохи хитре питання: Bash інтерпретує вирази у своєму первинному синтаксисі зліва направо, але в арифметичному синтаксисі він дотримується пріоритету C. Проте вирази відрізняються від розширень . З EXPANSIONрозділу посібника з Bash:

Порядок розкладання: розширення фігурних дужок; розширення тильди, розширення параметрів та змінних, арифметичне розширення та підстановка команд (виконується зліва направо); розбиття слів; та розширення назви шляху.

Якщо ви розумієте розділення слів, розширення імені шляху та розширення параметрів, ви на шляху до розуміння більшості того, що робить bash. Зверніть увагу, що розширення імені шляху, яке відбувається після розбиття слів, є критичним, оскільки воно гарантує, що файл із пробілами в його назві все одно може відповідати глобусу. Ось чому хороше використання розширення глобуса краще, ніж загальний аналіз команд , загалом.

Сфера дії

Сфера функцій

Подібно до старого 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позиційним параметром. Я вважаю це заплутаним, оскільки воно не включає його до числа аргументів $#, але це нумерований параметр, тому meh. $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Розділ керівництва Баша
  2. XCU Shell Command Language документація
  3. Керівництво Bash на вікі Greycat в.
  4. Розширене програмування в середовищі UNIX

Будь ласка, коментуйте, якщо хочете, щоб я розширювався далі у певному напрямку.


16
+1: Чудове пояснення. Оцініть час, витрачений на написання цього, на прикладах.
jaypal singh

+1 за yes | make config;-) Але серйозно, дуже хороший запис.
Цифрова травма

щойно почав це читати .. приємно. залишить кілька коментарів. 1) ще більший сюрприз виникає, коли ви бачите це, /bin/[і /bin/testчасто це те саме застосування. 2) "Припустимо, що перше слово - це команда". - очікуйте, коли будете виконувати завдання ...
Каролі Горват

@KarolyHorvath Так, я навмисно виключив призначення з моєї демонстраційної оболонки, оскільки змінні є складним безладом. Ця демо-оболонка не була написана з урахуванням цієї відповіді - вона була написана набагато раніше. Думаю, я міг би це зробити execleта інтерполювати перші слова в середовищі, але це все одно зробило б це дещо складнішим.
Коджіро

@kojiro: ні, це просто надмірно ускладнило б це, це, звичайно, не було моїм наміром! але призначення працює дещо інакше (x), і IMHO ви повинні згадати про це десь у тексті. (x): і джерело певної плутанини ... Я вже навіть не можу підрахувати, скільки разів я бачив людей, які скаржаться на те, що a = 1не працюють).
Каролі Горват

5

Відповідь на ваше запитання "Що таке дисципліна набору тексту, наприклад, чи все це рядок" Змінні 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 Позначте кожну змінну для експорту до наступних команд через середовище.

1

Сторінка bash має набагато більше інформації, ніж більшість сторінок, і включає те, що ви просите. Я припускаю, що після більш ніж десятиліття сценарію bash є те, що завдяки своїй "історії як розширення sh, він має певний синтаксис (для підтримки зворотної сумісності з sh).

FWIW, мій досвід був схожий на ваш; хоча різні книги (наприклад, О'Рейлі "Вивчення оболонки Баша" та подібні) допомагають у синтаксисі, існує безліч дивних способів вирішення різних проблем, і деякі з них відсутні в книзі, і їх потрібно гуглити.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.