15

我有这个文件

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. 删除包含foo和不包含的行bar

我在某处(找不到链接)读到我必须为此使用 :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 个排除)。

另请注意,最后两个不起作用的示例,它们仅用于删除行,但是当我在不删除的情况下运行它们(即查看所选行)时它们返回不正确的信息,并且它们的行为如上所述。

4

11 回答 11

10

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

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

编辑:实际上这留下了空行。您可能希望在第二个搜索模式中添加一个可选的换行符。

于 2012-08-04T11:08:56.387 回答
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

这将创建一个命令,允许您自动执行“regex hack”。这样你可以做

:G +foo -bar

获取所有带有 foo 而不是 bar 的行。如果参数不以+or开头,-则将其视为添加到命令末尾的:g命令。所以你也可以做

:G d +foo -bar

删除行,甚至

:G norm\ foXp +two\ foos -bar

如果你逃离你的空间。它还需要一个范围,例如:1,3G +etc,您可以在搜索词中使用正则表达式,但您必须转义您的空格。希望这可以帮助。

于 2012-08-07T22:02:11.967 回答
3

这是正则表达式变得有点麻烦的地方。您需要使用零宽度匹配\(search_string\)\@=。如果要以任何顺序匹配项目列表,search_string则应以 开头.*(因此匹配每次都从行首开始)。要匹配非出现,请\@!改用。

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

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

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

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

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

  3. 选择包含 foo 且不包含 bar 的行

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

  4. 删除包含 foo 和 bar 的行

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

  5. 删除包含 foo 且不包含 bar 的行

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

于 2012-08-04T10:56:32.727 回答
3

除非您愿意使用一些正则表达式,否则您将无法满足您的要求,因为表达式是驱动力:global,而相反:vglobal

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

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


假设我们是思想开放的好人,我们需要一个正则表达式,当一行包含foo 而不是 bar时为真。

炖牛肉王子的第 5条建议是相当有的,但如果出现在 之后就不起作用了。foobar

这个表达式完成了这项工作(即打印所有行):

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

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

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

描述:

  • ^行首开始
  • \(.*\<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\>\)\@!

每个包括由模式组成

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

打印所有包含foo但不包含barnor的行baz

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

打印所有包含fooand42但不包含barnor的行baz

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

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

g/^\(.*\<bar\>\)\@!\(.*\<42\>\)\@=\(.*\<baz\>\)\@!\(.*\<foo\>\)\@=/
于 2012-08-07T21:04:30.717 回答
2

有人可能会认为这样的组合:g/foo/v/bar/d会起作用,但不幸的是,这是不可能的,您将不得不重复使用建议的解决方法之一。

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

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

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

于 2012-08-04T15:35:32.810 回答
0

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

原始问题的解决方案

我使用 Unix 程序nl(请耐心等待!)插入行号,但您也可以使用纯 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$和类似的空间)是一个非常著名的技巧(您可以在著名的答案一中找到它)。另请注意,:g保留行的相对顺序 - 这是至关重要的。开始了:

准备工作:数行,清除“累加器”寄存器一个

:%!nl
qaq

迭代位:

  1. :execute全局命令,通过将匹配行附加到累加器寄存器中来收集匹配行:d A
  2. 将收集到的行粘贴到文件末尾
  3. 重复范围.,$(暂存空间,或者在我们的例子中,“匹配”空间)

这是一个扩展示例:删除包含“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*



我相信其他人可以改进解决方案的细节,但是如果您绝对需要将多个:gs 和:vs 合并为一个,这似乎是最有希望的解决方案。

于 2012-08-13T22:43:04.870 回答
0

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

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

没有必要重新发明轮子。

于 2012-10-19T16:00:45.187 回答
0

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

文档: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'删除。

于 2012-08-14T08:38:31.333 回答
0

你想用消极的眼光看待未来。本文或多或少地提供了您尝试实现的具体示例。 http://www.littletechtips.com/2009/09/vim-negative-match-using-negative-look.html

我将其更改为 :g/foo(.*bar)\@!/d

如果您认为这是 regex hack,请告诉我们。

于 2012-08-08T15:48:27.873 回答
0

我会把我的帽子扔进擂台。由于 vim 的文档明确指出递归全局命令是无效的,并且正则表达式解决方案会很快变得很麻烦,我认为这是自定义函数和命令的工作。我已经创建了:G命令。

用法如下,由 .:G包围的模式/。任何不应匹配的模式都以 . 为前缀!

:G /foo/ !/bar/ d

这将删除所有匹配/foo/和不匹配的行/bar/

:G /42 baz/ !/bar/ norm A$

这会将 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>)

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

于 2012-08-13T18:55:03.860 回答
0

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

删除包含 foo 且不包含 bar 的行

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

:v/bar/s/.*foo.*//g
于 2018-03-18T06:43:36.337 回答