文章目录
- 一、文本处理工具
- 1. grep(重要)
- 语法和选项
- 2. cut工具(重要)
- 语法和选项
- 3. sort工具
- 语法和选项
- 工具
- 工具
- 工具
- 语法和选项
- 7. paste工具
- 8. tr工具
- 语法和选项
- 二、bash的特性
- 1、命令和文件自动补全
- 2、常见的快捷键
- 3、==常用的通配符(重点)==
- 4、==bash中的引号(重点)==
- 三、SHELL介绍
- 1. shell的种类
- 2. shell脚本的基本写法
- 3. shell脚本的执行方法
- 四、变量的定义
- 1. 变量的定义规则
- 2. 变量的定义方式有哪些?
- (1)直接赋值
- (2)命令执行结果赋值给变量
- (3)交互式定义变量(read)
- (4)定义有类型的变量(==declare==)
- 3. 变量的分类
- (1)本地变量
- (2)环境变量
- (3)全局变量
- (4)系统变量
- 五、简单四则运算
- 六、数组
- 1. 数组定义
- ㈠ 数组分类
- ㈡ 普通数组定义
- ㈢ 数组的读取
- ㈣ 关联数组定义
- ①首先声明关联数组
- ② 数组赋值
- 2. 其他变量定义
- (1)取出一个目录下的目录和文件:`dirname`和 `basename`
- (2)变量"内容"的删除和替换
- 七、条件判断语法结构
- 1. 条件判断语法格式
- 2. 条件判断相关参数
- (1)判断文件类型
- (2)判断文件权限
- (3)判断文件新旧
- (4)判断整数
- (5)==判断字符串==
- (6)==多重条件判断==
- ② 逻辑运算符总结
- 八、流程控制语句
- 1. 基本语法结构
- (1) if结构
- (2) if...else结构
- (3) if...elif...else结构
- 2. 应用案例
- (1)判断两台主机是否ping通
- (2)判断一个进程是否存在
- (3)判断一个服务是否正常
- 九、for循环语句
- 1. for循环语法结构
- (1)列表循环
- (2)不带列表循环
- (3)类C风格的for循环
- 2. 循环控制语句
- 3. 应用案例
- (1)局域网内脚本检查主机网络通讯
- (2)shift 的使用:用户自定义输入数字,然后脚本计算和:
- 4. shell脚本并发
- 十、while循环语句
- 1. while循环语法结构
- 2. 应用案例
- (1)脚本同步系统时间
- 十一、until循环
- 1. until语法结构
- 2. 应用案例
- 十二、随机数
- 1. 如何生成随机数?
- 2. 应用案例
- (1)文件中随机选出5行
- 十三、Here Document
- 1. 使用场景
- 2. 语法格式:
- 3. 变量转义
- 4.
- 十四、expect
- 1. 定义变量
- 2. 使用位置参数
- 3. expect
- 4. 案例
- (1)远程登录到服务器,不进行操作
- (2)远程登录到服务器上操作
- (3)shell脚本和expect结合使用,在多台服务器上创建用户
- 十五、case语句
- 1. 语法结构
- 2. 应用案例
- (1)根据用户需求选择做事
- (2)菜单提示让用户选择需要做的事
- 十六、==函数==
- 1. 什么是函数?
- 2. 如何定义函数?
- 3. 函数如何调用?
- ㈠ 当前终端调用
- ㈡ 定义到用户的环境变量中
- ㈢ 脚本中调用
- 4. 应用案例
- (1)收集用户输入输入信息
- (2)跳板机安全策略
- a. 任务背景
- b. 具体要求
- c. 综合分析
- d. 落地实现
- 十七、正则表达式
- 1. 正则表达式是什么?
- 2. 正则能干什么?
- 3. 正则当中名词解释
- 4. 第一类正则表达式
- ㈠ 正则中普通常用的元字符
- ㈡ 正则中其他常用元字符
- ㈢ 扩展类正则常用元字符
- 5. 第二类正则
- 6. 正则表达式总结
- 十八、正则元字符一栏表
- 十九、案例
- 1. 脚本搭建web服务
- 2. 脚本推送公钥
- ㈠ 具体需求
- ㈡ 案例分析
- ㈢ 落地实现
- ① 代码拆分
- ② 最终实现
- 3. 统计web服务的不同连接状态个数
一、文本处理工具
1. grep(重要)
grep是行过滤工具;用于根据关键字对行进行过滤
语法和选项
> grep [选项] '关键字' 文件名
OPTIONS:
-i: 不区分大小写
-v: 查找不包含指定内容的行,反向选择
-w: 按单词搜索
-o: 打印匹配关键字
-c: 统计匹配到的行数
-n: 显示行号
-r: 逐层遍历目录查找
-A: 显示匹配行及后面多少行
-B: 显示匹配行及前面多少行
-C: 显示匹配行前后多少行
-l:只列出匹配的文件名
-L:列出不匹配的文件名
-e: 使用正则匹配
-E:使用扩展正则匹配
^key:以关键字开头
key$:以关键字结尾
^$:匹配空行
--color=auto :可以将找到的关键词部分加上颜色的显示
小扩展:颜色显示,使用
grep
时无需指定--color
参数即可高亮关键词(别名设置)临时设置: 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 ```
举例说明:
说明:不要直接使用/etc/passwd文件,将其拷贝到/tmp下做实验!
# 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/ 匹配以#号开头的行
# grep -v ^# /etc/vsftpd/ 匹配不以#号开头的行
# grep -A 5 mail passwd 匹配包含mail关键字及其后5行
# grep -B 5 mail passwd 匹配包含mail关键字及其前5行
# grep -C 5 mail passwd 匹配包含mail关键字及其前后5行
2. cut工具(重要)
cut是列截取工具,用于列的截取
语法和选项
语法:
cut 选项 文件名
常见选项:
-c: 以字符为单位进行分割,截取
-d: 自定义分隔符,默认为制表符\t
-f: 与-d一起使用,指定截取哪个区域
举例说明:
# cut -d: -f1 以:冒号分割,截取第1列内容
# cut -d ' ' -f1,6,7 以空格分割,截取第1,6,7列内容
# cut -c4 截取文件中每行第4个字符
# cut -c1-4 截取文件中每行的1-4个字符
# cut -c5- 从第5个字符开始截取后面所有字符
3. sort工具
sort工具用于排序;它将文件的每一行作为一个单位,从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。
语法和选项
-u :去除重复行
-r :降序排列,默认是升序
-o : 将排序结果输出到文件中,类似重定向符号>
-n :以数字排序,默认是按字符排序
-t :分隔符
-k :第N列
-b :忽略前导空格。
-R :随机排序,每次运行的结果均不同
举例说明
# sort -n -t: -k3 按照用户的uid进行升序排列
# sort -nr -t: -k3 按照用户的uid进行降序排列
# sort -n 按照数字排序
# sort -nu 按照数字排序并且去重
# sort -n -o 按照数字排序并将结果重定向到文件
工具
uniq用于去除连续的重复行
常见选项:
-i: 忽略大小写
-c: 统计重复行次数
-d: 只显示重复行
工具
tee工具是从标准输入读取并写入到标准输出和文件,即:双向覆盖重定向(屏幕输出|文本输入)
选项:
-a: 双向追加重定向
# echo hello world|tee file1
工具
diff工具用于逐行比较文件的不同
注意:diff 描述两个文件不同的方式是告诉我们怎样改变第一个文件之后与第二个文件匹配。
语法和选项
语法:
diff [选项] 文件1 文件2
常用选项:
-b: 不检查空格
-B: 不检查空白行
-i: 不检查大小写
-w: 忽略所有的空格
--normal: 正常格式显示(默认)
-c: 上下文格式显示
-u: 合并格式显示
-q: 仅显示有无差异,不显示详细的信息
举例说明:
-
比较两个普通文件异同,文件准备:
> cat file1 aaaa 111 > cat file2 aaa
合并格式显示:
> diff -u file1 file2 # 前两行主要列出需要比较的文件名和文件的时间戳;文件名前面的符号---表示file1,+++表示file2 --- aaa.txt 2021-04-14 10:24:50.000000000 +0800 +++ bbb.txt 2021-04-14 10:25:02.000000000 +0800 @@ -1,2 +1 @@ # -1,2 表示的1-2行; +1 表示的1行; 这两者相比较 -aaaa # -表示删除 -111 +aaa # +表示新增
-
比较两个目录不同
> diff -q dir1 dir2 Only in dir1: file4 Only in dir2: test1
其他小技巧:
有时候我们需要以一个文件为标准,去修改其他文件,并且修改的地方较多时,我们可以通过打补丁的方式完成。
# 1)先找出文件不同,然后输出到一个文件
> diff -uN file1 file2 > file.patch
-u: 合并上下文模式
-N: 将不存在的文件当作空文件
# 2)将不同内容打补丁到文件
> patch file1 file.patch
patching file file1
# 3)测试验证
> diff file1 file2
7. paste工具
paste 工具用于合并两个文件的文件行(file1第一行后面接file2第一行)
常用选项:
-d:自定义间隔符,默认是tab
-s:串行处理,非并行(第一行全第一个文件,第二行全第二个文件)
合并两个文件,将file2追加到file1后面:
> cat file1 file2 > file1 > cat file2 >> file1
8. tr工具
tr
用于字符转换,替换和删除;主要用于删除文件中控制字符或进行字符转换
语法和选项
语法:
用法1:命令的执行结果交给tr处理,其中string1用于查询,string2用于转换处理
> commands|tr 'string1' 'string2'
用法2:tr处理的内容来自文件,记住要使用"<"标准输入
> tr 'string1' 'string2' < filename
用法3:根据选项匹配 string1 进行相应操作,如删除操作
> tr [options] 'string1' < filename
常用选项:
-d 删除字符串1中所有输入字符。
-s 删除所有重复出现字符序列,只保留第一个;即将重复出现字符串压缩为一个字符串 "abcaaa" > "abca"
常匹配字符串:
字符串 | 含义 | 备注 |
---|---|---|
a-z或[:lower:] | 匹配所有小写字母 | 所有大小写和数字[a-zA-Z0-9] |
A-Z或[:upper:] | 匹配所有大写字母 | |
0-9或[:digit:] | 匹配所有数字 | |
[:alnum:] | 匹配所有字母和数字 | |
[:alpha:] | 匹配所有字母 | |
[:blank:] | 所有水平空白 | |
[:punct:] | 匹配所有标点符号 | |
[:space:] | 所有水平或垂直的空格 | |
[:cntrl:] | 所有控制字符 | \f Ctrl-L 走行换页 \n Ctrl-J 换行 \r Ctrl-M 回车 \t Ctrl-I tab键 |
举例说明:
> 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
aaaaaaaaaaaaaaaaaaaa
bbbbbb111111122222222222233333333cccccccc
hello world 888
666
777
999
# tr -d '[:/]' < 删除文件中的:和/
# cat |tr -d '[:/]' 删除文件中的:和/
# tr '[0-9]' '@' < 将文件中的数字替换为@符号
# tr '[a-z]' '[A-Z]' < 将文件中的小写字母替换成大写字母
# tr -s '[a-z]' < 匹配小写字母并将重复的压缩为一个
# tr -s '[a-z0-9]' < 匹配小写字母和数字并将重复的压缩为一个
# tr -d '[:digit:]' < 删除文件中的数字
# tr -d '[:blank:]' < 删除水平空白
# tr -d '[:space:]' < 删除所有水平和垂直空白
二、bash的特性
1、命令和文件自动补全
Tab 只能补全命令和文件 (RHEL6/Centos6)base-complete
2、常见的快捷键
^c 终止前台运行的程序
^z 将前台运行的程序挂起到后台(暂停运行)
^d 退出 等价exit
^l 清屏
^a |home 光标移到命令行的最前端
^e |end 光标移到命令行的后端
^u 删除光标前所有字符
^k 删除光标后所有字符
^r 搜索历史命令
3、常用的通配符(重点)
*: 匹配0或多个任意字符
?: 匹配任意单个字符
[list]: 匹配[list]中的任意单个字符,或者一组单个字符 [a-z]
[!list]: 匹配除list中的任意单个字符
{string1,string2,...}:匹配string1,string2或更多字符串
> touch file{1..3}:创建文件:file1,file2,file3
> touch file{{1..3},5}:创建文件:file1,file2,file3,file5
4、bash中的引号(重点)
-
双引号
""
:会把引号的内容当成整体来看待,允许通过$
符号引用其他变量值 -
单引号
''
:会把引号的内容当成整体来看待,禁止引用其他变量值,shell 中特殊符号都被视为普通字符 -
反撇号
``
: 反撇号和$()
一样,引号或括号里的命令会优先执行,如果存在嵌套,反撇号不能用
> echo "$(hostname)"
server
> echo '$(hostname)'
$(hostname)
> echo $(date +%F)
2018-11-22
> echo `date +%F`
2018-11-22
三、SHELL介绍
简单来说,shell 就是将需要执行的命令保存到文本中,按照顺序执行。它是解释型的,意味着不需要编译。把重复化、复杂化的工作,通过把工作的命令写成脚本,以后仅仅需要执行脚本就能完成这些工作。例如:
- 自动化软件部署:LAMP/LNMP/Tomcat…
- 自动化管理:系统初始化脚本、批量更改主机密码、推送公钥…
- 自动化分析处理:统计网站访问量
- 自动化备份:数据库备份、日志转储…
- 自动化监控脚本
1. shell的种类
> cat /etc/shells
/bin/sh #是bash的一个快捷方式
/bin/bash #bash是大多数Linux默认的shell,包含的功能几乎可以涵盖shell所有的功能
/sbin/nologin #表示非交互,不能登录操作系统
/bin/dash #小巧,高效,功能相比少一些
/bin/csh #具有C语言风格的一种shell,具有许多特性,但也有一些缺陷
/bin/tcsh #是csh的增强版,完全兼容csh
2. shell脚本的基本写法
1)脚本第一行,使用魔法字符 #!
指定解释器【必写】
-
#!/bin/bash
:表示以下内容使用bash
解释器解析
注意:如果直接将解释器路径写死在脚本里,可能在某些系统就会存在找不到解释器的兼容性问题,所以可以使用:
-
#!/bin/env 解释器
(例如:#!/bin/env bash
)自动的在你的用户
PATH
变量中所定义的目录中寻找 解释器 来执行的
2)脚本第二部分,注释(#
)说明,对脚本的基本信息进行描述【可选】
3)脚本第三部分,脚本要实现的具体代码内容
3. shell脚本的执行方法
-
标准脚本执行方法(建议)
#1) 编写shell脚本 > cat first_shell.sh #!/bin/env bash echo "hello world" #2) 脚本增加可执行权限 > chmod +x first_shell.sh #3) 标准方式执行脚本 # 绝对路径执行脚本 > /shell01/first_shell.sh # 相对路径执行脚本 > ./first_shell.sh
注意:标准执行方式脚本必须要有可执行权限。
-
非标准的执行方法(不建议)
- 直接在命令行指定解释器执行
常用选项:
-x: 一般用于排错,查看脚本的执行过程
-n: 用来查看脚本的语法是否有问题
> bash first_shell.sh
> sh first_shell.sh
> bash -x first_shell.sh
+ echo 'hello world'
hello world
- 使用
source
命令读取脚本文件,执行文件里的代码
> source first_shell.sh
hello world
四、变量的定义
> A="hello world" #定义变量A
> echo $A #调用变量A
hello world
> echo ${A} #还可以这样调用,不管你的姿势多优雅,总之要给钱
hello world
> unset A #取消变量
1. 变量的定义规则
- 变量名区分大小写
- 变量名不能有特殊符号
- 变量名不能以数字开头
- 等号两边不能有任何空格
- 变量名尽量做到见名知意(一般变量名使用大写)
2. 变量的定义方式有哪些?
(1)直接赋值
> A=1234567
> echo $A
1234567
> echo ${A:2:4} 表示从A变量中第3个字符开始截取,截取4个字符
3456
$变量名
和 ${变量名}
的异同:
- 相同点:都可以调用变量
- 不同点:
${变量名}
可以只截取变量的一部分,而$变量名
不可以
(2)命令执行结果赋值给变量
> B=`date +%F`
> echo $B
2019-04-16
> C=$(uname -r)
> echo $C
2.6.32-696.el6.x86_64
(3)交互式定义变量(read)
通过终端让用户自己给变量赋值,比较灵活。
语法:read [选项] 变量名
常见选项:
选项 | 释义 |
---|---|
-p prompt | 定义提示用户的信息 |
-n | 定义字符数(限制变量值的长度) |
-s | 不显示(不显示用户输入的内容) |
-t timeout | 定义超时时间,默认单位为秒(限制用户输入变量值的超时时间) |
举例说明:
- 用户自己定义变量值
> read -p "Input your name:" name
Input your name:tom
> echo $name
tom
- 从文件中读取
> cat 1.txt
10.1.1.1 255.255.255.0
> read ip mask < 1.txt
> echo $ip
10.1.1.1
> echo $mask
255.255.255.0
(4)定义有类型的变量(declare)
给变量做一些限制,固定变量的类型,比如:整型、只读
用法:declare 选项 变量名=变量值
常用选项:
选项 | 释义 | 举例 |
---|---|---|
-i | 将变量看成整数 | declare -i A=123 |
-r | 定义只读变量 | declare -r B=hello |
-a | 定义普通数组;查看普通数组 | |
-A | 定义关联数组;查看关联数组 | |
-x | 将变量通过环境导出(定义环境变量) | declare -x AAA=123456 等于 export AAA=123456 |
3. 变量的分类
(1)本地变量
- 本地变量:当前用户自定义的变量。当前进程中有效,其他进程及当前进程的子进程无效。
(2)环境变量
-
环境变量:当前进程有效,并且能够被子进程调用。
-
env
:查看系统的环境变量 -
set
:查询当前用户的所有变量(本地变量与环境变量) -
export 变量名=变量值
:定义环境变量(或者变量名=变量值;export 变量名
)
-
(3)全局变量
-
全局变量:全局所有的用户和程序都能调用,且继承,新建的用户也默认能调用.
-
解读相关配置文件
文件名 | 说明 | 备注 |
---|---|---|
$HOME/.bashrc |
当前用户的bash信息,用户登录时读取 | 定义用户的别名、umask、函数等 |
$HOME/.bash_profile |
当前用户的环境变量,用户登录时读取 | 定义用户的环境变量 |
$HOME/.bash_logout |
当前用户退出当前shell时最后读取 | 定义用户退出时执行的程序等 |
/etc/bashrc |
全局的bash信息,所有用户都生效 | 系统和所有用户都生效,用来定义全局的别名、umask、函数等 |
/etc/profile |
全局环境变量信息 | 系统和所有用户都生效,用来定义全局变量 |
说明:以上文件修改后,都需要重新 source
让其生效或者退出重新登录。
-
用户登录系统读取相关文件的顺序:
/etc/profile
$HOME/.bash_profile
$HOME/.bashrc
/etc/bashrc
$HOME/.bash_logout
(4)系统变量
- 系统变量(内置bash中变量) : shell 本身已经固定好了它的名字和作用.
内置变量 | 含义 |
---|---|
$? |
上一条命令执行后返回的状态;状态值为0表示执行正常,非0表示执行异常或错误 |
$0 | 当前执行的程序或脚本名 |
$# |
脚本后面接的参数的个数 |
$* |
脚本后面所有参数,参数当成一个整体输出,每一个变量参数之间以空格隔开 |
$@ |
脚本后面所有参数,参数是独立的,也是全部输出 |
$1~$9 |
脚本后面的位置参数,$1表示第1个位置参数,依次类推 |
${10}~${n} |
扩展位置参数,第10个位置变量必须用{}大括号括起来(2位数字以上扩起来) |
$$ |
当前所在进程的进程号,如echo $$
|
$! | 后台运行的最后一个进程号 (当前终端) |
!$ | 调用最后一条命令历史中的参数 |
进一步了解 $*
和 $@
的区别:
-
$*
:表示将变量看成一个整体 -
$@
:表示变量是独立的#!/bin/bash for i in "$@" do echo $i done echo "======我是分割线=======" for i in "$*" do echo $i done > bash 3.sh a b c a b c ======我是分割线======= a b c
五、简单四则运算
算术运算:默认情况下,shell 就只能支持简单的整数运算
运算内容:加(+)、减(-)、乘(*)、除(/)、求余数(%)
表达式 | 说明 | 举例 |
---|---|---|
$(( )) | echo $((1+1)) | |
$[ ] | echo $[10-5] | |
expr 表达式 | 用空格隔开每个项; 用反斜杠 \ 放在 shell 特定的字符前面;表达式之间必须加空格 |
expr 10 / 5 expr 10 * 2 |
let 表达式 | 变量计算中不需要加上 $ 来表示变量;表达式中间不能有空格 |
n=1;let n+=1 (n+=1 等价于 n=n+1)let n=n**3(n 的3次方) let n++ |
bc | 计算小数 | echo 1+1.5 | bc 执行 bc 命令进入交互模式:1+1.5 |
六、数组
1. 数组定义
㈠ 数组分类
- 普通数组:只能使用整数作为数组索引(元素的下标)
- 关联数组:可以使用字符串作为数组索引(元素的下标)
㈡ 普通数组定义
- 一次赋予一个值
数组名[索引下标]=值
array[0]=v1
array[1]=v2
array[2]=v3
array[3]=v4
- 一次赋予多个值
数组名=(值1 值2 值3 ...)
array=(var1 var2 var3 var4)
array1=(`cat /etc/passwd`) 将文件中每一行赋值给array1数组
array2=(`ls /root`)
array3=(harry amy jack "zhang san")
array4=(1 2 3 4 "hello world" [10]=linux)
㈢ 数组的读取
${数组名[元素下标]}
echo ${array[0]} 获取数组里第一个元素
echo ${array[*]} 获取数组里的所有元素
echo ${#array[*]} 获取数组里所有元素个数
echo ${!array[@]} 获取数组元素的索引下标
echo ${array[@]:1:2} 访问指定的元素;1代表从下标为1的元素开始获取;2代表获取后面几个元素
查看普通数组信息:
> declare -a
㈣ 关联数组定义
①首先声明关联数组
declare -A 数组名
declare -A asso_array1
declare -A asso_array2
declare -A asso_array3
② 数组赋值
- 一次赋一个值
# 数组名[索引or下标]=变量值
> asso_array1[linux]=one
> asso_array1[java]=two
> asso_array1[php]=three
- 一次赋多个值
> asso_array2=([name1]=harry [name2]=jack [name3]=amy [name4]="Miss Hou")
- 查看关联数组
> 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" )'
- 获取关联数组值
> echo ${asso_array1[linux]}
one
> echo ${asso_array1[php]}
three
> echo ${asso_array1[*]}
three two one
> echo ${!asso_array1[*]}
php java linux
> echo ${#asso_array1[*]}
3
> echo ${#asso_array2[*]}
4
> echo ${!asso_array2[*]}
name3 name2 name1 name4
- 其他定义方式
> declare -A books
> let books[linux]++
> declare -A|grep books
declare -A books='([linux]="1" )'
> let books[linux]++
> declare -A|grep books
declare -A books='([linux]="2" )'
2. 其他变量定义
(1)取出一个目录下的目录和文件:dirname
和 basename
- 取出一个目录下的目录和文件:
dirname
和basename
> A=/root/Desktop/shell/
> echo $A
/root/Desktop/shell/
> dirname $A 取出目录
/root/Desktop/shell
> basename $A 取出文件
(2)变量"内容"的删除和替换
一个“%”代表从右往左删除
两个“%%”代表从右往左去掉最多
一个“#”代表从左往右去掉删除
两个“##”代表从左往右去掉最多
举例说明:
> url=
> echo ${#url} #14 获取变量的长度
> echo ${url#*.} #
> echo ${url##*.} #com
> echo ${url%.*} #
> echo ${url%%.*} #www
/:从左往右第一个
//:替代所有
> echo ${url/ao/AO} #
> echo ${url//ao/AO} #
替代: -
和 :-
、+
和:+
${变量名-新的变量值} 或者 ${变量名=新的变量值}
变量没有被赋值:会使用“新的变量值“ 替代
变量有被赋值(包括空值): 不会被替代
> echo ${abc-123}
> abc=hello
> echo ${abc-444}
> echo $abc
> abc=
> echo ${abc-222}
${变量名:-新的变量值} 或者 ${变量名:=新的变量值}
变量没有被赋值或者赋空值:会使用“新的变量值“ 替代
变量有被赋值: 不会被替代
> echo ${ABC:-123}
> ABC=HELLO
> echo ${ABC:-123}
> ABC=
> echo ${ABC:-123}
${变量名:+新的变量值}
变量没有被赋值:不会使用“新的变量值“ 替代
变量有被赋值(包括空值): 会被替代
> echo ${abc=123}
> echo ${abc:=123}
> unset abc
> echo ${abc:+123}
> abc=hello
> echo ${abc:+123}
123
> abc=
> echo ${abc:+123}
${变量名+新的变量值}
变量没有被赋值或者赋空值:不会使用“新的变量值“ 替代
变量有被赋值: 会被替代
> unset abc
> echo ${abc+123}
> abc=hello
> echo ${abc+123}
123
> abc=
> echo ${abc+123}
123
${变量名?新的变量值}
变量没有被赋值:提示错误信息
变量被赋值(包括空值):不会使用“新的变量值“ 替代
> unset abc
> echo ${abc?123}
-bash: abc: 123
> abc=hello
> echo ${abc?123}
hello
> abc=
> echo ${abc?123}
${变量名:?新的变量值}
变量没有被赋值或者赋空值时:提示错误信息
变量被赋值:不会使用“新的变量值“ 替代
说明:?主要是当变量没有赋值提示错误信息的,没有赋值功能
> unset abc
> echo ${abc:?123}
-bash: abc: 123
> abc=hello
> echo ${abc:?123}
hello
> abc=
> echo ${abc:?123}
-bash: abc: 123
七、条件判断语法结构
1. 条件判断语法格式
- 格式1:
test 条件表达式
- 格式2:
[ 条件表达式 ]
(注意中括号与表达式之间有空格) - 格式3:
[[ 条件表达式 ]]
支持正则(注意中括号与表达式之间有空格)
2. 条件判断相关参数
(1)判断文件类型
判断参数 | 含义 |
---|---|
-e | 判断文件是否存在(任何类型文件) |
-f | 判断文件是否存在并且是一个普通文件 |
-d | 判断文件是否存在并且是一个目录 |
-L | 判断文件是否存在并且是一个软连接文件 |
-b | 判断文件是否存在并且是一个块设备文件 |
-S | 判断文件是否存在并且是一个套接字文件 |
-c | 判断文件是否存在并且是一个字符设备文件 |
-p | 判断文件是否存在并且是一个命名管道文件 |
-s | 判断文件是否存在并且是一个非空文件(有内容) |
举例说明:
test -e file 只要文件存在条件为真
[ -d /shell01/dir1 ] 判断目录是否存在,存在条件为真
[ ! -d /shell01/dir1 ] 判断目录是否存在,不存在条件为真
[[ -f /shell01/ ]] 判断文件是否存在,并且是一个普通的文件
(2)判断文件权限
判断参数 | 含义 |
---|---|
-r | 当前用户对其是否可读 |
-w | 当前用户对其是否可写 |
-x | 当前用户对其是否可执行 |
-u | 是否有suid,高级权限冒险位 |
-g | 是否sgid,高级权限强制位 |
-k | 是否有t位,高级权限粘滞位 |
(3)判断文件新旧
说明:这里的新旧指的是文件的修改时间。
判断参数 | 含义 |
---|---|
file1 -nt file2 | 比较file1是否比file2新 |
file1 -ot file2 | 比较file1是否比file2旧 |
file1 -ef file2 | 比较是否为同一个文件,或者用于判断硬连接,是否指向同一个inode |
(4)判断整数
判断参数 | 含义 |
---|---|
-eq | 相等 |
-ne | 不等 |
-gt | 大于 |
-lt | 小于 |
-ge | 大于等于 |
-le | 小于等于 |
-
数值比较
> [ 1 -eq 2 ];echo $? > [ `id -u` -eq 0 ];echo $? > [ $(id -u) -eq 0 ] && echo this is root
-
类C风格的数值比较
注意:在(( ))中,=表示赋值;==表示判断 > ((1==2));echo $? > ((1<2));echo $? > ((`id -u`==0));echo $?
(5)判断字符串
判断参数 | 含义 |
---|---|
-z | 判断是否为空字符串,字符串长度为0则成立 |
-n | 判断是否为非空字符串,字符串长度不为0则成立 |
string1 = string2 或者 string1 == string2 |
判断字符串是否相等 |
string1 != string2 | 判断字符串是否相不等 |
> a='hello world';b=world
> [ $a = $b ];echo $?
> [ "$a" = "$b" ];echo $?
> [ "$a" == "$b" ];echo $?
> [ "$a" != "$b" ];echo $?
> [ '' = $a ];echo $?
-bash: [: : unary operator expected
2
> [[ '' = $a ]];echo $?
0
(6)多重条件判断
判断符号 | 含义 | 举例 |
---|---|---|
-a 和 && | 逻辑与 | [ $(id -u) -eq 1 -a 1 -ne 0 ] [ 1 -eq 1 ] && [ 1 -ne 0 ] |
-o 和 || | 逻辑或 | [ 1 -eq 1 -o 1 -ne 1 ] |
特别说明:
-
&&
前面的表达式为真,才会执行后面的代码 -
||
前面的表达式为假,才会执行后面的代码 -
;
只用于分割命令或表达式
② 逻辑运算符总结
- 符号
;
和&&
和||
都可以用来分割命令或者表达式 - 分号
;
完全不考虑前面的语句是否正确执行,都会执行;
号后面的内容 -
&&
符号,需要考虑&&
前面的语句的正确性,前面语句正确执行才会执行&&
后的内容;反之亦然 -
||
符号,需要考虑||
前面的语句的非正确性,前面语句执行错误才会执行||
后内容;反之亦然 - 如果
&&
和||
一起出现,按照以上原则,从左往右依次执行
八、流程控制语句
1. 基本语法结构
(1) if结构
if [ condition ];then
command
command
fi
if test 条件;then
命令
fi
if [[ 条件 ]];then
命令
fi
[ 条件 ] && command
(2) if…else结构
if [ condition ];then
command1
else
command2
fi
[ 条件 ] && command1 || command2
(3) if…elif…else结构
if [ condition1 ];then
command1
elif [ condition2 ];then
command2
else
command3
fi
2. 应用案例
(1)判断两台主机是否ping通
判断当前主机是否和远程主机是否ping通
#!/bin/env bash
# 该脚本用于判断当前主机是否和远程指定主机互通
# 交互式定义变量,让用户自己决定ping哪个主机
read -p "请输入你要ping的主机的IP:" ip
# 使用ping程序判断主机是否互通 -c指定ping的次数
ping -c1 $ip &>/dev/null
if [ $? -eq 0 ];then
echo "当前主机和远程主机$ip是互通的"
else
echo "当前主机和远程主机$ip不通的"
fi
# 逻辑运算符
# test $? -eq 0 && echo "当前主机和远程主机$ip是互通的" || echo "当前主机和远程主机$ip不通的"
(2)判断一个进程是否存在
判断web服务器中httpd进程是否存在
#!/bin/env bash
# 判断一个程序(httpd)的进程是否存在
pgrep httpd &>/dev/null
if [ $? -ne 0 ];then
echo "当前httpd进程不存在"
else
echo "当前httpd进程存在"
fi
或者
test $? -eq 0 && echo "当前httpd进程存在" || echo "当前httpd进程不存在"
-
补充命令
pgrep命令:以名称为依据从运行进程队列中查找进程,并显示查找到的进程id 选项 -o:仅显示找到的最小(起始)进程号; -n:仅显示找到的最大(结束)进程号; -l:显示进程名称; -P:指定父进程号;pgrep -p 4764 查看父进程下的子进程id -g:指定进程组; -t:指定开启进程的终端; -u:指定进程的有效用户ID。
(3)判断一个服务是否正常
判断门户网站是否能够正常访问
#!/bin/env bash
# 判断门户网站是否能够正常提供服务
#定义变量
web_server=www.baidu.com
#访问网站
wget -P /shell/ $web_server &>/dev/null
[ $? -eq 0 ] && echo "当前网站服务是ok" && rm -f /shell/index.* || echo "当前网站服务不ok,请立刻处理"
九、for循环语句
1. for循环语法结构
循环体: do…done之间的内容
(1)列表循环
列表 for 循环:用于将一组命令执行**已知的次数**
for variable in {list}
do
command
command
…
done
或者
for variable in a b c
do
command
command
done
- 举例说明
> for var in {1..10};do echo $var;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 {0..10..2};do echo $var;done # {起始..结尾..步长}
> for var in {2..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 # seq 起始 步长 结尾
(2)不带列表循环
不带列表的 for 循环,执行时将用户指定参数和参数的个数当做列表
for variable
do
command
command
…
done
- 举例说明
#!/bin/bash
for var
do
echo $var
done
echo "脚本后面有$#个参数"
(3)类C风格的for循环
# expr1:定义变量并赋初值
# expr2:决定是否进行循环(条件)
# expr3:决定循环变量如何改变,决定循环什么时候退出
for(( expr1;expr2;expr3 ))
do
command
command
…
done
- 举例说明
for (( i=1;i<=5;i++))
do
echo $i
done
2. 循环控制语句
-
continue
:继续;表示循环体内下面的代码不执行,重新开始下一次循环 -
break
:打断;马上停止执行本次循环,执行循环体后面的代码 -
exit
:表示直接跳出程序 -
shift
:使位置参数向左移动,默认移动1位,可以使用shift 2
(参考案例2)
> cat
#!/bin/bash
for i in {1..5}
do
test $i -eq 2 && break || touch /tmp/file$i
done
echo hello
3. 应用案例
(1)局域网内脚本检查主机网络通讯
写一个脚本,局域网内,把能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
> time ./ping.sh
ip is ok....
./ping.sh 0.01s user 0.03s system 0% cpu 1:50.50 total
(2)shift 的使用:用户自定义输入数字,然后脚本计算和:
> cat aa.sh
#!/bin/bash
sum=0
while [ $# -ne 0 ]
do
let sum=$sum+$1
shift
done
echo sum=$sum
> ./aa.sh 1 2 3 4 5
4. shell脚本并发
并行执行:{程序}&
:表示将程序放到后台并行执行,如果需要等待程序执行完毕再进行下面内容,需要加 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...."
> time ./ping.sh
ip is ok....
./ping.sh 0.02s user 0.03s system 0% cpu 11.026 total
十、while循环语句
特点:条件为真就进入循环;条件为假就退出循环
1. while循环语法结构
while 表达式
do
command...
done
# 从文件中获取参数,以空格分隔
while read 参数1 参数2
do
useradd $user
echo $pass|passwd --stdin $user
done < 文件
2. 应用案例
(1)脚本同步系统时间
- 写一个脚本,30秒同步一次系统时间,时间同步服务器10.1.1.1
- 如果同步失败,则进行邮件报警,每次失败都报警
- 同步成功,也进行邮件通知,但是成功100次才通知一次
#!/bin/bash
#定义变量
count=0
ntp_server=10.1.1.1
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
十一、until循环
特点:条件为假就进入循环;条件为真就退出循环
1. until语法结构
until 表达式
do
command
command
...
done
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
#课程目标
- 掌握for循环语句的基本语法结构
- 掌握while和until循环语句的基本语法结构
- 能会使用RANDOM产生随机数
- 理解嵌套循环
十二、随机数
1. 如何生成随机数?
系统变量:RANDOM,默认会产生 0~32767
的随机整数
# 打印一个随机数
> echo $RANDOM
# 查看系统上一次生成的随机数
> set | grep RANDOM
RANDOM=28325
#产生0~100内的随机数
> echo $[$RANDOM%101]
#产生50-100之内的随机数
> echo $[$RANDOM%51+50]
#产生三位数的随机数
> echo $[$RANDOM%900+100]
2. 应用案例
(1)文件中随机选出5行
#!/bin/bash
#定义变量
file=/shell03/
#循环选出5行
for ((i=1;i<=5;i++))
do
#定位随机行号
total=`wc -l $file |cut -d' ' -f1`
line_num=$[RANDOM%$total+1]
#取出行号对应的行
str=`head -$line_num $file|tail -1`
#显示到屏幕
echo $str
#删除已经被抽取的行
#sed -i "/$line_num/d" $file
done
十三、Here Document
参考博客:/liumiaocn/article/details/86715953
1. 使用场景
交互式的命令行:比如 sftp
或者 oracle
的 sqlplus
,或者 mysql
的命令控制台,以 sftp
为例子,当我们输入 sftp 用户名@sftp服务器
登录之后,需要在 sftp>
的提示下进行各种 sftp
命令的操作。
2. 语法格式:
命令 << 分隔串 # 分割串常见的为EOF,但不一定固定为EOF,可以使用开发者自行定义的,比如END
# 缺省方式下第一个分割串(EOF)前后均可有空格或者tab,运行时会自动剔除,不会造成影响
字符串1
…
字符串n
分隔串 # 缺省方式下第二个分割串(EOF)必须顶格写,前后均不可有空格或者tab
# 简单打印
cat << EOF
> hello
> world
> EOF
hello
world
# 内容写入文件
cat << end >
#include <>
void main(){
printf("hello world by using here document\n");
}
end
3. 变量转义
使用 '
或者 "
或者 \
对第一个 EOF
进行封装,可以对命令中的变量转义,使其原样输出
# 变量未转义
> cat << eof
heredoc> aaa
heredoc> $bbb
heredoc> $ccc
heredoc> ddd
heredoc> eof
aaa
ddd
>
# 变量转义后:
> cat << 'eof'
heredoc> aaa
heredoc> $bbb
heredoc> $ccc
heredoc> ddd
heredoc> eof
aaa
$bbb
$ccc
ddd
4. <<- 与 << 的区别
使用 <<-
代替 <<
唯一的作用在于分割串所扩起来的内容和第二个分隔符,顶格的 tab
会被删除
# 使用 <<
> cat << eof
heredoc> 前面是tab
heredoc> 前面是两个空格
heredoc> eof # 当eof前有tab时,无法结束
heredoc> eof # 当eof前有空格时,无法结束
heredoc> eof # eof顶格写,才能结束
前面是tab
前面是两个空格
eof
eof
# 使用 <<-
> cat <<- eof
heredocd> 前面是tab
heredocd> 前面是两个空格
heredocd> eof # 当eof前有空格时,无法结束
heredocd> eof # 当eof前有tab时,tab会被删除,然后结束输入
前面是tab
前面是两个空格
eof
vim
中使用命令:set list
查看文档内的特殊字符
十四、expect
expect
是一个自动化交互套件,主要应用于执行命令和程序时,系统以交互形式要求输入指定字符串,实现交互通信。
expect常用命令总结:
spawn 交互程序开始后面跟命令或者指定程序
expect 获取匹配信息匹配成功则执行expect后面的程序动作
send exp_send 用于发送指定的字符串信息
exp_continue 在expect中多次匹配就需要用到
send_user 用来打印输出 相当于shell中的echo
exit 退出expect脚本
eof expect执行结束 退出
set 定义变量
puts 输出变量
set timeout 设置超时时间
interact 交互,表示接下来让用户自己去和服务器交互
1. 定义变量
格式:set 变量名 变量值
#!/usr/bin/expect
set ip 10.1.1.1
set pass 123456
set timeout 5
2. 使用位置参数
格式:[ lindex $argv 参数下标 ]
#!/usr/bin/expect
set ip [ lindex $argv 0 ]
set pass [ lindex $argv 1 ]
3. expect
expect {
# 当遇到期望值时,发送命令;
# exp_continue 用来表示多次匹配
# 命令输出完后,使用 \r 或者 \n 换行
"期望值1" { send "命令1\r";exp_continue }
"期望值2" { send "命令2\r" }
}
4. 案例
(1)远程登录到服务器,不进行操作
#!/usr/bin/expect
# 开启一个程序
spawn ssh root@10.1.1.1
# 捕获相关内容
expect {
"(yes/no)?" { send "yes\r";exp_continue }
"password:" { send "123456\r" }
}
interact #交互,表示接下来让用户自己去和服务器交互,如果不写,则程序执行完会退出登录的服务器
(2)远程登录到服务器上操作
#!/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. 循环 useradd username
2. 登录远程主机——>ssh——>从文件里获取IP和密码分别赋值给两个变量
3. 使用expect程序来解决交互问题
#!/bin/bash
# 循环在指定的服务器上创建用户和文件
while read ip pass
do
# -END 指定结束标志,当看到END时,表示expect执行完毕,也可以指定其它字符串,-表示删除命令前的 tab
/usr/bin/expect <<-END &>/dev/null
# 注意:需要交互的脚本应放在 expect 中
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
echo "$ip服务器用户创建完毕"
done <
注意:
- 需要交互的脚本应放在
expect
中 - expect 未安装则进行安装
yum install -y expect
十五、case语句
- case语句为多重匹配语句
- 如果匹配成功,执行相匹配的命令
1. 语法结构
说明:pattern表示需要匹配的模式
case var in 定义变量;var代表是变量名
pattern 1) 模式1;用 | 分割多个模式,相当于or
command1 需要执行的语句
;; 两个分号代表命令结束
pattern 2)
command2
;;
pattern 3)
command3
;;
*) default,不满足以上模式,默认执行*)下面的语句
command4
;;
esac esac表示case语句结束
2. 应用案例
(1)根据用户需求选择做事
具体需求:
脚本提示让用户输入需要管理的服务名,然后提示用户需要对服务做什么操作,如启动,关闭等操作
#!/bin/env bash
read -p "请输入你要管理的服务名称(vsftpd):" service
case $service in
vsftpd|ftp)
read -p "请选择你需要做的事情(restart|stop):" action
case $action in
stop|S)
service vsftpd stop &>/dev/null && echo "该$serivce服务已经停止成功"
;;
start)
service vsftpd start &>/dev/null && echo "该$serivce服务已经成功启动"
;;
esac
;;
httpd|apache)
echo "apache hello world"
;;
*)
echo "请输入你要管理的服务名称(vsftpd)"
;;
esac
(2)菜单提示让用户选择需要做的事
具体需求:
模拟一个多任务维护界面;当执行程序时先显示总菜单,然后进行选择后做相应维护监控操作
**********请选择*********
h 显示命令帮助
f 显示磁盘分区
d 显示磁盘挂载
m 查看内存使用
u 查看系统负载
q 退出程序
*************************
思路:
- 菜单打印出来
- 交互式让用户输入操作编号,然后做出相应处理
落地实现:
- 菜单打印(分解动作)
#!/bin/env bash
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. 什么是函数?
- shell中允许将一组命令集合或语句形成一段可用代码,这些代码块称为shell函数
- 给这段代码起个名字称为函数名,后续可以直接调用该段代码的功能
2. 如何定义函数?
方法1:
函数名()
{
函数体(一堆命令的集合,来实现某个功能)
}
方法2:
function 函数名()
{
函数体(一堆命令的集合,来实现某个功能)
}
函数中return说明:
- return可以结束一个函数。类似于循环控制语句break(结束当前循环,执行循环体后面的代码)。
- return默认返回函数中最后一个命令状态值,也可以给定参数值,范围是0-256之间。
- 如果没有return命令,函数将返回最后一个指令的退出状态值。
3. 函数如何调用?
㈠ 当前终端调用
> 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
hello lilei 888
MissHou.itcast.cc
> menu
1. mysql
2. web
3. app
4. exit
㈡ 定义到用户的环境变量中
> vim ~/.bashrc
文件中增加如下内容:
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 //调用函数
4. 应用案例
(1)收集用户输入输入信息
具体需求:
- 写一个脚本收集用户输入的基本信息(姓名,性别,年龄),如不输入一直提示输入
- 最后根据用户的信息输出相对应的内容
思路:
-
交互式定义多个变量来保存用户信息 姓名、性别、年龄
-
如果不输一直提示输入
- 循环直到输入字符串不为空 while 判断输入字符串是否为空
- 每个信息都必须不能为空,该功能可以定义为一个函数,方便下面脚本调用
-
根据用户输入信息做出匹配判断
代码实现:
#!/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" var
if [ -z $var ];then
fun $1
else
echo $var
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
扩展延伸:
描述以下代码含义:
:()
{
:|:&
}
:
(2)跳板机安全策略
a. 任务背景
现有的跳板机虽然实现了统一入口来访问生产服务器,yunwei用户权限太大可以操作跳板机上的所有目录文件,存在数据被误删的安全隐患,所以希望你做一些安全策略来保证跳板机的正常使用。
b. 具体要求
- 只允许yunwei用户通过跳板机远程连接后台的应用服务器做一些维护操作
- 公司运维人员远程通过yunwei用户连接跳板机时,跳出以下菜单供选择:
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
- 当用户选择相应主机后,直接免密码登录成功
- 如果用户不输入一直提示用户输入,直到用户选择退出
c. 综合分析
- 将脚本放到yunwei用户家目录里的.bashrc文件里(/shell05/)
- 将菜单定义为一个函数[打印菜单],方便后面调用
- 用case语句来实现用户的选择【交互式定义变量】
- 当用户选择了某一台服务器后,进一步询问用户需要做的事情 case…esac 交互式定义变量
- 使用循环来实现用户不选择一直让其选择
- 限制用户退出后直接关闭终端 exit
d. 落地实现
#!/bin/bash
# jumper-server
# 定义菜单打印功能的函数
menu()
{
cat <<-EOF
欢迎使用Jumper-server,请选择你要操作的主机:
1. DB1-Master
2. DB2-Slave
3. Web1
4. Web2
h. help
q. exit
EOF
}
# 屏蔽以下信号
trap '' 1 2 3 19
# 调用函数来打印菜单
menu
#循环等待用户选择
while true
do
# 菜单选择,case...esac语句
read -p "请选择你要访问的主机:" host
case $host in
1)
ssh root@10.1.1.1
;;
2)
ssh root@10.1.1.2
;;
3)
ssh root@10.1.1.3
;;
h)
clear;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
自己完善功能:
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. 正则能干什么?
- 匹配邮箱、匹配身份证号码、手机号、银行卡号等
- 匹配某些特定字符串,做特定处理等等
3. 正则当中名词解释
-
元字符
指那些在正则表达式中具有特殊意义的专用字符,如:点(.) 星(*) 问号(?)等
-
前导字符
位于元字符前面的字符. ab**c*** aoo**o.**
4. 第一类正则表达式
㈠ 正则中普通常用的元字符
元字符 | 功能 | 备注 |
---|---|---|
. | 匹配除了换行符以外的任意单个字符 | |
* | 前导字符出现0次或连续多次 | |
.* | 任意长度字符 | ab.* |
^ | 行首(以…开头) | ^root |
$ | 行尾(以…结尾) | bash$ |
^$ | 空行 | |
[] | 匹配括号里任意单个字符或一组单个字符 | [abc] |
[^] | 匹配不包含括号里任一单个字符或一组单个字符 | [^abc] |
^[] |
匹配以括号里任意单个字符或一组单个字符开头 | ^[abc] |
\^[\^] |
匹配不以括号里任意单个字符或一组单个字符开头 | \^\[^abc] |
- 示例文本
# cat
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
- 举例说明
㈡ 正则中其他常用元字符
元字符 | 功能 | 备注 |
---|---|---|
< | 取单词的头 | |
> | 取单词的尾 | |
< > | 精确匹配 | |
{n} | 匹配前导字符连续出现n次 | |
{n,} | 匹配前导字符至少出现n次 | |
{n,m} | 匹配前导字符出现n次与m次之间 | |
( ) | 保存被匹配的字符 | |
\d | 匹配数字(grep -P) | [0-9] |
\w | 匹配字母数字下划线(grep -P) | [a-zA-Z0-9_] |
\s | 匹配空格、制表符、换页符(grep -P) | [\t\r\n] |
举例说明:
需求:将10.1.1.1替换成10.1.1.254
1)vim编辑器支持正则表达式
> vim 1.txt
:%s#\(10.1.1\).1#\1.254#g
:%s/\(10.1.1\).1/\1.254/g
2)sed支持正则表达式【后面学】
> sed -n 's#\(10.1.1\).1#\1.254#p' 1.txt
10.1.1.254
说明:
找出含有10.1.1的行,同时保留10.1.1并标记为标签1,之后可以使用\1来引用它。
最多可以定义9个标签,从左边开始编号,最左边的是第一个。
需求:将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
> sed -n 's/helloworld yourself/hellolilei myself/p' 1.txt
hellolilei myself
> sed -n 's/\(hello\)world your\(self\)/\1lilei my\2/p' 1.txt
hellolilei myself
Perl内置正则:
\d 匹配数字 [0-9]
\w 匹配字母数字下划线[a-zA-Z0-9_]
\s 匹配空格、制表符、换页符[\t\r\n]
> grep -P '\d' 1.txt
> grep -P '\w' 2.txt
> grep -P '\s' 3.txt
㈢ 扩展类正则常用元字符
-
grep:必须加 -E 或者使用
egrep
-
sed:必须加 -r
扩展元字符 | 功能 | 备注 |
---|---|---|
+ | 匹配一个或多个前导字符 | bo+ 匹配boo、 bo |
? | 匹配零个或一个前导字符 | bo? 匹配b、 bo |
| | 或 | 匹配a或b |
() | 组字符(看成整体) | (my|your)self:表示匹配myself或匹配yourself |
{n} | 前导字符重复n次 | |
{n,} | 前导字符重复至少n次 | |
{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'
> grep -E 'o?gle'
> egrep 'go{2,}' 1.txt
> egrep '(my|your)self' 1.txt
使用正则过滤出文件中的IP地址:
> grep '[0-9]\{2\}\.[0-9]\{1\}\.[0-9]\{1\}\.[0-9]\{1\}' 1.txt
10.1.1.1
> grep '[0-9]{2}\.[0-9]{1}\.[0-9]{1}\.[0-9]{1}' 1.txt
> grep -E '[0-9]{2}\.[0-9]{1}\.[0-9]{1}\.[0-9]{1}' 1.txt
10.1.1.1
> grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' 1.txt
10.1.1.1
> grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}' 1.txt
10.1.1.1
5. 第二类正则
表达式 | 功能 | 示例 |
---|---|---|
[:alnum:] | 字母与数字字符 | [[:alnum:]]+ |
[:alpha:] | 字母字符(包括大小写字母) | [[:alpha:]]{4} |
[:blank:] | 空格与制表符 | [[:blank:]]* |
[:digit:] | 数字 | [[:digit:]]? |
[:lower:] | 小写字母 | [[:lower:]]{4,} |
[:upper:] | 大写字母 | [[:upper:]]+ |
[:punct:] | 标点符号 | [[:punct:]] |
[:space:] | 包括换行符,回车等在内的所有空白 | [[:space:]]+ |
> grep -E '^[[:digit:]]+' 1.txt
> grep -E '^[^[:digit:]]+' 1.txt
> grep -E '[[:lower:]]{4,}' 1.txt
6. 正则表达式总结
把握一个原则,让你轻松搞定可恶的正则符号:
- 我要找什么?
- 找数字 [0-9]
- 找字母 [a-zA-Z]
- 找标点符号 [[:punct:]]
- 我要如何找?看心情找
- 以什么为首 ^key
- 以什么结尾 key$
- 包含什么或不包含什么 [abc] ^[abc] [^abc] ^[^abc]
- 我要找多少呀?
- 找前导字符出现0次或连续多次 ab==*==
- 找任意单个(一次)字符 ab==.==
- 找任意字符 ab==.*==
- 找前导字符连续出现几次 {n} {n,m} {n,}
- 找前导字符出现1次或多次 go==+==
- 找前到字符出现0次或1次 go==?==
十八、正则元字符一栏表
元字符:在正则中,具有特殊意义的专用字符,如: 星号(*)、加号(+)等
前导字符:元字符前面的字符叫前导字符
元字符 | 功能 | 示例 |
---|---|---|
* | 前导字符出现0次或者连续多次 | ab* abbbb |
. | 除了换行符以外,任意单个字符 | ab. ab8 abu |
.* | 任意长度的字符 | ab.* adfdfdf |
[] | 括号里的任意单个字符或一组单个字符 | [abc][0-9][a-z] |
[^] | 不匹配括号里的任意单个字符或一组单个字符 | [^abc] |
^[] | 匹配以括号里的任意单个字符开头 | ^[abc] |
^[^] | 不匹配以括号里的任意单个字符开头 | |
^ | 行的开头 | ^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 | 匹配字母数字下划线 |
十九、案例
1. 脚本搭建web服务
要求如下:
- 用户输入web服务器的IP、域名以及数据根目录
- 如果用户不输入则一直提示输入,直到输入为止
- 当访问时可以访问到数据根目录里的首页文件“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 []:")
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
2. 脚本推送公钥
㈠ 具体需求
写一个脚本,将跳板机上yunwei用户的公钥推送到局域网内可以ping通的所有机器上
说明:主机和密码文件已经提供
10.1.1.1:123456
10.1.1.2:123456
㈡ 案例分析
- 跳板机上的yunwei用户生成秘钥对
- 判断账号是否存在 (id yunwei)
- 判断该用户是否有密钥对文件 [ -f xxx ]
- 判断expect程序是否安装
- 判断局域网内主机是否ping通(循环判断|for while until)
- 循环判断 for while
- 循环体do…done ping 主机 如果ping通 调用expect程序自动应答推送公钥
- 测试验证是否免密登录成功
- 检查服务器上ssh服务端口号
- 把公钥推送成功的主机的信息保存到文件
- 关闭防火墙和selinux
- 日志记录
- 推送公钥需要自动应答expect
㈢ 落地实现
① 代码拆分
功能1:管理员root创建yunwei用户和安装expect软件包
#!/bin/env bash
# 实现批量推送公钥
# 判断jumper上的yunwei账号是否存在
{
id yunwei
[ $? -ne 0 ] && useradd yunwei && echo 123|passwd --stdin yunwei
} &>/dev/null
#判断expect程序是否安装
rpm -q expect
[ $? -ne 0 ] && yum -y install expect && echo "expect软件已经成功安装"
功能2:判断主机是否ping通并且yunwei用户推送公钥
#!/bin/env bash
# 判断yunwei用户密钥对是否存在
home_dir=/home/yunwei
[ ! -f $home_dir/.ssh/id_rsa.pub ] && ssh-keygen -P '' -f id_rsa &>/dev/null
#循环检查主机的网络并且进行公钥推送
ip_txt=$home_dir/ip.txt
for i in `cat $ip_txt`
do
ip=`echo $i|cut -d: -f1`
pass=`echo $i|cut -d: -f2`
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\n";exp_continue }
"password:" { send "$pass\n" }
}
expect eof
END
else
echo $ip >> $home_dir/ip_down.txt
fi
done
# 测试验证
remote_ip=`head -1 ~/ip_up.txt`
ssh root@$remote_ip hostname
[ $? -eq 0 ] && echo "公钥推送成功"
② 最终实现
- 环境准备
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服务的不同连接状态个数
写一个脚本,统计web服务的不同连接状态个数
- 找出查看网站连接状态的命令
ss -natp|grep :80
- 如何统计不同的状态 循环去统计,需要计算
#!/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