目录

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 \text 当前行追加文本

i \text 当前行上面插入文本

c \text 所选行替换新文本

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/(.*) (.*)(#.*)/\1\2test \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)(.*)/\1\3\/\2\4/'

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/([^:]+)(;.*:)([^:]+$)/\3\2\1/'

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/#\0/'

1

2

#3

#4

5

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

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

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 \test'

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 \test'

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 \test'

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 \test'

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\n&/'

1

2

txt

3

4

5

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

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 追加下一行内容到模式空间,并以换行符\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/\n//'

12

34

56

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

行 q 退出。

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

为空,此时模式空间是 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\n2,执行 D 命令删除

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

读取第二行,执行 N 命令,此时模式空间是 3\n4,执行 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\n1,执行 h 命令将 2\n1 覆盖到保持空间,d 删除模式空间。

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

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

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

以此类推,读到第 5 行时,模式空间是 5,执行 G 命令,将保持空间的 4\n3\n2\n1 追加模式空间,

然后复制到模式空间,5\n4\n3\n2\n1,不执行 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\n5,标签循环完成后打印模式空间 4\n5。

标签(:、b 和 t)

标签可以控制流,实现分支判断。

: lable name 定义标签

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

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

1)将换行符替换成逗号

方法 1:

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

1,2

3,4

5,6

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

追加后的 1\n2 这样替换。

这时就可以用到标签了:

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

1,2,3,4,5,6

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

sed 读取第

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

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

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

方法 2:

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

1,2,3,4,5,6

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

执行全局替换。

# seq 6 |sed ':a;N;b a;s/\n/,/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\nA\nb\nc" |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 ("\n")

RLENGTH: number (0)

RS: string ("\n")

RSTART: number (0)

RT: string ("\n")

SUBSEP: string ("\034")

TEXTDOMAIN: string ("messages")

5)BEGIN 和 END

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

题。

例如:打印页眉

# tail /etc/services |awk 'BEGIN{print "Service\t\tPort\t\t\tDescription\n==="}{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 "===\nEND......"}'

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\t\tPort\t\t\tDescription\n==="}{print $0}END{print "===\nEND......"}'

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\t\tPort\t\t\tDescription\n===

}

# Rule(s)

{

print $0

}

# END block(s)

END {

print "===\nEND......"

}

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"\n"}'

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 输入记录分隔符,默认是换行符\n

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

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

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

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

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

ARGC 命令行参数数量

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

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

ENVIRON 当前系统的环境变量

FILENAME 输出当前处理的文件名

IGNORECASE 忽略大小写

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

示例:

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 默认是\n 分隔每行,如果想指定以某个字符作为分隔符来处理记录:

# 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 "\ntxt"}'

1

2

3

txt

4

5

6

txt

7

8

9

txt

10

两行合并一行:

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

1 2

3 4

5 6

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

1 2

3 4

5 6

# seq 6 |awk '{if(NR%2)ORS=" ";else ORS="\n";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"\n";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

"\047"$i"\047"}

'10.10.10.1' '10.10.10.2' '10.10.10.3'

\047 是 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(默认\034)作为分隔下标字段,存储后是这样 a\034b。

示例:

# 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\nabc123\n123abc123" | 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\n&")}{print}'

1

txt

2

3

4

5

# seq 5 | awk 'NR==2{sub('/.*/',"&\ntxt")}{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\nabc#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\nabc#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\nabc#cde#fgh" |awk '{print

substr($0,4)}'

#456#789

#cde#fgh

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

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

#456#

#cde#

11)tolower()和 toupper()

转换小写:

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

123#456#789

abc#cde#fgh

转换大写:

# echo -e "123#456#789\nabc#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\n","abc"}'

a

保留一个小数点:

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

3.33

格式化输出:

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

user:abc pass:123

左对齐宽度 10:

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

ID Name Passwd

右对齐宽度 10:

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

ID Name Passwd

打印表格:

# vi test.awk

BEGIN{

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

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

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

}

# awk -f test.awk

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

|Name |Number |

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

格式化输出:

# awk -F: 'BEGIN{printf "UserName\t\tShell\n-----------------------------\n"}{printf

"%-20s %-20s\n",$1,$7}END{print "END...\n"}' /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

Linux三剑客详解带实验相关推荐

  1. Linux系统详解 系统的启动、登录、注销与开关机

    Linux系统详解 第六篇:系统的启动.登录.注销与开关机 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://johncai.blo ...

  2. c linux time微秒_学习linux,看这篇1.5w多字的linux命令详解(6小时讲明白Linux)

    用心分享,共同成长 没有什么比每天进步一点点更重要了 本篇文章主要讲解了一些linux常用命令,主要讲解模式是,命令介绍.命令参数格式.命令参数.命令常用参数示例.由于linux命令较多,我还特意选了 ...

  3. Linux系统结构 详解

    Linux系统结构 详解 标签: 产品产品设计googleapple互联网 2011-01-07 14:14 31038人阅读 评论(6) 收藏 举报 分类: Linux(21) 版权声明:本文为博主 ...

  4. linux系统服务详解 用于Linux系统服务优化

    linux系统服务详解 用于Linux系统服务优化 服务名        必需(是/否)用途描述        注解 acon              否       语言支持        特别支 ...

  5. Linux: 系统结构详解

    Linux系统一般有4个主要部分: 内核.shell.文件系统和应用程序.内核.shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序.管理文件并使用系统.部分层次结构如图1-1所 ...

  6. 《嵌入式Linux软硬件开发详解——基于S5PV210处理器》——1.2 S5PV210处理器

    本节书摘来自异步社区<嵌入式Linux软硬件开发详解--基于S5PV210处理器>一书中的第1章,第1.2节,作者 刘龙,更多章节内容可以访问云栖社区"异步社区"公众号 ...

  7. Linux Socket详解 大全 基础知识

    1. Socket基础概念: 1.1:形象类比: Socket和电话网络的概念可以做一个很好的类比: Linux 编程中所说的socket就如同一个端点,类比到电话网中,它就如同一个电话机. 而Soc ...

  8. Linux命令详解之 ls

    linux 命令详解 本文主要内容来自Linux man 手册 命令名称: ls ( list files / list directory contents )列举目录内容 命令用法: ls [选项 ...

  9. 云计算概念及Linux系统详解

    云计算概念及linux系统详解 先来看一下维基百科上的定义: 云计算是一种按使用量付费的模式,这种模式提供可用的.便捷的.按需的网络访问,进入可配置的网络.服务器.存储.应用软件.服务等能够被快速提供 ...

最新文章

  1. 做python自动化得时候怎么添加断言_在编写Web自动化测试用例的时候,如何写断言使新手不解,严格意义上来讲,没有断言的自动化脚本不能叫测试用例。就像功能测试一样,当测试人员做了一些操作...
  2. 3.10 神经网络的梯度下降法-推导
  3. qudp socket信号不触发_QT下udpsocket一段时间接收不到数据的问题
  4. vFORUM 2018,开启多云未来
  5. 中国设备工程杂志中国设备工程杂志社中国设备工程编辑部2022年第18期目录
  6. xnote1.5——WebShell
  7. 微信小程序云开发数据导出为Excel下载并打开
  8. 数据仓库工程师基本技能
  9. Laravel Excel实现Excel/CSV文件导入导出的功能详解(合并单元格,设置单元格样式)
  10. Android Hander post与sendMessage的区别
  11. 《中华人民共和国数据安全法》条款解读与分析
  12. 【大牛之路】大牛指导,报酬丰厚的开源项目---“谷歌编程之夏”
  13. 【无标题】android 代码混淆 垃圾代码制造
  14. Java基础:Java八大基本数据类型
  15. 计算机控制多釜串联 实验报告,多釜串联流动特性的测定..doc
  16. 机智云OTA过程MCU端程序设计学习(二)
  17. 给入行新人的一点忠告
  18. uniapp vue 实现模仿微信支付密码弹窗
  19. 如何处理阿里云ECS服务器提示存在漏洞
  20. 蓝税携手管理软件巨头SAP,打造个税服务解决方案

热门文章

  1. 两种服务器机柜的冷却解决方案
  2. 冬至由来介绍PPT模板
  3. 锐捷网络交换机配置命令大全,网络工程师收藏!
  4. 三、上传织物图片至SQL Server并提供name进行展示织物照片
  5. vue 使用addRoutes()合并动态有权路由
  6. 安卓搜不到airpods_安卓可以使用AirPods Pro吗?告诉你真实体验
  7. python取模运算
  8. 利用jsdelivr创建免费的CDN
  9. 华为mate40pro鸿蒙2.0,再见Mate30Pro,华为Mate40Pro来了:鸿蒙2.0+麒麟1020
  10. 《乐高EV3机器人搭建与编程》——2.8 拓展配件箱