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

330

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

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

rm -f *.pdf

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

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

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

  • 17
    Вы можете найти эту ссылку полезной
Показать ещё 5 комментариев
Теги:

22 ответа

497
Лучший ответ

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

Попробуйте следующее:

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

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

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

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

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

find . -name "*.pdf" -delete
  • 1
    Разве это не отправляет точно такие же аргументы в rm, как в rm -f * .pdf? (Или, если есть подкаталоги, еще больше аргументов.)
  • 6
    Нет, xargs специально разбивает список и при необходимости выдает несколько команд.
Показать ещё 22 комментария
213

TL;DR

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

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

Это системная проблема, связанная с константой execve и ARG_MAX. Существует много документации по этому поводу (см. man execve, debian wiki).

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

getconf ARG_MAX
# 2097152 # on 3.5.0-40-generic

Решение

Используйте цикл for, как рекомендуется на BashFAQ/095, и нет ограничений, кроме RAM/памяти:

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

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

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

find . -name '*.pdf' -exec rm {} +

Ссылки

  • 21
    Отличный ответ, так нужно отвечать на все вопросы. Спасибо!
  • 0
    +1 за упоминание цикла for . Раньше я использовал find , но я всегда смотрю, как это сделать, так как я постоянно забываю опции и т. Д. for кажется легче вспомнить ИМХО
Показать ещё 6 комментариев
153

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

find . -maxdepth 1 -name '*.pdf' -delete
  • 4
    Это все равно вернет «Список аргументов слишком длинный». По крайней мере, для меня это так. Использование xargs , согласно ответу Денниса, работает как задумано.
  • 7
    Это звучит как ошибка в поиске.
Показать ещё 5 комментариев
15

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

echo *.pdf | xargs -n 100 rm

  • 4
    Для удаления команды в Linux, которая может быть катастрофой, если вы инженер и ввели ошибку, я считаю, что это «самый безопасный и я знаю, что происходит», это лучший вариант. Не то, что если вы пропустите ввод точки, ваша компания потерпит крах за одну минуту.
  • 1
    Как мы можем сделать это расширением по умолчанию для определенных команд? Существует множество «стандартных» команд linux, в которых известно, нужны ли они всем сразу или нет (например, «rm»)
Показать ещё 1 комментарий
8

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

find . -name '*.pdf' -exec rm -f {} \;
  • 0
    Это также удаляет файлы из подкаталогов. Как это предотвратить?
  • 0
    @NikunjChauhan Добавить опцию -maxdepth: find . -maxdepth 1 -name '*.pdf' -exec rm -f {} \;
Показать ещё 2 комментария
6

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

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

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

  • 1
    Для большого количества файлов это кажется значительно быстрее
5

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

find -name "*.pdf"  -delete
5

вы можете попробовать следующее:

for f in *.pdf
do
  rm $f
done

EDIT: Комментарий ThiefMaster предлагает мне не раскрывать такую ​​опасную практику молодым оболочка jedis, поэтому я добавлю более "безопасную" версию (ради сохранения вещей, когда у кого-то есть файл "-rf...pdf" )

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

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

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

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

  • 5
    Я думаю, что это будет действительно хорошо с файлом с именем, например, -rf .. .pdf
  • 0
    да, но, как правило, при использовании в оболочке издатель команды «должен» посмотреть, что он делает :). На самом деле я предпочитаю перенаправить в файл, а затем проверять каждую строку.
Показать ещё 1 комментарий
3

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

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

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

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

  • 0
    Хотя этот фрагмент кода может решить вопрос, в том числе объяснение того, как и почему это решает проблему , действительно поможет улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос для читателей в будущем, а не только для того, кто спрашивает сейчас! Пожалуйста, отредактируйте свой ответ, чтобы добавить объяснение и указать, какие ограничения и предположения применяются.
3

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

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

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

Вы также можете удалить их через команду find:

find . -name "*.pdf" -exec rm {} \;
  • 1
    Нет, rm не имеет такого ограничения на количество файлов, которые он будет обрабатывать (кроме того, его argc не может быть больше, чем INT_MAX ). Это ограничение ядра на максимальный размер всего массива аргументов (поэтому длина имен файлов значительна).
3

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

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

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

cp -r abc/def/

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

  • 0
    Я не знаю, почему кто-то отрицал это, даже не комментируя это (это политика, ребята!). Мне нужно было удалить все файлы внутри папки (вопрос не к PDF-файлам, обратите внимание), и для этого этот трюк работает хорошо, в конце концов все, что нужно сделать, это воссоздать папку, которая была удалена, когда Я использовал `rm -R / path / to / folder".
  • 1
    Это работает, потому что в случае с OP он использовал *, который расширился до огромного списка .pdf, давая каталог, который будет обрабатывать это внутренне, таким образом, не имея дело с проблемой OP. Я думаю, что по этой причине было отклонено. Он может быть недоступен для OP, если у него есть вложенный каталог или другие файлы (не pdf) в его каталоге
2

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

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

В итоге я написал python script для удаления файлов на основе первых 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 000 файлов temp в папке примерно через 15 минут. Я прокомментировал tar из небольшого числа кода, поэтому любой, обладающий минимальным знанием питона, может манипулировать этим кодом.

1

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

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 {} \;

Если вы хотите giz файлы для файлов более чем на 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 {} \;
1

Я обнаружил, что для чрезвычайно больших списков файлов ( > 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)
1

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

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

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

split -l 600 list.txt

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

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

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

  • 5
    Если у вас есть файл с именем pdf_format_sucks.docx он также будет удален ... ;-) Вы должны использовать правильное и точное регулярное выражение при подборе файлов pdf.
  • 0
    Ах, это правда. Извини, мой плохой: D. Я пытаюсь редактировать это.
Показать ещё 1 комментарий
1

И еще один:

cd  /path/to/pdf
printf "%s\0" *.[Pp][Dd][Ff] | xargs -0 rm
  • 1
    Хотя этот фрагмент кода может решить вопрос, в том числе объяснение того, как и почему это решает проблему , действительно поможет улучшить качество вашего сообщения. Помните, что вы отвечаете на вопрос для читателей в будущем, а не только для того, кто спрашивает сейчас! Пожалуйста, отредактируйте свой ответ, чтобы добавить объяснение и указать, какие ограничения и предположения применяются.
  • 0
    В частности, если printf не является встроенной оболочкой, на него распространяются те же ограничения.
0

Использование GNU parallel (sudo apt install parallel) очень просто

Он выполняет команды многопоточности, где '{}' - это аргумент, переданный

например.

ls /tmp/myfiles* | parallel 'rm {}'

  • 0
    Почему отрицательный голос?
  • 0
    Я не знаю, но я предполагаю, что это потому, что передача вывода ls напрямую другим командам является опасным антипаттерном, и тот факт, что расширение подстановочного знака вызовет тот же сбой при выполнении ls как и в оригинальная команда rm .
0

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

Итак, вместо

grep "something" *

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

cd ..
grep "something" -R search_in_this_dir/

Обратите внимание, что он также будет рекурсивно искать подпапки в каталоге "search_in_this_dir".

-2

Более безопасная версия, чем использование xargs, также не рекурсивная: ls -p | grep -v '/$' | grep '\.pdf$' | while read file; do rm "$file"; done

Фильтрация наших каталогов здесь немного не нужна, так как "rm" в любом случае не удалит ее, и ее можно удалить для простоты, но зачем запускать что-то, что обязательно вернет ошибку?

  • 3
    Это вовсе не безопасно и не работает с именами файлов с символами новой строки в них, чтобы указать на один очевидный угловой случай. Парсинг ls является распространенным антипаттерном, которого определенно следует избегать, и добавляет сюда ряд дополнительных ошибок. grep | grep просто не очень элегантный.
  • 0
    В любом случае, это не новая и экзотическая проблема, требующая комплексного решения. Ответы с find хороши и хорошо документированы здесь и в других местах. См., Например, mywiki.wooledge.org, чтобы узнать больше об этой и смежных темах.
-2

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

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

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

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

-2

Предположим, что введено имя входного каталога и выведено имя выходного каталога. Затем вы можете использовать простой цикл для копирования всех

for f in input/*
do
cp $f output
done
-4

Этот параметр кажется простым для этой проблемы. Я получил эту информацию из какой-то другой темы, но это помогло мне.

for file in /usr/op/data/Software/temp/application/openpages-storage/*; do
    cp "$file" /opt/sw/op-storage/
done

Просто запустите указанную выше команду, и она выполнит задачу.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню