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

Сообщение об ошибке GNU xargs для этого с именем файла, содержащим одинарную кавычку, гораздо более полезно: xargs: непревзойденная одинарная кавычка; по умолчанию кавычки являются специальными для xargs, если вы не используете параметр -0.   —  person Drew Stephens    schedule 27.09.2008

GNU xargs также имеет параметр --delimiter (-d). Попробуйте использовать \n в качестве разделителя. Это не позволяет xargs разделять строки пробелами на несколько слов / аргументов.   —  person Drew Stephens    schedule 27.02.2017

См. также:  Как проверить конкретную версию Subversion из командной строки?
Понравилась статья? Поделиться с друзьями:
IT Шеф
Комментарии: 22
  1. Drew Stephens

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

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

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

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

    -exec будет работать с любой находкой, я никогда не понимал, почему люди используют xargs (и просто подождите, пока вы не достигнете предела длины каталога xargs !!) person Drew Stephens; 02.10.2008

    Что такое «ограничение длины каталога xargs»? Вы имеете в виду максимальный размер команды? Если да, предполагается, что xargs разбивает свои аргументы на группы подходящего размера. person Drew Stephens; 14.10.2008

    Люди используют xargs, потому что обычно быстрее вызывать исполняемый файл 5 раз с 200 аргументами каждый раз, чем каждый раз вызывать его 1000 раз с одним аргументом. person Drew Stephens; 14.10.2008

    Ответ Криса Джестера-Янга должен быть здесь хорошим ответом … Кстати, это решение не работает, если имя файла начинается с -. По крайней мере, это необходимо — после cp. person Drew Stephens; 24.01.2009

    Пример скорости — более 829 файлов, метод find -exec занял 26 секунд, в то время как find -print0 | xargs —null method tool 0,7 секунды. Значительная разница. person Drew Stephens; 27.08.2012

    Использование cp — это нормально, но не так — в этом случае cp запускается для каждого файла, что накладывает нетривиальные накладные расходы на fork + execve для каждого отдельного файла. Лучше использовать параметр -t команды cp. Нравится: find .. -exec cp -t ~/foo/bar -- {} + person Drew Stephens; 30.01.2013

    @tzot Поздний комментарий, но в любом случае xargs не требуется для решения проблемы, которую вы описываете, find уже поддерживает его с помощью -exec + знаков препинания. person Drew Stephens; 26.06.2013

    Привет, если foobar — результат какой-то другой команды, как я могу это сделать? Большое тебе спасибо person Drew Stephens; 21.05.2015

    @ ying17zi Замените foobar на $(your-command args) или `your-command args`. person Drew Stephens; 24.05.2015

    не отвечает на вопрос, как работать с пробелами person Drew Stephens; 11.01.2019

    @BenGlasser Помещение {} в кавычки защищает пробелы в имени файла / пути. Это то, о чем вы говорите? person Drew Stephens; 12.01.2019

    @godbyk no Я имею в виду, что этот ответ до сих пор не ответил на вопрос о том, как обрабатывать пробелы с помощью xargs, который является заголовком вопроса person Drew Stephens; 14.01.2019

    @BenGlasser А, попался. См. ответ Криса об использовании xargs с пробелами. person Drew Stephens; 14.01.2019

  2. Drew Stephens

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

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

    Leopard поддерживает -Z (это GNU grep) и, конечно же, find (1) и xargs (1) поддерживают -0. person Drew Stephens; 24.01.2009

    @Keltia Обратите внимание, что в OS X Mountain Lion (10.8) Apple заменила GNU grep на BSD grep, и переключатель — {z | Z} не работает. person Drew Stephens; 28.11.2012

    В OS X 10.9 grep -{z|Z} означает вести себя как zgrep (распаковывать), а не предполагать печать нулевого байта после каждого имени файла. Используйте grep --null для достижения последнего. person Drew Stephens; 07.02.2014

    @ ChrisJester-Young Добро пожаловать. Хотя теперь ваш комментарий по поводу опции -z потерял свой контекст;) person Drew Stephens; 07.02.2014

    Что не так с find . -name 'FooBar' -print0 | xargs -0 ...? person Drew Stephens; 17.05.2014

    @QuentinPradet Очевидно, что для фиксированной строки, такой как FooBar, -name или -path работают нормально. OP указали использование grep, предположительно потому, что они хотят фильтровать список с помощью регулярных выражений. person Drew Stephens; 17.05.2014

    Вы можете получить Binary file (standard input) matches от grep, когда попробуете это из-за -print0; используйте grep -a, чтобы заставить его интерпретировать канал. person Drew Stephens; 21.06.2015

    Не работает с duperemove — передача имени файла с нулевым окончанием приводит к No such file or directory. person Drew Stephens; 30.07.2018

    Хорошо, я подумал, вы неправильно используете xargs. Вы должны использовать xargs -d '\n', иначе xargs разбивает файлы на пробелы. Попробуйте следующее: touch "foo bar"; echo -e "foo bar" | xargs -0 ls, вы получите сообщение об ошибке. Но замена -0 на -d '\n' работает. См. также этот ответ. person Drew Stephens; 30.07.2018

    @ Hi-Angel. Именно поэтому именно я использую xargs -0 вместе с find -print0. Последний печатает имена файлов с ограничителем NUL, а первый получает файлы таким образом. Почему? Имена файлов в Unix могут содержать символы новой строки. Но они не могут содержать символы NUL. person Drew Stephens; 30.07.2018

    Используйте это, если вам нужны другие аргументы, например целевой каталог для cp: ... | xargs -0 -J % cp -v % /target/dir person Drew Stephens; 31.12.2020

  3. Drew Stephens

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

  4. Drew Stephens

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

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

    У меня это не сработало. Он пытался вставить ~ / foo / bar во все, что вы нашли, но не наоборот person Drew Stephens; 21.06.2010

    Флаг -t для cp является расширением GNU, AFAIK, и недоступен в OS X. Но если бы это было так, оно бы работало, как показано в этом ответе. person Drew Stephens; 18.05.2012

    Я использую Linux. Спасибо за ключ -t. Вот чего мне не хватало :-) person Drew Stephens; 21.04.2017

  5. Drew Stephens

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

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

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

    GitHub или этого не произошло person Drew Stephens; 26.09.2017

    @CoreyGoldberg: Думаю, тогда этого не произошло. person Drew Stephens; 26.09.2017

    POSIX find вообще не нуждается в xargs (и это уже было верно 11 лет назад). person Drew Stephens; 11.02.2019

  6. Drew Stephens

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

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

  7. Drew Stephens

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

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

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

  8. Drew Stephens

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

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

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

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

  9. Drew Stephens
    find | perl -lne 'print quotemeta' | xargs ls -d
    

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

    Можно ли использовать перевод строки в имени файла? Никогда об этом не слышал. person Drew Stephens; 16.05.2012

    В самом деле. Попробуйте, например, mkdir test && cd test && perl -e 'open $fh, ">", "this-file-contains-a-\n-here"' && ls | od -tx1 person Drew Stephens; 17.05.2012

    |perl -lne 'print quotemeta' это именно то, что я искал. Другие сообщения здесь мне не помогли, потому что вместо find мне нужно было использовать grep -rl, чтобы значительно сократить количество файлов PHP до только файлов, зараженных вредоносным ПО. person Drew Stephens; 14.06.2013

    perl и quotemeta гораздо более общие, чем print0 / -0 — спасибо за общее решение конвейерной обработки файлов с пробелами person Drew Stephens; 16.10.2015

  10. Drew Stephens

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

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

    Я использовал sed, чтобы заменить каждую строку ввода той же строкой, но в двойных кавычках. На sed странице руководства: «… Амперсанд (` `& »), появляющийся в замене, заменяется строкой, соответствующей RE …» — в данном случае .*, вся линия.

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

    Я работаю в Windows и использую gnuwin32, поэтому мне пришлось использовать sed s/.*/\"&\"/, чтобы заставить его работать. person Drew Stephens; 16.10.2012

    Да, но, по-видимому, это не будет обрабатывать имена файлов с " в — если sed также не цитирует кавычки? person Drew Stephens; 27.05.2015

    Использование sed — это гениально, и на данный момент это правильное решение без переписывания проблемы! person Drew Stephens; 03.06.2015

  11. Drew Stephens

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

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

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

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

    Это решение:

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

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

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

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

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

    Почему быстрее? Пользователь tzot написал: Люди используют xargs, потому что обычно быстрее вызывать исполняемый файл 5 раз с 200 аргументами каждый раз, чем каждый раз вызывать его 1000 раз с одним аргументом. person Drew Stephens; 29.07.2018

    @PeterMortensen Вы, наверное, не замечаете финальный плюс. find может делать то, что делает xargs, без каких-либо накладных расходов. person Drew Stephens; 30.07.2018

  12. Drew Stephens

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

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

    Согласно странице руководства -i устарел, и вместо него следует использовать -I. person Drew Stephens; 25.08.2014

  13. Drew Stephens

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

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

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

    Это работает, но -I подразумевает -L 1 (так говорится в руководстве), что означает, что команда cp запускается один раз для каждого файла = v медленно. person Drew Stephens; 27.05.2015

    xargs -J% cp% ‹каталог назначения› Возможно, более эффективен в OSX. person Drew Stephens; 26.01.2016

    Извините, но это НЕПРАВИЛЬНО. Сначала он выдает именно ту ошибку, которой хотел избежать TO. Вы должны использовать find ... -print0 и xargs -0 для работы вокруг xargs. По умолчанию кавычки являются особенными. Во-вторых, обычно используйте '{}', а не {} в командах, передаваемых в xargs, для защиты от пробелов и специальных символов. person Drew Stephens; 20.03.2016

    Извините, Андреас Шпиндлер, я не так хорошо знаком с xargs и нашел эту строку после некоторых экспериментов. Кажется, это работает для большинства людей, которые прокомментировали это и проголосовали за него. Не могли бы вы подробнее рассказать о том, какую ошибку он выдает? Кроме того, не могли бы вы опубликовать точные данные, которые, по вашему мнению, были бы более правильными? Спасибо. person Drew Stephens; 21.03.2016

    Это часть xargs, которая работала для меня в MacOS 10.15: xargs -0 -J % cp -v % /foo/bar person Drew Stephens; 31.12.2020

  14. Drew Stephens

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

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

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

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

    Работает, но медленно, потому что -I подразумевает -L 1 person Drew Stephens; 27.05.2015

  16. Drew Stephens

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

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

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

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

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

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

    Недостатки:

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

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

  17. Drew Stephens

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

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

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

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

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

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

    Этот ответ прост, эффективен и прямо по делу: разделитель по умолчанию, установленный для xargs, слишком широк, и его необходимо сузить для того, что хочет сделать OP. Я знаю это из первых рук, потому что сегодня я столкнулся с той же проблемой, делая что-то подобное, за исключением cygwin. Если бы я прочитал справку по команде xargs, я бы, возможно, избежал некоторых головных болей, но ваше решение исправило это за меня. Спасибо ! (Да, OP был на MacOS с использованием BSD xargs, который я не использую, но я надеюсь, что параметр xargs -d существует во всех версиях). person Drew Stephens; 07.05.2016

    Хороший ответ, но не работает на Mac. Вместо этого мы можем передать find в sed -e 's_\(.*\)_"\1"_g', чтобы заключить имя файла в кавычки. person Drew Stephens; 21.08.2016

    Это должен быть принятый ответ. Вопрос был об использовании xargs. person Drew Stephens; 06.11.2016

    Я получаю xargs: illegal option -- d person Drew Stephens; 12.11.2017

    Стоит отметить, что имена файлов могут содержать символ новой строки во многих системах * nix. Вы вряд ли когда-нибудь столкнетесь с этим в дикой природе, но если вы запускаете команды оболочки на ненадежном вводе, это может быть проблемой. person Drew Stephens; 19.01.2019

    (Пример: файлы B и C существуют в большом каталоге. Пользователь создает новый файл с именем B\nC. Позже этот пользователь уходит, и кто-то очищает все файлы этого пользователя, опрашивая владельцев и передавая список их файлов через xargs -d "\n" rm. Файлы B и C немедленно удаляются.) person Drew Stephens; 19.01.2019

  21. Drew Stephens

    Я создал небольшой переносимый сценарий оболочки под названием «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

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

  22. Drew Stephens

    Задача кадра — вы спрашиваете, как использовать 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; вам все еще нужно это, потому что символы _12 _ / _ 13_ интерпретируются 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 utility [аргумент …] {} + То же, что и -exec, за исключением того, что «{}» заменяется максимально возможным количеством путей для каждого вызова утилиты. Это поведение похоже на поведение xargs (1). в реализации BSD. person Drew Stephens; 30.10.2019

Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: