按 bash 中的行数对文本列进行排序

Sort text columns by number of lines in bash

假设一个文本文件包含 x 个字符串列。

$cat file # where x=3
foo  foo  foo
bar  bar  bar
     baz  baz
     qux

bash 中有没有一种方法可以根据这些列包含的数字文本字符串(即填充的行)对这些列进行排序,同时保持行的内部顺序在每列中?

$sought_command file
foo  foo  foo
bar  bar  bar
baz  baz
qux

本质上,行数最多的列排在第一位,行数第二多的列排在第二位,依此类推

(这个任务很容易通过 R 实现,但我想知道通过 bash 的解决方案。)

编辑 1:

这里有一些额外的细节:每一列至少包含一个文本字符串(即,一个填充行)。文本字符串可以构成任何字母数字组合并具有任何长度(但显然不包含空格)。输出列不得插入空行。列分隔符没有先验限制,只要它在 table.

中保持一致即可

此任务所需要做的就是按原样移动列,以便它们按列长度排序。 (我知道在 bash 中实现这个听起来比实际更容易。)

首先像这样创建一个名为 transpose 的函数:

transpose() {
   awk -v FPAT='[^[:blank:]]+|[ \t]{3,}' '{
     for (i=1; i<=NF; i++)
        a[i,NR]=$i
        max=(max<NF?NF:max)
     }
     END {for (i=1; i<=max; i++)
        for (j=1; j<=NR; j++)
           printf "%s%s", a[i,j], (j==NR?ORS:OFS)
   }'
}

然后将其用作:

transpose < file | awk '{print NF "\t" [=11=]}' | sort -k1nr | cut -f2- | transpose

foo foo foo
bar bar bar
baz baz
qux

步骤是:

  1. 调用transpose函数将列转置为行
  2. 使用awk在每行的开头添加# of fields
  3. 在第一列中按相反的数字顺序使用 sort
  4. 使用cut删除第一列
  5. 再次调用 transpose 将列转置为行以获得原始顺序

PS: 由于使用 FPAT 我们这里需要 gnu-awk。

sed -e 's/^ *//' columns.txt
# =>
# foo  foo  foo
# bar  bar  bar
# baz  baz
# qux

我整个星期都会在这里! :D

更严肃地说,您可能想要 transpose your columns with bash,使用 awkrs。这将使您更容易对列(现在是行)进行排序,然后再将它们转回原来的位置。 不过,多个空格可能会给 awk 带来问题。

使用 unix 工具集

$ tr '\t' '\n' <file                  | 
  pr -4ts                             |  
  awk '{print gsub(/-/,"-") "\t" [=10=]}' | 
  sort -k1n                           | 
  cut -f2-                            | 
  tr '\t' '\n'                        | 
  pr -3ts

foo     foo     foo
bar     bar     bar
baz     baz     -
qux     -       -

假设列以制表符分隔,缺失值用“-”表示。幻数4和3分别是行数和列数。

将其用作输入文件

$ cat file
foo     foo     foo
bar     bar     bar
-       baz     baz
-       qux     -

使用 GNU awk for sorted_in 并假设您的列是制表符分隔的:

$ cat tst.awk
BEGIN{ FS=OFS="\t" }
{
    for (i=1; i<=NF; i++) {
        if ($i ~ /[^[:space:]]/) {
            cell[NR,i] = $i
            cnt[i]++
        }
    }
    next
}
END {
    PROCINFO["sorted_in"] = "@val_num_desc"
    for (row=1; row<=NR; row++) {
        c=0
        for (col in cnt) {
            printf "%s%s", (c++?OFS:""), cell[row,col]
        }
        print ""
    }
}

$ awk -f tst.awk file
foo     foo     foo
bar     bar     bar
baz     baz
qux