Как я могу использовать 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.

21 ответов


вы можете объединить все это в единую :

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

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

Примечание:-- флаг передан cp предотвращает его обработку файлов, начиная с - как варианты.


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

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


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

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

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

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

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

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

Я sed для замены каждой строки ввода одной и той же строкой, но окруженной двойными кавычками. От sed man page,"...амперсанд ( " & " ), появляющийся в замене, заменяется строкой, соответствующей RE..." -- в данном случае, .* вся линия.

скачать xargs: unterminated quote ошибка.


этот метод работает на Mac OS X v10.7.5 (Лев):

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

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


вот портативное (POSIX) решение, т. е. то, которое не требует find, xargs или cp специфические расширения GNU:

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

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


посмотрите на использование опции -- 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

Я считаю, что это будет надежно работать для любого символа, кроме line-feed (и я подозреваю, что если у вас есть line-Feed в ваших именах файлов, то у вас есть худшие проблемы, чем это). Он не требует 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) для решения проблем пробелов в именах (только новые строки разделяются-хотя "найти"... - print0' и 'xargs -0' комбинация довольно аккуратная, учитывая, что имена файлов не могут содержать символы ASCII NUL '\0'. Мой xargl не так полон, как нужно, чтобы его стоило публиковать - тем более, что GNU имеет средства, которые по крайней мере так же хороши.


Если версии 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 принимает один путь на строку. Имена путей могут содержать любой символ, кроме (очевидно) байтов newline или 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 " failed!" >& 2' 0

if IFS= read -r first
then
        {
                printf '%s\n' "$first"
                cat
        } | sed 's/./\&/g' | xargs ${1+"$@"}
fi

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

$ chmod +x xargsL

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


версия 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.


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

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

Я немного поиграл с этим, начал подумывать об изменении 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.


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

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

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

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

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

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

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

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

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

    однако некоторые команды не имеют таких особенность.

недостатки:

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

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