Обязательное чтение:
a) в последнем примере с использованием массивов, почему двойные кавычки требуются вокруг *.$i
?
Вам необходимо использовать некоторыеформа цитирования, чтобы не допустить, чтобы оболочка выполнила расширение glob на *
.Переменные не раскрываются в одинарных кавычках, поэтому '*.$i'
не работает.Это препятствует расширению глобуса, но также останавливает расширение переменной."*.$i"
запрещает расширение глобуса, но допускает расширение переменной, что идеально.
Чтобы по-настоящему углубиться в детали, здесь нужно сделать две вещи:
- Escape или quote
*
для предотвращения расширения глобуса. - Считайте
$i
расширением переменной, но заключайте его в кавычки, чтобы предотвратить разбиение слов и расширение глобуса.
Любая форма цитирования подойдет дляпункт 1: \*
, "*"
, '*'
и $'*'
- все приемлемые способы гарантировать, что он рассматривается как буквальная звездочка.
Для пункта 2 единственным ответом является двойное цитирование.Голые $i
могут быть разбиты на слова и разбиты - если у вас есть i='foo bar'
или i='foo*'
, пробелы и глобусы могут вызвать проблемы.\$i
и '$i'
оба трактуют знак доллара буквально, поэтому их нет.
"$i"
- единственная цитата, которая делает все правильно.Вот почему общий совет оболочки: всегда использовать двойные кавычки для расширений переменных .
Конечный результат: любое из следующих действий будет работать:
"*.$i"
\*."$i"
'*'."$i"
"*"."$i"
'*.'"$i"
Очевидно, первоеявляется самым простым.
b) использование массива таким образом должно распространяться «на все элементы, заключенные в кавычки».Как бы сделать это с переменной (см. Мою первую попытку)?Получив эту функцию, я вернулся и снова попытался использовать переменную с черными черточками или \\'
, но ничего не получалось (я только что получил bar
).Что мне нужно сделать, чтобы эмулировать «вручную», как это было бы, цитирование, выполняемое при использовании массивов?
Вам нужно что-то собрать вместе с eval
, но это опасно.По сути, массивы более мощные, чем простые строковые переменные.Не существует волшебной комбинации кавычек и обратной косой черты, которая позволила бы вам делать то, что может делать массив.Массивы - правильный инструмент для этой работы.
Не могли бы вы объяснить немного подробнее, почему ... PATTERN="-name bar -or -name \"*.$i\""
не работает?Двойные кавычки в кавычках должны, когда команда find
действительно выполняется, расширять $i
, но не глобус.
Конечно.Допустим, мы пишем:
i=a
PATTERN="-name bar -or -name \"*.$i\""
find . \( $PATTERN \)
После того, как будут выполнены первые две строки, каково значение $PATTERN
?Давайте проверим:
$ i=a
$ PATTERN="-name bar -or -name \"*.$i\""
$ printf '%s\n' "$PATTERN"
-name bar -or -name "*.a"
Вы заметите, что $i
уже заменено на a
, а обратные слеши удалены.
Теперь давайте посмотрим, как именно find
команда разбираетсяВ последней строке $PATTERN
не заключено в кавычки, потому что мы хотим, чтобы все слова были разделены, верно?Если вы пишете пустое имя переменной, Bash завершает выполнение подразумеваемой операции split + glob .Он выполняет разбиение слов и расширение глобуса.Что именно это означает?
Давайте посмотрим, как Bash выполняет расширение командной строки.В справочной странице Bash в разделе «Расширение» мы видим порядок операций:
- Расширение скобки
- Расширение тильды, расширение параметров и переменных,арифметическое расширение, подстановка команд и подстановка процессов
- Разделение слов
- Расширение пути (AKA glob)
- Удаление кавычек
Давайте пройдемся по этимоперации вручную и посмотрите, как анализируется find . \( $PATTERN \)
.Конечным результатом будет список строк, поэтому я буду использовать JSON-подобный синтаксис для отображения каждой стадии.Начнем со списка, содержащего одну строку:
['find . \( $PATTERN \)']
В качестве предварительного шага командная строка в целом может быть разбита на слова.
['find', '.', '\(', '$PATTERN', '\)']
Расширение скобки - Без изменений.
Переменное расширение
['find', '.', '\(', '-name bar -or -name "*.a"', '\)']
$PATTERN
заменяетсяНа данный момент это все одна строка, пробел и все.
Разделение слов
['find', '.', '\(', '-name', 'bar', '-or', '-name', '"*.a"', '\)']
Оболочка просматривает результаты переменныхрасширение, которое не встречалось в двойных кавычках для разделения слов.$PATTERN
без кавычек, поэтому он расширен.Теперь это куча отдельных слов.Пока все хорошо.
Расширение глобуса
['find', '.', '\(', '-name', 'bar', '-or', '-name', '"*.a"', '\)']
Bash сканирует результаты разбиения слов на глобусы.Не вся командная строка, а только токены -name
, bar
, -or
, -name
и "*.a"
.
Похоже, ничего не произошло, да?Не так быстро!Внешность может быть обманчива.Bash фактически выполнил расширение glob.Просто так случилось, что шар не соответствовал ничему.Но это может ... †
Удаление цитаты
['find', '.', '(', '-name', 'bar', '-or', '-name', '"*.a"', ')']
Обратные слэши исчезли.Но двойные кавычки все еще там .
После предыдущих расширений все вхождения без кавычек символов \
, '
и "
, которыене в результате одного из вышеперечисленных расширений удалены.
И это конечный результат.Двойные кавычки все еще там, поэтому вместо поиска файлов с именем *.a
он ищет файлы с именем "*.a"
с буквальными символами двойных кавычек в их имени.Этот поиск обречен на неудачу.
Добавление пары экранированных кавычек \"
вовсе не соответствует тому, что мы хотели.Цитаты не исчезли, как они должны были, и прервали поиск.Мало того, но они также не препятствовали сглаживанию, как должны были.
TL; DR - Кавычки внутри переменная не анализируется так же, как кавычки снаружи переменная.
† Первые четыре токена не имеют специальных символов.Но последний, "*.a"
, делает.Эта звездочка является символом подстановки.Если вы внимательно прочитаете раздел «Расширение пути» на странице руководства, то увидите, что там нет упоминаний о игнорируемых кавычках.Двойные кавычки не защищают звездочку.
Держись!Какие?Я думал, что кавычки запрещают расширение глобуса!
Они делают - нормально.Если вы пишете кавычки вручную, они действительно останавливают расширение глобуса.Но если вы помещаете их в переменную без кавычек, они этого не делают.
$ touch 'foobar' '"foobar"'
$ ls
foobar "foobar"
$ ls foo*
foobar
$ ls "foo*"
ls: foo*: No such file or directory
$ var="\"foo*\""
$ echo "$var"
"foo*"
$ ls $var
"foobar"
Прочтите это внимательно.Если мы создадим файл с именем "foobar"
, то есть в его имени есть буквальные двойные кавычки, то ls $var
напечатает "foobar"
.Глобус развернут и соответствует (по общему мнению, надуманному) имени файла!
Почему кавычки не помогли?Ну, объяснение тонкое и хитрое.На странице руководства написано:
После разделения слов ... bash сканирует каждое слово на наличие символов *
, ?
и [
.
Любойtime Bash выполняет разбиение слов , а также расширяет глобусы .Помните, как я сказал, что переменные без кавычек подчиняются подразумеваемому оператору split + glob ?Это то, что я имел в виду.Расщепление и сглаживание идут рука об руку.
Если вы пишете ls "foo*"
, кавычки не позволяют foo*
подвергаться расщеплению и сглаживанию.Однако, если вы напишите ls $var
, то $var
будет развернут, разделен и выделен.Он не был окружен двойными кавычками.Неважно, что содержит двойные кавычки.К тому времени, когда появляются эти двойные кавычки, уже слишком поздно.Разделение слов уже было выполнено, и поэтому сглаживание также выполняется.