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

16

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

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

Rajesh Keladimath
quelle

Antworten:

21

Im übergeordneten Verzeichnis können Sie nur die folgenden Dateien verwenden findund ausführen grep:

find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +
Zanna
quelle
2
Ich empfehle auch, an -Hzu übergeben , grepdamit in Fällen, in denen nur ein Pfad übergeben wird, dieser Pfad weiterhin gedruckt wird (und nicht nur die übereinstimmenden Zeilen aus der Datei).
Eliah Kagan
24

Sie könnten auch Globstar verwenden.

Das Erstellen von grepBefehlen mit find, wie in Zannas Antwort , ist eine äußerst robuste, vielseitige und tragbare Möglichkeit, dies zu tun (siehe auch die Antwort von sudodus ). Und muru hat einen ausgezeichneten Ansatz für die Verwendung grepder --includeOption veröffentlicht . Wenn Sie jedoch nur den grepBefehl und Ihre Shell verwenden möchten , gibt es eine andere Möglichkeit: Sie können die Shell selbst dazu bringen , die erforderliche Rekursion durchzuführen :

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

Das -HFlag grepzeigt den Dateinamen an, auch wenn nur eine passende Datei gefunden wird. Sie können den Pass -a, -iund -nFahnen (von Ihrem Beispiel) grepals auch, wenn es das ist , was Sie brauchen. Aber nicht bestanden -roder -Rbei dieser Methode. Es ist die Shell , die Verzeichnisse beim Erweitern des Glob-Musters rekursiv verwendet **und nichtgrep .

Diese Anweisungen gelten speziell für die Bash-Shell. Bash ist die Standardbenutzershell unter Ubuntu (und den meisten anderen GNU / Linux-Betriebssystemen). Wenn Sie also unter Ubuntu arbeiten und Ihre Shell nicht kennen, ist dies mit ziemlicher Sicherheit Bash. Beliebte Shells unterstützen zwar normalerweise Verzeichnis- **übergreifende Globs, funktionieren jedoch nicht immer auf die gleiche Weise. Weitere Informationen finden Sie Stéphane Chazelas ‚s ausgezeichnete Antwort auf Das Ergebnis der ls *, ls ** und ls *** auf Unix.SE .

Wie es funktioniert

Einschalten der globstar bash Shell Option macht **Spiel Pfade Verzeichnistrenner enthält ( /). Es ist also ein verzeichnisrekursiver Glob. Im Einzelnen, wie man basherklärt:

Wenn die Globstar- Shell-Option aktiviert ist und * in einem Pfadnamen-Erweiterungskontext verwendet wird, stimmen zwei benachbarte * s, die als ein einziges Muster verwendet werden, mit allen Dateien und null oder mehr Verzeichnissen und Unterverzeichnissen überein. Wenn gefolgt von einem /, stimmen zwei benachbarte * nur mit Verzeichnissen und Unterverzeichnissen überein.

Sie sollten vorsichtig sein, da Sie Befehle ausführen können, mit denen weit mehr Dateien geändert oder gelöscht werden, als Sie beabsichtigen, insbesondere, wenn Sie schreiben, **wann Sie schreiben möchten *. (Es ist sicher in diesem Befehl, der keine iles ändert.) shopt -u globstarSchaltet die Globstar-Shell-Option wieder aus.

Es gibt einige praktische Unterschiede zwischen globstar und find.

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

Die obige Methode sucht nicht in Verzeichnissen, deren Namen mit a 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 grepanstelle des Glob als Argumente an Ihr Kommando ( ). Wenn Sie so viele Dateien aufgerufen haben, file.txtdass der resultierende Befehl für die Ausführung durch das System zu lang wäre, 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 verwendeten Methoden findunterliegen nicht dieser Einschränkung, da:

  • Zannas Methode erstellt und führt einen grepBefehl 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 -execden Befehl mit einigen Pfaden aus und führt ihn dann erneut mit einigen weiteren Pfaden aus. Im Falle grepeiner Zeichenfolge in mehreren Dateien führt dies zum korrekten Verhalten.

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

  • der weg von sudodus verläuft grepfür jedes file.txtgefundene separat . Wenn es viele Dateien gibt, ist es möglicherweise langsamer als einige andere Methoden, aber es funktioniert.

    Bei dieser Methode werden Dateien gesucht und ihre Pfade gedruckt, gefolgt von etwaigen übereinstimmenden Zeilen. Dies ist ein anderes Ausgabeformat als das von meiner Methode, Zanna und Murus , erzeugte .

Farbe bekommen mit find

Einer der unmittelbaren Vorteile von Globstar ist, dass Ubuntu standardmäßig grepkolorierte Ausgaben erzeugt. Aber man kann dies leicht zu bekommen find, auch .

Benutzerkonten in Ubuntu werden mit einem Alias ​​erstellt , der grepwirklich zum Laufen bringt grep --color=auto(run alias grepto see). Es ist eine gute Sache, dass Aliase so gut wie nur dann erweitert werden, wenn Sie sie interaktiv ausgeben. Wenn Sie findjedoch grepmit dem --colorFlag aufrufen möchten , müssen Sie es explizit schreiben. Beispielsweise:

find . -name file.txt -exec grep --color=auto -H 'pattern' {} +
Eliah Kagan
quelle
Möglicherweise möchten Sie klarer angeben, dass Sie die bashShell verwenden müssen, damit dies funktioniert. Sie haben zu sagen , dass es implizit in „der globstar Bash - Shell - Option“ , aber es kann leicht von Menschen lesen zu schnell übersehen werden.
Stig Hemmer
Ich habe meine Antwort entfernt, weil es viele kritische Kommentare gab. Entfernen Sie daher in Ihrer Antwort den Verweis darauf.
Sudodus
@StigHemmer Danke - Ich habe klargestellt, dass nicht alle Shells diese Funktion haben. Obwohl viele Shells (nicht nur bash) verzeichnisübergreifende **Globs unterstützen, ist Ihre Kernkritik korrekt: Die Darstellung **in dieser Antwort ist spezifisch für bash, wobei shopt nur bash und der Begriff "globstar" (glaube ich) bash und ist nur tcsh. Ich hatte das ursprünglich wegen dieser Komplexität beschönigt, aber Sie haben Recht, dass es etwas verwirrend ist. Anstatt es in dieser Antwort ausführlich zu diskutieren, habe ich auf einen anderen (ziemlich gründlichen) Post verwiesen, der das schwere Heben übernimmt.
Eliah Kagan
@sudodus hab ich ja gemacht, aber ich hoffe das ist nur vorübergehend. Ich und andere haben Ihre Antwort für wertvoll befunden. Es ist wahr, -esollte nicht auf Pfade angewendet werden, aber dies ist leicht zu beheben. Für den ersten Befehl einfach weglassen -e. Verwenden Sie für die Sekunde find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;oder find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;. Benutzer bevorzugen manchmal Ihren Weg (mit -efester Verwendung) gegenüber den anderen, die einen Pfad pro übereinstimmender Zeile drucken . Ihr druckt einen Pfad pro gefundener Datei, gefolgt von den grepErgebnissen.
Eliah Kagan
@sudodus Also grepselbst macht nicht was du machst. Einige andere Kritikpunkte waren auch falsch. grep -HLaufen durch -execwird nicht ohne --color(oder GREP_COLOR) färben . 1.003,1-2.008 IEEE nicht garantieren {}expandiert in ##### {}:, aber Ubuntu hat GNU find, was tut . Wenn es für Sie in Ordnung ist, bearbeite ich Ihren Beitrag, um den -eFehler zu beheben (und den Anwendungsfall zu klären), und Sie können sehen, ob Sie den Löschvorgang rückgängig machen möchten. (Ich habe den Repräsentanten, um gelöschte Beiträge anzuzeigen / zu bearbeiten.)
Eliah Kagan
18

Das brauchst du nicht find; grepkann dies 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).
muru
quelle
Schön - das scheint der beste Weg zu sein. Einfach und effizient. Ich wünschte, ich hätte von dieser Methode gewusst (oder wollte sie in der Manpage nachschlagen). Vielen Dank!
Eliah Kagan
@EliahKagan Ich bin eher überrascht, dass Zanna dies nicht gepostet hat - ich hatte vor einiger Zeit ein Beispiel für diese Option für eine andere Antwort gezeigt. :)
muru
2
Langsamer Lerner, leider, aber ich komme irgendwann dorthin, Ihre Lehren sind nicht vollständig auf mich verschwendet;)
Zanna
Dies ist sehr einfach und leicht zu merken. Danke.
Rajesh Keladimath
Ich stimme zu, dass dies die beste Antwort ist. Sollte ich meine Antwort entfernen, um Verwirrung zu find?
lindern
8

Die in der Antwort von muru angegebene Methode , grepmit dem --includeFlag einen Dateinamen anzugeben, ist häufig die beste Wahl. Dies kann jedoch auch mit erfolgen find.

Der Ansatz in dieser Antwort Anwendungen findausgeführt werden grepseparat für jede Datei gefunden, und druckt den Pfad zu jeder Datei genau einmal , über den passenden in jeder Datei gefunden Zeilen. (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 genannten Datei aus file.txt, gefolgt von allen übereinstimmenden Zeilen in der Datei. Dies funktioniert, weil {}ein Platzhalter für die Datei gefunden wird. Der Pfad jeder Datei unterscheidet sich von ihrem Inhalt durch das Präfix #####und wird nur einmal vor den übereinstimmenden Zeilen aus dieser Datei gedruckt. (Aufgerufene Dateien file.txt, die keine Übereinstimmungen enthalten, werden immer noch mit Pfaden gedruckt.) Diese Ausgabe ist möglicherweise übersichtlicher als die Ausgabe von Methoden, die am Anfang jeder übereinstimmenden Zeile einen Pfad ausgeben.

Eine findsolche Verwendung ist fast immer schneller als die Ausführung grepfür jede Datei ( grep -arin "pattern" *), da findnach Dateien mit dem richtigen Namen gesucht wird und alle anderen Dateien übersprungen werden.

Ubuntu benutzt GNU find , das sich immer erweitert, {}auch wenn es in einer größeren Zeichenkette vorkommt , wie ##### {}:. Wenn Sie Ihren Befehl für die Arbeit mit findSystemen benötigen , die dies möglicherweise nicht unterstützen , oder die -execAktion 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 Farbe zu tun. Diese Escapezeichen werden an übergeben find, das sie verwendet, wenn ein Dateiname gedruckt wird. ( $' 'Anführungszeichen sind hier erforderlich, da finddie -printfAktion \efür die Interpretation von ANSI-Escape-Codes nicht erkannt wird .)

Wenn Sie möchten, können Sie stattdessen -execmit dem printfSystembefehl (das tut Unterstützung \e). Eine andere Möglichkeit, dasselbe zu tun, ist:

find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;
Sudodus
quelle
Ich wollte eine "for-Schleife" mit einem Array machen und ich habe nicht über die Option exec native von find nachgedacht. Guter! Aber ich denke, mit dot werden Sie in dem Verzeichnis gefunden, in dem Sie sich bereits befinden. Korrigiere mich, wenn ich falsch liege. Wäre es nicht besser, das direkt zu analysierende Element in der Suchreihenfolge anzugeben? find abc/def/efg -name "file.txt" -type f -exec echo -e "##### {}:" \; -exec grep -i "pattern" {} \;
kcdtv
Klar, das wird den cd abc/def/efgBefehl 'change directory' eliminieren :-)
sudodus
(1) Warum geben Sie die -eOption an echo? Dadurch werden alle Dateinamen, die umgekehrte Schrägstriche enthalten, unleserlich. (2) Es kann nicht garantiert werden, dass die Verwendung {}als Teil eines Arguments funktioniert. Es wäre besser zu sagen -exec echo "#####" {} \;oder -exec printf "##### %s:\n" {} \;. (3) Warum nicht einfach -printoder verwenden -printf? (4) Betrachten Sie auch grep -H.
G-Man sagt, dass Monica am
@ G-man, 1) Da ich ursprünglich ANSI-Farben verwendet habe: find . -name "file.txt" -type f -exec echo -e "\0033[32m{}:\0033[0m" \; -exec grep -i "pattern" {} \;2) Sie haben vielleicht Recht, aber bisher funktioniert dies bei mir. 3) -print und -printf sind ebenfalls Alternativen. 4) Dies ist bereits in der Hauptantwort enthalten. - Wie auch immer, Sie sind mit Ihrer eigenen Antwort willkommen :-)
Sudodus
Sie brauchen die beiden -execAnrufe nicht. Verwenden Sie einfach grep -Hund das wird den Dateinamen (in Farbe) sowie den passenden Text drucken.
Terdon
0

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

quelle