有没有办法在 Unix 中删除文件中的重复行?
我可以使用 sort -u
和 uniq
命令,但我想使用 sed
或 awk
。
那可能吗?
uniq
就足够了。
awk
是可能的,但在更大的文件上会非常消耗资源。
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]
将评估为 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'
$!
部分是否必要? sed 'N; /^\(.*\)\n\1$/!P; D'
不做同样的事情吗?我想不出一个在我的机器上两者不同的例子(我确实在最后尝试了一个空行,两个版本都很好)。
[ -~]
表示从 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
使用 Vim(与 Vi 兼容)的另一种方法:
从文件中删除重复的连续行:
vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq
从文件中删除重复的、不连续的和非空的行:
vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq
当输入文件以空行结束且没有字符时,除了最新版本的 sed 之外,Andre Miller posted 的单行符有效。在我的 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;”。
第一个解决方案也来自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 命令将下一行读入模式空间。 /^(.*)\n\1$/!P:如果当前模式空间的内容是两个用\n隔开的重复字符串,表示下一行与当前行相同,我们可以不打印我们的核心理念;否则,这意味着当前行是其所有重复的连续行的最后一次出现。我们现在可以使用 P 命令打印当前模式空间中的字符,直到 \n(也打印了 \n)。 D:我们用D命令删除当前模式空间中的字符,直到\n(\n也被删除),然后模式空间的内容就是下一行。 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 命令设置一个名为 loop 的标签。使用 N 将下一行读入模式空间。如果下一行与当前行相同,则使用 s/^(.*)\n\1$/\1/ 删除当前行。我们使用 s 命令来执行删除操作。如果 s 命令执行成功,则使用 tloop 命令强制 sed 跳转到名为 loop 的标签处,这将对下一行进行相同的循环,直到最近打印的行没有重复的连续行;否则,使用 D 命令删除与最新打印的行相同的行,并强制 sed 跳转到第一个命令,即 p 命令。当前模式空间的内容是下一个新行。
busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
uniq 会被尾随空格和制表符所欺骗。为了模拟人类进行比较的方式,我在比较之前修剪了所有尾随空格和制表符。
我认为 $!N;
需要花括号,否则它会继续,这就是无限循环的原因。
我在 Ubuntu 20.10(Groovy Gorilla)中有 Bash 5.0 和 sed 4.7。在字符集匹配时,第二个单行代码不起作用。
是三种变体。第一个是消除相邻的重复行,第二个是消除重复行,无论它们出现在哪里,第三个是消除文件中除最后一个行之外的所有行。
# 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;
';
}
这可以使用 AWK 来实现。
以下行将显示唯一值:
awk file_name | uniq
您可以将这些唯一值输出到新文件:
awk file_name | uniq > uniq_file_name
新文件 uniq_file_name 将仅包含唯一值,没有任何重复项。
利用:
cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'
它使用 AWK 删除重复的行。
不定期副业成功案例分享
awk '!seen[$0]++' merge_all.txt > output.txt
for f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done
的操作