Вкладена таємниця розширення дужок у Bash


19

Це:

$ echo {{a..c},{1..3}}

виробляє це:

a b c 1 2 3

Що приємно, але важко пояснити, враховуючи це

$ echo {a..c},{1..3}

дає

a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3

Це десь задокументовано? Bash Reference не згадує про це (навіть якщо у нього є приклад , використовуючи його).

Відповіді:


18

Ну, це розплутується один за одним:

X{{a..c},{1..3}}Y

задокументовано як розширення на X{a..c}Y X{1..3}Y(це X{A,B}Yрозширене XA XBз Aбуттям {a..c}і Bбуттям {1..3}), а самі документовано як розширення XaY XbY XcY X1Y X2Y X3Y.

Що може бути вартим документування, це те, що вони можуть бути вкладені (щоби перший }не закривав, наприклад, першого {).

Я вважаю, що оболонки могли вирішити спочатку внутрішні дужки, як, наприклад, діючи при кожному закритті }по черзі:

  1. X{{a..c},{1..3}}
  2. X{a,{1..3}}Y X{b,{1..3}}Y X{c,{1..3}}Y

    (що A{a..c}Bрозширюється на AaB AbB AcB, де Aє X{і Bє ,{1..3}Y)

  3. X{a,1}Y X{a,2}Y X{a,3}Y X{b,1}Y X{b,2}Y X{b,3}Y X{c,1}Y X{c,2}Y X{c,3}Y

  4. XaY X1Y XaY Xa2...

Але я не вважаю, що особливо інтуїтивно зрозумілим чи корисним (див. Приклад Кевіна в коментарях, наприклад), все одно залишатиметься певна неоднозначність щодо порядку виконання розширень, і це не так csh(оболонка, яка ввела дужку розширення в кінці 70-х, тоді як {1..3}форма з'явилася пізніше (1995), zshа {a..c}ще пізніше (2004) з bash).

Зауважте, що csh(з початку див. Сторінку людини 2BSD (1979) ) було задокументовано факт, що розширення дужок можна вкладати , хоча явно не говорило про те, як будуть розширюватися вкладені розширення дужок. Але ви можете подивитися на cshкод з 1979 року, щоб побачити, як це робилося тоді. Подивіться, як це явно обробляє гніздування дійсно, і як це вирішується, починаючи із зовнішніх брекетів.

У будь-якому випадку, я насправді не бачу, як розширення {a..c},{1..3}може мати якесь значення. Там ,не є оператор розширення дужок (як це не знаходиться в брекетах), тому трактується як будь-який звичайний персонаж.


Мені здається дивним, що зовнішні дужки повинні бути вирішені перед внутрішніми.
Hauke ​​Laging

@ stéphane-chazelas Є два очевидних способи розбору цього виразу. Чому він розбирається в один бік, а не в інший? Схоже, ваш коментар не дає пояснень.
igal

Отже, це пояснення має сенс, але якщо це "задокументовано як розширення на ..." , чи є URL-адреса?
ксеноїд

@xenoid Дивіться моє оновлене рішення.
igal

1
@ (усі): Розгляньте розширення /dev/{h,s}d{a..d}{1..4,}. Тепер припустимо, ви хочете розширити його, щоб він також включав /dev/nullі /dev/zero. Якби розширення дужок працювало зсередини назовні, це розширення було б дуже прикро. Але оскільки це працює ззовні, це досить тривіально:/dev/{null,zero,{h,s}d{a..d}{1..4,}}
Кевін

7

Ось коротка відповідь. У першому виразі кома використовується як роздільник, тому розширення дужок - це лише з'єднання двох вкладених підвиражень. У другому вираженні кома сам по собі розглядається як Односимвольний подвираженія, тому вираження продукту будуть сформовані.

Те, що вам не вистачало, було визначення того, як виконуються розширення дужок. Ось три посилання:

Далі йде більш детальне пояснення.


Ви порівняли результат цього виразу:

$ echo {{a..c},{1..3}}
a b c 1 2 3

до результату цього виразу:

$ echo {a..c},{1..3}
a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3

Ви говорите, що це важко пояснити, тобто що це контрінтуїтивно. Не вистачає формального визначення способу обробки розширень дужок. Ви зазначаєте, що керівництво Bash не дає повного визначення.

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

Джерело містить пару корисних коментарів. Спочатку - огляд алгоритму розширення брекетів на високому рівні:

Basic idea:

Segregate the text into 3 sections: preamble (stuff before an open brace),
postamble (stuff after the matching close brace) and amble (stuff after
preamble, and before postamble).  Expand amble, and then tack on the
expansions to preamble.  Expand postamble, and tack on the expansions to
the result so far.

Отже, формат маркера розширення дужок такий:

<PREAMBLE><AMBLE><POSTAMBLE>

Основна точка входу до розширення - це функція, brace_expandяка називається так:

Return an array of strings; the brace expansion of TEXT.

Таким чином, brace_expandфункція приймає рядок, що представляє вираз розширення дужок і повертає масив розширених рядків.

Поєднуючи ці два спостереження, ми бачимо, що амблі розгорнуто до списку рядків, кожна з яких з'єднана в преамбулу. Потім перекладний елемент розширюється на список рядків, і кожна рядок у списку постамбелів об'єднується в кожну рядок у списку преамбули / амблі (тобто утворюється добуток двох списків). Але це не описує, як обробляються амблі та поштовби. На щастя, є коментар, який також описує це. Амбл обробляється функцією під назвою expand_amble, визначенню якої передує наступний коментар:

Expand the text found inside of braces.  We simply try to split the
text at BRACE_ARG_SEPARATORs into separate strings.  We then brace
expand each slot which needs it, until there are no more slots which
need it.

В іншому коді ми бачимо, що BRACE_ARG_SEPARATOR визначений як кома. Це дає зрозуміти, що amble - це список розділених комами рядків, деякі з яких також можуть бути виразами розширення дужок. Ці рядки потім утворюють єдиний масив. Нарешті, ми можемо бачити , що після того, як expand_ambleназивається brace_expandфункція потім викликається рекурсивно на постамбула. Це дає нам повний опис алгоритму.

Є деякі інші (неофіційні) посилання, які підтверджують цю знахідку.

Для однієї довідки перегляньте Вікі Bash Hackers . Розділ про поєднання та вкладення не зовсім вирішує вашу проблему, але на цій сторінці подано синтаксис / граматику розширення дужок, що, на мою думку, відповідає на ваше запитання. Синтаксис задається такими шаблонами:

{string1,string2,...,stringN}

{<START>..<END>}

<PREAMBLE>{........}

{........}<POSTSCRIPT>

<PREAMBLE>{........}<POSTSCRIPT>

А розбір описується так:

Розширення дужок використовується для генерації довільних рядків. Зазначені рядки використовуються для генерації всіх можливих комбінацій з необов'язковими оточуючими преамбулами та постскриптами.

Для ще однієї посилання ознайомтеся з Посібником для початківців Bash , де сказано наступне:

Brace expansion is a mechanism by which arbitrary strings may be generated. Patterns to be brace-expanded take the form of an optional PREAMBLE, followed by a series of comma-separated strings between a pair of braces, followed by an optional POSTSCRIPT. The preamble is prefixed to each string contained within the braces, and the postscript is then appended to each resulting string, expanding left to right.

Отже, для розбору виразів розширення дужок ми рухаємося вліво-вправо, розширюючи кожен вираз і утворюючи послідовні продукти (щодо операції конкатенації рядків).

Тепер розглянемо ваш перший вираз:

{{a..c},{1..3}}

Мовою Віки Баша Хакера це відповідає першій формі:

{string1,string2,...,stringN}

Там , де N=2, string1={a..c}і string2={1..3}- внутрішні дужки розкладання виконується першим , і кожен з них форми {<START>..<END>}. Як варіант, ми можемо сказати, що це вираження розширення дужок, яке складається лише з амбла (без преамбули та постамбули). Амбл - це список, розділений комами, тому ми проходимо по одному слоту за раз і виконуємо додаткові розширення, де потрібно. Жоден твір не утворюється, оскільки немає суміжних виразів (кома використовується як роздільник).

Далі розглянемо ваш другий вираз:

{a..c},{1..3}

Мовою Віки Баша Хакера цей вираз відповідає формі:

{........}<POSTSCRIPT>

де постскрипт - це підвираз ,{1..3}. Як варіант, ми можемо сказати, що цей вираз має {a..c}амблі ( ( ) і постамбл ( ,{1..3}). Амбл розгортається до списку, a b cа потім кожне з них з'єднується з кожним з рядків у розширенні постамбля. Оброблювана поштою є рекурсивно: вона має преамбулу ,та амблію {1..3}. Це розширено до списку ,1 ,2 ,3. Два списки a b cі ,1 ,2 ,3потім об'єднуються для формування списку товарів a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3.

Це може допомогти дати псуедо-алгебраїчний опис того, як ці вирази розбираються, де дужки "[]" позначають масиви "+" позначають конкатенацію масиву, а "*" позначає декартовий продукт (щодо конкатенації).

Ось як розширюється перший вираз (один крок на рядок):

{{a..c},{1..3}}
{a..c} + {1..3}
[a b c] + [1 2 3]
a b c 1 2 3

А ось як розширюється другий вираз:

{a..c},{1..3}
{a..c} * ,{1..3}
[a b c] * [,1 ,2 ,3]
a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3

2

Моє розуміння таке:

Внутрішні дужки вирішуються спочатку (як завжди), який обертається

{{a..c},{1..3}}

в

{a,b,c,1,2,3}

Оскільки в ,межах брекетів він просто відокремлює елементи дужок.

Але у випадку з

{a..c},{1..3}

,не в фігурних дужках , тобто це звичайний символ викликає фігурні дужки перестановки з обох сторін.


Отже, {a..c}або вирішується, a,b,cабо a b cзалежить від вологості та Доу Джонса? Акуратний.
kubanczyk

Це здається трохи заплутаним. Якщо {{a..c},{1..3}}це те саме {a,b,c,1,2,3}, що не повинно {{a..c}.{1..3}}бути таким же, як {a,b,c.1,2,3}? Звичайно, це не так.
ilkkachu

@ilkkachu Чому це повинно бути те саме? ,є символом поділу розширення дужок, .ні. Чому звичайний персонаж повинен призводити до тих же результатів, що і спеціальний? c.1є елементом дужки. Але в є якорем для скріплюють розширень на лівій і правій боку . З зовнішніми брекетами використовуються для розширення розпірки , так як їх зміст має формат розширення розпірки, з їх не тому , що їх зміст не має той же формат. {a..c}.{1..3}.,.
Hauke ​​Laging

@HaukeLaging, добре, якщо {{a..c},{1..3}}перетворюється на {a,b,c,1,2,3}то, якісь коми ставали щойно між a, bі c. Чому б вони не з'явилися так само {a..c}.{1..3}? Коментар @kubanczyk - це приблизно те саме, якщо коми з’являються там так, то як ми можемо знати, коли розширення створює коми, а коли - ні? Відповідь, звичайно, полягає в тому, що він ніколи не породжує жодних коми, а створює список слів. Тож нічого не перетворюється на {a,b,c,1,2,3}або {a,b,c.1,2,3}.
ilkkachu

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