【问题标题】:Bash - adding values in row based on columnBash - 根据列在行中添加值
【发布时间】:2015-06-05 19:03:54
【问题描述】:

我的 csv 文件中的第二列有重复项。我想根据这些重复项添加第 1 列中的关联值。

示例 csv:

56,  cc=DK
49,  cc=US
34,  cc=GB
32,  cc=DE
32,  cc=NZ
31,  cc=DK
31,  cc=GB
31,  cc=GB

示例结果:

96,  cc=GB # where 96 = 34+31+31
87,  cc=DK # where 87 = 56+31
32,  cc=DE
32,  cc=NZ

【问题讨论】:

  • awk -F, '{a[$2]+=$1}END{for(i in a)print a[i] FS i}' file。你也让我们离开了输出

标签: bash csv awk sed command-line-interface


【解决方案1】:

你可以在awk中使用关联数组:

awk '{s[$2]+=$1}END{for(k in s)print s[k]", ",k}' inFile

为了可读性而扩展它,并使用sum/key而不是s/k

{                                 # Do for each line.
    sum[$2] += $1                 # Add first field to accumulator,
                                  #   indexed by second field.
                                  #   initial value is zero.
}
END {                             # Do this bit when whole file processed.
    for (key in sum)              # For each key like cc=US:
        print sum[key] ", " key   # Output the sum and key.
}

这是在我的盒子上运行的示例:

pax$ echo;echo '56,  cc=DK
49,  cc=US
34,  cc=GB
32,  cc=DE
32,  cc=NZ
31,  cc=DK
31,  cc=GB
31,  cc=GB' | awk '{s[$2]+=$1}END{for(k in s)print s[k]", "k}'

32, cc=DE
96, cc=GB
32, cc=NZ
49, cc=US
87, cc=DK

尽管第一列的形式是999,(注意末尾的逗号),这仍然有效,因为awk 在数字上下文中评估字符串时,仅使用在那个上下文。因此45xyzzy 将变为45,更重要的是,49, 变为49

【讨论】:

  • 您还可以根据我的代码更改 FS 而不是 gsub(这是没有意义的,因为您正在更改一个 , 并且可以只使用 sub,就像 $1 一样,因为它默认为 $0)对问题的评论。
  • @JID,我确实想过,但事实证明 gsub/sub 甚至不需要,所以我删除了它。
  • 哦,是的,您可以再删除 2 个字符,因为不需要 (k in s) 之后的 {}
  • @JID:完成,虽然我不相信我们应该“打代码”这个:-)
  • 使用您的 awk 行以及我最初帖子的评论中的行对我的情况有效。谢谢!我最终使用: awk '{s[$2]+=$1}END{for(k in s)print s[k]", ",k}' inFile |排序 -rn
【解决方案2】:

Perl 解决方案:

perl -ane '$h{ $F[1] } += $F[0] }{ print "$h{$_}\t$_\n" for keys %h' input.csv

解释:

  • -n逐行处理输入
  • -a 将空格上的输入行拆分为 @F 数组中的字段
  • 哈希表 %h 记录每个键的总和(2nd 列)。它只是将第一列的值添加到其中。
  • }{(称为“爱斯基摩人问候语”)将每行 (-n) 执行的内容与处理整个输入后要运行的代码分开

【讨论】:

    【解决方案3】:

    这样简单的任务可以使用awk,但是如果您有一堆类似的任务并且您将来可能需要更改它,那么很容易搞砸。

    由于这是典型的数据库问题,请考虑使用sqlite

    你可以:

    1. 添加行名并删除多余的空格:

      $ cat <(echo "num, name") originalInput.txt | tr -d ' ' > input.csv
      
    2. 将数据导入临时sqlite db:

      $ sqlite3 --batch temp.db <<EOF!
      .mode csv
      .import input.csv input
      EOF!
      
    3. 从数据库中选择:

      $sqlite3 temp.db 'SELECT sum(num), name FROM input GROUP BY name'
      32|cc=DE
      87|cc=DK
      96|cc=GB
      32|cc=NZ
      49|cc=US
      

    它的代码稍微多一点,并使用外部sqlite3 命令,但它明显不易出错且更灵活。您可以轻松加入多个 csv 文件、使用精美的排序等等。

    另外,想象一下自己在六个月后查看代码,试图快速理解它的作用。

    【讨论】:

    • 您最后的陈述取决于您对该语言的熟悉程度,与上面的 awk 相比,我要花更长的时间才能弄清楚您的代码的作用。 Tbh 这似乎有点矫枉过正。您不仅必须安装外部程序(每个人都可能由于某种原因无法做到这一点)您还必须编辑原始数据,创建一个数据库,然后获得输出不再是 CSV 格式。如果您想要一种比 awk 更强大的方法,您可以使用 csv 解析器。
    • @JID,“在这里使用 awk 没问题,但是如果你有一堆类似的任务,考虑 sqlite”。我已经在生产中看到了数百个这样的神奇 awk/sed/perl/*sh 脚本,在许多情况下,它不是适合这项工作的工具。顺便说一句,csv 解析器不会为你做聚合。
    • 我也看过很多magic awk/sed/perl/*sh scripts,如果它们写得不是很糟糕,并且您对该语言有基本的了解,那么您将毫不费力地立即理解正在做的事情。另外我意味着python或perl中的csv解析器,读回来我可能应该更清楚。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多