ログファイル、つまり時間で並べ替えられているが複数行もあるファイルをマージするにはどうすればよいですか。最初の行だけに時間があり、残りの行には時間がありません。
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のコマンドラインで仕事を成し遂げる簡単な方法はありますか?
編集これらのログファイルはギガバイト単位であることが多いため、(既にソートされている)ログファイルを再ソートせずに、またファイルをロードせずにマージを実行する必要があります完全にメモリに。
トリッキー。 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'
それを行う1つの方法(改行置換のアイデアを@terdonに感謝します):
sort -m
置き換えられたファイル例
複数行の連結は複数回使用されるため、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
に頼らずに、可変数のログファイルを提供できるかどうかはわかりません。
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