Ich habe ein paar tausend Dateien im Format Dateiname.12345.end. Ich möchte nur jede 12. Datei behalten, also file.00012.end, file.00024.end ... file.99996.end und alles andere löschen.
Die Dateien können auch Nummern in ihrem Dateinamen haben und haben normalerweise die folgende Form: file.00064.name.99999.end
Ich verwende die Bash-Shell und kann nicht herausfinden, wie man die Dateien durchläuft, und dann die Nummer herausholt und prüfe, ob number%%12=0
die Datei gelöscht wird, wenn nicht. Kann mir jemand helfen?
Vielen Dank, Dorina
Antworten:
Hier ist eine Perl-Lösung. Dies sollte für Tausende von Dateien viel schneller sein:
Was kann weiter kondensiert werden in:
Wenn Sie zu viele Dateien haben und das einfache nicht verwenden können
*
, können Sie Folgendes tun:Im Folgenden finden Sie einen Vergleich dieses Ansatzes mit dem in einer der anderen Antworten angegebenen Ansatz:
Wie Sie sehen, ist der Unterschied erwartungsgemäß enorm .
Erläuterung
-e
wird lediglich empfohlenperl
, das in der Befehlszeile angegebene Skript auszuführen.@ARGV
ist eine spezielle Variable, die alle Argumente für das Skript enthält. Da wir es geben*
, enthält es alle Dateien (und Verzeichnisse) im aktuellen Verzeichnis.Das
grep
durchsucht die Liste der Dateinamen und sucht nach allen, die mit einer Folge von Zahlen, einem Punkt undend
( übereinstimmen/(\d+)\.end/)
.Da sich die Nummern (
\d
) in einer Erfassungsgruppe (Klammern) befinden, werden sie als gespeichert$1
. Also dasgrep
wird dann prüfen , ob diese Zahl ein Vielfaches von 12 ist und, wenn dies nicht der Fall, wird der Dateiname zurückgegeben werden. Mit anderen Worten, das Array@bad
enthält die Liste der zu löschenden Dateien.Die Liste wird dann übergeben, um
unlink()
Dateien (aber nicht Verzeichnisse) zu entfernen.quelle
Da Ihre Dateinamen im Format vorliegen
file.00064.name.99999.end
, müssen wir zuerst alles außer unserer Nummer entfernen. Wir werden einefor
Schleife verwenden, um dies zu tun.Wir müssen der Bash-Shell auch mitteilen, dass sie die Basis 10 verwenden soll, da die Bash-Arithmetik sie mit einer 0 beginnende Zahlen als Basis 8 behandelt, was die Sache für uns durcheinander bringt.
Als Skript, das im Verzeichnis mit den Dateien gestartet werden soll, verwenden Sie:
Oder Sie können diesen sehr langen, hässlichen Befehl verwenden, um dasselbe zu tun:
So erklären Sie alle Teile:
for f in ./*
bedeutet für alles im aktuellen Verzeichnis, mache .... Dies setzt jede gefundene Datei oder jedes Verzeichnis als Variable $ f.if [[ -f "$f" ]]
prüft, ob es sich bei dem gefundenen Element um eine Datei handelt. Wenn dies nichtecho "$f is not...
der Fall ist, springen wir zum Teil, was bedeutet, dass wir nicht versehentlich mit dem Löschen von Verzeichnissen beginnen.file="${f%.*}"
Legt die Variable $ file als Dateinamen fest und schneidet ab, was nach dem letzten kommt.
.if [[ $((10#${file##*.} % 12)) -eq 0 ]]
Hier setzt die Hauptarithmetik an. Das${file##*.}
schneidet alles vor dem letzten.
in unserem Dateinamen ohne Erweiterung.$(( $num % $num2 ))
ist die Syntax für die Bash-Arithmetik zur Verwendung der Modulo-Operation, die10#
Bash am Anfang anweist, die Basis 10 zu verwenden, um mit diesen nervigen führenden Nullen umzugehen.$((10#${file##*.} % 12))
Dann lässt uns der Rest unserer Dateinamen durch 12 geteilt.-ne 0
Überprüft, ob der Rest "ungleich" zu Null ist.rm
Befehl, können Sie ersetzenrm
mit ,echo
wenn zuerst die ausgeführt wird , um zu überprüfen , dass Sie die erwarteten Dateien erhalten zu löschen.Diese Lösung ist nicht rekursiv, dh, es werden nur Dateien im aktuellen Verzeichnis verarbeitet und keine Unterverzeichnisse angelegt.
Die
if
Anweisung mit demecho
Befehl zum Warnen vor Verzeichnissen ist nicht unbedingt erforderlich, darm
Verzeichnisse von sich aus beanstandet und nicht gelöscht werden.Oder
Funktioniert auch einwandfrei.
quelle
rm
Ein paar tausend Anrufe können sehr langsam sein. Ich schlage vor , umecho
die Dateinamen statt und Rohr den Ausgang der Schleifexargs rm
(Add - Optionen je nach Bedarf):for f in *; do if ... ; then echo "$f"; fi; done | xargs -rd '\n' -- rm --
.xargs
Version 5 Minuten und 1 Sekunde. Könnte dies am Overhead vonecho
@DavidFoerster liegen?time { for f in *; do echo "$f"; done | xargs rm; }
vs. 1m11.450s / 0m10.695s / 0m16.800s mittime { for f in *; do rm "$f"; done; }
einem tmpfs. Bash ist v4.3.11, Kernel ist v4.4.19.Sie können die Bash-Klammer-Erweiterung verwenden, um Namen zu generieren, die jede 12. Zahl enthalten. Lassen Sie uns einige Testdaten erstellen
Dann können wir folgendes verwenden
Funktioniert hoffnungslos langsam bei einer großen Anzahl von Dateien - es braucht Zeit und Speicher, um Tausende von Namen zu generieren - es ist also eher ein Trick als eine effektive Lösung.
quelle
Ein bisschen lang, aber es ist mir eingefallen.
Erläuterung: Löschen Sie jede 12. Datei elf Mal.
quelle
Trotz aller Bescheidenheit finde ich diese Lösung viel netter als die andere Antwort:
Eine kleine Erklärung: Zuerst generieren wir eine Liste von Dateien mit
find
. Wir erhalten alle Dateien, deren Name mit endet.end
1 und die eine Tiefe von 1 haben (das heißt, sie befinden sich direkt im Arbeitsverzeichnis und nicht in einem Unterordner. Sie können dies weglassen, wenn es keine Unterordner gibt). Die Ausgabeliste wird alphabetisch sortiert.Dann leiten wir diese Liste in
awk
, wo wir die spezielle Variable verwenden,NR
die die Zeilennummer ist. Wir lassen jede 12. Datei aus, indem wir die Dateien dort ausdrucken, wo sie sindNR%12 != 0
. Derawk
Befehl kann auf abgekürzt werdenawk 'NR%12'
, da das Ergebnis des Modulo-Operators als boolescher Wert interpretiert wird und der{print}
implizit trotzdem erfolgt.Jetzt haben wir eine Liste der Dateien, die gelöscht werden müssen, was wir mit xargs und rm tun können.
xargs
Führt den angegebenen Befehl aus (rm
) mit der Standardeingabe als Argument aus.Wenn Sie über viele Dateien verfügen, erhalten Sie eine Fehlermeldung mit der Aufschrift "Argumentliste zu lang" (auf meinem Computer ist das Limit 256 kB, und POSIX benötigt mindestens 4096 Byte). Dies kann durch das
-n 100
Flag vermieden werden , das die Argumente alle 100 Wörter aufteilt (keine Zeilen, was zu beachten ist, wenn Ihre Dateinamen Leerzeichen enthalten) und einen separatenrm
Befehl mit jeweils nur 100 Argumenten ausführt .quelle
-depth
muss vorher sein-name
; ii) dies schlägt fehl, wenn einer der Dateinamen Leerzeichen enthält; iii) Sie gehen davon aus, dass die Dateien in aufsteigender numerischer Reihenfolge aufgelistet werden (das ist es, worauf Sieawk
testen), aber dies wird mit ziemlicher Sicherheit nicht der Fall sein. Dies löscht daher einen zufälligen Satz von Dateien.-depth
. Dies war jedoch das geringste Problem. Das wichtigste ist, dass Sie eine zufällige Gruppe von Dateien löschen und nicht die, die das OP haben möchte.-depth
nimmt keinen Wert an und tut das Gegenteil von dem, was Sie denken, dass es tut. Sieheman find
: "-depth Verarbeitet den Inhalt jedes Verzeichnisses vor dem Verzeichnis selbst." Dies wird also tatsächlich in Unterverzeichnisse absteigen und überall Chaos anrichten.-depth n
und-maxdepth n
existiert. Ersteres setzt voraus, dass die Tiefe genau n ist, und letzteres kann <= n sein. II). Ja, das ist schlecht, aber für dieses Beispiel ist es kein Problem. Sie können das Problem beheben, indem Siefind ... -print0 | awk 'BEGIN {RS="\0"}; NR%12 != 0' | xargs -0 -n100 rm
das Null-Byte als Datensatztrennzeichen verwenden (was in Dateinamen nicht zulässig ist). III) Auch in diesem Fall ist die Annahme angemessen. Andernfalls könnten Sie einsort -n
zwischenfind
und einfügenawk
oderfind
zu einer Datei umleiten und sie sortieren, wie Sie möchten.find
. Das Hauptproblem ist jedoch, dass Sie davon ausgehen, dassfind
eine sortierte Liste zurückgegeben wird. Das tut es nicht.Um nur bash zu verwenden, würde mein erster Ansatz darin bestehen, 1. alle Dateien, die Sie behalten möchten, in ein anderes Verzeichnis zu verschieben (dh alle Dateien, deren Dateiname ein Vielfaches von 12 ist) und 2. alle verbleibenden Dateien im Verzeichnis zu löschen, 3. Setzen Sie dann die 12er-Dateien, die Sie behalten haben, an den Ort, an dem sie sich befanden. So etwas könnte funktionieren:
quelle
filename
Teil, wenn es nicht konsistent ist?