Linux三剑客
Linux
运维想做好,三剑客少不了。所谓Linux
三剑客,指的是三个Linux
程序:grep
、sed
和awk
。这三个程序都是处理文本时所用的。因为三个命令具有互补性,经常一起使用,因此称为Linux
三剑客。
具体来说,三剑客的功能分别是:
grep
:擅长单纯的查找、匹配文本sed
:擅长编辑、处理匹配到的文本awk
:擅长格式化文本,对文本进行复杂的处理
下面我们将详细介绍Linux
三剑客。
文本过滤/搜索器:grep
grep (Global search Regular Expression(RE) and Print out the line
),直白的翻译就是全面搜索正则表达式并把行打印出来
。grep
其实是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。
通常,我们使用grep
来过滤、搜索的特定字符。正是因为grep
能够使用正则表达式,因此grep
+ 正则表达式 + 多种命令,就可以非常灵活的使用。
1. 基础用法:查找单个文件
grep
的基础语法是
grep [option] [pattern] file
其中:
option
是命令行选项partter
是要查找的内容或者正则表达式,统称为模式file
是将被查找的文件
当然,一般来说更常见的用法是下面这种:
grep [pattern] file [option]
因为这种用法更加灵活,我们先写好需要查询的模式,而后再根据需要写好选项。
无参数查找文本
使用grep
的基础语法,会待查找到内容输出到屏幕上
cat /etc/passwd > test.txt
grep "root" test.txt
也可以使用正则表达式来匹配
cat /etc/passwd > test.txt
grep "^#" test.txt
查找多个文件
事实上,grep
支持从多个文件中查询文本。只需要给出多个文件即可,即
grep [option] [pattern] file1 file2 file3 ...
例如
cat /etc/passwd > test1.txt
cat /etc/ssh/sshd_config >> test2.txt
grep "root" test1.txt test2.txt
事实上,我们可以使用Bash
的花括号扩展来直接多次查询
cat /etc/passwd > test1.txt
cat /etc/ssh/sshd_config >> test2.txt
grep "root" test{1,2}.txt
-E 使用扩展正则表达式
如果要使用扩展正则表达式的话,需要使用-E
参数
cat /etc/passwd > test.txt
grep -e "(root)+" -e "(.:)" test.txt -i -n -E
-i 忽略大小写
使用-i
参数忽略大小写
cat /etc/passwd > test.txt
grep -i "root" test.txt
-n 显示行数
使用-n
参数显示匹配的行的行号
cat /etc/passwd > test.txt
grep -n "root" test.txt
-c 统计行数
使用-c
参数输出匹配到的行数
cat /etc/passwd > test.txt
grep -c "root" test.txt
-v 反向匹配
使用-v
来反向匹配,即排除给定的关键字
cat /etc/passwd > test.txt
grep -v "root" test.txt
-e 多个模式匹配
使用-e
参数匹配多个模式
cat /etc/passwd > test.txt
grep -e "root" -e "install" test.txt -i -n
-o 输出匹配内容
使用-o
参数输出匹配到的内容,一般和-n
联合使用
cat /etc/passwd > test.txt
grep -o "install" test.txt -i -n
-q 条件测试
一般grep
的结果都会在屏幕上输出,使用-q
将会强制不输出。
一般来说使用-q
参数都是为了在shell
脚本中使用,用于条件测试。例如:
cat /etc/passwd > test.txt
grep "root" test.txt -q
echo $?
使用-q
参数则不会输出任何信息,如果命令运行成功返回0,失败则返回非0值。换而言之,如果在文件中找到了要查找的内容,则返回0,否则返回其他非0值。一般用于条件测试。
运行结果为:
2. 进阶用法:递归查找
有时候,我们需要对一个目录中的所有文件全部进行查找。可能有人会说,我们直接写出多个文件不就可以了么?
但是更多的时候,我们需要对一个目录树进行递归查找,例如:
tree ./ -I build
当前文件夹下既有文件,也有文件夹,而文件夹下又有文件,单纯的罗列出当前文件夹下所有的文件就很蠢笨了。
所以,正式我们可能需要对一个文件夹进行递归查找,而非多文件查找,grep
提供了特殊的功能。
-r 递归查找
使用-r
参数进行递归查找,同时grep
命令的语法格式为
grep [option] [pattern] folder -r
-r
参数放在option
中还是单独放在后面都没问题,但是一般更常用的是下面这种写法
grep [pattern] folder -r [option]
例如查找tools
文件夹下所有有echo
的内容
grep "echo" ./tools -r -n
3. 管道符结合其他命令
事实上,grep
如果只是查找文件的话,那么可能还不是那么强大,grep
真正强大的地方就再无,我们使用管道符可以把前一个命令输出到屏幕上的内容作为输入,而后进行查询。
此时,grep
的语法为
command | grep [option] [pattern]
其中:
option
是命令行选项partter
是要查找的内容或者正则表达式,统称为模式
当然,一般来说更常见的写法依旧是
command | grep [pattren] [option]
原因么还是一样的,先确定好pattern
,然后再根据我们的需要给出option
多次筛选/查找文件
grep
命令本身会将查找的结果输出在屏幕上,因此command
当然可以是grep
命令。
单独使用一次grep
只能够对文本进行一次筛选,但是结合管道符|
就可以进行多次筛选。
例如下面我们先排除空行,然后再排除echo
的行
wget https://raw.githubusercontent.com/jackwang0108/CS537-Project3/main/tools/autobuild.sh -O test.txt
cat test.txt
grep -v "^$" test.txt | grep -v "^echo" -n
搜索文件
grep
和ls
结合,就可以实现查找符合指定模式的文件。而我们找到所有的文件名后,可以在用xargs
进一步去用一个新的程序处理这些文件,或者使用awk
去生成报告。
例如我们下面找出/etc
中所有的配置文件。配置文件一般的名字就是run config
,所以一般都是以rc
为名。
ls /etc/ | grep "rc"
再举一个例子,我们找出所有QQ下载的图片
ls ~/Pictures | grep "QQ" -i
流编辑器:sed
sed
的全称是Stream Editor
,即流编辑器。它是操作、过滤和转换文本内容的强大工具。一般来说,我们通过grep
和ls
命令获取到文件的名称之后,接下来就可以把文件名传给sed
,而后由sed
来处理文件。
详细的说,sed
是一种流编辑器,它是文本处理中非常重要的工具,能够完美的配合正则表达式使用,功能不同凡响。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space
),接着用sed
命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。
sed
处理文件时,文件的内容并没有改变,除非使用重定向存储输出。sed
主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。
简单的来说,sed
功能结合正则表达式对文件试试快速增删改查。其中查询功能中最常用的两大功能是:
- 过滤:过滤指定字符串
- 取行:取出指定的行
sed
的工作模式如下图所示:
- 第一步:
sed
逐行读取输入的文件(也可以管道传入的上一个文件的输出) - 第二步:对读入的行进行模式匹配
- 第三步:若匹配成功则按照指定的方式进行编辑
- 第四步:若匹配失败则输出到屏幕(可以取消)
举一个形象的例子:
1. sed介绍
sed
的基础语法为:
sed [option] [command] [file]
其中:
option
是命令行选项command
是sed
命令,指定了sed
处理文本的方式,例如删除、增加等等file
是将源文件
一个简单的例子为:
echo "First Line.\nThis is my book.\nLook at my book.\nBefore last line.\nLast Line." > test.txt
cat test.txt
sed '1d;$d' test.txt
其中:
1d
和$d
是sed
命令,表示删除第一行和第二行
2. sed参数
sed
中常用的参数如下(先写这么多,用到了再增加):
参数 | 作用 |
---|---|
-n |
sed 默认会将未匹配成功的行输出,使用-n 参数则将不会输出匹配失败的行。 |
-i |
sed 默认是对文本的副本进行处理的,所以源文件不会受到sed 的影响。如果我们想要修改源文件,那么就需要加上-i 参数。 |
-e |
类似于grep 命令使用-e 指定多个模式,sed 命令也可以使用-e 选项来制定多个编辑命令。 |
-r |
grep 中使用-E 来支持扩展正则表达式,而sed 使用-r 来支持扩展正则表达式 |
3. sed命令
一个sed
命令由两部分组成:范围命令+操作命令
,例如:2p
表示将第二行的内容打印出来。
多个sed
命令可以使用;
连接。
操作命令
sed
中的操作命令具体有(目前先写这么多,后面用到了再加):
命令 | 作用 |
---|---|
a |
在当前行下面插入文本,即append |
i |
在当前行上面插入文本,即insert |
c |
把选定的行改为新的文本,即change |
d |
删除选定的行,即delete,一般不和-n 参数一起使用 |
p |
打印匹配的行的内容,即print,通常与-n 参数一起使用 |
s/正则/替换内容/g |
将匹配到的正则内容替换为替换内容,g 表示所有匹配到的都进行替换,否则仅替换一个此外,也可以是 s#正则#替换内容#g ,或者是s@正则@替换内容@g |
范围命令
sed
中的范围命令有(先写这么多,用到了再加):
命令 | 作用 |
---|---|
空范围 | 全文 |
num |
第num 行 |
$ |
最后一行 |
/pattern/ |
模式匹配成功的行 |
start,end |
从start 行到end 行 |
start,+offset |
从start 行到start+offset 行 |
start~leap |
从start 行开始以leap 为间隔的航 |
4. sed案例
下面我们将给出一些sed
的使用案例。
注意:
macOS
中echo
会自动转移\n
,但是在Linux
中需要加上-e
参数- 此外,
macOS
上原生的sed
和GNU
的gnn-sed
不同,如果要在macOS
上使用GNU
的sed
,使用brew install gnu-sed
然后使用gsed
即可
输出文件第二、三行内容
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
sed -n "2p;3p" test.txt
由于sed
默认是:
- 对于匹配成功的行,处理后打印
- 对于匹配失败的行,直接打印
所以如果我们没有指定-n
参数,那么结果如下:
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
sed "2p;3p" test.txt
发现第二、第三行输出了两次,这其实就是sed
的两条路径的处理方式。
输出含有指定单词的行
sed
中可以利用正则表达式来指定行,因此我们可以使用sed
输出含有指定单词的行。
例如:
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
sed -n '/like/p' test.txt
结果如下:
删除含有特定单词的行
查找特定单词简单,使用正则表达式即可,删除则使用d
操作命令即可。
例如删除所有含有like
的行:
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
sed '/like/d' test.txt
结果如下:
输出第三到最后一行
在输出范围中使用$
来表示最后一行
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
sed -n '3,$p' test.txt
运行结果如下:
替换某个单词
使用s
操作命令来进行替换
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
cat test.txt
sed -n 's/My/His/g' test.txt
运行结果如下:
我们可以使用-e
来指定多个sed
命令
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
cat test.txt
sed -n 's/My/His/g' test.txt
增加(Append)一行
使用a
操作命令来在某行后面添加一行。
例如在第二行后添加My Linux skill is good.
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
sed '2a My Linux skill is good.' test.txt
运行结果如下:
再举一个例子,在每行后增加----------
作为分隔符。
因为要匹配每行,直接使用空地址即可
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
gsed "a ----------------" test.txt
但是注意,macOS
原生的sed
在a
之后必须要加\ + <Enter>
换行来输入需要插入的内容
例如:
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
sed '2a\<回车键
quote> My Linux skill is good' test.txt
运行结果如下:
插入(Insert)一行
我们接下来使用insert
插入一行
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
gsed "4i My phone number is 00000000" test.txt
类似于a
,i
的表现在Linux
和macOS
上也不同,macOS
上的sed
的i
表现如下:
echo "My name is Jack Wang.\nI like Linux.\nI like writing computer programs.\nMy email is 123456789@gmail.com.\nMy website is http://jackwang.cafe ." > test.txt
sed '4i\<回车键
quote> My phone number is 00000000' test.txt
运行结果为:
5. sed进阶案例
我们上面都只讲解了sed
的基础用法,下面我们将讲解sed
的进阶用法。
获取行内内容:掐头去尾
我们前面介绍的sed
的用法,都是针对行进行操作的。但是很多时候,我们往往只需要获取一行中的某个内容。
例如我们使用ifconfig
查看计算机在当前网络下的IP
地址
ifconfig
但是我们在Shell
脚本中往往只需要本机的IP
即可,而后在后面的脚本中使用IP
地址,例如:
IP=魔法命令 #获取IP
echo $IP
所以我们接下来将讲解sed
如何获得一个单词。
sed
获取单词的基本思路就是:
- 第一步:筛选出行
- 第二步:掐头去尾,用空替换所有不要的字符
例如我们获取IP
地址:
第一步:筛选出行
ifconfig en0 | sed -n '5p'
第二步:掐头去尾,用空替换掉
inet
之前的内容以及netmask
之后的内容# 首先去掉inet之前的东西 ifconfig en0 | sed -n '5p' | sed 's/^.*inet //'
# 然后去掉netmask之后的内容 ifconfig en0 | sed -n '5p' | sed 's/^.*inet //' | sed 's/netmask.*$//'
这样,我们就成功通过sed
获取到了行内的内容。另外,我们其实可以用grep
和匹配IP
地址的正则表达式带代替掐头去尾。
相比于管道符多次替换,我们其实可以用-e
参数来使用多个匹配,但是在多个匹配的时候,最后一个命令必须要加上p
来输出
第一步:掐头
ifconfig en0 | gsed -e '5s/^.*inet//p' -n
第二步:去尾。注意,因为两个
gsed command
的输入都是ifconfig
的输出,所以命令都是5s
ifconfig en0 | gsed -e '5s/^.*inet//' -n -e "5s/net.*$//p" -n
文本格式化工具:awk
awk
是用于对文本和数据进行处理的工具。类似于python
解释器可以解释、执行python
语言编写的脚本,awk
也可以解释、执行awk
语言编写的脚本。
因此awk
根据上下文的不同,有时候指代awk
工具本身,有时候指代的是awk
这个编程语言。
awk
编程语言用于在Linux/Unix
下对文本和数据进行处理。数据可以来自标准输入(stdin
)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是Linux/Unix
下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk
语言内还有很多内建的功能,比如数组、函数等,这是它和C语言
的相同之处,灵活性是awk
最大的优势。
1. awk基础
学习akw
的时候需要学习:
awk
程序的用法awk
语言的语法
因此,我们下面将先介绍awk
的基础知识,而后再慢慢讲解awk
的进阶知识
A. awk的工作原理
和sed
逐行处理相似,awk
也是从文件file
中读取数据,而后针对每一行按照指定的命令序列(称为脚本script
)处理该行数据。
对于每一行,awk
中将其称为一个记录(Record)。每一行中使用分隔符分割出来不同的内容,称为字段(field)。例如针对test.txt
文件:
// test.txt
Jack1 Jack2 Jack3 Jack4 Jack5
Jack6 Jack7 Jack8 Jack9 Jack10
Jack11 Jack12 Jack13 Jack14 Jack15
Jack16 Jack17 Jack18 Jack19 Jack20
test.txt
中包含了4个记录(Record),每个记录有5个字段(Field)。
举一个概念上的例子来介绍awk
的工作原理:
awk 'BEGIN{ commands } pattern{ commands } END{ commands }' test.txt
- 第一步:读取
test.txt
- 第二步:执行
awk
程序,这里的awk
程序为BEGIN{ commands } pattern{ commands } END{ commands }
这里的awk
程序的解释如下:
- 执行
BEGIN{ commands }
语句块中的语句; - 逐行扫描文件,然后执行
pattern{ commands }
语句块,即首先查看该行是否和指定的模式pattern
符合,若符合则对改行执行commands
指令,若不符合则调到下一行。从第一行到最后一行重复这个过程,直到处理完文件所有行。 - 当处理完最后一行后,执行
END{ commands }
语句块。
关于BEGIN
、END
这些awk
程序具体得解释将在下面介绍awk
程序的部分进行介绍。
下面举一个真实的例子(中间的pattern
为空表示匹配所有行):
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk 'BEGIN{print "Start"} {print $1,$3,$5} END{print "End"}' test.txt
运行结果如下:
B. awk调用方式
awk
的调用方式有两种:
- 字符串命令方式
- 脚本文件方式
字符串命令方式
字符串命令方式的调用方式如下
awk [options] 'command' var=value file(s)
其中:
options
指定了命令行参数'command'
中指定了需要执行的命令序列,注意命令序列必须要用单引号包裹var
赋值一个用户定义变量,将外部变量传递给awk
file(s)
指定了要读取的文件
举一个例子,下面的例子是输出每行中的第二个字段的值
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk '{print $2}' test.txt
运行结果如下:
注意,上面的例子中我们没有使用指定命令行参数,也没有赋值用户定义变量。
脚本文件方式
awk [options] -f scriptfile var=value file(s)
其中:
options
指定了命令行参数scriptfile
中指定了需要读取的脚本文件。需要执行的命令序列存储在脚本文件中var
赋值一个用户定义变量,将外部变量传递给awk
file(s)
指定了要读取的文件
上面输出每行中的第二个字段的值的脚本文件的方式如下
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
echo '{print $2}' > cmd.awk
awk -f cmd.awk test.txt
C. awk模式和操作
和sed
命令类似,awk
命令也是由模式和操作组成的,操作必须要放在{}
中,故awk
命令:模式{操作}
。
awk
中的模式和操作具体有:
模式可以是以下任意一个:
- /正则表达式/:使用通配符的扩展集
- 关系表达式:使用运算符进行操作,可以是字符串或数字的比较测试
- 模式匹配表达式:用运算符
~
(匹配)和!~
(不匹配) - BEGIN语句块、pattern语句块、END语句块:稍后会进行详细的介绍
操作由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内,主要部分是:
- 变量或数组赋值
- 输出命令
- 内置函数
- 控制流语句
例如:
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
awk 'NR==2{print $1,$3,$5}' test.txt
其中:
NR==2
是模式,这里是表示第二行{print $1,$3,$5}
是操作。
运行结果如下:
D. awk常用参数
下面我们将列举awk
中常用的参数
参数 | 说明 |
---|---|
-F |
指定分割字符段 |
-v |
定义或修改一个awk 内部的变量 |
-f |
从脚本文件中读取awk 命令 |
2. awk 程序及语法
上面我们介绍了awk
的基础知识,我们接下来讲介绍awk
程序.
下面这一块,随用随看。没必要一次性看完,因为语法基本上和C是一样的。
A. awk程序基本结构
awk
程序的基本结构如下:
BEGIN{ print "start" } pattern{ commands } END{ print "end" }
一个awk
程序通常由3部分组成:
- BEGIN语句块:在awk开始从输入流中读取行 之前 被执行,这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中
- 能够使用模式匹配的通用语句块:在读取完每一行后,如果该行符合模式匹配,则按照指令处理改行。即上面的
pattern { commands }
- END语句块:在awk从输入流中读取完所有的行 之后 即被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块
这三个部分是可选的。任意一个部分都可以不出现在脚本中,脚本通常是被 单引号 中,例如:
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk 'BEGIN{print "Start"} {print $1,$3,$5} END{print "End"}' test.txt
运行结果如下:
B. 内置变量
类似于Python
中有__name__
、C
中有__LINE__
等内置变量(不严格的说),awk
语言也有内置的变量。
这里只列出一些常用的变量,更多的变量使用man
手册去查看即可。
下表中,[A][N][P][G]
表示第一个支持变量的awk
实现,[A]=awk
、[N]=nawk
、[P]=POSIXawk
、[G]=gawk
。不过一般来说这些变量现在所有的实现都支持了
工具 | 变量 | 说明 |
---|---|---|
全部 | $n |
当前记录的第n个字段(field ),比如n为1表示第一个字段,n为2表示第二个字段 |
全部 | $0 |
这个变量包含执行过程中当前行的文本内容 |
[N] |
argc |
命令行参数的数目 |
[N] |
argv |
包含命令行参数的数组 |
[N] |
ERRNO |
系统错误号 |
[N] |
RSTART |
由match函数所匹配的字符串的第一个位置 |
[N] |
RLENGTH |
由match函数所匹配的字符串的长度 |
[N] |
SUBSEP |
数组下标分隔符(默认值是34) |
[G] |
ARGIND |
命令行中当前文件的位置(从0开始算) |
[G] |
CONVFMT |
数字转换格式(默认值为%.6g) |
[G] |
FIELDWIDTHS |
字段宽度列表(用空格键分隔) |
[G] |
IGNORECASE |
如果为真,则进行忽略大小写的匹配 |
[P] |
ENVIRON |
环境变量关联数组 |
[P] |
FNR |
同NR,但相对于当前文件 |
[A] |
FILENAME |
当前输入文件的名 |
[A] |
FS |
字段分隔符(默认是任何空格) |
[A] |
NF |
当前记录分割后的字段数 |
[A] |
NR |
表示记录数,在执行过程中对应于当前的行号 |
[A] |
OFMT |
数字的输出格式(默认值是%.6g) |
[A] |
OFS |
输出字段分隔符(默认值是一个空格) |
[A] |
ORS |
输出记录分隔符(默认值是一个换行符) |
[A] |
RS |
记录分隔符(默认是一个换行符) |
C. 转移序列
为了能够输出控制字符,awk
也支持转义序列。awk
中的转义字符如下表所示:
转义字符 | 说明 |
---|---|
\\ |
\ 自身 |
\$ |
转义$ |
\t |
制表符 |
\n |
换行符 |
\r |
回车符 |
\b |
退格符 |
\c |
取消换行 |
D. 运算符
算术运算符
awk
中的算术运算符有:
算术运算符 | 描述 |
---|---|
+ - |
加,减 |
* / & |
乘,除与求余 |
+ - |
一元加,减 |
^ *** |
求幂 |
++ -- |
增加或减少,作为前缀或后缀 |
注意,使用算术运算符的时候,操作数自动转为数值,所有非数值都变为0
赋值运算符
awk
中的赋值运算符有:
运算符 | 描述 |
---|---|
= += -= *= /= %= ^= **= |
赋值语句 |
逻辑运算符
awk
中的逻辑运算符有:
运算符 | 描述 | ||
---|---|---|---|
` | ` | 逻辑或 | |
&& |
逻辑与 | ||
! |
逻辑非 |
正则运算符
运算符 | 描述 |
---|---|
~ |
匹配正则表达式 |
!~ |
不匹配正则表达式 |
awk
中支持基础正则表达式:
^ 行首
$ 行尾
. 除了换行符以外的任意单个字符
* 前导字符的零个或多个
.* 所有字符
[] 字符组内的任一字符
[^]对字符组内的每个字符取反(不匹配字符组内的每个字符)
^[^] 非字符组内的字符开头的行
[a-z] 小写字母
[A-Z] 大写字母
[a-Z] 小写和大写字母
[0-9] 数字
\< 单词头单词一般以空格或特殊字符做分隔,连续的字符串被当做单词
\> 单词尾
如果要使用正则表达式的话,需要用/正则/
的形式使用。
关系运算符
运算符 | 描述 |
---|---|
< <= > >= != == |
关系运算符 |
其它运算符
运算符 | 描述 |
---|---|
$ |
字段引用 |
空格 |
字符串连接符 |
?: |
C条件表达式 |
in |
数组中是否存在某键值 |
E. 流程控制语句
awk
的while
、do-while
和for
语句中允许使用break
,continue
语句来控制流程走向,也允许使用exit
这样的语句来退出。break
中断当前正在执行的循环并跳到循环外执行下一条语句。if
是流程选择用法。
awk
中的流程控制语句、语法结构与C语言很类似。而正是因为有了这些语句,很多Shell
程序都可以交给awk
,而且性能是非常快的。下面是各个语句用法。
条件判断语句
if(表达式)
语句1
else
语句2
格式中语句1可以是多个语句,为了方便判断和阅读,最好将多个语句用{}括起来。awk分枝结构允许嵌套,其格式为:
if(表达式)
{语句1}
else if(表达式)
{语句2}
else
{语句3}
示例:
awk 'BEGIN{
test=100;
if(test>90){
print "very good";
}
else if(test>60){
print "good";
}
else{
print "no pass";
}
}'
very good
每条命令语句后面可以用;
分号 结尾。
循环语句
while语句
while(表达式)
{语句}
示例:
awk 'BEGIN{
test=100;
total=0;
while(i<=test){
total+=i;
i++;
}
print total;
}'
5050
for循环
for循环有两种格式:
格式1:
for(变量 in 数组)
{语句}
示例:
awk 'BEGIN{
for(k in ENVIRON){
print k"="ENVIRON[k];
}
}'
TERM=linux
G_BROKEN_FILENAMES=1
SHLVL=1
pwd=/root/text
...
logname=root
HOME=/root
SSH_CLIENT=192.168.1.21 53087 22
注:ENVIRON是awk常量,是子典型数组。
格式2:
for(变量;条件;表达式)
{语句}
示例:
awk 'BEGIN{
total=0;
for(i=0;i<=100;i++){
total+=i;
}
print total;
}'
5050
do循环
do
{语句} while(条件)
例子:
awk 'BEGIN{
total=0;
i=0;
do {total+=i;i++;} while(i<=100)
print total;
}'
5050
其他语句
- break 当 break 语句用于 while 或 for 语句时,导致退出程序循环。
- continue 当 continue 语句用于 while 或 for 语句时,使程序循环移动到下一个迭代。
- next 能能够导致读入下一个输入行,并返回到脚本的顶部。这可以避免对当前输入行执行其他的操作过程。
- exit 语句使主输入循环退出并将控制转移到END,如果END存在的话。如果没有定义END规则,或在END中应用exit语句,则终止脚本的执行。
F. 数组应用
数组是awk的灵魂,处理文本中最不能少的就是它的数组处理。因为数组索引(下标)可以是数字和字符串在awk中数组叫做关联数组(associative arrays)。awk 中的数组不必提前声明,也不必声明大小。数组元素用0或空字符串来初始化,这根据上下文而定。
数组的定义
数字做数组索引(下标):
Array[1]="sun"
Array[2]="kai"
字符串做数组索引(下标):
Array["first"]="www"
Array"[last"]="name"
Array["birth"]="1987"
使用中print Array[1]
会打印出sun;使用print Array[2]
会打印出kai;使用print["birth"]
会得到1987。
读取数组的值
{ for(item in array) {print array[item]}; } #输出的顺序是随机的
{ for(i=1;i<=len;i++) {print array[i]}; } #Len是数组的长度
数组相关函数
得到数组长度:
awk 'BEGIN{info="it is a test";lens=split(info,tA," ");print length(tA),lens;}'
4 4
length返回字符串以及数组长度,split进行分割字符串为数组,也会返回分割得到数组长度。
awk 'BEGIN{info="it is a test";split(info,tA," ");print asort(tA);}'
4
asort对数组进行排序,返回数组长度。
输出数组内容(无序,有序输出):
awk 'BEGIN{info="it is a test";split(info,tA," ");for(k in tA){print k,tA[k];}}'
4 test
1 it
2 is
3 a
for…in
输出,因为数组是关联数组,默认是无序的。所以通过for…in
得到是无序的数组。如果需要得到有序数组,需要通过下标获得。
awk 'BEGIN{info="it is a test";tlen=split(info,tA," ");for(k=1;k<=tlen;k++){print k,tA[k];}}'
1 it
2 is
3 a
4 test
注意:数组下标是从1开始,与C数组不一样。
判断键值存在以及删除键值:
# 错误的判断方法:
awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";if(tB["c"]!="1"){print "no found";};for(k in tB){print k,tB[k];}}'
no found
a a1
b b1
c
以上出现奇怪问题,tB[“c”]
没有定义,但是循环时候,发现已经存在该键值,它的值为空,这里需要注意,awk数组是关联数组,只要通过数组引用它的key,就会自动创建改序列。
# 正确判断方法:
awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";if( "c" in tB){print "ok";};for(k in tB){print k,tB[k];}}'
a a1
b b1
if(key in array)
通过这种方法判断数组中是否包含key
键值。
#删除键值:
awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";delete tB["a"];for(k in tB){print k,tB[k];}}'
b b1
delete array[key]
可以删除,对应数组key
的,序列值。
二维、多维数组使用
awk的多维数组在本质上是一维数组,更确切一点,awk在存储上并不支持多维数组。awk提供了逻辑上模拟二维数组的访问方式。例如,array[2,4]=1
这样的访问是允许的。awk使用一个特殊的字符串SUBSEP(�34)
作为分割字段,在上面的例子中,关联数组array存储的键值实际上是2�344。
类似一维数组的成员测试,多维数组可以使用if ( (i,j) in array)
这样的语法,但是下标必须放置在圆括号中。类似一维数组的循环访问,多维数组使用for ( item in array )
这样的语法遍历数组。与一维数组不同的是,多维数组必须使用split()
函数来访问单独的下标分量。
awk 'BEGIN{
for(i=1;i<=9;i++){
for(j=1;j<=9;j++){
tarr[i,j]=i*j; print i,"*",j,"=",tarr[i,j];
}
}
}'
1 * 1 = 1
1 * 2 = 2
1 * 3 = 3
1 * 4 = 4
1 * 5 = 5
1 * 6 = 6
...
9 * 6 = 54
9 * 7 = 63
9 * 8 = 72
9 * 9 = 81
可以通过array[k,k2]
引用获得数组内容。
另一种方法:
awk 'BEGIN{
for(i=1;i<=9;i++){
for(j=1;j<=9;j++){
tarr[i,j]=i*j;
}
}
for(m in tarr){
split(m,tarr2,SUBSEP); print tarr2[1],"*",tarr2[2],"=",tarr[m];
}
}'
G. 内置函数
awk内置函数,主要分以下3种类似:算数函数、字符串函数、其它一般函数、时间函数。
算术函数
函数名 | 描述 |
---|---|
atan2( y, x ) |
返回 y/x 的反正切。 |
cos( x ) |
返回 x 的余弦;x 是弧度。 |
sin( x ) |
返回 x 的正弦;x 是弧度。 |
exp( x ) |
返回 x 幂函数。 |
log( x ) |
返回 x 的自然对数。 |
sqrt( x ) |
返回 x 平方根。 |
int( x ) |
返回 x 的截断至整数的值。 |
rand( ) |
返回任意数字 n,其中 0 <= n < 1。 |
srand( [expr] ) |
将 rand 函数的种子值设置为 Expr 参数的值,或如果省略 Expr 参数则使用某天的时间。返回先前的种子值。 |
举例说明:
awk 'BEGIN{OFMT="%.3f";fs=sin(1);fe=exp(10);fl=log(10);fi=int(3.1415);print fs,fe,fl,fi;}'
0.841 22026.466 2.303 3
OFMT 设置输出数据格式是保留3位小数。
获得随机数:
awk 'BEGIN{srand();fr=int(100*rand());print fr;}'
78
awk 'BEGIN{srand();fr=int(100*rand());print fr;}'
31
awk 'BEGIN{srand();fr=int(100*rand());print fr;}'
41
字符串函数
函数名 | 描述 |
---|---|
gsub( Ere, Repl, [ In ] ) |
除了正则表达式所有具体值被替代这点,它和 sub 函数完全一样地执行。 |
sub( Ere, Repl, [ In ] ) |
用 Repl 参数指定的字符串替换 In 参数指定的字符串中的由 Ere 参数指定的扩展正则表达式的第一个具体值。sub 函数返回替换的数量。出现在 Repl 参数指定的字符串中的 &(和符号)由 In 参数指定的与 Ere 参数的指定的扩展正则表达式匹配的字符串替换。如果未指定 In 参数,缺省值是整个记录($0 记录变量)。 |
index( String1, String2 ) |
在由 String1 参数指定的字符串(其中有出现 String2 指定的参数)中,返回位置,从 1 开始编号。如果 String2 参数不在 String1 参数中出现,则返回 0(零)。 |
length [(String)] |
返回 String 参数指定的字符串的长度(字符形式)。如果未给出 String 参数,则返回整个记录的长度($0 记录变量)。 |
blength [(String)] |
返回 String 参数指定的字符串的长度(以字节为单位)。如果未给出 String 参数,则返回整个记录的长度($0 记录变量)。 |
substr( String, M, [ N ] ) |
返回具有 N 参数指定的字符数量子串。子串从 String 参数指定的字符串取得,其字符以 M 参数指定的位置开始。M 参数指定为将 String 参数中的第一个字符作为编号 1。如果未指定 N 参数,则子串的长度将是 M 参数指定的位置到 String 参数的末尾 的长度。 |
match( String, Ere ) |
在 String 参数指定的字符串(Ere 参数指定的扩展正则表达式出现在其中)中返回位置(字符形式),从 1 开始编号,或如果 Ere 参数不出现,则返回 0(零)。RSTART 特殊变量设置为返回值。RLENGTH 特殊变量设置为匹配的字符串的长度,或如果未找到任何匹配,则设置为 -1(负一)。 |
split( String, A, [Ere] ) |
将 String 参数指定的参数分割为数组元素 A[1], A[2], . . ., A[n],并返回 n 变量的值。此分隔可以通过 Ere 参数指定的扩展正则表达式进行,或用当前字段分隔符(FS 特殊变量)来进行(如果没有给出 Ere 参数)。除非上下文指明特定的元素还应具有一个数字值,否则 A 数组中的元素用字符串值来创建。 |
tolower( String ) |
返回 String 参数指定的字符串,字符串中每个大写字符将更改为小写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。 |
toupper( String ) |
返回 String 参数指定的字符串,字符串中每个小写字符将更改为大写。大写和小写的映射由当前语言环境的 LC_CTYPE 范畴定义。 |
sprintf(Format, Expr, Expr, . . . ) |
根据 Format 参数指定的 printf 子例程格式字符串来格式化 Expr 参数指定的表达式并返回最后生成的字符串。 |
注:Ere都可以是正则表达式。
gsub,sub使用
awk 'BEGIN{info="this is a test2010test!";gsub(/[0-9]+/,"!",info);print info}'
this is a test!test!
在 info中查找满足正则表达式,/[0-9]+/
用””
替换,并且替换后的值,赋值给info 未给info值,默认是$0
查找字符串(index使用)
awk 'BEGIN{info="this is a test2010test!";print index(info,"test")?"ok":"no found";}'
ok
未找到,返回0
正则表达式匹配查找(match使用)
awk 'BEGIN{info="this is a test2010test!";print match(info,/[0-9]+/)?"ok":"no found";}'
ok
截取字符串(substr使用)
[wangsl@centos5 ~]$ awk 'BEGIN{info="this is a test2010test!";print substr(info,4,10);}'
s is a tes
从第 4个 字符开始,截取10个长度字符串
字符串分割(split使用)
awk 'BEGIN{info="this is a test";split(info,tA," ");print length(tA);for(k in tA){print k,tA[k];}}'
4
4 test
1 this
2 is
3 a
分割info,动态创建数组tA,这里比较有意思,awk for …in
循环,是一个无序的循环。 并不是从数组下标1…n ,因此使用时候需要注意。
格式化字符串输出(sprintf使用)
格式化字符串格式:
其中格式化字符串包括两部分内容:一部分是正常字符,这些字符将按原样输出; 另一部分是格式化规定字符,以"%"
开始,后跟一个或几个规定字符,用来确定输出内容格式。
格式 | 描述 | 格式 | 描述 |
---|---|---|---|
%d |
十进制有符号整数 | %u |
十进制无符号整数 |
%f |
浮点数 | %s |
字符串 |
%c |
单个字符 | %p |
指针的值 |
%e |
指数形式的浮点数 | %x |
%X 无符号以十六进制表示的整数 |
%o |
无符号以八进制表示的整数 | %g |
自动选择合适的表示法 |
awk 'BEGIN{n1=124.113;n2=-1.224;n3=1.2345; printf("%.2f,%.2u,%.2g,%X,%on",n1,n2,n3,n1,n1);}'
124.11,18446744073709551615,1.2,7C,174
一般函数
格式 | 描述 |
---|---|
close( Expression ) |
用同一个带字符串值的 Expression 参数来关闭由 print 或 printf 语句打开的或调用 getline 函数打开的文件或管道。如果文件或管道成功关闭,则返回 0;其它情况下返回非零值。如果打算写一个文件,并稍后在同一个程序中读取文件,则 close 语句是必需的。 |
system(command ) |
执行 Command 参数指定的命令,并返回退出状态。等同于 system 子例程。 |
Expression getline [ Variable ] |
从来自 Expression 参数指定的命令的输出中通过管道传送的流中读取一个输入记录,并将该记录的值指定给 Variable 参数指定的变量。如果当前未打开将 Expression 参数的值作为其命令名称的流,则创建流。创建的流等同于调用 popen 子例程,此时 Command 参数取 Expression 参数的值且 Mode 参数设置为一个是 r 的值。只要流保留打开且 Expression 参数求得同一个字符串,则对 getline 函数的每次后续调用读取另一个记录。如果未指定 Variable 参数,则 $0 记录变量和 NF 特殊变量设置为从流读取的记录。 |
getline [ Variable ] < Expression |
从 Expression 参数指定的文件读取输入的下一个记录,并将 Variable 参数指定的变量设置为该记录的值。只要流保留打开且 Expression 参数对同一个字符串求值,则对 getline 函数的每次后续调用读取另一个记录。如果未指定 Variable 参数,则 $0 记录变量和 NF 特殊变量设置为从流读取的记录。 |
getline [ Variable ] |
将 Variable 参数指定的变量设置为从当前输入文件读取的下一个输入记录。如果未指定 Variable 参数,则 $0 记录变量设置为该记录的值,还将设置 NF、NR 和 FNR 特殊变量。 |
打开外部文件(close用法)
awk 'BEGIN{while("cat /etc/passwd"|getline){print $0;};close("/etc/passwd");}'
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
逐行读取外部文件(getline使用方法)
awk 'BEGIN{while(getline < "/etc/passwd"){print $0;};close("/etc/passwd");}'
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
awk 'BEGIN{print "Enter your name:";getline name;print name;}'
Enter your name:
chengmo
chengmo
调用外部应用程序(system使用方法)
awk 'BEGIN{b=system("ls -al");print b;}'
total 42092
drwxr-xr-x 14 chengmo chengmo 4096 09-30 17:47 .
drwxr-xr-x 95 root root 4096 10-08 14:01 ..
b返回值,是执行结果。
时间函数
函数名 | 说明 |
---|---|
mktime( YYYY MM dd HH MM ss[ DST]) |
生成时间格式 |
strftime([format [, timestamp]]) |
格式化时间输出,将时间戳转为时间字符串具体格式,见下表。 |
systime() |
得到时间戳,返回从1970年1月1日开始到当前时间(不计闰年)的整秒数 |
建指定时间(mktime使用)
awk 'BEGIN{tstamp=mktime("2001 01 01 12 12 12");print strftime("%c",tstamp);}'
2001年01月01日 星期一 12时12分12秒
awk 'BEGIN{tstamp1=mktime("2001 01 01 12 12 12");tstamp2=mktime("2001 02 01 0 0 0");print tstamp2-tstamp1;}'
2634468
求2个时间段中间时间差,介绍了strftime使用方法
awk 'BEGIN{tstamp1=mktime("2001 01 01 12 12 12");tstamp2=systime();print tstamp2-tstamp1;}'
308201392
strftime日期和时间格式说明符
格式 | 描述 |
---|---|
%a |
星期几的缩写(Sun) |
%A |
星期几的完整写法(Sunday) |
%b |
月名的缩写(Oct) |
%B |
月名的完整写法(October) |
%c |
本地日期和时间 |
%d |
十进制日期 |
%D |
日期 08/20/99 |
%e |
日期,如果只有一位会补上一个空格 |
%H |
用十进制表示24小时格式的小时 |
%I |
用十进制表示12小时格式的小时 |
%j |
从1月1日起一年中的第几天 |
%m |
十进制表示的月份 |
%M |
十进制表示的分钟 |
%p |
12小时表示法(AM/PM) |
%S |
十进制表示的秒 |
%U |
十进制表示的一年中的第几个星期(星期天作为一个星期的开始) |
%w |
十进制表示的星期几(星期天是0) |
%W |
十进制表示的一年中的第几个星期(星期一作为一个星期的开始) |
%x |
重新设置本地日期(08/20/99) |
%X |
重新设置本地时间(12:00:00) |
%y |
两位数字表示的年(99) |
%Y |
当前月份 |
%% |
百分号(%) |
3. awk基础案例
其实说老实话,上面说的一堆语法,很多都平时都应不大,需要用了现查就行。
因为我们的目的是面向运用的,因此我们下面将结合案例来学习awk
输出文件第二列的信息
因为awk
对输入的文件是按行处理的,所以我们针对每行输出第二个字段的值即可,即输出$2
的内容即可。
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk '{print $2}' test.txt
运行结果如下:
注意,默认awk
是以空格作为分隔符的,并且多个空格在一起也视为一个空格。
同样,我们也能够输出倒数第二列和最后一列。
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk '{print $(NF-1),$NF}' test.txt
输出文件第二行信息
因为我们只需要输出文件第二行,所以只需要在要执行的命令前增加匹配第二行的模式即可,即NR==1
。NR
即Number of Record
,即当前处理的行数,从1开始。
此外,因为要输出一整行,所以使用$0
变量即可。
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk 'NR==2{print $0}' test.txt
运行结果如下:
自定义输出内容
我们前面都是通过引用变量来输出了文件本身的内容,但是如果我们想要在输出文件本身内容的基础上,输出诸如表头等等信息的时候,这个就需要进行自定义输出内容了。
其实实现的方法也很简单,就是使用字符串,awk
将所有被""
包围的都视为是字符串。所以我们只需要使用""
添加字符串即可。例如
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk '{print "第一列:"$1,"第二列:"$2}' test.txt
指定输入分隔符
awk
中有两种分隔符:
- 输入分隔符(
Field Separator
,FS
),它指定了输入的文本的分隔符是什么 - 输出分隔符(
Output Field Separator
,OFS
),它指定了输出文本的分隔符是什么
awk
默认使用空格来作为输入分隔符。但是很多配制文件中适用的分隔符不是空格,而是诸如:
等分隔符。这个时候我们就需要改变分隔符了。
awk
中,我们可以使用-F
参数来指定输入分隔符。例如我们下面分割/etc/passwd
这个文件
tail /etc/passwd
tail /etc/passwd | awk -F ':' '{print $1}'
使用-F
来更改输入分隔符只是一种方式。输入分隔符的值其实是由内部变量FS
指定的,所以我们也可以使用-v
参数来修改FS
的值,从而实现修改输入分隔符。
tail /etc/passwd
tail /etc/passwd | awk -v FS=':' '{print $1}'
修改输入的分隔符只是最基本的,很多时候我们使用awk
都是为了获得更好看的报表,所以我们有很多时候需要修改输出分隔符。
例如查看系统上每个用户当前的登录shell
tail /etc/passwd
tail /etc/passed | awk -F ':' -v OFS='\t\t' '{print $0, $NF}'
运行结果为:
显示文件2-4行
输出2-4行的难点在于模式怎么写,因为前面的NR==2
只是指定了输出一行。
sed
中使用start,end
来指定范围,awk
中也可以通过类似的方法来实现范围输出。
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk '{print "第一列:"$1,"第二列:"$2}' test.txt
注意,我们这里省略了{}
里面的action
,在省略action
的时候,awk
默认输出这一行的内容,即默认的action
是{print $0}
结合前面的关系运算符,我们其实还可以用下面的方式来指定范围
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk '2<=NR,NR<=4' test.txt
在每一行的内容前添加行号
每一行的行号其实就是NR
变量,所以我们只需要在这一行的内容,即$0
前加上行号即可
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk '{print NR":",$0}' test.txt
注意输出变量的值直接使用即可,而不用像shell
中一样需要使用的话,结果就会变成
awk '{print $NR":",$0}' test.txt
这是因为,$NR
在awk
眼中就成了$1
、$2
、$3
等等,所以就成了提取字段的值
分行输出一行内容
有的时候我们可能只需要输出一行信息,但是如果只用一行输出的话,可能看起来就会很麻烦,所以这个时候我们就可以指定RS
参数,即Record Separator
参数。
类似于Field Separator
分为输入和输出两种,awk
中的Record Separator
也分为两种:输入和输出。awk
中默认Record Seperator
的分隔符都是换行,所以我们其实只需要更改输入的记录分隔符即可。
tail -n 1 /etc/passwd
tail -n 1 /etc/passwd | awk -v RS=':' '{print $0}'
注意,因为我们现在指定记录分隔符为:
所以现在每个record
其实都只有一个字段
使用自定义变量
awk
中的变量有两种,一种是内置变量,一种是自定义变量。
内置变量具有特殊的效果,而自定义变量就是单纯的变量。我们前面示范了如何通过修改内置变量来实现特殊的效果。我们下面将展示如何设置自定义变量,以及如何使用自定义变量。
awk -v myname='Jack Wang' 'BEGIN{print "我的名字是?",myname}'
注意,BEGIN
模式表示在读取文件前开始执行,因此就首先输出了我的名字。而由于没有输入的文件,所以后面就没有任何输出了
awk
程序中有一套变量,而Shell
脚本中也有一套变量。所以很多时候我们都需要讲Shell
脚本中的变量传给awk
程序。下面就是具体的做法
echo $USER
awk -v user=$USER 'BEGIN{print "Who am I?",user}'
4. awk进阶案例
获取IP地址
在sed
命令的讲解中我们讲到可以使用掐头去尾的方法来获取机器的IP
地址。而从awk
的角度出发,就有一种完全不同的获取方式。
ifconfig en0
我们可以看到,ifconfig
命令的输出其实是使用空格分隔的。所以我们能够用awk
来直接获得IP
地址
这里IP
地址是第五列的第二个字段,所以使用下述的awk
命令
ifconfig
ifconfig en0 | awk 'NR==5{print $2}'
事实上,这也就是awk
命令最强大的地方,就是因为英文文本是以空格分割的,所以我们利用空格分割,就可以精准的定位到任何一个我们需要的字段。这也就是为什么awk
如此强大的最根本原因。
5. awk格式化输出
我们前面讲了awk
的基础用法。可是仅仅只是学会基础用法,我们其实是没有办法做到最前面说的,awk
是用于生成专业报表(文本格式化)的。
说到文本格式化,我们第一个想到的就是C语言中的printf
。C语言中的printf
结合各种格式控制字符,例如%d
、%.4f
等等,就可以实现精准的格式化输出。
事实上,awk
也借鉴了C语言中的printf
,从而使得我们基本上可以无缝的从C语言的printf
切换到awk
的printf
基础语法
awk
中printf
的基础语法为
printf f-staring, var1, var2, ...
其中:
- 格式字符串
f-string
中的格式数量要和后面的变量对应 printf
不会自动打印换行符
格式化字符
awk
支持的格式化字符有:
控制字符 | 说明 |
---|---|
%c |
输出单个字符 |
%d |
输出十进制数字 |
%e/%E |
科学计数法显示数值 |
%f |
输出显示浮点数 |
%g/%G |
科学计数法显示小数 |
%o |
输出无符号的八进制数 |
%s |
输出字符串 |
%x |
输出无符号的十六进制数 |
%% |
输出一个百分号 |
此外,还可以增加修饰符:
修饰符 | 说明 |
---|---|
- |
右对齐,默认左对齐 |
+ |
显示数值的符号 |
6. awk格式化输出案例
printf和print的不同
我们上面说过,print
处理完每一行后,默认会添加换行符号,而printf
不会添加换行符号。
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk '{print $1}' test.txt
awk '{printf $1}' test.txt
运行结果如下
printf按行输出
printf
默认是不会换行的,但是实际上我们可以手动添加换行符
echo Jack{1..5} > test.txt && echo Jack{6..10} >> test.txt && echo Jack{11..15} >> test.txt && echo Jack{16..20} >> test.txt
cat test.txt
awk '{print $0}' test.txt
awk '{printf "%s\n",$0}' test.txt
printf格式化字符与变量匹配
C语言中,如果f-string
中的格式化字符和后面的变量不匹配的话,在编译的时候是会报错的。
而awk
中,如果不匹配的话是不会报错的,而是不会输出没有匹配的变量
awk 'BEGIN{printf "%d\n",1,2,3}'
awk 'BEGIN{printf "%d\n%d\n%d\n",1,2,3}'
格式化输出/etc/passwd
最后,我们用awk
格式化输出/etc/passwd
中的内容
tail /etc/passwd
tail /etc/passwd | awk -F ':' 'BEGIN{printf "%-22s\t%-10s\t%-4s\t%-4s\t%-35s\t%-20s\t%-15s\n","User Name","Password","UID","GID","User Comment","User Home","Loging Shell"} {printf "%-22s\t%-10s\t%-4s\t%-4s\t%-35s\t%-20s\t%-15s\n",$1,$2,$3,$4,$5,$6,$7}'