Contents

awk“就地”输入文件

1. 概述

awk 命令和sed 命令都是强大的 Linux 命令行文本处理实用程序。我们知道sed命令有一个方便的*-i*选项来“就地”编辑输入文件。换句话说,它可以将更改保存回输入文件。

在本教程中,我们将通过示例探索如何使用awk命令进行“就地”编辑。

2. 输入文件示例

在我们查看awk命令之前,让我们创建一个名为scores.txt的输入文件:

$ cat scores.txt
Kai 77
Eric 97.5
Amanda 97
Jerry 60
Tom 80

如上面的输出所示,文本文件包含学生的姓名和他们的考试成绩。

我们想使用awk命令在文件末尾附加平均分数。

接下来,让我们看看如何就地编辑文件。

3. 使用GNU awk的就地扩展

GNU awk 是一种广泛使用的awk实现。自 4.1.0 版以来,GNU awk附带了就地扩展 来模拟 GNU sed-i(就地)选项。**

3.1.使用 -i 就地选项

使用gawk命令的就地扩展的语法非常简单:

gawk -i inplace '... awk code ...' input_files

上面的*-i选项用于包含awk源库。在这种情况下,我们希望包含就地扩展。因此,我们有论点:-i inplace*。

现在,让我们计算平均分并将其附加到我们的 scores.txt 中:

$ gawk -i inplace '{sum+=$2} 1; ENDFILE {printf "----\nAVG: %.2f\n",sum/NR}' scores.txt
$ cat scores.txt
Kai 77
Eric 97.5
Amanda 97
Jerry 60
## Tom 80
AVG: 82.30

伟大的!该文件已按照我们的预期进行了更新。

3.2. ENDENDFILE

上一节中的awk one-liner 非常简单。它将所有分数相加并在最后计算平均值。

但是,如果我们仔细阅读单行代码,我们会注意到我们使用了ENDFILE块而不是通常的END

我们可能会问,如果我们使用END块会不会一样?让我们做一个小测试来找出答案。

在下一个测试中,我们要在score.txt文件中添加一个标题*“文件的开头”和一个页脚“文件的结尾”*。

首先,让我们分别输出BEGINEND块中的页眉和页脚:

$ gawk -i inplace 'BEGIN { print "The beginning of the file" } 
                   { print }
                   END { print "The end of the file" }' scores.txt
The beginning of the file
The end of the file

如上面的输出所示,打印了页眉和页脚。但是,文件中的记录没有显示出来。不过不用担心——我们已经包含了就地扩展,因此无论如何都应该更新输入文件。让我们检查一下我们的输入文件scores.txt

$ cat scores.txt
Kai 77
Eric 97.5
Amanda 97
Jerry 60
Tom 80

哎呀!页眉和页脚不会添加到文件中!为什么会这样?

为了解释为什么会发生这种情况,我们需要了解就地扩展是如何工作的。

**就地扩展仅在处理文件时才有意义。**但是,BEGINEND块将在文件被处理之前和文件被完全处理之后运行。

因此,就地扩展不适用于BEGINEND中的更改。为了解决这个问题,我们应该使用 GNU awk的两种特定模式:BEGINFILEENDFILE

$ gawk -i inplace 'BEGINFILE { print "The beginning of the file" } 
                   { print }
                   ENDFILE { print "The end of the file" }' scores.txt

尽管我们执行上面的命令后没有输出,但页眉和页脚已经添加到score.txt文件中:

$ cat scores.txt
The beginning of the file
Kai 77
Eric 97.5
Amanda 97
Jerry 60
Tom 80
The end of the file

3.3. 就地编辑多个文件

就地扩展也适用于多个输入文件。让我们通过一个例子来解决它。

首先,让我们创建三个小文件:

$ head *.txt
==> java.txt <==
Java!
==> kotlin.txt <==
Kotlin!
==> linux.txt <==
Linux!

其次,我们将编写一个简短的awk单行代码来更改每个文件中的文本:

$ gawk -i inplace '$0 = "I Love " $0' java.txt kotlin.txt linux.txt

最后,让我们重新检查三个文件:

$ head *.txt
==> java.txt <==
I Love Java!
==> kotlin.txt <==
I Love Kotlin!
==> linux.txt <==
I Love Linux!

凉爽的!所有文件都原地更改。

在我们的awk代码中,我们不必单独处理每个文件或手动控制重定向。相反,我们只是在awk命令从文件中读取每一行时更改文本。就地扩展负责处理当前正在处理的 文件,并将任何更改自动写回文件。

4.使用临时文件

如果我们没有 GNU awk或者我们的gawk版本低于 4.1.0,我们就不能使用方便的 就地扩展。没关系——我们仍然可以使用临时文件将更改保存回输入文件:

awk '... code ...' input_file > tmp_file && mv tmp_file input_file

由于我们使用了 &&操作符,只有成功执行了 awk命令,才会执行后面的mv命令。

让我们通过一个临时文件将平均分数添加到我们的 scores.txt中:

$ awk '{sum+=$2} 1; END {printf "----\nAVG: %.2f\n",sum/NR}' scores.txt > score.tmp && mv score.tmp scores.txt
$ cat scores.txt
Kai 77
Eric 97.5
Amanda 97
Jerry 60
## Tom 80
AVG: 82.30

如示例所示,使用临时文件进行就地编辑也非常方便。但是,值得一提的是,如果我们想对多个输入文件应用就地编辑,这种方法将不起作用

5. 一些常见的陷阱

到目前为止,我们已经看到了两种使用awk命令进行就地编辑的不同方法。

现在,我们将首先展示一些不推荐的就地编辑方法。然后,我们将了解为什么它们不是可取的解决方案,以便我们可以避免在现实世界中使用它们。

5.1. echo命令和命令替换

命令替换 是 shell 脚本中获取命令输出的一种便捷技术。有时,我们可能会看到它与echo命令一起使用,将更改重定向回输入文件:

echo "$(awk  '... code ...' input_file)" > input_file

这种方法看起来很简单。如果awk命令运行良好,它将正常工作。但是,如果awk命令发生错误,它可能会破坏我们的输入数据

让我们使用这种方法将平均分数附加到 score.txt中:

$ echo "$(awk '{sum+ =$2} 1; END {printf "----\nAVG: %.2f\n",sum/NR}' scores.txt)" > scores.txt
awk: cmd. line:1: {sum+ =$2} 1; END {printf "----\nAVG: %.2f\n",sum/NR}
awk: cmd. line:1:       ^ syntax error

在上面的示例中,我们错误地在*sum+*和 *=*之间键入了一个空格。不出所料,  awk命令抱怨并打印出错误消息。

我们可能想要修复错误并重新运行该命令。但在我们这样做之前,让我们检查输入文件:

$ cat scores.txt
$

哎呀!我们输入文件中的数据不见了!

发生这种情况是因为**失败的awk命令没有向stdout打印任何内容。**此外,  echo命令打印空字符串并将其重定向到输入文件。

因此,我们不应该使用这种方式来处理输入文件。

5.2. 重定向陷阱

使用IO 重定向 可以巧妙地解决很多问题。有时,我们可能会看到尝试通过重定向将更改保存回输入文件的代码:

command < input_file > input_file

这种方法看起来既聪明又紧凑。但是,它不会起作用。此外,这很危险,因为如果我们运行这样的命令,input_file无论如何都会被截断。

这是因为,在执行命令之前,它的输入和输出可能会使用 shell 解释的特殊符号重定向。也就是说,**shell 在将控制权交给命令之前执行重定向。**因此,“ > ”重定向将清空input_file 。因此,当 命令被执行并想要从input_file中读取时,该文件已经是空的。

让我们使用cat命令做一个小测试:

$ cat < scores.txt > scores.txt 
$ wc -c scores.txt
0 scores.txt