ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

那可能吗?

如果您的意思是连续重复,那么仅 uniq 就足够了。
否则,我相信使用 awk 是可能的,但在更大的文件上会非常消耗资源。
重复的 stackoverflow.com/q/24324350stackoverflow.com/q/11532157 有有趣的答案,理想情况下应该在这里迁移。

P
Peter Mortensen
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,并且该行将不会写入输出。


要将其保存在文件中,我们可以这样做 awk '!seen[$0]++' merge_all.txt > output.txt
这里有一个重要的警告:如果您需要对多个文件执行此操作,并且在命令末尾添加更多文件,或者使用通配符……“seen”数组将填满所有文件中的重复行。如果您想独立处理每个文件,则需要执行类似 for f in *.txt; do gawk -i inplace '!seen[$0]++' "$f"; done 的操作
@NickK9 在多个文件中累积重复数据删除本身就很棒。不错的提示
这也归功于'++'运算符的结果不是递增后的值,而是前一个值。
A
Andre Miller

来自 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 ... <sniff>让我伤心。 ;) 无论如何,[ -~] 表示从 0x20(空格)到 0x7E(波浪号)的 ASCII 字符范围。这些被视为 the printable ASCII characters(链接页面也有 0x7F/delete,但这似乎不正确)。这使得任何不使用 ASCII 或使用制表符的任何人的解决方案都无法解决。更便携的 [^\n] 包括更多的字符......事实上,除了一个之外,所有这些字符都是如此。
P
Peter Mortensen

类似于 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 重定向到标准输出?管道不适用于这种方法。
我的原始答案输出到标准输出,以及第一个变体
P
Peter Mortensen

使用 Vim(与 Vi 兼容)的另一种方法:

从文件中删除重复的连续行:

vim -esu NONE +'g/\v^(.*)\n\1$/d' +wq

从文件中删除重复的、不连续的和非空的行:

vim -esu NONE +'g/\v^(.+)$\_.{-}^\1$/d' +wq


P
Peter Mortensen

当输入文件以空行结束且没有字符时,除了最新版本的 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;”。


P
Peter Mortensen

第一个解决方案也来自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 命令。当前模式空间的内容是下一个新行。


在 Windows 上使用 busybox 的相同命令:busybox echo -e "1\n2\n2\n3\n3\n3\n4\n4\n4\n4\n5" | busybox sed -nr "$!N;/^(.*)\n\1$/!P;D"
P
Peter Mortensen

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

P
Peter Mortensen

这可以使用 AWK 来实现。

以下行将显示唯一值:

awk file_name | uniq

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

awk file_name | uniq > uniq_file_name

新文件 uniq_file_name 将仅包含唯一值,没有任何重复项。


我认为 awk 在这里有点矫枉过正。
这只会删除连续的重复项。
P
Peter Mortensen

利用:

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

它使用 AWK 删除重复的行。


这会打乱行的顺序。
大约 20 GB 的文本文件是多少?太慢了。
与以往一样,the cat is useless. 无论如何,uniq 已经自己完成了这项工作,并且不需要输入每行恰好是一个单词。