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

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

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

答案1

为什么不用vim呢?

vim打开所有文件

vim $(find . -type f)

或者仅打开相关文件(按照 Caleb 的建议)

vim $(grep 'from' . -Rl)

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

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

你也可以用 来做到这一点sed,但我的 sed 知识有限。

答案2

您可以使用一个小的 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]

如果您需要更好地控制要替换的文件,或者您的 shell 没有递归目录匹配构造**,或者如果您有太多文件并收到“命令行太长”错误,请使用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. 在由此产生的迪雷德buffer,按t标记所有文件,然后Q( dired-do-query-replace-regexp)对标记的文件进行提示替换。

答案3

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

相关内容