如何在大文件夹层次结构中进行文本替换?


11

我想搜索并替换大量文件中的某些文本(某些实例除外)。对于每行,我都需要提示询问我是否需要替换该行。与vim类似:%s/from/to/gc(带有c,提示进行确认),但跨一组文件夹。是否有一些可以使用的良好命令行工具或脚本?


关于正确格式化的重要性:我最初阅读命令时会s/from/to/g带有格式错误,而不是s/from/to/gc着重于c尝试编写的命令(您无法使用Markdown做到这一点,可以通过<code><strong>HTML标签)。
吉尔(Gilles)'所以

Answers:


19

为什么不使用vim?

在vim中打开所有文件

vim $(find . -type f)

或仅打开相关文件(由Caleb建议)

vim $(grep 'from' . -Rl)

然后在所有缓冲区中运行替换

:bufdo %s/from/to/gc | update

您也可以使用进行操作sed,但是我的sed知识有限。


谢谢,您的回答使我迟来了两次:我意识到我完全错过了互动环节。我认为使用sed(输入/输出通道不足)甚至是不可能的。
吉尔(Gilles)'所以

1
您可以通过使用grep代替不打开当前缓冲区中的所有文件来加快速度,find从而只打开已知匹配的文件。vim $(grep 'from' . -Rl)
迦勒(Caleb)

谢谢。需要cc周围的atreriks)吗?还是格式问题?
2011年

@balki是“格式”问题。修正了它
Gert

5

您可以使用一个小的Perl脚本来执行粗略的操作,该脚本指示-l -pe在作为参数(-i)传递的文件上逐行()进行替换:

perl -i -l -pe '
    if (/from/) {                            # is the source text present on this line?
        printf STDERR ("%s: %s [y/N]? ", $ARGV, $_);  # display a prompt
        $r=<STDIN>;                                   # read user response
        if ($r =~ /^[Yy]/) {                          # if user entered Y:
            s/from/to/g;                              # replace all occurences on this line
    }' /path/to/files

可能的改进是为提示的某些部分着色并支持诸如“替换当前文件中的所有事件”之类的事情。单独提示一行中的每个事件都比较困难。

第二部分,匹配文件。如果涉及的文件太多,并且您正在运行zsh,则可以递归匹配当前目录及其子目录中的所有文件:

perl -i -l -pe '…' **/*(.)

如果您的shell是bash≥4,则可以运行perl … **/*,但是这会产生虚假的错误消息,因为sed会尝试(失败)在目录上运行。如果只想在一组文件(例如C文件)中执行替换,则可以限制匹配项(适用于bash≥4或zsh):

perl -i -l -pe '…' **/*.[hc]

如果您需要更好地控制要替换的文件,或者您的外壳没有递归目录匹配结构**,或者您的文件太多并收到“命令行太长”错误,请使用find。例如,要在所有名为*.h*.c当前目录及其子目录中的文件中执行替换(在较旧的系统上,您可能需要使用\;而不是+在行尾使用(+格式较快,但并非在所有地方都可用)。

find . -type f -name '*.[hc]' -exec perl -i -l -pe '…' {} +

话虽如此,如果您需要交互,我会坚持使用交互式编辑器。Gert在Vim中展示了一种实现此目的的方法,尽管它要求打开要搜索的所有文件,如果文件太多,可能会出现问题。

在Emacs中,这是执行此操作的方法:

  1. 使用M-x find-name-dired(指定顶级目录)或M-x find-dired(指定任意find命令行)收集文件名。
  2. 在生成的干燥缓冲区中,按t标记所有文件,然后按Qdired-do-query-replace-regexp进行替换,并提示标记的文件。

1

sdiff(请参阅http://www.gnu.org/software/diffutils/manual/diffutils.html#Invoking-sdiff)在这里可能会派上用场。有了它,您可以进行交互式修补。因此,使用通过执行替换操作创建的临时文件来进行处理sed可能是一种解决方案:

# use file descriptor 3 to still allow use of stdin
while IFS= read -r -d '' file <&3; do

  # write the result of the replacement into a temporary file
  sed -r 's/something/something_else/g' -- "$file" > replacer_tmp

  if cmp -s -- "$file" replacer_tmp; then
    continue; # nothing was replaced
  fi

  echo "There is something to replace in '$file'! Starting interactive diff."
  echo

  sdiff -o "$file" -s -d -- "$file" replacer_tmp

  echo

done 3< <(find . -type f -print0)

(使用非POSIX进程替换的文件循环,并read -d受支持bash。)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.