I'm trying to formulate a regular expression that matches the names of a set of files I would like to batch process in Vim but am finding that I cannot seem to use \| (regex OR) as expected...
Specifically, I would like to create an argument list consisting of the following files in the current directory:
f0148.e, f0149.e, f0150.e ... f0159.e (i.e., 12 files total)
The vim command I entered goes as follows:
:arg f01\(\(4[89]\)\|\(5[0-9]\)\).e
Vim completes this command without any noticeable result -- there's no message and the output from :args remains unchanged (doesn't produce the desired list of file names).
If I split up the regular expression to:
:arg f01\(\(4[89]\)\).e (note: leaving parenthesis here as in above full expression)
...and...
:arg f01\(\(5[0-9]\)\).e
... then :args produces f0148.e f0149.e and f0150.e ... f0159.e respectively (as desired).
Also, if I enter the above mentioned list of file names in a text file and use the above mentioned regular expression as a search pattern (i.e., /f01\(\(4[89]\)\|\(5[0-9]\)\).e), it works just as desired.
Thus, I determined that the alternation (\|) is somehow causing the the expression to fail. Please note that I'm using Vim on Windows 7, if this is relevant (since both backslash and pipe are valid symbols at the Windows command prompt).
A quick workaround would be to use:
:arg f014[89].e
:argadd f015[0-9].e
...but I would really like to figure out how to make the above regular expression work.
Thanks for your help!
I could suggest:
:let file_list = filter(split(globpath('.','**'),nr2char(10)), 'v:val =~ ''f01\(\(4[89]\)\|\(5[0-9]\)\)\.e'' ')
:execute 'args ' . join(map(file_list,'fnameescape(v:val)'),' ')
How this works:
globpath('.','**') makes a list of all files in current directory and all subdirectories. :help globpath().
split(..., nr2char(10)) will make a list of it, because the separator was Line Feed
filter(..., 'v:val =~ ''pattern'' ') filters the list keeping only items matching pattern. :help v:val. Doubling single quote is escaping them inside single quote string.
map(..., fnameescape()) escapes all spaces and backslashes
join() adds spaces between file names
If you want to make it a function you can put this into your vimrc:
function! ArgsPattern(pat)
let file_list = filter(split(globpath('.','**'),nr2char(10)), 'v:val =~ ''' . substitute(a:pat,"'","''",'g') . '''')
execute 'args ' . join(map(file_list,'fnameescape(v:val)'),' ')
endfunction
command! -nargs=+ ArgsPattern call ArgsPattern(<q-args>)
And then you only have to do:
:ArgsPattern f01\(\(4[89]\)\|\(5[0-9]\)\)\.e
Note that if there is no match, then the execute command inside the function evaluates to :args and therefore the list of your current arguments are printed.
Related
I have the following lines:
source = "git::ssh://git#github.abc.com/test//bar"
source = "git::ssh://git#github.abc.com/test//foo?ref=tf12"
resource = "bar"
I want to update any lines that contain source and git words by adding ?ref:tf12 to the end of the line but inside ". If the line already contains ?ref=tf12, it should skip
source = "git::ssh://git#github.abc.com/test//bar?ref=tf12"
source = "git::ssh://git#github.abc.com/test//foo?ref=tf12"
resource = "bar"
I have the following expression using sed, but it outputs wrongly
sed 's#source.*git.*//.*#&?ref=tf12#' file.tf
source = "git::ssh://git#github.abc.com/test//bar"?ref=tf12
source = "git::ssh://git#github.abc.com/test//foo"?ref=tf12?ref=tf12
resource = "bar"
Using simple regular expressions for this is rather brittle; if at all possible, using a more robust configuration file parser would probably be a better idea. If that's not possible, you might want to tighten up the regular expressions to make sure you don't modify unrelated lines. But here is a really simple solution, at least as a starting point.
sed -e '/^ *source *= *"git/!b' -e '/?ref=tf12" *$/b' -e 's/" *$/?ref=tf12"/' file.tf
This consists of three commands. Remember that sed examines one line at a time.
/^ * source *= *"git/!b - if this line does not begin with source="git (with optional spaces between the tokens) leave it alone. (! means "does not match" and b means "branch (to the end of this script)" i.e. skip this line and fetch the next one.)
/?ref=tf12" *$/b similarly says to leave alone lines which match this regex. In other words, if the line ends with ?ref=tf12" (with optional spaces after) don't modify it.
s/"* $/?ref=tf12"/ says to change the last double quote to include ?ref=tf12 before it. This will only happen on lines which were not skipped by the two previous commands.
sed '/?ref=tf12"/!s#\(source.*git.*//.*\)"#\1?ref=tf12"#' file.tf
/?ref=tf12"/! Only run substitude command if this pattern (?ref=tf12") doesn't match
\(...\)", \1 Instead of appending to the entire line using &, only match the line until the last ". Use parentheses to match everything before that " into a group which I can then refer with \1 in the replacement. (Where we re-add the ", so that it doesn't get lost)
I search for a particular string in a file in vim, and I want all the lines with matching string to be displayed, perhaps in another vim window.
Currently I do this:
Search for 'string'
/string
and move to next matching string
n or N
Bur, I want all the lines with matching string at one place.
For example:
1 Here is a string
2 Nothing here
3 Here is the same string
I want lines 1 and 3 to be displayed as below, highlighting string
1 Here is a string
3 Here is the same string
:g/pattern/#<CR>
lists all the lines matching pattern. You can then do :23<CR> to jump to line 23.
:ilist pattern<CR>
is an alternative that filters out comments and works across includes.
The command below:
:vimgrep pattern %|cwindow<CR>
will use Vim's built-in grep-like functionality to search for pattern in the current file (%) and display the results in the quickfix window.
:grep pattern %|cwindow<CR>
does the same but uses an external program. Note that :grep and :vimgrep work with files, not buffers.
Reference:
:help :g
:help include-search
:help :vimgrep
:help :grep
:help :cwindow
FWIW, my plugin vim-qlist combines the features of :ilist and the quickfix window.
From the comments I believe your file looks like this, i.e. the line numbers are not part of the text:
Here is a string
Nothing here
Here is the same string
You could copy all lines matching a pattern into a named register ("a" in the example below), then paste it into a new file:
:g/string/y A
:e newfile
:"ap
Which gets you:
Here is a string
Here is the same string
Alternatively, you can use the grep command and add -n to include line numbers:
:grep -n string %
1:~/tmp.txt [text] line: 3 of 3, col: 23 (All)
:!grep -nH -n string /home/christofer/tmp.txt 2>&1| tee /tmp/vHg7GcV/3
[No write since last change]
/home/christofer/tmp.txt:1:Here is a string
/home/christofer/tmp.txt:3:Here is the same string
(1 of 2): Here is a string
Press ENTER or type command to continue
By default you'll get the output in the "command buffer" down at the bottom (don't know its proper name), but you can store it in several different places, using :copen for example.
Following this answer over on the Vi StackExchange:
:v/mystring/d
This will remove all lines not containing mystring and will highlight mystring in the remaining lines.
I would like to replace for instance every occurrence of "foo{...}" with anything except newlines inside the bracket (there may be spaces, other brackets opened AND closed, etc) NOT followed by "bar".
For instance, the "foo{{ }}" in "foo{{ }}, bar" would match but not "foo{hello{}}bar".
I've tried /foo{.*}\(bar\)\#! and /foo{.\{-}}\(bar\)\#! but the first one would match "foo{}bar{}" and the second would match "foo{{}}bar" (only the "foo{{}" part).
this regex:
foo{.*}\([}]*bar\)\#!
matches:
foo{{ }}
foo{{ }}, bar
but not:
foo{hello{}}bar
It is impossible to correctly match an arbitrary level of nested
parentheses using regular expressions. However, it is possible to
construct a regex to match supporting a limited amount of nesting (I
think this answer did not attempt to do so). – Ben
This does ...
for up to one level of inner braces:
/foo{[^{}]*\({[^{}]*}[^{}]*\)*}\(bar\)\#!
for up to two levels of inner braces:
/foo{[^{}]*\({[^{}]*\({[^{}]*}[^{}]*\)*}[^{}]*\)*}\(bar\)\#!
for up to three levels of inner braces:
/foo{[^{}]*\({[^{}]*\({[^{}]*\({[^{}]*}[^{}]*\)*}[^{}]*\)*}[^{}]*\)*}\(bar\)\#!
...
Depends on what replacement you want to perform exactly, you might be able to do that with macros.
For example: Given this text
line 1 -- -- -- -- array[a][b[1]]
line 2 -- array[c][d]
line 3 -- -- -- -- -- -- -- array[e[0]][f] + array[g[0]][h[0]]
replace array[A][B] with get(A, B).
To do that:
Position the cursor at the begin of the text
/array<cr>
qq to begin recording a macro
Do something to change the data independent of the content inside (use % to go to matching bracket, and some register/mark/plugin to delete around the bracket). For example cwget(<esc>ldi[vhpa, <esc>ldi[vhpa)<esc>n -- but macros are usually unreadable.
n to go to next match, q to stop recording
#q repeatedly (## can be used from the second time)
This is probably not very convenient because it's easy to make a mistake (press I, <home>, A for example) and you have to redo the macro from the beginning, but it works.
Alternatively, you can do something similar to eregex.vim plugin to extend vim's regex format to support this (so you don't have to retype the huge regex every time).
Proof of concept:
"does not handle different magic levels
"does not handle '\/' or different characters for substitution ('s#a#b#')
"does not handle brackets inside strings
" usage: `:M/pattern, use \zm for matching block/replacement/flags`
command -range -nargs=* M :call SubstituteWithMatching(<q-args>, <line1>, <line2>)
":M/ inspired from eregex.vim
function SubstituteWithMatching(command, line1, line2)
let EscapeRegex={pattern->escape(pattern, '[]\')}
let openbracket ='([{'
let closebracket=')]}'
let nonbracketR='[^'.EscapeRegex(openbracket.closebracket).']'
let nonbracketsR=nonbracketR.'*'
let LiftLevel={pattern->
\nonbracketsR
\.'\%('
\.'['.EscapeRegex(openbracket).']'
\.pattern
\.'['.EscapeRegex(closebracket).']'
\.nonbracketsR
\.'\)*'
\}
let matchingR=LiftLevel(LiftLevel(LiftLevel(nonbracketsR)))
if v:false " optional test suite
echo "return 0:"
echo match('abc', '^'.matchingR.'$')
echo match('abc(ab)de', '^'.matchingR.'$')
echo match('abc(ab)d(e)f', '^'.matchingR.'$')
echo match('abc(a[x]b)d(e)f', '^'.matchingR.'$')
echo match('abc(a]b', '^'.matchingR.'$')
"current flaw (not a problem if there's only one type of bracket, or if
"the code is well-formed)
echo "return -1:"
echo match('abc(a(b', '^'.matchingR.'$')
echo match('abc)a(b', '^'.matchingR.'$')
endif
let [pattern, replacement, flags]=split(a:command, "/")
let pattern=substitute(pattern, '\\zm', EscapeRegex(matchingR), 'g')
execute a:line1.','.a:line2.'s/'.pattern.'/'.replacement.'/'.flags
endfunction
After this, :'<,'>M/array\[\(\zm\)\]\[\(\zm\)\]/get(\1, \2)/g can be used to do the same task above (after selecting the text in visual mode)
I've got a file that uses an outdated macro to read 32 bit integers,
READ32(dest, src)
I need to replace all calls with
dest = readUint32(&src);
I'm trying to write a SED style Vim search & replace command, but not having luck.
I can match the 1st part using READ32([a-z]\+, cmd) using the / search prompt, but it does not seem to match in the :s syntax.
Here's what I finally figured out to work:
:%s/READ32(\(\a\+\),\(\a\+\)/\1 = readUint32(\&\2);
The trick is wrapping the values you want to store in \1 & \2 in \( and \) The other trick was you have to escape the & operator as & in vim replacement is "the whole match".
EDIT: improved further as I refined it:
:%s/READ32(\(\w\+\),\s*\(\w\+\)/\1 = readUint32(\&\2);
Changed \a to \w as I had variables with _ in them.
Added \s* to take care of white space issues between the , and second variable.
Now just trying to deal with c++ style variables of style class.variable.subvariable
EDIT 2:
replaced \w with [a-zA-Z0-9_.] to catch all of the ways my variables were named.
This should do what you want or at least get you started:
%s-READ32(\s*\(\i\+\)\s*,\s*\(\i\+\)\s*)-\1 = readUint32(\&\2);-g
I'd do the macro style again: hit * to 'highlight' search for READ32.
Now, we are going to record a macro (q..qq):
n (move to next match)
cwreadUint32Esc (change the function name)
wwdt, (delete the first argument)
"_dw (remove the redundant ,)
bbPa=Esc (insert the result variable appending = before readUint32)
A; (append ; to the end of the line)
Now you can just repeat the macro (1000#q).
This command line parses a contact list document that may or may not have either a phone, email or web listed. If it has all three then everything works great - appending the return from the FormatContact() at the end of the line for data uploading:
silent!/^\d/+1|ki|/\n^\d\|\%$/-1|kj|'i,'jd|let #a = substitute(#",'\s*Phone: \([^,]*\)\_.*','\1',"")|let #b = substitute(#",'^\_.*E-mail:\s\[\d*\]\([-_#.0-9a-zA-Z]*\)\_.*','\1',"")|let #c = substitute(#",'^\_.*Web site:\s*\[\d*\]\([-_.:/0-9a-zA-Z]*\)\_.*','\1',"")|?^\d\+?s/$/\=','.FormatContact(#a,#b,#c)
or, broken down:
silent!/^\d/+1|ki|/\n^\d\|\%$/-1|kj|'i,'jd
let #a = substitute(#",'\s*Phone: \([^,]*\)\_.*','\1',"")
let #b = substitute(#",'^\_.*E-mail:\s\[\d*\]\([-_#.0-9a-zA-Z]*\)\_.*','\1',"")
let #c = substitute(#",'^\_.*Web site:\s*\[\d*\]\([-_.:/0-9a-zA-Z]*\)\_.*','\1',"")
?^\d\+?s/$/\=','.FormatContact(#a,#b,#c)
I created three separate searches so as not to make any ONE search fail if one atom failed to match because - again - the contact info may or may not exist per contact.
The Problem that solution created was that when the pattern does not match I get the whole #" into #a. Instead, I need it to be empty when the match does not occur. I need each variable represented (phone,email,web) whether it be empty or not.
I see no flags that can be set in the substitution function that
will do this.
Is there a way to return "" if \1 is empty?
Is there a way to create an optional atom so the search query(ies) could still account for an empty match so as to properly record it as empty?
Instead of using substitutions that replace the whole captured text
with its part of interest, one can match only that target part. Unlike
substitution routines, matching ones either locate the text conforming
to the given pattern, or report that there is no such text. Thus,
using the matchstr() function in preference to substitute(), the
parsing code listed in the question can be changed as follows:
let #a = matchstr(#", '\<Phone:\s*\zs[^,]*')
let #b = matchstr(#", '\<E-mail:\s*\[\d*\]\zs[-_#.0-9a-zA-Z]*')
let #c = matchstr(#", '\<Web site:\s*\[\d*\]\zs[-_.:/0-9a-zA-Z]*')
Just in case you want linewise processing, consider using in combination with :global, e.g.
let #a=""
g/text to match/let #A=substitute(getline("."), '^.*\(' . #/ . '\).*$', '\1\r', '')
This will print the matched text for any line that contained it, separated with newlines:
echo #a
The beautiful thing here, is that you can make it work with the last-used search-pattern very easily:
g//let #A=substitute(getline("."), '^.*\(' . #/ . '\).*$', '\1\r', '')