AWK实用工具带有其自己的自包含语言,它是Unix/Linux
中也是任何环境中现有的功能最强大的数据处理引擎之一。这种编程及数据操作语言(其名称得自于它的创始人 Alfred Aho、Peter Weinberger
和 Brian Kernighan
姓氏的首个字母)的最大功能取决于一个人所拥有的知识。它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。
1 AWK简介
最简单地说,AWK 是一种用于处理文本的编程语言工具。AWK 实用工具的语言在很多方面类似于 shell 编程语言,尽管 AWK
具有完全属于其本身的语法。在最初创造 AWK
时,其目的是用于文本处理,并且这种语言的基础是,只要在输入数据中有模式匹配,就执行一系列指令。该实用工具扫描文件中的每一行,查找与命令行中所给定内容相匹配的模式。如果发现匹配内容,则进行下一个编程步骤。如果找不到匹配内容,则继续处理下一行。
1.1 命令格式及选项
1.1.1 Awk的语法有两种形式:
? Awk [options] ‘script’ var=value files
? Awk [options] –f scriptfile var=value files
尽管操作可能会很复杂,但命令的语法始终是:
awk '{pattern + action}'
其中 pattern 表示 AWK 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。花括号 ({})
不需要在程序中始终出现,但它们用于根据特定的模式对一系列指令进行分组。
1.1.2 命令选项 [option]
-F fs or –field-separator fs #指定域分割符
-v var=value or –assign var=value #定义用户自定义变量并赋值
-f scriptfile or –file scriptfile #指定awk命令脚本文件
还有一个扩展功能选项(下面这些功能AIX平台不支持):
-W compact
-W tranditional
-W copyleft
-W copyright
-W help
-W usage
等等
2 了解字段
实用工具将每个输入行分为记录和字段。记录是单行的输入,而每条记录包含若干字段。默认的字段分隔符是空格或制表符,而记录的分隔符是换行。虽然在默认情况下将制表符和空格都看作字段分隔符(多个空格仍然作为一个分隔符),但是可以将分隔符从空格改为任何其它字符。
为了进行演示,请查看以下保存为 emp_names 的员工列表文件:
46012 DULANEY EVAN MOBILE AL 46013 DURHAM JEFF MOBILE AL 46015 STEEN BILL
MOBILE AL 46017 FELDMAN EVAN MOBILE AL 46018 SWIM STEVE UNKNOWN AL 46019 BOGUE
ROBERT PHOENIX AZ 46021 JUNE MICAH PHOENIX AZ 46022 KANE SHERYL UNKNOWN AR
46024 WOOD WILLIAM MUNCIE IN 46026 FERGUS SARAH MUNCIE IN 46027 BUCK SARAH
MUNCIE IN 46029 TUTTLE BOB MUNCIE IN
当 AWK 读取输入内容时,整条记录被分配给变量 。每个字段以字段分隔符分开,被分配给变量
$1、$2等等。一行在本质上可以包含无数个字段,通过字段号来访问每个字段。因此,命令
awk '{print $1, $2, $3, $4, $5}' emp_names
将会产生的打印输出是
46012 DULANEY EVAN MOBILEAL 46013DURHAMJEFF MOBILEAL 46015 STEEN BILL MOBILEAL
46017 FELDMAN EVAN MOBILEAL 46018 SWIM STEVE UNKNOWNAL 46019 BOGUE
ROBERTPHOENIXAZ 46021 JUNE MICAHPHOENIXAZ 46022 KANE SHERYL UNKNOWN AR 46024
WOOD WILLIAMMUNCIEIN 46026 FERGUS SARAHMUNCIEIN 46027 BUCK SARAHMUNCIEIN 46029
TUTTLE BOBMUNCIEIN
值得注意的一项重要内容是,AWK
解释由空格分隔的五个字段,但当它打印显示内容时,在每个字段间只有一个空格。利用为每个字段指定了唯一号码的功能,您可以选择只打印特定的字段。例如,只打印每条记录的姓名时,只需选择第二个和第三个字段进行打印:
$ awk '{print $2, $3}' emp_names DULANEY EVAN DURHAM JEFF STEEN BILL FELDMAN
EVAN SWIM STEVE BOGUE ROBERT JUNE MICAH KANE SHERYL WOOD WILLIAM FERGUS SARAH
BUCK SARAH TUTTLE BOB $
如果不加逗号(,)则输出的内容会连接才一起,形成一个字段,例如:
$ awk '{print $2$3}' emp_names DULANEYEVAN DURHAMJEFF STEENBILL FELDMANEVAN
SWIMSTEVE BOGUEROBERT JUNEMICAH KANESHERYL WOODWILLIAM FERGUSSARAH BUCKSARAH
TUTTLEBOB $
您还可以指定按任何顺序打印字段,而无论它们在记录中是如何存在的。因此,只需要显示姓名字段,并且使其顺序颠倒,先显示名字再显示姓氏:
$ awk '{print $3, $2}' emp_names EVAN DULANEY JEFFDURHAM BILL STEEN EVAN
FELDMAN STEVE SWIM ROBERT BOGUE MICAH JUNE SHERYL KANE WILLIAM WOOD SARAH
FERGUS SARAH BUCK BOB TUTTLE $
3 使用模式
3.1 模式的种类
? Regexp 正则表达式
? 关系表达式 关系运算符(< <= == >= > !=)连接的表达式,例如:$1 > $2,表示匹配第一个域大于第二个域的行记录
? 模式匹配表达式 匹配符(~)或者不匹配符(~!)构成的表达式,例如:’$1 ~/AL/’,表示第一个域中包括AL的行记录
? 行范围模式:1-100
? 范围模板:范围模板匹配从第一个模板的第一次出现到第二个模板的第一次出现之间所有行。
3.2 范围模板
范围模板匹配从第一个模板的第一次出现到第二个模板的第一次出现之间所有行,如果有一个模板没出现,则匹配到开头或末尾。如:
awk ‘/root/, /mysql/’ test.txt
将显示root第一次出现到mysql第一次出现之间的所有行。
3.3 gawk专用正则表达式元字符
以下元字符是gawk专用的,不适合unix版本的awk。
\Y 匹配一个单子开通或结尾的空字符串
\B 匹配单词内的空字符串
\< 匹配一个单词开头的空字符串
\> 匹配一个单词末尾的空字符串
\w 匹配一个字母数字组成的单词
\W 匹配一个非字母数字组成的单词
\` 匹配字符串开头的空字符串
\’ 匹配字符串结尾的空字符串
3.4 模式举例
通过包含一个必须匹配的模式,您可以选择只对特定的记录而不是所有的记录进行操作。模式匹配的最简单形式是搜索,其中要匹配的项目被包含在斜线
(/pattern/) 中,pattern 支持正则表达式。例如,只对那些居住在阿拉巴马州的员工执行前面的操作:
$ awk '/AL/ {print $3, $2}' emp_names EVAN DULANEY JEFFDURHAM BILL STEEN EVAN
FELDMAN STEVE SWIM $
找出匹配AL与IN的记录,语句如下:
$ awk '/(AL|IN)/ {print $3, $2}' emp_names EVAN DULANEY JEFFDURHAM BILL STEEN
EVAN FELDMAN STEVE SWIM WILLIAM WOOD SARAH FERGUS SARAH BUCK BOB TUTTLE $
如果您不指定要打印的字段,则会打印整个匹配的条目:
$ awk '/AL/' emp_names 46012 DULANEY EVAN MOBILE AL 46013 DURHAM JEFF MOBILE
AL 46015 STEEN BILL MOBILE AL 46017 FELDMAN EVAN MOBILE AL 46018 SWIM STEVE
UNKNOWN AL $
对同一数据集的多个命令可以用分号 (;) 分隔开。例如,在一行中打印姓名,而在另一行中打印城市和州名:
$ awk '/AL/ {print $3, $2 ; print $4, $5}' emp_names EVAN DULANEY MOBILEAL
JEFFDURHAM MOBILEAL BILL STEEN MOBILEAL EVAN FELDMAN MOBILEAL STEVE SWIM
UNKNOWNAL $
如果没有使用分号(print $3, $2, $4, $5),则会在同一行中显示所有内容。另一方面,如果分别给出两个打印语句,则会产生完全不同的结果:
$ awk '/AL/ {print $3, $2} {print $4, $5}' emp_names EVAN DULANEY MOBILEAL
JEFFDURHAM MOBILEAL BILL STEEN MOBILEAL EVAN FELDMAN MOBILEAL STEVE SWIM
UNKNOWNAL PHOENIX AZ PHOENIX AZ UNKNOWN AR MUNCIE IN MUNCIE IN MUNCIE IN
MUNCIE IN $
只有在列表中找到 AL
时才会给出字段三和字段二。但是,字段四和字段五是无条件的,始终打印它们。只有第一组花括号中的命令对前面紧邻的命令(/AL/)起作用(注解:第二组花括号中的命令也会执行,但是第一个模式不会应用到它,他可以在花括号指定自己的模式)。,例如:**
$ awk '/AL/ {print $3, $2} /IN/ {print $4, $5}' emp_names EVAN DULANEY JEFF
DURHAM BILL STEEN EVAN FELDMAN STEVE SWIM MUNCIE IN MUNCIE IN MUNCIE IN MUNCIE
IN $
**
**
awk '/AL/ {print $3, $2 ; print $4, $5}'
emp_names的输出结果非常不便于阅读,可以使其稍微更清晰一些。首先,在城市与州之间插入一个空格和逗号。然后,在每两行显示之后放置一个空行:
$ awk '/AL/ {print $3,$2 ; print $4", "$5"\n"}' emp_names
EVAN DULANEY MOBILE,AL JEFFDURHAM MOBILE,AL BILL STEEN MOBILE,AL EVAN FELDMAN
MOBILE,AL STEVE SWIM UNKNOWN, AL $
在第四和第五个字段之间,添加一个逗号和一个空格(在引号之间),在第五个字段后面,打印一个换行符 (\n)。在 AWK 打印语句中还可以使用那些可在 echo
命令中使用的所有特殊字符,包括:
\n(换行) \t(制表) \b(退格) \f(进纸) \r(回车)
因此,要读取全部五个最初由制表符分隔开的字段,并且也利用制表符打印它们,您可以编程如下
$ awk '{print $1"\t"$2"\t"$3"\t"$4"\t"$5}' emp_names
46012 DULANEY EVAN MOBILE AL 46013 DURHAM JEFF MOBILE AL 46015 STEEN BILL
MOBILE AL 46017 FELDMAN EVAN MOBILE AL 46018 SWIM STEVE UNKNOWN AL 46019 BOGUE
ROBERT PHOENIX AZ 46021 JUNE MICAH PHOENIX AZ 46022 KANE SHERYL UNKNOWN AR
46024 WOOD WILLIAM MUNCIE IN 46026 FERGUS SARAH MUNCIE IN 46027 BUCK SARAH
MUNCIE IN 46029 TUTTLE BOB MUNCIE IN $
通过连续设置多项标准并用管道 (|) 符号将其分隔开,您可以一次搜索多个模式匹配:
$ awk '/AL|IN/' emp_names 46012 DULANEY EVAN MOBILE AL 46013 DURHAM JEFF
MOBILE AL 46015 STEEN BILL MOBILE AL 46017 FELDMAN EVAN MOBILE AL 46018 SWIM
STEVE UNKNOWN AL 46024 WOOD WILLIAM MUNCIE IN 46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN 46029 TUTTLE BOB MUNCIE IN $
这样可找到每个阿拉巴马州和印第安那州居民的匹配记录。但是在试图找出居住在亚利桑那州的人时,出现了一个问题:
$ awk '/AR/' emp_names 46019 BOGUE ROBERT PHOENIX AZ 46021 JUNE MICAH PHOENIX
AZ 46022 KANE SHERYL UNKNOWN AZ 46026 FERGUS SARAH MUNCIE IN 46027 BUCK SARAH
MUNCIE IN $
员工 46026 和 46027 没有住在亚利桑那州;但是他们的名字中包含所搜索的字符序列。切记,当在 AWK 中进行模式匹配时,例如 grep、sed
或者大部分其他 Linux/Unix
命令,将在记录(行)中的任何位置查找匹配,除非指定进行其他操作。为解决这一问题,必须将搜索与特定字段联系起来。通过利用代字号 (~)
以及对特定字段的说明,可以达到这一目的,如下例所示:
$ awk ' $5 ~ /AR/' emp_names 46019 BOGUE ROBERT PHOENIX AZ 46021 JUNE MICAH
PHOENIX AZ 46022 KANE SHERYL UNKNOWN AZ $
代字号(表示匹配)的对应符号是一个前面带有感叹号的代字号 (~!)。这些字符通知程序,如果搜索序列没有出现在指定字段中,则找出与搜索序列相匹配的所有行:
$ awk '$5 !~ /AR/' emp_names 46012 DULANEY EVAN MOBILE AL 46013 DURHAM JEFF
MOBILE AL 46015 STEEN BILL MOBILE AL 46017 FELDMAN EVAN MOBILE AL 46018 SWIM
STEVE UNKNOWN AL 46024 WOOD WILLIAM MUNCIE IN 46026 FERGUS SARAH MUNCIE IN
46027 BUCK SARAH MUNCIE IN 46029 TUTTLE BOB MUNCIE IN $
在这种情况下,将显示第五个字段中没有 AR 的所有行 — 包括两个 Sarah 条目,这两个条目确实包含 AR,但却是在第三个字段而不是第五个字段中。
4 理解操作(action)部分
操作(action)由一个或多个命令、函数、表达式组成,直间由换行符或分号隔开,并位于大括号内。主要有四部分:
? 变量或数组赋值
? 输出命令
? 内置函数
? 控制流命令
4.1 花括号和字段分隔符
括号字符在 AWK 命令中起着很重要的作用。出现在括号之间的操作指出将要发生什么以及何时发生。当只使用一对括号时:
{print $3, $2}
括号间的所有操作同时发生。当使用多于一对的括号时:
{print $3}{print $2}
执行第一组命令,在该命令完成后执行第二组命令。注意以下两列清单的区别:
$ awk '{print $3, $2}' names EVAN DULANEY JEFFDURHAM BILL STEEN EVAN FELDMAN
STEVE SWIM ROBERT BOGUE MICAH JUNE SHERYL KANE WILLIAM WOOD SARAH FERGUS SARAH
BUCK BOB TUTTLE $ $ awk '{print $3}{print $2}' names EVAN DULANEY JEFF DURHAM
BILL STEEN EVAN FELDMAN STEVE SWIM ROBERT BOGUE MICAH JUNE SHERYL KANE WILLIAM
WOOD SARAH FERGUS SARAH BUCK BOB TUTTLE $
要利用多组括号进行重复查找,执行第一组中的命令直到完成为止;然后处理第二组命令。如果有第三组命令,则在第二组命令完成后执行它,以此类推。在所生成的打印输出中,有两个分隔的打印命令,因此先执行第一个命令,随后执行第二个命令,这样导致每个条目显示在两行而不是一行中。
区分两个字段的字段分隔符不一定始终是空格;它可以是任何可识别的字符。为进行演示,假定emp_names文件利用冒号而不是制表符来分隔字段:
$ cat emp_names 46012:DULANEY:EVAN:MOBILE:AL 46013:DURHAM:JEFF:MOBILE:AL
46015:STEEN:BILL:MOBILE:AL 46017:FELDMAN:EVAN:MOBILE:AL
46018:SWIM:STEVE:UNKNOWN:AL 46019:BOGUE:ROBERT:PHOENIX:AZ
46021:JUNE:MICAH:PHOENIX:AZ 46022:KANE:SHERYL:UNKNOWN:AR
46024:WOOD:WILLIAM:MUNCIE:IN 46026:FERGUS:SARAH:MUNCIE:IN
46027:BUCK:SARAH:MUNCIE:IN 46029:TUTTLE:BOB:MUNCIE:IN $
如果试图通过指定所需要的第二个字段来打印姓氏
$ awk '{print $2}' emp_names
您最后会得到十二个空行。因为文件中没有空格,除了第一个字段之外没有可认别的字段。为解决这一问题,必须通知 AWK
是空格之外的另一个字符作为分隔符,有两种方法可通知 AWK 使用新的字段分隔符:使用命令行参数 -F,或在程序中指定变量
FS。两种方法的效果相同,只有一种例外情况,如下例所示:
$ awk '{FS=":"}{print $2}' emp_names
DURHAM STEEN FELDMAN SWIM BOGUE JUNE KANE WOOD FERGUS BUCK TUTTLE $ $ awk -F:
'{print $2}' emp_names DULANEY DURHAM STEEN FELDMAN SWIM BOGUE JUNE KANE WOOD
FERGUS BUCK TUTTLE $
在第一个命令中,头一条记录返回不正确的空行,而其他结果正确。直到读取第二条记录时,才识别字段分隔符并正确地执行。通过使用 BEGIN
语句可以纠正这一缺点(在后文详述)。-F 的功能非常类似于 BEGIN,能够正确地读取第一条记录并按要求执行。
这里要特别注意:
awk '{FS=":"}{print $2}' emp_names
与
awk -F: '{print $2}' emp_names
的区别
在本文开始处我曾提到,默认的显示/输出字段分隔符是空格。通过使用输出字段分隔符 (OFS)
变量,可以在程序中更改此特性。例如,要读取文件(由冒号分隔)并以短划线显示,则命令是
$ awk -F":" '{OFS="-"}{print $1,$2,$3,$4,$5}' emp_names
46012-DULANEY-EVAN-MOBILE-AL 46013-DURHAM-JEFF-MOBILE-AL 46015-STEEN-BILL-
MOBILE-AL 46017-FELDMAN-EVAN-MOBILE-AL 46018-SWIM-STEVE-UNKNOWN-AL
46019-BOGUE-ROBERT-PHOENIX-AZ 46021-JUNE-MICAH-PHOENIX-AZ 46022-KANE-SHERYL-
UNKNOWN-AR 46024-WOOD-WILLIAM-MUNCIE-IN 46026-FERGUS-SARAH-MUNCIE-IN
46027-BUCK-SARAH-MUNCIE-IN 46029-TUTTLE-BOB-MUNCIE-IN $
特别注意上面的语句中不能使用print $0,因为在数据没有变动过程的情况下,$0的值不会因为指定了OFS而改变,如果要使用,需用下面的方法,增加变动过程:
$ awk -F":" '{OFS="-"; tmp=$1; $1=tmp; print $0}' emp_names
46012-DULANEY-EVAN-MOBILE-AL 46013-DURHAM-JEFF-MOBILE-AL 46015-STEEN-BILL-
MOBILE-AL 46017-FELDMAN-EVAN-MOBILE-AL 46018-SWIM-STEVE-UNKNOWN-AL
46019-BOGUE-ROBERT-PHOENIX-AZ 46021-JUNE-MICAH-PHOENIX-AZ 46022-KANE-SHERYL-
UNKNOWN-AR 46024-WOOD-WILLIAM-MUNCIE-IN 46026-FERGUS-SARAH-MUNCIE-IN
46027-BUCK-SARAH-MUNCIE-IN 46029-TUTTLE-BOB-MUNCIE-IN $
FS 和 OFS 分别是:输入字段分隔符和输出字段分隔符,它们只是一对可以在 AWK 实用工具中使用的变量。例如,要在打印时为每行编号,可以采用以下方式使用
NR 变量:
$ awk -F":" '{print NR,$1,$2,$3}' emp_names
1 46012 DULANEY EVAN 2 46013DURHAMJEFF 3 46015 STEEN BILL 4 46017 FELDMAN EVAN
5 46018 SWIM STEVE 6 46019 BOGUE ROBERT 7 46021 JUNE MICAH 8 46022 KANE SHERYL
9 46024 WOOD WILLIAM 10 46026 FERGUS SARAH 11 46027 BUCK SARAH 12 46029 TUTTLE
BOB $
找出员工号码处于 46012 和 46015 之间的所有行:
$ awk -F":" '/4601[2-5]/' emp_names 46012 DULANEY EVAN MOBILEAL 46013 DURHAM
JEFF MOBILEAL 46015 STEEN BILL MOBILEAL $
4.2 添加文本
可以按照添加控制序列或其他字符的相同方式将文本添加到显示中。例如,要将分隔符从空格改为冒号,则命令是
awk '{print ":"":"":"":"}' emp_names > new_emp_names
在这种情况下,字符 (:) 包含在引号 ("/")
中,它被添加到每个字段之间。在引号之间的值可以是任何内容。例如,创建一个关于居住在阿拉巴马州的员工的外观类似数据库的显示:
$ awk '{print $1":"$2":"$3":"$4":"$5}' emp_names > new_emp_names
NAME: DULANEY, EVAN CITY-STATE:MOBILE,AL NAME:DURHAM, JEFF CITY-
STATE:MOBILE,AL NAME: STEEN, BILL CITY-STATE:MOBILE,AL NAME: FELDMAN, EVAN
CITY-STATE:MOBILE,AL NAME: SWIM, STEVE CITY-STATE: UNKNOWN, AL $
4.3 Awk运算符
Table1运算符表
运算符 | 描述 | ||
---|---|---|---|
= += -+ = /= %= ^= *= | 赋值运算符 | ||
?: | C条件表达式 | ||
&& | ! | 逻辑表达式 与 或 非 | |
~ ~! | 匹配 不匹配 | ||
< <= == != > >= | 关系运算符 | ||
空格 | 连接 | ||
+ - * / % ++ -- | 算术运算符(加减乘除求余自加自减) | ||
^ * | 求幂 | ||
$ | 字段引用 | ||
in | 数组成员 | ||
4.4 数学操作举例
AWK 除了提供文本功能,还提供全部范围的算术操作符,包括以下符号:
+ 将数字相加 - 减 * 乘 / 除 ^ 执行指数运算 % 提供模 ++ 将变量值加一 += 将其他操作的结果分配给变量 — 将变量减一 -=
将减法操作的结果分配给变量 *= 分配乘法操作的结果 /= 分配除法操作的结果 %= 分配求模操作的结果
例如,假定您的机器上存在以下的文件,详细地列出硬件商店中的物品:
$ cat inventory hammers 5 7.99 drills 2 29.99 punches 7 3.59 drifts 2 4.09
bits 55 1.19 saws 123 14.99 nails 800 .19 screws 80 .29 brads 100 .24 $
第一项业务定单是通过将第二个字段(数量)的值乘以第三个字段(价格)的值,计算每种物品的库存价值:
$ awk '{print $1,"QTY: "$2,"PRICE: "$3,"TOTAL: "$2*$3}' inventory
hammers QTY: 5 PRICE: 7.99 TOTAL: 39.95 drills QTY: 2 PRICE: 29.99 TOTAL:
59.98 punches QTY: 7 PRICE: 3.59 TOTAL: 25.13 drifts QTY: 2 PRICE: 4.09 TOTAL:
8.18 bits QTY: 55 PRICE: 1.19 TOTAL: 65.45 saws QTY: 123 PRICE: 14.99 TOTAL:
1843.77 nails QTY: 800 PRICE: .19 TOTAL: 152 screws QTY: 80 PRICE: .29 TOTAL:
23.2 brads QTY: 100 PRICE: .24 TOTAL: 24 $
如果这些行本身并不重要,您只是希望确定商店中有多少件物品,则可以分配一个普通变量,按照每条记录中的物品数量增加:
$ awk '{x=x+$2} {print x}' inventory
5 7 14 16 71 194 994 1074 1174 $
根据这一数据,商店中有 1174 件物品。第一次执行时,变量 x
没有值,因此它采用第一行第二个字段的值。第二次执行时,它保留了第一行的值并加上第二行的值,以此类推,直到达到累计的总合。
可以应用相同的过程来确定现有库存的总价值:
$ awk '{x=x+($2*$3)} {print x}' inventory
39.95 99.93 125.06 133.24 198.69 2042.46 2194.46 2217.66 2241.66 $
因此,1174 件物品的价值是 ,241.66。虽然这一过程可以获得总计值,但它的外观很差,需要加工成实际的报表。利用一些附加项,很容易使显示变得更整洁:
$ awk '{x=x+($2*$3)}{print $1,"QTY: "$2,"PRICE: "$3,"TOTAL: "$2*$3,"BAL: "x}' inventory
hammers QTY: 5 PRICE: 7.99 TOTAL: 39.95 BAL: 39.95 drills QTY: 2 PRICE: 29.99
TOTAL: 59.98 BAL: 99.93 punches QTY: 7 PRICE: 3.59 TOTAL: 25.13 BAL: 125.06
drifts QTY: 2 PRICE: 4.09 TOTAL: 8.18 BAL: 133.24 bits QTY: 55 PRICE: 1.19
TOTAL: 65.45 BAL: 198.69 saws QTY: 123 PRICE: 14.99 TOTAL: 1843.77 BAL:
2042.46 nails QTY: 800 PRICE: .19 TOTAL: 152 BAL: 2194.46 screws QTY: 80
PRICE: .29 TOTAL: 23.2 BAL: 2217.66 brads QTY: 100 PRICE: .24 TOTAL: 24 BAL:
2241.66 $
上面的语句修改为:
awk '{x=x+($2*$3); print $1,"QTY: "$2,"PRICE: "$3,"TOTAL: "$2*$3,"BAL: "x}'
inventory
更合理一些,因为如果有过滤条件(模式)的话,第一种写法就错误了。
该过程提供了每条记录的清单,同时将总价值分配给库存值,并保持商店资产的运作平衡。
4.5 Awk内置变量
Table2 内置变量
变量 | 描述 | |
---|---|---|
$n | (n>0)当前记录第n个字段,字段之间由FS分割 | |
$0 | 完整的当前记录,当$n的值发生变动时,$0会自动被修改。 | |
ARGC | 命令行参数个数, ARGC表示命令行上除了选项-F,-v,-f以及这些选项所对应的参数之外的所有参数的个数,如果awk命令是直接书写在命令行上,那ARGC在计算参数个数的时候也会忽略这个的. | |
ARGV | 命令行参数的数组,ARGV[0] 为awk命令本身,从ARGV[1]开始为待处理的文件名 | |
ARGIND | 命令行中当前文件的位置,在第一个文件开始处理之前ARGIND为0,开始处理第一个文件,ARGIND为1,依次类推,因此可以根据ARGIND对不同的文件进行不同的操作 | |
CONVFMT | 数字转换格式(默认为:%.6g) | |
ENVIRON | 环境变量关联数组,ENVIRON是子典型数组,可以通过对应键值获得它的值,例如: ENVIRON["PATH"]、ENVIRON["HOME"] | |
ERRNO | 最后一个系统错码,没有错误为0 | |
FIELDWIDTHS | 字段宽度列表(用空格键分割),FIELDWIDTHS其格式为空格分隔的一串数字,用以对记录进行域的分隔,FIELDWIDTHS="4 2 2 2 2 2"就表示$1宽度是4,$2是2,$3是2 .... 。这个时候会忽略:FS分隔符;例如: echo 20100117054932 | awk 'BEGIN{FIELDWIDTHS="4 2 2 2 2 3"}{print $1"-"$2"-"$3,$4":"$5":"$6}',结果如下: 2010-01-17 05:49:32 |
FILENAME | 当前文件名 | |
FNR | 同NR,但相对于当前文件 | |
FS | 字段分割符 | |
IGNORECASE | 如果为真,则进行忽略大小写匹配,一般在BEGIN模块设置为1(BEGIN{IGNORECASE=1}),则模式匹配时不区分大小写。 | |
NF | 当前记录中的字段数 | |
NR | 当前记录数 | |
OFMT | 数字输出格式,默认为:%.6g | |
OFS | 输出字段分割符 | |
ORS | 输出记录分割符 | |
RLENGTH | 有match函数所匹配的字符串长度 | |
RS | 记录分割符 | |
RSTART | 有match函数所匹配的字符串的第一个位置 | |
SUBSEP | 数组下标分割符,默认值为:\034 | |
4.6 Awk内置函数
函数 | 描述 |
---|---|
gsub(r, s) | 在整个$0中用s代替r |
gsub(r, s, t) | 在整个t中用s代替r |
index(s, t) | 返回s中字符串t的第一个位置 |
length(s) | 返回s字符串的长度 |
match(s, r) | 测试s是否包含匹配r的字符串。 例子: 1)awk 'BEGIN{start=match("this is a test",/[a-z]+$/); print start, RSTART, RLENGTH }' 结果:11 11 4 2)awk 'BEGIN{start=match("this is a test",/^[a-z]+$/); print start, RSTART, RLENGTH }' 结果:0 0 –1 |
split(s, a, fs) | 在数组fs上将s分成序列a |
sprintf(fmt, exp) | 返回经过fmt格式化后的exp |
sub(r, s) | 用$0中最左边最长的字符串代替s |
substr(s, p) | 返回s中从p开始的后缀部分 |
substr(s, p, n) | 返回s中从p开始长度为n的后缀部分 |
strftime(format, timestamp) | 返回一个基于format格式的日期或时间串,timestamp是system()函数返回的时间 |
system() | 返回自1970年1月1日以来经过的时间(秒数) |
tolower(s) | 返回将s转换为小写后的串 |
toupper(s) | 返回将s转换为大写后的串 |
exp(x) | e的x幂 |
int(x) | x的整数部分 |
5 BEGIN 和 END 模块
使用 BEGIN 和 END 语句可以分别指定在处理实际开始之前或者完成之后进行操作。BEGIN 语句最常用于建立变量或显示标题。另一方面,END
语句可用于在程序结束后继续进行处理。
在前面的示例中,利用以下例程生成了物品的总价值:
awk '{x=x+($2*$3)} {print x}' inventory
该例程在运行总计累加时显示了文件中的每一行。没有其他方法可以指定它,而不让在每一行进行打印也导致它始终不打印出来。但是,利用 END 语句可以避免这一问题:
$ awk '{x=x+($2*$3)} END {print "Total Value of Inventory:"x}' inventory
Total Value of Inventory: 2241.66 $
定义了变量
x,它对每一行进行处理;但是,在所有处理完成之前不会生成显示。尽管可以作为独立例程使用,它也可以置入到先前的代码列表,添加更多信息并生成更完整的报表:
$ awk '{x=x+$2*$3; print $1, "QTY: ", $2, "PRICE: ", $3, "TOTAL: ", $2*$3} END
{print "Total Value of Inventory: " x}' inventory hammers QTY: 5 PRICE: 7.99
TOTAL: 39.95 drills QTY: 2 PRICE: 29.99 TOTAL: 59.98 punches QTY: 7 PRICE:
3.59 TOTAL: 25.13 drifts QTY: 2 PRICE: 4.09 TOTAL: 8.18 bits QTY: 55 PRICE:
1.19 TOTAL: 65.45 saws QTY: 123 PRICE: 14.99 TOTAL: 1843.77 nails QTY: 800
PRICE: .19 TOTAL: 152 screws QTY: 80 PRICE: .29 TOTAL: 23.2 brads QTY: 100
PRICE: .24 TOTAL: 24 Total Value of Inventory: 2241.66 $
BEGIN 命令与 END 的工作方式相同,但它建立了那些需要在完成其他工作之前所做的项目。该过程最常见的目的是创建报表的标题。此例程的语法类似于
$ awk 'BEGIN {print "ITEM QUANTITY PRICE TOTAL"}'
6 输入、输出和源文件
AWK工具可以从文件中读取其输入,正如在此之前所有示例所做的那样,它也可以从其他命令的输出中获取输入。例如:
$ sort emp_names | awk '{print $3,$2}'
awk命令的输入是排序操作的输出。除了sort,还可以使用任何其他的 Linux 命令 — 例如
grep。该过程允许您在离开所选定字段前对文件执行其他操作。
类似于解释程序,AWK使用输出改向操作符 > 和 >> 将其输出放入文件中而不是标准输出设备。这些符号的作用类似于它们在解释程序中的对应符号,因此 >
在不存在文件时创建文件,而 >> 追加到现有文件的尾部。请看以下的示例:
$ awk '{print NR, $1}' emp_names > /tmp/filez $ cat /tmp/filez 1 46012 2 46013
3 46015 4 46017 5 46018 6 46019 7 46021 8 46022 9 46024 10 46026 11 46027 12
46029 $
检查该语句的语法,您会看到输出改向是在打印语句完成后进行的;上面的语句也可以这样写:
awk '{print NR, $1}' > /tmp/filez emp_names
或者
awk '{print NR, $1 >” /tmp/filez “}' emp_names(注意这种方式时,重定向文件名必须用双引号引起来。
在 AWK 中输出到管道也类似于解释程序中所实现的相同操作。要将打印命令的输出发送到管道中,可以在打印命令后附加管道符号以及命令的名称,如下所示:
$ awk '{ print | "sort" }' emp_names BOGUE BUCK DULANEY DURHAM FELDMAN FERGUS JUNE KANE STEEN SWIM TUTTLE WOOD $
这是输出改向的情况,必须将命令包含在引号中,而管道的名称是被执行命令的名称。
AWK 所使用的命令可以来自两个地方。首先,可以在命令行中指定它们,如示例中所示。其次,它们可以由源文件提供。如果是这种情况,通过 -f 选项将这种情况向
AWK 发出警告。演示如下:
$ cat awklist
{print $3,$2}
{print $4,$5,"\n"}
$ $ awk -f awklist emp_names EVAN DULANEY MOBILEAL JEFFDURHAM MOBILEAL BILL
STEEN MOBILEAL EVAN FELDMAN MOBILEAL STEVE SWIM UNKNOWNAL ROBERT BOGUE PHOENIX
AZ MICAH JUNE PHOENIX AZ SHERYL KANE UNKNOWN AR WILLIAM WOOD MUNCIE IN SARAH
FERGUS MUNCIE IN SARAH BUCK MUNCIE IN BOB TUTTLE MUNCIE IN $
注意,在源文件中的任何地方或者在命令行中调用它时,不使用单引号。单引号只用于区别命令行中的命令与文件名称。
如果简单的输出不能处理您的程序中所需要的复杂信息,则可以尝试由 printf 命令获得的更加复杂的输出,其语法是
printf( format, value, value ...)
该语法类似于 C 语言中的 printf 命令,而格式的规格是相同的。通过插入一项定义如何打印数值的规格,可以定义该格式。格式规格包含一个跟有字母的
%。类似于打印命令,printf 不必包含在圆括号中,但是可以认为使用圆括号是一种良好的习惯。
下表列出 printf 命令提供的各种规格。
规格 说明 %c 打印单个 ASCII 字符 %d 打印十进制数 %e 打印数字的科学计数表示 %f 打印浮点表示 %g 打印 %e 或
%f;两种方式都更简短 %o 打印无符号的八进制数 %s 打印 ASCII 字符串 %x 打印无符号的十六进制数 %% 打印百分号;不执行转换
可以在 % 与字符之间提供某些附加的格式化参数。这些参数进一步改进数值的打印方式:
参数说明
- 将字段中的表达式向左对齐
,width 根据需要将字段补齐到指定宽度(前导零使用零将字段补齐)
.prec 小数点右面数字的最大字符串宽度或最大数量
printf 命令能够控制并将数值从一种格式转换为另一种格式。当需要打印变量的值时,只需提供一种规格,指示 printf
如何打印信息(通常包含在双引号中)即可。必须为每个传递到 printf 的变量包含一个规格参数;如果包含过少的参数,则 printf 不会打印所有的数值。
7 处理错误
AWK工具报告所发生错误的方式很令人恼火。一个错误会阻碍任何操作的进行,所提供的错误信息非常含混不清:
awk: syntax error near line 2
awk: bailing out near line 2
您可能会花几小时的时间查看第 2 行,试图找出它为什么阻碍程序运行;这就是支持使用源文件的一个有力论据。
切记有两条规则可以帮助您避免出现语法错误:
1. 确保命令位于括号中,而括号位于单引号中。没有使用这些字符之一必然导致程序无法运行。
2. 搜索命令需要位于斜线之间。要找出住在印第安那州的员工,您必须使用“/IN/”而不是“IN”。
8 awk简单用例
8.1 并联文件
有多个 .ind 的文件如下:
cat aaa.ind
rs1 A A
rs2 A A
rs3 C C
rs4 C C
cat bbb.ind
rs1 T T
rs2 T T
rs3 G G
rs4 G G
cat ccc.ind
rs1 B B
rs2 B B
rs3 L L
rs4 L L
awk 'BEGIN{for(i=1;i
得到如下结果:
aaa.ind bbb.ind ccc.ind
AA TT BB
AA TT BB
CC GG LL
CC GG LL
【解析】
1)该例子中,我们可以很好的学习到ARGC, ARGV FNR 这三个内置变量的用法。
2)这里注意一个问题,我们在 BEGIN 操作文件名,因为 BEGIN 模块中无法使用 FILENAME 变量,这里就需要 ARGC 参数数量和
ARGV[] 参数名数组来完成对文件名的打印,这点几乎和 C 是完全一致的。最后再利用 FNR 做为下标的数组把内容拼接起来。
8.2 一个验证passwd文件有效性的例子
cat /etc/passwd |awk -F: '\
NF!=7 {printf("line %d, does not have 7 fields: %s\n", NR, $0)}\
$1 !~/[A-Za-z0-9]/ {printf("line %d, no alpha and numeric user id: %s\n", NR,
$0)}\
$2=="*" {printf("line %d, no password: %s\n", NR, $0)}'
特别说明:
\ 为续行符,去掉也可以运行,因为awk支持用换行做命令分割符。
该例子实现3个功能:
1)验证passwd文件的没行是否都是7个域
2)验证第一个字段(用户名)是否以字母或数字开头
3)验证用户是否有密码
8.3 其他简单例子
1)打印所有以no或so开头的行
awk '/^(no|so)/' test.txt
2)打印root开头到mysql开头范围内的所有记录;如果找到一个新的正则表达式root开头的记录,则继续打印直到下一个正则表达式mysql开头的记录为止,或到文件尾。
awk '/^root/, /^mysql/' test.txt
3)如果第一个域等于100或者第二个域小于50,则打印该行
awk '$1==100 || $2<50' test.txt
9 awk进阶(awk高级编程)
9.1 awk变量
awk中的变量和Shell中的变量相似,都不用事先定义,也没有类型的区别(都是字符串型),在使用时可随时进行定义。awk中的变量分以下几种:
9.1.1 字段变量
不做解释
9.1.2 内置变量
9.1.3 自定义变量
在awk语言的任意动作区间,即在{}之内,均可随时定义变量,无须事先说明。但一般情况下是在BEGIN中定义变量并赋以初值,在动作区域内使用。
awk变量也可以在awk命令行中定义并赋值:
awk –F:-f awkscript month=4 year=2004 test.txt
9.1.4 引用外部变量
例如要引用外部变量var_name,格式如下:
“’$var_name’”
或者
‘$var_name’
举例:
i = 5
awk ‘$1==”’$i’”’ file.txt
awk ‘$’$i’==”Z”’ file.txt
9.2 条件语句
9.2.1 单重判断
格式如下:
{if (expression) {
Statement
Statement
…
}
}
9.2.2 双重判断
格式如下:
{if (expression) {
Statement; statement;…
}
else {
statement
statement
…
}
}
9.2.3 多重判断
格式如下:
{if (expression) {
Statement
Statement
…
else if (expression) {
statement
statement
…
}
else if (expression) {
statement
statement
…
}
…
else {
statement
statement
…
}
}
9.3 循环语句
9.3.1 while 循环
包括两种格式:
? while
{
while (expression)
{
Statement
Statement
…
}
}
例如:
awk ‘{i=1
while (i<=3)
{
printf(“%s”, $i)
i++
}
print “” #换行
}’ test.txt
? do while
{
do
{
body
} while (expression)
}
例如:
awk ‘{i=1
do {
print $0
i++
} while (i<=10)
}’ test.txt
9.3.2 for循环
? 常规格式如下:
for (赋初值;条件;动作)
{
body
}
例如:
awk '{ for (i=1; i<=3; i++)
printf("%s ",$i)
print "" #换行
}' 文件名
? 特殊格式( special for),用于读取关联数组中的元素。
{for (item in arrayname) {
print arrayname[item]
}
}
例如:
awk ‘/^tom/ {name[NR]=$1};END {for (i in name) {print name[i]}}’ test.txt
9.3.3 其他流程控制语句
9.3.3.1 break
break 叙述会跳出包含它的 for、while、do-while 回圈的最内层。
9.3.3.2 continue
continue 叙述使用於 for、while、do-while 回圈内部,它会跳过回圈 body 的剩馀部分,使得它立刻进行下一次回圈的执行。
9.3.3.3 next
next 叙述强迫 gawk 立刻停止处理目前的记录(record)而继续下一
个记录。
9.3.3.4 next file
next file 叙述类似 next。然而,它强迫 gawk 立刻停止处理目前
的资料档。(当同时处理多个文件时,跳到下一个文件)
9.3.3.5 exit
exit 叙述会使得 gawk 程式停止执行而跳出。然而,如果 END 出现,它会去执行 END 的 actions。
9.4 数组
Awk中的数组的下标可以是数字和字母,称为关联数组。
通常用变量作为数组下标,例如:
awk ‘{name[x++]=$2}; END {for (i=0; i
字符串也可作为数组下标:count[“one”]
delete函数用于删除数组元素。例如:
awk ‘{line[x++]=$1} END {for (x in line) delete(line[x])}’ test.txt
说明:分配给数组line的是第一个域的值,所有记录处理完毕后,special for循环删除每个元素。
9.5 函数
9.5.1 内建函数
9.5.2 自定义函数
在awk中可以自定义函数,格式如下:
function name(parameter, parameter, …) {
statements
return expression #the return statement and expression are optional
}
例如:
{print "sum =", SquareSum($1,$2)}
function SquareSum(x,y) {
sum=xx+yy
return sum
}
9.6 重定向和管道
awk可以使用shell的重定向符进行重定向输出,例如:
awk ‘$1==100 {print $1 > ”out_file”} test.txt
上式表示如果第一个域的值等于100,则将它输出到文件out_file中。也可以用>>来重定向输出,但是不清空文件,只是追究。
输入重定向需要用到getline函数。getline函数从标准输入、管道或者在在处理的文件之外的其他输入文件获得输入,他负责从输入获取下一行内容,并给NF,NR和FNR等内建变量赋值。如果得到一条记录,getline函数返回1,如果到达文件结尾,返回0,如果出现错误,例如打开文件失败,则返回-1。
例如:
awk ‘BEGIN {“date” |getline d; print d}’ test.txt
awk ‘BEGIN “date” |getline d; split(d, mon); print mon[2]}’ test.txt
执行shell的date命令,并通过管道输出给getline,存储到变量d中,split函数将变量d转换成数组mon,然后打印数组的第二个元素。
awk ‘BEGIN {while(“ls” |getline) print}’
命令ls的输出传递给getline作为输入,循环使getline从ls读取每一行,并把它打印到屏幕上。这里没有输入文件,因为BEGIN模块在打开文件之前执行,所以可以忽略输入文件。
awk 'BEGIN{printf "What is your name?"; getline name < "/dev/tty"} $1 ~name
{print "Found" name on line ", NR
"."} END{print "See you," name "."} test.txt
在屏幕上打印”What is your
name?",并等待用户应答。当一行输入完毕后,getline函数从终端接收该行输入,并把它储存在自定义变量name中。
如果第一个域匹配变量name的值,print函数就被执行,END块打印Seeyou和name的值。
awk 'BEGIN{while (getline < "/etc/passwd" > 0) lc++; print lc}'
awk将逐行读取文件/etc/passwd的内容,在到达文件末尾前,计数器lc一直增加,当到末尾时,打印lc的值。
注意,如果文件不存在,getline返回-1,如果到达文件的末尾就返回0,如果读到一行,就返回1,
所以命令 while (getline < "/etc/passwd")在文件不存在的情况下将陷入无限循环,因为返回-1表示逻辑真。
可以在awk中打开一个管道,且同一时刻只能有一个管道存在。通过close()可关闭管道,如:$ awk '{print $1, $2 | "sort" }' END {close("sort")}' test.txt
awk把print语句的输出通过管道作为linux命令sort的输入,END块执行关闭管道操作。
system函数可以在awk中执行linux的命令。如:$ awk 'BEGIN{system("clear")'。
fflush函数用以刷新输出缓冲区,如果没有参数,就刷新标准输出的缓冲区,如果以空字符串为参数,如fflush(""),则刷新所有文件和管道的输出缓冲区。
10 awk复杂用例
10.1 文件最长纪录
expand filename | awk '{if (x < length()) x = length()}
END {print "maximum line length is " x}'
此程序会将指定文件里最长一行的长度输出。expand 会将 tab 改成 space,所以是用实际的右边界来做长度的比较。
10.2 暂无
11 结论
尽管AWK完全代表另外的含意,但它应该是管理员智能工具包的首字母缩写。连同 SED 一起,AWK 实用工具是 Linux
管理员所拥有的功能最强大和灵活的工具之一。通过了解其语言的一些特性,您可以开辟出能够简化任务的领域,否则这些任务将会是非常费时和困难的。
评论 (0)