Как я могу использовать xargs для копирования файлов с пробелами и кавычками в именах?

178

Я пытаюсь скопировать кучу файлов под каталогом, и у нескольких файлов есть пробелы и одиночные кавычки в их именах. Когда я пытаюсь соединить find и grep с xargs, я получаю следующую ошибку:

find .|grep "FooBar"|xargs -I{} cp "{}" ~/foo/bar
xargs: unterminated quote

Любые предложения по более надежному использованию xargs?

Это находится на MacOS 10.5.3 с BSD xargs.

  • 2
    Сообщение об ошибке GNU xargs для этого с именем файла, содержащим одиночную кавычку, является более полезным: «xargs: непарная одинарная кавычка; по умолчанию кавычки являются специальными для xargs, если вы не используете опцию -0».
  • 2
    GNU xargs также имеет опцию --delimiter ( -d ). Попробуйте это с \n в качестве разделителя. Это не xargs разделить строки с пробелами на несколько слов / аргументов.
Теги:
macos
command-line
xargs

21 ответ

178
Лучший ответ

Вы также можете объединить все это в одну команду find:

find . -iname "*foobar*" -exec cp "{}" ~/foo/bar \;

Это будет обрабатывать имена файлов и каталоги с пробелами в них. Вы можете использовать -name для получения результатов, чувствительных к регистру.

(Эти аргументы командной строки будут работать с GNU find; я не знаю, доступны ли они с помощью BSD или OS X.)

  • 0
    -exec будет работать с любой находкой, я никогда не понимал, почему люди используют xargs (и просто подождите, пока вы не достигнете предела длины каталога xargs !!)
  • 1
    Что такое «ограничение длины каталога xargs»? Вы имеете в виду максимальный размер команды? Если да, xargs должен разделить свои аргументы в соответствии с размерами группы.
Показать ещё 11 комментариев
104

find . -print0 | grep --null 'FooBar' | xargs -0 ...

Я не знаю, поддерживает ли grep --null, и не поддерживает ли xargs -0 на Leopard, но на GNU все это хорошо.

  • 1
    Leopard поддерживает "-Z" (это GNU grep) и, конечно, find (1) и xargs (1) поддерживают "-0".
  • 0
    @Keltia Обратите внимание, что в OS X Mountain Lion (10.8) Apple заменила GNU grep на BSD grep, и ключ - {z | Z} не работает.
Показать ещё 9 комментариев
60

Это более эффективно, так как он не запускает "cp" несколько раз:

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar
  • 1
    Это не сработало для меня. Он пытался перебросить ~ / foo / bar во все, что вы найдете, но не наоборот
  • 13
    Флаг -t для cp является расширением GNU AFAIK и недоступен в OS X. Но если бы он был, он работал бы так, как показано в этом ответе.
Показать ещё 1 комментарий
50

Я столкнулся с той же проблемой. Вот как я это решил:

find . -name '*FoooBar*' | sed 's/.*/"&"/' | xargs cp ~/foo/bar

Я использовал sed, чтобы подставить каждую строку ввода той же строкой, но окружен двойными кавычками. На странице sed "... Амперсанд (` `& ''), появляющийся в замене, заменяется строкой, соответствующей RE..." - в этом случае .*, весь линия.

Это решает ошибку xargs: unterminated quote.

  • 3
    Я использую windows и использую gnuwin32, поэтому мне пришлось использовать sed s/.*/\"&\"/ чтобы заставить его работать.
  • 0
    Милая! Я думаю, что я буду использовать это много.
Показать ещё 2 комментария
47

Самый простой способ сделать то, что хочет исходный плакат, - это изменить разделитель от любого пробела до только конца строки следующим образом:

find whatever ... | xargs -d "\n" cp -t /var/tmp
  • 3
    Этот ответ прост, эффективен и понятен: заданный по умолчанию разделитель для xargs слишком широк и его необходимо сузить для того, что хочет сделать OP. Я знаю это из первых рук, потому что я столкнулся с той же самой проблемой сегодня, делая что-то подобное, за исключением Cygwin. Если бы я прочитал справку по команде xargs, я мог бы избежать нескольких головных болей, но ваше решение помогло мне. Спасибо ! (Да, OP был на MacOS с использованием BSD xargs, который я не использую, но я надеюсь, что параметр "-d" xargs существует во всех версиях).
  • 6
    Хороший ответ, но не работает на Mac. Вместо этого мы можем передать поиск в sed -e 's_\(.*\)_"\1"_g' чтобы sed -e 's_\(.*\)_"\1"_g' в кавычки имя файла
Показать ещё 5 комментариев
41

Этот метод работает на Mac OSx Lion 10.7.5

find . | grep FooBar | xargs -I{} cp {} ~/foo/bar

Изменить: также просто протестирован точный синтаксис, который вы опубликовали. Это также отлично работало на 10.7.5.

  • 0
    Работает на Linux тоже.
  • 0
    Это работает для меня.
Показать ещё 4 комментария
8

Вот портативное (POSIX) решение, то есть одно, которое не требует find, xargs или cp конкретных расширений GNU:

find . -name "*FooBar*" -exec sh -c 'cp -- "$@" ~/foo/bar' sh {} +

Он будет корректно обрабатывать файлы и каталоги со встроенными пространствами, новыми символами и т.д., и более эффективен (читается быстрее), чем принятый ответ.

8

Посмотрите на использование опции командной строки -null для xargs с опцией -print0 в поиске.

5

Для тех, кто полагается на команды, кроме поиска, например ls:

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
  • 1
    Работает, но медленно, потому что -I подразумевает -L 1
  • 1
    Это должен быть принятый ответ.
5
find | perl -lne 'print quotemeta' | xargs ls -d

Я считаю, что это будет надежно работать для любого персонажа, кроме строки (и я подозреваю, что если у вас есть фиды в именах файлов, то у вас проблемы с этим хуже). Он не требует GNU findutils, просто Perl, поэтому он должен работать практически везде.

  • 0
    Возможно ли иметь перевод строки в имени файла? Никогда об этом не слышал.
  • 2
    Это действительно так. Попробуйте, например, mkdir test && cd test && perl -e 'open $fh, ">", "this-file-contains-a-\n-here"' && ls | od -tx1
Показать ещё 2 комментария
5

Я обнаружил, что следующий синтаксис хорошо работает для меня.

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" как один аргумент.

Билл Старр Пт, 23 янв 2009, 17:40 EST

2

Помните, что большинство вариантов, обсуждаемых в других ответах, не являются стандартными для платформ, которые не используют утилиты GNU (например, Solaris, AIX, HP-UX). См. Спецификацию POSIX для стандартного поведения xargs.

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

Я написал свою собственную приватную версию xargs (xargl) для решения проблем пространств в именах (только новые линии разделены - хотя сочетание "find... -print0" и "xargs -0" довольно аккуратно, учитывая, что имена файлов не могут содержать символы ASCII NUL '\ 0'. Мой xargl не такой полный, как его нужно было бы опубликовать, особенно потому, что у GNU есть объекты, которые по крайней мере хороши.

  • 2
    GitHub или этого не произошло
  • 0
    @ КорейГолдберг: Я думаю, что этого не произошло тогда.
Показать ещё 1 комментарий
1

Для меня я пытался сделать что-то совсем другое. Я хотел скопировать файлы txt в папку tmp. Имена файлов txt содержат пробелы и символы апострофа. Это сработало на моем mac.

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/
1

Я немного поиграл с этим, начал размышлять о модификации xargs и понял, что для такого варианта использования, о котором мы говорим здесь, простое повторное выполнение в python - лучшая идея. Во-первых, наличие ~ 80 строк кода для всего, что означает, что легко понять, что происходит, и если требуется другое поведение, вы можете просто взломать его в новый script за меньшее время, чем требуется чтобы получить ответ где-то вроде stackoverflow.

См. https://github.com/johnallsup/jda-misc-scripts/blob/master/yargs и https://github.com/johnallsup/jda-misc-scripts/blob/master/zargs.py

С помощью yargs, записанного (и установленного python3), вы можете ввести

find .|grep "FooBar"|yargs -l 203 cp --after ~/foo/bar

выполнить копирование 203 файлов за раз. (Здесь 203 - это просто местозаполнитель, конечно, и использование странного числа, такого как 203, дает понять, что это число не имеет другого значения.)

Если вы действительно хотите что-то быстрее и без необходимости использовать python, возьмите zargs и yargs в качестве прототипов и перепишите на С++ или C.

1

С помощью bash (не POSIX) вы можете использовать подстановку процессов, чтобы получить текущую строку внутри переменной. Это позволяет использовать кавычки для вызова специальных символов:

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)
1

Я использовал ответ Bill Star, слегка измененный в Solaris:

find . -mtime +2 | perl -pe 's{^}{\"};s{$}{\"}' > ~/output.file

это будет помещать кавычки вокруг каждой строки. Я не использовал параметр "-l", хотя он, вероятно, помог бы.

Список файлов, в которые я шел, может иметь '-', но не новые строки. Я не использовал выходной файл с любыми другими командами, так как хочу просмотреть, что было найдено до того, как я просто начал массово удалять их через xargs.

1

Версия perl, приведенная выше, не будет работать хорошо для встроенных новых строк (только с пробелами). Для тех, напр. solaris, где у вас нет инструментов gnu, более полная версия может быть (с использованием sed)...

find -type f | sed 's/./\\&/g' | xargs grep string_to_find

скорректируйте аргументы find и grep или другие команды по мере необходимости, но sed установит ваши встроенные новые строки/пробелы/вкладки.

0

Если версии 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

0

Вам может понадобиться grep каталог Foobar, например:

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .
  • 1
    Для справочной страницы -i устарела, и вместо -I следует использовать -I .
0

Для пользователей глупой версии, отличной от GNU, решение Bill Starr не работает, если в имени файла есть апострофы. У rjb1 также есть такая же проблема, я думаю, хотя я не могу повторить ее с помощью теста. Работа Карла Ямамото-Фурста работает.

-1

Если вы используете bash, вы можете преобразовать stdout в массив строк mapfile,

find . | grep "FooBar" | (mapfile -t; cp "${MAPFILE[@]}" ~/foobar)

преимущества:

  • Он встроен, поэтому он быстрее.
  • Выполните команду со всеми именами файлов за один раз, поэтому быстрее.
  • Вы можете добавить другие аргументы в имена файлов. Для cp вы также можете:

    find . -name '*FooBar*' -exec cp -t ~/foobar -- {} +
    

    однако некоторые команды не имеют такой функции.

недостатки:

  • Может быть, недостаточно масштабировать, если слишком много имен файлов. (Ограничение? Я не знаю, но я тестировал с 10MB списком файлов, который содержит 10000+ имен файлов без проблем, в Debian)

Ну... кто знает, доступен ли bash в OS X?

  • 1
    с пробелами в именах файлов |grep не будет работать

Ещё вопросы

Сообщество Overcoder
Наверх
Меню