web-dev-qa-db-ja.com

日付のない行(スタックトレースなど)も含む複数のログファイルを日付でマージします

ログファイル、つまり時間で並べ替えられているが複数行もあるファイルをマージするにはどうすればよいですか。最初の行だけに時間があり、残りの行には時間がありません。

log1

01:02:03.6497,2224,0022 foo
foo1
2foo
foo3
01:04:03.6497,2224,0022 bar
1bar
bar2
3bar

log2

01:03:03.6497,2224,0022 FOO
FOO1
2FOO
FOO3

期待される結果

01:02:03.6497,2224,0022 foo
foo1
2foo
foo3
01:03:03.6497,2224,0022 FOO
FOO1
2FOO
FOO3
01:04:03.6497,2224,0022 bar
1bar
bar2
3bar

数字で始まるタイムスタンプ以外の行がない場合は、単純な sort -nm log1 log2 で十分です。

UNIX/Linuxのコマンドラインで仕事を成し遂げる簡単な方法はありますか?

編集これらのログファイルはギガバイト単位であることが多いため、(既にソートされている)ログファイルを再ソートせずに、またファイルをロードせずにマージを実行する必要があります完全にメモリに。

6

トリッキー。 dateとbash配列を使用することは可能ですが、これは実際のプログラミング言語から恩恵を受けるようなものです。たとえばPerlの場合:

$ Perl -ne '$d=$1 if /(.+?),/; $k{$d}.=$_; END{print $k{$_} for sort keys(%k);}' log*
01:02:03.6497,2224,0022 foo
foo1
2foo
foo3
01:03:03.6497,2224,0022 FOO
FOO1
2FOO
FOO3
01:04:03.6497,2224,0022 bar
1bar
bar2
3bar

コメント付きのスクリプトに凝縮されていないものと同じものがあります。

#!/usr/bin/env Perl

## Read each input line, saving it 
## as $_. This while loop is equivalent
## to Perl -ne 
while (<>) {
    ## If this line has a comma
    if (/(.+?),/) {
        ## Save everything up to the 1st 
        ## comma as $date
        $date=$1;
    }
    ## Add the current line to the %k hash.
    ## The hash's keys are the dates and the 
    ## contents are the lines.
    $k{$date}.=$_;
}

## Get the sorted list of hash keys
@dates=sort(keys(%k));
## Now that we have them sorted, 
## print each set of lines.
foreach $date (@dates) {
    print "$k{$date}";
}

これは、すべての日付行とのみ日付行にコンマが含まれていることを前提としていることに注意してください。そうでない場合は、代わりにこれを使用できます。

Perl -ne '$d=$1 if /^(\d+:\d+:\d+\.\d+),/; $k{$d}.=$_; END{print $k{$_} for sort keys(%k);}' log*

上記のアプローチでは、ファイルの内容全体をメモリに保持する必要があります。それが問題である場合、これは問題ではありません:

$ Perl -pe 's/\n/\0/; s/^/\n/ if /^\d+:\d+:\d+\.\d+/' log* | 
    sort -n | Perl -lne 's/\0/\n/g; printf'
01:02:03.6497,2224,0022 foo
foo1
2foo
foo3    
01:03:03.6497,2224,0022 FOO
FOO1
2FOO
FOO3    
01:04:03.6497,2224,0022 bar
1bar
bar2
3bar

これは、改行を\0に置き換えることにより、連続するタイムスタンプ間のすべての行を1行にまとめるだけです(これがログファイルに含まれる可能性がある場合は、決して存在しないことがわかっている文字のシーケンスを使用してください)。これはsortに渡され、次にtrに渡されて行が返されます。


OPによって非常に正確に指摘されているように、上記のすべてのソリューションを再利用する必要があり、ファイルをマージできることを考慮していません。これは機能しますが、他のファイルとは異なり、2つのファイルでのみ機能します。

$ sort -m <(Perl -pe 's/\n/\0/; s/^/\n/ if /^\d+:\d+:\d+\.\d+/' log1) \
            <(Perl -pe 's/\n/\0/; s/^/\n/ if /^\d+:\d+:\d+\.\d+/' log2) | 
    Perl -lne 's/[\0\r]/\n/g; printf'

また、Perlコマンドをエイリアスとして保存すると、次のようになります。

$ alias a="Perl -pe 's/\n/\0/; s/^/\n/ if /^\d+:\d+:\d+\.\d+/'"
$ sort -m <(a log1) <(a log2) | Perl -lne 's/[\0\r]/\n/g; printf'
10
terdon

それを行う1つの方法(改行置換のアイデアを@terdonに感謝します):

  1. これらの改行をたとえば次のように置き換えることにより、すべてのマルチラインをシングルラインに連結します。各入力ファイルのNUL
  2. sort -m置き換えられたファイル
  3. NULを改行に戻します

複数行の連結は複数回使用されるため、alias離れてみましょう。

alias a="awk '{ if (match(\$0, /^[0-9]{2}:[0-9]{2}:[0-9]{2}\\./, _))\
    { if (NR == 1) printf \"%s\", \$0; else printf \"\\n%s\", \$0 }\
    else printf \"\\0%s\", \$0 } END { print \"\" }'"

上記のエイリアスを使用したマージコマンドは次のとおりです。

sort -m <(a log1) <(a log2) | tr '\0' '\n'

シェルスクリプトとして

このように使うために

merge-logs log1 log2

私はそれをシェルスクリプトに入れました:

x=""
for f in "$@";
do
 x="$x <(awk '{ if (match(\$0, /^[0-9]{2}:[0-9]{2}:[0-9]{2}\\./, _)) { if (NR == 1) printf \"%s\", \$0; else printf \"\\n%s\", \$0 } else printf \"\\0%s\", \$0 } END { print \"\" }' $f)"
done

eval "sort -m $x | tr '\0' '\n'"

邪悪なevalに頼らずに、可変数のログファイルを提供できるかどうかはわかりません。

1

Javaがオプションである場合は、 log-merger :を試してください。

Java -jar log-merger-0.0.3-jar-with-dependencies.jar -f 1 -tf "HH:MM:ss.SSS" -d "," -i log1,log2
01:02:03.6497,2224,0022 foo
foo1
2foo
foo3
01:03:03.6497,2224,0022 FOO
FOO1
2FOO
FOO3
01:04:03.6497,2224,0022 bar
1bar
bar2
3bar
0
siom