【问题标题】:Sorting on the last field of a line对一行的最后一个字段进行排序
【发布时间】:2010-07-11 11:05:39
【问题描述】:

对行列表进行排序的最简单方法是什么,对每行的最后一个字段进行排序?每行可能有可变数量的字段。

类似

sort -k -1

是我想要的,但是 sort(1) 不采用负数从末尾而不是开头选择字段。

我也希望能够选择字段分隔符。

编辑:为问题添加一些特殊性:我要排序的列表是路径名列表。路径名可能具有任意深度,因此字段数可变。我想对文件名组件进行排序。

这些附加信息可能会改变操作该行以提取最后一个字段的方式(可以使用 basename(1)),但不会改变排序要求。

例如

/a/b/c/10-foo
/a/b/c/20-bar
/a/b/c/50-baz
/a/d/30-bob
/a/e/f/g/h/01-do-this-first
/a/e/f/g/h/99-local

我希望这个列表按文件名排序,所有文件名都以数字开头,表示文件的读取顺序。

我在下面添加了我的答案,这就是我目前的做法。我曾希望有一种更简单的方法——也许是不同的排序实用程序——也许不需要操作数据。

【问题讨论】:

  • +100 表示sort -k -1。在日常工作中,我应该如何记住那些“你只需要这条带有 7 根管道的小管线”的解决方案......
  • 你试过rev吗?例如。 find . -type f | rev | sort | rev
  • @A.Danischewski:这将首先对行的最后一个字符进行排序,而不是最后一个字段的第一个字符。尝试使用我在问题中的示例数据对其进行测试,看看效果如何。
  • 这取决于你想做什么,如果你需要排序以删除目录中的重复项,使用 rev 可能就足够了。如果您出于其他原因需要对其进行排序,您可能会使用类似:cat t.txt | sed 's#.*/##' |sort| xargs -n1 -i grep {} t.txt

标签: linux shell sorting


【解决方案1】:
awk '{print $NF,$0}' file | sort | cut -f2- -d' '

基本上,这个命令会:

  1. 在开头重复最后一个字段,用空格分隔(默认 OFS)
  2. 排序,使用完整路径 ($0) 解决重复的文件名进行排序
  3. 剪切重复的第一个字段,f2-表示从第二个字段到最后一个字段

【讨论】:

  • 按照@Gabe 的回答将最后一个字段放在行首会导致许多解决方案。我最终使用的是:find "$@" -maxdepth 1 -name "[0-9]*" -printf '%f/%p\n' | sort -t / | cut -f2- -d/,我使用find(1) 输出文件名和完整路径(重复文件名)。
【解决方案2】:

这是一个 Perl 命令行(请注意,您的 shell 可能要求您转义 $s):

perl -e "print sort {(split '/', $a)[-1] <=> (split '/', $b)[-1]} <>"

只需将列表导入其中,或者,如果列表在文件中,则将文件名放在命令行末尾。

请注意,此脚本实际上不会更改数据,因此您不必小心使用的分隔符。

这是示例输出:

>perl -e "打印排序 {(split '/', $a)[-1] (split '/', $b)[-1]} " files.txt /a/e/f/g/h/01-先做这个 /a/b/c/10-foo /a/b/c/20-bar /a/d/30-鲍勃 /a/b/c/50-baz /a/e/f/g/h/99-本地

【讨论】:

  • 不是 perl 的真正粉丝,但 +1 表示答案不会影响数据。如果可以的话,我也会为功能风格再 +1。
  • 我收回了 - 它对我不起作用(perl v5.10.1)。我在问题中剪切了你的行和我的数据,所以没有错别字。
  • 我使用的是 Perl v5.8.8,但我怀疑它有什么不同。我的程序适用于您的示例,所以也许您应该发布您的实际输入,我可以看到问题所在。
  • 我的输入是问题中的示例输入。我想知道为什么我的 perl 行为不同?
  • 我更喜欢反转引号以避免外壳扩展,例如perl -e 'print sort { (split "/", $a)[-1] &lt;=&gt; (split "/", $b)[-1] } &lt;&gt;' filename
【解决方案3】:

类似的东西

awk '{print $NF"|"$0}' file | sort -t"|" -k1 | awk -F"|" '{print $NF }'

【讨论】:

  • 您也可以更改为$(NF-1)$(NF-2) 等,但是您当然必须根据您的输入来调整最后一条语句。 &lt;input-no-space&gt; | awk -F. 'NF&gt;1 { printf("%s %s\n", $(NF-1), $0) | "sort" } END { close("sort") }' | awk '{ print $2 }'
【解决方案4】:

perl 中用于反转一行中字段顺序的单行代码:

perl -lne 'print join " ", reverse split / /'

您可以使用一次,通过管道输出进行排序,然后通过管道返回,您就可以实现您想要的。您可以将/ / 更改为/ +/,以便压缩空间。你当然可以随意使用任何你想分割行的正则表达式。

【讨论】:

  • 为什么要调用 Perl 两次?它可以排序,那为什么不也使用 Perl 进行排序呢?
  • 因为在 Perl 中排序意味着将整个流拉入内存,而排序不需要。如果您尝试对千兆字节的数据进行排序,它不会在火灾中崩溃——它将使用它合并的存储桶和临时文件来提供最终输出流。在 Perl 中完全重复排序的功能会花费大量代码而几乎没有收益。
  • 嗯,我现在在一个大型数据集上进行了测试。我尝试使用 sortperl -e 'print sort &lt;&gt;' 对 250MB 的字符串文件(大约 13M 行)进行排序——在前一种情况下需要一段时间,但排序从未使用超过 120MB 的常驻内存,在第二种情况下,Perl 贪婪地爆发了到 1.4+GB 常驻(我不知道为什么会这么多?存储字符串列表有那么多冗余吗?),CPU 使用率为零,因为它所做的只是交换,我的计算机变得无法使用,有打破它...
【解决方案5】:

我认为唯一的解决方案是使用awk:

  1. 使用awk将最后一个字段放在前面。
  2. 对行进行排序。
  3. 再次将第一个字段放在末尾。

【讨论】:

    【解决方案6】:

    将行上的最后一个定界符替换为不在列表中以其他方式出现的另一个定界符,使用该另一个定界符作为 sort(1) 定界符对第二个字段进行排序,然后恢复定界符更改。

    delim=/
    new_delim=" "
    cat $list \
    | sed "s|\(.*\)$delim|\1$new_delim|" \
    | sort -t"$new_delim" -k 2,2 \
    | sed "s|$new_delim|$delim|"
    

    问题在于知道要使用的没有出现在列表中的分隔符。您可以对列表进行多次遍历,然后 grep 查找一系列潜在的分隔符,但这一切都相当讨厌 - 特别是当“在一行的最后一个字段上排序”的概念被如此简单地表达时,但解决方案却不是。

    编辑:用于 $new_delim 的一个安全分隔符是 NUL,因为它不能出现在文件名中,但我不知道如何将 NUL 字符放入 bourne/POSIX shell 脚本(不是 bash)以及是否排序和 sed会妥善处理。

    【讨论】:

      【解决方案7】:
      #!/usr/bin/ruby
      
      f = ARGF.read
      lines = f.lines
      
      broken = lines.map {|l| l.split(/:/) }
      
      sorted = broken.sort {|a, b|
          a[-1] <=> b[-1]
      }
      
      fixed = sorted.map {|s| s.join(":") }
      
      puts fixed
      

      如果所有答案都涉及 perl 或 awk,还不如用脚本语言解决整个事情。 (顺便说一句,我首先在 Perl 中尝试过,但很快就想起我不喜欢 Perl 的列表列表。我很想看看 Perl 大师的版本。)

      【讨论】:

      • 请注意/:/ 是因为我在 /etc/passwd 上进行了测试 .. 随意更改分隔符,或者更好的是,参数化它。
      • 我不是 Perl 大师,但请参阅我的解决方案:stackoverflow.com/questions/3222810/…
      【解决方案8】:

      我希望这个列表按文件名排序,所有文件名都以数字开头 指示应读取文件的顺序。

      find . | sed 's#.*/##' | sort
      

      sed 替换结果列表中以斜杠结尾的所有部分。文件名是剩下的,然后你就可以排序了。

      【讨论】:

      • 这会破坏输入。我想要所有输入,按最后一个字段排序。
      【解决方案9】:

      这是一个python oneliner版本,注意它假设字段是整数,你可以根据需要更改。

      echo file.txt | python3 -c 'import sys; list(map(sys.stdout.write, sorted(sys.stdin, key=lambda x: int(x.rsplit(" ", 1)[-1]))))'
      

      【讨论】:

        【解决方案10】:
        | sed "s#(.*)/#\1"\\$'\x7F'\# \
        | sort -t\\$'\x7F' -k2,2 \
        | sed s\#\\$'\x7F'"#/#"
        

        仍然比 sort(1) 的简单负字段索引更糟糕,但在这种情况下使用 DEL 字符作为分隔符不会导致任何问题。

        我也喜欢它的对称性。

        【讨论】:

          【解决方案11】:

          sort 允许您使用-t 选项指定分隔符,如果我记得很清楚的话。要计算最后一个字段,您可以执行一些操作,例如计算一行中的分隔符数量并求和。例如这样的事情(假设“:”分隔符):

          d=`head -1 FILE | tr -cd :  | wc -c`
          d=`expr $d + 1`
          

          $d 现在包含最后一个字段索引)。

          【讨论】:

          • 但原始问题在每行指定了一个可能可变的字段数。
          • 啊,我明白了。然后我建议通过使每一行具有相同数量的字段来使输入文件更加统一:)领域...事情好多了...那一刻:))
          猜你喜欢
          • 1970-01-01
          • 2011-04-19
          • 2014-06-05
          • 1970-01-01
          • 1970-01-01
          • 2021-05-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多