Contents

递归搜索并替换文本文件

1. 概述

在文件中搜索模式并将其替换为新文本是我们在 Linux 命令行中工作时的典型操作。

有时,我们希望搜索并替换给定目录下的所有文本文件,包括其子目录下的文本文件。

在本教程中,我们将通过示例讨论如何在文本文件中递归搜索和替换。

2. 例子

在我们进入解决方案的细节之前,让我们创建一个名为myDir的示例目录,并在其下创建一些子目录和文件:

$ tree myDir
myDir
├── dir1
│   ├── dir1.1
│   │   ├── dir1.1.1
│   │   │   └── text1.1.1.txt
│   │   └── text1.1.txt
│   └── text1.txt
└── parent.txt

让我们看一下每个文本文件的内容:

$ head $(find myDir -name "*.txt")
==> myDir/dir1/dir1.1/dir1.1.1/text1.1.1.txt <==
text1.1.1: I like Linux.
==> myDir/dir1/dir1.1/text1.1.txt <==
text1.1: I like Linux.
==> myDir/dir1/text1.txt <==
text1: I like Linux.
==> myDir/parent.txt <==
Parent: I like Linux.

接下来,**我们要把myDir目录下的所有文本文件中的 “ Linux”替换为“ Linux operating system ” 。

3. 分而治之

我们并没有真正面临算法问题。但是,我们可以借用“分而治之 ”的思想来解决。

我们可以将问题分为两个子问题:

  1. 在单个文件中将“ Linux ”替换为“ Linux 操作系统”
  2. 查找给定目录myDir下的所有文本文件

我们将分别解决这两个子问题,然后将它们组合起来解决我们原来的问题。

接下来,让我们看看如何分而治之。

4. 在单个文本文件中搜索和替换

首先,让我们解决问题:在单个文件中将“ Linux ”替换为“ Linux operating system ”。

在 Linux 命令行中,我们可以通过多种方式进行文本替换并将结果保存回文件。

为了解决这个问题,我们将选择sed 命令来执行搜索和替换工作。

让我们看看一个简单的sed命令如何在parent.txt中将“ *Linux”*替换为“ Linux operating system ” :

$ sed -i 's/Linux/& operating system/g' parent.txt 
$ cat parent.txt
Parent: I like Linux operating system.

接下来,我们需要找到myDir目录下的所有文本文件,并将找到的文件传递给上面的**sed命令来解决问题。

5.递归搜索和替换

有很多方法可以找到给定目录下的所有文本文件,并对找到的文件调用sed命令。

在本节中,我们将介绍四种不同的方法。

5.1. 使用find命令和 *-exec command {} +*选项

find 命令 可以递归地查找给定目录下的文件。此外,它提供了一个选项“ *-exec command {} +”*来对所有找到的文件执行命令。

让我们组装我们的 sed命令和一个find命令来解决我们的问题:

$ find myDir -name '*.txt' -exec sed -i 's/Linux/& operating system/g' {} +

在上面的命令中,**“ {} ”是一个占位符,将由所有找到的文件填充。**因此,  sed命令将如下所示:

$ sed -i '..code..' foundFile1 foundFile2 foundFile3...foundFileN

这样,我们只调用 一次sed命令而不是n次。

现在,让我们检查目录 myDir下的所有文本文件是否都已更改:

$ head $(find myDir -name "*.txt")
==> myDir/parent.txt <==
Parent: I like Linux operating system.
==> myDir/dir1/text1.txt <==
text1: I like Linux operating system.
==> myDir/dir1/dir1.1/text1.1.txt <==
text1.1: I like Linux operating system.
==> myDir/dir1/dir1.1/dir1.1.1/text1.1.1.txt <==
text1.1.1: I like Linux operating system.

5.2. 使用find命令和xargs命令

在现实世界中,我们经常看到find命令和 /xargs 命令一起工作。

xargs命令可以读取find命令的输出,这是找到的文件列表,然后将它们构建到另一个命令的参数中。

让我们看看如何结合这两个命令来解决我们的问题:

$ find myDir -name '*.txt' | xargs sed -i 's/Linux/& operating system/g'

我们执行上面的命令后,myDir目录下的所有文本文件都会被递归修改。

5.3. 使用grep命令和xargs命令

顾名思义,find命令可以查找文件。加上-rl选项grep 命令也可以做到。

这里,-R 选项告诉grep递归搜索目录,而*-l选项是跳过匹配信息,告诉grep*只打印匹配文件的文件名。

让我们看看如何使用grep命令查找所有包含模式“ Linux ”的文件:

$ grep -Rl 'Linux' myDir
myDir/parent.txt
myDir/dir1/text1.txt
myDir/dir1/dir1.1/text1.1.txt
myDir/dir1/dir1.1/dir1.1.1/text1.1.1.txt

现在,为了解决我们的问题,我们只需将此结果通过管道传递给 xargs命令:

$ grep -Rl 'Linux' myDir | xargs sed -i 's/Linux/& operating system/g'

myDir目录下的所有文本文件中,所有出现的“ Linux ”再次被替换为“ Linux operating system ” 。

5.4. 使用 Zsh Glob (**)

Zsh 是一个功能强大且流行的 shell。zsh glob 支持双星号 (**) glob 来匹配当前目录及其所有子目录下的文件。

让我们看看如何 使用 Zsh递归列出myDir目录下的所有文本文件:

(zsh)$ ls -1 myDir/**/*.txt
myDir/dir1/dir1.1/dir1.1.1/text1.1.1.txt
myDir/dir1/dir1.1/text1.1.txt
myDir/dir1/text1.txt
myDir/parent.txt

因此,我们可以使用 Zsh 更简单地解决我们的问题:

(zsh)$ sed -i 's/Linux/& operating system/g' myDir/**/*.txt

我们看到只用sed命令就可以解决问题。

同样,我们可以使用相同的 glob 检查是否所有文本文件中出现的所有“ Linux ”都被替换:

(zsh)$ head myDir/**/*.txt
==> myDir/parent.txt <==
Parent: I like Linux operating system.
==> myDir/dir1/text1.txt <==
text1: I like Linux operating system.
==> myDir/dir1/dir1.1/text1.1.txt <==
text1.1: I like Linux operating system.
==> myDir/dir1/dir1.1/dir1.1.1/text1.1.1.txt <==
text1.1.1: I like Linux operating system.