Argumentenlijst te lange fout voor rm-, cp-, mv-opdrachten

Ik heb enkele honderden PDF’s in een directory in UNIX. De namen van de PDF’s zijn erg lang (ongeveer 60 tekens).

Als ik probeer alle PDF’s samen te verwijderen met de volgende opdracht:

rm -f *.pdf

Ik krijg de volgende foutmelding:

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

Wat is de oplossing voor deze fout?
Treedt deze fout ook op voor de opdrachten mven cp? Zo ja, hoe deze commando’s op te lossen?


Antwoord 1, autoriteit 100%

De reden dat dit gebeurt, is omdat bash de asterisk uitbreidt naar elk overeenkomend bestand, waardoor een erg lange opdrachtregel ontstaat.

Probeer dit:

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

Waarschuwing:dit is een recursieve zoekopdracht en zal ook bestanden in submappen vinden (en verwijderen). Plak -falleen op het rm-commando als je zeker weet dat je geen bevestiging wilt.

U kunt het volgende doen om de opdracht niet-recursief te maken:

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

Een andere optie is om de -deletevlag van find te gebruiken:

find . -name "*.pdf" -delete

Antwoord 2, autoriteit 46%

tl;dr

Het is een kernelbeperking voor de grootte van het opdrachtregelargument. Gebruik in plaats daarvan een for-lus.

Oorsprong van het probleem

Dit is een systeemprobleem, gerelateerd aan execveen ARG_MAXconstante. Daar is veel documentatie over (zie man execve, wiki van debian).

Kortom, de uitbreiding produceert een opdracht(met zijn parameters) die de ARG_MAXlimiet overschrijdt.
Op kernel 2.6.23was de limiet ingesteld op 128 kB. Deze constante is verhoogd en u kunt de waarde ervan verkrijgen door het volgende uit te voeren:

getconf ARG_MAX
# 2097152 # on 3.5.0-40-generic

Oplossing: gebruik forLoop

Gebruik een for-lus zoals aanbevolen op BashFAQ/095en er is geen limiet behalve RAM/geheugenruimte:

Droog lopen om te controleren of het verwijdert wat u verwacht:

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

En voer het uit:

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

Dit is ook een draagbare benadering, aangezien glob sterk en consistent gedrag vertoont tussen shells (onderdeel van POSIX-specificatie).

Opmerking:Zoals opgemerkt door verschillende opmerkingen, is dit inderdaad langzamer maar beter te onderhouden omdat het complexere scenario’s kan aanpassen, bijv.waarbij men meer dan slechts één wil doen actie.

Oplossing: gebruik find

Als je erop staat, kun je findgebruiken, maar gebruik geen xargsomdat het “gevaarlijk is (kapot, exploiteerbaar, enz.) tijdens het lezen niet-NUL-gescheiden invoer”:

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

Door -maxdepth 1 ... -deletete gebruiken in plaats van -exec rm {} +kan findeenvoudig de vereiste systeemaanroepen uitvoeren zichzelf zonder een extern proces te gebruiken, dus sneller (dankzij @chepner opmerking).

Referenties


Antwoord 3, autoriteit 19%

findheeft een -deleteactie:

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

Antwoord 4, autoriteit 2%

Een ander antwoord is om xargste dwingen de commando’s in batches te verwerken. Bijvoorbeeld om deletede bestanden 100tegelijk, cdin de directory en voer dit uit:

echo *.pdf | xargs -n 100 rm


Antwoord 5

Of je kunt het proberen:

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

Antwoord 6

Als je een zeer groot aantal bestanden tegelijk probeert te verwijderen (ik heb vandaag een map met 485.000+ verwijderd), zul je waarschijnlijk deze fout tegenkomen:

/bin/rm: Argument list too long.

Het probleem is dat wanneer u iets typt als rm -rf *, de *wordt vervangen door een lijst van elk overeenkomend bestand, zoals “rm -rf file1 file2 file3 file4” enzovoort. Er is een relatief kleine geheugenbuffer toegewezen om deze lijst met argumenten op te slaan en als deze vol is, zal de shell het programma niet uitvoeren.

Om dit probleem te omzeilen, zullen veel mensen het find commando gebruiken om elk bestand te vinden en ze een voor een door te geven aan het “rm” commando als volgt:

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

Mijn probleem is dat ik 500.000 bestanden moest verwijderen en dat het veel te lang duurde.

Ik stuitte op een veel snellere manier om bestanden te verwijderen: het “find”-commando heeft een ingebouwde “-delete”-vlag! Dit is wat ik uiteindelijk heb gebruikt:

find . -type f -delete

Met deze methode verwijderde ik bestanden met een snelheid van ongeveer 2000 bestanden/seconde – veel sneller!

U kunt de bestandsnamen ook weergeven terwijl u ze verwijdert:

find . -type f -print -delete

…of zelfs laten zien hoeveel bestanden zullen worden verwijderd, en vervolgens bepalen hoe lang het duurt om ze te verwijderen:

[email protected]# ls -1 | wc -l && time find . -type f -delete
100000
real    0m3.660s
user    0m0.036s
sys     0m0.552s

Antwoord 7

u kunt dit proberen:

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

BEWERKEN:
ThiefMaster-commentaar stelt me voor om zulke gevaarlijke praktijken niet aan jonge shell’s jedi’s te onthullen, dus ik zal een meer “veiligere” versie toevoegen (om dingen te behouden wanneer iemand een “-rf …pdf” -bestand heeft)

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

Na het bovenstaande te hebben uitgevoerd, opent u gewoon het bestand /tmp/dummy.shin uw favoriete editor en controleert u elke regel op gevaarlijke bestandsnamen, en geeft u er commentaar op als ze worden gevonden.

Kopieer vervolgens het dummy.sh-script in uw werkmap en voer het uit.

Dit alles om veiligheidsredenen.


Antwoord 8

Je zou een bash-array kunnen gebruiken:

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

Op deze manier wordt het gewist in batches van 1000 bestanden per stap.


Antwoord 9

je kunt deze aanbeveling gebruiken

find -name "*.pdf"  -delete

Antwoord 10

De opdracht rmheeft een beperking van bestanden die u tegelijkertijd kunt verwijderen.

Een mogelijkheid om ze te verwijderen door meerdere keren de opdracht rmte baseren op uw bestandspatronen, zoals:

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

Je kunt ze ook verwijderen via de opdracht find:

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

Antwoord 11

Het verbaast me dat er hier geen ulimitantwoorden zijn. Elke keer dat ik dit probleem heb, beland ik hierof hier. Ik begrijp dat deze oplossing beperkingen heeft, maar ulimit -s 65536lijkt vaak de oplossing voor mij te zijn.


Antwoord 12

ik had hetzelfde probleem tijdens het kopiëren van de bronmap van het formulier naar de bestemming

bronmap had bestanden ~3 lakcs

ik gebruikte cp met optie -ren het werkte voor mij

cp -r abc/ def/

het kopieert alle bestanden van abc naar def zonder waarschuwing voor te lange argumentlijst


Antwoord 13

Als het bestandsnamen zijn met spaties of speciale tekens, gebruik dan:

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

Deze zin doorzoekt alle bestanden in de huidige map (-maxdepth 1) met de extensie pdf (-name ‘*.pdf’), en verwijder ze vervolgens allemaal (-exec rm “{}”).

De uitdrukking {} vervangt de naam van het bestand, en “{}” stelt de bestandsnaam in als tekenreeks, inclusief spaties of speciale tekens.


Antwoord 14

Probeer dit ook. Als u bestanden/mappen boven de 30/90 dagen (+) of anders onder de 30/90(-) dagen wilt verwijderen, kunt u de onderstaande ex-commando’s gebruiken

Bijvoorbeeld: voor 90 dagen exclusief bovenstaande na 90 dagen verwijderen van bestanden/mappen, betekent dit 91,92….100 dagen

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

Bijvoorbeeld: voor alleen bestanden van de laatste 30 dagen die u wilt verwijderen, gebruikt u de onderstaande opdracht (-)

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

Als je de bestanden langer dan 2 dagen wilt gebruiken

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

Als je alleen de bestanden/mappen van de afgelopen maand wilt zien.
Bijv.:

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

Alleen meer dan 30 dagen langer dan de bestanden/mappen vermelden
Bijv.:

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

Antwoord 15

En nog een:

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

printfis een ingebouwde shell, en voor zover ik weet is het altijd zo geweest. Aangezien printfgeen shell-commando is (maar ingebouwd), is het niet onderhevig aan de fatale fout “argument list too long ...“.

Dus we kunnen het veilig gebruiken met shell-globbing-patronen zoals *.[Pp][Dd][Ff], dan pijpen we de uitvoer om te verwijderen (rm) commando, via xargs, die ervoor zorgt dat er genoeg bestandsnamen in de commandoregel passen om het rmcommando, dat een shell-commando is, niet te laten mislukken.

De \0in printfdient als een null-scheidingsteken voor de bestandsnamen die vervolgens worden verwerkt door de opdracht xargs, door het te gebruiken (-0) als scheidingsteken, zodat rmniet faalt als er spaties of andere speciale tekens in de bestandsnamen staan.


Antwoord 16

Ik had hetzelfde probleem met een map vol tijdelijke afbeeldingen die met de dag groeide en deze opdracht hielp me om de map te wissen

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

Het verschil met de andere commando’s is de mtime parameter die alleen bestanden ouder dan X dagen nodig heeft (in het voorbeeld 50 dagen)

Door dat meerdere keren te gebruiken, waarbij ik bij elke uitvoering het dagbereik afnam, kon ik alle onnodige bestanden verwijderen


Antwoord 17

Om alle *.pdfin een directory /path/to/dir_with_pdf_files/

te verwijderen

mkdir empty_dir        # Create temp empty dir
rsync -avh --delete --include '*.pdf' empty_dir/ /path/to/dir_with_pdf_files/

Het verwijderen van specifieke bestanden via rsyncmet een jokerteken is waarschijnlijk de snelste oplossing voor het geval je miljoenen bestanden hebt. En het zorgt voor de fout die je krijgt.


(Optionele stap): DRY RUN. Om te controleren wat wordt verwijderd zonder te verwijderen. `

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

.
.
.

Klik op rsync-tips en -trucsvoor meer rsync-hacks


Antwoord 18

Argumentenlijst te lang

Als titel van deze vraag voor cp, mven rm, maar het antwoord staat meestal voor rm.

Un*x-opdrachten

Lees aandachtig de man-pagina van het commando!

Voor cpen mvis er een -tschakelaar, voor target:

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

en

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

Script-manier

Er is een algemene oplossing die wordt gebruikt in script:

#!/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

Antwoord 19

Ik weet alleen een manier om dit te omzeilen.
Het idee is om die lijst met pdf-bestanden die je hebt naar een bestand te exporteren. Splits dat bestand vervolgens in verschillende delen. Verwijder vervolgens de pdf-bestanden die in elk deel worden vermeld.

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

wc -l is om te tellen hoeveel regels de list.txt bevat. Als je een idee hebt van hoe lang het is, kun je besluiten om het in tweeën te splitsen, door of zoiets. De opdracht split -l gebruiken
Splits het bijvoorbeeld in elk 600 regels.

split -l 600 list.txt

dit zal een paar bestanden maken met de namen xaa,xab,xac enzovoort, afhankelijk van hoe je het splitst.
Om nu elke lijst in dat bestand te “importeren” in command rm, gebruik je dit:

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

Sorry voor mijn slechte Engels.


Antwoord 20

Ik ben dit probleem een paar keer tegengekomen. Veel van de oplossingen voeren de opdracht rmuit voor elk afzonderlijk bestand dat moet worden verwijderd. Dit is erg inefficiënt:

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

Ik heb uiteindelijk een python-script geschreven om de bestanden te verwijderen op basis van de eerste 4 tekens in de bestandsnaam:

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

Dit werkte heel goed voor mij. Ik was in staat om in ongeveer 15 minuten meer dan 2 miljoen tijdelijke bestanden in een map te wissen. Ik heb de tar uit het kleine stukje code gecommentarieerd, zodat iedereen met minimale tot geen kennis van python deze code kan manipuleren.


Antwoord 21

U kunt een tijdelijke map maken, alle bestanden en submappen die u wilt behouden naar de tijdelijke map verplaatsen en vervolgens de oude map verwijderen en de tijdelijke map hernoemen naar de oude map. Probeer dit voorbeeld totdat u zeker weet dat u het kunt doen live:

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

de rm -r big_folderzal alle bestanden in de big_folderverwijderen, ongeacht hoeveel. Je moet alleen heel voorzichtig zijn dat je eerst alle bestanden/mappen hebt die je wilt bewaren, in dit geval was het file1.pdf


Antwoord 22

Ik ontdekte dat voor extreem grote lijsten met bestanden (>1e6) deze antwoorden te traag waren. Hier is een oplossing met parallelle verwerking in python. Ik weet het, ik weet het, dit is geen Linux… maar niets anders hier werkte.

(Dit heeft me uren bespaard)

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

Antwoord 23

Ik heb een soortgelijk probleem gehad toen er miljoenen nutteloze logbestanden werden gemaakt door een applicatie die alle inodes vulde. Ik nam mijn toevlucht tot “lokaliseren”, kreeg alle bestanden “gelokaliseerd” in een tekstbestand en verwijderde ze vervolgens een voor een. Het heeft even geduurd, maar het is gelukt!


Antwoord 24

Hoe zit het met een kortere en betrouwbaardere?

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

Antwoord 25

Als je zowel bestanden als mappen wilt verwijderen, kun je zoiets gebruiken als:

echo /path/* | xargs rm -rf

Antwoord 26

Een iets veiligere versie dan het gebruik van xargs, ook niet recursief:

ls -p | grep -v '/$' | grep '\.pdf$' | while read file; do rm "$file"; done

Het is een beetje onnodig om onze mappen hier te filteren omdat ‘rm’ het toch niet verwijdert, en het kan voor de eenvoud worden verwijderd, maar waarom iets uitvoeren dat zeker een fout zal opleveren?


Antwoord 27

GNU parallel gebruiken (sudo apt install parallel) is supereenvoudig

Het voert de opdrachten multithreaded uit waarbij ‘{}’ het doorgegeven argument is

Bijvoorbeeld

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


Antwoord 28

Voor het verwijderen van de eerste 100 bestanden:

rm -rf ‘ls | hoofd -100’


Antwoord 29

De onderstaande optie lijkt eenvoudig voor dit probleem. Ik heb deze info van een ander draadje, maar het heeft me geholpen.

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

Voer gewoon het bovenstaande commando uit en het zal de taak uitvoeren.

Other episodes