Несколько команд: 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 исключает).
Также обратите внимание, что в последних двух примерах вещей, которые не работают, они работают только для удаления строк, но они возвращают неверную информацию, когда я запускаю их без удаления (т.е. просматривая выбранные строки), и они ведут себя как упомянутых выше.
Ответы
Ответ 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/'
Нет необходимости повторно изобретать колесо.