Как я могу использовать xargs для копирования файлов, в названиях которых есть пробелы и кавычки?
-
02-07-2019 - |
Вопрос
Я пытаюсь скопировать кучу файлов под каталогом, и в названиях некоторых файлов есть пробелы и одинарные кавычки.Когда я пытаюсь собраться воедино find
и grep
с xargs
, я получаю следующую ошибку:
find .|grep "FooBar"|xargs -I{} cp "{}" ~/foo/bar
xargs: unterminated quote
Есть какие-либо предложения по более надежному использованию xargs?
Это включено Mac OS X 10.5.3 (Leopard) с BSD xargs
.
Решение
Вы можете объединить все это в один find
команда:
find . -iname "*foobar*" -exec cp -- "{}" ~/foo/bar \;
Это будет обрабатывать имена файлов и каталогов с пробелами в них.Вы можете использовать -name
чтобы получить результаты с учетом регистра.
Примечание:Тот Самый --
флаг передан cp
запрещает ему обрабатывать файлы, начинающиеся с -
в качестве вариантов.
Другие советы
find . -print0 | grep --null 'FooBar' | xargs -0 ...
Я не знаю, стоит ли grep
поддерживает --null
, и не является ли xargs
поддерживает -0
, на Leopard, но на GNU все хорошо.
Самый простой способ сделать то, что хочет исходный плакат, - это изменить разделитель с любого пробела только на символ конца строки, подобный этому:
find whatever ... | xargs -d "\n" cp -t /var/tmp
Это более эффективно, так как не запускает "cp" несколько раз:
find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar
Я столкнулся с той же проблемой.Вот как я решил эту проблему:
find . -name '*FoooBar*' | sed 's/.*/"&"/' | xargs cp ~/foo/bar
Я использовал sed
заменить каждую строку ввода одной и той же строкой, но заключенной в двойные кавычки.Из самого sed
справочная страница",...Амперсанд (`&"), появляющийся при замене, заменяется строкой, соответствующей RE..." ... в данном случае, .*
, вся линия.
Это решает проблему xargs: unterminated quote
ошибка.
Этот метод работает на Mac OS X версии 10.7.5 (Лев):
find . | grep FooBar | xargs -I{} cp {} ~/foo/bar
Я также протестировал точный синтаксис, который вы опубликовали.Это также отлично работало в версии 10.7.5.
Просто не используйте xargs
.Это аккуратная программа, но она плохо сочетается с find
когда сталкиваешься с нетривиальными случаями.
Вот переносимое (POSIX) решение, т.е.тот, который не требует find
, xargs
или cp
Расширения, специфичные для GNU:
find . -name "*FooBar*" -exec sh -c 'cp -- "$@" ~/foo/bar' sh {} +
Обратите внимание на окончание +
вместо более привычного ;
.
Это решение:
корректно обрабатывает файлы и каталоги со встроенными пробелами, символами перевода строки или любыми другими экзотическими символами.
работает в любой системе Unix и Linux, даже в тех, которые не предоставляют GNU toolkit.
не использует
xargs
это хорошая и полезная программа, но для правильной работы с ней требуется слишком много настроек и нестандартных функцийfind
результат.также более эффективный (читать быстрее), чем общепринятые и большинство, если не все остальные ответы.
Обратите также внимание, что, несмотря на то, что указано в некоторых других ответах или комментариях, цитирующих {}
бесполезен (если только вы не используете экзотический fish
оболочка).
Посмотрите, как использовать параметр командной строки --null для xargs с параметром -print0 в find.
Для тех, кто полагается на команды, отличные от find, например ls
:
find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
find | perl -lne 'print quotemeta' | xargs ls -d
Я считаю, что это будет надежно работать для любого символа, кроме перевода строки (и я подозреваю, что если у вас есть переводы строк в именах файлов, то у вас проблемы похуже этой).Для этого не требуется GNU findutils, только Perl, так что он должен работать практически везде.
Я обнаружил, что следующий синтаксис хорошо работает для меня.
find /usr/pcapps/ -mount -type f -size +1000000c | perl -lpe ' s{ }{\\ }g ' | xargs ls -l | sort +4nr | head -200
В этом примере я ищу 200 самых больших файлов размером более 1 000 000 байт в файловой системе, смонтированной в "/usr/pcapps".
Строка Perl между "find" и "xargs" экранирует / заключает в кавычки каждый пробел, поэтому "xargs" передает любое имя файла со встроенными пробелами в "ls" в качестве единственного аргумента.
Имейте в виду, что большинство вариантов, обсуждаемых в других ответах, не являются стандартными для платформ, которые не используют утилиты GNU (например, Solaris, AIX, HP-UX).Смотрите на POSIX спецификация для "стандартного" поведения xargs.
Я также нахожу поведение xargs, при котором он запускает команду по крайней мере один раз, даже без ввода, неприятным.
Я написал свою собственную версию xargs (xargl), чтобы справиться с проблемами пробелов в именах (разделяются только новые строки - хотя 'find ...комбинация "-print0" и "xargs -0" довольно аккуратна, учитывая, что имена файлов не могут содержать символов ASCII NUL '\ 0'.Мой xargl не такой полный, каким должен был бы быть, чтобы его стоило публиковать, тем более что у GNU есть средства, которые, по крайней мере, не хуже.
С помощью Bash (не POSIX) вы можете использовать замену процесса, чтобы получить текущую строку внутри переменной.Это позволяет вам использовать кавычки для экранирования специальных символов:
while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)
Что касается меня, то я пытался сделать что-то немного другое.Я хотел скопировать свои файлы .txt в свою папку tmp.Имена файлов .txt содержат пробелы и символы апострофа.Это сработало на моем Mac.
$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/' | xargs -I{} cp -v {} ./tmp/
Если версии find и xarg в вашей системе не поддерживаются -print0
и -0
переключатели (например, AIX find и xargs), вы можете использовать этот ужасно выглядящий код:
find . -name "*foo*" | sed -e "s/'/\\\'/g" -e 's/"/\\"/g' -e 's/ /\\ /g' | xargs cp /your/dest
Здесь sed позаботится об экранировании пробелов и кавычек для xargs.
Протестировано на AIX 5.3
Я создал небольшой переносимый скрипт-оболочку под названием "xargsL" вокруг "xargs", который решает большинство проблем.
В отличие от xargs, xargsL принимает один путь к строке.Имена путей могут содержать любой символ, за исключением (очевидно) перевода строки или нулевых байтов.
В списке файлов кавычки не разрешены и не поддерживаются - имена ваших файлов могут содержать всевозможные пробелы, обратную косую черту, обратные метки, подстановочные знаки оболочки и тому подобное - xargsL обработает их как буквенные символы, никакого вреда не причинит.
В качестве дополнительной бонусной функции xargsL будет нет запустите команду один раз, если ввода нет!
Обратите внимание на разницу:
$ true | xargs echo no data
no data
$ true | xargsL echo no data # No output
Любые аргументы, переданные xargsL, будут переданы в xargs.
Вот сценарий POSIX-оболочки "xargsL":
#! /bin/sh # Line-based version of "xargs" (one pathname per line which may contain any # amount of whitespace except for newlines) with the added bonus feature that # it will not execute the command if the input file is empty. # # Version 2018.76.3 # # Copyright (c) 2018 Guenther Brunthaler. All rights reserved. # # This script is free software. # Distribution is permitted under the terms of the GPLv3. set -e trap 'test $? = 0 || echo "$0 failed!" >& 2' 0 if IFS= read -r first then { printf '%s\n' "$first" cat } | sed 's/./\\&/g' | xargs ${1+"$@"} fi
Поместите скрипт в какой-нибудь каталог в вашем $PATH и не забудьте
$ chmod +x xargsL
скрипт есть для того, чтобы сделать его исполняемым.
версия bill_starr на Perl от bill_starr не будет хорошо работать со встроенными символами перевода строк (справляется только с пробелами).Для тех, кто находится, например, наSolaris, где у вас нет инструментов GNU, может быть более полная версия (с использованием sed)...
find -type f | sed 's/./\\&/g' | xargs grep string_to_find
отрегулируйте аргументы find и grep или другие команды по своему усмотрению, но sed исправит ваши встроенные переводы строк / пробелы / табуляции.
Я использовал Ответ Билла Стара немного изменено на Solaris:
find . -mtime +2 | perl -pe 's{^}{\"};s{$}{\"}' > ~/output.file
Это позволит заключить каждую строку в кавычки.Я не использовал опцию "-l", хотя это, вероятно, помогло бы.
Список файлов, который я просматривал, мог содержать '-', но не новые строки.Я не использовал выходной файл с какими-либо другими командами, поскольку хочу просмотреть то, что было найдено, прежде чем просто начну массово удалять их с помощью xargs.
Я немного поиграл с этим, начал подумывать об изменении xargs и понял, что для того варианта использования, о котором мы здесь говорим, простая переопределение в Python - лучшая идея.
С одной стороны, наличие ~ 80 строк кода для всего этого означает, что легко понять, что происходит, и если требуется другое поведение, вы можете просто взломать его в новом скрипте за меньшее время, чем требуется для получения ответа на что-то вроде Stack Overflow.
Видишь https://github.com/johnallsup/jda-misc-scripts/blob/master/yargs и https://github.com/johnallsup/jda-misc-scripts/blob/master/zargs.py.
С помощью написанного yargs (и установленного Python 3) вы можете ввести:
find .|grep "FooBar"|yargs -l 203 cp --after ~/foo/bar
выполнять копирование 203 файлов одновременно.(Здесь 203, конечно, просто заполнитель, и использование странного числа, такого как 203, дает понять, что это число не имеет никакого другого значения.)
Если вы действительно хотите что-то более быстрое и без необходимости использования Python, возьмите zargs и yargs в качестве прототипов и перепишите на C ++ или C.
Возможно, вам потребуется создать grep-каталог Foobar следующим образом:
find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .
Frame challenge — вы спрашиваете, как использовать xargs.Ответ таков:вы не используете xargs, потому что вам это не нужно.
Тот Самый комментарий автора user80168
описывает способ сделать это непосредственно с помощью cp, без вызова cp для каждого файла:
find . -name '*FooBar*' -exec cp -t /tmp -- {} +
Это работает, потому что:
- тот самый
cp -t
флаг позволяет указать целевой каталог ближе к началуcp
, а не ближе к концу.Отman cp
:
-t, --target-directory=DIRECTORY copy all SOURCE arguments into DIRECTORY
Тот Самый
--
флаг подсказываетcp
интерпретировать все, что следует после, как имя файла, а не флаг, поэтому файлы, начинающиеся с-
или--
не путайтеcp
;вы все еще нуждаетесь в этом, потому что-
/--
символы интерпретируются с помощьюcp
, тогда как любые другие специальные символы интерпретируются командной оболочкой.Тот Самый
find -exec command {} +
variant по сути делает то же самое, что и xargs.Отman find
:
-exec command {} + This variant of the -exec action runs the specified command on the selected files, but the command line is built by appending each selected file name at the end; the total number of invoca‐ matched files. The command line is built in much the same way that xargs builds its command lines. Only one instance of `{}' is allowed within the command, and (when find is being invoked from a shell) it should be quoted (for example, '{}') to protect it from interpretation by shells. The command is executed in the starting directory. If any invocation returns a non-zero value as exit status, then find returns a non-zero exit status. If find encounters an error, this can sometimes cause an immedi‐ ate exit, so some pending commands may not be run at all. This variant of -exec always returns true.
Используя это непосредственно в find, это позволяет избежать необходимости вызова канала или командной оболочки, так что вам не нужно беспокоиться о каких-либо неприятных символах в именах файлов.
Если вы используете Bash, вы можете конвертировать стандартный выход к массиву строк с помощью mapfile
:
find . | grep "FooBar" | (mapfile -t; cp "${MAPFILE[@]}" ~/foobar)
Преимущества заключаются в следующем:
- Он встроенный, так что работает быстрее.
- Выполните команду со всеми именами файлов за один раз, так это будет быстрее.
Вы можете добавить другие аргументы к именам файлов.Для
cp
, вы также можете:find . -name '*FooBar*' -exec cp -t ~/foobar -- {} +
однако некоторые команды не имеют такой функции.
Недостатки:
- Возможно, недостаточно хорошо масштабируется, если слишком много имен файлов.(Предел?Я не знаю, но я тестировал файл списка размером 10 МБ, который включает более 10000 имен файлов без проблем, под Debian)
Что ж...кто знает, доступен ли Bash в OS X?