awk是一条命令,也可以说是一种编程语言, 数据来源可以来自stdin,一个或多个文件。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。
本文默认你有一门编程语言的基础,如js, c\c++, php
等
从stdin读取数据
echo 123 | awk '{print}'
{} 是一个循环的语法,循环文件中的每一行,使用print默认打印每一行。
以下是test.txt文件的内容
123
456
789
awk '{print}' test.txt
输出
123
456
789
打印字符串
awk '{print "hello world"}' test.txt
# 输出
hello
hello
hello
如果使用print指令的时候可以指定变量,此时就打印变量的值
以下是传递外部变量的示例
awk '{print a}' a=hello test.txt
# 输出
hello
hello
hello
指定多个变量的值,打印多个变量时使用空格或者字符串隔开
awk '{print a aa aaa}' a=hello aa=" " aaa=world test.txt
# 输出
hello world
hello world
hello world
awk '{print a" "aaa}' a=hello aaa=world test.txt # 使用字符串隔开 " "
# 输出
hello world
hello world
hello world
awk 'BEGIN{ print "start" } pattern{ commands } END{ print "end" }' file
看起来很复杂的样子,中间的pattern我们就在上面用过
echo 123 | awk '{print}'
上面的 '{print}'
就是pattern
可以发现一共有三个语句块, BEGIN, pattern, END, 每个语句块使用{}
包裹。
BEGIN语句块
awk 'BEGIN{print "start"}' test.txt
# 输出
start
加上 pattern语句块
awk 'BEGIN{print "start"}{print}' test.txt
# 输出
start
123
456
789
加上end语句块
awk 'BEGIN{print "start"}{print}END{print "end"}' test.txt
# 输出
start
123
456
789
end
使用 -f 来指定脚本文件
比如这条指令
awk 'BEGIN{print "start"}{print}END{print "end"}' test.txt
我们可以将命令行上的脚本写入在文件中执行,写入文件有以下几个好处
test.awk 文件
BEGIN{print "start"}{print}END{print "end"}
awk -f test.awk test.txt
# 输出
start
123
456
789
end
也可以这样编写脚本,让脚本有更好的可阅读性。
test.awk
BEGIN{
print "start"
}
{
print
}
END{
print "end"
}
或者这样
BEGIN{print "start"}
{print}
END{print "end"}
使用 ;
或者回车
来执行多条指令
print指令最后都会使用换行进行输出
{
print "hello"
print "world"
}
awk '{aaa="hello";print aaa}'
\\ \自身
\$ 转义$
\t 制表符
\b 退格符
\r 回车符
\n 换行符
\c 取消换行
$0 # 当前处理的文本内容
$n # n为数字,表示第n个字段
FILENAME # 当前输入文件的名。
FS # 字段分隔符(默认是任何空格)。
NF # 表示字段数,在执行过程中对应于当前的字段数。
NR # 表示记录数,在执行过程中对应于当前的行号。
OFMT # 数字的输出格式(默认值是%.6g)。
OFS # 输出字段分隔符(默认值是一个空格)。
ORS # 输出记录分隔符(默认值是一个换行符)。
RS # 记录分隔符(默认是一个换行符)。
修改 test.txt 进行示例
1 2 3
4 5 6
7 8 9
awk '{print "line ->" $0}' test.txt
# 输出
line ->1 2 3
line ->4 5 6
line ->7 8 9
awk '{print "line ->" $1}' test.txt
# 输出
line ->1
line ->4
line ->7
awk '{print "line ->" $2}' test.txt
# 输出
line ->2
line ->5
line ->8
awk '{print "line ->" $3}' test.txt # 这条命令是什么聪明的同学们应该已经会抢答了
FS 是分割符,是任意多的空白字符,使用正则就是\s*
修改 test.txt
1 2 3
4 5 6
7 8 9
awk '{print $1 " <-> " $3}' test.txt
# 输出
1 <-> 3
4 <-> 6
7 <-> 9
很厉害有木有。即使空格不一致也能精准的获取我们想要的列
如果我们修改FS会发生什么呢,这里先修改 test.txt 文件
1:2:3:4
5:6:7:8
将FS修改为:
awk 'BEGIN{FS=":"}{print "第一列->"$1 " | 第二列->" $2}' test.txt
# 输出
第一列->1 | 第二列->2
第一列->5 | 第二列->6
此时判断列的分割符就变了
使用 -F
参数能达到和FS变量一样的效果
test.sh
awk -F ":" '{print "第一列->"$1 " | 第二列->" $2}' test.txt
NF就是记录的列数,这个多少列就是看使用FS分割符分割当前行时有多少列
修改 test.txt
1 2 3
4 5 6 7
8 9 10 11 12 13
awk '{print "第一行有 " NF " 列"}' test.txt
# 输出
第一行有 3 列
第一行有 4 列
第一行有 6 列
也可以配合 $n
来获取最后一列
awk '{print $NF}' test.txt
# 输出
3
7
13
当前操作的是哪一行
test.txt
123
456
789
awk '{print "这是第" NR "行"}' test.txt
# 输出
这是第1行
这是第2行
这是第3行
输出字段分隔符(默认值是一个空格)
test.txt
1 2 3
4 5 6
7 8 9
使用 ,
来表示输出一个字段
awk '{print $1, $2, $3}' test.txt
# 输出
1 2 3
4 5 6
7 8 9
以上输出一个字段时默认为一个空格,此时我们修改OFS的值
awk 'BEGIN{OFS=" | "}{print $1,$2,$3}' test.txt
# 输出
1 | 2 | 3
4 | 5 | 6
7 | 8 | 9
输出记录分隔符, 默认是换行符
test.txt 文件
第一行 1
第二行 2
第三行 3
awk 'BEGIN{ORS=" <-> "}{print $1}' test.txt
# 输出
第一行 <-> 第二行 <-> 第三行 <->
记录分隔符(默认是一个换行符)
test.txt
123|456|789
awk 'BEGIN{RS="|"}{print}' test.txt
# 输出
123
456
789
原本处理文件是以一行为一次处理,现在以|
分隔为一次处理
用于如何输出数字,OFMT 默认的输出格式是 “%.6g”,它表示只输出6位数字
echo 1 | awk '{print 3.123456789;print 111111.89999;}'
# 输出
3.12346
111112
可以看到输出是会进行四舍五入的,并且默认只会保存6个数字
该格式化只对数字有用, 对字符串是无效的
echo 1 | awk '{print "111111.89999";}'
# 输出
111111.89999
test.txt
3.243243434242 3.243243434242
3.243243434242 3.243243434242
awk '{print $1}' test.txt
# 输出
3.243243434242
3.243243434242
但是可以使用+号强制转换字符串为数字
awk '{print $1+0}' test.txt
# 输出
3.24324
3.24324
其实加号是用来做数字运算的
echo 1 | awk '{print 100+100}'
当然它和其他编程语言一样有其他的数字运算符。而且语法都差不多
下面是一些常见的输出格式示例:
%e
:以科学计数法表示,例如 “1.23e+03”。%.1e
:以科学计数法表示,保留一位小数%.g
: 等同与 %.e
, 不保留小数的科学计数法%g
:自动选择合适的显示方式,可以根据数值大小自动切换为普通表示法或科学计数法。%.1g
:将数值四舍五入为整数并输出。用于输出当前处理的文件的文件名
思考为什么会这样输出?
test.txt
2003.3.22|2002.1.14|2004.3.22
test.awk
BEGIN{FS=".";RS="|"}
{print $1 "年" $2 "月" $3 "日"}
awk -f test.awk test.txt
# 思考为什么会这样输出
2003年3月22日
2002年1月14日
2004年1月22日
$n 输出的是一个字段
$0 输出的是一条记录
数据变量设置的为数据
$0 # 当前记录
$n # 第几个字段
FILENAME # 当前输入文件的名。
NF # 表示字段数,在执行过程中对应于当前的字段数。
NR # 表示记录数,在执行过程中对应于当前的行号。
数据变量中设置的字符用来控制awk如何识别字段和记录
FS # 字段分隔符(默认是任何空格)。
RS # 记录分隔符(默认是一个换行符)。
输出格式变量用来指示awk应该如何输出字段和记录
OFS # 输出字段分隔符(默认值是一个空格)。
ORS # 输出记录分隔符(默认值是一个换行符)。
OFMT # 数字的输出格式(默认值是%.6g)。
运算符 | 描述 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
^ | 幂 |
% | 取余 |
运算符 |
---|
+= |
-= |
*= |
/= |
^= |
%= |
示例
test.awk
{
print "# 赋值运算符会对变量本身进行修改"
a = 1
print "a = 1 | a=" a
a += 1
print "a += 1 | a=" a
a -= 1
print "a -= 1 | a=" a
a *= 10
print "a *= 10 | a=" a
a /= 2
print "a /= 2 | a=" a
a %= 3
print "a %= 3 | a=" a
a ^= 3
print "a ^= 3 | a=" a
a++
print "a++ | a=" a
a--
print "a-- | a=" a
print "\n--------------------------\n"
print "# 算数运算符不会对变量进行修改"
print "a+5: " a+5
print "a-3: " a-3
print "a*2: " a*2
print "a/2: " a/2
print "a^2: " a^2
print "a%3: " a%3
}
运行结果
# 赋值运算符会对变量本身进行修改
a = 1 | a=1
a += 1 | a=2
a -= 1 | a=1
a *= 10 | a=10
a /= 2 | a=5
a %= 3 | a=2
a ^= 3 | a=8
a++ | a=9
a-- | a=8
--------------------------
# 算数运算符不会对变量进行修改
a+5: 13
a-3: 5
a*2: 16
a/2: 4
a^2: 64
a%3: 2
在awk中,使用非零
和0
来表示真和假
该布尔值写在{}
的前面
test2.txt
0{
print "为假不执行"
}
1{
print "非0为真"
}
1+5{
print "6也是非0"
}
echo 1 | awk -f test2.awk
# 输出
非0为真
6也是非0
运算符 | 描述 |
---|---|
> | 大于 |
< | 小于 |
== | 等于 |
!= | 不等于 |
>= | 大于等于 |
<= | 小于等于 |
举例
test2.awk
1 != 1 {
print 0
}
1 == 1{
print 1
}
echo | awk -f test2.awk
# 输出
1
运算符 | 描述 |
---|---|
|| | 逻辑或,一边条件为真则为真 |
&& | 逻辑与,一边为假则为假 |
! | 逻辑非,真为假 |
test2.awk
1 < 0 || 1==1 {
print "||"
}
1 < 0 && 1==1{
print "&&"
}
echo | awk -f test2.awk
# 输出
||
可以使用()来调整优先级
!(1==1 && 1<3){
print 1
}
还可以使用正则表达式来进行判断, 正则表达式使用
//
包裹
判断字符串是否包含hello
test2.awk
"hhhh! hello world!" ~ /hello/{
print 1
}
echo | awk -f test2.awk
# 输出
1
运算符 | 描述 |
---|---|
~ | 匹配该正则表达式 |
!~ | 不匹配该正则表达式 |
不输出开头有 total
的行
ls -l | awk '{if($0 !~ /^total/){print $0}}'
这里使用了if语句
该语句的结构为
if(条件){指令}
以下是正则表达式的语法
^ 行首
$ 行尾
. 除了换行符以外的任意单个字符
* 前导字符的零个或多个
.* 所有字符
[] 字符组内的任一字符
[^] 对字符组内的每个字符取反(不匹配字符组内的每个字符)
^[^] 非字符组内的字符开头的行
[a-z] 小写字母
[A-Z] 大写字母
[a-Z] 小写和大写字母
[0-9] 数字
\< 单词头单词一般以空格或特殊字符做分隔,连续的字符串被当做单词
\> 单词尾
echo 1 | awk '{print 1<2 ? "真" : "假"}'
# 输出
真
字符串使用一个或多个空格连接
print "hello" "-" "world"
next进行下一条记录处理
{next}{print}
此时后面的print不会执行
echo| awk '{print 123}{next}{print 456}'
# 输出
123
如果你要对数组初始化,那么可能没法像其他语言一样直接声明进行初始化。
a[0] = 1
a[1] = 2
a["数据"] = "数据"
print a[0] ,a[1], a["数据"]
# 输出
1 2 数据
a["数据",0] = 1
a["数据",0,3] = 2
print a["数据",0],a["数据",0,3]
a[0,0]
就等同与c语言中的 a[0][0]
我们常见的循环有for、while、do while, awk当然也有
for循环有两种格式
for(变量 in 数组)
{语句}
示例
{
a[9] = 1
a[2] = 2
a[0] = 3
a["你好"] = 3
a["b"] = 3
a["a"] = 3
for(i in a){
print i
}
# print a["数据",0],a["数据",0,3]
# getline < "/etc/passwd"
# print
}
以上会输出所有的键
# 输出
你好
a
b
0
2
9
不过键的位置并不是按你初始化的来,而且有一定顺序。
for(变量;条件;表达式)
{语句}
{
a=0;
for(i=0; i<=100; i++){
a+=i;
}
print a;
}
while(条件)
{语句}
示例
{
i = 1
while(i < 4){
print i++
}
}
do{语句}
while(条件)
示例
{
i=0
do{
print i++
}
while(i < 4)
}
awk的continue和break用法和其他语言一样用于跳过一次循环和中断循环
我们在处理文件的时候可能需要从其它文件进行内容的读取
此时我们需要用到getline这个函数
getline有3种使用方式,格式为
getline [变量] < 文件
命令 | getline [变量]
getline [变量]
其中变量都是可选值,可以指定也可以不指定
有变量时
getline v < "/etc/passwd"
此时会将第一行的文本读取出来,并保存在变量v上
再次读取时会读取第二含
getline v < "/etc/passwd"
getline v2 < "/etc/passwd"
无变量时
{
getline < "/etc/passwd"
print
}
没有变量的时候会设置一些内置变量($0 , $n
),这些内置变量同样受FS等变量的影响
{
FS=":"
getline < "/etc/passwd"
print $0
print $1
}
print
本质就是输出$0
,所以没有指定变量时,直接使用print也能输出文件中的一行,但是如果指定了变量就会保存在变量中,直接使用print并不会输出getline中读取的内容。
而且再次读取也是读取下一行,和getline v < "/etc/passwd"
一样
有变量时
"cat /etc/passwd" | getline v
print v
这里的输出依旧是第一行
"cat /etc/passwd" | getline v
print v
"cat /etc/passwd" | getline v2
print v2
这里输出文件的前两行,会发现和 getline v < "/etc/passwd"
大同小异,不过前面的字符串指定的是一个命令,更加灵活
无变量时
没有变脸,那么就会将数据设置在$0 和 $n
上,所以和 getline < "/etc/passwd"
也是类似的
有变量
getline v
与前面不同的是,此时读取的数据是从当前文本进行读取
如
awk -f test.awk test.txt
那么getline 就是读取的test.txt 文件的内容,但是并不是读取的第一行,因为第一行在最开始被awk读取了,所以此时指针指向了下一行,getline此时读取的是第二行。同时下一轮读取的时候,awk会读取第三行,你会发现,他们是互相影响的
echo -e "1\n2\n3\n4\n5" | awk -f test.awk
test.awk
getline v
print "getline: " v
输出
getline: 2
getline: 4
getline: 4
最后你会发现输出了两个4,为什么呢?
我的猜测: awk最后读取了5,getline此时的指针还在第4行,读取第5行发现awk读取过了,就读取第6行,发现6行为空,就返回false,此时指针还在第4行,所以输出4
无变量时
读到这里的同学已经很聪明了,所以你们肯定会抢答啦,但是确实这里稍微会有点不同
echo -e "1\n2\n3\n4\n5" | awk -f test.awk
test.awk
{
print "getline 前: " $0
getline
print "getline 后: " $0
}
输出
getline 前: 1
getline 后: 2
getline 前: 3
getline 后: 4
getline 前: 5
getline 后: 5
能给出的解释是: getline
和awk
共用了一个指针!
更多用法在后面进行更新