Кавычки обрабатываются по-разному в выводе обратной кавычки

Я искал способ передать переменную окружения вызовам chroot из debootstrap и заметил, что следующее также работает:

$ sudo MY_VAR=5 chroot mychroot env | grep MY_VAR
MY_VAR=5
0
05.09.2019, 18:08
3 ответа

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

eval./test.py "$(find./testdata -type f  -printf "\"%p\" ")"

Обновлен :, теперь он ведет себя как другие примеры, я поставил eval впереди, это приведет к правильному расширению/цитированию для вас, чтобы получить отдельные аргументы в кавычках для python.

0
28.01.2020, 02:29

xargsвыполняет собственную специальную обработку своего ввода.

Он обрабатывает все последовательности символов новой строки и пробелов (, по крайней мере, пробела и табуляции, а в некоторых реализациях )больше как разделители, игнорирует начальные и конечные символы и обрабатывает кавычки по-своему :'...', "..."и \можно использовать для заключения в кавычки, но иначе, чем в синтаксисе sh"...", и '...'являются сильными кавычками, но не могут содержать новую строку, а \newlineявляется буквальным переводом строки вместо продолжения строки ).

Итак, при вводе типа:

   "foo \ bar" 'x'\
y

xargsгенерирует два аргумента foo \ barи x<newline>y.

Подстановка команды (как в архаичной `...`, так и в современной $(...)формах )без кавычек в контекстах списка в оболочках POSIX — это оператор split+glob. Ввод разбивается на $IFSсимволов с использованием сложных правил, а полученные слова подвергаются генерации имени файла . Там вообще нет обработки котировок.

При вводе типа

  "a* b"

Со значением по умолчанию$IFS(SPC, TAB, NL ),он генерирует слово "a*, которое далее расширяется до списка имен файлов в текущем каталоге, начинающихся с "aи слова b".

Командная строка типа:

cmd "a* b"
cmd2 "x\"y"

— это код в синтаксисе оболочки. В синтаксисе оболочки пробелы, новые строки и кавычки также имеют особое значение и интерпретируются по-разному для xargs. Приведенный выше код анализируется как две команды, так как команды разделяются новой строкой, cmd "a* b"анализируется как два слова:cmdи a* b, поскольку слова разделяются пробелом, а "..."является оператором кавычек оболочки, который предотвращает *и SPC внутри от особого обращения... и т. д.

Чтобы выполнить токенизацию так же, как это делает оболочка, zshимеет квалификатор zglob для этого (обратите внимание, что zsh по умолчанию не соответствует POSIX, поскольку он разделяет только, а не разделяет + glob при отсутствии кавычек. подстановки команд в контекстах списка ), а также квалификатор Qglob для удаления одного уровня цитирования. В этой оболочке вы можете сделать:

output_of_cmd=$(find...) # no split+glob here as we're assigning to
                         # scalar variable. It's not a list context

words=("${(Q@)${(z)output_of_cmd}}") # array assignment
your-app "${words[@]}"
0
28.01.2020, 02:29

So the next best explanation seems to be that every white-space separated string is treated as a separate argument, without any regards to quoting. Is this correct?

Да, см., например,.https://mywiki.wooledge.org/WordSplittingи Почему мой сценарий оболочки забивается пробелами или другими специальными символами? и Когда необходимо двойное -цитирование?

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

And if so, why do backticks have this strange behaviour? I guess this is not what we would want most of the time...

Что ж, странность относительна. И то, что хочется в одном случае, может быть совсем не тем, чего хотят в другом.

Но рассмотрим нечто подобное:

a="blah blah"
somecmd -f "$a"

Это работает следующим образом: somecmdполучает в качестве аргумента строку, содержащуюся в переменной a, независимо от того, что она содержит . Это похоже на то, как это работает в «настоящих» языках программирования, скажем subprocess.call(["somecmd", "-f", a])в Python. Простой, чистый и полностью безопасный :никакие специальные символы в переменной не могут все испортить.

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

echo "Please enter a filename: "
read -r a
somecmd -f "$a"

Если результат расширений обрабатывался на кавычки, то нельзя было вводить Don't stop me now.mp3в качестве имени файла, так как там непарная кавычка.

Кроме того, должны ли результаты всех расширений обрабатываться и для дальнейших расширений? Установка aна $(rm -rf $HOME).txtприведет к довольно неприятным последствиям. Обратите внимание, что это совершенно правильное имя файла, поэтому оно может появиться в результате глобуса, такого как *.txt.

Я знаю, это немного преувеличено, поскольку мы могли бы предложить, чтобы после расширения обрабатывались только кавычки и escape-последовательности, а не какие-либо дальнейшие расширения. Непарные одинарные -кавычки по-прежнему будут проблемой, а $(find -printf "\"%p\"")по-прежнему не будет работать для имен файлов, содержащих двойные -кавычки.

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


Но вы правы, это означает, что не существует очевидного прямого способа получить список строк из findв оболочку. На самом деле это то, что вам действительно нужно, список строк, например sys.argvв Python. Не цитаты.

Вот что вы можете сделать:

find -print0 | xargs -0./test.py

-print0просит findпечатать имена файлов с байтом NUL в качестве разделителя (вместо новой строки ), а -0говорит xargsожидать именно этого. Это работает, поскольку байт NUL — это единственное, что не может содержаться в имени файла. -print0и -0можно найти как минимум в GNU и FreeBSD.

Или, в Баше:

mapfile -d '' files < <(find -print0)
./test.py "${files[@]}"

Это те же строки, разделенные NUL -, которые используются с подстановкой процесса и массивом.

Или, в Bash (сshopt -s globstar)и другими, которые имеют аналогичную функцию, и если вам не нужно фильтровать на основе чего-либо, кроме имени файла:

shopt -s globstar
./test.py./testdata/**

**похож на *, только рекурсивный.

Или со стандартными инструментами:

find -exec./test.py {} +

Это позволяет обойти всю проблему, попросив findзапустить саму test.py, без передачи списка имен файлов куда-либо еще. Однако не помогает, если вам действительно нужно где-то хранить список. Обратите внимание на +в конце, -exec./test.py {} \;будет запускать test.pyодин раз для каждого файла.

2
28.01.2020, 02:29

Теги

Похожие вопросы