Linux之文本处理小工具+shell从入门到精通
目录
1. 文件处理工具
1.1 grep工具(文本三剑客之一):行过滤
1.2 cut工具:列截取
1.3 sort工具:排序
1.4 uniq工具:去除连续的重复行
1.5 tee工具:双向覆盖重定向
1.6 paste工具:用于合并文件行
1.7 tr工具: 字符转换 -> 替换,删除
eg:
2. 编程语言分类
3. shell介绍
4. shell脚本
5. bash基本特性
5.1 命令和文件自动补全
5.2 常见的快捷键
5.3 常用的通配符(重点)
5.4 bash中的引号(重点)
6.变量的定义
6.1. 变量的分类
6.2. 什么时候需要定义变量?
6.3. 变量的定义规则
6.4.简单的四则运算
6.5.条件判断
语法格式
条件判断相关参数
与文件存在与否的判断
文件权限相关的判断
两个文件的比较判断
整数之间的判断
字符串之间的判断
多重条件判断
7、流程控制语句
7.1. 基本语法结构
7.2. 应用案例
8、循环语句
8.1. for循环
8.1.1 语法结构
列表循环
不带列表循环
类C风格的for循环
8.1.2 举例说明
循环控制:
8.1.3练习
8.2. while循环
8.2.1 语法结构
8.2.2 举例说明
8.2.3 应用案例
8.3. until循环
8.3.1 语法结构
8.3.2 举例说明
练习题目:
补充:随机数
案例:写一个脚本,产生一个phonenum.txt文件,随机产生以139开头的手机号1000个,每个一行。
8.4、嵌套循环
补充扩展:expect
案例:写一个脚本,将跳板机上yunwei用户的公钥推送到局域网内可以ping通的所有机器上
最终实现
案例:写一个脚本,统计web服务的不同连接状态个数
练习:
9、case语句
案例:脚本提示让用户输入需要管理的服务名,然后提示用户需要对服务做什么操作,如启动,关闭,重启等
案例:模拟一个多任务维护界面。当执行程序时先显示总菜单,然后进行选择后做相应维护监控操作。
10、函数
10.1 函数定义
10.2 函数的调用
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 :可以将找到的关键词部分加上颜色的显示示例:
# 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个字符开始截取后面所有字符eg:用小工具列出你当系统的运行级别。
# runlevel | cut -d' ' -f21.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 双向【追加】重定向,默认不加任何选项的时候tee是覆盖# echo hello world|tee file1# echo 999|tee -a file11.6 paste工具:用于合并文件行
-d:自定义间隔符,默认是tab
-s:串行处理,非并行# paste -d'@' b.txt a.txt 
hello world@hello
888@
999@# paste -s b.txt a.txt  等同于 cat
hello world     888     999
hello1.7 tr工具: 字符转换 -> 替换,删除
tr
1、用来从标准输入中通过替换或删除操作进行字符转换;
2、主要用于删除文件中控制字符或进行字符转换。使用tr时要转换两个字符串:字符串1用于查询,字符串2用于处理各种转换。语法:
commands|tr  'string1'  'string2'
tr  'string1'  'string2' < filenametr 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	所有字符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 		删除所有水平和垂直空白eg:
- 使用小工具分别截取当前主机IP;截取NETMASK;截取广播地址;截取MAC地址
- 将系统中所有普通用户的用户名、密码和默认shell保存到一个文件中,要求用户名密码和默认shell之间用tab键分割
2. 编程语言分类
- 编译型语言:
程序在执行之前需要一个专门的编译过程,把程序编译成为机器语言文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++
- 解释型语言:
 程序不需要编译,程序在运行时由**解释器**翻译成机器语言,每执行一次都要翻译一次。因此效率比较低。比如Python/JavaScript/ Perl /ruby/Shell等都是解释型语言。
- 总结: 编译型语言比解释型语言速度较快,但是不如解释型语言跨平台性好。如果做底层开发或者大型应用程序或者操作系开发一般都用编译型语言;如果是一些服务器脚本及一些辅助的接口,对速度要求不高、对各个平台的兼容性有要求的话则一般都用解释型语言。
3. shell介绍
- shell就是人机交互的一个桥梁
- shell的种类
#  查看sehll 的种类
# 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)。 
4. shell脚本
-  什么是shell脚本? - 一句话概括
 简单来说就是将需要执行的命令保存到文本中,按照顺序执行。它是解释型的,意味着不需要编译。(说简单一点就是一些基本语法和命令的堆积) - 准确叙述
 若干命令 + 脚本的基本格式 + 脚本特定语法 + 思想= shell脚本 
-  什么时候用到脚本? 重复化、复杂化的工作,通过把工作的命令写成脚本,以后仅仅需要执行脚本就能完成这些工作。①自动化分析处理;②自动化备份;③自动化批量部署安装;④等等。 
-  如何学习shell脚本? 
- 尽可能记忆更多的命令
- 掌握脚本的标准的格式(指定魔法字节、使用标准的执行方式运行脚本)
- 必须==熟悉掌握==脚本的基本语法(重点)
- 学习脚本的秘诀:
多看(看懂)——> 多模仿(多练)——> 多思考
- 脚本的基本写法:
#!/bin/bash
//脚本第一行, #!魔法字符,指定脚本代码执行的程序。即它告诉系统这个脚本需要什么解释器来执行,也就是使用哪一种Shell//以下内容是对脚本的基本信息的描述
# Name: 名字
# Desc:描述describe
# Path:存放路径
# Usage:用法
# Update:更新时间//下面就是脚本的具体内容
commands
...
脚本执行方法:
- 标准脚本执行方法(建议):(魔法字节指定的程序会生效)
# chmod +x 1.sh # /shell/shell01/1.sh 绝对路径的方式执行 # ./1.sh 相对路径的方式执行- 非标准的执行方法(不建议):(魔法字节指定的程序不会运作)
# bash 1.sh # sh 1.sh# bash -x 1.sh -x:一般用于排错,查看脚本的执行过程 -n:用来查看脚本的语法是否有问题注意:如果脚本没有加可执行权限,不能使用标准的执行方法执行,bash 1.sh其他: # source 2.sh # . 2.sh source 和 . 表示读取文件,执行文件里的命令
5. bash基本特性
5.1 命令和文件自动补全
Tab只能补全命令、文件 、路径、变量名
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}.jpg5.4 bash中的引号(重点)
- 双引号" " :会把引号的内容当成整体来看待,允许通过$符号引用其他变量值
- 单引号' ' :会把引号的内容当成整体来看待,禁止引用其他变量值,shell中特殊符号都被视为普通字符
- 反撇号`` :反撇号和$()一样,引号或括号里的命令会优先执行,如果存在嵌套,反撇号不能用
# echo "$(hostname)"
# echo '$(hostname)'  # 把变量就当成普通变量了
# echo "hello world"
# echo 'hello world'
# echo $(date +%F)
# echo `echo $(date +%F)`
# echo `date +%F`
# echo `echo `date +%F``
# echo $(echo `date +%F`)
6.变量的定义
6.1. 变量的分类
- 本地变量:当前用户自定义的变量。当前进程中有效,其他进程及当前进程的子进程无效。
- 环境变量:当前进程有效,并且能够被子进程调用。 - 查看当前用户的环境变量 env
- 查询当前用户的所有变量(临时变量与环境变量) set
- **export //将当前变量变成环境变量 **
 
# export A=hello		//临时将一个本地变量(临时变量)变成环境变量
# env|grep ^A
A=hello永久生效:
vim /etc/profile 或者 ~/.bashrc
export A=hello   # 推荐这种方式,更加简洁高效
或者
A=hello
export Aeg:系统中有一个变量PATH,环境变量
export PATH=/usr/local/mysql/bin:$PATH注意: set AAA=10 设置的是未知参数的值,echo $AAA是空值,而echo $1则打印出的就是10;取消所有位置参数设置的值使用set --,shift 命令是从左向右删除未知参数的值- 全局变量:全局所有的用户和程序都能调用,且继承,新建的用户也默认能调用.
$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用户全局变量
/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 退出当前会话
$!:后台运行的最后一个进程号 (当前终端)
!$:调用最后一条命令历史中的参数
!! :调用最后一条命令历史
$#:脚本后面接的参数的个数
$*:脚本后面所有参数,参数当成一个整体输出,每一个变量参数之间以空格隔开
$@: 脚本后面所有参数,参数是独立的,也是全部输出(了解$*和$@的区别:$* :表示将变量看成一个整体;$@ :表示变量是独立的)$0:当前执行的进程/程序名 echo $0
$1~$9 :位置参数变量
${10}~${n}: 扩展位置参数变量 第10个位置变量必须用{}大括号括起来# 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}" #下面的这段代码可以就可以说明$@(单独)和$*(看成整体) # cat 2.sh #!/bin/bash for i in "$@" do echo $i done echo "======我是分割线=======" for i in "$*" do echo $i done# bash 2.sh a b c a b c ======我是分割线======= a b c
6.2. 什么时候需要定义变量?
- 如果某个内容需要多次使用,并且在代码中重复出现,那么可以用变量代表该内容。这样在修改内容的时候,仅仅需要修改变量的值。
- 在代码运作的过程中,可能会把某些命令的执行结果保存起来,后续代码需要使用这些结果,就可以直接使用这个变量。
6.3. 变量的定义规则
1. 默认情况下,shell里定义的变量是不分类型的,可以给变量赋与任何类型的值;等号两边不能有空格,对于有空格的字符串做为赋值时,要用引号引起来 变量名=变量值2. 变量的获取方式:$变量名 、${变量名}	a=1234567
# echo $a
# echo ${a}
# echo ${a:2:3}	#字符串切片和Pyhon中略微有区别(3的意义), a表示变量名;2表示从第3个字符开始;3表示后面3个字符# 注:如果获取变量的全部两个都可以;如果获取变量的某一部分,用${}3. 取消变量: unset  变量名
4. 变量名区分大小写,同名称但大小写不同的变量名是不同的变量
5. 变量名可以是 字母或数字或下划线,但是不能以数字开头或者特殊字符
6. 命令的执行结果可以保存到变量
# kernel=`uname -r`
# echo $kernel
# name=$(uname -n)
# echo $name
server.itcast.cc7. 有类型变量 declare
-i 将变量看成整数 
-r 使变量只读  readonly
-x 标记变量通过环境导出  export
-a 指定为索引数组(普通数组);查看普通数组
-A 指定为关联数组;查看关联数组# a=10
# b=20
# echo $a+$b
10+20# declare -i a=2
# declare -i b=4
# declare -i c=$a+$b
# echo $c# declare -x BBBB=hello
# env|grep BBBB8. 数组
普通数组:只能使用 整数 作为数组索引(元素的下标)
关联数组:可以使用 字符串 作为数组索引(元素的下标),和Python中的字典类似普通数组定义:用括号来表示数组,数组元素(变量)用“空格”符号分割开。定义数组的一般形式为:
一次赋一个值:
变量名=变量值
array[0]=v1
一次赋多个值:
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表示元素的下标
使用@ 或 * 可以获取数组中的所有元素:获取第x个元素
echo ${array[0]}
echo ${array[*/@]}		获取数组里的所有元素
echo ${#array[*/@]}	    获取数组里所有元素个数
echo ${!array[@/*]}    	获取数组元素的索引下标
echo ${array[@/*]:1:2}  访问指定的元素;1代表从下标为1的元素开始获取;2代表获取后面几个元素# declare -a  # 查看普通数组信息关联数组定义:
首先声明关联数组
declare -A asso_array数组赋值:
一次赋一个值:数组名[索引|下标]=变量值
# name_array[linux]=one
一次赋多个值:
# name_array=([name1]=harry [name2]=jack [name3]=amy [name4]="Miss Hou")
查看关联数组:
# declare -A
访问关联数组的某一元素
# echo ${name_array[linux]}
访问方法和普通数组相同9. 交互式定义变量的值 read    主要用于让用户去定义变量值
-p 提示信息
-n 字符数 (限制变量值的字符数)
-s 不显示   
-t 超时(默认单位秒)(限制用户输入变量值的超时时间)# cat 1.txt 
10.1.1.1 255.255.255.0
# read  ip  < 1.txt   从文件读取,默认值读取一行
# echo $ip
10.1.1.110. 其他变量(扩展)
1)取出一个目录下的目录和文件:dirname和 basename 
2)变量"内容"的删除和替换
一个“%”代表从右往左去掉一个/key/
两个“%%”代表从右往左最大去掉/key/
一个“#”代表从左往右去掉一个/key/
两个“##”代表从左往右最大去掉/key/# A=/root/Desktop/shell/mem.txt 
# dirname $A   取出目录
# basename $A  取出文件# url=www.taobao.com
# echo ${#url}		     获取变量的长度++++++++++++++++++++++++++++++++++++++++++++++++++
以下内容自己完成:
替换:/ 和 //
# echo ${url/ao/AO}
# echo ${url//ao/AO}   贪婪替换替代: - 和 :-  +和:+
# echo ${abc-123} 
${变量名-新的变量值} 或者 ${变量名=新的变量值}
变量没有被赋值:会使用“新的变量值“ 替代
变量有被赋值(包括空值): 不会被替代# echo ${ABC:-123}
${变量名:-新的变量值} 或者 ${变量名:=新的变量值}
变量没有被赋值或者赋空值:会使用“新的变量值“ 替代
变量有被赋值: 不会被替代# echo ${abc=123}
# echo ${abc:=123}# echo ${abc:+123}
${变量名+新的变量值}
变量没有被赋值或者赋空值:不会使用“新的变量值“ 替代
变量有被赋值: 会被替代
# unset abc
# echo ${abc+123}# abc=hello
# echo ${abc+123}
# echo ${abc+123}
${变量名:+新的变量值}
变量没有被赋值:不会使用“新的变量值“ 替代
变量有被赋值(包括空值): 会被替代# echo ${abc?123}
${变量名?新的变量值}
变量没有被赋值:提示错误信息
变量被赋值(包括空值):不会使用“新的变量值“ 替代# echo ${abc:?123}
${变量名:?新的变量值}
变量没有被赋值或者赋空值时:提示错误信息
变量被赋值:不会使用“新的变量值“ 替代说明:?主要是当变量没有赋值提示错误信息的,没有赋值功能
6.4.简单的四则运算
算术运算:默认情况下,shell就只能支持简单的整数运算
+ - * /  %(取模,求余数)  **(求幂,如2**3=8) 
Bash shell 的算术运算有四种方式:
1. 使用 $(( ))
2. 使用$[ ]
3. 使用 expr 外部程式 ;expr 注意空格,*要进行转义 \
4. 使用let 命令注意:n=1
let n+=1  等价于let n=n+1思考:能不能用shell做小数运算? 借助bc工具
# echo 1+1.5|bc
2.5i++ 和 ++i (了解)对变量的值的影响:和C语言那块所讲的一样
对变量本身没有影响(自己+1);
表达式中有影响;i++ 先赋值再运算  ++i先运算再赋值6.5.条件判断
语法格式
- 格式1: test 条件表达式
- 格式2: [ 条件表达式 ]
- 格式3: [[ 条件表达式 ]] 支持正则 =~
说明:man test去查看,很多的参数都用来进行条件判断
条件判断相关参数
-  与文件存在与否的判断
-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文件里有内容的。
# test -s 1.sh
# echo $?# test ! -s aaa
# echo $?-  文件权限相关的判断
-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 		不等(注意=两边有空格)
eg:
# AAA=hello
# BBB=world
# test -z $AAA
# echo $?# [ $AAA = $BBB ]
# echo $?
# [ $AAA != $BBB ]
# echo $?-  多重条件判断
逻辑判断符号:-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 ]逻辑短路的使用
# [ 1 -eq 0 ] && echo true || echo false
# [ 1 -eq 1 ] && echo true || echo false&&:前面的表达式为真  ||:前面的表达式为假总结:
1、; && ||都可以用来分割命令或者表达式
2、; 完全不考虑前面的语句是否正确执行,都会执行;号后面的内容
3、&& 需要考虑&&前面的语句的正确性,前面语句正确执行才会执行&&后的内容;反之亦然
make && make install
4、|| 需要考虑||前面的语句的非正确性,前面语句执行错误才会执行||后的内容;反之亦然
5、如果&&和||一起出现,从左往右依次看,按照以上原则7、流程控制语句
7.1. 基本语法结构
- F: false 假
- T: true 真
if [ condition ];thencommandcommand
fi[ 条件 ] && command
if [ condition ];thencommand1elsecommand2
fi[ 条件 ] && command1 || command2
if [ condition1 ];thencommand1  结束elif [ condition2 ];thencommand2   结束elsecommand3
fi
注释:
如果条件1满足,执行命令1后结束;
如果条件1不满足,再看条件2,如果条件2满足执行命令2后结束;
如果条件1和条件2都不满足执行命令3结束.
if [ condition1 ];thencommand1		if [ condition2 ];thencommand2fielseif [ condition3 ];thencommand3elif [ condition4 ];thencommand4elsecommand5fi
fi
注释:
如果条件1满足,执行命令1;
如果条件2也满足执行命令2,如果不满足就只执行命令1结束;
如果条件1不满足,不看条件2;直接看条件3,如果条件3满足执行命令3;
如果不满足则看条件4,如果条件4满足执行命令4;否则执行命令5
7.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 ];thenecho "当前主机和远程主机$IP是互通的。"	
elseecho "当前主机和远程主机$IP是不通的。"
fi或者
Remote_IP='xxx.xxx.xxx.xxx'
/usr/bin/ping -c1 $Remote_IP > /dev/null && echo "$Remote_IP 可以 ping 通" || echo "$Remote_IP 不可以 ping 通"需求2:判断一个进程是否存在
思路:
1.查看进程的相关命令 ps -ef 、 pgrep 、 ps auxf、 pidof
2.根据命令的返回状态值来判断进程是否存在  $?
3.根据逻辑用脚本语言实现# 定义变量
#!/bin/bash
read -p "请你输入你要检测的进程名称: " process
pgrep $process > /dev/null
if [ $? -eq 0 ];thenecho "$process 进程存在"elseecho "$process 进程不存在"
fipgrep命令:以名称为依据从运行进程队列中查找进程,并显示查找到的进程id
选项
-o:仅显示找到的最小(起始)进程号;
-n:仅显示找到的最大(结束)进程号;
-l:显示进程名称;
-P:指定父进程号;pgrep -p 4764  查看父进程下的子进程id
-g:指定进程组;
-t:指定开启进程的终端;
-u:指定进程的有效用户ID。
eg:
1、输入一个用户,用脚本判断该用户是否存在
2、判断vsftpd软件包是否安装,如果没有则自动安装(yum源已配好,rpm -q vsftpd 查询是否安装该软件)
3、判断当前内核主版本是否为2,且次版本是否大于等于6;如果都满足则输出当前内核版本
思路:
1. 先查看内核的版本号	uname -r
2. 先将内核的版本号保存到一个变量里,然后再根据需求截取出该变量的一部分:主版本和次版本
3. 根据需求进步判断
3.10.0-1160.119.1.el7.x86_64
主版本号.次版本号.修订号-补丁级别-内核类型4、判断ftp服务是否已启动,如果已启动输出以下信息:
vsftpd已启动,vsftpd监听的端口是: vsftpd的进程PID是:
#!/bin/bash
read -p "请你输入要判断的服务名称:" process
systemctl status $process &> /dev/null
statu1=$?
statu2=$?
if [ $statu1 -eq 0 ];thenecho "$process 已经启动"
fi
if [ $statu1 -ne 0 ];thenecho "$process 未启动,正在启动中......"systemctl start $process &> /dev/nullstatu2=$?[ $statu2 -eq 0 ] && echo "$process 启动成功!" || echo "$process 启动异常... 请进行排查!"
fi
if [ "$statu1" -eq 0 -o "$statu2" -eq 0 ];thenport=`netstat -naltp| grep nginx |head -1| cut -d: -f2 | cut -d' ' -f1 `echo "$process 监听的端口是:$port,进程号是:$(pidof $process) "
fi8、循环语句
8.1. for循环
8.1.1 语法结构
-  列表循环
列表for循环:用于将一组命令执行已知的次数,下面给出了for循环语句的基本格式:
for variable in {list}docommand command…done
或者
for variable in a b cdocommandcommanddone
语法结构举例说明:
for i in {1..9};do echo $i; done
for var in 1 2 3 4 5;do echo $var;done
for var in `seq 10`;do echo $var;done
for var in $(seq 10);do echo $var;done
for var in {0..10..2};do echo $var;done
for var in {10..1};do echo $var;done
for var in {10..1..-2};do echo $var;done
for var in `seq 10 -2 1`;do echo $var;done-  不带列表循环
不带列表的for循环执行时由用户指定参数和参数的个数,下面给出了不带列表的for循环的基本格式:
for variabledocommand command…done
语法结构举例说明:
#!/bin/bash
for vardoecho $vardone
echo "脚本后面有$#个参数"
-  类C风格的for循环
for(( expr1;expr2;expr3 ))docommandcommand…done
expr1:定义变量并赋初值
expr2:决定是否进行循环(条件)
expr3:决定循环变量如何改变,决定循环什么时候退出语法结构举例说明:
 for ((i=1;i<=9;i++));do echo $i; done8.1.2 举例说明
**例1:**计算1到100的奇数之和,方法不止一种
思路:
1. 定义一个变量来保存奇数的和	sum=0
2. 找出1-100的奇数,保存到另一个变量里  i
3. 从1-100中找出奇数后,再相加,然后将和赋值给sum变量
4. 遍历完毕后,将sum的值打印出来# 非脚本
for ((i=1;i<=100;i+=2));do let sum+=i;done ;echo $sum;unset sum
循环控制:
循环体: do…done之间的内容
- continue:继续;表示循环体内下面的代码不执行,重新开始下一次循环
- break:打断;马上停止执行本次循环,执行循环体后面的代码
- exit:表示直接跳出程序
# 区分下面两个的输出结果的不同之处
for ((i=1;i<5;i+=1));do if [ $i -eq 2 ];then continue;else echo $i;fi;done
for ((i=1;i<5;i+=1));do if [ $i -eq 2 ];then break;else echo $i;fi;doneeg:输入一个正整数,判断是否为质数(素数) 质数:只能被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
#!/bin/bash
read -p "请你输入要判断的数字:" num
[ "$num" -eq 1 ] && echo "$num 不是质数" && exit
[ "$num" -eq 2 ] && echo "$num 是质数" && exit
for ((i=2;i<"$num";i+=1))
doif [ $(($num%i)) -eq 0 ];thenecho "$num 不是质数" && exitfi
done
[ $num -gt 0 ] && echo "$num 是质数" || echo "$num 不是质数"bash -x adj.sh
注: -x 是检查语法是否有问题eg:批量加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}
douseradd -G class u$iecho 123|passwd --stdin u$i
done8.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++))
douseradd -d /rhome/stu$i stu$iecho 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++))
doping -c1 $ip.$i &>/dev/nullif [ $? -eq 0 ];thenecho "$ip.$i is ok" >> /tmp/ip_up.txtelseecho "$ip.$i is down" >> /tmp/ip_down.txtfi或者[ $? -eq 0 ] && echo "$ip.$i is ok" >> /tmp/ip_up.txt || echo "$ip.$i is down" >> /tmp/ip_down.txt
done# time ./ping.sh         
real    0m24.129s
user    0m0.006s
sys     0m0.005s
并行执行:
{程序}&表示将程序放到后台并行执行,如果需要等待程序执行完毕再进行下面内容,需要加wait- 输入一个年份,判断是否是润年(能被4整除但不能被100整除,或能被400整除的年份即为闰年。)
#!/bin/bash
read -p "请你输入要判断的年份:" year
if [ $[$year%4] -eq 0 -a $[$year%100] -ne 0 ];thenecho "$year 是闰年"
elif [ $[$year%400] -eq 0 ];thenecho "$year 是闰年"
elseecho "$year 是平年"
fi
8.2. while循环
特点:条件为真就进入循环;条件为假就退出循环
8.2.1 语法结构
while 表达式docommand...donewhile  [ 1 -eq 1 ] 或者 (( 1 < 2 ))docommandcommand...done
=========================================================
打印1-5数字#  i=1;while [ $i -lt 5 ];do echo $i;let i++;done8.2.2 举例说明
需求:用while循环计算1-50的偶数和
#!/bin/bash
#定义变量
sum=0
i=2
#循环打印1-50的偶数和并且计算后重新赋值给sum
while [ $i -le 50 ]
dolet sum=sum+ilet i+=2
done
#打印sum的值
echo "1-50的偶数和为:$sum"# 非脚本
# i=2;sum=0;while ((i<=50));do let sum+=i;let i+=2;done;echo $sum;unset  i sum8.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
dordate -s $ntp-server &>/dev/nullif [ $? -ne 0 ];thenecho "system date failed" |mail -s 'check system date'  root@localhost	elselet count++if [ $[$count%100] -eq 0 ];thenecho "system date successfull" |mail -s 'check system date'  root@localhost && count=0fifi
sleep 3
done以上脚本还有更多的写法,自己完成8.3. until循环
8.3.1 语法结构
特点:条件为假就进入循环;条件为真就退出循环(直到什么条件时就结束循环)
until expression   [ 1 -eq 1 ]  或 (( 1 == 1 ))docommandcommand...done# 打印1-4
# i=1;until ((i>=5));do echo $i;let i++;done;unset i;8.3.2 举例说明
使用until语句批量创建10个用户,要求stu1—stu5用户的UID分别为1001—1005;stu6~stu10用户的家目录分别在/rhome/stu6—/rhome/stu10
#!/bin/bash
i=1
until [ $i -gt 10 ]
doif [ $i -le 5 ];thenuseradd -u $[1000+$i] stu$i && echo 123|passwd --stdin stu$ielse[ ! -d /rhome ] && mkdir /rhomeuseradd -d /rhome/stu$i stu$i && echo 123|passwd --stdin stu$i		fi
let i++
done
练习题目:
- 判断/tmp/run目录是否存在,如果不存在就建立,如果存在就删除目录里所有文件
- 输入一个路径,判断路径是否存在,而且输出是文件还是目录,如果是链接文件,还得输出是 有效的连接还是无效的连接
- 交互模式要求输入一个ip,然后脚本判断这个IP 对应的主机是否 能ping 通,输出结果类似于: Server 10.1.1.20 is Down! 最后要求把结果邮件到本地管理员root@localhost mail01@localhost
- 写一个脚本/home/program,要求当给脚本输入参数hello时,脚本返回world,给脚本输入参数world时,脚本返回hello。而脚本没有参数或者参数错误时,屏幕上输出“usage:/home/program hello or world”
- 写一个脚本自动搭建nfs服务
补充:随机数
bash默认有一个$RANDOM的变量		
默认是0~32767。使用set |grep RANDOM	查看上一次产生的随机数
echo $RANDOM产生0~i之间的随机数
echo $[$RANDOM%(i+1)]产生a-b之内的随机数
echo $[$RANDOM%(b-a+1)+a]
eg :产生三位数的随机数 100-999
echo $[$RANDOM%900+100]
案例:写一个脚本,产生一个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}
don1=$[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++))
don1=$[$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 ]
don1=$[$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.txtlet i++
done#!/bin/bash
for i in {1..1000}
don1=$[$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++))
don1=$[$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
don1=$[$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 ];thenbreakfi
done
在上面的1000个手机号里抽奖5个幸运观众,显示出这5个幸运观众。但只显示头3个数和尾号的4个数,中间的都用*代替
思路:
- 确定幸运观众所在的行 随机生成 RANDOM $[RANDOM%1000+1]
- 将电话号码提取出来 head 和 tail
- 显示前3个和后4个数到屏幕 最后将电话号码输出到屏幕 echo ${电话号码部分}
#!/bin/bash
file=/shell04/phonenum.txt
for i in {1..5}
dofile_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++))
donum=`wc -l phonenum.txt |cut -d' ' -f1`line=`echo $[$RANDOM%$num+1]`luck=`head -$line $phone |tail -1`sed -i "/$luck/d" $phoneecho "幸运观众是:139****${luck:7:4}"
done
- 批量创建5个用户,每个用户的密码为一个随机数
思路:循环5次创建用户;产生一个密码文件来保存用户的随机密码;从密码文件中取出随机密码赋值给用户
# pwgen工具产生随机密码: # 参数 -c 强制包含至少一个大写字母,pwgen -c 12 5(生成 5 个 12 位密码,含大写) -n 强制包含至少一个数字,pwgen -n 10 3(3 个 10 位密码,含数字) -y 强制包含至少一个特殊符号(如!@#$%),pwgen -y 12 2(2 个 12 位密码,含特殊符号) -s 生成完全随机的密码(无规律,安全性最高,但难记忆),pwgen -s 16 1(1 个 16 位纯随机密码) -B 禁止密码包含容易混淆的字符(如 O/0、I/l、1),pwgen -B 12 4(避免混淆字符,适合视觉识别) -1 让密码每行显示 1 个(默认多行排列,-1 适合单密码场景),pwgen -1 -cny 14 1(1 个 14 位复杂密码,单行显示) -v 显示密码生成的详细信息(如字符类型统计)
8.4、嵌套循环
一个循环体内又包含另一个完整的循环结构,称为循环的嵌套。在外部循环的每次执行过程中都会触发内部循环,直至内部完成一次循环,才接着执行下一次的外部循环。for循环、while循环和until循环可以相互嵌套。
eg1::打印九九乘法表
for ((i=1;i<=9;i++))
dofor ((j=1;j<=$i;j+=1))doecho -en "$j*$i=$(($i*$j))\t"done
echo
done
# -e 启用转义字符解析
# -n 取消末尾自动换行
补充扩展:expect
- expect 自动应答 tcl语言
需求1:A远程登录到server上什么都不做
#!/usr/bin/expect
# 开启一个程序
spawn ssh root@192.168.177.92
# 捕获相关内容
expect {"(yes/no)?" { send "yes\r";exp_continue }"password:" { send "123456\r" }
}
interact  ;# 进入交互模式,允许用户手动输入命令(脚本不自动退出)# 注意 和shell脚本执行方式略有差异:
# ./expect1.sh
# /shell04/expect1.sh
# expect -f expect1.sh# 使用位置参数
#!/usr/bin/expect
set ip [ lindex $argv 0 ] # tcl 语言 从脚本的命令行参数中获取第1个参数,并赋值给变量 ip
set pass [ lindex $argv 1 ] # 从脚本的命令行参数中获取第2个参数,并赋值给变量 pass
set timeout 5需求2:A远程登录到server上操作
# 1.sh
#!/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# 2.sh
#!/usr/bin/expect  ;# 必须添加这一行,声明为 expect 脚本
set ip "192.168.177.92"
set pass "123456"  ;# 假设密码是 123456,根据实际情况修改
set timeout 10spawn ssh root@$ipexpect {# 处理首次登录的确认提示"(yes/no)?" {send "yes\r";exp_continue  ;# 继续等待后续提示}# 匹配密码提示(关键:确保与实际提示一致)"password:" {send "$pass\r"  ;# 发送密码(注意:这里不要加注释)}
}interact  # 登录成功后进入交互模式**需求3:**shell脚本和expect结合使用,在多台服务器上创建1个用户
# cat ip.txt 
10.1.1.1 123456
10.1.1.2 1234561. 循环
2. 登录远程主机——>ssh——>从ip.txt文件里获取IP和密码分别赋值给两个变量
3. 使用expect程序来解决交互问题#!/bin/bash
# 循环在指定的服务器上创建用户和文件
while read ip pass
do/usr/bin/expect <<-END &> /dev/nullspawn ssh root@$ipexpect {"yes/no" { send "yes\r";exp_continue }"password:" { send "$pass\r" }}expect "#" { send "useradd yy1;rm -rf /tmp/*;exit\r" }expect eofEND
done < ip.txt#!/bin/bash
cat ip.txt|while read ip pass
do{/usr/bin/expect <<-HOUspawn ssh root@$ipexpect {"yes/no" { send "yes\r";exp_continue }"password:" { send "$pass\r" }}expect "#"send "hostname\r"send "exit\r"expect eofHOU}&
done
wait
echo "user is ok...."或者
#!/bin/bash
while read ip pass
do{/usr/bin/expect <<-HOUspawn ssh root@$ipexpect {"yes/no" { send "yes\r";exp_continue }"password:" { send "$pass\r" }}expect "#"send "hostname\r"send "exit\r"expect eofHOU}&
done < ip.txt
wait
echo "user is ok...."
案例:写一个脚本,将跳板机上yunwei用户的公钥推送到局域网内可以ping通的所有机器上
说明:主机和密码文件已经提供
10.1.1.1:123456
10.1.1.2:123456
最终实现
环境准备: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/nullif [ $? -eq 0 ];thenecho $ip >> ~/ip_up.txt/usr/bin/expect <<-END &> /dev/nullspawn ssh-copy-id root@$ipexpect {"yes/no" { send "yes\r";exp_continue }"password:" { send "$pass\r" }}expect eofENDfi
}&
done
wait
echo "公钥已经推送完毕,正在测试...."
#测试验证
remote_ip=`tail -1 ~/ip_up.txt`
ssh root@$remote_ip hostname &>/dev/null
test $? -eq 0 && echo "公钥成功推送完毕"案例:写一个脚本,统计web服务的不同连接状态个数
#!/bin/bash
#count_http_80_state
#统计每个状态的个数
declare -A array1
states=`ss -ant|grep 80|cut -d' ' -f1`
for i in $states
dolet array1[$i]++
done
#通过遍历数组里的索引和元素打印出来
for j in ${!array1[@]}
doecho $j:${array1[$j]}
done练习:
- 将/etc/passwd里的用户名分类,分为管理员用户,系统用户,普通用户。
- 写一个倒计时脚本,要求显示离2019年1月1日(元旦)的凌晨0点,还有多少天,多少时,多少分,多少秒。
- 写一个脚本把一个目录内的所有空文件都删除,最后输出删除的文件的个数。
9、case语句
case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相 匹配的命令。
case var in            # 定义变量;var代表是变量名
pattern 1)             # 模式1;用 | 分割多个模式,相当于orcommand1           # 需要执行的语句;;                 # 两个分号代表命令结束
pattern 2)command2;;
pattern 3)command3;;*)           # default,不满足以上模式,默认执行*)下面的语句command4;;
esac				   # esac表示case语句结束(case 倒置)
案例:脚本提示让用户输入需要管理的服务名,然后提示用户需要对服务做什么操作,如启动,关闭,重启等
#!/bin/bash
read -p "请输入需要管理的服务名称(vsftpd):" service
case $service invsftpd)read -p "请输入要操作的动作:" actioncase $action instart|S)service vsftpd start;;stop|P)service vsftpd stop;;reload|restart|R)service vsftpd reload;;	esac;;httpd)echo "apache is running...";;*)echo "请输入需要管理的服务名称(vsftpd):";;
esac		案例:模拟一个多任务维护界面。当执行程序时先显示总菜单,然后进行选择后做相应维护监控操作。
#!/bin/bash
menu(){
cat <<-ENDh       显示帮助f       显示磁盘分区d       显示磁盘挂载m       查看内存使用u       查看系统负载q       退出END
}
menu
while true
doread -p "请输入你要选择的选项【h for help】:" optioncase $option inh)menu;;f)lsblk;;d)df -h;;m)free -mh;;u)uptime;;q)exit;;esacdone10、函数
shell中允许将一组命令集合或语句形成一段可用代码,这些代码块称为shell函数。给这段代码起个名字称为函数名,后续可以直接调用该段代码的功能。
10.1 函数定义
函数名()
{函数体(一堆命令的集合,来实现某个功能)   
}function 函数名()
{函数体(一堆命令的集合,来实现某个功能)  
}函数中return说明:
1.return可以结束一个函数,类似于前面讲的循环控制语句break(结束当前循环,执行循环体后面的代码)
2.return默认返回函数中最后一个命令的退出状态,也可以给定参数值,该参数值的范围是0-256之间。
3.如果没有return命令,函数将返回最后一个Shell的退出值。
10.2 函数的调用
- 当前命令行调用
# cat fun1.sh 
#!/bin/bash
hello(){
echo "hello lilei $1"
hostname
}
menu(){
cat <<-EOF
1. mysql
2. web
3. app
4. exit
EOF
}# source fun1.sh 
# . fun1.sh 
# hello 888
# menu
- 定义到用户的环境变量中
/etc/profile	/etc/bashrc		~/.bash_profile	~/.bashrc# 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
fihello(){
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 <<-ENDh	显示命令帮助f	显示磁盘分区d	显示磁盘挂载m	查看内存使用u	查看系统负载q	退出程序END
}
menu		//调用函数本 篇 完 结 … …
持 续 更 新 中 … …

