Вопрос или проблема
Пример:
% touch -- safe-name -name-with-dash-prefix "name with space" \
'name-with-double-quote"' "name-with-single-quote'" \
'name-with-backslash\'
xargs
не может обрабатывать двойные кавычки:
% ls | xargs ls -l
xargs: несоответствующая двойная кавычка; по умолчанию кавычки являются специальными для xargs, если вы не используете опцию -0
ls: недействительная опция -- 'e'
Попробуйте 'ls --help' для получения дополнительной информации.
Если мы используем опцию -0
, она вызывает проблемы с именем, которое имеет дефис в префиксе:
% ls -- * | xargs -0 -- ls -l --
ls: недействительная опция -- 'e'
Попробуйте 'ls --help' для получения дополнительной информации.
Это до использования других потенциально проблемных символов, таких как новая строка, управляющий символ и т.д.
Спецификация POSIX дает вам пример:
ls | sed -e 's/"/"\\""/g' -e 's/.*/"&"/' | xargs -E '' printf '<%s>\n'
(с именами файлов в виде произвольных последовательностей байтов (кроме /
и NULL) и sed
/xargs
ожидающими текст, вам также нужно будет установить локаль в C (где все не-NUL байты будут действительными символами), чтобы сделать это надежным (за исключением реализации xargs
, которые имеют очень низкий предел на максимальную длину аргумента))
Опция -E ''
необходима для некоторых реализаций xargs
, которые без нее бы поняли аргумент _
как указание на конец ввода (где echo a _ b | xargs
выводит только a
, к примеру).
С GNU xargs
вы можете использовать:
ls | xargs -rd '\n' printf '<%s>\n'
(также добавляя -r
(также расширение GNU), чтобы команда не выполнялась, если ввод пустой).
GNU xargs
также имеет -0
, который был скопирован несколькими другими реализациями, поэтому:
ls | tr '\n' '\0' | xargs -0 printf '<%s>\n'
является немного более портативным.
Все это предполагает, что имена файлов не содержат символов новой строки. Если могут быть имена файлов с символами новой строки, вывод ls
просто нельзя обрабатывать после. Если вы получите:
a
b
Это может быть как оба файла a
и b
, так и один файл с именем a<newline>b
, нет способа это определить.
GNU ls
имеет --quoting-style=shell-always
, который делает его вывод недвусмысленным и потенциально обрабатываемым после, но кавычки не совместимы с кавычками, ожидаемыми xargs
. xargs
распознает формы кавычек "..."
, \x
и '...'
. Но как "..."
, так и '...'
являются сильными кавычками и не могут содержать символы новой строки (только \
может экранировать символы новой строки для xargs
), так что это не совместимо с кавычками sh, где только '...'
являются сильными кавычками (и могут содержать символы новой строки), но \<newline>
является продолжением строки (удаляется) вместо экранированной новой строки.
Вы можете использовать оболочку, чтобы разобрать этот вывод и затем вывести его в формате, ожидаемом xargs
:
eval "files=($(ls --quoting-style=shell-always))"
[ "${#files[@]}" -eq 0 ] || printf '%s\0' "${files[@]}" |
xargs -0 printf '<%s>\n'
Или вы можете заставить оболочку получить список файлов и передать его с разделением NUL в xargs
. Например:
-
с
zsh
:print -rNC1 -- *(N) | xargs -r0 printf '<%s>\n'
-
с
ksh93
:(set -- ~(N)*; (($# == 0)) || printf '%s\0' "$@") | xargs -r0 printf '<%s>\n'
-
с
fish
:begin set -l files *; string join0 -- $files; end | xargs -r0 printf '<%s>\n'
-
с
bash
:( shopt -s nullglob set -- * (($# == 0)) || printf '%s\0' "$@" ) | xargs -r0 printf '<%s>\n'
Редактирование 2023. Начиная с версии 9.0 GNU coreutils (сентябрь 2021), GNU ls
теперь имеет опцию --zero
, которую можно использовать вместе с xargs -r0
:
ls --zero | xargs -r0 printf '<%s>\n'
Для того чтобы xargs
понимал опцию ввода, разделенной нулем -0
, отправляющая сторона также должна применить нулевой разделитель к данным, которые они отправляют.
Иначе между ними не будет синхронизации.
Одной из опций является команда GNU find
, которая может устанавливать такие разделители:
find . -maxdepth 1 ! -name . -print0 | xargs -0 ls -ld
Как вы сказали, xargs
не любит несоответствующие двойные кавычки, если вы не используете -0
, но -0
имеет смысл только если вы передаете ей данные, завершающиеся нулем. Поэтому это не срабатывает:
$ echo * | xargs
xargs: несоответствующая двойная кавычка; по умолчанию кавычки являются специальными для xargs, если вы не используете опцию -0
name-with-backslash -name-with-dash-prefix
Но это работает:
$ printf '%s\0' -- * | xargs -0
-- name-with-backslash\ -name-with-dash-prefix name-with-double-quote" name-with-single-quote' name with space safe-name
В любом случае, ваш основной подход не является наилучшим способом сделать это. Вместо того чтобы возиться с xargs
и ls
и прочим, просто используйте оболочечные шаблоны:
$ for f in *; do ls -l -- "$f"; done
-rw-r--r-- 1 terdon terdon 4142 Aug 11 16:03 a
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name-with-backslash\'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 -name-with-dash-prefix
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name-with-double-quote"'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 "name-with-single-quote'"
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name with space'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 safe-name
Это крайне глупо пытаться разобрать вывод команды ls
, которая не предназначена для разбора, чтобы передать команде, которая не предназначена для работы с несколькими символами (например: новые строки
и {}
), когда оболочка делает это сама:
set -- *; for f; do echo "<$f>"; done
set -- *
for f
do ls "$f"
done
Или, в одной строке команды:
$ set -- *; for f; do echo "<$f>"; done
<name-with-backslash\>
<-name-with-dash-prefix>
<name-with-double-quote">
<name-with-single-quote'>
<name with space>
<safe-name>
<with_a
newline>
Обратите внимание, что вывод обрабатывает (и имеет пример в качестве последнего имени файла) новые строки вполне нормально.
Или, если количество файлов делает оболочку медленной, используйте find:
$ find ./ -type f -exec echo '<{}>' \;
<./safe-name>
<./with_a
newline>
<./name-with-double-quote">
<./-name-with-dash-prefix>
<./name with space>
<./name-with-single-quote'>
<./name-with-backslash\>
Просто имейте в виду, что find обрабатывает все скрытые файлы и все подкаталоги по-другому, чем оболочка.
Решение с xargs -d '\n'
Если ваша команда не поддерживает разделение нулем, и если входные данные также не содержат буквальных новых строк, мы также можем решить проблему, передав -d '\n'
в xargs.
Например, эта попытка объединить каждую пару строк завершается ошибкой:
printf "1'\n2\n3\n4\n" | xargs -n2 echo
но нам удается обойти это с помощью:
printf "1'\n2\n3\n4\n" | xargs -d '\n' -n2 echo
что дает желаемый вывод:
1' 2
3 4
man xargs
говорит:
--delimiter=delim, -d delim
Элементы ввода завершаются указанным символом. Указанный разделитель может быть одиночным символом, символом экранирования в стиле C, таким как
\n
, или восьмеричным или шестнадцатеричным экранирующим кодом. Восьмеричные и шестнадцатеричные экранирующие коды понимаются так же, как и для команды printf. Много байтные символы не поддерживаются. При обработке ввода кавычки и обратная косая черта не являются специальными; каждый символ во вводе принимается буквально. Опция -d отключает любую строку конца файла, которая обрабатывается как любой другой аргумент. Вы можете использовать эту опцию, когда ввод состоит просто из строк, разделенных новой строкой, хотя почти всегда лучше разработать вашу программу с использованием--null
, если это возможно.
Связано:
- https://askubuntu.com/questions/842935/how-can-i-solve-unmatched-double-quote-error-using-dbus-monitor-in-combination тот же вопрос для двойных кавычек
- https://askubuntu.com/questions/1106805/xargs-unmatched-single-quote-by-default-quotes-are-special-to-xargs-unless-you то же на Ask Ubuntu
- https://www.reddit.com/r/bash/comments/pl1ukb/xargs_unmatched_single_quote_by_default_quotes/ то же на Reddit
Почему происходит эта ошибка
По умолчанию printf позволяет входным данным содержать аргументы со специальными символами, такими как пробелы, если они окружены сбалансированными кавычками, например:
printf "'1 2' 3 4" | xargs -n1 echo
выдает:
1 2
3
4
поэтому мы понимаем, что вызовы, где:
echo 1 2
echo 3
echo 4
Тестировалось на xargs 4.9.0, Ubuntu 24.04.
Ответ или решение
Эффективное экранирование вывода в xargs: Полное руководство
В данной статье мы подробно рассмотрим, как правильно экранировать вывод для использования с командой xargs
, чтобы избежать ошибок, связанных с неподходящими символами, такими как кавычки, пробелы и другие специальные знаки.
Проблема с экранированием в xargs
При использовании xargs
для обработки вывода из других команд, пользователи часто сталкиваются с проблемами экранирования. Например, если команды, такие как ls
, выводят имена файлов, содержащие пробелы или кавычки, xargs
может не корректно интерпретировать эти имена файлов. Рассмотрим следующие примеры:
$ ls | xargs ls -l
xargs: unmatched double quote; by default quotes are special to xargs unless you use the -0 option
Этот пример демонстрирует, что xargs
не может справиться с неподходящими кавычками, что приводит к ошибкам.
Решение с использованием -0
Для безопасной передачи данных в xargs
, рекомендуется использовать опцию -0
. Она ожидает, что данные будут разделены нулевыми символами (\0
). Однако, для этого необходимо передавать данные в таком формате из исходной команды. Например, можно использовать find
, который поддерживает вывод с нулевыми разделителями:
find . -maxdepth 1 -name '*' -print0 | xargs -0 ls -l
Этот подход позволяет избежать проблем с кавычками и пробелами в именах файлов.
Альтернативные решения
Если ваше окружение не поддерживает нулевые разделители, можно использовать механизмы оболочки для решения подобных проблем. Например, в bash
можно воспользоваться следующей конструкцией:
for f in *; do
ls -l -- "$f"
done
Этот метод избегает использования xargs
, что может быть более надежным при работе с именами файлов, содержащими специальные символы.
Совместимость с другими оболочками
Каждая оболочка предлагает свои методы работы с именами файлов и экранированием. Например, в zsh
и ksh93
можно использовать аналогичные подходы:
zsh
print -rNC1 -- *(N) | xargs -r0 printf '<%s>\n'
ksh93
(set -- ~(N)*; (($# == 0)) || printf '%s\0' "$@") | xargs -r0 printf '<%s>\n'
Каждый из этих примеров демонстрирует, как можно безопасно обрабатывать имена файлов.
Передача через printf и xargs
Также можно воспользоваться printf
для формирования строки с нулевыми разделителями, что будет полезно при использовании xargs
. Пример:
printf '%s\0' -- * | xargs -0 printf '<%s>\n'
Этот подход уверенно обрабатывает имена файлов, избегая проблем с кавычками и другими специальными символами.
Заключение
Для успешного использования xargs
с потенциально проблемными именами файлов, рекомендуется заранее обрабатывать вывод, используя нулевые разделители или механизмы оболочки для корректного экранирования. Запомните, что команда ls
не предназначена для парсинга, и лучше использовать альтернативы, такие как find
для безопасного и надежного взаимодействия с файловой системой.
Применяя описанные методы, вы значительно повысьте надежность своих сценариев и избежите распространенных ошибок, связанных с экранированием символов в командной строке.