2018-07-04 12:08:23 +03:00
|
|
|
---
|
2019-03-08 19:18:42 +03:00
|
|
|
category: tool
|
|
|
|
tool: awk
|
2018-07-04 12:08:23 +03:00
|
|
|
contributors:
|
|
|
|
- ["Marshall Mason", "http://github.com/marshallmason"]
|
|
|
|
translators:
|
|
|
|
- ["Tian Zhipeng", "https://github.com/tianzhipeng-git"]
|
|
|
|
filename: learnawk-cn.awk
|
|
|
|
lang: zh-cn
|
|
|
|
---
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
AWK是POSIX兼容的UNIX系统中的标准工具. 它像简化版的Perl, 非常适用于文本处理任务和其他脚本类需求.
|
|
|
|
它有着C风格的语法, 但是没有分号, 没有手动内存管理, 没有静态类型.
|
|
|
|
他擅长于文本处理, 你可以通过shell脚本调用AWK, 也可以用作独立的脚本语言.
|
|
|
|
|
|
|
|
为什么使用AWK而不是Perl, 大概是因为AWK是UNIX的一部分, 你总能依靠它, 而Perl已经前途未卜了.
|
|
|
|
AWK比Perl更易读. 对于简单的文本处理脚本, 特别是按行读取文件, 按分隔符分隔处理, AWK极可能是正确的工具.
|
2018-07-04 12:08:23 +03:00
|
|
|
|
|
|
|
```awk
|
|
|
|
#!/usr/bin/awk -f
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 注释使用井号
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# AWK程序由一系列 模式(patterns) 和 动作(actions) 组成.
|
|
|
|
# 最重要的模式叫做 BEGIN. 动作由大括号包围.
|
2018-07-04 12:08:23 +03:00
|
|
|
BEGIN {
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# BEGIN在程序最开始运行. 在这里放一些在真正处理文件之前的准备和setup的代码.
|
|
|
|
# 如果没有文本文件要处理, 那就把BEGIN作为程序的主入口吧.
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 变量是全局的. 直接赋值使用即可, 无需声明.
|
2018-07-04 12:08:23 +03:00
|
|
|
count = 0
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 运算符和C语言系一样
|
2018-07-04 12:08:23 +03:00
|
|
|
a = count + 1
|
|
|
|
b = count - 1
|
|
|
|
c = count * 1
|
2018-07-04 16:56:04 +03:00
|
|
|
d = count / 1 # 整数除法
|
|
|
|
e = count % 1 # 取余
|
|
|
|
f = count ^ 1 # 取幂
|
2018-07-04 12:08:23 +03:00
|
|
|
|
|
|
|
a += 1
|
|
|
|
b -= 1
|
|
|
|
c *= 1
|
|
|
|
d /= 1
|
|
|
|
e %= 1
|
|
|
|
f ^= 1
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 自增1, 自减1
|
2018-07-04 12:08:23 +03:00
|
|
|
a++
|
|
|
|
b--
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 前置运算, 返回增加之后的值
|
2018-07-04 12:08:23 +03:00
|
|
|
++a
|
|
|
|
--b
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 注意, 不需要分号之类的标点来分隔语句
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 控制语句
|
2018-07-04 12:08:23 +03:00
|
|
|
if (count == 0)
|
|
|
|
print "Starting with count of 0"
|
|
|
|
else
|
|
|
|
print "Huh?"
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 或者三目运算符
|
2018-07-04 12:08:23 +03:00
|
|
|
print (count == 0) ? "Starting with count of 0" : "Huh?"
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 多行的代码块用大括号包围
|
2018-07-04 12:08:23 +03:00
|
|
|
while (a < 10) {
|
|
|
|
print "String concatenation is done" " with a series" " of"
|
|
|
|
" space-separated strings"
|
|
|
|
print a
|
|
|
|
|
|
|
|
a++
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 10; i++)
|
|
|
|
print "Good ol' for loop"
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 标准的比较运算符
|
|
|
|
a < b # 小于
|
|
|
|
a <= b # 小于或等于
|
|
|
|
a != b # 不等于
|
|
|
|
a == b # 等于
|
|
|
|
a > b # 大于
|
|
|
|
a >= b # 大于或等于
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 也有逻辑运算符
|
|
|
|
a && b # 且
|
|
|
|
a || b # 或
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 并且有超实用的正则表达式匹配
|
2018-07-04 12:08:23 +03:00
|
|
|
if ("foo" ~ "^fo+$")
|
|
|
|
print "Fooey!"
|
|
|
|
if ("boo" !~ "^fo+$")
|
|
|
|
print "Boo!"
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 数组
|
2018-07-04 12:08:23 +03:00
|
|
|
arr[0] = "foo"
|
|
|
|
arr[1] = "bar"
|
2018-07-04 16:56:04 +03:00
|
|
|
# 不幸的是, 没有其他方式初始化数组. 必须像这样一行一行的赋值.
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 关联数组, 类似map或dict的用法.
|
2018-07-04 12:08:23 +03:00
|
|
|
assoc["foo"] = "bar"
|
|
|
|
assoc["bar"] = "baz"
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 多维数组. 但是有一些局限性这里不提了.
|
2018-07-04 12:08:23 +03:00
|
|
|
multidim[0,0] = "foo"
|
|
|
|
multidim[0,1] = "bar"
|
|
|
|
multidim[1,0] = "baz"
|
|
|
|
multidim[1,1] = "boo"
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 可以检测数组包含关系
|
2018-07-04 12:08:23 +03:00
|
|
|
if ("foo" in assoc)
|
|
|
|
print "Fooey!"
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 可以使用in遍历数组
|
2018-07-04 12:08:23 +03:00
|
|
|
for (key in assoc)
|
|
|
|
print assoc[key]
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 命令行参数是一个叫ARGV的数组
|
2018-07-04 12:08:23 +03:00
|
|
|
for (argnum in ARGV)
|
|
|
|
print ARGV[argnum]
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 可以从数组中移除元素
|
|
|
|
# 在 防止awk把文件参数当做数据来处理 时delete功能很有用.
|
2018-07-04 12:08:23 +03:00
|
|
|
delete ARGV[1]
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 命令行参数的个数是一个叫ARGC的变量
|
2018-07-04 12:08:23 +03:00
|
|
|
print ARGC
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# AWK有很多内置函数, 分为三类, 会在接下来定义的各个函数中介绍.
|
2018-07-04 12:08:23 +03:00
|
|
|
|
|
|
|
return_value = arithmetic_functions(a, b, c)
|
|
|
|
string_functions()
|
|
|
|
io_functions()
|
|
|
|
}
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 定义函数
|
2018-07-04 12:08:23 +03:00
|
|
|
function arithmetic_functions(a, b, c, d) {
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 或许AWK最让人恼火的地方是没有局部变量, 所有东西都是全局的,
|
|
|
|
# 对于短的脚本还好, 对于长一些的就会成问题.
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 这里有一个技巧, 函数参数是对函数局部可见的, 并且AWK允许定义多余的参数,
|
|
|
|
# 因此可以像上面那样把局部变量插入到函数声明中.
|
|
|
|
# 为了方便区分普通参数(a,b,c)和局部变量(d), 可以多键入一些空格.
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 现在介绍数学类函数
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 多数AWK实现中包含标准的三角函数
|
2018-07-04 12:08:23 +03:00
|
|
|
localvar = sin(a)
|
|
|
|
localvar = cos(a)
|
|
|
|
localvar = atan2(a, b) # arc tangent of b / a
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 对数
|
2018-07-04 12:08:23 +03:00
|
|
|
localvar = exp(a)
|
|
|
|
localvar = log(a)
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 平方根
|
2018-07-04 12:08:23 +03:00
|
|
|
localvar = sqrt(a)
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 浮点型转为整型
|
2018-07-04 12:08:23 +03:00
|
|
|
localvar = int(5.34) # localvar => 5
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 随机数
|
|
|
|
srand() # 接受随机种子作为参数, 默认使用当天的时间
|
|
|
|
localvar = rand() # 0到1之间随机
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 函数返回
|
2018-07-04 12:08:23 +03:00
|
|
|
return localvar
|
|
|
|
}
|
|
|
|
|
|
|
|
function string_functions( localvar, arr) {
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# AWK, 作为字符处理语言, 有很多字符串相关函数, 其中大多数都严重依赖正则表达式.
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 搜索并替换, 第一个出现的 (sub) or 所有的 (gsub)
|
|
|
|
# 都是返回替换的个数
|
2018-07-04 12:08:23 +03:00
|
|
|
localvar = "fooooobar"
|
|
|
|
sub("fo+", "Meet me at the ", localvar) # localvar => "Meet me at the bar"
|
2022-07-11 16:13:35 +03:00
|
|
|
gsub("e", ".", localvar) # localvar => "m..t m. at th. bar"
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 搜索匹配正则的字符串
|
|
|
|
# index() 也是搜索, 不支持正则
|
2018-09-14 11:55:28 +03:00
|
|
|
match(localvar, "t") # => 4, 't'在4号位置.
|
|
|
|
# (译者注: awk是1开始计数的,不是常见的0-base)
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 按分隔符分隔
|
2018-07-04 12:08:23 +03:00
|
|
|
split("foo-bar-baz", arr, "-") # a => ["foo", "bar", "baz"]
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 其他有用的函数
|
2018-07-04 12:08:23 +03:00
|
|
|
sprintf("%s %d %d %d", "Testing", 1, 2, 3) # => "Testing 1 2 3"
|
|
|
|
substr("foobar", 2, 3) # => "oob"
|
|
|
|
substr("foobar", 4) # => "bar"
|
|
|
|
length("foo") # => 3
|
|
|
|
tolower("FOO") # => "foo"
|
|
|
|
toupper("foo") # => "FOO"
|
|
|
|
}
|
|
|
|
|
|
|
|
function io_functions( localvar) {
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 你已经见过的print函数
|
2018-07-04 12:08:23 +03:00
|
|
|
print "Hello world"
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 也有printf
|
2018-07-04 12:08:23 +03:00
|
|
|
printf("%s %d %d %d\n", "Testing", 1, 2, 3)
|
|
|
|
|
2018-09-14 11:55:28 +03:00
|
|
|
# AWK本身没有文件句柄, 当你使用需要文件的东西时会自动打开文件,
|
|
|
|
# 做文件I/O时, 字符串就是打开的文件句柄. 这看起来像Shell
|
2018-07-04 12:08:23 +03:00
|
|
|
print "foobar" >"/tmp/foobar.txt"
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 现在"/tmp/foobar.txt"字符串是一个文件句柄, 你可以关闭它
|
2018-07-04 12:08:23 +03:00
|
|
|
close("/tmp/foobar.txt")
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 在shell里运行一些东西
|
2018-07-04 12:08:23 +03:00
|
|
|
system("echo foobar") # => prints foobar
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 从标准输入中读一行, 并存储在localvar中
|
2018-07-04 12:08:23 +03:00
|
|
|
getline localvar
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 从管道中读一行, 并存储在localvar中
|
2018-07-04 12:08:23 +03:00
|
|
|
"echo foobar" | getline localvar # localvar => "foobar"
|
|
|
|
close("echo foobar")
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 从文件中读一行, 并存储在localvar中
|
2018-07-04 12:08:23 +03:00
|
|
|
getline localvar <"/tmp/foobar.txt"
|
|
|
|
close("/tmp/foobar.txt")
|
|
|
|
}
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 正如开头所说, AWK程序由一系列模式和动作组成. 你已经看见了重要的BEGIN pattern,
|
|
|
|
# 其他的pattern在你需要处理来自文件或标准输入的的数据行时才用到.
|
|
|
|
#
|
|
|
|
# 当你给AWK程序传参数时, 他们会被视为要处理文件的文件名, 按顺序全部会处理.
|
|
|
|
# 可以把这个过程看做一个隐式的循环, 遍历这些文件中的所有行.
|
|
|
|
# 然后这些模式和动作就是这个循环里的switch语句一样
|
2018-07-04 12:08:23 +03:00
|
|
|
|
|
|
|
/^fo+bar$/ {
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 这个动作会在匹配这个正则(/^fo+bar$/)的每一行上执行. 不匹配的则会跳过.
|
|
|
|
# 先让我们打印它:
|
2018-07-04 12:08:23 +03:00
|
|
|
print
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 哦, 没有参数, 那是因为print有一个默认参数 $0.
|
|
|
|
# $0 是当前正在处理的行, 自动被创建好了.
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 你可能猜到有其他的$变量了.
|
|
|
|
# 每一行在动作执行前会被分隔符分隔. 像shell中一样, 每个字段都可以用$符访问
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 这个会打印这行的第2和第4个字段
|
2018-07-04 12:08:23 +03:00
|
|
|
print $2, $4
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# AWK自动定义了许多其他的变量帮助你处理行. 最常用的是NF变量
|
|
|
|
# 打印这一行的字段数
|
2018-07-04 12:08:23 +03:00
|
|
|
print NF
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 打印这一行的最后一个字段
|
2018-07-04 12:08:23 +03:00
|
|
|
print $NF
|
|
|
|
}
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 每一个模式其实是一个true/false判断, 上面那个正则其实也是一个true/false判断, 只不过被部分省略了.
|
|
|
|
# 没有指定时默认使用当前处理的整行($0)进行匹配. 因此, 完全版本是这样:
|
2018-07-04 12:08:23 +03:00
|
|
|
|
|
|
|
$0 ~ /^fo+bar$/ {
|
|
|
|
print "Equivalent to the last pattern"
|
|
|
|
}
|
|
|
|
|
|
|
|
a > 0 {
|
2018-07-04 16:56:04 +03:00
|
|
|
# 只要a是整数, 这块会在每一行上执行.
|
2018-07-04 12:08:23 +03:00
|
|
|
}
|
|
|
|
|
2018-09-14 11:55:28 +03:00
|
|
|
# 就是这样, 处理文本文件, 一次读一行, 对行做一些操作.
|
|
|
|
# 按分隔符分隔, 这在UNIX中很常见, awk都帮你做好了.
|
2018-07-04 16:56:04 +03:00
|
|
|
# 你所需要做的是基于自己的需求写一些模式和动作.
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 这里有一个快速的例子, 展示了AWK所擅长做的事.
|
|
|
|
# 它从标准输入读一个名字, 打印这个first name下所有人的平均年龄.
|
|
|
|
# 示例数据:
|
2018-07-04 12:08:23 +03:00
|
|
|
#
|
|
|
|
# Bob Jones 32
|
|
|
|
# Jane Doe 22
|
|
|
|
# Steve Stevens 83
|
|
|
|
# Bob Smith 29
|
|
|
|
# Bob Barker 72
|
|
|
|
#
|
2018-07-04 16:56:04 +03:00
|
|
|
# 示例脚本:
|
2018-07-04 12:08:23 +03:00
|
|
|
|
|
|
|
BEGIN {
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 首先, 问用户要一个名字
|
2018-07-04 12:08:23 +03:00
|
|
|
print "What name would you like the average age for?"
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 从标准输入获取名字
|
2018-07-04 12:08:23 +03:00
|
|
|
getline name <"/dev/stdin"
|
|
|
|
}
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 然后, 用给定的名字匹配每一行的第一个字段.
|
2018-07-04 12:08:23 +03:00
|
|
|
$1 == name {
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 这里我们要使用几个有用的变量, 已经提前为我们加载好的:
|
|
|
|
# $0 是整行
|
|
|
|
# $3 是第三个字段, 就是我们所感兴趣的年龄
|
|
|
|
# NF 字段数, 这里是3
|
|
|
|
# NR 至此为止的行数
|
|
|
|
# FILENAME 在处理的文件名
|
|
|
|
# FS 在使用的字段分隔符, 这里是空格" "
|
|
|
|
# ...等等, 还有很多, 在帮助文档中列出.
|
|
|
|
|
|
|
|
# 跟踪 总和以及行数
|
2018-07-04 12:08:23 +03:00
|
|
|
sum += $3
|
|
|
|
nlines++
|
|
|
|
}
|
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
# 另一个特殊的模式叫END. 它会在处理完所有行之后运行. 不像BEGIN, 它只会在有输入的时候运行.
|
|
|
|
# 它在所有文件依据给定的模式和动作处理完后运行, 目的通常是输出一些最终报告, 做一些数据聚合操作.
|
2018-07-04 12:08:23 +03:00
|
|
|
|
|
|
|
END {
|
|
|
|
if (nlines)
|
|
|
|
print "The average age for " name " is " sum / nlines
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
2018-07-04 16:56:04 +03:00
|
|
|
更多:
|
2018-07-04 12:08:23 +03:00
|
|
|
|
2018-07-04 16:56:04 +03:00
|
|
|
* [Awk 教程](http://www.grymoire.com/Unix/Awk.html)
|
|
|
|
* [Awk 手册](https://linux.die.net/man/1/awk)
|
|
|
|
* [The GNU Awk 用户指南](https://www.gnu.org/software/gawk/manual/gawk.html) GNU Awk在大多数Linux中预装
|