Awk是一种非常通用的编程语言,用于处理文件。我们将只教您足够的理解这一页中的例子,再加上一个smidgen。
为什么学习AWK ?
和grep、sed一样,AWK是UNIX shell编程的另一个基础。
AWK有三种变体:
- AWK - the (very old) original from AT&T
- NAWK - A newer, improved version from AT&T
- GAWK - The Free Software foundation's version
最初,我不打算讨论NAWK,但是一些UNIX供应商已经用NAWK替换了AWK,而且两者之间存在一些不兼容之处。如果我不警告你们这些不同之处,那就太残忍了。所以当我讲到这些的时候,我会强调一下。重要的是要知道,所有AWK的功能都在NAWK和GAWK中。NAWK的大部分功能(如果不是全部的话)都在GAWK中。NAWK是Solaris的一部分。GAWK 则不是。然而,Internet上的许多站点都有免费的资源。如果你使用Linux,你有了GAWK。但一般来说,假设我说的是经典的AWK,除非另有说明。
为什么AWK如此重要?它是一个优秀的过滤器和报告编写器。许多UNIX程序生成行和列信息。AWK是处理这些行和列的优秀工具,并且比大多数传统编程语言更容易使用AWK。它可以被认为是一个伪C解释器,因为它理解与C相同的算术运算符。AWK还具有字符串操作函数,因此它可以搜索特定的字符串并修改输出。AWK也有关联数组,这是非常有用的,也是大多数计算语言所缺乏的特性。关联数组可以使复杂的问题变得简单。
我将试着介绍基本的部分或AWK,并提到扩展/变体。“new AWK”或“nawk”出现在太阳系统上,您可能会发现它在许多方面都优于旧AWK。特别是,它有更好的诊断,并且不会打印出原始AWK容易输出的臭名昭著的“在行附近进行救援”消息。相反,“nawk”打印出它不理解的行,并用箭头突出显示不好的部分。GAWK 也会如此,这真的很有帮助。如果你发现自己需要一个功能,在AWK是非常困难的或不可能的,我建议你使用NAWK,GAWK,或者使用“a2p”转换程序把你的AWK脚本转换成PERL。PERL是一种了不起的语言,我一直在使用它,但是我不打算在这些教程中介绍PERL。我已经表明了我的意图,我可以问心无愧地继续下去。
许多UNIX实用程序都有奇怪的名称。AWK就是其中之一。它不是awkward的缩写。事实上,它是一种优雅而简单的语言。作品“AWK”来源于该语言的三个开发者的首字母:A. Aho, B. W. Kernighan和P. Weinberger。
基本结构
AWK程序的基本组织形式如下:
pattern { action }
模式(pattern)指定何时执行操作。与大多数UNIX实用程序一样,AWK是面向行的。也就是说,模式指定了一个测试,该测试以每一行读取为输入来执行。如果条件为真,则采取操作。默认模式匹配每一行。这是空白或null模式。另外两个重要的模式由关键字“BEGIN”和“END”指定。如您所料,这两个词指定了在读取任何行之前和读取最后一行之后要执行的操作。AWK程序如下:
BEGIN { print "START" }
{ print }
END { print "STOP" }
在输入文件之前和之后分别添加一行。这不是很有用,但通过一个简单的改变,我们可以把它变成一个典型的AWK程序:
BEGIN { print "File\tOwner"}
{ print $8, "\t", $3}
END { print " - DONE -" }
在下一节中,我将改进这个脚本,但是我们将它称为“FileOwner”。但我们先不把它放到脚本或文件中。我将稍微介绍一下这部分。等一下,跟着我,这样你就能尝到AWK的味道了。
字符“\t”表示制表符,因此输出在偶数边界上对齐。“3”的含义类似于shell脚本。不是第8和第3个参数,而是输入行的第8和第3个字段。您可以将字段视为一列,您指定的操作对每一行列进行操作。
AWK和处理双引号内字符的shell之间有两个不同之处。AWK可以理解像“t”这样跟在“\”字符后面的特殊字符。Bourne和C UNIX shell则不理解。另外,与shell(和PERL)不同,AWK不计算字符串中的变量。为了解释,第二行不能这样写:
{print "$8\t$3" }
该示例将打印“3”。在引号内,$符号不是一个特殊字符。在外部,它对应一个字段。我说的第三和第八个字段是什么意思?考虑Solaris“/usr/bin/ls -l”命令,它有8列信息。System V版本(类似于Linux版本),“/usr/5bin/ls -l”有9列。第三列是所有者,第八列(或第九列)是文件名称。这个AWK程序可以用来处理“ls -l”命令的输出,打印出每个文件的文件名,然后是所有者。我来教你怎么做。
更新:在linux系统上,将“9”。
关于使用符号表示接下来的单词是变量的名称。Awk是不同的。”有不同的含义。因此,下面的代码将打印两个“字段”以进行标准输出。打印的第一个字段是数字“5”,第二个字段是输入行上的第五个字段(或列)。
BEGIN { x=5 }
{ print x, $x}
执行AWK脚本
让我们开始编写第一个AWK脚本。有几种方法可以做到这一点。
假设第一个脚本名为“FileOwner”,那么调用将是
ls -l | FileOwner
如果当前目录中只有两个文件,则可能生成以下内容:
File Owner
a.file barnett
another.file barnett
- DONE -
这个脚本有两个问题。这两个问题都很容易解决,但是我将在介绍基本知识之前暂时不讨论这个问题。
脚本本身可以用多种方式编写。我已经展示了C shell (csh/tcsh)和Bourne/Bash/POSIX shell脚本。C shell版本应该是这样的:
#!/bin/csh -f
# Linux users have to change $8 to $9
awk '\
BEGIN { print "File\tOwner" } \
{ print $8, "\t", $3} \
END { print " - DONE -" } \
'
当然,一旦您创建了这个脚本,您需要使这个脚本可执行:
chmod +x awk_example.1.csh
test:
ls -l | sh FileOwner
正如您在上面的脚本中看到的,如果不是脚本的最后一行,AWK脚本的每一行都必须有一个反斜杠。这是必要的,因为默认情况下,C shell不允许字符串超过一行。我有一长串关于使用C shell的抱怨。
Bourne shell(和大多数shell一样)允许引用字符串跨越几行:
!/bin/sh
# Linux users have to change $8 to $9
awk '
BEGIN { print "File\tOwner" }
{ print $8, "\t", $3}
END { print " - DONE -" }
再次说明,一旦它被创建,它必须是可执行的:
chmod +x awk_example1.sh
顺便说一下,我在教程中给出了示例脚本,并在文件名上使用扩展名来指示脚本的类型。当然,您可以通过输入以下代码把脚本“install”到您的home“bin”目录中:
cp awk_example1.sh $HOME/bin/awk_example1
chmod +x $HOME/bin/awk_example1
第三种类型的AWK脚本是“原生的”AWK脚本,其中不使用shell。您可以在文件中编写命令并执行:
awk -f filename
因为AWK也是一个解释器,就像shell一样,你可以节省一个步骤,在文件的开头添加一行代码,使文件可执行:
#!/bin/awk -f
BEGIN { print "File\tOwner" }
{ print $8, "\t", $3}
END { print " - DONE -" }
然后执行“chmod +x”并将此文件作为一个新的UNIX命令进行ise。
注意“#!/bin/awk”后面的“-f”选项,它也用于第三种格式,在这种格式中,您使用AWK直接执行文件,即“awk - f文件名”。“-f”选项指定包含指令的AWK文件。如您所见,AWK将以“#”开头的行视为注释,就像shell一样。准确地说,从“#”到行尾的任何内容都是注释(除非它在AWK字符串中)。但是,我总是在行首用“#”来注释AWK脚本,原因我将在后面讨论。
应该使用哪种格式?如果可能的话,我更喜欢最后一种格式。它更短更简单。调试问题也更容易。如果您需要使用shell,并且希望避免使用太多的文件,您可以像我们在第一个和第二个示例中所做的那样组合它们。
用哪种shell搭配AWK?
原始AWK的格式不是自由格式的。你不能把换行符放在任何地方。他们必须去特定的地方。确切地说,在最初的AWK中,您可以在大括号后面和命令末尾插入一个新行字符,但不能在其他地方插入。如果你想在任何地方把一条长线分成两行,你必须使用反斜杠(awk_example2.awk):
#!/bin/awk -f
BEGIN { print "File\tOwner" }
{ print $8, "\t", \
$3}
END { print " - DONE -" }
Bourne shell版本是( awk_example2.sh):
#!/bin/sh
awk '
BEGIN { print "File\tOwner" }
{ print $8, "\t", \
$3}
END { print "done"}
'
C shell版本是( awk_example2.csh):
#!/bin/csh -f
awk '
BEGIN { print "File\tOwner" }\
{ print $8, "\t", \\
$3}\
END { print "done"}\
'
正如您所看到的,这演示了C shell在封装AWK脚本时是多么笨拙。不仅每一行都需要反斜杠,有些行需要两个斜杠,然后使用原来的AWK。更新的AWK更灵活,可以添加新行。
很多人,像我一样,会警告你注意C shell。有些问题很微妙,你可能永远也看不到。尝试在C shell脚本中包含AWK或sed脚本,而反斜杠会让您发疯。这就是多年前说服我学习Bourne shell的原因,当时我刚开始学习(在Korn shell或Bash shell可用之前)。即使您坚持使用C shell,至少也应该学习足够多的Borne/POSIX shell来设置变量,由于某种奇怪的巧合,这将是下一节的主题。
动态变量
因为您可以通过把"#!/bin/awk -f"写在第一行中使脚本可执行,所以不需要在shell脚本中包含AWK脚本,除非您希望消除对额外文件的需要,或者希望将变量传递到AWK脚本的内部。由于这是一个常见的问题,现在正是解释该技术的好时机。我将通过显示一个只打印一列的简单AWK程序来实现这一点。注意:第一个版本会有一个bug。 列的数目将由第一个参数指定。程序的第一个版本的名字我们称之为“列”,看起来是这样的:
#!/bin/sh
#NOTE - this script does not work!
column=$1
awk '{print $column}'
建议的用法是:
ls -l | Column 3
未完待续...