Ошибка «Список аргументов слишком длинный» при копировании большого количества файлов

12

Я использую следующую команду:

\cp -uf /home/ftpuser1/public_html/ftparea/*.jpg /home/ftpuser2/public_html/ftparea/

И я получаю ошибку:

-bash: /bin/cp: Argument list too long

Я также попробовал:

ls /home/ftpuser1/public_html/ftparea/*.jpg | xargs -I {} cp -uf {} /home/ftpuser2/public_html/ftparea/

Все еще есть -bash: / bin / ls: список аргументов слишком длинный

Есть идеи?

icelizard
источник
Я пытаюсь скопировать все jpgs из одного каталога в другой, но только новые файлы и те, которые были обновлены.
ледяная ящерица
lsне предназначен для такого рода вещей. Использование find.
Приостановлено до дальнейшего уведомления.
Проблема не в ls, а в количестве аргументов, которые оболочка передает ls. Вы получите ту же ошибку с vi или с любой не встроенной командой.
Крис
Но lsэто особенно не предназначены для этого: mywiki.wooledge.org/ParsingLs
не Приостановлена до дальнейшего уведомления.
Правда, но в этом случае ошибка не из-за ошибки синтаксического анализа с ls, а с передачей миллиарда аргументов новому процессу, который оказывается ls. Помимо неуместного использования ls, оно также сталкивается с ограничением ресурсов / дизайна unix. В этом случае у пациента болит живот и сломана нога.
Крис

Ответы:

19

* .jpg расширяется до списка длиннее, чем может обработать оболочка. Попробуйте это вместо

find  /home/ftpuser/public_html/ftparea/ -name "*.jpg" -exec cp -uf "{}" /your/destination \;
Шон Чин
источник
Я использовал find / home / ftpuser1 / public_html / ftparea / -name "* jpg" -exec cp -uf "{}" / home / ftpuser2 / public_html / ftparea / и получил следующую ошибку: пропущенный аргумент для `-exec '
ледяная ящерица
Вы пропустили последний аргумент cp, правильно сказал ответчик. Дважды проверьте вашу реализацию. Обратите внимание, что в этом ответе точка в «* .jpg» отсутствует, это может привести к неправильному поведению (например, каталог «myjpg»). Обратите внимание, что это может быть параноиком, но безопаснее точно указать, что вы собираетесь копировать, используя файл -type (предотвращая воздействие на
каталоги
После более тщательной проверки я пропустил «\;» завершить команду, которую должен выполнить -exec. Дурак я!
ледяная ящерица
@AlberT: спасибо за головы за недостающую точку. Это была опечатка. Ответ обновлен.
Шон Чин
Дело не в том, что cp не может справиться с этим. Оболочка не может.
d -_- b
6

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

Из-за того, как оболочка обрабатывает глобирование, это повлияет на большинство системных команд, когда вы используете один и тот же аргумент ("* .jpg"). Поскольку глоб обрабатывается сначала оболочкой, а затем отправляется команде, команда:

cp -uf *.jpg /targetdir/

по сути то же самое для оболочки, как если бы вы написали:

cp -uf 1.jpg 2.jpg ... n-1.jpg n.jpg /targetdir/

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

cp -uf /sourcedir/[a-m]*.jpg /targetdir/
cp -uf /sourcedir/[n-z]*.jpg /targetdir/

Это может сработать, но как именно это будет эффективно, зависит от того, насколько хорошо вы сможете разбить ваш список файлов на удобные блоки, которые можно сгладить.

Globbable. Мне нравится это слово.

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

find /sourcedir/ -name '*.jpg' -exec cp -uf {} /targetdir/ \;

Аргумент -exec будет запускать оставшуюся часть командной строки один раз для каждого файла, найденного командой find , заменяя {} на каждое найденное имя файла. Поскольку команда cp выполняется только для одного файла за раз, ограничение списка аргументов не является проблемой.

Это может быть медленным из-за необходимости обрабатывать каждый файл отдельно. Использование xargs может обеспечить более эффективное решение:

find /sourcedir/ -name '*.jpg' -print0 | xargs -0 cp -uf -t /destdir/

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

Конечно, есть также возможность просто перекомпилировать ядро, установив большее значение для MAX_ARG_PAGES. Но перекомпиляция ядра - это больше работы, чем я хочу объяснить в этом ответе.

goldPseudo
источник
Я понятия не имею, почему за это проголосовали. Это единственный ответ, который, кажется, объясняет, почему это происходит. Может быть, потому что вы не предлагали использовать xargs для оптимизации?
Крис
добавлено в решение xargs, но я все еще волнуюсь, что отрицательные отзывы вызваны тем, что в моих данных явно что-то не так, и никто не хочет говорить мне, что это такое. :(
goldPseudo
xargsкажется гораздо более эффективным, так как итоговое количество командных вызовов намного меньше. В моем случае я вижу в 6-12 раз лучшую производительность при использовании, argsчем когда -execрешение с ростом числа файлов повышает эффективность.
Ян Влчинский
3

Это происходит потому, что ваше подстановочное выражение ( *.jpg) превышает предел длины аргумента командной строки при расширении (возможно, потому что у вас много файлов .jpg в /home/ftpuser/public_html/ftparea).

Есть несколько способов обойти это ограничение, например использование findили xargs. Посмотрите на эту статью для получения более подробной информации о том, как это сделать.

mfriedman
источник
+1 за хороший внешний ресурс по теме.
viam0Zah
3

Как прокомментировал GoldPseudo, существует ограничение на количество аргументов, которые вы можете передать процессу, который вы порождаете. Смотрите его ответ для хорошего описания этого параметра.

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

Цикл for в оболочке find и ls, grep и цикл while в этой ситуации делают одно и то же:

for file in /path/to/directory/*.jpg ; 
do
  rm "$file"
done

и

find /path/to/directory/ -name '*.jpg' -exec rm  {} \;

и

ls /path/to/directory/ | 
  grep "\.jpg$" | 
  while
    read file
  do
    rm "$file"
  done

у всех есть одна программа, которая читает каталог (сама оболочка, find и ls), и другая программа, которая фактически принимает один аргумент за выполнение и выполняет итерацию по всему списку команд.

Теперь это будет медленным, потому что rm нужно разветвлять и выполнять для каждого файла, который соответствует шаблону * .jpg.

Это где Xargs вступает в игру. xargs принимает стандартный ввод и для каждой N (для freebsd по умолчанию 5000) строк она порождает одну программу с N аргументами. xargs - это оптимизация вышеуказанных циклов, потому что вам нужно только разветвить программы 1 / N, чтобы перебрать весь набор файлов, которые читают аргументы из командной строки.

Крис
источник
2

В программе может быть указано максимальное количество аргументов, bash расширяет * .jpg до множества аргументов для cp. Вы можете решить это, используя find, xargs или rsync и т. Д.

Посмотрите здесь про Xargs и найдите

/programming/143171/how-can-i-use-xargs-to-copy-files-that-have-spaces-and-quotes-in-their-names

Маттиас Вадман
источник
1

Глобус * расширяется до слишком большого количества имен файлов. Вместо этого используйте find / home / ftpuser / public_html -name '* .jpg'.

Уильям Перселл
источник
Find и echo * приводят к одному и тому же выводу - ключ здесь использует xargs, а не просто передает все 1 миллиард аргументов командной строки команде, которую оболочка пытается форкнуть.
Крис
echo * завершится ошибкой, если файлов слишком много, но поиск завершится успешно. Также использование find -exec с + эквивалентно использованию xargs. (Не все находят поддержку +, хотя)
Уильям Перселл
1

Использование +опции find -execзначительно ускорит работу.

find  /home/ftpuser/public_html/ftparea/ -name "*jpg" -exec cp -uf -t /your/destination "{}" +

+Вариант требует , {}чтобы быть последним аргументом поэтому использование -t /your/destination(или --target-directory=/your/destination) опции cpделает его работу.

От man find:

-exec команда {} +

          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  
          tions of the command will  be  much  less  than  the  number  of  
          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.  The command is executed in the  
          starting directory.

Изменить : переставить аргументы в cp

Приостановлено до дальнейшего уведомления.
источник
Я получаю поиск: отсутствует аргумент `-exec '/ home / ftpuser1 / public_html / ftparea / -name' * jpg '-exec cp -uf" {} "/ home / ftpuser2 / public_html / ftparea / +
icelizard
Я переставил аргументы, cpчтобы исправить эту ошибку.
Приостановлено до дальнейшего уведомления.
1

Похоже, у вас слишком много *.jpgфайлов в этом каталоге, чтобы поместить их все в командную строку одновременно. Вы можете попробовать:

find /home/ftpuser/public_html/ftparea1 -name '*.jpg' | xargs -I {} cp -uf {} /home/ftpuser/public_html/ftparea2/

Возможно, вам придется проверить man xargsсвою реализацию, чтобы убедиться, что -Iкоммутатор подходит для вашей системы.

На самом деле, вы действительно собираетесь скопировать эти файлы в то же место, где они уже находятся?

Грег Хьюгилл
источник
извините, это две разные директории должны быть ftpuser1 и ftpuser2
icelizard
Только что попробовал это: ls /home/ftpuser1/public_html/ftparea/*.jpg | xargs -I {} cp -uf {} / home / ftpuser2 / public_html / ftparea / Все еще есть -bash: / bin / ls: список аргументов слишком длинный
icelizard
О, вы совершенно правы, конечно же lsвозникнет такая же проблема! Я изменился на findчто не будет.
Грег Хьюгилл
0

Перейти в папку

cd /home/ftpuser1/public_html/

и выполните следующее:

cp -R ftparea/ /home/ftpuser2/public_html/

Таким образом, если в папке «ftparea» есть подпапки, это может иметь негативные последствия, если вы хотите, чтобы из нее были только файлы «* .jpg», но если нет подпапок, этот подход определенно будет намного быстрее, чем используя find и xargs

pinpinokio
источник