如何删除文件中的重复行而不在 Unix 中对其进行排序
Posted
技术标签:
【中文标题】如何删除文件中的重复行而不在 Unix 中对其进行排序【英文标题】:How to delete duplicate lines in a file without sorting it in Unix 【发布时间】:2010-11-29 11:41:18 【问题描述】:有没有办法在 Unix 中删除文件中的重复行?
我可以使用sort -u
和uniq
命令来做到这一点,但我想使用sed
或awk
。
这可能吗?
【问题讨论】:
如果您的意思是连续重复,那么仅uniq
就足够了。
否则,我相信awk
是可能的,但在更大的文件上会非常消耗资源。
重复的***.com/q/24324350 和***.com/q/11532157 有有趣的答案,理想情况下应该在这里迁移。
【参考方案1】:
awk '!seen[$0]++' file.txt
seen
是一个关联数组,AWK 会将文件的每一行传递给它。如果数组中没有一行,则seen[$0]
将评估为假。 !
是逻辑 NOT 运算符,会将 false 反转为 true。 AWK 将打印表达式计算结果为 true 的行。
++
递增 seen
以便在第一次找到一行之后 seen[$0] == 1
然后是 seen[$0] == 2
,依此类推。
AWK 将除 0
和 ""
(空字符串)之外的所有内容评估为 true。如果在seen
中放置了重复行,则!seen[$0]
将评估为假,并且该行不会被写入输出。
【讨论】:
要将其保存在文件中,我们可以这样做awk '!seen[$0]++' merge_all.txt > output.txt
这里有一个重要的警告:如果您需要对多个文件执行此操作,并且您在命令末尾添加更多文件,或者使用通配符...... 'seen' 数组将充满重复所有文件中的行。如果您想独立处理每个文件,则需要执行for f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
之类的操作
@NickK9 在多个文件中累积重复数据删除本身就很棒。不错的提示
这也归功于'++'运算符的结果不是递增后的值,而是前一个值。【参考方案2】:
来自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'
【讨论】:
geekery;-) +1,但资源消耗在所难免。 '$!N; /^(.*)\n\1$/!P; D'的意思是“如果你不在最后一行,读另一行。现在看看你有什么,如果它不是东西,然后是换行符,然后又是同样的东西,打印出这些东西。现在删除东西(直到换行符)。” 'G; s/\n/&&/; /^([ -~]*\n).*\n\1/d; s/\n//; H; P' 大致意思是“将整个保留空间附加到这一行,然后如果你看到重复的行将整个内容扔掉,否则将整个混乱复制回保留空间并打印第一部分(这是你刚刚的行阅读。”$!
部分是否必要? sed 'N; /^\(.*\)\n\1$/!P; D'
不做同样的事情吗?我想不出一个在我的机器上两者不同的例子(我确实在最后一个空行尝试了两个版本,它们都很好)。
差不多 7 年后,没有人回答 @amichair ... [ -~]
表示从 0x20(空格)到 0x7E(波浪号)的 ASCII 字符范围。这些被认为是the printable ASCII characters(链接页面也有 0x7F/delete,但这似乎不对)。这使得任何不使用 ASCII 或使用制表符的人的解决方案都被打破了。更便携的[^\n]
包含更多的字符......事实上,除了一个之外,所有这些字符。类似于jonas's AWK solution的Perl单行:
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
【讨论】:
如何将 otuput 重定向到标准输出?管道不适用于这种方法。 我的原始答案输出到标准输出,以及第一个变体【参考方案4】:使用 Vim 的另一种方式(与 Vi 兼容):
从文件中删除重复的连续行:
vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq
从文件中删除重复的、不连续的和非空的行:
vim -esu NONE +'g/\v^(.+)$\_.-^\1$/d' +wq
【讨论】:
【参考方案5】:Andre Miller posted 在输入文件以空行和无字符结尾时,除了最新版本的 sed 之外,可以使用单行。在我的 Mac 上,我的 CPU 只是旋转。
如果最后一行是空白且没有任何字符,则这是一个无限循环:
sed '$!N; /^\(.*\)\n\1$/!P; D'
它没有挂起,但是你丢失了最后一行:
sed '$d;N; /^\(.*\)\n\1$/!P; D'
解释在sed FAQ的最后:
GNU sed 维护者认为,尽管存在可移植性问题 这会导致,将 N 命令更改为打印(而不是 删除)模式空间更符合直觉 关于“追加下一行”的命令应该如何表现。 另一个有利于改变的事实是“N;command;”将 如果文件有奇数行,则删除最后一行,但是 如果文件有偶数行,则打印最后一行。
转换使用前 N 行为的脚本(删除 到达 EOF 时的模式空间)到与 兼容的脚本 所有版本的 sed,改变一个单独的“N;”到“$d;N;”。
【讨论】:
【参考方案6】:第一个解也是来自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
核心思想是:
每个重复的连续行在其最后出现时打印仅一次,并使用D命令实现循环。
解释:
$!N;
:如果当前行不是最后一行,使用N
命令将下一行读入pattern space。
/^(.*)\n\1$/!P
:如果当前pattern space的内容是两个重复的字符串,用\n
隔开,表示下一行是相同 有了当前行,我们可以不按照我们的核心思想打印它;否则,这意味着当前行是其所有重复连续行的 last 外观。我们现在可以使用P
命令打印当前模式空间 中的字符,直到\n
(\n
也被打印)。
D
:我们使用D
命令删除当前pattern space中的字符直到\n
(\n
也被删除),然后pattern space 是下一行。
和D
命令将强制sed
跳转到其第一个 命令$!N
,但不从文件或标准输入读取下一行流。
第二种解决方案很容易理解(来自我自己):
$ 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
核心思想是:
仅在每个重复的连续行中第一次出现打印一次,并使用
:
命令和t
命令来实现循环。
解释:
-
从输入流或文件中读取一个新行并打印一次。
使用
:loop
命令设置一个名为loop的标签。
使用N
将下一行读入模式空间。
如果下一行与当前行相同,则使用s/^(.*)\n\1$/\1/
删除当前行。我们使用s
命令来执行删除操作。
如果s
命令执行成功,则使用tloop命令强制sed
跳转到名为loop的标签 >,它将对下一行执行相同的循环,直到 最新打印的行没有重复的连续行;否则,使用D
命令到delete
与最新打印行 相同的行,并强制sed
跳转到第一个命令,即p
命令。当前模式空间的内容是下一个新行。
【讨论】:
在 Windows 上使用 busybox 的相同命令:busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
【参考方案7】:
这可以使用 AWK 来实现。
以下行将显示唯一值:
awk file_name | uniq
您可以将这些唯一值输出到新文件:
awk file_name | uniq > uniq_file_name
新文件 uniq_file_name 将只包含唯一值,没有任何重复。
【讨论】:
我认为 awk 在这里有点矫枉过正。 这只会删除连续的重复项。【参考方案8】:uniq 会被尾随空格和制表符所欺骗。为了模仿人类进行比较的方式,我在比较之前修剪了所有尾随空格和制表符。
我认为$!N;
需要花括号,否则它会继续,这就是无限循环的原因。
我在Ubuntu 20.10(Groovy Gorilla)中有 Bash 5.0 和 sed 4.7。在字符集匹配时,第二个单行代码不起作用。
共有三种变体。第一个是消除相邻的重复行,第二个是消除重复行,无论它们出现在哪里,第三个是消除文件中除最后一个实例之外的所有行。
pastebin
# First line in a set of duplicate lines is kept, rest are deleted.
# Emulate human eyes on trailing spaces and tabs by trimming those.
# Use after norepeat() to dedupe blank lines.
dedupe()
sed -E '
$!
N;
s/[ \t]+$//;
/^(.*)\n\1$/!P;
D;
';
# Delete duplicate, nonconsecutive lines from a file. Ignore blank
# lines. Trailing spaces and tabs are trimmed to humanize comparisons
# squeeze blank lines to one
norepeat()
sed -n -E '
s/[ \t]+$//;
G;
/^(\n)2,/d;
/^([^\n]+).*\n\1(\n|$)/d;
h;
P;
';
lastrepeat()
sed -n -E '
s/[ \t]+$//;
/^$/
H;
d;
;
G;
# delete previous repeated line if found
s/^([^\n]+)(.*)(\n\1(\n.*|$))/\1\2\4/;
# after searching for previous repeat, move tested last line to end
s/^([^\n]+)(\n)(.*)/\3\2\1/;
$!
h;
d;
;
# squeeze blank lines to one
s/(\n)3,/\n\n/g;
s/^\n//;
p;
';
【讨论】:
【参考方案9】:用途:
cat filename | sort | uniq -c | awk -F" " '$1<2 print $2'
它使用 AWK 删除重复的行。
【讨论】:
这会打乱行的顺序。 什么是大约 20 GB 的文本文件?太慢了。 一如既往,thecat
is useless. 无论如何,uniq
已经自己完成了这项工作,并且不需要每行输入恰好是一个单词。以上是关于如何删除文件中的重复行而不在 Unix 中对其进行排序的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Hive 中对文件进行重复数据删除并保持原始排序顺序?