{"msg":"操作成功","code":200,"data":{"createBy":"admin","createTime":"2021-03-12 17:08:02","updateBy":"admin","updateTime":"2021-03-12 17:08:02","remark":null,"id":53,"articleTitle":"Shell（十二）文本处理awk命令","articleUrl":"linux_awk","articleThumbnail":"https://www.asumimoe.com/imgfiles/20220908/7769fde9ec5445068e41583cb1c71894.jpg","articleFlag":"0","draftStatus":"1","reprintStatement":"0","articleSummary":"awk是一个处理文本的编程语言工具，能用简短的程序处理标准输入或文件、数据排序、计算以及生成报表等等。 在Linux系统下默认awk是gawk，它是awk的GNU版本。可以通过命令查看应用的版本：ls -l /bin/awk ","articleContent":"## awk介绍\n\nawk是一个处理文本的编程语言工具，能用简短的程序处理标准输入或文件、数据排序、计算以及生成报表等等。 在Linux系统下默认awk是gawk，它是awk的GNU版本。可以通过命令查看应用的版本：ls -l /bin/awk 。\n\n基本的命令语法：`awk option 'pattern {action}' file` 其中pattern表示AWK在数据中查找的内容，而action是在找到匹配内容时所执行的一系列命令。花括号用于根据特定的模式对一系列指令进行分组。\n\nawk处理的工作方式与数据库类似，支持对记录和字段处理，这也是grep和sed不能实现的。 在awk中，缺省的情况下将文本文件中的一行视为一个记录，逐行放到内存中处理，而将一行中的某一部分作为记录中的一个字段。用1,2,3...数字的方式顺序的表示行（记录）中的不同字段。用$后跟数字，引用对应的字段，以逗号分隔，0表示整个行。\n\n![](https://www.asumimoe.com/imgfiles/20220916/54803552108c40fbb7e7fc0d1ea4a6ab.jpg)\n\n### 选项\n\n```bash\n-f program-file # 从文件中读取awk程序源文件\n-F fs # 指定fs为输入字段分隔符，默认为空格分隔\n-v var=value # 变量赋值\n--posix # 兼容POSIX正则表达式\n--dump-variables=[file] # 把awk命令时的全局变量写入文件，默认是awkvars.out\n--profile=[file] # 格式化awk语句到文件，默认是awkprof.out\n```\n\n### 模式\n\n```bash\nBEGIN{} # 给程序赋予初始状态，先执行的工作\nEND # 程序结束之后执行的一些扫尾工作\n/regular expression/ # 为每个输入记录匹配正则表达式\npattern && pattern # 逻辑与，满足两个模式\npattern || pattern # 逻辑或，满足其中一个模式\n!pattern # 逻辑非，不满足模式\npattern1,pattern2 # 范围模式，匹配所有模式1的记录，直到匹配到模式2\n```\n\n1）指定分隔符，打印字段\n\n```bash\n[root@l1 ~]# tail /etc/services | awk '{print $2}'\n48049/tcp\n48128/tcp\n48128/udp\n# 默认的分隔符为空格，当需要指定分隔符时，用-F指定\n# 可以指定多个分隔符，awk -F '[/#]'，该命令为每遇到一个/或#时就分隔一个字段，当用多个分隔符时，就能更方面处理字段了\n```\n\n2）变量赋值\n\n```bash\n# awk -v a=123 'BEGIN{print a}' \n123 \n# 系统变量作为awk变量的值： \n# a=123 \n# awk -v a=$a 'BEGIN{print a}' \n123 \n# 或使用单引号 \n# awk 'BEGIN{print '$a'}'\n```\n\n3）BEGIN和END\n\nBEGIN是在处理文件之前执行的操作，常用于修改内置变量、变量赋值和打印页眉。\n\nEND是在程序处理完之后才会执行。\n\n```bash\n[root@l1 ~]# tail /etc/services |awk 'BEGIN{print \"Service\\t\\tPort\\t\\t\\tDescription\\n===\"}{print $0}END{print \"===\\nEND......\"}'\nService\t\tPort\t\t\tDescription\n===\n3gpp-cbsp       48049/tcp               # 3GPP Cell Broadcast Service Protocol\nisnetserv       48128/tcp               # Image Systems Network Services\nisnetserv       48128/udp               # Image Systems Network Services\nblp5            48129/tcp               # Bloomberg locator\n===\nEND......\n```\n\n4）正则匹配\n\n```bash\n# 匹配开头是blp5的行\n[root@l1 ~]# tail /etc/services |awk '/^blp5/{print $0}'\nblp5            48129/tcp               # Bloomberg locator\nblp5            48129/udp               # Bloomberg locator\n# 匹配第一个字段是8个字符的行\n[root@l1 ~]# tail /etc/services |awk '/^[a-z0-9]{8} /{print $0}'\niqobject        48619/tcp               # iqobject\niqobject        48619/udp               # iqobject\nmatahari        49000/tcp               # Matahari Broker\n```\n\n5）逻辑and、or和not\n\n```bash\n# 不匹配开头是#和空行\n# awk '! /^#/ && ! /^$/{print $0}' /etc/httpd/conf/httpd.conf \n# 或 \n# awk '! /^#|^$/' /etc/httpd/conf/httpd.conf\n```\n\n6）匹配范围\n\n```bash\n[root@l1 ~]# seq 5 |awk '/3/,/^$/{printf /3/?\"\":$0\"\\n\"}'\n4\n5\n```\n\n\n\n## 内置变量\n\n```bash\nFS # 输入字段分隔符，默认是空格或制表符\nOFS # 输出字段分隔符，默认是空格\nRS # 输入记录分隔符，默认是换行符\\n\nORS # 输出记录分隔符，默认是换行符\\n\nNF # 统计当前记录中字段个数\nNR # 统计记录编号，每处理一行记录，编号就会+1\nFNR # 统计记录编号，每处理一行记录，编号也会+1，与NR不同的是，处理第二个文件时，编号会重新计数。\nARGC # 命令行参数数量\nARGV # 命令行参数数组序列数组，下标从0开始，ARGV[0]是awk\nARGIND # 当前正在处理的文件索引值。第一个文件是1，第二个文件是2，以此类推\nENVIRON # 当前系统的环境变量\nFILENAME # 输出当前处理的文件名\nIGNORECASE # 忽略大小写\nSUBSEP # 数组中下标的分隔符，默认为\"\\034\"\n```\n\n1）FS和OFS\n\n在程序开始前重新赋值FS变量，改变默认分隔符，与-F作用相同。\n\n```bash\n[root@l1 ~]# awk 'BEGIN{FS=\":\"}{print $1,$2}' /etc/passwd |head -n5\nroot x\nbin x\ndaemon x\nadm x\nlp x\n# 也可以使用-v来重新赋值这个变量\n[root@l1 ~]# awk -v FS=':' '{print $1,$2}' /etc/passwd |head -n5\nroot x\nbin x\ndaemon x\nadm x\nlp x\n```\n\n由于OFS默认以空格分隔，反向引用多个字段分隔的也是空格，可以用如下方式制定分隔符：\n\n```bash\n[root@l1 ~]# awk 'BEGIN{FS=\":\";OFS=\":\"}{print $1,$2}' /etc/passwd |head -n5\nroot:x\nbin:x\ndaemon:x\nadm:x\nlp:x\n[root@l1 ~]# awk 'BEGIN{FS=\":\"}{print $1\"#\"$2}' /etc/passwd |head -n5\nroot#x\nbin#x\ndaemon#x\nadm#x\nlp#x\n```\n\n2）NF\n\nNF是字段个数\n\n```bash\n# echo \"a b c d e f\" |awk '{print NF}' \n6 \n# 打印最后一个字段： \n# echo \"a b c d e f\" |awk '{print $NF}' \nf \n# 打印倒数第二个字段： \n# echo \"a b c d e f\" |awk '{print $(NF-1)}' \ne \n# 排除最后两个字段： \n# echo \"a b c d e f\" |awk '{$NF=\"\";$(NF-1)=\"\";print $0}'\na b c d\n# 排除第一个字段：\n# echo \"a b c d e f\" |awk '{$1=\"\";print $0}'\nb c d e f\n```\n\n3）NR和FNR\n\nNR统计记录编号，每处理一行记录，编号就会+1，FNR不同的是在统计第二个文件时会重新计数\n\n```bash\n# 打印行数\n[root@l1 ~]# tail -n5 /etc/services |awk '{print NR,$0}'\n1 com-bardac-dw   48556/tcp               # com-bardac-dw\n2 com-bardac-dw   48556/udp               # com-bardac-dw\n3 iqobject        48619/tcp               # iqobject\n4 iqobject        48619/udp               # iqobject\n5 matahari        49000/tcp               # Matahari Broker\n# 打印总行数\n[root@l1 ~]# tail -n5 /etc/services |awk 'END{print NR}'\n5\n# 打印前三行\n[root@l1 ~]# tail -n5 /etc/services |awk 'NR<=3{print NR,$0}'\n1 com-bardac-dw   48556/tcp               # com-bardac-dw\n2 com-bardac-dw   48556/udp               # com-bardac-dw\n3 iqobject        48619/tcp               # iqobject\n```\n\n4）ARGC和ARGV\n\nARGC是命令行参数数量 ARGV是将命令行参数存到数组，元素由ARGC指定，数组下标从0开始。\n\n```bash\n[root@l1 ~]# awk 'BEGIN{print ARGC}' 1 2 3\n4\n[root@l1 ~]# awk 'BEGIN{print ARGV[0]}'\nawk\n[root@l1 ~]# awk 'BEGIN{print ARGV[1]}' 1 2\n1\n[root@l1 ~]# awk 'BEGIN{print ARGV[2]}' 1 2\n2\n```\n\n5）ENVIRON\n\n调用系统变量\n\n```bash\n[root@l1 ~]# awk 'BEGIN{print ENVIRON[\"HOME\"]}'\n/root\n# 如果是设置的环境变量，还需要用export导入到系统变量才可以调用\n[root@l1 ~]# export a=123\n[root@l1 ~]# awk 'BEGIN{print ENVIRON[\"a\"]}'\n123\n\n```\n\n## 操作符\n\n| 运算符              | 描述                                                         |\n| ------------------- | ------------------------------------------------------------ |\n| (....)              | 分组                                                         |\n| $                   | 字段引用                                                     |\n| ++ --               | 递增和递减                                                   |\n| + - !               | 加号、减号和逻辑否定                                         |\n| * / %               | 乘、除和取余                                                 |\n| + -                 | 加法、减法                                                   |\n| \\| \\|&              | 管道，用于getline、print和printf                             |\n| < > <= >= != ==     | 关系运算符                                                   |\n| ~ !~                | 正则表达式匹配，否定正则表达式匹配                           |\n| in                  | 数组成员                                                     |\n| && \\|\\|             | 逻辑and，逻辑or                                              |\n| ?:                  | 三目运算符： expr1 ? expr2 : expr3 第一个表达式为真，执行expr2，否则执行expr3 |\n| = += -= *= /= %= ^= | 变量赋值运算符                                               |\n\n**注意： 在awk中，有3种情况表达式为假：数字是0，空字符串和未定义的值。 数值运算，未定义变量初始值为0。字符运算，未定义变量初始值为空。**\n\n1）感叹号\n\n```bash\n打印奇数行： \n# seq 6 |awk 'i=!i' \n1 \n3 \n5 \n打印偶数行： \n# seq 6 |awk '!(i=!i)'\n2\n4\n6\n```\n\n读取第一行：i是未定义变量，也就是i=!0，!取反意思。感叹号右边是个布尔值，0或空字符串为假，非0或非空字符串为真，!0就是真，因此i=1，条件为真打印当前记录。没有print为什么会打印呢？因为模式后面没有动作，默认会打印整条记录。读取第二行：因为上次i的值由0变成了1，此时就是i=!1，条件为假不打印。读取第三行：上次条件又为假，i恢复初始值0，取反，继续打印。以此类推...\n\n可以看出，运算时并没有判断行内容，而是利用布尔值真假判断输出当前行。\n\n2）乘除法\n\n```bash\n打印偶数行： \n# seq 5 |awk '$0%2==0{print $0}' \n2 \n4 \n打印奇数行： \n# seq 5 |awk '$0%2!=0{print $0}' \n1 \n3 \n5 \n```\n\n3）正则表达式匹配\n\n```bash\n# seq 5 |awk '$0~3{print $0}' \n3 \n# seq 5 |awk '$0!~3{print $0}' \n1 \n2 \n4 \n5 \n# seq 5 |awk '$0~/[34]/{print $0}' \n3 \n4 \n# seq 5 |awk '$0!~/[34]/{print $0}' \n1 \n2 \n5 \n# seq 5 |awk '$0~/[^34]/{print $0}' \n1 \n2 \n5 \n```\n\n4）三目运算符\n\n```bash\n# awk 'BEGIN{print 1==1?\"yes\":\"no\"}'  # 三目运算作为一个表达式，里面不允许写print \nyes \n# seq 3 |awk '{print $0==2?\"yes\":\"no\"}' \nno \nyes \nno \n替换换行符为逗号： \n# seq 5 |awk '{print n=(n?n\",\"$0:$0)}' \n1 \n1,2 \n1,2,3 \n1,2,3,4 \n1,2,3,4,5 \n# seq 5 |awk '{n=(n?n\",\"$0:$0)}END{print n}' \n1,2,3,4,5 \n说明：读取第一行时，n没有变量，为假输出$0也就是1，并赋值变量n，读取第二行时，n是1为真，输出1,2 以此类推，后面会一直为真。 \n\n每三行后面添加新一行： \n# seq 10 |awk '{print NR%3?$0:$0 \"\\ntxt\"}' \n1 \n2 \n3 \ntxt \n4 \n5 \n6 \ntxt \n7 \n8 \n9 \ntxt \n10 \n\n两行合并一行： \n# seq 6 |awk '{printf NR%2!=0?$0\" \":$0\" \\n\"}'   \n1 2  \n3 4  \n5 6  \n# seq 6 |awk 'ORS=NR%2?\" \":\"\\n\"'  \n1 2 \n3 4 \n5 6 \n```\n\n## 流程控制\n\n### if条件判断\n\n格式：if (condition) statement [ else statement ]，if条件判断包括单分支，双分支，多分支判断。\n\n```bash\n单分支： \n# seq 5 |awk '{if($0==3)print $0}'    \n3 \n也支持正则匹配判断，一般在写复杂语句时使用： \n# echo \"123abc#456cde 789aaa#aaabbb \" |xargs -n1 |awk -F# '{if($2~/[0-9]/)print $2}'    \n456cde \n# echo \"123abc#456cde 789aaa#aaabbb \" |xargs -n1 |awk -F# '{if($2!~/[0-9]/)print $2}' \naaabbb \n或 \n# echo \"123abc#456cde 789aaa#aaabbb\" |xargs -n1 |awk -F# '$2!~/[0-9]/{print $2}'      \naaabbb \n\n双分支： \n# seq 5 |awk '{if($0==3)print $0;else print \"no\"}' \nno \nno \n3 \nno \nno \n\n多分支： \n# cat file \n1 2 3 \n4 5 6 \n7 8 9 \n# awk '{if($1==4){print \"1\"} else if($2==5){print \"2\"} else if($3==6){print \"3\"} else {print \"no\"}}' file            \nno \n1 \nno \n```\n\n### while循环\n\n格式：while (condition) statement\n\n```bash\n遍历打印所有字段： \n# awk '{i=1;while(i<=NF){print $i;i++}}' file \n1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \nawk是按行处理的，每次读取一行，并遍历打印每个字段。\n```\n\n### for循环\n\n格式：for (expr1; expr2; expr3) statement\n\n```bash\n遍历打印所有字段： \n# cat file \n1 2 3 \n4 5 6 \n7 8 9 \n# awk '{for(i=1;i<=NF;i++)print $i}' file \n1 \n2 \n3 \n4 \n5 \n6 \n7 \n8 \n9 \n倒叙打印文本： \n# awk '{for(i=NF;i>=1;i--)print $i}' file        \n3 \n2 \n1 \n6 \n5 \n4 \n9 \n8 \n7 \n都换行了，这并不是我们要的结果。怎么改进呢？ \n# awk '{for(i=NF;i>=1;i--){printf $i\" \"};print \"\"}' file  # print本身就会新打印一行 \n3 2 1 \n6 5 4 \n9 8 7 \n或 \n# awk '{for(i=NF;i>=1;i--)if(i==1)printf $i\"\\n\";else printf $i\" \"}' file \n3 2 1 \n6 5 4 \n9 8 7 \n在这种情况下，是不是就排除第一行和倒数第一行呢？我们正序打印看下 \n排除第一行： \n# awk '{for(i=2;i<=NF;i++){printf $i\" \"};print \"\"}' file \n2 3 \n5 6 \n8 9 \n排除第二行： \n# awk '{for(i=1;i<=NF-1;i++){printf $i\" \"};print \"\"}' file \n1 2 \n4 5 \n7 8 \nIP加单引号： \n# echo '10.10.10.1 10.10.10.2 10.10.10.3' |awk '{for(i=1;i<=NF;i++)printf \"\\047\"$i\"\\047\"} \n'10.10.10.1'  '10.10.10.2'  '10.10.10.3' \n# \\047是ASCII码，可以通过showkey -a命令查看。\n```\n\nfor语句遍历数组\n\n格式：for (var in array) statement\n\n```bash\n# seq -f \"str%.g\" 5 |awk '{a[NR]=$0}END{for(v in a)print v,a[v]}' \n4 str4 \n5 str5 \n1 str1 \n2 str2 \n3 str3 \n```\n\n### break和continue\n\nbreak跳过所有循环，continue跳过当前循环。\n\n```bash\n# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){break};print i}}' \n1 \n2 \n# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){continue};print i}}' \n1 \n2 \n4 \n5 \n```\n\n### exit语句 \n\n格式：exit [ expression ] \n\nexit退出程序，与shell的exit一样。[ expr ]是0-255之间的数字。 \n\n```bash\n\\# seq 5 |awk '{if($0~/3/)exit (123)}'\n\\# echo $?\n123\n```\n\n## 数组\n\n数组：存储一系列相同类型的元素，键/值方式存储，通过下标（键）来访问值。 awk中数组称为关联数组，不仅可以使用数字作为下标，还可以使用字符串作为下标。 数组元素的键和值存储在awk程序内部的一个表中，该表采用散列算法，因此数组元素是随机排序。 \n\n数组格式：array[index]=value\n\n### 自定义数组\n\n```bash\n# awk 'BEGIN{a[0]=\"test\";print a[0]}' \ntest \n\n通过NR设置记录下标，下标从1开始 \n# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[1]}' \nsystemd-network \n# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[2]}' \nzabbix \n# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[3]}' \nuser \n```\n\n### for循环遍历数组\n\n```bash\n# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(v in a)print a[v],v}' \nzabbix 4 \nuser 5 \nadmin 1 \nsystemd-bus-proxy 2 \nsystemd-network 3 \n# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(i=1;i<=NR;i++)print a[i],i}' \nadmin 1 \nsystemd-bus-proxy 2 \nsystemd-network 3 \nzabbix 4 \nuser 5 \n```\n\n上面打印的i是数组的下标。 第一种for循环的结果是乱序的，刚说过，数组是无序存储。 第二种for循环通过下标获取的情况是排序正常。 \n\n所以当下标是数字序列时，还是用for(expr1;expr2;expr3)循环表达式比较好，保持顺序不变。\n\n### 使用++方式或者字段方式的下标\n\n++方式\n\n```bash\n# tail -n5 /etc/passwd |awk -F: '{a[x++]=$1}END{for(i=0;i<=x-1;i++)print a[i],i}' \nadmin 0 \nsystemd-bus-proxy 1 \nsystemd-network 2 \nzabbix 3 \nuser 4 \nx被awk初始化值是0，没循环一次+1\n```\n\n字段方式\n\n```bash\n# tail -n5 /etc/passwd |awk -F: '{a[$1]=$7}END{for(v in a)print a[v],v}' \n/sbin/nologin admin \n/bin/bash user \n/sbin/nologin systemd-network \n/sbin/nologin systemd-bus-proxy \n/sbin/nologin zabbix\n```\n\n### 数组内字段统计\n\n1）统计相同字段出现次数\n\n```bash\n# tail /etc/services |awk '{a[$1]++}END{for(v in a)print a[v],v}' \n2 com-bardac-dw \n1 3gpp-cbsp \n2 iqobject \n1 matahari \n2 isnetserv \n2 blp5 \n```\n\n上述`{a[$1]++}`条件也可换成`{a[$1]+=1}`。\n\n第一个字段作为下标，值被++初始化是0，每次遇到下标（第一个字段）一样时，对应的值就会被+1，因此实现了统计出现次数。 想要实现去重的的话就简单了，只要打印下标即可。\n\n2）只打印出现次数大于等于2的\n\n```bash\n# tail /etc/services |awk '{a[$1]++}END{for(v in a) if(a[v]>=2){print a[v],v}}' \n2 com-bardac-dw \n2 iqobject \n2 isnetserv \n2 blp5 \n```\n\n3）去重\n\n```bash\n只打印重复的行： \n# tail /etc/services |awk 'a[$1]++' \nisnetserv       48128/udp               # Image Systems Network Services \nblp5           48129/udp               # Bloomberg locator \ncom-bardac-dw     48556/udp               # com-bardac-dw \niqobject        48619/udp               # iqobject \n不打印重复的行： \n# tail /etc/services |awk '!a[$1]++' \n3gpp-cbsp       48049/tcp               # 3GPP Cell Broadcast Service  \nisnetserv       48128/tcp               # Image Systems Network Services \nblp5           48129/tcp               # Bloomberg locator \ncom-bardac-dw     48556/tcp               # com-bardac-dw \niqobject        48619/tcp               # iqobject \nmatahari        49000/tcp               # Matahari Broker\n```\n\n先明白一个情况，当值是0是为假，非0整数为真，知道这点就不难理解了。 \n\n只打印重复的行说明：当处理第一条记录时，执行了++，初始值是0为假，就不打印，如果再遇到相同的记录，值就会+1，不为0，则打印。 \n\n不打印重复的行说明：当处理第一条记录时，执行了++，初始值是0为假，感叹号取反为真，打印，如果再遇到相同的记录，值就会+1，不为0为真，取反为假就不打印。 \n\n4）统计每个相同字段的某字段总数\n\n```bash\n# tail /etc/services |awk -F'[ /]+' '{a[$1]+=$2}END{for(v in a)print v, a[v]}' \ncom-bardac-dw 97112 \n3gpp-cbsp 48049 \niqobject 97238 \nmatahari 49000 \nisnetserv 96256 \nblp5 96258 \n```\n\n## 内置函数\n\n1）int( )：截断为整数\n\n```bash\n# echo -e \"123abc\\nabc123\\n123abc123\" | awk '{print int($0)}' \n123 \n0\n123\n\n# awk 'BEGIN{print int(10/3)}' \n3 \n```\n\n2）rand( )与srand( )：生成随机数\n\n```bash\nrand()并不是每次运行就是一个随机数，会一直保持一个不变： \n# awk 'BEGIN{print rand()}' \n0.237788 \n\n当执行srand()函数后，rand()才会发生变化，所以一般在awk着两个函数结合生成随机数，但是也有很大几率生成一样： \n# awk 'BEGIN{srand();print rand()}' \n0.31687 \n\n如果想生成1-10的随机数可以这样： \n# awk 'BEGIN{srand();print int(rand()*10)}' \n4\n```\n\n3）asort(a,b)：对数组a的值进行排序，把排序后的值存到新的数组b中，新排序的数组下标从1开始\n\n```bash\n# seq -f \"str%.g\" 5 |awk '{a[x++]=$0}END{s=asort(a,b);for(i=1;i<=s;i++)print b[i],i}'              \nstr1 1 \nstr2 2 \nstr3 3 \nstr4 4 \nstr5 5 \n# asort将a数组的值放到数组b，a下标丢弃，并将数组b的总行号赋值给s，新数组b下标从1开始，然后遍历。\n```\n\n4）sub(/regexp/, replacement, target)：对输入的记录用replacement替换regexp正则匹配，t可选针对某字段替换（注意第三个参数target，如果忽略则使用$0作为参数，即整行文本），但只替换第一个字符串，gsub为替换所有字段\n\n```bash\n替换正则匹配的字符串： \n# tail /etc/services |awk '/blp5/{sub(/tcp/,\"icmp\");print $0}' \nblp5            48129/icmp               # Bloomberg locator \nblp5            48129/udp               # Bloomberg locator \n# tail /etc/services |awk '/blp5/{gsub(/c/,\"9\");print $0}' \nblp5            48129/t9p               # Bloomberg lo9ator \nblp5            48129/udp               # Bloomberg lo9ator \n\n只替换特定的字段\n# echo \"1 2 2 3 4 5\" |awk 'gsub(2,7,$2){print $0}' \n1 7 2 3 4 5\n```\n\n5）index(s,t)：返回s中字符串t的索引位置\n\n```bash\n获取字段索引起始位置： \n# tail -n 5 /etc/services |awk '{print index($2,\"tcp\")}' \n7 \n0 \n7 \n0 \n7 \n```\n\n6）length([s])：返回s的长度\n\n```bash\n统计字段长度： \n# tail -n 5 /etc/services |awk '{print length($2)}' \n9 \n9 \n9 \n9 \n9 \n统计数组的长度： \n# tail -n 5 /etc/services |awk '{a[$1]=$2}END{print length(a)}' \n3\n```\n\n7）match(s, r)：测试字符串s是否包含匹配r的字符串，如果不包含返回0\n\n```bash\n# echo \"123abc#456cde 789aaa#234bbb 999aaa#aaabbb\" |xargs  -n1 |awk '{print match($0,234)}'          \n0 \n8 \n0 \n如果记录匹配字符串234，则返回索引位置，否则返回0。 \n\n那么，我们只想打印包含这个字符串的记录就可以这样： \n# echo \"123abc#456cde 789aaa#234bbb 999aaa#aaabbb\" |xargs  -n1 |awk '{if(match($0,234)!=0)print $0}'\n789aaa#234bbb\n```\n\n8）substr(s, i, n)：截取字符串s从i开始到长度n，如果n没指定则是剩余部分\n\n```bash\n截取字符串索引4到最后： \n# echo -e \"123#456#789\\nabc#cde#fgh\" |awk '{print substr($0,4)}'                      \n#456#789 \n#cde#fgh \n截取字符串索引4到长度5： \n# echo -e \"123#456#789\\nabc#cde#fgh\" |awk '{print substr($0,4,5)}' \n#456# \n#cde# \n```\n\n9）tolower()与toupper()：大小写转换\n\n```bash\n转换小写： \n# echo -e \"123#456#789\\nABC#cde#fgh\" |awk '{print tolower($0)}' \n123#456#789 \nabc#cde#fgh \n\n转换大写： \n# echo -e \"123#456#789\\nabc#cde#fgh\" |awk '{print toupper($0)}' \n123#456#789 \nABC#CDE#FGH \n```\n\n## 实例\n\n### Nginx日志分析\n\n日志格式：\n\n```bash\n'$remote_addr - $remote_user [$time_local] \"$request\" $status $body_bytes_sent \"$http_referer\" \"$http_user_agent\" \"$http_x_forwarded_for\"'\n```\n\n需求\n\n```bash\n1）统计访问 IP 次数：\n# awk '{a[$1]++}END{for(v in a)print v,a[v]}' access.log\n2）统计访问访问大于 100 次的 IP：\n# awk '{a[$1]++}END{for(v in a){if(a[v]>100)print v,a[v]}}' access.log\n3）统计访问 IP 次数并排序取前 10：\n# awk '{a[$1]++}END{for(v in a)print v,a[v] |\"sort -k2 -nr |head -10\"}' access.log\n4）统计时间段访问最多的 IP：\n# 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\n5）统计上一分钟访问量：\n# date=$(date -d '-1 minute' +%d/%d/%Y:%H:%M)\n# awk -vdate=$date '$4~date{c++}END{print c}' access.log\n6）统计访问最多的 10 个页面：\n# awk '{a[$7]++}END{for(v in a)print v,a[v] |\"sort -k1 -nr|head -n10\"}' access.log\n7）统计每个 URL 数量和返回内容总大小：\n# awk '{a[$7]++;size[$7]+=$10}END{for(v in a)print a[v],v,size[v]}' access.log\n8）统计每个 IP 访问状态码数量：\n# awk '{a[$1\" \"$9]++}END{for(v in a)print v,a[v]}' access.log\n9）统计访问 IP 是 404 状态次数：\n# awk '{if($9~/404/)a[$1\" \"$9]++}END{for(i in a)print v,a[v]}' access.log\n```\n\n### 将文件内相同IP的服务名合并\n\n```bash\n# cat a\n192.168.1.1: httpd\n192.168.1.1: tomcat\n192.168.1.2: httpd\n192.168.1.2: postfix\n192.168.1.3: mysqld\n192.168.1.4: httpd\n# awk 'BEGIN{FS=\":\";OFS=\":\"}{a[$1]=a[$1] $2}END{for(v in a)print v,a[v]}' a\n192.168.1.4: httpd\n192.168.1.1: httpd tomcat\n192.168.1.2: httpd postfix\n192.168.1.3: mysqld\n```\n\n说明：\n\n数组 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`。每次遇到相同的下标（第一个 字段）就会获取上次这个下标对应的值与当前字段并作为此下标的新值。\n\n### 统计字符串中每个字母出现的次数\n\n```bash\n# echo \"a.b.c,c.d.e\" |awk -F '[.,]' '{for(i=1;i<=NF;i++)a[$i]++}END{for(v in a)print\nv,a[v]}'\na 1\nb 1\nc 2\nd 1\ne 1\n```\n\n### 获取 Nginx 负载均衡配置端 IP 和端口（去除第一行和最后一行）\n\n```bash\n# cat nginx.conf\nupstream example-servers1 {\n server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s;\n}\nupstream example-servers2 {\n server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s;\n server 127.0.0.1:82 backup;\n}\n# awk '/example-servers1/,/}/{if(NR>2){print s}{s=$2}}' nginx.conf \n127.0.0.1:80\n# awk '/example-servers1/,/}/{if(i>1){print s}{s=$2;i++}}' nginx.conf\n127.0.0.1:80\n```\n\n说明：\n\n读取第一行，i 初始值为 0，0>1 为假，不执行 print s，x=example-servers1，i=1\n\n读取第二行，i=1，1>1 为假，不执行 print s，s=127.0.0.1:80,i=2\n\n读取第三行，i=2，2>1 为真，执行 print s，此时 s 是上一次 s 赋值内容 127.0.0.1:80，i=3\n\n最后一行，执行 print s，打印倒数第二行，s=最后一行。 这种方式与上面一样，只是用 i++作为计数器。","categoryId":5,"viewCount":724,"categoryName":"Shell","author":"球接子","authorAvatar":null,"tagIds":[4],"tagNames":["Shell"]}}