在UNIX shell脚本中从列表中选择唯一或不同的值

318
我有一个ksh脚本,返回一长串以换行符分隔的数值,我想只看到唯一/不同的数值。这可能吗?
例如,假设我的输出是目录中文件后缀名的列表:
tar
gz
java
gz
java
tar
class
class
我想要看到像这样的列表:
tar
gz
java
class
8个回答

556
您可能需要查看uniqsort应用程序。
./yourscript.ksh | sort | uniq
(FYI,是的,在此命令行中必须使用sort,uniq仅删除紧随其后的重复行)
编辑:
Aaron Digullauniq的命令行选项方面所发布的相反的是:
给定以下输入:
class
jar
jar
jar
bin
bin
java
uniq将仅输出所有行一次:
class
jar
bin
java
uniq -d将输出所有出现多次的行,并将它们打印一次:
jar
bin
uniq -u将输出所有仅出现一次的行,并将它们打印一次:
class
java

2
晚来者请注意:@AaronDigulla的答案已经被更正。 - mklement0
3
非常好的观点,这个命令行中需要使用这种排序,uniq只能去除紧接在一起的重复行。我刚学到了这一点! - HattrickNZ
4
GNU sort 提供了一个 -u 选项,可以使排序结果只保留唯一的值。 - Mingye Wang
1
我发现uniq似乎默认只处理相邻的行,这意味着在输入之前可能需要对其进行排序。 - Stphane
我对400MB的数据进行了一些测试。
  • sort | uniq花费了95秒。
  • sort -u花费了77秒。
  • @ajak6提供的awk '!a[$0]++'花费了9秒。所以awk胜出,但也是最难记住的。
- MikeKulls

111
./script.sh | sort -u

这与monoxide答案基本相同,只是更加简明扼要。


10
你太谦虚了:你的解决方案也会表现得更好(可能只有在处理大数据集时才能注意到)。 - mklement0
我认为这应该比“... | sort | uniq”更有效,因为它可以一次性完成。 - Adrian Antunez
2
@AdrianAntunez 也许这也是因为 sort -u 不需要在找到已经遇到的早期值时更新已排序列表。而 sort | 必须在将其传递给 uniq 之前对 所有 项目进行排序。 - whyer
1
@mklement0 @AdrianAntunez 首先我认为 sort -u 可能更快,因为任何最优比较排序算法的复杂度都是 O(n*log(n)),但是使用哈希集数据结构可以在 O(n) 的复杂度下找到所有唯一值。尽管如此,sort -usort | uniq 的性能几乎相同,它们都很慢。我已经在我的系统上进行了一些测试,更多信息请参见 https://gist.github.com/sda97ghb/690c227eb9a6b7fb9047913bfe0e431d。 - Divano
谢谢!你的解决方案对我很有用,而 ./script.sh | sort | uniq -u 没有输出任何内容。也许是因为输出太大了?虽然它并不是很大,但输出有 50,000 行,只有 4 个不同的值。 - Ferran Maylinch

16
使用zsh,您可以做到这一点:
% cat infile 
tar
more than one word
gz
java
gz
java
tar
class
class
zsh-5.0.0[t]% print -l "${(fu)$(<infile)}"
tar
more than one word
gz
java
class

或者您可以使用AWK:

% awk '!_[$0]++' infile    
tar
more than one word
gz
java
class

3
不涉及对输入进行排序的巧妙解决方案。 注意事项: 非常聪明但晦涩难懂的“awk”解决方案(请参见https://dev59.com/GHvaa4cB1Zd3GeqPDXSM#21200722以获取说明)将与大文件一起工作,只要唯一行数足够小(因为唯一行保存在内存中)。 “zsh”解决方案首先将整个文件读入内存,这在处理大文件时可能不是一个选项。此外,如所写,仅正确处理没有嵌入空格的行;要修复此问题,请改用“IFS=$'\n' read -d '' -r -A u <file; print -l ${(u)u}”。 - mklement0
正确。 - Dimitre Radoulov
1
谢谢,这样更简单(假设您不需要设置子shell外部需要的变量)。我很好奇何时需要使用[@]后缀来引用数组的所有元素 - 看起来至少在版本5中它可以不用;或者您只是为了清晰而添加了它? - mklement0
1
@mklement0,你说得对!我在写帖子时没有考虑到这一点。实际上,这应该足够了:print -l "${(fu)$(<infile)}" - Dimitre Radoulov
1
太棒了,感谢您更新帖子 - 我顺便修复了awk示例输出。 - mklement0

16

使用 AWK,您可以进行以下操作:

 ./yourscript.ksh | awk '!a[$0]++'

我发现它比sort和uniq更快


这绝对是我最喜欢的完成工作的方式,非常感谢!特别是对于较大的文件,sort|uniq解决方案可能不是你想要的。 - Schmitzi
我进行了一些测试,这个解决方案比其他方案快10倍,但是记忆难度也增加了10倍 :-) - MikeKulls
1
是的,我不太确定awk在这里做什么。但感谢您提供的解决方案! - Barbituate

13
将它们通过 `sort` 和 `uniq` 进行管道处理。这将移除所有重复项。
`uniq -d` 仅提供重复项,`uniq -u` 仅提供唯一项(去除重复项)。

看起来首先得排序。 - brabster
1
是的,你需要这样做。或者更准确地说,你需要将所有重复的行分组在一起。排序本质上就是这样做的 ;) - Matthew Scharley
另外,uniq -u不是默认行为(有关详细信息,请参见我的答案中的编辑)。 - Matthew Scharley

11

如果数据集较大,可能不希望排序,可以使用以下 Perl 脚本:

./yourscript.ksh | perl -ne 'if (!defined $x{$_}) { print $_; $x{$_} = 1; }'

这基本上只是记住每个输出行,以便不再输出它。

与"sort | uniq"解决方案相比,它的优点在于不需要预先排序。


2
请注意,使用sort对非常大的文件进行排序本身并不是问题;它可以对比可用RAM+交换空间更大的文件进行排序。然而,如果只有很少的重复项,Perl将会失败。 - Aaron Digulla
1
是的,这取决于预期的数据而做出权衡。对于具有许多重复项的大型数据集,Perl 更好(无需基于磁盘的存储)。具有少量重复项的大型数据集应使用 sort(和磁盘存储)。小数据集可以使用任何一种。个人而言,我会首先尝试 Perl,如果失败则切换到 sort。 - paxdiablo
只有在排序需要交换到磁盘时,才能获得排序的好处。 - paxdiablo
5
当我想要每行的第一个出现时,这很棒。排序会破坏这个。 - Bluu
最终,Perl 将以某种形式对条目进行排序,以放入其字典中(或者在 Perl 中称为其他名称),因此您实际上无法避免排序的处理时间。 - MikeKulls
tail -F -n+1 urls.txt | perl -ne 'if (!defined $x{$}) { print $; $x{$_} = 1; }' | while read -r url; ... 这个版本适用于当你需要立即流到另一个管道时。+1 - Phillmac

1

独特的,按要求(但未排序);
在测试时间内使用少于70个元素的系统资源更少;
编写以从stdin获取输入,
(或修改并包含在另一个脚本中):
(Bash)

bag2set () {
    # Reduce a_bag to a_set.
    local -i i j n=${#a_bag[@]}
    for ((i=0; i < n; i++)); do
        if [[ -n ${a_bag[i]} ]]; then
            a_set[i]=${a_bag[i]}
            a_bag[i]=$'\0'
            for ((j=i+1; j < n; j++)); do
                [[ ${a_set[i]} == ${a_bag[j]} ]] && a_bag[j]=$'\0'
            done
        fi
    done
}
declare -a a_bag=() a_set=()
stdin="$(</dev/stdin)"
declare -i i=0
for e in $stdin; do
    a_bag[i]=$e
    i=$i+1
done
bag2set
echo "${a_set[@]}"

0

我得到了一个更好的技巧,可以在文件中获取非重复条目

awk '$0 != x ":FOO" && NR>1 {print x} {x=$0} END {print}' file_name | uniq -f1 -u


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接