如何按行长度和第二列字母顺序对文件进行排序?

3

假设我有一个文件:

ab
aa
c
aaaa

我希望它能像这样排序

c
aa
ab
aaaa

这是按行长度排序,然后再按字母顺序排序。在bash中是否可能实现?


5
我们鼓励提问者展示他们已经尝试过的去解决问题的方法。 - Cyrus
@Anush:不要忘记接受其中一个答案! - user1934428
3个回答

10

您可以在每行之前添加行长度,然后进行数字排序,最后切除数字。

< your_file awk '{ print length($0), $0; }' | sort -n | cut -f2

你看到我通过sort -n完成了排序,而没有进行任何多键排序。老实说,我很幸运这个方法起作用:

  • 我认为行不能以数字开头,因此我期望sort -n可以工作,因为如果所有字符串长度相同,字母和数字排序会得到相同的结果,正是因为我们是根据我通过 awk 添加的行长度进行排序。

  • 结果证明,即使您的输入以数字开头,也可以正常工作,原因是sort -n

    1. 根据行的 前导数字部分 进行数字排序
    2. 平局的情况下,使用strcmp比较整个行

    这里有一些演示:

$ echo -e '3 11\n3 2' | sort -n
3 11
3 2
# the `3 ` on both lines makes them equal for numerical sorting
# but `3 11` comes before `3 2` by `strcmp` before `1` comes before `2`

$ echo -e '3 11\n03 2' | sort -n
03 2
3 11
# the `03 ` vs `3 ` is a numerical tie,
# but `03 2` comes before `3 11` by `strcmp` because `0` comes before `3`

所以幸运的是,我在 awk 命令中包含的逗号, 插入了一个空格(实际上是一个OFS),也就是一个非数字,从而“打破”了数字排序,让 strcmp 排序生效 (在这种情况下比较数值相等的整个行)。

这种行为是否符合 POSIX 标准,我不知道,但是我使用的是 GNU coreutils 8.32sort。有关详细信息,请参见 我的这个问题 和 Unix 上的 这个答案

awk 可以全部做完,但我认为使用 sort 进行排序更符合惯用法(即,使用 sort 进行排序),并且更有效率,正如一条评论所解释的那样(毕竟,你为什么不期望 sort 是在 shell 中排序东西性能最好的工具呢?)。


1
排序更符合惯用语。我认为这不是一个真正的争论点。然而,如果你想使用awk内置的sort,那么sort可以很好地处理大文件;如果你走得这么远,我甚至不会使用awk,而是使用像Perl或Ruby这样更适合的语言。因此,最终,对我来说,这是支持使用... | sort的一个论点。顺便说一句,在你的解决方案中,你应该将多键排序直接放入代码示例中,因为OP要求对于相等长度的键,按字母顺序进行排序。 - user1934428
@user1934428,请看看现在是否满意。至于Ruby和Perl,我不了解它们,所以我甚至不知道它们的性能如何。你可以再添加一个答案,我猜。 - Enlico
如果存在并列的情况,它会基于行的其余部分使用字母排序。我认为这不是正确的。实际上,顺序是未指定的,并且它只是在您的示例中发生,但在一般情况下可能会出现问题。为了证明这一点,我添加了选项“-s”,它表示“如果无法根据提供的排序标准进行决策,则保留原始顺序:(echo 3 b; echo 3 a)| sort -n -s”。实际上,我认为您最初明确指定两个排序键的想法更好。 - user1934428
1
@user1934428,请考虑我在我链接的问题中编辑后的答案。 - Enlico
我明白了!谢谢你再次明确地指出这一点。 - user1934428
@user1934428,感谢你推动我进行调查。正如我在答案中所写的那样,我很幸运,我不知道现在我所知道的这些细节。 - Enlico

2

使用gawk插入行的长度(填充为四位以便正确排序),按照两个键排序(首先是长度,然后是行中的第一个单词),然后移除长度:

gawk '{printf "%04d %s\n", length($0), $0}' | sort -k1 -k2 | cut -d' ' -f2-

如果必须使用bash:
while read -r line; do printf "%04d %s\n" ${#line} "${line}"; done | sort -k1 -k2 | (while read -r len remainder; do echo "${remainder}"; done)

1
对于GNU awk:
$ gawk '{
    a[length()][$0]++                             # hash to 2d array
}
END {
    PROCINFO["sorted_in"]="@ind_num_asc"          # first sort on length dim
    for(i in a) {
        PROCINFO["sorted_in"]="@ind_str_asc"      # and then on data dim
        for(j in a[i])
            for(k=1;k<=a[i][j];k++)               # in case there are duplicates
                print j
        # PROCINFO["sorted_in"]="@ind_num_asc"    # I don t think this is needed?
    }
}' file

输出:

c
aa
ab
aaaa
aaaaaaaaaa
aaaaaaaaaa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接