1

Suppose I have a buffer containing

a
b
c

How can I use a single search and replace command to obtain the following (cyclic) permutation?

b
c
a

In this question, I found one can use the "bar" operator | to execute multiple commands at once. However, it seems these commands are applied consecutively, as the command

:%s/a/b/ | %s/b/c/ | %s/c/a/

gives me

a % a -> b -> c -> a
a % b -> c -> a
a % c -> a

which is not what I want. I looked at this guide and found it's possible to match from several options using the syntax \(a\|b\|c\) and subsequently refer back to the matched objects using \1, \2 and \3. I feel this is getting close, but something like

:%s:\(a\|b\|c\):\3\2\1:

does not cut it -- it leaves my original buffer invariant, probably since there's only one match on each line, and that's the only one of the three options that's substituted back. Ideally, I guess I would like to specify a substitution for each of the backreferences, so something like "\1 goes to b" etcetera. Is that possible?

3 Answers 3

2

You can use :substitute's expression replacement feature to specify an expression instead of a simple replacement string. A quick and dirty solution to your problem is the following command that uses a nested ternary expression:

:%s/[a-c]/\=submatch(0)=='a'?'b':submatch(0)=='b'?'c':'a'

Instead of giving a replacement string, we use \= to give a replacement expression. The submatch(0) is the whole matched pattern, the same as \0 or & in a regular substitution.

All of this is documented in :help sub-replace-expression.

For the ternary, see :help ternary. For nested ternaries, see a therapist.

A more sane command involves a dictionary that contains the replacements, like this:

:let g:replace_dict = {'a': 'b', 'b': 'c', 'c': 'a'}
:%s/[a-c]/\=g:replace_dict[submatch(0)]

Or as a one-liner; to lose a global variable and a bit of readability:

:%s/[a-c]/\={'a': 'b', 'b': 'c', 'c': 'a'}[submatch(0)]

See, e.g. Replace all quotes with escaped quotes and vice versa or What is the easiest way to swap occurrences of two strings in Vim? on SO (thanks to @D. Ben Knoble for the references).

Since you are incrementing characters, you might also cast them to their numerical values, increment those and cast back:

 :%s/[a-c]/\=submatch(0)=='c'?'a':nr2char(char2nr(submatch(0))+1)

Once more, we need a ternary for the wraparound from 'c' to 'a'. For more on the used functions, see :help char2nr() and :help nr2char().

And, last but not least, the orthodox way to do such replacements is to use placeholders like this:

 :%s/a/xxx/
 :%s/b/yyy/
 :%s/c/a/
 :%s/xxx/b/
 :%s/yyy/c/

Not quite as sophisticated, but gets the job done.

By the way, your observation that chaining commands with a bar | runs them in sequence is right on the mark. It's a way to squeeze more commands into the command-line. And :help :bar will tell you so.

1
  • Thank you for the quick and elaborate answer, that has introduced me to some new and useful concepts! I guess the dictionary method will be most useful to me, as I mostly will want to do this kind of substitution for longer strings rather than individual characters. The syntax is a bit involved but writing a function that does this could be a good first vim-scripting problem, I guess :-)
    – 907456
    Commented Jul 17 at 17:29
1

With Abolish, I believe you'd do

:%S/{a,b,c}/{b,c,a}
2
  • Nice. This is perhaps closest to what OP initially tried to do.
    – Friedrich
    Commented Jul 10 at 6:45
  • Thanks! I would have never guessed this was possible from the description of the plugin...
    – 907456
    Commented Jul 17 at 17:30
1

Less sophisticated than other answers, but this works as well:

:%s/c/d/ | %s/b/c/ | %s/a/b/ | %s/d/a/
2
  • Welcome to the site. Actually, it's a smarter version of the placeholder approach in my answer. You need only one placeholder (vs. two in my solution) by clever choice of direction.
    – Friedrich
    Commented Jul 10 at 9:05
  • Thanks! This is a good solution too, but I'll mostly want to do this on parts of the buffer selected in visual mode, so some of the other options would be easier than typing '<,'> three times -- although I guess this should also be a good basis for a function!
    – 907456
    Commented Jul 17 at 17:33

Not the answer you're looking for? Browse other questions tagged or ask your own question.