美文网首页三剑客ShellLinux
Linux文本编辑三剑客之---awk的使用

Linux文本编辑三剑客之---awk的使用

作者: 莫讠 | 来源:发表于2021-08-27 09:29 被阅读0次

    1、awk

    1.1 认识awk

    awk是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。

    awk其实不仅仅是工具软件,还是一种编程语言。不过,本文只介绍它的命令行用法,对于大多数场合,应该足够用了。

    1.2 使用awk

    1.2.1 语法

    awk [options] 'program' var=value file``…

    awk [options] -f programfile var=value file``…

    awk [options] 'BEGIN{ action;… } pattern{ action;… } END{ action;… }' file ...

    1.2.2 常用命令选项

    • -F fs:fs指定输入分隔符,fs可以是字符串或正则表达式,如-F:
    • -v var=value:赋值一个用户定义变量,将外部变量传递给awk
    • -f scripfile:从脚本文件中读取awk命令

    1.3 awk变量

    变量:内置和自定义变量,每个变量前加 -v 命令选项

    1.3.1 内置变量

    (1)格式

    • FS输入字段分隔符默认为空白字符
    • OFS输出字段分隔符,默认为空白字符
    • RS :输入记录分隔符,指定输入时的换行符,原换行符仍有效
    • ORS :输出记录分隔符,输出时用指定符号代替换行符
    • NF :字段数量,共有多少字段, NF引用最后一列,(NF-1)引用倒数第2列
    • NR行号,后可跟多个文件,第二个文件行号继续从第一个文件最后行号开始
    • FNR :各文件分别计数, 行号,后跟一个文件和NR一样,跟多个文件,第二个文件行号从1开始
    • FILENAME :当前文件名
    • ARGC :命令行参数的个数
    • ARGV :数组,保存的是命令行所给定的各参数,查看参数
    $ cat awkdemo
    
    hello:world
    linux:redhat:lalala:hahaha
    kevin:love:youou
    
    • -FS输入字段分隔符默认为空白字符
    $ awk -v FS=":" '{print $1,$2}' awkdemo 
    
    hello world
    linux redhat
    kevin love
    

    当然也可以写成另外一种形式:

    $ awk -F: '{print $1,$2}' awkdemo 
    
    hello world
    linux redhat
    kevin love
    

    另外通过awk命令可以指定自己所需要的字符,比如e:

    awk -v FS="e:" '{print $1,$2}' awkdemo 
    
    hello:world 
    linux:redhat:lalala:hahaha 
    kevin:lov youou
    

    但是如果用cut命令的话会导致报错,因为通过cut指定的分割符只能是单个字符,但是awk可以指定多个字符作为分割符

    $ cut -d "e:" -f1,2 awkdemo 
    
    cut: the delimiter must be a single character
    Try 'cut --help' for more information.
    
    • -OFS指定输出分隔符
    awk -v FS=':' -v OFS='---' '{print $1,$2}' awkdemo 
    

    这样便通过awk的方法将文本中的分隔符变成---

    hello---world
    linux---redhat
    kevin---love
    
    • NF :字段数量,共有多少字段, $NF引用最后一列,$(NF-1)引用倒数第2列
    $ awk -F: {'print NF'} awkdemo 
    
    2
    4
    3
    
    • NR行号,后可跟多个文件,第二个文件行号继续从第一个文件最后行号开始
    $ awk '{print NR}' awkdemo awkdemo 
    
    1
    2
    3
    4
    5
    6
    
    • FNR :各文件分别计数, 行号,后跟一个文件和NR一样,跟多个文件,第二个文件行号从1开始
    $ awk '{print FNR}' awkdemo awkdemo 
    
    1
    2
    3
    1
    2
    3
    
    • 可以直接通过awk来统计这两个文件一共多少行
    $ awk END'{print NR}' awkdemo awkdemo 
    
    6
    
    • FILENAME :当前文件名
    $ awk "{print FILENAME}" awkdemo demo
    
    awkdemo
    awkdemo
    awkdemo
    demo
    demo
    demo
    demo
    

    1.3.2 自定义变量

    自定义变量( 区分字符大小写)

    (1)-v var=value

    ① 先定义变量,后执行动作print

    相当于在我的当前的文件的前面可以添加任何我想要的字段

    $ awk -v name="kevin" -F: '{print name":"$0}' awkdemo 
    
    kevin:hello:world
    kevin:linux:redhat:lalala:hahaha
    kevin:kevin:love:youou
    

    ② 在执行动作print后定义变量

    $ awk -F: '{print name":"$0, name="kevin"}' awkdemo 
    
    :hello:world kevin
    kevin:linux:redhat:lalala:hahaha kevin
    kevin:kevin:love:youou kevin
    

    (2)在program 中直接定义

    可以把执行的动作放在脚本中,直接调用脚本 -f

    $ awk -F: -f awk.txt awkdemo 
    
    kevin hello
    kevin linux
    kevin kevin
    

    1.4 printf命令

    比print更强大

    1.4.1 格式

    (1)格式化输出

    printf "FORMAT"``, item1,item2, ...

    ① 必须指定FORMAT

    不会自动换行,需要显式给出换行控制符,\n

    ③ FORMAT 中需要分别为后面每个item 指定格式符

    (2)格式符:与item 一一对应

    • %c: 显示字符的ASCII码
    • %d, %i: 显示十进制整数
    • %e, %E: 显示科学计数法数值
    • %f :显示为浮点数,小数 %5.1f,带整数、小数点、整数共5位,小数1位,不够用空格补上
    • %g, %G :以科学计数法或浮点形式显示数值
    • %s :显示字符串;例:%5s最少5个字符,不够用空格补上,超过5个还继续显示
    • %u :无符号整数
    • %%: 显示% 自身

    (3)修饰符:放在%c[/d/e/f...]之间

    • #[.#]:第一个数字控制显示的宽度;第二个# 表示小数点后精度,%5.1f
    • -:左对齐(默认右对齐) %-15s
    • +:显示数值的正负符号 %+d

    1.4.2 演示

    当使用print的时候:

    $ awk -F: '{print $1,$3}' /etc/passwd | head -n 2
    
    root 0
    bin 1
    
    • 第一列显示小于20的字符串;第2列显示整数并换行
    $ awk -F: '{printf "%20s---%u\n",$1,$3}' /etc/passwd | head -n 2
                    root---0
                     bin---1
    
    • 使用-进行左对齐;第2列显示浮点数
    $ awk -F: '{printf "%-20s---%-15.3f\n",$1,$3}' /etc/passwd | head -n 2
    root                ---0.000          
    bin                 ---1.000  
    

    1.5 操作符

    1.5.1 格式

    • 算术操作符:
      • x+y, x-y, x*y, x/y, x^y, x%y
      • -x: 转换为负数
      • +x: 转换为数值
    • 字符串操作符:没有符号的操作符,字符串连接
    • 赋值操作符:
      • =, +=, -=, *=, /=, %=, ^=
      • ++, --
    • 比较操作符:
      • ==, !=, >, >=, <, <=
    • 模式匹配符:~ :左边是否和右边匹配包含 !~ :是否不匹配
    • 逻辑操作符:与&& ,或|| ,非!
    • 函数调用: function_name(argu1, argu2, ...)
    • 条件表达式(三目表达式):selector?if-true-expression:if-false-expression
      • 注释:先判断selector,如果符合执行 ? 后的操作;否则执行 : 后的操作

    1.5.2 演示

    (1)模式匹配符

    • 模式匹配符:~ :左边是否和右边匹配包含 !~ :是否不匹配
    $ df -h |awk -F: '$0 ~ /^\/dev/'
    
    /dev/mapper/cl-root                   165G   81G   84G  50% /
    /dev/sdb1                              37T   30T  7.0T  81% /GP
    /dev/sda1                             197M  155M   43M  79% /boot
    /dev/mapper/cl-var                     50G  1.8G   49G   4% /var
    /dev/sdc1                             3.7T  2.9T  788G  79% /media/sdisk/usb2
    /dev/sdd1                             1.9T  1.2T  728G  61% /media/sdisk/usb3
    

    这种操作模式有些类似于

    $ df -h |awk -F: '$0' | grep '/dev/'
    
    tmpfs                                 126G   21G  106G  17% /dev/shm
    /dev/mapper/cl-root                   165G   81G   84G  50% /
    /dev/sdb1                              37T   30T  7.0T  81% /GP
    /dev/sda1                             197M  155M   43M  79% /boot
    /dev/mapper/cl-var                     50G  1.8G   49G   4% /var
    /dev/sdc1                             3.7T  2.9T  788G  79% /media/sdisk/usb2
    /dev/sdd1                             1.9T  1.2T  728G  61% /media/sdisk/usb3
    

    结合之前提到的NF :字段数量,共有多少字段, NF引用最后一列,(NF-1)引用倒数第2列

    只显示磁盘使用状况和磁盘名

    $ df -h | awk '$0 ~ /^\/dev/ {print $(NF-1)"---"$1}'
    
    50%---/dev/mapper/cl-root
    81%---/dev/sdb1
    79%---/dev/sda1
    4%---/dev/mapper/cl-var
    79%---/dev/sdc1
    61%---/dev/sdd1
    

    找出磁盘占比大于40%的
    相当于在之前的命令的基础上添加了应该对第一行的筛选

    $ df -h | awk '$0 ~ /^\/dev/ {print $(NF-1)"---"$1}' |awk -F%  '$1>40'
    
    50%---/dev/mapper/cl-root
    81%---/dev/sdb1
    79%---/dev/sda1
    79%---/dev/sdc1
    61%---/dev/sdd1
    

    (2)逻辑操作符

    $ awk -F: '$3 >=6 && $3<=66 {print $1,$3}' /etc/passwd 
    
    shutdown 6
    halt 7
    mail 8
    uucp 10
    operator 11
    games 12
    gopher 13
    ftp 14
    rpc 32
    oprofile 16
    rpcuser 29
    gdm 42
    ntp 38
    apache 48
    mailnull 47
    smmsp 51
    mysql 27
    pegasus 66
    postgres 26
    
    $ awk -F: '$3==0 || $3>1000 {print $1,$3}' /etc/passwd 
    
    root 0
    nfsnobody 65534
    

    (3)条件表达式(三目表达式)

    • 先判断selector,如果符合执行 ? 后的操作;否则执行 : 后的操作
    $ awk -F: '{$3 >= 1000?usertype="common user":usertype="sysadmin user";print usertype,$1,$3}' /etc/passwd | head -n 30
    
    sysadmin user root 0
    sysadmin user bin 1
    sysadmin user daemon 2
    sysadmin user adm 3
    sysadmin user lp 4
    sysadmin user sync 5
    sysadmin user shutdown 6
    sysadmin user halt 7
    sysadmin user mail 8
    sysadmin user uucp 10
    sysadmin user operator 11
    sysadmin user games 12
    sysadmin user gopher 13
    sysadmin user ftp 14
    sysadmin user nobody 99
    sysadmin user dbus 81
    sysadmin user usbmuxd 113
    sysadmin user rpc 32
    sysadmin user oprofile 16
    sysadmin user vcsa 69
    sysadmin user rtkit 499
    sysadmin user abrt 173
    sysadmin user hsqldb 96
    sysadmin user avahi-autoipd 170
    sysadmin user saslauth 498
    sysadmin user postfix 89
    sysadmin user rpcuser 29
    common user nfsnobody 65534
    sysadmin user haldaemon 68
    sysadmin user gdm 42
    

    1.6 awk PATTERN 匹配部分

    1.6.1 格式

    PATTERN:根据pattern 条件,过滤匹配的行,再做处理

    (1)如果未指定:空模式,匹配每一行

    (2)/regular expression/ :仅处理能够模式匹配到的行,正则,需要用/ / 括起来

    (3)relational expression:关系表达式,结果为“真”才会被处理

    真:结果为非0值,非空字符串

    假:结果为空字符串或0值

    (4)line ranges:行范围

    startline(起始行),endline(结束行):/pat1/,/pat2/ 不支持直接给出数字,可以有多段,中间可以有间隔

    (5)BEGIN/END 模式

    BEGIN{}: 仅在开始处理文件中的文本之前执行一次

    END{} :仅在文本处理完成之后执行

    1.6.2 演示

    $ awk -F: '{print $1,$2}' awkdemo 
    hello world
    linux redhat
    kevin love
    
    • /regular expression/ :仅处理能够模式匹配到的行,正则,需要用/ / 括起来
    $ awk -F: '/kevin/{print $1,$2}' awkdemo 
    
    kevin love
    

    relational expression:关系表达式,结果为“真”才会被处理 ; 真:结果为非0值,非空字符串

    $ awk -F: "1{print $1}" awkdemo 
    
    hello:world
    linux:redhat:lalala:hahaha
    kevin:love:youou
    

    结果为空字符串或0值时,以下代码则不会被执行

    $ awk -F: "0{print $1}" awkdemo
    
    • 匹配第一个首字母为h到第一个首字母为a的之间所有字符
    $ awk -F: '/^h/,/^a/ {print $1}' awkdemo 
    
    hello
    linux
    kevin
    
    • BEGIN/END 模式

    - BEGIN{}: 仅在开始处理文件中的文本之前执行一次

    - END{} :仅在文本处理完成之后执行

    $ awk -F: 'BEGIN{print "第一列"}{print $1} END{print "结束"}' awkdemo
    
    第一列
    hello
    linux
    kevin
    结束
    

    1.7 awk有意思的案例

    $ seq 10
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

    因为i=1为真,所以全部打印

    $ seq 10 | awk 'i=1'
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

    因为i=0为假,所以不打印

    $ seq 10 | awk 'i=0'
    

    只打印奇数行;奇数行i进入时本身为空,被赋为!i,即不为空,所以打印;偶数行i进入时本身不为空,被赋为!i,即为空,所以不打印

    $ seq 10 | awk 'i=!i'
    
    1
    3
    5
    7
    9
    

    解释上一个操作,i在奇偶行的时候到底是一个什么样的值

    $ seq 10 |awk '{i=!i;print i}'
    
    1
    0
    1
    0
    1
    0
    1
    0
    1
    0
    

    当然你也可以选择只打印偶数行,相当于是上边打印奇数行的取反

    $ seq 10 | awk '!(i=!i)'
    
    2
    4
    6
    8
    10
    

    当然为了打印出偶数行,我们也可以对i进行赋值,这样i在最开始的时候就不为空,刚好与打印奇数行的时候相反

    $ seq 10 | awk -v 'i=1' 'i=!i'
    
    2
    4
    6
    8
    10
    

    1、awk高阶用法

    1.1 awk控制语句—if-else判断

    (1)语法

    if (condition){statement;…} [else statement] 双分支

    if (condition1){statement1} else if (condition2){statement2} else {statement3} 多分支

    (2)使用场景:对awk 取得的整行或某个字段做条件判断

    (3)演示

    $ awk -F: '{if($3>10 && $3<20)print $1,$3}' /etc/passwd
    
    operator 11
    games 12
    gopher 13
    ftp 14
    oprofile 16
    

    打印出所有路径为/bin/bash的所有用户名以及路径

    $ awk -F: '{if($NF=="/bin/bash") print $1,$NF}' /etc/passwd | head -n 10
    
    root /bin/bash
    chenys /bin/bash
    liutao /bin/bash
    git-admin /bin/bash
    sysadmin /bin/bash
    zhanglc /bin/bash
    mysql /bin/bash
    changlp /bin/bash
    zhanghc /bin/bash
    wangt /bin/bash
    

    输出总例数大于3的行

    $ awk -F: '{if(NF>2) print $0}' awkdemo
    
    linux:redhat:lalala:hahaha
    kevin:love:youou
    

    第三列大于等于100的为Common user用户, 反之则为root or Sysuser用户

    $ awk -F: '{if($3>=100) {printf "Common user: %s\n",$1} else{printf "root or Sysuser : %s\n",$1}}' 
    /etc/passwd | head -n 20
    
    root or Sysuser : root
    root or Sysuser : bin
    root or Sysuser : daemon
    root or Sysuser : adm
    root or Sysuser : lp
    root or Sysuser : sync
    root or Sysuser : shutdown
    root or Sysuser : halt
    root or Sysuser : mail
    root or Sysuser : uucp
    root or Sysuser : operator
    root or Sysuser : games
    root or Sysuser : gopher
    root or Sysuser : ftp
    root or Sysuser : nobody
    root or Sysuser : dbus
    Common user: usbmuxd
    root or Sysuser : rpc
    root or Sysuser : oprofile
    root or Sysuser : vcsa
    

    找到磁盘利用率超过40的设备名以及利用率

    $ df -h | awk -F% '/^\/dev/ {print $1}' | awk '$NF>40{print $1,$NF}'
    
    /dev/sda3 63
    /dev/sdb2 74
    /dev/sda2 69
    

    做一个判断,当test>=90的时候为excellent,当60 < test <90的时候为pass,当test<60的时候为failed

    $ awk 'BEGIN{test=100; if(test>=90){print "excellent"} else if(test>=60 && test <90){print "pass"} else{print "failed"}}'
    excellent
    
    $ awk 'BEGIN{test=55; if(test>=90){print "excellent"} else if(test>=60 && test <90){print "pass"} else{print "failed"}}'
    failed
    
    $ awk 'BEGIN{test=65; if(test>=90){print "excellent"} else if(test>=60 && test <90){print "pass"} else{print "failed"}}'
    pass
    

    1.2 awk控制语句—while循环

    (1)语法

    while``(condition){statement;…}

    注:条件“真”,进入循环;条件“假”, 退出循环

    (2)使用场景

    对一行内的多个字段逐一类似处理时使用

    对数组中的各元素逐一处理时使用

    (3)演示

    $ awk -F: '/^kevin/{i=1; while(i<=NF){print $i, length($i); i++}}' awkdemo
    
    kevin 5
    love 4
    youou 5
    

    为分隔,显示每一行的长度大于6的单词和其长度

    $ awk -F: '{i=1;while(i<=NF) {if(length($i)>=6){print $i,length($i)}; i++}}' 
    
    awkdemo
    redhat 6
    lalala 6
    hahaha 6
    

    计算从1加到100的和

    $ awk 'BEGIN{i=1;sum=0;while(i<=100){sum=sum+i;i++} {print sum}}'
    
    5050
    

    1.3 awk控制语句—do-while循环

    (1)语法

    do {statement;…} while (condition)

    意义:无论真假,至少执行一次循环体

    (2)计算1+2+3+...+100=5050

    $ awk 'BEGIN{i=1; sum=0; do{sum=sum+i;i++}while(i<=100) {print sum}}'
    5050
    

    1.4 awk控制语句—for循环

    (1)语法

    for``(expr1;expr2;expr3) {statement;…}

    (2)特殊用法:遍历数组中的元素

    for``(var in array) {``for``-body}

    (3)演示

    $ awk -F: '{for(i=1;i<=NF;i++){print $i,length($i)}}' awkdemo
    
    hello 5
    world 5
    linux 5
    redhat 6
    lalala 6
    hahaha 6
    kevin 5
    love 4
    youou 5
    
    $ cat sort.txt 
    xiaoming m 90
    xiaohong f 93
    xiaohei m 80
    xiaofang f 99
    

    统计男m、女f 各自的平均分

    $ awk '{m[$2]++; score[$2]+=$3} END{for(i in m){printf "%s:%6.2f\n",i,score[i]/m[i]}}' sort.txt 
    
    m: 85.00
    f: 96.00
    

    1.5 数值\字符串处理

    (1)数值处理

    • rand():返回0和1之间一个随机数,需有个种子 srand(),没有种子,一直输出0.237788

    演示:

    $ awk 'BEGIN{print rand()}'
    
    0.237788
    

    当加了srand()之后,就可以正常输出随机数了

    $ awk 'BEGIN{srand();print rand()}'
    0.625523
    
    $ awk 'BEGIN{srand();print rand()}'
    0.592696
    
    $ awk 'BEGIN{srand(); print int(rand()*100%50)+1}'
    
    21
    

    (2)字符串处理:

    • length([s]) :返回指定字符串的长度
    • sub(r,s,[t]) :对t 字符串进行搜索r 表示的模式匹配的内容,并将第一个匹配的内容替换为s
    • gsub(r,s,[t]) :对t 字符串进行搜索r 表示的模式匹配的内容,并全部替换为s 所表示的内容
    • split(s,array,[r]) :以r 为分隔符,切割字符串s ,并将切割后的结果保存至array 所表示的数组中,第一个索引值为1, 第二个索引值为2,…

    演示:
    将前面文本中的第一个:改变成-

    $ echo "2008:08:08 08:08:08" | awk 'sub(/:/,"-",$1)'
    
    2008-08:08 08:08:08
    
    $ echo "2008:08:08 08:08:08" | awk 'gsub(/:/,"-",$0)'
    
    2008-08-08 08-08-08
    
    • split(s,array,[r]) :以r 为分隔符,切割字符串s ,并将切割后的结果保存至array 所表示的数组中,第一个索引值为1, 第二个索引值为2,…
    $ echo "2008:08:08 08:08:08" | awk '{split($0,i,":")} {for(n in i){print n,i[n]}}'
    
    1 2008
    2 08
    3 08 08
    4 08
    5 08
    

    1.8 awk中调用shell 命令

    (1)system 命令

    空格是awk 中的字符串连接符,如果system中需要使用awk中的变量可以使用空格分隔,或者说除了awk 的变量外其他一律用"" 引用 起来。

    $ awk 'BEGIN{system("hostname")}'
    
    R290-1.GenePlus
    
    $ awk 'BEGIN{score=100; system("echo your score is " score)}'
    
    your score is 100
    

    相关文章

      网友评论

        本文标题:Linux文本编辑三剑客之---awk的使用

        本文链接:https://www.haomeiwen.com/subject/vstmiltx.html