Я искал способ передать переменную окружения вызовам chroot из debootstrap и заметил, что следующее также работает:
$ sudo MY_VAR=5 chroot mychroot env | grep MY_VAR
MY_VAR=5
Вы теряете кавычки из-за расширения оболочки при подстановке команд, вам просто нужно снова заключить это в кавычки. Рекомендуется использовать форму $()
вместо обратных кавычек. Это делает ваш код более читабельным.
eval./test.py "$(find./testdata -type f -printf "\"%p\" ")"
Обновлен :, теперь он ведет себя как другие примеры, я поставил eval впереди, это приведет к правильному расширению/цитированию для вас, чтобы получить отдельные аргументы в кавычках для python.
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
имеет квалификатор z
glob для этого (обратите внимание, что zsh по умолчанию не соответствует POSIX, поскольку он разделяет только, а не разделяет + glob при отсутствии кавычек. подстановки команд в контекстах списка ), а также квалификатор Q
glob для удаления одного уровня цитирования. В этой оболочке вы можете сделать:
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[@]}"
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
один раз для каждого файла.