Тайна раскрытия вложенных скобок в 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 не упоминает об этом (даже если у него есть пример , используя его).

xenoid
источник

Ответы:

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 Есть два очевидных способа, которыми это выражение может быть проанализировано. Почему разбирается в одну сторону, а не в другую? Ваш комментарий не дает объяснения.
Игаль
Итак, это объяснение имеет смысл, но если это «задокументировано как расширяемое до ...», есть ли URL?
ксеноид
@xenoid Смотрите мое обновленное решение.
Игаль
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функция принимает строку, представляющую выражение расширения фигурной скобки, и возвращает массив развернутых строк.

Комбинируя эти два наблюдения, мы видим, что значение amble расширено до списка строк, каждая из которых объединена в преамбуле. Затем метка раскладывается в список строк, и каждая строка в списке меток объединяется с каждой строкой в ​​списке преамбул / амблей (т.е. формируется произведение двух списков). Но это не описывает, как обрабатываются amble и postamble. К счастью, есть комментарий, описывающий это также. Обрамление обрабатывается функцией, вызванной 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 Wiki . Раздел о комбинировании и вложении не совсем решает вашу проблему, но на странице есть синтаксис / грамматика расширения фигурных скобок, что, я думаю, отвечает на ваш вопрос. Синтаксис задается следующими шаблонами:

{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}}

На языке вики Bash Hacker's это соответствует первой форме:

{string1,string2,...,stringN}

Там , где N=2, string1={a..c}и string2={1..3}- внутренние скобки разложения выполняется первым , и каждый из них формы {<START>..<END>}. В качестве альтернативы, мы можем сказать, что это выражение расширения скобки, которое состоит только из amble (без преамбулы или постамбла). Amble - это список, разделенный запятыми, поэтому мы просматриваем список по одному слоту за раз и выполняем дополнительные расширения, где это необходимо. Продукт не формируется, потому что нет смежных выражений (запятая используется в качестве разделителя).

Теперь давайте посмотрим на ваше второе выражение:

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

На языке вики Bash Hacker's это выражение соответствует форме:

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

где постскриптум это подвыражение ,{1..3}. В качестве альтернативы, мы можем сказать, что это выражение имеет amble ( {a..c}) и postamble ( ,{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зависит от влажности и Доу-Джонса? Ухоженная.
Кубанчик
Это кажется немного запутанным. Если {{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}.,.
Хауке Лагинг
@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 Вы не должны высмеивать ответы, которые вы не понимаете.
Хауке Лагинг