如何删除文件中的重复行而不在 Unix 中排序

有没有办法在 Unix 中删除文件中的重复行?

我可以使用 sort -uuniq命令,但是我想使用 sedawk

这可能吗?

125989 次浏览

来自 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'
awk '!seen[$0]++' file.txt

Abc0是一个关联数组,AWK 将把文件的每一行都传递给它。如果数组中没有一行,那么 seen[$0]将计算为 false。!是逻辑 NOT 操作符,它将把 false 反转为 true。AWK 将打印表达式计算结果为 true 的行。

++增加 seen使得 seen[$0] == 1在第一次找到一行之后,然后是 seen[$0] == 2,以此类推。 AWK 将除 0""(空字符串)之外的所有值都计算为 true。如果在 seen中放置了一个重复的行,那么 !seen[$0]将计算为 false,并且该行不会写入输出。

除了当输入文件以空行结束且没有字符时,sed 的最新版本之外,Andre Miller 发布的都可以使用一行程序。在我的 Mac 上,我的 CPU 只会旋转。

如果最后一行是空的并且没有任何特征,那么这就是一个无限循环:

sed '$!N; /^\(.*\)\n\1$/!P; D'

没挂起来,但最后一句没了:

sed '$d;N; /^\(.*\)\n\1$/!P; D'

解释在 常见问题的最后:

GNU sed 维护人员认为,尽管存在可移植性问题
这将导致将 N 命令更改为 print (而不是
删除)模式空间更符合人的直觉
关于命令“ append the Next line”应该的行为。
另一个有利于更改的事实是“{ 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

用途:

cat filename | sort | uniq -c | awk -F" " '$1<2 {print $2}'

它使用 AWK 删除重复的行。

类似于 Jonas 的 AWK 解决方案的 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

第一个解决方案也来自 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 命令实现 循环

说明:

  1. $!N;: 如果当前行是 没有的最后一行,使用 N命令将下一行读入 模式空间
  2. /^(.*)\n\1$/!P: 如果当前 模式空间的内容是由 \n分隔的两个 重复的字符串,这意味着下一行是带有当前行的 一样,我们可以 没有根据我们的核心思想打印它; 否则,这意味着当前行是所有重复连续行的 最后外观。我们现在可以使用 P命令打印当前 模式空间中的字符,直到 \n(\n也打印出来)。
  3. D: 我们使用 D命令删除当前 模式空间中的字符,直到 \n(\n也被删除) ,然后 模式空间的内容是下一行。
  4. 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。

说明:

  1. 从输入流或文件中读取新行并打印一次。
  2. 使用 :loop命令设置一个名为 循环标签
  3. 使用 N将下一行读入 模式空间
  4. 如果下一行与当前行相同,则使用 s/^(.*)\n\1$/\1/删除当前行。我们使用 s命令执行 删除操作。
  5. 如果 s命令执行成功,那么使用 Tloop命令强制 sed跳转到名为 循环标签,这将对下一行执行相同的循环,直到没有重复的连续行的行是 最新印刷品; 否则,使用 D命令对 delete的行是相同的 sed0,并强制 sed跳转到第一个命令,这是 p命令。当前 sed1的内容是下一个新行。

这可以通过使用 AWK 来实现。

下面一行将显示唯一的值:

awk file_name | uniq

您可以将这些唯一值输出到一个新文件:

awk file_name | uniq > uniq_file_name

新文件 Uniq _ file _ name将只包含唯一的值,没有任何重复。

Uniq 会被尾随的空格和制表符愚弄。为了模拟人类如何进行比较,我在比较之前修剪了所有的尾随空格和制表符。

我认为 $!N;需要花括号,否则它会继续,这就是无限循环的原因。

Ubuntu 20.10中有 Bash 5.0和 sed 4.7(Groovy Gorilla)。在字符集匹配时,第二个一行程序不起作用。

有三种变化。第一种方法是消除相邻的重复行,第二种方法是在重复行出现的地方消除重复行,第三种方法是消除文件中除最后一个实例以外的所有重复行。

面糊

# 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;
';
}