Linux-gawk命令

背景

此前讲到sed命令,它是流编辑方式的一种,但是由于其自身的限制,导致其无法很好的组织文件中的数据。这一点就被gawk给解决了,gawk提供了类编程环境的方式去处理文本:支持变量、提供算数计算、结构化编程、数据提取,所以gawk要强大很多。

内容

入门

gawk命令的格式如下:

1
2
3
4
# options是gawk的参数
# program是处理文本的指令
# file是处理文本对象,如果不指定则从STDIN中获取
gawk options program file

其中options部分常用的参数如下(摘录自《Linux命令行与Shell编程大全》):

1
2
3
4
5
6
# -F fs:指定行中划分数据字段的字段分隔符
# -f file:从指定的文件中读取数据
# -v var=value:定义gawk程序中的一个变量及默认值
# -mf N:指定要处理的数据文件中的最大字段数
# -mr N:指定数据文件中的最大数据行数
# -W keyword:指定gawk的兼容模式或警告等级

program是处理文本的指令,首先讲到打印命令print

1
2
# 它会在STDOUT中输出对应的字符
gawk '{print "hello world"}' file

你会发现上面的内容还是有点特点的,这个就要说到gawkprogram的格式:

1
2
3
4
5
6
7
8
# 在命令行中,以引号包裹全部内容,然后用{}包裹program命令,多条命令之间用;进行间隔
gawk '{print "hello world"; print "niubi"}'

# 通过-f指定program文件,则program文件中的内容可以不用;进行间隔,而是一行一条命令
{
print "hello world"
print "niubi"
}

gawk的一大特性是会自动的给一行的每个数据元素分配一个变量,这些变量可以通过$1~$n依次获取,其中$0表示的是该文本行的所有数据。比如:

1
gawk '{print $0}' /etc/passwd

如果需要修改某个数据,则可以通过$num=valiable进行修改,然后再将修改后的打印出来

1
gawk '{$4="hahahah";print $0}' file

如果将program独立成单独的文件来供调用,则可以借助参数:-f

1
gawk -f script.gawk file

gawk还支持在处理数据前后运行指定脚本用于输出提示内容,这个需要借助于参数:BEGINEND

1
2
3
4
5
6
# BEGIN:数据处理前执行的脚本
# END:数据处理后运行的脚本
# 格式:
BEGIN {commands};...;END {commands}

gawk 'BEGIN {print "this is the first"}; {print "处理数据中"}; END {print "数据处理结束"}' file

中级

gawk内置一整套类编程语言处理机制,而作为一款语言,它的一大重要特性就是变量。gawk中支持两种类型的变量:内建变量、自定义变量,其中内建变量之前我们接触过:

1
2
# $1~$n:标识了文本行中每个文本数据的位置
gawk '{print $1}' file

每一行的单个文本数据之间的间隔标识默认为空格/制表符,当然也可以在使用命令的时候就通过参数-F fs来指定,比如;

1
2
# 以:作为文本数据之间的间隔符
gawk -F : '{print $1}' /etc/passwd

但是在命令行直接通过-F的方式,其作用于整个program运行期间,不是很明智,其实我们可以在program中通过命令:FS指定输入字段分隔符,比如:

1
2
# FS也是gawk的内建变量之一
gawk 'BEGIN {FS=":"};{print $1}' /etc/passwd

此外还有输出字段分隔符:OFS,它的作用与FS类似,只不过用在print上。

1
2
3
4
5
6
# 默认情况下,gawk将OFS设置为空格,OFS的字符用于拼接print输出的每个字段
gawk 'BEGIN {FS=":";OFS="-"};{print $1,$2}' /etc/passwd

# 得到如下类似结果
mysql-x
redis-x

有别于FS设置间隔符的方式,gawk还提供了设置定长格式内建变量:FIELDWIDTHS,专门用于根据字段宽度来分割文本行数据的:

1
2
3
4
5
6
# 若一行分割后仍有剩余,则直接丢弃了
# 格式:num表示分割字符的长度
FIELDWIDTHS="num num num num ...."

# 将文本行按照3、5、6、9分割为4个数据文本,然后其打印出来
gawk '{BEGIN {FIELDWIDTHS="3 5 6 9"};{print $1,$2,$3,$4}}' file

然而一旦设置了FIELDWIDTHS,则不能够再改变,所起其不适用长度变化的字符串。

默认情况下,gawk是读取一行处理一行数据的,但是也可以借助输入记录分隔符:RS,一次性读取到文本的指定位置后再进行处理,比如:

1
2
3
# gawk将间隔空行后的每个内容部分当作一个文本处理对象,同时会将每个文本处理对象中以换行符间隔的内容当作一个文本数据。
# RS=""表示一个空行
gawk 'BEGIN {FS="\n"; RS=""}; {print $1}' file

RS相类似的还有输出记录分割符:ORS,只不过它作用的是print,默认情况下,gawkRSORS设置初始值为\n。比如:

1
gawk 'BEGIN {FS="\n";RS="";ORS="\n\n";OFS="  |  "}; {print $1,$2,$3,$4}' file

除了上述描述的内建变量外,gawk还提供了其它的内建变量,常用如下:

变量 描述
ARGC 获取命令行参数个数
ARGIND 当前文件在ARGV中的位置
ARGV 包含命令行参数的数组
CONVFMT 数字的转换格式。默认为%.6g
ENVIRON 当前shell环境变量及其值组成的关联数组
ERRNO 当读取、关闭输入文件发生错误时的系统错误号
FILENAME 用作gawk输入数据的数据文件的文件名
FNR 当前数据文件中的数据行数
IGNORECASE 设置非0值,忽略gawk命令中出现的字符串的字符大小写
NF 读取的数据文本中的字段总数
NR 已处理的输入记录数
OFMT 数字的输出格式,默认%.6g
RLENGTH 由match函数匹配的子字符串的长度
RSTART 由match函数匹配的子字符串的起始位置

上面摘录自《Linux命令行与Shell编程大全》的参数看着纷繁复杂,但是文中针对以下几个常用的做了描述:

  • ARGCARGV

    1
    2
    3
    4
    # ARGC:获取命令行传递过来的参数总个数,但是将脚本部分(program)不计算在内
    # ARGV:获取指定参数位置的值,格式 --> ARGV[num],num从0开始计数

    gawk 'BEGIN {print ARGC, ARGV[1]}' file
  • ENVIRON

    1
    2
    3
    4
    5
    6
    # ENVIRON:从gawk中直接获取Shell的环境变量
    # 格式:
    ENVIRON["环境变量名"] file

    # 比如
    gawk 'BEGIN {print ENVIRON["HOME"]}' file
  • NF

    1
    2
    # NF:获取数据文本的总个数,是数字
    gawk 'BEGIN {print $NF}' file
  • NRFNR

    1
    2
    3
    4
    5
    # NR:记录已经处理的文本块数,文本处理对象变换后,也不会重置
    gawk '{print "Count="NR}' file

    # FNR:记录当前文本处理对象的处理个数, 当文本处理对象变换后,FNR重置从1开始
    gawk '{print "Count="FNR}' file

上面描述的是gawk的内建变量,对于用户自己定义的变量,则可以直接通过如下方式:

1
2
3
4
5
6
# gawk变量名支持字母、数字、下划线,但不允许数字开头,数据有两种类型:数字、字符
# 格式
name=value

# 比如
gawk '{devin="wudashuai";print devin}' file

你应该也注意到了,在gawk中,变量的引用都是直接使用其名字的,不需要加$符号。此外,gawk还支持数学运算,如下:

1
2
# 除了普通的四则运算外,还支持%(取余)、**(幂运算)、^(幂运算)
gawk 'BEGIN {x=4; y=x*x+5; print y}' file

gawk中还提供了数组类型的数据,于其说它是数组,起始更像是Python中的字典,索引必须唯一,定义格式如下:

1
2
3
4
5
6
7
8
9
10
11
# index可以是字符、字符串、数字
# index、value如果是字符串就一定要用引号包裹好
name[index]=value

# 比如
gawk 'BEGIN {
name['lisan']=lisa1
name['wangwu']=wuwang1
name['zhaoliu']=zhaoliu
print name['lisan']
}' file

gawk为了遍历访问数据元素,还提供了for循环,也极其的像Python中的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 切记,此处的var记录的是name的索引值
# 格式
for ( var in name )
{
statement;
}

# 比如
gawk 'BEGIN {
name["lisan"]="lisa1"
name["wangwu"]="wuwang1"
name["zhaoliu"]="zhaoliu"
for (var in name)
{
print "key is ", var, "value is ", name[var]
}
}' file

而对于gawk中用不到的数组元素,也可以通过:delete删除:

1
2
3
4
5
6
7
8
9
# 格式;
delete array[index]

# 比如:
gawk 'BEGIN {
name["lisan"]="lisa1"
delete name["lisan"]
print name["lisan"]
}' file

高级

上面的都没有描述过区间筛选的内容,当我们需要定位指定的位置去操作指令的时候,这个时候就要讲到匹配模式

1
2
3
4
5
6
# 匹配模式的内容一定要位于{}的左花括号外
# 格式:
/pattern/{command}

# ak就是匹配模式中的字符,它会拿它在文本段中进行匹配
gawk '/ak/{print $1}' file

匹配模式同样支持正则:

1
gawk '/.d/{print $1}' file

然而上面的内容,针对的是一个处理单元,匹配模式还支持针对处理单元中的单个数据文本进行定位匹配处理,这个就需要借助命令:~

1
2
3
4
5
6
# $num外面没有{}包裹
# 格式:
$num ~ /pattern/{command}

# gawk会取第二个数据文本来匹配正则,匹配成功就执行command
gawk '$2 ~ /ba/{print $1, $NF}' file

同样,也可以增加!来表示对正则取反

1
2
# gawk会取第二个数据文本来匹配正则,不符合正则的元素都会被执行command
gawk '$2 !~ /ba/{print $1, $NF}' file

此外匹配模式下还支持数值比较:

1
2
# 比较的字符:==、>=、<=、>、<、!=
gawk '$2 == 2{print $0}' file

也支持字符串比较,不过只有一个比较符:==

1
2
# ==:必须完全匹配
gawk '$1 == "shuai"{print $1}'

gawk支持标准的if -- else语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 单个if
if (condition) commands

# 单个if支持换行
if (condition)
{
commands
}

# 完整的if--else
if (condition) commands; else commands

# 完整的if -- else换行
if (condition)
{
commands
} else
{
commands
}

比如:

1
2
3
4
5
6
7
8
gawk '{ if ($2 < 10)
{
print $2
} else
{
print $1
}
}'

同样,gawk支持whiledo -- whilefor三种循环:

  • while循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 格式
    while (condition)
    {
    commands
    }

    # 比如求出每一列的数据单个数据和
    total=0
    i=1
    while (i <= NF)
    {
    total+=$i # 看见木有,都支持这种表达式了
    i++
    }
  • do -- while循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #while循环不一样,do -- while循环是先执行循环体
    do
    {
    commands
    } while (condition)

    # 比如
    total=0
    i=1
    do
    {
    total += $i
    i++
    } while (i < NF)
  • for循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 格式
    for (var; condition; iteration)
    {
    command
    }

    # 比如
    total=0
    for(i=1;i<=NF;i++)
    {
    total += $i
    }

此外,gawk还支持跳出循环的break、continue

1
2
3
4
5
6
for(i=1;i<=NF;i++)
{
if (i==2) break
if (i==3) continue
print i
}

gawk的格式化输出中还有一个与print类似的命令:printf,它具有高度的可定制化输出的功能。

1
2
3
4
5
# 和c语言的printf简直神似,格式
printf "format string", var1, var2, var3 ...

# 没有参数的话,就不需要加参数
printf "wudashuai zei shuai"

但是有一点和print不大一样的是,printf默认是不会输出换行符的,所以需要用户自己在对应的位置加上换行符,否则输出内容将全部打印到一行中:

1
gawk '{printf "wudashuai zeishuai\n"}' file

printf中的format string支持格式化输出:

1
2
# 格式:
%-[width.prec]control-letter

其中control-letter支持以下参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# c:将一个数当作ASCII值显示,这个我没懂
gawk '{var=1;printf "%c\n",var}' file

# d、i:显示整数值,两个都一样
gawk '{var=1;printf "%d\n",var}' file
gawk '{var=1;printf "%i\n",var}' file

# f:显示浮点数
gawk '{var=1.8;printf "%f\n",var}' file

# e:显示浮点数,或科学计数,取决于哪种表述方式短
gawk '{var=1.8;printf "%e\n",var}' file

# g:显示浮点数,或科学计数,取决于哪种表述方式短
gawk '{var=1.8;printf "%g\n",var}' file

# s:显示文本字符串
gawk '{var="shuai";printf "%s\n",var}' file

# o:显示整数的8进制
gawk '{var=8;printf "%s\n",var}' file

# x、X:都是整数的16进制,只不过x和X最后采用的a~f是大小写的方式
gawk '{var=888;printf "%x\n",var}' file

width用于控制输出字段的最小宽度,大于这个宽度则原长度输出,反之则以空格补充。而prec用于控制显示小数点后的位数,或者是文本字符串中的最大字符数。

1
2
3
4
5
6
7
8
9
10
11
# 对于浮点数
gawk 'BEGIN {c=1.808; printf "%10.5f\n",c}' file

# 你会得到如下输出结果
1.80800

# 对于字符串
gawk 'BEGIN {c="ni shi zhen de niu bi a";printf "%10.5s\n",c}' out.txt

# 最终得到的结果
ni sh

默认情况下,输出内容在输出的区间内是右对齐的,如果需要左对齐,则需要借助:-

1
2
3
4
gawk 'BEGIN {c=1.808; printf "%-10.5f\n",c}' file

# 最终得到的结果
1.80800

此外,gawk还提供了很多的内建函数用于数据处理,以下就直接摘录《LInux命令行和Shell编程大全》:

对于数学运算,gawk提供如下函数:

函数 描述 举例
atan2(x, y) 求x/y的反正切,x和y以弧度为单位 gawk 'BEGIN {x=atan2(2,3); printf "%2.4f", x}' file
sin(x) x的正弦值,x以弧度为单位 gawk 'BEGIN {x=sin(2); printf "%2.4f", x}' file
cos(x) x的余弦值,x以弧度为单位 gawk 'BEGIN {x=cos(2); printf "%2.4f", x}' file
exp(x) x的指数函数,
x=exp(100)表示求x的100次方值
gawk 'BEGIN {x=exp(2); printf "%2.4f", x}' file
log(x) 求x的自然对数 gawk 'BEGIN {x=log(2); printf "%2.4f", x}' file
sqrt(x) x的平方根 gawk 'BEGIN {x=sqrt(2); printf "%2.4f", x}' file
int(x) 取x靠近0一侧的整数值 gawk 'BEGIN {x=int(2.1); printf "%2.4f", x}' file
rand() 取0~1之间的随机数值 gawk 'BEGIN {x=rand(); printf "%.4f", x}' file

不过gawk的算数运算也有最大的上限值,如果超过该值,则gawk报错。

对于位运算,gawk提供如下函数(都是操作值对应的二进制位):

函数 描述 举例
and(x,y) 执行x和y的按位与运算 gawk 'BEGIN {x=and(1,2); printf "%2.4f", x}' file
or(x,y) 执行x和y的按位或运算 gawk 'BEGIN {x=or(1,2); printf "%2.4f", x}' file
xor(x,y) 执行x和y的按位异或运算 gawk 'BEGIN {x=xor(1,2); printf "%2.4f", x}' file
compl(x) 执行x的补运算,这个不大懂 gawk 'BEGIN {x=compl(2); printf "%2.4f", x}' file
lshift(var, count) 将值var左移count位 gawk 'BEGIN {x=lshift(2,1); printf "%2.4f", x}' file
rshift(var, count) 将值var右移count位 gawk 'BEGIN {x=rshift(2,1); printf "%2.4f", x}' file

对有字符串处理,gawk提供如下函数:

  • asort(s [,d])

    数组s按数据元索值排序,索引值会被替换成表示新的排序顺序的连续数字,若指定了d,则排序后的数组存储在数组d

    1
    2
    3
    4
    5
    gawk 'BEGIN {asort(list, d); 
    for (var in d)
    {
    printf d[var]
    }}' file
  • asorti(s [,d])

    数组S按索引值排序,生成的数组会将索引值作为数据元索值,用连续数字索引来表明排序顺序,若指定了d,排序后的数组会存储在数组d

    1
    2
    3
    4
    5
    gawk 'BEGIN {asorti(list, d); 
    for (var in d)
    {
    printf d[var]
    }}' file
  • gensub(r, s, h [, t])

    查找变量$0,或者目标字符串t来匹配正则表达式r。如果h是以g/G开头的字符串,则用s替代匹配的文本。如果h是一个数字,它表示要替换掉第h处r匹配的地方。

    它不直接修改读取的数据,但会返回替换后的数据。

    默认会将匹配的每一处正则都替换为指定的数据。

    1
    2
    3
    4
    gawk '/BUGS/{
    data=gensub("BUGS", "wuxiang", "2", $1) --> 处理每个文本单元中的第一个数据文本
    print data
    }' out.txt
  • gsub(r,s [,t])

    查找变量$0,或者目标字符串t来匹配正则表达式r。如果找到了,就全部替换成字符串s

    它会直接修改读取的数据,默认替换所有匹配处的内容,并且会返回字符串t中匹配正则r的个数。

    1
    2
    3
    4
    gawk '{
    count=gsub("log", "wuxiang")
    printf "%2d %s\n", count, $0
    }' out.txt
  • index(s,t)

    返回字符串t字符串s中的索引位置(是字符串数目的位置,不是数据文本的位置),如果没找到则返回0

    1
    2
    3
    4
    5
    6
    gawk '{
    count=index($0, "log")
    if (count != 0) {
    print count
    }
    }' out.txt
  • length([s])

    返回字符串s的长度;若没有指定s,则返回$0的长度

    1
    2
    3
    4
    5
    6
    gawk '{
    count=length($1)
    if (count != 0) {
    print count
    }
    }' out.txt
  • match(s, r [,a])

    返回字符串s正则表达式r出现位置的索引。如果指定了数组a,它会存储s中匹配正则表达式的那部分。

    指定的数组a中会依次记录数据:匹配正则的起始位置正则匹配内容的字符长度正则匹配的字符内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    gawk '{
    count=match($0, "log", a)
    if (count != 0) {
    i=1
    printf "处理的文本位于: %d | ",FNR
    for (var in a) {
    if (i%3 == 1) {
    printf "匹配字符的起始位置为: %d | ", a[var]
    }
    if (i%3 == 2) {
    printf "正则匹配的内容长度为:%d | ", a[var]
    }
    if (i%3 == 0) {
    printf "正则匹配的内容为:%s\n", a[var]
    }
    i++
    }
    }
    }' out.txt

    # 你会得到类似如下结果
    处理的文本位于: 4 | 匹配字符的起始位置为: 8 | 正则匹配的内容长度为:3 | 正则匹配的内容为:log
  • split(s, a [,r])

    sFS字符或正则表达式r拆分开,放到数组a中,并返回数组a字段的总数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 正则r拆分的逻辑我也没弄懂
    gawk '{
    count=split($0,a, " i")
    printf "words is %d\n", count
    for (var in a)
    {
    printf "Loop %d, word is %s\n", FNR, $var
    }
    printf "----------------------------\n"
    }' out.txt
  • sprintf(format, variables)

    用提供的formatvariable返回一个类似于printf输出的字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    gawk '{
    count=split($0,a)
    printf "words is %d\n", count
    for (var in a)
    {
    data=sprintf("Loop %d, word is %s", FNR, $var)
    print data
    }
    printf "----------------------------\n"
    }' out.txt
  • sub(r, s [,t])

    变量$0或目标字符串t中查找正则表达式r的匹配。如果找到了,就用字符串s替换掉第一处匹配。

    1
    2
    3
    4
    gawk '{
    count=sub("name", "wuxiang", $1)
    printf "%s\n",$0
    }' out.txt
  • substr(s, i, [,n])

    返回字符串s索引值i开始的n个字符组成的子字符串。如果未提供n,则返回s剩下的部分

    1
    2
    3
    4
    gawk '{
    string=substr($1,2,3)
    printf "%s\n", string
    }' out.txt
  • tolower(s)

    字符串s中的所有字符转换为小写,并将转换后的字符串返回

    1
    2
    3
    4
    gawk '{
    string=tolower($1)
    printf "%s\n", string
    }' out.txt
  • toupper(s)

    字符串s中的所有字符转换为大写,并将转换结果返回

    1
    2
    3
    4
    gawk '{
    $1=toupper($1)
    printf "%s\n", $0
    }' out.txt

对于时间处理,gawk提供如下函数

  • mktime(datespec)

    将一个按YYYY MM DD HH MM SS [DST]格式指定的日期转换为时间戳值

    1
    2
    3
    4
    5
    gawk '{
    # 中间一定要用" "间隔,不然无法展示内容
    date=mktime(2014" "12" "21" "12" "59" "59)
    print date
    }' out.txt
  • strftime(format [,timestamp])

    将当前时间的时间戳timestamp转化格式化日期(采用shell函数的date())

    1
    2
    3
    4
    5
    gawk '{
    date=systime()
    time=strftime("%A, %B %d, %Y", date)
    print time
    }' out.txt

    关于strftimeformat格式可以参考连接:https://blog.csdn.net/huangzx3/article/details/82792734

  • systime()

    返回当前时间的时间戳

    1
    2
    3
    4
    gawk '{
    date=systime()
    print date
    }' out.txt

gawk同样支持自定义函数,定义格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 没有参数的话,就可以不写参数
# 格式:
function name([variable])
{
commands
}

# 在gawk命令中定义
gawk '
function name(name)
{
printf "$s",name
}
BEGIN {
name("shuai")
}
' file

gawk的函数允许通过return关键字返回值

1
2
3
4
5
function name(i)
{
str=name"最帅" # 这个拼接很佛系
return str
}

同样也可以在单独的文件中进行定义:

1
2
3
4
5
# 建议自己将文件与其它文件独立开,加.gawk后缀
function name(i)
{
printf "$s",name
}

如果是单独的gawk脚本函数文件,想要在命令中使用,则可以通过-f命令将脚本文件引入即可

1
gawk -f script.gawk -f script file

至此,gawk的知识点讲完,补充一句:sedgawk是大爷!