. 句点表示任何单个字符
[] 方括号包括一个字符序列
- 短横线在字符之间使用以创建一个序列(在 [] 内)
^ 脱字符用于对序列(在 [] 内)取反
* 星号搜索某个搜索项的零个、一个或多个实例
? 问号搜索某个搜索项的零个或一个实例
+ 加号搜索某个搜索项的一个或多个实例
$ 美元符号搜索行尾
^ 脱字符搜索行首
\ 特殊字符前的反斜杠使该字符成为普通字符(请参见下一部分。)
使用 sed 来编辑文件
sed 是
流编辑器 (stream editor) 的简写。文本编辑器的传统、现代定义是可用于创建和编辑文本文件的交互式应用程序。sed 也是一个文本编辑器,但它是一个命令行实用程序而不是交互式实用程序,从而使之成为一个极其强大的批处理编辑工具。sed 通常在 UNIX Shell 脚本中用于过滤较大的文本文件集。在本教程的第一部分中,您使用了一个讨论 golf 的小型测试文件。为了演示 sed 编辑器的高级功能,您将使用一个很小的代码片段,开发人员可能希望在批处理过程中更改该代码片段。
请将以下文本复制并粘贴到一个名为 sed.txt 的文件中:
system "echo 'project:$project' >> logfile";
system "echo 'version:$version' >> logfile";
system "echo 'optionalid:$optionalid' >> logfile";
system "echo 'nodes:$nodes' >> logfile";
system "echo 'threads:$threads' >> logfile";
|
正斜杠
前面解释过的用于 grep 的所有特殊字符在 sed 中也有效。然而,若要使用 sed,您必须了解一些附加语法。sed 中的基本表达式由四个部分组成,各个部分之间用正斜杠 (/) 分隔。以下是用于基本 sed 命令的常见语法:
sed s/REGULAREXPRESSION/REPLACEMENTSTRING/flags INPUT_FILE
|
s——搜索和替换
s 指示您希望执行
搜索和替换。正斜杠用于绑定 sed 中的正则表达式。例如,如果您只希望将词条 logfile 替换为 logfile.txt,则可以运行以下命令:
sed s/logfile/logfile.txt/ sed.txt
|
输出应该类似如下:
system "echo 'project:$project' >> logfile.txt";
system "echo 'version:$version' >> logfile.txt";
system "echo 'optionalid:$optionalid' >> logfile.txt";
system "echo 'nodes:$nodes' >> logfile.txt";
system "echo 'threads:$threads' >> logfile.txt";
|
在此情况下要注意的一个要点在于,sed 不会实际更改 sed.txt 的内容。相反,它将输出发送到标准输出设备。对于这些示例,您将把输出发送到标准输出设备,以便能够立即看到操作结果。
为便于将来参考,可以捕获输出或将其发送到某个新文件。例如,若要将输出发送到 sed_new.txt,可以运行以下命令:
sed s/logfile/logfile.txt/ sed.txt > sed_new.txt
|
反斜杠
在学习使用斜杠的同时,还有另一个非常重要的特殊字符需要学习。反斜杠 (\) 称为转义字符,因为它对正则表达式解释中的下一个字符进行
转义。更简单的是,将一个反斜杠放在特殊字符前,将使该字符成为普通项而不是命令项。这非常重要,因为许多文件(尤其是在编写代码的时候)广泛利用了与用于执行正则表达式的字符相同的字符。在您的 sed.txt 文件中,您会注意到美元符号的使用。如果您希望替换 $project 而不替换 project,则需要在搜索和替换中使用转义字符:
sed s/\$project/\$project_name/ sed.txt
|
您可以在输出中看到 $project 被更改了,但是 project 没有被更改。
system "echo 'project:$project_name' >> logfile";
system "echo 'version:$version' >> logfile";
system "echo 'optionalid:$optionalid' >> logfile";
system "echo 'nodes:$nodes' >> logfile";
system "echo 'threads:$threads' >> logfile";
|
更改某个项的多个实例
这引入了 sed 中的另一个重要功能。如果您希望同时更改 project 的两个实例,该怎么办呢?通过到目前为止已学到的知识,合理的回答是只需使用 project 作为正则表达式,但是此回答并不是非常正确。下面将继续并进行尝试,以便能够演示和解释该过程:
sed s/project/project_name/ sed.txt
|
在输出中可以看到,project 的第一个实例被更改为 project_name:
system "echo 'project_name:$project' >> logfile";
system "echo 'version:$version' >> logfile";
system "echo 'optionalid:$optionalid' >> logfile";
system "echo 'nodes:$nodes' >> logfile";
system "echo 'threads:$threads' >> logfile";
|
然而,第二个实例未更改,尽管它肯定匹配您的正则表达式。您从第一个示例中知道,sed 似乎更改其输入中的每个匹配字符串,而不是仅更改第一个匹配字符串,因为它更改 logfile 的每个实例。
区别在于,logfile 的每个实例在单独的行上,而同一行上却有两个 project 实例。这为什么非常重要?因为 sed 被实现为一个行编辑器。它一次将一个单独的行放到内存中,并将其作为单个单元来操作。在运行 sed 时务必记住这点,因为所有命令行选项都是按这个设计原则来设计的(从而使大多数 sed 实现不会受到与系统内存有关的文件大小限制)。缺省情况下,每一行都视为 sed 命令的一次新的执行。尽管在第一个示例中似乎不是这样,但是其中 sed 命令仅替换匹配字符串的第一个实例。然而,您可以简单地使用一个 g 标志来改变此行为。
g 标志
执行同样的 sed 命令,但这次在结尾附加一个 g:
sed s/project/project_name/g sed.txt
|
这次,第一行上的两个 project 实例都被更改为 project_name:
system "echo 'project_name:$project_name' >> logfile";
system "echo 'version:$version' >> logfile";
system "echo 'optionalid:$optionalid' >> logfile";
system "echo 'nodes:$nodes' >> logfile";
system "echo 'threads:$threads' >> logfile";
|
您可能记得,g 是
global 的简写。
运行初步的搜索
sed 的另一个强大功能是在搜索和替换操作前运行初步搜索,以确定当前是否在您希望执行命令的行上。这差不多类似于在 sed 中执行 grep。在您的例子中,您可能希望更改 node 变量的日志文件,而不是将它与所有其他输出分组在一起。为此,您需要将字符串 logfile 更改为 logfile_nodes,但是仅在属于节点的行上执行更改。以下命令可以确切完成此任务:
sed /nodes/s/logfile/logfile_nodes/ sed.txt
|
下面是其输出:
system "echo 'project:$project' >> logfile";
system "echo 'version:$version' >> logfile";
system "echo 'optionalid:$optionalid' >> logfile";
system "echo 'nodes:$nodes' >> logfile_nodes";
system "echo 'threads:$threads' >> logfile";
|
更改以冒号结尾的每个字符串
现在,尝试使用一些您在使用 grep 时学习到的正则表达式知识,不过这次是在 sed 命令中使用。通过在 sed 中使用以下正则表达式,您可以更改以冒号结尾的每个字符串:
sed s/[a-z]*:/value:/g sed.txt
|
输出应该类似如下:
system "echo 'value:$project' >> logfile";
system "echo 'value:$version' >> logfile";
system "echo 'value:$optionalid' >> logfile";
system "echo 'value:$nodes' >> logfile";
system "echo 'value:$threads' >> logfile";
|
这相当酷,但不是非常合理。它不是非常合理的原因在于,您的所有变量前都有单词 value,没有办法对各个变量进行区分。然而,通过使用 sed 的另一个功能,您可以使这转变为一个实际的示例。
“和”号
“和”号 (&) 表示与您的正则表达式匹配的字符串。换句话说,如果 [a-z]*: 在某个特定行上被证明为 project:,则“和”号将包含该值。这会非常有用。看一下以下这个示例:
sed s/[a-z]*:/new_\&/g sed.txt
|
这次,您修改了每个匹配字符串,但是保留了与每个变量关联的标识符:
system "echo 'new_project:$project' >> logfile";
system "echo 'new_version:$version' >> logfile";
system "echo 'new_optionalid:$optionalid' >> logfile";
system "echo 'new_nodes:$nodes' >> logfile";
system "echo 'new_threads:$threads' >> logfile";
|
执行多个命令序列
使用 sed,您还可以一次做多件事情。若要一次执行多个命令序列,您必须在每个表达式前使用 -e 标志。缺省情况下,sed 将第一个参数解释为一个表达式,但是在运行多个命令时,您需要作出更明确的指定,因此要使用 -e 标志。例如:
sed -e s/[a-z]*:/value:/g -e s/logfile/name/g sed.txt
|
可以在此例中看到,sed 在适当位置插入了 value: 和 name:
system "echo 'value:$project' >> name";
system "echo 'value:$version' >> name";
system "echo 'value:$optionalid' >> name";
system "echo 'value:$nodes' >> name";
system "echo 'value:$threads' >> name";
|
正如您开始看到的,在大规模批处理过程中,sed 可以是个非常强大的文件编辑工具。在前一示例中,您是在对单个文件进行操作,就像在使用 grep 时所做的那样。不要忘了,这些实用程序的部分强大功能在于跨多个文件运行它们,这可以使用通配符或文件列表替换单个文件来实现,您已在本教程中这样使用过了。
在命令行上使用 awk
本教程首先对正则表达式进行了基本的解释,然后介绍了 grep 和 sed。grep 是一个强大的搜索实用程序,而 sed 则是一个更加强大的搜索和替换实用程序。awk 则更进一步,它在全功能的命令行编程语言中使用正则表达式。正如 sed 一样,当在命令行上使用 awk 时,它接受基于行的输入。awk 一次解释一行输入,但是与 sed 不同,它将该行上的每个部分作为变量来处理,这些变量可用作内联代码的输入和输出。
应该指出的是,AWK(大写)是一个可用于编写脚本(而不只是在命令行上使用)的全功能编程语言,但本教程集中于 awk,后者是动态解释 AWK 命令的命令行实用程序。
顺便提一下,任何人阅读到这里都会考虑如何实际运用所学到的知识,我刚才就使用 grep 在某些旧代码中搜索理想的 awk 示例:
grep awk */*.pl
大多数系统管理员或程序员每天都会看到这些工具的应用。下面是我的输出中的一些行:
Edaemon/m_checkcurrentdisk.pl:$freespace = `awk '(NR==1) {print \$4 / 1024 / 1024}' grep.tmp`;
|-------- XML error: The previous line is longer than the max of 90 characters ---------|
Edaemon/m_getdatetime.pl:$month = `awk '(NR==1) {print \$2}' datetime.txt`;
Odaemon/odaemon.beowulf.dvd.pl:$filesize = `awk '(NR==1) {print \$1}' temp.txt`;
这些是非常好的示例,因为它们说明了非常基础的 awk 应用。对于您的第一次尝试,甚至可以使它更简单。对于您的 awk 测试,请在一个空目录中创建以下文件(每个文件的内容无关紧要,并且它们可以是空的)。
Screenshot_1.jpg
Screenshot_2.jpg
Screenshot_3.jpg
awk.txt
regular.txt
sed.txt
使用 ls 的输出作为 awk 的输入
缺省情况下,awk 读取输入文件中的每一行,并将内容分离为由空格确定的变量。在非常简单的示例中,您可以使用 ls 的输出作为 awk 的输入并打印结果。此示例结合使用管道字符 (|) 和 ls 来将输出发送到 awk:
ls | awk ' { print $1 } '
awk 随后打印每行上的第一项,在此例中为每行上的唯一项:
Screenshot_1.jpg
Screenshot_2.jpg
Screenshot_3.jpg
awk.txt
regular.txt
sed.txt
使用 ls -l 来为 awk 生成多列输入
这确实是非常基本的功能。对于下一个示例,请使用 ls -l 来为 awk 生成多列输入:
ls -l
不同系统的 ls 实现稍有差别,下面是一些示例输出:
total 432
-rw-rw-rw- 1 guest guest 169074 Oct 15 14:51 Screenshot_1.jpg
-rw-rw-rw- 1 guest guest 23956 Oct 15 20:56 Screenshot_2.jpg
-rw-rw-rw- 1 guest guest 12066 Oct 15 20:57 Screenshot_3.jpg
-rw-r--r-- 1 tuser tuser 227 Oct 15 20:16 awk.txt
-rw-r--r-- 1 tuser tuser 233 Oct 15 19:35 regular.txt
-rw-r--r-- 1 tuser tuser 227 Oct 15 23:16 sed.txt
请注意,文件所有者是每行上的第三个项,文件名是每行上的第九个项(缺省情况下,awk 中的项之间用空格分隔)。通过打印每行上的第三和第九个变量,您可以使用 awk 来从该列表提取文件所有者和文件名。下面是完成这项工作的命令:
ls -l | awk ' { print $3 " " $9 } '
您将注意到,awk 中的 print 命令有两个引号,而且引号中有一个空格。这只是为了在输出的文件所有者和文件名之间打印一个空格:
guest Screenshot_1.jpg
guest Screenshot_2.jpg
guest Screenshot_3.jpg
tuser awk.txt
tuser regular.txt
tuser sed.txt
您可以在 awk 打印语句中的变量之间的引号中放置任何文本。
使用正则表达式来指定行
现在您已经学习了如何使用 awk 的基础知识,但本教程不是关于正则表达式的吗?awk 中大量使用了正则表达式。最常见的示例是在 awk 命令前附加一个正则表达式,用于指定您想要操作的行。与 sed 一样,awk 中的正则表达式位于两个正斜杠之间。例如,如果您只希望操作 tuser 所拥有的文件,则可以使用以下命令:
ls -l | awk ' /tuser/ { print $3 " " $9 } '
该命令产生以下输出:
tuser awk.txt
tuser regular.txt
tuser sed.txt
更改文件扩展名
在另一个示例中,您可能希望更改每个文本文件的文件扩展名而不更改图像文件。为此,您将需要使用句点而不是空格来分隔输入变量,然后使用正则表达式来指示您仅希望搜索文本文件。若要基于句点来分隔变量,可以使用 -F 标志,后跟后跟您希望使用的字符(用引号引起来)。尝试此示例,通过管道将 awk 输出发送到某个 Shell(此 Shell 将执行 awk 生成的命令):
ls | awk -F"." ' /txt/ { print "mv " $1 "." $2 " " $1 ".doc" } ' | bash
后续的 ls -l 将显示新的文件名:
-rw-rw-rw- 1 guest guest 169074 Oct 15 14:51 Screenshot_1.jpg
-rw-rw-rw- 1 guest guest 23956 Oct 15 20:56 Screenshot_2.jpg
-rw-rw-rw- 1 guest guest 12066 Oct 15 20:57 Screenshot_3.jpg
-rw-r--r-- 1 tuser tuser 227 Oct 15 20:16 awk.doc
-rw-r--r-- 1 tuser tuser 233 Oct 15 19:35 regular.doc
-rw-r--r-- 1 tuser tuser 227 Oct 15 23:16 sed.doc
记住,这些只是 awk 的入门基础知识,但是 AWK 是一种全功能的编程语言,所具有的功能远远超出了本教程所介绍的内容。请查看一下 awk man 页。如果您希望学习更多的知识,花钱买一本优秀的图书是明智的。