bash (grep/sed/etc) を使用して、ログファイルの 2 つのタイムスタンプ間のセクションを取得するにはどうすればよいですか?
質問
一連のメールログがあります。mail.log mail.log.0 mail.log.1.gz mail.log.2.gz
これらの各ファイルには、次のようなタイムスタンプで始まる時系列にソートされた行が含まれています。
5月3日 13:21:12 ...
特定の日付/時刻以降と別の日付/時刻以前のすべてのログ エントリを簡単に取得するにはどうすればよいですか? バッシュを使用する (および関連するコマンドラインツール)すべての行を比較せずに?変更前と変更後の日付は、ログファイル内のどのエントリとも正確に一致しない可能性があることに注意してください。
最初の行のオフセットは開始タイムスタンプより大きく、最後の行のオフセットは終了タイムスタンプより小さく、何らかの方法でそのセクションを切り取る必要があるように思えます。
解決 2
これを行う方法の基本的なアイデアを 1 つ示します。
- を調べてください ファイルの日付スタンプ 無関係かどうかを確認するために
- それであれば できた 関連性があれば、必要に応じて解凍し、 最初と最後の行 ファイルの開始時刻または終了時刻が含まれているかどうかを確認します。
- その場合は、 再帰関数 開始時間がファイルの前半に含まれているか後半に含まれているかを判断します。再帰関数を使用すると、約 20 件の比較で 100 万行のログファイルから任意の日付を見つけることができると思います。
- 最初のエントリのオフセットから最後のエントリのオフセットまでの順序でログファイルをエコーします (それ以上の比較は行いません)
私が知らないことは次のとおりです:ファイルの n 行目を読み取る最適な方法 (どのように効率的に使用できるか) 尾部 n+**n|頭 1**?)
何か助けはありますか?
他のヒント
最小/最大日付を「エポックからの秒数」に変換します
MIN=`date --date="$1" +%s`
MAX=`date --date="$2" +%s`
各ログ行の最初の n
単語を同じものに変換します
L_DATE=`echo $LINE | awk '{print $1 $2 ... $n}'`
L_DATE=`date --date="$L_DATE" +%s`
MIN
に達するまで、行を比較して捨てます。
if (( $MIN > $L_DATE )) ; then continue ; fi
MAX
に達するまで行を比較して印刷します。
if (( $L_DATE <= $MAX )) ; then echo $LINE ; fi
MAX
を超えたら終了します。
if (( $L_DATE > $MAX )) ; then exit 0 ; fi
スクリプト全体 minmaxlog.sh は次のようになります。
#!/usr/bin/env bash
MIN=`date --date="$1" +%s`
MAX=`date --date="$2" +%s`
while true ; do
read LINE
if [ "$LINE" = "" ] ; then break ; fi
L_DATE=`echo $LINE | awk '{print $1 " " $2 " " $3 " " $4}'`
L_DATE=`date --date="$L_DATE" +%s`
if (( $MIN > $L_DATE )) ; then continue ; fi
if (( $L_DATE <= $MAX )) ; then echo $LINE ; fi
if (( $L_DATE > $MAX )) ; then break ; fi
done
このファイルで実行しました minmaxlog.input 、
May 5 12:23:45 2009 first line
May 6 12:23:45 2009 second line
May 7 12:23:45 2009 third line
May 9 12:23:45 2009 fourth line
June 1 12:23:45 2009 fifth line
June 3 12:23:45 2009 sixth line
このように、
./minmaxlog.sh "May 6" "May 8" < minmaxlog.input
(範囲内にあるかどうかを判断するために)必要な範囲内のすべての行を確認する必要があるため、ファイル内のすべての行ではないことを推測しています。最低限、ファイル内の範囲外の最初の行を含むすべての行を確認する必要があります(行は日付/時刻の順序であると想定しています)。
これはかなり単純なパターンです:
state = preprint
for every line in file:
if line.date >= startdate:
state = print
if line.date > enddate:
exit for loop
if state == print:
print line
必要に応じて、awk、Perl、Python、さらにはCOBOLで記述できますが、ロジックは常に同じです。
最初に行番号を見つけて(たとえばgrepで)、その行範囲を盲目的に印刷するだけでは、grepはすべての行( all だけでなく、範囲外の最初の行まで、おそらく 2回、最初の行に1つ、最後の行に1つ)。
これが非常に頻繁に行われることである場合は、「ファイルを安定させるたびに」「一度行う」から「一度行う」に努力をシフトすることを検討することができます。例としては、ログファイルの行をデータベースにロードし、日付/時刻でインデックスを付けます。
セットアップには少し時間がかかりますが、クエリが非常に高速になります。私は必ずしもデータベースを提唱しているわけではありません。おそらく、ログファイルを1時間ごとのログに分割することで同じ効果を得ることができます。
2009/
01/
01/
0000.log
0100.log
: :
2300.log
02/
: :
その後、一定時間、どこから見るか、どこで見るかを正確に知ることができます。 2009/01 / 01-15:22
から 2009/01 / 05-09:07
の範囲は次のようになります。
- ファイルの一部(最後のビット)
2009/01/01 / 1500.txt
。 - すべてのファイル
2009/01/01/1 [6-9] *。txt
。 - すべてのファイル
2009/01/01/2 * .txt
。 - すべてのファイル
2009/01/0 [2-4] / *。txt
。 - すべてのファイル
2009/01/05/0 [0-8] *。txt
。 - ファイルの一部(最初のビット)
2009/01/05 / 0900.txt
。
もちろん、毎回手動で行うのではなく、これらの行を返すスクリプトを作成します。
おそらくこれを試すことができます:
sed -n "/BEGIN_DATE/,/END_DATE/p" logfile
Bash環境では可能かもしれませんが、文字列と日付を扱うためのサポートが組み込まれたツールを実際に活用する必要があります。たとえば、RubyにはDate形式を解析する機能が組み込まれているようです。その後、簡単に比較可能なUnixタイムスタンプ(エポックからの秒数を表す正の整数)に変換できます。
irb> require 'time'
# => true
irb> Time.parse("May 3 13:21:12").to_i
# => 1241371272
その後、Rubyスクリプトを簡単に作成できます。
- 開始日と終了日を指定します。これらをこのUnixタイムスタンプ番号に変換します。
- ログファイルを1行ずつスキャンし、日付をUnixタイムスタンプに変換し、開始日と終了日の範囲内にあるかどうかを確認します。
注:整数の比較は非常に簡単で効率的であるため、最初にUnixタイムスタンプ整数に変換すると便利です。
「すべての行を比較せずに」と述べました。 「推測」するのは難しいでしょう。ログファイルのどこでエントリが古すぎるか、または間にあるすべての値をチェックせずに新しくなりすぎます。ただし、実際に単調に増加する傾向がある場合、行の解析を停止するタイミングをすぐに知ることができます。次のエントリが新しすぎる(またはデータのレイアウトに応じて古い)場合は、すぐに検索を停止できるためです。それでも、目的の範囲で最初の行を見つけるには問題があります。
あなたの編集に気付きました。これが私が言うことです:
開始エントリと終了エントリを効率的に見つけることを本当に心配している場合は、それぞれに対してバイナリ検索を実行できます。または、bashツールでやり過ぎまたは難しすぎると思われる場合は、行の5%のみを読み取り(20回に1回)、すぐに正確な回答に近づき、必要に応じてそれを改良することができます。これらは、パフォーマンスを改善するためのいくつかの提案です。
このスレッドは古いことは知っていますが、最近、私のニーズに合った1行のソリューションを見つけた後、つまずいたばかりです。
awk -v ts_start="2018-11-01" -v ts_end="2018-11-15" -F, '$1>=ts_start && $1<ts_end' myfile
この場合、ファイルにはカンマ区切りの値と最初のフィールドのタイムスタンプを持つレコードがあります。開始タイムスタンプと終了タイムスタンプに任意の有効なタイムスタンプ形式を使用でき、必要に応じてこれらをシェル変数に置き換えます。
新しいファイルに書き込みたい場合は、上記の最後に追加された通常の出力リダイレクト(&gt; newfile
)を使用します。