Подтвердить что ты не робот

Несколько команд: g и: v в одном выражении

У меня есть этот файл

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

Я хочу

  • Выберите строки, которые содержат foo и не содержат bar
  • Удалить строки, содержащие 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, и не использует хаки regex, поскольку условия могут быть не одинаковыми (у меня может быть 2 включает, 3 исключает).

Также обратите внимание, что в последних двух примерах вещей, которые не работают, они работают только для удаления строк, но они возвращают неверную информацию, когда я запускаю их без удаления (т.е. просматривая выбранные строки), и они ведут себя как упомянутых выше.

4b9b3361

Ответ 1

Это может по-прежнему быть хакерским для вас, но вы можете написать 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

Я не мастер vim, но если все, что вы хотите сделать, это "Удалить строки, которые содержат foo и не содержат бара", тогда это должно делать (я попробовал в вашем файле примера):

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

EDIT: на самом деле это оставляет пустые строки позади. Вероятно, вы захотите добавить дополнительную строку новой строки для этого второго шаблона поиска.

Ответ 3

Здесь регулярные выражения становятся немного громоздкими. Вам нужно использовать совпадение нулевой ширины \(search_string\)\@=. Если вы хотите совместить список элементов в любом порядке, search_string должен начинаться с .* (поэтому совпадение начинается с начала строки каждый раз). Чтобы соответствовать невозвращению, используйте \@! вместо этого.

Я думаю, что эти команды должны делать то, что вы хотите (для ясности я использую # как разделитель, а не обычный /):

  • Выберите строки, содержащие foo и bar:

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

  • Выберите строки, содержащие foo, bar и baz

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

  • Выберите строки, содержащие foo и не содержащие бар

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

  • Удалить строки, содержащие foo и bar

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

  • Удалить строки, содержащие foo и не содержащие бар

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

Ответ 4

Можно подумать, что комбинация типа :g/foo/v/bar/d будет работать, но, к сожалению, это невозможно, и вам придется вернуться к одному из предложенных вариантов работы.

Как описано в справке, за кулисами команда :global работает в два этапа,

  • сначала маркировка строк для работы,
  • затем выполнив операцию над ними.

Из интереса я взглянул на соответствующие части источника Vim: в ex_cmds.c, ex_global() вы обнаружите, что глобальный флаг global_busy предотвращает повторное выполнение команда, когда она занята.

Ответ 5

Вы не достигнете своих требований, если не захотите использовать некоторые регулярные выражения, поскольку выражения - это то, что диски :global и напротив :vglobal.

Это не хакинг, а как команды должны работать: им нужно выражение для работы. Если вы не хотите использовать регулярные выражения, я боюсь, что вы не сможете этого достичь.

Ответ завершается здесь, если вы не хотите использовать какие-либо регулярные выражения.


Предполагая, что мы хорошие парни с открытым умом, нам нужно регулярное выражение, которое истинно, если строка содержит foo , а не bar.

Предложение число 5 Принц Гулаш, но не работает, если foo происходит после bar.

Это выражение выполняет задание (т.е. печатает все строки):

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

Если вы хотите удалить их, добавьте команду d elete:

: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, но не bar и baz:

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

Распечатайте все строки, содержащие foo и 42, но ни bar, ни baz:

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

Последовательность включений и исключений не важна, вы даже можете их смешивать:

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

Ответ 6

Вы хотите использовать негативный прогноз. Эта статья дает более или менее конкретный пример, который вы пытаетесь достичь. http://www.littletechtips.com/2009/09/vim-negative-match-using-negative-look.html

Я изменил его на : (. * Бар)! Г /Foo \@/d

Пожалуйста, дайте нам знать, если вы считаете, что это румянец.

Ответ 7

Я брошу свою шляпу в кольцо. Поскольку документация 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/

script для команды :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>)

Функция в основном анализирует аргументы, а затем помечает все соответствующие строки с каждым заданным шаблоном отдельно. Затем выполняет заданную команду в каждой строке, которая помечена соответствующим количеством раз.

Ответ 8

Хорошо, здесь, который фактически имитирует рекурсивное использование глобальных команд. Он позволяет объединить любое количество команд :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

Итериальный бит:

  • :execute глобальная команда собирает совпадающие строки, добавляя их в регистр накопителя с :d A.
  • вставьте собранные строки в конец файла
  • repeat для диапазона .,$ (пространство царапин или, в нашем случае, пространство "совпадение" )

Вот расширенный пример: удалите строки, которые содержат "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 и :v в один, это, кажется, наиболее перспективное решение.

Ответ 9

Встроенные решения выглядят очень сложными. Одним из простых способов было бы использовать плагин 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", если хотите.

Ответ 10

Я понимаю, что вы явно заявили, что хотите решения с использованием :g и :v, но я твердо верю, что это прекрасный пример того случая, когда вы действительно должны использовать внешний инструмент.

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

Нет необходимости повторно изобретать колесо.