Liste der Unterverzeichnisse abrufen, die eine Datei enthalten, deren Name eine Zeichenfolge enthält

45

Wie kann ich eine Liste der Unterverzeichnisse abrufen, die eine Datei enthalten, deren Name mit einem bestimmten Muster übereinstimmt?

Insbesondere suche ich nach Verzeichnissen, die eine Datei mit dem Buchstaben 'f' irgendwo im Dateinamen enthalten.

Im Idealfall enthält die Liste keine Duplikate und nur den Pfad ohne Dateinamen.

Muhd
quelle

Antworten:

43
find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort |uniq

Das Obige findet alle Dateien unterhalb des aktuellen Verzeichnisses ( .), die reguläre Dateien ( -type f) sind und firgendwo in ihrem Namen ( -name '*f*') stehen. sedEntfernt als Nächstes den Dateinamen und belässt nur den Verzeichnisnamen. Anschließend wird die Liste der Verzeichnisse sortiert ( sort) und Duplikate entfernt ( uniq).

Der sedBefehl besteht aus einem einzelnen Ersatz. Es sucht nach Übereinstimmungen mit dem regulären Ausdruck /[^/]+$und ersetzt alles, was damit übereinstimmt, durch nichts. Das Dollarzeichen bedeutet das Ende der Zeile. [^/]+'bedeutet ein oder mehrere Zeichen, die keine Schrägstriche sind. Bedeutet /[^/]+$also alle Zeichen vom letzten Schrägstrich bis zum Zeilenende. Dies entspricht also dem Dateinamen am Ende des vollständigen Pfads. Daher entfernt der Befehl sed den Dateinamen, wobei der Name des Verzeichnisses, in dem sich die Datei befand, unverändert bleibt.

Vereinfachungen

Viele moderne sortBefehle unterstützen ein -uFlag, das uniqunnötig macht . Für GNU sed:

find . -type f -name '*f*' | sed -r 's|/[^/]+$||' |sort -u

Und für MacOS sed:

find . -type f -name '*f*' | sed -E 's|/[^/]+$||' |sort -u

Wenn Ihr findBefehl dies unterstützt, ist es auch möglich, finddie Verzeichnisnamen direkt auszudrucken. Dies vermeidet die Notwendigkeit für sed:

find . -type f -name '*f*' -printf '%h\n' | sort -u

Robustere Version (GNU-Tools erforderlich)

Die obigen Versionen werden durch Dateinamen verwechselt, die Zeilenumbrüche enthalten. Eine robustere Lösung ist das Sortieren von Zeichenfolgen mit NUL-Abschluss:

find . -type f -name '*f*' -printf '%h\0' | sort -zu | sed -z 's/$/\n/'
John1024
quelle
Ich habe viele Dateien, die das Sortieren zu teuer machen. uniqIn die Mischung zu werfen hilft sehr, indem die wiederholten Linien, die bereits direkt nebeneinander sind, entfernt werden. find . -type f -name '*f*' -printf '%h\0' | uniq -z | sort -zu | tr '\0' '\n'. Oder wenn Ihre Tools etwas älter sind, hat uniq möglicherweise nicht die Option -z. find . -type f -name '*f*' -printf '%h\n' | uniq | sort -u
jbo5112
1
MacOS-Benutzer: Das sed-Flag ist nicht -r. Aus irgendeinem Grund ist es -E
David
@ David Sehr wahr. Antwort -Efür MacOS aktualisiert .
John1024
23

Warum versuchst du das nicht:

find / -name '*f*' -printf "%h\n" | sort -u
Patrick Taylor
quelle
Beste Antwort. Völlig POSIX-kompatibel, im Gegensatz zu den obigen Antworten, und außerdem mit dem Sonderpreis The Shortest Pipeline :) ausgezeichnet.
km
Ich würde gerne jemanden sehen, der das Timing von diesem gegen die anderen oben zeigt, weil ich das Gefühl habe, dass dies bei weitem das schnellste ist.
Dlamblin
4
@kkm Ich bin damit einverstanden, dass dies die beste Lösung ist, aber POSIX-Spezifikationen fürfind sind eigentlich recht spärlich - der -printfOperator ist nicht angegeben. Dies funktioniert nicht mit BSD find. Also nicht "ganz POSIX-kompatibel". (Obwohl sort -u in POSIX .)
Wildcard
8

Es gibt im Wesentlichen 2 Methoden, die Sie verwenden können, um dies zu tun. Einer analysiert die Zeichenfolge, während der andere die einzelnen Dateien bearbeitet. Die Zeichenfolge ein Tool wie verwenden Parsen grep, sedoder awkist offensichtlich schneller sein würde , aber hier ist ein Beispiel sowohl, als auch, wie zeigt man „Profil“ die 2 - Methoden.

Beispieldaten

Für die folgenden Beispiele verwenden wir die folgenden Daten

$ touch dir{1..3}/dir{100..112}/file{1..5}
$ touch dir{1..3}/dir{100..112}/nile{1..5}
$ touch dir{1..3}/dir{100..112}/knife{1..5}

Löschen Sie einige der *f*Dateien von dir1/*:

$ rm dir1/dir10{0..2}/*f*

Ansatz Nr. 1 - Parsen über Zeichenfolgen

Hier werden wir die folgenden Werkzeuge verwenden, find, grep, und sort.

$ find . -type f -name '*f*' | grep -o "\(.*\)/" | sort -u | head -5
./dir1/dir103/
./dir1/dir104/
./dir1/dir105/
./dir1/dir106/
./dir1/dir107/

Ansatz 2 - Analysieren mit Dateien

Dieselbe Werkzeugkette wie zuvor, außer dass wir diesmal dirnameanstelle von verwenden werden grep.

$ find . -type f -name '*f*' -exec dirname {} \; | sort -u | head -5
./dir1/dir103
./dir1/dir104
./dir1/dir105
./dir1/dir106
./dir1/dir107

HINWEIS: In den obigen Beispielen wird head -5lediglich der Umfang der Ausgabe beschränkt, mit der wir uns in diesen Beispielen befassen. Sie werden normalerweise entfernt, um Ihre vollständige Auflistung zu erhalten!

Ergebnisse vergleichen

Wir können timeeinen Blick auf die beiden Ansätze werfen.

dirname

real        0m0.372s
user        0m0.028s
sys         0m0.106s

grep

real        0m0.012s
user        0m0.009s
sys         0m0.007s

Deshalb ist es immer am besten, wenn es möglich ist, mit den Saiten umzugehen.

Alternative Methoden zum Parsen von Zeichenfolgen

grep & PCRE

$ find . -type f -name '*f*' | grep  -oP '^.*(?=/)' | sort -u

sed

$ find . -type f -name '*f*' | sed 's#/[^/]*$##' | sort -u

awk

$ find . -type f -name '*f*' | awk -F'/[^/]*$' '{print $1}' | sort -u
slm
quelle
+1 Weil es funktioniert, aber interessanterweise dauert dies um ein Vielfaches länger als die Antwort von @ John1024
Muhd
@ Muhd ​​- ja, die Aufrufe von dirname sind langsam. Ich arbeite an einer Alternative.
slm
2

Hier ist eine, die ich nützlich finde:

find . -type f -name "*somefile*" | xargs dirname | sort | uniq
Martin Tapp
quelle
1

Diese Antwort basiert unverschämt auf der Antwort von slm. Es war ein interessanter Ansatz, hat aber eine Einschränkung, wenn die Datei- und / oder Verzeichnisnamen Sonderzeichen (Leerzeichen, Zwischenspalte ...) enthielten. Eine gute Angewohnheit ist zu benutzen find /somewhere -print0 | xargs -0 someprogam.

Beispieldaten

Für die folgenden Beispiele verwenden wir die folgenden Daten

mkdir -p dir{1..3}/dir\ {100..112}
touch dir{1..3}/dir\ {100..112}/nile{1..5}
touch dir{1..3}/dir\ {100..112}/file{1..5}
touch dir{1..3}/dir\ {100..112}/kni\ fe{1..5}

Löschen Sie einige der *f*Dateien von dir1/*/:

rm dir1/dir\ 10{0..2}/*f*

Ansatz 1 - Analysieren mit Dateien

$ find -type f -name '*f*' -print0 | sed -e 's#/[^/]*\x00#\x00#g' | sort -zu | xargs -0 -n1 echo | head -n5
./dir1/dir 103
./dir1/dir 104
./dir1/dir 105
./dir1/dir 106
./dir1/dir 107

HINWEIS : In den obigen Beispielen wird head -5lediglich der Umfang der Ausgabe beschränkt, mit der wir uns in diesen Beispielen befassen. Sie werden normalerweise entfernt, um Ihre vollständige Auflistung zu erhalten! Ersetzen echoSie auch den gewünschten Befehl.

Franklin Piat
quelle
1

Mit zsh:

typeset -aU dirs # array with unique values
dirs=(**/*f*(D:h))

printf '%s\n' $dirs
Stéphane Chazelas
quelle