Расширение параметра для команды поиска - PullRequest
0 голосов
/ 07 декабря 2018

Рассмотрим код (переменная $i существует, потому что она была в цикле, добавляя к шаблону несколько условий, например, *.a и *.b, ..., но чтобы проиллюстрировать эту проблему, используется только один шаблон подстановкидостаточно):

#!/bin/bash

i="a"
PATTERN="-name bar -or -name *.$i"
find . \( $PATTERN \)

Если запустить папку, содержащую файлы bar и foo.a, она заработает:

./foo.a
./bar

Но если вы теперь добавите новый файл впапка, а именно zoo.a, больше не работает:

find: paths must precede expression: zoo.a

Предположительно, поскольку подстановочный знак в *.$i расширяется оболочкой до foo.a zoo.a, что приводит к неверному findшаблон команды.Поэтому одна попытка исправить это заключить в кавычки шаблон подстановки.Кроме того, он не работает:

  • с одинарными кавычками - PATTERN="-name bar -or -name '*.$i'" команда find выводит только bar.Удаление одинарных кавычек (\') приводит к тому же результату.

  • То же самое с двойными кавычками: PATTERN="-name bar -or -name \"*.$i\"" - возвращается только bar.

  • в команде find, если $PATTERN заменено на "$PATTERN", то возникает ошибка (для одинарных кавычек та же ошибка, но с одинарными кавычками вокруг шаблона подстановки):

    найти: неизвестный предикат -name bar -or -name "*.a"'

Конечно, замена $PATTERN на '$PATTERN' также не работает ... (никакого расширения не происходит).

Единственный способ заставить его работать - это использовать ... eval!

FINDSTR="find . \( $PATTERN \)"
eval $FINDSTR

Это работает правильно:

./zoo.a
./foo.a
./bar

Теперь, после долгих поисков, яЯ видел, как несколько раз упоминалось, что для этого нужно использовать массивы .Но это не работает:

i="a"
PATTERN=( -name bar -or -name '*.$i' )
find . \( "${PATTERN[@]}" \)

# result: ./bar

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

i="a"
PATTERN=( -name bar -or -name *.$i )
find . \( "${PATTERN[@]}" \)

# result: find: paths must precede expression: zoo.a

НО ДВОЙНЫЕ ЦИТАТЫ РАБОТАЮТ !!на самом деле два вопроса:

a) в последнем примере с использованием массивов, почему двойные кавычки требуются для *.$i?

b) использование массива таким образом должно расширяться «всем элементам, указанным индивидуально» .Как бы сделать это с переменной (см. Мою первую попытку)?После того, как это стало работать, я вернулся и снова попытался использовать переменную с одинарными кавычками, выделенными черным, или \\', но ничего не получалось (у меня только bar).Что мне нужно сделать, чтобы эмулировать «вручную» как бы цитату, выполняемую при использовании массивов?

Заранее благодарю за помощь.

1 Ответ

0 голосов
/ 07 декабря 2018

Обязательное чтение:

a) в последнем примере с использованием массивов, почему двойные кавычки требуются вокруг *.$i?

Вам необходимо использовать некоторыеформа цитирования, чтобы не допустить, чтобы оболочка выполнила расширение glob на *.Переменные не раскрываются в одинарных кавычках, поэтому '*.$i' не работает.Это препятствует расширению глобуса, но также останавливает расширение переменной."*.$i" запрещает расширение глобуса, но допускает расширение переменной, что идеально.

Чтобы по-настоящему углубиться в детали, здесь нужно сделать две вещи:

  1. Escape или quote* для предотвращения расширения глобуса.
  2. Считайте $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 в разделе «Расширение» мы видим порядок операций:

  1. Расширение скобки
  2. Расширение тильды, расширение параметров и переменных,арифметическое расширение, подстановка команд и подстановка процессов
  3. Разделение слов
  4. Расширение пути (AKA glob)
  5. Удаление кавычек

Давайте пройдемся по этимоперации вручную и посмотрите, как анализируется find . \( $PATTERN \).Конечным результатом будет список строк, поэтому я буду использовать JSON-подобный синтаксис для отображения каждой стадии.Начнем со списка, содержащего одну строку:

['find . \( $PATTERN \)']

В качестве предварительного шага командная строка в целом может быть разбита на слова.

['find', '.', '\(', '$PATTERN', '\)']
  1. Расширение скобки - Без изменений.

  2. Переменное расширение

    ['find', '.', '\(', '-name bar -or -name "*.a"', '\)']
    

    $PATTERNзаменяетсяНа данный момент это все одна строка, пробел и все.

  3. Разделение слов

    ['find', '.', '\(', '-name', 'bar', '-or', '-name', '"*.a"', '\)']
    

    Оболочка просматривает результаты переменныхрасширение, которое не встречалось в двойных кавычках для разделения слов.$PATTERN без кавычек, поэтому он расширен.Теперь это куча отдельных слов.Пока все хорошо.

  4. Расширение глобуса

    ['find', '.', '\(', '-name', 'bar', '-or', '-name', '"*.a"', '\)']
    

    Bash сканирует результаты разбиения слов на глобусы.Не вся командная строка, а только токены -name, bar, -or, -name и "*.a".

    Похоже, ничего не произошло, да?Не так быстро!Внешность может быть обманчива.Bash фактически выполнил расширение glob.Просто так случилось, что шар не соответствовал ничему.Но это может ...

  5. Удаление цитаты

    ['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 будет развернут, разделен и выделен.Он не был окружен двойными кавычками.Неважно, что содержит двойные кавычки.К тому времени, когда появляются эти двойные кавычки, уже слишком поздно.Разделение слов уже было выполнено, и поэтому сглаживание также выполняется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...