Split on pattern in zsh - linux

I want to do something like ${(#s/<->/)param} where <-> is a pattern for numbers. If param=a567b38c898d then I'd get (a b c d).
Note: This is not the only case I'd like to solve, so a general solution would be preferred.

I'm not sure you can split on patterns, only on literal text. (Although this being zsh, I may be wrong and just need to do a little more research.) However, I have found how to use the substitution modifier to replace strings of characters with a specific string which you can subsequently split on. (You'll need to set the hist_substpattern option to allow a pattern as the left-hand side of the substitution operator. Still looking if there is a way to enable that just for a single modifier.)
$ param=a567b38c898d
$ setopt hist_substpattern
$ print -l ${(s/1/)param:gs/<->/1}
a
b
c
d
First, the modifier :gs/<->/1 replaces each string that matches the pattern <-> (i.e., the numbers) with a single 1. Then, the expansion flag (s/1/) splits the string on the .

Related

Substitute and change case for program variables

I'm changing some notation in a few source code files.
In particular, variable names using the format
m_variable1
m_anothervariable
should be renamed and reformatted to
mVariable1
mAnotherVariable
That is, substitute m_ with m and make the next character uppercase.
I know how todo simple substitutions, like
%s/m_/m/gc
using vim, but not sure how to add syntax for changing a char to uppercase in a substitute statement?
You can make the first character of variable name uppercase, but I think you can hardly separate words from a consecutive string simply by built-in command.
I hope following command will help you:
:%s/\vm_(\w+)/m\u\1/g
Explaination
\v enables the 'very magic' mode
\u makes the first character of word after it uppercase
\1 references the first captured group
Result
mVariable1
mAnothervariable

vim: replace sub-match with the same number of new strings

My plan is to do a pretty standard search replace, replacing all instances of old_string with new_string. The problem is that I only want to do this for an arbitrary number of old_strings following a specific prefix. So for example:
old_string = "a"
new_string = "b"
prefix = "xxxx"
xxxxaaaaaaaa => xxxxbbbbbbbb
xxxxaaapostfix => xxxxbbbpostfix
xxaaaa => xxaaaa
etc. I'm not sure how to do this. I imagine there's some way to say s/xxxxa*/xxxxb{number of a's}/g or something, but I have no idea what it is.
You can definitely do this! I would use the \= register to evaluate some vimscript. From :h s/\=:
Substitute with an expression *sub-replace-expression*
*sub-replace-\=* *s/\=*
When the substitute string starts with "\=" the remainder is interpreted as an
expression.
The special meaning for characters as mentioned at |sub-replace-special| does
not apply except for "<CR>". A <NL> character is used as a line break, you
can get one with a double-quote string: "\n". Prepend a backslash to get a
real <NL> character (which will be a NUL in the file).
Then you can use the repeat and submatch functions to build the right string. For example:
:%s/\(xxxx\)\(a\+\)/\=submatch(1).repeat('b', len(submatch(2)))
I chose to use \+ instead of * because then the pattern will not be found after the substitute command finished (this effects hlsearch and n)
Of course, if you use the \zs and \ze (start/end of match) atoms, you can use less capturing groups, which makes this waaay shorter and clearer.
:%s/xxxx\zsa\+/\=repeat('b', len(submatch(0)))
If you have perl support, you can use
:%perldo s/xxxx\Ka+/"b" x length($&)/ge
xxxx\Ka+ match one or more a only if preceded by xxxx
lookbehind with \K
/ge replace all occurrences in line, e allows to use Perl code in replacement section
"b" x length($&) the string b repeated length($&) number of times
See :h perl for more info

Appending to the end of a pattern with a word in the middle using vim

I have a file that is out-of-date and needs to be updated. The names have changed somewhat and I would like to clean them all up using a single substitution.
Here's what I'm trying to accomplish:
foo.foo_[single word] -> foo_bar.foo_[single word]_bar
where a single word is a string of n characters. In the file, they are always preceded by an underscore, but it needs to have "_bar" appended. There is always a "." after these instances, so I thought the following might work:
%s/foo\.foo_*\./foo_bar\.foo_*_bar\./g
Sadly, the first part doesn't even match what I want, so I'm back to square one.
I would first change:
foo_[word] -> foo_[word]_bar
and then
foo. -> foo_bar.
i.e.:
%s,\(foo_\w\+\),\1_bar,g|%s,foo\.,foo_bar\.,g
There are many ways to skin a cat but following should do the trick
%s/\vfoo.foo_(\w+)/foo_bar.foo_\1_bar/gc
what loosely translates to
\v Very Magic (:help magic)
foo.foo_ Search for exact string
(\w+) Search for a "word" and store in a backreference
/foo_bar.foo Replace search pattern with this exact string
\1 appended with backreference 1
_bar appended with _bar
or if you don't want to repeat the search in the replace part, you can go a bit nuts with backreferences and use
%s/\v(foo)\.foo_(\w+)/\1_bar.\1_\2_bar/gc
The most important parts you were missing were
using backreferences (:helpgrep backref)
using character classes (:h \w)
using repetition (_* is searching for 0 or more underscores. You probably meant _.*)

How to perform following search and replace in vim?

I have the following string in the code at multiple places,
m_cells->a[ Id ]
and I want to replace it with
c(Id)
where the string Id could be anything including numbers also.
A regular expression replace like below should do:
%s/m_cells->a\[\s\(\w\+\)\s\]/c(\1)/g
If you wish to apply the replacement operation on a number of files you could use the :bufdo command.
Full explanation of #BasBossink's answer (as a separate answer because this won't fit in a comment), because regexes are awesome but non-trivial and definitely worth learning:
In Command mode (ie. type : from Normal mode), s/search_term/replacement/ will replace the first occurrence of 'search_term' with 'replacement' on the current line.
The % before the s tells vim to perform the operation on all lines in the document. Any range specification is valid here, eg. 5,10 for lines 5-10.
The g after the last / performs the operation "globally" - all occurrences of 'search_term' on the line or lines, not just the first occurrence.
The "m_cells->a" part of the search term is a literal match. Then it gets interesting.
Many characters have special meaning in a regex, and if you want to use the character literally, without the special meaning, then you have to "escape" it, by putting a \ in front.
Thus \[ and \] match the literal '[' and ']' characters.
Then we have the opposite case: literal characters that we want to treat as special regex entities.
\s matches white*s*pace (space, tab, etc.).
\w matches "*w*ord" characters (letters, digits, and underscore _).
(. matches any character (except a newline). \d matches digits. There are more...)
If a character is not followed by a quantifier, then exactly one such character matches. Thus, \s will match one space or tab, but not fewer or more.
\+ is a quantifier, and means "one or more". (\? matches 0 or 1; * (with no backslash) matches any number: zero or more. Warning: matching on zero occurrences takes a little getting used to; when you're first learning regexes, you don't always get the results you expected. It's also possible to match on an arbitrary exact number or range of occurrences, but I won't get into that here.)
\( and \) work together to form a "capturing group". This means that we don't just want to match on these characters, we also want to remember them specially so that we can do something with them later. You can have any number of capturing groups, and they can be nested too. You can refer to them later by number, starting at 1 (not 0). Just start counting (escaped) left-parantheses from the left to determine the number.
So here, we are matching a space followed by a group (which we will capture) of at least one "word" character followed by a space, within the square brackets.
Then section between the second and third / is the replacement text.
The "c" is literal.
\1 means the first captured group, which in this case will be the "Id".
In summary, we are finding text that matches the given description, capturing part of it, and replacing the entire match with the replacement text that we have constructed.
Perhaps a final suggestion: c after the final / (doesn't matter whether it comes before or after the 'g') enables *c*onfirmation: vim will highlight the characters to be replaced and will show the replacement text and ask whether you want to go ahead. Great for learning.
Yes, regexes are complicated, but super powerful and well worth learning. Once you have them internalized, they're actually fairly easy. I suggest that, as with learning vim itself, you start with the basics, get fluent in them, and then incrementally add new features to your repertoire.
Good luck and have fun.

How to implement Caesar cipher-like text substitution in Vim?

I was doing some puzzle where each English letter is replaced by the one two letters down the alphabet. For example, the word apple is to be transformed into crrng, as a + 2 → c, b + 2 → d, etc.
In Python, I was able to implement this transformation using the maketrans()
string method. I wonder: Is it possible to do the same via search and replace in Vim?
1. If the alphabetic characters are arranged sequentially in the target
encoding (as is the case for ASCII and some alphabets in UTF-8, like
English), one can use the following substitution command:
:%s/./\=nr2char(char2nr(submatch(0))+2)/g
(Before running the command, make sure that the encoding option
is set accordingly.)
However, this replacement implements a non-circular letter shift.
A circular shift can be implemented by two substitutions separately
handling lowercase and uppercase letters:
:%s/\l/\=nr2char(char2nr('a') + (char2nr(submatch(0)) - char2nr('a') + 2) % 26)/g
:%s/\u/\=nr2char(char2nr('A') + (char2nr(submatch(0)) - char2nr('A') + 2) % 26)/g
2. Another way is to translate characters using the tr() function.
Let us assume that the variable a contains lowercase characters
of an alphabet arranged in correct order, and the variable a1 hold
the string of characters corresponding to those in a (below is
an example for English letters).
:let a = 'abcdefghijklmnopqrstuvwxyz'
:let a1 = a[2:] . a[:1]
To avoid typing the whole alphabet by hand, the value of a can be
produced as follows:
:let a = join(map(range(char2nr('a'), char2nr('z')), 'nr2char(v:val)'), '')
Then, to replace each letter on a line by the letter two positions down
the alphabet, one can use the following substitution:
:%s/.*/\=tr(submatch(0), a . toupper(a), a1 . toupper(a1))
Yes, \= will execute the function
%s/\(.\)/\=nr2char(char2nr(submatch(1)) + 2)/g
Can't think of anything in vim, but you could use the unix command line utility 'tr' (stands for translate, I believe).
The puzzle you describe is widely known as the caesar cipher, and is normally implemented via the tr command or sed -e y/. Since y is not available in vim, you'll need a pretty dirty hack like ib proposed, but calling tr is much nicer work.
Especially considering the corner case of y and z: I assume these should be mapped to a and b, respectively?

Resources