私は現在、1つ(または複数)のサーバーログから関連する行をすべて読み、順番に表示するスクリプトを作成しようとしています。シェルスクリプトに初めて遭遇して間違いをたくさんしましたが、今ではスクリプトが遅すぎて正常に動作しません。
これが私の問題です。まず、エラーコードでいくつかのレコードを識別します。そのエラーコードを含む行は非構造化xmlです。このエラーに関連する他の行を取得するには、XMLでIDを含む他のタグを見つける必要があります。このIDは、タイムスタンプと関連するスレッドまたはジョブ番号を持つ他の行と一致します。これら2つの値を使用すると、理論的には、エラーメッセージに「近い」スレッド番号を持つすべての行を見つけて、エラーに関連するすべての行を取得できます。理論的にはファイルサイズは150MBだからです。
だから私は次のようにコードを書いた。
grep -c
エラー数を取得し、grep
そのエラーを含む各行を を使用してファイルにsed
書き込み、ファイルを繰り返してIDを見つけて配列に書き込みます。
その後、for
IDを繰り返して
grep
初期ファイルを使用してID、ジョブ、およびタイムスタンプを含む行を検索する- ジョブとタイムラインを変数に保存
- その後、元のファイルをもう一度繰り返して、自分の操作を含む各行と私の参照に近いタイムスタンプ(秒単位)を見つけます。
...ここでは遅くなって死にます。
sed
sループでsを実行するのは理想的ではありませgrep
んが、特定の時点でファイルを読み取るためのツールが見つかりませんでした(xy行などを使用)。
その行が私のエラーコードを含む行、または私のIDとタスクを含む行で始まらず、そこに関連していない行が複数ある可能性があるため、最初の行を知っていますgrep
。そして、テキストの最後の行にもかかわらず、それを有利に使用する方法を見つけることができませんでした。
どんな助けでも大変感謝します。ありがとうございます。
編集:はい、申し訳ありません。少しあいまいです。これで、外部プログラムの呼び出し回数を減らし、パフォーマンスが大幅に向上しました。私が以前にしたことは次のとおりです。
while read Buffer; do
TimeStamp=$(echo $Buffer | sed 's/blabla(Timestamp)blabla/\1/g')
[ TimeStamp -ge CompareStamp ] || continue
echo $Buffer >>./mylog
done
だから私は各行を見て、その行のタイムスタンプが私が保存したタイムスタンプに近いことを確認しています。遅すぎます。コードを行のタイムスタンプ部分のみを比較し、参照の前後の2番目または2番目に合っていることを確認する3つのgrepに置き換えました。これはうまくいきますが、本当に醜いです。また、サーバーが3秒以内にこのタスクの複数のケースを処理できるため、参照用の行のみを見つけることは保証できません。
私のログは次のとおりです。
timestamp first entry task blabla
timestamp blabla task blabla
timestamp blabla task blabla
timestamp blabla task reference
timestamp blabla task blabla
timestamp blabla task blabla
<xml><error>error</error></error>
timestamp blabla task blabla
timestamp last entry task blabla
最後のものと最初のものが何であるかを知っているので、検索できます。同じジョブを含むブロックはログファイル内の対応するブロックの前後に繰り返され、他のジョブの行もそのブロック内にあります。だから最初のステップは、そのタスクを含むすべての行を別々のファイルに入れて、より少ないデータを収集し、他のタスクについて心配しないことでした。
したがって、通常のプログラミングでは、1行ずつ読み込み、それが最初の項目であることを確認し、常に最後の最初の項目の位置を保存してから参照を見つけて、場所の保存に戻って各項目行を読み込みます。最後の項目が見つかるまで。スクリプトを人間のペースで遅らせずにシェルを使用してこの効果を得る方法はありますか?
Edit2:良いです。ほとんどは次のとおりです。正規表現と検索文字列を削除しました。
grep 'Errorcode' logfile >> ./grablog
NumOfErrors=$(grep -c 'Errorcode' grablog)
AllPrimaryReferences=($(sed -r 's/^.*(<Referencetag>)([^<]*)(<\/Referencetag>).*$/\2/g' grablog))
j=0
for ((i=0;i<NumOfErrors;i=i+2))
do
Reference=$(grep 'blablablabla = '${AllPrimaryRefernces[i]} logfile)
TimeStamp=$(echo "$Reference" | sed -r 's/^ganze Zeile/timestamp/g')
AllTasks[j]=$(echo "$Reference" | sed -r 's/ganze Zeile/Reference/g')
grep "${AllTasks[j]}" logfile >>./tempfile
CompTimeStamp=$(date -d "$TimeStamp" +%Y-%m-%d' '%X)
grep 'CompTimeStamp' tempfile >>./output
rm tempfile
let j++
done
rm grablog
´´´
答え1
スクリプトには、無駄のように見え、効率性という名前で再配置できる部分があります。
配列の構築
grep 'Errorcode' logfile >> ./grablog
NumOfErrors=$(grep -c 'Errorcode' grablog)
AllPrimaryReferences=($(sed -r 's/^.*(<Referencetag>)([^<]*)(<\/Referencetag>).*$/\2/g' grablog))
後で「NumOfErrors」を終了条件として使用し、+=2
whileループを使用して配列を繰り返します。で直接配列の長さにアクセスできます${#arrayname[@]}
。
つまり、一度だけ読む必要があるか、grablog
パイプから読み取る必要があるため、一時ファイルが削除されます。
AllPrimaryReferences=( $(grep 'Errorcode' logfile | sed -r '...' | uniq ) )
NumOfRefs=${#AllPrimaryReferences[@]}
uniq
重複した項目を+=2
スキップするために使用する代わりに、削除する必要があります。私は彼らが常に互いに対していると仮定しています(ログファイルを見ることができないので確認できません)。そうでない場合は、sort
以前に追加できますが、uniq
項目が多いと速度が遅くなります。
ただし、ループ内のスクリプト部分は、より注意を払う必要がある部分です。
変数の抽出sed
sed
各ループを2回実行すると、速度が遅くなるのではないかと心配です。これを防ぐためのいくつかの方法は次のとおりです。
二人が一つでsed
sed
1つのコマンドでライン全体を配列にリンクして2つの変数を抽出できますか?
ts_task=( $(echo "$Reference" | sed 's/\(timestamp_regex\).*\(task_regex\)/\1 \2/' ) )
timestamp="${ts_task[0]}"
task="${ts_task[1]}"
bash
代替の使用
sed
おそらくまったく必要なく、$References
必要な部分文字列を抽出することもできます。パラメータ拡張。例えば。最初のスペースの前のすべての内容を抽出します。
timestamp=${References%% *}
名前付きパイプの使用
気になるならスタート sed
ゆっくりしてください、あなたできるループが始まる前にバックグラウンドで開始し、名前付きパイプを使用してメインループと通信します。読み取り/書き込みが中断されたプロセスを覚えていて、それをバックグラウンドに置くか、適切に待つ必要があるため、これは面倒です。
入力ファイル全体を2回読み、毎回1回繰り返します。
これはおそらく最も遅い部分であり、改善の可能性が最も高い部分です。
参照から詳細を抽出する
あなたのコメントで引用符ごとに1行しかないと言われましたblablabla
。これは、最初のラウンドがgrep
入力ファイル全体を表示し、一致するエントリで停止し、次のラウンドが最初から始まり、次のエントリを見つけることを意味します。
参照ごとにこれらの行が1つしかない場合、完全な「配列構築」ステップはおそらく不要で、ループを直接入力できます。
grep 'blablablabla = ' logfile | # match each line that defines a primary reference
sed '...' | # command to extract just the timestamp and taskname
while read ts task ; do # assign the two required variables
# use ts and task to extract everything as before
done
これはgrep
、2 つの s の最初がループの外にあるので、一度だけ実行されることを意味します。
結果のフィルタリング
行のサブセットをダンプし、tempfile
しばらくgrep
の間結果を実行します。以前と同様に、これらの2つの手順をパイプラインに結合すると、一時ファイルの使用を回避できます。
grep "$task" logfile | grep "$timestamp" >> output
または、行全体の形式について十分に知っている場合
grep "$timestamp <match other part of line> $task" logfile >> output
完全なアルゴリズム
これらのすべての改善にもかかわらず、ログファイル全体を再読み込みし、すべての参照/操作に対してそのファイルのすべての行を再確認するボトルネックが発生する可能性があります。これは、必要な行がログファイルのどこにでも任意の順序で表示される場合に適しています。これはすべての行を見つけるための無差別の代入方法なので、時間がかかります。
しかし、あなたが持っています。ヒント構造とコンテキスト(「最初と最後の行」、「最初の項目」)を使用すると、よりスマートなアプローチが可能になります。入力ファイルの構造/順序についてもっと知っている場合は、作業の重複を避けるために追加のショートカットを使用できます。
「保存場所」と「保存場所に戻る」方法についてお問い合わせいただきました。 grep -n
各一致の行番号を報告します。tail
(1)このコマンドはファイルの先頭から複数行をスキップできますが、改行を見つけるにはファイルを再読み込みする必要があります。ファイル全体while read
をループとして処理できますか?