5

Linux Mint 20.2

Emacs 27.1

Task:

I has folder with MyProject with 5 subfolders.

Total count of files in all this folders are about 500 files.

I need all files that content

import org.apache.log4j.Logger;

replace by line:

import org.slf4j.Logger;

To do I use the next steps:

 M-x "find-name-dired" - you will be prompted for a root directory and a filename pattern.
 Press "t" to "toggle mark" for all files found.
 Press "Q" for text to search: "import org.apache.log4j.Logger;"and replace by "import org.slf4j.Logger;"  
 Proceed as with query-replace-regexp: SPACE to replace and move to next match, n to skip a match, etc.
 Press "C-x s" and then choose "!" for saving all buffers.

OK, this work... but so many steps. Maybe has more simple approach to replace some text in many files?

a_subscriber
  • 3,854
  • 1
  • 17
  • 47
  • I take it that the main problem is step 4, where you are doing query-replaces on each file that matches. The others are all short. There may be a way to replace the interactive query replace with a batch one, but personally, I'd write a shell script to do all that and test it on a backup copy of the original directory, diffing against the original directory to make sure that the changes are what I expect. – NickD Nov 30 '21 at 18:16

5 Answers5

1

You could do this with wgrep, which you can install from Melpa.

The workflow would be:

  1. Call grep: M-x grep
  2. Fill in an invocation that will match your files and patterns: grep --color=auto -nH --null -e 'import org.apache.log4j.Logger;' ./* (note that the first part is the default, you only need to enter 'import org.apache.log4f.Logger;'./*
  3. The results are presented in a *grep* buffer
  4. Make the buffer editable: C-c C-p.
  5. Query-replace/M-%, and enter your search and replace patterns
  6. hit ! to instantly replace all matches in the buffer
  7. C-c C-e to make those changes in each of the files you searched

This is still a lot of steps, but it would save you pressing space for every match, or ! for every file.

Alternatively, you could do this from the command line with sed:

sed -i s/import org.apache.log4j.Logger;/import org.slf4j.Logger;/g ./path/to/files/*txt
Tyler
  • 21,719
  • 1
  • 52
  • 92
  • I use "rgrep" because I need to replace in many files in current folder and all subfolders. – a_subscriber Nov 30 '21 at 21:16
  • 1
    @a_subscriber You could also use [`elgrep`](https://github.com/TobiasZawada/elgrep). There you have `elgrep-menu` that opens a menu for all the available settings. That menu also offers a toggle for recursive search. Editing the `elgrep`-buffer with the search results is simple. `C-e` enters the edit mode. You do your changes, e.g., with `query-replace` as suggested here and then you save the `elgrep`-buffer with `save-buffer` bound to `C-x C-s` as you are used to. This saves the changes in the respective files. **AND:** `elgrep` [offers much more](https://github.com/TobiasZawada/elgrep). – Tobias Dec 01 '21 at 05:20
1

This is trivial in a Dired buffer for the directory possibly containing (possibly with subdirs, recursively) matches for some search pattern.

Just use Q, which is bound to dired-do-find-regexp-and-replace:

Q runs the command dired-do-find-regexp-and-replace (found in dired-mode-map), which is an interactive autoloaded compiled Lisp function in dired-aux.el.

It is bound to Q, <menu-bar> <operate> <query-replace>.

(dired-do-find-regexp-and-replace FROM TO &optional ARG INTERACTIVEP)

Replace matches of FROM with TO, in all marked files.

If no files are marked, use the file under point.

For any marked directory, matches in all of its files are replaced, recursively. However, files matching grep-find-ignored-files and subdirectories matching grep-find-ignored-directories are skipped in the marked directories.

REGEXP should use constructs supported by your local grep command.

See also this, about replacing literal matches.

Drew
  • 75,699
  • 9
  • 109
  • 225
  • The problem is after success executed "dired-do-find-regexp-and-replace" I need to press MANY TIMES "!" to save every files. It's not very convenient. – a_subscriber Nov 30 '21 at 20:40
  • 1
    This is what I thought at first too. It would be good if `dired-do-find-regexp-and-replace` provided a 'super-!', to let the user instantly complete the replacement for every file in the set, and not have to press it once for each file. Maybe this exists, but I couldn't find it. – Tyler Nov 30 '21 at 20:52
  • 1
    @Tyler: Yep, you're right. And the same is true of the alternative command (what `Q` used to be bound to), `dired-do-query-replace-regexp`. You could code a command that does what you want, but I don't know of a predefined command that does it. – Drew Nov 30 '21 at 21:16
0

If you use library Dired+ then you can use command diredp-do-eval-in-marked-recursive to do what you want.

If you use it directly it asks you for a sexp to evaluate at the beginning of each file to be acted on. In this case, the sexp you want is just a replace-string sexp.

So this simple command does the job:

(defun bar ()
  (interactive)
  (diredp-do-eval-in-marked-recursive
   '(replace-string "import org.apache.log4j.Logger;"
                    "import org.slf4j.Logger;")))

Command diredp-do-eval-in-marked-recursive is bound by default to key sequence M-+ @ M-:. It's also on menu-bar menu Multiple > Marked Here and Below > Apply (Map) > Eval Sexp In.

diredp-do-eval-in-marked-recursive is an interactive Lisp function in dired+.el.

(diredp-do-eval-in-marked-recursive SEXP &optional IGNORE-MARKS-P DETAILS)

Evaluate SEXP in each of the marked files, including in marked subdirs.

Like diredp-do-eval-in-marked but act recursively on subdirs, and do no result-logging, error-logging, or echoing.

The files included are those that are marked in the current Dired buffer, or all files in the directory if none are marked. Marked subdirectories are handled recursively in the same way.

With a prefix argument, ignore all marks - include all files in this Dired buffer and all subdirs, recursively.

When called from Lisp, optional arg DETAILS is passed to diredp-get-files.

[All of the Dired+ commands that act on marked files not only in the current Dired buffer but also in Dired buffers for any subdirs in that buffer that are themselves marked, and so on, recursively, are on prefix key M-+. Those that apply a function, evaluate a sexp, or invoke a command are on prefix key M-+ @. And M-: is mnemonic for evaluating a sexp. (The same is true of the non-"recursive" commands - same key bindings, but without the M-+.)]

Drew
  • 75,699
  • 9
  • 109
  • 225
0

This may actually be easier on the command line with perl than within emacs:

perl -i.bak -pe 's/import org.apache.log4j.Logger;/import org.apache.log4j.Logger;/;' * */*

(Add more */*/* if you have deeper subfolders.)

Added advantage: backup copies of your original files are created with the suffix ".bak".

  • Combine it with `find` if you want to select *some* files to operate on, instead of operating on every file. – NickD Dec 01 '21 at 17:47
0

Surprisingly, no answer was given yet to make use of -batch Emacs mode that has been created for situations like this. So here's a simple example:

$ echo "import org.apache.log4j.Logger;" > 1 && cp 1 2 && cp 1 3
$ emacs -batch --eval '(progn (dolist (file command-line-args-left) (find-file file) (replace-regexp "apache.log4j" "slf4j") (save-buffer) (kill-buffer)))' ./1 ./2 ./3
Mark set
Replaced 1 occurrence
Mark set
Replaced 1 occurrence
Mark set
Replaced 1 occurrence
$ grep "" ./1 ./2 ./3
./1:import org.slf4j.Logger;
./2:import org.slf4j.Logger;
./3:import org.slf4j.Logger;

Worth noting separately that if your replacement does not fit into a small one-liner you can also save it to a file like this:

$ cat my_replace.el
(dolist (file command-line-args-left)
  (find-file file)
  (replace-regexp "apache.log4j" "slf4j")
  (save-buffer)
  (kill-buffer))
$ emacs -batch -l my_replace.el ./1 ./2 ./3
Mark set
Replaced 1 occurrence
Mark set
Replaced 1 occurrence
Mark set
Replaced 1 occurrence
Hi-Angel
  • 525
  • 6
  • 18