如何使用 bash 命令查找第一行中唯一单词的数量?

如何使用 bash 命令查找第一行中唯一单词的数量?

我想在我的文件 cdj.tsv 中查找唯一单词的数量。我可以使用head -n 1 cdj.tsv这个来获取第一行。现在我想要这一行中唯一单词的数量。我该如何得到这个数字?命令的结果head -n 1 cdj.tsv如下:

Country China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   China   Denmark Denmark Denmark Denmark Denmark DenmarkDenmark  Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark DenmarkDenmark  Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark DenmarkDenmark  Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark DenmarkDenmark  Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark Denmark DenmarkDenmark  Denmark

所以,我希望输出为 3(代表国家、中国和丹麦)。

谢谢

答案1

简单的方式:

  • 使用以下命令获取文件的第一行head -n 1 cdj.tsv (你已经知道了)或者通过名称从多个文件中提取,如下head -q -n 1 cdj.tsv file2.tsv file3.tsv所示-q将禁止打印额外的标题/文件名,以便只打印文件中的行。您可以使用通配符 *使用输入文件名,就像这样head -q -n 1 *.tsv处理当前目录中所有以.tsv扩展名作为输入的文件。

  • 然后,管道|tr -s ' ' '\n'处理每个单词在新的一行上即一次一个...(注意:许多替代工具可以用于在这一步做同样的事情,即使效率低得多xargs -n 1答案是@Peter Cordes这方面的资料值得一读。

  • 然后,将其传输到sort -u仅对唯一的单词进行排序并打印。

  • 然后,为了获得单词本身及其计数,将其传输到teewc -l在一个流程替代语法>(wc -l),并将两者都放在子壳(...)对输出进行分组的语法如下:

    head -q -n 1 *.tsv | tr -s ' ' '\n' | sort -u | (tee >(wc -l))
    
  • 示例的结果将如下所示:

    China
    Country
    Denmark
    DenmarkDenmark
    4
    

其他快点方式awk或者呆呆地

  • 开始字(字段)计数器,将其初始值设置为1像这样i=1,将其最大值限制为可用字段数 像这样,并随着每个新字段i<=NF增加像这样,并将所有这些放入1i++awkfor控制语句像这样的语法for ( i=1; i<=NF; i++ )

  • 然后,对于每个字段,检查正在处理的行是否是文件中的第一行NR==1,如果为真,则检查当前字段(单词)之前是否没有出现过,如果为真,则将其引用添加到这样的数组中!seen[$i]++,然后打印出来print $i,并将所有内容放入awkif控制语句{...}像这样的动作组中的语法{ if ( NR==1 && !seen[$i]++ ) print $i }

  • 然后,打印唯一单词(字段)的总数,并将print length(seen)其放在awk 条件模式元素 END像这样END { print length(seen) }

  • 然后,在单个输入文件上使用它,如下所示:

    awk '{ for ( i=1; i<=NF; i++ ) { if ( NR==1 && !seen[$i]++ ) print $i }} END { print length(seen) }' cdj.tsv
    
  • 或者在多个输入文件上使用它,FNR==1而不是NR==1像这样:

    awk '{ for ( i=1; i<=NF; i++ ) { if ( FNR==1 && !seen[$i]++ ) print $i }} END { print length(seen) }' *.tsv
    
  • <(head -q -n 1 *.tsv)或者在有或没有的多个输入文件上使用它,NR==1或者FNR==1像这样:

    awk '{ for ( i=1; i<=NF; i++ ) { if ( !seen[$i]++ ) print $i }} END { print length(seen) }' <(head -q -n 1 *.tsv)
    

答案2

perl -ne 'undef %c; grep($c{$_}++,split); print join(" ",scalar(%c),keys %c),"\n"'

4 Country China DenmarkDenmark Denmark

答案3

 head -q -n 1 *.tsv | tr -s '[:space:]' '\n' | sort -u | tee /dev/tty | wc -l

tr -s characters \n是将空格拆分为独立行的好方法。任何数量的空格字符都会被“压缩”( tr -s) 为一个换行符。

如果您还想删除前导空格(第一个单词之前),则可以使用sed两个单独的-e操作,一个用于删除该空格,另一个用于压缩后面的空格。 -E扩展正则表达式

... | sed -E -e 's/^[[::space:]]+// -e 's/[[:space:]]+/\n/g' | ...

或者使用tee终端(这会干扰将整个内容重定向到文件),@Raffa 的回答建议| (tee >(wc -l))进行进程替换。(不过,不确定为什么tee它本身必须位于子 shell 中。)


更高效的版本只是将直方图放入管道中,awkperl不是通过多个过程进行排序然后计数。另外两个答案展示了如何做到这一点。


不过,Raffa 答案的另一部分对于一般用途来说并不是一个好建议。 xargs -n 1(使用其默认命令echo)在某些情况下可以替代tr,但速度要慢得多(为每个单词 fork/exec 整个过程)。

更糟糕的是,GNUecho会吃掉代码-n-e因此如果你的文本包含这些“单词”,它就不够健壮。例如,

echo -e 'foo \t  -n \n\n bar\n\n\nbaz' |  tr -s '[:space:]' '\n' | sort -u | tee /dev/tty | wc -l
bar
baz
foo
-n
4
# this version loses the -n
echo -e 'foo \t  -n \n\n bar\n\n\nbaz' |  xargs -n 1 | sort -u | tee /dev/tty | wc -l
bar
baz
foo
3

黑客xargs -n 1还会将您的数据作为命令行参数暴露给其他用户,ps auxw如果他们在恰当的时间运行它,他们就可以通过/proc命令行参数看到您的数据。(例如,在循环中,或者通过某种监视来获取新 PID 的命令行参数。)

如果您不关心小数据集的效率,并且发现它更易于交互式使用,那么您可以随意使用它,但这是对xargs的调试功能的奇怪滥用,默认使用echo而不是有意义的命令。这绝对不是您想要放入脚本中的东西;如果您要花时间编写脚本,请使用一些强大而高效的脚本,只要它只需要多花几秒钟来输入或提醒自己手册页的内容即可。

它确实有一个优点,就是忽略前导空格。

相关内容