$ awk '{ print }' /etc/passwd
调用 awk 时,我们指定 /etc/passwd 作为输入文件。执行 awk 时,它依次对 /etc/passwd 中的每一行执行 print 命令,所有输出都发送到 stdout。所以这条命令等价于cat /etc/passwd。
在 awk 中, $0 变量表示整个当前行,所以 print 和 print $0 的作用完全一样。所以
$ awk '{ print $0 }' /etc/passwd <=> $ awk '{ print }' /etc/passwd
如果您愿意,可以创建一个 awk 程序,让它输出与输入数据完全无关的数据。如下:
awk '{ print "hiya" }' /etc/passwd
该语句执行结果是打印出和/etc/passwd中文本行数一样的hiya到屏幕。
多个字段
awk 非常善于处理分成多个逻辑字段的文本,而且让您可以毫不费力地引用 awk 脚本中每个独立的字段。如下例子:
$ awk -F ":" '{ print $1 }' /etc/passwd
awk 处理print $1 命令时,它会打印出在输入文件中每一行中出现的第一个字段。注意-F参数,在调用 awk 时,使用 -F 选项来指定 ":" 作为字段分隔符。默认下,awk使用whitespace作为分隔符,-F可以显式地指明分隔符。
打印多个字段:
$ awk -F":" '{ print $1 $3 }' /etc/passwd
会发现问题:字段1、3之间没有空格,数据连在了一起。如下:
halt7
operator11
root0
shutdown6
sync5
解决方法:
$ awk -F":" '{ print $1 " " $3 }' /etc/passwd
上面语句,会在$1 和 $3之间添加一个whitespace,换个角度来说,上面语句把$1、" "和$3连接在一起了。再试试下面这个语句:
$ awk -F":" '{ print "username: " $1 "\t\tuid:" $3" }' /etc/passwd
外部脚本
将脚本作为命令行自变量传递给 awk 对于小的单行程序来说是非常简单的,而对于多行程序,它就比较复杂。您肯定想要在外部文件中撰写脚本。
换句话说,就是把执行动作放到awk的外部脚本中,然后通过-f参数,调用该脚本。
$ cat myscript.awk
BEGIN {
FS=":"
}
{ print $1 }
$ awk -f myscript.awk myfile.in
这两个方法的差别在于如何设置字段分隔符。在这个脚本中,字段分隔符在代码自身中指定(通过设置 FS 变量),而在前一个示例中,通过在命令行上向 awk 传递 -F":" 选项来设置 FS。通常,最好在脚本自身中设置字段分隔符,只是因为这表示您可以少输入一个命令行自变量。
BEGIN 和 END 块
通常,对于每个输入行,awk 都会执行每个脚本代码块一次。然而,在许多编程情况中,可能需要在 awk 开始处理输入文件中的文本之前执行初始化代码。对于这种情况,awk 允许您定义一个 BEGIN 块。因为 awk 在开始处理输入文件之前会执行 BEGIN 块,因此它是初始化 FS(字段分隔符)变量、打印页眉或初始化其它在程序中以后会引用的全局变量的极佳位置。
awk 还提供了另一个特殊块,叫作 END 块。awk 在处理了输入文件中的所有行之后执行这个块。通常,END 块用于执行最终计算或打印应该出现在输出流结尾的摘要信息。
规则表达式和块(正则表达式)
awk 允许使用规则表达式,根据规则表达式是否匹配当前行来选择执行独立代码块。以下示例脚本只输出包含字符序列nobody的那些行:
ATCA1-slot12:/ # awk -F ":" '/nobody/ { print }' /etc/passwd
nobody:x:65534:65533:nobody:/var/lib/nobody:/bin/bash
当然,可以使用更复杂的规则表达式。如下:
表达式和块
判断语句不仅仅只有匹配成功或者失败,还有很多逻辑运算表达式(布尔表达式)。如下:
$1 == "fred" { print $3 }
我们可以将任意一种布尔表达式放在一个代码块之前,以控制何时执行某特定块。
awk 提供了完整的比较运算符集合,包括 "=="、"<"、">"、"<="、">=" 和 "!="。另外,awk 还提供了 "~" 和 "!~" 运算符,它们分别显式地表达“匹配”和“不匹配”。
在思考如何运用这些运算符来勾勒我们的表达式时,我们要保持冷静。要知道我们处理的对象是什么,比如:
做正则表达式匹配的时候,我们匹配的文本如果显式的表示就是$0,如果单单给出正则表达式,那默认就是把输入文本作为匹配的数据对象。即
/match/ {action} <=> $0 ~ /match/ {action}
例子:
ATCA1-slot12:/ # awk -F ":" '/nobody/ { print }' /etc/passwd
nobody:x:65534:65533:nobody:/var/lib/nobody:/bin/bash
ATCA1-slot12:/ # awk -F ":" '$0 ~ /nobody/ { print }' /etc/passwd
nobody:x:65534:65533:nobody:/var/lib/nobody:/bin/bash
条件语句
awk 还提供了非常好的类似于 C 语言的if 语句
example 1:
{
if ( $0 ~ /match/ ) {
print $0
}
}
example 2:
if ($1 == "nobody"){
print "1"
}
else{
print "0"
}
example 3:
if ($1 == "nobody"){
print "1"
}
else if ($1 == "root"){
print "2"
}
else{
print "0"
}
否定判词 ! 的用法:
! /match/ { print $1 $3 $4 } => /match/ { print $1 $3 $4 } 表示模式串匹配当前行成功,执行print $1 $3 $4 => 加上!,表示模式串匹配当前行不功,执行print $1 $3 $4。
可以看出来否定判词!的作用就是用来修饰其他的逻辑判词,获得相反的逻辑判词。
有趣的是,上面的语句等价于:
{
if ( $0 !~ /match/ ) {
print $1 $3 $4
}
}
可以看出如果使用if等语句实现逻辑判词,需要加上块分隔符{};如果使用隐式语法实现简单逻辑(操作对象隐去,使用默认的操作对象),不要加{}。
awk 还允许使用布尔运算符 "||"(逻辑与)和 "&&"(逻辑或),以便创建更复杂的布尔表达式:
( $1 == "a" ) && ( $2 == "b" ) { print } #仅当当前行中第一个字段的值等于a,第二个字段的值等于b,执行{print}
数值变量!
Oh, my god! 没想到awk还支持数值运算,很惊艳,华丽丽地超过我的期望了。曾经以为它只是一个用来处理字符串文本的小不点儿,现在知道awk 还允许我们执行整数和浮点运算。通过使用数学表达式,可以很方便地编写计算文件中空白行数量的脚本。
BEGIN {
x=0 # init x = 0
FS=":"
}
$1 == "nobody" {x=x+1}
END { print "I found " x " blank lines. :)" }
字符串化变量
awk 的优点之一就是“简单和字符串化”。我认为 awk 变量“字符串化”是因为所有 awk 变量在内部都是按字符串形式存储的。同时,awk 变量是“简单的”,因为可以对它执行数学操作,且只要变量包含有效数字字符串,awk 会自动处理字符串到数字的转换步骤。
example:
x="1.01"
# We just set x to contain the *string* "1.01"
x=x+1
# We just added one to a *string*
print x
# Incidentally, these are comments :)
结果是2.01
如果数学表达式中的某个特定变量不包含有效数字,awk 在对数学表达式求值时会将该变量当作数字零处理。有效数字包括整数、浮点数,还有字符型的数字,如上例中的"1.01"。
众多运算符
完整的数学运算符集合:
加、减、乘、除 (a+b、a-b、a*b、a/b)
指数运算符"^"、模(余数)运算符 "%" (a^b、a%b)
易于使用的赋值操作符 (a = b)
前后加减 ( i++、--foo )
加/减/乘/除赋值运算符 ( a+=3 、 b*=2 、 c/=2.2 、 d-=6.2 )
模/指数赋值运算符 ( a^=2 、 b%=4 )
AWK特殊变量初步
awk 有它自己的特殊变量集合。其中一些允许调整 awk 的运行方式,而其它变量可以被读取以收集关于输入的有用信息。
在BEGIN代码块中,我们已经接触过这些特殊变量中的一个FS,这个变量让您可以设置 awk 要查找的字段之间的字符序列。FS 值并没有被限制为单一字符;可以通过指定任意长度的字符模式,将它设置成规则表达式。比如:
如果正在处理由一个或多个tab 分隔的字段,您可能希望按以下方式设置 FS:
FS="\t+" #"+" 规则表达式字符,它表示“一个或多个前一字符”
字段数量
NF 变量, 也叫做“字段数量”变量。
记录号 (NR) 是当前记录的编号(awk 将第一个记录算作记录号 1)。
记录、循环和数组
多行记录
如果每条记录都是单行记录,直接设置FS就可以开始按行(line by line)扫描。但如果记录是多行时,如何设置awk的特殊变量?
引入一个记录分隔符变量RS, RS变量告诉 awk 当前记录什么时候结束,新记录什么时候开始。
example:
对于address.txt,从开始,每三行作为一条记录。如下:
Jimmy the Weasel
100 Pleasant Drive
San Francisco, CA 12345
Big Tony
200 Incognito Ave.
Suburbia, WA 67890
BEGIN {
FS="\n"
RS=""
}
在上面这段代码中,将 FS 设置成 "\n" 告诉 awk 每个字段都占据一行。通过将 RS 设置成 "",还会告诉 awk 每个地址记录都
由空白行分隔。一旦 awk 知道是如何格式化输入的,它就可以为我们执行所有分析工作,脚本的其余部分很简单。让我们研
究一个完整的脚本,它将分析这个地址列表,并将每个记录打印在一行上,用逗号分隔每个字段。如下:
#BEGIN语句体必须要跟在BEGIN关键字后面,END同理.
BEGIN{
FS="\n"
RS=""
}
{
print $1 ", " $2 ", " $3
}
等价于下面的代码:
BEGIN{
FS="\n"
RS=""
OFS=", " # 它将作为分隔符,被插入所有print的变量中间
}
{
print $1 , $2 , $3
}
总结一下学到的特殊变量:
名字 全称 意思 作用
NF Num of Field 当前行记录的字段个数 在主代码块中,利用NF变量,可以循环遍历所有字段,然后处理。
NR Num of Record 文本记录总数 NR变量是根据awk做行扫描的当前行的行号来确定的,当前扫描到第5行记录,NR的值就为5。
FS Field Split 字段分隔 字段分隔变量定义了扫描文本中每条记录中各个字段是以什么作分隔的,包括普通字符或者grep。
RS Record Split 记录分隔 记录分隔变量定义了扫描文本中每条记录是以什么作分隔的,语法同FS。
OFS Output Field Split 输出字段分隔 输出字段分隔变量定义了Output一条记录时,各个字段之间用什么作分隔(或者说连接),语法同FS。
ORS Output Record Split 输出记录分隔 输出记录分隔变量定义了Output的相邻两条记录之间用什么作分隔(或者说连接),语法同FS。
循环结构
基本等同于C语言语法,语法结构如下:
while 循环:
while (condition)
{
statement
}
do
{
statement
} while (condition)
for 循环:
for (initial assgnment; condition; increment)
{
statement
}
跳转语句 break、continue:
while (1)
{
if (condition)
{
print "break out the loop"
break
}
}
for (;;)
{
if (condition)
{
print "continue the loop"
continue
}
}
数组
在 awk 中,数组下标通常从 1 开始,而不是 0。
定义一个数组的方式很简单,不需要像C那样做声明,并在声明的时候决定下数组的大小。(脚本语言的优势啊!也是我很喜欢的一个脚本语言特性),直接按照数组名[下标]=value就可以。如下:
array[1] = "yc"
array[2] = 123
就定义了一个数组array,该数组有两个元素,分别是"yc"和123。
数组迭代遍历
利用循环控制结构,注意语法
for ( x in array ) {
print array[x]
}
这里的数组本质是一个容器,利用for循环来迭代遍历,在数组下标之间轮转时,它不会依照任何特定的顺序。
迭代数组内容就像一盒巧克力 -- 您永远不知道将会得到什么。
数组下标字符串化
说白了,就是关联数组的一种实现方式。
相比于C中的数组,awk中的数组描述了一种更宽泛的映射关系。
C中只可以把
非负整数-> value
而awk中可以把
一切字符串(包括非负整数) -> value
谈到数组时,awk 给予我们许多灵活性。可以使用字符串下标,而且不需要连续的数字序列下标(例如,可以定义 myarr[1]
和 myarr[1000] ,但不定义其它所有元素)。这就是容器数据结构的本质。
总结一下数组的操作:
增/改: array[x] = value
删: delete array[x]
查: if ( x in array)
格式化输出
基本输出 -------------------------> 格式化输出
print printf() 和 sprintf()
这两个函数等同于相应的 C 语言标准函数。其中,
printf() 将格式化字符串打印到 stdout ;
sprintf() 返回可以赋值给变量的格式化字符串。
notes:如果用%d格式化string,将会输出0。
字符串函数
在 awk 中,确实需要字符串函数,因为不能象在其它语言(如 C、C++ 和 Python)中那样将字符串看作是字符数组。 换言之,所有对字符串的操作,全部封装成了接口。
总结如下:
length(string):
它返回字符串的长度。
index(string, substr):
它将返回子字符串substr在另一个字符串string中出现的位置,如果没有找到该字符串则返回0。注意位置从1开始计数。
tolower(string):
返回string中所有字符对应的小写形式,不改写string本身。
toupper(string):
返回string中所有字符对应的大写形式,不改写string本身。
substr(string, startpos, len):
从字符串string中选择子串[startpos, startpos+len],如果子串尾部超出原串,会被截断。同样,substr() 不会修改原始字符串,而是返回子串。如果您通常用于编程的语言使用数组下标访问部分字符串(以及不使用这种语言的人),请记住 substr() 是 awk 代替方法。需要使用它来抽取单个字符和子串;因为 awk 是基于字符串的语言,所以会经常用到它。
match(string, patternstr):
与 index() 的区别在于它并不搜索子串,它搜索的是规则表达式。match() 函数将返回匹配的起始位置,如果没有找到匹配,则返回 0。。此外,match() 还将设置两个变量,叫作 RSTART 和 RLENGTH。这两个变量的逻辑意义:
RSTART 包含返回值(第一个匹配的位置)
RLENGTH 指定它占据的字符跨度(如果没有找到匹配,则返回 -1)。
使用 RSTART、RLENGTH、substr() 和一个小循环,可以轻松地迭代字符串中的每个匹配。(因为基于模式串匹配的原串中,可能有多个匹配点,构成一个匹配集)
example:
str = "how are you doing?"
for(i=1;i<=length(str);i++)
{
s = substr(str, i, length(str)) #循环设置s为str的所有后缀
subs = match(s, /you/) #对每一个后缀做正则匹配
if (RLENGTH != -1) #如果匹配,输出匹配子串,然后把 i 设置成匹配子串的最大后缀(除了子串本身)
{
print substr(s, RSTART, RLENGTH)
i = RSTART + 1
}
}
字符串替换(需要理解正则表达式原理,并有一定的实践经验)
这些函数与目前已经讨论过的函数略有不同,因为它们确实修改原始字符串。
sub(regexp, replstring, string):
在 string 中匹配 regexp 的第一个子串,并且用 replstring 替换该子串。
gsub(regexp, replstring, string):
在 string 中匹配 regexp 的所有子串,并且用 replstring 替换所有子串。
split(string, array, delimiter):
把string按照delimiter定义的分割符做切割,把各个切片有序地赋给array。返回分割的字符串元素的数量。
example:
numelements=split("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",mymonths,",")
print mymonths[1], mymonths[numelements] #将会打印Jan Dec
特殊字符串形式, 一些函数入参可以利用上下文语境,不用显式的指定,如下:
length()、sub() 或 gsub() 时,可以去掉最后一个自变量 —— 原串string ,这样 awk 将对 $0(整个当前行)应用函数调用。应用实例:
要打印文件中每一行的长度,使用以下 awk 脚本:
{
print length()
}