ChatGPT解决这个技术问题 Extra ChatGPT

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

作为 this question 的后续,它试图找出如何做这样的事情,这应该很容易,尤其是阻止我更习惯使用 Emacs,而是启动我已经熟悉的编辑器。我在编辑多个文件时经常使用这里的示例。

在 Ultraedit 中,我会执行 Alt+s 然后 p 显示一个对话框,其中包含以下选项:查找(包括在多行中使用正则表达式)、替换为、在文件/类型中、目录、匹配大小写、仅匹配整个单词、列表更改的文件和搜索子目录。通常我会先用鼠标点击并拖动选择我想要替换的文本。

仅使用 Emacs 本身(在 Windows XP 上),不调用任何外部实用程序,如何将某些文件夹及其下所有文件夹中的 *.c*.h 文件中的所有 foo\nbar 替换为 bar\nbaz。也许 Emacs 不是最好的工具,但如何用最少的命令轻松完成呢?


R
Rudolf Adamkovič

Mx find-name-dired:系统会提示您输入根目录和文件名模式。按 t 为找到的所有文件“切换标记”。按 Q 选择“Query-Replace in Files...”:系统将提示您输入查询/替换正则表达式。与 query-replace-regexp 一样继续:SPACE 替换并移动到下一个匹配项,n 跳过匹配项等。按 Cx s 保存缓冲区。 (然后您可以按 y 表示是,n 表示否,或 ! 表示全部表示是)


不,它是递归的。在 dired 模式下,非递归版本将是 %m。
可能是因为您调用的是 C:\WINDOWS\system32\find.exe,而不是 GNU find。
Steen, Chris:可能不完全是您想要的,但您可以使用“Cx s”(保存一些缓冲区),它会提示您输入每个修改过的文件,因此您只需多次点击“y”。
C-x s ! 一次保存所有文件。
M-, 继续搜索和替换(例如,如果磁盘上的文件已更改,则在“恢复文件”提示之后)。
C
Community

Mx find-name-dired RET 所有文件可能需要一些时间才能出现在列表中,滚动到底部(M->)直到出现“find finished”以确保它们都已加载

所有文件可能需要一些时间才能出现在列表中,滚动到底部(M->)直到出现“查找完成”以确保它们都已加载

按 t 为找到的所有文件“切换标记”

按 Q 选择“Query-Replace in Files...”:系统将提示您输入查询/替换正则表达式。

与 query-replace-regexp 一样继续:SPACE 或 y 替换并移动到下一个匹配项,n 跳过匹配项等。键入!替换当前文件中的所有出现而不询问,N 跳过当前文件其余部分的所有可能替换。 (N 仅是 emacs 23+)要在不进一步询问的情况下对所有文件进行替换,请键入 Y。

类型 !替换当前文件中的所有出现而不询问,N 跳过当前文件其余部分的所有可能替换。 (N 仅是 emacs 23+)

要在不进一步询问的情况下对所有文件进行替换,请键入 Y。

调用“ibuffer”(如果绑定到 ibuffer,则为 Cx Cb,或 Mx ibuffer RET)列出所有打开的文件。

输入 * u 标记所有未保存的文件,输入 S 保存所有标记的文件

* RET 取消标记所有标记,或键入 D 关闭所有标记的文件

这个答案结合了this answerthis site和我自己的笔记。使用 Emacs 23+。


默认情况下,ibuffer 未绑定到 C-x C-b
好吧,我个人建议人们C-x C-b 绑定到 ibuffer,因为它比默认绑定好得多。只需将 (global-set-key (kbd "C-x C-b") 'ibuffer) 添加到您的 .emacs 文件中。否则,请在上述说明中使用 M-x ibuffer RET
好的。我想我有 emacs 启动工具包,这就是我的绑定的原因
文件 bla-bla-bla 以只读方式访问并停止搜索。如何避免/忽略这一点并继续更换?谢谢。
当您可以 C-x s (save-some-buffers) 并输入 ! 时,为什么还要 ibuffer?我不想吐槽,只是好奇。 PS plus 用于描述 !NY
e
eflanigan00

弹丸真的很好:Cc pr 运行命令弹丸替换


我可以仅将其应用于 python 文件吗?
遍历所有查询后需要保存缓冲区。
o
ocodo

提供的答案很棒,但是我想我会添加一种稍微不同的方法。

这是一种更具交互性的方法,需要 wgreprgrepieditieditwgrep 都必须通过 MELPA 或 Marmalade 安装(使用 M-x package-list-packages

首先运行 M-x rgrep 以查找您要查找的字符串。

您将能够指定文件类型/模式和要递归的文件夹。

接下来您需要运行 wgrepC-s C-p 启动它。

Wgrep 将允许您编辑 rgrep 结果,因此在字符串上设置一个区域以匹配并以 C-; 开头 iedit-mode(取决于您的终端,您可能需要重新绑定它)

所有事件都可以立即编辑。 C-x C-s 提交 wgrep。然后 C-x s ! 保存更改的文件。

此方法的主要好处是您可以使用 iedit-mode 来关闭某些匹配 M-;。您还可以使用 rgrep 中的结果跳转到文件中,例如,如果您有意外匹配。

我发现它对于在项目中进行源代码编辑和重命名符号(变量、函数名等)非常有用。

如果您还不知道/使用 iedit 模式,它是一个非常方便的工具,我强烈建议您看看。


即使不使用 iedit,这种技术也非常强大。它将知道如何 grep(使用 lgrep/rgrep 轻松)变成了知道如何批量搜索替换!
结合 rgrep/wgrep/macros 很棒。
开始 wgrep 的键绑定是 C-c C-p 顺便说一句。
B
Blair Conrad

我通常使用其他工具来执行此任务,并且似乎 EmacsWiki's Find and Replace Across Files entry 中提到的许多方法都已被淘汰,但 Findr Package 看起来很有希望。

窃取 the source file 的一部分:

(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: Mx findr-query-replace RET Collectible RET <替换> RET .*\.java RET <开始搜索的目录> RET
我喜欢这种方法(它避免了直接方法中涉及的所有切换)。我使用一些方便的功能将它与弹丸(project-root-discover)结合起来,并替换点:gist.github.com/anonymous/055a9d62f9fc824153599724b8f44d57
我可以仅将其应用于 python 文件吗?
P
Prashant Sharma

信息来源:1

对于 emacs 专业用户:

调用 dired 列出 dir 中的文件,如果需要所有子目录,则调用 find-dired。标记您想要的文件。您可以通过输入【% m】来使用正则表达式进行标记。键入 Q 调用 dired-do-query-replace-regexp。键入您的查找正则表达式并替换字符串。 〔☛ common elisp regex pattern〕 每次出现,输入 y 替换,n 跳过。按【Ctrl+g】中止整个操作。类型 !替换当前文件中的所有出现而不询问,N 跳过当前文件其余部分的所有可能替换。 (N 仅是 emacs 23)要在不进一步询问的情况下对所有文件进行替换,请键入 Y。(仅 Emacs 23)调用 ibuffer 以列出所有打开的文件。输入【* u】标记所有未保存的文件,输入S保存所有标记的文件,输入D关闭所有文件。

Emacs 初学者分步指南

选择目标文件

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

选择目录中的文件

首先,您需要选择要替换的文件。使用图形菜单〖File ▸ Open Directory〗。 Emacs 会要求您提供目录路径。键入目录路径,然后按 Enter。

现在,您将看到文件列表,现在您需要标记您希望正则表达式查找/替换处理的文件。通过将光标移动到所需文件来标记文件,然后按 m。按 u 取消标记。 (要列出子目录,请将光标移到目录并按 i。子目录的内容将列在底部。) 要使用正则表达式标记所有文件,请键入【% m】,然后键入您的正则表达式模式。例如,如果要标记所有 HTML 文件,则键入【%m】,然后键入 .html$。 (您可以在图形菜单“Mark”中找到标记命令列表(当您处于 dired 模式时会出现此菜单。)

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

如果您想查找/替换目录中的文件,包括数百个子目录,这里有一种选择所有这些文件的方法。

调用 find-dired。 (按【Alt+x】调用命令)然后,输入目录名,⁖ /Users/mary/myfiles

注意:如果您在 unix 非图形文本终端上使用 emacs,并且如果【Alt+x】不起作用,则等效的按键是【Esc x】。

Emacs 会通过提示“Run find (with args): ”询问您。如果您需要对所有 HTML 文件进行替换,请键入 -name "*html"。如果你不关心什么类型的文件,只关心那个目录下的所有文件,那么给“-type f”。

现在,如上所述标记文件。

交互式查找/替换

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

现在,emacs 将使用您的模式并检查文件,并在发生匹配时停止并向您显示。发生这种情况时,emacs 会提示您,您可以选择进行更改或跳过更改。要进行更改,请键入 y。要跳过,请键入 n。如果您只是希望 emacs 继续并对当前文件进行所有此类更改,请键入 !。

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

保存更改的文件

现在,在您经历了上述磨难之后,您还需要做一个步骤,那就是保存更改的文件。

如果你使用的是 emacs 22 或更高版本,则调用 ibuffer 进入缓冲区列表模式,然后输入【* u】标记所有未保存的文件,然后输入 S 将它们全部保存。 (这是shift-s)

如果您使用的是 emacs 版本 21,那么您可以这样做:调用 list-buffers,然后将光标移动到要保存的文件并键入 s。它将标记文件以供以后保存操作。输入 u 取消标记。完成后,键入 x 以执行所有标记为保存的文件的保存。 (在 emacs 中,打开的文件称为“缓冲区”。忽略那里的其他内容。)

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

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


这个答案应该比我希望的投票最多的答案更适合新手。顺便说一句,谢谢你激励我把整个事情而不是链接。
答案从 source: 作为第一行开始。复制粘贴的理由是:有时指向博客的外部链接会过时,然后答案将变得无用。因此,正如维基元所建议的那样。我决定复制整个内容。
A
Alex Coventry

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


刚刚尝试了一个包含超过 14,000 个文件的目录。花了几秒钟在 Emacs 中弹出。所以绝对不会慢(不再)。
y
yPhil

对于开放缓冲区,这就是我所做的:

(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)))))

D
Dinesh Kanivu

M-X Dired 和 t 标记所有文件,Q 查询替换所有文件中的文本。您可以在查询替换之前使用 i 命令展开子目录。我要添加的关键信息是,如果您为 i 命令提供前缀 (control-u),它将提示您输入 arg,并且 -R 参数将递归地将所有 subdirs 扩展到 dired 缓冲区。因此,现在您可以查询搜索整个目录中的每个文件。


D
Drew

另一种选择是使用 Icicles search。这是一种不同类型的增量搜索,它使用完成您的迷你缓冲区输入来对抗搜索命中。当您修改当前输入时,匹配命中集会在缓冲区 *Completions* 中更新。

您可以搜索任意数量的文件、缓冲区或书签位置,您可以使用 minibuffer 模式(例如 regexp)匹配来选择这些位置。

当您访问搜索命中时,您可以根据需要replace整个命中或与当前微型缓冲区输入匹配的部分命中。按需替换意味着您不会依次询问每个搜索命中;您可以按任意顺序直接访问您想要的点击量。如果要进行的替换数量有限,这种方法可能比查询替换更有效:跳过详尽的 y/n 提示。

搜索超出搜索您定义的上下文——您不限于搜索目标文件中的所有文本(例如,您可以跳过注释或特定类型的程序部分)。搜索上下文的一个简单示例是一行,如 grep,但上下文可以是您喜欢的任何与模式匹配的文本块。通常,您使用正则表达式定义搜索上下文,但您也可以使用函数。除了定义您自己的之外,还有针对不同类型上下文的预定义 Icicles 搜索命令:文本属性块或覆盖属性块、事物点事物等。

您还可以按各种排序顺序对搜索命中进行排序,以便于访问/导航。


D
Drew

find-name-dired 可以,但是:

您获得的所有文件都匹配相同的单个正则表达式。

find-dired 在这方面更灵活,但它也适用于使用一般规则(即使它们可以任意复杂)。当然 find 有自己的复杂语言。

如果您随后只想对一些名称收集在 find(-name)-dired 缓冲区中的文件执行操作,则需要标记它们或删除/省略那些您不想执行操作的行。

另一种方法是使用 Dired+ 命令作用于 (a) 标记的文件和 (b) 标记的子目录 中的所有标记文件(或所有文件,如果没有标记)...找到递归。这为您提供了对文件选择的通用性和轻松控制。这些“here-and-below”命令都在 Dired 模式下的前缀键 M-+ 上。

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

是的,使用此类此处和下方命令的替代方法是递归地插入所有子目录及其子目录,然后使用顶级命令,例如 Q。但是不打扰插入的子目录通常很方便。

为此,您无论如何都需要一种快速的方法来递归地插入所有此类子目录。在这方面,Dired+ 也可以提供帮助。 M-+ M-i 以递归方式插入所有标记的子目录和它们自己标记的子目录。也就是说,它类似于 M-i(将标记的子目录插入到 Dired+ 中),但它递归地作用于子目录。

(所有此类“此处和下方”Dired+ 命令都在菜单“多个”>“此处和下方标记”上。)

您还可以对 Emacs fileset 执行 Dired 操作,该文件集是保存在任何位置的文件名集。如果您使用 Icicles,那么您可以只为文件集中的文件或其他类型的已保存文件列表打开 Dired 缓冲区。

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


谁:愿意解释一下您为什么不赞成这个答案吗?
A
Andrzej Pronobis

我想推荐一种尚未提及的出色工具,即Helm

它可以很好地替代许多涉及完成、搜索等的标准 Emacs 操作。特别是,helm-find-files 允许在多个选定文件中执行查询替换(包括正则表达式)。

只需打开 helm-find-files,用 M-SPC 标记相关文件,然后使用 F6F7 在所选文件中运行查询替换或查询替换正则表达式。


可以用正则表达式而不是 M-SPC 标记 helm-find-files 吗?
y
young_souvlaki

Mx project-query-replace-regexp RET(确保您的文件已保存!)。 Mx rgrep RET 然后在每个缓冲区中进行查询替换。很好,因为您可以跟踪所有事件,并且因为它可以让您将搜索限制为某些扩展。


很遗憾,project-query-replace-regexp 不是那么可靠。
b
blais

它不是 Emacs,但 xxdiff 带有一个名为 xx-rename 的工具,它可以一次处理多个字符串(例如 From To from to FROM TO),具有交互式提示,保存所有修改文件的备份,并生成一个简短的使用上下文所做的更改的日志。这就是我在进行大型/全局重命名时倾向于使用的。


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅