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

StackOverflow https://stackoverflow.com/questions/143171

Вопрос

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top