Wie unterscheidet man eine Datei von einem Verzeichnis in einer ls-Ausgabe?

996
Kosarar

Wie unterscheidet man eine Datei von einem Verzeichnis in einer ls-Ausgabe? Ich möchte mit Dateien arbeiten und in Verzeichnisse gehen, aber ich bekomme nur eine Liste mit Namen von allen:

for i in ls B  do echo $i done 
2
Zumindest müssten Sie "ls -l" verwenden Mokubai vor 7 Jahren 2
Im Allgemeinen ist der Versuch, [ls “zu analysieren, eine schlechte Idee] (http://mywiki.wooledge.org/ParsingLs). Es neigt dazu, Dateinamen mit Leerzeichen zu unterbrechen, oder die aussehen wie Globs. 8bittree vor 7 Jahren 0

2 Antworten auf die Frage

2
user4556274

Auf der lsManpage können Sie sehen, welche Einträge Verzeichnisse verwenden

 -F, --classify append indicator (one of */=>@|) to entries 

Also wenn du verwendest

for i in $(ls -F B) ; do echo $i done 

Sie sollten sehen, dass Verzeichnisse /angehängt sind und andere Dateien nicht.


Wenn Sie jedoch in Verzeichnisse absteigen möchten, ist die Verwendung möglicherweise besser test

for f in $(ls B) ; do if [ -d $f ] ; then recurse_into_directory elif [ -f $f ] process_file else echo "$f: neither regular file nor directory" fi done 
Sie können die Probleme beim Parsen von 'ls' insgesamt vermeiden, indem Sie 'for f in B / *; do` stattdessen am Anfang Ihres `test`-Beispiels. 8bittree vor 7 Jahren 1
Gute Antwort! Könnten Sie mir bitte sagen, wie Sie überprüfen können, ob der Dateiname einem Muster folgt? Ich habe versucht `elif [-f $ f] -and -regex". * $ Patern. * "; dann` aber es stimmt nie ... Kosarar vor 7 Jahren 0
http://superuser.com/questions/1183035/how-do-i-check-if-the-name-of-some-file-matches-some-pattern-has-sss-in-it-f Kosarar vor 7 Jahren 0
0
Trey

Sie schreiben, dass Sie "mit Dateien arbeiten und in Verzeichnisse gehen wollen", so dass Sie direkt zu einer lsLö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 .txtErweiterung. 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 findBefehl Unix-Befehl für Directory Traversal gelöst .

findEs 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 findBefehl das folgende Format hat:

find DIR [PREDICATE, ..] 

DIRist das Startverzeichnis (in diesem Beispiel .ist dies immer das aktuelle Arbeitsverzeichnis). A PREDICATEist ein Ausdruck, der findverwendet 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 findlautet 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 finddie Elemente innerhalb des Verzeichnisses fortgesetzt, sobald das letzte Vergleichselement erreicht wurde oder ein Vergleichselement falsch ist . Es gibt zwei Hauptausnahmen:

  1. Das -prunePrädikat kann verwendet werden, um dies selektiv zu deaktivieren. wenn das -prunePrädikat erreicht ist und das aktuelle Element ein Verzeichnis ist, oder
  2. Die -maxdepth=NOption (kein Prädikat, das zuvor DIRin der Befehlszeile angezeigt wird) kann verwendet werden, um finddie Suchtiefe einzuschränken. Wenn das aktuelle Verzeichnis Noder 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 finddavon ausgegangen Sie tun bedeutete etwas, so Es wirkt so, als würde das -printPrädikat angegeben, um den Pfadnamen auszudrucken. In älteren Versionen von findwar 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 findwird 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 -oPrädikat, bei dem ODER zwei Prädikate sind (tatsächlich gibt es auch ein -aAND-Prädikat, das jedoch selten benötigt wird, da dies, wie ich oben schrieb, das Standardverhalten ist).
  • finderlaubt 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 .txtSuffix erhält :

  1. Wie bereits erwähnt, lautet der Befehl zum Abrufen der Zeilenzahl einer Datei wc -l.
  2. 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 CMDjedes Vorkommen des Tokens {}durch den Pfadnamen, der gerade untersucht wird.
  3. 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 .txtErweiterung benötigen, verwenden wir *.txtunser 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 *.txtals auch einen Backslash vor dem Semikolon, um zu verhindern, dass die Shell diese Zeichen als Sonderzeichen interpretiert, finddamit 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 findnur das -execVergleichselement 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 fvor 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 -pruneoder -maxdepthist vorhanden, wie zuvor erwähnt.)

Beachten Sie, dass die oben ist möglich unter Verwendung lsin 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 lseine 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 -ldie Anzahl der Dateien angegeben wird, gefolgt von einer Gesamtsumme. Die oben genannte Lösung hatte jedoch keine Gesamtsumme, da wcsie 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 -execPrä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, finddie diese Funktion nicht haben, würden Sie sie findzusammen mit einem anderen Programm verwenden xargs. xargsnimmt 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 wcsah jedes Wort des Dateinamens My Spacey File.txt als separates Argument. Um dies zu beheben, verwenden wir ein Feature findund ein entsprechendes Feature xargs, bei dem das Nullzeichen ( \0das 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 -print0Prädikat gibt findan, seine Ausgabe durch Nullen getrennt zu senden. Die -0Option von xargstut 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 findals xargsauch 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.