• Linux之(14)shell(6)gawk进阶


    Linux之shell(6)gawk进阶

    Author:onceday Date:2022年11月1日

    全系列文章请查看专栏: Linux Shell基础_Once_day的博客-CSDN博客

    漫漫长路,有人对你微笑过嘛…

    本文主要收集整理于以下文档:

    • 《Linux命令行与shell脚本编程大全》
    • 《鸟哥的Linux私房菜》

    1.使用变量

    gawk编程语言支持两种不同类型的变量:

    • 内建变量
    • 自定义变量

    gawk有一些内建变量,这些变量存放用来处理数据文件中的数据字段和记录的信息。也可以在gawk程序中创建自己的变量。

    1.1 内建变量

    数据字段变量是gawk中的一种内建变量类型,即使用$n来引用第n个位置的字段。如下:

    • $1引用记录中的第一个数据字段。
    • $2引用第二个字段。

    数据字段是有字符分割符来划定的,默认情况下,字段分割符是一个空白字符,也就是空格符或者制表符

    下面是常见的数据字段和记录变量:

    变量描述
    FIELDWIDTHS由空格分割的一列数字,定义了每个数据字段确切宽度
    FS输入字段分隔符
    RS输入记录分隔符
    OFS输出字段分隔符
    ORS输出记录分隔符

    下面是使用的例子:

    onceday@ubuntu:shell$ cat data1.txt
    data11,data12,data13,data14
    data21,data22,data23,data24
    data31,data32,data33,data34
    onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} {print $1,$2,$3}' data1.txt
    data11 data12 data13
    data21 data22 data23
    data31 data32 data33
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    可以看到,输入的字段分隔符由FS指定,而RS默认为\n,因此每一行为一个记录,每个记录按照,来区分不同的字段,前三个字段即上述对应的文本。输出OFSORS都是默认字符,因此为[space]\n

    onceday@ubuntu:shell$ gawk 'BEGIN{FS=",";OFS="-"} {print $1,$2,$3}' data1.txt
    data11-data12-data13
    data21-data22-data23
    data31-data32-data33
    onceday@ubuntu:shell$ gawk 'BEGIN{FS=",";OFS="-";ORS=";;;"} {print $1,$2,$3}' data1.txt
    data11-data12-data13;;;data21-data22-data23;;;data31-data32-data33;;;onceday@ubuntu:shell$ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    FIELDWIDTHS变量允许直接按照字段的宽度来读取指定文本,这是专门用于应对没有字段分割符的数据

    onceday@ubuntu:shell$ gawk 'BEGIN{FIELDWIDTHS="4 3 4 3 4 3 6"} {print $1,$2,$3,$4,$7}' data1.txt 
    data 11, data 12, data14
    data 21, data 22, data24
    data 31, data 32, data34
    
    • 1
    • 2
    • 3
    • 4

    下面是一个复杂的具体情况:

    Riley Mullen
    123 Main Street
    Chicago, IL 60601
    (312)55-1234
    
    • 1
    • 2
    • 3
    • 4

    对于这种情况,需要把FS变量设为换行符,表明数据流中的每行都是一个单独的字段,每行上的所有数据都属于同一个字段。为了判断一个记录从何开始,可以把记录分割符RS设置为“”,即空字符串,因为gawk会把空白行当成空字符串,即记录分割符。

    1.2 数据变量

    gawk提供了很多变量来辅助工作,比如提取shell环境的信息。

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

    其中ARGCARGV是从shell中获取的命令行参数,如下:

    onceday@ubuntu:shell$ gawk 'BEGIN{print ARGC,ARGV[0],ARGV[1]}' data1.txt
    2 gawk data1.txt
    
    • 1
    • 2

    可以发现,在脚本中引用gawk变量,变量名前面不加美元符

    ENVIRON变量使用关联数组来提取shell环境变量,关联数组用作数组的索引值,而不是竖直。

    onceday@ubuntu:shell$ gawk 'BEGIN{print ENVIRON["HOME"]}' data1.txt
    /home/onceday
    
    • 1
    • 2

    使用方法类似于字典,可以将shell的环境变量提取出来。

    NF变量可以在不知道具体位置的情况下,指定记录中的最后一个数据字段。

    需要注意的是,NF是含有最后一个行数的值,因此需要结合字段变量来使用

    onceday@ubuntu:shell$ cat data1.txt 
    data11,data12,data13,data14
    data21,data22,data23,data24
    data31,data32,data33,data34
    onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} {print $1,$NF}' data1.txt
    data11 data14
    data21 data24
    data31 data34
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    FNR是当前数据文件中的已处理过的记录数,NR是已处理过的记录总数,可能包含多个文件

    onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} {print $1,"FNR="FNR,"NR="NR}' data1.txt data1.txt 
    data11 FNR=1 NR=1
    data21 FNR=2 NR=2
    data31 FNR=3 NR=3
    data11 FNR=1 NR=4
    data21 FNR=2 NR=5
    data31 FNR=3 NR=6
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以清楚看到,FNR的值在处理第二个数据文件时被重置了,而NR变量则在处理第二个数据文件时,继续计数

    1.3 自定义变量

    gawk允许你定义自己的变脸并在程序代码中使用,gawk自定义变量名可以是任意数目的字母、数字和下划线,但是不能以数字开头。并且,要记住gawk变量名区分大小写

    可以直接在脚本中给变量赋值:

    onceday@ubuntu:shell$ gawk 'BEGIN{testing="this is a test";print testing}'
    this is a test
    
    • 1
    • 2

    也可以使用数学算式来处理数字值。

    onceday@ubuntu:shell$ gawk 'BEGIN{x=4;x=x * 2 + 3;print x}'
    11
    
    • 1
    • 2

    gawk命令行上也可以直接给变量赋值

    onceday@ubuntu:shell$ cat script1 
    BEGIN{FS=","}
    {print $n}
    onceday@ubuntu:shell$ gawk -f script1 n=2 data1.txt
    data12
    data22
    data32
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    但是有一个问题,这个变量值在BEGIN部分不可用,因此需要添加额外的命令参数-v

    onceday@ubuntu:shell$ gawk 'BEGIN{x=n; x=x * 2 + 3;print x}' n=5
    3
    onceday@ubuntu:shell$ gawk -v n=5 'BEGIN{x=n; x=x * 2 + 3;print x}'
    13
    
    • 1
    • 2
    • 3
    • 4
    2.处理数组

    gawk提供关联数组用于数组功能,它的索引值可以各种字符串,且每个索引字符串都需要能够唯一标识出赋给它的数据元素。

    下面可以定义一个数组元素:

    var[index] = element
    
    • 1

    var是变量名,index是关联数组的索引值,element是数据元素值。

    name["student1"] = "Mike"
    name["student2"] = "Job"
    name["student3"] = "Bob"
    
    • 1
    • 2
    • 3
    onceday@ubuntu:shell$ gawk 'BEGIN{name["st1"] = "bob";print name["st1"]}' data1.txt 
    bob
    
    • 1
    • 2

    使用非常简单,使用存储时的索引值去取值即可,也可以当成数组来使用:

    onceday@ubuntu:shell$ gawk 'BEGIN{
    > var[1]=21
    > var[2]=41
    > var[3]=54
    > var[4]=64
    > print var[1]+var[2]
    > }'
    62
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    遍历数组变量需要使用for语法,因为关联数组无法确定索引值是什么。

    for (var in array)
    {
    	statements
    }
    
    • 1
    • 2
    • 3
    • 4

    该语句每次循环时将关联数组array的下一个索引值赋给变量var,然后执行一遍statements,重要的是记住这个变量中存储的索引值而不是数组元素值。

    onceday@ubuntu:shell$ gawk 'BEGIN{
    >var[1]=21
    >var[2]=41
    >var[3]=54
    >var[4]=64
    >for (key in var) {
    >print "Index:",key," - value:",var[key]
    >}
    >}'
    Index: 1  - value: 21
    Index: 2  - value: 41
    Index: 3  - value: 54
    Index: 4  - value: 64
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    需要注意,并不能假设索引值key能按固定的顺序来返回,但是key总会遍历完成

    删除数组变量需要使用以下语法:

    delete array[index]
    
    • 1

    一旦从关联数组中删除了索引值,就没有办法再用它来提取元素值

    3.使用模式匹配

    gawk支持正则表达式,包括BRE基础模式和ERE扩展模式。

    onceday@ubuntu:shell$ cat data1.txt 
    data11,data12,data13,data14
    data21,data22,data23,data24
    data31,data32,data33,data34
    onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} /22/{print $1}' data1.txt
    data21
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    gawk程序会用正则表达式对记录中所有的数据字段进行匹配,包括字段分割符。

    匹配操作符(matching operator)允许将正则表达式限定在记录中的特定数据字段,匹配操作符是波浪线~

    $1 ~ /^data/
    
    • 1

    $1变量代表记录中的第一个数据字段。这个表达式会过滤出第一个字段以文本data开头的所有记录。

    onceday@ubuntu:shell$ cat data1.txt 
    data11,data12,data13,data14
    data21,data22,data23,data24
    data31,data32,data33,data34
    onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} $1 ~ /data2/{print $0}' data1.txt
    data21,data22,data23,data24
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    也可以使用!符号来排除正则表达式的匹配:

    $1 !~ /expression/
    
    • 1

    也可以在匹配模式中使用数学表达式,这个功能在匹配数据字段中的数字值时非常方便

    onceday@ubuntu:shell$ cat num.txt 
    12,47,875,58,658
    542,587,589,5632,45
    78,54,69,74,653
    onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} $2 > 100{print $0}' num.txt
    542,587,589,5632,45
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在数学表达式中可以使用常见的数学比较表达式:

    x == y: 值x等于y
    x <= y: 值x小于等于y
    x < y : 值x小于y
    x >= y: 值x大于等于y
    x > y : 值x大于y
    
    • 1
    • 2
    • 3
    • 4
    • 5

    对于文本数据也可以使用匹配,但表达式必须完全匹配,数据和模式严格匹配:

    onceday@ubuntu:shell$ cat num.txt
    12,47,875,58,658
    542,587,589,5632,45
    78,54,69,74,653
    onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} $2 == "47"{print $0}' num.txt
    12,47,875,58,658
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    4.结构化命令
    4.1 if语句
    if (condition)
    	statement1
    
    • 1
    • 2

    需要注意,if语句的花括号不能和其他地方的花括号弄混,必须独立出来:

    onceday@ubuntu:shell$ cat num.txt
    12,47,875,58,658
    542,587,589,5632,45
    78,54,69,74,653
    onceday@ubuntu:shell$ gawk 'BEGIN{FS=","} {if ($2 > 50) { print $0}}' num.txt
    542,587,589,5632,45
    78,54,69,74,653
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    if是支持else子语句的,单行上需要添加分号:

    if (condition) {statement1}; else {statement2};
    
    • 1
    4.2 while语句

    while语句为gawk程序提供了一个基本的循环功能,下面是while语句的格式。

    while (condition)
    {
    	statements
    }
    
    • 1
    • 2
    • 3
    • 4

    while循环允许遍历一组数据,并检查迭代的结束条件。

    onceday@ubuntu:shell$ cat num.txt 
    12,47,875,58,658
    542,587,589,5632,45
    78,54,69,74,653
    onceday@ubuntu:shell$ gawk 'BEGIN{FS="," };{
    total = 0
    i = 1
    while (i < 4)
    {
    total += $i
    i++
    }
    avg = total / 3
    print "Average:",avg
    }' num.txt
    Average: 311.333
    Average: 572.667
    Average: 67
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在while语句中也可以使用breakcontinue语句,允许从循环中跳出

    4.3 do-while语句

    类似于while语句,但会在检查语句条件之前执行命令。

    do {
    	statements
    } while (condition)
    
    • 1
    • 2
    • 3

    该语句会在求值之前先执行一次,其他和while没有区别

    4.4 for 语句

    风格和C语言是一致的。

    for (variable assignment; condition; iteration process)
    
    • 1
    onceday@ubuntu:shell$ gawk '
    > BEGIN {FS=","}
    > { total = 0
    > for (i = 1; i < 4; i++)
    > {
    >    total += $i
    > }
    > avg = total / 3
    > print "Average:",avg
    > }' num.txt
    Average: 311.333
    Average: 572.667
    Average: 67
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    5.格式化打印

    下面是printf命令格式:

    printf "format string", var1, var2 ...
    
    • 1

    该命令的使用方法和C语言类似。如下形式:

    %[modifier]control-letter
    
    • 1

    下面是可选的格式化指定符中的控制字母:

    控制字母描述
    c将一个数作为ASCII字符显示
    d显示一个整数值
    i显示一个整数值(跟d一样)
    e用科学计数法显示一个数
    f显示一个浮点值
    g用科学计数法或浮点数显示(选择较短的形式)
    o显示一个八进制
    s显示一个文本字符串
    x显示一个十六进制值
    X显示一个十六进制值,但用大写字母A-F

    下面是实例:

    onceday@ubuntu:shell$ gawk 'BEGIN{
    > x = 10 * 100
    > printf "The answer is: %e\n",x
    > }'
    The answer is: 1.000000e+03
    
    • 1
    • 2
    • 3
    • 4
    • 5

    除了上面所示的几种字符,还有3种修饰符可以进一部控制输出。

    • width,指定了输出字段最小宽度的数字值。如果输出短于这个值,printf会将文本右对齐,并用空格进行填充。如果输出比指定的宽度还要长,则按照实际的长度输出。
    • prec,这是一个数字值,指定了浮点数中小数点后面位数,或者文本字符串中显示的最大字符数。
    • -,指明在向格式化空间中放入数据时采用左对齐而不是右对齐。
    6.内建函数
    6.1数学函数
    函数描述
    atan2(x, y)x/y的反正切,x和y以弧度为单位
    cos(x)x的余弦,x以弧度为单位
    exp(x)x的指数部分
    int(x)x的整数部分,去靠近零一侧的值
    log(x)x的自然对数
    rand()比0大比1小的随机浮点值
    sin(x)x的正弦,x以弧度为单位
    sqrt(x)x的平方根
    srand(x)为计算随机数指定一个种子值
    6.2 位操作函数
    函数描述
    and(v1, v2)执行值v1和v2的按位与运算
    compl(val)执行val的补运算
    lshift(val,count)将值val左移count位
    or(v1,v2)执行值v1和v2的按位或运算
    rshift(val,count)将值val右移count位
    xor(v1, v2)执行值v1和v2的按位异或运算
    6.3 字符串函数
    函数描述
    asort(s [,d])将数组按照数据元素值排序,可指定存在d中。索引值被替换成表示新的排序顺序的连续数字
    asorti(s [,d])按照数据元素值排序,可指定存在d中.索引值作为数据元素值,连续数字索引表示排序顺序
    gensub(r, s, h [,t])查找变量$0或者目标t,来匹配正则表达式r,如果h是一个以g或G开头的字符串,就用s替换掉匹配的文本。如果h是一个数字,它表示要替换掉第n处r匹配的地方。
    gsub(r, s [,t])查找变量$0或者目标字符串t,来匹配正则表达式r,如果找到了,就全部替换为字符串s。
    index(s, t)返回字符串t在字符串s中的索引值,如果没有找到的话返回0
    length([s])返回字符串s的长度,如果没有指定的话,返回$0的长度
    match(s,r [,a])返回字符串s中正则表达式r出现位置的索引,如果指定了数组a,它会存储s中匹配正则表达式的那部分。
    split(s,a,[,r])将s用FS字符或正则表达式r分开放到数组a中,返回字段的总数
    sprintf(format,variables)用提供的format和variable返回一个类似于print输出的字符串
    sub(r,s [,t])在变量$0或目标字符串t中查找正则表达式r的匹配,如果找到了,就用字符串s替换掉第一处匹配。
    substr(s, i [,n])返回s中从索引值i开始的n个字符组成的子字符串。如果未提供n,则返回s剩下的部分。
    tolower(s)将s中的所有字符转换为小写
    toupper(s)将s中的所有字符转换为大写
    6.4 时间函数
    函数描述
    mktime(datespec)将一个按YYYY MM DD HH MM SS [DST]格式指定的日期转换成时间戳值
    strftime(format [, timestamp])将当前时间的时间戳货timestamp转化格式化日期(采用shell函数date()的格式)
    systime()返回当前时间的时间戳
    6.5 自定义函数

    定义函数:

    function name ([variables])
    {
    	statements
    }
    
    • 1
    • 2
    • 3
    • 4

    可以将自己写的gawk函数保存在一个函数库中,然后如下方式使用:

    gawk -f funclib -f script4 data1
    
    • 1

    下面是一个实例:

    onceday@ubuntu:shell$ cat num.txt 
    12,47,875,58,658
    542,587,589,5632,45
    78,54,69,74,653
    onceday@ubuntu:shell$ gawk '
    function myprint()
    {
        printf "%-16s - %s\n", $1, $2
    }
    BEGIN{FS=","}
    {
        myprint()
    }' num.txt
    12               - 47
    542              - 587
    78               - 54
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    [ 云计算 | AWS 实践 ] Java 如何重命名 Amazon S3 中的文件和文件夹
    Spring和SpringBoot比较,解惑区别
    JSON schema(模式)
    详解混合类型文件(Polyglot文件)的应用生成与检测
    Leedcode 每日一题: 2760. 最长奇偶子数组
    [附源码]Python计算机毕业设计SSM酒店客户管理系统(程序+LW)
    Linux编程:获取GMT(UTC)与Local时间,及其线程安全
    java毕业设计简历系统(附源码、数据库)
    【定语从句练习题】who、which
    git如何查看和修改用户名和邮箱
  • 原文地址:https://blog.csdn.net/Once_day/article/details/127660369