web-dev-qa-db-fra.com

Erreur trop longue de la liste d'arguments pour les commandes rm, cp, mv

J'ai plusieurs centaines de PDF sous un répertoire sous UNIX. Les noms des fichiers PDF sont très longs (environ 60 caractères).

Lorsque j'essaie de supprimer tous les fichiers PDF ensemble à l'aide de la commande suivante:

rm -f *.pdf

Je reçois l'erreur suivante:

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

Quelle est la solution à cette erreur? Cette erreur se produit-elle également pour les commandes mv et cp? Si oui, comment résoudre ces commandes?

474
Vicky

La raison en est que bash étend l'astérisque à tous les fichiers correspondants, générant ainsi une très longue ligne de commande.

Essaye ça:

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

Attention: ceci est une recherche récursive et trouvera (et supprimera) des fichiers dans des sous-répertoires également. Ne pointez sur -f dans la commande rm que si vous êtes sûr de ne pas vouloir de confirmation.

Vous pouvez effectuer les opérations suivantes pour rendre la commande non-récursive:

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

Une autre option consiste à utiliser l'indicateur -delete de find:

find . -name "*.pdf" -delete
679
DPlusV

tl; dr

C'est une limitation du noyau sur la taille de l'argument de ligne de commande. Utilisez plutôt une boucle for.

Origine du problème

Il s'agit d'un problème système, lié à execve et à ARG_MAX constant. Il y a beaucoup de documentation à ce sujet (voir man execve , wiki de debian ). 

En gros, l’extension produit une commande (avec ses paramètres) qui dépasse la limite ARG_MAX . Sur le noyau 2.6.23, la limite a été définie sur 128 kB. Cette constante a été augmentée et vous pouvez obtenir sa valeur en exécutant:

getconf ARG_MAX
# 2097152 # on 3.5.0-40-generic

Solution: utiliser la boucle for

Utilisez une boucle for comme recommandé sur BashFAQ/095 et il n'y a pas de limite, à l'exception de la RAM/de l'espace mémoire:

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

C'est aussi une approche portable car glob a un comportement fort et cohérent parmi les shells ( partie de la spécification POSIX ). 

Note: Comme l'ont noté plusieurs commentaires, ceci est en effet plus lent mais plus facile à gérer car il peut adapter des scénarios plus complexes, par exemple où l'on veut faire plus qu'une seule action.

Solution: en utilisant find

Si vous insistez, vous pouvez utiliser find mais vraiment n'utilisez pas xargs car "est dangereux (cassé, exploitable, etc.) lors de la lecture d'entrées non délimitées par NUL":

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

Utiliser -maxdepth 1 ... -delete au lieu de -exec rm {} + permet à find d'exécuter simplement les appels système requis sans utiliser de processus externe, donc plus rapidement (grâce à @chepner comment ).

Références

295
Édouard Lopez

find a une action -delete:

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

Une autre solution consiste à forcer xargs à traiter les commandes par lots. Par exemple, delete les fichiers 100 à la fois, cd dans le répertoire et exécutez ceci:

echo *.pdf | xargs -n 100 rm

18

Ou vous pouvez essayer:

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

vous pouvez essayer ceci:

for f in *.pdf
do
  rm $f
done

EDIT: ThiefMaster me suggère de ne pas divulguer une telle pratique dangereuse aux jeunes jedis de Shell, je vais donc ajouter une version plus "sûre" (pour préserver les choses lorsque quelqu'un a un "-rf. .Pdf" fichier)

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

Après avoir exécuté ce qui précède, ouvrez simplement le fichier /tmp/dummy.sh dans votre fav. éditeur et vérifiez chaque ligne pour les noms de fichiers dangereux, en les commentant s’ils le trouvent.

Copiez ensuite le script dummy.sh dans votre répertoire de travail et exécutez-le.

Tout cela pour des raisons de sécurité.

8
BigMike

Si vous essayez de supprimer un très grand nombre de fichiers à la fois (j'ai supprimé un répertoire de 485 000+ aujourd'hui), vous rencontrerez probablement cette erreur:

/bin/rm: Argument list too long.

Le problème est que lorsque vous tapez quelque chose comme rm -rf *, le * est remplacé par une liste de tous les fichiers correspondants, comme "rm -rf fichier1 fichier2 fichier3 fichier4", etc. Le tampon de mémoire alloué au stockage de cette liste d'arguments est relativement petit et s'il est rempli, le shell n'exécutera pas le programme.

Pour résoudre ce problème, beaucoup de gens vont utiliser la commande find pour trouver tous les fichiers et les passer un par un à la commande “rm” comme ceci:

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

Mon problème est que je devais supprimer 500 000 fichiers et que cela prenait trop de temps.

Je suis tombé sur un moyen beaucoup plus rapide de supprimer des fichiers - la commande "find" a un indicateur "-delete" intégré! Voici ce que j’ai fini par utiliser:

find . -type f -delete

En utilisant cette méthode, je supprimais des fichiers à un taux d’environ 2000 fichiers/seconde - beaucoup plus rapidement!

Vous pouvez également afficher les noms de fichiers au fur et à mesure que vous les supprimez:

find . -type f -print -delete

… Ou même montrer combien de fichiers seront supprimés, puis le temps qu'il faut pour les supprimer:

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

Vous pouvez utiliser un tableau bash:

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

De cette façon, il supprimera des lots de 1 000 fichiers par étape.

5
danjperron

vous pouvez utiliser cette recommandation 

find -name "*.pdf"  -delete
4
Sarath Ak

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

3
pigletfly

La commande rm comporte une limite de fichiers que vous pouvez supprimer simultanément.

Une possibilité consiste à les supprimer en utilisant plusieurs fois la commande rm sur la base de vos modèles de fichiers, comme:

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

Vous pouvez également les supprimer via find command:

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

S'il s'agit de noms de fichiers avec des espaces ou des caractères spéciaux, utilisez:

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

Cette phrase recherche dans tous les fichiers du répertoire en cours (-maxdepth 1) avec l'extension pdf (-name '* .pdf'), puis supprime chacun (-exec rm "{}").

L'expression {} remplace le nom du fichier et "{}" définit le nom du fichier sous forme de chaîne, y compris les espaces ou les caractères spéciaux.

je faisais face au même problème lors de la copie du répertoire source du formulaire vers la destination

le répertoire source avait des fichiers ~ 3 lakcs

j'ai utilisé cp avec l'option -r et cela a fonctionné pour moi

cp -r abc/def/

il copiera tous les fichiers d'abc à def sans donner d'avertissement trop long de la liste d'arguments

2
user3405020

Et un autre:

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

printf est un shell intégré et, autant que je sache, il a toujours été comme tel. Maintenant, étant donné que printf n'est pas une commande Shell (mais intégrée), elle n'est pas sujette à l'erreur fatale "argument list too long ...".

Ainsi, nous pouvons l’utiliser en toute sécurité avec les modèles globaux de shell tels que *.[Pp][Dd][Ff], puis nous dirigeons sa sortie vers la commande remove (rm), via xargs, ce qui permet de s’assurer qu’il contient suffisamment de noms de fichier dans la ligne de commande pour ne pas faire échouer la commande rm qui est une commande shell.

Le \0 dans printf sert de séparateur null pour les noms de fichiers qui sont ensuite traités par la commande xargs, en utilisant (-0) comme séparateur. Par conséquent, rm n'échoue pas lorsqu'il existe des espaces blancs ou d'autres caractères spéciaux dans les noms de fichier. 

1
lind

Je suis surpris qu'il n'y ait pas de réponses ulimit ici. Chaque fois que j'ai ce problème, je finis par ici ou ici . Je comprends que cette solution a des limites, mais ulimit -s 65536 semble faire souvent l'affaire pour moi.

1
dps

J'ai rencontré ce problème à quelques reprises. La plupart des solutions exécutent la commande rm pour chaque fichier à supprimer. C'est très inefficace:

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

J'ai fini par écrire un script python pour supprimer les fichiers en fonction des 4 premiers caractères du nom de fichier:

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')

Cela a très bien fonctionné pour moi. J'ai pu effacer plus de 2 millions de fichiers temporaires dans un dossier en environ 15 minutes. J'ai commenté le code dans le code afin que quiconque ayant une connaissance minimale ou inexistant de python puisse manipuler ce code.

1
Pedro Montero

Vous pouvez créer un dossier temporaire, déplacer tous les fichiers et sous-dossiers que vous souhaitez conserver dans le dossier temporaire, puis supprimer l'ancien dossier et renommer le dossier temporaire en ancien dossier. Essayez cet exemple jusqu'à ce que vous soyez sûr de le faire en direct:

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

le rm -r big_folder supprimera tous les fichiers du big_folder, peu importe le nombre. Vous devez simplement faire très attention à ce que vous ayez tous les fichiers/dossiers que vous souhaitez conserver, dans ce cas, c'était file1.pdf 

0
Keithhn

Essayez également ceci Si vous souhaitez supprimer des fichiers/dossiers supérieurs à 30/90 jours (+) ou inférieurs à 30/90 jours (-), vous pouvez utiliser les commandes ex ci-dessous.

Ex: Pour 90days exclut ci-dessus après 90 jours de suppression de fichiers/dossiers, cela signifie 91,92 .... 100 jours 

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

Ex: Pour ne supprimer que les derniers fichiers de 30 jours, utilisez la commande ci-dessous (-)

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

Si vous voulez conserver les fichiers pendant plus de 2 jours

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

Si vous voulez voir les fichiers/dossiers du dernier mois seulement . Ex:

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

Au-dessus de 30 jours, il n’ya plus que la liste des fichiers/dossiers Ex:

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

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

J'ai rencontré un problème similaire lorsqu'il y avait des millions de fichiers journaux inutiles créés par une application qui remplissait tous les inodes. J'ai eu recours à "localiser", j'ai récupéré tous les fichiers "situés" dans un fichier texte, puis je les ai supprimés un par un. Cela a pris du temps, mais j'ai fait le travail!

0
asatsi

Je connais seulement un moyen de contourner cela ... L'idée est d'exporter cette liste de fichiers pdf dans un fichier. Puis divisez ce fichier en plusieurs parties. Supprimez ensuite les fichiers pdf répertoriés dans chaque partie.

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

wc -l doit compter combien de lignes le fichier list.txt contient. Lorsque vous avez l’idée de sa durée, vous pouvez décider de la scinder en deux parties, ou autre. Utilisation de la commande split -l .__ Par exemple, divisez-la en 600 lignes chacune.

split -l 600 list.txt

cela créera quelques fichiers nommés xaa, xab, xac, etc., selon la façon dont vous l'avez scindé . Maintenant, pour "importer" chaque liste de ces fichiers dans la commande rm, utilisez ceci:

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

Désolé pour mon mauvais anglais.

0
user219776

J'ai trouvé que pour les listes de fichiers extrêmement volumineuses (> 1e6), ces réponses étaient trop lentes. Voici une solution utilisant le traitement parallèle en python. Je sais, je sais, ce n'est pas Linux ... mais rien d'autre ne fonctionne ici. 

(Cela m'a sauvé des heures)

# 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 __== '__main__':
    for file_name in files_names:
        print(file_name)
        pool.apply_async(delete_files,[file_name], error_callback=callback_error)
0
mmann1123

Pour supprimer tout *.pdf dans un répertoire /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/

Supprimer des fichiers spécifiques via rsync en utilisant un caractère générique est probablement la solution la plus rapide au cas où vous auriez des millions de fichiers. Et cela évitera les erreurs que vous obtenez.


(Étape facultative): DRY RUN. Pour vérifier ce qui sera supprimé sans supprimer. `

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

...

Cliquez trucs et astuces rsync pour plus de piratages rsync

0
Raman Kathpalia

Si vous devez conserver un serveur ou un système responsive lors de la suppression d'un grand nombre de fichiers, sleep entre chaque instruction de suppression peut constituer une bonne approche.

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