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 mv
en 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 -f
alleen 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 -delete
vlag 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 execve
en ARG_MAX
constante. Daar is veel documentatie over (zie man execve, wiki van debian).
Kortom, de uitbreiding produceert een opdracht(met zijn parameters) die de ARG_MAX
limiet overschrijdt.
Op kernel 2.6.23
was 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 for
Loop
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 find
gebruiken, 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 ... -delete
te gebruiken in plaats van -exec rm {} +
kan find
eenvoudig de vereiste systeemaanroepen uitvoeren zichzelf zonder een extern proces te gebruiken, dus sneller (dankzij @chepner opmerking).
Referenties
- Ik krijg “Argumentenlijst te lang”. Hoe kan ik een grote lijst in brokken verwerken?@ wooledge
- execve(2) – Linux-manpagina(zoek naar ARG_MAX) ;
- Fout: Argumentenlijst te lang@ Debian’s wiki ;
- Waarom krijg ik “ /bin/sh: Argumentenlijst te lang” bij het doorgeven van geciteerde argumenten?@ SuperUser
Antwoord 3, autoriteit 19%
find
heeft een -delete
actie:
find . -maxdepth 1 -name '*.pdf' -delete
Antwoord 4, autoriteit 2%
Een ander antwoord is om xargs
te dwingen de commando’s in batches te verwerken. Bijvoorbeeld om delete
de bestanden 100
tegelijk, cd
in 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:
root@devel# 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.sh
in 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 ulimit
antwoorden zijn. Elke keer dat ik dit probleem heb, beland ik hierof hier. Ik begrijp dat deze oplossing beperkingen heeft, maar ulimit -s 65536
lijkt 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
printf
is een ingebouwde shell, en voor zover ik weet is het altijd zo geweest. Aangezien printf
geen 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 rm
commando, dat een shell-commando is, niet te laten mislukken.
De \0
in printf
dient als een null-scheidingsteken voor de bestandsnamen die vervolgens worden verwerkt door de opdracht xargs
, door het te gebruiken (-0
) als scheidingsteken, zodat rm
niet 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 *.pdf
in 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 rsync
met 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
, mv
en rm
, maar het antwoord staat meestal voor rm
.
Un*x-opdrachten
Lees aandachtig de man-pagina van het commando!
Voor cp
en mv
is er een -t
schakelaar, 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 bashscript:
#!/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 rm
uit 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_folder
zal alle bestanden in de big_folder
verwijderen, 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.