Як я можу використовувати 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не дозволяє йому обробляти файли, починаючи з -параметрів.


70
Люди використовують xargs, оскільки, як правило, швидше викликати виконуваний файл 5 разів із 200 аргументами кожен раз, ніж викликати його 1000 разів одним аргументом кожен раз.
tzot

12
Відповідь Кріса Єстер-Янг повинна бути "хорошою відповіддю" там ... До речі, це рішення не працює, якщо ім'я файлу починається з "-". Принаймні, йому потрібно "-" після ср.
Келтія

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) і, звичайно, знаходить (1) і xargs (1) підтримує "-0".
Келтія

1
В OS X 10.9 grep -{z|Z}означає "поводитись як zgrep" (декомпресувати), а не призначений "друкувати нульовий байт після кожного імені файлу". Використовуйте grep --nullдля досягнення останнього.
bassim

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

4
Цей аналог простий, ефективний і прямо до суті: розмежувач за замовчуванням, встановлений для xargs, занадто широкий і його потрібно звузити для того, що хоче зробити ОП. Я знаю це з перших вуст, тому що я сьогодні зіткнувся з цією самою проблемою, роблячи щось подібне, за винятком Cygwin. Якби я прочитав допомогу для команди xargs, я міг би уникнути декількох головних болів, але ваше рішення вирішило це для мене. Дякую ! (Так, OP був на MacOS, використовуючи xargs BSD, який я не використовую, але я сподіваюся, що параметр xargs "-d" існує у всіх версіях).
Етьєн Делавеннат

7
Приємна відповідь, але не працює на Mac. Замість цього ми можемо труби з знахідкою в sed -e 's_\(.*\)_"\1"_g'застосування сили лапки навколо імені файлу
ishahak

10
Це має бути прийнятою відповіддю. Питання полягало у використанні xargs.
Мохаммед Алхашаш

2
Я отримуюxargs: illegal option -- d
nehem

1
Варто зазначити, що назви файлів можуть містити символ нового рядка у багатьох * nix системах. Ви навряд чи коли-небудь наткнетеся на це в дикій природі, але якщо ви виконуєте команди оболонки на ненадійному введенні, це може викликати занепокоєння.
Сорен Бьорнстад

71

Це більш ефективно, оскільки він не запускає "cp" кілька разів:

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

1
Це не спрацювало для мене. Це спробувало ввійти ~ / foo / bar у все, що ви знайдете, але не навпаки
Шервін Асгарі

13
Прапор -t до cp - це розширення GNU, AFAIK і недоступне для OS X. Але якби воно було, воно буде працювати, як показано в цій відповіді.
метамат

2
Я використовую Linux. Дякуємо за перемикач '-t'. Ось чого мені не вистачало :-)
Вахід Пазіранде

59

Я зіткнувся з тією ж проблемою. Ось як я це вирішив:

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

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

Це вирішує xargs: unterminated quoteпомилку.


3
Я в Windows і використовую gnuwin32, тому мені довелося використовувати sed s/.*/\"&\"/його, щоб він працював.
Пт

Так, але, мабуть, це не оброблятиме імена файлів "в - хіба що sed також наводить цитати?
artfulrobot

Використання sedгеніальне і на сьогодні правильне рішення, не переписуючи проблему!
entonio

53

Цей метод працює на Mac OS X v10.7.5 (Lion):

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

Я також перевірив точний синтаксис, який ви опублікували. Це також спрацювало чудово 10.7.5.


4
Це працює, але -Iмає на увазі -L 1(так говорить керівництво), що означає, що команда cp виконується один раз у файлі = v повільно.
artfulrobot

xargs -J% cp% <призначення dir> Можливо, більш ефективний для OSX.
Уокер Д

3
Вибачте, але це неправильно. По-перше, це створює саме ту помилку, яку хотів уникнути TO. Ви повинні використовувати find ... -print0та xargs -0обробляти "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оболонку).



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


8

Для тих, хто покладається на команди, крім знаходження, наприклад 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 і quometa набагато загальніші за print0 / -0 - дякую за загальне рішення конвеєрних файлів з пробілами
bmike

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 описується спосіб зробити це безпосередньо за допомогою 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 {} +Варіант по суті , робить те ж саме , як 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.

Використовуючи це в пошуку безпосередньо, це дозволяє уникнути необхідності виклику каналу або оболонки, таким чином, що вам не потрібно турбуватися про будь-які неприємні символи у назви файлів.


Дивовижна знахідка, я поняття не мав !!! "-exec утиліта [аргумент ...] {} + Те саме, що і -exec, за винятком того, що` `{} '' замінюється якомога більше імен шляхів для кожного виклику утиліти. Ця поведінка схожа на поведінку xargs (1 ) ". у впровадженні BSD.
conny

2

Майте на увазі, що більшість варіантів, що обговорюються в інших відповідях, не є стандартними для платформ, які не використовують утиліти GNU (наприклад, Solaris, AIX, HP-UX). Див. Специфікацію POSIX щодо "стандартної" поведінки xargs.

Я також вважаю, що поведінка xargs, згідно з якою вона виконує команду принаймні один раз, навіть без введення даних, викликає неприємності.

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


2
GitHub чи цього не сталося
Corey Goldberg

@CoreyGoldberg: Я думаю, це не сталося тоді.
Джонатан Леффлер

POSIX findне потрібно xargsв першу чергу (а це вже було правдою 11 років тому).
jlliagre

2

За допомогою Bash (не POSIX) ви можете використовувати підстановку процесу для отримання поточної лінії всередині змінної. Це дозволяє використовувати лапки, щоб уникнути спеціальних символів:

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

2

Для мене я намагався зробити щось трохи інше. Я хотів скопіювати свої .txt файли у свою папку tmp. Назви файлів .txt містять пробіли та символи апострофа. Це працювало на моєму Mac.

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

1

Якщо версії find and 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.

Ось сценарій оболонки "xargsL" POSIX:

#! /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

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


1

Я використав відповідь Білла Стар, трохи змінена на Solaris:

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

Це поставить лапки навколо кожного рядка. Я не використовував варіант '-l', хоча це, мабуть, допоможе.

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


1

Я трохи пограв з цим, почав роздумувати про зміни 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 .

З написаними ярлами (і встановлено Python 3) ви можете вводити:

find .|grep "FooBar"|yargs -l 203 cp --after ~/foo/bar

робити одночасно копіювання 203 файлів. (Тут, звичайно, 203 - це просто заповнювач, і якщо використовувати таке дивне число, як 203, ясно, що це число не має іншого значення.)

Якщо ви дійсно хочете чогось швидшого та без потреби в Python, візьміть зарги та ярги як прототипи та перепишіть на C ++ чи C.


0

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

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

1
Сторінка "man" -i- застаріла, і її -Iслід використовувати замість неї.
Acumenus

-1

Якщо ви використовуєте 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?

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.