Слишком длинный список аргументов для команд rm, cp, mv

У меня есть несколько сотен PDF-файлов в каталоге в UNIX. Названия PDF-файлов действительно длинные (около 60 символов).

Когда я пытаюсь удалить все PDF-файлы вместе, используя следующую команду:

rm -f *.pdf

Я получаю следующую ошибку:

/bin/rm: cannot execute [Argument list too long]

Каково решение этой ошибки? Эта ошибка возникает для mv а также cp команды тоже? Если да, как решить для этих команд?

35 ответов

Решение

Причина этого заключается в том, что bash фактически расширяет звездочку на каждый соответствующий файл, создавая очень длинную командную строку.

Попробуй это:

find . -name "*.pdf" -print0 | xargs -0 rm

Предупреждение: это рекурсивный поиск, который также найдет (и удалит) файлы в подкаталогах. Тэкс на -f в команду rm, только если вы уверены, что не хотите подтверждения.

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

find . -maxdepth 1 -name "*.pdf" -print0 | xargs -0 rm

Другой вариант заключается в использовании поиска -delete флаг:

find . -name "*.pdf" -delete

ТЛ; др

Это ограничение ядра на размер аргумента командной строки. Использовать for цикл вместо

Происхождение проблемы

Это системная проблема, связанная с execve а также ARG_MAX постоянная. Об этом много документации (см. Man execve, вики Debian).

По сути, расширение создает команду (с ее параметрами), которая превышает ARG_MAX предел. По ядру 2.6.23 предел был установлен на 128 kB, Эта константа была увеличена, и вы можете получить ее значение, выполнив:

getconf ARG_MAX
# 2097152 # on 3.5.0-40-generic

Решение: Использование for петля

Использовать for Цикл, как это рекомендовано для BashFAQ / 095, и нет ограничений, кроме объема памяти и памяти:

for f in *.pdf; do rm "$f"; done

Также это переносимый подход, поскольку у glob сильное и согласованное поведение среди оболочек ( часть спецификации POSIX).

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

Решение: Использование find

Если вы настаиваете, вы можете использовать find но на самом деле не используйте xargs, так как он "опасен (сломан, может использоваться и т. д.) при чтении ввода, не разделенного NUL":

find . -maxdepth 1 -name '*.pdf' -delete 

С помощью -maxdepth 1 ... -delete вместо -exec rm {} + позволяет find просто выполнить необходимые системные вызовы самостоятельно, без использования внешнего процесса, следовательно, быстрее (благодаря комментарию @chepner).

Рекомендации

find имеет -delete действие:

find . -maxdepth 1 -name '*.pdf' -delete

Другой ответ - заставить xargs обрабатывать команды в пакетном режиме. Например, чтобы delete файлы 100 вовремя, cd в каталог и запустите это:

echo *.pdf | xargs -n 100 rm

Если вы пытаетесь удалить очень большое количество файлов за один раз (сегодня я удалил каталог с 485 000+), вы, вероятно, столкнетесь с этой ошибкой:

/bin/rm: Argument list too long.

Проблема в том, что когда вы печатаете что-то вроде rm -rf *, * заменяется списком каждого соответствующего файла, например "rm -rf file1 file2 file3 file4" и так далее. Для хранения этого списка аргументов имеется относительно небольшой буфер памяти, и если он заполнен, оболочка не выполнит программу.

Чтобы обойти эту проблему, многие люди используют команду find, чтобы найти каждый файл и передать их один за другим команде "rm", например так:

find . -type f -exec rm -v {} \;

Моя проблема в том, что мне нужно было удалить 500 000 файлов, и это заняло слишком много времени.

Я наткнулся на гораздо более быстрый способ удаления файлов - команда "find" имеет встроенный флаг "-delete"! Вот что я в итоге использовал:

find . -type f -delete

Используя этот метод, я удалял файлы со скоростью около 2000 файлов в секунду - намного быстрее!

Вы также можете показать имена файлов при их удалении:

find . -type f -print -delete

... или даже показать, сколько файлов будет удалено, а затем время, необходимое для их удаления:

root@devel# ls -1 | wc -l && time find . -type f -delete
100000
real    0m3.660s
user    0m0.036s
sys     0m0.552s

Для того, у кого нет времени.Выполните следующую команду на терминале.

      ulimit -S -s unlimited

Затем выполните операцию cp/mv/rm.

Или вы можете попробовать:

find . -name '*.pdf' -exec rm -f {} \;

Вы можете попробовать это:

for f in *.pdf
do
  rm $f
done

РЕДАКТИРОВАТЬ: комментарий ThiefMaster предлагает мне не раскрывать такую ​​опасную практику для джедаев молодой оболочки, поэтому я добавлю более "более безопасную" версию (ради сохранения вещей, когда у кого-то есть файл "-rf . ..Pdf")

echo "# Whooooo" > /tmp/dummy.sh
for f in '*.pdf'
do
   echo "rm -i $f" >> /tmp/dummy.sh
done

После запуска вышеупомянутого, просто откройте файл /tmp/dummy.sh в вашем фаворе. редактировать и проверять каждую строку на наличие опасных имен файлов, комментируя их, если они найдены.

Затем скопируйте скрипт dummy.sh в ваш рабочий каталог и запустите его.

Все это по соображениям безопасности.

Я удивлен, что нет ulimit ответы здесь. Каждый раз, когда у меня возникает эта проблема, я оказываюсь здесь или здесь. Я понимаю, что это решение имеет ограничения, но ulimit -s 65536 кажется, часто делает трюк для меня.

Вы можете использовать массив bash:

files=(*.pdf)
for((I=0;I<${#files[*]};I+=1000)); do rm -f ${files[@]:I:1000}; done

Таким образом, он будет стирать партиями по 1000 файлов за шаг.

Вы можете использовать эту рекомендацию

find -name "*.pdf"  -delete

Если это имена файлов с пробелами или специальными символами, используйте:

find -maxdepth 1 -name '*.pdf' -exec rm "{}" \;

Это предложение ищет все файлы в текущем каталоге (-maxdepth 1) с расширением pdf (-name '*.pdf'), а затем удаляет каждый из них (-exec rm "{}").

Выражение {} заменяет имя файла, а "{}" задает имя файла в виде строки, включая пробелы или специальные символы.

У команды rm есть ограничение на количество файлов, которые вы можете удалить одновременно.

Одна возможность, вы можете удалить их, используя несколько раз команды rm, основанные на ваших шаблонах файлов, например:

rm -f A*.pdf
rm -f B*.pdf
rm -f C*.pdf
...
rm -f *.pdf

Вы также можете удалить их с помощью команды find:

find . -name "*.pdf" -exec rm {} \;

И еще один:

cd  /path/to/pdf
printf "%s\0" *.[Pp][Dd][Ff] | xargs -0 rm

printf это встроенная оболочка, и, насколько я знаю, она всегда была таковой. Теперь, учитывая, что printf это не команда оболочки (а встроенная), она не подлежитargument list too long ..." фатальная ошибка.

Таким образом, мы можем безопасно использовать его с такими шаблонами, как *.[Pp][Dd][Ff], тогда мы передадим свой вывод, чтобы удалить (rm) через xargs, который гарантирует, что он соответствует достаточно именам файлов в командной строке, чтобы не потерпеть неудачу rm команда, которая является командой оболочки.

\0 в printf служит нулевым разделителем для имен файлов, которые затем обрабатываются xargs команда, использующая его (-0) как разделитель, так rm не вызывает ошибку, если в именах файлов есть пробелы или другие специальные символы.

find . -type f -name '*xxx' -print -delete

А как насчет более короткого и надежного?

for i in **/*.pdf; do rm "$i"; done

Попробуйте это также. Если вы хотите удалить файлы / папки более 30/90 (+) или менее 30/90(-) дней, то вы можете использовать приведенные ниже команды ex

Пример: для 90 дней исключается выше после 90 дней удаления файлов / папок, это означает 91,92....100 дней

find <path> -type f -mtime +90 -exec rm -rf {} \;

Пример: только для последних 30-дневных файлов, которые вы хотите удалить, используйте команду ниже (-)

find <path> -type f -mtime -30 -exec rm -rf {} \;

Если вы хотите посмотреть файлы более чем на 2 дня

find <path> -type f -mtime +2 -exec gzip {} \;

Если вы хотите видеть файлы / папки только за последний месяц. Пример:

find <path> -type f -mtime -30 -exec ls -lrt {} \;

Более 30 дней больше, чем только список файлов / папок. Например:

find <path> -type f -mtime +30 -exec ls -lrt {} \;

find /opt/app/logs -type f -mtime +30 -exec ls -lrt {} \;

Я столкнулся с той же проблемой при копировании исходного каталога формы в место назначения

исходный каталог имел файлы ~3 lakcs

я использовал cp с опцией -r, и это сработало для меня

cp -r abc/ def/

он скопирует все файлы из abc в def без предупреждения о слишком длинном списке аргументов

Список аргументов слишком длинный

Как заголовок этого вопроса для cp, mv и rm, но ответ в основном означает rm.

Команды un*x

Внимательно прочтите справочную страницу команды!

За cp и mv, Существует -tпереключатель, для цели:

find . -type f -name '*.pdf' -exec cp -ait "/path to target" {} +

и

find . -type f -name '*.pdf' -exec mv -t "/path to target" {} +

Скриптовый путь

В сценарии bash используется общее решение:

#!/bin/bash

folder=( "/path to folder" "/path to anther folder" )

[ "$1" = "--run" ] && exec find "${target[@]}" -type f -name '*.pdf' -exec $0 {} +

for file ;do
    printf "Doing something with '%s'.\n" "$file"
done

Я столкнулся с этой проблемой несколько раз. Многие из решений будут запускать rm Команда для каждого отдельного файла, который необходимо удалить. Это очень неэффективно:

find . -name "*.pdf" -print0 | xargs -0 rm -rf

В итоге я написал скрипт на python для удаления файлов на основе первых 4 символов в имени файла:

import os
filedir = '/tmp/' #The directory you wish to run rm on 
filelist = (os.listdir(filedir)) #gets listing of all files in the specified dir
newlist = [] #Makes a blank list named newlist
for i in filelist: 
    if str((i)[:4]) not in newlist: #This makes sure that the elements are unique for newlist
        newlist.append((i)[:4]) #This takes only the first 4 charcters of the folder/filename and appends it to newlist
for i in newlist:
    if 'tmp' in i:  #If statment to look for tmp in the filename/dirname
        print ('Running command rm -rf '+str(filedir)+str(i)+'* : File Count: '+str(len(os.listdir(filedir)))) #Prints the command to be run and a total file count
        os.system('rm -rf '+str(filedir)+str(i)+'*') #Actual shell command
print ('DONE')

Это сработало очень хорошо для меня. Мне удалось очистить более 2 миллионов временных файлов в папке примерно за 15 минут. Я прокомментировал tar из небольшого фрагмента кода, чтобы любой, кто имеет минимальные знания Python или не обладал ими, мог манипулировать этим кодом.

я решил с for

я на macOSс zsh

Я перевел только тысячи jpgфайлы. В пределах mvв одной строке команды.

Убедитесь, что в имени файлов, которые вы пытаетесь переместить, нет пробелов или специальных символов.
      for i in $(find ~/old -type f -name "*.jpg"); do mv $i ~/new; done

Вы можете создать временную папку, переместить все файлы и подпапки, которые вы хотите сохранить, во временную папку, затем удалить старую папку и переименовать временную папку в старую папку, попробуйте этот пример, пока вы не будете уверены, что все это работает:

mkdir testit
cd testit
mkdir big_folder tmp_folder
touch big_folder/file1.pdf
touch big_folder/file2.pdf
mv big_folder/file1,pdf tmp_folder/
rm -r big_folder
mv tmp_folder big_folder

rm -r big_folder удалит все файлы в big_folder независимо от того, сколько. Вы просто должны быть очень осторожны, у вас сначала есть все файлы / папки, которые вы хотите сохранить, в данном случае это было file1.pdf

Я обнаружил, что для очень больших списков файлов (>1e6) эти ответы были слишком медленными. Вот решение, использующее параллельную обработку в Python. Я знаю, я знаю, что это не Linux... но ничего другого здесь не работает.

(Это спасло меня часы)

# delete files
import os as os
import glob
import multiprocessing as mp

directory = r'your/directory'
os.chdir(directory)


files_names = [i for i in glob.glob('*.{}'.format('pdf'))]

# report errors from pool

def callback_error(result):
    print('error', result)

# delete file using system command
def delete_files(file_name):
     os.system('rm -rf ' + file_name)

pool = mp.Pool(12)  
# or use pool = mp.Pool(mp.cpu_count())


if __name__ == '__main__':
    for file_name in files_names:
        print(file_name)
        pool.apply_async(delete_files,[file_name], error_callback=callback_error)

Удалить все *.pdf в каталоге /path/to/dir_with_pdf_files/

mkdir empty_dir        # Create temp empty dir

rsync -avh --delete --include '*.pdf' empty_dir/ /path/to/dir_with_pdf_files/

Чтобы удалить определенные файлы с помощью rsync Использование подстановочного знака, вероятно, самое быстрое решение, если у вас миллионы файлов. И это позаботится об ошибке, которую вы получаете.


(Необязательный шаг): DRY RUN. Чтобы проверить, что будет удалено без удаления. `

rsync -avhn --delete --include '*.pdf' empty_dir/ /path/to/dir_with_pdf_files/

,,,

Нажмите rsync советы и хитрости для более rsync хаков

Если вы хотите удалить и файлы, и каталоги, вы можете использовать что-то вроде:

      echo /path/* | xargs rm -rf

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

find . -name "*.png" -mtime +50 -exec rm {} \;

Разница с другими командами заключается в параметре mtime, который будет принимать только файлы старше X дней (в примере 50 дней)

Используя это несколько раз, уменьшая при каждом выполнении дневной диапазон, я смог удалить все ненужные файлы

Я знаю только способ обойти это. Идея состоит в том, чтобы экспортировать этот список файлов PDF в ваш файл. Затем разделите этот файл на несколько частей. Затем удалите файлы PDF, перечисленные в каждой части.

ls | grep .pdf > list.txt
wc -l list.txt

wc -l - подсчитать, сколько строк содержит список.txt. Когда у вас есть представление о том, как долго это происходит, вы можете разделить его пополам, вперёд или что-то в этом роде. Использование команды split -l Например, разбить его на 600 строк каждая.

split -l 600 list.txt

это создаст несколько файлов с именами xaa,xab,xac и т. д., в зависимости от того, как вы разделите его. Теперь, чтобы "импортировать" каждый список в этом файле в команду rm, используйте это:

rm $(<xaa)
rm $(<xab)
rm $(<xac)

Извините за мой плохой английский.

Если вам необходимо обеспечить отзывчивость сервера или системы при удалении огромного количества файлов, sleep между каждым оператором удаления может быть хороший подход.

find . -name "*.pdf" -print0 | while read -d $'\0' file
do
    rm "$file"
    sleep 0.005 # Sleeps for 5ms, tweak as needed
done

Я столкнулся с подобной проблемой, когда приложение создавало миллионы бесполезных файлов журналов, которые заполняли все иноды. Я прибег к "locate", собрал все файлы, "расположенные" d, в текстовый файл, а затем удалил их один за другим. Потребовалось время, но сделал работу!

Для удаления первых 100 файлов:

rm -rf 'ls | голова -100'

Другие вопросы по тегам