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

Posted

技术标签:

【中文标题】如何删除文件中的重复行而不在 Unix 中对其进行排序【英文标题】:How to delete duplicate lines in a file without sorting it in Unix 【发布时间】:2010-11-29 11:41:18 【问题描述】:

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

我可以使用sort -uuniq 命令来做到这一点,但我想使用sedawk

这可能吗?

【问题讨论】:

如果您的意思是连续重复,那么仅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] 包含更多的字符......事实上,除了一个之外,所有这些字符。【参考方案3】:

类似于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 的文本文件?太慢了。 一如既往,the cat is useless. 无论如何,uniq 已经自己完成了这项工作,并且不需要每行输入恰好是一个单词。

以上是关于如何删除文件中的重复行而不在 Unix 中对其进行排序的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Hive 中对文件进行重复数据删除并保持原始排序顺序?

事务的隔离级别和传播特性

如何根据python中的多个条件对excel文件进​​行重复数据删除?

删除重复行而不排序[重复]

如何在Python中对版本标签列表进行排序[重复]

给定一个未排序的数组,如何删除重复项然后对其进行排序?