【问题标题】:Awk combining multiple lines conditionallyawk 有条件地组合多行
【发布时间】:2011-10-27 10:53:29
【问题描述】:

如果它们与 ID 匹配,我想将多行不同长度的值合并为一行。

输入示例为:

ID:  Value:
a-1  49
a-2  75
b-1  120
b-2  150
b-3  211
c-1  289
d-1  301
d-2  322

所需的输出示例是:

ID:  Value:
a 49,75
b 120,150,211
c 289
d 301,322

我将如何编写一个 awk 表达式(或 sed 或 grep 之类的)来检查 ID 是否匹配,然后将所有这些值打印到一行上?我当然可以打印 将它们放入不同的列并稍后将它们组合起来,所以问题只是有条件地打印 ID 是否匹配且不开始新行。

【问题讨论】:

  • ID: Value: 是输入的一部分吗? ID 后面总是有-<digit> 吗?输入中是否有一个或两个空格?
  • 是否保证所有a-lines 都会被分组?我的意思是,是否有可能成为a-line,然后是 b-line,然后是另一个 a-line,例如?
  • 我可以很容易地对它们进行分组 - 实际数据的形式是 6 位数字,即 000000-1 000005-1 000005-2 000010-1 000010-2 000010-3 我可以取下标题并重新添加它(无论如何我都会这样做,因为我正在重新制作列)并且数据是空格分隔的,但也可以轻松更改谢谢
  • 另外一个澄清 - 索引号上升到两位数,但不是三位(我认为 000065-13 是最高的)并且 ID 本身上升到大约 500,000(568025 是最高)
  • 在 ID 和 value 之间的输入中是否有一个或两个空格?

标签: sed awk grep


【解决方案1】:

在 awk 中,如果您的 ID 聚集在一起:

awk 'NR==1 {print $0}
NR > 1 {sub("-.*", "", $1)}
NR == 2 {prev=$1; printf "%s %s", $1, $2}
NR > 2 && prev == $1 {printf ",%s", $2}
NR > 2 && prev != $1 {prev=$1; printf "\n%s %s", $1, $2}' your_input_file

【讨论】:

    【解决方案2】:

    鉴于您的意见:

    awk '
      NR == 1 {print; next}
      {
        split($1,a,/-/)
        sep = values[a[1]] == "" ? "" : ","
        values[a[1]] = values[a[1]] sep $2
      }
      END {for (key in values) print key, values[key]}
    '
    

    生产

    ID:  Value:
    a 49,75
    b 120,150,211
    c 289
    d 301,322
    

    支持“列表哈希”的语言也会很方便。这是一个 Perl 版本

    perl -lne '
      if ($. == 1) {print; next}
      if (/^(.+?)-\S+\s+(.*)/) {
        push @{$values{$1}}, $2;
      }
      END {
        $, = " ";
        foreach $key (keys %values) {
        print $key, join(",", @{$values{$key}});
        }
      }
    '
    

    【讨论】:

    • 如果文件很大,将 ids/values 保存在内存中会占用大量内存。
    【解决方案3】:

    在 sed 中,假设 ID 聚集在一起:

    sed -n -e '1p;2{s/-.* / /;h};3,${H;x;s/\(.*\) \(.*\)\n\1-.* /\1 \2,/;/\n/{P;s/.*\n//;s/-.* / /};x};${x;p}' your_input_file
    

    Bellow 是一个带注释的 sed 脚本文件,可以用sed -n -f script your_input_file 运行:

    # Print the 1st line as is.
    1p
    # For the 2nd line, remove what is after - in the ID and save in the hold space.
    2{s/-.* / /;h}
    # For all the other lines...
    3,${
    # Append the line to the hold space and place it in the pattern space.
    H;x
    # Substitute identical ids by a ,.
    s/\(.*\) \(.*\)\n\1-.* /\1 \2,/
    # If we have a \n left in the pattern space, it is a new ID, so print the old and prepare the next.
    /\n/{P;s/.*\n//;s/-.* / /}
    # Save what remains in hold space for next line.
    x}
    # For the last line, print what is left in the hold space.
    ${x;p}
    

    【讨论】:

    • 这很甜蜜。如果您添加#!/usr/bin/sed -nf,您可以使用script < your_input_file 运行它。
    • 显然awk更适合这个任务,但是+1不仅表明它可以sed完成,而且还解释了。
    【解决方案4】:

    鉴于您在 input.txt 文件中的输入:

    awk '{split($1, a, "-"); hsh[a[1]]=hsh[a[1]]$2","}END{for (i in hsh){print i" "hsh[i]}}' input.txt | sed 's/,$//'
    

    输出

    a 49,75
    b 120,150,211
    c 289
    d 301,322
    

    【讨论】:

      【解决方案5】:

      基于标准工具的解决方案,作为上述优秀解决方案的替代方案...

      $ for INDEX in $(cut -f1 input | uniq); do echo -n "$INDEX  ";grep "^$INDEX" input | cut -f2 | tr '\n' ' ';echo; done
      a  49 75 
      b  120 150 211 
      c  289 
      d  301 322 
      

      使用稍微修改的输入,没有标题和索引,使用

      创建
      awk 'NR>1' input | sed 's/-[0-9]*//'
      a       49
      a       75
      b       120
      b       150
      b       211
      c       289
      d       301
      d       322
      

      【讨论】:

        猜你喜欢
        • 2014-11-22
        • 2021-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-16
        • 1970-01-01
        • 2013-05-29
        相关资源
        最近更新 更多