Awk文本处理语言
AWK是一种处理文本文件的语言。它将文件作为记录序列处理。在一般情况下,文件内容的每行都是一个记录。每行内容都会被分割成一系列的域,因此,我们可以认为一行的第一个词为第一个域,第二个词为第二个,以此类推。AWK程序是由一些处理特定模式的语句块构成的。AWK一次可以读取一个输入行。对每个输入行,AWK解释器会判断它是否符合程序中出现的各个模式,并执行符合的模式所对应的动作。
1、AWK概述
Awk 是一个维护和处理文本数据文件的强大语言。在文本数据有一定的格式,即每行数据包 含多个以分界符分隔的字段时,显得尤其有用。它非常强大,专为文本处理而设计。 它的名字来源于其作者的姓氏Alfred Aho, Peter Weinberger, and Brian Kernighan。
AWK有下面几个变种:
- AWK是最原始的AWK, 来自 AT&T 实验室的原始AWK
- NAWK - 来自AT&T实验室的更新和改进的AWK版本
- GAWK是GNU AWK。 所有GNU/Linux发行版都默认提供GAWK, 它与AWK和NAWK完全兼容
AWK 可以用来处理很多任务,如文字处理、格式化的文本报告等。本文中AWK如果没有特别说明,那么指的就是GAWK。
2、AWK语法
Awk的基础语法:
awk options '/pattern/{action}' input-file
或
awk options '{action}' input-file
options
是可选参数,主要有-F
、-f
、-v
:-F fs 或 --field-separator fs
:字段分界符,如不指定,默认是使用空格作为分界符-f scripfile or --file scriptfile
:从文件中读取awk命令,文件可以使用任意扩展名(也可不用),使用.awk
扩展名便于维护-v var=value or --asign var=value
:赋值一个用户定义变量
/pattern/
和{action}
需要用单引号包围起来/pattern
是可选的,如不指定,awk将处理input-file中所有的记录,如果指定了,那么只会对处理匹配模式的记录{action}
是 awk 命令, 可以是单个命令,也可以是多个命令,所有的命令必须放在{}
中间input-file
是指要处理的文件
$ awk '/screen/{print $1}' /etc/passwd
_screensaver:*:203:203:Screensaver:/var/empty:/usr/bin/false
3、AWK执行流程
3.1、程序结构
在典型的awk程序中包含三个区域(BEGIN区域、body区域、END区域):
awk 'BEGIN{awk commands} /pattern/{action} END{awk commands}'
BEGIN区域:
该区域的语法:
BEGIN {awk commands}
BEGIN区域的命令在读取文件之前、在执行body区域命令之前执行,而且仅执行一次。
这里块区域适合初始化变量、打印报告头部信息等工作。此外需要注意的是BEGIN
是关键字且必须是大写的,同时BEGIN区域是可选的
BEGIN区域的命令包含在{}
中,可以是一个也可以是多个命令
body区域: 该区域的语法:
/pattern/{action}
body区域的命令会在文件读取一行就执行一次, /pattern/
是可选的。
END区域 该区域的语法:
END{awk commands}
END区域会在执行完所有操作后再执行,且只执行一次,这里适合执行一些清理操作,或打印报文结尾信息等。END
是关键字需要大写,END区域可以有一个或多个命令,需要包含在 {}
中,同时END区域是可选的。
3.2、AWK 执行流程
awk在读取文件并执行body区域命令前,执行BEGIN区域命令一次。然后每读取一次输入行,就会执行一次body区域命令,该区域命令可以由多个/pattern/{action}
组成,会依次执行。最后程序在结束时,会执行一次BEGIN区域命令。
$ date | awk 'BEGIN{print "----------Current Time-----------"} \
{print $4}END{print "----------- END -----------------"}'
----------Current Time-----------
15:51:45
----------- END -----------------
4、AWK内置变量
Awk提供了很多内置变量。如awk默认分隔符是空格,可以使用-F
选项来指定它,如:
awk -F':' '{print $1}' /etc/passwd
这样可以通过内置变量 FS
来完成,如:
awk 'BEGIN{FS=":"}{print $1}' /etc/passwd
awk还提供了其他的内置变量,如:
变量 | 说明 |
---|---|
ARGC | 保存着传递给awk脚本的所有参数的个数 |
ARGV | ARGV 是一个数组,保存着传递给awk脚本的所有参数,其索引范围从0到ARGC |
ARGIND | ARGIND 是 ARGV 的一个索引, ARGV[ARGIND] 会返回当前正在处理的文 件名 |
FILENAME | 当前处理的文件名(awk处理多个输入文件时很有用) |
FS | 输入字段分隔符 |
OFS | 输出字段分隔符 |
RS | 记录分隔符 |
ORS | 输出记录分隔符 |
NF | “number of fields in the current record”, 当前记录的字段个数 |
NR | “ordinal number of the current record”,当前记录在所有记录中的序号 |
FNR | 当前处理的记录号,在处理一个新的文件时FNR会被重置为1,而NR不会被重置 |
ENVIRON | ENVIRON 是一个包含所有 shell 环境变 量的数组,其索引就是环境变量的名称。 |
IGNORECASE | IGNORECASE 的默认值是0,所有awk区分大小写。值设置为 1 时,则不区分大小写 |
ERRNO | 当执行 I/O 操作(比如 getline)出错时,变量 ERRNO 会保存错误信息 |
FIELDWIDTHS | 按固定宽度来解析字段,如文件中有三列,每列分别含有4、5、6个字符,那么可以设置成BEGIN {FIELDWIDTHS="4 5 6"} |
RSTART | 被 match() 函数匹配的字符串的起始位置,如果没有匹配则为0(匹配时从1开始) |
RLENGTH | 被 match() 函数匹配的字符串的长度 |
SUBSEP | 数组中下标分隔符 |
$ date | awk 'BEGIN{OFS="-"} {print $2, $3, $6}'
May-23-2022
5、AWK变量的操作符
和其他程序设计语言一样,awk允许在程序中设置变量。变量以字母开头,后续字符可以是数字、字符、下划线,但关键字
不能作为awk变量。变量可以直接使用而不需要事先声明,而且没有数据类型的概念,一个awk变量是number还是string
取决于变量所处的上下文。如果要初始化变量,最好在 BEGIN
区域中操作(因为 BEGIN
区域只会执行一次)
也可以使用 -v
选项对用户定义的变量进行赋值,该变量在 BEGIN
区域也是可用的,如:
$ awk -v hello=$date '{print hello}'
awk 支持多种运算,这些运算与 C
语言基本相同。
5.1、一元操作符
操作符 | 描述 |
---|---|
+ | 取正(返回数字本身) |
- | 取反 |
++ | 自增 |
– | 自减 |
5.2、算术操作符
操作符 | 描述 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除 |
% | 取余 |
5.3、字符串操作符
操作符 | 描述 |
---|---|
空格 | 空格是连字符的操作符,如str3=str2 str1 str3为str2 连接str1后的内容 |
5.4、赋值操作符
操作符 | 描述 |
---|---|
= | 赋值 |
+= | 加法赋值 |
-= | 减法赋值 |
*= | 乘法赋值 |
/= | 触发赋值 |
%/ | 取余赋值 |
5.5、比较操作符
操作符 | 描述 |
---|---|
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
== | 等于 |
!= | 不等于 |
&& | 且 |
|| | 或 |
5.6、正则表达式操作符
操作符 | 描述 |
---|---|
~ | 匹配 |
!~ | 不匹配 |
6、AWK分支和循环
awk提供完备的流程控制语句类似于 C
语言:if
, if-else
, while
, do-while
, for
, break
, continue
。
7、AWK关联数组
Awk 的数组,都是关联数组,即一个数组包含多个”索引/值”的元素。索引没必要是一系列 连续的数字,实际上,它可以使字符串或者数字,并且不需要指定数组长度。 语法:
arrayname[string]=value
- arrayname是数组名称
- string是数组索引
- value是为数组元素赋的值
可以使用arrayname[index]访问数组中的某个特定元素:
$ cat array-assign.awk
BEGIN {
item[101]="HD Camcorder";
item[102]="Refrigerator";
item[103]="MP3 Player";
item[104]="Tennis Racket";
item[105]="Laser Printer";
item[1001]="Tennis Ball";
item[55]="Laptop";
item["na"]="Not Available";
print item["101"];
print item[102];
print item["103"];
print item[104];
print item["105"];
print item[1001];
print item["na"];
}
$ awk -f array-assign.awk
HD Camcorder
Refrigerator
MP3 Player
Tennis Racket
Laser Printer
Tennis Ball
Not Available
7.1、判断数组元素是否存在
如果视图访问一个不存在的数组元素,awk 会自动以访问时指定的索引建立该元素,并赋予 null
值。为了避免这种情况,在使用前最后检测元素是否存在。
可以使用if语句检查元素是否存在,返回true,则表示元素存在数组中,语法:
if(index in array-name)
$ cat array-refer.awk
BEGIN {
x = item[55];
if (55 in item)
print "Array index 55 contains",item[55];
item[101]="HD Camcorder";
if (101 in item)
print "Array index 101 contains",item[101];
if (1010 in item)
print "Array index 1010 contains",item[1010];
}
$ awk -f array-refer.awk
Array index 55 contains
Array index 101 contains HD Camcorder
7.2、遍历数组元素
可以使用for
来遍历数组,语法:
for (var in array-name)
actions
如:
$ cat array-for-loop.awk
BEGIN {
item[101]="HD Camcorder";
item[102]="Refrigerator";
item[1001]="Tennis Ball";
item[55]="Laptop";
item["no"]="Not Available";
for(x in item)
print item[x]
}
$ awk -f array-for-loop.awk
Not Available
Laptop
HD Camcorder
Refrigerator
Tennis Ball
7.3、删除数组元素
可以使用 delete
语句删除数组元素,语法:
delete arrayname[index]
如:
BEGIN {
item[101]="HD Camcorder";
item[102]="Refrigerator";
item[1001]="Tennis Ball";
item[55]="Laptop";
item["no"]="Not Available";
for(x in item){
print item[x]
}
print "------- After deleted --------"
delete item[101]
delete item[102]
delete item["no"]
for(x in item){
print item[x]
}
}
$ awk -f array-for-loop.awk
Not Available
Laptop
HD Camcorder
Refrigerator
Tennis Ball
------- After deleted --------
Laptop
Tennis Ball
8、AWK函数
awk定义并支持一系列的内置函数,这使得awk提供的功能更为完善、强大。
8.1、数值函数
函数 | 描述 |
---|---|
int(n) | 返回给定参数的整数部分值 |
log(n) | 返回给定参数的自然对数,参数 n 必须是正数,否则会抛出错误 |
sqrt(n) | 返回指定整数的正平方根,该函数参数也必须是整数,如果传递负数将会报错 |
exp(n) | 返回e的n次幂 |
sin(n) | 返回 n 的正弦值,n 是弧度值 |
cos(n) | 返回 n 的余弦值,n 是弧度值 |
atan2(m, n) | 该函数返回 m/n 的反正切值,m 和 n 是弧度值。 |
8.2、字符串函数
函数 | 描述 |
---|---|
index | 用来获取给定字符串在输入字符串中的索引(位置) |
length | 返回字符串的长度 |
split(input-string,output-array,separator) | split 函数把字符串分割成单个数组元素 |
substr(input-string,location,length) | substr 函数从字符串中取指定的部分(子串) |
sub(original-string,replacement-string,string-variable) | 在string-variable 中用replacement-string 替换第一次出现的original-string |
gsub(original-string,replacement-string,string-variable) | gsub 和 sub 类似,只是gsub 会把所有的 original-string 替换成 replacement-string |
match(input-string, search-string) | 函数从输入字符串中检索给定的字符串(或正则表达式),当检索到字符串时,返回一个正数值 |
tolower(input-string) | 把给定的字符串转换为小写 |
toupper(input-string) | 把给定的字符串转换为大写 |
printf “print format”, variable1,variable2,etc. | 格式化输出结果 |
8.3、字符串格式化
printf
可以非常灵活、简单的进行格式化输出结果,printf中可以使用的特殊字符:
特殊字符 | 描述 |
---|---|
\n | 换行 |
\t | 制表符 |
\v | 垂直制表符 |
\b | 退格 |
\r | 回车符 |
\f | 换页 |
printf 格式化字符
格式化字符 | 描述 |
---|---|
s | 字符串 |
c | 单个字符 |
d | 数值 |
e | 指数 |
f | 浮点数 |
g | 根据值决定使用e 或 f 中较短的输出 |
o | 八进制 |
x | 十六进制 |
% | 百分号 |
printf 支持复杂的格式化控制输出,比如:
- 指定字符串宽度时,在
%
和格式化字符之间加上一个-
,表示左对齐, 如:
$ awk 'BEGIN{printf("|%10s|%-10s|\n", "Hello", "Hello")}'
| Hello|Hello |
- 在字符串长度不够时可以进行补0,
%05d
和%.5d
效果相同,如:
$ awk 'BEGIN{ printf "|%5d|%05d|%.5d|\n", 10, 10, 10}'
| 10|00010|00010|
更多的情况如下:
Format | Variable | Results |
---|---|---|
%c | 100 | “d” |
%10c | 100 | " d" |
%010c | 100 | " 000000000d" |
%d | 10 | “10” |
%10d | 10 | " 10" |
%10.4d | 10.123456789 | " 0010" |
%10.8d | 10.123456789 | " 00000010" |
%.8d | 10.123456789 | " 00000010" |
%010d | 10.123456789 | “0000000010” |
%e | 987.1234567890 | “9.871235e+02” |
%10.4e | 987.1234567890 | “9.8712e+02” |
%10.8e | 987.1234567890 | “9.87123457e+02” |
%f | 987.1234567890 | “987.123457” |
%10.4f | 987.1234567890 | " 987.1235" |
%010.4f | 987.1234567890 | “00987.1235” |
%10.8f | 987.1234567890 | “987.12345679” |
%g | 987.1234567890 | “987.123” |
%10g | 987.1234567890 | " 987.123" |
%10.4g | 987.1234567890 | " 987.1" |
%010.4g | 987.1234567890 | “00000987.1” |
%.8g | 987.1234567890 | “987.12346” |
%o | 987.1234567890 | “1733” |
%10o | 987.1234567890 | " 1733" |
%010o | 987.1234567890 | “0000001733” |
%.8o | 987.1234567890 | “00001733” |
%s | 987.123 | “987.123” |
%10s | 987.123 | " 987.123" |
%10.4s | 987.123 | " 987." |
%010.8s | 987.123 | “000987.123” |
%x | 987.1234567890 | “3db” |
%10x | 987.1234567890 | " 3db" |
%010x | 987.1234567890 | “00000003db” |
%.8x | 987.1234567890 | “000003db” |
$ cat format.awk
#!/usr/bin/env awk -f
BEGIN{
printf "%s\n", 987.123
printf "%10s\n", 987.123
printf "%10.4s\n", 987.123
printf "%x\n", 987.1234567890
printf "%10x\n", 987.1234567890
printf "%010x\n", 987.1234567890
printf "%.8x\n", 987.1234567890
}
$ awk -f format.awk
987.123
987.123
987.
3db
3db
00000003db
000003db
8.3、自定义函数
需要编写大量代码同时又要多次重复执行其中某些片段时,可以使用自定义函数。语法:
function fn-name(parameters)
{
#.....
}
fn-name
: 函数名,名称规则和变量名一样,以字母开头,后续可以是字母、数值、下划线,关键字不能作为函数名parameters
: 多个参数使用逗号进行分隔,也可以没有参数
8.4、位操作
和 C 语言类似,awk 也可以进行位操作。
操作符 | 描述 |
---|---|
and | 按位与 |
or | 按位或 |
xor | 按位异或 |
compl | 取反, 如:15 = 01111, 15 compl = 10000 |
lshift | 左移,函数把操作数向左位移,可以指定位移多少次,位移后右边补 0 |
rshift | 右移,该函数把操作数向右位移,可以指定位移多少次,位移后左边补 0 |
简单的位操作示例:
$ cat bits.awk
BEGIN{
num1=15
num2=25
print "AND: " and(num1,num2);
print "OR: " or(num1,num2);
print "XOR: " xor(num1,num2);
print "LSHIFT: " lshift(num1,2);
print "RSHIFT: " rshift(num1,2);
}
$ awk -f bits.awk
AND: 9
OR: 31
XOR: 22
LSHIFT: 60
RSHIFT: 3
8.5、时间函数
systime()
函数返回系统的 POSIX
时间,即自1970 年 1 月 1 日起至今经过的 秒数。
$ awk 'BEGIN { print systime() }'
1365585325
可以使用 strftime(string)
和 strftime(string, timestamp)
函数对时间进行格式化。strftime 支持的格式标识符如下:
格式标识符 | 描述 |
---|---|
%Y | 年份的完整格式,如 2011 |
%y | 两位数字的年份,如 2011 显示为 11 |
%m | 两位数字月份,一月显示为 01 |
%d | 两位数字日期,4 号显示为 04 |
%H | 24 小时格式, 1 p.m 显示为 13 |
%I | 12 小时格式, 1 p.m 显示为 01 |
%M | 两位数字分钟,9 分显示为 09 |
%S | 两位数字秒,5 秒显示为 05 |
%c | 显示本地时间的完整格式,如:Fri May 20 21:24:25 2022 |
%D | 简单日期格式,和%m/%d/%y 相同 |
%F | 简单日期格式,和%Y-%m-%d 相同 |
%T | 简单时间格式,和%H:%M:%S 相同 |
%x | 基于本地设置的时间格式 |
%X | 基于本地设置的时间格式 |
%r | 简单时间格式,和%I:%M%:%S %p相同 |
%R | 简单时间格式,和%H:%M相同 |
%B | 月份完整单词,一月显示为 January |
%b | 月份缩写,一月显示为 Jan |
%p | 显示 AM 或 PM,和%l 搭配使用 |
%a | 三位字符星期,周一显示为 Mon |
%A | 完整的日期,周一显示为 Monday |
%Z | 时区,太平洋地区时区显示为 PST |
%n | 换行符 |
%t | 制表符 |
$ cat time.awk
BEGIN{
print strftime("%Y-%m-%d %H:%M:%S")
print strftime("%Y-%m-%d %H:%M:%S", systime())
print strftime("%F %T")
}
$ awk -f time.awk
2022-05-20 16:36:43
2022-05-20 16:36:43
2022-05-20 16:36:43
9、AWK脚本程序
awk 和 shell 一样是一个解释型语言,也可以用来写可以执行的脚本程序。
和shell脚本类似,awk脚本以下面一行开头:
#!/path/to/awk/utility -f
如在我的系统中,awk安装在/usr/local/bin/awk
,所以我的脚本开头第一行是:
#!/usr/local/bin/awk -f
#!
,指明使用那个解释器来执行脚本中的命令/usr/local/bin/awk
,解释器-f
,解释器选项,用来指定读取程序文件
需要注意的是,直接指定解释器位置,有可能导致一个问题,换到其他机器,同样脚本却无法执行,因为脚本解释器可能安装在不同的目录中。更好的解决办法是用#!/usr/bin/env awk
, env
会在系统PATH目录中查找awk。
编辑保存好脚本bits.awk,如:
#! /usr/bin/env awk -f
BEGIN{
num1=15
num2=25
print "AND: " and(num1,num2);
print "OR: " or(num1,num2);
print "XOR: " xor(num1,num2);
print "LSHIFT: " lshift(num1,2);
print "RSHIFT: " rshift(num1,2);
}
然后,给脚本添加可自行权限:
$ chmod +x bits.awk
此时就可以执行它了:
$ ./bits.awk
AND: 9
OR: 31
XOR: 22
LSHIFT: 60
RSHIFT: 3
10、有趣的使用案例
10.1、只打印特殊行号的行
可以使用内置变量NR
(表示当前记录在所有记录中的行号)进行处理。
myheart文件内容如下:
1 Every night in my dreams
2 I see you, I feel you
3 That is how I know you go on
输出文件myheart
的奇数行:
$ awk "NR % 2 == 1" myheart
1 Every night in my dreams
3 That is how I know you go on
10.2、显示一行最后一个字段值
内置变量 NF
可以获取到一行的字段数量,使用$NF
就可以获取到一行的最后一个字段值,如:
$ cat myheart
1 Every night in my dreams
2 I see you, I feel you
3 That is how I know you go on
$ awk '{print $1, $NF}' myheart
1 dreams
2 you
3 on
10.3、在Makefile中输出命令帮助信息
下面是来自项目kratos-layout 的Makefile:
GOPATH:=$(shell go env GOPATH)
VERSION=$(shell git describe --tags --always)
INTERNAL_PROTO_FILES=$(shell find internal -name *.proto)
API_PROTO_FILES=$(shell find api -name *.proto)
.PHONY: init
# init env
init:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install github.com/go-kratos/kratos/cmd/kratos/v2@latest
go install github.com/go-kratos/kratos/cmd/protoc-gen-go-http/v2@latest
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest
.PHONY: config
# generate internal proto
config:
protoc --proto_path=./internal \
--proto_path=./third_party \
--go_out=paths=source_relative:./internal \
$(INTERNAL_PROTO_FILES)
.PHONY: api
# generate api proto
api:
protoc --proto_path=./api \
--proto_path=./third_party \
--go_out=paths=source_relative:./api \
--go-http_out=paths=source_relative:./api \
--go-grpc_out=paths=source_relative:./api \
--openapi_out==paths=source_relative:. \
$(API_PROTO_FILES)
.PHONY: build
# build
build:
mkdir -p bin/ && go build -ldflags "-X main.Version=$(VERSION)" -o ./bin/ ./...
.PHONY: generate
# generate
generate:
go mod tidy
go get github.com/google/wire/cmd/wire@latest
go generate ./...
.PHONY: all
# generate all
all:
make api;
make config;
make generate;
# show help
help:
@echo ''
@echo 'Usage:'
@echo ' make [target]'
@echo ''
@echo 'Targets:'
@awk '/^[a-zA-Z\-\_0-9]+:/ { \
helpMessage = match(lastLine, /^# (.*)/); \
if (helpMessage) { \
helpCommand = substr($$1, 0, index($$1, ":")-1); \
helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \
printf "\033[36m%-22s\033[0m %s\n", helpCommand,helpMessage; \
} \
} \
{ lastLine = $$0 }' $(MAKEFILE_LIST)
.DEFAULT_GOAL := help
其中help
命令输出帮助信息是用awk从Makefile文件中收集注释信息生成的,提取awk命令如下:
awk '/^[a-zA-Z\-\_0-9]+:/ { \
helpMessage = match(lastLine, /^# (.*)/); \
if (helpMessage) { \
helpCommand = substr($1, 0, index($1, ":")-1); \
helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \
printf "\033[36m%-22s\033[0m %s\n", helpCommand,helpMessage; \
} \
} \
{ lastLine = $0 }' $(MAKEFILE_LIST)
这段awk程序中没有BEGIN
和END
区域命令,只有body部分,且是由两个body区域命令构成。
第一个由awk '/^[a-zA-Z\-\_0-9]+:/ { \
开始的这段body区域,由一段模式匹配开始,只有匹配的行才会交给
其后的{}
中命令进行执行,具体也就是第一个body区只处理Makefile中的命令行,也就是init:
、config:
…help:
这些行。
第二个body区域中只有一行命令:lastLine = $0
, 把读取到的行保存到变量lastLine
中,此时awk读取到下一行进行处理时,lastLine
保存的就是它上一行的内容。
$(MAKEFILE_LIST)
指定是当前的Makefile文件
此时再回头看第一个body区域命令,就很清晰了。该区域命令,在遇到当前行是Markfile中的命令,且上一行是以#
开始的注释行时,使用substr
和index
函数进行截取出Markfile指令(不包含:
),
并使用substr
截取上一行的注释内容(去掉开头的#
),再使用printf
函数进行格式化输出指令和它的说明信息,%-22s
以左对齐、最小22个字符宽度格式化指令进行输出,如:
init init env
config generate internal proto
api generate api proto
build build
generate generate
all generate all
help show help
10.4、解析处理CSV文件
逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),以纯文本形式存储表格数据(数字和文本)。 典型情况下每行一条记录,用分隔符来分隔字段。CSV格式的标准定义参见RFC 4180 。
awk
内置变量FS
, 指定分隔符,也就是出现两个字段之间的部分。FS
定义了字段不是什么,而不是直接去定义字段是什么。
在对于仅仅使用分隔符(如逗号)分割数据,字段中没有嵌入的分隔符时,如文件imgs.csv内容:
sj_mino1001.jpg,715282,4FB55FE8
sj_mino1002.jpg,471289,93203C5C
sj_mino1003.jpg,451929,C4E80467
对于这种情况,用FS
就可以正确解析出每个字段内容, 如:
$ awk -v FS="," '{print $1, $2, $3}' demo.csv
sj_mino1001.jpg 715282 4FB55FE8
sj_mino1002.jpg 471289 93203C5C
sj_mino1003.jpg 451929 C4E80467
但对于字段中嵌套了分隔符的CSV,如在双引号(double quotes)中嵌入逗号作为字段的情况,如下面的数据:
"SELECT `id`, `name`, `phone` FROM `customer` ORDER BY `id` DESC LIMIT ?, ?",saas,"17",3.146,"3.251","0","0","7.53","7530192","3.53","5"
变量FPAT
为这种情况提供了解决方案,变量 FPAT
值是一个正则表达式字符串,描述了每个字段的内容。上面的数据中,字段有用双引号包围并且其中
嵌套了逗号、没有逗号分隔符、用双引号包围没有逗号分隔符。这种情况下,可以用正则表达式 /([^,]+)|("[^"]+")/
来匹配,也就是匹配不还有"
的一个或多个字符,或者匹配
用双引号包围,但是包围的部分是非引号的一个或多个字符。赋值给FPAT
时需要将这个正则表达式转换为字符串,并对其中的双引号进行转义,也就是:
FPAT = "([^,]+)|(\"[^\"]+\")"
用这个来解析上面的csv数据:
$ cat sql.awk
BEGIN{
FPAT = "([^,]+)|(\"[^\"]+\")"
}
{
for(i=1;i<=NF;i++){
printf("$%d = %s\n", i, $i)
}
}
$ awk -f sql.awk sql.csv
$1 = "SELECT `id`, `name`, `phone` FROM `customer` ORDER BY `id` DESC LIMIT ?, ?"
$2 = saas
$3 = "17"
$4 = 3.146
$5 = "3.251"
$6 = "0"
$7 = "0"
$8 = "7.53"
$9 = "7530192"
$10 = "3.53"
$11 = "5"
上面的数据中,字段都是非空的,如果允许字段为空,那么可以把正则表达式中的+
改为*
来处理这种情况:
FPAT = "([^,]*)|(\"[^\"]*\")"
10.5、查看服务器当前80端口网络连接数
netstat
是查看网络相关数据的常用命令,可以显示路由表、实际的网络连接以及每一个网络接口设备的状态信息。ss
直接从Linux内核中获取TCP和连接状态信息,
效率比 netstat
好:
$ netstat -tan | awk '$4~/:80$/{++state[$NF]} END {for(key in state) print key, "\t", state[key]}'
LISTEN 1
$ ss -tan | awk '$4~/:80$/{++state[$1]} END {for(key in state) print key, "\t", state[key]}'
LISTEN 1
10.6、最常用命令列表
$ history | awk '{++a[$2]}END{for(i in a){print a[i] " " i}}' | sort -rn | head
或
$ history | awk '{print $2}' | sort | uniq -c | sort -rn | head