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
2
3
4
5
6
声明有这样几种,我们编写的shell脚本是主要使用#!/bin/bash
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
#!/usr/bin/ruby
#!/usr/bin/lua

shell 脚本注释规范

​ 紧随声明机制后面的应该是脚本注释,也就是脚本的作者、创建时间、脚本的功能及作用、版权信息、作者联系方式等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
例子

[root@ubuntu2204 ~]# vim test.sh
#!/bin/bash
#
#****************************************************
#Author:           作者名
#QQ:               QQ联系方式
#Date:             发布日期
#FileName:         文件名
#URL:              作者的博客或其它网页
#Description:      作用
#Copyright(C):     版权信息
#****************************************************
echo "hello world"

​ 这些内容也可以配置成 vim 自动生成注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
每个用户的家目录下.vimrc里存放这些内容后,当使用vim创建文件时就会自动将满足条件的内容添加进新文件中

[root@ubuntu2204 ~]# vim .vimrc
set nu
set cul
set ts=2
set shiftwidth=2
autocmd BufNewFile *.sh exec ":call SetTitle()"
fun SetTitle()
if expand("%:e") == 'sh'
call setline(1,"#!/bin/bash")
call setline(2,"#")
call setline(3,"#*********************************************************")
call setline(4,"#Author: zhouhao")
call setline(5,"#QQ: 1193306316")
call setline(6,"#Date: ".strftime("%Y-%m-%d"))
call setline(7,"#FileName: ".expand("%"))
call setline(8,"#URL: https://www.zhouhaoit.com")
call setline(9,"#Description: The test script")
call setline(10,"#Copyright: ".strftime("%Y")." All rights reserved")
call setline(11,"#********************************************************")
call setline(12,"")
call setline(13,"")
endif
endfunc
autocmd BufNewFile * normal G

第一个 shell 脚本

​ 和我们学其它编程语言一样,我们一开始会写一个脚本,来向世界问好~

1
2
3
4
5
vim hello.sh

#!/bin/bash

echo "Hello, World!"

如何运行它呢?

1
2
3
4
5
6
7
bash hello.sh

. /root/hello.sh

chmod +x hello.sh (给执行权限)
/root/hello.sh
. hello.sh(等同于 source 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
2
3
4
name='root'			#字符串
name="$USER" #变量引用
name=`COMMAND` #命令引用(存入命令的输出)
name=$(COMMAND) #同样也是命令引用

注:变量赋值是临时生效。当退出终端后,变量会自动删除,无法持久化保存。脚本中的变量会随着脚本的结束也自动删除。

变量引用(所有变量统一用法)

1
2
$name
${name}

弱引用和强引用

  • “$name” 弱引用,其中的变量引用会被替换为变量值
  • ‘$name’ 强引用,其中的变量引用不会被替换为变量值,而保持原字符串

环境变量

环境变量:

  • 可以使子进程(可以一直往下继承)继承父进程的变量,但是无法让父进程使用子进程的变量
  • 一旦子进程修改从父进程继承的变量,将会新的值传递给进程(孙子进程等等)
  • 一般只在系统配置文件中使用,在脚本中较少使用
1
2
3
4
5
6
7
# 声明并赋值
export name=VALUE
declare -x name=VALUE

# 其他方法
name=VALUE
export name

删除变量:

1
unset name

只读变量

​ 只读变量:只能在声明时定义,后续无法修改和删除。即常量

声明只读变量:

1
2
readonly name
declare -r name

查看只读变量

1
2
readonly [-p]
declare -r

位置变量

​ 位置变量:在bash shell中内置的变量,在脚本代码中调用通过命令行传递给脚本的参数

1
2
3
4
5
6
$1,$2,...	#对应第一个、第二个等参数,shift [n]换位置
$0 #执行脚本时使用的名字。启动时使用的什么名字就显示什么
$* #传递给脚本的所有参数,全部参数 合并 为一个字符串(默认使用空格分隔)
$@ #传递给脚本的所有参数,全部参数为 独立 的字符串(保留原始分隔)
$# #传递给脚本的参数的个数
shift n #删除前n个参数,并将后面的参数向前移(shift 1 删除$1,将$2的值给到$1)

清空所有位置变量

1
set --

展开命令行

​ 在命令中我们可能会使用了各种通配符。但系统并不认识通配符,所以在执行之前,通配符会被先解析成具体的信息。命令的展开执行顺序如下:

1
2
3
4
5
6
7
8
9
把命令行分成单个命令词
展开别名
展开大括号的声明{}
展开波浪符声明 ~
使用命令替换$() 和 ``
再次把命令行分成命令词
展开文件通配符*、?、[abc]等等
准备I/O重导向<、>
运行命令

如果有时候我们不希望被识别为特殊字符:

1
\			#使用反斜线,使随后的字符按原意解释

加引号来防止扩展

1
2
''			#单引号,里面的内容全部不会展开
"" #也可防止扩展。但当识别到$时例外

变量扩展

1
2
3
``			#反引号。执行其中的命令,使用返回值替换
\ #反斜线。禁止后一个字符的扩展
! #感叹号。后面跟数字后将会替换为历史中的第几条命令

脚本安全和 set

​ set 命令:可以用来定制 shell 环境

$- 变量

选项 含义 备注
h hashall 开启hash表缓存外部命令路径
i interactive-comments 允许在交互式Shell中使用注释(交互式Shell: 例如我们的远程连接,交互式的执行命令)
m monitor 开启监控模式,可通过 Job control 来控制进程的停止、继续,后台或者前台执行等
B braceexpand 是否支持大括号扩展(即是否能使用{})
H history 是否可用! 来展开历史命令
1
2
3
4
echo $-			#查看有哪些选项被启用(注:这个变量是+为取消,-为添加)

set +h #取消hash表缓存
set -h #启用hash表缓存

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
2
3
4
-u 在扩展一个没有设置的变量时,显示错误信息,等同 set -o nounset
-e 如果一个命令返回一个非0退出状态值(失败)就退出,等同set -o errexit
-o option 显示,打开或者关闭选项 显示选项:set -o 打开选项:set -o 选项 关闭选项:set +o 选项
-x 当执行命令时,打印命令及其参数,类似 bash -x

格式化输出 printf

​ 相当于增强版的 echo,实现丰富的格式化输出

格式

1
printf format args...

image-20250923192040898

常用格式替换符

替换符 功能
%s 字符串
%d,%i 十进制整数
%f 浮点格式
%c ASCII字符,即显示对应参数的第一个字符
%b 相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义
$o 八进制值
%u 不带正负号的十进制值
%x 十六进制值(a-f)
%X 十六进制值(A-F)
%% 表示%本身

说明:

1
2
3
%[N]s		# N表示输出宽度,不够使用空格补齐  -N 表示左对齐
%[0][N]d # 0表示宽度不够时在左边补0,N表示输出宽度
%[.N]f # .N 表示保留的小数位

常用转义字符

转义符 功能
\a 警告字符,通常为ASCII的BEL字符
\b 后退
\f 换页
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ 表示\本身

算术运算

​ Shell允许在某些情况下对算术表示式进行求值,比如:let和declare 内置命令,(( ))复合命令和算术扩展。求值以固定宽度的整数进行,不检查溢出,尽管除以0 被标记为错误。运算符及其优先级,关联性和值与C语言相同。以下运算符列表分组为等优先级运算符级别。级别按降序排列优先。

注意:bash 只支持整数,不支持小数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
**										#乘方(指数运算)
* / % #乘法、除法、取余
+ - #加法、减法
<< >> #左移位、右移位(按位)
<= >= < > #比较(小于等于、大于等于、小于、大于)
== != #等于、不等于
& #按位与
^ #按位异或
| #按位或
&& #逻辑与
|| #逻辑或
expr?expr:expr #条件运算符(三元运算)
expr1,expr2 #逗号运算符
= *= /= %= += -= <<= >>= &= ^= |= #赋值及复合赋值
++i --i #变量前置自增、前置自减
i++ i-- #变量后置自增、后置自减
! ~ #逻辑非、按位取反(否定)

Linux的随机数生成器

1
2
3
4
$RANDOM									#取值范围:0-32767

#生成固定范围随机数
echo $[RANDOM%10] #生成0-9的随机数

随机字体颜色

1
2
3
echo -e "\033[1;$[RANDOM%7+31]mhello\033[0m"

#详细的颜色和用法可以前往观看 <命令提示符自定义>

逻辑运算

​ 这部分与数电非常相似。但这里只会用到与逻辑门类似的几个运算符,用于数学计算

与或非

image-20250927161842440

bool值 二进制表示 说明
true 1
false 0

与(&):有一假则全假,全真才是真

1
2
3
4
# 在Linux中的数学计算中使用时,是以二进制形式来进行的比较
echo $((5 & 3)) # 5 的二进制是 101,3 是 011 → 按位与结果为 001 (十进制 1)
echo $((7 & 0)) # 任何数与 0 按位与结果均为 0
echo $((1 & 1)) # 1 的二进制是 1 → 按位与结果仍为 1

或(|):当两者有一个为真时,则结果为真

1
2
3
echo $((5 | 3))   # 5 的二进制是 101,3 是 011 → 按位或结果为 111 (十进制 7)
echo $((1 | 0)) # 1 的二进制是 1,0 是 0 → 按位或结果为 1
echo $((0 | 0)) # 结果为 0

非(!):当输入为假输出为真,输入为真时则输出为假

变量 运算 非运算结果
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
2
3
4
5
# 条件测试命令

[ 判断语句 ]
[[ 增强版,支持扩展正则表达式 ]]
# 注:方括号两侧与其中的判断语句必须有空格分隔,不然会报错

文件判断相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-a FILE     #如果文件存在则为真
-b FILE     #如果文件为块特殊文件则为真
-c FILE     #如果文件为字符特殊文件则为真
-d FILE     #如果文件为目录则为真
-e FILE     #如果文件存在则为真
-f FILE     #如果文件存在且为常规文件则为真
-g FILE     #如果文件的组属性设置打开则为真
-h FILE     #如果文件为符号链接则为真
-L FILE     #如果文件为符号链接则为真
-k FILE     #如果文件的粘滞 (sticky) 位设定则为真
-p FILE     #如果文件为命名管道则为真
-r FILE     #如果当前用户对文件有可读权限为真
-s FILE     #如果文件存在且不为空则为真
-S FILE     #如果文件是套接字则为真
-t FD       #如果文件描述符在一个终端上打开则为真
-u FILE   #如果文件设置了SUID特殊权限则为真
-w FILE     #如果当前用户对文件有可写权限为真
-x FILE     #如果当前用户对文件有可执行权限为真
-O FILE     #如果当前用户是文件属主为真
-G FILE     #如果当前用户对文件属组为真
-N FILE     #如果文件上次被读取之后修改过则为真
FILE1 -nt FILE2 #如果 file1 文件新于 file2 文件 则为真(根据修改日期)
FILE1 -ot FILE2 #如果 file1 文件旧于 file2 文件则为真
FILE1 -ef FILE2 #如果 file1 文件是 file2 文件的硬链接则为真

字符串判断相关

1
2
3
4
5
6
7
8
9
-z STRING     #判断字符串是否为空,为空则为真
-n STRING #如果字符串不为空则为真
STRING #同上

#两个字符串变量比较,一定要有空格,如果没有空格,就变成赋值了
STRING1 = STRING2 #如果 string1 和 string2 字符串相同则为真
STRING1 != STRING2 #如果 string1 和 string2 字符串不相同则为真
STRING1 < STRING2   #只比较第一个字符,以字母表顺序来比较,string1在string2 之前则为真,< 要转义
STRING1 > STRING2   #只比较第一个字符,以字母表顺序来比较,string1在string2 之后则为真, > 要转义

数学相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#数字相关 格式 arg1 OP arg2
-eq #等于
-ne #不等于
-lt #小于
-le #小于等于
-gt #大于
-ge #大于等于

#算术表达式比较
== #相等
!= #不相等
<= #小于或等于
>= #大于或等于
< #小于
> #大于

其它

1
2
3
4
5
6
-o OPTION       #如果在shell 中开启了某项,则为真
-v VAR         #如果变量被设置为真
-R VAR         #如果变量被设置且被引用则为真
! EXPR         #表达式结果取反
EXPR1 -a EXPR2 #如果 expr1 和 expr2 都为真则为真 &&
EXPR1 -o EXPR2 #如果 expr1 和 expr2 有一个为真则为真

关于()和{}

1
2
3
( cmd1;cmd2;... ) 和 { cmd1;cmd2;...; } 都可以将多个命令组合在一起,批量执行
( list ) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境
{ list; } 不会开启子shell, 在当前shell中运行,会影响当前shell环境,左侧要有空格,右侧要有; 结束

组合测试条件

​ 很多时候我们需要一次多个条件一起判断。不然会导致脚本中出现多层嵌套结构,降低代码的可读性,所以此时我们可以使用复合判断语句来进行判断。

第一种方式

1
2
3
4
5
# 使用-a , ! 和 -o 来进行连接

[ EXPRESSION1 -a EXPRESSION2 ] #并且,EXPRESSION1和EXPRESSION2都是真,结果才为真
[ EXPRESSION1 -o EXPRESSION2 ] #或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为真
[ ! EXPRESSION ] #取反

第二种方式

1
2
3
4
5
6
7
8
# 使用 && , ! , || 来进行连接

COMMAND1 && COMMAND2 #并且,短路与,代表条件性的AND THEN
#如果COMMAND1成功,将执行COMMAND2,否则,将不执行COMMAND2

COMMAND1 || COMMAND2 #或者,短路或,代表条件性的OR ELSE
#如果COMMAND1 成功,将不执行COMMAND2,否则,将执行COMMAND2
! COMMAND   #非,取反

使用read来接受输入

​ 使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变量,所有剩余单词都被分配给最后一个变量,如果变量名没有指定,默认标准输入的值赋值给系统内置变量REPLY

1
2
3
4
5
6
7
8
read [options] [name ...]

#常见选项
-p   #指定要显示的提示
-s #静默输入,一般用于密码
-n N #指定输入的字符长度N
-d 'CHAR'   #输入结束符
-t N #TIMEOUT为N秒

Bash shell 的配置文件

bash shell的配置文件很多,可以根据范围来划分为以下两类:

全局配置:针对所有用户都有效的配置。但会被个人配置覆盖。(先加载全局配置再加载个人配置)

1
2
3
4
# 文件所在地
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc ------------------------ /etc/bash.bashrc #ubuntu

个人配置:只在特定用户登录时生效。

1
2
~/.bash_profile
~/.bashrc

编辑配置文件生效

对profile和bashrc文件修改后,想使修改内容立刻生效需要一定的操作

  • 关闭当前shell进程重新启动一个
  • source path 使用该指令重新加载指定路径(path)的配置文件

流程控制

条件选择

选择执行 if 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
# 格式
if COMMANDS; then
COMMANDS;
elif COMMANDS; then
COMMANDS; ...
else
COMMANDS;
fi

# 其中if必须存在。elif可以存在多个。else至多存在一个

- 多个条件时,逐个条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句
- if 语句可嵌套

条件判断 case 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 格式
case 变量引用 in
PAT1)
分支1命令
;;
PAT2)
分支2命令
;;
...
*)
其它分支命令
;;
esac

# 例:
read -p "Do you agree(yes/no)? " INPUT
INPUT=`echo $INPUT | tr 'A-Z' 'a-z'`
case $INPUT in
y|yes)
echo "You input is YES"
;;
n|no)
echo "You input is NO"
;;
*)
echo "Input fales,please input yes or no!"
;;
esac

循环

将某代码段重复运行多次,通过满足进入循环的条件和退出循环的条件来达成任务

循环 for

1
2
3
4
5
6
7
8
9
10
11
12
13
# 格式
for NAME [in WORDS ... ] ; do COMMANDS; done

#方式1
for 变量名 in 列表;do
循环体
done

#方式2
for 变量名  in 列表
do
循环体
done

运行方式:

  • 依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束
  • 如果省略 [in WORDS … ] ,此时使用位置参数变量 in “$@”

for 循环列表生成方式:

  • 直接给出列表
  • 整数列表:如 {start..end}
  • 返回列表的命令:如 $(COMMAND)
  • 使用glob,如:*.sh
  • 变量引用,如:$@,$,$#

循环 while

1
2
3
4
5
6
7
8
9
10
11
12
# 格式
while CONDITION; do COMMANDS; done


while CONDITION; do
循环体
done

while CONDITION
do
循环体
done

说明:

CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环,因此:

CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正

进入条件:CONDITION为 true

退出条件:CONDITION为 false

注:while 存在特殊用法

1
2
3
while read line; do
循环体
done < /PATH/FROM/SOMEFILE

依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line

循环 until

1
2
3
4
5
# 格式
until CONDITION; do COMMANDS; done
until CONDITION; do
循环体
done

说明:

进入条件: CONDITION 为false

退出条件: CONDITION 为true

循环控制语句 continue

continue: 是跳过当前循环的剩余部分,直接进入下一次迭代

continue [N]:跳过从内向外数第N层循环的本次迭代

continue 2: 跳过外层循环的本次迭代(不带嵌套循环里会出错)

1
2
3
4
5
6
7
8
9
10
# 格式
while CONDITION1; do
CMD1
...
if CONDITION2; then
continue
fi
CMDn
...
done

循环控制语句 break

break [N]:提前结束第N层整个循环,最内层为第1层

1
2
3
4
5
6
7
8
9
10
11
# 格式

while CONDITION1; do
CMD1
...
if CONDITION2; then
break
fi
CMDn
...
done

循环控制 shift

shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。

参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到shift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 例子
[root@ubuntu2204 ~]# cat shift.sh
until [ -z "$1" ]; do
echo "$@ "
shift
done

[root@ubuntu2204 ~]# bash s.sh a b c d e f g
a b c d e f g
b c d e f g
c d e f g
d e f g
e f g
f g
g

循环与菜单 select

1
2
3
4
5
# 格式
select NAME [in WORDS ... ;] do COMMANDS; done
select NAME in list ;do
循环体命令
done

说明:

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
#语法一
func_name(){
...函数体...
}

#语法二
function func_name {
...函数体...
}

#语法三
function func_name(){
...函数体...
}

查看函数

1
2
3
4
5
6
7
8
9
10
11
#查看当前已定义的函数名(只查看函数名)
declare -F

#查看当前已定义的函数定义(查看函数名和函数内部的命令)
declare -f

#查看指定当前已定义的函数名
declare -f func_name

#查看当前已定义的函数名定义
declare -F func_name

删除函数

1
unset func_name

函数调用

函数的调用方式

  • 可在交互式环境下定义函数
  • 可将函数放在脚本文件中作为它的一部分
  • 可放在只包含函数的单独文件中

调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数代码

函数的生命周期:被调用时创建,返回时终止