在一个语句中有多个:g和:v命令

时间:2012-08-04 10:09:54

标签: vim

我有这个文件

foo
foo bar
foo bar baz
bar baz
foo baz
baz bar
bar
baz
foo 42
foo bar 42 baz
baz 42

我想

  1. 选择包含foo且不包含bar
  2. 的行
  3. 删除包含foo且不包含bar
  4. 的行

    我在某处找不到(找不到链接)我必须使用:exec |为此。

    我尝试了以下操作,但它不起作用

    :exec "g/foo" # works
    :exec "g/foo" | exec "g/bar" -- first returns lines with foo, then with bar
    :exec "g/foo" | :g/bar -- same as above
    

    当然,如果我不能选择一行,我就无法在其上执行normal dd

    有什么想法吗?

    修改

    赏金注意事项:

    我正在寻找一个使用正确的:g和:v命令的解决方案,并且不使用正则表达式黑客,因为条件可能不一样(我可以有2个包含,3个排除)。

    另请注意,最后两个不起作用的例子,它们只能删除行,但是当我运行它们而不删除(即查看所选行)时它们返回不正确的信息,它们表现为如上所述。

11 个答案:

答案 0 :(得分:10)

我不是vim向导,但是如果您要做的只是“删除包含foo且不包含bar的行”,那么这应该做(我试过你的示例文件):

:v /bar/s/.*foo.*//

编辑:实际上这会留下空行。您可能希望为第二种搜索模式添加可选换行符。

答案 1 :(得分:4)

这可能仍然是你的hackish,但你可以编写一些vimscript来为此创建一个函数和专门的命令。例如:

command! -nargs=* -range=% G <line1>,<line2>call MultiG(<f-args>)
fun! MultiG(...) range
   let pattern = ""
   let command = ""
   for i in a:000
      if i[0] == "-"
         let pattern .= "\\(.*\\<".strpart(i,1)."\\>\\)\\@!"
      elseif i[0] == "+"
         let pattern .= "\\(.*\\<".strpart(i,1)."\\>\\)\\@="
      else
         let command = i
      endif
   endfor
   exe a:firstline.",".a:lastline."g/".pattern."/".command
endfun

这会创建一个命令,允许您自动执行“正则表达式黑客攻击”。这样你可以做到

:G +foo -bar

用foo获取所有行而不是bar。如果参数不以+-开头,那么它将被视为添加到:g命令末尾的命令。所以你也可以这样做

:G d +foo -bar

删除行,甚至

:G norm\ foXp +two\ foos -bar

如果你逃离你的空间。它也需要:1,3G +etc之类的范围,你可以在搜索词中使用正则表达式,但你必须逃避你的空间。希望这会有所帮助。

答案 2 :(得分:3)

这是正则表达式有点麻烦的地方。您需要使用零宽度匹配\(search_string\)\@=。如果您想以任何顺序匹配项目列表,search_string应该以{{1​​}}开头(因此匹配从每行的开头开始)。要匹配不存在的情况,请改用.*

我认为这些命令应该做你想要的(为了清楚起见我使用\@!作为分隔符,而不是通常的#):

  1. 选择包含foo和bar的行:

    /

  2. 选择包含foo,bar和baz的行

    :g#\(.*foo\)\@=\(.*bar\)\@=

  3. 选择包含foo并且不包含栏的行

    :g#\(.*foo\)\@=\(.*bar\)\@=\(.*baz\)\@=

  4. 删除包含foo和bar的行

    :g#\(.*foo\)\@=\(.*bar\)\@!

  5. 删除包含foo但不包含bar

    的行

    :g#\(.*foo\)\@=\(.*bar\)\@=#d

答案 3 :(得分:2)

有人可能认为像:g/foo/v/bar/d这样的组合会起作用,但不幸的是,这是不可能的,你将不得不重新提出一个拟议的解决办法。

如帮助中所述,:global命令在幕后工作分为两个阶段,

  • 首先标记要操作的行,
  • 然后对它们进行操作。

出于兴趣,我查看了Vim源代码中的相关部分:在 ex_cmds.c ex_global()中,您会发现全局标记global_busy防止命令在忙碌时重复执行。

答案 4 :(得分:2)

除非您愿意使用某些正则表达式,否则您将无法达到您的要求,因为表达式驱动:global并且它与:vglobal相反。

这不是黑客攻击,而是命令应该如何工作:他们需要一个表达式来处理。如果你不愿意使用正则表达式,我恐怕你无法实现它。

如果您不愿意使用任何正则表达式,请在此处终止。


假设我们是一个心胸开阔的好人,我们需要一个正则表达式,当一行包含foo 而不是 bar时,这是正确的。

number 5的建议Prince Goulash完全存在但如果在foo之后发生bar则无效。

此表达式完成工作(即打印所有行):

:g/^\(.*\<bar\>\)\@!\(.*\<foo\>\)\@=/

如果要删除它们,请添加d elete命令:

:g/^\(.*\<bar\>\)\@!\(.*\<foo\>\)\@=/d

说明

  • ^行首
  • 开始
  • \(.*\<bar\>\)单词 bar
  • \@!绝不能出现
  • \(.*\<foo\>\)\@= foo 一词必须出现在该行的任何位置

这两种模式也可以互换:

:g/^\(.*\<foo\>\)\@=\(.*\<bar\>\)\@!/

产生相同的结果。

使用以下输入进行测试:

01      foo
02      foo bar
03      foo bar baz
04      bar baz
05      foo baz
06      baz bar
07      bar
08      baz
09      foo 42
10      foo bar 42 baz
11      42 foo baz
12      42 foo bar
13      42 bar foo
14      baz 42
15      baz foo
16      bar foo

关于多个包含/排除:

每个排除都由模式

组成
\(.*\<what_to_exclude\>\)\@!

每个 include 都由模式

组成
\(.*\<what_to_include\>\)\@=

打印包含foo但不包含barbaz的所有行:

g/^\(.*\<bar\>\)\@!\(.*\<baz\>\)\@!\(.*\<foo\>\)\@=/

打印包含foo42但不包括barbaz的所有行:

g/^\(.*\<bar\>\)\@!\(.*\<baz\>\)\@!\(.*\<foo\>\)\@=\(.*\<42\>\)\@=/

包含和排除的顺序并不重要,您甚至可以混合它们:

g/^\(.*\<bar\>\)\@!\(.*\<42\>\)\@=\(.*\<baz\>\)\@!\(.*\<foo\>\)\@=/

答案 5 :(得分:0)

您希望采用负面展望。本文或多或少地提供了您尝试实现的具体示例。 http://www.littletechtips.com/2009/09/vim-negative-match-using-negative-look.html

我把它改成了 :(。*巴)!克/富\ @ / d

如果您认为这是一个正则表达式黑客,请告诉我们。

答案 6 :(得分:0)

我会戴上帽子。由于vim的文档明确指出递归全局命令是无效的,并且正则表达式解决方案将很快变得非常毛茸茸,我认为这是自定义函数和命令的工作。我创建了:G命令。

用法为:G,后跟/包围的模式。任何不匹配的模式都以!为前缀。

:G /foo/ !/bar/ d

这将删除与/foo/匹配且与/bar/

不匹配的所有行
:G /42 baz/ !/bar/ norm A$

这会将$附加到与/42 baz/匹配且与/bar/不匹配的所有行

:G /foo/ !/bar/ !/baz/ d

这将删除与/foo/匹配且与/bar/不匹配且与/baz/不匹配的所有行

:G命令的脚本如下:

function! s:ManyGlobal(args) range
  let lnums = {}
  let patterns = []
  let cmd = ''
  let threshold = 0
  let regex = '\m^\s*\(!\|v\)\=/.\{-}\%(\\\)\@<!/\s\+'

  let args = a:args
  while args =~ regex
    let pat = matchstr(args, regex)
    let pat = substitute(pat, '\m^\s*\ze/', '', '')
    call add(patterns, pat)
    let args = substitute(args, regex, '', '')
  endwhile

  if args =~ '\s*'
    let cmd = 'nu'
  else
    let cmd = args
  endif

  for p in patterns
    if p =~ '^(!\|v)'
      let op = '-'
    else
      let op = '+'
      let threshold += 1
    endif
    let marker = "let l:lnums[line('.')] = get(l:lnums, line('.'), 0)" . op . "1"
    exe a:firstline . "," . a:lastline . "g" . substitute(p, '^(!\|v)', '', '') . marker
  endfor

  let patterns = []
  for k in keys(lnums)
    if threshold == lnums[k]
      call add(patterns, '\%' . k . 'l')
    endif
  endfor

  exe a:firstline . "," . a:lastline . "g/\m" . join(patterns, '\|') . "/ " . cmd
endfunction

command! -nargs=+ -range=% G <line1>,<line2>call <SID>ManyGlobal(<q-args>)

该函数基本上解析出参数,然后分别用每个给定的模式标记所有匹配的行。然后在标记了适当次数的每一行上执行给定的命令。

答案 7 :(得分:0)

好的,这是实际模拟全局命令的递归使用的一个。它允许您至少在理论上组合任意数量的:g命令。但我警告你,它不漂亮!

原始问题的解决方案

我使用Unix程序 nl (请耐心等我!)插入行号,但您也可以使用pure Vim

:%!nl -b a
:exec 'norm! qaq'|exec '.,$g/foo/d A'|exec 'norm! G"apddqaq'|exec '.,$v/bar/d'|%sort|%s/\v^\s*\d+\s*

完成!让我们看看解释和一般解决方案。

一般解决方案

这是我选择的方法:

  • 引入明确的行号
  • 将文件末尾用作临时空间并重复操作
  • 对文件进行排序,删除行号

使用文件的末尾作为临时空间(:g/foo/m$和类似的)是一个非常着名的技巧(你可以在着名的answer number one中找到它)。另请注意,:g保留了行的相对排序 - 这是至关重要的。我们走了:

准备:数字行,清除“累加器”寄存器 a

:%!nl
qaq

迭代位:

  1. :execute全局命令,通过将匹配行附加到带有:d A的累加器寄存器来收集匹配行。
  2. 将收集的行粘贴到文件末尾
  3. 重复范围.,$(临时空间,或在我们的情况下,“匹配”空间)
  4. 这是一个扩展示例:删除包含'foo'的行,不包含'bar',包含'42'(仅用于演示)。

    :exec '.,$g/foo/d A' | exec 'norm! G"apddqaq' | exec '.,$v/bar/d A' | exec 'norm! G"apddqaq' | exec '.,$g/42/d A' | exec 'norm! G"apddqaq'
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     (this is the repeating bit)
    

    当迭代位结束时,行.,$包含匹配以方便您使用。您可以删除它们(dVG)或其他任何内容。

    清理:排序,删除行号。

    :%sort
    :%s/\v^\s*\d+\s*
    



    我相信其他人可以提高解决方案的细节,但如果你确实需要多个组合:g S和:v s转换一个,这似乎是最有前途的解决方案

答案 8 :(得分:0)

内置解决方案看起来非常复杂。 一种简单的方法是使用LogiPat插件:

Doc:http://www.drchip.org/astronaut/vim/doc/LogiPat.txt.html

插件:http://www.drchip.org/astronaut/vim/index.html#LOGIPAT

有了这个,您可以轻松搜索模式。 例如,要搜索包含foo而不是bar的行,请使用:

:LogiPat "foo"&!("bar")

这将突出显示与逻辑模式匹配的所有行(如果已设置hls)。 这样你可以反复检查你是否得到了正确的行,然后用'n'遍历,如果你愿意,用'dd'删除。

答案 9 :(得分:0)

我意识到您明确表示您需要使用:g:v的解决方案,但我坚信这是您应该使用外部工具的完美示例。

:%!awk '\!/foo/ || /bar/'

没有必要重新发明轮子。

答案 10 :(得分:0)

  

选择包含foo的行并且不包含bar

     

删除包含foo但不包含bar

的行

这可以通过组合globalsubstitute命令来完成:

:v/bar/s/.*foo.*//g