検索: 正規表現を使用して、パス内に特定のディレクトリ名があり、パス内に別の特定のディレクトリ名がないすべてのファイルを取得します。

検索: 正規表現を使用して、パス内に特定のディレクトリ名があり、パス内に別の特定のディレクトリ名がないすべてのファイルを取得します。

find を使用して、パスに特定のディレクトリがあるが、ファイル パスのどこにも別の特定のディレクトリがないすべてのファイル名を返そうとしています。次のようになります。

myRegex= <regex> 
targetDir= <source directory>
find $targetDir -regex $myRegex -print

1 つの find コマンドを別の find コマンドにパイプすることでこれを行うこともできるかもしれませんが、単一の正規表現でこれを行う方法を知りたいです。

たとえば、パスに「good」ディレクトリがあるが、組み合わせに関係なくパスのどこにも「bad」ディレクトリがないすべてのファイルが必要です。例:

/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

一部のファイル名には「good」または「bad」が含まれる場合があることに注意してください。ただし、ここではディレクトリ名のみを考慮します。

/good/bad.txt #Captured
/bad/good.txt #Not captured

調査の結果、否定先読みと否定後読みを使用する必要があることがわかりました。しかし、これまで試した方法はどれもうまくいきませんでした。助けていただければ幸いです。よろしくお願いします。

答え1

Inian が言ったように、必要ありません-regex(これは非標準であり、サポートする実装間で構文が大きく異なります-regex¹)。

そのためにを使用できますが、というディレクトリを入力しないように-path指示することもできます。これは、 で後でフィルタリングするために、その中のすべてのファイルを検出するよりも効率的です。findbad-path

LC_ALL=C find . -name bad -prune -o -path '*/good/*.txt' -type f -print

(LC_ALL=Cしたがって、find*ワイルドカードは、ロケールで有効な文字を形成しないバイトシーケンスを含むファイル名では動作しません)。

または、複数のフォルダー名の場合:

LC_ALL=C find . '(' -name bad -o -name worse ')' -prune -o \
  '(' -path '*/good/*' -o -path '*/better/*' ')' -name '*.txt' -type f -print

を使用するとzsh、次のこともできます。

set -o extendedglob # best in ~/.zshrc
print -rC1 -- (^bad/)#*.txt~^*/good/*(ND.)
print -rC1 -- (^(bad|worse)/)#*.txt~^*/(good|better)/*(ND.)

または、配列内のリストの場合:

good=(good better best)
bad=(bad worse worst)
print -rC1 -- (^(${(~j[|])bad})/)#*.txt~^*/(${(~j[|])good})/*(ND.)

ないと呼ばれるディレクトリに降りるかbad、または ( の場合のように効率は悪くなります-path '*/good/*' ! -path '*/bad/*'):

print -rC1 -- **/*.txt~*/bad/*~^*/good/*(ND.)

ではzsh -o extendedglob~を除外する(論理和否定) グロブ演算子 は^否定演算子ですが、#は正規表現 のような 0 個以上の前のもの です*${(~j[|])array}は配列の要素を で結合し|、 は のリテラル|ではなくグロブ演算子として扱われます。|~

ではzsh、 の後に PCRE マッチングを使用できますset -o rematchpcre

set -o rematchpcre
regex='^(?!.*/bad/).*/good/.*\.txt\Z'
print -rC1 -- **/*(ND.e['[[ $REPLY =~ $regex ]]'])

しかし、すべてのファイル (ディレクトリ内のファイルも含む) に対してシェル コードを評価すると、bad他のソリューションよりも大幅に遅くなる可能性があります。

また、PCRE (zsh globs とは異なり) は、ロケールで有効な文字を形成しないバイト シーケンスで動作を停止し、UTF-8 以外のマルチバイト文字セットをサポートしないことに注意してください。ロケールを上記Cのように修正するとfind、この特定のパターンの両方に対処できます。

[[ =~ ]]のように拡張正規表現マッチングのみを実行したい場合はbash、代わりに pcre モジュール ( zmodload zsh/pcre) をロードし、[[ -pcre-match ]]の代わりに[[ =~ ]]を使用して PCRE マッチングを実行することもできます。

または、次のようにフィルタリングすることもできますgrep -zP(GNUgrepまたは互換の場合):

regex='^(?!.*/bad/).*/good/.*\.txt\Z'
find . -type f -print0 |
  LC_ALL=C grep -zPe "$regex" |
  tr '\0' '\n'

(ただし、findすべてのディレクトリ内のすべてのファイルは検出されますbad)。

これらのファイルに対して何か操作を行う必要がある場合 (1 行に 1 つずつ印刷する以外)tr '\0' '\n'は、 に置き換えます。xargs -r0 cmd


find¹ いずれにせよ、ルックアラウンド演算子に必要な、Perl のような正規表現や Vim のような正規表現をサポートする実装は知りません。

答え2

これには正規表現は必要ありません。-path述語を使用して、任意のレベルで特定の名前のディレクトリを除外できます。

find . -type f -path '*/good/*' '!' -path '*/bad/*'

答え3

の強力なフィルタリングよりも効率が悪く(確信はありませんが)、正確性も低い可能性がありますfind(たとえば、ここでの naive は改行文字を含む名前には機能しませんが、これは非常にまれであり、通常はエラーを表します)、より単純な一致と逆一致を使用して結果を連続的にフィルタリングするgrepインスタンスをいくつか積み重ねる方がはるかに簡単です。grep-v

これによって、ディレクトリ名を本当に見つけているかどうかを確認するために、部分文字列に関してより注意が必要になりますが、一般的には理解しやすい構文が提供され、必要なことはすべて実行できます。

find ./ | grep "/good/" | grep -v "/bad/" | grep '\.txt$'

関連情報