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

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

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

Какие-нибудь предложения для более надежного использования xargs?

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

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

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

find whatever ... | xargs -d "\n" cp -t /var/tmp

Ответ 4

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

find -name '*FooBar*' -print0 | xargs -0 cp -t ~/foo/bar

Ответ 5

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

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

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

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

Ответ 6

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

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

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

Ответ 7

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

Вот переносимое (POSIX) решение, то есть решение, которое не требует find, xargs или cp GNU:

find . -name "*FooBar*" -exec sh -c 'cp -- "[email protected]" ~/foo/bar' sh {} +

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

Это решение:

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

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

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

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

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

Ответ 8

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

Ответ 9

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

find . | grep "FooBar" | tr \\n \\0 | xargs -0 -I{} cp "{}" ~/foo/bar

Ответ 10

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

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

Ответ 11

find | perl -lne 'print quotemeta' | xargs ls -d

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

Ответ 12

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

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

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

Ответ 13

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

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

Ответ 14

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

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

Ответ 15

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

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

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

Ответ 16

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

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

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

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

Ответ 17

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

Ответ 18

Я немного поиграл с этим, начал размышлять над модификацией 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.

Ответ 19

Я создал небольшой переносимый скрипт-обертку под названием "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+"[email protected]"}
fi

Поместите скрипт в какой-то каталог в вашем $ PATH и не забудьте

$ chmod +x xargsL

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

Ответ 20

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

Ответ 21

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

find . -name "file.ext"| grep "FooBar" | xargs -i cp -p "{}" .

Ответ 22

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

Комментарий user80168 описывает способ сделать это напрямую с помощью cp без вызова cp для каждого файла:

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

Это работает, потому что:

  • флаг cp -t позволяет cp -t целевой каталог ближе к началу cp, а не к концу. От man cp:
   -t, --target-directory=DIRECTORY
         copy all SOURCE arguments into DIRECTORY
  • Флаг -- указывает cp интерпретировать все после как имя файла, а не как флаг, поэтому файлы, начинающиеся с - или -- не путают cp; вам все еще нужно это, потому что символы -/-- интерпретируются cp, тогда как любые другие специальные символы интерпретируются оболочкой.

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

Ответ 23

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

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

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

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

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

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

Недостатки:

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

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