在VIm中,如何删除所有重复的行?

时间:2014-03-05 09:29:35

标签: vim ex

我有一个包含以下行的文件:

one one
one one
two two two
one one
three three
one one
three three
four

我想从文件中删除所有出现的重复行,只留下非重复行。因此,在上面的示例中,结果应为:

two two two
four

我看到了this answer to a similar looking question。我尝试修改ex one-liner,如下所示:

:syn clear Repeat | g/^\(.*\)\n\ze\%(.*\n\)*\1$/exe 'syn match Repeat "^' . escape(getline ('.'), '".\^$*[]') . '$"' | d

但它不会删除所有重复的行,它只会删除一些事件。

我怎样才能在vim中这样做?或具体如何在vim中使用ex?

澄清一点,我不是在寻找sort u

9 个答案:

答案 0 :(得分:2)

如果您使用的是具有awk功能的Linux机箱,则该系列可满足您的需求:

:%!awk '{a[$0]++}END{for(x in a)if(a[x]==1)print x}'

答案 1 :(得分:2)

如果您可以访问UNIX样式的命令,则可以执行以下操作:

:%!sort | uniq -u

-u命令的uniq选项可执行您需要的任务。从uniq命令的帮助文本:

   -u, --unique
          only print unique lines

但是我应该注意,这个答案假定您不介意输出与输入文件可能已经存在的任何排序顺序不匹配。

答案 2 :(得分:1)

假设您使用的是UNIX衍生产品,下面的命令应该可以执行您想要的操作:

:sort | %!uniq -u

uniq仅适用于排序行,因此我们必须首先使用Vim的buit-in :sort命令对它们进行排序以保存一些输入(默认情况下它适用于整个缓冲区,因此我们不需要传递一个范围,这是一个内置命令,所以我们不需要!)。

然后我们通过uniq -u过滤整个缓冲区。

答案 3 :(得分:1)

它不保留剩余行的顺序,但这似乎有效:

:sort|%s/^\(.*\)\n\%(\1\n\)\+//

(这个版本是@Peter Rincker的想法,稍微改正一下。)在vim 7.3上,以下甚至更短的版本有效:

:sort | %s/^\(.*\n\)\1\+//

不幸的是,由于正则表达式引擎之间的差异,这不再适用于vim 7.4(包括补丁1-52)。

答案 4 :(得分:1)

我的PatternsOnText plugin版本1.30现在有一个

:DeleteAllDuplicateLinesIgnoring

命令。没有任何争论,它将按照你的问题所述工作。

答案 5 :(得分:0)

here获取代码并修改它以删除行而不是突出显示它们,你会得到这个:

function! DeleteDuplicateLines() range
  let lineCounts = {}
  let lineNum = a:firstline
  while lineNum <= a:lastline
    let lineText = getline(lineNum)
    if lineText != ""
        if has_key(lineCounts, lineText)
            execute lineNum . 'delete _'
            if lineCounts[lineText] > 0
              execute lineCounts[lineText] . 'delete _'
              let lineCounts[lineText] = 0
              let lineNum -= 1
            endif
        else
            let lineCounts[lineText] =  lineNum
            let lineNum += 1
        endif
    else
      let lineNum += 1
    endif
  endwhile
endfunction

command! -range=% DeleteDuplicateLines <line1>,<line2>call DeleteDuplicateLines()

答案 6 :(得分:0)

这并不比@Ingo Karkat的答案简单,但它更灵活一些。就像那个答案一样,这会使剩余的行保持原始顺序。

function! RepeatedLines(...)
  let first = a:0 ? a:1 : 1
  let last = (a:0 > 1) ? a:2 : line('$')
  let lines = []
  for line in range(first, last - 1)
    if index(lines, line) != -1
      continue
    endif
    let newlines = []
    let text = escape(getline(line), '\')
    execute 'silent' (line + 1) ',' last
      \ 'g/\V' . text . '/call add(newlines, line("."))'
    if !empty(newlines)
      call add(lines, line)
      call extend(lines, newlines)
    endif
  endfor
  return sort(lines)
endfun
:for x in reverse(RepeatedLines()) | execute x 'd' | endfor

一些注意事项:

  1. 我的函数接受参数而不是处理范围。它默认为整个缓冲区。
  2. 这说明了一些操作列表的功能。 :help list-functions
  3. 我使用/\V(非常没有魔法)所以我需要在搜索模式中逃脱的唯一字符就是反斜杠本身。 :help /\V

答案 7 :(得分:0)

请使用perl,perl可以轻松实现!

use strict;use warnings;use diagnostics;
#read input file
open(File1,'<input.txt') or die "can not open file:$!\n";my @data1=<File1>;close(File1);
#save row and count number of row in hash 
my %rownum;
foreach my $line1 (@data1)
{ 
    if (exists($rownum{$line1}))
    { 
        $rownum{$line1}++;
    }
    else
    {
        $rownum{$line1}=1;
    }
}
#if number of row in hash =1 print it
open(File2,'>output.txt') or die "can not open file:$!\n";
foreach my $line1 (@data1)
{ 
    if($rownum{$line1}==1)
    { 
        print File2 $line1;
    }
}
close(File2);

答案 8 :(得分:0)

  1. 添加行号,以便在排序前恢复顺序 :%s/^/=printf("%d ", line("."))/g
  2. 排序 :sort /^\d+/
  3. 删除重复行 :%s/^(\d+ )(.*)\n(\d+ \2\n)+//g
  4. 恢复秩序 :排序
  5. 删除 #1 中添加的行号 :%s/^\d+ //g