6

Как я могу объединить файлы журнала, то есть файлы, которые отсортированы по времени, но которые также имеют многострочные, где только первая строка имеет время, а остальные нет.

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 Linux, чтобы сделать работу?

Редактировать Поскольку эти файлы журналов часто находятся в гигабайтах, объединение должно выполняться без повторной сортировки (уже отсортированных) файлов журнала и без полной загрузки файлов в память.

3 ответа3

9

Tricky. Хотя это возможно с использованием массивов 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 (если это может быть в ваших файлах журнала, используйте любую последовательность символов, которую вы знаете, никогда не будет). Это передается для sort а затем tr чтобы вернуть строки.


Как очень правильно указал OP, все вышеперечисленные решения должны быть восстановлены и не принимать во внимание, что файлы могут быть объединены. Вот тот, который делает, но который в отличие от других будет работать только на двух файлах:

$ 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 за идею замены новой строки):

  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 .

0

Если вы можете использовать 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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .