shell脚本之正则表达式及sed,awk的使用

一,正则表达式

1,正则表达式语法Regular Expression

vim grep sed awk perl java nginx apache mail垃圾邮件过滤。。。等等都使用正则

什么叫正则表达式:她是一种字符串的表达方式

特点:
1.理解正则表达式类似理解算术表达式:
1+2 3*5 1+2*3 (1+2)*3

2.正则表达式像算术表达式一样也有优先级

构成:一个正则表达式描述了一种模式或字符序列(pattern)
1.除元字符之外的任意字符都是表示他字面意思的正则表达式
2.元字符(基本元字符、扩展元字符)
basic regular expression(regex)

extended...

2,正则表达式的匹配过程:

2.1 基本元字符

. 匹配除换行符之外的任意单个字符,awk中可以匹配换行符

例如:

[root@localhost /]# mkdir /sed
[root@localhost /]# cd sed
[root@localhost sed]# touch test.txt
[root@localhost sed]# vim test.txt 
[root@localhost sed]# sed 's/./A/' test.txt 
Aello world
Aice to meet you
Aow are you han meimei

[root@localhost sed]# sed 's/./A/g' test.txt 
AAAAAAAAAAA
AAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAA

[root@localhost sed]# sed 's/o./A/' test.txt 
hellAworld
nice tAmeet you
hA are you han meimei

[root@localhost sed]# sed 's/o. /A/' test.txt 
hello world
nice to meet you
hAare you han meimei

* 匹配任意一个(包括零个)在它前面的字符,表示他的前置字符有0个或者多个

如a* 表示*前面的这个a可以有0个或者多个

例如:*匹配都打印

[root@localhost ~]# cat 3.txt 
acbda
caac
caabb
aaac
acaca
cgdfsjh
fshfewh
[root@localhost ~]# grep -n 'a*' 3.txt 
1:acbda
2:caac
3:caabb
4:aaac
5:acaca
6:cgdfsjh             //即使没有a也打印
7:fshfewh             //即使没有a也打印

[...] 匹配方括号中的任意一个单个字符,^为否定匹配, -表示字符的范围

例如:匹配不打印纯字母a和c的行

[root@localhost ~]# cat 3.txt 
acbda
caac
caabb
aaac
acaca
cgdfsjh
fshfewh
[root@localhost ~]# grep '[^ac]' 3.txt 
acbda
caabb
cgdfsjh
fshfewh

^ 作为正则表达式的第一个字符,匹配行的开始。在awk中可以嵌入换行符

例如:匹配以c和f开头的行

[root@localhost ~]# cat 3.txt 
acbda
caac
caabb
aaac
acaca
cgdfsjh
fshfewh
[root@localhost ~]# grep -n '^[cf]' 3.txt 
2:caac
3:caabb
6:cgdfsjh
7:fshfewh
[root@localhost ~]# 

$ 作为正则表达式的最后一个字符,匹配行的结尾。在awk中可以嵌入换行符

例如:匹配以h结尾的行

[root@localhost ~]# cat 3.txt 
acbda
caac
caabb
aaac
acaca
cgdfsjh
fshfewh
[root@localhost ~]# grep -n 'h$' 3.txt 
6:cgdfsjh
7:fshfewh
[root@localhost ~]# 

\{n,m\} 匹配前置字符出现n和m之间的任意次数,\{n\}匹配出现n次。\{n,\}匹配至少出现n次

例如:打印b出现一次到两次的行

[root@localhost ~]# cat 3.txt 
acbda
caac
caabb
aaac
acaca
cgdfsjh
fshfewh
[root@localhost ~]# grep -n 'b\{1,2\}' 3.txt 
1:acbda
3:caabb
\(\) 基本正则小括号需要转义
\ 转义字符 把有意义的变得没有意义,把没有意义变得有意义
echo -e "a\nb" 换行打印ab 不加e是不起效果的
\< 词首定位符 /\<bin/ 匹配以bin开头的单词

\> 词尾定位符 /fix\>/ 匹配以fix结尾的单词

[root@wing ~]# echo wingabc wing | sed 's/\<wing/S/g'
Sabc S
[root@wing ~]# echo wingabc wing | sed 's/\<wing\>/S/g'  
wingabc S        //这里就是wing本身
'' 单引号 强引 硬引 打印的还是引号里面的东西
"" 双引号 弱引 软引 打印的不一定是他自己
例如:A=8
echo   "$A"      打印8
echo   '$A'       打印$A

2.2 扩展元字符

sed -r 扩展正则 不加r基本正则,对下面不适用   因为有-r选项,符号不需要转义

Extended Metacharacters (egrep and awk)

+ 匹配前面的正则表达式的一次出现或多次出现
? 匹配前面的正则表达式的零次出现或一次出现
| 可以匹配前面的或后面的正则表达式(替代方案)
() 对正则表达式分组
{n,m} 匹配出现的n到m次数, {n}匹配出现n次。{n,}匹配至少出现n次,大多数awk都不支持,用于POSIX egrep和POSIX awk

POSIX字符类:(了解一下就行,不好使用)
表达式 功能 示例
[:alnum:] 字母与数字字符 [[:alnum:]]+
[:alpha:] 字母字符(包括大小写字母) [[:alpha:]]{4}
[:blank:] 空格与制表符 [[:blank:]]*
[:digit:] 数字字母 [[:digit:]]?
[:lower:] 小写字母 [[:lower:]]{5,}
[:upper:] 大写字母 [[:upper:]]+
[:punct:] 标点符号 [[:punct:]]
[:space:] 包括换行符,回车等在内的所有空白[[:space:]]+

2.3 编写正则表达式的3 个步骤:

1 知道要匹配的内容以及它如何出现在文本中。
2 编写一个模式来描述要匹配的内容

3 测试模式来查看它匹配的内容

模式匹配产生的结果:
Hits(命中)
这是我想匹配的行

Misses(未命中)
这是我不想匹配的行

Omissions(遗漏)
这是我不能匹配但想要匹配的行

False alarms(假警报)
这是我不想匹配的但却匹配了的行

2.4 正则表达式分类应用:

字符类
[Ww]hat                  //匹配what和What
\.H[12345]              //匹配.H1到.H5任意一个

字符的范围  //匹配中括号里面的一个
[a-z]                   
[0-9]
[Cc]hapter[1-9]
[-+*/]
[0-1][0-9][-/][0-3][0-9][-/][0-9][0-9]

排除数字类
[^0-9]         //匹配非数字

重复出现的字符
10
50
100
500
1000
5000
[15]0*
[15]00*

字符的跨度
* 与 \{n,m\}
电话号码的匹配

[0-9]\{3\}-[0-9]\{7,8\}

分组操作
compan(y|ies)
注意:大多数sed 和grep 不能对圆括号()进行匹配,但是在egrep 和
awk 所有版本都是可以的

二,sed

1,语法:sed [options] ‘command’ in_file[s]

1.1 options部分

-n 静默输出(不打印默认输出)

-e 给予sed多个命令的时候需要-e选项

#sed -e 's/root/haha/g' -e 's/bash/wwwww/g' passwd > passwd.bak
如果不用-e选项也可以用分号“;”把多个命令隔开。
#sed‘s/haha/ro/g ; s/wwwww/kkkk/g' passwd | less
-i -i后面没有扩展名的话直接修改文件,如果有扩展名备份源文件,产生以扩展名结尾的新文件
#sed -iback1 -e 's/root/rottt/g' -e 's/bash/wwwww/g' passwd //选项-i后面没有空格

[root@localhost 桌面]# ls
manifest.txt passwdback1 
-f 当有多个要编辑的项目时,可以将编辑命令放进一个脚本里,再使用sed搭配-f选项
[root@localhost 桌面]# cat s.sed
s/bin/a/g
s/ftp/b/g
s/mail/c/g
[root@localhost 桌面]# sed -f s.sed passwd | less

1.2 command部分

‘[地址1,地址2] [函数] [参数(标记)]'

定址:
行地址对于任何命令都是可选的,它可以是一个模式,或者由斜杠、行号或行寻址符号括住的正则表达式, 大多数sed命令能接受由逗号分隔的两个地址,有些命令只接受单个行地址

命令还可以用大括号进行分组,第一个命令可以和大括号放在同一行,但是右大括号必须自己一行

定址的方法 1.数字 2.正则        数字(行号):十进制数
1	单行                       
#sed -n '1p' passwd      p是打印函数
		 
1,3	范围 从第一行到第三行      
#sed -n '1,3p' passwd
		
2,+4 	匹配行后若干行	
#sed -n '2,+4p' passwd
		
4,~3  从第四行到下一个3的倍数行
		
#sed -n '2,+4p' passwd
1~3 第一行起每间隔三行的行
                         #sed -n '1~3p' passwd

$ 尾行
                          #sed -n '$p'  passwd

1! 除了第一行以外的行
                          #sed -n '1!p'  passwd

正则
正则必须用//包裹起来

扩展正则需要用 -r 参数或转移

范例:

[root@localhost ~]# cat file.txt 
.TS
Beijing,CN
.TE
Shanghai,CN
guangzhou,CN
shenyang,CN

[root@localhost ~]# sed '/Beijing/s/CN/China/'  file.txt 
.TS
Beijing,China    //把这一行的CN换成了China
.TE
Shanghai,CN
guangzhou,CN
shenyang,CN

[root@localhost ~]# 
删除所有的行
d                         如: sed    'd'    file.txt        //删除file.txt  的第一行  下面操作类似
只删除第一行
1d
使用寻址符号$,删除最后一行
$d
删除空行,正则表达式必须封闭在斜杠//当中
/^$/d
删除.TS 和.TE 标记的tbl 输入
/^\.TS/,/^\.TE/d
删除第五行到结尾所有的行
5,$d
混合使用行地址和模式地址   删除第一行到第一个空格之间所有的内容
#sed ‘1,/^$/d' file.txt
删除除了那些行以外的行
1,5!d

使用脚本文件:

例如:  就是对文件file.txt进行a的脚本操作

[root@localhost ~]# cat a
/^\.TS/,/^\.TE/{
s/CN/China/
s/Beijing/BJ/
}
[root@localhost ~]# cat file.txt 
.TS
Beijing,CN
.TE
Shanghai,CN
guangzhou,CN
shenyang,CN

[root@localhost ~]# sed -f a file.txt 
.TS
BJ,China
.TE
Shanghai,CN
guangzhou,CN
shenyang,CN
[root@localhost ~]# 
sed '2,3{s/CN/china/;s/China/b/}'  file.txt  //把2到3行的CN换成china,把China换成b

函数:
增删改:
a 后插
c 替换
i 前插

d 删除
删除模式空间的内容,同时改变脚本的控制流,执行这个命令后,在“空的”模式空间不再有命令执行。删除命令会导致读取新的输入行

替换:[address ]s/pattern /replacement /flags
标志flags是:
n 可以是1-512,表示第n次出现的情况进行替换
g 全局更改
p 打印模式空间的内容
w file 写入到一个文件file中
i 忽略大小写

[root@wing ~]# echo Aaaaba | sed -r 's/a.*/B/'
AB
[root@wing ~]# echo Aaaaba | sed -r 's/a.*/B/i'
B
s 字符串替换 s/old/new/

替换的时候可以把/换成其他的符号,比如=

#sed -n 's/root/ABCDEF/p' /etc/passwd
ABCDEF:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/ABCDEF:/sbin/nologin
	  
#sed -n 's/root/ABCDEF/2p' /etc/passwd
root:x:0:0:ABCDEF:/root:/bin/bash

#sed -n 's/root/ABCDEF/3p' /etc/passwd
root:x:0:0:root:/ABCDEF:/bin/bash

#sed -n 's/root/ABCDEF/gp' /etc/passwd
ABCDEF:x:0:0:ABCDEF:/ABCDEF:/bin/bash
operator:x:11:0:operator:/ABCDEF:/sbin/nologin

replacement部分用下列字符会有特殊含义:
& 用正则表达式匹配的内容进行替换
\n 回调参数 n的范围是1-9
\(\)保存被匹配的字符以备反向引用\N时使用,最多9个标签 标签顺序从左至右

#cat test1
first:second
one:two
#sed ‘s/\(.*\):\(.*\)/\2:\1/' test1
second:first
two:one
        
# echo helloworld20150809.txt | sed -r 's/(.*)([0-9]{8})(.*)/\2/'
20150809
y 字符替换(变形) 可以替换一行的所有,而s替换一行的第一个
r 从文件中读入内容到指定行的后面
#sed ‘2r b.txt’ a.txt 将b.txt文件内容读入到文件a.txt的第2行后面

1.3 工作模式:模式空间和保持空间介绍

模式空间:初始化为空,处理完一行后会自动输出到屏幕并清除模式空间。
保持空间:初始化为一个空行,也就是默认带一个\n,处理完后不会自动清除。

置换:模式空间和保持空间(暂存空间)
h 把模式空间内容覆盖到保持空间中
H 把模式空间内容追加到保持空间中
g 把保持空间内容覆盖到模式空间中
G 把保持空间内容追加到模式空间中
x 交换模式空间与保持空间的内容

例如:

# cat test.sh 
1111111
2222222	
3333333
4444444
# sed  '{1h;2,3H;4G}'  ./test.sh 
1111111
2222222
3333333
4444444
1111111
2222222
3333333
# sed  '{1h;2x;3g;$G}'  ./test.sh 
1111111
1111111
2222222
4444444
2222222
控制流:
! 命令取反 例: 1!d 删除第一行以外的行
{} 命令组合 命令用分号分隔 {1h;G} 可以理解为 -e 参数的另一种写法
= 打印行号(输入行的号码,而非处理的次数行号) 例如: sed -n '2{=;p}' infile
n 读入下一行到模式空间 例:'4{n;d}' 删除第5行
N 而是追加下一行到模式空间,再把当前行和下一行同时应用后面的命令
P 输出多行模式空间的第一部分,直到第一个嵌入的换行符为止。在执行完脚本的最后一个命令之后,模式空间的内容自动输出。
P 命令经常出现在N命令之后和D命令之前。
d  删除模式空间内所有的内容,并且接着读入下一行到模式空间
D 删除模式空间中直到第一个换行符的内容。它不会导致读入新的输入行,相反,它返回到脚本的顶端,将这些指令应用与模式空间剩余的内容。
这三个命令能建立一个输入、输出循环,用来维护两行模式空间,但是一次只输出一行。
这个循环的目的是只输出模式空间的第一行,然后返回到脚本的顶端将所有的命令应用于模式空间的第二行。没有这个循环,当执行脚本中的最后一个命令时,模式空间中的这两行都将被输出。

删除文件倒数第二行                //要记住这种特定用法,背下来
sed 'N;$!P;D' a.txt
删除文件最后两行
sed 'N;$!P;$!D;$d' a.txt        //要记住这种特定的用法,背下来
将第一行插入到每个偶数行的后面 
# cat a.txt
1111111
2222222	
3333333
4444444
5555555
6666666
# sed  '1h;0~2G' a.txt
11111111
22222222
11111111
33333333
44444444
11111111
55555555
66666666
11111111
颠倒输出
[root@localhost ~]# cat rev.txt 
abc
def
xyz
[root@localhost ~]# sed '1!G;h;$!d' rev.txt
xyz
def
abc

大写转换
样本文件 a.txt
find the Match statement
Consult the Get statement.
using the Read statement to retrieve data
将 the 和statement之间的单词转换成大写
脚本:changsrc

# capitalize statement names
/the .* statement/{
h
s/.*the \(.*\) statement.*/\1/
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
G
s/\(.*\)\n\(.*the \).*\( statement.*\)/\2\1\3/
}

脚本方法

-f 参数 引用脚本(脚本的末尾不能有空格制表符或其他文本)

例如:

# cat test.txt
1111111
2222222
3333333
4444444
5555555
6666666
7777777
8888888
9999999
# cat sed.sh
2,4d
s/777/seker/
s/999/seker&seker/
# sed -f sed.sh test.txt 
1111111
5555555
6666666
seker7777
8888888
seker999seker9999
在脚本中指明解释器为sed
# cat sed.sh     //在脚本中写入下面四行
  #!/bin/sed -f
  2,4d
  s/777/seker/
  s/999/seker&seker/
# ./sed.sh test.txt 
1111111
5555555
6666666
seker7777
8888888
seker999seker9999

1.5 高级流控命令 b分支 t测试 (了解)

分支命令用于无条件转移,测试命令用于有条件转移

分支 branch

跳转的位置与标签相关联
如果有标签则跳转到标签所在的后面行继续执行
如果没有标签则跳转到脚本的结尾处.

标签 以冒号开始后接标签名 不要在标签名前后使用空格

跳转到标签指定位置
[root@stu254 ~]# grep seker /etc/passwd
seker:x:500:500::/home/seker:/bin/bash
[root@stu254 ~]#
[root@stu254 ~]# grep seker /etc/passwd |sed ':top;s/seker/blues/;/seker/b top;s/5/555/' 
blues:x:55500:500::/home/blues:/bin/bash
[root@stu254 ~]#
命令分析:让单次替换(cmd1)循环执行,直到条件不满足

:top; 定义一个top标签
s/seker/blues/; cmd1
/seker/b top; 如果模式匹配则跳转到top标签
s/5/555/ 当上一条模式不匹配时,既会继续执行这一条

选择执行
[root@stu254 ~]#  grep 'seker' /etc/passwd |sed 's/seker/blues/;/seker/b end;s/5/555/;:end;s/5/666/'
blues:x:66600:500::/home/seker:/bin/bash
[root@stu254 ~]# 

zorro:x:501:501::/home/zorro:/bin/bash
[root@stu254 ~]#  grep 'zorro' /etc/passwd |sed 's/seker/blues/;/seker/b end;s/5/555/;:end;s/5/666/'
zorro:x:6665501:501::/home/zorro:/bin/bash
[root@stu254 ~]# 
命令分析: 执行cmd1,再去模式匹配,成功则跳转到cmd3开始执行,否则(模式不匹配)会按命令顺序逐个执行
s/seker/blues/; cmd1
/seker/b end;
s/5/555/; cmd2
:end;
s/5/666/ cmd3
另一种选择执行
[root@stu254 ~]#  grep 'seker' /etc/passwd |sed 's/seker/blues/;/seker/b end;s/5/555/;b;:end;s/5/666/'
blues:x:66600:500::/home/seker:/bin/bash

[root@stu254 ~]#  grep 'zorro' /etc/passwd |sed 's/seker/blues/;/seker/b end;s/5/555/;b;:end;s/5/666/'
zorro:x:55501:501::/home/zorro:/bin/bash
[root@stu254 ~]# 
命令分析: 执行cmd1;模式匹配cmd2成功则执行cmd3;否则执行cmd2,再跳转到脚本末尾 
s/seker/blues/; cmd1
/seker/b end;
s/5/555/;  cmd2
b;
:end;
s/5/666/ cmd3

测试命令,如果前一个替换命令执行成功则跳转到脚本末尾 (case结构)

[root@stu254 ~]#  grep 'seker' /etc/passwd |sed 's/seker/ABC/;t;s/home/DEF/;t;s/bash/XYZ/'
ABC:x:500:500::/home/seker:/bin/bash

[root@stu254 ~]#  grep 'zorro' /etc/passwd |sed 's/seker/ABC/;t;s/home/DEF/;t;s/bash/XYZ/'
zorro:x:501:501::/DEF/zorro:/bin/bash
[root@stu254 ~]#
与标签关联,跳转到标签位置
[root@stu254 ~]#  grep 'seker' /etc/passwd |sed 's/seker/ABC/;t end;s/home/DEF/;t;:end;s/bash/XYZ/'
ABC:x:500:500::/home/seker:/bin/XYZ

三,awk基础

1,awk概述

流编辑器,适合处理有行有列比较规范的文本
awk读入一行,执行一次主输入循环
#awk ‘BEGIN{} {} END{}’ 文件名称
BEGIN{} 处理所有文件之前执行, 可选
{} 主输入循环 //就是文件中有多少行,他就打印多少行同样的内容
END{}处理所有文件之后执行,可选
字符串必须要加引号,如果一个字符串不加引号,会被当作一个变量来处理
a=8     awk {print a}  //a不加引号会当作变量处理,打印出8
a
8
Awk读入一行,执行一次括号里面的动作
读入一行,执行一次后面的主输入循环

#awk '{}' /etc/passwd

#awk 选项 'BEGIN{} {} END{}' 文件

hello world
# echo 'this line of data is ignored' > test
# awk '{ print "Hello, world" }' test
Hello, world

print语句如果没有参数,只简单输出每个输入行
# cat test2
Hello, world
# awk '{ print }' test2
Hello, world

2,awk 程序设计模型

awk 程序由所谓的主输入(main input)循环组成。一个循环称作一个例程。

awk允许你编写两个特殊的例程,他们在任何输入被读取前和所有输入都被读取后执行。他们是与BEGIN和END规则相关的过程。BEGIN和END过程是可选的。

# awk ‘BEGIN {print “hello,World”}’
Hello,World
BEGIN模式不需要等待输入,它在第一个输入行读入之前执行。

BEGIN{} 所有文本内容读入之前要执行的命令
{} 主输入循环 用的最多
END{} 所有文本全部读入完成之后执行的命令
print
不跟任何参数,打印awk读入的内容
跟参数:
数字 不要加引号
字符串 必须加引号
变量 没有加引号的字符串

print 5
print a
print "a"

3,模型匹配

#cat  src1.awk
/[0-9]+/ { print "That is an integer" }
/[A-Za-z]+/ { print "This is a string" }
/^$/ { print "This is a blank line." }
一个特殊的例子:一行文本可以匹配一条或多条规则
# echo 4t | awk -f src1.awk
That is an integer
This is a string
程序脚本的注释

# 以#号开始的一行

4,记录和子段(重点)

awk假设它的输入是有结构的,而不是一串无规则的字符。默认它将每个输入行作为一条记录,而将由空格或制表符分隔的单词作为字段。连续的多个空格和/或制表符被作为一个分隔符。

记录:以记录分割符分割的字符串
字段:以字段分割符分割的字符串
字段包括在记录里面

默认记录分隔符:\n
默认字段分隔符:单个或多个空格 \tab 或 当\n不是记录分隔符的时候
a=8
print a //a就是8
record 记录
NR FS 大写 //这是awk中的默认的变量
$0 整条记录
NR number record 记录号,就是行号
FNR 按照文件分开打印行号
RS 输入记录分隔符 record separate
ORS 输出记录分隔符 output record separate
$1 第一个字段
$2 第二个字段
$...
NF 字段个数 number of field
FS 输入字段分隔符
OFS 输出字段分隔符 ','表示默认的输出分隔符也就是空格,当然可以自定义
FILENAME 被处理的文件的名称

例如:打印passwd的第一行和第三行内容,第三行括号括起来

[root@localhost tmp]# awk 'BEGIN{FS=":"}{print $1"("$3")"}' passwd
root(0)
bin(1)
daemon(2)
adm(3)
lp(4)
sync(5)
shutdown(6)
halt(7)
mail(8)
operator(11)
games(12)
ftp(14)
nobody(99)
systemd-bus-proxy(999)
systemd-network(192)
dbus(81)
polkitd(998)
abrt(173)
unbound(997)
usbmuxd(113)
tss(59)
libstoragemgmt(996)
rpc(32)
colord(995)
amandabackup(33)
saslauth(994)
geoclue(993)
setroubleshoot(992)
rtkit(172)
qemu(107)
radvd(75)
chrony(991)
ntp(38)
sssd(990)
rpcuser(29)
nfsnobody(65534)
pulse(171)
gdm(42)
gnome-initial-setup(989)
avahi(70)
postfix(89)
sshd(74)
tcpdump(72)
loukun(1000)
halt(7)
'mail'(8)
手动制定FS的值:

# awk 'BEGIN{FS=":|="}{print $1}' a  //以输入分隔符:或者=来打印第一列
hello
wenjian 
bianji
chakan
sousuo
# awk -F ':|=' '{print $2}' a   //以分隔符:或者=来打印第二列

5,字段的引用和分离

John Robinson 666-555-1111
awk允许使用字段操作符$来指定字段。$后面可以跟着一个数字或者一个变量。
$1表示第一个字段,$2表示第二个字段,$0表示整个输入记录。

# awk '{ print $2, $1, $3 }' names
Robinson John 666-555-1111
可以使用计算值为整数的表达式来表示一个字段
# echo a b c d | awk 'BEGIN { one = 1; two = 2 }
> { print $(one + two) }'
c                           //打印c

可以使用-F来改变字段分隔符

# awk -F"\t" '{ print $2 }' names
666-555-1111
# awk -F"\t+" '{ print $2 }' names
# awk -F"[‘:\t]" '{ print $2 }' names     //任何3个字符之一都可以被解释为字段分隔符

也可以在脚本中指定域分隔符,通过系统变量FS来改变

BEGIN { FS = "," }    // comma-delimited fields
{ print $1 "-" $2 }

使用匹配规则

/MA/ { print $1 ", " $6 }

~表示匹配

为了避免假警报,可以使用更精确的匹配
$5 ~ /MA/ { print $1 ", " $6 }
还可以使用!来反转这个规则的意义
$5 !~ /MA/ { print $1 ", " $6 }

6,表达式

常量

分成两种:字符串型和数字型
字符串型在表达式中必须用引号括起来
字符串中可以使用转义序列,常用的转义序列有:
\n 换行 \t 水平制表符 \r 回车

变量

x=1
x是变量的名字 =是一个赋值操作符 1是一个数字常量
注意:变量区分大小写,所以x 和X(大写)表示不同的变量
变量名只能由数字字母下划线组成,而且不能以数字开头
变量使用不区分类型,使用前不必初始化
z = "Hello"
z = "Hello" "World"
z = $1

以上几种都是合法的

常用的算术操作符

+ Addition
- Subtraction
* Multiplication
/ Division
% Modulo //取余数

x=1 给x赋值
y=x+1 计算x的值,使它加1,并将结果赋给变量y。
print y 打印y的值。
我们可以将这3个语句减少为两个:
x=1
print x+1
7

常用赋值操作符

++ Add 1 to variable.
-- Subtract 1 from variable.
+= Assign result of addition.
-= Assign result of subtraction.
*= Assign result of multiplication.
/= Assign result of division.
%= Assign result of modulo.
^= Assign result of exponentiation.(取幂)

计算文件d中空行的数目
awk '/^$/{x++}END{print x}' d
计算学生的平均成绩
cat grades
    mona     70 77 85 83 70 89
    john     85 92 78 94 88 91
    andrea   89 90 85 94 90 95
    jasper   84 88 80 92 84 82
    dunce    64 80 60 60 61 62
    ellis    90 98 89 96 96 92
#vim src2.awk
    # average five grades
    { total = $2 + $3 + $4 + $5 + $6
    avg = total / 5
    print $1, avg }
# awk -f src2.awk grades
    mona 77
    john 87.4
    andrea 89.6
    jasper 85.6
    dunce 65
    ellis 93.8

系统变量

$0 整条记录
$1 第一个字段
FS 定义字段分隔符,默认为一个空格
OFS 输出的字段分隔符,默认为一个空格
RS 记录分隔符,默认为一个换行符
ORS 输出的记录分隔符,默认为一个换行符
NR 行数
FNR 行数,多文件操作时会重新排序
NF 字段的个数
FILENAME 文件名

例如:
cat h
   1:2:3
awk -F: 'OFS="aaa",ORS="bbb" {print NR,FNR,NF,FILENAME}' h
打印结果:
1aaa1aaa3aaahbbb[root@localhost awk]# 

解释:这里把输出子段分割符变成了aaa,两个1都是行数,3是字段数目,然后打印文件名h,最后默认的回车\n输出记录分隔符变成了bbb

处理多行记录

示例:vim a       
        John Robinson
        Koren Inc.
        978 Commonwealth Ave.
        Boston
        MA 01760
        696-0987

     vim  b
        # block.awk - print first and last fields
        # $1 = name; $NF = phone number
        BEGIN { FS = "\n"; RS = "" }  //这里把默认的输入字段分隔符由空格变成回车\n,又把默认的输入记录分隔符由回车\n变成了空行,所以匹配到最后
        { print $1, $NF }

     awk -f b a      
         John Robinson  696-0987    //输出的结果




7,关系操作符和布尔操作

关系操作符

< Less than
> Greater than
<= Less than or equal to
>= Greater than or equal to
== Equal to
!= Not equal to
~ Matches
!~ Does not match
NF == 5 NF(每个输入记录的字段数)的值和5相比较,如果结果为真,那
么就进行相应的处理,否则不进行处理。
$5 ~ /MA/ {print $1 “,”$6}  //第五个字段匹配到MA,则打印第一和第六个字段
注意:关系操作符==和赋值操作符=是不同的

布尔操作符  bool逻辑操作符

|| Logical OR
&& Logical AND
! Logical NOT

!=
!(NR> 5)
NR<=5

字段的数量必须等于6并且记录的编号必须大于1。
NF == 6 && NR > 1

NR >1 && NF >=2 || $1 ~ /\t/
NR >1 && (NF >=2 || $1 ~ /\t/)
!(NR > 1 && NF > 3)

获取文件的信息
# ls -l |awk -f src3.awk
BEGIN { print "BYTES", "\t", "FILE" }
{
sum += $5
++filenum
print $5, "\t", $8
}
END { print "Total: ", sum, "bytes (" filenum " files)" }

8,格式化打印

printf ( format-expression [, arguments] )
c ASCII 字符
d 十进制整数
f 浮点格式
s 字符串

x 无符号十六进制

常用举例:

语法 %-width.precision format-specifier
printf(" %d \t %s \n ", $5 , $8 )
printf("|%10s|\n", "hello") 右对齐
printf("|%-10s|\n", "hello") 左对齐
printf("%*.*f\n", 5, 3, myvar) 宽度5 精度3 打印myvar

例如:格式化打印/etc/passwd的用户和UID

[root@localhost ~]# awk 'BEGIN {FS=":";printf ("%-20s%-5s\n","UserName","UID")}{printf("%-20s%-5s\n",$1,$3)}' /etc/passwd
UserName            UID  
root                 0    
bin                  1    
daemon               2    
adm                  3    
lp                   4    
sync                 5    
shutdown             6    
halt                 7    
mail                 8    
operator             11   
games                12   
ftp                  14   
nobody               99   

9,向脚本传递参数

方式1:
# var=root
# awk –F: -v a=$var ‘$1==a {print}’ /etc/passwd  //向a传递root,打印root用户信息
root:x:0:0:root:/root:/bin/bash
方式2:
[root@wing ~]# a=9
[root@wing ~]# awk 'BEGIN{print '"$a"'}'
9

10,条件、循环部分

条件语句

if ( expression )
action1
[else
action2]

例如:

if ( x ) print x    //如果x是零,则print语句将不执行。如果x是一个非零值,将打印x的值。

if ( x == y ) print x  //如果x等于y,则打印x
if ( x ~ /[yY](es)?/ ) print x    //?表示前面字符一个或者多个,x匹配yes、Yes、yesss..、Yesss...、则打印x

如果操作是由多个语句组成,要用一个大括号将操作括起来
if ( expression ) {
statement1
statement2
}

其他的例子:

if ( avg >= 65 )
    grade = "Pass"
else
    grade = "Fail"

if (avg >= 90) grade = "A"
else if (avg >= 80) grade = "B"
else if (avg >= 70) grade = "C"
else if (avg >= 60) grade = "D"
else grade = "F"
这种能够连续条件只有当一个条件表达式计算结果为真时才停止求值,这时将
跳过其他的条件。如果没有一个条件表达式的计算结果为真,将执行最后的
else部分。

条件操作符:(了解一下)

expr ? action1 : action2
例如:

grade = (avg >= 60) ? "Pass" : "Fail"   //判断avg是否大于等于60,如果成功,输出Pass给grade,如果失败,输出Fail给grade
# awk -F: '{print ($3 > $4 ? $3 : $4)}' /etc/passwd
注意小括号

while循环

while (condition)
action

例:
i = 1
while ( i <= 4 ) {
    print $i
    ++i
}

do 循环

do
action
while (condition)

例:
BEGIN {
    do {
        ++x
        print x
    } while ( x <= 4 )
}

for 循环

for ( set_counter ; test_counter ; increment_counter )
action
for(变量初始化;变量变化的范围;增幅)
动作

例:
for(num=1;num<=4;num++)
  print num
  结果:1
            2
            3
            4
    num=1  //初始化
    条件成功  //判断
    1   //执行循环体
    
   num=2  //num++
    条件成功  //判断
    2   //执行循环体
    .
    .
    .
    一直执行下去,直到条件不成功,退出循环
例如:
从第一个字段到最后一个字段
for ( i = 1; i <= NF; i++ )        //NF表示字段数目
    print $i

从最后一个字段到第一个字段
for ( i = NF; i >= 1; i-- )
    print $i
用for实现  //可以用来求平均成绩
total = 0
for (i = 2; i <= NF; ++i)
#total = $2 + $3 + $4 + $5 + $6
#avg = total / 5
total += $i
avg = total / (NF - 1)
求阶乘 factorial
5!=5*4*3*2*1
number=8
fact = number
for (x = number - 1 ; x > 1; x--)
    fact *= x
print fact

影响控制流

break 退出循环
continue 终止当前的循环,并从循环的顶部开始一个新的循环

影响主输入循环

next 读入下一行,并返回脚本的顶部
exit 使主输入循环退出并将控制转移到END

awk重定向

# awk -F : '{print $1 >> "/tmp/awktest"}' /etc/passwd

awk管道

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

四,awk数组和函数

1,数组 array

      简单来说:数组是存的一组数,将一组相关数据按照一定的顺序存放在一起
一般用于从记录中收集信息、统计次数、记录某个模式出现的次数等等。

创建数组:

数组名[数组下标]=值
arr1[1]="a"
arr1[2]="b"
awk中,数组下标既可以是数字、也可以是变量、还可以是字符串(字符串必须加双引号)
注意:数组下标如果是数字:从1开始,而c是从0开始
数组可以定义后再使用,也可以直接使用。

删除单个键值:

delete ARRAY[INDEX]

# awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";delete tB["a"];for(k in tB){print k,tB[k];}}'                     
    b b1                  //打印的结果

删除整个数组:

方法 1:
for (VAR in ARRAY)
delete ARRAY[VAR]

方法 2: 该方法 gawk 专用,可移植性差,但效率是方法 1 的 3 倍左右
delete ARRAY

数组和变量命名空间:

Awk 的数组和变量用的是同一个地址空间,数组的名字和变量不能重名。
即使数组删除了也不能将名字用于变量命名,以下命令会报错:
a[1] = 3; delete a; a = 3

数组的遍历:

用循环去遍历数组元素
for (数组下标 in 数组名){..}

数组下标两种类型:

1.使用数值做下标

 arr[1] arr[3]  arr[x++]  arr[NR]
   # awk '{arr[x++]=$1};END{for (i=NR-1;i>=0;i--){print i,arr[i]}}'   employees
   # awk '{arr[NR]=$1};END{for (i=NR;i>0;i--){print i,arr[i]}}'   employees

2.使用字符串做下标

统计apache日志中每个IP的访问次数  *****
       # cut -d" " -f1 /var/log/httpd/access_log | sort | uniq -c | sort -nr
       
       # awk '{ip[$1]++}END{for (i in ip) {print i,ip[i]}}' /var/log/httpd/access_log

上面第二个例子用到了排序,这里补充一下sort命令:

sort 排序
-n 按照数字排序
-r 倒序
-t 指定分隔符
-k 第几列 排序键

数组相关函数:

得到数组长度:

 length(数组名称)
        # awk 'BEGIN{a[1]=8;a[2]=9;print length(a)}' 

    split(变量,数组名称,"分割符")    //得到数组长度
        # awk 'BEGIN{var="a,b,c,d";lens=split(var,test,",");print lens}'  //test是数组,无形之中创建一个test数组
        4
输出数组内容(无序,有序输出):

 # awk 'BEGIN{a="a b c d";lens=split(a,test);for (k in test){print k,test[k]}}' 
        4 d
        1 a
        2 b
        3 c
     for…in 输出,因为数组是关联数组,默认是无序的。所以通过for…in 得到是无序的数组。如果需要得到有序数组,需要通过下标获得

    # awk 'BEGIN{a="a b c d";lens=split(a,test);for (k=1;k<=lens;k++){print k,test[k]}}' 
        1 a
        2 b
        3 c
        4 d

判断键值存在:

一个错误的判断方法:

# awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";if(tB["c"]!="1"){print "no found";};for(k in tB){print k,tB[k];}}' 
    no found
    a a1
    b b1
    c 
以上出现奇怪问题,tB[“c”]没有定义,但是循环时候,发现已经存在该键值,它的值为空,这里需要注意,awk数组是关联数组,只要通过数组引用它的key,就会自动创建该序列.
正确判断方法:
# awk 'BEGIN{tB["a"]="a1";tB["b"]="b1";if( "c" in tB){print "ok";};for(k in tB){print k,tB[k];}}'  
    a a1
    b b1 
        
    if(key in array) 通过这种方法判断数组中是否包含”key”键值。

二维数组使用(多维数组使用)

awk的多维数组在本质上是一维数组,更确切一点,awk在存储上并不支持多维数组。awk提供了逻辑上模拟二维数组的访问方式。例 如,array[2,4] = 1这样的访问是允许的。
类似一维数组的成员测试,多维数组可以使用 if ( (i,j) in array)这样的语法,但是下标必须放置在圆括号中。
类似一维数组的循环访问,多维数组使用 for ( item in array )这样的语法遍历数组。与一维数组不同的是,多维数组必须使用split()函数来访问单独的下标分量。split ( item, subscr, SUBSEP

例如:打印9X9乘法表

方法一:
#awk 'BEGIN{ 
        for(i=1;i<=9;i++){
              for(j=1;j<=9;j++){
                    tarr[i,j]=i*j;
                    print i,"*",j,"=",tarr[i,j];
              }
        }
  }'
1 * 1 = 1
1 * 2 = 2
1 * 3 = 3
1 * 4 = 4
1 * 5 = 5
1 * 6 = 6 

…… 

可以通过array[k,k2]引用获得数组内容. 

方法二: 
#awk 'BEGIN{
    for(i=1;i<=9;i++){
      for(j=1;j<=9;j++) {
            tarr[i,j]=i*j;
      }
    }
    for(m in tarr){
    split(m,tarr2,SUBSEP);
    print tarr2[1],"*",tarr2[2],"=",tarr[m];
    }
}'

2,函数

内置函数
自定义函数

内置函数

1)大小写转换函数

toupper() 小写变大写
tolower() 大写变小写
# awk '{print toupper($1)}' score
# awk '{print tolower($1)}' datafile3
2)sub 查找并替换
 sub(正则表达式,替换字符串)  
 # awk '{sub(/mike/,"mk");print}' score

自定义函数(功能函数) function

格式:
函数名 (参数) { //参数是可选的
函数体;
返回值;
}

自定义一个函数
   # awk 'function over() {print "class is over"} {over()}'  a
     class is over
函数的调用: 函数名(参数)
     over()












  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值