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. END与ENDFILE
上一节中的awk one-liner 非常简单。它将所有分数相加并在最后计算平均值。
但是,如果我们仔细阅读单行代码,我们会注意到我们使用了ENDFILE块而不是通常的END块 。
我们可能会问,如果我们使用END块会不会一样?让我们做一个小测试来找出答案。
在下一个测试中,我们要在score.txt文件中添加一个标题*“文件的开头”和一个页脚“文件的结尾”*。
首先,让我们分别输出BEGIN和END块中的页眉和页脚:
$ 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
哎呀!页眉和页脚不会添加到文件中!为什么会这样?
为了解释为什么会发生这种情况,我们需要了解就地扩展是如何工作的。
**就地扩展仅在处理文件时才有意义。**但是,BEGIN和END块将在文件被处理之前和文件被完全处理之后运行。
因此,就地扩展不适用于BEGIN和END块中的更改。为了解决这个问题,我们应该使用 GNU awk的两种特定模式:BEGINFILE和ENDFILE:
$ 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