使用Emacs在未打开的文本文件中进行递归查找和替换

229
作为对这个问题的追踪,它试图找出如何做一些看似容易的事情,尤其是阻止我更多地习惯于使用Emacs,而是启动我已经熟悉的编辑器。我经常在编辑多个文件时使用这里的示例。
在Ultraedit中,我会按Alt+s然后按p,显示一个带有以下选项的对话框:查找(包括在多行中使用正则表达式),替换为,在文件/类型中,目录,匹配大小写,仅匹配整个单词,列出已更改的文件和搜索子目录。通常情况下,我会首先使用鼠标点击并拖动选择要替换的文本。
只使用Emacs本身(在Windows XP上),而不调用任何外部实用程序,如何将所有foo\nbar替换为bar\nbaz在某个文件夹及其所有子文件夹中的*.c*.h文件中。也许Emacs不是最好的工具来完成这个任务,但如何以最少的命令轻松地完成它呢?
15个回答

408
  1. M-x find-name-dired:您将被提示输入根目录和文件名模式。
  2. 按下t键可以为找到的所有文件“切换标记”。
  3. 按下Q键进行“文件中查询替换...”:您将被提示输入查询/替换正则表达式。
  4. 按照query-replace-regexp的方式进行操作:SPACE用于替换并跳转到下一个匹配项,n用于跳过一个匹配项,等等。
  5. 按下C-x s键保存缓冲区。(然后您可以按y表示是,n表示否,或者按!表示全部是)

10
不,这是递归的。非递归版本在dired-mode中为%m。 - Chris Conway
5
可能是因为你在调用C:\WINDOWS\system32\find.exe,而非GNU find。 - Jazz
6
Steen, Chris: 可能不完全符合您的要求,但您可以使用“C-x s”(保存多个缓冲区)命令,它会提示您每个被修改的文件,因此您只需要按多次“y”即可。 - danielpoe
52
使用C-x s !可以一次性保存所有文件。 - cYrus
10
在搜索和替换时,若出现类似“文件在磁盘上已更改”的提示后,按下 M-, 继续进行替换。 - tfischbach
显示剩余17条评论

41
  • M-x find-name-dired RET:使用该命令查找文件名并列出所有符合条件的文件
    • 可能需要一些时间才能在列表中看到所有文件,请向下滚动(M->)直到出现“find finished”以确保它们都已加载
  • 按下t键,“切换标记”以选中所有找到的文件
  • 按下Q键,调用“Query-Replace in Files...”命令:您将被提示输入查询/替换正则表达式。
  • 与query-replace-regexp操作类似: 按空格或y实现替换并移至下一个匹配项,n跳过当前匹配项等。
    • 输入!以替换当前文件中的所有出现而无需询问,N跳过当前文件中其余所有可能的替换。(N仅限于emacs 23+)
    • 要对所有文件执行替换而无需进一步询问,请键入Y
  • 调用“ibuffer”(如果绑定为ibuffer,则为C-x C-b,否则为M-x ibuffer RET)列出所有已打开的文件。
  • 键入* u以标记所有未保存的文件,键入S以保存所有标记的文件
  • * * RET取消所有标记,或键入D关闭所有标记的文件

本答案结合了此回答此网站和我的笔记。使用 Emacs 23+。


3
默认情况下,ibuffer 没有绑定到 C-x C-b - phils
3
个人建议把C-x C-b按键绑定到ibuffer,因为它比默认的绑定要好得多。只需在你的.emacs文件中添加(global-set-key (kbd "C-x C-b") 'ibuffer)即可。如果无法实现,请按照上述说明使用M-x ibuffer RET命令。 - phils
2
文件 bla-bla-bla 被以只读方式访问,搜索停止了。如何避免/忽略并继续替换?谢谢。 - Felipe
3
为什么要用ibuffer,而不是使用C-x ssave-some-buffers)并键入?我不是在发牢骚,只是好奇。另外,请描述一下 !NY 的作用。 - d12frosted
@alper,我不确定你遇到了什么问题,但请确保你正在使用ibuffer。 - Frank Henard
显示剩余2条评论

25

Projectile非常好用: C-c p r命令可运行projectile-replace。


我能只将其应用于Python文件吗? - alper
2
在遍历所有查询之后,需要保存缓冲区。 - Hellseher

20
提供的答案很好,但我想添加一种稍微不同的方法。
这是一种更加交互式的方法,需要使用 wgrep、rgrep 和 iedit。必须通过 MELPA 或 Marmalade 安装 iedit 和 wgrep(使用 M-x package-list-packages)。
首先运行 M-x rgrep 查找你要查找的字符串。
您将能够指定文件类型/模式和要递归的文件夹。
接下来,您需要使用 C-s C-p 启动 wgrep。
Wgrep 将让您编辑 rgrep 的结果,因此设置一个区域以匹配您要查找的字符串,并使用 C-;(根据您的终端,您可能需要重新绑定它)启动 iedit-mode。
所有匹配项都可以同时进行编辑。使用 C-x C-s 提交 wgrep。然后使用 C-x s ! 保存更改后的文件。
这种方法的主要好处是您可以使用 iedit-mode 切换某些匹配项 M-;。您还可以使用 rgrep 中的结果跳转到文件,例如如果您有一个意外的匹配项。
我发现这对于在整个项目中进行源编辑和重命名符号(变量、函数名称等)非常有用。
如果您还不知道/使用 iedit 模式,那么这是一个非常方便的工具,我强烈建议您去看一看。

即使不使用iedit,这种技术也非常强大。它可以将了解如何使用lgrep/rgrep轻松进行grep转化为了解如何批量搜索替换! - NeilenMarais
结合 rgrep/wgrep/macros 是非常棒的。 - ocodo
2
启动wgrep的快捷键是C-c C-p,顺便说一下。 - techniao

13

通常我使用其他工具来完成这项任务,看起来像是许多在EmacsWiki的查找和替换跨文件条目中提到的方法需要外部调用,但Findr Package看起来非常有前途。

源代码文件中借鉴了一部分:

(defun findr-query-replace (from to name dir)
  "Do `query-replace-regexp' of FROM with TO, on each file found by findr.

我该如何使用 findr.el 在所有扩展名为 java 和 hxx 的文件中尝试查找字符串“Collectible”? - swdev
@swdev:M-x findr-query-replace RET Collectible RET <替换内容> RET .*.java RET <开始搜索的目录> RET - ShreevatsaR
我喜欢这种方法(它避免了直接方法中涉及的所有切换)。 我使用一些方便的函数将其与projectile(project-root-discover)结合起来,并替换点处的内容:https://gist.github.com/anonymous/055a9d62f9fc824153599724b8f44d57 - Att Righ
我可以只将其应用于Python文件吗? - alper

8

信息来源:1

适用于 Emacs 专业用户:

  1. 调用 dired 列出目录中的文件,或者如果需要列出所有子目录,则调用 find-dired。
  2. 标记您想要的文件。可以通过输入【% m】进行正则表达式标记。
  3. 按 Q 调用 dired-do-query-replace-regexp。
  4. 输入您的查找正则表达式和替换字符串。〔☛ 常见的 Elisp 正则表达式模式〕
  5. 对于每个出现位置,键入 y 进行替换,键入 n 跳过。键入 【Ctrl+g】 以中止整个操作。
  6. 键入 ! 在当前文件中替换所有匹配项而不询问,键入 N 跳过当前文件的所有可能替换。(N 仅适用于 Emacs 23)
  7. 键入 Y 替换所有文件而无需再询问。(仅适用于 Emacs 23)
  8. 调用 ibuffer 列出所有打开的文件。键入【* u】标记所有未保存的文件,键入 S 保存所有标记的文件,键入 D 关闭它们所有文件。

Emacs 初学者的逐步指南

选择目标文件

通过在命令行界面提示符中键入“emacs”来启动 Emacs。 (或者,如果您处于图形用户界面环境中,则双击 Emacs 图标)

选择目录中的文件

首先,您需要选择要进行替换的文件。使用图形菜单〖文件▸打开目录〗。Emacs 会要求您输入一个目录路径。键入目录路径,然后按 Enter。

现在,您将看到文件列表,您需要标记要在其上执行正则表达式查找/替换的文件。通过移动光标到所需文件,然后按 m 来标记该文件。按 u 取消标记。 (要列出子目录,请将光标移动到目录并按 i。子目录的内容将显示在底部。)要通过正则表达式标记所有文件,请键入【% m】,然后输入您的正则表达式模式。例如,如果您想标记所有 HTML 文件,则键入【% m】,然后输入 .html$。 (您可以在 dired 模式下时显示的“标记”图形菜单中找到标记命令列表。)

选择目录及其所有子目录中的文件

如果您想要在包括数百个子目录在内的目录中查找/替换文件,则可以使用以下方法选择所有这些文件。

调用 find-dired。(通过按【Alt+x】调用命令)然后,键入目录名称,例如 /Users/mary/myfiles

注意:如果您在 Unix 非图形文本终端上使用 Emacs,并且【Alt+x】不起作用,则相应的键盘快捷键为【Esc x】。

Emacs将提示您:“运行find(带args):”。如果您需要在所有HTML文件上进行替换,则输入-name“*html”。如果您不关心文件类型,只是想要目录下的所有文件,则给出“-type f”。

现在,按照上述方法标记文件。

交互式查找/替换

现在,您已经准备好执行交互式查找替换了。为简单起见,假设您只想用“super”替换“quick”这个词。现在调用dired-do-query-replace-regexp。它会提示您输入正则表达式字符串和替换字符串。键入“quick”,然后输入“super”。

现在,Emacs将使用您的模式检查文件,并在匹配时停止并向您显示。当这种情况发生时,Emacs会提示您选择是进行更改还是跳过更改。若想进行更改,请键入y。若想跳过,请键入n。如果您只想让Emacs更改当前文件的所有这些更改,请键入!。

如果您想取消整个操作而不保存您所做的任何更改,请键入【Ctrl + g】,然后使用菜单〖文件▸退出Emacs〗退出Emacs。

保存更改后的文件

现在,在通过上述折磨之后,您需要执行的一项任务是保存更改后的文件。

如果您正在使用Emacs版本22或更高版本,则调用ibuffer以进入缓冲区列表模式,然后键入【* u】以标记所有未保存的文件,然后键入S以保存它们所有。 (即shift-s)

如果您正在使用Emacs版本21,则可以执行以下操���:调用list-buffers,然后将光标移动到要保存的文件并键入s。它将标记该文件以供稍后保存操作。键入u以取消标记。完成后,键入x以执行保存所有已标记为保存的文件的操作。 (在Emacs中,已打开的文件称为“缓冲区”。请忽略其他内容。)

除了上面的选项之外,您还可以调用save-some-buffers 【Ctrl + x s】。然后,Emacs将显示每个未保存的文件并询问您是否要保存它。

注意:Emacs的正则表达式与Perl或Python的不同,但非常相似。有关摘要和常见模式,请参见:Emacs Regex。


3
希望我的回答比获得最多票数的回答更适合新手。顺便说一句,谢谢你激励我把整个内容都放在这里而不是链接中。 - Prashant Sharma
Answer starts with source: as the first line. 复制粘贴的理由是:有时候博客的外部链接会过期,那么答案就会变得无用。因此,根据维基百科的建议,我决定将整个内容复制下来。 - Prashant Sharma

5

使用dired递归深层目录树对于此任务来说会有点慢。您可以考虑使用tags-query-replace。这意味着要外壳创建一个标签表,但那通常也很有用,而且很快。


刚刚尝试了一个包含超过14,000个文件的目录。在Emacs中弹出只花了几秒钟的时间。所以绝对不慢(再也不慢了)。 - Berend de Boer

3

M-X Dired,按下 t 标记所有文件,再按下Q进行文本替换。 你可以在执行查询-替换之前使用 i 命令扩展子目录。 我要加的关键信息是:如果你在 i 命令前加上前缀(Control-U),它会提示你输入参数,-R 参数会递归扩展所有 子目录dired 缓冲区。这样你就可以在整个目录中搜索每个文件了。


3
对于打开的缓冲区,我会执行以下操作:
(defun px-query-replace-in-open-buffers (arg1 arg2)
  "query-replace in all open files"
  (interactive "sRegexp:\nsReplace with:")
  (mapcar
   (lambda (x)
     (find-file x)
     (save-excursion
       (goto-char (point-min))
       (query-replace-regexp arg1 arg2)))
   (delq
    nil
    (mapcar
     (lambda (x)
       (buffer-file-name x))
     (buffer-list)))))

2

find-name-dired是可以的,但是:

  • 你得到的所有文件都匹配同一个正则表达式。
  • find-dired在这方面更加灵活,但它也是为使用通用规则而设计的(即使它们可以任意复杂)。当然,find有自己的复杂语言。
  • 如果你只想对在find(-name)-dired缓冲区中收集的某些文件进行操作,则需要标记它们或删除/省略那些你不想操作的行。

另一种选择是使用Dired+命令来操作标记的文件和所有标记的文件(如果没有标记,则为所有文件)在标记的子目录中找到递归。这既具有通用性,又易于控制文件选择。这些“此处及以下”命令都在Dired模式下的前缀键M-+上。

例如,M-+ QQ相同---查询替换,但目标文件是当前目录和任何标记的子目录中标记的所有文件,向下,向下,向下...

是的,使用这些“此处及以下”命令的替代方法是插入所有子目录及其子目录,递归地,然后使用顶级命令,如Q。但是,不必费心插入子目录通常是很方便的。

而要做到这一点,你需要一个快速的方式来递归地插入所有这样的子目录。在这里,Dired+也可以帮助。 M-+ M-i递归地插入所有标记的子目录及其自己标记的子目录。也就是说,它类似于M-i(在Dired+中插入标记的子目录),但它对子目录进行递归操作。

(所有这些“此处及以下”Dired+命令都在菜单Multiple>Marked Here and Below中。)

你还可以对Emacs的文件集执行Dired操作,文件集是任何地方保存的文件名称集合。如果你使用Icicles,那么你可以为文件集或其他类型的保存文件列表中的文件打开一个Dired缓冲区。

您还可以收藏任何Dired缓冲区,包括使用find(-name)-dired创建的缓冲区。这样可以快速返回到这样的一个集合(例如项目集)。如果您使用Bookmark+,则收藏Dired缓冲区会记录(a)其ls开关,(b)标记哪些文件,(c)插入哪些子目录以及(d)隐藏哪些(子)目录。当您“跳转”到该书签时,所有这些都会被恢复。 Bookmark+ 还允许您收藏整个Dired缓冲区树---跳转到书签将恢复树中的所有缓冲区。

谁能解释一下为什么你给这个答案点了踩? - Drew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接