【问题标题】:How can I use awk to sort columns by the last value of a column?如何使用 awk 按列的最后一个值对列进行排序?
【发布时间】:2013-09-02 04:17:22
【问题描述】:

我有一个这样的文件(有数百行和列)

1  2 3
4  5 6
7 88 9

我想根据最后一行的值(或特定的行值)对列重新排序

1 3 2
4 6 5
7 9 88

如何使用 awk(或其他)来完成这项任务? 提前感谢您的帮助

编辑:我要感谢大家,如果我不够清楚,我深表歉意。 我想做的是:

  • 取一行(例如最后一行);
  • 使用所选行的排序值重新排序矩阵的列以确定顺序。

所以,最后一行是7 88 9,排序后是7 9 88,那么这三列必须以某种方式重新排序,在这种情况下,最后两列交换。


一个更通用的四列示例,再次基于最后一行:

输入:

1    2 3  4
4    5 6  7
7 88.0 9 -3

输出:

 4 1 3 2
 7 4 6 5
-3 7 9 88.0

【问题讨论】:

  • 你的意思是对最后一行和所有行进行排序之前进行相同的 col 交换?
  • 为什么前两行被交换了,它们不再按任何方向排序!?
  • 我完全不知道这个问题是关于什么的。有人知道吗?
  • @EdMorton 我相信他想切换列的顺序,使最后一行按升序排列。
  • @EdMorton 我的理解是,他想对最后一行的字段进行排序,在排序过程中,字段会被交换。 (所谓重新排序),最后一行排序后,他希望上面的所有行都做同样的事情(重新排序)。

标签: bash sorting awk


【解决方案1】:

这是一个快速、肮脏且可改进的解决方案:(已编辑,因为 OP 澄清了数字是浮点数)。

$ cat test.dat
1 2 3
4 5 6
.07 .88 -.09
$ awk "{print $(printf '$%d%.0s\n' \
                  $(i=0; for x in $(tail -n1 test.dat); do
                           echo $((++i)) $x
                         done |
                  sort -k2g) | paste -sd,)}" test.dat
3 1 2
6 4 5
-.09 .07 .88

看看那里发生了什么(或至少部分):

$ echo "{print $(printf '$%d%.0s\n' \
                      $(i=0; for x in $(tail -n1 test.dat); do
                               echo $((++i)) $x
                             done |
                      sort -k2g) | paste -sd,)}" test.dat
{print $3,$1,$2} test.dat

要使其适用于任意行,请将tail -n1 替换为tail -n+$L|head -n1

【讨论】:

  • @JonathanLeffler:Perl 实际上只是一个“改进的”外壳,改进了疣。
  • 您的代码适用于该示例,但奇怪的是它不适用于我的大型矩阵(100 列和 27000 行)。可能是格式的原因?所有数字都是浮点数,如`0.018344 -0.001763 0`等。
  • @leonardvertighel:你没有提到数字是浮点数。 sort -n 不适用于浮点数。尝试使用sort -k2g 而不是sort -k2n
  • @rici 不幸的是 sort -k2g 不会产生变化
  • export LANG=en_US.UTF-8!!!!带有逗号分隔小数的愚蠢语言环境。感谢大家的耐心等待
【解决方案2】:

使用 GNU awk 的数组排序功能可以优雅地解决这个问题。 GNU awk 允许您使用PROCINFO 控制数组遍历。因此需要对文件进行两次传递,第一次传递将最后一条记录拆分为数组,第二次传递以值顺序循环遍历数组的索引,并根据索引输出字段。下面的代码可能比我解释得更好。

awk 'BEGIN{PROCINFO["sorted_in"] = "@val_num_asc"};
    NR == FNR {for (x in arr) delete arr[x]; split($0, arr)};
    NR != FNR{sep=""; for (x in arr) {printf sep""$x; sep=" "} print ""}' file.txt file.txt
4 1 3 2
7 4 6 5
-3 7 9 88.0

【讨论】:

  • 感谢您提供这个优雅的解决方案。最后一条记录是$0?那么我可以在那里放任何一列吗?
  • @leonardvertighel,你具体想做什么?
  • 使用自定义行(在本例中为最后一行,但可以是任何行或某些行的中位数)作为参考对矩阵的列进行重新排序。
  • @leonardvertighel,比如说第 30 行,从文件中重新排序,您可以将上面脚本中的 NR == FNR 更改为 NR == FNR && NR == 30
【解决方案3】:

更新:

像这样创建一个名为 transpose.awk 的文件:

{ 
    for (i=1; i<=NF; i++)  {
        a[NR,i] = $i
    }
}
NF>p { p = NF }
END {    
    for(j=1; j<=p; j++) {
        str=a[1,j]
        for(i=2; i<=NR; i++){
            str=str OFS a[i,j];
        }
        print str
    }
}

下面是应该为您工作的脚本:

awk -f transpose.awk file | sort -n -k $(awk 'NR==1{print NF}' file) | awk -f transpose.awk
1 3 2
4 6 5
7 9 88

我在这里使用了两次transpose.awk。一次将行转置为列,然后我将按最后一列进行数字排序,然后再次将行转置为列。它可能不是最有效的解决方案,但它可以按照 OP 的要求工作。

转置 awk 脚本来自:@ghostdog74 from An efficient way to transpose a file in Bash

【讨论】:

  • 我不确定只是切换最后两个值是 OP 真正想要的。
  • 我有一个这样的文件(有数百行和列)
  • @Kent:我不太明白你的评论。 (虽然我也可能误解了 OP 的要求:P)
  • @Kent:老实说,我无法从原始问题中弄清楚这一点。如果您注意到我的答案发布后 OP 已经说明了关于排序的时间戳。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-10
  • 1970-01-01
  • 1970-01-01
  • 2015-03-31
  • 1970-01-01
相关资源
最近更新 更多