【问题标题】:Sort entries of lines using shell使用 shell 对行的条目进行排序
【发布时间】:2013-05-16 00:40:41
【问题描述】:

考虑以下输入和输出:

  infile   |   outfile
1 3 5 2 4  |  1 2 3 4 5
2 4 5      |  2 4 5
4 6 2 1    |  1 2 4 6

是否有任何 UNIX 程序的组合不涉及编程语言 - 除了 shell 脚本本身 - 对每一行中的条目进行排序比以下方法更快的文件:

while read line; do
    tr ' ' '\n' <<< ${line} | sort | tr '\n' ' '
    echo ""
done < infile > outfile

我的意思是,我可以创建一个小的 cpp/python/awk/... 程序来做到这一点,但这与使用通常的 one-liners 神奇地解决问题不同。 p>

编辑:

我一定是加了太多文字,而不是简单地问我想要什么;直截了当,我想确认是否有任何 UNIX 程序/程序组合(使用管道、fors、whiles、...)能够对一行中的条目进行排序,但没有像上面的一种解决方案。

我知道我可能会用一种编程语言(如 perl、awk、python)来完成讨厌的工作,但我实际上是在寻找一种不涉及这些语言解释器的 UNIX 程序组合。从答案中,我必须得出结论,不存在这样的 inline sort 工具,我非常感谢我所拥有的解决方案——主要是非常简洁的 Perl 单线。

然而,我并不真正理解我发布的 Bash 方法开销如此之大的原因。真的是由于大量的上下文切换,还是仅仅是来回转换输入并对其进行排序的开销?

我似乎无法理解这些步骤中的哪一个会大大减慢执行速度。对大约 500k 行的文件中的条目进行排序需要几分钟时间,每行大约有 30 个值。

【问题讨论】:

  • 似乎没有一个响应者完全阅读/理解您的问题:)
  • 您的代码适用于 10 以下的数字。如果您在一行中添加 11,它将无法正确排序。如果需要数字排序,请使用 sort -n
  • 您可以通过创造性地使用tsort 来节省一些周期,但这也取决于您输入数据的类型。

标签: linux bash shell unix sorting


【解决方案1】:

Perl 可以作为单行 Unix/Linux 命令很好地做到这一点:

perl -n -e "print join ' ', sort{a<=>b} split ' '" < input.txt > output.txt

这是“古老”的 Perl,在 ab 之前没有美元,它允许命令在 Windows 和 bash shell 中运行良好。如果你在 bash 中使用美元,它们必须用反斜杠转义,或者你必须反转单引号和双引号。

请注意,您试图在命令、编程语言和程序之间进行区分是非常细微的。 Bash 是一种编程语言。 Perl 当然可以用作外壳。两者都是命令。

您的脚本运行缓慢的原因是它每次循环迭代都会产生 3 个进程。进程创建非常昂贵。

【讨论】:

  • 与其转义美元符号,为什么不在 perl 脚本周围使用单引号,而在脚本中的字符串使用双引号?
  • @Barmar 当然没关系。我试图使两者尽可能相似。
  • 或者,在 Unix 上,简单地反转单引号和双引号:perl -n -e 'print join " ", sort{$a &lt;=&gt; $b} split " "' &lt; input.txt &gt; output.txt
【解决方案2】:

这个问题比看起来更微妙。您似乎在询问是否有更快的方法来执行排序,并且您使用 Perl 和 awk 等获得了很多(优雅的!)答案。但是您的问题似乎是您是否可以使用 shell built-ins 进行更快的排序,对此,答案是否定的。

显然,sort 不是内置的 shell,tr 也不是。没有内置函数可以执行 sort 的功能,并且可能替代“tr”的内置函数在这里不太可能对您有所帮助(例如,要删除 bash 的 IFS 变量需要做很多工作对 tr 的调用只是与 tr 一起生活)。

就个人而言,我会选择 Perl。请注意,如果您的数据集很大或很时髦,您可以选择使用 sort pragma 更改 Perls 默认排序算法。我不认为您将需要它来对整数文件进行排序,但也许这只是您的一个说明。

【讨论】:

  • 我没有命名 sorttr 内置插件;我只是想将普通的 UNIX 文本处理工具与语言解释器/shell 区分开来(如 perl(i)pythonawk 等)。我实际上并不介意这个工具,我只是在寻找更简单的东西,比如使用fors/pipes 链接工具,或者使用 Perl 的非常优雅的解决方案。而且,当然,有些东西不需要像我的方法那么长——我还没有弄清楚为什么会如此缓慢。
  • @Rubens 它非常慢,因为它读取整个数据 4 次(读取、tr、排序、tr)。它还为每一行创建子外壳、管道和许多进程。
【解决方案3】:
#!awk -f
{
  baz = 0
  PROCINFO["sorted_in"] = "@val_num_asc"
  split($0, foo)
  for (bar in foo)
    $++baz = foo[bar]
}
1

结果

1 2 3 4 5 2 4 5 1 2 4 6

【讨论】:

    【解决方案4】:

    它不漂亮(绝对不是 1-liner),但您可以仅使用内置 shell 命令对行进行排序,但是对于短行,它可能比重复调用外部函数更快。

    #!/bin/sh
    sortline(){
    for x in $@;do
        [ ! "$FIRST" ] && FIRST=t && set --
        i=0
        while [ $i -le $# ];do
            [ $x -lt $((${@:$((i+1)):1})) ] && break || i=$((i+1))
        done
        set -- ${@:1:$i}  $x   ${@:$((i+1)):$(($#-$i))}
    done
    echo $@
    }
    while read LINE || [ "$LINE" ];do
        sortline $LINE
    done <$1 >$2
    

    编辑:顺便说一句,这是一种选择排序算法,以防有人想知道

    Edit2:这仅适用于数值,对于字符串,您需要使用一些比较,例如[ "$x" -lt "${@:$((i+1)):1}" ](未选中),但是我将这个 C 程序用于字符串(我只是称之为 qsort),但它可以被修改在 argv 上使用 atoi:

    #include <stdlib.h>
    #include <string.h>
    static inline int cmp(const void *a, const void *b){
       return strcmp(*(const char **)a, *(const char **)b);
    }
    
    int main(int argc, char *argv[]){
        qsort(++argv, --argc, sizeof(char *), cmp);
        while (argc){
          write(1,argv[0],strlen(argv[0]));
          write(1,(--argc && argv++)?"\t":"\n",1);
       }
    }
    

    【讨论】:

    • 作为杰作,它非常出色 (+1);作为建议的答案,它令人震惊(-1)。净 - 没有投票权。
    • @JonathanLeffler -eh,听起来像是一个有趣的挑战,但它只比在每行不到 10 个字段的情况下调用 sort 更快。如果您认为这很骇人听闻,请查看distro.ibiblio.org/amigolinux/download/AmigoProjects/BashTrix/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-22
    • 1970-01-01
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多