Вопрос или проблема
У меня есть папка с более чем миллионом файлов, которые необходимо отсортировать, но я не могу ничего сделать, потому что mv
постоянно выводит это сообщение:
-bash: /bin/mv: Argument list too long
Я использую эту команду для перемещения файлов без расширений:
mv -- !(*.jpg|*.png|*.bmp) targetdir/
xargs
— это инструмент для этой задачи. Также можно использовать find
с -exec … {} +
. Эти инструменты выполняют команду несколько раз, с максимально возможным количеством аргументов за один раз.
Оба метода легче осуществить, когда переменный список аргументов стоит в конце, но в данном случае это не так: последний аргумент для mv
— это назначение. С утилитами GNU (например, на не встроенной Linux или Cygwin), полезна опция -t
для mv
, чтобы передать место назначения сначала.
Если имена файлов не содержат пробелов и любых из \"'
и не начинаются с -
¹, то вы можете просто представить имена файлов как входные данные для xargs
(команда echo
встроена в bash, так что она не подпадает под ограничение длины командной строки; если вы видите !: event not found
, вам нужно включить синтаксис глобов с помощью shopt -s extglob
):
echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir --
Вы можете использовать опцию -0
для xargs
, чтобы использовать входные данные с разделителями NULL вместо формата с кавычками по умолчанию.
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 ''
)
Если достаточно работать с ядром 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
, как в sudo mv *.dat somewhere/.
, запуск ulimit
не может исправить эту проблему, потому что sudo
сбрасывает размер стека перед выполнением mv
на самом деле. Чтобы обойти это, вы должны запустить оболочку root с помощью sudo -s
, затем выполнить ulimit -S -s unlimited
и, наконец, запустить команду без sudo
в этой оболочке root.
Иногда проще всего написать небольшой скрипт, например на Python:
import glob, shutil
for i in glob.glob('*.jpg'):
shutil.move(i, 'new_dir/' + i)
Попробуйте это:
find currentdir -name '*.*' -exec mv {} targetdir \;
find
: поиск в папке-name
: соответствует заданному критерию-exec
: выполнение команды, которая следует{}
: вставка найденного имени файла\;
: отмечает конец выполняемой команды
Ограничение на передачу аргументов операционной системы не применимо к расширениям, которые выполняются внутри интерпретатора оболочки. Таким образом, помимо использования xargs
или find
, мы можем просто использовать цикл оболочки для разделения обработки на отдельные команды mv
:
for x in *; do case "$x" in *.jpg|*.png|*.bmp) ;; *) mv -- "$x" target ;; esac ; done
Это использует только особенности и утилиты языка команд POSIX Shell. Этот однострочник более ясен с отступами, с удалением ненужных точек с запятой:
for x in *; do
case "$x" in
*.jpg|*.png|*.bmp)
;; # ничего
*) # все остальное
mv -- "$x" target
;;
esac
done
Более простое решение с использованием "$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 # не копировать файлы типов *.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
Для более агрессивного решения, чем те, которые предлагались ранее, откройте исходный код вашего ядра и отредактируйте include/linux/binfmts.h
.
Увеличьте размер MAX_ARG_PAGES
на большее значение, чем 32. Это увеличивает количество памяти, которое ядро будет допускать для аргументов программы, что позволяет вам указать вашу команду mv
или rm
для миллиона файлов или чего-то еще, что вы делаете. Пересоберите, установите, перезагрузите.
ОСТОРОЖНО! Если вы установите это значение слишком большим для вашей системной памяти, и затем запустите команду с большим количеством аргументов, ПРОИЗОЙДУТ ПЛОХИЕ ВЕЩИ! Будьте крайне осторожны, делая это на многопользовательских системах, это облегчает для злонамеренных пользователей использование всей вашей памяти!
Если вы не знаете, как вручную пересобрать и переустановить ядро, возможно, лучше пока просто игнорировать это решение.
Вы можете обойти это ограничение, продолжая использовать mv
, если не возражаете выполнить его несколько раз.
Вы можете перемещать части за раз. Допустим, у вас есть длинный список файлов с алфавитно-цифровыми именами.
mv ./subdir/a* ./
Это сработает. Затем обработайте другой большой кусок. После пары перемещений вы можете снова использовать mv ./subdir/* ./
Вот мое предложение, добавьте это в .bash_profile
mv() {
if [[ -d $1 ]]; then # перемещение директории
/bin/mv $1 $2
elif [[ -f $1 ]]; then # перемещение файла
/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%/} # убрать завершающий слеш
echo "Перемещение $f в $destination_path/$source_file"
/bin/mv $f $destination_path/$source_file
done
fi
}
export -f mv
Использование
mv '*.jpg' ./цель/
mv '/путь/*' ./цель/
Благодарность DUHAIME;
Python работает для меня очень просто и легко протестировать. ! Я не являюсь регулярным пользователем Python !
print ("начало")
start = pathlib.Path('.')
print(start)
for subPath in start.iterdir():
print(subPath)
if subPath.is_dir():
print("каталог")
for i in subPath.glob('*.jpeg'):
print(i)
shutil.move(i, start)
for i in subPath.glob('*.heic'):
print(i)
shutil.move(i, start)
for i in subPath.glob('*.mov'):
print(i)
shutil.move(i, start)
else:
print("не каталог")
print("конец")
.
Ответ или решение
Вопрос о том, как решить проблему с ошибкой "mv: Argument list too long", возникает достаточно часто при работе с большими объемами файлов в Unix-подобных операционных системах. Такие ситуации встречаются, когда пытаются передать слишком много аргументов в команду mv
, и это ограничение связано с системными лимитами передачи аргументов командной строке. Рассмотрим, как можно решить эту проблему с использованием различных методов и техник.
Теория
Основная причина появления ошибки "Argument list too long" заключается в ограничении операционной системы на максимальную длину аргументов, которые могут быть переданы через командную строку. В большинстве систем это ограничение связано с пределом на размер стека. При попытке использования команды mv
с огромным количеством файлов она может превысить данный лимит.
Пример
Предположим, у вас есть директория с более чем миллионом файлов, которые необходимо переместить, но команда mv
сгенерирует ошибку:
-bash: /bin/mv: Argument list too long
Вы пытаетесь использовать команду:
mv -- !(*.jpg|*.png|*.bmp) targetdir/
Применение
Чтобы обойти это ограничение, есть несколько основных методов:
-
Использование
xargs
сecho
Команда
xargs
разбивает список аргументов на более мелкие части, которые можно передать в командуmv
.echo !(*.jpg|*.png|*.bmp) | xargs mv -t targetdir --
Здесь команда
echo
генерирует список файлов, аxargs
обрабатывает его частями, перемещая файлы в указанный каталогtargetdir
. -
Использование
find
с-exec
Команда
find
позволяет выполнять действия для каждого найденного файла без передачи всех аргументов сразу:find . -type f ! -name "*.jpg" ! -name "*.png" ! -name "*.bmp" -exec mv -t targetdir/ {} +
Эта команда ищет все файлы, кроме изображений, и перемещает их в целевой каталог.
-
Изменение параметров среды
Для Linux можно временно увеличить размер стека, чтобы расширить лимиты на длину аргументов:
ulimit -S -s unlimited
Однако этот способ может быть менее безопасным в многопользовательских системах, так как позволяет одному пользователю использовать все ресурсы памяти.
-
Скрипты на Python
Использование Python-скрипта для обхода по всем файлам без ограничения на количество аргументов:
import glob import shutil for file in glob.glob('*.jpg'): shutil.move(file, 'targetdir/')
-
Циклы в шелле
Простой цикл в шелле может обеспечить поочередное перемещение каждого файла:
for file in "$origin"/!(*.jpg|*.png|*.bmp); do mv -- "$file" "$destination" done
-
Изменение исходного кода ядра
Изменение параметра
MAX_ARG_PAGES
в исходном коде ядра Linux. Этот метод требует опыт работы с компиляцией ядра и связан с определенными рисками использования системной памяти.
Каждый из вышеперечисленных методов имеет свои преимущества и ограничения. На практике обычно комбинируются несколько подходов, чтобы достичь наилучших результатов в зависимости от конкретной ситуации и ограничения системных ресурсов. Выбор зависит от конкретных требований и условий использования: будь то безопасность, объем обрабатываемых данных или требуемая скорость выполнения.