【问题标题】:Sum duplicate row values with awk使用 awk 对重复的行值求和
【发布时间】:2017-02-03 12:26:10
【问题描述】:

我有一个结构如下的文件:

1486113768 3656
1486113768 6280
1486113769 530912
1486113769 5629824
1486113770 5122176
1486113772 3565920
1486113772 530912
1486113773 9229920
1486113774 4020960
1486113774 4547928

我的目标是消除第一列中的重复值,对第二列中的值求和并使用新列值更新该行:来自上面输入的工作输出将是:

1486113768 9936      # 3656 + 6280
1486113769 6160736   # 530912 + 5629824
1486113770 5122176   # ...
1486113772 4096832
1486113773 9229920
1486113774 8568888

我知道cutuniq:到目前为止,我设法在第一列中找到了重复值:

cut -d " " -f 1 file.log | uniq -d

1486113768
1486113769
1486113772
1486113774

有没有“awk 方式”来实现我的目标?我知道它是一个非常强大和简洁的工具:我之前用过

awk '{print $2 " " $3 >> $1".log"}' log.txt

扫描 log.txt 中的所有行并创建一个以 $1 为名称的 .log 文件,并用 $2 和 $3 值填充它,所有这些都在一个 bash 行中(用read 循环!);有没有办法找到第一列重复项,对其第二列值求和并重写删除重复项的行并打印第二列的结果总和?

【问题讨论】:

  • 您之前使用的那个 awk 脚本应该写成awk '{print $2, $3 > ($1".log")}' log.txt 以实现可移植性、健壮性、可维护性等。
  • 这样做有很多充分的理由:谢谢,我会马上更正。
  • 不客气。展望未来-当您发布问题时,最好等待几个小时后再接受答案,而不是仅仅接受您得到的第一个答案,因为一旦您接受答案,它甚至会阻止其他人查看您的问题和第一个答案可能不是你能得到的最好的。
  • 我很抱歉,我急于得到我的情节工作,我接受了第一个工作答案,没有考虑空间和时间性能。您的答案似乎比接受的答案更有效。
  • @elmazzun,您可以更改选择以获得最佳答案。 :)

标签: bash awk


【解决方案1】:

如下使用Awk

awk '{ seen[$1] += $2 } END { for (i in seen) print i, seen[i] }' file1
1486113768 9936
1486113769 6160736
1486113770 5122176
1486113772 4096832
1486113773 9229920
1486113774 8568888

{seen[$1]+=$2} 创建一个哈希映射,其中 $1 被视为索引值,并且仅对文件中来自 $1 的那些唯一项递增总和。

【讨论】:

  • 智能快速,谢谢。我只需要在awk 之后通过管道传输sort,对于非常大的文件,它不像以前那样按第一列排序。
  • @elmazzun 不,你不知道。您只需要通过管道输出进行排序,因为这是错误的方法,因为它将整个文件读入内存,然后以随机顺序打印输出。你根本不需要这样做。
【解决方案2】:
$ awk '$1!=p{ if (NR>1) print p, s; p=$1; s=0} {s+=$2} END{print p, s}' file
1486113768 9936
1486113769 6160736
1486113770 5122176
1486113772 4096832
1486113773 9229920
1486113774 8568888

以上内容几乎不使用内存(仅 1 个字符串和 1 个整数变量),并且会按照输入中出现的顺序打印输出。

如果您打算同时使用 awk,我强烈建议您阅读 Arnold Robbins 的《Effective Awk Programming, 5th Edition可以很好地理解其他人的脚本,以便在给定 2 个脚本的情况下将正确的方法与错误的方法区分开来,这些脚本在给定一些特定的示例输入的情况下会产生预期的输出。

【讨论】:

  • Gawk: Effective AWK Programming查看免费副本
  • @toraritte 请停止发帖。 Arnold 提供 GNU awk 并编写文档并提供免费的在线副本供参考,仅从本书的销售中获得报酬,用于支持我们其他人的所有工作。引导人们购买这本书,而不是免费下载。
  • 这对我来说不适用于GNU Awk 5.1.0, API: 3.0,而接受的答案却可以(尽管我更喜欢这种方法,因为我有一个巨大的 CSV)。
  • @toraritte This doesn't work 是最糟糕的问题陈述,因为它不包含任何可以让任何人帮助您调试它的信息。想象一下把你的车送到修理工那里,然后只是说“它不工作”并期望他们修理它。正如您在我的回答中看到的那样,它会从发布的示例输入中产生预期的输出,因此在没有更多信息的情况下,我只能建议如果它对您“不起作用”,那么您在复制/粘贴脚本或您的脚本时做错了输入看起来不像这个问题中的输入。您可能应该提出一个新问题,以便我们为您提供帮助。
【解决方案3】:

如果datamash 没问题

$ datamash -t' ' -g 1 sum 2 < ip.txt 
1486113768 9936
1486113769 6160736
1486113770 5122176
1486113772 4096832
1486113773 9229920
1486113774 8568888
  • -t' ' 设置空格作为字段分隔符
  • -g 1 按第一个字段分组
  • sum 2 对第二个字段值求和
  • 如果输入文件未排序,请使用datamash -st' ' -g 1 sum 2,其中-s 选项负责排序

【讨论】:

  • 好一个,无论谁使用它,请确保您的数据按第一个字段排序
【解决方案4】:

假设您将许多日志文件输出中的前十行连接在一个文件中(并按“排序”排序),结果如下:

   2142 /pathtofile1/00.jpg
   2173 /pathtofile1/00.jpg
   2100 /pathtofile1/00.jpg
   2127 /pathtofile1/00.jpg

你也可以改变求和的顺序:

$ awk '{ seen[$2] += $1 } END { for (i in seen) print i, seen[i] }' top10s.txt | sort -k 2 -rn

你会得到这个总数:

/pathtofile1/00.jpg 8542

【讨论】:

    【解决方案5】:

    使用 Perl

    $ cat elmazzun.log
    1486113768 3656
    1486113768 6280
    1486113769 530912
    1486113769 5629824
    1486113770 5122176
    1486113772 3565920
    1486113772 530912
    1486113773 9229920
    1486113774 4020960
    1486113774 4547928
    $ perl -lane ' $kv{$F[0]}+=$F[1];END { print "$_ $kv{$_}" for (sort keys %kv)}' elmazzun.log
    1486113768 9936
    1486113769 6160736
    1486113770 5122176
    1486113772 4096832
    1486113773 9229920
    1486113774 8568888
    $
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-29
      • 2013-12-20
      • 2015-04-11
      • 1970-01-01
      • 2020-03-26
      • 2012-12-12
      • 2015-10-08
      相关资源
      最近更新 更多