【问题标题】:Sort text columns by number of lines in bash按bash中的行数对文本列进行排序
【发布时间】:2017-04-12 13:58:48
【问题描述】:

假设一个文本文件包含 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

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

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

【问题讨论】:

  • 列可以有间隙吗?也就是说,在文件的第 5 行中,第 2 列是否没有条目,而第 1 列和第 3 列有条目?假设 2 列具有相同数量的条目;列是否应该保持顺序(所以如果第 2 列和第 3 列都有 4 个值,则第 2 列应该出现在第 3 列之前)?
  • 一个问题是简单地确定哪些列存在,哪些不存在。默认情况下,Awk 将任何空白序列视为等同于单个空白,至少就分隔字段而言。列的宽度是否一致(您在示例数据中每个条目显示 3 个字符;真实数据是否同样严格)?您可以修改数据以便在其他空列中有一个标记(例如破折号-)吗?那会让工作轻松很多。还是列值选项卡分开?
  • @JonathanLeffler 所有列都包含至少一个文本字符串(即至少一行)。我更新了我的问题以提供所需的详细信息。
  • bash 是一个外壳。 shell 是一个环境,可以从中创建/销毁文件/进程和对工具的序列调用,它不是像您尝试那样操作文本的工具。所以,当你说你想“用 bash”做这件事时——你到底是什么意思?另外,您的列是固定宽度还是制表符分隔或什么?
  • 好的,那么您正在寻找一个 awk 解决方案,因为 sed 用于在单个行上进行简单替换,而 grep 用于打印与正则表达式匹配的字符串,而这些都不是您想要的,因此留下 awk 和该解决方案将独立于您使用的任何 shell、bash 或其他方式。那么 - 是什么将样本输入中的“列”分开?标签?还是它们是固定宽度的?还是别的什么?

标签: string bash sorting awk multiple-columns


【解决方案1】:

首先像这样创建一个名为 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" $0}' | sort -k1nr | cut -f2- | transpose

foo foo foo
bar bar bar
baz baz
qux

步骤是:

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

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

【讨论】:

  • 理论上看起来不错。实际上,它仅适用于提供的示例,因为所有列都以 foo bar 开头,并且您不会注意到列被合并。将bar bar bar 替换为bir bar ber,您会看到。看起来你只是用空字符串替换第一个空格,就像我的 sed 笑话一样。
  • @anubhava 很好的解释!
【解决方案2】:
sed -e 's/^ *//' columns.txt
# =>
# foo  foo  foo
# bar  bar  bar
# baz  baz
# qux

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

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

【讨论】:

  • 这只是一个技巧,因为所有列都以相同的值开头。只需删除每行开头的第一个空格,就会给人一种按高度对列进行排序的印象。
【解决方案3】:

使用 unix 工具集

$ tr '\t' '\n' <file                  | 
  pr -4ts                             |  
  awk '{print gsub(/-/,"-") "\t" $0}' | 
  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     -

【讨论】:

    【解决方案4】:

    使用 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
    

    【讨论】:

    • 看起来比转置逻辑简单! +1
    • @EdMorton 就像一个魅力。谢谢!
    • 比我的尝试好得多 ++
    • 唯一需要注意的是 OP 没有提到输入是制表符分隔的:)
    • 对,因为 OP 没有告诉我们,我们必须对列的分隔方式做出一些假设,所以我选择了固定宽度或其他任何东西。
    猜你喜欢
    • 2021-02-07
    • 2021-10-28
    • 2012-09-20
    • 2012-08-09
    • 2015-06-29
    • 2015-11-13
    • 2014-05-27
    • 2010-12-17
    • 1970-01-01
    相关资源
    最近更新 更多