shell快讲第一节--shell基础
shell快讲第二节--管道与重定向
shell快讲第三节--正则表达式
shell快讲第四节--sed与awk
shell快讲第五节--shell语法基础
shell快讲第六节--shell函数编程
shell快讲第七节--shell编程规范与调试
shell快讲第八节--shell编程实战
sed与awk
简介
这节课讲sed和awk,这两个可能是在写脚本,尤其是监控脚本最常用到的两个命令了,所以按照传统,我们讲一下三剑客,这几个命令其实都有超级复杂的用法,但是本着学以致用的原则,我们就介绍我们最常接触的
sed
sed属于流媒体编辑器(stream editor),我们可以用它编辑任何我们想编辑的东西(键盘输入,文本,管道,字符串,变量),这里我们只研究简单的场景,下面看下常见的代码,pattern代表正则表达。
>>> echo 'hello word' |sed 's/word/world/'
>>> sed 's/pattern/replace_string' file #将file中的字符1全部替换成字符2
>>> sed '2,5s/pattern/replace_string' file #将file第2行到第5行进行字符替换
>>> cat file | sed sed 's/pattern/replace_string'
>>> sed 's/pattern/replace_string' file >newfile #将修改结果重定向到一个新文件
>>> sed -i 's/pattern/replace_string' file #将修改结果到源文件
>>> sed 4a\newline testfile #使用sed 在第四行后添加新字符串
>>> sed '2d' testfile #删除第二行
>>> sed '2,5d' testfile #删除第2到第5行
>>> sed '3,$d' testfile #删除第3到最后一行
>>> sed '2a hellworld' #在第2行后加 注意上面的4a\newline的写法
>>> sed '2i hellworld' #插在第二行前变成第二行
>>> sed '2c helloworld' #第二行内容用helloworld取代
>>> sed -n '5,7p' testfile #仅打印5到7行的字符串
>>> sed -n '/aNum/p' testfile #仅打印包含aNum字符串的行
>>> sed '/aNum/a/helloworld' testfile #将所有包含aNum字符串的行后面加上helloworld作为一行
>>> sed '/helloworld/d' testfile #将包含helloworld的行删除
>>> sed -i 's/\.$/\!/g' test.txt #利用 sed 将 regular_express.txt 内每一行结尾若为 . 则换成 !
>>> sed -i '$a This is a test'test.txt #利用 sed 直接在 regular_express.txt 最后一行加入 # This is a test
sed替换文件内的字符串是我们最常见的,如上命令仅仅会替换到每行的第一个匹配,如果一行只有一个匹配那自然没问题,如果一行存在多个匹配的时候呢,我们就要使用如下的方法:
>>> echo 'hello word word word word' |sed 's/word/world/g' #匹配所有的word
>>> echo 'hello word word word word' |sed 's/word/world/2g' #匹配前1
>>> echo 'hello word word word word' |sed 's/word/world/Ng' #匹配N-1个
>>> sed -e 's/brown/green/; s/dog/cat/' data1.txt #复合用法
sed 脚本
sed本身是支持自己的语法的,解释器即是sed,本文只写个简单的例子,另外sed就抠到这里,sed的某些用法也许一辈子也用不到
>>> cat script1.sed
s/brown/green/
s/fox/elephant/
s/dog/cat/
>>> sed -f script1.sed testfile #将sed脚本作用于某个文件
>>> ./script1.sed testfile
sed常见选项
选项太多,大家下去自行使用体会
a\ 在当前行下面插入文本。
i\ 在当前行上面插入文本。
c\ 把选定的行改为新的文本。
d 删除,删除选择的行。
D 删除模板块的第一行。
e 按顺序匹配并操作
s 替换指定字符
h 拷贝模板块的内容到内存中的缓冲区。
H 追加模板块的内容到内存中的缓冲区。
g 获得内存缓冲区的内容,并替代当前模板块中的文本。
G 获得内存缓冲区的内容,并追加到当前模板块文本的后面。
l 列表不能打印字符的清单。
n 读取下一个输入行,用下一个命令处理新的行而不是用第一个命令。
N 追加下一个输入行到模板块后面并在二者间嵌入一个新行,改变当前行号码。
p 打印模板块的行。
P(大写) 打印模板块的第一行。
q 退出Sed。
b lable 分支到脚本中带有标记的地方,如果分支不存在则分支到脚本的末尾。
r file 从file中读行。
t label if分支,从最后一行开始,条件一旦满足或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾。
T label 错误分支,从最后一行开始,一旦发生错误或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾。
w file 写并追加模板块到file末尾。
W file 写并追加模板块的第一行到file末尾。
! 表示后面的命令对所有没有被选定的行发生作用。
= 打印当前行号码。
# 把注释扩展到下一个换行符以前
awk
awk也是我讨厌的命令之一,它很强大,但是复杂的用法着实让人记不住,当然它是个优秀的软件,所谓三大命令之首,awk脚本的结果基本如下:
**awk 'BEGIN{print "start"} pattern {commands} END{print "end"}' **
工作原理
1.先执行BEGIN{commands}语句块。这个语句块是可选的
2.从文件或者管道或者其他等等方式中读取一行,然后执行pattern,重复这个过程,直到读取完毕
3.执行END{commands}语句块。这个语句块是可选的
4.print 参数是 以逗号作为分隔符,参数打印时是以空格作为定界符
看如下例子:
>>>echo -e "line1\nline2" |awk 'BEGIN{print "start"} {print} END{print "END"}'
>>>echo -e "hello world" |awk 'BEGIN{print "start"} {print $1} END{print "END"}'
>>>echo -e "hello world" |awk '{print $1}' #当然这是我们最常用的,省略了begin和end
特殊变量
awk有一些特殊变量,就像上面的$1,这些变量集中整理一下,当然这个使用起来是比较简单的,只要知道NR,NF这两个特殊的就行了,后面的命令演示都会用到
特殊变量 | 描述 |
---|---|
NR | 表示记录数量,在执行过程中对应于当前行号 |
NF | 表示字段数量,在执行过程中对应当前行的字段数 |
$0 | 这个变量包含执行过程当前行的内容 |
$1 | 这个变量包当前行的第一个字段 |
FS | 输入隔断符号 |
OFS | 输出分隔符 |
RS | 输入行分隔符 |
ORS | 输出行分隔符 |
awk选项
选项 | 选项 |
---|---|
-F fs 指定输入文件折分隔符 | -v var=value 赋值一个用户定义变量。 |
-f scripfile 从脚本文件中读取awk命令 |
例子演示
>>> ps axu |awk '{print $1,$4}'
>>>cat /etc/passwd |awk '{print $1,$3}'
>>>cat /etc/passwd |awk -F: '{print $1,$3}' #看下和如上命令结果有何不同
>>>awk -F: '{printf "%-8s %-8d\n",$1,$3}' /etc/passwd #格式化输出,大家尽量理解下格式化的意思
------带上BEGIN---------
>>> awk 'BEGIN{FS=":"}{print $1,$3}' /etc/passwd #感受下BEGIN的作用
>>> awk 'BEGIN{FS="[:/]"}{print $NF}' /etc/passwd #先用:做分割,再用/做分割,感受下不同,也记住这诡异的语法
>>> awk -F: 'BEGIN{print "用户 ID"}$3>=1000{print $1,$3}' /etc/passwd #打印用户id>=1000的用户,这里体验一下运算符号(==,>,<,>=,<=)
>>> awk -F: 'BEGIN{OFS="|";print "用户 ID"}$3>=1000{print $1,$3}' /etc/passwd #输出内容自定义分隔符
>>> awk -F: '$1=="root" || $1=="capricorn" {print $1,$3}' /etc/passwd #打印用户root和capricorn的ID
>>> awk -F: '/^ssh/ {print $0}' /etc/passwd #打印ssh开头的行,这里体验一下针对整行的正则表达式
>>> awk -F: '$NF ~ /nologin/ {print $1,$3,$NF}' /etc/passwd #输出最后一个字段包含nologin的用户名和ID,感受下如果针对某一个字段进行正则
>>> awk 'BEGIN{FS=":";print "特殊用户"}$1 ~/root/ {print $NF}' /etc/passwd
>>> ls -l *.md|awk '{sum+=$5} END{print sum}' #查找所有md文件并计算总大小
>>> echo "hello world " |awk -v hostname=$HOSTNAME '{print $1,hostname}'#从外界传递变量到awk
awk输出个格式化
输出格式化对程序来说不是很必要,格式化主要是为了给人看的,下面大家感受下格式化就行了,这部分不打算展开,大家感受下如下代码,注意这里不是print,是printf,另外注意理解格式化语句,“\n”代表换行,我开始就吃着亏了,一定记得加上。
awk -F: 'BEGIN{printf "%-16s %8s\n","用户","ID"}{printf "%-16s %-8s\n",$1,$3}' /etc/passwd
awk脚本
awk脚本编程老实说是比较复杂的,因为它有自己的语法,这显然增加了学习压力,其实我不太建议上来就研究这么深,如果有需要配合百度琢磨琢磨也就差不多了。
假设有这么一个文件(学生成绩表),懒得复制也可以直接下载score.txt
$ cat score.txt
Marry 2143 78 84 77
Jack 2321 66 78 45
Tom 2122 48 77 71
Mike 2537 87 97 95
Bob 2415 40 57 62
我们的awk脚本如下,这段代码包含了awk脚本的写法,脚本的格式化,运算符,是一个不错的例子,当然是我网上抄的,代码尽量自己敲,下载代码文件
$ cat cal.awk
#!/usr/bin/awk -f
#运行前
BEGIN {
math = 0
english = 0
computer = 0
printf "NAME NO. MATH ENGLISH COMPUTER TOTAL\n"
printf "---------------------------------------------\n"
}
#运行中
{
math+=$3
english+=$4
computer+=$5
printf "%-6s %-6s %4d %8d %8d %8d\n", $1, $2, $3,$4,$5, $3+$4+$5
}
#运行后
END {
printf "---------------------------------------------\n"
printf " TOTAL:%10d %8d %8d \n", math, english, computer
printf "AVERAGE:%10.2f %8.2f %8.2f\n", math/NR, english/NR, computer/NR
}
>>> ./1-05.01.awk score.txt #执行脚本获取结果
总结 awk还是不少其他的用法,比如带for循环,比如各种各种的运算表达式,当然目前学的太深好处也不明显(笔者也没研究那么深),大家学的时候要记住技巧,awk的常规格式,这些格式的执行过程是咋样的,如果变成脚本来使用,知道这些原理,具体的细节一旦忘记直接百度查查,基本上就可以满足工作需要,另外技术还是熟能生巧的道理,相信各位一定比笔者用的多,自然未来也会比我更熟悉,这里提供一个超详细的awk讲解