【问题标题】:Using awk to sort fields and arrange使用 awk 对字段进行排序和排列
【发布时间】:2013-05-26 03:15:57
【问题描述】:

我目前正在尝试学习 awk,并且我想做一项特定的任务。我的问题与之前发布的问题(Using awk to transpose column to row)的范围相似,但不适用于我的数据。我一直在尝试找出原因,我相信这很简单。

我在一个只有两个字段的制表符分隔表中有大量数据(示例如下):

1101\t7778
1101\t7755
1101\t8889
1101\t6789
2300\t1220
4000\t2333
4000\t7555
4000\t9000
4000\t1111

当字段匹配时,我想最终将第二个字段附加到一行。期望的输出是:

1101\t7778\t7755\t8889\t6789
2300\t1220
4000\t2333\t7555\t9000\t1111

如果可能的话,我想对命令中的所有部分进行解释,以便我将来理解它。提前致谢。

【问题讨论】:

  • 对于任何提供纯 bash 解决方案的人来说 +1 :)
  • @abasu:见下文... ;)
  • @gammyknee:奥斯卡奖颁给...?

标签: awk


【解决方案1】:
awk '    { list[$1] = list[$1] "\t" $2 }
     END { for (i in list) printf "%s%s\n", i, list[i] }' data

第一行将一个选项卡和第二个字段添加到由$1 索引的list 元素。第二行打印出键和值的累积列表。

样本输出:

1101    7778    7755    8889    6789
4000    2333    7555    9000    1111
2300    1220

如果您想要对第一列进行排序,您可以通过sort -n 管道输出。如果你有 GNU awk,你也可以研究一下内置的排序功能:

/usr/gnu/bin/awk '    { list[$1] = list[$1] "\t" $2 }
                  END { n = asorti(list, indexes);
                        for (i = 1; i <= n; i++)
                            printf "%s%s\n", indexes[i], list[indexes[i]]
                      }' data

排序输出:

1101    7778    7755    8889    6789
2300    1220
4000    2333    7555    9000    1111

【讨论】:

  • 非常感谢!不同角色会不会有冲突?使用数字数据,这个答案非常有效,但对于其他一些字符,该解决方案不起作用。 for eg: PAK_74_MK_1st_fwd\tPBK_85_MK_2nd_RC PAK_74_MK_1st_fwd\tPBK_124_MK_2nd_RC PAK_74_MK_1st_fwd\tPBK_94_MK_2nd_RC PAK_74_MK_1st_fwd\tPBK_150_MK_2nd_RC PAK_75_MK_1st_fwd\tPBK_128_MK_2nd_RC PAK_75_MK_1st_fwd\tPBK_134_MK_2nd_RC PAK_75_MK_1st_fwd\tPBK_139_MK_2nd_RC PAK_75_MK_1st_fwd\tPBK_69_MK_2nd_RC Is there any reason why it won't work?
  • 实际上无论如何都可以。稍后在文件中必须是一个狡猾的角色。感谢您对 awk 命令的解释 :)
  • 输入范围相当广泛应该没问题。它在空白处拆分(空白和制表符的任意序列);它不需要任何特定顺序的键值,也不需要给定键的所有条目出现在相邻行上。如果某个键包含两个值,那么您需要在累加行中迭代NF(字段数)。如果您需要对键的值进行排序,则需要额外的处理。唯一会阻止它“工作”的是数据中实际的反斜杠小写“t”序列。
【解决方案2】:

对于abasu的要求,纯bash版本:

#!/bin/bash

declare -A hash
while read x y; do
  hash[$x]=${hash[$x]}"\t"$y
done <<XXX
1101    7778
1101    7755
1101    8889
1101    6789
2300    1220
4000    2333
4000    7555
4000    9000
4000    1111
XXX

for i in ${!hash[*]}; { echo -e $i${hash[$i]};}

输出:

2300    1220
1101    7778    7755    8889    6789
4000    2333    7555    9000    1111

在 here-is-the-document 中,列之间以及输出列之间都有一个制表符。如果在echo 之后的最后一行中删除了-e,则输出为:

2300\t1220
1101\t7778\t7755\t8889\t6789
4000\t2333\t7555\t9000\t1111

【讨论】:

  • awesome :) 不错的解决方案,无需任何外部工具
  • 没什么,如果你把最后一行设为for i in ${!hash[*]}; { echo $i${hash[$i]};} | sort,它会给出一个排序的输出
  • @abasu:不幸的是,sort 不是bash 内部命令,因此使用它不是纯粹的bash 解决方案! sort 功能应该被编程为 bash 功能,但我现在跳过它。 :)
  • 真的,我没抓住重点,sort 使它成为一个非纯 bash 解决方案。
【解决方案3】:

此版本最终不会将整个文件存储在内存中。它也不会重新排列键的顺序。

awk -F '\t' '
    $1 != prev {
        if (prev) print ""
        printf "%s", $1
        prev=$1
    }
    {printf "%s%s", FS, $2}
    END {print ""}
' f
1101    7778    7755    8889    6789
2300    1220
4000    2333    7555    9000    1111

【讨论】:

    【解决方案4】:

    灵感来自Kent's 答案。

    awk '{
             a[$1]=a[$1] ? a[$1] FS $2 : $2
        } 
    END {
            for (key in a) print key,a[key]
        }' FS='\t' OFS='\t' f
    

    【讨论】:

      【解决方案5】:

      另一个纯粹的 bash 实现只是为了好玩,使用字符串切片而不是正则表达式,只假设 4 位数字,所以它并不可靠,但我正在使用的 bash 版本没有内置正则表达式所以我不知道我还能做什么!

      #!/bin/bash
      
      while read line; do
              array[${line:0:4}]="${array[${line:0:4}]}${line:4:8}"
              indicies[${line:0:4}]=${line:0:4}
      
      done < $1
      
      for i in ${indicies[@]}; do
              echo "$i${array[$i]}"
      done
      

      【讨论】:

        猜你喜欢
        • 2018-09-13
        • 1970-01-01
        • 2014-05-05
        • 2017-01-26
        • 2020-09-21
        • 1970-01-01
        • 1970-01-01
        • 2021-11-09
        • 1970-01-01
        相关资源
        最近更新 更多