1つのディレクトリに複数のファイルがあります。与えられた文字列で終わるすべての文字列を検索して見つけようとします。ディレクトリ内のすべてのファイルではなく、特定のファイル名セットのみを検索したいと思います。最後に、出力は、各ファイル名とそのファイルで見つかったセミコロンで区切られた文字列の発生回数でなければなりません。
単純化されたテストケースは次のとおりです。ディレクトリに5つのファイルがあります。
file.a.txt
file.b.txt
file.c.txt
file.d.txt
file.e.txt
searchFiles.txt
上記のリストの最初の3つのファイル名を含むファイルもあります。だから、リストされているファイル名からのみ文字列を検索したいと思いますsearchFiles.txt
。
私は試した:
for i in $(cat searchFiles.txt); do grep -o '[^ ]*_XYZ' /dev/null $i ; done | awk -F: '{a[$1]=a[$1]";"$2;} END{for (x in a) print x ":" substr(a[x],2);}'
しかし、出力は次のように言います。
: No such file or directory
: No such file or directory
file.c.txt:FOUND1_XYZ;FOUND2_XYZ
したがって、何らかの方法でsearchFiles.txtで指定された最後のファイル名のみを検索できますが、他の初期ファイルは見つからないため、「該当するファイルまたはディレクトリはありません」というエラーが発生します。
私の予想結果は次のとおりです。
file.a.txt:FOUNDSTR_XYZ
file.b.txt:FOUNDSTR1_XYZ;FOUNDSTR2_XYZ;FOUNDSTR3_XYZ
file.c.txt:FOUND1_XYZ;FOUND2_XYZ
また、「find」コマンドの「-name」フラグが役に立つかどうかを調べようとしましたが、ここではsearchFiles.txtのファイルリストを正確に提供する方法を理解していません。次の試みが失敗しました。
find . -type f -name `cat searchFiles.txt` -exec grep -o '[^ ]*_XYZ' /dev/null {} \;
返品:
ディレクトリには最大数千のファイルを含めることができ、searchFiles.txtで検索されるファイル名は数百にすることができます。
ファイル名は何でも構いませんが、どのようなパターンにも従いません。
searchFiles.txt で提供されるファイル名は、file.a.txt の代わりに a.txt のような部分名であってもよい。ファイル名「file」の初期静的部分を意味する。 searchFiles.txt に存在する場合も存在しない場合もあります。
シェルスクリプトではなく、1行のコマンドを見つける方が良いでしょう。
これに助けが必要ですか?
答え1
awk
GNUを使用して、次のすべてのことを実行できる必要があります。
find . -type f -print0 |
gawk '
step == 1 {files[$0]; next} # record file names in "files" array
step == 2 {
# determine which files to look into (added to ARGV array for
# processing in step 3)
if ($NF in files) ARGV[ARGC++] = $0; next
}
NF {
# record all matches (here in fields matched by FPAT)
$1 = $1 # force a rebuild of $0 joining fields with OFS
matches[FILENAME] = matches[FILENAME] \
(matches[FILENAME] == "" ? "" : OFS) \
$0
}
END {
for (file in matches)
print file ": " matches[file]
}' step=1 searchFiles.txt \
step=2 RS='\0' FS=/ - \
step=3 RS='\n' FPAT='[^ ]*_XYZ' OFS=';'
上記では、ファイル名はに保存されますsearchFiles.txt
。ファイルの行がサフィックスのリストである場合は、連想配列の代わりに正規表現を作成できます。
find . -type f -print0 |
gawk '
step == 1 {
gsub(/[][^$*()+{}?\\.|]/, "\\\\&") # escape regexp operators
regex = regex sep $0; sep = "|"
next
}
step == 2 {
# determine which files to look into (added to ARGV array for
# processing in step 3)
if ($NF ~ ("(" regex ")$")) ARGV[ARGC++] = $0; next
}
NF {
# record all matches (here in fields matched by FPAT)
$1 = $1 # force a rebuild of $0 joining fields with OFS
matches[FILENAME] = matches[FILENAME] \
(matches[FILENAME] == "" ? "" : OFS) \
$0
}
END {
for (file in matches)
print file ": " matches[file]
}' step=1 searchFiles.txt \
step=2 RS='\0' FS=/ - \
step=3 RS='\n' FPAT='[^ ]*_XYZ' OFS=';'
難読化が必要な場合は、1行に入力できます。
find . -type f -print0|gawk '!s{gsub(/[][^$*()+{}?\\.|]/,"\\\\&");r=r p $0;p="|";next};s==2{if($NF~("("r")$"))ARGV[ARGC++]=$0;next};NF{$1=$1;m[FILENAME]=m[FILENAME](m[FILENAME]==""?"":OFS)$0};END{for(f in m)print f":"m[f]}' searchFiles.txt s=2 RS=\\0 FS=/ - s=3 RS=\\n FPAT='[^ ]*_XYZ' OFS=\;
ファイル名と内容に含めることができる文字については想定しません。ただし、その文字はロケールで有効な文字でなければなりません。サフィックスには改行文字を使用できませんが、これはsearchFiles.txt
。
答え2
私はコメントで議論されたDOSスタイルの行の終わりを修正しましたが、searchFiles.txt
実際には空の行が含まれていないとします。
-name
テストでは、find
1つのファイル名パターンのみを使用します。パターンにはシェルのglob文字を含めることができますが、シェルがファイル名を早期に生成しないように、これらの文字を保護する必要があります。論理ORを使用してこれらのテストを複数組み合わせることができますが、-o
演算子の優先順位に注意する必要があります。
シェルが配列をサポートしている場合、これを行うには1つの方法があります(bash
ここではそれを使用していますが、同様のプロセスが他のシェルでも機能する必要があります)。
files=( -false )
while IFS= read -r f || [ -n "$f" ]; do files+=( -o -name "*$f"); done < searchFiles.txt
${files[@]}
これは代替サービスに拡張する必要があります。
-false -o -name *file.a.txt -o -name *file.b.txt -o -name *file.c.txt -o -name *file.d.txt -o -name *file.e.txt
find
その後、次のコマンドで使用できます。
find . \( "${files[@]}" \) -exec grep -Ho '[^ ]*_XYZ' {} +
(オプションを/dev/null
追加するためにダミーファイルを省略しました)。-H
ファイル数がsearchFiles.txt
多すぎると、制限によりこの方法が失敗する可能性がありますARG_MAX
。searchFiles.txt
複数の小さなファイルに分割することで、この制限を解決できます。
答え3
grep -f
含める名前を使用して、テキストファイルを介してファイル名をディレクトリにフィルタリングできます(部分一致を許可)。その後、これらのファイルは多数のgrep
検索パターンを介して最終的に小さいawk
。
GNUの使用bash
:
grep -Ff filenames.txt <(printf '%s\n' *) |
xargs -d '\n' grep -oH '[^[:space:]]*_XYZ$' | awk -F: '
{f[$1] = f[$1] ? f[$1] ";" $2 : $0}
END {for (x in f) print f[x]}'
いくつかの仮定(問題はまだ完全にはっきりしていません):
- ファイル名は便利に改行やコロン(出力用
grep
)がありません。スペースが処理されました。 - そこには一致するサブディレクトリはありません。それ以外の場合、2番目のサブディレクトリは
grep
メッセージを表示しますが、結果を返します。 - 2番目は、
grep
行の終わりにパターンを探します。単語の末尾を一致させたい場合は、それを修正できます。 -H
1つのファイルがあるgrep
極端な場合は、ファイル名を出力に印刷します(複数のファイルがある場合はデフォルトです)。