Решение "mv: список аргументов слишком длинный"?

64

У меня есть папка с более чем миллионом файлов, которые нужно отсортировать, но я ничего не могу сделать, потому что mvвыводит это сообщение постоянно

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

Я использую эту команду для перемещения файлов без расширений:

mv -- !(*.jpg|*.png|*.bmp) targetdir/
Dominique
источник

Ответы:

82

xargsэто инструмент для работы. Это или findс -exec … {} +. Эти инструменты запускают команду несколько раз, используя столько аргументов, сколько можно передать за один раз.

Оба метода проще выполнить, когда список аргументов переменной находится в конце, а здесь это не так: последний аргумент to mv- это пункт назначения. Для утилит GNU (т. Е. Для не встроенных Linux или Cygwin) полезна -tопция to mv, чтобы сначала передать пункт назначения.

Если имена файлов не имеют ни пробелов, ни каких-либо из них \"', вы можете просто предоставить имена файлов в качестве входных данных xargs( echoкоманда является встроенной в bash, поэтому она не подпадает под ограничение длины командной строки):

echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir

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

printf '%s\0' !(*.jpg|*.png|*.bmp) | xargs -0 mv -t targetdir

Кроме того, вы можете создать список имен файлов с помощью find. Чтобы избежать повторного использования в подкаталогах, используйте -type d -prune. Поскольку для перечисленных файлов изображений не указано никаких действий, перемещаются только другие файлы.

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec mv -t targetdir/ {} +

(Это включает файлы точек, в отличие от методов подстановки оболочки.)

Если у вас нет утилит GNU, вы можете использовать промежуточную оболочку, чтобы получить аргументы в правильном порядке. Этот метод работает во всех системах POSIX.

find . -name . -o -type d -prune -o \
       -name '*.jpg' -o -name '*.png' -o -name '*.bmp' -o \
       -exec sh -c 'mv "$@" "$0"' targetdir/ {} +

В zsh вы можете загрузить mvвстроенное :

setopt extended_glob
zmodload zsh/files
mv -- ^*.(jpg|png|bmp) targetdir/

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

setopt extended_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- ^*.(jpg|png|bmp) targetdir/

или с шариками в стиле ksh:

setopt ksh_glob
zmodload -Fm zsh/files b:zf_\*
zf_mv -- !(*.jpg|*.png|*.bmp) targetdir/

В качестве альтернативы, используя GNU mvи zargs:

autoload -U zargs
setopt extended_glob
zargs -- ./^*.(jpg|png|bmp) -- mv -t targetdir/
Жиль "ТАК - перестань быть злым"
источник
1
Первые две команды вернули «-bash:!: Событие не найдено», а следующие две команды вообще не перемещали файлы. Я на CentOS 6.5, если вы должны знать
Доминик
1
@ Доминик Я использовал тот же синтаксис, который вы использовали в своем вопросе. Вам нужно shopt -s extglobбудет включить его. Я пропустил шаг в findкомандах, я исправил их.
Жиль "ТАК - перестань быть злым"
Я получаю это с помощью команды find «find: неверное выражение; вы использовали бинарный оператор« -o », перед которым ничего не было». Я сейчас попробую другие.
Доминик
@Dominique findКоманды, которые я опубликовал (сейчас), работают. Вы должны были оставить часть при вставке копии.
Жиль "ТАК - перестань быть злым"
Жиль, для команд поиска, почему бы не использовать оператор not !? Это более явно и легче понять, чем странный трейлинг -o. Например,! -name '*.jpg' -a ! -name '*.png' -a ! -name '*.bmp'
CivFan
13

Если работы с ядром Linux достаточно, вы можете просто

ulimit -s 100000

это будет работать, потому что ядро ​​Linux включило исправление около 10 лет назад, которое изменило лимит аргументов в зависимости от размера стека: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/ фиксация /? ID = b6a2fea39318e43fee84fa7b0b90d68bed92d2ba

Обновление: если вы чувствуете себя смелым, вы можете сказать,

ulimit -s unlimited

и вы будете в порядке с любыми расширениями оболочки, если у вас достаточно оперативной памяти.

Микко Ранталайнен
источник
Это взломать Как бы вы узнали, на что установить ограничение стека? Это также влияет на другие процессы, запущенные в том же сеансе.
Кусалананда
1
Да, это взломать В большинстве случаев такого рода взломы являются одноразовыми (как часто вы вручную перемещаете огромное количество файлов?). Если вы уверены, что процесс не собирается использовать всю вашу оперативную память, вы можете установить ulimit -s unlimitedего, и он будет работать практически с неограниченным количеством файлов.
Микко Ранталайнен
При ulimit -s unlimitedфактической командной строке ограничение составляет 2 ^ 31 или 2 ГБ. ( MAX_ARG_STRLENв исходном коде ядра.)
Микко Ранталайнен
9

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

for x in *; do case "$x" in *.jpg|*.png|*.bmp) ;; *) mv -- "$x" target ;; esac ; done

При этом используются только функции и утилиты POSIX Shell Command Language. Этот однострочник более понятен с отступом, удалив ненужные точки с запятой:

for x in *; do
  case "$x" in
    *.jpg|*.png|*.bmp) 
       ;; # nothing
    *) # catch-all case
       mv -- "$x" target
       ;;
  esac
done
Kaz
источник
При наличии более миллиона файлов это, в свою очередь, приведет к появлению более миллиона mvпроцессов, а не только нескольких, необходимых для использования findрешения POSIX, опубликованного @Gilles. Другими словами, этот способ приводит к большому количеству ненужного оттока процессора.
CivFan
@CivFan Еще одна проблема - убедить себя, что измененная версия эквивалентна оригинальной. Легко видеть, что caseутверждение о результате *расширения для фильтрации нескольких расширений эквивалентно исходному !(*.jpg|*.png|*.bmp)выражению. findОтвет на самом деле не эквивалентны; он спускается в подкаталоги (я не вижу -maxdepthпредиката).
Каз
-name . -o -type d -prune -oзащищает от спуска в подкаталоги. -maxdepthочевидно, не POSIX-совместимый, хотя это не упоминается на моей findстранице руководства.
CivFan
Откат к ревизии 1. Вопрос ничего не говорит о переменных источника или назначения, поэтому это добавляет ненужную путаницу к ответу.
Каз
5

Для более агрессивного решения, чем предложенные ранее, откройте исходный код ядра и отредактируйте include/linux/binfmts.h

Увеличьте размер MAX_ARG_PAGESдо значения, превышающего 32. Это увеличивает объем памяти, который ядро ​​будет предоставлять программным аргументам, что позволит вам указать вашу команду mvили rmкоманду для миллиона файлов или что бы вы ни делали. Перекомпилируйте, установите, перезагрузите компьютер.

BEWARE! Если вы установите это значение слишком большим для вашей системной памяти, а затем запустите команду с большим количеством аргументов, БАДОВЫЕ ВЕЩИ БУДУТ! Будьте предельно осторожны, делая это с многопользовательскими системами, так как злоумышленникам будет проще использовать всю вашу память!

Если вы не знаете, как перекомпилировать и переустановить ядро ​​вручную, вероятно, лучше всего сделать вид, что этого ответа пока не существует.

Perkins
источник
5

Более простое решение, использующее "$origin"/!(*.jpg|*.png|*.bmp)вместо блока catch:

for file in "$origin"/!(*.jpg|*.png|*.bmp); do mv -- "$file" "$destination" ; done

Благодаря @Score_Under

Для многострочного сценария вы можете сделать следующее (обратите внимание на то, что ;перед doneудалением):

for file in "$origin"/!(*.jpg|*.png|*.bmp); do        # don't copy types *.jpg|*.png|*.bmp
    mv -- "$file" "$destination" 
done 

Чтобы сделать более обобщенное решение, которое перемещает все файлы, вы можете сделать одну строку:

for file in "$origin"/*; do mv -- "$file" "$destination" ; done

Который выглядит так, если вы делаете отступ:

for file in "$origin"/*; do
    mv -- "$file" "$destination"
done 

Это берет каждый файл в источнике и перемещает их один за другим к месту назначения. Кавычки $fileнеобходимы, если в именах файлов есть пробелы или другие специальные символы.

Вот пример этого метода, который работал отлично

for file in "/Users/william/Pictures/export_folder_111210/"*.jpg; do
    mv -- "$file" "/Users/william/Desktop/southland/landingphotos/";
done
Белая кошка
источник
Вы можете использовать что-то вроде оригинального шарика в цикле for, чтобы получить более близкое решение к тому, о чем просят.
Score_Under
Что вы имеете в виду оригинальный шар?
Whitecat
К сожалению , если это было немного загадочным, я имел в виду Glob в вопросе: !(*.jpg|*.png|*.bmp). Вы можете добавить это к своему циклу for, используя глобализацию, "$origin"/!(*.jpg|*.png|*.bmp)которая позволит избежать необходимости использования переключателя, используемого в ответе Kaz, и сохранит простое тело цикла for.
Score_Under
Потрясающий счет. Я включил ваш комментарий и обновил свой ответ.
Whitecat
3

Иногда проще всего написать небольшой скрипт, например, на Python:

import glob, shutil

for i in glob.glob('*.jpg'):
  shutil.move(i, 'new_dir/' + i)
duhaime
источник
1

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

Вы можете перемещать порции за раз. Допустим, например, у вас был длинный список буквенно-цифровых имен файлов.

mv ./subdir/a* ./

Это работает. Затем выбейте еще один большой кусок. После пары движений вы можете просто вернуться к использованиюmv ./subdir/* ./

Kristian
источник
0

Вот мои два цента, добавьте это в .bash_profile

mv() {
  if [[ -d $1 ]]; then #directory mv
    /bin/mv $1 $2
  elif [[ -f $1 ]]; then #file mv
    /bin/mv $1 $2
  else
    for f in $1
    do
      source_path=$f
      #echo $source_path
      source_file=${source_path##*/}
      #echo $source_file
      destination_path=${2%/} #get rid of trailing forward slash

      echo "Moving $f to $destination_path/$source_file"

      /bin/mv $f $destination_path/$source_file
    done
  fi
}
export -f mv

использование

mv '*.jpg' ./destination/
mv '/path/*' ./destination/
Ако
источник