zoukankan      html  css  js  c++  java
  • 运维之路-文本处理-三剑客(grep、sed、awk)

    转载:https://blog.csdn.net/zisefeizhu/article/details/82526749

    目录

     grep

    支持的正则 描述

    输出控制 描述

    内容行控制 描述

    示例:

    sed

    Usage:

    命令 描述

    地址 描述

    匹配删除(d)

     替换(s///)

     多重编辑(-e)

    打印和删除模式空间第一行(P 和 D)

    标签(:、b 和 t)

    获取总行数(#)

     选项

    选项 描述

    常用模式有:

    示例:

    内置变量

    示例:

    运算符 描述

    示例:

     流程控制

    语句 描述

    示例:

    printf 语句

     自定义函数

     需求案例

    1)分析 Nginx 日志

    2)两个文件对比

    3)合并两个文件

    4)将第一列合并到一行

    5)字符串拆分,统计出现的次数

    6)统计平均成绩

    7)费用统计

    8)数字字段最大值

    9)去除第一行和最后一行

    本篇博文整理于wiki

     grep
    过滤来自一个文件或标准输入匹配模式内容。

    除了 grep 外,还有 egrep、fgrep。egrep 是 grep 的扩展,相当于 g

    f,用的少。

    Usage: grep [OPTION]... PATTERN [FILE]...

    支持的正则 描述
    -E,--extended-regexp 模式是扩展正则表达式(ERE)

    -P,--perl-regexp 模式是 Perl 正则表达式

    -e,--regexp=PATTERN 使用模式匹配,可指定多个模式匹

    -f,--file=FILE 从文件每一行获取匹配模式

    -i,--ignore-case 忽略大小写

    -w,--word-regexp 模式匹配整个单词

    -x,--line-regexp 模式匹配整行

    -v,--invert-match 打印不匹配的行

    输出控制 描述
    -m,--max-count=NUM 输出匹配的结果 num 数

    -n,--line-number 打印行号

    -H,--with-filename 打印每个匹配的文件名

    -h,--no-filename 不输出文件名

    -o,--only-matching 只打印匹配的内容

    -q,--quiet 不输出正常信息

    -s, --no-messages 不输出错误信息

    -r,--recursive 递归目录

    -c,--count 只打印每个文件匹配的行数

    --include=FILE_PATTERN   只检索匹配的文件

    --exclude=FILE_PATTERN   跳过匹配的文件

    --exclude-from=FILE       跳过匹配的文件,来自文件模式

    --exclude-dir=PATTERN     跳过匹配的目录

    内容行控制 描述
    -B,--before-context=NUM 打印匹配的前几行

    -A,--after-context=NUM 打印匹配的后几行

    -C,--context=NUM 打印匹配的前后几行

    --color[=WHEN], 匹配的字体颜色

    示例:
    1)输出 b 文件中在 a 文件相同的行

    # grep -f a b

    2)输出 b 文件中在 a 文件不同的行

    # grep -v -f a b

    3) 匹配多个模式

    # echo "a bc de" |xargs -n1 |grep -e 'a' -e 'bc

    a

    bc

    4)去除空格 http.conf 文件空行或开头#号的行

    # grep -E -v "^$|^#" /etc/httpd/conf/httpd.conf

    5) 匹配开头不分大小写的单词

    # echo "A a b c" |xargs -n1 |grep -i a

    # echo "A a b c" |xargs -n1 |grep '[Aa]'

    A

    a

    6)只显示匹配的字符串

    # echo "this is a test" |grep -o 'is'

    is

    is

    7)输出匹配的前五个结果

    # seq 1 20 |grep -m 5 -E '[0-9]{2}'

    10

    11

    12

    13

    14

    8)统计匹配多少行

    # seq 1 20 |grep -c -E '[0-9]{2}'

    11

    9) 匹配 b 字符开头的行

    # echo "a bc de" |xargs -n1 |grep '^b'

    bc

    10) 匹配 de 字符结尾的行并输出匹配的行

    # echo "a ab abc abcd abcde" |xargs -n1 |grep -n 'de$'

    5:abcde

    11) 递归搜索/etc 目录下包含 ip 的 conf 后缀文件

    # grep -r '192.167.1.1' /etc --include *.conf

    12) 排除搜索 bak 后缀的文件

    # grep -r '192.167.1.1' /opt --exclude *.bak

    13) 排除来自 file 中的文件

    # grep -r '192.167.1.1' /opt --exclude-from file

    14) 匹配 41 或 42 的数字

    # seq 41 45 |grep -E '4[12]'

    41

    42

    15) 匹配至少 2 个字符

    # seq 13 |grep -E '[0-9]{2}'

    10

    11

    12

    13

    16) 匹配至少 2 个字符的单词,最多 3 个字符的单词

    # echo "a ab abc abcd abcde" |xargs -n1 |grep -E -w -o '[a-z]{2,3}'

    ab

    abc

    17) 匹配所有 IP

    # ifconfig |grep -E -o "[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}"

    18) 打印匹配结果及后 3 行

    # seq 1 10 |grep 5 -A 3

    5

    6

    7

    8

    19) 打印匹配结果及前 3 行

    # seq 1 10 |grep 5 -B 3

    2

    3

    4

    5

    20) 打印匹配结果及前后 3 行

    # seq 1 10 |grep 5 -C 3

    2

    3

    4

    5

    6

    7

    8

    21) 不显示输出

    不显示错误输出:

    # grep 'a' abc

    grep: abc: No such file or directory

    # grep -s 'a' abc

    # echo $?

    2

    不显示正常输出:

    # grep -q 'a' a.txt

    grep 支持基础和扩展正则表达式字符

    sed
    流编辑器,过滤和替换文本。

    工作原理:sed 命令将当前处理的行读入模式空间进行处理,处理完把结果输出,并清空模式空间。然后再将下一行读入模式空间进行处理输出,以此类推,直到最后一行。还有一个空间叫保持空间,又称暂存空间,可以暂时存放一些处理的数据,但不能直接输出,只能放到模式空间输出。

    这两个空间其实就是在内存中初始化的一个内存区域,存放正在处理的数据和临时存放的数据。

    Usage:
    sed [OPTION]... {script-only-if-no-other-script} [input-file]...

    sed [选项] '地址 命令' file

    选项 描述

    -n 不打印模式空间

    -e 执行脚本、表达式来处理

    -f 执行动作从文件读取执行

    -i 修改原文件

    -r 使用扩展正则表达式

    命令 描述
    s/regexp/replacement/ 替换字符串

    p 打印当前模式空间

    P 打印模式空间的第一行

    d 删除模式空间,开始下一个循环

    D 删除模式空间的第一行,开始下一个循环

    = 打印当前行号

    a ext 当前行追加文本

    i ext 当前行上面插入文本

    c ext 所选行替换新文本

    q 立即退出 sed 脚本

    r 追加文本来自文件

    : label label 为 b 和 t 命令

    b label 分支到脚本中带有标签的位置,如果分支不存在则分支到脚本

    的末尾

    t label 如果 s///是一个成功的替换,才跳转到标签

    h H 复制/追加模式空间到保持空间

    g G 复制/追加保持空间到模式空间

    x 交换模式空间和保持空间内容

    l 打印模式空间的行,并显示控制字符$

    n N 读取/追加下一行输入到模式空间

    w filename 写入当前模式空间到文件

    ! 取反、否定

    & 引用已匹配字符串

    地址 描述
    first~step 步长,每 step 行,从第 first 开始

    $ 匹配最后一行

    /regexp/ 正则表达式匹配行

    number 只匹配指定行

    addr1,addr2 开始匹配 addr1 行开始,直接 addr2 行结束

    addr1,+N 从 addr1 行开始,向后的 N 行

    addr1,~N 从 addr1 行开始,到 N 行结束

    借助以下文本内容作为示例讲解:

    # tail /etc/services

    nimgtw 48003/udp # Nimbus Gateway

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    匹配打印(p) 

    1)打印匹配 blp5 开头的行

    # tail /etc/services |sed -n '/^blp5/p'

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    2)打印第行

    # tail /etc/services |sed -n '1p'

    nimgtw 48003/udp # Nimbus Gateway

    3)打印第一行至第三行

    # tail /etc/services |sed -n '1,3p'

    nimgtw 48003/udp # Nimbus Gateway

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protoc

    isnetserv 48128/tcp # Image Systems Network Services

    4)打印奇数行

    # seq 10 |sed -n '1~2p'

    1

    3

    5

    7

    9

    5)打印匹配行及后一行

    # tail /etc/services |sed -n '/blp5/,+1p'

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    6)打印最后一行

    # tail /etc/services |sed -n '$p'

    iqobject 48619/udp # iqobject

    7)不打印最后一行

    # tail /etc/services |sed -n '$!p'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    Protocol

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    感叹号也就是对后面的命令取反。

    8)匹配范围

    # tail /etc/services |sed -n '/^blp5/,/^com/p'

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    匹配开头行到最后一行:

    # tail /etc/services |sed -n '/blp5/,$p'

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    以逗号分开两个样式选择某个范围。

    9)引用系统变量,用引号

    # a=1

    # tail /etc/services |sed -n ''$a',3p'

    # tail /etc/services |sed -n "$a,3p"

    sed 命令用单引号时,里面变量用单引号引起来,或者 sed 命令用双引号,因为双引号解释特殊符号原有意义。

    匹配删除(d)
    删除与打印使用方法类似,简单举几个例子。

    # tail /etc/services |sed '/blp5/d'

    nimgtw 48003/udp # Nimbus Gateway

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    # tail /etc/services |sed '1d'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    Protocol

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    # tail /etc/services |sed '1~2d'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/udp # iqobject

    # tail /etc/services |sed '1,3d'

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    去除空格 http.conf 文件空行或开头#号的行:

    # sed '/^#/d;/^$/d' /etc/httpd/conf/httpd.conf

    打印是把匹配的打印出来,删除是把匹配的删除,删除只是不用-n 选项。

     替换(s///)
    1)替换 blp5 字符串为 test

    # tail /etc/services |sed 's/blp5/test/'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    test 48129/tcp # Bloomberg locator

    test 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    全局替换加 g:

    # tail /etc/services |sed 's/blp5/test/g'

    2)替换开头是 blp5 的字符串并打印

    # tail /etc/services |sed -n 's/^blp5/test/p'

    test 48129/tcp # Bloomberg locator

    test 48129/udp # Bloomberg locator

    3)使用&命令引用匹配内容并替换

    # tail /etc/services |sed 's/48049/&.0/'

    3gpp-cbsp 48049.0/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    IP 加单引号:

    # echo '10.10.10.1 10.10.10.2 10.10.10.3' |sed -r 's/[^ ]+/"&"/g'

    "10.10.10.1" "10.10.10.2" "10.10.10.3"

    4)对 1-4 行的 blp5 进行替换

    # tail /etc/services | sed '1,4s/blp5/test/'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    test 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    5)对匹配行进行替换

    # tail /etc/services | sed '/48129/tcp/s/blp5/test/'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    test 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    6)二次匹配替换

    # tail /etc/services |sed 's/blp5/test/;s/3g/4g/'

    4gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    test 48129/tcp # Bloomberg locator

    test 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    7)分组使用,在每个字符串后面添加 123

    # tail /etc/services |sed -r 's/(.*) (.*)(#.*)/12test 3/'

    3gpp-cbsp 48049/tcp test # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp test # Image Systems Network Services

    isnetserv 48128/udp test # Image Systems Network Services

    blp5 48129/tcp test # Bloomberg locator

    blp5 48129/udp test # Bloomberg locator

    blp5 48129/udp test # Bloomberg locator

    com-bardac-dw 48556/tcp test # com-bardac-dw

    com-bardac-dw 48556/udp test # com-bardac-dw

    iqobject 48619/tcp test # iqobject

    iqobject 48619/udp test # iqobject

    matahari 49000/tcp test # Matahari Broker

    第一列是第一个小括号匹配,第二列第二个小括号匹配,第三列一样。将不变的字符串匹配分组,

    再通过数字按分组顺序反向引用。

    8)将协议与端口号位置调换

    # tail /etc/services |sed -r 's/(.*)(<[0-9]+>)/(tcp|udp)(.*)/13/24/'

    3gpp-cbsp tcp/48049 # 3GPP Cell Broadcast Service

    isnetserv tcp/48128 # Image Systems Network Services

    isnetserv udp/48128 # Image Systems Network Services

    blp5 tcp/48129 # Bloomberg locator

    blp5 udp/48129 # Bloomberg locator

    com-bardac-dw tcp/48556 # com-bardac-dw

    com-bardac-dw udp/48556 # com-bardac-dw

    iqobject tcp/48619 # iqobject

    iqobject udp/48619 # iqobject

    matahari tcp/49000 # Matahari Broker

    9)位置调换

    替换 x 字符为大写:

    # echo "abc cde xyz" |sed -r 's/(.*)x/1X/'

    abc cde Xyz

    456 与 cde 调换:

    # echo "abc:cde;123:456" |sed -r 's/([^:]+)(;.*:)([^:]+$)/321/'

    abc:456;123:cde

    10)注释匹配行后的多少行

    # seq 10 |sed '/5/,+3s/^/#/'

    1

    2

    3

    4

    #5

    #6

    #7

    #8

    9

    10

    11)注释指定多行

    # seq 5 |sed -r 's/^3|^4/&#/'

    1

    2

    3#

    4#

    5

    # seq 5 |sed -r '/^3|^4/s/^/#/'

    1

    2

    #3

    #4

    5

    # seq 5 |sed -r 's/^3|^4/#/'

    1

    2

    #3

    #4

    5

    12)去除开头和结尾空格或制表符

    # echo " 1 2 3 " |sed 's/^[ ]*//;s/[ ]*$//'

    1 2 3

     多重编辑(-e)
    # tail /etc/services |sed -e '1,2d' -e 's/blp5/test/'

    isnetserv 48128/udp # Image Systems Network Services

    test 48129/tcp # Bloomberg locator

    test 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    也可以使用分号分隔:

    # tail /etc/services |sed '1,2d;s/blp5/test/'

    7.2.5 添加新内容(a、i 和 c)

    1)在 blp5 上一行添加 test

    # tail /etc/services |sed '/blp5/i est'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    test

    blp5 48129/tcp # Bloomberg locator

    test

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    2)在 blp5 下一行添加 test

    # tail /etc/services |sed '/blp5/a est'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    test

    blp5 48129/udp # Bloomberg locator

    test

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    3)将 blp5 替换新行

    # tail /etc/services |sed '/blp5/c est'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    test

    test

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    4)在指定行下一行添加一行

    # tail /etc/services |sed '2a est'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    test

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    5)在指定行前面和后面添加一行

    # seq 5 |sed '3s/.*/txt &/'

    1

    2

    txt

    3

    4

    5

    # seq 5 |sed '3s/.*/& txt/'

    1

    2

    3

    txt

    4

    5

     读取文件并追加到匹配行后(r)

    # cat a.txt

    123

    456

    # tail /etc/services |sed '/blp5/r a.txt'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    123

    456

    blp5 48129/udp # Bloomberg locator

    123

    456

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    7.2.7 将匹配行写到文件(w)

    # tail /etc/services |sed '/blp5/w b.txt'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    bdd48556/t# bdd

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    # cat b.txt

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

     读取下一行(n 和 N)

    n 读取下一行到模式空间。

    N 追加下一行内容到模式空间,并以换行符 分隔。

    1)打印匹配的下一行

    # seq 5 |sed -n '/3/{n;p}'

    4

    2)打印偶数

    # seq 6 |sed -n 'n;p'

    2

    4

    6

    sed 先读取第一行 1,执行 n 命令,获取下一行 2,此时模式空间是 2,执行 p 命令,打印模式空

    间。 现在模式空间是 2,sed 再读取 3,执行 n 命令,获取下一行 4,此时模式空间为 4,执行 p 命

    令,以此类推。

    3)打印奇数

    # seq 6 |sed 'n;d'

    1

    3

    5

    sed 先读取第一行 1,此时模式空间是 1,并打印模式空间 1,执行 n 命令,获取下一行 2,执行 d

    命令,删除模式空间的 2,sed 再读取 3,此时模式空间是 3,并打印模式空间,再执行 n 命令,获

    取下一行 4,执行 d 命令,删除模式空间的 3,以此类推。

    # seq 6 |sed -n 'p;n'

    1

    3

    5

    4)每三行执行一次 p 命令

    # seq 6 |sed 'n;n;p'

    1

    2

    3

    3

    4

    5

    6

    6

    sed 先读取第一行 1,并打印模式空间 1,执行 n 命令,获取下一行 2,并打印模式空间 2,再执行 n

    命令,获取下一行 3,执行 p 命令,打印模式空间 3。sed 读取下一行 3,并打印模式空间 3,以此类

    推。

    5)每三行替换一次

    方法 1:

    # seq 6 |sed 'n;n;s/^/=/;s/$/=/'

    1

    2

    =3=

    4

    5

    =6=

    我们只是把 p 命令改成了替换命令。

    方法 2:

    这次用到了地址匹配,来实现上面的效果:

    # seq 6 |sed '3~3{s/^/=/;s/$/=/}'

    1

    2

    =3=

    4

    5

    =6=

    当执行多个 sed 命令时,有时相互会产生影响,我们可以用大括号{}把他们括起来。

    6)再看下 N 命令的功能

    # seq 6 |sed 'N;q'

    1

    2

    将两行合并一行:

    # seq 6 |sed 'N;s/ //'

    12

    34

    56

    第一个命令:sed 读取第一行 1,N 命令读取下一行 2,并以 2 追加,此时模式空间是 1 2,再执

    行 q 退出。

    为了进一步说明 N 的功能,看第二个命令:执行 N 命令后,此时模式空间是 1 2,再执行把 替换

    为空,此时模式空间是 12,并打印。

    # seq 5 |sed -n N;p

    1

    2

    3

    4

    # seq 6 |sed -n 'N;p'

    1

    2

    3

    4

    5

    6

    为什么第一个不打印 5 呢?

    因为 N 命令是读取下一行追加到 sed 读取的当前行,当 N 读取下一行没有内容时,则退出,也不会

    执行 p 命令打印当前行。

    当行数为偶数时,N 始终就能读到下一行,所以也会执行 p 命令。

    7)打印奇数行数时的最后一行

    # seq 5 |sed -n '$!N;p'

    1

    2

    3

    4

    5

    加一个满足条件,当 sed 执行到最后一行时,用感叹号不去执行 N 命令,随后执行 p 命令。

    打印和删除模式空间第一行(P 和 D)
    P 打印模式空间的第一行。

    D 删除模式空间的第一行。

    1)打印奇数

    # seq 6 |sed -n 'N;P'

    1

    3

    5

    2)保留最后一行

    # seq 6 |sed 'N;D'

    6

    读取第一行 1,执行 N 命令读取下一行并追加到模式空间,此时模式空间是 1 2,执行 D 命令删除

    模式空间第一行 1,剩余 2。

    读取第二行,执行 N 命令,此时模式空间是 3 4,执行 D 命令删除模式空间第一行 3,剩余 4。

    以此类推,读取最后一行打印时,而 N 获取不到下一行则退出,不再执行 D,因此模式空间只剩余 6

    就打印。

     保持空间操作(h 与 H、g 与 G 和 x)

    h 复制模式空间内容到保持空间(覆盖)。

    H 复制模式空间内容追加到保持空间。

    g 复制保持空间内容到模式空间(覆盖)。

    G 复制保持空间内容追加到模式空间。

    模式空间与保持空间内容换

    1)将匹配的内容覆盖到另一个匹配

    # seq 6 |sed -e '/3/{h;d}' -e '/5/g'

    1

    2

    4

    3

    6

    h 命令把匹配的 3 复制到保持空间,d 命令删除模式空间的 3。后面命令再对模式空间匹配 5,并用

    g 命令把保持空间 3 覆盖模式空间 5。

    2)将匹配的内容放到最后

    # seq 6 |sed -e '/3/{h;d}' -e '$G'

    1

    2

    4

    5

    6

    3

    3)交换模式空间和保持空间

    # seq 6 |sed -e '/3/{h;d}' -e '/5/x' -e '$G'

    1

    2

    4

    3

    6

    5

    看后面命令,在模式空间匹配 5 并将保持空间的 3 与 5 交换,5 就变成了 3,。最后把保持空间的 5

    追加到模式空间的。

    4)倒叙输出

    # seq 5 |sed '1!G;h;$!d'

    5

    4

    3

    2

    1

    分析下:

    1!G 第一行不执行把保持空间内容追加到模式空间,因为现在保持空间还没有数据。

    h 将模式空间放到保持空间暂存。

    $!d 最后一行不执行删除模式空间的内容。

    读取第一行 1 时,跳过 G 命令,执行 h 命令将模式空间 1 复制到保持空间,执行 d 命令删除模式空

    间的 1。

    读取第二行 2 时,模式空间是 2,执行 G 命令,将保持空间 1 追加到模式空间,此时模式空间是

    2 1,执行 h 命令将 2 1 覆盖到保持空间,d 删除模式空间。

    读取第三行 3 时,模式空间是 3,执行 G 命令,将保持空间 2 1 追加到模式空间,此时模式空间是

    3 2 1,执行 h 命令将模式空间内容复制到保持空间,d 删除模式空间。

    以此类推读到第 行时模式空间是 执行 命令将保持空间的 追加模式空间

    以此类推,读到第 5 行时,模式空间是 5,执行 G 命令,将保持空间的 4 3 2 1 追加模式空间,

    然后复制到模式空间,5 4 3 2 1,不执行 d,模式空间保留,输出。

    由此可见,每次读取的行先放到模式空间,再复制到保持空间,d 命令删除模式空间内容,防止输

    出,再追加到模式空间,因为追加到模式空间,会追加到新读取的一行的后面,循环这样操作, 就

    把所有行一行行追加到新读取行的后面,就形成了倒叙。

    5)每行后面添加新空行

    # seq 10 |sed G

    1

    2

    3

    4

    5

    6)打印匹配行的上一行内容

    # seq 5 |sed -n '/3/{x;p};h'

    2

    读取第一行 1,没有匹配到 3,不执行{x;p},执行 h 命令将模式空间内容 1 覆盖到保持空间。

    读取第二行 2,没有匹配到 3,不执行{x;p},执行 h 命令将模式空间内容 2 覆盖到保持空间。

    读取第三行 3,匹配到 3,执行 x 命令把模式空间 3 与保持空间 2 交换,再执行 p 打印模式空间 2.

    以此类推。

    7)打印匹配行到最后一行或下一行到最后一行

    # seq 5 |sed -n '/3/,$p'

    3

    4

    5

    # seq 5 |sed -n '/3/,${h;x;p}'

    3

    4

    5

    # seq 5 |sed -n '/3/{:a;N;$!ba;p}'

    3

    4

    5

    # seq 5 |sed -n '/3/{n;:a;N;$!ba;p}'

    4

    5

    匹配到 3 时,n 读取下一行 4,此时模式空间是 4,执行 N 命令读取下一行并追加到模式空间,此时

    模式空间是 4 5,标签循环完成后打印模式空间 4 5。

    标签(:、b 和 t)
    标签可以控制流,实现分支判断。

    : lable name 定义标签

    b lable 跳转到指定标签,如果没有标签则到脚本末尾

    t lable 跳转到指定标签,前提是 s///命令执行成功

    1)将换行符替换成逗号

    方法 1:

    # seq 6 |sed 'N;s/ /,/'

    1,2

    3,4

    5,6

    这种方式并不能满足我们的需求,每次 sed 读取到模式空间再打印是新行,替换 也只能对 N 命令

    追加后的 1 2 这样替换。

    这时就可以用到标签了:

    # seq 6 |sed ':a;N;s/ /,/;b a'

    1,2,3,4,5,6

    看看这里的标签使用,:a 是定义的标签名,b a 是跳转到 a 位置。

    sed 读取第

    行 1,N 命令读取下行 2,此时模式空间是 1 2$,执行替换,此时模式空间是

    1,2$,执行 b 命令再跳转到标签 a 位置继续执行 N 命令,读取下一行 3 追加到模式空间,此时模式

    空间是 1,2 3$,再替换,以此类推,不断追加替换,直到最后一行 N 读不到下一行内容退出。

    方法 2:

    # seq 6 |sed ':a;N;$!b a;s/ /,/g'

    1,2,3,4,5,6

    先将每行读入到模式空间,最后再执行全局替换。$!是如果是最后一行,则不执行 b a 跳转,最后

    执行全局替换。

    # seq 6 |sed ':a;N;b a;s/ /,/g'

    1

    2

    3

    4

    5

    6

    可以看到,不加$!是没有替换,因为循环到 N 命令没有读到行就退出了,后面的替换也就没执行。

    2)每三个数字加个一个逗号

    # echo "123456789" |sed -r 's/([0-9]+)([0-9]+{3})/1,2/'

    123456,789

    # echo "123456789" |sed -r ':a;s/([0-9]+)([0-9]+{3})/1,2/;t a'

    123,456,789

    # echo "123456789" |sed -r ':a;s/([0-9]+)([0-9]+{2})/1,2/;t a'

    1,23,45,67,89

    执行第一次时,替换最后一个,跳转后,再对 123456 匹配替换,直到匹配替换不成功,不执行 t 命

    令。

    7.2.12 忽略大小写匹配(I)

    # echo -e "a A b c" |sed 's/a/1/Ig'

    b

    c

    获取总行数(#)
    # seq 10 |sed -n '$='

     awk

    awk 是一个处理文本的编程语言工具,能用简短的程序处理标准输入或文件、数据排序、计算以及

    生成报表等等。

    在 Linux 系统下默认 awk 是 gawk,它是 awk 的 GNU 版本。可以通过命令查看应用的版本:ls -l

    /bin/awk

    基本的命令语法:awk option 'pattern {action}' file

    其中 pattern 表示 AWK 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。

    花括号用于根据特定的模式对一系列指令进行分组。

    awk 处理的工作方式与数据库类似,支持对记录和字段处理,这也是 grep 和 sed 不能实现的。

    在 awk 中,缺省的情况下将文本文件中的一行视为一个记录,逐行放到内存中处理,而将一行中的

    某一部分作为记录中的一个字段。用 1,2,3...数字的方式顺序的表示行(记录)中的不同字段。用

    $后跟数字,引用对应的字段,以逗号分隔,0 表示整个行。

     选项
    选项 描述
    -f program-file 从文件中读取 awk 程序源文件

    -F fs 指定 fs 为输入字段分隔符

    -v var=value 变量赋值

    --posix 兼容 POSIX 正则表达式

    --dump-variables=[file] 把 awk 命令时的全局变量写入文件,

    默认文件是 awkvars.out

    --profile=[file] 格式化 awk 语句到文件,默认是 awkprof.out

    8.3.2 模式

    常用模式有:
    Pattern Description

    BEGIN{ } 给程序赋予初始状态,先执行的工作

    END{ } 程序结束之后执行的一些扫尾工作

    /regular expression/ 为每个输入记录匹配正则表达式

    pattern && pattern 逻辑 and,满足两个模式

    pattern || pattern 逻辑 or,满足其中一个模式

    ! pattern 逻辑 not,不满足模式

    pattern1, pattern2 范围模式,匹配所有模式 1 的记录,直到匹配到模式 2

    而动作呢,就是下面所讲的 print、流程控制、I/O 语句等。

    示例:
    1)从文件读取 awk 程序处理文件

    # vi test.awk

    {print $2}

    # tail -n3 /etc/services |awk -f test.awk

    48049/tcp

    48128/tcp

    49000/tcp

    2)指定分隔符,打印指定字段

    打印第二字段,默认以空格分隔:

    # tail -n3 /etc/services |awk '{print $2}'

    48049/tcp

    48128/tcp

    48128/udp

    指定冒号为分隔符打印第一字段:

    # awk -F ':' '{print $1}' /etc/passwd

    root

    bin

    daemon

    adm

    lp

    sync

    ......

    还可以指定多个分隔符,作为同一个分隔符处理:

    # tail -n3 /etc/services |awk -F'[/#]' '{print $3}'

    iqobject

    iqobject

    Matahari Broker

    # tail -n3 /etc/services |awk -F'[/#]' '{print $1}'

    iqobject 48619

    iqobject 48619

    matahari 49000

    # tail -n3 /etc/services |awk -F'[/#]' '{print $2}'

    tcp

    udp

    tcp

    # tail -n3 /etc/services |awk -F'[/#]' '{print $3}'

    iqobject

    iqobject

    Matahari Broker

    # tail -n3 /etc/services |awk -F'[ /]+' '{print $2}'

    48619

    48619

    49000

    []元字符的意思是符号其中任意个字符,也就是说每遇到个/或#时就分隔个字段,当用多个

    分隔符时,就能更方面处理字段了。

    3)变量赋值

    # awk -v a=123 'BEGIN{print a}'

    123

    系统变量作为 awk 变量的值:

    # a=123

    # awk -v a=$a 'BEGIN{print a}'

    123

    或使用单引号

    # awk 'BEGIN{print '$a'}'

    123

    4)输出 awk 全局变量到文件

    # seq 5 |awk --dump-variables '{print $0}'

    1

    2

    3

    4

    5

    # cat awkvars.out

    ARGC: number (1)

    ARGIND: number (0)

    ARGV: array, 1 elements

    BINMODE: number (0)

    CONVFMT: string ("%.6g")

    ERRNO: number (0)

    FIELDWIDTHS: string ("")

    FILENAME: string ("-")

    FNR: number (5)

    FS: string (" ")

    IGNORECASE: number (0)

    LINT: number (0)

    NF: number (1)

    NR: number (5)

    OFMT: string ("%.6g")

    OFS: string (" ")

    ORS: string (" ")

    RLENGTH: number (0)

    RS: string (" ")

    RSTART: number (0)

    RT: string (" ")

    SUBSEP: string ("34")

    TEXTDOMAIN: string ("messages")

    5)BEGIN 和 END

    BEGIN 模式是在处理文件之前执行该操作,常用于修改内置变量、变量赋值和打印输出的页眉或标

    题。

    例如:打印页眉

    # tail /etc/services |awk 'BEGIN{print "Service Port Description ==="}{print

    $0}'

    Service Port Description

    ===

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    END 模式是在程序处理完才会执行。

    例如:打印页尾

    # tail /etc/services |awk '{print $0}END{print "=== END......"}'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Service

    isnetserv 48128/udp # Image Systems Network Service

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    ===

    END......

    6)格式化输出 awk 命令到文件

    # tail /etc/services |awk --profile 'BEGIN{print

    "Service Port Description ==="}{print $0}END{print "=== END......"}'

    Service Port Description

    ===

    nimgtw 48003/udp # Nimbus Gateway

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    ===

    END......

    # cat awkprof.out

    # gawk profile, created Sat Jan 7 19:45:22 2017

    # BEGIN block(s)

    BEGIN {

    print Service Port Description ===

    }

    # Rule(s)

    {

    print $0

    }

    # END block(s)

    END {

    print "=== END......"

    }

    7)/re/正则匹配

    匹配包含 tcp 的行:

    # tail /etc/services |awk '/tcp/{print $0}'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    matahari 49000/tcp # Matahari Broker

    匹配开头是 blp5 的行:

    # tail /etc/services |awk '/^blp5/{print $0}'

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    匹配第一个字段是 8 个字符的行:

    # tail /etc/services |awk '/^[a-z0-9]{8} /{print $0}'

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    如果没有匹配到,请查看你的 awk 版本(awk --version)是不是 3,因为 4 才支持{}

    8)逻辑 and、or 和 not

    匹配记录中包含 blp5 和 tcp 的行:

    # tail /etc/services |awk '/blp5/ && /tcp/{print $0}'

    blp5 48129/tcp # Bloomberg locator

    匹配记录中包含 blp5 或 tcp 的行:

    # tail /etc/services |awk '/blp5/ || /tcp/{print $0}'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    matahari 49000/tcp # Matahari Broker

    不匹配开头是#和空行:

    # awk '! /^#/ && ! /^$/{print $0}' /etc/httpd/conf/httpd.conf

    # awk '! /^#|^$/' /etc/httpd/conf/httpd.conf

    # awk '/^[^#]|"^$"/' /etc/httpd/conf/httpd.conf

    9)匹配范围

    # tail /etc/services |awk '/^blp5/,/^com/'

    blp5 48129/tcp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    对匹配范围后记录再次处理,例如匹配关键字下一行到最后一行:

    # seq 5 |awk '/3/,/^$/{printf /3/?"":$0" "}'

    4

    5

    另一种判断真假的方式实现:

    # seq 5 |awk '/3/{t=1;next}t'

    4

    5

    1 和 2 都不匹配 3,不执行后面{},执行 t,t 变量还没赋值,为空,空在 awk 中就为假,就不打印

    当前行。匹配到 3,执行 t=1,next 跳出,不执行 t。4 也不匹配 3,执行 t,t 的值上次赋值的 1,

    为真,打印当前行,以此类推。(非 0 的数字都为真,所以 t 可以写任意非 0 数字)

    如果想打印匹配行都最后一行,就可以这样了:

    # seq 5 |awk '/3/{t=1}t'

    3

    4

    5

    内置变量
    变量名 描述

    FS 输入字段分隔符,默认是空格或制表符

    OFS 输出字段分隔符,默认是空格

    RS 输入记录分隔符,默认是换行符

    ORS 输出记录分隔符,默认是换行符

    NF 统计当前记录中字段个数

    NR 统计记录编号,每处理一行记录,编号就会+1

    FNR 统计记录编号,每处理一行记录,编号也会+1,与 NR 不同的是,处理

    文件时,编号会重新计数。

    ARGC 命令行参数数量

    ARGV 命令行参数数组序列数组,下标从 0 开始,ARGV[0]是 awk

    ARGIND 当前正在处理的文件索引值。第一个文件是 1,第二个文件是 2,以此

    ENVIRON 当前系统的环境变量

    FILENAME 输出当前处理的文件名

    IGNORECASE 忽略大小写

    SUBSEP 数组中下标的分隔符,默认为"34"

    示例:
    1)FS 和 OFS

    在程序开始前重新赋值 FS 变量,改变默认分隔符为冒号,与-F 一样。

    #awk 'BEGIN{FS=":"}{print $1,$2}' /etc/passwd |head -n5

    root x

    bin x

    daemon x

    adm x

    lp x

    也可以使用-v 来重新赋值这个变量:

    # awk -vFS=':' '{print $1,$2}' /etc/passwd |head -n5 # 中间逗号被换成了 OFS 的默

    认值

    root x

    bin x

    daemon x

    adm x

    lp x

    由于 OFS 默认以空格分隔,反向引用多个字段分隔的也是空格,如果想指定输出分隔符这样:

    # awk 'BEGIN{FS=":";OFS=":"}{print $1,$2}' /etc/passwd |head -n5

    root:x

    bin:x

    daemon:x

    adm:x

    lp:x

    也可以通过字符串拼接实现分隔:

    # awk 'BEGIN{FS=":"}{print $1"#"$2}' /etc/passwd |head -n5

    root#x

    bin#x

    daemon#x

    adm#x

    lp#x

    2)RS 和 ORS

    RS 默认是 分隔每行,如果想指定以某个字符作为分隔符来处理记录:

    # echo "www.baidu.com/user/test.html" |awk 'BEGIN{RS="/"}{print $0}'

    www.baidu.com

    user

    test.html

    RS 也支持正则,简单演示下:

    # seq -f "str%02g" 10 |sed 'n;n;a-----' |awk 'BEGIN{RS="-+"}{print $1}'

    str01

    str04

    str07

    str10

    将输出的换行符替换为+号:

    # seq 10 |awk 'BEGIN{ORS="+"}{print $0}'

    1+2+3+4+5+6+7+8+9+10+

    替换某个字符:

    # tail -n2 /etc/services |awk 'BEGIN{RS="/";ORS="#"}{print $0}'

    iqobject 48619#udp # iqobject

    matahari 49000#tcp # Matahari Broker

    3)NF

    NF 是字段个数。

    # echo "a b c d e f" |awk '{print NF}'

    6

    打印最后一个字段:

    # echo "a b c d e f" |awk '{print $NF}'

    f

    打印倒数第二个字段:

    # echo "a b c d e f" |awk '{print $(NF-1)}'

    e

    排除最后两个字段:

    # echo "a b c d e f" |awk '{$NF="";$(NF-1)="";print $0}'

    a b c d

    排除第一个字段:

    # echo "a b c d e f" |awk '{$1="";print $0}'

    b c d e f

    4)NR 和 FNR

    NR 统计记录编号,每处理一行记录,编号就会+1,FNR 不同的是在统计第二个文件时会重新计数。

    打印行数:

    # tail -n5 /etc/services |awk '{print NR,$0}'

    1 com-bardac-dw 48556/tcp # com-bardac-dw

    2 com-bardac-dw 48556/udp # com-bardac-dw

    3 iqobject 48619/tcp # iqobject

    4 iqobject 48619/udp # iqobject

    5 matahari 49000/tcp # Matahari Broker

    打印总行数:

    # tail -n5 /etc/services |awk 'END{print NR}'

    5

    打印第三行:

    # tail -n5 /etc/services |awk 'NR==3'

    iqobject 48619/tcp # iqobject

    打印第三行第二个字段:

    # tail -n5 /etc/services |awk 'NR==3{print $2}'

    48619/tcp

    打印前三行:

    # tail -n5 /etc/services |awk 'NR<=3{print NR,$0}'

    1 com-bardac-dw 48556/tcp # com-bardac-dw

    2 com-bardac-dw 48556/udp # com-bardac-dw

    3 iqobject 48619/tcp # iqobject

    看下 NR 和 FNR 的区别:

    # cat a

    a

    b

    c

    # cat b

    c

    d

    e

    # awk '{print NR,FNR,$0}' a b

    1 1 a

    2 2 b

    3 3 c

    4 1 c

    5 2 d

    6 3 e

    可以看出 NR 每处理一行就会+1,而 FNR 在处理第二个文件时,编号重新计数。同时也知道 awk 处理

    两个文件时,是合并到一起处理。

    # awk 'FNR==NR{print $0"1"}FNR!=NR{print $0"2"}' a b

    a1

    b1

    c1

    c2

    d2

    e2

    当 FNR==NR 时,说明在处理第一个文件内容,不等于时说明在处理第二个文件内容。

    一般 FNR 在处理多个文件时会用到,下面会讲解。

    5)ARGC 和 ARGV

    ARGC 是命令行参数数量

    ARGV 是将命令行参数存到数组,元素由 ARGC 指定,数组下标从 0 开始

    # awk 'BEGIN{print ARGC}' 1 2 3

    4

    # awk 'BEGIN{print ARGV[0]}'

    awk

    # awk 'BEGIN{print ARGV[1]}' 1 2

    1

    # awk 'BEGIN{print ARGV[2]}' 1 2

    2

    6)ARGIND

    ARGIND 是当前正在处理的文件索引值,第一个文件是 1,第二个文件是 2,以此类推,从而可以通

    过这种方式判断正在处理哪个文件。

    # awk '{print ARGIND,$0}' a b

    1 a

    1 b

    1 c

    2 c

    2 d

    2 e

    # awk 'ARGIND==1{print "a->"$0}ARGIND==2{print "b->"$0}' a b

    a->a

    a->b

    a->c

    b->c

    b->d

    b->e

    7)ENVIRON

    ENVIRON 调用系统变量。

    # awk 'BEGIN{print ENVIRON["HOME"]}'

    /root

    如果是设置的环境变量,还需要用 export 导入到系统变量才可以调用:

    # awk 'BEGIN{print ENVIRON["a"]}'

    # export a

    # awk 'BEGIN{print ENVIRON["a"]}'

    123

    8)FILENAME

    FILENAME 是当前处理文件的文件名。

    # awk 'FNR==NR{print FILENAME"->"$0}FNR!=NR{print FILENAME"->"$0}' a b

    a->a

    a->b

    a->c

    b->c

    b->d

    b->e

    9)忽略大小写

    # echo "A a b c" |xargs -n1 |awk 'BEGIN{IGNORECASE=1}/a/'

    A

    a

    等于 1 代表忽略大小写。

     操作符 

    运算符 描述
    (....) 分组

    $ 字段引用

    ++ -- 递增和递减

    + - ! 加号,减号,和逻辑否定

    * / % 乘,除和取余

    + - 加法,减法

    | |& 管道,用于 getline,print 和 printf

    < > <= >= != == 关系运算符

    ~ !~ 正则表达式匹配,否定正则表达式匹配

    in 数组成员

    && || 逻辑 and,逻辑 or

    ?: 简写条件表达式:

    expr1 ? expr2 : expr3

    第一个表达式为真,执行 expr2,否则执行 expr3

    = += -= *= /= %= ^= 变量赋值运算符

    须知:

    在 awk 中,有 3 种情况表达式为假:数字是 0,空字符串和未定义的值。

    数值运算,未定义变量初始值为 0。字符运算,未定义变量初始值为空。

    举例测试:

    # awk 'BEGIN{n=0;if(n)print "true";else print "false"}'

    false

    # awk 'BEGIN{s="";if(s)print "true";else print "false"}'

    false

    # awk 'BEGIN{if(s)print "true";else print "false"}'

    false

    示例:
    1)截取整数

    # echo "123abc abc123 123abc123" |xargs -n1 | awk '{print +$0}'

    123

    0

    123

    # echo "123abc abc123 123abc123" |xargs -n1 | awk '{print -$0}'

    -123

    0

    -123

    2)感叹号

    打印奇数行:

    # seq 6 |awk 'i=!i'

    1

    3

    5

    打印偶数行:

    # seq 6 |awk '!(i=!i)'

    2

    4

    6

    读取第行:i 是未定义变量,也就是 i!0,!取反意思。感叹号右边是个布尔值,0 或空字符串为假,非 0 或非空字符串为真,!0 就是真,因此 i=1,条件为真打印当前记录。

    没有 print 为什么会打印呢?因为模式后面没有动作,默认会打印整条记录。

    读取第二行:因为上次 i 的值由 0 变成了 1,此时就是 i=!1,条件为假不打印。

    读取第三行:上次条件又为假,i 恢复初始值 0,取反,继续打印。以此类推...

    可以看出,运算时并没有判断行内容,而是利用布尔值真假判断输出当前行。

    2)不匹配某行

    # tail /etc/services |awk '!/blp5/{print $0}'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    isnetserv 48128/udp # Image Systems Network Services

    com-bardac-dw 48556/tcp # com-bardac-dw

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    iqobject 48619/udp # iqobject

    matahari 49000/tcp # Matahari Broker

    3)乘法和除法

    # seq 5 |awk '{print $0*2}'

    2

    4

    6

    8

    10

    # seq 5 |awk '{print $0%2}'

    1

    0

    1

    0

    1

    打印偶数行:

    # seq 5 |awk '$0%2==0{print $0}'

    2

    4

    打印奇数行:

    # seq 5 |awk '$0%2!=0{print $0}'

    1

    3

    5

    4)管道符使用

    # seq 5 |shuf |awk '{print $0|"sort"}

    1

    2

    3

    4

    5

    5)正则表达式匹配

    # seq 5 |awk '$0~3{print $0}'

    3

    # seq 5 |awk '$0!~3{print $0}'

    1

    2

    4

    5

    # seq 5 |awk '$0~/[34]/{print $0}'

    3

    4

    # seq 5 |awk '$0!~/[34]/{print $0}'

    1

    2

    5

    # seq 5 |awk '$0~/[^34]/{print $0}'

    1

    2

    5

    6)判断数组成员

    # awk 'BEGIN{a["a"]=123}END{if("a" in a)print "yes"}' </dev/null

    yes

    7)三目运算符

    # awk 'BEGIN{print 1==1?"yes":"no"}' # 三目运算作为一个表达式,里面不允许写 print

    yes

    # seq 3 |awk '{print $0==2?"yes":"no"}'

    no

    yes

    no

    替换换行符为逗号:

    # seq 5 |awk '{print n=(n?n","$0:$0)}'

    1

    1,2

    1,2,3

    1,2,3,4

    1,2,3,4,5

    # seq 5 |awk '{n=(n?n","$0:$0)}END{print n}'

    1,2,3,4,5

    说明:读取第一行时,n 没有变量,为假输出$0 也就是 1,并赋值变量 n,读取第二行时,n 是 1 为

    真,输出 1,2 以此类推,后面会一直为真。

    每三行后面添加新一行:

    # seq 10 |awk '{print NR%3?$0:$0 " txt"}'

    1

    2

    3

    txt

    4

    5

    6

    txt

    7

    8

    9

    txt

    10

    两行合并一行:

    # seq 6 |awk '{printf NR%2!=0?$0" ":$0" "}'

    1 2

    3 4

    5 6

    # seq 6 |awk 'ORS=NR%2?" ":" "'

    1 2

    3 4

    5 6

    # seq 6 |awk '{if(NR%2)ORS=" ";else ORS=" ";print}'

    8)变量赋值

    字段求和:

    # seq 5 |awk '{sum+=1}END{print sum}'

    5

    # seq 5 |awk '{sum+=$0}END{print sum}'

    15

     流程控制
    1)if 语句

    格式:if (condition) statement [ else statement ]

    单分支:

    # seq 5 |awk '{if($0==3)print $0}'

    3

    也支持正则匹配判断,一般在写复杂语句时使用:

    # echo "123abc#456cde 789aaa#aaabbb " |xargs -n1 |awk -F# '{if($2~/[0-9]/)print $2}'

    456cde

    # echo "123abc#456cde 789aaa#aaabbb " |xargs -n1 |awk -F# '{if($2!~/[0-9]/)print $2}'

    aaabbb

    # echo "123abc#456cde 789aaa#aaabbb" |xargs -n1 |awk -F# '$2!~/[0-9]/{print $2}'

    aaabbb

    双分支:

    # seq 5 |awk '{if($0==3)print $0;else print "no"}'

    no

    no

    3

    no

    no

    多分支:

    # cat file

    1 2 3

    4 5 6

    7 8 9

    # awk '{if($1==4){print "1"} else if($2==5){print "2"} else if($3==6){print "3"} else

    {print "no"}}' file

    no

    1

    no

    2)while 语句

    格式:while (condition) statement

    遍历打印所有字段:

    # awk '{i=1;while(i<=NF){print $i;i++}}' file

    1

    2

    3

    4

    5

    6

    7

    8

    9

    awk 是按行处理的,每次读取一行,并遍历打印每个字段。

    3)for 语句 C 语言风格

    格式:for (expr1; expr2; expr3) statement

    遍历打印所有字段:

    # cat file

    1 2 3

    4 5 6

    7 8 9

    # awk '{for(i=1;i<=NF;i++)print $i}' file

    1

    2

    3

    4

    5

    6

    7

    8

    9

    倒叙打印文本:

    # awk '{for(i=NF;i>=1;i--)print $i}' file

    3

    2

    1

    6

    5

    4

    9

    8

    7

    都换行了,这并不是我们要的结果。怎么改进呢?

    # awk '{for(i=NF;i>=1;i--){printf $i" "};print ""}' file # print 本身就会新打印一行

    3 2 1

    6 5 4

    9 8 7

    # awk '{for(i=NF;i>=1;i--)if(i==1)printf $i" ";else printf $i" "}' file

    3 2 1

    6 5 4

    6 5 4

    9 8 7

    在这种情况下,是不是就排除第一行和倒数第一行呢?我们正序打印看下

    排除第一行:

    # awk {for(i=2;i<=NF;i++){printf $i};print }file

    2 3

    5 6

    8 9

    排除第二行:

    # awk '{for(i=1;i<=NF-1;i++){printf $i" "};print ""}' file

    1 2

    4 5

    7 8

    IP 加单引号:

    # echo '10.10.10.1 10.10.10.2 10.10.10.3' |awk '{for(i=1;i<=NF;i++)printf

    "47"$i"47"}

    '10.10.10.1' '10.10.10.2' '10.10.10.3'

    47 是 ASCII 码,可以通过 showkey -a 命令查看。

    4)for 语句遍历数组

    格式:for (var in array) statement

    # seq -f "str%.g" 5 |awk '{a[NR]=$0}END{for(v in a)print v,a[v]}'

    4 str4

    5 str5

    1 str1

    2 str2

    3 str3

    5)break 和 continue 语句

    break 跳过所有循环,continue 跳过当前循环。

    # awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){break};print i}}'

    1

    2

    # awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){continue};print i}}'

    1

    2

    4

    5

    6)删除数组和元素

    格式:

    delete array[index] 删除数组元素

    delete array 删除数组

    # seq -f "str%.g" 5 |awk '{a[NR]=$0}END{delete a;for(v in a)print v,a[v]}'

    空的…

    # seq -f "str%.g" 5 |awk '{a[NR]=$0}END{delete a[3];for(v in a)print v,a[v]}'

    4 str4

    5 str5

    1 str1

    2 str2

    7)exit 语句

    格式:exit [ expression ]

    exit 退出程序,与 shell 的 exit 一样。[ expr ]是 0-255 之间的数字。

    # seq 5 |awk '{if($0~/3/)exit (123)}'

    # echo $?

    123

     数组

    数组:存储一系列相同类型的元素,键/值方式存储,通过下标(键)来访问值。

    awk 中数组称为关联数组,不仅可以使用数字作为下标,还可以使用字符串作为下标。

    数组元素的键和值存储在 awk 程序内部的一个表中,该表采用散列算法,因此数组元素是随机排

    序。

    数组格式:array[index]=value

    1)自定义数组

    # awk 'BEGIN{a[0]="test";print a[0]}'

    test

    2)通过 NR 设置记录下标,下标从 1 开始

    # tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[1]}'

    systemd-network

    # tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[2]}'

    zabbix

    # tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[3]}'

    user

    3)通过 for 循环遍历数组

    # tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(v in a)print a[v],v}'

    zabbix 4

    user 5

    admin 1

    systemd-bus-proxy 2

    systemd-network 3

    # tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(i=1;i<=NR;i++)print a[i],i}'

    admin 1

    systemd-bus-proxy 2

    systemd-network 3

    zabbix 4

    user 5

    上面打印的 i 是数组的下标。

    第一种 for 循环的结果是乱序的,刚说过,数组是无序存储。

    第二种 for 循环通过下标获取的情况是排序正常。

    所以当下标是数字序列时,还是用 for(expr1;expr2;expr3)循环表达式比较好,保持顺序不变。

    4)通过++方式作为下标

    # tail -n5 /etc/passwd |awk -F: '{a[x++]=$1}END{for(i=0;i<=x-1;i++)print a[i],i}'

    admin 0

    systemd-bus-proxy 1

    systemd-network 2

    zabbix 3

    user 4

    x 被 awk 初始化值是 0,没循环次1

    5)使用字段作为下标

    # tail -n5 /etc/passwd |awk -F: '{a[$1]=$7}END{for(v in a)print a[v],v}'

    /sbin/nologin admin

    /bin/bash user

    /sbin/nologin systemd-network

    /sbin/nologin systemd-bus-proxy

    /sbin/nologin zabbix

    6)统计相同字段出现次数

    # tail /etc/services |awk '{a[$1]++}END{for(v in a)print a[v],v}'

    2 com-bardac-dw

    1 3gpp-cbsp

    2 iqobject

    1 matahari

    2 isnetserv

    2 blp5

    # tail /etc/services |awk '{a[$1]+=1}END{for(v in a)print a[v],v}'

    2 com-bardac-dw

    1 3gpp-cbsp

    2 iqobject

    1 matahari

    2 isnetserv

    2 blp5

    # tail /etc/services |awk '/blp5/{a[$1]++}END{for(v in a)print a[v],v}'

    2 blp5

    第一个字段作为下标,值被++初始化是 0,每次遇到下标(第一个字段)一样时,对应的值就会被+1,因此实现了统计出现次数。

    想要实现去重的的话就简单了,只要打印下标即可。

    7)统计 TCP 连接状态

    # netstat -antp |awk '/^tcp/{a[$6]++}END{for(v in a)print a[v],v}'

    9 LISTEN

    6 ESTABLISHED

    6 TIME_WAIT

    8)只打印出现次数大于等于 2 的

    # tail /etc/services |awk '{a[$1]++}END{for(v in a) if(a[v]>=2){print a[v],v}}'

    2 com-bardac-dw

    2 iqobject

    2 isnetserv

    2 blp5

    9)去重

    只打印重复的行:

    # tail /etc/services |awk 'a[$1]++'

    isnetserv 48128/udp # Image Systems Network Services

    blp5 48129/udp # Bloomberg locator

    com-bardac-dw 48556/udp # com-bardac-dw

    iqobject 48619/udp # iqobject

    不打印重复的行:

    # tail /etc/services |awk '!a[$1]++'

    3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service

    isnetserv 48128/tcp # Image Systems Network Services

    blp5 48129/tcp # Bloomberg locator

    com-bardac-dw 48556/tcp # com-bardac-dw

    iqobject 48619/tcp # iqobject

    matahari 49000/tcp # Matahari Broker

    先明白一个情况,当值是 0 是为假,非 0 整数为真,知道这点就不难理解了。

    只打印重复的行说明:当处理第一条记录时,执行了++,初始值是 0 为假,就不打印,如果再遇到

    相同的记录,值就会+1,不为 0,则打印。

    不打印重复的行说明:当处理第一条记录时,执行了++,初始值是 0 为假,感叹号取反为真,打

    印,如果再遇到相同的记录,值就会+1,不为 0 为真,取反为假就不打印。

    # tail /etc/services |awk '{if(a[$1]++)print $1}'

    isnetserv

    blp5

    com-bardac-dw

    iqobject

    使用三目运算:

    # tail /etc/services |awk

    {print a[$1]++?$1:no}

    no

    no

    isnetserv

    no

    blp5

    no

    com-bardac-dw

    no

    iqobject

    no

    # tail /etc/services |awk '{if(!a[$1]++)print $1}'

    3gpp-cbsp

    isnetserv

    blp5

    com-bardac-dw

    iqobject

    matahari

    10)统计每个相同字段的某字段总数:

    # tail /etc/services |awk -F'[ /]+' '{a[$1]+=$2}END{for(v in a)print v, a[v]}'

    com-bardac-dw 97112

    3gpp-cbsp 48049

    iqobject 97238

    matahari 49000

    isnetserv 96256

    blp5 96258

    11)多维数组

    awk 的多维数组,实际上 awk 并不支持多维数组,而是逻辑上模拟二维数组的访问方式,比如

    a[a,b]=1,使用 SUBSEP(默认34)作为分隔下标字段,存储后是这样 a34b。

    示例:

    # awk 'BEGIN{a["x","y"]=123;for(v in a) print v,a[v]}'

    xy 123

    我们可以重新复制 SUBSEP 变量,改变下标默认分隔符:

    # awk 'BEGIN{SUBSEP=":";a["x","y"]=123;for(v in a) print v,a[v]}'

    x:y 123

    根据指定的字段统计出现次数:

    # cat file

    A 192.168.1.1 HTTP

    B 192.168.1.2 HTTP

    B 192.168.1.2 MYSQL

    C 192.168.1.1 MYSQL

    C 192.168.1.1 MQ

    D 192.168.1.4 NGINX

    # awk 'BEGIN{SUBSEP="-"}{a[$1,$2]++}END{for(v in a)print a[v],v}' file

    1 D-192.168.1.4

    1 A-192.168.1.1

    2 C-192.168.1.1

    2 B-192.168.1.2

    8.3.7 内置函数

    函数 描述

    int(expr) 截断为整数

    sqrt(expr) 平方根

    rand() 返回一个随机数 N,0 和 1 范围,0 < N < 1

    srand([expr]) 使用 expr 生成随机数,如果不指定,默认使用当前时间为种子,如

    果前面有种子则使用生成随机数

    asort(a, b) 对数组 a 的值进行排序,把排序后的值存到新的数组 b 中,新排序

    的数组下标从 1 开始

    asorti(a,b) 对数组 a 的下标进行排序,同上

    sub(r, s [, t]) 对输入的记录用 s 替换 r 正则匹配,t 可选针对某字段替换,但只替

    换第一个字符串

    gsub(r, s [, t]) 对输入的记录用 s 替换 r 正则匹配,t 可选针对某字段替换,否则替

    换所有字符串

    gensub(r, s, h [, t]) 对输入的记录用 s 替换 r 正则匹配,h 替换指定索引位置

    index(s, t) 返回 s 中字符串 t 的索引位置,0 为不存在

    length([s]) 返回 s 的长度

    match(s, r [, a]) 测试字符串 s 是否包含匹配 r 的字符串,如果不包含返回 0

    split(s, a [, r [,

    seps] ]) 根据分隔符 seps 将 s 分成数组 a

    substr(s, i [, n]) 截取字符串 s 从 i 开始到长度 n,如果 n 没指定则是剩余部分

    tolower(str) str 中的所有大写转换成小写

    toupper(str) str 中的所有小写转换成大写

    systime() 当前时间戳

    strftime([format [,

    timestamp[, utcflag]]])格式化输出时间,将时间戳转为字符串

    示例:

    1)int()

    截断为整数:

    # echo -e "123abc abc123 123abc123" | awk '{print int($0)}'

    123

    0

    123

    # awk 'BEGIN{print int(10/3)}'

    3

    2)sqrt()

    获取 9 的平方根:

    # awk 'BEGIN{print sqrt(9)}'

    3

    3)rand()和 srand()

    rand()并不是每次运行就是一个随机数,会一直保持一个不变:

    # awk 'BEGIN{print rand()}'

    0.237788

    当执行 srand()函数后,rand()才会发生变化,所以一般在 awk 着两个函数结合生成随机数,但是

    也有很大几率生成一样:

    # awk 'BEGIN{srand();print rand()}'

    0.31687

    如果想生成 1-10 的随机数可以这样:

    # awk 'BEGIN{srand();print int(rand()*10)}'

    4

    如果想更完美生成随机数,还得做相应的处理!

    4)asort()和 asorti()

    排序数组:

    # seq -f "str%.g" 5 |awk '{a[x++]=$0}END{s=asort(a,b);for(i=1;i<=s;i++)print

    b[i],i}'

    str1 1

    str2 2

    str3 3

    str4 4

    str5 5

    # seq -f "str%.g" 5 |awk '{a[x++]=$0}END{s=asorti(a,b);for(i=1;i<=s;i++)print

    b[i],i}'

    0 1

    1 2

    2 3

    3 4

    4 5

    asort 将 a 数组的值放到数组 b,a 下标丢弃,并将数组 b 的总行号赋值给 s,新数组 b 下标从 1 开始,然后遍历。

    5)sub()和 gsub()

    替换正则匹配的字符串:

    # tail /etc/services |awk '/blp5/{sub(/tcp/,"icmp");print $0}'

    blp5 48129/icmp # Bloomberg locator

    blp5 48129/udp # Bloomberg locator

    # tail /etc/services |awk '/blp5/{gsub(/c/,"9");print $0}'

    blp5 48129/t9p # Bloomberg lo9ator

    blp5 48129/udp # Bloomberg lo9ator

    # echo "1 2 2 3 4 5" |awk 'gsub(2,7,$2){print $0}'

    1 7 2 3 4 5

    # echo "1 2 3 a b c" |awk 'gsub(/[0-9]/, '0'){print $0}'

    0 0 0 a b c

    在指定行前后加一行:

    # seq 5 | awk 'NR==2{sub('/.*/',"txt &")}{print}'

    1

    txt

    2

    3

    4

    5

    # seq 5 | awk 'NR==2{sub('/.*/',"& txt")}{print}'

    1

    2

    txt

    3

    4

    5

    6)index()

    获取字段索引起始位置:

    # tail -n 5 /etc/services |awk '{print index($2,"tcp")}'

    7

    0

    7

    0

    7

    7)length()

    统计字段长度:

    # tail -n 5 /etc/services |awk '{print length($2)}'

    9

    9

    9

    9

    9

    统计数组的长度:

    # tail -n 5 /etc/services |awk '{a[$1]=$2}END{print length(a)}'

    3

    8)match

    # echo "123abc#456cde 789aaa#234bbb 999aaa#aaabbb" |xargs -n1 |awk '{print

    match($0,234)}'

    0

    8

    0

    如果记录匹配字符串 234,则返回索引位置,否则返回 0。

    那么,我们只想打印包含这个字符串的记录就可以这样:

    # echo "123abc#456cde 789aaa#234bbb 999aaa#aaabbb" |xargs -n1 |awk

    '{if(match($0,234)!=0)print $0}'

    789aaa#234bbb

    9)split()

    切分记录为数组 a:

    # echo -e "123#456#789 abc#cde#fgh" |awk '{split($0,a);for(v in a)print a[v],v}'

    123#456#789 1

    abc#cde#fgh 1

    以#号切分记录为数据 a:

    # echo -e "123#456#789 abc#cde#fgh" |awk '{split($0,a,"#");for(v in a)print a[v],v}'

    123 1

    456 2

    789 3

    abc 1

    cde 2

    fgh 3

    10)substr()

    截取字符串索引 4 到最后:

    # echo -e "123#456#789 abc#cde#fgh" |awk '{print

    substr($0,4)}'

    #456#789

    #cde#fgh

    截取字符串索引 4 到长度 5:

    # echo -e "123#456#789 abc#cde#fgh" |awk '{print substr($0,4,5)}'

    #456#

    #cde#

    11)tolower()和 toupper()

    转换小写:

    # echo -e "123#456#789 ABC#cde#fgh" |awk '{print tolower($0)}'

    123#456#789

    abc#cde#fgh

    转换大写:

    # echo -e "123#456#789 abc#cde#fgh" |awk '{print toupper($0)}'

    123#456#789

    ABC#CDE#FGH

    12)时间处理

    返回当前时间戳:

    # awk 'BEGIN{print systime()}'

    1483297766

    将时间戳转为日期和时间

    # echo "1483297766" |awk '{print strftime("%Y-%m-%d %H:%M:%S",$0)}'

    2017-01-01 14:09:26

     I/O 语句

    语句 描述
    getline 读取下一个输入记录设置给$0

    getline var 读取下一个输入记录并赋值给变量 var

    command | getline [var] 运行 Shell 命令管道输出到$0 或 va

    next 停止当前处理的输入记录后面动作

    print 打印当前记录

    printf fmt, expr-list 格式化输出

    printf fmt, expr-list >file 格式输出和写到文件

    system(cmd-line) 执行命令和返回状态

    print ... >> file 追加输出到文件

    print ... | command 打印输出作为命令输入

    示例:
    )getline

    获取匹配的下一行:

    # seq 5 |awk '/3/{getline;print}'

    4

    # seq 5 |awk '/3/{print;getline;print}'

    3

    4

    在匹配的下一行加个星号:

    # seq 5 |awk

    /3/{getline;sub(.*,&*);print}

    4*

    # seq 5 |awk '/3/{print;getline;sub(".*","&*")}{print}'

    1

    2

    3

    4*

    5

    2)getline var

    把 a 文件的行追加到 b 文件的行尾:

    # cat a

    a

    b

    c

    # cat b

    1 one

    2 two

    3 three

    # awk '{getline line<"a";print $0,line}' b

    1 one a

    2 two b

    3 three c

    把 a 文件的行替换 b 文件的指定字段:

    # awk '{getline line<"a";gsub($2,line,$2);print}' b

    1 a

    2 b

    3 c

    把 a 文件的行替换 b 文件的对应字段:

    # awk '{getline line<"a";gsub("two",line,$2);print}' b

    1 one

    2 b

    3 three

    3)command | getline [var]

    获取执行 shell 命令后结果的第一行:

    # awk 'BEGIN{"seq 5"|getline var;print var}'

    1

    循环输出执行 shell 命令后的结果:

    # awk 'BEGIN{while("seq 5"|getline)print}'

    1

    2

    3

    4

    5

    4)next

    不打印匹配行:

    # seq 5 |awk '{if($0==3){next}else{print}}'

    1

    2

    4

    5

    删除指定行:

    # seq 5 |awk 'NR==1{next}{print $0}'

    2

    3

    4

    5

    如果前面动作成功,就遇到 next,后面的动作不再执行,跳过。

    或者:

    # seq 5 |awk 'NR!=1{print}'

    2

    3

    4

    5

    把第一行内容放到每行的前面:

    # cat a

    hello

    1 a

    2 b

    3 c

    # awk 'NR==1{s=$0;next}{print s,$0}' a

    hello 1 a

    hello 2 b

    hello 3 c

    # awk 'NR==1{s=$0}NF!=1{print s,$0}' a

    hello 1 a

    hello 2 b

    hello 3 c

    5)system()

    执行 shell 命令判断返回值:

    # awk 'BEGIN{if(system("grep root /etc/passwd &>/dev/null")==0)print "yes";else print

    "no"}'

    yes

    6)打印结果写到文件

    # tail -n5 /etc/services |awk '{print $2 > "a.txt"}'

    # cat a.txt

    48049/tcp

    48128/tcp

    48128/udp

    48129/tcp

    48129/udp

    7)管道连接 shell 命令

    将结果通过 grep 命令过滤:

    # tail -n5 /etc/services |awk '{print $2|"grep tcp"}'

    48556/tcp

    48619/tcp

    49000/tcp

    printf 语句
    格式化输出,默认打印字符串不换行。

    格式:printf [format] arguments

    Format 描述

    %s 一个字符串

    %d,%i 一个小数

    %f 一个浮点数

    %.ns 输出字符串,n 是输出几个字符

    %m.nf 输出浮点数,m 是输出整数位数,n 是输出的小数位数

    %x 不带正负号的十六进制,使用 a 至 f 表示 10 到 15

    %X 不带正负号的十六进制,使用 A 至 F 表示 10 至 15

    %% 输出单个%

    %-5s 左对齐,对参数每个字段左对齐,宽度为 5

    %-4.2f 左对齐,宽度为 4,保留两位小数

    %5s 右对齐,不加横线表示右对齐

    示例:

    将换行符换成逗号:

    # seq 5 |awk '{if($0!=5)printf "%s,",$0;else print $0}'

    1,2,3,4,5

    小括号中的 5 是最后一个数字。

    输出一个字符:

    # awk 'BEGIN{printf "%.1s ","abc"}'

    a

    保留一个小数点:

    # awk 'BEGIN{printf "%.2f ",10/3}'

    3.33

    格式化输出:

    # awk 'BEGIN{printf "user:%s pass:%d ","abc",123}'

    user:abc pass:123

    左对齐宽度 10:

    # awk 'BEGIN{printf "%-10s %-10s %-10s ","ID","Name","Passwd"}'

    ID Name Passwd

    右对齐宽度 10:

    # awk 'BEGIN{printf "%10s %10s %10s ","ID","Name","Passwd"}'

    ID Name Passwd

    打印表格:

    # vi test.awk

    BEGIN{

    print "+--------------------+--------------------+";

    printf "|%-20s|%-20s| ","Name","Number";

    print "+--------------------+--------------------+";

    }

    # awk -f test.awk

    +--------------------+--------------------+

    |Name |Number |

    +--------------------+--------------------+

    格式化输出:

    # awk -F: 'BEGIN{printf "UserName Shell ----------------------------- "}{printf

    "%-20s %-20s ",$1,$7}END{print "END... "}' /etc/passwd

    打印十六进制:

    # awk 'BEGIN{printf "%x %X",123,123}'

    7b 7B

     自定义函数
    格式:function name(parameter list) { statements }

    示例:

    # awk 'function myfunc(a,b){return a+b}BEGIN{print myfunc(1,2)}'

    3

     需求案例
    1)分析 Nginx 日志
    日志格式:

    '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "

    $http_referer" "$http_user_agent" "$http_x_forwarded_for"'

    统计访问 IP 次数:

    # awk '{a[$1]++}END{for(v in a)print v,a[v]}' access.log

    统计访问访问大于 100 次的 IP:

    # awk '{a[$1]++}END{for(v in a){if(a[v]>100)print v,a[v]}}' access.log

    统计访问 IP 次数并排序取前 10:

    # awk '{a[$1]++}END{for(v in a)print v,a[v] |"sort -k2 -nr |head -10"}' access.log

    统计时间段访问最多的 IP:

    # awk '$4>="[02/Jan/2017:00:02:00" && $4<="[02/Jan/2017:00:03:00"{a[$1]++}END{for(v in

    a)print v,a[v]}' access.log

    统计上一分钟访问量:

    # date=$(date -d '-1 minute' +%d/%d/%Y:%H:%M)

    # awk -vdate=$date '$4~date{c++}END{print c}' access.log

    统计访问最多的 10 个页面:

    # awk '{a[$7]++}END{for(v in a)print v,a[v] |"sort -k1 -nr|head -

    n10"}' access.log

    统计每个 URL 数量和返回内容总大小:

    # awk '{a[$7]++;size[$7]+=$10}END{for(v in a)print a[v],v,size[v]}' access.log

    统计每个 IP 访问状态码数量:

    # awk '{a[$1" "$9]++}END{for(v in a)print v,a[v]}' access.log

    统计访问 IP 是 404 状态次数:

    # awk '{if($9~/404/)a[$1" "$9]++}END{for(i in a)print v,a[v]}' access.log

    2)两个文件对比
    找出 b 文件在 a 文件相同记录:

    # seq 1 5 > a

    # seq 3 7 > b

    方法 1:

    # awk 'FNR==NR{a[$0];next}{if($0 in a)print $0}' a b

    3

    4

    5

    # awk FNR==NR{a[$0];next}{if($0 in a)print FILENAME,$0}a b

    b 3

    b 4

    b 5

    # awk 'FNR==NR{a[$0]}NR>FNR{if($0 in a)print $0}' a b

    3

    4

    5

    # awk 'FNR==NR{a[$0]=1;next}(a[$0]==1)' a b # a[$0]是通过 b 文件每行获取值,如果是 1

    说明有

    # awk 'FNR==NR{a[$0]=1;next}{if(a[$0]==1)print}' a b

    3

    4

    5

    方法 2:

    # awk 'FILENAME=="a"{a[$0]}FILENAME=="b"{if($0 in a)print $0}' a b

    3

    4

    5

    方法 3:

    # awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]==1' a b

    3

    4

    5

    找出 b 文件在 a 文件不同记录:

    方法 1:

    # awk 'FNR==NR{a[$0];next}!($0 in a)' a b

    6

    7

    # awk 'FNR==NR{a[$0]=1;next}(a[$0]!=1)' a b

    # awk 'FNR==NR{a[$0]=1;next}{if(a[$0]!=1)print}' a b

    6

    7

    方法 2:

    # awk 'FILENAME=="a"{a[$0]=1}FILENAME=="b" && a[$0]!=1' a b

    方法 3:

    # awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]!=1' a b

    3)合并两个文件
    将 a 文件合并到 b 文件:

    # cat a

    zhangsan 20

    lisi 23

    wangwu 29

    # cat b

    zhangsan man

    lisi woman

    wangwu man

    # awk 'FNR==NR{a[$1]=$0;next}{print a[$1],$2}' a b

    zhangsan 20 man

    lisi 23 woman

    wangwu 29 man

    # awk 'FNR==NR{a[$1]=$0}NR>FNR{print a[$1],$2}' a b

    zhangsan 20 man

    lisi 23 woman

    wangwu 29 man

    将 a 文件相同 IP 的服务名合并:

    # cat a

    192.168.1.1: httpd

    192.168.1.1: tomcat

    192.168.1.2: httpd

    192.168.1.2: postfix

    192.168.1.3: mysqld

    192.168.1.4: httpd

    # awk 'BEGIN{FS=":";OFS=":"}{a[$1]=a[$1] $2}END{for(v in a)print v,a[v]}' a

    192.168.1.4: httpd

    192.168.1.1: httpd tomcat

    192.168.1.2: httpd postfix

    192.168.1.3: mysqld

    说明:数组 a 存储是$1=a[$1] $2,第一个 a[$1]是以第一个字段为下标,值是 a[$1] $2,也就是

    $1=a[$1] $2,值的 a[$1]是用第一个字段为下标获取对应的值,但第一次数组 a 还没有元素,那么

    a[$1]是空值,此时数组存储是 192.168.1.1=httpd,再遇到 192.168.1.1 时,a[$1]通过第一字段

    下标获得上次数组的 httpd,把当前处理的行第二个字段放到上一次同下标的值后面,作为下标

    192.168.1.1 的新值。此时数组存储是 192.168.1.1=httpd tomcat。每次遇到相同的下标(第一个

    字段)就会获取上次这个下标对应的值与当前字段并作为此下标的新值。

    4)将第一列合并到一行
    # cat file

    1 2 3

    4 5 6

    7 8 9

    # awk '{for(i=1;i<=NF;i++)a[i]=a[i]$i" "}END{for(v in a)print a[v]}' file

    1 4 7

    2 5 8

    3 6 9

    说明:

    for 循环是遍历每行的字段,NF 等于 3,循环 3 次。

    读取第一行时:

    第一个字段:a[1]=a[1]1" " 值 a[1]还未定义数组,下标也获取不到对应的值,所以为空,因此

    a[1]=1 。

    第二个字段:a[2]=a[2]2" " 值 a[2]数组 a 已经定义,但没有 2 这个下标,也获取不到对应的

    值,为空,因此 a[2]=2 。

    第三个字段:a[3]=a[3]3" " 值 a[2]与上面一样,为空,a[3]=3 。

    读取第二行时:

    第一个字段:a[1]=a[1]4" " 值 a[2]获取数组 a 的 2 为下标对应的值,上面已经有这个下标了,

    对应的值是 1,因此 a[1]=1 4

    第二个字段:a[2]=a[2]5" " 同上,a[2]=2 5

    第三个字段:a[3]=a[3]6" " 同上,a[2]=3 6

    读取第三行时处理方式同上,数组最后还是三个下标,分别是 1=1 4 7,2=2 5 8,3=3 6 9。最后

    for 循环输出所有下标值。

    5)字符串拆分,统计出现的次数
    字符串拆分

    方法 1:

    # echo "hello world" |awk -F '' '{print $1}'

    h

    # echo "hello" |awk -F '' '{for(i=1;i<=NF;i++)print $i}'

    h

    e

    l

    l

    o

    方法 2:

    # echo "hello" |awk '{split($0,a,"''");for(v in a)print a[v]}'

    l

    o

    h

    e

    l

    统计字符串中每个字母出现的次数:

    # echo "a.b.c,c.d.e" |awk -F '[.,]' '{for(i=1;i<=NF;i++)a[$i]++}END{for(v in a)print

    v,a[v]}'

    a 1

    b 1

    c 2

    d 1

    e 1

    6)统计平均成绩
    # cat file

    job 80

    dave 84

    tom 75

    dave 73

    job 72

    tom 83

    dave 88

    # awk '{a[$1]+=$2;b[$1]++}END{for(i in a)print i,a[i]/b[i]}' file

    job 76

    dave 81.6667

    tom 79

    7)费用统计
    # cat file

    zhangsan 8000 1

    zhangsan 5000 1

    lisi 1000 1

    lisi 2000 1

    wangwu 1500 1

    zhaoliu 6000 1

    zhaoliu 2000 1

    zhaoliu 3000 1

    # awk '{name[$1]++;cost[$1]+=$2;number[$1]+=$3}END{for(v in name)print

    v,cost[v],number[v]}' file

    zhangsan 5000 1

    lisi 3000 2

    wangwu 1500 1

    zhaoliu 11000 3

    8)获取数字字段最大值
    # cat file

    a b 1

    c d 2

    e f 3

    g h 3

    i j 2

    获取第三字段最大值:

    # awk 'BEGIN{max=0}{if($3>max)max=$3}END{print max}' file

    3

    打印第三字段最大行:

    # awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)print v,a[v],max}' a

    g h 3 3 3

    e f 3 3 3

    c d 2 2 3

    a b 1 1 3

    i j 2 2 3

    # awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)if(a[v]==max)print v}' a

    g h 3

    e f 3

    9)去除第一行和最后一行
    # seq 5 |awk 'NR>2{print s}{s=$0}'

    2

    3

    4

    读取第一行,NR=1,不执行 print s,s=1

    读取第二行,NR=2,不执行 print s,s=2 (大于为真)

    读取第三行,NR=3,执行 print s,此时 s 是上一次 p 赋值内容 2,s=3

    最后一行,执行 print s,打印倒数第二行,s=最后一行

    获取 Nginx 负载均衡配置端 IP 和端口:

    # cat nginx.conf

    upstream example-servers1 {

    server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s;

    }

    upstream example-servers2 {

    server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s;

    server 127.0.0.1:82 backup;

    }

    # awk '/example-servers1/,/}/{if(NR>2){print s}{s=$2}}' nginx.conf

    127.0.0.1:80

    # awk '/example-servers1/,/}/{if(i>1)print s;s=$2;i++}' nginx.conf

    # awk '/example-servers1/,/}/{if(i>1){print s}{s=$2;i++}}' nginx.conf

    127.0.0.1:80

    读取第一行,i 初始值为 0,0>1 为假,不执行 print s,x=example-servers1,i=1

    读取第二行,i=1,1>1 为假,不执行 print s,s=127.0.0.1:80,i=2

    读取第三行,i=2,2>1 为真,执行 print s,此时 s 是上一次 s 赋值内容 127.0.0.1:80,i=3

    最后一行,执行 print s,打印倒数第二行,s=最后一行。

    这种方式与上面一样,只是用 i++作为计数器。

    10)知道上述方式,就可以实现这种需求了,打印匹配行的上一行

    # seq 5 |awk '/3/{print s}{s=$0}'

    2

    其他参考资料:http://www.gnu.org/software/gawk/manual/gawk.html


    ---------------------
    作者:紫色飞猪
    来源:CSDN
    原文:https://blog.csdn.net/zisefeizhu/article/details/82526749
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    [抄书]The Pipes and Filters pattern
    [抄书]The Layers pattern
    OpenGL Step by Step (1)
    [HOOPS]二维点向三维空间投影
    心仪已久的工具:BoundsChecker v7.2
    [HOOPS]用HC_Show_...获取正确的点的坐标位置
    小试zlib
    XML (2) Document Type Definitions (DTD)
    UML (1) 设计模式及作业附图
    XML (1) 什么是XML
  • 原文地址:https://www.cnblogs.com/leo001/p/10117411.html
Copyright © 2011-2022 走看看