使用join对日志文件进行排序

时间:2013-02-18 13:18:10

标签: bash shell join awk

我有一个文件,其中包含按时间戳排序的条目,但其中包含同一时间戳的多个实例,每个实例都有一个单独的主题。我想将具有相同时间戳的所有条目连接到一行。时间戳是第1列

输入文件可能会读取

Time,Tag,Value  
1,ABC,3  
2,ABC,2.7  
2,DEF,3.4  
3,ABC,2.8  
3,DEF,3.6  
3,GHI,2.99  
3,JKL,3.01  
4,ABC,3.42  
4,DEF,3.62  
4,JKL,3.82  

期望的输出就像(选项1);

Time,Tag,Value  
1,ABC,3  
2,ABC,2.7,DEF,3.4  
3,ABC,2.8,DEF,3.6,GHI,2.99,JKL,3.01  
4,ABC,3.42,DEF,3.62,JKL,3.82  

更好的是(选项2);

1,ABC,3  
2,ABC|DEF,2.7|3.4  
3,ABC|DEF|GHI|JKL,2.8|3.6|2.99|3.01  
4,ABC|DEF|JKL,3.42|3.62|3.82  

我认为我可以通过使用循环编写脚本来获得选项1。首先要求我获取“Tag”的所有值的唯一列表,以确定需要循环的迭代次数。

但我也在假设;

1)即使在bash中,这对于长文件来说也是昂贵的;和 2)可能有一些更优雅的方式。

Newb问题。所有帮助表示赞赏

由于

12 个答案:

答案 0 :(得分:1)

这样可行:

awk -F, '{if($1 in a){ split(a[$1],t,","); a[$1]=t[1]"|"$2","t[2]"|"$3
}else a[$1]=$2","$3;}END{asort(a);for(x in a)print x","a[x]}' file|sort -n

以你的例子:

kent$  awk -F, '{if($1 in a){split(a[$1],t,","); a[$1]=t[1]"|"$2","t[2]"|"$3
}else a[$1]=$2","$3;}END{asort(a);for(x in a)print x","a[x]}' file|sort -n                                                                                                  
1,ABC,3
2,ABC|DEF,2.7|3.4
3,ABC|DEF|GHI|JKL,2.8|3.6|2.99|3.01
4,ABC|DEF|JKL,3.42|3.62|3.82

答案 1 :(得分:1)

新答案:

我意识到我以前的答案可能难以阅读和理解 - 特别是对于初学者。然而,它确实充分利用了gawk的数组排序功能,这对于处理您在问题中谈到的“标记”的唯一值非常有用。但是,在阅读了一些评论之后,我相信我可能误解了你的问题 - 也许只是略有不同。这是一种不关心“标签”及其值的唯一性的方法。它只是加入了他们。它也应该是非常易读和可扩展的。像:

一样运行
awk -f script.awk file

script.awk的内容:

BEGIN {
    FS=OFS=","
}

NR==1 {
    print
    next
}

{
    tag[$1]=(tag[$1] ? tag[$1] "|" : "") $2
    val[$1]=(val[$1] ? val[$1] "|" : "") $3
}

END {
    for (i in tag) {
        print i, tag[i], val[i] | "sort -n"
    }
}

结果:

Time,Tag,Value
1,ABC,3
2,ABC|DEF,2.7|3.4
3,ABC|DEF|GHI|JKL,2.8|3.6|2.99|3.01
4,ABC|DEF|JKL,3.42|3.62|3.82

或者,这是单行:

awk -F, 'NR==1 { print; next } { tag[$1]=(tag[$1] ? tag[$1] "|" : "") $2; val[$1]=(val[$1] ? val[$1] "|" : "") $3 } END { for (i in tag) print i, tag[i], val[i] | "sort -n" }' OFS=, file

以前的答案:

这是使用GNU awk的一种方式。像:

一样运行
awk -f script.awk file

script.awk的内容:

BEGIN {
    FS=OFS=","
}

NR==1 {
    print
    next
}

{
    a[$1][$2]=$3
}

END {

    for (i in a) {
        b[x++] = i
    }

    n = asort(b)

    for (j=1;j<=n;j++) {

        m = asorti(a[b[j]],c)

        for (k=1;k<=m;k++) {

            s = (s ? s "|" : "") c[k]
            r = (r ? r "|" : "") a[b[j]][c[k]]
        }

        print b[j], s, r
        s = r = ""
    }
}

结果:

Time,Tag,Value
1,ABC,3
2,ABC|DEF,2.7|3.4
3,ABC|DEF|GHI|JKL,2.8|3.6|2.99|3.01
4,ABC|DEF|JKL,3.42|3.62|3.82

或者,这是单行:

awk -F, 'NR==1 { print; next } { a[$1][$2]=$3 } END { for (i in a) b[x++] = i; n = asort(b); for (j=1;j<=n;j++) { m = asorti(a[b[j]],c); for (k=1;k<=m;k++) { s = (s ? s "|" : "") c[k]; r = (r ? r "|" : "") a[b[j]][c[k]] } print b[j], s, r; s = r = "" } }' OFS=, file

答案 2 :(得分:1)

假设您的数据按时间顺序排列,您可以使用此awk解决方案:

parse.awk

# Use comma as input and output field separators
BEGIN { FS = OFS = "," }

# Print header and skip to next line
NR == 1 { print; next }

# If previous timestamp is the same as current append tag and value
pt == $1 {
  tag = tag "|" $2
  val = val "|" $3
}

# If not the first data line and timestamps are not equal then print
NR != 2 && pt != $1 { print pt, tag, val }

# Save previous timestamp and reset accumulator variables    
pt != $1 {
  pt  = $1
  tag = $2
  val = $3
}

END { print pt, tag, val }

像这样运行:

awk -f parse.awk infile

输出:

Time,Tag,Value
1,ABC,3
2,ABC|DEF,2.7|3.4
3,ABC|DEF|GHI|JKL,2.8|3.6|2.99|3.01
4,ABC|DEF|JKL,3.42|3.62|3.82

或者作为一个单行:

<infile awk 'BEGIN {FS=OFS=","} NR==1{print;next} pt==$1 {tag=tag"|"$2;val=val"|"$3} NR!=2&&pt!=$1 {print pt,tag,val} pt!=$1 {pt=$1;tag=$2;val=$3} END {print pt,tag,val}'

答案 3 :(得分:0)

bash可能是错误的工具。试试Python:

import fileinput
import sys

oldTime = None
for line in fileinput.input():
    line = line.strip()
    pos = line.find(',')
    time = line[0:pos]
    if oldTime == time:
        sys.stdout.write(',')
        sys.stdout.write(line[pos+1:])
    else:
        if oldTime is not None:
            sys.stdout.write('\n')
        sys.stdout.write(line)

    oldTime = time

sys.stdout.write('\n')

答案 4 :(得分:0)

如果这是您想要经常重复的操作,我会选择使用更多'完整'脚本语言编写的实用程序脚本。然后,您可以在自己的bash脚本中调用脚本,或者在需要时在命令行中使用它。

这是一个Python示例:

#!/usr/bin/env python
# --- merge_groups.py ----
import fileinput, operator, itertools
lines = (line.strip() for line in fileinput.input())
data = (line.split(",") for line in lines if line)
for key, group in itertools.groupby(data, operator.itemgetter(0)):
  _, label, value =  zip(*group)
  print "%s,%s,%s" % (key, "|".join(label), "|".join(value))

请注意,该脚本假定具有相同时间戳的条目已经组合在一起。

您可以使用该脚本直接处理现有数据文件或管道数据,例如:

[me@home]$ ./merge_groups.py data.txt  # parse existing data file
Time,Tag,Value
1,ABC,3
2,ABC|DEF,2.7|3.4
3,ABC|DEF|GHI|JKL,2.8|3.6|2.99|3.01
4,ABC|DEF|JKL,3.42|3.62|3.82

[me@home]$ cat data.txt | ./merge_groups.py  # post-process command output
Time,Tag,Value
1,ABC,3
2,ABC|DEF,2.7|3.4
3,ABC|DEF|GHI|JKL,2.8|3.6|2.99|3.01
4,ABC|DEF|JKL,3.42|3.62|3.82

答案 5 :(得分:0)

答案 6 :(得分:0)

以防万一你需要一个awk解决方案!

function read() {
  split($0, buf, ",")
}

function write() {
  for (i = 1; i < length(buf); i++) {
    printf "%s,", buf[i]
  }
  print buf[length(buf)]
}

BEGIN {
  FS = ","
}

NR == 1 {
  print
  next
}

NR == 2 {
  read()
  next
}

{
  if ($1 != time) { # new time                                                                                                                                                                   
    time = $1
    write()
    read()
  } else { # repeated time                                                                                                                                                                       
    for (i = 2; i <= NF; i++) {
      buf[i] = buf[i] "|" $i
    }
  }
}

END {
  write()
}

我对awk不太好,所以我必须强调可读性!

答案 7 :(得分:0)

嗯,您确实说过“所有帮助”,那么包含Ruby解决方案会是什么?

require 'csv'

puts(CSV.read('f.csv').group_by(&:first).map do |k, v|
  t = v.transpose
  [k, t[1].join('|'), t[2].join('|')].join(',')
end.drop(1))

答案 8 :(得分:0)

Perl没有代表。

use strict;
my $skip_header = <>;
my %d;
while(<>) {
    s/\s+$//;
    my ($no, $k, $v )  = split ",";
    push @{$d{int($no)}}, [ $k,  $v ];
}
END {
    foreach my $no (sort { $a <=> $b } keys %d  )  {
        print $no, ",";
        print join("|", map { $_->[0] } @{$d{$no}});
        print ",";
        print join("|", map { $_->[1] } @{$d{$no}});
        print "\n";
    }
}

给出:

1,ABC,3
2,ABC|DEF,2.7|3.4
3,ABC|DEF|GHI|JKL,2.8|3.6|2.99|3.01
4,ABC|DEF|JKL,3.42|3.62|3.82

答案 9 :(得分:0)

对于第一个选项,您可以尝试:

awk -F, 'p x!=$1{if(p x)print s; p=s=$1} {sub($1,x); s=s $0} END{print s}' file

答案 10 :(得分:0)

第一个:

> awk -F, '{a[$1]=a[$1]","$2","$3}END{for(i in a)print i","substr(a[i],2)}' temp | sort
1,ABC,3
2,ABC,2.7,DEF,3.4
3,ABC,2.8,DEF,3.6,GHI,2.99,JKL,3.01
4,ABC,3.42,DEF,3.62,JKL,3.82
第二个:

> awk -F, '{a[$1]=a[$1]"|"$2;b[$1]=b[$1]"|"$3}END{for(i in a)print i","substr(a[i],2)","substr(b[i],2)}' temp | sort
1,ABC,3
2,ABC|DEF,2.7|3.4
3,ABC|DEF|GHI|JKL,2.8|3.6|2.99|3.01
4,ABC|DEF|JKL,3.42|3.62|3.82

答案 11 :(得分:0)

更短:sed方式

sed -ne ':a;$!N;/^\([0-9]\+\),.*\n\1,/s/\n[0-9]*//;ta;P;D'
Time,Tag,Value
1,ABC,3
2,ABC,2.7,DEF,3.4
3,ABC,2.8,DEF,3.6,GHI,2.99,JKL,3.01
4,ABC,3.42,DEF,3.62,JKL,3.82