文章目录
第一章节 SHELL编程之变量定义
SHELL编程
-
该课程主要包括以下内容:
① Shell的基本语法结构
如:变量定义、条件判断、循环语句(for、until、while)、分支语句、函数和数组等;
② 基本正则表达式的运用;
③ 文件处理三剑客:grep、sed、awk工具的使用;
④ 使用shell脚本完成一些较复杂的任务,如:服务搭建、批量处理等。
说明:以上内容仅仅是基本要求,还有很多更深更难的语法需要扩充学习。
-
今日目标
- 熟悉grep、cut、sort等小工具和shell中的通配符的使用
- 熟练掌握shell变量的定义和获取(重点)
- 能够进行shell简单的四则运算
- 熟悉条件判断语句,如判断整数,判断字符串等
学习前奏
1. 文件处理工具
1.1 grep工具
行过滤
grep用于根据关键字进行行过滤
grep options 'keys' filename
OPTIONS:
-i: 不区分大小写
-v: 查找不包含指定内容的行,反向选择
-w: 按单词搜索
-o: 打印匹配关键字
-c: 统计匹配到的次数
-n: 显示行号
-r: 逐层遍历目录查找
-A: 显示匹配行及后面多少行
-B: 显示匹配行及前面多少行
-C: 显示匹配行前后多少行
-l:只列出匹配的文件名
-L:列出不匹配的文件名
-e: 使用正则匹配
-E:使用扩展正则匹配
^key:以关键字开头
key$:以关键字结尾
^$:匹配空行
--color=auto :可以将找到的关键词部分加上颜色的显示
临时设置:
# alias grep='grep --color=auto' //只针对当前终端和当前用户生效
永久设置:
1)全局(针对所有用户生效)
vim /etc/bashrc
alias grep='grep --color=auto'
source /etc/bashrc
2)局部(针对具体的某个用户)
vim ~/.bashrc
alias grep='grep --color=auto'
source ~/.bashrc
示例:
# grep -i root passwd 忽略大小写匹配包含root的行
# grep -w ftp passwd 精确匹配ftp单词
# grep -w hello passwd 精确匹配hello单词;自己添加包含hello的行到文件中
# grep -wo ftp passwd 打印匹配到的关键字ftp
# grep -n root passwd 打印匹配到root关键字的行好
# grep -ni root passwd 忽略大小写匹配统计包含关键字root的行
# grep -nic root passwd 忽略大小写匹配统计包含关键字root的行数
# grep -i ^root passwd 忽略大小写匹配以root开头的行
# grep bash$ passwd 匹配以bash结尾的行
# grep -n ^$ passwd 匹配空行并打印行号
# grep ^# /etc/vsftpd/vsftpd.conf 匹配以#号开头的行
# grep -v ^# /etc/vsftpd/vsftpd.conf 匹配不以#号开头的行
# grep -A 5 mail passwd 匹配包含mail关键字及其后5行
# grep -B 5 mail passwd 匹配包含mail关键字及其前5行
# grep -C 5 mail passwd 匹配包含mail关键字及其前后5行
1.2 cut工具
列截取
cut用于列截取
-c: 以字符为单位进行分割。
-d: 自定义分隔符,默认为制表符。\t
-f: 与-d一起使用,指定显示哪个区域。
# cut -d: -f1 1.txt 以:冒号分割,截取第1列内容
# cut -d: -f1,6,7 1.txt 以:冒号分割,截取第1,6,7列内容
# cut -c4 1.txt 截取文件中每行第4个字符
# cut -c1-4 1.txt 截取文件中每行的1-4个字符
# cut -c4-10 1.txt
# cut -c5- 1.txt 从第5个字符开始截取后面所有字符
课堂练习:
用小工具列出你当系统的运行级别。5/3
1.3 sort工具
排序
sort:将文件的每一行作为一个单位,从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。
-u :去除重复行
-r :降序排列,默认是升序
-o : 将排序结果输出到文件中 类似 重定向符号>
-n :以数字排序,默认是按字符排序
-t :分隔符
-k :第N列
-b :忽略前导空格。
-R :随机排序,每次运行的结果均不同。
示例:
# sort -n -t: -k3 1.txt 按照用户的uid进行升序排列
# sort -nr -t: -k3 1.txt 按照用户的uid进行降序排列
# sort -n 2.txt 按照数字排序
# sort -nu 2.txt 按照数字排序并且去重
# sort -nr 2.txt
# sort -nru 2.txt
# sort -nru 2.txt
# sort -n 2.txt -o 3.txt 按照数字排序并将结果重定向到文件
# sort -R 2.txt
# sort -u 2.txt
1.4 uniq工具
去除连续的重复行
uniq:去除连续重复行
-i: 忽略大小写
-c: 统计重复行次数
-d:只显示重复行
# uniq 2.txt
# uniq -d 2.txt
# uniq -dc 2.txt
1.5 tee工具
tee工具从标准输入读取并写入标准输出和文件,即:双向覆盖重定向<屏幕输出|文本输入>
-a 双向追加重定向
# echo hello world
# echo hello world|tee file1
# cat file1
# echo 999|tee -a file1
# cat file1
1.6 paste工具
paste工具用于合并文件行
-d:自定义间隔符,默认是tab
-s:串行处理,非并行
[root@server shell01]# cat a.txt
hello
[root@server shell01]# cat b.txt
hello world
888
999
[root@server shell01]# paste a.txt b.txt
hello hello world
888
999
[root@server shell01]# paste b.txt a.txt
hello world hello
888
999
[root@server shell01]# paste -d'@' b.txt a.txt
hello world@hello
888@
999@
[root@server shell01]# paste -s b.txt a.txt
hello world 888 999
hello
1.7 tr工具
字符转换:替换,删除
tr用来从标准输入中通过替换或删除操作进行字符转换;主要用于删除文件中控制字符或进行字符转换。
使用tr时要转换两个字符串:字符串1用于查询,字符串2用于处理各种转换。
语法:
commands|tr 'string1' 'string2'
tr 'string1' 'string2' < filename
tr options 'string1' < filename
-d 删除字符串1中所有输入字符。
-s 删除所有重复出现字符序列,只保留第一个;即将重复出现字符串压缩为一个字符串。
a-z 任意小写
A-Z 任意大写
0-9 任意数字
[:alnum:] all letters and digits 所有字母和数字
[:alpha:] all letters 所有字母
[:blank:] all horizontal whitespace 所有水平空白
[:cntrl:] all control characters 所有控制字符
\b Ctrl-H 退格符
\f Ctrl-L 走行换页
\n Ctrl-J 新行
\r Ctrl-M 回车
\t Ctrl-I tab键
[:digit:] all digits 所有数字
[:graph:] all printable characters, not including space
所有可打印的字符,不包含空格
[:lower:] all lower case letters 所有小写字母
[:print:] all printable characters, including space
所有可打印的字符,包含空格
[:punct:] all punctuation characters 所有的标点符号
[:space:] all horizontal or vertical whitespace 所有水平或垂直的空格
[:upper:] all upper case letters 所有大写字母
[:xdigit:] all hexadecimal digits 所有十六进制数字
[=CHAR=] all characters which are equivalent to CHAR 所有字符
[root@server shell01]# cat 3.txt 自己创建该文件用于测试
ROOT:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
boss02:x:516:511::/home/boss02:/bin/bash
vip:x:517:517::/home/vip:/bin/bash
stu1:x:518:518::/home/stu1:/bin/bash
mailnull:x:47:47::/var/spool/mqueue:/sbin/nologin
smmsp:x:51:51::/var/spool/mqueue:/sbin/nologin
aaaaaaaaaaaaaaaaaaaa
bbbbbb111111122222222222233333333cccccccc
hello world 888
666
777
999
# tr -d '[:/]' < 3.txt 删除文件中的:和/
# cat 3.txt |tr -d '[:/]' 删除文件中的:和/
# tr '[0-9]' '@' < 3.txt 将文件中的数字替换为@符号
# tr '[a-z]' '[A-Z]' < 3.txt 将文件中的小写字母替换成大写字母
# tr -s '[a-z]' < 3.txt 匹配小写字母并将重复的压缩为一个
# tr -s '[a-z0-9]' < 3.txt 匹配小写字母和数字并将重复的压缩为一个
# tr -d '[:digit:]' < 3.txt 删除文件中的数字
# tr -d '[:blank:]' < 3.txt 删除水平空白
# tr -d '[:space:]' < 3.txt 删除所有水平和垂直空白
1.8 diff工具
diff工具用于逐行比较文件的不同
注意:diff描述两个文件不同的方式是告诉我们怎样改变第一个文件之后与第二个文件匹配。
语法和选项
语法:
diff [选项] 文件1 文件2
常用选项:
选项 | 含义 | 备注 |
---|---|---|
-b | 不检查空格 | |
-B | 不检查空白行 | |
-i | 不检查大小写 | |
-w | 忽略所有的空格 | |
–normal | 正常格式显示(默认) | |
-c | 上下文格式显示 | |
-u | 合并格式显示 |
举例说明:
- 比较两个普通文件异同,文件准备:
[root@MissHou ~]# cat file1
aaaa
111
hello world
222
333
bbb
[root@MissHou ~]#
[root@MissHou ~]# cat file2
aaa
hello
111
222
bbb
333
world
1)正常显示
diff目的:file1如何改变才能和file2匹配
[root@MissHou ~]# diff file1 file2
1c1,2 第一个文件的第1行需要改变(c=change)才能和第二个文件的第1到2行匹配
< aaaa 小于号"<"表示左边文件(file1)文件内容
--- ---表示分隔符
> aaa 大于号">"表示右边文件(file2)文件内容
> hello
3d3 第一个文件的第3行删除(d=delete)后才能和第二个文件的第3行匹配
< hello world
5d4 第一个文件的第5行删除后才能和第二个文件的第4行匹配
< 333
6a6,7 第一个文件的第6行增加(a=add)内容后才能和第二个文件的第6到7行匹配
> 333 需要增加的内容在第二个文件里是333和world
> world
2)上下文格式显示
[root@MissHou ~]# diff -c file1 file2
前两行主要列出需要比较的文件名和文件的时间戳;文件名前面的符号***表示file1,---表示file2
*** file1 2019-04-16 16:26:05.748650262 +0800
--- file2 2019-04-16 16:26:30.470646030 +0800
*************** 我是分隔符
*** 1,6 **** 以***开头表示file1文件,1,6表示1到6行
! aaaa !表示该行需要修改才与第二个文件匹配
111
- hello world -表示需要删除该行才与第二个文件匹配
222
- 333 -表示需要删除该行才与第二个文件匹配
bbb
--- 1,7 ---- 以---开头表示file2文件,1,7表示1到7行
! aaa 表示第一个文件需要修改才与第二个文件匹配
! hello 表示第一个文件需要修改才与第二个文件匹配
111
222
bbb
+ 333 表示第一个文件需要加上该行才与第二个文件匹配
+ world 表示第一个文件需要加上该行才与第二个文件匹配
3)合并格式显示
[root@MissHou ~]# diff -u file1 file2
前两行主要列出需要比较的文件名和文件的时间戳;文件名前面的符号---表示file1,+++表示file2
--- file1 2019-04-16 16:26:05.748650262 +0800
+++ file2 2019-04-16 16:26:30.470646030 +0800
@@ -1,6 +1,7 @@
-aaaa
+aaa
+hello
111
-hello world
222
-333
bbb
+333
+world
- 比较两个目录不同
默认情况下也会比较两个目录里相同文件的内容
[root@MissHou tmp]# diff dir1 dir2
diff dir1/file1 dir2/file1
0a1
> hello
Only in dir1: file3
Only in dir2: test1
如果只需要比较两个目录里文件的不同,不需要进一步比较文件内容,需要加-q选项
[root@MissHou tmp]# diff -q dir1 dir2
Files dir1/file1 and dir2/file1 differ
Only in dir1: file3
Only in dir2: test1
其他小技巧:
有时候我们需要以一个文件为标准,去修改其他文件,并且修改的地方较多时,我们可以通过打补丁的方式完成。
1)先找出文件不同,然后输出到一个文件
[root@MissHou ~]# diff -uN file1 file2 > file.patch
-u:上下文模式
-N:将不存在的文件当作空文件
2)将不同内容打补丁到文件
[root@MissHou ~]# patch file1 file.patch
patching file file1
3)测试验证
[root@MissHou ~]# diff file1 file2
[root@MissHou ~]#
小试牛刀
- 使用小工具分别截取当前主机IP;截取NETMASK;截取广播地址;截取MAC地址
[root@server shell01]# ifconfig eth0|grep 'Bcast'|tr -d '[a-zA-Z ]'|cut -d: -f2,3,4
10.1.1.1:10.1.1.255:255.255.255.0
[root@server shell01]# ifconfig eth0|grep 'Bcast'|tr -d '[a-zA-Z ]'|cut -d: -f2,3,4|tr ':' '\n'
10.1.1.1
10.1.1.255
255.255.255.0
[root@server shell01]# ifconfig eth0|grep 'HWaddr'|cut -d: -f2-|cut -d' ' -f4
00:0C:29:25:AE:54
# ifconfig eth1|grep Bcast|cut -d: -f2|cut -d' ' -f1
# ifconfig eth1|grep Bcast|cut -d: -f2|tr -d '[ a-zA-Z]'
# ifconfig eth1|grep Bcast|tr -d '[:a-zA-Z]'|tr ' ' '@'|tr -s '@'|tr '@' '\n'|grep -v ^$
# ifconfig eth0|grep 'Bcast'|tr -d [:alpha:]|tr '[ :]' '\n'|grep -v ^$
# ifconfig eth1|grep HWaddr|cut -d ' ' -f11
# ifconfig eth0|grep HWaddr|tr -s ' '|cut -d' ' -f5
# ifconfig eth1|grep HWaddr|tr -s ' '|cut -d' ' -f5
- 将系统中所有普通用户的用户名、密码和默认shell保存到一个文件中,要求用户名密码和默认shell之间用tab键分割
[root@server shell01]# grep 'bash$' passwd |grep -v '^root'|cut -d: -f1,2,7|tr ':' '\t'
stu1 x /bin/bash
code x /bin/bash
kefu x /bin/bash
kefu1 x /bin/bash
kefu2 x /bin/bash
user01 x /bin/bash
stu2 x /bin/bash
[root@server shell01]# grep bash$ passwd |grep -viE 'root|mysql'|cut -d: -f1,2,7|tr ':' '\t' |tee a.txt
注释:
-E 匹配扩展正则表达式,|代表或者,是一个扩展正则
2. 编程语言分类
- 编译型语言:
程序在执行之前需要一个专门的编译过程,把程序编译成为机器语言文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++
- 解释型语言:
程序不需要编译,程序在运行时由**解释器**翻译成机器语言,每执行一次都要翻译一次。因此效率比较低。比如Python/JavaScript/ Perl /ruby/Shell等都是解释型语言。
- 总结:
编译型语言比解释型语言速度较快,但是不如解释型语言跨平台性好。如果做底层开发或者大型应用程序或者操作系开发一般都用编译型语言;如果是一些服务器脚本及一些辅助的接口,对速度要求不高、对各个平台的兼容性有要求的话则一般都用解释型语言。
3. shell介绍
总结:
- shell就是人机交互的一个桥梁
- shell的种类
[root@MissHou ~]# cat /etc/shells
/bin/sh #是bash shell的一个快捷方式
/bin/bash #bash shell是大多数Linux默认的shell,包含的功能几乎可以涵盖shell所有的功能
/sbin/nologin #表示非交互,不能登录操作系统
/bin/dash #小巧,高效,功能相比少一些
/bin/tcsh #是csh的增强版,完全兼容csh
/bin/csh #具有C语言风格的一种shell,具有许多特性,但也有一些缺陷
-
用户在终端(终端就是bash的接口)输入命令
|
bash //bash就是shell的一种类型(bash shell)
|
kernel
|
物理硬件等
4. shell脚本
-
什么是shell脚本?
- 一句话概括
简单来说就是将需要执行的命令保存到文本中,按照顺序执行。它是解释型的,意味着不需要编译。
- 准确叙述
若干命令 + 脚本的基本格式 + 脚本特定语法 + 思想= shell脚本
-
什么时候用到脚本?
重复化、复杂化的工作,通过把工作的命令写成脚本,以后仅仅需要执行脚本就能完成这些工作。
①自动化分析处理
②自动化备份
③自动化批量部署安装
④等等…
-
如何学习shell脚本?
- 尽可能记忆更多的命令
- 掌握脚本的标准的格式(指定魔法字节、使用标准的执行方式运行脚本)
- 必须熟悉掌握脚本的基本语法(重点)
- 学习脚本的秘诀:
多看(看懂)——>多模仿(多练)——>多思考
- 脚本的基本写法:
#!/bin/bash
//脚本第一行, #!魔法字符,指定脚本代码执行的程序。即它告诉系统这个脚本需要什么解释器来执行,也就是使用哪一种Shell
//以下内容是对脚本的基本信息的描述
# Name: 名字
# Desc:描述describe
# Path:存放路径
# Usage:用法
# Update:更新时间
//下面就是脚本的具体内容
commands
...
-
脚本执行方法:
- 标准脚本执行方法(建议):(魔法字节指定的程序会生效)
[root@MissHou shell01]# cat 1.sh #!/bin/bash #xxxx #xxx #xxx hostname date [root@MissHou shell01]# chmod +x 1.sh [root@MissHou shell01]# ll total 4 -rwxr-xr-x 1 root root 42 Jul 22 14:40 1.sh [root@MissHou shell01]# /shell/shell01/1.sh MissHou.itcast.cc Sun Jul 22 14:41:00 CST 2018 [root@MissHou shell01]# ./1.sh MissHou.itcast.cc Sun Jul 22 14:41:30 CST 2018
- 非标准的执行方法(不建议):(魔法字节指定的程序不会运作)
[root@MissHou shell01]# bash 1.sh MissHou.itcast.cc Sun Jul 22 14:42:51 CST 2018 [root@MissHou shell01]# sh 1.sh MissHou.itcast.cc Sun Jul 22 14:43:01 CST 2018 [root@MissHou shell01]# [root@MissHou shell01]# bash -x 1.sh + hostname MissHou.itcast.cc + date Sun Jul 22 14:43:20 CST 2018 -x:一般用于排错,查看脚本的执行过程 -n:用来查看脚本的语法是否有问题 注意:如果脚本没有加可执行权限,不能使用标准的执行方法执行,bash 1.sh 其他: [root@server shell01]# source 2.sh server Thu Nov 22 15:45:50 CST 2018 [root@server shell01]# . 2.sh server Thu Nov 22 15:46:07 CST 2018 source 和 . 表示读取文件,执行文件里的命令
5. bash基本特性
5.1 命令和文件自动补全
Tab只能补全命令和文件 (RHEL6/Centos6)
5.2 常见的快捷键
^c 终止前台运行的程序
^z 将前台运行的程序挂起到后台
^d 退出 等价exit
^l 清屏
^a |home 光标移到命令行的最前端
^e |end 光标移到命令行的后端
^u 删除光标前所有字符
^k 删除光标后所有字符
^r 搜索历史命令
5.3 常用的通配符(重点)
*: 匹配0或多个任意字符
?: 匹配任意单个字符
[list]: 匹配[list]中的任意单个字符
[!list]: 匹配除list中的任意单个字符
{string1,string2,...}:匹配string1,string2或更多字符串
举例:
touch file{1..3}
touch file{1..13}.jpg
# ls file*
# ls *.jpg
# ll file?
# ll file?.jpg
# ll file??.jpg
# ll file1?.jpg
# ll file?.jpg
# ll file[1023].jpg
# ll file[0-13].jpg
# ll file1[0-9].jpg
# ll file[0-9].jpg
# ll file?[1-13].jpg
# ll file[1,2,3,10,11,12].jpg
# ll file1{11,10,1,2,3}.jpg
# ll file{1..10}.jpg
# ll file{1...10}.jpg
5.4 bash中的引号(重点)
- 双引号"" :会把引号的内容当成整体来看待,允许通过$符号引用其他变量值
- 单引号’’ :会把引号的内容当成整体来看待,禁止引用其他变量值,shell中特殊符号都被视为普通字符
- 反撇号`` :反撇号和$()一样,引号或括号里的命令会优先执行,如果存在嵌套,反撇号不能用
[root@server dir1]# echo "$(hostname)"
server
[root@server dir1]# echo '$(hostname)'
$(hostname)
[root@server dir1]# echo "hello world"
hello world
[root@server dir1]# echo 'hello world'
hello world
[root@server dir1]# echo $(date +%F)
2018-11-22
[root@server dir1]# echo `echo $(date +%F)`
2018-11-22
[root@server dir1]# echo `date +%F`
2018-11-22
[root@server dir1]# echo `echo `date +%F``
date +%F
[root@server dir1]# echo $(echo `date +%F`)
2018-11-22
变量的定义
1. 变量的分类
- 本地变量:当前用户自定义的变量。当前进程中有效,其他进程及当前进程的子进程无效。
- 环境变量:当前进程有效,并且能够被子进程调用。
- 查看当前用户的环境变量 env
- 查询当前用户的所有变量(临时变量与环境变量) set
- export //将当前变量变成环境变量
[root@MissHou tmp]# export A=hello //临时将一个本地变量(临时变量)变成环境变量
[root@MissHou tmp]# env|grep ^A
A=hello
永久生效:
vim /etc/profile 或者 ~/.bashrc
export A=hello
或者
A=hello
export A
说明:系统中有一个变量PATH,环境变量
export PATH=/usr/local/mysql/bin:$PATH
- 全局变量:全局所有的用户和程序都能调用,且继承,新建的用户也默认能调用.
$HOME/.bashrc 当前用户的bash信息(aliase、umask等)
$HOME/.bash_profile 当前用户的环境变量()
oracle——>oracle用户——>$oracle/.bash_profile——>export home_install=/u01/app/xxx
$HOME/.bash_logout 每个用户退出当前shell时最后读取的文件
/etc/bashrc 使用bash shell用户全局变量
grep --color=auto
umask
/etc/profile 系统和每个用户的环境变量信息
mycat_home=/usr/local/mycat/bin
export mycat_home
执行mycat命令
# mycat
$ mycat
用户登录系统读取相关文件的顺序:
/etc/profile——>$HOME/.bash_profile——>$HOME/.bashrc——>/etc/bashrc——>$HOME/.bash_logout
source /etc/bashrc
- 系统变量(内置bash中变量) : shell本身已经固定好了它的名字和作用.
$?:上一条命令执行后返回的状态,当返回状态值为0时表示执行正常,非0值表示执行异常或出错
若退出状态值为0,表示命令运行成功
若退出状态值为127,表示command not found
若退出状态值为126,表示找到了该命令但无法执行(权限不够)
若退出状态值为1&2,表示没有那个文件或目录
$$:当前所在进程的进程号 echo $$ eg:kill -9 `echo $$` = exit 退出当前会话
$!:后台运行的最后一个进程号 (当前终端) # gedit &
!$ 调用最后一条命令历史中的参数
!! 调用最后一条命令历史
$#:脚本后面接的参数的个数
$*:脚本后面所有参数,参数当成一个整体输出,每一个变量参数之间以空格隔开
$@: 脚本后面所有参数,参数是独立的,也是全部输出
$0:当前执行的进程/程序名 echo $0
$1~$9 位置参数变量
${10}~${n} 扩展位置参数变量 第10个位置变量必须用{}大括号括起来
./1.sh a b c
[root@MissHou shell01]# cat 2.sh
#!/bin/bash
#xxxx
echo "\$0 = $0"
echo "\$# = $#"
echo "\$* = $*"
echo "\$@ = $@"
echo "\$1 = $1"
echo "\$2 = $2"
echo "\$3 = $3"
echo "\$11 = ${11}"
echo "\$12 = ${12}"
了解$*和$@的区别:
$* :表示将变量看成一个整体
$@ :表示变量是独立的
#!/bin/bash
for i in "$@"
do
echo $i
done
echo "======我是分割线======="
for i in "$*"
do
echo $i
done
[root@MissHou shell01]# bash 3.sh a b c
a
b
c
======我是分割线=======
a b c
2. 什么时候需要定义变量?
- 如果某个内容需要多次使用,并且在代码中重复出现,那么可以用变量代表该内容。这样在修改内容的时候,仅仅需要修改变量的值。
- 在代码运作的过程中,可能会把某些命令的执行结果保存起来,后续代码需要使用这些结果,就可以直接使用这个变量。
3. 变量的定义规则
1. 默认情况下,shell里定义的变量是不分类型的,可以给变量赋与任何类型的值;等号两边不能有空格,对于有空格的字符串做为赋值时,要用引号引起来
变量名=变量值
2. 变量的获取方式: $变量名 ${变量名}
[root@MissHou shell01]# a=12345678
[root@MissHou shell01]# echo $a
12345678
[root@MissHou shell01]# echo ${a}
12345678
[root@MissHou shell01]# echo ${a:2:3} a表示变量名;2表示从第3个字符开始;3表示后面3个字符
345
如果获取变量的全部两个都可以;如果获取变量的某一部分,用${}
3. 取消变量: unset 变量名
4. 变量名区分大小写,同名称但大小写不同的变量名是不同的变量
5. 变量名可以是字母或数字或下划线,但是不能以数字开头或者特殊字符
[root@MissHou shell01]# 1a=hello
-bash: 1a=hello: command not found
[root@MissHou shell01]# ?a=hello
-bash: ?a=hello: command not found
[root@MissHou shell01]# _a=hello
[root@MissHou shell01]# echo $_a
hello
6. 命令的执行结果可以保存到变量
[root@server shell01]# kernel=`uname -r`
[root@server shell01]# echo $kernel
2.6.32-431.el6.x86_64
[root@server shell01]# name=$(uname -n)
[root@server shell01]# echo $name
server.itcast.cc
7. 有类型变量 declare
-i 将变量看成整数
-r 使变量只读 readonly
-x 标记变量通过环境导出 export
-a 指定为索引数组(普通数组);查看普通数组
-A 指定为关联数组;查看关联数组
[root@server shell01]# a=10
[root@server shell01]# b=20
[root@server shell01]# echo $a+$b
10+20
[root@server shell01]# declare -i a=2
[root@server shell01]# declare -i b=4
[root@server shell01]# declare -i c=$a+$b
[root@server shell01]# echo $c
6
[root@server shell01]# AAAA=hello
[root@server shell01]# export AAAA
[root@server shell01]# env|grep AAAA
AAAA=hello
[root@server shell01]# declare -x BBBB=hello
[root@server shell01]# env|grep BBBB
BBBB=hello
8. 数组
普通数组:只能使用整数作为数组索引(元素的下标)
关联数组:可以使用字符串作为数组索引(元素的下标)
普通数组定义:用括号来表示数组,数组元素(变量)用“空格”符号分割开。定义数组的一般形式为:
一次赋一个值:
变量名=变量值
array[0]=v1
array[1]=v2
array[3]=v3
一次赋多个值:
array=(var1 var2 var3 var4)
array1=(`cat /etc/passwd`) //将文件中每一行赋值给array1数组
array2=(`ls /root`)
array3=(harry amy jack "Miss Hou")
array4=(1 2 3 4 "hello world" [10]=linux)
读取数组:
${array[i]} i表示元素的下标
使用@ 或 * 可以获取数组中的所有元素:
获取第一个元素
echo ${array[0]}
echo ${array[*]} 获取数组里的所有元素
echo ${#array[*]} 获取数组里所有元素个数
echo ${!array[@]} 获取数组元素的索引下标
echo ${array[@]:1:2} 访问指定的元素;1代表从下标为1的元素开始获取;2代表获取后面几个元素
[root@server shell01]# array[0]=var1
[root@server shell01]# array[1]=var2
[root@server shell01]# array[2]=var3
[root@server shell01]# array1=(uu1 uu2 uu3 uu4)
[root@server shell01]# ls
1.sh 2.sh 3.sh 4.sh passwd
[root@server shell01]# array2=(`ls ./`)
[root@server shell01]# array3=(jack harry "Miss Hou" [5]=tom)
查看普通数组信息:
[root@server shell01]# declare -a
declare -a array='([0]="var1" [1]="var2" [2]="var3")'
declare -a array1='([0]="uu1" [1]="uu2" [2]="uu3" [3]="uu4")'
declare -a array2='([0]="1.sh" [1]="2.sh" [2]="3.sh" [3]="4.sh" [4]="passwd")'
declare -a array3='([0]="jack" [1]="harry" [2]="Miss Hou" [5]="tom")'
[root@server shell01]#
[root@server shell01]#
[root@server shell01]# echo ${array[*]}
var1 var2 var3
[root@server shell01]# echo ${array[@]}
var1 var2 var3
[root@server shell01]# echo ${array[2]}
var3
[root@server shell01]# echo ${array2[@]}
1.sh 2.sh 3.sh 4.sh passwd
[root@server shell01]# echo ${array2[3]}
4.sh
[root@server shell01]#
[root@server shell01]# echo ${array2[*]:2:2}
3.sh 4.sh
[root@server shell01]# echo ${#array2[*]}
5
[root@server shell01]# echo ${!array2[*]}
0 1 2 3 4
[root@server shell01]# echo ${!array3[*]}
0 1 2 5
关联数组定义:
首先声明关联数组
declare -A asso_array1
declare -A asso_array2
declare -A asso_array3
数组赋值:
一次赋一个值:
数组名[索引|下标]=变量值
[root@server ~]# asso_array1[linux]=one
[root@server ~]# asso_array1[java]=two
[root@server ~]# asso_array1[php]=three
一次赋多个值:
[root@server ~]# asso_array2=([name1]=harry [name2]=jack [name3]=amy [name4]="Miss Hou")
查看关联数组:
[root@server ~]# declare -A
declare -A asso_array1='([php]="three" [java]="two" [linux]="one" )'
declare -A asso_array2='([name3]="amy" [name2]="jack" [name1]="harry" [name4]="Miss Hou" )'
[root@server ~]# echo ${asso_array1[linux]}
one
[root@server ~]# echo ${asso_array1[php]}
three
[root@server ~]# echo ${asso_array1[*]}
three two one
[root@server ~]# echo ${!asso_array1[*]}
php java linux
[root@server ~]# echo ${#asso_array1[*]}
3
[root@server ~]# echo ${#asso_array2[*]}
4
[root@server ~]# echo ${!asso_array2[*]}
name3 name2 name1 name4
9. 交互式定义变量的值 read 主要用于让用户去定义变量值
-p 提示信息
-n 字符数 (限制变量值的字符数)
-s 不显示
-t 超时(默认单位秒)(限制用户输入变量值的超时时间)
[root@MissHou shell01]# cat 1.txt
10.1.1.1 255.255.255.0
[root@MissHou shell01]# read -p "Input your IP and Netmask:" ip mask < 1.txt
[root@MissHou shell01]# echo $ip
10.1.1.1
[root@MissHou shell01]# echo $mask
255.255.255.0
10. 其他变量(扩展)
1)取出一个目录下的目录和文件:dirname和 basename
2)变量"内容"的删除和替换
一个“%”代表从右往左去掉一个/key/
两个“%%”代表从右往左最大去掉/key/
一个“#”代表从左往右去掉一个/key/
两个“##”代表从左往右最大去掉/key/
# A=/root/Desktop/shell/mem.txt
# echo $A
/root/Desktop/shell/mem.txt
# dirname $A 取出目录
/root/Desktop/shell
# basename $A 取出文件
mem.txt
# url=www.taobao.com
# echo ${#url} 获取变量的长度
# echo ${url#*.}
# echo ${url##*.}
# echo ${url%.*}
# echo ${url%%.*}
++++++++++++++++++++++++++++++++++++++++++++++++++
以下内容自己完成:
替换:/ 和 //
1015 echo ${url/ao/AO}
1017 echo ${url//ao/AO} 贪婪替换
替代: - 和 :- +和:+
1019 echo ${abc-123}
1020 abc=hello
1021 echo ${abc-444}
1022 echo $abc
1024 abc=
1025 echo ${abc-222}
${变量名-新的变量值} 或者 ${变量名=新的变量值}
变量没有被赋值:会使用“新的变量值“ 替代
变量有被赋值(包括空值): 不会被替代
1062 echo ${ABC:-123}
1063 ABC=HELLO
1064 echo ${ABC:-123}
1065 ABC=
1066 echo ${ABC:-123}
${变量名:-新的变量值} 或者 ${变量名:=新的变量值}
变量没有被赋值或者赋空值:会使用“新的变量值“ 替代
变量有被赋值: 不会被替代
1116 echo ${abc=123}
1118 echo ${abc:=123}
[root@server ~]# unset abc
[root@server ~]# echo ${abc:+123}
[root@server ~]# abc=hello
[root@server ~]# echo ${abc:+123}
123
[root@server ~]# abc=
[root@server ~]# echo ${abc:+123}
${变量名+新的变量值}
变量没有被赋值或者赋空值:不会使用“新的变量值“ 替代
变量有被赋值: 会被替代
[root@server ~]# unset abc
[root@server ~]# echo ${abc+123}
[root@server ~]# abc=hello
[root@server ~]# echo ${abc+123}
123
[root@server ~]# abc=
[root@server ~]# echo ${abc+123}
123
${变量名:+新的变量值}
变量没有被赋值:不会使用“新的变量值“ 替代
变量有被赋值(包括空值): 会被替代
[root@server ~]# unset abc
[root@server ~]# echo ${abc?123}
-bash: abc: 123
[root@server ~]# abc=hello
[root@server ~]# echo ${abc?123}
hello
[root@server ~]# abc=
[root@server ~]# echo ${abc?123}
${变量名?新的变量值}
变量没有被赋值:提示错误信息
变量被赋值(包括空值):不会使用“新的变量值“ 替代
[root@server ~]# unset abc
[root@server ~]# echo ${abc:?123}
-bash: abc: 123
[root@server ~]# abc=hello
[root@server ~]# echo ${abc:?123}
hello
[root@server ~]# abc=
[root@server ~]# echo ${abc:?123}
-bash: abc: 123
${变量名:?新的变量值}
变量没有被赋值或者赋空值时:提示错误信息
变量被赋值:不会使用“新的变量值“ 替代
说明:?主要是当变量没有赋值提示错误信息的,没有赋值功能
简单的四则运算
算术运算:默认情况下,shell就只能支持简单的整数运算
+ - * / %(取模,求余数)
Bash shell 的算术运算有四种方式:
1. 使用 $(( ))
2. 使用$[ ]
3. 使用 expr 外部程式
4. 使用let 命令
注意:
n=1
let n+=1 等价于let n=n+1
思考:能不能用shell做小数运算?
[root@server shell01]# echo 1+1.5|bc
2.5
i++ 和 ++i (了解)
对变量的值的影响:
[root@node1 ~]# i=1
[root@node1 ~]# let i++
[root@node1 ~]# echo $i
2
[root@node1 ~]# j=1
[root@node1 ~]# let ++j
[root@node1 ~]# echo $j
2
对表达式的值的影响:
[root@node1 ~]# unset i j
[root@node1 ~]# i=1;j=1
[root@node1 ~]# let x=i++ 先赋值,再运算
[root@node1 ~]# let y=++j 先运算,再赋值
[root@node1 ~]# echo $i
2
[root@node1 ~]# echo $j
2
[root@node1 ~]# echo $x
1
[root@node1 ~]# echo $y
2
总结:
$(()) $[]
expr 注意空格,*要进行转义 \
let n+=1 等价 let n=n+1
let n=n**5 n有初值,然后求次幂
i++ ++i
对变量本身没有影响(自己+1);
表达式中有影响;i++ 先赋值再运算 ++i先运算再赋值
let x=i++ let x=++i
条件判断
1. 语法格式
- 格式1: test 条件表达式
- 格式2: [ 条件表达式 ]
- 格式3: [[ 条件表达式 ]] 支持正则 =~
说明:
man test去查看,很多的参数都用来进行条件判断
2. 条件判断相关参数
- 与文件存在与否的判断
-e 是否存在 不管是文件还是目录,只要存在,条件就成立
-f 是否为普通文件
-d 是否为目录
-S socket
-p pipe
-c character
-b block
-L 软link
三种语法格式:
test -e file 只要文件存在条件为真
[ -d /shell01/dir1 ] 判断目录是否存在,存在条件为真
[ ! -d /shell01/dir1 ] 判断目录是否存在,不存在条件为真
[[ -f /shell01/1.sh ]] 判断文件是否存在,并且是一个普通的文件
-s 判断文件是否有内容(大小),非空文件条件满足
说明:-s表示非空,! -s 表示空文件
说明:1.sh文件里有内容的。
[root@server shell01]# test -s 1.sh
[root@server shell01]# echo $?
0
[root@server shell01]# touch aaa
[root@server shell01]# cat aaa
[root@server shell01]# test -s aaa
[root@server shell01]# echo $?
1
[root@server shell01]# test ! -s aaa
[root@server shell01]# echo $?
0
[root@server shell01]# test ! -s 1.sh
[root@server shell01]# echo $?
1
- 文件权限相关的判断
-r 当前用户对其是否可读
-w 当前用户对其是否可写
-x 当前用户对其是否可执行
-u 是否有suid
-g 是否sgid
-k 是否有t位
- 两个文件的比较判断
file1 -nt file2 比较file1是否比file2新
file1 -ot file2 比较file1是否比file2旧
file1 -ef file2 比较是否为同一个文件,或者用于判断硬连接,是否指向同一个inode
test file1 -nt file2
[ file1 -ot file2 ]
- 整数之间的判断
-eq 相等
-ne 不等
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于
- 字符串之间的判断
-z 是否为空字符串 字符串长度为0,就成立
-n 是否为非空字符串 只要字符串非空,就是成立
string1 = string2 是否相等
string1 != string2 不等
[root@server shell01]# AAA=hello
[root@server shell01]# BBB=world
[root@server shell01]# test -z $AAA
[root@server shell01]# echo $?
1
[root@server shell01]# test -n $AAA
[root@server shell01]# echo $?
0
[root@server shell01]# [ $AAA = $BBB ]
[root@server shell01]# echo $?
1
[root@server shell01]# [ $AAA != $BBB ]
[root@server shell01]# echo $?
0
- 多重条件判断
逻辑判断符号:
-a 和 && (and 逻辑与) 两个条件同时满足,整个大条件为真
-o 和 || (or 逻辑或) 两个条件满足任意一个,整个大条件为真
[ 1 -eq 1 -a 1 -ne 0 ] 整个表达式为真
[ 1 -eq 1 ] && [ 1 -ne 0 ]
[ 1 -eq 1 -o 1 -ne 1 ] 整个表达式为真
[ 1 -eq 1 ] || [ 1 -ne 1 ]
[root@server shell01]# [ 1 -eq 0 ] && echo true || echo false
false
[root@server shell01]# [ 1 -eq 1 ] && echo true || echo false
true
&&:前面的表达式为真
||:前面的表达式为假
总结:
1、; && ||都可以用来分割命令或者表达式
2、; 完全不考虑前面的语句是否正确执行,都会执行;号后面的内容
3、&& 需要考虑&&前面的语句的正确性,前面语句正确执行才会执行&&后的内容;反之亦然
make && make install
4、|| 需要考虑||前面的语句的非正确性,前面语句执行错误才会执行||后的内容;反之亦然
5、如果&&和||一起出现,从左往右依次看,按照以上原则
3. 示例
示例:
数值比较:
[root@server ~]# [ $(id -u) -eq 0 ] && echo "the user is admin"
[root@server ~]$ [ $(id -u) -ne 0 ] && echo "the user is not admin"
[root@server ~]$ [ $(id -u) -eq 0 ] && echo "the user is admin" || echo "the user is not admin"
[root@server ~]# uid=`id -u`
[root@server ~]# test $uid -eq 0 && echo this is admin
this is admin
[root@server ~]# [ $(id -u) -ne 0 ] || echo this is admin
this is admin
[root@server ~]# [ $(id -u) -eq 0 ] && echo this is admin || echo this is not admin
this is admin
[root@server ~]# su - stu1
[stu1@server ~]$ [ $(id -u) -eq 0 ] && echo this is admin || echo this is not admin
this is not admin
[stu1@server ~]$
类C风格的数值比较:
注意:在(( ))中,=表示赋值;==表示判断
1159 ((1==2));echo $?
1160 ((1<2));echo $?
1161 ((2>=1));echo $?
1162 ((2!=1));echo $?
1163 ((`id -u`==0));echo $?
1209 ((a=123));echo $a
1210 unset a
1211 ((a==123));echo $?
字符串比较:
注意:双引号引起来,看作一个整体;= 和 == 在 [ 字符串 ] 比较中都表示判断
1196 a='hello world';b=world
1197 [ $a = $b ];echo $?
1198 [ "$a" = "$b" ];echo $?
1199 [ "$a" != "$b" ];echo $?
1200 [ "$a" !== "$b" ];echo $? 错误
1201 [ "$a" == "$b" ];echo $?
1202 test "$a" != "$b";echo $?
思考:[ ] 和 [[ ]] 有什么区别?
1213 a=
1214 test -z $a;echo $?
1215 a=hello
1216 test -z $a;echo $?
1217 test -n $a;echo $?
1217 test -n "$a";echo $?
# [ '' = $a ];echo $?
-bash: [: : unary operator expected
2
# [[ '' = $a ]];echo $?
0
1278 [ 1 -eq 0 -a 1 -ne 0 ];echo $?
1279 [ 1 -eq 0 && 1 -ne 0 ];echo $?
1280 [[ 1 -eq 0 && 1 -ne 0 ]];echo $?
第二章节 SHELL编程之流程控制和循环语句
一、流程控制语句
1. 基本语法结构
F: false 假
T: true 真
if [ condition ];then
command
command
fi
[ 条件 ] && command
if [ condition ];then
command1
else
command2
fi
[ 条件 ] && command1 || command2
if [ condition1 ];then
command1 结束
elif [ condition2 ];then
command2 结束
else
command3
fi
注释:
如果条件1满足,执行命令1后结束;如果条件1不满足,再看条件2,如果条件2满足执行命令2后结束;如果条件1和条件2都不满足执行命令3结束.
if [ condition1 ];then
command1
if [ condition2 ];then
command2
fi
else
if [ condition3 ];then
command3
elif [ condition4 ];then
command4
else
command5
fi
fi
注释:
如果条件1满足,执行命令1;如果条件2也满足执行命令2,如果不满足就只执行命令1结束;
如果条件1不满足,不看条件2;直接看条件3,如果条件3满足执行命令3;如果不满足则看条件4,如果条件4满足执行命令4;否则执行命令5
2. 应用案例
需求1:判断当前主机是否和远程主机是否ping通
思路:
1. 使用哪个命令实现 ping -c
2. 根据命令的执行结果状态来判断是否通 $?
3. 根据逻辑和语法结构来编写脚本(条件判断或者流程控制)
步骤:
vim ping.sh
#!/bin/bash
# Name:ping.sh
# Path:/shell02/
# Usage:/shell02/ping.sh
# ...
#获取远程主机的IP地址(定义变量让用户自己输入)
read -p "请输入你要ping的远程主机IP:" IP
#使用ping命令来判断是否和远程主机互通
ping -c1 $IP &>/dev/null
if [ $? -eq 0 ];then
echo "当前主机和远程主机$IP是互通的。"
else
echo "当前主机和远程主机$IP是不通的。"
fi
或者
#!/bin/bash
# Name:ping.sh
# Path:/shell02/
# Usage:/shell02/ping.sh
# ...
#使用ping命令来判断是否和远程主机互通
if [ $# -ne 1 ];then
echo "Usage:$0 remote_ip" && exit
fi
或者
[ $# -ne 1 ] && echo "Usage:$0 remote_ip" && exit
ping -c1 $1 &>/dev/null
[ $? -eq 0 ] && echo "当前主机和远程主机$1是互通的。" ||echo "当前主机和远程主机$1是不通的。"
说明:exit直接退出程序
需求2:判断一个进程是否存在
思路:
1.查看进程的相关命令 ps -ef pgrep ps auxf pidof
2.根据命令的返回状态值来判断进程是否存在 $?
3.根据逻辑用脚本语言实现
#!/bin/bash
# Name:process.sh
# Path:/shell02/
# Usage:/shell02/process.sh
# Describe:判断一个进程是否存在
# 定义变量
read -p "请输入需要判断的进程名(httpd):" process
# 通过命令来查看进程是否存在
pgrep $process &>/dev/null
# 通过命令执行的状态来判断是否存在
if [ $? -eq 0 ];then
echo "进程$process存在"
else
echo "进程$process不存在"
fi
或者
[ $? -eq 0 ] && echo "进程$process存在" || echo "进程$process不存在"
pgrep命令:以名称为依据从运行进程队列中查找进程,并显示查找到的进程id
选项
-o:仅显示找到的最小(起始)进程号;
-n:仅显示找到的最大(结束)进程号;
-l:显示进程名称;
-P:指定父进程号;pgrep -p 4764 查看父进程下的子进程id
-g:指定进程组;
-t:指定开启进程的终端;
-u:指定进程的有效用户ID。
需求3:判断一个服务是否正常(以httpd为例):
思路:
- 可以判断进程是否存在,用/etc/init.d/httpd status判断状态等方法
- 最好的方法是直接去访问一下,通过访问成功和失败的返回值来判断
#!/bin/bash
wget http://10.1.1.2 &>/dev/null
[ $? -eq 0 ] && echo "该web服务是正常的" && rm -f /shell/shell01/index.* || echo "该web服务异常请检查"
3. 课堂练习
1、输入一个用户,用脚本判断该用户是否存在
read -p "请输入需要判断的用户名:" user
id $user &>/dev/null
test $? -eq 0 && echo "该$user存在" || echo "该$user不存在"
2、判断vsftpd软件包是否安装,如果没有则自动安装(yum源已配好)
#!/bin/bash
rpm -q vsftpd &> /dev/null
if [ $? -eq 0 ];then
echo "vsftpd已经安装"
else
echo "该软件包没有安装,正在安装...."
yum install -y vsftpd &> /dev/null
if [ $? -eq 0 ];then
echo "vsftpd安装成功"
else
echo "vsftpd安装失败"
fi
fi
3、判断当前内核主版本是否为2,且次版本是否大于等于6;如果都满足则输出当前内核版本
思路:
1. 先查看内核的版本号 uname -r
2. 先将内核的版本号保存到一个变量里,然后再根据需求截取出该变量的一部分:主版本和次版本
3. 根据需求进步判断
#!/bin/bash
kernel=`uname -r`
var1=`echo $kernel|cut -d. -f1`
var2=`echo $kernel|cut -d. -f2`
test $var1 -eq 2 -a $var2 -ge 6 && echo $kernel || echo "当前内核版本不符合要求"
或者
[ $var1 -eq 2 -a $var2 -ge 6 ] && echo $kernel || echo "当前内核版本不符合要求"
或者
[[ $var1 -eq 2 && $var2 -ge 6 ]] && echo $kernel || echo "当前内核版本不符合要求"
或者
#!/bin/bash
kernel=`uname -r`
test ${kernel:0:1} -eq 2 -a ${kernel:2:1} -ge 6 && echo $kernel || echo '不符合要求'
其他命令参考:
uname -r|grep ^2.[6-9] || echo '不符合要求'
4、判断ftp服务是否已启动,如果已启动输出以下信息:
vsftpd服务器已启动…
vsftpd监听的端口是:
vsftpd的进程PID是:
参考1:
#!/bin/bash
service vsftpd status &>/dev/null
if [ $? -eq 0 ];then
port=`netstat -tnltp|grep vsftpd|cut -d: -f2|cut -d' ' -f1`
pid=`pgrep -l vsftpd|cut -d ' ' -f1`
echo -e "vsftpd服务器已启动...\nvsftpd监听的端口是:$port\nvsftpd的进程PID是:$pid"
else
service vsftpd start &>/dev/null
port=`netstat -tnltp|grep vsftpd|cut -d: -f2|cut -d' ' -f1`
pid=`pgrep -l vsftpd|cut -d ' ' -f1`
echo -e "vsftpd服务器已启动...\nvsftpd监听的端口是:$port\nvsftpd的进程PID是:$pid"
fi
参考2:
[root@server shell02]# cat liufeng.sh
#!/bin/bash
service $1 status
if [ $? -eq 0 ];then
echo " '$1'服务器已启动..."
a=$( netstat -tnulp | grep $1 )
array=($a)
echo "$1的监听端口是:$(echo ${array[3]} | cut -d: -f2) "
echo "$1的进程ID为:$(echo ${array[6]}| cut -d/ -f1)"
else
echo "$1进程未启动!"
fi
参考3:
vim /lt/2.sh
#! /bin/bash
duankou=`netstat -ntlp|grep vsftpd|cut -d: -f2|cut -d" " -f1`
pid=`pgrep -o vsftpd`
vim 1.sh
pgrep -l vsftpd >/dev/null
if [ $? -eq 0 ];then
echo "vsftpd服务器已启动..."
echo "vsftpd监听的端口是:$duankou"
echo "vsftpd的进程PID是:$pid"
else
echo "vsftpd服务器没启动"
service vsftpd start
source /lt/2.sh
fi
二、循环语句
1. for循环
1.1 语法结构
- 列表循环
列表for循环:用于将一组命令执行已知的次数,下面给出了for循环语句的基本格式:
for variable in {list}
do
command
command
…
done
或者
for variable in a b c
do
command
command
done
语法结构举例说明:
1045 for var in {1..10};do echo $var;done
1046 for var in 1 2 3 4 5;do echo $var;done
1047 for var in `seq 10`;do echo $var;done
1048 for var in $(seq 10);do echo $var;done
1049 for var in {0..10..2};do echo $var;done
1050 for var in {2..10..2};do echo $var;done
1051 for var in {10..1};do echo $var;done
1052 for var in {10..1..-2};do echo $var;done
1055 for var in `seq 10 -2 1`;do echo $var;done
- 1.2 不带列表循环
不带列表的for循环执行时由用户指定参数和参数的个数,下面给出了不带列表的for循环的基本格式:
for variable
do
command
command
…
done
语法结构举例说明:
#!/bin/bash
for var
do
echo $var
done
echo "脚本后面有$#个参数"
- 1.3 类C风格的for循环
for(( expr1;expr2;expr3 ))
do
command
command
…
done
for (( i=1;i<=5;i++))
do
echo $i
done
expr1:定义变量并赋初值
expr2:决定是否进行循环(条件)
expr3:决定循环变量如何改变,决定循环什么时候退出
语法结构举例说明:
1068 for ((i=1;i<=5;i++));do echo $i;done
1069 for ((i=1;i<=10;i+=2));do echo $i;done
1070 for ((i=2;i<=10;i+=2));do echo $i;done
1.2 举例说明
**例1:**计算1到100的奇数之和,方法不止一种
思路:
1. 定义一个变量来保存奇数的和 sum=0
2. 找出1-100的奇数,保存到另一个变量里 i
3. 从1-100中找出奇数后,再相加,然后将和赋值给sum变量
4. 遍历完毕后,将sum的值打印出来
#!/bin/bash
#定义一个变量来保存奇数的和
sum=0
#打印1-100的奇数并且相加重新赋值给sum
for i in {1..100..2}
do
sum=$[ $i + $sum ]
done
#打印1-100的奇数和
echo "1-100的奇数和为:$sum"
#!/bin/bash
#定义一个变量来保存奇数的和
sum=0
#打印1-100的奇数并且相加重新赋值给sum
for (( i=1;i<=100;i+=2))
do
let sum=sum+$i
或者
let sum=sum+i
或者
let sum=$sum+$i
done
#打印1-100的奇数和
echo "1-100的奇数和为:$sum"
#!/bin/bash
sum=0
for ((i=1;i<=100;i++))
do
if [ $[$i%2] -ne 0 ];then
let sum=sum+$i
fi
done
echo "1-100的奇数和是:$sum"
#!/bin/bash
sum=0
for ((i=1;i<=100;i++))
do
[ $[$i%2] -eq 0 ] && true || let sum=sum+$i
done
echo "1-100的奇数和是:$sum"
延伸:
true 真
: 真
false 假
方法1:
#!/bin/bash
sum=0
for i in {1..100..2}
do
sum=$[$i+$sum]
done
echo "1-100的奇数和为:$sum"
方法2:
#!/bin/bash
sum=0
for ((i=1;i<=100;i+=2))
do
let sum=$i+$sum
done
echo "1-100的奇数和为:$sum"
方法3:
#!/bin/bash
sum=0
for ((i=1;i<=100;i++))
do
if [ $[$i%2] -ne 0 ];then
let sum=$sum+$i
fi
或者
test $[$i%2] -ne 0 && let sum=$sum+$i
done
echo "1-100的奇数和为:$sum"
方法4:
sum=0
for ((i=1;i<=100;i++))
do
if [ $[$i%2] -eq 0 ];then
continue
else
let sum=$sum+$i
fi
done
echo "1-100的奇数和为:$sum"
#!/bin/bash
sum=0
for ((i=1;i<=100;i++))
do
test $[$i%2] -eq 0 && continue || let sum=sum+$i
done
echo "1-100的奇数和是:$sum"
循环控制:
循环体: do…done之间的内容
- continue:继续;表示循环体内下面的代码不执行,重新开始下一次循环
- break:打断;马上停止执行本次循环,执行循环体后面的代码
- exit:表示直接跳出程序
[root@server ~]# cat for5.sh
#!/bin/bash
for i in {1..5}
do
test $i -eq 2 && break || touch /tmp/file$i
done
echo hello hahahah
**例2:**输入一个正整数,判断是否为质数(素数)
质数:只能被1和它本身整除的数叫质数。
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
思路:
0. 让用户输入一个数,保存到一个变量里 read num
1、如果能被其他数整除就不是质数——>$num%$i 是否等于0 $i=2~$num-1
2、如果输入的数是1或者2取模根据上面判断又不符合,所以先排除1和2
3、测试序列从2开始,输入的数是4——>得出结果$num不能和$i相等,并且$num不能小于$i
#!/bin/bash
read -p "请输入一个正整数字:" number
[ $number -eq 1 ] && echo "$number不是质数" && exit
[ $number -eq 2 ] && echo "$number是质数" && exit
for i in `seq 2 $[$number-1]`
do
[ $[$number%$i] -eq 0 ] && echo "$number不是质数" && exit
done
echo "$number是质数" && exit
bash -x for6.sh
**举例3:**批量加5个新用户,以u1到u5命名,并统一加一个新组,组名为class,统一改密码为123
思路:
1. 添加用户的命令 useradd -G
2. 判断class组是否存在 grep -w class /etc/group;echo $?
3. 根据题意,判断该脚本循环5次来添加用户 for循环
4. 给用户设置密码,应该放到循环体里面
#!/bin/bash
#判断class组是否存在
grep -w class /etc/group &>/dev/null
[ $? -ne 0 ] && groupadd class
#批量创建5个用户
for i in {1..5}
do
useradd -G class u$i
echo 123|passwd --stdin u$i
done
#!/bin/bash
#判断class组是否存在
cut -d: -f1 /etc/group|grep -w class &>/dev/null
[ $? -ne 0 ] && groupadd class
#循环增加用户,循环次数5次,for循环,给用户设定密码
for ((i=1;i<=5;i++))
do
useradd u$i -G class
echo 123|passwd --stdin u$i
done
#!/bin/bash
grep -w class /etc/group &>/dev/null
test $? -ne 0 && groupadd class
或者
groupadd class &>/dev/null
for ((i=1;i<=5;i++))
do
useradd -G class u$i && echo 123|passwd --stdin u$i
done
1.3 课堂练习
- 批量新建5个用户stu1~stu5,要求这几个用户的家目录都在/rhome.提示:需要判断该目录是否存在
#!/bin/bash
#判断/rhome是否存在
[ -f /rhome ] && mv /rhome /rhome.bak
test ! -f /rhome -a ! -d /rhome && mkdir /rhome
或者
[ -f /rhome ] && mv /rhome /rhome.bak || [ ! -d /rhome ] && mkdir /rhome
#创建用户,循环5次
for ((i=1;i<=5;i++))
do
useradd -d /rhome/stu$i stu$i
echo 123|passwd --stdin stu$i
done
-
写一个脚本,局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里,这是一个局域网内机器检查通讯的一个思路。
以10.1.1.1~10.1.1.10为例
#!/bin/bash
#定义变量
ip=10.1.1
#循环去ping主机的IP
for ((i=1;i<=10;i++))
do
ping -c1 $ip.$i &>/dev/null
if [ $? -eq 0 ];then
echo "$ip.$i is ok" >> /tmp/ip_up.txt
else
echo "$ip.$i is down" >> /tmp/ip_down.txt
fi
或者
[ $? -eq 0 ] && echo "$ip.$i is ok" >> /tmp/ip_up.txt || echo "$ip.$i is down" >> /tmp/ip_down.txt
done
[root@server shell03]# time ./ping.sh
real 0m24.129s
user 0m0.006s
sys 0m0.005s
并行执行:
{程序}&表示将程序放到后台并行执行,如果需要等待程序执行完毕再进行下面内容,需要加wait
#!/bin/bash
#定义变量
ip=10.1.1
#循环去ping主机的IP
for ((i=1;i<=10;i++))
do
{
ping -c1 $ip.$i &>/dev/null
if [ $? -eq 0 ];then
echo "$ip.$i is ok" >> /tmp/ip_up.txt
else
echo "$ip.$i is down" >> /tmp/ip_down.txt
fi
}&
done
wait
echo "ip is ok...."
[root@server ~]# time ./ping.sh
ip is ok...
real 0m3.091s
user 0m0.001s
sys 0m0.008s
发送邮件参考博客:https://blog.csdn.net/whandgdh/article/details/127425496
3、输入一个年份,判断是否是润年(能被4整除但不能被100整除,或能被400整除的年份即为闰年。)
#!/bin/bash
read -p "Please input year:(2017)" year
if [ $[$year%4] -eq 0 -a $[$year%100] -ne 0 ];then
echo "$year is leap year"
elif [ $[$year%400] -eq 0 ];then
echo "$year is leap year"
else
echo "$year is not leap year"
fi
1.4 总结
- FOR循环语法结构
- FOR循环可以结合条件判断和流程控制语句
- do …done 循环体
- 循环体里可以是命令集合,再加上条件判断以及流程控制
- 控制循环语句
- continue 继续,跳过本次循环,继续下一次循环
- break 打断,跳出循环,执行循环体外的代码
- exit 退出,直接退出程序
2. while循环
特点:条件为真就进入循环;条件为假就退出循环
2.1 语法结构
while 表达式
do
command...
done
while [ 1 -eq 1 ] 或者 (( 1 > 2 ))
do
command
command
...
done
=========================================================
打印1-5数字
FOR循环打印:
for ((i=1;i<=5;i++))
do
echo $i
done
while循环打印:
i=1
while [ $i -le 5 ]
do
echo $i
let i++
done
2.2 举例说明
需求:用while循环计算1-50的偶数和
#!/bin/bash
#定义变量
sum=0
i=2
#循环打印1-50的偶数和并且计算后重新赋值给sum
while [ $i -le 50 ]
do
let sum=sum+i
let i+=2
done
#打印sum的值
echo "1-50的偶数和为:$sum"
2.3 应用案例
需求:
写一个30秒同步一次时间,向同步服务器10.1.1.250的脚本,如果同步失败,则进行邮件报警,每次失败都报警;同步成功,也进行邮件通知,但是成功100次才通知一次。
分析:
- 每个30s同步一次时间,该脚本是一个死循环
- while true;do 同步时间,然后休息30s(sleep 30)done
- 同步失败发送邮件
- 在do…done循环体之间加if…else…(判断同步失败还是成功)
- 同步成功100次发送邮件
- 统计成功次数——>count=0——>成功1次加+1——>let count++
#!/bin/bash
#定义变量
count=0
ntp_server=10.1.1.250
while true
do
rdate -s $ntp-server &>/dev/null
if [ $? -ne 0 ];then
echo "system date failed" |mail -s 'check system date' root@localhost
else
let count++
if [ $[$count%100] -eq 0 ];then
echo "system date successfull" |mail -s 'check system date' root@localhost && count=0
fi
fi
sleep 3
done
以上脚本还有更多的写法,课后自己完成
3. until循环
3.1 语法结构
特点:条件为假就进入循环;条件为真就退出循环
until expression [ 1 -eq 1 ] (( 1 >= 1 ))
do
command
command
...
done
i=1
while [ $i -le 5 ]
do
echo $i
let i++
done
i=1
until [ $i -gt 5 ]
do
echo $i
let i++
done
3.2 举例说明
使用until语句批量创建10个用户,要求stu1—stu5用户的UID分别为1001—1005;stu6~stu10用户的家目录分别在/rhome/stu6—/rhome/stu10
#!/bin/bash
i=1
until [ $i -gt 10 ]
do
if [ $i -le 5 ];then
useradd -u $[1000+$i] stu$i && echo 123|passwd --stdin stu$i
else
[ ! -d /rhome ] && mkdir /rhome
useradd -d /rhome/stu$i stu$i && echo 123|passwd --stdin stu$i
fi
let i++
done
第三章节 SHELL编程之嵌套循环+随机数及综合案例
一、随机数
bash默认有一个$RANDOM的变量 默认是0~32767。使用set |grep RANDOM 查看上一次产生的随机数
echo $RANDOM
产生0~1之间的随机数
echo $[$RANDOM%2]
产生0~2之间的随机数
echo $[$RANDOM%3]
产生0~3之间的随机数
echo $[$RANDOM%4]
。。。。
产生0~9内的随机数
echo $[$RANDOM%10]
产生0~100内的随机数
echo $[$RANDOM%101]
产生50-100之内的随机数
echo $[$RANDOM%51+50]
产生三位数的随机数
echo $[$RANDOM%900+100]
实战案例1
- 写一个脚本,产生一个phonenum.txt文件,随机产生以139开头的手机号1000个,每个一行。
分析:
1. 产生1000个电话号码,脚本需要循环1000次
2. 139+8位,后8位随机产生,可以让每一位数字都随机产生,$[RANDOM%10] 0-9
3. 将随机产生的数字分别保存到变量里,然后加上139保存到文件里
#!/bin/bash
# random phonenum
# 循环1000次产生电话号码并保存到文件
for i in {1..1000}
do
n1=$[RANDOM%10]
n2=$[RANDOM%10]
n3=$[RANDOM%10]
n4=$[RANDOM%10]
n5=$[RANDOM%10]
n6=$[RANDOM%10]
n7=$[RANDOM%10]
n8=$[RANDOM%10]
echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> phonenum.txt
done
#!/bin/bash
# random phonenum
# 循环1000次产生电话号码
for ((i=1;i<=1000;i++))
do
n1=$[$RANDOM%10]
n2=$[$RANDOM%10]
n3=$[$RANDOM%10]
n4=$[$RANDOM%10]
n5=$[$RANDOM%10]
n6=$[$RANDOM%10]
n7=$[$RANDOM%10]
n8=$[$RANDOM%10]
echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> phonenum.txt
done
#!/bin/bash
i=1
while [ $i -le 1000 ]
do
n1=$[$RANDOM%10]
n2=$[$RANDOM%10]
n3=$[$RANDOM%10]
n4=$[$RANDOM%10]
n5=$[$RANDOM%10]
n6=$[$RANDOM%10]
n7=$[$RANDOM%10]
n8=$[$RANDOM%10]
echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> phonenum.txt
let i++
done
continue:继续,跳过本次循环,执行下一次循环
break:打断,执行循环体外的代码do..done外
exit:退出程序
#!/bin/bash
for i in {1..1000}
do
n1=$[$RANDOM%10]
n2=$[$RANDOM%10]
n3=$[$RANDOM%10]
n4=$[$RANDOM%10]
n5=$[$RANDOM%10]
n6=$[$RANDOM%10]
n7=$[$RANDOM%10]
n8=$[$RANDOM%10]
echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> phonenum.txt
done
#!/bin/bash
#create phone num file
for ((i=1;i<=1000;i++))
do
n1=$[$RANDOM%10]
n2=$[$RANDOM%10]
n3=$[$RANDOM%10]
n4=$[$RANDOM%10]
n5=$[$RANDOM%10]
n6=$[$RANDOM%10]
n7=$[$RANDOM%10]
n8=$[$RANDOM%10]
echo "139$n1$n2$n3$n4$n5$n6$n7$n8" |tee -a phonenum.txt
done
#!/bin/bash
count=0
while true
do
n1=$[$RANDOM%10]
n2=$[$RANDOM%10]
n3=$[$RANDOM%10]
n4=$[$RANDOM%10]
n5=$[$RANDOM%10]
n6=$[$RANDOM%10]
n7=$[$RANDOM%10]
n8=$[$RANDOM%10]
echo "139$n1$n2$n3$n4$n5$n6$n7$n8" |tee -a phonenum.txt && let count++
if [ $count -eq 1000 ];then
break
fi
done
- 在上面的1000个手机号里抽奖5个幸运观众,显示出这5个幸运观众。但只显示头3个数和尾号的4个数,中间的都用*代替
思路:
- 确定幸运观众所在的行 随机生成 RANDOM $[RANDOM%1000+1]
- 将电话号码提取出来 head 和 tail
- 显示前3个和后4个数到屏幕 最后将电话号码输出到屏幕 echo ${电话号码部分}
#!/bin/bash
#定义变量
phone=/shell04/phonenum.txt
for ((i=1;i<=5;i++))
do
#定位幸运观众所在行号
line=`wc -l $phone |cut -d' ' -f1`
luck_line=$[RANDOM%$line+1]
#取出幸运观众所在行的电话号码
luck_num=`head -$luck_line $phone|tail -1`
#显示到屏幕
echo "139****${luck_num:7:4}"
echo $luck_num >> luck.txt
#删除已经被抽取的幸运观众号码
sed -i "/$luck_num/d" $phone
done
#!/bin/bash
file=/shell04/phonenum.txt
for i in {1..5}
do
file_num=`wc -l $file |cut -d' ' -f1`
line=`echo $[$RANDOM%$file_num+1]`
luck=`head -n $line $file|tail -1`
echo "139****${luck:7:4}" && echo $luck >> /shell04/luck_num.txt
done
#!/bin/bash
for ((i=1;i<=5;i++))
do
file=phonenum.txt
line=`cat phonenum.txt |wc -l` 1000
luckline=$[$RANDOM%$line+1]
phone=`cat $file|head -$luckline|tail -1`
echo "幸运观众为:139****${phone:7:4}"
done
或者
#!/bin/bash
# choujiang
phone=phonenum.txt
for ((i=1;i<=5;i++))
do
num=`wc -l phonenum.txt |cut -d' ' -f1`
line=`echo $[$RANDOM%$num+1]`
luck=`head -$line $phone |tail -1`
sed -i "/$luck/d" $phone
echo "幸运观众是:139****${luck:7:4}"
done
- 批量创建5个用户,每个用户的密码为一个随机数
思路:
- 循环5次创建用户
- 产生一个密码文件来保存用户的随机密码
- 从密码文件中取出随机密码赋值给用户
#!/bin/bash
#crate user and set passwd
#产生一个保存用户名和密码的文件
echo user0{1..3}:itcast$[$RANDOM%9000+1000]#@~|tr ' ' '\n'>> user_pass.file
#循环创建5个用户
for ((i=1;i<=5;i++))
do
user=`head -$i user_pass.file|tail -1|cut -d: -f1`
pass=`head -$i user_pass.file|tail -1|cut -d: -f2`
useradd $user
echo $pass|passwd --stdin $user
done
或者
for i in `cat user_pass.file`
do
user=`echo $i|cut -d: -f1`
pass=`echo $i|cut -d: -f2`
useradd $user
echo $pass|passwd --stdin $user
done
#!/bin/bash
#crate user and set passwd
#产生一个保存用户名和密码的文件
echo user0{1..3}:itcast$[$RANDOM%9000+1000]#@~|tr ' ' '\n'|tr ':' ' ' >> user_pass.file
#循环创建5个用户
while read user pass
do
useradd $user
echo $pass|passwd --stdin $user
done < user_pass.file
pwgen工具产生随机密码:
[root@server shell04]# pwgen -cn1 12
Meep5ob1aesa
[root@server shell04]# echo user0{1..3}:$(pwgen -cn1 12)
user01:Bahqu9haipho user02:Feiphoh7moo4 user03:eilahj5eth2R
[root@server shell04]# echo user0{1..3}:$(pwgen -cn1 12)|tr ' ' '\n'
user01:eiwaShuZo5hi
user02:eiDeih7aim9k
user03:aeBahwien8co
二、嵌套循环
一个循环体内又包含另一个完整的循环结构,称为循环的嵌套。在外部循环的每次执行过程中都会触发内部循环,直至内部完成一次循环,才接着执行下一次的外部循环。for循环、while循环和until循环可以相互嵌套。
demo1:打印如下图案
1
12
123
1234
12345
X轴:
for ((i=1;i<=5;i++));do echo -n $i;done
Y轴:
负责打印换行
#!/bin/bash
for ((y=1;y<=5;y++))
do
for ((x=1;x<=$y;x++))
do
echo -n $x
done
echo
done
#!/bin/bash
for ((y=1;y<=5;y++))
do
x=1
while [ $x -le $y ]
do
echo -n $x
let x++
done
echo
done
demo2:打印如下图案
5
54
543
5432
54321
Y轴:打印换行
X轴:打印数字 5-1
#!/bin/bash
y=5
while (( $y >= 1 ))
do
for ((x=5;x>=$y;x--))
do
echo -n $x
done
echo
let y--
done
#!/bin/bash
for (( y=5;y>=1;y--))
do
for (( x=5;x>=$y;x--))
do
echo -n $x
done
echo
done
#!/bin/bash
y=5
while [ $y -ge 1 ]
do
for ((x=5;x>=$y;x--))
do
echo -n $x
done
echo
let y--
done
#!/bin/bash
y=1
until (( $y >5 ))
do
x=1
while (( $x <= $y ))
do
echo -n $[6-$x]
let x++
done
echo
let y++
done
课后打印:
54321
5432
543
54
5
课堂练习:打印九九乘法表(三种方法)
1
12
123
1234
12345
for ((y=1;y<=5;y++))
do
for ((x=1;x<=$y;x++))
do
echo -n $x
done
echo
done
1*1=1
1*2=2 2*2=4
1*3=3 2*3=6 3*3=9
1*4=4 2*4=8 3*4=12 4*4=16
1*5=5 2*5=10 3*5=15 4*5=20 5*5=25
1*6=6 2*6=12 3*6=18 4*6=24 5*6=30 6*6=36
1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49
1*8=8 2*8=16 3*8=24 4*8=32 5*8=40 6*8=48 7*8=56 8*8=64
1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81
Y轴:循环9次,打印9行空行
X轴:循环次数和Y轴相关;打印的是X和Y轴乘积 $[] $(())
#!/bin/bash
for ((y=1;y<=9;y++))
do
for ((x=1;x<=$y;x++))
do
echo -ne "$x*$y=$[$x*$y]\t"
done
echo
echo
done
#!/bin/bash
y=1
while [ $y -le 9 ]
do
x=1
while [ $x -le $y ]
do
echo -ne "$x*$y=$[$x*$y]\t"
let x++
done
echo
echo
let y++
done
或者
#!/bin/bash
for i in `seq 9`
do
for j in `seq $i`
do
echo -ne "$j*$i=$[$i*$j]\t"
done
echo
echo
done
或者
#!/bin/bash
y=1
until [ $y -gt 9 ]
do
x=1
until [ $x -gt $y ]
do
echo -ne "$x*$y=$[ $x*$y ]\t"
let x++
done
echo
echo
let y++
done
三、阶段性总结
1. 变量定义
普通变量定义:
变量名=值 shell变量默认可以赋予任何类型
$变量名 ${变量名} ${变量名:从第几个字符开始:截取几个字符}
unset 变量名
交互式:
read 变量名
-p
-t
-s
-n
数组定义:
array=(var1 var2 var3 ...)
array[0]=var1
array[1]=var2
array[2]=var3
普通数组:数组的索引是整数
定义关联数组
关联数组:索引是字符串
获取数组里的元素:
${array[*]}
${array[2]}
${array[@]:1:2}
${!array[@]} 获取数组的索引号(下标)
${#array[@]} 获取数组索引号的个数
定义有类型的变量:
declare
-i
-x
-a
-A
2. 循环语句
for:
列表循环、非列表循环、类C风格 循环次数已知
while:
条件为真,进入循环,条件为假,退出循环 循环次数跟条件有关
until:
条件为假,进入循环,条件为真,退出循环 循环次数跟条件有关
3. 影响shell程序的内置命令
exit 退出整个程序
break 结束当前循环,或跳出本层循环
continue 忽略本次循环剩余的代码,直接进行下一次循环
shift 使位置参数向左移动,默认移动1位,可以使用shift 2
以下脚本都能够实现用户自定义输入数字,然后脚本计算和:
[root@MissHou shell04]# cat shift.sh
#!/bin/bash
sum=0
while [ $# -ne 0 ]
do
let sum=$sum+$1
shift
done
echo sum=$sum
[root@MissHou shell04]# cat for3.sh
#!/bin/bash
sum=0
for i
do
let sum=$sum+$i
done
echo sum=$sum
:
true
false
4. 补充扩展expect
expect 自动应答 tcl语言
需求1:A远程登录到server上什么都不做
#!/usr/bin/expect
# 开启一个程序
spawn ssh root@10.1.1.1
# 捕获相关内容
expect {
"(yes/no)?" { send "yes\r";exp_continue }
"password:" { send "123456\r" }
}
interact //交互
脚本执行方式:
# ./expect1.sh
# /shell04/expect1.sh
# expect -f expect1.sh
1)定义变量
#!/usr/bin/expect
set ip 10.1.1.2
set pass 123456
set timeout 5
spawn ssh root@$ip
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
interact
2)使用位置参数
#!/usr/bin/expect
set ip [ lindex $argv 0 ]
set pass [ lindex $argv 1 ]
set timeout 5
spawn ssh root@$ip
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
interact
需求2:A远程登录到server上操作
#!/usr/bin/expect
set ip 10.1.1.1
set pass 123456
set timeout 5
spawn ssh root@$ip
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
expect "#"
send "rm -rf /tmp/*\r"
send "touch /tmp/file{1..3}\r"
send "date\r"
send "exit\r"
expect eof
需求3:shell脚本和expect结合使用,在多台服务器上创建1个用户
[root@server shell04]# cat ip.txt
10.1.1.1 123456
10.1.1.2 123456
1. 循环
2. 登录远程主机——>ssh——>从ip.txt文件里获取IP和密码分别赋值给两个变量
3. 使用expect程序来解决交互问题
#!/bin/bash
# 循环在指定的服务器上创建用户和文件
while read ip pass
do
/usr/bin/expect <<-END &>/dev/null
spawn ssh root@$ip
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
expect "#" { send "useradd yy1;rm -rf /tmp/*;exit\r" }
expect eof
END
done < ip.txt
#!/bin/bash
cat ip.txt|while read ip pass
do
{
/usr/bin/expect <<-HOU
spawn ssh root@$ip
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
expect "#"
send "hostname\r"
send "exit\r"
expect eof
HOU
}&
done
wait
echo "user is ok...."
或者
#!/bin/bash
while read ip pass
do
{
/usr/bin/expect <<-HOU
spawn ssh root@$ip
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
expect "#"
send "hostname\r"
send "exit\r"
expect eof
HOU
}&
done<ip.txt
wait
echo "user is ok...."
四、综合案例
实战案例2
写一个脚本,将跳板机上yunwei用户的公钥推送到局域网内可以ping通的所有机器上
说明:主机和密码文件已经提供
10.1.1.1:123456
10.1.1.2:123456
案例分析
- 关闭防火墙和selinux
- 判断ssh服务是否开启(默认ok)
- 循环判断给定密码文件里的哪些IP是可以ping通 ip pass
- 判断IP是否可以ping通——>$?—>流程控制语句
- 密码文件里获取主机的IP和密码保存变量 ip pass
- 判断公钥是否存在—>不存在创建它
- ssh-copy-id 将跳板机上的yunwei用户的公钥推送到远程主机—>expect解决交互
- 将ping通的主机IP单独保存到一个文件
- 测试验证
代码拆分
1.判断yunwei用户的公钥是否存在
[ ! -f /hoem/yunwei/.ssh/id_rsa ] && ssh-keygen -P '' -f ./id_rsa
2.获取IP并且判断是否可以ping通
1)主机密码文件ip.txt
10.1.1.1:123456
10.1.1.2:123456
2) 循环判断主机是否ping通
tr ':' ' ' < ip.txt|while read ip pass
do
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
推送公钥
fi
done
3.非交互式推送公钥
/usr/bin/expect <<-END &>/dev/null
spawn ssh-copy-id root@$ip
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
expect eof
END
最终实现
环境准备:
jumper-server 有yunwei用户
yunwei用户sudo授权:
visudo
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
yunwei ALL=(root) NOPASSWD:ALL,!/sbin/shutdown,!/sbin/init,!/bin/rm -rf /
解释说明:
1)第一个字段yunwei指定的是用户:可以是用户名,也可以是别名。每个用户设置一行,多个用户设置多行,也可以将多个用户设置成一个别名后再进行设置。
2)第二个字段ALL指定的是用户所在的主机:可以是ip,也可以是主机名,表示该sudo设置只在该主机上生效,ALL表示在所有主机上都生效!限制的一般都是本机,也就是限制使用这个文件的主机;一般都指定为"ALL"表示所有的主机,不管文件拷到那里都可以用。比如:10.1.1.1=...则表示只在当前主机生效。
3)第三个字段(root)括号里指定的也是用户:指定以什么用户身份执行sudo,即使用sudo后可以享有所有root账号下的权限。如果要排除个别用户,可以在括号内设置,比如ALL=(ALL,!oracle,!pos)。
4)第四个字段ALL指定的是执行的命令:即使用sudo后可以执行所有的命令。除了关机和删除根内容以外;也可以设置别名。NOPASSWD: ALL表示使用sudo的不需要输入密码。
5)也可以授权给一个用户组
%admin ALL=(ALL) ALL 表示admin组里的所有成员可以在任何主机上以任何用户身份执行任何命令
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
脚本实现:
#!/bin/bash
#判断公钥是否存在
[ ! -f /home/yunwei/.ssh/id_rsa ] && ssh-keygen -P '' -f ~/.ssh/id_rsa
#循环判断主机是否ping通,如果ping通推送公钥
tr ':' ' ' < /shell04/ip.txt|while read ip pass
do
{
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo $ip >> ~/ip_up.txt
/usr/bin/expect <<-END &>/dev/null
spawn ssh-copy-id root@$ip
expect {
"yes/no" { send "yes\r";exp_continue }
"password:" { send "$pass\r" }
}
expect eof
END
fi
}&
done
wait
echo "公钥已经推送完毕,正在测试...."
#测试验证
remote_ip=`tail -1 ~/ip_up.txt`
ssh root@$remote_ip hostname &>/dev/null
test $? -eq 0 && echo "公钥成功推送完毕"
实战案例3
写一个脚本,统计web服务的不同连接状态个数
#!/bin/bash
#count_http_80_state
#统计每个状态的个数
declare -A array1
states=`ss -ant|grep 80|cut -d' ' -f1`
for i in $states
do
let array1[$i]++
done
#通过遍历数组里的索引和元素打印出来
for j in ${!array1[@]}
do
echo $j:${array1[$j]}
done
第四章 SHELL编程之CASE语句+函数+正则
课程目标
- 掌握case语句的基本语法结构
- 掌握函数的定义及调用
- 掌握常用的正则表达式元字符含义
一、case语句
case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相
匹配的命令。
case var in 定义变量;var代表是变量名
pattern 1) 模式1;用 | 分割多个模式,相当于or
command1 需要执行的语句
;; 两个分号代表命令结束
pattern 2)
command2
;;
pattern 3)
command3
;;
*) default,不满足以上模式,默认执行*)下面的语句
command4
;;
esac esac表示case语句结束
案例1
- 当给程序传入start、stop、reload三个不同参数时分别执行相应命令。
#!/bin/bash
case $1 in
start|S)
echo "service is running...."
;;
stop|T)
echo "service is stoped..."
;;
reload|R)
echo "service is restart..."
;;
*)
echo "请输入你要的动作"
;;
esac
- 脚本提示让用户输入需要管理的服务名,然后提示用户需要对服务做什么操作,如启动,关闭,重启等
#!/bin/bash
read -p "请输入需要管理的服务名称(vsftpd):" service
case $service in
vsftpd)
read -p "请输入要操作的动作:" action
case $action in
start|S)
service vsftpd start
;;
stop|P)
service vsftpd stop
;;
reload|restart|R)
service vsftpd reload
;;
esac
;;
httpd)
echo "apache is running..."
;;
*)
echo "请输入需要管理的服务名称(vsftpd):"
;;
esac
案例2
模拟一个多任务维护界面。当执行程序时先显示总菜单,然后进行选择后做相应维护监控操作。
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
分析:
1. 打印菜单
2. 等待用户输入需要的操作编号 read case
echo "h 显示命令帮助"
...
cat <<EOF
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
EOF
#!/bin/bash
#打印菜单
cat <<-EOF
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
EOF
#让用户输入需要的操作
while true
do
read -p "请输入需要操作的选项[f|d]:" var1
case $var1 in
h)
cat <<-EOF
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
EOF
;;
f)
fdisk -l
;;
d)
df -h
;;
m)
free -m
;;
u)
uptime
;;
q)
exit
;;
esac
done
#!/bin/bash
#打印菜单
menu(){
cat <<-END
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
END
}
menu
while true
do
read -p "请输入你的操作[h for help]:" var1
case $var1 in
h)
menu
;;
f)
read -p "请输入你要查看的设备名字[/dev/sdb]:" var2
case $var2 in
/dev/sda)
fdisk -l /dev/sda
;;
/dev/sdb)
fdisk -l /dev/sdb
;;
esac
;;
d)
lsblk
;;
m)
free -m
;;
u)
uptime
;;
q)
exit
;;
esac
done
课堂练习1
- 输入一个等级(A-E),查看每个等级的成绩;如:输入A,则显示“90分~100分”,依次类推
- 模拟2人第一次相亲的场景,使用read让用户输入它的名字,性别,年龄(年龄放在性别判断后);在case里面再嵌套case菜单,使之选项更丰富。
要求:
1)
- 对性别进行判断,如果不输入男或者女,则显示”你是泰国来的吗?“
- 如果是男的,对其年龄进行判断。
2)
- 如果男的年龄大于等于18岁则显示“某某先生,你结婚了吗?”;
- 如果对方回答结了或者yes,则显示“结了你来这凑什么热闹”;
- 如果对方回答没有或者no,再次询问“那你有房有车吗?”;
- 如果既不说结了也不说没结则显示:“你到底结没结婚啊?”
- 如果回答有房有车,则显示”咱去民政局领证吧“;
- 如果回答没有,则显示“不好意思,我去下洗手间。”;
- 如果既不说有又不说没有,则显示“别浪费时间,请正面回答”。
- 如果男的年龄小于18岁,则显示“某某某你个小毛孩也来这凑热闹啦”
3)如果是女的,并且年龄大于等于18岁,则显示”某某女士你好“;否则显示”某某小姐你好“
参考:
#!/bin/bash
read -p "输入你的姓名:" name
read -p "输入你的性别:" gender
case "$gender" in
男|man|male|boy )
read -p "输入你的年龄:" age
if [ $age -ge 18 ];then
read -p "$name先生,你结婚了吗?" anwser
case "$anwser" in
结了|有|yes )
echo "结了你还来干嘛?"
;;
没结|没有|没|no )
read -p "有房有车吗?" anwser2
case "$anwser2" in
有)
echo "咱就直接去民政局领证吧"
;;
没有 )
echo "不好意思,我去下洗手间"
;;
* )
echo "别浪费时间,请正面回答"
esac
;;
* )
echo "你到底结没结?"
esac
else
echo "$name小子"
fi
;;
女|woman|female|girl|lady )
read -p "输入你的年龄:" age
if [ $age -ge 18 ];then
echo "$name女士"
else
echo "$name小姐"
fi
;;
* )
echo "你是泰国来的吗?"
esac
该程序有个bug:如果输入年龄为负数或者0也是可以的,如何修复bug?增加一个条件:如果输入的年龄小于等于10则显示:”不跟你玩了。。。“
在最后加入如下语句即可:
if [ $age -ge 18 ];then
echo "$name女士"
elif
[ $age -le 10 ];then
echo "不跟你玩了"
exit 1
else
echo "$name小姐"
fi
;;
* )
echo "你是泰国来的吗?"
esac
二、函数
shell中允许将一组命令集合或语句形成一段可用代码,这些代码块称为shell函数。给这段代码起个名字称为函数名,后续可以直接调用该段代码的功能。
2.1 函数定义
函数名()
{
函数体(一堆命令的集合,来实现某个功能)
}
function 函数名()
{
函数体(一堆命令的集合,来实现某个功能)
}
function_name() {
command
command
}
function function_name() {
command
command
}
函数中return说明:
1.return可以结束一个函数,类似于前面讲的循环控制语句break(结束当前循环,执行循环体后面的代码)
2.return默认返回函数中最后一个命令的退出状态,也可以给定参数值,该参数值的范围是0-256之间。
3.如果没有return命令,函数将返回最后一个Shell的退出值。
2.2 函数的调用
- 当前命令行调用
[root@MissHou shell04]# cat fun1.sh
#!/bin/bash
hello(){
echo "hello lilei $1"
hostname
}
menu(){
cat <<-EOF
1. mysql
2. web
3. app
4. exit
EOF
}
[root@MissHou shell04]# source fun1.sh
[root@MissHou shell04]# . fun1.sh
[root@MissHou shell04]# hello 888
hello lilei 888
MissHou.itcast.cc
[root@MissHou shell04]# menu
1. mysql
2. web
3. app
4. exit
- 定义到用户的环境变量中
/etc/profile /etc/bashrc ~/.bash_profile ~/.bashrc
[root@MissHou shell04]# cat ~/.bashrc
# .bashrc
# User specific aliases and functions
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
hello(){
echo "hello lilei $1"
hostname
}
menu(){
cat <<-EOF
1. mysql
2. web
3. app
4. exit
EOF
}
注意:
当用户打开bash的时候会读取该文件
- 脚本中调用
#!/bin/bash
#打印菜单
source ./fun1.sh
menu(){
cat <<-END
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
END
}
menu //调用函数
2.3 应用案例
需求:写一个脚本让用户输入基本信息(姓名,性别,年龄),如不输入一直提示输入,最后根据用户的信息输出相对应的内容
思路:
- 循环直到输入字符串不为空 (该功能可以定义为一个函数,方便下面脚本调用)
- 根据用户输入信息做出匹配判断 case 语句
#!/bin/bash
#该函数实现用户如果不输入内容则一直循环直到用户输入为止,并且将用户输入的内容打印出来
input_fun()
{
input_var=""
output_var=$1
while [ -z $input_var ]
do
read -p "$output_var" input_var
done
echo $input_var
}
input_fun 请输入你的姓名:
或者
#!/bin/bash
fun(){
read -p "$1" name
if [ -z $name ];then
fun $1
else
echo $name
fi
}
#调用函数并且获取用户的姓名、性别、年龄分别赋值给name、sex、age变量
name=$(input_fun 请输入你的姓名:)
sex=$(input_fun 请输入你的性别:)
age=$(input_fun 请输入你的年龄:)
#根据用户输入的性别进行匹配判断
case $sex in
man)
if [ $age -gt 18 -a $age -le 35 ];then
echo "中年大叔你油腻了吗?加油"
elif [ $age -gt 35 ];then
echo "保温杯里泡枸杞"
else
echo "年轻有为。。。"
fi
;;
woman)
xxx
;;
*)
xxx
;;
esac
描述以下代码含义:
:()
{
:|:&
}
:
:(){:|:&}:
三、综合案例
任务/背景:
现有的跳板机虽然实现了统一入口来访问生产服务器,yunwei用户权限太大可以操作跳板机上的所有目录文件,存在数据被误删的安全隐患,所以希望你做一些安全策略来保证跳板机的正常使用。
具体要求:
- 只允许yunwei用户通过跳板机远程连接后台的应用服务器做一些维护操作
- 公司运维人员远程通过yunwei用户连接跳板机时,跳出以下菜单供选择:
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
- 当用户选择相应主机后,直接免密码登录成功
- 如果用户不输入一直提示用户输入,直到用户选择退出
分析:
- 打印菜单——>定义函数 echo cat
- 让用户选择需要操作的机器 case…esac
- 配置免密登录
- 每个菜单提供功能——>case…esac用户选择做的事情
- 循环让用户输入选择
- 每个功能写成函数——>不是必须
- 脚本放的位置——>yunwei用户的家目录
#!/bin/bash
# jumper-server
# 定义菜单打印功能的函数
menu()
{
cat <<-EOF
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
EOF
}
while true
do
# 调用函数来打印菜单
clear
menu
# 菜单选择,case...esac语句
read -p "请选择你要访问的主机:" host
case $host in
1)
ssh root@10.1.1.2
;;
2)
ssh root@10.1.1.3
;;
3)
ssh root@10.1.1.2
;;
h)
menu
;;
q)
exit
;;
esac
done
将脚本放到yunwei用户家目录里的.bashrc里执行:
bash ~/jumper-server.sh
exit
#!/bin/bash
#公钥推送成功
trap '' 1 2 3 19
#打印菜单用户选择
menu(){
cat <<-EOF
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
EOF
}
#调用函数来打印菜单
menu
while true
do
read -p "请输入你要选择的主机[h for help]:" host
#通过case语句来匹配用户所输入的主机
case $host in
1|DB1)
ssh root@10.1.1.1
;;
2|DB2)
ssh root@10.1.1.2
;;
3|web1)
ssh root@10.1.1.250
;;
h|help)
clear;menu
;;
q|quit)
exit
;;
esac
done
#!/bin/bash
#jumper-server
#菜单打印
trap '' 1 2 3
while true
do
cat <<-END
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
5. exit
END
#让用户选择相应的操作
read -p "请输入你要操作的主机:" host
case $host in
1)
ssh root@10.1.1.2
;;
2)
ssh root@10.1.1.3
;;
3)
ssh root@10.1.1.4
;;
5)
exit
;;
*)
clear
echo "输入错误,请重新输入..."
;;
esac
done
自己完善功能:
1. 用户选择主机后,需要事先推送公钥;如何判断公钥是否已推
2. 比如选择web1时,再次提示需要做的操作,比如:
clean log
重启服务
kill某个进程
补充:
1) SIGHUP 重新加载配置
2) SIGINT 键盘中断^C
3) SIGQUIT 键盘退出
9) SIGKILL 强制终止
15) SIGTERM 终止(正常结束),缺省信号
18) SIGCONT 继续
19) SIGSTOP 停止
20) SIGTSTP 暂停^Z
四、正则表达式
1. 什么是正则表达式
正则表达式(Regular Expression、regex或regexp,缩写为RE),也译为正规表示法、常规表示法,是一种字符模式,用于在查找过程中匹配指定的字符。
许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。
正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。
支持正则表达式的程序如:locate |find| vim| grep| sed |awk
2. 第一类正则
- 名词解释:
元字符:指那些在正则表达式中具有特殊意义的专用字符,如:点(.) 星(*) 问号(?)等
前导字符:即位于元字符前面的字符 abc* aooo.
- 正则中常用的元字符
示例文本:
[root@server ~]# cat 1.txt
ggle
gogle
google
gooogle
goooooogle
gooooooogle
taobao.com
taotaobaobao.com
jingdong.com
dingdingdongdong.com
10.1.1.1
Adfjd8789JHfdsdf/
a87fdjfkdLKJK
7kdjfd989KJK;
bSKJjkksdjf878.
cidufKJHJ6576,
hello world
helloworld yourself
(1). 任意单个字符,除了换行符
(2)* 前导字符出现0次或连续多次 ab*能匹配“a”,“ab”以及“abb”,但是不匹配“cb”
(3).* 任意长度的字符 ab.* ab123 abbb abab
(4)^ 行的开头
(5)$ 行的结尾
(6)^$ 空行
(7)[] 匹配指定字符组内的任一单个字符 [abc]
(8)[^] 匹配不在指定字符组内的任一字符 [^abc]
(9)^[] 匹配以指定字符组内的任一字符开头 ^[abc]
(10)^[^] 匹配不以指定字符组内的任一字符开头 ^[^abc]
(11)\< 取单词的头
(12)\> 取单词的尾
(13)\<\> 精确匹配符号 grep -w 'xxx'
(14)\{n\} 匹配前导字符连续出现n次 go\{2\} google gooogle
(15)\{n,\} 匹配前导字符至少出现n次
(16)\{n,m\} 匹配前导字符出现n次与m次之间
(17) \(strings\) 保存被匹配的字符
将192.168.0.254 换成 192.168.1.254
vim 1.txt
:%s#\(192\.168\)\.0\.\(254\)#\1\.100\.\2 //底行模式下匹配
将10.1.1.1替换成10.1.1.254
:%s#\(10.1.1\).1#\1.254#g
:%s/\(10.1.1\).1/\1.254/g
# sed -n 's#\(192\.168\)\.0\.254#\1\.1\.254#p'
找出含有192.168的行,同时保留192.168并标记为标签1,之后可以使用\1来引用它。最多可以定义9个标签,从左边开始编号,最左边的是第一个。
[root@server shell05]# sed -n 's#10.1.1.1#10.1.1.254#p' 1.txt
10.1.1.254
[root@server shell05]# sed -n 's#\(10.1.1\).1#\1.254#p' 1.txt
10.1.1.254
将helloworld yourself 换成hellolilei myself
vim 1.txt
:%s#\(hello\)world your\(self\)#\1lilei my\2#g
# sed -n 's/\(hello\)world your\(self\)/\1lilei my\2/p' 1.txt
hellolilei myself
[root@server shell05]# sed -n 's/helloworld yourself/hellolilei myself/p' 1.txt
hellolilei myself
[root@server shell05]# sed -n 's/\(hello\)world your\(self\)/\1lilei my\2/p' 1.txt
hellolilei myself
[0-9] [a-z] [A-Z] [a-zA-Z] [a-Z]
=================================================================================
Perl内置正则:
\d 匹配数字 [0-9]
\w 匹配字母数字下划线[a-zA-Z0-9_]
\s 匹配空格、制表符、换页符[\t\r\n]
#grep -P '\d' test.txt
#grep -P '\w' test.txt
#grep -P '\s' test.txt
- 扩展类的正则表达式 grep -E 或则 egrep
扩展正则表达式元字符
+ 匹配一个或多个前导字符 bo+ boo bo
? 匹配零个或一个前导字符 bo? b bo
a|b 匹配a或b
() 组字符 hello myself yourself (my|your)self
{n} 前导字符重复n次 \{n\}
{n,} 前导字符重复至少n次 \{n,\}
{n,m} 前导字符重复n到m次 \{n,m\}
# grep "root|ftp|adm" /etc/passwd
# egrep "root|ftp|adm" /etc/passwd
# grep -E "root|ftp|adm" /etc/passwd
# grep -E 'o+gle' test.txt
# grep -E 'o?gle' test.txt
# egrep 'go{2,}' 1.txt
# egrep '(my|your)self' 1.txt
[root@jumper shell06]# grep '[0-9]\{2\}\.[0-9]\{1\}\.[0-9]\{1\}\.[0-9]\{1\}' 1.txt
10.1.1.1
[root@jumper shell06]# grep '[0-9]{2}\.[0-9]{1}\.[0-9]{1}\.[0-9]{1}' 1.txt
[root@jumper shell06]# grep -E '[0-9]{2}\.[0-9]{1}\.[0-9]{1}\.[0-9]{1}' 1.txt
10.1.1.1
[root@jumper shell06]# grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' 1.txt
10.1.1.1
[root@jumper shell06]# grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt
10.1.1.1
3. 第二类正则
表达式 | 功能 | 示例 |
---|---|---|
[:alnum:] | 字母与数字字符 | [[:alnum:]]+ |
[:alpha:] | 字母字符(包括大小写字母) | [[:alpha:]]{4} |
[:blank:] | 空格与制表符 | [[:blank:]]* |
[:digit:] | 数字 | [[:digit:]]? |
[:lower:] | 小写字母 | [[:lower:]]{4,} |
[:upper:] | 大写字母 | [[:upper:]]+ |
[:punct:] | 标点符号 | [[:punct:]] |
[:space:] | 包括换行符,回车等在内的所有空白 | [[:space:]]+ |
[root@server shell05]# grep -E '^[[:digit:]]+' 1.txt
[root@server shell05]# grep -E '^[^[:digit:]]+' 1.txt
[root@server shell05]# grep -E '[[:lower:]]{4,}' 1.txt
4. 课堂作业
在自己虚拟机里创建如下内容的文件:
# cat test.txt
Aieur45869Root0000
9h847RkjfkIIIhello
rootHllow88000dfjj
8ikuioerhfhupliooking
hello world
192.168.0.254
welcome to uplooking.
abcderfkdjfkdtest
rlllA899kdfkdfj
iiiA848890ldkfjdkfj
abc
12345678908374
123456@qq.com
123456@163.com
abcdefg@itcast.com23ed
要求如下:
1、查找不以大写字母开头的行(三种写法)。
2、查找有数字的行(两种写法)
3、查找一个数字和一个字母连起来的
4、查找不以r开头的行
5、查找以数字开头的
6、查找以大写字母开头的
7、查找以小写字母开头的
8、查找以点结束的
9、去掉空行
10、查找完全匹配abc的行
11、查找A后有三个数字的行
12、统计root在/etc/passwd里出现了几次
13、用正则表达式找出自己的IP地址、广播地址、子网掩码
ifconfig eth0|grep Bcast|grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}'
ifconfig eth0|grep Bcast| grep -E -o '([0-9]{1,3}.){3}[0-9]{1,3}'
ifconfig eth0|grep Bcast| grep -P -o '\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}'
ifconfig eth0|grep Bcast| grep -P -o '(\d{1,3}.){3}\d{1,3}'
ifconfig eth0|grep Bcast| grep -P -o '(\d+.){3}\d+'
# egrep --color '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' /etc/sysconfig/network-scripts/ifcfg-eth0
IPADDR=10.1.1.1
NETMASK=255.255.255.0
GATEWAY=10.1.1.254
# egrep --color '[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}' /etc/sysconfig/network-scripts/ifcfg-eth0
IPADDR=10.1.1.1
NETMASK=255.255.255.0
GATEWAY=10.1.1.254
14、找出文件中的ip地址并且打印替换成172.16.2.254
grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt |sed -n 's/192.168.0.\(254\)/172.16.2.\1/p'
15、找出文件中的ip地址
grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt
16、找出全部是数字的行
grep -E '^[0-9]+$' test
17、找出邮箱地址
grep -E '^[0-9]+@[a-z0-9]+\.[a-z]+$'
grep --help:
匹配模式选择:
Regexp selection and interpretation:
-E, --extended-regexp 扩展正则
-G, --basic-regexp 基本正则
-P, --perl-regexp 调用perl的正则
-e, --regexp=PATTERN use PATTERN for matching
-f, --file=FILE obtain PATTERN from FILE
-i, --ignore-case 忽略大小写
-w, --word-regexp 匹配整个单词
五、正则总结
元字符:在正则中,具有特殊意义的专用字符. *
前导字符:元字符前面的字符叫前导字符
元字符 | 字符说明 | 示例 |
---|---|---|
* | 前导字符出现0次或者连续多次 | ab* abbbb |
. | 除了换行符以外,任意单个字符 | ab. ab8 abu |
.* | 任意长度的字符 | ab.* adfdfdf |
[] | 括号里的任意单个字符或一组单个字符 | [abc][0-9][a-z] |
[^] | 不匹配括号里的任意单个字符或一组单个字符 | [^abc] |
1 | 匹配以括号里的任意单个字符开头 | 2 |
^[^] | 不匹配以括号里的任意单个字符开头 | |
^ | 行的开头 | ^root |
$ | 行的结尾 | bash$ |
^$ | 空行 | |
\{n\}和{n} | 前导字符连续出现n次 | [0-9]\{3\} |
\{n,\}和{n,} | 前导字符至少出现n次 | [a-z]{4,} |
\{n,m\}和{n,m} | 前导字符连续出现n-m次 | go{2,4} |
\<\> | 精确匹配单词 | \<hello\> |
\(\) | 保留匹配到的字符 | \(hello\) |
+ | 前导字符出现1次或者多次 | [0-9]+ |
? | 前导字符出现0次或者1次 | go? |
| | 或 | ^root|^ftp |
() | 组字符 | (hello|world)123 |
\d | perl内置正则 | grep -P \d+ |
\w | 匹配字母数字下划线 | grep -P ‘\w’ |
六、课后作业
写一个自动搭建apache服务的脚本,要求如下:
1、用户输入web服务器的IP、域名以及数据根目录
2、如果用户不输入则一直提示输入,直到输入为止
3、当访问www.test.cc时可以访问到数据根目录里的首页文件“this is test page”
参考:
#!/bin/bash
conf=/etc/httpd/conf/httpd.conf
input_fun()
{
input_var=""
output_var=$1
while [ -z $input_var ]
do
read -p "$output_var" input_var
done
echo $input_var
}
ipaddr=$(input_fun "Input Host ip[192.168.0.1]:")
web_host_name=$(input_fun "Input VirtualHostName [www.test.cc]:")
root_dir=$(input_fun "Input host Documentroot dir:[/var/www/html]:")
[ ! -d $root_dir ] && mkdir -p $root_dir
chown apache.apache $root_dir && chmod 755 $root_dir
echo this is $web_host_name > $root_dir/index.html
echo "$ipaddr $web_host_name" >> /etc/hosts
[ -f $conf ] && cat >> $conf <<end
NameVirtualHost $ipaddr:80
<VirtualHost $ipaddr:80>
ServerAdmin webmaster@$web_host_name
DocumentRoot $root_dir
ServerName $web_host_name
ErrorLog logs/$web_host_name-error_log
CustomLog logs/$web_host_name-access_loh common
</VirtualHost>
end
第五章 SHELL编程之文本处理工具SED
课程目标
- 掌握sed的基本语法结构
- 熟悉sed常用的命令,如打印p,删除d,插入i等
一、sed介绍
1. sed的工作流程
- 首先sed把当前正在处理的行保存在一个临时缓存区中(也称为模式空间),然后处理临时缓冲区中的行,完成后把该行发送到屏幕上。
- sed把每一行都存在临时缓冲区中,对这个副本进行编辑,所以不会修改原文件。
- Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。
2. sed使用方法
sed常见的语法格式有两种,一种叫命令行模式,另一种叫脚本模式。
2.1 命令行格式
- 格式
sed [option] 'sed的命令|地址定位' filename
说明:引用shell script中的变量应使用双引号,而非通常使用的单引号
option:
-e 进行多项编辑,即对输入行应用多条sed命令时使用
-n 取消默认的输出
-f 指定sed脚本的文件名
-r 使用扩展正则表达式
-i inplace,原地编辑(修改源文件)
- 常用命令和选项
参考文件:
[root@server shell06]# cat 2.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
298374837483
172.16.0.254
10.1.1.1
p 打印行
d 删除行
[root@server ~]# sed -n 'p' a.txt
[root@server ~]# sed -n '1p' a.txt
[root@server ~]# sed -n '2p' a.txt
[root@server ~]# sed -n '1,5p' a.txt
[root@server ~]# sed -n '5,10p' a.txt
[root@server ~]# sed -n '$p' a.txt
[root@server ~]# sed '5p' a.txt
[root@server ~]# sed -n '5p' a.txt
[root@server ~]# sed '1d' a.txt
[root@server ~]# sed -n '1d' a.txt
[root@server ~]# sed '1d' a.txt
[root@server ~]# sed '1,5d' a.txt
[root@server ~]# sed '$d' a.txt
i\ 在当前行之前插入文本。多行时除最后一行外,每行末尾需用"\"续行 vim——>O
a\ 在当前行后添加一行或多行。多行时除最后一行外,每行末尾需用“\”续行 vim——> o
c\ 用此符号后的新文本替换当前行中的文本。多行时除最后一行外,每行末尾需用"\"续行 整行替换
[root@server ~]# sed '$a99999' a.txt
[root@server ~]# sed 'a99999' a.txt
[root@server ~]# cat -n a.txt
[root@server ~]# sed '5chello world' a.txt
[root@server ~]# sed 'chello world' a.txt
[root@server ~]# cat -n a.txt
[root@server ~]# sed '1,5chello world' a.txt
[root@server ~]# sed 'i\
aaaaa\
bbbbb\
88888' 1.txt
# sed '$a\
yyyyy\
8888' 1.txt
[root@server ~]# sed '/^user01/c888888' 1.txt
[root@server ~]# sed '18chello world' 1.txt
命令2:
r 从文件中读取输入行
w 将所选的行写入文件
[root@server ~]# sed '3r /etc/hosts' 2.txt
[root@server ~]# sed '$r /etc/hosts' 2.txt
[root@server ~]# sed '/root/w a.txt' 2.txt
[root@server ~]# sed '/[0-9]{4}/w a.txt' 2.txt
[root@server ~]# sed -r '/([0-9]{1,3}\.){3}[0-9]{1,3}/w b.txt' 2.txt
! 对所选行以外的所有行应用命令,放到行数之后
[root@server ~]# sed -n '1!p' 1.txt
[root@server ~]# sed -n '4p' 1.txt
[root@server ~]# sed -n '4!p' 1.txt
[root@server ~]# cat -n 1.txt
[root@server ~]# sed -n '1,17p' 1.txt
[root@server ~]# sed -n '1,17!p' 1.txt
s 用一个字符串替换另一个
g 在行内进行全局替换
[root@server ~]# sed -n 's/root/ROOT/p' 1.txt
[root@server ~]# sed -n 's/root/ROOT/gp' 1.txt
[root@server ~]# sed -n 's/^#//gp' 1.txt
[root@server ~]# sed -n 's@/sbin/nologin@itcast@gp' a.txt
[root@server ~]# sed -n 's/\/sbin\/nologin/itcast/gp' a.txt
[root@server ~]# sed -n '10s#/sbin/nologin#itcast#p' a.txt
uucp:x:10:14:uucp:/var/spool/uucp:itcast
[root@server ~]# sed -n 's@/sbin/nologin@itcastheima@p' 2.txt
注意:搜索替换中的分隔符可以自己指定
[root@server ~]# sed -n '1,5s/^/#/p' a.txt 注释掉文件的1-5行内容
#root:x:0:0:root:/root:/bin/bash
#bin:x:1:1:bin:/bin:/sbin/nologin
#daemon:x:2:2:daemon:/sbin:/sbin/nologin
#adm:x:3:4:adm:/var/adm:/sbin/nologin
#lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
& 保存查找串以便在替换串中引用 \(\)
[root@server ~]# sed -n '/root/p' a.txt
root:x:0:0:root:/root:/bin/bash
[root@server ~]# sed -n 's/root/#&/p' a.txt
#root:x:0:0:root:/root:/bin/bash
# sed -n 's/^root/#&/p' passwd 注释掉以root开头的行
# sed -n -r 's/^root|^stu/#&/p' /etc/passwd 注释掉以root开头或者以stu开头的行
# sed -n '1,5s/^[a-z].*/#&/p' passwd 注释掉1~5行中以任意小写字母开头的行
# sed -n '1,5s/^/#/p' /etc/passwd 注释1~5行
或者
sed -n '1,5s/^/#/p' passwd 以空开头的加上#
sed -n '1,5s/^#//p' passwd 以#开头的替换成空
[root@server ~]# sed -n '/^root/p' 1.txt
[root@server ~]# sed -n 's/^root/#&/p' 1.txt
[root@server ~]# sed -n 's/\(^root\)/#\1/p' 1.txt
[root@server ~]# sed -nr '/^root|^stu/p' 1.txt
[root@server ~]# sed -nr 's/^root|^stu/#&/p' 1.txt
= 打印行号
# sed -n '/bash$/=' passwd 打印以bash结尾的行的行号
# sed -ne '/root/=' -ne '/root/p' passwd
# sed -n '/nologin$/=;/nologin$/p' 1.txt
# sed -ne '/nologin$/=' -ne '/nologin$/p' 1.txt
综合运用:
[root@server ~]# sed -n '1,5s/^/#&/p' 1.txt
#root:x:0:0:root:/root:/bin/bash
#bin:x:1:1:bin:/bin:/sbin/nologin
#daemon:x:2:2:daemon:/sbin:/sbin/nologin
#adm:x:3:4:adm:/var/adm:/sbin/nologin
#lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
[root@server ~]# sed -n '1,5s/\(^\)/#\1/p' 1.txt
#root:x:0:0:root:/root:/bin/bash
#bin:x:1:1:bin:/bin:/sbin/nologin
#daemon:x:2:2:daemon:/sbin:/sbin/nologin
#adm:x:3:4:adm:/var/adm:/sbin/nologin
#lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
选项:-e -r -i
-e 多项编辑
-r 扩展正则
-i 修改原文件
[root@server ~]# sed -ne '/root/p' 1.txt -ne '/root/='
root:x:0:0:root:/root:/bin/bash
1
[root@server ~]# sed -ne '/root/=' -ne '/root/p' 1.txt
1
root:x:0:0:root:/root:/bin/bash
在1.txt文件中的第5行的前面插入“hello world”;在1.txt文件的第8行下面插入“哈哈哈哈”
[root@server ~]# sed -e '5ihello world' -e '8a哈哈哈哈哈' 1.txt -e '5=;8='
过滤vsftpd.conf文件中以#开头和空行:
[root@server ~]# grep -Ev '^#|^$' /etc/vsftpd/vsftpd.conf
[root@server ~]# sed -e '/^#/d' -e '/^$/d' /etc/vsftpd/vsftpd.conf
[root@server ~]# sed '/^#/d;/^$/d' /etc/vsftpd/vsftpd.conf
[root@server ~]# sed -r '/^#|^$/d' /etc/vsftpd/vsftpd.conf
过滤smb.conf文件中生效的行:
[root@server shell06]# sed -e '/^#/d' -e '/^;/d' -e '/^$/d' -e '/^\t$/d' -e '/^\t#/d' smb.conf
[root@server shell06]# sed -r '/^(#|$|;|\t#|\t$)/d' smb.conf
[root@server shell06]# sed -e '/^#/d' -e '/^;/d' -e '/^$/d' -e '/^\t$/d' -e '/^\t#/' smb.conf
[root@server ~]# grep '^[^a-z]' 1.txt
[root@server ~]# sed -n '/^[^a-z]/p' 1.txt
过滤出文件中的IP地址:
[root@server ~]# grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt
192.168.0.254
[root@server ~]# sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' 1.txt
192.168.0.254
[root@server ~]# grep -o -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 2.txt
10.1.1.1
10.1.1.255
255.255.255.0
[root@server ~]# sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' 2.txt
10.1.1.1
10.1.1.255
255.255.255.0
过滤出ifcfg-eth0文件中的IP、子网掩码、广播地址
[root@server shell06]# grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' ifcfg-eth0
10.1.1.1
255.255.255.0
10.1.1.254
[root@server shell06]# sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' ifcfg-eth0|cut -d'=' -f2
10.1.1.1
255.255.255.0
10.1.1.254
[root@server shell06]# sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/p' ifcfg-eth0|sed -n 's/[A-Z=]//gp'
10.1.1.1
255.255.255.0
10.1.1.254
[root@server shell06]# ifconfig eth0|sed -n '2p'|sed -n 's/[:a-Z]//gp'|sed -n 's/ /\n/gp'|sed '/^$/d'
10.1.1.1
10.1.1.255
255.255.255.0
[root@server shell06]# ifconfig | sed -nr '/([0-9]{1,3}\.)[0-9]{1,3}/p' | head -1|sed -r 's/([a-z:]|[A-Z/t])//g'|sed 's/ /\n/g'|sed '/^$/d'
[root@server shell06]# ifconfig eth0|sed -n '2p'|sed -n 's/.*addr:\(.*\) Bcast:\(.*\) Mask:\(.*\)/\1\n\2\n\3/p'
10.1.1.1
10.1.1.255
255.255.255.0
-i 选项 直接修改原文件
# sed -i 's/root/ROOT/;s/stu/STU/' 11.txt
[root@server ~]# sed -i '17{s/YUNWEI/yunwei/;s#/bin/bash#/sbin/nologin#}' 1.txt
[root@server ~]# sed -i '1,5s/^/#&/' a.txt
注意:
-ni 不要一起使用
p命令 不要再使用-i时使用
- 总结
sed 选项 'sed命令或者正则表达式或者地址定位=='== 文件名
定址用于决定对哪些行进行编辑。地址的形式可以是数字、正则表达式、或二者的结合。如果没有指定地址,sed将处理输入文件的所有行。
x 指定x行号 sed -n '5p' 1.txt
x,y 指定x到y行号 sed -n '1,5p' 1.txt
/key/ 查询包含关键字的行 sed -n '/root/p' 1.txt
/key1/,/key2/ 匹配包含两个关键字之间的行 sed -n '/^adm/,/^mysql/p' 1.txt
/key/,x 从匹配关键字的行开始到文件第x行之间的行(包含关键字所在行)sed -n '/^lp/,7p'
x,/key/ 从第x行开始到与关键字的匹配行之间的行
x,y! 不包含x到y行
[root@server ~]# sed -n '/bash$/!p' 1.txt
注意:sed使用的正则表达式是括在斜杠线"/"之间的模式。
//以下命令是找出以lp开头或者以mail开头的行
- 其他命令讲解
y命令
该命令与UNIX/Linux中的tr命令类似,字符按照一对一的方式从左到右进行转换。
正则表达式元字符对y命令不起作用。与s命令的分隔符一样,斜线可以被替换成其它的字符。
s/xxx/xxx/
y/xxx/xxx/
# sed '39,41y/stu/STU/' /etc/passwd
# sed '39,41y/stu:x/STU@%/' /etc/passwd
q 退出
# sed '5q' 1.txt
# sed '/mail/q' 1.txt
# sed -r '/^yunwei|^mail/q' 1.txt
[root@server ~]# sed -n '/bash$/p;10q' 1.txt
ROOT:x:0:0:root:/root:/bin/bash
2.2 脚本格式
- 用法
# sed -f scripts.sed file //使用脚本处理文件
建议使用 ./sed.sh file
脚本的第一行写上
#!/bin/sed -f
1,5d
s/root/hello/g
3i777
5i888
a
p
- 注意事项
1) 脚本文件是一个sed的命令行清单。'commands'
2) 在每行的末尾不能有任何空格、制表符(tab)或其它文本。
3) 如果在一行中有多个命令,应该用分号分隔。
4) 不需要且不可用引号保护命令
5) #号开头的行为注释
- 示例
# cat passwd
stu3:x:509:512::/home/user3:/bin/bash
stu4:x:510:513::/home/user4:/bin/bash
stu5:x:511:514::/home/user5:/bin/bash
# cat sed.sh
#!/bin/sed -f
2a\
******************
2,$s/stu/user/
$a\
we inster new line
s/^[a-z].*/#&/
[root@server ~]# cat 1.sed
#!/bin/sed -f
3a**********************
$chelloworld
1,3s/^/#&/
[root@server ~]# sed -f 1.sed -i 11.txt
[root@server ~]# cat 11.txt
#root:x:0:0:root:/root:/bin/bash
#bin:x:1:1:bin:/bin:/sbin/nologin
#daemon:x:2:2:daemon:/sbin:/sbin/nologin
**********************
adm:x:3:4:adm:/var/adm:/sbin/nologin
helloworld
3. sed和正则的综合运用
1、正则表达式必须以”/“前后规范间隔
例如:sed '/root/d' file
例如:sed '/^root/d' file
2、如果匹配的是扩展正则表达式,需要使用-r选来扩展sed
grep -E
sed -r
+ ? () {n,m} | \d
注意:
在正则表达式中如果出现特殊字符(^$.*/[]),需要以前导 "\" 号做转义
eg:sed '/\$foo/p' file
3、逗号分隔符
例如:sed '5,7d' file 删除5到7行
例如:sed '/root/,/ftp/d' file
删除第一个匹配字符串"root"到第一个匹配字符串"ftp"的所有行本行不找 循环执行
4、组合方式
例如:sed '1,/foo/d' file 删除第一行到第一个匹配字符串"foo"的所有行
例如:sed '/foo/,+4d' file 删除从匹配字符串”foo“开始到其后四行为止的行
例如:sed '/foo/,~3d' file 删除从匹配字符串”foo“开始删除到3的倍数行(文件中)
例如:sed '1~5d' file 从第一行开始删每五行删除一行
例如:sed -nr '/foo|bar/p' file 显示配置字符串"foo"或"bar"的行
例如:sed -n '/foo/,/bar/p' file 显示匹配从foo到bar的行
例如:sed '1~2d' file 删除奇数行
例如:sed '0-2d' file 删除偶数行 sed '1~2!d' file
5、特殊情况
例如:sed '$d' file 删除最后一行
例如:sed '1d' file 删除第一行
6、其他:
sed 's/.//' a.txt 删除每一行中的第一个字符
sed 's/.//2' a.txt 删除每一行中的第二个字符
sed 's/.//N' a.txt 从文件中第N行开始,删除每行中第N个字符(N>2)
sed 's/.$//' a.txt 删除每一行中的最后一个字符
[root@server ~]# cat 2.txt
1 a
2 b
3 c
4 d
5 e
6 f
7 u
8 k
9 o
[root@server ~]# sed '/c/,~2d' 2.txt
1 a
2 b
5 e
6 f
7 u
8 k
9 o
4. 课堂练习
- 将任意数字替换成空或者制表符
- 去掉文件1-5行中的数字、冒号、斜杠
- 匹配root关键字替换成hello itcast,并保存到test.txt文件中
- 删除vsftpd.conf、smb.conf、main.cf配置文件里所有注释的行及空行(不要直接修改原文件)
- 使用sed命令截取自己的ip地址
- 使用sed命令一次性截取ip地址、广播地址、子网掩码
- 注释掉文件的2-3行和匹配到以root开头或者以ftp开头的行
1、将文件中任意数字替换成空或者制表符
2、去掉文件1-5行中的数字、冒号、斜杠
3、匹配root关键字的行替换成hello itcast,并保存到test.txt文件中
4、删除vsftpd.conf、smb.conf、main.cf配置文件里所有注释的行及空行(不要直接修改原文件)
5、使用sed命令截取自己的ip地址
# ifconfig eth0|sed -n '2p'|sed -n 's/.*addr://pg'|sed -n 's/Bcast.*//gp'
10.1.1.1
# ifconfig eth0|sed -n '2p'|sed 's/.*addr://g'|sed 's/ Bcast:.*//g'
6、使用sed命令一次性截取ip地址、广播地址、子网掩码
# ifconfig eth0|sed -n '2p'|sed -n 's#.*addr:\(.*\) Bcast:\(.*\) Mask:\(.*\)#\1\n\2\n\3#p'
10.1.1.1
10.1.1.255
255.255.255.0
7、注释掉文件的2-3行和匹配到以root开头或者以ftp开头的行
# sed -nr '2,3s/^/#&/p;s/^ROOT|^ftp/#&/p' 1.txt
#ROOT:x:0:0:root:/root:/bin/bash
#bin:x:1:1:bin:/bin:/sbin/nologin
#3daemon:x:2:2:daemon:/sbin:/sbin/nologin
# sed -ne '1,2s/^/#&/gp' a.txt -nre 's/^lp|^mail/#&/gp'
# sed -nr '1,2s/^/#&/gp;s/^lp|^mail/#&/gp' a.txt
二、课后作业
1、写一个初始化系统的脚本
1)自动修改主机名(如:ip是192.168.0.88,则主机名改为server88.itcast.cc)
a. 更改文件非交互式 sed
/etc/sysconfig/network
b.将本主机的IP截取出来赋值给一个变量ip;再然后将ip变量里以.分割的最后一位赋值给另一个变量ip1
2)自动配置可用的yum源
3)自动关闭防火墙和selinux
2、写一个搭建ftp服务的脚本,要求如下:
1)不支持本地用户登录 local_enable=NO
2) 匿名用户可以上传 新建 删除 anon_upload_enable=YES anon_mkdir_write_enable=YES
3) 匿名用户限速500KBps anon_max_rate=500000
仅供参考:
#!/bin/bash
ipaddr=`ifconfig eth0|sed -n '2p'|sed -e 's/.*inet addr:\(.*\) Bcast.*/\1/g'`
iptail=`echo $ipaddr|cut -d'.' -f4`
ipremote=192.168.1.10
#修改主机名
hostname server$iptail.itcast.com
sed -i "/HOSTNAME/cHOSTNAME=server$iptail.itcast.com" /etc/sysconfig/network
echo "$ipaddr server$iptail.itcast.cc" >>/etc/hosts
#关闭防火墙和selinux
service iptables stop
setenforce 0 >/dev/null 2>&1
sed -i '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
#配置yum源(一般是内网源)
#test network
ping -c 1 $ipremote > /dev/null 2>&1
if [ $? -ne 0 ];then
echo "你的网络不通,请先检查你的网络"
exit 1
else
echo "网络ok."
fi
cat > /etc/yum.repos.d/server.repo << end
[server]
name=server
baseurl=ftp://$ipremote
enabled=1
gpgcheck=0
end
#安装软件
read -p "请输入需要安装的软件,多个用空格隔开:" soft
yum -y install $soft &>/dev/null
#备份配置文件
conf=/etc/vsftpd/vsftpd.conf
\cp $conf $conf.default
#根据需求修改配置文件
sed -ir '/^#|^$/d' $conf
sed -i '/local_enable/c\local_enable=NO' $conf
sed -i '$a anon_upload_enable=YES' $conf
sed -i '$a anon_mkdir_write_enable=YES' $conf
sed -i '$a anon_other_write_enable=YES' $conf
sed -i '$a anon_max_rate=512000' $conf
#启动服务
service vsftpd restart &>/dev/null && echo"vsftpd服务启动成功"
#测试验证
chmod 777 /var/ftp/pub
cp /etc/hosts /var/ftp/pub
#测试下载
cd /tmp
lftp $ipaddr <<end
cd pub
get hosts
exit
end
if [ -f /tmp/hosts ];then
echo "匿名用户下载成功"
rm -f /tmp/hosts
else
echo "匿名用户下载失败"
fi
#测试上传、创建目录、删除目录等
cd /tmp
lftp $ipaddr << end
cd pub
mkdir test1
mkdir test2
put /etc/group
rmdir test2
exit
end
if [ -d /var/ftp/pub/test1 ];then
echo "创建目录成功"
if [ ! -d /var/ftp/pub/test2 ];then
echo "文件删除成功"
fi
else
if [ -f /var/ftp/pub/group ];then
echo "文件上传成功"
else
echo "上传、创建目录删除目录部ok"
fi
fi
[ -f /var/ftp/pub/group ] && echo "上传文件成功"
第六章 SHELL编程之文本处理工具AWK
课程目标
- 熟悉awk的命令行模式基本语法结构
- 熟悉awk的相关内部变量
- 熟悉awk常用的打印函数print
- 能够在awk中匹配正则表达式打印相关的行
一、awk介绍
-
awk是一种编程语言,主要用于在linux/unix下对文本和数据进行处理,是linux/unix下的一个工具。数据可以来自标准输入、一个或多个文件,或其它命令的输出。
-
awk的处理文本和数据的方式:逐行扫描文件,默认从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。
-
awk分别代表其作者姓氏的第一个字母。因为它的作者是三个人,分别是Alfred Aho、Brian Kernighan、Peter Weinberger。
-
gawk是awk的GNU版本,它提供了Bell实验室和GNU的一些扩展。
-
下面介绍的awk是以GNU的gawk为例的,在linux系统中已把awk链接到gawk,所以下面全部以awk进行介绍。
1. awk使用方式
-
命令模式语法
awk 选项 'commands' 文件名
-
常用选项
- -F 定义字段分割符号,默认的分隔符是空格
- -v 定义变量并赋值
-
'命名部分(commands)’
- 正则表达式,地址定位
'/root/{awk语句}' sed中: '/root/p' 'NR==1,NR==5{awk语句}' sed中: '1,5p' '/^root/,/^ftp/{awk语句}' sed中:'/^root/,/^ftp/p'
- {awk语句1*;awk语句2*;…}
'{print $0;print $1}' sed中:'p' 'NR==5{print $0}' sed中:'5p' 注:awk命令语句间用分号间隔
- BEGIN…END…
'BEGIN{awk语句};{处理中};END{awk语句}' 'BEGIN{awk语句};{处理中}' '{处理中};END{awk语句}'
- 引用shell变量需用双引号引起
-
-
脚本模式
- 脚本执行
方法1: awk 选项 -f awk的脚本文件 要处理的文本文件 awk -f awk.sh filename sed -f sed.sh -i filename 方法2: ./awk的脚本文件(或者绝对路径) 要处理的文本文件 ./awk.sh filename ./sed.sh filename
- 脚本编写
#!/bin/awk -f 定义魔法字符 以下是awk引号里的命令清单,不要用引号保护命令,多个命令用分号间隔 BEGIN{FS=":"} NR==1,NR==3{print $1"\t"$NF} ...
2. awk内部相关变量
变量 | 变量说明 | 备注 |
---|---|---|
$0 | 当前处理行的所有记录 | |
$1,$2,$3…$n | 文件中每行以间隔符号分割的不同字段 | awk -F: ‘{print $1,$3}’ |
NF | 当前记录的字段数(列数) | awk -F: ‘{print NF}’ |
$NF | 最后一列 | $(NF-1)表示倒数第二列 |
FNR/NR | 行号 | |
FS | 定义间隔符 | ‘BEGIN{FS=“:”};{print $1,$3}’ |
OFS | 定义输出字段分隔符,默认空格 | ‘BEGIN{OFS=“\t”};print $1,$3}’ |
RS | 输入记录分割符,默认换行 | ‘BEGIN{RS=“\t”};{print $0}’ |
ORS | 输出记录分割符,默认换行 | ‘BEGIN{ORS=“\n\n”};{print $1,$3}’ |
FILENAME | 当前输入的文件名 |
- 示例1
[root@server shell07]# awk -F: '{print $1,$(NF-1)}' 1.txt
[root@server shell07]# awk -F: '{print $1,$(NF-1),$NF,NF}' 1.txt
[root@server shell07]# awk '/root/{print $0}' 1.txt
[root@server shell07]# awk '/root/' 1.txt
[root@server shell07]# awk -F: '/root/{print $1,$NF}' 1.txt
root /bin/bash
[root@server shell07]# awk -F: '/root/{print $0}' 1.txt
root:x:0:0:root:/root:/bin/bash
[root@server shell07]# awk 'NR==1,NR==5' 1.txt
[root@server shell07]# awk 'NR==1,NR==5{print $0}' 1.txt
[root@server shell07]# awk 'NR==1,NR==5;/^root/{print $0}' 1.txt
root:x:0:0:root:/root:/bin/bash
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
- 示例2
FS和OFS:
[root@server shell07]# awk 'BEGIN{FS=":"};/^root/,/^lp/{print $1,$NF}' 1.txt
[root@server shell07]# awk -F: 'BEGIN{OFS="\t\t"};/^root/,/^lp/{print $1,$NF}' 1.txt
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
lp /sbin/nologin
[root@server shell07]# awk -F: 'BEGIN{OFS="@@@"};/^root/,/^lp/{print $1,$NF}' 1.txt
root@@@/bin/bash
bin@@@/sbin/nologin
daemon@@@/sbin/nologin
adm@@@/sbin/nologin
lp@@@/sbin/nologin
[root@server shell07]#
RS和ORS:
修改源文件前2行增加制表符和内容:
vim 1.txt
root:x:0:0:root:/root:/bin/bash hello world
bin:x:1:1:bin:/bin:/sbin/nologin test1 test2
[root@server shell07]# awk 'BEGIN{RS="\t"};{print $0}' 1.txt
[root@server shell07]# awk 'BEGIN{ORS="\t"};{print $0}' 1.txt
- 格式化输出
print
和printf
print函数 类似echo
# date |awk '{print "Month: "$2 "\nYear: "$NF}'
# awk -F: '{print "username is: " $1 "\t uid is: "$3}' /etc/passwd
printf函数 类似echo -n
# awk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}' /etc/passwd
# awk -F: '{printf "|%15s| %10s| %15s|\n", $1,$2,$3}' /etc/passwd
# awk -F: '{printf "|%-15s| %-10s| %-15s|\n", $1,$2,$3}' /etc/passwd
awk 'BEGIN{FS=":"};{printf "%-15s %-15s %-15s\n",$1,$6,$NF}' a.txt
%s 字符类型 strings %-20s
%d 数值类型
占15字符
- 表示左对齐,默认是右对齐
printf默认不会在行尾自动换行,加\n
3. awk工作原理
awk -F: '{print $1,$3}' /etc/passwd
-
awk使用一行作为输入,并将这一行赋给内部变量$0,每一行也可称为一个记录,以换行符(RS)结束
-
每行被间隔符**:**(默认为空格或制表符)分解成字段(或域),每个字段存储在已编号的变量中,从$1开始
问:awk如何知道用空格来分隔字段的呢?
答:因为有一个内部变量FS来确定字段分隔符。初始时,FS赋为空格
-
awk使用print函数打印字段,打印出来的字段会以空格分隔,因为$1,$3之间有一个逗号。逗号比较特殊,它映射为另一个内部变量,称为输出字段分隔符OFS,OFS默认为空格
-
awk处理完一行后,将从文件中获取另一行,并将其存储在$0中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处理。该过程将持续到所有行处理完毕
4. awk变量定义
# awk -v NUM=3 -F: '{ print $NUM }' /etc/passwd
# awk -v NUM=3 -F: '{ print NUM }' /etc/passwd
# awk -v num=1 'BEGIN{print num}'
1
# awk -v num=1 'BEGIN{print $num}'
注意:
awk中调用定义的变量不需要加$
5. awk中BEGIN…END使用
①BEGIN:表示在程序开始前执行
②END :表示所有文件处理完后执行
③用法:'BEGIN{开始处理之前};{处理中};END{处理结束后}'
- 示例:
- 打印最后一列和倒数第二列(登录shell和家目录)
awk -F: 'BEGIN{ print "Login_shell\t\tLogin_home\n*******************"};{print $NF"\t\t"$(NF-1)};END{print "************************"}' 1.txt
awk 'BEGIN{ FS=":";print "Login_shell\tLogin_home\n*******************"};{print $NF"\t"$(NF-1)};END{print "************************"}' 1.txt
Login_shell Login_home
************************
/bin/bash /root
/sbin/nologin /bin
/sbin/nologin /sbin
/sbin/nologin /var/adm
/sbin/nologin /var/spool/lpd
/bin/bash /home/redhat
/bin/bash /home/user01
/sbin/nologin /var/named
/bin/bash /home/u01
/bin/bash /home/YUNWEI
************************************
- 打印/etc/passwd里的用户名、家目录及登录shell
u_name h_dir shell
***************************
***************************
awk -F: 'BEGIN{OFS="\t\t";print"u_name\t\th_dir\t\tshell\n***************************"}{printf "%-20s %-20s %-20s\n",$1,$(NF-1),$NF};END{print "****************************"}'
# awk -F: 'BEGIN{print "u_name\t\th_dir\t\tshell" RS "*****************"} {printf "%-15s %-20s %-20s\n",$1,$(NF-1),$NF}END{print "***************************"}' /etc/passwd
格式化输出:
echo print
echo -n printf
{printf "%-15s %-20s %-20s\n",$1,$(NF-1),$NF}
6. awk和正则的综合运用
运算符 | 说明 |
---|---|
== | 等于 |
!= | 不等于 |
> | 大于 |
< | 小于 |
>= | 大于等于 |
<= | 小于等于 |
~ | 匹配 |
!~ | 不匹配 |
! | 逻辑非 |
&& | 逻辑与 |
|| | 逻辑或 |
- 示例
从第一行开始匹配到以lp开头行
awk -F: 'NR==1,/^lp/{print $0 }' passwd
从第一行到第5行
awk -F: 'NR==1,NR==5{print $0 }' passwd
从以lp开头的行匹配到第10行
awk -F: '/^lp/,NR==10{print $0 }' passwd
从以root开头的行匹配到以lp开头的行
awk -F: '/^root/,/^lp/{print $0}' passwd
打印以root开头或者以lp开头的行
awk -F: '/^root/ || /^lp/{print $0}' passwd
awk -F: '/^root/;/^lp/{print $0}' passwd
显示5-10行
awk -F':' 'NR>=5 && NR<=10 {print $0}' /etc/passwd
awk -F: 'NR<10 && NR>5 {print $0}' passwd
打印30-39行以bash结尾的内容:
[root@MissHou shell05]# awk 'NR>=30 && NR<=39 && $0 ~ /bash$/{print $0}' passwd
stu1:x:500:500::/home/stu1:/bin/bash
yunwei:x:501:501::/home/yunwei:/bin/bash
user01:x:502:502::/home/user01:/bin/bash
user02:x:503:503::/home/user02:/bin/bash
user03:x:504:504::/home/user03:/bin/bash
[root@server shell07]# awk 'NR>=3 && NR<=8 && /bash$/' 1.txt
stu7:x:1007:1007::/rhome/stu7:/bin/bash
stu8:x:1008:1008::/rhome/stu8:/bin/bash
stu9:x:1009:1009::/rhome/stu9:/bin/bash
理解;号和||的含义:
[root@server shell07]# awk 'NR>=3 && NR<=8 || /bash$/' 1.txt
[root@server shell07]# awk 'NR>=3 && NR<=8;/bash$/' 1.txt
打印IP地址
# ifconfig eth0|awk 'NR>1 {print $2}'|awk -F':' 'NR<2 {print $2}'
# ifconfig eth0|grep Bcast|awk -F':' '{print $2}'|awk '{print $1}'
# ifconfig eth0|grep Bcast|awk '{print $2}'|awk -F: '{print $2}'
# ifconfig eth0|awk NR==2|awk -F '[ :]+' '{print $4RS$6RS$8}'
# ifconfig eth0|awk "-F[ :]+" '/inet addr:/{print $4}'
7. 课堂练习
1、显示可以登录操作系统的用户所有信息 从第7列匹配以bash结尾,输出整行(当前行所有的列)
[root@MissHou ~] awk '/bash$/{print $0}' /etc/passwd
[root@MissHou ~] awk '/bash$/{print $0}' /etc/passwd
[root@MissHou ~] awk '/bash$/' /etc/passwd
[root@MissHou ~] awk -F: '$7 ~ /bash/' /etc/passwd
[root@MissHou ~] awk -F: '$NF ~ /bash/' /etc/passwd
[root@MissHou ~] awk -F: '$0 ~ /bash/' /etc/passwd
[root@MissHou ~] awk -F: '$0 ~ /\/bin\/bash/' /etc/passwd
2、显示可以登录系统的用户名
# awk -F: '$0 ~ /\/bin\/bash/{print $1}' /etc/passwd
3、打印出系统中普通用户的UID和用户名如下显示:
# awk -F: 'BEGIN{print "UID\tUSERNAME"} {if($3>=500 && $3 !=65534 ) {print $3"\t"$1} }' /etc/passwdUID USERNAME
500 stu1
501 yunwei
502 user01
503 user02
504 user03
# awk -F: '{if($3 >= 500 && $3 != 65534) print $1,$3}' a.txt
redhat 508
user01 509
u01 510
YUNWEI 511
4、以数字开头
awk '/^[0-9]/{print $0}' 1.txt
5、以任意大小写字母开头
awk '/^[a-Z]/{print $0}' 1.txt
8. awk的脚本编程
8.1 流程控制语句
if语句:
if [ xxx ];then
xxx
fi
格式:
awk 选项 '正则,地址定位{awk语句}' 文件名
{ if(表达式){语句1;语句2;...}}
awk -F: '{if($3>=500 && $3<=60000) {print $1,$3} }' passwd
[root@MissHou shell05]# awk -F: '{if($3==0) {print $1"是管理员"} }' passwd
root是管理员
# awk 'BEGIN{if($(id -u)==0) {print "admin"} }'
admin
if...else语句:
if [ xxx ];then
xxxxx
else
xxx
fi
格式:
{if(表达式){语句;语句;...}else{语句;语句;...}}
awk -F: '{ if($3>=500 && $3 != 65534) {print $1"是普通用户"} else {print $1,"不是普通用户"}}' passwd
awk 'BEGIN{if( $(id -u)>=500 && $(id -u) !=65534 ) {print "是普通用户"} else {print "不是普通用户"}}'
if [xxxx];then
xxxx
elif [xxx];then
xxx
....
else
...
fi
if...else if...else语句:
格式:
{ if(表达式1){语句;语句;...}else if(表达式2){语句;语句;...}else if(表达式3){语句;语句;...}else{语句;语句;...}}
awk -F: '{ if($3==0) {print $1,":是管理员"} else if($3>=1 && $3<=499 || $3==65534 ) {print $1,":是系统用户"} else {print $1,":是普通用户"}}'
awk -F: '{ if($3==0) {i++} else if($3>=1 && $3<=499 || $3==65534 ) {j++} else {k++}};END{print "管理员个数为:"i "\n系统用户个数为:"j"\n普通用户的个数为:"k }'
# awk -F: '{if($3==0) {print $1,"is admin"} else if($3>=1 && $3<=499 || $3==65534) {print $1,"is sys users"} else {print $1,"is general user"} }' a.txt
root is admin
bin is sys users
daemon is sys users
adm is sys users
lp is sys users
redhat is general user
user01 is general user
named is sys users
u01 is general user
YUNWEI is general user
awk -F: '{ if($3==0) {print $1":管理员"} else if($3>=1 && $3<500 || $3==65534 ) {print $1":是系统用户"} else {print $1":是普通用户"}}' /etc/passwd
awk -F: '{if($3==0) {i++} else if($3>=1 && $3<500 || $3==65534){j++} else {k++}};END{print "管理员个数为:" i RS "系统用户个数为:"j RS "普通用户的个数为:"k }' /etc/passwd
管理员个数为:1
系统用户个数为:28
普通用户的个数为:27
# awk -F: '{ if($3==0) {print $1":是管理员"} else if($3>=500 && $3!=65534) {print $1":是普通用户"} else {print $1":是系统用户"}}' passwd
awk -F: '{if($3==0){i++} else if($3>=500){k++} else{j++}} END{print i; print k; print j}' /etc/passwd
awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print "管理员个数: "i; print "普通用个数: "k; print "系统用户: "j}' /etc/passwd
如果是普通用户打印默认shell,如果是系统用户打印用户名
# awk -F: '{if($3>=1 && $3<500 || $3 == 65534) {print $1} else if($3>=500 && $3<=60000 ) {print $NF} }' /etc/passwd
8.2 循环语句
while:
# awk 'BEGIN { i=1; while(i<=10) {print i;i++} }'
文件里的每一行循环打印10次:
# awk -F: '{ i=1; while(i<=10) {print $0;i++} }' /etc/passwd
for:
# awk 'BEGIN { for(i=1;i<=5;i++) {print i} }'
文件里的每一行循环打印10次:
# awk -F: '{ for(i=1;i<=10;i++) {print $0}}' /etc/passwd
打印1~5
# awk 'BEGIN { for(i=1;i<=5;i++) {print i} }'
# awk 'BEGIN { i=1;while(i<=5) {print i;i++} }'
打印1~10中的奇数
# awk 'BEGIN{i=1;while(i<=10) {print i;i+=2} }'
awk 选项 '{awk语句1 语句2 }'
计算1-5的和:
# awk 'BEGIN { for(i=1;i<=5;i++) {(sum+=i)};{print sum} }'
# awk 'BEGIN{for(i=1;i<=5;i++) (sum+=i);{print sum}}'
# awk 'BEGIN{for(i=1;i<=5;i++) (sum+=i);print sum}'
# awk 'BEGIN { i=1;while(i<=5) {(sum+=i) i++};print sum }'
打印1-10的奇数和
for ((i=1;i<=10;i+=2));do echo $i;done|awk -v sum=0 '{sum+=$0};END{print sum}'
嵌套循环:
#!/bin/bash
for ((y=1;y<=5;y++))
do
for ((x=1;x<=$y;x++))
do
echo -n $x
done
echo
done
# awk 'BEGIN { for(y=1;y<=5;y++) { for(x=1;x<=y;x++) {printf x};print} }'
1
12
123
1234
12345
# awk 'BEGIN{ y=1;while(y<=5) { for(x=1;x<=y;x++) {printf x};y++;print}}'
1
12
123
1234
12345
尝试用三种方法打印99口诀表:
#awk 'BEGIN{for(y=1;y<=9;y++) { for(x=1;x<=y;x++) {printf x"*"y"="x*y"\t"};print} }'
#awk 'BEGIN{for(y=1;y<=9;y++) { for(x=1;x<=y;x++) printf x"*"y"="x*y"\t";print} }'
#awk 'BEGIN{i=1;while(i<=9){for(j=1;j<=i;j++) {printf j"*"i"="j*i"\t"};print;i++ }}'
#awk 'BEGIN{for(i=1;i<=9;i++){j=1;while(j<=i) {printf j"*"i"="i*j"\t";j++};print}}'
循环的控制:
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
# awk 'BEGIN{i=1;while(i<=5){if(i==3) break;print i;i++}}'
1
2
# awk 'BEGIN{i=0;while(i<5){i++;if(i==3) continue;print i}}'
1
2
4
5
8.3 算数运算
+ - * / %(模) ^(幂2^3)
可以在模式中执行计算,awk都将按浮点数方式执行算术运算
# awk 'BEGIN{print 1+1}'
# awk 'BEGIN{print 1**1}'
# awk 'BEGIN{print 2**3}'
# awk 'BEGIN{print 2/3}'
二. awk统计案例
1. 统计/etc/passwd 中各种类型shell的数量
# awk -F: '{ shells[$NF]++ };END{for (i in shells) {print i,shells[i]} }' /etc/passwd
books[linux]++
books[linux]=1
shells[/bin/bash]++
shells[/sbin/nologin]++
/bin/bash 5
/sbin/nologin 6
shells[/bin/bash]++ a
shells[/sbin/nologin]++ b
shells[/sbin/shutdown]++ c
books[linux]++
books[php]++
2. 网站访问状态统计 <当前时实状态 netstat>
# ss -antp|grep 80|awk '{states[$1]++};END{for(i in states){print i,states[i]}}'
TIME_WAIT 578
ESTABLISHED 1
LISTEN 1
# ss -an |grep :80 |awk '{states[$2]++};END{for(i in states){print i,states[i]}}'
LISTEN 1
ESTAB 5
TIME-WAIT 25
# ss -an |grep :80 |awk '{states[$2]++};END{for(i in states){print i,states[i]}}' |sort -k2 -rn
TIME-WAIT 18
ESTAB 8
LISTEN 1
3. 统计当前访问的每个IP的数量 <当前时实状态 netstat,ss>
# netstat -ant |grep :80 |awk -F: '{ip_count[$8]++};END{for(i in ip_count){print i,ip_count[i]} }' |sort
# ss -an |grep :80 |awk -F":" '!/LISTEN/{ip_count[$(NF-1)]++} END{for(i in ip_count){print i,ip_count[i]}}' |sort -k2 -rn |head
4. 统计Apache/Nginx日志中某一天的PV量 <统计日志>
# grep '27/Jul/2017' mysqladmin.cc-access_log |wc -l
14519
5. 统计Apache/Nginx日志中某一天不同IP的访问量 <统计日志>
# grep '27/Jul/2017' mysqladmin.cc-access_log |awk '{ips[$1]++};END{for(i in ips){print i,ips[i]} }' |sort -k2 -rn |head
# grep '07/Aug/2017' access.log |awk '{ips[$1]++} END{for(i in ips){print i,ips[i]} }' |awk '$2>100' |sort -k2 -rn
名词引入:
名词:VV = Visit View(访问次数)
说明:从访客来到您网站到最终关闭网站的所有页面离开,计为1次访问。若访客连续30分钟没有新开和刷新页面,或者访客关闭了浏览器,则被计算为本次访问结束。
独立访客(UV)
名词:UV= Unique Visitor(独立访客数)
说明:1天内相同的访客多次访问您的网站只计算1个UV。
网站浏览量(PV)
名词:PV=PageView (网站浏览量)
说明:指页面的浏览次数,用以衡量网站用户访问的网页数量。多次打开同一页面则浏览量累计。用户每打开一个页面便记录1次PV。
独立IP(IP)
名词:IP=独立IP数
说明:指1天内使用不同IP地址的用户访问网站的数量。同一IP无论访问了几个页面,独立IP数均为1
三、课后作业
作业1:
1、写一个自动检测磁盘使用率的脚本,当磁盘使用空间达到90%以上时,需要发送邮件给相关人员
2、写一个脚本监控系统内存和交换分区使用情况
作业2:
输入一个IP地址,使用脚本判断其合法性:
必须符合ip地址规范,第1、4位不能以0开头,不能大于255不能小于0
四、企业实战案例
1. 任务/背景
web服务器集群中总共有9台机器,上面部署的是Apache服务。由于业务不断增长,每天每台机器上都会产生大量的访问日志,现需要将每台web服务器上的apache访问日志保留最近3天的,3天以前的日志转储到一台专门的日志服务器上,已做后续分析。如何实现每台服务器上只保留3天以内的日志?
2. 具体要求
- 每台web服务器的日志对应日志服务器相应的目录里。如:web1——>web1.log(在日志服务器上)
- 每台web服务器上保留最近3天的访问日志,3天以前的日志每天凌晨5:03分转储到日志服务器
- 如果脚本转储失败,运维人员需要通过跳板机的菜单选择手动清理日志
3. 涉及知识点
- shell的基本语法结构
- 文件同步rsync
- 文件查找命令find
- 计划任务crontab
- apache日志切割
- 其他