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

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

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

$ 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}не работает?

решение1

Это комбинация двух вещей. Во-первых, расширение скобок не является шаблоном, который соответствует именам файлов: это чисто текстовая замена — см.В чем разница между `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.

решение2

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

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

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

Результат:

1lala 2lala

решение3

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

Из bashстраницы руководства:

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

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

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

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

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

решение4

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

ls $(echo test{1,2}\.txt)

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

Связанный контент