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

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

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

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

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

mv -- !(*.jpg|*.png|*.bmp) targetdir/

решение1

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

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

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

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

Вы можете использовать -0опцию to 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/ --

¹ в некоторых xargsреализациях имена файлов также должны быть допустимым текстом в текущей локали. Некоторые также рассматривают имя файла _как указание на конец ввода (можно избежать с помощью -E '')

решение2

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

ulimit -S -s unlimited

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

Если вам не нужно неограниченное пространство в стеке, вы можете сказать, например:

ulimit -S -s 100000

чтобы ограничить стек до 100 МБ. Обратите внимание, что вам нужно установить пространство стека на нормальное использование стека (обычно 8 МБ) плюс размер командной строки, которую вы хотите использовать.

Фактический лимит можно запросить следующим образом:

getconf ARG_MAX

который выведет максимальную длину командной строки в байтах. Например, Ubuntu по умолчанию устанавливает это значение, 2097152что означает примерно 2 МБ. Если я запускаю с неограниченным стеком, я получаю, 4611686018427387903что составляет ровно 2^62 или около 46000 ТБ. Если ваша командная строка превышаетчто, я ожидаю, что вы сможете решить эту проблему самостоятельно.

Обратите внимание, что если вы используете sudoas в sudo mv *.dat somewhere/.running, ulimitто не сможете исправить эту проблему, поскольку sudoсбрасывает размер стека перед выполнением mvfor real. Чтобы обойти это, вам нужно запустить оболочку root с sudo -s, затем запустить ulimit -S -s unlimitedи, наконец, выполнить команду без sudoв этой оболочке root.

решение3

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

import glob, shutil

for i in glob.glob('*.jpg'):
  shutil.move(i, 'new_dir/' + i)

решение4

Ограничение передачи аргументов операционной системы не распространяется на расширения, которые происходят внутри интерпретатора оболочки. Поэтому в дополнение к использованию 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

Связанный контент