Sie schreiben, dass Sie "mit Dateien arbeiten und in Verzeichnisse gehen wollen", so dass Sie direkt zu einer ls
Lösung springen können, da Ihre Lösung möglicherweise verfrüht ist. Es ist hilfreich, genau zu wissen, was Sie unter "Arbeiten mit Dateien und in Verzeichnissen" verstehen, um die beste Lösung zu erhalten.
Hier sind ein paar Beispiele für häufige Anwendungsfälle:
Einzelne Dateien rekursiv bearbeiten
Angenommen, Sie möchten für jede Datei etwas tun, das einem bestimmten Kriterium entspricht, das im aktuellen Verzeichnis beginnt und in jedem Unterverzeichnis fortgesetzt wird.
Zum Beispiel: Ermitteln Sie die Zeilenzahl jeder Datei mit einer .txt
Erweiterung. Der Befehl zum Abrufen einer Zeilenzahl einer einzelnen Datei lautet wc -l $filename
. (Wenn Sie mehrere Dateinamen angeben, wird die Zeilenzahl jedes einzelnen gefolgt von der Summe ausgegeben.)
So lösen Sie das Problem mit einer Datei - das ist immer die erste Frage, die Sie beantworten müssen, bevor Sie fortfahren können -, aber wie wird dies rekursiv für alle Dateien durchgeführt? Dieser Teil des Problems wird mit dem find
Befehl Unix-Befehl für Directory Traversal gelöst .
find
Es kann schwierig sein, den Befehl im Detail zu lernen, aber für einfache Fälle wie diesen ist es ziemlich einfach. Das erste, was Sie wissen sollten, ist, dass jeder find
Befehl das folgende Format hat:
find DIR [PREDICATE, ..]
DIR
ist das Startverzeichnis (in diesem Beispiel .
ist dies immer das aktuelle Arbeitsverzeichnis). A PREDICATE
ist ein Ausdruck, der find
verwendet wird, um entweder zu entscheiden, was als nächstes bei der Betrachtung einer Datei oder eines Verzeichnisses zu tun ist, oder um etwas mit dieser Datei oder diesem Verzeichnis zu tun .
Der grundlegende Algorithmus find
lautet wie folgt: Versuchen Sie das erste Prädikat (ganz links in der Befehlszeile) des aktuell untersuchten Elements (Datei oder Verzeichnis). Wenn das Prädikat wahr ist, versuchen Sie es mit dem nächsten Prädikat in der Befehlszeile. Fahren Sie fort, bis alle angegebenen Prädikate ausprobiert wurden. Wenn ein Prädikat falsch ist, beenden Sie die Arbeit mit diesem Element und beginnen Sie erneut mit dem nächsten Element (beginnend mit dem ersten Prädikat).
Wenn es sich bei dem untersuchten Element um ein Verzeichnis handelt, werden find
die Elemente innerhalb des Verzeichnisses fortgesetzt, sobald das letzte Vergleichselement erreicht wurde oder ein Vergleichselement falsch ist . Es gibt zwei Hauptausnahmen:
- Das
-prune
Prädikat kann verwendet werden, um dies selektiv zu deaktivieren. wenn das -prune
Prädikat erreicht ist und das aktuelle Element ein Verzeichnis ist, oder Die -maxdepth=N
Option (kein Prädikat, das zuvor DIR
in der Befehlszeile angezeigt wird) kann verwendet werden, um find
die Suchtiefe einzuschränken. Wenn das aktuelle Verzeichnis N
oder mehr Ebenen tiefer als das Startverzeichnis ist,
In beiden Fällen werden dann der Inhalt des Verzeichnisses (und dessen Inhalt rekursiv) nicht untersucht, und das nächste Element ist das gleiche, als wäre das aktuelle Element eine Datei und nicht ein Verzeichnis.
Apropos: Wenn es sich bei dem zu untersuchenden Objekt um eine Datei handelt, ist das nächste Element der nächste Eintrag in demselben Verzeichnis. Wenn sich im Verzeichnis keine Objekte befinden, wird das aktuelle Verzeichnis aus dem Ordner "geknackt" und die Verarbeitung wird fortgesetzt wobei das nächste Element das ist, was das nächste Element gewesen wäre, als das Verzeichnis eingegeben wurde.
Was bedeutet "Bearbeitung eines Artikels"? Dies bedeutet, dass jedes Prädikat von links nach rechts in der Befehlszeile versucht wird, bis eines falsch ist oder alle versucht wurden.
(An dieser Stelle gibt es eine Divergenz zwischen einigen unterschiedlichen Versionen find
. In vielen neueren, wie die Version auf Linux gefunden, wenn das letzte Prädikat wahr ist und war keine „Action“ Prädikat, dann wird find
davon ausgegangen Sie tun bedeutete etwas, so Es wirkt so, als würde das -print
Prädikat angegeben, um den Pfadnamen auszudrucken. In älteren Versionen von find
war dies nicht der Fall, und das Ergebnis der Verarbeitung eines solchen Elements wäre gleich Null.
Zur Veranschaulichung: Der einfachste Befehl find .
ohne Prädikate. Bei den neueren Varianten von find
, führt dies zu einer Liste aller Pfadnamen, die im aktuellen Verzeichnis beginnen und rekursiv ablaufen, bis alle gedruckt wurden. Bei den älteren Varianten von find
wird derselbe Befehl genauso lange dauern (er muss alle Dateien rekursiv mit den - in diesem Fall nicht vorhandenen - Prädikaten vergleichen), gibt aber absolut nichts aus .
Bevor ich das Thema der Verarbeitung von Prädikaten verlasse, möchte ich anmerken, dass meine Erklärung bisher dazu geführt hat, dass die einzige Möglichkeit für Prädikate darin besteht, diese logisch UND-Verknüpfung zu erstellen. Das stimmt nicht, weil
- Es gibt auch ein
-o
Prädikat, bei dem ODER zwei Prädikate sind (tatsächlich gibt es auch ein -a
AND-Prädikat, das jedoch selten benötigt wird, da dies, wie ich oben schrieb, das Standardverhalten ist). find
erlaubt die Verwendung von Klammern (die aufgrund von Shell-Escape-Regeln normalerweise geschrieben werden \(
und \)
), um mehrere Prädikate in einem Ausdruck zu gruppieren; und - Es gibt einen Negationsoperator, der normalerweise geschrieben wird
\!
.
Wenn das alles aus dem Weg ist, können wir jetzt auf die Frage zurückkommen, wie man die Zeilenzahl jeder Datei mit einem .txt
Suffix erhält :
- Wie bereits erwähnt, lautet der Befehl zum Abrufen der Zeilenzahl einer Datei
wc -l
. - Es gibt ein Prädikat für die Ausführung eines Befehls in der gerade untersuchten Datei
find
. Dies ist -exec CMD ;
, einschließlich des Semikolons (das bei Bedarf mit Escapezeichen versehen werden muss), und ersetzt im Text von CMD
jedes Vorkommen des Tokens {}
durch den Pfadnamen, der gerade untersucht wird. - Ein weiteres Prädikat läßt uns für das Suffix einer Datei überprüfen:
-name PATTERN
. In diesem Fall, in dem wir Dateien mit einer .txt
Erweiterung benötigen, verwenden wir *.txt
unser Muster.
Wenn wir all dies wissen, lautet der Befehl, den wir schreiben können:
find . -name '*.txt' -exec wc -l {} \;
(Wir verwenden sowohl Anführungszeichen *.txt
als auch einen Backslash vor dem Semikolon, um zu verhindern, dass die Shell diese Zeichen als Sonderzeichen interpretiert, find
damit sie sie sehen können.) Dadurch wird die Zeilenanzahl jeder so genannten Datei rekursiv geprüft.
Es gibt hier eine kleine Falte, die je nach Kontext ignoriert werden kann: Was wäre, wenn Sie ein Verzeichnis mit dem Namen etwas hätten, das auf endet .txt
? Sie erhalten so etwas wie das Folgende:
$ find . -name '*.txt' -exec wc -l {} \; 42 ./myfile.txt wc: ./foo.txt: Is a directory 0 ./foo.txt 1 ./foo.txt/bar.txt
Um dies zu beheben, müssen Sie ein weiteres Vergleichselement hinzufügen -type f
, um find
nur das -exec
Vergleichselement für Dateien auszuführen, die normale Textdateien sind:
$ find . -type f -name '*.txt' -exec wc -l {} \; 42 ./myfile.txt 1 ./foo.txt/bar.txt
(Sie fragen sich vielleicht, ob es wichtig ist, ob das -type f
vor oder nach dem -name '*.txt'
Prädikat erscheint. Dies ist jedoch nicht der Fall, da Verzeichnisse immer in absteigender Reihenfolge abgelegt werden, es sei denn, das -prune
oder -maxdepth
ist vorhanden, wie zuvor erwähnt.)
Beachten Sie, dass die oben ist möglich unter Verwendung ls
in Kombination mit erweiterten Funktionen der Bash oder Zsh Muscheln. Diese Lösungen sind jedoch viel schwieriger zu erklären und richtig zu stellen, daher gehe ich davon aus, dass Ihre Erwähnung ls
eine verfrühte Implementierung war. (Siehe das XY-Problem .)
Eine Liste von Dateien zusammenstellen und diese dann gemeinsam bearbeiten
Ich habe erwähnt, dass, wenn mehr als ein Dateiname angegeben wird, wc -l
die Anzahl der Dateien angegeben wird, gefolgt von einer Gesamtsumme. Die oben genannte Lösung hatte jedoch keine Gesamtsumme, da wc
sie für jede Datei einmal ausgeführt wurde *.txt
. Aber was wäre, wenn Sie diese große Summe wollten?
In diesem Fall könnten Sie verwenden ls
, aber Sie hätten ein Problem: Wenn einer Ihrer Dateinamen möglicherweise Leerzeichen oder andere Zeichen enthielt, die für die Shell spezifisch sind, könnten Sie einen Fehler erhalten oder sogar versehentlich einen Befehl ausführen, den Sie nicht meinen zu.
Also noch einmal, es ist besser sich an zu wenden find
. Neuere Versionen von find
(meistens dieselben, die ich zuvor erwähnt habe, würden Sie einfügen -print
, wenn Sie sie weglassen) haben ein Feature dafür: Verwenden Sie das -exec
Prädikat wie zuvor, aber beenden Sie nicht mit einem Semikolon, sondern mit einem Pluszeichen ( +
). So:
$ find . -type f -name '*.txt' -exec wc -l {} \+ 42 ./myfile.txt 1 ./foo.txt/bar.txt 43 total
Für diejenigen, find
die diese Funktion nicht haben, würden Sie sie find
zusammen mit einem anderen Programm verwenden xargs
. xargs
nimmt seine Eingabe und führt einen Befehl aus, wobei die Eingabe als Argument des Befehls angegeben wird. So würden wir es verwenden, um unseren ersten Befehl zu replizieren:
$ find . -type f -name '*.txt' -print | xargs wc -l 42 ./myfile.txt 1 ./foo.txt/bar.txt 43 total
Dieser Befehl hat jedoch immer noch ein Problem, wenn einer der Dateinamen ein Leerzeichen enthält:
$ ls My Spacey File.txt foo.txt myfile.txt rakudo-info.md $ find . -type f -name '*.txt' -print | xargs wc -l 42 ./myfile.txt wc: ./My: No such file or directory wc: Spacey: No such file or directory wc: File.txt: No such file or directory 1 ./foo.txt/bar.txt 43 total
In diesem Fall wc
sah jedes Wort des Dateinamens My Spacey File.txt als separates Argument. Um dies zu beheben, verwenden wir ein Feature find
und ein entsprechendes Feature xargs
, bei dem das Nullzeichen ( \0
das in Dateinamen unzulässig ist) anstelle von Zeilenumbrüchen als Trennzeichen verwendet wird:
$ find . -type f -name '*.txt' -print0 | xargs -0 wc -l 42 ./myfile.txt 1 ./My Spacey File.txt 1 ./foo.txt/bar.txt 44 total
Das -print0
Prädikat gibt find
an, seine Ausgabe durch Nullen getrennt zu senden. Die -0
Option von xargs
tut dasselbe für ihre Eingabe.
Ein letzter Vorbehalt
Wenn Sie über eine sehr große Anzahl von Dateien verfügen oder die Gesamtzahl der Zeichen aller Dateinamen insgesamt sehr groß ist, kann die Anzahl oder Größe der vom System zulässigen Argumente begrenzt sein. In diesem Fall teilen sowohl das -exec ... \+
Prädikat find
als xargs
auch die Liste auf und führen den Befehl mehrmals aus, sodass jeder Dateiname einmal verwendet wird.
Auf modernen Systemen ist dieses Limit so groß, dass Sie sich nicht darum kümmern müssen, bis Sie mindestens in die Tausende von Dateinamen geraten.