Schneller Weg, um Dateien mit weniger als x Zeilen zu löschen


10

Was ist eine schnelle und nicht zu komplizierte Methode, um alle Dateien in einem Verzeichnis, die weniger als x Zeilen lang sind, in Bash zu löschen?

Antworten:


10

Hier ist eine POSIX-Lösung, die ziemlich einfach zu verstehen sein sollte:

find . -type f -exec awk -v x=10 'NR==x{exit 1}' {} \; -exec echo rm -f {} \;

Entfernen Sie wie in Stephanes Antwort das , echowenn Sie mit dem, was entfernt werden soll, zufrieden sind.


Erklärungen für diejenigen, die für Unix / Linux völlig neu sind:

Der Punkt .repräsentiert das aktuelle Verzeichnis. findfindet Dateien und Verzeichnisse rekursiv darin .und kann Dinge damit tun.

-typeeines von find‚s Vorwahlen ; Es ist ein Test, der für jede Datei und jedes Verzeichnis durchgeführt wird, die rekursiv (innerhalb .) gefunden werden, und der Rest der Primärdaten in der Zeile wird nur ausgewertet, wenn dies zu "true" führt.

In diesem speziellen Fall fahren wir nur fort, wenn es sich um eine reguläre Datei handelt , nicht um ein Verzeichnis oder etwas anderes (z. B. ein Blockgerät).


Der -execprimäre (von find) ruft einen externen Befehl auf und fährt nur mit dem nächsten primären Befehl fort, wenn der externe Befehl erfolgreich beendet wurde (Beendigungsstatus "0"). Das {}wird durch den Dateinamen ersetzt, der vom findBefehl "berücksichtigt" wird . Der erste -execAufruf entspricht also dem folgenden Shell-Befehl, der nacheinander für jede Datei ausgeführt wird:

awk -v x=10 'NR==x{exit 1}' ./somefilename

Awk ist eine ganze Sprache für sich, die für den Umgang mit begrenzten Textdateien wie CSVs entwickelt wurde. Die Awk-Bedingungen und -Befehle (die zwischen einfachen Anführungszeichen stehen und mit den Buchstaben beginnen NR) werden für jede Zeile einer Textdatei ausgeführt. (Implizite Schleife.)

Um Awk vollständig zu lernen, empfehle ich das Grymoire-Tutorial , aber ich werde die Awk-Funktionen erläutern, die im obigen Befehl verwendet werden.


Das -vFlag für Awk ermöglicht es uns, eine Awk-Variable (einmal) zu setzen, bevor die Awk-Befehle ausgeführt werden (für jede Zeile der Datei). In diesem Fall setzen wir xauf 10.


NRist eine spezielle Variable Awk auf den Bezug „ N umber der aktuellen R ECORD.“ Mit anderen Worten, es ist die Zeilennummer, die wir in einem bestimmten Durchgang durch die Schleife betrachten.

(Beachten Sie, dass es ist möglich, wenn auch ungewöhnlich, einen anderen „zu verwenden , R ecord S eparator“ als die Standardeinstellung eines Newline - Zeichen, durch Einstellung RS. Hier ist ein Beispiel mit einem Rekord Separatoren zu spielen. )


Awk-Skripte bestehen im Allgemeinen aus Bedingungen (äußere geschweifte Klammern) in Kombination mit Aktionen (innere geschweifte Klammern). Es kann zusammengesetzte Bedingungen und zusammengesetzte Aktionen geben, und es gibt eine Standardbedingung (true) und eine Standardaktion (print), die wir jedoch nicht benötigen kümmere dich nicht darum.

Die Bedingung hier ist: "Ist dies die 10. Zeile?" Wenn dies der Fall ist, beenden wir mit einem Exit-Status ungleich Null, was in Shell-Skripten "erfolglose Befehlsbeendigung" bedeutet.

Daher kann dieser Awk-Befehl nur dann erfolgreich beendet werden, wenn das Ende der Datei erreicht ist, bevor die 10. Zeile erreicht ist.

Wenn das Awk-Skript erfolgreich beendet wird, bedeutet dies, dass Sie eine Datei mit weniger als zehn Zeilen haben.


Der nächste -execAufruf (wenn Sie das entfernen echo) entfernt jede Datei (die bei der Auswertung der findPrimärdaten so weit kommt), indem Folgendes ausgeführt wird:

rm -f ./somefilename

5

Angenommen, eine findImplementierung, die das -readablePrädikat unterstützt (wenn Sie findes nicht unterstützen, entfernen Sie es einfach, Sie erhalten nur Fehlermeldungen für nicht lesbare Dateien oder ersetzen durch -exec test -r {} \;):

x=10 find . -type f -readable -exec sh -c '
  for file do
    lines=$(wc -l < "$file") && [ "$((lines))" -lt "$x" ] && echo rm -f "$file"
  done' sh {} +

Entfernen Sie die, echowenn glücklich.

Dies ist nicht besonders effizient, da alle Zeilen in jeder Datei gezählt werden, während nur die xdritte angehalten werden muss und für jede Datei ein wc(und möglicherweise ein rm) Befehl ausgeführt wird.

Mit GNU awkkönnen Sie es viel effizienter machen mit:

x=10
find . -type f -readable -exec awk -v x="$x" -v ORS='\0' '
  FNR == x {nextfile}
  ENDFILE {if (FNR < x) print FILENAME}' {} +|
  xargs -r0 echo rm -f

(wieder entfernen, echowenn glücklich).

Das gleiche mit perl:

x=10 find . -type f -readable -exec perl -Tlne '
  if ($. == $ENV{x}) {close ARGV}
  elsif (eof) {print $ARGV; close ARGV}' {} +

Ersetzen Sie printdurch, unlinkwenn Sie glücklich sind.


1. Wofür ist der letzte sh? 2. Ist wc -l < "$file"schneller als wc -l "$file"? 3. Woher kennt sh den Wert von $x, der in der aufrufenden Bash-Shell definiert ist?

3
@tomas, das Letzte shist, was in diesem Inline-Skript steht $0, um zum Beispiel für Fehlermeldungen verwendet zu werden. wc -l "$file"würde den Dateinamen drucken, den wir hier nicht wollen, und würde wcauch dann ausgeführt , wenn die Datei nicht geöffnet werden kann. $xwird nach find( x=10 find...) exportiert, das es selbst an weitergibt sh.
Stéphane Chazelas

Vielen Dank! Aber ich denke, dieser Fehler, den ich unter OSX bekomme, bedeutet, dass meine Bash-Version das Flag -readable nicht unterstützt? find: -readable: unknown primary or operator.
Durrrutti

1
@durrrutti, das liegt nicht daran bash. bashist nur ein Befehlszeileninterpreter, aber der findImplementierung. -readableist eine GNU-Erweiterung, die in OS / X nicht verfügbar ist find. Es wird nur verwendet, um sich auf die lesbaren Dateien zu beschränken (Sie könnten die Zeilenanzahl für nicht lesbare Dateien nicht ermitteln). Sie können es für das erste weglassen. Beim Öffnen der Dateien wcfür die nicht lesbaren Dateien werden dann nur Fehlermeldungen angezeigt.
Stéphane Chazelas

@ StéphaneChazelas, diese Antwort ist so knifflig, dass ich mich frage: Habe ich mit meiner Antwort irgendwelche Randfälle verpasst? :)
Wildcard

2

Der Vollständigkeit halber können Sie neben AWK auch GNU sed verwenden, um das gleiche Ergebnis zu erzielen:

find . -type f -exec sed 11q1 '{}' ';' -exec echo rm -f '{}' ';'

Dies führt zu einer etwas präziseren Befehlszeile.

Erläuterung

11 - is the address, i.e. "the eleventh line"
q - is for _q_uit (abort the execution)
1 - is the exit code parameter for q (GNU sed extension) 
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.