如何根据文件的一部分(多行)的列值对数据进行排序?

How to sort data based on the value of a column for part (multiple lines) of a file?

我在文件file1中的数据看起来像

3
0
2 0.5
1 0.8
3 0.2
3
1
2 0.1
3 0.8
1 0.4
3
2
1 0.8
2 0.4
3 0.3

每个块的行数相同(这里是3+2=5)。在每个块中,前两行是header,接下来的3行有两列,第一列是标签,它是从1到3的数字之一。我想对每个块中的行进行排序,基于第一列的值(前两行除外)。所以预期的结果是

3 
0
1 0.8
2 0.5
3 0.2
3
1
1 0.4
2 0.1
3 0.8
3
2
1 0.8
2 0.4
3 0.3

我认为 sort -k 1 -n file1 将对整个文件有好处。 它给了我错误的结果:

0
1
2
3
3
3
2 0.1
3 0.2
3 0.3
1 0.4
2 0.4
2 0.5
1 0.8
1 0.8
3 0.8

这不是预期的结果。

如何对每个块进行排序对我来说仍然是一个问题。我认为 AWK 可以解决这个问题。请给点建议。

使用任何 awk+sort+cut 应用 DSU (Decorate/Sort/Undecorate) 习语,无论每个块中有多少行:

$ awk -v OFS='\t' '
    NF<pNF || NR==1 { blockNr++ }
    { print blockNr, NF, NR, (NF>1 ?  : NR), [=10=]; pNF=NF }
' file |
sort -n -k1,1 -k2,2 -k4,4 -k3,3 |
cut -f5-
3
0
1 0.8
2 0.5
3 0.2
3
1
1 0.4
2 0.1
3 0.8
3
2
1 0.8
2 0.4
3 0.3

要了解其作用,只需查看前 2 个步骤:

$ awk -v OFS='\t' 'NF<pNF || NR==1{ blockNr++ } { print blockNr, NF, NR, (NF>1 ?  : NR), [=11=]; pNF=NF }' file
1       1       1       1       3
1       1       2       2       0
1       2       3       2       2 0.5
1       2       4       1       1 0.8
1       2       5       3       3 0.2
2       1       6       6       3
2       1       7       7       1
2       2       8       2       2 0.1
2       2       9       3       3 0.8
2       2       10      1       1 0.4
3       1       11      11      3
3       1       12      12      2
3       2       13      1       1 0.8
3       2       14      2       2 0.4
3       2       15      3       3 0.3

$ awk -v OFS='\t' 'NF<pNF || NR==1{ blockNr++ } { print blockNr, NF, NR, (NF>1 ?  : NR), [=12=]; pNF=NF }' file |
    sort -n -k1,1 -k2,2 -k4,4 -k3,3
1       1       1       1       3
1       1       2       2       0
1       2       4       1       1 0.8
1       2       3       2       2 0.5
1       2       5       3       3 0.2
2       1       6       6       3
2       1       7       7       1
2       2       10      1       1 0.4
2       2       8       2       2 0.1
2       2       9       3       3 0.8
3       1       11      11      3
3       1       12      12      2
3       2       13      1       1 0.8
3       2       14      2       2 0.4
3       2       15      3       3 0.3

并注意 awk 命令只是创建 sort 所需的键值,以便按块号、行号或 $1 等进行排序。所以 awk修饰输入,sort 对其进行排序,然后 cut 通过删除 awk 脚本添加的修饰值来取消修饰。

您可以在 gawk

中使用 sort 和数组
awk 'NF==1 && a[1]{
        n=asort(a); 
        for(k=1; k<=n; k++){print a[k]}; 
        delete a; i=1
    }NF==1{print}
    NF==2{a[i]=[=10=];++i}
    END{n=asort(a); for(k=1; k<=n; k++){print a[k]}}
' file1

你得到

3
0
1 0.8
2 0.5
3 0.2
3
1
1 0.4
2 0.1
3 0.8
3
2
1 0.8
2 0.4
3 0.3

这类似于 Ed Morton 的解决方案,但没有变量赋值,它只使用 built-in 个变量:

λ cat input.txt 
3
0
2 0.5
1 0.8
3 0.2
3
1
2 0.1
3 0.8
1 0.4
3
2
1 0.8
2 0.4
3 0.3
awk '{ print int((NR-1)/5), ((NR-1)%5<2) ? 0 : 1, (NF>1 ?  : NR), NR, [=11=] }' input.txt |
  sort -n -k1,1 -k2,2 -k3,3 -k4,4 | cut -d ' ' -f5-
3
0
1 0.8
2 0.5
3 0.2
3
1
1 0.4
2 0.1
3 0.8
3
2
1 0.8
2 0.4
3 0.3

工作原理

awk '{ print int((NR-1)/5), ((NR-1)%5<2) ? 0 : 1, (NF>1 ?  : NR), NR, [=13=] }' input.txt
0 0 1 1 3
0 0 2 2 0
0 1 2 3 2 0.5
0 1 1 4 1 0.8
0 1 3 5 3 0.2
1 0 6 6 3
1 0 7 7 1
1 1 2 8 2 0.1
1 1 3 9 3 0.8
1 1 1 10 1 0.4
2 0 11 11 3
2 0 12 12 2
2 1 1 13 1 0.8
2 1 2 14 2 0.4
2 1 3 15 3 0.3

一个ruby:

ruby -e '$<.read.split(/\n/).map(&:split).
         slice_when { |a, b| b.length == 1 && b.length < a.length }.
         map{|e| e.sort_by{|sl| sl.length()>1 ? -sl[-1].to_f : -1.0/0}}.
         each{|e| e.each{|x| puts "#{x.join(" ")}"}}' file

或者 DSU 形式 ruby:

ruby -lane 'BEGIN{lines=[]; block=0; lnf=0}
            block+=1 if $F.length()>1 && lnf==1
            lnf=$F.length()
            lines << [block, -($F.length()>1 ? $F[-1].to_f : (-1.0/0)), $.] + $F
            END{lines.sort().each{|sl| puts "#{sl[3..].join(" ")}"}}
' file