Shell脚本
Shell脚本
到达了这里,那就是对前面的内容进行了系统学习后,可以开始接触较为简单的脚本执行了。
脚本实际上就是命令的集合,和我们人工去处理Linux系统的基础环境没啥区别。但如果你要部署上百台机器时,那脚本就是一个很好的选择了。
脚本最大的缺点是需要对执行时的正确与否进行判断,不然容易出现某步出错但继续往下走的情况,容易导致环境出问题。在虚拟机上我们可以对环境进行镜像来还原避免,可在实际生产环境中会导致另一个问题:镜像和回滚都需要大量的时间,同时需要大量的存储空间。在商业中存储空间也是钱,老板不太可能给你包这么大的底,所以使用脚本时一定要注意再注意,先在测试环境保证没问题了再去生产环境进行上线!
注:Linux上的脚本也可以使用python或其它方式实现,但本篇专注于Shell脚本
编程基础
1 | Talk is cheap,show me the code --Linus |
编写脚本时其实也类似于C、Java、Python等编程语言一样,有一定的共通之处。但在排查问题和询问他人时一定要少描述,将代码和报错给到他人。这样会减少大家的沟通成本,同时大家也能更加融洽的讨论真正的问题而不是在描述上纠结。
Shell 脚本语言的基本用法
shell 脚本的用途
- 将简单的命令组合完成复杂的工作,自动化执行命令,提高工作效率
- 减少手工命令的输入,一定程度上避免人为错误
- 将软件或应用的安装及配置实现标准化
- 用于实现日常性的,重复性的,非交互式的运维工作,如:文件打包压缩备份,监控系统运行状态并实现告警等
shell 脚本基本结构
shell是基于过程式、解释执行的语言(即脚本本身和人为交互执行命令没较大区别)
shell脚本是包含一些命令或声明,并符合一定格式的文本文件。
一个shell脚本文件中主要包含以下内容
- 各种系统命令的组合
- 数据存储:变量、数组
- 表达式:a + b
- 控制语句:if
shell 脚本创建过程
1.用编辑器(vi/vim)创建新文件,首行必须是 shell 声明(shebang)。写完内容后保存退出
2.添加可执行权限(x)
3.运行脚本
格式要求:首行shebang机制
1 | 声明有这样几种,我们编写的shell脚本是主要使用#!/bin/bash |
shell 脚本注释规范
紧随声明机制后面的应该是脚本注释,也就是脚本的作者、创建时间、脚本的功能及作用、版权信息、作者联系方式等
1 | 例子 |
这些内容也可以配置成 vim 自动生成注释
1 | 每个用户的家目录下.vimrc里存放这些内容后,当使用vim创建文件时就会自动将满足条件的内容添加进新文件中 |
第一个 shell 脚本
和我们学其它编程语言一样,我们一开始会写一个脚本,来向世界问好~
1 | vim hello.sh |
如何运行它呢?
1 | bash hello.sh |
同时也很明显了,shell脚本其实就是一串命令的集合!
脚本如何还是需要自己去多加尝试,这里就不放更多的脚本示例了
变量
变量
变量表示命令的内存空间,将数据放在内存空间中,通过变量名来进行引用,进而获得数据
数据要存在内存空间中,而内存空间又是通过内存地址来访问的,但内存地址一般是16进制的编号
但为了方便使用(谁会记16进制地址名啊),所以就有了变量名。当访问变量时,实际是访问其对应的内存地址的对应内存空间
变量类型
变量类型:
- 内置变量,如:PS1、PATH、UID、HOSTNAME、$$、BASHPID、PPID、$?、HISTSIZE
- 用户自定义变量
不同的变量存放的数据不同,决定了以下
- 数据存储方式
- 参与的运算
- 表示的数据范围
变量数据类型:
- 字符
- 数值:整型、浮点型 (bash 不支持浮点数)
- 布尔
- 指针
- 结构体:自定义的复合类型的数据类型
- ……
Shell 中变量命名法则
命名要求
- 区分大小写
- 不能使用程序中的保留字和内置变量:如: if、for
- 只能使用数字、字母及下划线,且不能以数字开头。注意:不支持短横线”-“,和主机名相反
命名习惯
- 见名知义,用英文单词命名,并体现出实际作用,不要用简写,如:ATM
- 变量名大写
- 局部变量小写
- 函数名小写
- 大驼峰StudentFirstName,由多个单词组成,且每个单词的首字母是大写,其它小写
- 小驼峰studentFirstName,由多个单词组成,第一个单词的首字母小写,其它部分沿用大驼峰
- 下划线: student_first_name
变量定义和引用
按变量的生效范围等标准划分变量类型
- 普通变量: 生效范围为当前shell进程; 对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
- 环境变量: 生效范围为当前shell进程及其子进程
- 本地变量: 生效范围为当前shell进程中某代码片段,通常指函数
变量赋值:
1 | name='value' |
value可以是多种类型
1 | name='root' #字符串 |
注:变量赋值是临时生效。当退出终端后,变量会自动删除,无法持久化保存。脚本中的变量会随着脚本的结束也自动删除。
变量引用(所有变量统一用法)
1 | $name |
弱引用和强引用
- “$name” 弱引用,其中的变量引用会被替换为变量值
- ‘$name’ 强引用,其中的变量引用不会被替换为变量值,而保持原字符串
环境变量
环境变量:
- 可以使子进程(可以一直往下继承)继承父进程的变量,但是无法让父进程使用子进程的变量
- 一旦子进程修改从父进程继承的变量,将会新的值传递给进程(孙子进程等等)
- 一般只在系统配置文件中使用,在脚本中较少使用
1 | # 声明并赋值 |
删除变量:
1 | unset name |
只读变量
只读变量:只能在声明时定义,后续无法修改和删除。即常量
声明只读变量:
1 | readonly name |
查看只读变量
1 | readonly [-p] |
位置变量
位置变量:在bash shell中内置的变量,在脚本代码中调用通过命令行传递给脚本的参数
1 | $1,$2,... #对应第一个、第二个等参数,shift [n]换位置 |
清空所有位置变量
1 | set -- |
展开命令行
在命令中我们可能会使用了各种通配符。但系统并不认识通配符,所以在执行之前,通配符会被先解析成具体的信息。命令的展开执行顺序如下:
1 | 把命令行分成单个命令词 |
如果有时候我们不希望被识别为特殊字符:
1 | \ #使用反斜线,使随后的字符按原意解释 |
加引号来防止扩展
1 | '' #单引号,里面的内容全部不会展开 |
变量扩展
1 | `` #反引号。执行其中的命令,使用返回值替换 |
脚本安全和 set
set 命令:可以用来定制 shell 环境
$- 变量
| 选项 | 含义 | 备注 |
|---|---|---|
| h | hashall | 开启hash表缓存外部命令路径 |
| i | interactive-comments | 允许在交互式Shell中使用注释(交互式Shell: 例如我们的远程连接,交互式的执行命令) |
| m | monitor | 开启监控模式,可通过 Job control 来控制进程的停止、继续,后台或者前台执行等 |
| B | braceexpand | 是否支持大括号扩展(即是否能使用{}) |
| H | history | 是否可用! 来展开历史命令 |
1 | echo $- #查看有哪些选项被启用(注:这个变量是+为取消,-为添加) |
set 命令实现脚本安全
| 字符 | 作用 |
|---|---|
| u | 开启此项,在使用一个没有声明的变量时,会报错,并终止脚本。同 set -o nounset |
| e | 开启此项,命令返回非0时直接退出脚本,不继续执行后面代码。同 set -o errexit |
| o | set -o 显示所有;set -o option 开启 option 项;set +o option 关闭 option 项 |
| x | 执行命令时,打印命令及其参数。同set -x |
1 | -u 在扩展一个没有设置的变量时,显示错误信息,等同 set -o nounset |
格式化输出 printf
相当于增强版的 echo,实现丰富的格式化输出
格式
1 | printf format args... |

常用格式替换符
| 替换符 | 功能 |
|---|---|
| %s | 字符串 |
| %d,%i | 十进制整数 |
| %f | 浮点格式 |
| %c | ASCII字符,即显示对应参数的第一个字符 |
| %b | 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义 |
| $o | 八进制值 |
| %u | 不带正负号的十进制值 |
| %x | 十六进制值(a-f) |
| %X | 十六进制值(A-F) |
| %% | 表示%本身 |
说明:
1 | %[N]s # N表示输出宽度,不够使用空格补齐 -N 表示左对齐 |
常用转义字符
| 转义符 | 功能 |
|---|---|
| \a | 警告字符,通常为ASCII的BEL字符 |
| \b | 后退 |
| \f | 换页 |
| \n | 换行 |
| \r | 回车 |
| \t | 水平制表符 |
| \v | 垂直制表符 |
| \ | 表示\本身 |
算术运算
Shell允许在某些情况下对算术表示式进行求值,比如:let和declare 内置命令,(( ))复合命令和算术扩展。求值以固定宽度的整数进行,不检查溢出,尽管除以0 被标记为错误。运算符及其优先级,关联性和值与C语言相同。以下运算符列表分组为等优先级运算符级别。级别按降序排列优先。
注意:bash 只支持整数,不支持小数
1 | ** #乘方(指数运算) |
Linux的随机数生成器
1 | $RANDOM #取值范围:0-32767 |
随机字体颜色
1 | echo -e "\033[1;$[RANDOM%7+31]mhello\033[0m" |
逻辑运算
这部分与数电非常相似。但这里只会用到与逻辑门类似的几个运算符,用于数学计算
与或非

| bool值 | 二进制表示 | 说明 |
|---|---|---|
| true | 1 | 真 |
| false | 0 | 假 |
与(&):有一假则全假,全真才是真
1 | # 在Linux中的数学计算中使用时,是以二进制形式来进行的比较 |
或(|):当两者有一个为真时,则结果为真
1 | echo $((5 | 3)) # 5 的二进制是 101,3 是 011 → 按位或结果为 111 (十进制 7) |
非(!):当输入为假输出为真,输入为真时则输出为假
| 变量 | 运算 | 非运算结果 |
|---|---|---|
| 0 | ! 1 | 0 |
| 1 | ! 0 | 1 |
异或(^):输入的两个值不同是为真,相同时为假
| 变量1 | 变量2 | 异或运算 | 异或运算结果 |
|---|---|---|---|
| 0 | 0 | 0^0 | 0 |
| 0 | 1 | 0^1 | 1 |
| 1 | 0 | 1^0 | 1 |
| 1 | 1 | 1^1 | 0 |
短路运算
用于链接命令。上一条命令结果为(假/真)时,选择性执行后面的命令
短路与(&&)
| command1值 | command2值 | 短路与运算 | 短路与运算结果($?) |
|---|---|---|---|
| true | true | cmd1 && cmd2 | true |
| true | false | cmd1 && cmd2 | false |
| false | (被跳过) | cmd1 && cmd2 | false |
只有cmd1成功时,才会执行后面的命令。但不会管后面命令执行成功与否
短路或(||)
| command1值 | command2值 | 短路或运算 | 短路或运算结果($?) |
|---|---|---|---|
| true | (被跳过) | cmd1 || cmd2 | true |
| false | true | cmd1 || cmd2 | true |
| false | false | cmd1 || cmd2 | false |
当cmd1执行失败时,执行后面的cmd2
注:Shell中的($?)是用于存储上一条命令执行是否成功。当命令成功是则为0,命令失败时为1-255中任意一个数。具体报错数字得看命令中的定义
条件测试命令
条件测试:
用于判断某需求是否满足,就需要使用测试机制来检查。专用的测试表达式需要有测试命令进行辅助。便于与条件判断等方法组合实现自动化。
若真,则状态码变量**$?**返回0
若假,则状态码变量**$?**返回1
表达式条件测试
1 | # 条件测试命令 |
文件判断相关
1 | -a FILE #如果文件存在则为真 |
字符串判断相关
1 | -z STRING #判断字符串是否为空,为空则为真 |
数学相关
1 | #数字相关 格式 arg1 OP arg2 |
其它
1 | -o OPTION #如果在shell 中开启了某项,则为真 |
关于()和{}
1 | ( cmd1;cmd2;... ) 和 { cmd1;cmd2;...; } 都可以将多个命令组合在一起,批量执行 |
组合测试条件
很多时候我们需要一次多个条件一起判断。不然会导致脚本中出现多层嵌套结构,降低代码的可读性,所以此时我们可以使用复合判断语句来进行判断。
第一种方式
1 | # 使用-a , ! 和 -o 来进行连接 |
第二种方式
1 | # 使用 && , ! , || 来进行连接 |
使用read来接受输入
使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量,如果变量名没有指定,默认标准输入的值赋值给系统内置变量REPLY。
1 | read [options] [name ...] |
Bash shell 的配置文件
bash shell的配置文件很多,可以根据范围来划分为以下两类:
全局配置:针对所有用户都有效的配置。但会被个人配置覆盖。(先加载全局配置再加载个人配置)
1 | # 文件所在地 |
个人配置:只在特定用户登录时生效。
1 | ~/.bash_profile |
编辑配置文件生效
对profile和bashrc文件修改后,想使修改内容立刻生效需要一定的操作
- 关闭当前shell进程重新启动一个
- source path 使用该指令重新加载指定路径(path)的配置文件
流程控制
条件选择
选择执行 if 语句
1 | # 格式 |
条件判断 case 语句
1 | # 格式 |
循环
将某代码段重复运行多次,通过满足进入循环的条件和退出循环的条件来达成任务
循环 for
1 | # 格式 |
运行方式:
- 依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束
- 如果省略 [in WORDS … ] ,此时使用位置参数变量 in “$@”
for 循环列表生成方式:
- 直接给出列表
- 整数列表:如 {start..end}
- 返回列表的命令:如 $(COMMAND)
- 使用glob,如:*.sh
- 变量引用,如:$@,$,$#
循环 while
1 | # 格式 |
说明:
CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环,因此:
CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正
进入条件:CONDITION为 true
退出条件:CONDITION为 false
注:while 存在特殊用法
1 | while read line; do |
依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
循环 until
1 | # 格式 |
说明:
进入条件: CONDITION 为false
退出条件: CONDITION 为true
循环控制语句 continue
continue: 是跳过当前循环的剩余部分,直接进入下一次迭代
continue [N]:跳过从内向外数第N层循环的本次迭代
continue 2: 跳过外层循环的本次迭代(不带嵌套循环里会出错)
1 | # 格式 |
循环控制语句 break
break [N]:提前结束第N层整个循环,最内层为第1层
1 | # 格式 |
循环控制 shift
shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到shift
1 | # 例子 |
循环与菜单 select
1 | # 格式 |
说明:
- select 循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准输出上,并显示 PS3 提示符,等待用户输入
- 用户输入菜单列表中的某个数字,执行相应的命令
- 用户输入菜单列表中的某个数字,会将对应的WORD值赋值给NAME变量
- 用户输入被保存在内置变量 REPLY 中
- select 是个无限循环,因此要写一个选项是使用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c 退出循环
- select 经常和 case 联合使用
- 与 for 循环类似,可以省略 in list,此时使用位置参量
函数
函数介绍
函数是指由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分
函数和shell程序区别
- Shell程序在子Shell中运行
- 函数在当前Shell中运行。因此在当前Shell中,函数可对shell中变量进行修改
管理函数
函数由两部分组成:函数名和函数体
查看帮助
1 | help function |
定义函数
1 | #语法一 |
查看函数
1 | #查看当前已定义的函数名(只查看函数名) |
删除函数
1 | unset func_name |
函数调用
函数的调用方式
- 可在交互式环境下定义函数
- 可将函数放在脚本文件中作为它的一部分
- 可放在只包含函数的单独文件中
调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时创建,返回时终止