1

把这归结为我 25 年前知道的事情,然后忘记了......

我有来自 Windows 事件日志的日志输出,并且无法控制时间戳格式(如果我这样做了,我会选择像 YYYYMMDD HH24MMSS 这样合理的东西,所以当它被视为字符串时很容易排序。
我确信有一个简单的使用 sed 或某种排序参数执行此操作的方法。有人对此有快速解决方案吗?

样本数据:

SERVER01,1/1/2013 12:00:01 AM,8,FOO,TOO
SERVER01,4/10/2012 4:43:06 PM,8,FOO,TOO
SERVER01,4/11/2012 4:43:06 PM,8,FOO,TOO
SERVER01,4/9/2012 4:43:06 PM,8,FOO,TOO
SERVER02,12/31/2012 11:59:59 PM,8,FOO,TOO
SERVER02,4/10/2012 4:43:06 PM,8,FOO,TOO
SERVER02,4/9/2012 4:43:06 PM,8,FOO,TOO

所需顺序:

SERVER01,4/9/2012 4:43:06 PM,8,FOO,TOO
SERVER02,4/9/2012 4:43:06 PM,8,FOO,TOO
SERVER01,4/10/2012 4:43:06 PM,8,FOO,TOO
SERVER02,4/10/2012 4:43:06 PM,8,FOO,TOO
SERVER01,4/11/2012 4:43:06 PM,8,FOO,TOO
SERVER02,12/31/2012 11:59:59 PM,8,FOO,TOO
SERVER01,1/1/2013 12:00:01 AM,8,FOO,TOO

重新格式化时间戳是可以的,甚至是可取的。我只是不知道怎么做。这需要在 Windows 上运行,并且我有 Cygwin 可用(并且已经在使用它对同一个文件进行一些 grep 过滤)。

4

5 回答 5

2

这是一个 Perl 脚本,它为每一行添加了一个可排序的时间戳:

#!/usr/bin/perl

use strict;
use warnings;

while (<>) {
    my $timestamp = (split /,/)[1];
    my($mon, $mday, $year, $hour, $min, $sec, $ampm) =
        $timestamp =~ m{^(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)\s+(AM|PM)};
    die "Can't parse timestamp on line $.\n" if not defined $ampm;
    if ($ampm eq 'AM') {
        $hour = 0 if $hour == 12;
    }
    else {
        $hour += 12 if $hour != 12;
    }

    printf "%04d-%02d-%02d %02d:%02d:%02d,%s",
           $year, $mday, $mon, $hour, $min, $sec, $_;
}

对于您的示例数据,它会产生以下输出:

2013-01-01 00:00:01,SERVER01,1/1/2013 12:00:01 AM,8,FOO,TOO
2012-10-04 16:43:06,SERVER01,4/10/2012 4:43:06 PM,8,FOO,TOO
2012-11-04 16:43:06,SERVER01,4/11/2012 4:43:06 PM,8,FOO,TOO
2012-09-04 16:43:06,SERVER01,4/9/2012 4:43:06 PM,8,FOO,TOO
2012-31-12 23:59:59,SERVER02,12/31/2012 11:59:59 PM,8,FOO,TOO
2012-10-04 16:43:06,SERVER02,4/10/2012 4:43:06 PM,8,FOO,TOO
2012-09-04 16:43:06,SERVER02,4/9/2012 4:43:06 PM,8,FOO,TOO

要按日期对样本数据进行排序,假设上面的 Perl 脚本是foo.pl

./foo.pl in.txt | sort | sed 's/^[^,]*,//'

这会产生与您问题中指定的输出相同的输出。

如果您愿意,对 Perl 脚本稍作调整可以避免使用sortandsed命令,但代价是在内存中存储、修改和排序整个文件,这对于非常大的输入可能是个问题:

#!/usr/bin/perl

use strict;
use warnings;

my @lines = ();

while (<>) {
    my $timestamp = (split /,/)[1];
    my($mon, $mday, $year, $hour, $min, $sec, $ampm) =
        $timestamp =~ m{^(\d+)/(\d+)/(\d+)\s+(\d+):(\d+):(\d+)\s+(AM|PM)};
    die "Can't parse timestamp on line $.\n" if not defined $ampm;
    if ($ampm eq 'AM') {
        $hour = 0 if $hour == 12;
    }
    else {
        $hour += 12 if $hour != 12;
    }

    push @lines, sprintf "%04d-%02d-%02d %02d:%02d:%02d,%s",
                         $year, $mday, $mon, $hour, $min, $sec, $_;
}

@lines = sort @lines;
foreach (@lines) {
    s/^[^,]*,//;
}
print @lines;
于 2012-05-03T08:36:51.617 回答
2

awk 肯定是 cygwin 附带的,它可以将日期移动到可排序的格式到行的前面(我已经将新手退出到 awk,所以这很丑我敢肯定但它有效),所以你可以将日志记录到此脚本中,然后进行简单排序

#! /bin/awk -f
BEGIN {
   FS=",";
}
{
   linedate=$2;
   split(linedate,datetime," ");
   split(datetime[1],datepieces,"/");
   date=sprintf( "%d/%02d/%02d", datepieces[3], datepieces[1], datepieces[2]);
   split(datetime[2],timepieces,":");
   time=sprintf( "%02d:%02d:%02d", timepieces[1], timepieces[2], timepieces[3] );
   print date " " time " " datetime[3] "," $1 "," $3 "," $4 "," $5;
}
于 2012-05-07T20:33:51.037 回答
1

我必须做这样的事情——我有多个带有 log4j 时间戳的日志文件需要合并。

我确定的解决方案是gawk将时间戳转换为从纪元开始的毫秒数,并在所有行前面加上它。之后使用sort就很简单了。

我转换为上述格式是因为我还想对 t9imestamp 值进行一些算术运算。您也许可以走捷径并转换为yymmddXhhmmssin sedX用于和am/pm用于_ 0_ _am1pm

进一步考虑,您也最好使用gawk, not sed,以便您可以使用printf来获取零填充数字。

于 2012-04-30T17:59:30.650 回答
1

试试这个unix命令。

我只完成了时间戳部分。

输入

1/1/2013 12:00:01 AM
4/10/2012 4:43:06 PM
4/9/2012 4:43:06 PM
12/31/2012 11:59:59 PM
4/10/2012 4:43:06 PM
4/9/2012 4:43:06 PM

Unix 命令

$>sort -t "/"  -k 1.8,1.4 Input| sort -t ":" -r -k 1 -k 2.1,2.2 -k 3.1,3.2 | sort -t " " -r -k 3.1

输出

4/9/2012 4:43:06 PM
4/9/2012 4:43:06 PM
4/10/2012 4:43:06 PM
4/10/2012 4:43:06 PM
12/31/2012 11:59:59 PM
1/1/2013 12:00:01 AM

您可以根据需要修改脚本。

于 2012-05-08T11:35:14.483 回答
1

克里斯,

您可能需要使用以下提供的代码,特别是查看 sort 命令。

我编写的 awk 脚本清理了 Windows Server 2003 “时间戳”,以便单个数字预先填充零。更改生成的健全时间戳的格式非常容易。

应该使用默认的 cygwin 安装。

让我知道您的想法,可能需要一些 tweeking,我很乐意根据您的反馈来做。

$ gawk -f foo.awk event_log.txt | sort  -n -k2
SERVER01,04/09/2012 04:43:06 PM,8,FOO,TOO
SERVER01,04/10/2012 04:43:06 PM,8,FOO,TOO
SERVER01,04/11/2012 04:43:06 PM,8,FOO,TOO
SERVER02,04/09/2012 04:43:06 PM,8,FOO,TOO
SERVER02,04/10/2012 04:43:06 PM,8,FOO,TOO
SERVER02,12/31/2012 11:59:59 PM,8,FOO,TOO
SERVER01,01/01/2013 12:00:01 AM,8,FOO,TOO

foo.awk 在哪里

BEGIN { FS = "," }
{ print $1"," prepadDate($2) "," $3 "," $4 "," $5 }

#
# Returnes a useful timestamp given the timestamp received in event logs on Windows Server 2003
#
function prepadDate(winSrvr2003ts) {

        padded_day = ""
        padded_month = ""
        year = ""

        padded_date = ""
        split(winSrvr2003ts,numbers," ")

        split(numbers[1], date, "/")
        split(numbers[2], time, ":")
        antePostMeridian = numbers[3]

        padded_day = prePadAZero(date[1])
        padded_month = prePadAZero(date[2])
        year = date[3]
        padded_hour = prePadAZero(time[1])
        minute = time[2]
        seconds = time[3]

        #
        # Alter the return statememnt to format the timestamp according to your needs
        # rememebering that string concatenation in gawk is simply a space.
        #
        return padded_day "/" padded_month "/" year " " padded_hour ":" minute ":" seconds " " antePostMeridian
}

#
# Prepend a zero to number if it is a single digit
#
function prePadAZero(number){

        if (length(number) == 1)
                padded = "0" number
        else
                padded = number

        return padded
}
于 2012-05-08T19:12:42.407 回答