如何在Unix中删除文件中的重复行?

时间:2009-09-18 12:58:26

标签: unix shell scripting sed awk

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

我可以使用sort -uuniq命令执行此操作,但我想使用sedawk。 这可能吗?

9 个答案:

答案 0 :(得分:236)

awk '!seen[$0]++' file.txt

seen是一个关联数组,Awk会将文件的每一行传递给。如果一行不在数组中,那么seen[$0]将评估为false。 !是逻辑NOT运算符,将false反转为true。 Awk将打印表达式计算结果为true的行。 ++增加seen,以便在第一次找到一行后再seen[$0] == 1,然后seen[$0] == 2,依此类推。
Awk会评估除0""(空字符串)之外的所有内容。如果在seen中放置了重复的行,则!seen[$0]将评估为false,并且该行不会被写入输出。

答案 1 :(得分:27)

来自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'

答案 2 :(得分:11)

Perl one-liner类似于@ jonas的awk解决方案:

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

答案 3 :(得分:6)

除了最近版本的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;”

答案 4 :(得分:4)

使用Vim(Vi兼容)的替代方法:

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

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

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

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

答案 5 :(得分:3)

第一个解决方案也来自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

核心理念是:

print ONLY once of each duplicate consecutive lines at its LAST appearance and use D command to implement LOOP.

说明:

  1. $!N;:如果当前行不是最后一行,请使用N命令将下一行读入pattern space
  2. /^(.*)\n\1$/!P:如果当前pattern space的内容是由duplicate string分隔的两个\n,则表示下一行是带有当前行的same,我们不能按照我们的核心理念打印它;否则,这意味着当前行是其所有重复连续行的最后一次出现,我们现在可以使用P命令在当前pattern space util \n中打印字符({{1}也印刷了。)。
  3. \n:我们使用D命令删除当前D util pattern space\n也已删除)中的字符,然后\n的内容1}}是下一行。
  4. pattern space命令会强制D跳转到sed命令FIRST,但不会从文件或标准输入流中读取下一行。
  5. 第二种解决方案很容易理解(来自我自己):

    $!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
    

    说明:

    1. 从输入流或文件中读取新行并打印一次。
    2. 使用print ONLY once of each duplicate consecutive lines at its FIRST appearance and use : command & t command to implement LOOP. 命令设置名为:loop的{​​{1}}。
    3. 使用label将下一行读入loop
    4. 如果下一行与当前行相同,则使用N删除当前行,我们使用pattern space命令执行s/^(.*)\n\1$/\1/操作。
    5. 如果成功执行s命令,则使用delete命令强制s跳转到名为tloop的{​​{1}},这将执行相同操作循环到下一行util,没有重复的行连续行sed;否则,使用label命令looplatest printed相同的行,并强制D跳转到第一个命令,即delete命令,当前latest-printed line的内容是下一个新行。

答案 6 :(得分:1)

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

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

我在Ubuntu 20.10中拥有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;
 ';
}

答案 7 :(得分:-2)

这可以通过 awk
实现 行下方将显示唯一值

awk file_name | uniq

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

awk file_name | uniq > uniq_file_name

新文件uniq_file_name仅包含唯一值,没有重复值

答案 8 :(得分:-4)

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

使用awk删除重复的行。