У меня есть папка с более чем миллионом файлов, которую нужно отсортировать, но я ничего не могу сделать, потому что mv
все время выводит это сообщение
-bash: /bin/mv: Argument list too long
Я использую эту команду для перемещения файлов без расширений:
mv -- !(*.jpg|*.png|*.bmp) targetdir/
решение1
xargs
это инструмент для работы. Это илиfind
с -exec … {} +
. Эти инструменты запускают команду несколько раз, с таким количеством аргументов, которое можно передать за один раз.
Оба метода проще реализовать, когда список переменных аргументов находится в конце, что не так в данном случае: последний аргумент to mv
— это назначение. С утилитами GNU (т. е. на невстроенном Linux или Cygwin) опция -t
to 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 ТБ. Если ваша командная строка превышаетчто, я ожидаю, что вы сможете решить эту проблему самостоятельно.
Обратите внимание, что если вы используете sudo
as в sudo mv *.dat somewhere/.
running, ulimit
то не сможете исправить эту проблему, поскольку sudo
сбрасывает размер стека перед выполнением mv
for 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