【问题标题】:Sorting alphabetically using last column, using awk使用 awk 使用最后一列按字母顺序排序
【发布时间】:2017-09-26 17:55:28
【问题描述】:

我正在尝试对可变数量的文本列进行排序,有时有 3 个字段有时有 2 个。

示例输入:

        George W. Bush
        Brack Obama
        Micky Mouse
        John F. Kennedy

想要的结果:

         George W. Bush
         John F. Kennedy
         Micky Mouse
         Brack Obama

我想按姓氏的字母顺序获取它们,所以使用$3$2 字段。

到目前为止,我已经翻转了每一行以将姓放在前面。但是,要对它们进行排序,我似乎无法将它们翻转回来。我试过数组,我得到的输出比预期的多(重复)。

我只想将其保留为 awk 文件。

我考虑过使用另一个 awk 文件将它们翻转回(比如说)awk 文件的脚本,但我无法在 awk 中创建文件(使用 bash 脚本)。我一直在阅读Linux 实用指南,但我看到的示例似乎都一样。感谢您查看我的问题。

目前这是我完成它的方式

    {
         #print  $3 " " $1 " " $2;
         if($3 == ""){
            #print "me";
            print  $2 " " $1;
            #list[$3]= $2"  "$1
        }else{ 
            print $3" "$1" "$2 ;
            #list[$3]= $3" " $2" "$1;}
            #for(result in list){    print list[result];   }
        }
    }


    gawk -f fileUsed alphRecoredToBeUsed | sort

给我留下按我想要的方式排序的范围值。然而,在保持 alpha 排序的同时向它们呈现第一个原始值。

【问题讨论】:

  • 能否请您在代码标签中也发布预期的输出?

标签: bash awk gawk


【解决方案1】:

使用 GNU awk 进行 sorted_in:

$ awk '
    { a[$NF]=($NF in a ? a[$NF] ORS : "") $0 }
    END { PROCINFO["sorted_in"]="@ind_str_asc"; for (i in a) print a[i] }
' file
George W. Bush
John F. Kennedy
Micky Mouse
Brack Obama

或使用任何 awk + ​​sort + cut:

$ awk '{print $NF "\t" $0}' file | sort | cut -f2-
George W. Bush
John F. Kennedy
Micky Mouse
Brack Obama

【讨论】:

  • 根据输入顺序,这可能会以错误的顺序显示“Robert F. Kennedy”和“John F. Kennedy”。
  • OP 希望输出按姓氏排序,这大概意味着当多人具有相同姓氏时保留输入顺序,这就是 awk 脚本的作用。鉴于重复的姓氏,没有理由认为任何其他顺序更好或更正确 话虽如此,awk | sort 将根据重复姓氏的名字排序。这也可能是正确的,但如果不是,OP 可以添加-k1,1。我的主要观点是,自然没有“正确”的顺序,因此也没有“错误”的顺序。 OP 根本没有告诉我们如何处理重复项,因此任何顺序都是正确的。
【解决方案2】:

这是使用 gawk 根据每行最后一个单词进行排序的脚本:

#!/bin/sh
gawk '
function compare(i1, v1, i2, v2) {
    ct1 = split(v1, pcs1)
    ct2 = split(v2, pcs2)
    f1 = ct1 < 1 ? "" : pcs1[ct1]
    f2 = ct2 < 1 ? "" : pcs2[ct2]
    if (f1 < f2) return -1;
    if (f1 > f2) return 1;
    return 0
}
{ lines[++ct] = $0 }
END {
    asort(lines, sorted_lines, "compare");
    for (i = 1; i <= length(sorted_lines); i++)
        print sorted_lines[i]
}
' "$@"

它适用于您的示例:

$ cat input
George W. Bush
Brack Obama
Micky Mouse
John F. Kennedy
$ ./s input
George W. Bush
John F. Kennedy
Micky Mouse
Brack Obama

(我使用的是gawk 4.0.1,它支持用户提供的比较功能。)

【讨论】:

    【解决方案3】:

    这可能更容易:

    sh-4.4$ awk '{print $NF,$0}' file |sort -k1|awk '{$1="";print $0}'                                                                                                                   
     George W. Bush                                                                                                                                                                      
     John F. Kennedy                                                                                                                                                                     
     Micky Mouse                                                                                                                                                                         
     Barack Obama
    

    正在做什么:将姓放在前面,排序,然后从输出中删除。

    希望对你有帮助

    【讨论】:

    • 哇,产生的子shell 的数量如此沉重(每个实用程序和管道一个)。此外,如果您发现自己在执行 cat file ... 并且实际上并未连接文件,那可能是 Unnecessary Use Of cat(称为 UUOc)。至少您可以缩短您对awk '{print $NF,$0}' filename | ... 的回答并消除UUOc :)
    • 嘿@DavidC.Rankin 谢谢,我编辑了答案。 :) 希望这是请求的人想要的
    • 好的,这是努力的 A。 UUOc 移除工作做得很好,排序顺序也很好。
    • 如果前导空格有问题,也可以使用 sed:sed 's/^ *//g'
    【解决方案4】:

    我最喜欢的awk 变量之一是NF,它是记录中的字段数;意思是,$1$2...$NF 的数量,其中$NF 是您的最后一个元素。您甚至可以使用print $(NF-1) 使awk 将您的 打印到最后一个元素,或者如果您发现需要,可以使用$(integer-after-math) 符号进行任何其他数学运算。

    不要尝试交换所有内容,只需根据 $NF 组织它们,这是数据示例中每一行的姓氏。

    【讨论】:

      【解决方案5】:

      这里有一行awk 命令来获得所需的输出,

      $ awk '{a[$NF]=$0} END{PROCINFO["sorted_in"]="@ind_str_asc"; for(i in a)print a[i]}' file
              George W. Bush
              John F. Kennedy
              Micky Mouse
              Brack Obama
      

      简要说明,

      • 使用数组a[$NF]=$0 创建$NF$0 映射。
      • PROCINFO["sorted_in"]="@ind_str_asc":按索引升序排序,与字符串比较。更多详情请参考awk manual。请注意,它特定于 gawk
      • for(i in a)print a[i]:由于之前预定义的数组扫描顺序,数组会按升序扫描。

      【讨论】:

      • 如果老布什。与他的儿子 George H.W. 一起出现在输入文件中,其中只有一个被选中、更正、打印。
      • 是的,你是对的。该方法只能扫描最后一个字段的订单。
      【解决方案6】:

      在 GNU awk 中:

      $ awk '
      {
          b=$NF                 # initialize the key buffer
          if(NF>1)              # if there are more than one word in the name
              for(i=1;i<NF;i++) # add them to the buffer
                  b=b OFS $i
          a[b]=$0               # hash
      }
      END{
          PROCINFO["sorted_in"]="@ind_str_asc"  # order on the index using for
          for(i in a)
              print a[i]
      }' file
      

      输出(将一些常见的嫌疑人添加到列表中进行测试):

      George H. W. Bush
      George W. Bush
      John F. Kennedy
      John G. Kennedy
      Madonna
      Micky Mouse
      Barack Obama
      Brack Obama
      

      脚本使用lastname firstname_if_exists 1st_middle_if_exists 等作为哈希键,即。 a["Bush George H. W."]="George H. W. Bush".

      【讨论】:

      • 与这里早期给出的解决方案相比,没有什么新的。请。解释你自己。
      • 不是asorting,只是使用for。使用名称中的所有单词(以相反的顺序)进行排序,而不仅仅是姓氏(解决@CWLiu 的答案中提到的问题......并且显然引入了一个新问题)。
      • ..从@CWLiu的答案复制。
      • 如果您要根据其他人的答案改编代码,那么您必须明确表扬他们的努力。我们称之为attribution,这里总是需要它。根据我们用于所有内容的 CC BY-SA 许可,您可以(甚至鼓励)调整和重新混合其他人的解决方案,但您必须提供他们的归属。请edit您的回答符合这些要求,否则可能会被删除。
      • @CodyGray 我也会。我在这里的过程不是阅读其他人的解决方案,而是从头开始构建自己的解决方案。 GNU awk (asort vs for) 确实提供了两种排序方式,而我只是碰巧使用了另一种。如果我从其他人那里复制了部分解决方案,我会参考他们的工作。在我看来,我为解决这个问题带来的新方法是将名称的各个部分排序为哈希键,这是正确的方法。多么讽刺,这被复制到另一个解决方案......我的错误是在这里发布正在进行的工作。
      【解决方案7】:

      您需要对所有字段进行排序才能使这有价值。

      单行:

      $ awk '{s="";for (i=1;i<NF;i++)s=s $i;a[s]=$0}END{n=asorti(a,b);for(j=1;j<=n;j++)print a[b[j]]}' input.txt
      

      解释:

      {
        s=""                                 # initialize s
        for (i=1;i<NF;i++) s=s $i            # concatenate first and middle names
        a[$NF s]=$0                          # use last name followed by other names 
                                             # as index
      }
      END{
        n=asorti(a,b);                       # sort index of a
        for(j=1;j<=n;j++) print a[b[j]]      # print results
      }
      

      使用这个输入:

      $ cat input.txt
      George W. Bush
      George H.W. Bush
      Michelle Obama
      Barack Obama
      Micky Mouse
      John F. Kennedy
      

      给予:

      $ awk '{s="";for (i=1;i<NF;i++)s=s $i;a[$NF s]=$0}END{n=asorti(a,b);for(j=1;j<=n;j++)print a[b[j]]}' input.txt
      George H.W. Bush
      George W. Bush
      John F. Kennedy
      Micky Mouse
      Barack Obama
      Michelle Obama
      

      gnu awk 4.1 开始,您可以使用 join 功能:

      @include "join"
      {
        n=split($0, a, " ")
        s=join(a, 1, n-1)
        b[$NF s]=$0
      }
      END{
        n=asorti(b,c);
        for(j=1;j<=n;j++) print b[c[j]]
      }
      

      【讨论】:

      • 如果H.W.H. W. 之类的空格分隔(我想会这样),这个解决方案首先给我George W. Bush,然后是George H. W. Bush。这是我在解决方案中尝试并解决的问题之一。
      • Rule 2.1.5 Add little or no space within strings of initials 来自印刷风格的元素 - R. Bringhurst。纳夫说。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-28
      • 2011-09-08
      • 1970-01-01
      • 2020-09-03
      • 1970-01-01
      相关资源
      最近更新 更多