Вирішення "mv: список аргументів занадто довгий"?


64

У мене є папка з більш ніж мільйоном файлів, для якої потрібне сортування, але я не можу реально нічого зробити, тому що mvвиводить це повідомлення весь час

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

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

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

Відповіді:


82

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

Обидва методи легше здійснити, коли список аргументів змінної знаходиться в кінці, що тут не так: останнім аргументом mvє адресат. З утилітами GNU (тобто на невбудованому Linux або Cygwin) -tможливість 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
@Dominique Я використав той самий синтаксис, який ви використовували у своєму запитанні. Вам потрібно shopt -s extglobбуде це ввімкнути. Я пропустив крок у findкомандах, я їх виправив.
Жиль

Я отримую це за допомогою команди find "find: неприпустимий вираз; ви використовували двійковий оператор '-o', не маючи нічого перед цим". Зараз я спробую інші.
Домінік

@Dominique findКоманди, які я розмістив (зараз), працюють. Ви, мабуть, залишили частину, коли вставляєте копію.
Жиль

Жил, для команд знаходження, чому б не використати оператор "не" !,? Це більш чітко і простіше зрозуміти, ніж дивний крок -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

і ви будете добре з будь-якими розширеннями оболонок, поки у вас буде достатня кількість оперативної пам’яті.


Це хак. Як би ви знали, для чого встановити ліміт стека? Це також впливає на інші процеси, розпочаті в тому ж сеансі.
Kusalananda

1
Так, це злом. Більшість випадків такі хаки є одноразовими (як часто ви вручну переміщуєте величезну кількість файлів?). Якщо ви впевнені, що процес не з'їсть всю вашу оперативну пам'ять, ви можете встановити, ulimit -s unlimitedі він буде працювати практично без обмежень файлів.
Мікко Ранталайнен

При ulimit -s unlimitedфактичному обмеженні командного рядка 2 ^ 31 або 2 ГБ. ( MAX_ARG_STRLENу джерелі ядра.)
Mikko Rantalainen

9

Ліміт передачі аргументів операційної системи не застосовується до розширень, що трапляються в інтерпретаторі оболонки. Отже, крім використання xargsабо find, ми можемо просто використовувати цикл оболонки, щоб розділити обробку на окремі mvкоманди:

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

При цьому використовуються лише функції та утиліти мови командної оболонки POSIX. Цей одноклапник чіткіший з відступом, видаляючи зайві крапки з комою:

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

Маючи більше мільйона файлів, це, в свою чергу, породить більше мільйона mvпроцесів, а не лише декілька необхідних для використання POSIX findрішення @Gilles розміщено. Іншими словами, цей спосіб призводить до великої кількості непотрібних процесорів.
CivFan

@CivFan Ще одна проблема - переконати себе, що модифікована версія еквівалентна оригіналу. Неважко помітити, що caseтвердження про результат *розширення для фільтрації декількох розширень еквівалентно початковому !(*.jpg|*.png|*.bmp)виразу. findВідповідь насправді не еквівалентні; вона спускається у підкаталоги (я не бачу -maxdepthприсудка).
Каз

-name . -o -type d -prune -oзахищає від спуску в підкаталоги. -maxdepthмабуть, не сумісний з POSIX, хоча це не згадується на моїй findсторінці man.
CivFan

Відновлюється до редакції 1. Питання не говорить нічого про змінні джерела чи місця призначення, тому це додає непотрібної суворості відповіді.
Каз

5

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

Збільшити розмір MAX_ARG_PAGESдо чогось більшого, ніж 32. Це збільшує об'єм пам'яті, яке ядро ​​дозволить програмним аргументам, тим самим дозволяючи вказувати свій mvабо rmкомандний для мільйона файлів або все, що ви робите. Перекомпілюйте, встановіть, перезавантажте.

ПОДЕРЖАЙТЕ! Якщо ви встановите це занадто велике для вашої системної пам’яті, а потім запустіть команду з великою кількістю аргументів. Будьте вкрай обережні, роблячи це для багатокористувацьких систем, це полегшує зловмисним користувачам використання всієї вашої пам’яті!

Якщо ви не знаєте, як перекомпілювати та перевстановити ядро ​​вручну, можливо, найкраще просто зробити вигляд, що ця відповідь наразі не існує.


5

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

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-for, щоб ближче вирішити питання, про що вимагають.
Score_Under

Що ти маєш на увазі оригінальний глобус?
Whitecat

До жаль , якщо це було трохи загадковим, я мав в виду Glob в питанні: !(*.jpg|*.png|*.bmp). Ви можете додати це до своєї for-loop шляхом глобулювання, "$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)

1

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

Ви можете переміщувати частини за раз. Скажімо, наприклад, у вас був довгий список буквено-цифрових імен файлів.

mv ./subdir/a* ./

Це працює. Потім вибийте ще один великий шматок. Після того як пара рухається, ви можете просто повернутися до використанняmv ./subdir/* ./


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/
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.