私のフォルダには5000個のファイルがあります。ファイル名はXX0000001~XX0005000です。
各ファイルから単語を取得し、その単語と他のファイル(target.txt)の次の行をgrepしようとしています。
私のXX*ファイルの中には、約30,000語が含まれています。
これを行う方法はありますか?
私は試した:
start_number=0000001
end_number=0005000
words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"
for ((i=start_number; i <=end_number; i++)); do
filename="XX$(printf "%07d" "$i")"
output_file="$output_folder/output_${filename}.txt"
while read -r word; do
awk -v word="$word" '{for (i=1; i<=NF; i++) if($1 ~ word) {print; next}}' "$filename" >> "$output_file"
done < "$words_file"
done
これを行うより速い方法がありますか?私のターゲットファイルには何百万もの検索があります。正確なターゲットファイルサイズは20GB、106441678行です。
例: XX0000001 ファイルは次のようになります。
Big1 Big5 Big7 Big10 Big11
(そして単語が多すぎます。一部のXXファイルには最大30,000単語が含まれる場合があります。)
Target.txt ファイルは次のとおりです。
#Big1
This_is_a_file_containing_xxxxx
#Big2
This_is_a_file_containing_xxxxx
#Big3
This_is_a_file_containing_xxxxx
#Big4
This_is_a_file_containing_xxxxx
#Big5
This_is_a_file_containing_xxxxx
#Big6
This_is_a_file_containing_xxxxx
#Big7
This_is_a_file_containing_xxxxx
#Big8
This_is_a_file_containing_xxxxx
#Big9
This_is_a_file_containing_xxxxx
#Big10
This_is_a_file_containing_xxxxx
#Big11
This_is_a_file_containing_xxxxx
#Big12
This_is_a_file_containing_xxxxx
答え1
私のフォルダには5000個のファイルがあります。ファイル名はXX0000001~XX0005000です。
ファイルセットを繰り返すには、for f in XX0*
ここと同じシェルglobを使用します。一連の数字を繰り返す必要がある場合、Bash は数字を 8 進数で表示するので、前の 0 に注意する必要があります。たとえば、ループを試して、数字を印刷して最後の数字を見てください。
start_number=0000001
end_number=0005000
for ((i=start_number; i <=end_number; i++)); do
echo $i
done |tail -1
出力はyes 2560
、noです5000
。しかし、zshではそうではなく、どのシェルを実行しているのか言及していませんが、問題は指摘する価値があります。
ここでは、
while read -r word; do
awk -v word="$word" '{for (i=1; i<=NF; i++) if($1 ~ word) {print; next}}' "$filename" >> "$output_file"
done < "$words_file"
これが何をしているのかはわかりませんが、AWKスクリプトでは入力行のすべてのフィールドを繰り返しますが、$1
ループ内ではフィールド1()のみを参照していることがわかりました。
ここで、ファイルが次のようになるとします。
% cat XX0000001
Big1 Big7
% cat XX0000002
Big5 Big10
% cat target.txt
#Big1
This_is_a_file_containing_xxxxx
#Big2
This_is_a_file_containing_xxxxx
[...]
Big1
つまり、ファイルの1行に複数の異なるパターン(たとえば、および)があります(たとえば、1行に1つずつ)。また、どのパターンにも一致する行を見つけて、次の行と一緒に印刷したいと思います。Big7
XX0*
target.txt
これで、標準のgrepは一致後に「1行」を印刷でき、同時に複数のパターンを見つけることができます。この-f
オプションは、行がパターンを形成するファイルの名前を使用するため、XX0*
各パターンが単一の行として表示されるようにファイルを前処理する必要があります。すべてのスペースを改行文字に変更するだけですtr
。最も簡単な方法は、プロセスオーバーライドを使用してtr
toの出力をgrep
ファイルとして使用することですが、一時ファイルを使用することもできます(または出力をtoにパイプすることもできますtr
)grep -f -
。
たとえば、
% grep -A1 -f <(tr ' ' '\n' < XX0000001 ) target.txt
#Big1
This_is_a_file_containing_xxxxx
--
#Big7
This_is_a_file_containing_xxxxx
--
#Big10
This_is_a_file_containing_xxxxx
もちろん、Big1
その行でもパターンが発生するため、#Big10
一致します。 (しかし、grepオプションを使用してフルワードマッチングを要求できます-w
。)区切り文字を削除するには、--
結果をパイプすることができますgrep -ve --
。
これがどれだけ効率的かはgrepの実装によって異なりますが、この目的のために設計されたツールとして、シェルスクリプトで同じことを行うよりも最適化の可能性が高くなります。シェルスクリプトは遅いです。すべてのパターンがフォーマットであれば、Big*
共通部分を一度だけ見つけることが賢明です。パターンリストを単一のパターンに変更することもできます。Big(1|5|7|10)
正規表現エンジンでうまく機能できることを願っています。
答え2
多数のクエリに対して非常に大きなファイルを検索しており、シェル/標準ツールで「クイック」ソリューションを見つける可能性はほとんどありません。つまり、あなたのアプローチは特に非効率的だと思います。
おそらく次のようになります(テストされていません):
words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"
for filename in XX* ; do
output_file="$output_folder/output_${filename}.txt"
grep -f "$filename" -Fx -A1 "$words_file" > "$output_file"
done
しかし、正直なところ、その速度も速くはないと思います。
これはマルチスレッドであり、スループットを向上させることができます。
これはマルチスレッドの選択肢です。threads
この値を動作環境に合わせて調整する必要があります。
threads=4
words_file=target.txt
output_folder="output_results"
mkdir -p "$output_folder"
find . -maxdepth 1 -type f -name 'XX*' -print0 | \
xargs -I% -0 -P$threads bash -c '
file="%"
output_file="$output_folder/output_${file#./}.txt"
grep -f "%" -Fx -A1 "$words_file" > "$output_file"
'
これによって得られる利点は、使用可能なメモリ、CPUコアの数、ストレージ速度、およびサーバーの他のアクティビティなどのハードウェア要素によって異なります。
答え3
あなたの質問が不明瞭で予想される出力を提供しないため、何が必要かを明確にするのに役立ちません。潜在的な解決策が機能するかどうかをテストする方法はありませんが、awkを使用しておそらく望むかもしれません。 :
awk '
FILENAME != ARGV[ARGC-1] {
for ( i=1; i<=NF; i++ ) {
words[$i]
}
next
}
f {
print
}
{
f = 0
for ( word in words ) {
if ( $0 ~ word ) {
print
f = 1
next
}
}
}
' some_folder/XX* target.txt
XX*
ファイルが多すぎる場合は、次ARG_MAX
のように変更してください。
printf '%s\n' some_folder/XX* |
awk '
FILENAME == "-" {
ARGV[ARGC++] = $0
next
}
FILENAME != ARGV[ARGC-1] {
for ( i=1; i<=NF; i++ ) {
words[$i]
}
next
}
f {
print
}
{
f = 0
for ( word in words ) {
if ( $0 ~ word ) {
print
f = 1
next
}
}
}
' - target.txt