Слишком длинный список аргументов при копировании файлов

26

Я только что задал вопрос, связанный с тем, как я могу считать файлы определенного расширения. Теперь я хочу, чтобы cpэти файлы новые dir.

Я пытаюсь,

cp *.prj ../prjshp/

а также

cp * | grep '\.prj$' ../prjshp/

но они дают ту же ошибку,

bash: / bin / cp: список аргументов слишком длинный

Как мне их скопировать?

Sam007
источник

Ответы:

36

cp *.prj ../prjshp/это правильная команда, но вы столкнулись с редким случаем, когда она сталкивается с ограничением размера. Вторая команда, которую вы попробовали, не имеет никакого смысла.

Один из способов - запускать cpфайлы по частям. Команда findзнает, как это сделать:

find -maxdepth 1 -name '*.prj' -exec mv -t ../prjshp {} +
  • find рекурсивно пересекает текущий каталог и каталоги под ним.
  • -maxdepth 1 означает останавливаться на глубине 1, т.е. не переходить в подкаталоги.
  • -name '*.prj'означает работать только с файлами, имя которых соответствует указанному шаблону. Обратите внимание на кавычки вокруг шаблона: он будет интерпретироваться findкомандой, а не оболочкой.
  • -exec … {} +означает выполнить указанную команду для всех файлов. При необходимости он вызывает команду несколько раз, стараясь не превышать лимит командной строки.
  • mv -t ../prjshpперемещает указанные файлы в ../prjshp. Эта -tопция используется здесь из-за ограничения findкоманды: найденные файлы (обозначенные символом {}) передаются в качестве последнего аргумента команды, вы не можете добавить пункт назначения после него.

Другой метод заключается в использовании rsync.

rsync -r --include='*.prj' --exclude='*' . ../prjshp
  • rsync -r … . ../prjshp копирует текущий каталог в ../prjshp рекурсивную
  • --include='*.prj' --exclude='*'означает копировать совпадающие файлы *.prjи исключать все остальное (включая подкаталоги, поэтому .prjфайлы в подкаталогах не будут найдены).
Жиль "ТАК - перестань быть злым"
источник
3
Rsync, безусловно, самое простое решение здесь.
ntk4
Чтобы быть немного придирчивым, вторая команда cp * | grep '\.prj$' ../prjshp/ не имеет никакого смысла, но может быть синтаксически допустимой, если *расширяется до списка файлов с последним, являющимся каталогом (иначе cp SOURCE1 SOURCE2....DEST). Канал не имеет никакого смысла, конечно, но он также синтаксически действителен в отношении оболочки - он будет dup()прекрасно работать с файловыми дескрипторами, просто в конце канала чтения не будет никаких данных, потому что cpон не записывает никаких ,
Сергей Колодяжный
И find, и rsync выдавали один и тот же список аргументов, слишком длинная ошибка для меня. Цикл for был самым простым обходным путем.
Meezaan-ud-Din
Действительно, rsync - это способ сделать любое массовое копирование, хотя я озадачен тем, как далеко мы продвинулись с Linux, и у нас есть такая глупая ошибка / ошибка, и да, я бы посчитал это ошибкой / ошибкой.
MitchellK
22

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

for i in *; do cp "$i" ../prjshp/; done
ccshields
источник
Это работает для меня.
1rq3fea324wre
1
Просто и эффективно. У меня была похожая проблема с удалением ~ 1/4 миллионов jpegs, которые я извлек из видео для проекта. Это подход, который я использовал.
Старейшина Гик
5

Есть 3 ключевых момента, которые следует иметь в виду при возникновении Argument list too longошибки:

  • Длина аргументов командной строки ограничена ARG_MAXпеременной, которая по определению POSIX равна "... [m] максимальная длина аргумента для функций exec, включая данные среды" (выделение добавлено) ". То есть, когда оболочка выполняет не Команда -built-it, она должна вызывать одну из них, чтобы вызвать exec()процесс этой команды, и именно здесь ARG_MAXвступает в игру. Кроме того, /bin/echoиграет роль имя или путь к самой команде (например, ).

  • Встроенные команды оболочки выполняются оболочкой, что означает, что оболочка не использует exec()семейство функций и поэтому не подвержена влиянию ARG_MAXпеременных.

  • Некоторые команды, такие как xargsи findзнают о ARG_MAXпеременной, и многократно выполняют действия в рамках этого ограничения

Из вышеприведенных пунктов и, как показано в превосходном ответе Кусалананды на связанный вопрос, это Argument list too longтакже может произойти, когда обстановка велика. Таким образом, принимая во внимание, что среда каждого пользователя может варьироваться, и размер аргумента в байтах является релевантным, трудно придумать одно количество файлов / аргументов.

Как справиться с такой ошибкой?

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

Использовать встроенные модули оболочки

Как уже говорилось ранее, встроенные функции оболочки защищены от ARG_MAXограничений, таких как forциклы, whileциклы, встроенные echoи встроенные функции printf- все они будут работать достаточно хорошо.

for i in /path/to/dir/*; do cp "$i" /path/to/other/dir/; done

На связанный вопрос об удалении файлов было решение как таковое:

printf '%s\0' *.jpg | xargs -0 rm --

Обратите внимание, что здесь используется встроенная оболочка printf. Если мы вызываем внешнее printf, это будет связано exec(), следовательно, потерпит неудачу с большим количеством аргументов:

$ /usr/bin/printf "%s\0" {1..7000000}> /dev/null
bash: /usr/bin/printf: Argument list too long

массивы bash

Согласно ответу по jlliagre, bashне накладывают ограничения на массивах, поэтому строить массив имен файлов и использования срезов каждой итерации петли можно сделать так, как показано на danjpreron в ответе :

files=( /path/to/old_dir/*.prj )
for((I=0;I<${#files[*]};I+=1000)); do 
    cp -t /path/to/new_dir/ "${files[@]:I:1000}" 
done

Это, однако, имеет ограничение быть специфичным для bash и не относящимся к POSIX.

Увеличить пространство стека

Иногда вы можете видеть , что люди предполагают увеличение пространства стека с ulimit -s <NUM>; в Linux значение ARG_MAX составляет 1/4 стекового пространства для каждой программы, что означает, что увеличение стекового пространства пропорционально увеличивает пространство для аргументов.

# getconf reports value in bytes, ulimit -s in kilobytes
$ getconf ARG_MAX
2097152
$ echo $((  $(getconf ARG_MAX)*4 ))
8388608
$ printf "%dK\n" $(ulimit -s) | numfmt --from=iec --to=none
8388608
# Increasing stack space results in increated ARG_MAX value
$ ulimit -s 16384
$ getconf ARG_MAX
4194304

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

Избегайте раковины

Другой способ, это использовать pythonили python3которые поставляются по умолчанию с Ubuntu. Пример python + here-doc, приведенный ниже, - это то, что я лично использовал для копирования большого каталога файлов где-то в диапазоне 40000 элементов:

$ python <<EOF
> import shutil
> import os
> for f in os.listdir('.'):
>    if os.path.isfile(f):
>         shutil.copy(f,'./newdir/')
> EOF

Для рекурсивных обходов вы можете использовать os.walk .

Смотрите также:

Сергей Колодяжный
источник
2

ИМХО, оптимальными инструментами для работы с ордами файлов являются findи xargs. См man find. См man xargs. findс его -print0переключателем создает NULразделенный список имен файлов (имена файлов могут содержать любой символ execpt NULили /), который xargsпонимает, используя -0переключатель.xargsзатем создает самую длинную разрешенную команду (большинство имен файлов, без половины имени файла в конце) и выполняет ее. xargsповторяет это, пока findне предоставит больше имен файлов. Бегxargs --show-limits </dev/null чтобы увидеть пределы.

Чтобы решить вашу проблему, (и после проверки man cpнайти --target-directory=):

find . -maxdepth 1 -type f -name '*.prj' -print0 | xargs -0 cp --target-directory=../prjshp/
waltinator
источник