shell 脚本编程
1. 编程基础
1.1 程序组成
- 程序:算法+数据结构
- 数据:程序的核心
- 数据结构:数据在计算机中的类型和组织方式
- 算法:处理数据的方式
1.2 程序编程风格
- 面向过程语言
1.做一件事情,排出步骤,第一步干什么,第二部干什么。如果出现情况A,做什么处理,如果出现B情况,做什么处理
2. 问题规模小,可以步骤化,按部就班处理
3. 以指令为中心,数据服务于指令
4. C, shell - 面向对象语言
- 一种认识世界,分析世界的方法论。将万事万物抽象为各种对象
- 类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合
- 对象是类的具象,是一个实体
- 问题规模大,复制系统
- 以数据为中心,指令服务于数据
- java ,C# ,python ,golangdeng
1.3 编程语言
计算机:运行二进制指令
编程语言:人与计算机之间的交互的语言。分为:低级语言和高级语言
- 低级编程语言:
1.机器:二进制的0和1的序列,称为机器指令。与自然语言差异太大
2. 汇编:用一些助记符代替机器指令,称为汇编语言 - 高级编程语言
- 编译:高级语言–>编译器–>机器代码文件–>执行,如C,C++
- 编译:高级语言–>执行–>解释器–>机器代码文件,如:shell,python,JavaScript,perl
1.4 三种处理逻辑
- 顺序执行:程序从上到下顺序执行
- 选择执行:程序执行过程中,根据条件的不同,进行选择不同分支继续执行
- 循环执行:程序执行过程中需要重复执行多次某段语句
2. shell 脚本语言的基本用法
2.1 shell 脚本的用途
- 将简单的命令组合完成复杂的工作,自动化执行命令,提高工作效率
- 减少手工命令的重复输入,一定程度上避免人为错误
- 将软件或应用的安装及配置实现标准化
- 用于实现日常性的,复杂性的运维工作,如:文件的打包压缩备份,监控系统运行状态并实现警告等
2.2 shell 脚本基本结构
shell 脚本编程:基于过程式、解释执行的语言
编程语言的基本结构
- 各种系统命令的组合
- 数据存储:变量、数组
- 表达式:a+b
- 控制语句:if
2.3 shell 脚本调试
- bash -n 检测脚本的语法错误,但无法检测命令错误,不真正执行脚本
- bash -x 调试并执行
总结:脚本错误常见有三种
- 语法错误,会导致后续的命令不执行,可以用bash -n 检查错误,提示的出错行数不一定是准确的
- 命令错误,默认后续的命令还会继续执行,bash -n 无法检测,可以使用bash -x进行观察
- 逻辑错误:只能bash -x 进行观察
2.4 变量
2.4.1 变量
变量表明命名的内存空间,将数据保存在内存空间中,通过变量名引用,获取数据
2.4.2 变量类型
变量类型:
- 内置变量,如:PS1,PATH,UID,HOSTNAME,等
- 用户自定义变量
不同的变量存放的数据不同,决定了以下 - 数据存储方式
- 参与的运算
- 表示的数据范围
变量的数据类型: - 字符
- 数值:整型,浮点型,bash不支持浮点数
2.4.3 编程语言分类
静态和动态语言
- 静态编译语言:使用变量前,先声明变量类型,之后又类型不能改变,如:java,C
- 动态编译语言:不用事先声明,可随时改变类型,如:bash,Python
强类型和弱类型语言 - 强类型语言:不同类型数据操作,必须经过强制转换成同一类型才能运算,如java,python
- 弱类型语言:语言的运行会隐式做数据转换。无须指定类型,默认均为字符,参与运算会自动进行转换,变量无须事先定义可直接调用。如bash,php,javascript
2.4.4 shell中变量命名法则
- 不能使用程序中的保留字和内置变量:if,for
- 只能使用数字,字母及下划线,且不能数字开头,注意:不支持短横线-,与主机名相反
- 见名知意,用英文单词命名,并体现出实际作用,不要用简写
- 同一命名规则,驼峰命名法studentname,大驼峰StudentName,小驼峰studentName
- 变量名大写
- 局部变量小写
- 函数名小写
2.4.5 变量定义和引用
变量的生效范围
- 普通变量:生效范围为当前shell;对除当前shell之外的其他shell,包括子shell均无效
- 环境变量:当前shell及子进程
- 本地变量:当前shell中某代码片段,通常指函数
变量赋值
name='value'
直接字串 name='root'
变量引用 name='$USER'
命令引用 name=`COMMAND` 或 name=$(SOMMAND)
变量引用
$naem
${name}
弱引用和强引用
- “$name” 弱引用,其中的变量引用会被替换为变量值
- “${name}” 强引用,其中的变量引用不会被替换为变量值,保存原字符串
显示已定义的所有变量
set
删除变量
unset <name>
2.4.6 环境变量
环境变量
- 可以使子进程继承父进程的变量,但无法让父进程使用子进程的变量
- 一般子进程修改从父进程继承的变量,将会将新的值传递到孙子进程
- 一般只在系统配置文件中使用,在脚本中较少使用
变量声明和赋值
#声明赋值
export name=VALUE
declare -x name=VALUE
#或分步进行
name=VALUE
export name
显示所有环境变量
env
printenv
export
declare -x
删除变量
unset name
2.4.7 只读变量
只读变量:只能声明定义,但后续不能修改和删除
声明只读变量
readonly name
declare -r name
查看只读变量
readonly [-p]
declare -r
2.4.8 位置变量
位置变量:在bash shell中内置的变量,在代码中调用通过命令行传递给脚本的参数
$1,$2,... 对应第1个、第2个等参数,shift [n]换位置
$O 命令本身,包括路径
$* 传递给脚本的所有参数,全部参数合为一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
$# 传递给脚本的参数的个数
注意: $@ $*只在被双引号包起来的时候才会有差异
清空所有位置变量
set --
范例:
在这里插入代码片
2.4.9 退出状态码变量
我们浏览网页时,有时会看到404,403等数字,表示网页的错误信息,我们称为状态码。
进行进程时,使用
?
保
存
状
态
码
的
相
关
数
字
,
?保存状态码的相关数字,
?保存状态码的相关数字,?取值范围0-255
- 0 表示成功
- 1-255 表示失败
用户可以在脚本中使用exit自定义退出状态码
exit [n]
- 脚本中一旦遇见exit命令,脚本会立即终止,终止退出状态码取决于exit后的数字
- 如果脚本未指定退出状态码,脚本的退出状态码取决于最后一个命令的状态码
2.4.10 展开命令行
展开命令执行的顺序
把命令行分成单个命令
展开别名
展开大括号
展开~
命令替换$()和**``**
再次把命令行分成命令
展开文件通配符
重定向
运行命令
防止扩展:
- 反斜线(\)
- 单引号(’’)
- 双引号("")注意变量不能防止扩展
变量扩展
- `` 反引号,命令替换
- \ 禁止单个字符扩展
- !历史命令替换
2.4.11 脚本安全 set
2.4.11.1 $- 变量
- h:hashell 打开选项,shell会将命令的路径hash下来,避免每次查询,set +h可以关闭
- i: interactive-comments, 包含这个选项说明当前的shell是一个交互式的shell。所谓的交互式shell,在脚本中,i选项是关闭的
- m: monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等
- B: braceexpand, 大括号扩展
- H: history, H选项打开,可以展开历史列表中的命令,可以通过!感叹号来完成,如"!"返回上最近的一个历史命令, "!n"返回第n个历史命令
2.4.11.2 set脚本安全
- u 在扩展一个没有设置的变量时,显示错误信息,等同set -o nounset
- e 如果一个命令返回一个非0退出状态就退出,等同于 set -o errexit
- o 显示打开或关闭选项
显示:set -o
打开:set -o 选项
关闭:set +o 选项 - x 执行命令时,打印命令及参数,类似 bash -x
2.5 格式化输出
printf "指定的格式" "文本1" "文本2
常用格式替换符
替换符 | 功能 |
---|---|
%s | 字符串 |
%f | 浮点格式 |
%b | 参数中包含转义,可以用此替换,对应的转义字符会被转义 |
%c | ASCLL字符,即显示对应参数的第一个字符 |
%d,%i | 十进制整数 |
%o | 八进制值 |
%u | 不带正负号的十进制值 |
%x | 十六进制值(a-f) |
%X | 十六进制值(A-F) |
%% | 表示%本身 |
常用转义字符
转义符 | 功能 |
---|---|
\a | 警告字符 |
\b | 后退 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\\ | \本身 |
[root@centos8 ~]#printf "%.2f\n" 1 2 3 4
1.00
2.00
3.00
4.00
[root@centos8 ~]#printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小白 男 15 60
姓名 性别 年龄 体重
小白 男 15 60
2.6 算数计算
shell允许在某些情况下对算术表达式进行求值,比如let和declare,(())和算术扩展,
注意:bash只支持整数,不支持浮点数
% 取模,即取余数
i++ i-- 自增,自减
** 乘方
实现算术运算
- let var=算术表达式
- ((var=算术表达式))
- var=$[算术表达式]
- var=$((算术表达式))
- var=$(expr arg1 arg2)
- declare -r var=数值
- echo ‘算术表达式’ |bc
内建的随机数生成器变量
$RANDOM 取值范围:0-32767
增强型赋值
+= i+=10 相当于 i=i+10
-= i-=j 相当于 i=i-j
/=
%=
++ i++,++i 相当于i=i+1
-- i--,--i 相当于 i=i-1
范例:今有雉兔同笼,上有三十五头,下有九十四足,问雉兔各几何?
read -p "请输入头的数量:"HEAD
read -p "请输入脚的数量:"FOOT
RABBIT=$(((FOOT-HEAD-HEAD)/2))
CHOOK=$[HEAD-RABBIT]
echo RABBIT:$RABBIT
echo CHOOK:$CHOOK
2.7 逻辑运算
2.7.1 与运算(&)
与运算(&):
1 &1=1
1&0=0
0&1=0
0&0=0
2.7.2 或运算(|)
或运算(|)
1 | 1=1
1 | 0=1
0 | 1=1
0 | 0=0
2.7.3 非(!)
非(!):
!0=1
!1=0
2.7.4 异或(^)
异或(^):
异或的2个值,相同为假,不同为真
2个数字X,Y异或得到的值Z,Z再和任意一个值异或,将得出另一个值
1 ^ 1= 0
1 ^ 0= 1
0 ^ 1= 1
0 ^ 0= 0
2.7.5 短路运算
- 短路与
CMD1 && CMD2
第一个CMD1结果为真,第二个CMD2必须参与运算,才能得到最终结果
第一个CMD1结果为假,最终结果必为假,第二个CMD2不需要执行
- 短路与
CMD1 || CMD2
第一个CMD1结果为真,最终结果必为真,第二个CMD2不需要执行
第一个CMD1结果为假,第二个CMD2必须参与运算,才能得到最终结果
2.8 条件测试命令
条件测试:判断某需求是否满足。
若结果为真,则状态码
?
返
回
0
如
结
果
为
假
,
则
状
态
码
?返回0 如结果为假,则状态码
?返回0如结果为假,则状态码?返回非0
条件测试命令
- test EXPRESSION
- [EXPRESSION ]
- [[EXPRESSION]]
注意:EXPRESSION前后必须有空白字符
2.8.1 变量测试
#判断 NAME 变量是否定义
[ -v NAME ]
#判断 NAME 变量是否定义并且是名称引用
[ -R NAME ]
2.8.2 数值测试
-eq 是否等于 ==
-ne 是否不等于 !=
-gt 是否大于 >
-ge 是否大于等于 >=
-lt 是否小于 <
le 是否小于等于 <=
2.8.3 字符串测试
test和[]用法
-z STRING 是否为空,没定义或空为真,不空为假
-[n] STRING 是否不空,不空为真,空为假
STRING1 = STRING2 是否等于,注意 = 前后有空格
STRING1 != STRING2 是否不等于
> ascii 码是否大于ascii
< 是否小于
[[]] 用法
[[ expression ]]
== 左侧字符串是否和右侧的PATTERN相同
注意:PATTERN为通配符
=~ 左侧字符串是否能把右侧的正则表达式PATTERN匹配
注意:此处为扩展的正常表达式
2.8.4 文件测试
2.8.4.1 存在性测试
-a FIEL 是否存在,存在为真,否则为假
-e FIEL 是否存在,存在为真,否则为假
-b FIEL 是否为块设备文件
-c FIEL 是否为字符设备文件
-d FIEL 是否为目录文件
-f FIEL 是否为普通文件
-h FIEL -L FIEL 是否为符号链接文件
-p FIEL 是否为管道文件
-s FIEL 是否为套接字文件
2.8.4.2 文件权限测试
-r FIEL 是否存在且可读
-w FIEL 是否存在且可xie
-x FIEL 是否存在且可执行
-u FIEL 是否存在且拥于suid权限
-g FIEL 是否存在且拥于sgid权限
-k FIEL 是否存在且拥于sticky权限
注意:最终结果是有用户对文件的实际权限决定,而非文件属性决定
2.8.4.3 文件属性测试
-s FIEL 是否存在且非空
-t fd fd 文件描述符是否在某终端已经打开
-N FIEL 文件自上次读取之后是否被修改过
-O FIEL 当前有效用户是否为文件属主
-G FIEL 当前有效用户是否为文件属组
FIEL1 -ef FIEL2 FIEL1是否是FIEL2的硬链接
FIEL1 -nt FIEL2 FIEL1是否新于FIEL2(mtime)
FIEL1 -ot FIEL2 FIEL1是否旧于FIEL2
2.9 关于()和{}
(){} 都可以讲多个命令组合在一起,批量执行
( list ) 会开启子shell,并且list中的变量赋值及内部命令执行后,请不再影响后续环境
{ list } 不会开启子shell,在当前shell中运行,会影响当前shell环境
2.10 组合测试条件
2.10.1 第一种方式[]
[ EXPRESSION1 -a EXPRESSION2 ] 并且,EXPRESSION1和EXPRESSION2都为真,结果才为真
[ EXPRESSION1 -o EXPRESSION2 ] 或者,EXPRESSION1和EXPRESSION2只要有1个为真,结果就为真
[ ! EXPRESSION ] 取反
注意:-a 和 -o 在[[]]不支持
2.10.2 第二种方式 && ||
COMMAND1 && COMMAND2 并且,短路与,COMMAND1为真,则执行COMMAND2
COMMAND1 || COMMAND2 或者,短路或,COMMAND1为真,则不执行COMMAND2,COMMAND1为假,则执行COMMAND2
! COMMAND 非 取反
2.11 使用read 命令接受输入
使用read来把输入的值,分配到一个或多个变量,read从标准输入中读取值,如果变量没有指定,则默认赋值给内置变量 REPLY
read [options] [name ...]
选项
- -p 指定要显示的提示
- -s 静默输入,一般用于密码
- -n N 指定输入字符长度
- -d ‘字符’ 输入结束符
- -t N TIMEOUT为N秒
3. bash shell 的配置文件
3.1按生效范围
按生效范围可分为
全局配置
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
个人配置
~/.bash_profile
~/.bashrc
3.2 shell 的登录方式
3.2.1 交互式登录
- 直接通过终端输入账号密码
- 使用 su - username 切换用户
配置文件生效和执行的顺序
# 放在每个文件最前面
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
~/.bash_profile
~/.bashrc
/etc/bashrc
#放在每个文件的最后
/etc/profile.d/*.sh
/etc/bashrc
/etc/profile
/etc/bashrc
~/.bashrc
~/.bash_profile
3.2.3 非交互式登录
- su username
- 图形界面下打开终端
- 执行脚本
- 任何其他的bash实例
执行顺序
/etc/profile.d/*.sh
/etc/bashrc
~/.bashrc
3.3 按功能
profile和bashrc
3.3.1 Profilelei类
proflie类为交互登录的shell通过配置
- 全局:/etc/profile ,/etc/profile.d/*.sh
- 个人:~/.bash_profile
功用: - 用于定义环境变量
- 运行命令或脚本
3.3.2 Bashrc类
bashrc类:为非交互式和交互式登录的shell通过配置
- 全局:/etc/bahsrc
- 个人:~/.bashrc
功用: - 用于定义别名和函数
- 定义本地变量
3.4 编辑配置文件生效
修改profile和bashrc文件后需生效的2种方法:
- 重启shell
- source 或 . 配置文件
3.5 Bash 退出任务
保存~/.bash_logout在文件中,在退出登录shell式运行
功能:
- 创建自动备份
- 清除临时文件