【问题标题】:What did I do wrong? Not sorting properly with awk我做错了什么?无法使用 awk 正确排序
【发布时间】:2017-03-01 20:06:48
【问题描述】:

嗨,基本上我有一个我正在使用的“临时”文本文件,其中包含一长串各种电子邮件地址(有些重复)。我要输出的是按频率最高的电子邮件地址,最后是唯一电子邮件地址的总数。

awk '{printf "%s %s\n", $2, $1} END {print "total "NR}' temp | sort -n | uniq -c -i

到目前为止,我得到了我想要的输出除了,因为它没有按照最高频率排序。相反,它是按字母顺序排列的。

我已经坚持了几个小时了,不知道为什么。我知道我可能做错了什么,但我不确定。如果您需要更多信息并且我提供的代码不是问题,请告诉我。提前谢谢你。

编辑:我也尝试过排序 -nk1(输出在第一列中有频率)甚至 -nk2

edit2:这是我的“临时”文件的示例

aol.com
netscape.net
yahoo.com
yahoo.com
adelphia.net
twcny.rr.com
charter.net
yahoo.com

编辑 3:

预期输出:

33 aol.com
24 netscape.net
18 yahoo.com
5 adelphia.net
4 twcny.rr.com
3 charter.net
total 6

(无重复电子邮件,共有 6 个唯一电子邮件地址)

【问题讨论】:

  • 向我们展示来自文件temp 的简短但有代表性的样本。
  • @Daniel 为了清晰起见,最好添加预期的输出
  • 您的预期输出既不符合您的样本输入,也不符合您正在寻找的解决方案的既定意图(​​最高频率优先)。
  • @Sundeep 这是一个很好的猜测,但我想指导 OP 提供一个MCVE (Minimal, Complete, and Verifiable Example)。另请注意,输出甚至不符合“输出是电子邮件地址按最高频率排序”的要求。
  • @Daniel:通常最好先开始您的问题解释您要解决的问题,然后尝试(不成功的)解决方案 - 这可能是也可能不是基本上在正确的轨道上。否则,您将成为XY problem 的受害者。

标签: awk


【解决方案1】:

修改示例输入以包含具有两个实例的电子邮件

$ cat ip.txt 
aol.com
netscape.net
yahoo.com
yahoo.com
adelphia.net
twcny.rr.com
netscape.net
charter.net
yahoo.com

使用perl

$ perl -lne '
$c++ if !$h{$_}++;
END
{
    @k = sort { $h{$b} <=> $h{$a} } keys %h;
    print "$h{$_} $_" foreach (@k);
    print "total ", $c;
}' ip.txt
3 yahoo.com
2 netscape.net
1 adelphia.net
1 charter.net
1 aol.com
1 twcny.rr.com
total 6
  • $c++ if !$h{$_}++ 为唯一输入行递增计数器,以输入行为键递增哈希值。两者的默认初始值为0
  • 处理完所有输入行后:
    • @k = sort { $h{$b} &lt;=&gt; $h{$a} } keys %h 获取按哈希值降序排序的键
    • print "$h{$_} $_" foreach (@k) 根据排序的键打印每个哈希值和键 @k
    • print "total ", $c 打印唯一行总数


如果愿意,可以写成单行:

perl -lne '$c++ if !$h{$_}++; END{@k = sort { $h{$b} <=> $h{$a} } keys %h; print "$h{$_} $_" foreach (@k); print "total ", $c}' ip.txt


参考: How to sort perl hash on values and order the keys correspondingly

【讨论】:

  • 将在我的摘要中包含这个很好的答案。
【解决方案2】:

在 Gnu awk 中使用 @Sundeep 的数据:

$ cat program.awk
{ a[$0]++ }                                # count domains 
END {
    PROCINFO["sorted_in"]="@val_num_desc"  # sort in desc order in for loop
    for(i in a) {                          # this for in desc order
        print a[i], i 
        j++                                # count total
    } 
    print "total", j
}

运行它:

$ awk -f program.awk ip.txt
3 yahoo.com
2 netscape.net
1 twcny.rr.com
1 aol.com
1 adelphia.net
1 charter.net
total 6

【讨论】:

    【解决方案3】:

    更新/总结

    在此为这款方便的排序工具总结一些经过测试的方法:

    使用 bash(在我的例子中是 v4.3.46)

    sortedfile="$(sort temp)" ; countedfile="$(uniq -c <<< "$sortedfile")" ; uniquefile="$(sort -rn <<< "$countedfile")" ; totalunique="$(wc -l <<< "$uniquefile")" ; echo -e "$uniquefile\nTotal: $totalunique"
    

    使用sh/ash/busybox(虽然它们并不完全相同,但它们在这些测试中的工作原理都是一样的)

    time (sort temp > /tmp/sortedfile ; uniq -c /tmp/sortedfile > /tmp/countedfile ; sort -rn /tmp/countedfile > /tmp/uniquefile ; totalunique="$(cat /tmp/uniquefile | wc -l)" ; cat /tmp/uniquefile ; echo "Total: $totalunique")
    

    使用perl(请参阅此答案https://stackoverflow.com/a/40145395/3544399

    perl -lne '$c++ if !$h{$_}++; END{@k = sort { $h{$b} <=> $h{$a} } keys %h; print "$h{$_} $_" foreach (@k); print "Total: ", $c}' temp
    

    测试了什么

    使用随机生成器创建了一个文件 temp

    • @domain.com 的唯一地址不同
    • 重复地址分散
    • 文件有 55304 总地址
    • 文件有17012重复地址

    文件的一个小样本如下所示:

    24187@9674.com 29397@13000.com 18398@27118.com 23889@7053.com 24501@7413.com 9102@4788.com 16218@20729.com 991@21800.com 4718@19033.com 22504@28021.com

    性能:

    为了完整起见,值得一提的是性能;

    perl:               sh:                 bash:
    
    Total: 17012        Total:    17012     Total:    17012
    
    real    0m0.119s    real    0m0.838s    real    0m0.973s
    user    0m0.061s    user    0m0.772s    user    0m0.894s
    sys     0m0.027s    sys     0m0.025s    sys     0m0.056s
    

    原始答案(计算总地址而非唯一地址):

    tcount="$(cat temp | wc -l)" ; sort temp | uniq -c -i | sort -rn ; echo "Total: $tcount"
    
    • tcount="$(cat temp | wc -l)":使用行数制作变量
    • sort temp: 为 uniq 准备的群组电子邮件地址
    • uniq -c -i:计算允许大小写变化的出现次数
    • sort -rn:按出现次数排序并颠倒顺序(最高在上)
    • echo "Total: $tcount":在底部显示总地址

    示例临时文件

    john@domain.com
    john@domain.com
    donald@domain.com
    john@domain.com
    sam@domain.com
    sam@domain.com
    bill@domain.com
    john@domain.com
    larry@domain.com
    sam@domain.com
    larry@domain.com
    larry@domain.com
    john@domain.com
    

    样本输出:

       5 john@domain.com
       3 sam@domain.com
       3 larry@domain.com
       1 donald@domain.com
       1 bill@domain.com
    Total:       13
    

    编辑:关于sort的使用参见下面的cmets

    【讨论】:

    • 我建议不要对整行进行排序,除非你真的想这样做;要按特定字段排序,请使用-k&lt;n&gt;,&lt;n&gt; - 请注意,即使只对单个字段进行排序,也必须指定开始和停止字段(否则所有 starting 字段都会被排序)。
    • 现在我们对要求更清楚了:您快到了,但所需的总计数(唯一电子邮件地址)不是的计数输入行,但uniq -c输出的行数。
    • @mklement0 hmedia1 嗨,我编写了这两行代码并得到了我想要的输出: tcount="$(uniq -c -i temp | wc -l)" ;排序温度 | uniq -c -i |排序-rn; echo "Total : $tcount" 但是我仍然不完全确定我们为什么要排序两次。我的假设是我们对临时文件进行排序以在其上使用命令 uniq 然后对其进行数字排序。我在正确的轨道上吗?也谢谢你们的解释和帮助。
    • @Daniel:uniq -c 需要排序的输入才能有意义地工作,它会在其输出中保留输入排序顺序,并以频率计数为前缀。由于您希望输出按频率计数降序排序,因此您需要再次排序,这次按第一个字段,以相反的顺序。
    • 啊,我明白了。这更有意义。谢谢你的解释,下次我一定会让你们更容易。这里有点新,但提供的帮助质量和数量都很棒。再次感谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-05
    • 2020-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多