5

I would like to repeat replacing a string with another string in a text file, until it is not possible. Is it possible to do that in Emacs? I only know to replace just once with M-% and !.

For example, replace " " (two spaces) with " " (one space) repeatedly until not possible.

  • 7
    Of course, in the case with spaces it is trivial using regexen: " +" -> " " changes all strings of two or more spaces to one space. – mbork Jan 21 '15 at 0:24
  • Tim: Can you please add an example with the before and desired after states? The current wording is ambiguous. – phils Jan 22 '15 at 22:01
8

It's worth learning how to use regular expressions to do things like this. Use M-x replace-regexp and replace " +" with " " (without the quotes). " +" means "one or more space". You are telling emacs to replace all instances of one or more space with a single space.

Finally, if you do end up learning to use regular expressions, I recommend using https://github.com/joddie/pcre2el with pcre-mode, which will let you use so-called PCRE (Perl Compatible Regular Expressions), for which there are a lot more resources online, and which are much easier to use interactively.

  • thanks. Does regex replacement always recursive replace until impossible? Does nonregex replacement always only replace nonrecursively? – Tim Feb 18 '15 at 18:45
  • @Tim Regex doesn't recursive replace. The point is that the pattern " +" will match any number of spaces, so that you only need to run the replacement once. – asmeurer Feb 18 '15 at 19:38
5

The question is not very clear. M-% followed by ! replaces all occurrences (after the cursor). Just put the cursor at the beginning (M-<) and then use M-% followed by !.

Now maybe what you mean is that you want to repeat the replacement (e.g. from the buffer beginning again) until no more such replacements are possible. That can of course lead to an infinite loop, depending on your replacement.

Anyway, here is a quick-and-dirty command to do that. The interactive spec is essentially from query-replace.

(defun foo (from-string to-string &optional delimited start end backward)
  (interactive
   (let ((common  (query-replace-read-args
                   (concat "Query replace"
                           (if current-prefix-arg
                               (if (eq current-prefix-arg '-) " backward" " word")
                             "")
                           (if (and transient-mark-mode  mark-active) " in region" ""))
                   nil)))
     (list (nth 0 common) (nth 1 common) (nth 2 common)
       (and transient-mark-mode  mark-active  (region-beginning))
       (and transient-mark-mode  mark-active  (region-end))
       (nth 3 common))))
  (goto-char (point-min))
  (while (save-excursion (re-search-forward from-string nil t))
    (while (re-search-forward from-string nil t)) (replace-match to-string nil nil)
    (goto-char (point-min))))

If you want to hit ! for each iteration, to obviate the problem of infinite looping, you can do something like this instead:

(defun foo (from-string to-string &optional delimited start end backward)
  (interactive
   (let ((common  (query-replace-read-args
                   (concat "Query replace"
                           (if current-prefix-arg
                               (if (eq current-prefix-arg '-) " backward" " word")
                             "")
                           (if (and transient-mark-mode  mark-active) " in region" ""))
                   nil)))
     (list (nth 0 common) (nth 1 common) (nth 2 common)
       (and transient-mark-mode  mark-active  (region-beginning))
       (and transient-mark-mode  mark-active  (region-end))
       (nth 3 common))))
  (goto-char (point-min))
  (while (perform-replace from-string to-string t nil delimited nil nil start end backward)
    (goto-char (point-min))))

That just uses perform-replace repeatedly, which gives you a chance (and the obligation) to act on each iteration.

1

Here's how I removed extra spaces after a comma with a keyboard macro. It should also work with other scenarios such as extra spaces after a :.

  • M-< - top of buffer
  • M-% - query replace - ", " (comma and two spaces) -> ", " (comma and one space)
  • ! - replace all
  • <F3> - start recording
  • M-< - top of buffer again
  • M-% - query replace - repeat last replacement
  • ! - replace all
  • <F4> - stop recording
  • run macro with <F4> and repeat until all the excess spaces are gone

BTW this answer is a much better option to achieve the same results

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.