4

I am aware of the substitute command and of its usage as an ex command :%s/<old>/<new>/g

However when <new> contains a very complicated string, say a complicated URL with lots of characters like % [ ] : / \ etc, then the characters start being part of the command (especially / or :) and I have to start using escape sequences ie \

Is there a way of switching off all the need for escaping and all the interpretation, so that I can replace <old> with <new> even if <old> and <new> have arbitrary ASCII characters , including / : [ ] % # $ ( ) etc ?

Currently I resort to Notepad to do this, but I hope I can stay in VIM.

7
  • 3
    Does this answer your question? How to search literally without any regex pattern? – Christian Brabandt Aug 21 '20 at 11:16
  • I usually use this solution mentioned here: vi.stackexchange.com/a/17474/71 – Christian Brabandt Aug 21 '20 at 11:17
  • 2
    @ChristianBrabandt that covers searching, but not replacing? – D. Ben Knoble Aug 21 '20 at 13:39
  • Vim (or any program) needs somehow to decide where the search and replacement terms begin/end. If any character could be present in those terms, it is impossible to discern the delimiters from the actual text without escaping. GUI interfaces can overcome this by presenting separate fields for the search and replace bits. – Quasímodo Aug 21 '20 at 15:53
  • 1
    :h :promptfind and :h :promptrepl (only in GVim). – Matt Aug 21 '20 at 17:04
1

Well, assuming pattern doesn't contain \ then this would work:

:%s/\Vpattern/\='string'

In all cases, when the pattern&replace string are in variables, this would work:

let pattern='<old>'
let str='<new>'
:exec "s/\\V" . escape(escape( pattern, '\'),'/') . "/\\=str"

of course, you can use different methods like input(), or command args to set the variables.

3
  • This also breaks with / in the pattern... – filbranden Aug 22 '20 at 0:11
  • is it OK now?___ – eyal karni Aug 22 '20 at 2:28
  • 1
    This will work, but you can simplify it by escaping both characters at once, with escape(pattern, '\/') or escape(pattern, '/\'). – filbranden Aug 22 '20 at 2:45
0

After reading comments about my first answer, I realize there is perhaps a very simple solution that differs greatly from my first suggestion. This new suggestion still requires using the occasional '.' as a stand-in for matching special characters, but since there isn't a true ability to search for literal strings in vim, you're going to have to compromise in some manner. There are specialized functions you can create that may work for you. But I find this solution very simple to implement and very simple to use.

[since this solution has almost nothing in common with my first solution, I'm posting it as a second answer rather than an edit of my first answer. If I edit the entire answer, the several helpful comments added by other members would no longer make any sense]

My new suggestion is as follows..

First, I place this in my .vimrc...

set incsearch

From :h incsearch...

While typing a search command, show where the pattern, as it was typed
so far, matches. The matched string is highlighted. If the pattern
is invalid or not found, nothing is shown. 

[For the example below, assume your search pattern is part of a ":global" or ":substitute" command]

If you want to match a URL, you start to type http:. As you type, notice that the next possible match in your file or selected text is shown, and highlighted, on your screen. When you type the first letter 'h' it may highlight the 'h' in the word elephant (if elephant has the first occurrence of 'h' in your file or in the currently selected text)

When you type the second letter ('t'), your search string is now 'ht' and the highlighted focus will move ahead to the first match of 'ht'. This most likely matches a URL (not many words contain 'ht'). It might not be the URL you're looking for. But you're not done entering your search pattern.

So, you continue typing. Another 't' ('htt'), then 'p' ('http'), and a colon ('http:'). So far, so good. The first URL is still highlighted on your screen. It still might not be the URL you want, but at least you know your pattern thus far (http:) is working.

But as soon as you type '/' you notice the highlighting no longer tracks what you're typing. Something is wrong with searching for 'http:/'. You might have forgotten that '/' needs to be escaped.

But you don't need to remember what needs to be escaped!!!

The live feedback, when it no longer matches a URL, tells you immediately that adding '/' to 'http:' isn't going to match anything.

You need to escape the '/'. But instead of escaping the '/', I prefer to type a single '.' because it is simpler to type than '\/'. But you could type '.' or '\/'

The downsides of this approach are (1) it does not completely do away with the need for escapes or '.'; and (2) your screen position potentially moves through the file while you are typing your search pattern.

In my experience, neither one bothers me. You may find otherwise.

For (1), you will know immediately as you type what needs to be escaped. This takes all the guesswork out of making a search string. I find it very simple compromise and quite easy to work with.

For (2), I rarely need to see my original position in the file while I'm typing a search string. In fact, the live movement ahead to the next pattern match - and the feedback it provides in confirming your search pattern is valid - is much more preferable (IMO) than simply typing a string and hoping it matches what you want it to match. But if the need to see the original position persists, a simple horizontal split screen should do the trick.

0

My .vimrc has these lines ...

set incsearch
let @o = ".nzz"
map <F8> @o

These commands allow me to make all sorts of edits without having to worry about escaping special characters of any kind. They also allow me to easily make multi line edits.

Once these commands are in your .vimrc, the procedure is as follows...

First, you must search for a pattern that matches all the occurrences you wish to change. This is important because my solution depends on moving to the next occurrence by using the find-next operator ('n')

For example, if you wish to edit all URLs from host 'abcxyz.com' that end in, say, .php, you might search (from normal mode) as follows..

/http:..abcxyz.com.*.php

(... the leading '/' is what you type in normal mode to start entering a search pattern. I wildcarded the '//' in the URL with '..' because I can't recall if '//' will affect the search operation. The beauty is, I don't have to remember. As long as my search pattern is precise enough to catch all occurrences I need to change, and not match patterns I do not want to change, I'm good to go)

Once you hit enter and are positioned at the first occurrence of the URL, you MANUALLY change that URL as desired. For example if you want to change the hostname from 'abcxyz.com' to 'a111b222.com' you could make the edit by typing...

'cfzhttp://a111b222'

.. this is just one of many ways to edit this. Just make sure it is a normal mode edit, not a command mode edit.

Next, you advance to the next occurrence ('n').

Finally, still in normal mode, you enter some integer that exceeds the number of changes you think need to be made. The integer doesn't have to be exact, it only need be greater than the number of changes to make. You could even use 100000000000 if you really don't know how many changes need to be made. If there were only 20 changes to make, my macro will stop after the 20th occurrence).

Once you've typed the large integer - don't press ENTER - press F8 (or whatever key you've assigned the macro to)

This will change every occurrence of the pattern exactly as you did manually for the first occurrence.

One assumption my mappings assume is that when you press F8 for the first time, you are positioned in the file exactly where you want the first change to be made.

The nice thing about the repeat previous edit operator is it isn't restricted to a particular type of edit. It can change 1 character, 1 word, to the end of a line, to the start of a line, an entire paragraph, etc.

So, again, the idea is you make your manual change one time - it matters not if you change a word, 5 words, half a line, 7 lines ... the repeat-last-edit operator remembers this.

Then you search for the next occurrence to be changed, make sure the cursor is at the proper location (e.g., if you're changing an entire URL, make sure the cursor is at the start of "http://" rather than in the middle of the URL) and then press 10000F8.

Change the integer higher if you have more than 10,000 occurrences.

The downside of this is performance. But that only becomes an issue when you have truly massive numbers of edits to make (for me, over 10,000 changes can be slow but tolerable. over 100,000 can take a very long time and reverting back to :s is advisable - but your mileage may vary)

Another downside, as has been pointed out by others, is that I haven't done away with wildcards, although I have done away with escapes. But finding out what needs to be wildcarded is very easy if set incsearch is in your vimrc. This feature highlights matched text as you type your search pattern. When live feedback stops highlighting anything, you know your pattern is wrong - usually because of a character that needs to be escaped. I prefer to replace the character with a '.' as a catch-all place holder (just because I find it simpler to type, but using an escape is OK as well)

7
  • 1
    Not sure this answers the question about literal search/replacement, but you may like to know your mapping is effectively nnoremap <F8> .nzz, and I use cgn instead with good results. – D. Ben Knoble Aug 21 '20 at 20:31
  • @D. Ben Knoble Not sure how you see this as not answering the question. I've presented a very simple way to replace any text pattern with a literal replacement, without any need for escapes and worries about interpretation, so that <new> may contain "arbitrary ASCII characters , including / : [ ] % # $ ( ) etc?"Also, I'm aware that I could have consolidated @o and F8 into F8 only. I probably should update my answer accordingly. I had originally used a register and a map for flexibility reasons, but I never use that flexibility so indeed it is unnecessary. – Paperclip Bob Aug 21 '20 at 21:02
  • In your own answer you said: " wildcarded the '//' in the URL with '..'" That's exactly what the OP is asking. How can I search for a literal string without having to replace characters with wildcards or escape them manually... So, I don't think your answer is really addressing the actual question... – filbranden Aug 21 '20 at 21:07
  • 1
    @D. Ben Knoble Actually I recall now why I used a register for .nzz and a map for replaying the register. If you only have F8 nnoremap to .nzz, the counter prefix causes .nzz to run literally that many times - with almost certainly undesired changes. In my experience when 'n' fails during the replay of a register it causes F8 to short circuit. – Paperclip Bob Aug 21 '20 at 21:16
  • 1
    OP mentioned "even if <old> and <new> have arbitrary ASCII characters, including / : [ ] % # $ ( ) etc". I don't see how replacing those with wildcards is really better than escaping manually... The issue OP is trying to avoid is that you need to figure out which characters need handling and handle them explicitly. If you need to replace them with wildcards, you need to do that. That's why it doesn't look like an answer to the asked question to me. – filbranden Aug 21 '20 at 22:02
0

Use cgn, and repeat with ..

  • First, search for the 'old' string with /.

  • Next, hit cgn. This replaces the next occurrence of 'old', and puts you in insert mode.

  • Type in the 'new' replacement text, and hit Esc when done.

  • Repeat, for all other occurrences of 'old', with [count]..

The idea is to type the 'new' replacement string in good ol' insert mode, so that we don't have to worry about escaping special characters.

For the [count], any large number will suffice; or find out the number of occurrences of 'old' with :%sgn.

(As for special characters in 'old', the best I can think of is /\V<old>. You will still have to escape \ and /, however.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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