Bash 按数字 AND 字长 AND 字母顺序排序

Bash sort by number AND word length AND alphabetically

我的数组中有这些字符串:

3 rere 33.33%
2 ena 22.22%
1 something 11.11%
1 som 11.11%
1 ok 11.11%
1 evo 11.11%

预期结果是:

3 rere 33.33%
2 ena 22.22%
1 something 11.11%
1 evo 11.11%
1 som 11.11%
1 ok 11.11%

它们按数字降序排列。

我也想按中间单词的长度排序,但如果单词长度相同,则按字母顺序排序。

这些不是专栏。

我想把它分成两个数组然后排序,但是如何将它们连接在一起? 有人有想法吗?

Perl 来拯救!

perl -l -0777 -aF'\n' -ne '
    print for map join(" ", @$_),
              sort { $b->[0] <=> $a->[0] 
                     || length($a->[1]) <=> length($b->[1])
                     || $a->[1] cmp $b->[1] }
              map [ split ],
              @F;
    ' input-file
  • -n逐条读取输入记录
  • -0777 将整个文件设置为一条记录
  • -l 在打印中添加换行符
  • -a 拆分输入
  • -F'\n' 告诉 -a 在换行符上拆分
  • 每行然后按 split 空格分割,按第 0 列的数字 (<=>) 排序,或按第一列的长度排序,或按字母顺序排序 (cmp)按第一列

您不能使用 sort 按长度排序。让我们试试 Schwartzian transform:

awk '{print length(), [=10=]}' file | sort -k2,2nr -k1,1nr -k3,3 | cut -d" " -f2-

awk 命令接受 1 something 11.11% 并输出 9 1 something 11.11%
Then sort 首先按第 2 个字段的数值排序,然后按第 1 个字段的数值排序,然后按第 3 个字段的词法排序。
然后 cut 删除第一个字段。

这背后的想法与Schwartzian transform used in 非常相似:我们添加一个排序字段(在本例中是第二列的长度),用它来排序,然后再次删除它:

while read -r col1 word rest; do
    printf "%d\t%s %s %s\n" "${#word}" "$col1" "$word" "$rest"
done < infile | sort -k 2,2nr -k 1,1nr -k 3,3 | cut -f 2

这导致

3 rere 33.33%
2 ena 22.22%
1 something 11.11%
1 evo 11.11%
1 som 11.11%
1 ok 11.11%

while 循环后,输出如下所示:

4   3 rere 33.33%
3   2 ena 22.22%
9   1 something 11.11%
3   1 som 11.11%
2   1 ok 11.11%
3   1 evo 11.11%

第二列中有一个新列,其中包含字符串的长度。它是制表符分隔的,以便以后更容易 cut

对于 sort,我们使用 -k 参数指定用于排序的内容(sort 不关心字段是制表符还是 space 分隔) : 2,2nr 仅使用第二个字段,按数字和降序排列; 1,1nr 也是如此,3,3 只是您的标准词法排序。

输出现在看起来像这样:

4   3 rere 33.33%
3   2 ena 22.22%
9   1 something 11.11%
3   1 evo 11.11%
3   1 som 11.11%
2   1 ok 11.11%

现在我们只需去掉第一列,为此我们使用 cut 并利用 printf 引入的制表符分隔。

Bash while 循环非常慢,Perl 解决方案可能快几个数量级。