Почему {1,2}, напечатанный командой в $ (), не интерполируется?

8

Я нахожусь в каталоге, в котором у меня есть два текстовых файла:

$ touch test1.txt
$ touch test2.txt

Когда я пытаюсь перечислить файлы (с Bash), используя какой-то шаблон, он работает:

$ ls test?.txt
test1.txt  test2.txt
$ ls test{1,2}.txt
test1.txt  test2.txt

Однако, когда шаблон создается командой, вложенной в $(), работает только один из шаблонов:

$ ls $(echo 'test?.txt')
test1.txt  test2.txt
$ ls $(echo 'test{1,2}.txt')
ls: cannot access test{1,2}.txt: No such file or directory

Что тут происходит? Почему шаблон {1,2}не работает?

Герослав Мирашевский
источник
4
Расширение
Сергей Колодяжный,
3
@SergiyKolodyazhnyy Суть вопроса в том, что ?он также заключен в кавычки и расширяется после $(...)его замены, а расширение скобки - нет.
Майкл Гомер
1
@ Муру Нет, это не та проблема. Здесь порядок расширений не имеет значения, важно то, какое расширение происходит в каком контексте. Я не удивлюсь, если этот вопрос будет дубликатом, но я не смог его найти.
Жиль "ТАК - перестань быть злым"
1
@mosvy Ksh и bash выполняют расширения в одном и том же порядке, но ksh выполняет расширение в скобках в случае, когда bash не делает этого вообще. Zsh-with-globsubst выполняет те же расширения, что и bash, но в другом порядке.
Жиль "ТАК - перестань быть злым"
1
@ Жиль, нет. Как задокументировано и легко продемонстрировано, ksh (и zsh) выполнят раскрытие фигурной скобки непосредственно перед глобализацией. ЗШ-с-globsubst не будет выполнять любое расширение распорки на всех по результатам $-разложений: zsh -o globsubst -c 'a=/e*; b={/b*,/v*}; echo $a; echo $b'.
Мосви

Ответы:

17

Это сочетание двух вещей. Во-первых, расширение скобок - это не шаблон, который соответствует именам файлов: это просто текстовая подстановка - см. В чем разница между `a [bc] d` (скобки) и` a {b, c} d` (скобки)? , Во-вторых, когда вы используете результат подстановки команды вне двойных кавычек ( ls $(…)), происходит только сопоставление с шаблоном (и разбиение слов: оператор «split + glob»), а не полный повторный анализ.

С ls $(echo 'test?.txt')помощью команды echo 'test?.txt'выводится строка test?.txt(с последним переводом строки). Подстановка команды приводит к строке test?.txt(без заключительного перевода строки, потому что подстановка команды удаляет завершающие переводы строки). Эта подстановка без кавычек подвергается разделению слов, в результате чего получается список, состоящий из одной строки, test?.txtпоскольку в ней нет пробельных символов (точнее, символов внутри $IFS). Каждый элемент этого одноэлементного списка затем подвергается условному расширению подстановочного знака, и, поскольку ?в строке есть подстановочный знак, расширение подстановочного знака действительно происходит. Поскольку шаблон test?.txtсоответствует как минимум одному имени файла, элемент списка test?.txtзаменяется списком имен файлов, соответствующих шаблонам, в результате получается двухэлементный список, содержащийtest1.txtи test2.txt. Наконец lsвызывается с двумя аргументами test1и test2.

С ls $(echo 'test{1,2}')помощью команды echo 'test{1,2}'выводится строка test{1,2}(с последним переводом строки). Подстановка команды приводит к строке test{1,2}. Эта подстановка без кавычек подвергается разделению слов, в результате получается список, состоящий из одной строки test{1,2}. Каждый элемент этого одноэлементного списка затем подвергается условному расширению подстановочного знака, которое ничего не делает (элемент остается как есть), так как в строке нет подстановочного знака. Так lsназывается с единственным аргументом test{1,2}.

Для сравнения вот что происходит ls $(echo test{1,2}). Команда echo test{1,2}выводит строку test1 test2(с последним переводом строки). Подстановка команды приводит к строке test1 test2(без заключительного перевода строки). Эта подстановка без кавычек подвергается разбиению слов, что приводит к двум строкам test1и test2. Затем, поскольку ни одна из строк не содержит подстановочный знак, они остаются одни, поэтому lsвызывается с двумя аргументами test1и test2.

Жиль "ТАК - перестань быть злым"
источник
3
Обратите внимание, что pdksh и ksh93 выполняют раскрытие фигурных скобок при расширениях (перед глобализацией; не с помощью noglob, но в случае ksh93, даже при отключенном braceexpand!)
Стефан
Вы, кажется, забыли .txtво втором объяснении.
говорит
10

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

Расширение скобок не произойдет после подстановки команд. Вы можете использовать eval, чтобы вызвать другой раунд расширения:

eval echo $(echo '{1,2}lala')

Это результат:

1lala 2lala
dedowsdi
источник
6

Эта проблема очень специфична bash, и это потому, что они решили bashотделить расширение фигурной скобки от расширения имени файла (globbing) и выполнить его в первую очередь, перед всеми другими расширениями.

Из bashсправочной страницы:

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

В вашем примере bashваши фигурные скобки будут видны только после выполнения подстановки команды (the $(echo ...)), когда будет слишком поздно.

Это отличается от всех других оболочек, которые выполняют расширение фигурной скобки непосредственно перед (а некоторые даже как часть) расширением пути (globbing). Это включает, но не ограничивается тем, cshгде были впервые разработаны скобки-расширения.

$ csh -c 'ls `echo "test{1,2}.txt"`'
test1.txt test2.txt
$ ksh -c 'ls $(echo "test{1,2}.txt")'
test1.txt  test2.txt

$ var=nope var1=one var2=two bash -c 'echo $var{1,2}'
one two
$ var=nope var1=one var2=two csh -c 'echo $var{1,2}'
nope1 nope2

Последний пример является одинаковым в csh, zsh, ksh93, mkshили fish.

Кроме того, обратите внимание, что расширение скобок как часть globbing также доступно через glob(3)библиотечную функцию (по крайней мере, в Linux и всех BSD) и в других независимых реализациях (например, в perl:) perl -le 'print join " ", <test{1,2}.txt>'.

Почему это было сделано по-другому bash, вероятно, есть история, но я не смог найти никакого логического объяснения, и я считаю, что все последующие объяснения неубедительны.

mosvy
источник
3
Обратите внимание, что perlраньше cshвызывался для расширения глобусов, поэтому неудивительно, что он по-прежнему распознает те же операторы глобинга, что иcsh
Стефан
1

Пожалуйста, попробуй:::

ls $ (эхо-тест {1,2} \. txt)

С обратной косой чертой. Это работает сейчас. Также удалите, как говорилось в предыдущем постере, цитаты. Точка предназначена не для сопоставления с образцом, а для буквального обозначения Периода.

mkzia
источник
(1) Вопрос спрашивает: «Что здесь происходит? Почему [шаблон] {1,2}ведет себя так, как он? Вопрос не задается: «Как я могу получить команду, использующую {1,2}поведение, с которым команда ?работает?» Вы отвечаете не на тот вопрос. (2) Обратная косая черта не имеет к этому никакого отношения. Ваша команда работает так, как работает, потому что вы удалили кавычки, которые были в команде в вопросе.
G-Man говорит: «Восстановите Монику»
0

Это работает, если вы удалите кавычки

$ ls $(echo test{1,2})
test1  test2
darxmurf
источник
9
Расширение теперь происходит до подстановки команд, и я не думаю, что это вопрос, о котором идет речь (сравните, что происходит с ?).
Майкл Гомер