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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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