it-swarm.com.de

Rekursives Durchsuchen eines Musters / Texts nur im angegebenen Dateinamen eines Verzeichnisses?

Ich habe ein Verzeichnis (z. B. abc/def/efg) mit vielen Unterverzeichnissen (z. B .: abc/def/efg/(1..300)). Alle diese Unterverzeichnisse haben eine gemeinsame Datei (z. B. file.txt). Ich möchte nur in diesem file.txt eine Zeichenfolge suchen, ohne andere Dateien. Wie kann ich das machen?

Ich habe grep -arin "pattern" * verwendet, aber es ist sehr langsam, wenn wir viele Unterverzeichnisse und Dateien haben.

16

Im übergeordneten Verzeichnis können Sie find verwenden und dann grep nur für diese Dateien ausführen:

find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +
21
Zanna

Sie könnten auch Globstar verwenden.

Erstellen von grep -Befehlen mit find, wie in Zannas Antwort beschrieben, ist eine äußerst robuste, vielseitige und tragbare Möglichkeit, dies zu tun (siehe auch sudodus's answer ). Und muru hat einen ausgezeichneten Ansatz für die Verwendung der grep 's _--include_ Option veröffentlicht. Wenn Sie jedoch nur den Befehl grep und Ihre Shell verwenden möchten, gibt es eine andere Möglichkeit - Sie können die Shell selbst erstellen führe die notwendige Rekursion durch :

_shopt -s globstar   # you can skip this if you already have globstar turned on
grep -H 'pattern' **/file.txt_

Das Flag _-H_ bewirkt, dass grep den Dateinamen anzeigt, auch wenn nur eine übereinstimmende Datei gefunden wird. Sie können die Flags _-a_, _-i_ und _-n_ (aus Ihrem Beispiel) auch an grep übergeben, wenn Sie dies benötigen. Übergeben Sie jedoch nicht _-r_ oder _-R_, wenn Sie diese Methode verwenden. Es ist die Shell , die Verzeichnisse beim Erweitern des Glob-Musters mit _**_ rekursiv verwendet, und nicht grep .

Diese Anweisungen gelten nur für die Bash Shell. Bash ist die Standardbenutzershell in Ubuntu (und den meisten anderen GNU/Linux-Betriebssystemen). Sind Sie auf Ubuntu und wissen nicht, was Ihre Shell ist, es ist fast sicher Bash. Obwohl gängige Shells in der Regel Directory-Traversing _**_ unterstützen, funktionieren sie nicht immer auf die gleiche Weise. Weitere Informationen finden Sie unter Stéphane Chazelas 's ausgezeichnete Antwort to Das Ergebnis von ls *, ls * * und ls *** on Unix.SE .

Wie es funktioniert

Wenn Sie die Option globstar bash Shell option aktivieren, wird _**_ mit Pfaden verglichen, die das Verzeichnistrennzeichen enthalten (_/_). . Es ist also ein verzeichnisrekursiver Glob. Im Einzelnen erklärt as man bash :

Wenn die Shell-Option globstar aktiviert ist und * in einem Pfadnamen-Erweiterungskontext verwendet wird, stimmen zwei benachbarte * s, die als einzelnes Muster verwendet werden, mit allen Dateien und überein Keine oder mehrere Verzeichnisse und Unterverzeichnisse. Wenn gefolgt von einem /, stimmen zwei benachbarte * nur mit Verzeichnissen und Unterverzeichnissen überein.

Sie sollten vorsichtig sein, da Sie Befehle ausführen können, die weit mehr Dateien ändern oder löschen, als Sie beabsichtigen, insbesondere wenn Sie _**_ schreiben, als Sie _*_ schreiben wollten. (Es ist sicher in diesem Befehl, der keine Dateien ändert.) _shopt -u globstar_ deaktiviert die Globstar-Shell-Option wieder.

Es gibt einige praktische Unterschiede zwischen globstar und find.

find ist viel vielseitiger als globstar. Alles, was Sie mit globstar tun können, können Sie auch mit dem Befehl find tun. Ich mag globstar und manchmal ist es praktischer, aber globstar ist keine allgemeine Alternative zu find.

Die obige Methode sucht nicht in Verzeichnissen, deren Namen mit _._ beginnen. Manchmal möchten Sie solche Ordner nicht wiederverwenden, manchmal jedoch.

Wie bei einem normalen Glob erstellt die Shell eine Liste aller übereinstimmenden Pfade und übergibt sie anstelle des Glob als Argumente an Ihren Befehl (grep). Wenn Sie über so viele Dateien mit dem Namen _file.txt_ verfügen, dass der resultierende Befehl zu lang ist, um vom System ausgeführt zu werden, schlägt die oben beschriebene Methode fehl. In der Praxis würden Sie (mindestens) Tausende solcher Dateien benötigen, aber es könnte passieren.

Die Methoden, die find verwenden, unterliegen dieser Einschränkung nicht, weil:

  • Zannas Weg erstellt und führt einen grep -Befehl mit möglicherweise vielen Pfadargumenten aus. Wenn jedoch mehr Dateien gefunden werden, als in einem einzelnen Pfad aufgeführt werden können, führt die Aktion _+_- terminated _-exec_ den Befehl mit einigen Pfaden aus und führt ihn dann erneut mit einigen weiteren Pfaden aus und so weiter . Im Fall von grep für eine Zeichenfolge in mehreren Dateien führt dies zum richtigen Verhalten.

    Wie bei der hier beschriebenen Globstar-Methode werden alle übereinstimmenden Zeilen mit vorangestellten Pfaden gedruckt.

  • sudodus's way führt grep für jeden gefundenen _file.txt_ separat aus. Wenn es viele Dateien gibt, ist es möglicherweise langsamer als einige andere Methoden, aber es funktioniert.

    Diese Methode findet Dateien und druckt ihre Pfade, gefolgt von passenden Zeilen, falls vorhanden. Dies ist ein anderes Ausgabeformat als das von meiner Methode erzeugte Format Zanna's und muru's .

Farbe bekommen mit find

Einer der unmittelbaren Vorteile der Verwendung von Globstar ist, dass grep unter Ubuntu standardmäßig eine farbige Ausgabe erzeugt. Aber das können Sie auch mit find leicht erreichen .

Benutzerkonten in Ubuntu werden mit und einem Alias ​​ erstellt, wodurch grep wirklich _grep --color=auto_ (_alias grep_ ausführen kann). Es ist eine gute Sache , dass Aliase fast nur erweitert werden, wenn Sie sie interaktiv ausgeben , aber es bedeutet, dass wenn Sie find Um grep mit dem Flag _--color_ aufzurufen, müssen Sie es explizit schreiben. Zum Beispiel:

_find . -name file.txt -exec grep --color=auto -H 'pattern' {} +_
24
Eliah Kagan

Sie brauchen dafür nicht find; grep kann das vollkommen alleine bewältigen:

_grep "pattern" . -airn --include="file.txt"
_

Von man grep :

_--exclude=GLOB
      Skip  files  whose  base  name  matches  GLOB  (using   wildcard
      matching).   A  file-name  glob  can  use  *,  ?,  and [...]  as
      wildcards, and \ to quote  a  wildcard  or  backslash  character
      literally.

--exclude-from=FILE
      Skip  files  whose  base name matches any of the file-name globs
      read from FILE  (using  wildcard  matching  as  described  under
      --exclude).

--exclude-dir=DIR
      Exclude  directories  matching  the  pattern  DIR from recursive
      searches.

--include=GLOB
      Search  only  files whose base name matches GLOB (using wildcard
      matching as described under --exclude).
_
18
muru

Die in muru's answer angegebene Methode, grep mit dem Flag _--include_ auszuführen, um einen Dateinamen anzugeben, ist häufig die beste Wahl. Dies kann jedoch auch mit find erfolgen.

Der Ansatz in dieser Antwort verwendet find, um grep für jede gefundene Datei separat auszuführen, und gibt den Pfad zu jeder Datei genau einmal aus über den übereinstimmenden Zeilen in jeder Datei. (Methoden, die den Pfad vor jeder übereinstimmenden Zeile ausgeben, werden in anderen Antworten behandelt.)


Sie können das Verzeichnis in den oberen Bereich des Verzeichnisbaums verschieben, in dem sich diese Dateien befinden. Dann renne:

_find . -name "file.txt" -type f -exec echo "##### {}:" \; -exec grep -i "pattern" {} \;
_

Das gibt den Pfad (relativ zum aktuellen Verzeichnis _._ und einschließlich des Dateinamens selbst) jeder Datei mit dem Namen _file.txt_ aus, gefolgt von allen übereinstimmenden Zeilen in der Datei. Dies funktioniert, weil {} ein Platzhalter für die gefundene Datei ist. Der Pfad jeder Datei wird von ihrem Inhalt durch das Präfix _#####_ abgegrenzt und nur einmal vor den übereinstimmenden Zeilen aus dieser Datei gedruckt. (Bei Dateien mit dem Namen _file.txt_, die keine Übereinstimmungen enthalten, werden die Pfade noch gedruckt.) Diese Ausgabe ist möglicherweise übersichtlicher als bei Methoden, die am Anfang jeder übereinstimmenden Zeile einen Pfad ausgeben.

Die Verwendung von find ist fast immer schneller als die Ausführung von grep für jede Datei (_grep -arin "pattern" *_), da find sucht für die Dateien mit dem richtigen Namen und überspringt alle anderen Dateien.

buntu verwendet GNU find , das erweitert immer _{}_, auch wenn es in einer größeren Zeichenkette erscheint , wie _##### {}:_. Wenn Sie Ihren Befehl für die Arbeit mit find auf Systemen benötigen, die dies möglicherweise nicht unterstützen oder die Aktion _-exec_ nur dann verwenden möchten, wenn dies unbedingt erforderlich ist, können Sie Folgendes verwenden:

_find . -name "file.txt" -type f -printf '##### %p:\n' -exec grep -i "pattern" {} \;
_

Um die Ausgabe besser lesbar zu machen , können Sie ANSI-Escape-Sequenzen verwenden, um farbige Dateinamen zu erhalten. Dadurch hebt sich die Pfadüberschrift jeder Datei besser von den übereinstimmenden Zeilen ab, die darunter gedruckt werden:

_find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;
_

Das veranlasst Ihre Shell , den Escape-Code für Grün in die tatsächliche Escape-Sequenz umzuwandeln, die in einem Terminal Grün erzeugt, und dasselbe mit dem Escape-Code für normale Farben zu tun . Diese Escapezeichen werden an find übergeben, das sie beim Drucken eines Dateinamens verwendet. (_$'_ _'_ Anführungszeichen sind hier erforderlich, da die Aktion _-printf_ von find _\e_ für die Interpretation von ANSI-Escape-Codes nicht erkennt.)

Wenn Sie möchten, können Sie stattdessen _-exec_ mit dem Befehl printf des Systems (der _\e_ unterstützt) verwenden. Eine andere Möglichkeit, dasselbe zu tun, ist:

_find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;
_
8
sudodus

Um darauf hinzuweisen, dass Sie direkt grep verwenden können, wenn die Bedingungen der Frage literarisch sind:

grep 'pattern' abc/def/efg/*/file.txt

oder

grep 'pattern' abc/def/efg/{1..300}/file.txt
0
user216043