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.

2
  • 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, 2015 at 0:24
  • 1
    Tim: Can you please add an example with the before and desired after states? The current wording is ambiguous.
    – phils
    Jan 22, 2015 at 22:01

3 Answers 3

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.

2
  • thanks. Does regex replacement always recursive replace until impossible? Does nonregex replacement always only replace nonrecursively?
    – Tim
    Feb 18, 2015 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, 2015 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 and acknowledge that you have read and understand our privacy policy and code of conduct.

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