我怎样才能删除Unix中的文件中的重复行?
有没有办法在Unix中删除文件中的重复行?
我可以用sort -u
和uniq
命令来完成。 但我想用sed
或awk
。 那可能吗?
awk '!seen[$0]++' file.txt
seen
是一个Awk会传递文件的每一行的关联数组。 如果一行不在数组中,则seen[$0]
将计算为false。 那!
是一个逻辑NOT运算符,将会将false转换为真。 Awk将打印expression式计算结果为真的行。 seen
++
增量,以便在第一次find一行后seen[$0] == 1
,然后seen[$0] == 2
,依此类推。
Awk评估一切,但0
和""
(空string)为true。 如果一个重复的行被放在seen
那么!seen[$0]
将计算为false,并且该行不会被写入输出。
从http://sed.sourceforge.net/sed1line.txt :(请不要问我这是如何工作;-))
# delete duplicate, consecutive lines from a file (emulates "uniq"). # First line in a set of duplicate lines is kept, rest are deleted. sed '$!N; /^\(.*\)\n\1$/!P; D' # delete duplicate, nonconsecutive lines from a file. Beware not to # overflow the buffer size of the hold space, or else use GNU sed. sed -n 'G; s/\n/&&/; /^\([ -~]*\n\).*\n\1/d; s/\n//; h; P'
Perl单线程类似于@ jonas的awk解决scheme:
perl -ne 'print if ! $x{$_}++' file
比较之前,此变体将删除尾随的空格:
perl -lne 's/\s*$//; print if ! $x{$_}++' file
这个变化在原地编辑文件:
perl -i -ne 'print if ! $x{$_}++' file
这种变化就地编辑文件,并做一个备份file.bak
perl -i.bak -ne 'print if ! $x{$_}++' file
安德烈·米勒(Andre Miller)在上面发表的一行内容,除了最近版本的sed,当input文件以空白行结束并且没有字符时。 在我的Mac上,我的CPU只是旋转。
无限循环,如果最后一行是空白的,没有字符 :
sed '$!N; /^\(.*\)\n\1$/!P; D'
不挂,但你失去了最后一行
sed '$d;N; /^\(.*\)\n\1$/!P; D'
解释是在sed常见问题的最后 :
GNU sed的维护者觉得尽pipe有可移植性的问题
这将导致,改变N命令打印(而不是
删除)模式空间更符合自己的直觉
关于“追加下一行”的命令应该如何performance。
另一个有利于改变的事实是“{N; command;}”
如果文件有奇数行,删除最后一行,但是
如果文件具有偶数行,则打印最后一行。转换使用N的前一个行为的脚本(删除
在达到EOF时的模式空间)到兼容的脚本
所有版本的sed, 改变一个孤独的“N” 到“$ d; N” 。
使用Vim(Vi兼容)的另一种方法 :
从文件中删除重复的连续行:
vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq
从文件中删除重复,非连续和非空行:
vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq
第一个解决scheme也来自http://sed.sourceforge.net/sed1line.txt
$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr '$!N;/^(.*)\n\1$/!P;D' 1 2 3 4 5
核心思想是:
print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.
解释:
-
$!N;
:如果当前行不是最后一行,则使用N
命令将下一行读入pattern space
。 -
/^(.*)\n\1$/!P
:如果当前pattern space
的内容是由\n
分隔的两个duplicate string
,这意味着下一行与当前行same
,我们不能根据我们的核心理念 否则,这意味着当前行是所有重复连续行的最后一行,现在我们可以使用P
命令在当前pattern space
util\n
(\n
也打印)中打印字符。 -
D
:我们使用D
命令删除当前pattern space
的字符util\n
(\n
也删除),那么pattern space
的内容就是下一行。 -
D
命令将强制sed
跳转到它的FIRST
命令$!N
,但是不会从文件或标准inputstream中读取下一行。
第二个解决scheme很容易理解(从我自己):
$ echo -e '1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5' |sed -nr 'p;:loop;$!N;s/^(.*)\n\1$/\1/;tloop;D' 1 2 3 4 5
核心思想是:
print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP.
解释:
- 从inputstream或文件中读取一行并打印一次。
- 使用
:loop
命令设置一个名为loop
的label
。 - 使用
N
读取下一行到pattern space
。 - 如果下一行与当前行相同,使用
s/^(.*)\n\1$/\1/
删除当前行,我们使用s
命令来执行delete
操作。 - 如果
s
命令执行成功,则使用tloop
命令强制sed
跳转到名为loop
的label
,这将对下一行执行相同的循环util没有latest printed
的行的重复连续行; 否则使用D
命令delete
与最后一行相同的latest-printed line
,强制sed
跳转到第一个命令,即p
命令,当前pattern space
的内容是下一个新行。
cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'
使用awk删除重复的行。