Ich versuche, mit find alle Dateinamen zurückzugeben, die ein bestimmtes Verzeichnis in ihrem Pfad haben, aber nirgendwo im Dateipfad ein anderes bestimmtes Verzeichnis haben. So etwas wie:
myRegex= <regex>
targetDir= <source directory>
find $targetDir -regex $myRegex -print
Ich weiß, dass ich dies möglicherweise auch tun kann, indem ich einen Find-Befehl in einen anderen weiterleite, aber ich würde gerne wissen, wie das mit einem einzelnen regulären Ausdruck geht.
Ich möchte beispielsweise jede Datei, die das Verzeichnis „good“ in ihrem Pfad hat, aber das Verzeichnis „bad“ nirgendwo in ihrem Pfad hat, unabhängig von der Kombination. Einige Beispiele:
/good/file_I_want.txt #Captured
/good/bad/file_I_dont_want.txt #Not captured
/dir1/good/file_I_want.txt #Captured
/dir2/good/bad/file_I_dont_want.txt #Not captured
/dir1/good/dir2/file_I_want.txt #Captured
/dir1/good/dir2/bad/file_I_want.txt #Not captured
/bad/dir1/good/file_I_dont_want.txt #Not captured
Bedenken Sie, dass einige Dateinamen „gut“ oder „schlecht“ enthalten könnten, aber ich möchte nur Verzeichnisnamen berücksichtigen.
/good/bad.txt #Captured
/bad/good.txt #Not captured
Meine Recherchen legen nahe, dass ich einen negativen Lookahead und einen negativen Lookbehind verwenden sollte. Bisher hat jedoch nichts von dem, was ich versucht habe, funktioniert. Für etwas Hilfe wäre ich dankbar. Danke.
Antwort1
Wie Inian sagte, ist das nicht nötig -regex
(was nicht dem Standard entspricht und die Syntax zwischen den Implementierungen, die ¹ unterstützen, stark variiert -regex
).
Sie können -path
hierfür verwenden, Sie können aber auch angeben , find
dass Verzeichnisse mit dem Namen nicht betreten werden sollen. bad
Dies wäre effizienter, als alle darin enthaltenen Dateien zu ermitteln und sie später mit herauszufiltern -path
:
LC_ALL=C find . -name bad -prune -o -path '*/good/*.txt' -type f -print
( LC_ALL=C
sodass find
das *
Platzhalterzeichen nicht bei Dateinamen mit Bytefolgen, die im Gebietsschema keine gültigen Zeichen bilden, blockiert wird).
Oder für mehr als einen Ordnernamen:
LC_ALL=C find . '(' -name bad -o -name worse ')' -prune -o \
'(' -path '*/good/*' -o -path '*/better/*' ')' -name '*.txt' -type f -print
Mit zsh
können Sie außerdem Folgendes tun:
set -o extendedglob # best in ~/.zshrc
print -rC1 -- (^bad/)#*.txt~^*/good/*(ND.)
print -rC1 -- (^(bad|worse)/)#*.txt~^*/(good|better)/*(ND.)
Oder für die Listen in Arrays:
good=(good better best)
bad=(bad worse worst)
print -rC1 -- (^(${(~j[|])bad})/)#*.txt~^*/(${(~j[|])good})/*(ND.)
ZunichtWechseln Sie in Verzeichnisse mit dem Namen bad
, oder (weniger effizient wie bei -path '*/good/*' ! -path '*/bad/*'
):
print -rC1 -- **/*.txt~*/bad/*~^*/good/*(ND.)
In zsh -o extendedglob
ist ~
dieaußer(und-nicht) Glob-Operator, während ^
der Negationsoperator ist und #
0 oder mehr vom Vorhergehenden ist, wie bei regulärem Ausdruck *
. ${(~j[|])array}
Verbindet die Elemente des Arrays mit |
, wobei dies |
als Glob-Operator und nicht als Literal |
mit behandelt wird ~
.
In zsh
könnten Sie PCRE-Matching nach folgendem verwenden set -o rematchpcre
:
set -o rematchpcre
regex='^(?!.*/bad/).*/good/.*\.txt\Z'
print -rC1 -- **/*(ND.e['[[ $REPLY =~ $regex ]]'])
Aber die Auswertung des Shell-Codes für jede Datei (einschließlich der Dateien in bad
Verzeichnissen) macht es wahrscheinlich viel langsamer als andere Lösungen.
Beachten Sie auch, dass PCRE (im Gegensatz zu zsh-Globs) an Bytefolgen scheitert, die keine gültigen Zeichen im Gebietsschema bilden, und keine anderen Multibyte-Zeichensätze als UTF-8 unterstützt. Wenn Sie das Gebietsschema wie C
oben beschrieben korrigieren find
, können Sie beide Probleme für dieses spezielle Muster beheben.
Wenn Sie lieber [[ =~ ]]
nur erweiterte Regexp-Übereinstimmungen wie in durchführen möchten bash
, können Sie stattdessen auch einfach das PCRE-Modul ( zmodload zsh/pcre
) laden und [[ -pcre-match ]]
anstelle von verwenden [[ =~ ]]
, um die PCRE-Übereinstimmung durchzuführen.
Oder Sie können die Filterung folgendermaßen durchführen (GNU oder kompatibel grep -zP
vorausgesetzt ):grep
regex='^(?!.*/bad/).*/good/.*\.txt\Z'
find . -type f -print0 |
LC_ALL=C grep -zPe "$regex" |
tr '\0' '\n'
( find
erkennt aber immer noch alle Dateien in allen bad
Verzeichnissen).
Ersetzen Sie es tr '\0' '\n'
durch xargs -r0 cmd
, wenn Sie mit diesen Dateien etwas tun müssen (außer sie zeilenweise auszudrucken).
¹ Auf jeden Fall kenne ich keine find
Implementierung, die Perl- oder Vim-ähnliche reguläre Ausdrücke unterstützt, die Sie für Lookaround-Operatoren benötigen würden.
Antwort2
Sie benötigen hierfür keinen regulären Ausdruck. Sie können das -path
Prädikat verwenden, um Verzeichnisse mit einem bestimmten Namen auf jeder Ebene auszuschließen
find . -type f -path '*/good/*' '!' -path '*/bad/*'
Antwort3
Obwohl es wahrscheinlich weniger effizient (obwohl ich mir nicht sicher bin!) und weniger „korrekt“ ist als find
die leistungsstarke Filterung von (beispielsweise grep
funktioniert naiv hier nicht für Namen, die Zeilenumbruchzeichen enthalten, obwohl diese äußerst selten sind und normalerweise einen Fehler darstellen), ist es oft viel einfacher, einige Instanzen davon zu stapeln, grep
die die Ergebnisse sukzessive mithilfe einfacherer Übereinstimmungen und inverser Übereinstimmungen filtern.-v
Dadurch ist im Umgang mit Teilzeichenfolgen mehr Vorsicht geboten, um sicherzustellen, dass Sie tatsächlich einen Verzeichnisnamen finden. Im Allgemeinen ist die Syntax jedoch viel einfacher zu verstehen und kann möglicherweise alles tun, was Sie brauchen.
find ./ | grep "/good/" | grep -v "/bad/" | grep '\.txt$'