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

232

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

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

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

Это на Mac OS X 10.5.3 (Leopard) с BSD xargs.

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

Ответы:

199

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

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

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

Примечание. Переданный --флаг не cpпозволяет обрабатывать файлы, начиная с -опций.

godbyk
источник
70
Люди используют xargs, потому что обычно быстрее вызывать исполняемый файл 5 раз с 200 аргументами каждый раз, чем 1000 раз с одним аргументом каждый раз.
Цот
12
Ответ Криса Джестера-Янга должен быть "хорошим ответом" ... Кстати, это решение не работает, если имя файла начинается с "-". По крайней мере, ему нужно "-" после cp.
Келтия
11
Пример скорости - более 829 файлов, метод «find -exec» занял 26 секунд, а инструмент «find -print0 | xargs --null» - 0,7 секунды. Значительная разница.
Питер Портер
7
@tzot Поздний комментарий, но, тем xargsне менее, не обязателен для решения проблемы, которую вы описываете, findуже поддерживает -exec +пунктуацию.
Jlliagre
3
не отвечает на вопрос о том, как бороться с пробелами
Бен Глассер
117

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

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

Крис Шут-Янг
источник
1
Leopard поддерживает "-Z" (это GNU grep) и, конечно, find (1) и xargs (1) поддерживают "-0".
Келтия
1
В OS X 10.9 grep -{z|Z}означает «вести себя как zgrep» (распаковка), а не как «вывести нулевой байт после каждого имени файла». Используйте grep --nullдля достижения последнего.
Басим
4
Что не так с find . -name 'FooBar' -print0 | xargs -0 ...?
Квентин Праде
1
@QuentinPradet Очевидно, для фиксированной строки, такой как «FooBar», -nameили -pathработает просто отлично. ОП указал использование grep, предположительно, потому что они хотят фильтровать список с помощью регулярных выражений.
Крис Джестер-Янг
1
@ Привет-Angel Вот именно поэтому я использую xargs -0 в сочетании с find -print0 . Последний печатает имена файлов с терминатором NUL, а первый получает файлы таким образом. Зачем? Имена файлов в Unix могут содержать символы новой строки. Но они не могут содержать NUL-символов.
Крис Шестер-Янг
92

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

find whatever ... | xargs -d "\n" cp -t /var/tmp
user87601
источник
4
Этот ответ прост, эффективен и понятен: заданный по умолчанию разделитель для xargs слишком широк и его необходимо сузить для того, что хочет OP. Я знаю это из первых рук, потому что я столкнулся с той же самой проблемой сегодня, делая что-то подобное, за исключением Cygwin. Если бы я прочитал справку по команде xargs, я мог бы избежать нескольких головных болей, но ваше решение помогло мне. Спасибо ! (Да, OP был на MacOS с использованием BSD xargs, который я не использую, но я надеюсь, что параметр "-d" xargs существует во всех версиях).
Этьен Делавеннат
7
Хороший ответ, но не работает на Mac. Вместо этого мы можем sed -e 's_\(.*\)_"\1"_g'
направить
10
Это должен быть принятый ответ. Вопрос был об использовании xargs.
Мухаммед Альхашаш
2
Я получаюxargs: illegal option -- d
Нехем
1
Стоит отметить, что имена файлов могут содержать символ новой строки во многих системах * nix. Вы вряд ли когда-нибудь столкнетесь с этим в дикой природе, но если вы запускаете команды оболочки на ненадежном вводе, это может быть проблемой.
Сорен Бьорнстад
71

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

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar
Tometzky
источник
1
Это не сработало для меня. Он пытался вбить ~ / foo / bar во все, что вы найдете, но не наоборот
Shervin Asgari
13
Флаг -t для cp является расширением GNU AFAIK и недоступен в OS X. Но если бы он был, он работал бы так, как показано в этом ответе.
metamatt
2
Я использую Linux. Спасибо за переключатель '-t'. Вот чего мне не хватало :-)
Вахид Пазиранде
59

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

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

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

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

oyouareatubeo
источник
3
Я нахожусь на окнах и использую gnuwin32, поэтому мне пришлось использовать, sed s/.*/\"&\"/чтобы заставить его работать.
Пэт
Да, но, вероятно, это не будет обрабатывать имена файлов с помощью "in - разве что sed также заключает в кавычки кавычки?
artfulrobot
Использование sedгениально и пока правильное решение без переписывания проблемы!
entonio
53

Этот метод работает на Mac OS X v10.7.5 (Lion):

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

Я также проверил точный синтаксис, который вы разместили. Это также хорошо работало на 10.7.5.

the_minted
источник
4
Это работает, но -Iподразумевает -L 1(так говорит руководство), что означает, что команда cp выполняется один раз для файла = v slow.
artfulrobot
xargs -J% cp% <destination dir> Возможно более эффективен в OSX.
Walker D
3
Извините, но это НЕПРАВИЛЬНО. Сначала он выдает именно ту ошибку, которую хотел избежать. Вы должны использовать find ... -print0и xargs -0для работы Arround Xargs "по умолчанию кавычки являются специальными". Во-вторых, обычно используйте '{}'не {}в командах, передаваемых в xargs, для защиты от пробелов и специальных символов.
Андреас Шпиндлер
3
Извините, Андреас Шпиндлер, я не очень знаком с xargs и нашел эту строку после некоторых экспериментов. Кажется, это работает для большинства людей, которые прокомментировали это и проголосовали за это. Не могли бы вы немного подробнее рассказать о том, какую ошибку он вызывает? Кроме того, не могли бы вы опубликовать точный вклад, который, по вашему мнению, был бы более правильным? Спасибо.
the_minted
12

Просто не используйте xargs. Это аккуратная программа, но она плохо сочетается сfind когда сталкиваешься с нетривиальными случаями.

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

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

Обратите внимание на окончание +вместо более привычного; .

Это решение:

  • правильно обрабатывает файлы и каталоги со встроенными пробелами, символами новой строки или любыми экзотическими символами.

  • работает в любой системе Unix и Linux, даже в тех, которые не предоставляют инструментарий GNU.

  • не использует xargsхорошую и полезную программу, но требует слишком много настроек и нестандартных функций для правильной обработки findвывода.

  • также более эффективен (читай быстрее ), чем принятый, и большинство, если не все остальные ответы.

Также обратите внимание, что несмотря на то, что указано в некоторых других ответах или комментариях, цитирование {}бесполезно (если только вы не используете экзотическую fishоболочку).

jlliagre
источник
1
@PeterMortensen Вы, вероятно, упускаете из виду окончание плюс. findможет делать то, что xargsделает без каких-либо накладных расходов.
Jlliagre
8

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

Шеннон Нельсон
источник
8

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

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar
Александр Гвидревич
источник
1
Работает, но медленно, потому что -Iподразумевает-L 1
artfulrobot
6
find | perl -lne 'print quotemeta' | xargs ls -d

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

МАВИТ
источник
Возможно ли иметь перевод строки в имени файла? Никогда об этом не слышал.
MTK
2
В самом деле. Попробуйте, например,mkdir test && cd test && perl -e 'open $fh, ">", "this-file-contains-a-\n-here"' && ls | od -tx1
mavit
1
|perl -lne 'print quotemeta'это именно то, что я искал. Другие посты здесь не помогли мне, потому что вместо того, чтобы findмне приходилось использовать, grep -rlчтобы значительно сократить количество PHP-файлов до только зараженных вредоносным ПО.
Маркос
Perl и quotemeta гораздо более общие, чем print0 / -0 - спасибо за общее решение для конвейерной
передачи
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» в качестве единственного аргумента.

Питер Мортенсен
источник
3

Задача фрейма - вы спрашиваете, как использовать xargs. Ответ таков: вы не используете xargs, потому что он вам не нужен.

Замечаниеuser80168 описывает способ сделать это непосредственно сП, без вызова ф для каждого файла:

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 {} +Вариант по существу , делает то же самое , как 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, вы избегаете необходимости вызова канала или оболочки, так что вам не нужно беспокоиться о каких-либо неприятных символах в именах файлов.

Геррит
источник
Удивительная находка, я понятия не имел !!! "Утилита -exec [аргумент ...] {} + То же, что и -exec, за исключением того, что` `{} '' заменяется как можно большим количеством путей для каждого вызова утилиты. Это поведение аналогично поведению xargs (1 ) «. в реализации BSD.
Конни
2

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

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

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

Джонатан Леффлер
источник
2
GitHub или этого не произошло
Кори Голдберг,
@CoreyGoldberg: Я думаю, этого не случилось тогда.
Джонатан Леффлер
Во -первых, POSIX findне нужен xargs(а это было уже 11 лет назад).
jlliagre
2

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

while read line ; do cp "$line" ~/bar ; done < <(find . | grep foo)
StackedCrooked
источник
2

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

$ find . -type f -name '*.txt' | sed 's/'"'"'/\'"'"'/g' | sed 's/.*/"&"/'  | xargs -I{} cp -v {} ./tmp/
Мойзес
источник
1

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

Ян Птачник
источник
1

Я создал небольшой переносимый скрипт-обертку под названием «xargsL» вокруг «xargs», который решает большинство проблем.

В отличие от xargs, xargsL принимает одно имя пути на строку. Имена путей могут содержать любой символ, кроме (очевидно) символа новой строки или байтов NUL.

В списке файлов цитирование не допускается и не поддерживается - имена файлов могут содержать все виды пробелов, обратных косых черт, обратных черт, подстановочных знаков оболочки и т. П. - 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

сценарий, чтобы сделать его исполняемым.

Гюнтер Брунталер
источник
1

Perl-версия bill_starr не будет хорошо работать для встроенных символов новой строки (справляется только с пробелами). Например, для Solaris, где у вас нет инструментов GNU, может быть более полная версия (с использованием sed) ...

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

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

Питер Мортенсен
источник
1

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

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

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

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

Карл Ямамото-Фёрст
источник
1

Я немного поиграл с этим, начал размышлять над модификацией xargs и понял, что для случая использования, о котором мы здесь говорим, простая переопределение в Python - лучшая идея.

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

См. 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.

Джон Аллсуп
источник
0

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

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .
Фред
источник
1
По man-странице, -iустарела и -Iдолжна использоваться вместо.
Acumenus
-1

Если вы используете Bash, вы можете преобразовать стандартный вывод в массив строк mapfile:

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

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

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

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

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

Недостатки:

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

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

Xiè Jìléi
источник