Shell脚本介绍与变量
# Shell脚本介绍
# Shell简介
Shell是一个命令解释器,他在操作系统的最外层,负责翻译用户的命令给内核,并返回结果屏幕。他可以以两种方式执行命令:交互式和非交互式。
- 交互式:日常使用最多的一种模式,比如登录终端、执行命令、返回结果等等,交互方式进行会话。
- 非交互式:Shell不与用户直接交互,而是读取某个文件中的Shell命令进行执行,直到结束。
# Shell脚本简介
Shell脚本实际就是Shell命令的集合,将许多可执行的命令放在文本中成为Shell脚本,包括各种条件表达式、判断语句、数组、开头格式等。
学习Shell脚本一般需要掌握:环境变量、条件表达式、if判断、for循环、while循环、case语句、数组、流程控制等语句。
# Shell脚本的作用
Shell的主要作用是简化操作步骤,减少重复工作,减少系统故障,提高效率。
- 基础配置
- 系统初始化、系统更新、内核调整、网络、时区、SSH优化、加大文件描述符等。
- 安装程序
- 部署Nginx、MySQL、PHP、Sersync、NFS等各种服务。
- 配置变更
- nginx 、php、mysql、nfs、rsync等服务配置文件的修改。
- 业务部署
- Shell配合git、jenkins实现自动化部署php、java代码,或者配合link实现代码秒级回滚等操作。
- 日常备份
- 对MySQL、重要数据等,进行每天的全备与增量备份。
- 信息采集
- Zabbix+Shell实现对硬件、系统、服务、网络、等等的信息采集。
- 日志分析
- 取值 -> 排序 -> 去重 -> 统计 -> 分析。
- 服务扩容
- 监控服务器集群的CPU,如CPU负载均衡持续80% + 则触发动作(脚本)。
- 脚本调用云厂商提供的API启动云主机 —> 初始化环境 —> 加入集群 —> 对外提供。
- 服务缩容
- 监控服务器集群的CPU,如CPU负载均衡低于20% -> 检测当前有多少web节点 -> 判断是否超过预设 -> 缩减到对应的预设状态 -> 变更负载的配置。
# Shell脚本规范
脚本存放在统一的目录,比如:
/data/scripts/
。脚本开头第一行必须是
#!/bin/bash
或#!/bin/sh
。- 绝大部分系统会将
/bin/sh
软链接到/bin/bash
,因为bash比sh的功能更加全面。
- 绝大部分系统会将
脚本必须以
.sh
结尾。脚本要有注释信息,注释尽量使用英文,但中文也行。
脚本作者名、联系方式(邮箱、QQ等)、版本、时间、脚本作用、代码行的作用。
例如:
#!/bin/bash # Author: Harmoniar # Mail: test@qq.com # Version: 1.0 # Date: 2023-04-02 # Description: This script is used to regularly clean up logs. # 清理xxx业务两天前的日志 find /data/logs/xxx/ -ctime +2 -name "*.log.gz" -delete
1
2
3
4
5
6
7
8
9
成对的语句符号一次性写完,例如:
if []...then...fi
。数值的比较要用-eq等,字符串的比较要用==。
重要操作要提示用户是否确认操作[Y/N],且提示要尽量的少而简洁。
文件路径等常用配置参数,尽量使用变量。
# 脚本的执行方式
- 使用命令解释器执行,例如:
bash [脚本路径]
。 - 使用路径执行脚本,例如:
[脚本路径]
。- 脚本需要有执行权限才能通过路径执行。
- 如果不写开头的解释器注释
#!/bin/bash
,则默认会以bash解释器去执行脚本。 - 如果写了开头的解释器注释
#!/bin/bash
,则会以指定的解释器去执行脚本。 - 所以如果需要通过路径执行Python脚本等,则必须指定解释器路径。
- 使用
source
或者.
执行脚本,例如:. [脚本路径]
。- 使用命令解释器或者路径执行脚本,都是创建一个子Shell来执行脚本。
- 而使用
source
或者.
方式执行脚本,则会在用户当前所在的Shell执行脚本。
# Shell脚本变量
# 变量简介
变量是传递数据的一种方式,就是用一个固定的字符其表示不固定的值,以便于后续引用。定义变量会在内存中分配一个变量空间用来存放数据。
# Shell变量分类
- 普通变量
- 就是仅针对当前Shell生效的局部变量。
- 在Shell中执行定义命令即可,例如:
Name="Harmoniar"
- 环境变量
- 就是针对所有Shell生效的变量,例如:
export Name="Harmoniar"
。 - 定义临时环境变量直接在Shell中执行定义命令即可,重启后失效。
- 定义永久环境变量则需要将环境变量定义在
/etc/profile
或/etc/bashrc
文件中,然后通过source
或.
执行一次文件即可,重启后仍有效。
- 就是针对所有Shell生效的变量,例如:
- 用户自定义变量
- 用户环境的变量,在用户登录时会自动定义的变量。
- 在
$HOME/.bashrc
文件中定义普通变量即可。
- 系统环境变量
- 系统环境相关的变量。
- 例如:
$HOME、$PATH...
- 位置参数变量
- 向脚本中进行参数传递的变量,变量名不能自定义,变量作用也是固定的。
- 例如:
$1、$2、$3...
- 内置变量
- Bash中已经定义好的变量,变量名不能自定义,变量作用也是固定的。
- 例如:
$?、$0、$n、$$...
# 变量名称定义规范
- 变量名要见名知其意。
- 下划线、数字、字母的组合,需以字母或下划线开头,不能以数字开头。
- 定义变量时,等号两边不能有空格。
- 变量名不能是Shell关键字,例如:
if、while
等。 - 命名例子:
girl_age
- 下划线命名法GIRL_AGE
- 系统使用的命名法girlAge
- 小驼峰命名法GirlAge
- 大驼峰命名法
# 调用变量的方法
- 直接调用
- 例如:
echo "$var"
- 例如:
- 调用的变量后面接字符
- 将变量名用
{}
括起来,将其作为一个整体。 - 例如:
echo "${变量名}_test"
- 将变量名用
- 使$变成普通字符不解析
$
符号前加反斜杠\
即可,例如:echo "\$test"
。- 或者使用
'
单引号包裹字符串,不解析变量,例如:echo '$test'
。
- 调用系统变量
- 通过export命令可查看已定义的环境变量,然后直接调用即可。
# 变量值的类型
定义数值
变量定义的数值必须是连续的数字。
例如:
test=123456
定义字符串
定义字符串需要加引号,如果加的是"双引号则会解析其中的特殊符号,如果加的是'单引号则会将内容全部作为普通字符串。
例如:
test="my name is $NAME"
定义命令值
定义的命令值变量,会将命令执行的返回结果复制给变量。
以下两种方式作用相同,都是创建一个子Shell来执行命令并获取返回结果。
# 使用``解析命令 test=`date` # 使用$()解析命令 test=$(date)
1
2
3
4
# 内置变量
$0 - 代表脚本的名称
如果是绝对路径执行,则脚本名将为绝对路径。
可用于提示用户该怎样正确使用脚本,例如:
echo "Usage: $0 {start|stop|restart}" >Usage: /scripts/test.sh {start|stop|restart}
1
2
$n - 传入脚本的第n个参数
- 代表用户执行脚本时传递的参数,例如:
sh test.sh [参数1] [参数2]...
- n代表传入脚本的第n个位置参数,例如:
$1、$2、$3...
- $9以上的参数需要加
{}
以将数值视为一个整体,例如:${11}
- 代表用户执行脚本时传递的参数,例如:
$# - 获取脚本传参的总个数
可用于控制脚本的传参个数,例如:
if [ $# -ne 2 ] && echo "请输入两个参数" && exit
1
$ - 获取脚本所有的参数*
在脚本中$*与$@作用相同。
在循环体中,不加双引号和$@相同,将以空格为分隔进行迭代,例如:
for i in $*;do echo $i done > test.sh "I am" HaHa # 输出: > I > am > HaHa
1
2
3
4
5
6
7
8
9在循环体中,加上双引号则把所以参数视为一个参数
$1 $2 ...
,例如:for i in "$*";do echo $i; done > test.sh "I am" HaHa # 输出: I am HaHa
1
2
3
4
5
6
7
$@ - 获取脚本所有的参数
在脚本中$*与$@作用相同。
在循环体中,不加双引号和$*相同,将以空格为分隔进行迭代。
在循环体中,加上双引号则把每个参数视为正常独立的参数
$1 $2 ...
,例如:for i in "$@";do echo $i; done > test.sh "I am" HaHa # 输出: I am HaHa
1
2
3
4
5
6
7
8
$? - 获取上一条命令的返回值
- Linux中的规范是,成功返回0,失败返回非0。
exit [返回值]
- 值需要在0~255之间,脚本中一般会使用的结束脚本并返回值的操作。
$$ - 获取脚本的PID
借助该变量,我们可以在开始时创建脚本的pid文件,例如:
# 脚本开始执行时 echo $$ > /tmp/test.pid # 脚本结束执行时 rm -f /tmp/test.pid # pid文件可以用于判断脚本是否在运行,如果已经有在运行则返回不能重复运行。 # 也可以用于杀进程 kill -9 `cat /tmp/test.pid`
1
2
3
4
5
6
7
8
$! - 获取上一个在后台运行的脚本的PID
- 一般在调试时才会用到,我们也可以使用该变量杀掉上一个脚本进程
kill $!
。
- 一般在调试时才会用到,我们也可以使用该变量杀掉上一个脚本进程
$_ - 获取命令行最后一个参数
- 例如传入的位置参数有
$1 $2 $3
,那么$_
就等于$3
。
- 例如传入的位置参数有
# 交互式赋值
主要通过read命令进行实现,我们可以将用户交互式输入的信息传递给变量。
例如:
# -p参数可以在读取前先打印一条信息 # 用户输入信息并回车后,会将用户输入的信息作为值传递给变量 read -p "提示信息:" 变量名 # 用户也可以一次输入多个变量,输入的变量需要以空格隔开 read -p "提示信息:" 变量名 变量名...
1
2
3
4
5
6
# 变量替换
匹配变量的值,然后将匹配的内容删除或替换,但不会实际修改变量本身。
删除
$(变量#匹配规则)
- 从左往右匹配,非贪婪匹配- 例如:
echo ${TEST#*c}
会将*c匹配的内容删除然后显示。
- 例如:
$(变量##匹配规则)
- 从左往右匹配,贪婪匹配$(变量%匹配规则)
- 从右往左匹配,非贪婪匹配$(变量%%匹配规则)
- 从右往左匹配,贪婪匹配
替换
$(变量/旧字符串/新字符串)
- 从左往替换字符串,只替换一次$(变量//旧字符串/新字符串)
- 从左往替换字符串,全部替换
其他
${#变量}
- 获取变量字符串的长度- 同等于
echo $var | wc -L
。
- 同等于
${变量:开始索引:结束索引}
- 截取变量字符串的内容索引从0开始,没有开始或结束的索引时,则将从0、$开始或结尾。
例如:
test=12345 echo ${test:0:2} > 12
1
2
3
# Shell数值运算
# 整数运算
- 整数运算将会省略小数点后面的数值。
- expr
- 需要用空格隔开,且特殊字符需要转义 如\*。
- 例如:
expr 1 + 1
- $(())
- 将
$(())
内的数值进行运算,另外也可以写成$[]
。 - 例如:
echo "this is $((2+1))"
- 将
# 小数运算
- 可以使用awk命令来进行运算。
- 例如:
awk "BEGIN{print 1/2}"
- 例如:
- awk调用shell变量方法
awk 'BEGIN{print "'$var'"}'
- "'$var'" 双引号内扩上单引号进行引用awk -v nvar="$var" '{print nvar}'
- 使用-v将shell变量传递给awk变量
# Shell数组
# Shell数组介绍
Shell的数组也算是变量,区别在于变量只能存储一个值,而数组可以存储多个值。
Shell数组可以分为两类:普通数组和关联数组
普通数组只能使用整数作为数组索引,类似于Python的列表,例如:
test=(one two three four five) echo ${test[0]}
1
2关联数组只能使用字符串作为数组索引,类似于Python的字典,例如:
declare -A test test=([name]=one [age]=18) echo ${test[name]}
1
2
3
# 定义普通数组
数组名[数值索引]=值 - 一次定义单个值
如果数组或元素原本不存在则会创建,例如:
test[0]="one"
Shell数组的索引可不按顺序,所以我们可以赋值给不存在的索引,例如:
test=(one two three) test[8]=eight # 此时test的索引就为0 1 2 8
1
2
3
数组名=(元素1 元素2 元素3...) - 一次定义多个值
例如:
# 一次定义多个值 test=(one two "I am Shell") # 也可以指定索引定义 test=(one two [6]=six)
1
2
3
4
5
数组名=(`命令`) - 命令定义多个值
会将命令的执行结果以空白符作为分隔符,按顺序赋值给索引,空白字符也就是
\n、\t、空格
。例如:
test=(`date`) # 同等于 test=($(date))
1
2
3
# 定义关联数组
declare -A [关联数组名]
- 定义关联数组需要先进行声明- 数组名[字符串索引]=值 - 一次定义单个值
- 如果数组或元素原本不存在则会创建,例如:
test[name]="one"
- 如果数组或元素原本不存在则会创建,例如:
- 数组名=([键名1]=值 [键名2]=值 [键名3]=值 ...) - 一次定义多个值
- 例如:
test=([name]="one" [age]=18)
- 例如:
# 查看数组元素
${数组名[索引]} - 访问数组中的元素
# 访问普通数组元素 echo ${test[0]} # 访问关联数组元素 echo ${test[name]}
1
2
3
4
5${数组名[@]} - 列出数组的所有元素
# 列出数组中的所有元素,[@]和[*]作用一致 echo ${test[@]} echo ${test[*]}
1
2
3${!数组名[@]} - 列出数组的所有索引
# 列出数组中的所有索引,[@]和[*]作用一致 echo ${!test[@]} echo ${!test[*]}
1
2
3${#数组名[@]} - 列出数组的元素总数
# 列出数组的元素总数 echo ${#test[@]}
1
2
# 其他数组操作
declare -a
- 查看所有普通数组declare -A
- 查看所有关联数组unset [数组名]
- 取消定义数组