22.shell编程实战(一)
文章目录
- 2.创建第一个 Shell 脚本
- 2.1 Shell 脚本
- 2.1.1 脚本开头
- 2.1.2 脚本注释
- 2.1.3 bash 与 sh 的区别
- 2.2 Shell 脚本的执行
- 3.什么是 Shell 变量
- 3.1.1 什么是变量
- 3.1.2 Shell 变量的特性
- 3.1.3 变量类型
- 3.2 环境变量
- 3.2.1 设置环境变量
- 1. 设置环境变量
- 2. 设置登录后提示信息
- 3.2.2 显示与取消环境变量
- 3.2.3 环境变量初始化与对应文件的生效顺序
- 3.2.4 环境变量的知识小结
- 3.3 普通变量
- 3.3.1 普通变量定义
- 3.3.2 变量名及变量值要求
- 4.Shell 变量进阶知识
- 4.1 Shell 中特殊变量
- 4.1.1 Shell 位置参数变量
- 4.1.2 Shell 进程中的特殊状态变量
- $?
- 4.2 Shell 内置变量命令
- 4.2.1 read
- 5.变量的数值计算实践
- 5.1 算术运算符
- 6. Shell 脚本的条件测试
- 6.1 Shell 脚本的条件测试
- 6.1.1 条件测试方法综述
- 6.1.2 test 条件测试的简单语法及示例
- 6.1.3 \[](中括号)条件测试语法及示例
- 6.1.4 [[]](双中括号)条件测试语法及示例
- 6.2 文件判断表达式
- 6.2.1 文件判断表达式的用法
- 6.2.2 文件判断表达式举例
- 6.2.3 特殊条件判断表达式案例
- 6.3 字符串判断表达式
- 6.3.1 字符串测试操作符
- 6.4 整数二元比较操作符
- 6.4.1 整数二元比较操作符介绍
- 6.5 逻辑操作符
- 6.5.1 逻辑操作符介绍
- 6.6 判断表达式 test、[]、[[]]、(())的区别总结
2.创建第一个 Shell 脚本
2.1 Shell 脚本
在 Linux 系统中, Shell 脚本通常是在编辑器 vi/vim 中编写的,由UNIX/Linux命令、bash Shell 命令、程序结构控制语句和注释等内容组成。这里推荐用Linux自带的功能更强大的vim编辑器来编写,可以事先做一个别名alias vi=’vim’
,并使其永久生效,这样以后习惯输入 vi 的读者也就可以直接调用 vim
编辑器了。设置方法如下:
[root@server ~ 12:46:45]# echo "alias vi='vim'" >> /etc/bashrc# 刷新
[root@server ~ 12:47:43]# source /etc/bashrc
2.1.1 脚本开头
一个规范的 Shell 脚本在第一行会指出由哪个程序(解释器)来执行脚本中的内容,这一行内容在Linux bash 的编程一般为:
#!/bin/bash
或
#!/bin/sh
其中,开头的"#!“字符又称为幻数(其实叫什么都无所谓,知道它的作用就好),在执行bash脚本的时候,内核会根据”#!"字符后的解释器来确定该用哪个程序解释这个脚本中的内容。
注意,这一行必须位于每个脚本顶端的第一行,如果不是第一行则为脚本注释行,
例如下面的例子:
[ghl@server bin 09:24:33]$ vim hello.sh
#!/bin/bash
echo Hello World
文件后缀一般加.sh
CentOS 和 RHEL下默认的 Shell 均为 bash。因此,在写 Shell 脚本的时候,脚本的开头即使不加幻数,也会交给bash解释。如果写脚本不希望使用系统默认的 Shell 解释,那么就必须要指定解释器了,否则脚本文件执行后的结果可能就不是你所要的。
建议读者养成好的编程习惯,不管采用什么脚本,最好都加上相应的开头解释器语言标识,遵守 Shell 编程规范。
2.1.2 脚本注释
在 Shell 脚本中,跟在# 后面的内容表示注释,用来对脚本进行注释说明,注释部分不会被当作程序来执行,仅仅是给开发者和使用者看的,系统解释器是看不到的,更不会执行。
注释可自成一行,也可以跟在脚本命令的后面与命令在同一行。
开发脚本时,如果没有注释,那么团队里的其他人就会很难理解脚本对应内容的用途,而且若时间长了,自己也会忘记。因此,我们要尽量养成为所开发的 Shell 脚本书写关键注释的习惯,书写注释不光是为了方便别人,更是为了方便自己,避免影响团队的协作效率,以及给后来接手的人带来维护困难。
特别提示一下,注释尽量不要用中文,在脚本中最好也不要有中文。
2.1.3 bash 与 sh 的区别
早期的bash与sh稍有不同,但大多数脚本都可以不加修改地在sh上运行。
sh为bash的软链接,大多数情况下,脚本的开头使用 #!/bin/bash
和 #!/bin/sh
是没有区别的,但更规范的写法是在脚本的开头使用 #!/bin/bash
。
2.2 Shell 脚本的执行
当 Shell 脚本运行时,它会先查找系统环境变量ENV,该变量指定了环境文件,再加载了上述环境变量文件后, Shell 就开始执行 Shell 脚本中的内容。
Shell 脚本是从上至下、从左至右依次执行每一行的命令及语句的,即执行完了一个命令后再执行下一个,如果在 Shell 脚本中遇到子脚本(即脚本嵌套)时,就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句。通常情况下,在执行 Shell 脚本时,会向系统内核请求启动一个新的进程,以便在该进程中执行脚本的命令及子 Shell 脚本,基本流程如下。
Shell 脚本的执行通常可以采用以下几种方式:
-
bash script-name 或sh script-name:这是当脚本文件本身没有可执行权限(即文件权限属性x位为-号)时常使用的方法,或者脚本文件开头没有指定解释器时需要使用的方法。
[ghl@server bin 09:49:35]$ bash hello.sh Hello World[ghl@server tmp 12:58:28]$ bash ~/bin/hello.sh Hello World
-
/path/script-name 或 ./script-name:指在当前路径下执行脚本(脚本需要有执行权限),需要将脚本文件的权限先改为可执行,然后通过脚本绝对路径或相对路径就可以直接执行脚本了。
[ghl@server bin 09:25:32]$ chmod +x hello.sh # 相对路径 [ghl@server bin 09:26:56]$ ./hello.sh Hello World# 绝对路径 [ghl@server bin 09:27:07]$ /home/ghl/bin/hello.sh Hello World
在企业生产环境中,不少运维人员在写完 Shell 脚本之后,由于忘记为该脚本设置执行权限,然后就直接应用了,结果导致脚本没有按照自己的意愿手动或定时执行,对于这一点,避免出现该问题的方法就是用第1种方法替代第2种。
-
/path/script-name 或 ./script-name:指在当前路径下执行脚本(脚本需要有执行权限),需要将脚本文件的权限先改为可执行,然后通过脚本绝对路径或相对路径就可以直接执行脚本了。
[ghl@server bin 12:58:56]$ source hello.sh Hello World # 等效于 [ghl@server bin 13:00:58]$ . hello.sh Hello World
# source 读取文件内容到当前shell [root@server ~ 14:15:47]# source users.info [root@server ~ 14:15:58]# echo $user_name zhangsan
-
bash < script-name 或 cat scripts-name|sh:同样适用于bash,这种用法也很常见。
[ghl@server bin 13:01:06]$ bash < hello.sh Hello World[ghl@server bin 13:01:42]$ cat hello.sh | bash Hello World
案例
[ghl@server bin 13:01:55]$ vim gather_os_information.sh #!/bin/bash
# 由bash解释执行# 收集系统块设备信息
echo ============系统块设备信息============
lsblk
echo ============系统块设备信息============
echo# 收集文件系统信息
echo ==============文件系统信息==============
df -h | grep -v tmpfs
echo ==============文件系统信息==============
echo# 收集系统中CPU和内存使用情况
echo ============CPU Memory tasks信息============
os_info_file=/tmp/tasks.info
top -n 1 | head -5 > ${os_info_file}
echo 系统总任务数量:$(cat ${os_info_file} | awk '/^Tasks/ {print $2}')
echo 系统正在运行的进程数量:$(cat ${os_info_file} | awk '/^Tasks/ {print $4}')
echo 系统中僵尸进程数量:$(cat ${os_info_file} | awk 'NR==2 {print $(NF-1)}')
echo 系统总内存为:$(cat ${os_info_file} | awk 'NR==4 {print $4}')K
echo 系统可用内存为:$(cat ${os_info_file} | awk 'NR==4 {print $6}') K
echo ============CPU Memory tasks信息============
rm -f ${os_info_file}
结果如下:
[ghl@server bin 10:25:26]$ bash ~/bin/gather_os_information.sh
============系统块设备信息============
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 100G 0 disk
├─sda1 8:1 0 1G 0 part /boot
└─sda2 8:2 0 99G 0 part ├─centos-root 253:0 0 50G 0 lvm /├─centos-swap 253:1 0 2G 0 lvm [SWAP]└─centos-home 253:2 0 47G 0 lvm /home
sr0 11:0 1 4.4G 0 rom
============系统块设备信息==========================文件系统信息==============
文件系统 容量 已用 可用 已用% 挂载点
/dev/mapper/centos-root 50G 2.0G 49G 4% /
/dev/mapper/centos-home 47G 33M 47G 1% /home
/dev/sda1 1014M 139M 876M 14% /boot
==============文件系统信息==========================CPU Memory tasks信息============
系统总任务数量:198
系统正在运行的进程数量:1
系统中僵尸进程数量:0
系统总内存为:4026116K
系统可用内存为:3599032 K
============CPU Memory tasks信息============
3.什么是 Shell 变量
3.1.1 什么是变量
简单地说,变量名是用一个固定的字符串(字符、数字和下划线的组合,不能以数字开头)代替更多、更复杂的内容,该内容里可能还会包含变量、路径、字符串等其他的内容。
变量是暂时存储数据的地方及数据标记,所存储的数据存在于内存空间中,通过正确地调用内存空间中变量的名字就可以取出与变量对应的数据。使用变量的最大好处就是使程序开发更为方便,当然,在编程中使用变量也是必须的,否则就很难完成相关的程序开发工作。
下面是定义变量和打印变量的示例:
# 定义变量,名字为username,对应的内容ghl
[ghl@server ~ 22:25:14]$ username="ghl"# 打印变量的值
[ghl@server ~ 22:25:27]$ echo $username
ghl
变量的赋值方式为:先写变量名称,紧接着是这个字符,最后是值,中间无任何空格。
通过 echo
命令加上 $username
即可输出 username
变量的值。变量的内容一般要加双引号,以防止出错,特别是当值里的内容之间有空格时。
3.1.2 Shell 变量的特性
默认情况下,在bash Shell 中是不会区分变量类型的。例如:常见的变量类型为整数、字符串、小数等都当做字符串变量。这和其他强类型语言(例如:Java/C语言)是有区别的,当然,如果需要指定 Shell 变量的类型,也可以使用 declare
命令定义变量的类型,但在一般情况下没有这个需求。
3.1.3 变量类型
变量根据范围可分为两类:
-
全局变量,在创建它们的 Shell 及其派生出来的任意子进程 Shell 中使用。
-
局部变量,只能在创建它们的 Shell 函数或 Shell 脚本中使用。
变量根据是否是用户自定义也可分为两类:
- 普通变量:也称为常规变量,由开发者在开发脚本程序时创建。
- 环境变量:定义shell 执行环境。环境变量又可分为自定义环境变量和bash内置的环境变量。
3.2 环境变量
环境变量一般是指用export内置命令导出的变量,用于定义 Shell 的运行环境,保证 Shell 命令的正确执行。 Shell 通过环境变量来确定登录用户名、命令路径、终端类型、登录目录等,所有的环境变量都是系统全局变量,可用于所有子进程中,这包括编辑器、 Shell 脚本和各类应用。
环境变量可以在命令行中设置和创建,但用户退出命令行时这些变量值就会丢失,因此,如果希望永久保存环境变量,可在用户家目录下的 .bash_profile
或 .bashrc
(非用户登录模式特有,例如远程SSH)文件中,或者全局配置 /etc/bashrc
(非用户登录模式特有,例如远程SSH)或 /etc/profile
文件中定义。在将环境变量放入上述的文件中后,每次用户登录时这些变量都将被初始化。
按照系统规范,所有环境变量的名字均采用大写形式。在将环境变量应用于用户进程程序之前,都应该用 export
命令导出定义。有一些环境变量,比如HOME
、PATH
、USER
等,在用户登录之前就已经被/bin/login程序设置好了。通常环境变量被定义并保存在用户家目录下的 .bash_profile
文件或全局的配置文件 /etc/profile
中。
部分bash环境变量展示:
- EDITOR,默认编辑器。
- HISTFILE,历史文件位置。
- HISTSIZE,历史命令个数。
- PS1,命令行提示符。
- LANG,Shell 环境语言。
- PATH,命令搜素路径。
- HOME,当前用户家目录。
- USER,当前用户。
- PWD,当前 Shell 路径。
- IFS,字符串分隔符。
在查看设置的变量时,有3个命令可以显示变量的值:
- set 命令,输出所有的变量,包括全局变量和局部变量。
- env 命令,只显示全局变量,包括shell的环境。
- declare 命令,输出所有的变量、函数、整数和已经导出的变量。
set -o
命令显示bash Shell 的所有参数配置信息。
3.2.1 设置环境变量
1. 设置环境变量
如果想要设置环境变量,就要在给变量赋值之后或在设置变量时使用export
命令,具体设置见下文的示例。其实,除了 export
命令,带-x
选项的declare
内置命令也可以完成同样的功能(注意:此处不要在变量名前面加$)。
export
命令和 declare
命令的格式如下:
export 变量名=value
变量名=value ; export 变量名
declare -x 变量名=value
示例:
export NAME=ghl
declare -x NAME=ghl
NAME=ghl;export NAME
下面来看看让环境变量永久生效的常用设置文件。
-
用户的环境变量配置文件:
~/.bash_profile
和~/.bashrc
。推荐在(~/.bashrc)文件中设置。 -
全局环境变量的配置文件:
/etc/profile
、/etc/bashrc
、/etc/profile.d/
。**推荐在 /etc/bashrc 文件中设置。**若要在登录后初始化或显示加载内容,则把脚本文件放在/etc/profile.d/
下即可(无须加执行权限)。
2. 设置登录后提示信息
- 第一种是在
/etc/motd
里增加提示的字符串,如下:
[ghl@server ~ 22:25:35]$ vim /etc/motd
welcome to ghl linux Shell training.
2.第二种是在 /etc/profile.d/ 下面增加如下脚本。
[ghl@server ~ 22:30:16]$ sudo vim /etc/profile.d/ghl.sh
echo Welcome to ghl linux Shell training.
补充:
- 登录前提示符由文件 /etc/issue 提供。
- 登录后时间提示符由ssh服务提供:在
/etc/ssh/sshd_config
文件中配置PrintLastLog yes
,默认启用。
3.2.2 显示与取消环境变量
- 通过
echo
或printf
命令打印环境变量。 - 用
env
或set
显示默认的环境变量。 - 用
unset
消除本地变量和环境变量。
3.2.3 环境变量初始化与对应文件的生效顺序
在登录 Linux 系统并启动一个bash shell
时,默认情况下 bash
会在若干个文件中查找环境变量的设置。这些文件可统称为系统环境文件。bash
检查的环境变量文件的情况取决于系统运行 Shell 的方式。
系统运行 Shell 的方式一般有3种:
- 通过系统用户登录后默认运行的 Shell 。
- 非登录交互式运行 Shell 。
- 执行脚本运行非交互式 Shell 。
当用户登录 Linux 系统时, Shell 会作为登录 Shell 启动。此时的登录 Shell 加载环境变量的顺序如下图所示。
-
用户登录系统后首先会加载
/etc/profile
全局环境变量文件,这是Linux系统上默认的 Shell 主环境变量文件。系统上每个用户登录都会加载这个文件。 -
当加载完
/etc/profile
文件后,才会执行/etc/profile.d
目录下的脚本文件,这个目录下的脚本文件有很多,例如:系统的字符集设置等。 -
之后开始运行
$HOME/.bash_profile
。在这个文件中,又会去找$HOME/.bashrc
:如果有,则执行;如果没有,则不执行。在HOME/.bashrc
文件中又会去找/etc/bashrc
(全局环境变量文件),如果有,则执行,如果没有,则不执行。 -
如果用户的 Shell 不是登录时启动的,比如手动敲下bash时启动,那么这种非登录 Shell 只会加载
$HOME/.bashrc
(用户环境变量文件),并会去找/etc/bashrc
(全局环境变量文件)。因此如果希望在非登录 Shell 下也可读到设置的环境变量等内容,就需要将变量设定等写入
$HOME/.bashrc
或者/etc/bashrc
,而不是$HOME/.bash_profile
或/etc/profile
。
3.2.4 环境变量的知识小结
- 变量名通常要大写。
- 变量可以在自身的 Shell 及子 Shell 中使用。
- 常用
export
来定义环境变量。 - 执行
env
默认可以显示所有的环境变量名称及对应的值。 - 输出时用
$变量名
,取消时用unset变量名
。 - 书写
crond
定时任务时要注意,脚本要用到的环境变量最好先在所执行的 Shell 脚本中重新定义。 - 如果希望环境变量永久生效,则可以将其放在用户环境变量文件或全局环境变量文件里。
3.3 普通变量
本地变量在用户当前 Shell 生存期的脚本中使用。例如,本地变量laoma
的定义为bingbing
, 这个值只在用户当前 Shell 生存期中有意义。如果在 Shell 中启动另一个进程或退出,那么变量laoma
的值将会无效。
3.3.1 普通变量定义
为普通变量的定义赋值,一般有以下3 种写法:
变量名=value
,赋值时不加引号。变量名='value'
,赋值时加单引号。变量名="value"
,赋值时加双引号。
3.3.2 变量名及变量值要求
- 变量名:一般是由字母、 数字 、下划线组成的,只可以以字母或下划线开头,例如:
laoma
、laoma_123
、laoma_training
。 - 变量值:可以用单引号或双引号引起来,也可不加引号,但是这三者的含义是不同的。
4.Shell 变量进阶知识
4.1 Shell 中特殊变量
4.1.1 Shell 位置参数变量
在 Shell 中存在一些特殊且重要的变量,例如:$0
、$1
,我们称之为位置参数变量。要从命令行、函数或脚本执行等处传递参数时,就需要在 Shell 脚本中使用位置参数变量。
部分位置参数变量如下:
- $0,获取当前执行的 Shell 脚本的文件名,如果执行脚本包含了路径,那么就包括脚本路径。
- **n∗∗,获取当前执行的Shell脚本的∗∗第n个参数值∗∗。如果n大于9,则用大括号括起来,例如n**,获取当前执行的 Shell 脚本的**第n个参数值**。如果n大于9,则用大括号括起来,例如n∗∗,获取当前执行的Shell脚本的∗∗第n个参数值∗∗。如果n大于9,则用大括号括起来,例如{10},接的参数以空格隔开。
- $#,获取当前执行的 Shell 脚本后面接的参数数量。
- **∗∗∗,获取当前Shell脚本所有传参的参数,不加引号和‘***,获取当前 Shell 脚本所有传参的参数,不加引号和`∗∗∗,获取当前Shell脚本所有传参的参数,不加引号和‘@
相同;如果给
∗‘加上双引号,例如:‘"*`加上双引号,例如:`"∗‘加上双引号,例如:‘"*",则表示将所有的参数视为单个字符串,相当于
$1 $2 $3`。 - **@∗∗,获取当前Shell脚本所有传参的参数,不加引号和@**,获取当前 Shell 脚本所有传参的参数,不加引号和@∗∗,获取当前Shell脚本所有传参的参数,不加引号和*相同;如果给@加上双引号,例如:‘@加上双引号,例如:`@加上双引号,例如:‘@
,则表示将所有的参数视为不同的独立字符串,相当于
"$1"、“$2”、“$3”…`,这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在每个参数里的任何空白。
4.1.2 Shell 进程中的特殊状态变量
$?
作用:获取执行上一个指令的执行状态返回值:0为成功,非零为失败,这个变量最常用。
[ghl@server bin 11:33:08]$ ls
gather_os_information.sh hello.sh service_ctl show_args.sh ssh_ctl[ghl@server bin 11:33:28]$ echo $?
0
[ghl@server bin 11:33:37]$ ls sdfddss
ls: 无法访问sdfddss: 没有那个文件或目录
[ghl@server bin 11:33:46]$ echo $?
2
[ghl@server bin 11:33:48]$ grep selinux /etc/selinux/config
[ghl@server bin 11:34:22]$ echo $?
1
[ghl@server bin 11:34:25]$ grep -i selinux /etc/selinux/config
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of three values:
SELINUXTYPE=targeted
[ghl@server bin 11:34:33]$ echo $?
0
4.2 Shell 内置变量命令
bash Shell 包含一些内置命令。 这些内置命令在目录列表里是看不见的,它们由 Shell 本身提供。常用的内部命令有: echo
、eval
、exec
、read
、shift
等。
4.2.1 read
从标准输入读取字符串等信息, 传给 Shell 程序内部定义的变量。
[ghl@server bin 11:49:09]$ cat prepare_os.sh
#!/bin/bash
#设置防火墙
read -p "请输入start或stop控制防火墙" status
systemctl $status firewalld
# status是变量名# 设置操作权限
[ghl@server bin 11:49:14]$ chmod +x prepare_os.sh [ghl@server bin 11:49:30]$ ./prepare_os.sh
请输入start或stop控制防火墙start
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to manage system services or units.
Authenticating as: root
Password: `123`
==== AUTHENTICATION COMPLETE ===[ghl@server bin 11:49:47]$ ./prepare_os.sh
请输入start或stop控制防火墙status
● firewalld.service - firewalld - dynamic firewall daemonLoaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled)Active: active (running) since 四 2025-10-09 11:49:47 CST; 15s agoDocs: man:firewalld(1)Main PID: 2163 (firewalld)CGroup: /system.slice/firewalld.service└─2163 /usr/bin/python2 -Es /usr/sbin/firewalld --nofork --nopid
**案例1:**配置脚本文件添加用户信息及设置用户密码
[root@server ~ 14:22:13]# cat useradd.sh
#!/bin/bash# 提供用户名
read -p "请输入用户名:" user_name#提供用户性别
read -p "请输入用户性别:" user_sex# 提供用户年龄
read -p "请输入用户年龄:" user_age# 提供用户密码
read -sp "请输入用户密码:" user_pass
echo # 添加用户
useradd ${user_name}#设置用户密码
echo ${user_pass} | passwd --stdin ${user_name}# 将以上用户信息存储到/etc/users.info中
echo ${user_name}:${user_sex}:${user_age}:x >> /etc/users.info# 打印用户信息已存储到/etc/users.info中
echo “用户信息已存储到/etc/users.info中。”[root@server ~ 13:57:43]# bash useradd.sh
请输入用户名:zhangsan
请输入用户性别:male
请输入用户年龄:20
请输入用户密码:
更改用户 zhangsan 的密码 。
passwd:所有的身份验证令牌已经成功更新。
“用户信息已存储到/etc/users.info中。”[root@server ~ 14:26:46]# cat /etc/users.info
zhangsan:male:20:x
**案例2:**根据users.info中的用户信息添加用户
[root@server ~ 14:16:09]# vim users.info
user_name=zhangsan
user_sex=male
user_age=18
user_pass=123[root@server ~ 14:17:30]# cat useradd-2.sh
#!/bin/bash# 添加用户信息:读取用户信息到当前shell
source /root/users.info# 添加用户
useradd ${user_name}#设置用户密码
echo ${user_pass} | passwd --stdin ${user_name}[root@server ~ 14:17:18]# bash useradd-2.sh
useradd:用户“zhangsan”已存在
更改用户 zhangsan 的密码 。
passwd:所有的身份验证令牌已经成功更新。
5.变量的数值计算实践
5.1 算术运算符
如果要执行算术运算,就会离不开各种运算符号,和其他编程语言类似,Shell 也有很多算术运算符。
下面就给大家介绍一下常见的 Shell 算术运算符:
- +、-,一元正号和负号。
- +、-,加法和减法。
- *、/、%,乘法、除法、取余(取模)。
- **,幂运算。
- ++、–,增加及减少,可前置也可放在变量结尾。
- !、&&、||,逻辑非(取反)、逻辑与(and)、逻辑或(or)。
- <、<=、>、>=,比较符号(小于、小于等于、大于、大于等于)。
- ==、!=、=,比较符号(相等、不相等,对于字符串也可以表示相当于)。
- <<、>>,向左移位、向右移位。
- ~、|、&、^,按位取反、按位异或、按位与、按位。
- =、+=、-=、*=、/=、%=,赋值运算符,例如
a+=1
相当于a=a+1
,a-=1
相当于a=a-1
。
Shell 中常见的算术运算命令:
- (()),用于整数运算的常用运算符,效率很高。
- let,用于整数运算,类似于
(())
。 - expr,可用于整数运算,但还有很多其他的额外功能。
- bc,Linux下的一个计算器程序(适合整数及小数运算)。
- $[],用于整数运算。
- awk,awk 既可以用于整数运算,也可以用于小数运算。
- declare,定义变量值和属性,-i参数可以用于定义整形变量,做运算。
[root@server ~ 14:38:41]# echo $[1+2]
3
[root@server ~ 14:38:49]# echo $[$(echo {1..100} | sed 's/ /+/g')]
5050
[root@server ~ 14:39:43]# echo $(($(echo {1..100} | sed 's/ /+/g') ))
5050
[root@server ~ 14:40:13]# echo $((1+2))
3
[root@server ~ 14:40:43]# ((1+2))
[root@server ~ 14:40:56]# echo $?
0
[root@server ~ 14:40:58]# let 1+2
[root@server ~ 14:41:08]# let sum=1+2
[root@server ~ 14:41:18]# echo $sum
3
[root@server ~ 14:41:41]# bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
1.1+2.2
3.3
quit
[root@server ~ 14:42:10]# echo 1.1+2.2 | bc
3.3
6. Shell 脚本的条件测试
6.1 Shell 脚本的条件测试
6.1.1 条件测试方法综述
通常,在 bash 的各种条件结构和流程控制结构中都要进行各种测试,然后根据测试结果执行不同的操作,有时也会与if等条件语句相结合,来完成测试判断,以减少程序运行的错误。
执行条件判断表达式后通常会返回"真"或"假",就像执行命令后的返回值为0,表示真;非0,表示假。
在 bash 编程里,条件测试常用的语法如下:
- 语法1:test <判断表达式>,test命令和"<判断表达式>"之间至少有一个空格。
- 语法2:[ <判断表达式> ],和test命令的用法相同,这是推荐方法。
[]
的边界和内容之间至少有一个空格。 - 语法3:[[ <判断表达式> ]],是比test和[]更新的语法格式。
[[]]
的边界和内容之间至少有一个空格。 - 语法4:(( <判断表达式> )),一般用于 if 语句。
(())
(双小括号)两端不需要有空格。 - 语法5:comand,命令的返回值确定表达式的真值或假值。
有几个注意事项需要说明一下:
- 语法1中的
test命令
和语法2中的[]
是等价的,语法3
中的[[]]
为扩展的test命令,语法4中的(())
常用于计算。建议使用相对友好的语法2,即中括号[]
的语法格式。 - 在
[[ ]]
(双中括号)中可以使用通配符等进行模式匹配,这是其区别于其他几种语法格式的地方。 &&、||、>、<
等操作符可以应用于[[]]
中,但不能应用于[]
中,在[]
中一般用-a、-o、-gt(
用于整数)、-lt
(用于整数)代替上述操作符。- 对于整数的关系运算,也可以使用 Shell 的算术运算符
(())
。
6.1.2 test 条件测试的简单语法及示例
test条件测试的语法格式为: test <判断表达式>
[ghl@server bin 11:50:36]$ help test
test: test [表达式]Evaluate conditional expression.Exits with a status of 0 (true) or 1 (false) depending onthe evaluation of EXPR. Expressions may be unary or binary. Unaryexpressions are often used to examine the status of a file. Thereare string operators and numeric comparison operators as well.The behavior of test depends on the number of arguments. Read thebash manual page for the complete specification.File operators:-a FILE True if file exists.-b FILE True if file is block special.-c FILE True if file is character special.-d FILE True if file is a directory.-e FILE True if file exists.-f FILE True if file exists and is a regular file.-g FILE True if file is set-group-id.-h FILE True if file is a symbolic link.-L FILE True if file is a symbolic link.-k FILE True if file has its `sticky' bit set.-p FILE True if file is a named pipe.-r FILE True if file is readable by you.-s FILE True if file exists and is not empty.-S FILE True if file is a socket.-t FD True if FD is opened on a terminal.-u FILE True if the file is set-user-id.-w FILE True if the file is writable by you.-x FILE True if the file is executable by you.-O FILE True if the file is effectively owned by you.-G FILE True if the file is effectively owned by your group.-N FILE True if the file has been modified since it was last read.FILE1 -nt FILE2 True if file1 is newer than file2 (according tomodification date).FILE1 -ot FILE2 True if file1 is older than file2.FILE1 -ef FILE2 True if file1 is a hard link to file2.String operators:-z STRING True if string is empty.-n STRINGSTRING True if string is not empty.STRING1 = STRING2True if the strings are equal.STRING1 != STRING2True if the strings are not equal.STRING1 < STRING2True if STRING1 sorts before STRING2 lexicographically.STRING1 > STRING2True if STRING1 sorts after STRING2 lexicographically.Other operators:-o OPTION True if the shell option OPTION is enabled.-v VAR True if the shell variable VAR is set! EXPR True if expr is false.EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.EXPR1 -o EXPR2 True if either expr1 OR expr2 is true.arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,-lt, -le, -gt, or -ge.Arithmetic binary operators return true if ARG1 is equal, not-equal,less-than, less-than-or-equal, greater-than, or greater-than-or-equalthan ARG2.Exit Status:Returns success if EXPR evaluates to true; fails if EXPR evaluates tofalse or an invalid argument is given.
**示例1:**判断文件是否存在
[ghl@server bin 15:56:11]$ test -f file && echo true || echo false
false
该语句表示如果file文件存在,则输出true,否则输出false。这里的&&
是逻辑与,||
是逻辑或。test的-f
参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&后面的命令,而||
后面的命令是test命令执行失败之后(为假)所执行的命令。
test命令判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个&&或||)来测试,示例如下。
[ghl@server bin 16:04:38]$ test -f file && echo true
[ghl@server bin 16:04:53]$ test -f file || echo false
false
另外,逻辑操作符&&
和||
的两端既可以有空格,也可以无空格。带空格看起来会更美观一些,建议使用带空格的格式。
示例2:-z
选项判断变量值是否为空,如果为空,则为真值,反之为假值。
[ghl@server bin 16:05:02]$ unset string
[ghl@server bin 16:07:16]$ test -z "$string" && echo null string || echo $string
null string
[ghl@server bin 16:07:53]$ string="hello world"
[ghl@server bin 16:08:17]$ test -z "$string" && echo null string || echo $string
hello world
结论:test命令测试的功能很强大,但是和[]
、[[]]
的功能有所重合,因此,在实际工作中选择一种适合自己的语法就好了。对于其他的语法,能读懂别人写的脚本就可以了。
6.1.3 [](中括号)条件测试语法及示例
[]
条件测试的语法格式为:[ < 判断表达式> ]
注意:中括号内部的两端要有空格,[]
和test等价,即test的所有判断选项都可以直接在[]
里使用。
**示例1:**判断文件是否存在
[ghl@server bin 16:08:23]$ [ -f file ] && echo ture || echo false
逻辑操作符
&&
和||
的两端既可以有空格,也可以无空格。带空格看起来会更美观一些,建议使用带空格的格式。
该语句表示如果file文件存在,则输出true,否则输出false。这里的&&
是逻辑与,||
是逻辑或。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&
后面的命令,而||
后面的命令是test命令执行失败之后(为假)所执行的命令。
[]条件判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个&&或||)来测试。
6.1.4 [[]](双中括号)条件测试语法及示例
[[]]
条件测试的语法格式为: [[ < 判断表达式> ]]
。
注意:双中括号里的两端也要有空格。
**示例1:**判断文件是否存在
[ghl@server bin 16:47:03]$ [[ -f file ]] && echo true || echo false
该语句表示如果file文件存在,则输出true,否则输出false。这里的&&是逻辑与,||是逻辑或。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&后面的命令,而||后面的命令是test命令执行失败之后(为假)所执行的命令。
[]条件判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个&&或||)来测试
6.2 文件判断表达式
6.2.1 文件判断表达式的用法
文件测试常用选项:
- -d 文件,d的全拼为directory文件存在且为目录则为真,即判断表达式成立。
- -f 文件,f的全拼为file文件存在且为普通文件则为真,即判断表达式成立。
- -e 文件,e的全拼为exist文件存在则为真,即判断表达式成立。区别于"-f",-e不辨别是目录还是文件。
- -r 文件,r的全拼为read文件存在且可读则为真,即判断表达式成立。
- -s 文件,s的全拼为size文件存在且文件size不为0则为真,即判断表达式成立。
- -w 文件,w的全拼为write文件存在且可写则为真,即判断表达式成立。
- -x 文件,x的全拼为executable文件存在且可执行则为真,即判断表达式成立。
- -L 文件,L的全拼为link文件存在且为链接文件则为真,即判断表达式成立。
- f1 -nt f2,nt的全拼为newer than,文件fl比文件f2新则为真,即判断表达式成立。根据文件的修改时间来计算。
- f1 -ot f2,ot的全拼为older than,文件fl比文件f2旧则为真,即判断表达式成立。根据文件的修改时间来计算。
6.2.2 文件判断表达式举例
**示例1:**判断文件状态
# 如果目录不存在,就创建目录
[ghl@server bin 15:03:08]$ test ! -d /tmp/zhangsan && mkdir /tmp/zhangsan
[ghl@server bin 15:03:22]$ ls -d /tmp/zhangsan/
/tmp/zhangsan/# 如果目录存在,就提示目录存在
[ghl@server bin 15:03:32]$ test -d /tmp/zhangsan && echo /tmp/zhangsan/ is exist.
/tmp/zhangsan/ is exist.# 第二次执行,目录已经存储
[ghl@server bin 15:04:08]$ test ! -d /tmp/zhangsan && mkdir /tmp/zhangsan
[ghl@server bin 15:04:20]$ echo $?
1
其他
[ghl@server bin 16:47:43]$ data_path=/tmp/ghl
[ghl@server bin 16:52:53]$ mkdir ${data_path}
[ghl@server bin 16:53:14]$ [ -d ${data_path} ] && echo ${data_path} is exist
/tmp/ghl is exist[ghl@server bin 16:54:23]$ rmdir ${data_path}
[ghl@server bin 16:54:56]$ [ -d ${data_path} ] || echo ${data_path} is not exist
/tmp/ghl is not exist[ghl@server bin 16:55:18]$ [ -r /etc/shadow ] && cat /etc/shadow || echo hello
hello[ghl@server bin 16:56:04]$ [ -w ~/.bashrc ] && echo "alias setpass='echo 123 | passwd --stdin'" >> ~/.bashrc
[ghl@server bin 16:58:16]$ tail -1 ~/.bashrc
alias setpass='echo 123 | passwd --stdin'
文件常用状态判断
-d -e
-r -w -x
-u -g
6.2.3 特殊条件判断表达式案例
以下写法适用于所有的条件判断表达式,是工作中比较常用的替代if语句的方法。判断条件判断表达式的条件成立或不成立后,还需要继续执行多条命令语句的语法。
例如,当条件1成立时,同时执行命令1、命令2、命令3。
# 格式1
[ 条件1 ] && {
命令1
命令2
命令3
}# 格式2
[[ 条件1 ]] && {
命令1
命令2
命令3
}# 格式3
test 条件1 && {
命令1
命令2
命令3
}
示例:
[ghl@server bin 17:16:16]$ [ -w ~/.bashrc ] && {
> echo "alias sa='ssh root@servera'" >> ~/.bashrc
> echo "alias sb='ssh root@serverb'" >> ~/.bashrc
> echo "alias sc='ssh root@serverc'" >> ~/.bashrc
> }[ghl@server bin 17:18:50]$ tail -3 ~/.bashrc
alias sa='ssh root@servera'
alias sb='ssh root@serverb'
alias sc='ssh root@serverc'# 一行写法
# 大括号里的两端要有空格
[ghl@server bin 17:20:48]$ [ -r /etc/passwd ] && { echo ha;echo ha;echo ha; }
ha
ha
ha
6.3 字符串判断表达式
6.3.1 字符串测试操作符
字符串测试操作符的作用包括:比较两个字符串是否相同、测试字符串的长度是否为零、字符串是否为NULL等。
常用字符串测试操作符:
- -n “字符串”,若字符串的长度不为0,则为真,即判断表达式成立,n可以理解为no zero。
- -z “字符串”,若字符串的长度为0,则为真,即判断表达式成立,z可以理解为zero的缩写。
- “串1” = “串2”,若字符串1等于字符串2,则为真,即判断表达式成立,可使用"==“代替”="。
- “串1” != “串2”,若字符串1不等于字符串2,则为真,即判断表达式成立,但不能用"!==“代替”!="。
以下是针对字符串测试操作符的提示:
- 对于字符串的测试,一定要将字符串加双引号之后再进行比较,如
[ -n "$myvar" ]
,特别是使用[]
的场景。 - 比较符号(例如=和!=)的两端一定要有空格。
示例1:-z 选项,判断变量值是否为空,如果为空,则为真值,反之为假值。
[ghl@server bin 17:20:56]$ unset string
[ghl@server bin 17:24:49]$ [ -z "$string" ] && echo null string || echo $string
null string[ghl@server bin 17:25:33]$ string="hello world"
[ghl@server bin 17:25:49]$ [ -z "$string" ] && echo null string || echo $string
hello world
示例2:-n 选项,判断变量值是否为非空,如果为非空,则为真值,反之为假值。
[ghl@server bin 17:29:44]$ unset string
[ghl@server bin 17:30:17]$ [ -n "$string" ] && echo $string || echo null string
null string[ghl@server bin 17:30:37]$ string="hello world"
[ghl@server bin 17:30:45]$ [ -n "$string" ] && echo $string || echo null string
hello world
**示例3:**判断是否相对
[ghl@server bin 17:30:57]$ [ "$string"="hi" ] && echo hi
hi[ghl@server bin 17:32:30]$ [ "$string"!="hi" ] && echo no hi
no hi
其他
[ghl@server bin 15:17:45]$ vim ssh_ctl
#!/bin/bash
test "$USER" != "root" && echo Pls run as root. && exit
systemctl $1 sshd[ghl@server bin 15:17:38]$ bash ssh_ctl status
Pls run as root.# 切换到root用户
[root@server ~ 15:18:07]# cd /home/ghl/bin
[root@server bin 15:18:33]# bash ssh_ctl status
● sshd.service - OpenSSH server daemonLoaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)Active: active (running) since 四 2025-10-09 11:18:00 CST; 4h 0min ago
......
6.4 整数二元比较操作符
6.4.1 整数二元比较操作符介绍
整数二元比较操作符使用说明如下:
[]和test中比较符号 | (())和[[]]中比较符号 | 说明 |
---|---|---|
-eq | ==或= | 相等,全拼为 equal |
-ne | != | 不相等,全拼为 not equal |
-gt | > | 大于,全拼为 greater than |
-ge | >= | 大于等于,全拼为 greater equal |
-It | < | 小于,全拼为less than |
-le | <= | 小于等于,全拼为 less equal |
以下是针对上述符号的特别说明:
=
和!=
也可在[]中做比较使用,但在[]中使用包含>
和<
的符号时,需要用反斜线转义,有时不转义虽然语法不会报错,但是结果可能会不对。- 也可以在[[]]中使用包含
-gt
和-lt
的符号,但是不建议这样使用。 - 比较符号两端也要有空格。
示例1:
[ghl@server bin 17:32:40]$ [ 2 -lt 3 ] && echo true || echo false
true
[ghl@server bin 17:36:27]$ [ 2 -gt 3 ] && echo true || echo false
false
[ghl@server bin 17:36:35]$ [ 2 -le 3 ] && echo true || echo false
true
[ghl@server bin 17:36:46]$ [ 2 -ge 3 ] && echo true || echo false
false
示例2: 错误的示例
[ghl@server bin 17:38:04]$ [ 2 > 1 ] && echo true || echo false
true# 事实是2<3
[ghl@server bin 17:36:52]$ [ 2 > 3 ] && echo true || echo false
true# 转义>符号
[ghl@server bin 17:38:18]$ [ 2 \> 3 ] && echo true || echo false
false
[]、[[]]、(())用法的小结:
- 整数加双引号的比较是对的。
[[]]
中用类似-eq等的写法是对的,[[]]中用类似>、<的写法也可能不对,有可能会只比较第一位,逻辑结果不对。[]
中用类似>、<的写法在语法上虽然可能没错,但逻辑结果不对,可以使用=、!=正确比较。(())
中不能使用类似-eq等的写法,可以使用类似>、<的写法。
数值如果使用
!=
>
等使用(())
[ghl@server bin 15:22:13]$ vim ssh_ctl
[ghl@server bin 15:55:49]$ bash ssh_ctl status
Pls run as root.
[ghl@server bin 15:56:04]$ cat ssh_ctl
#!/bin/bash
(($UID!=0)) && echo Pls run as root. && exit
systemctl $1 sshd[root@server bin 15:18:46]# bash ssh_ctl status
● sshd.service - OpenSSH server daemonLoaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)Active: active (running) since 四 2025-10-09 11:18:00 CST; 4h 38min ago
......
6.5 逻辑操作符
6.5.1 逻辑操作符介绍
表达式中逻辑操作符使用说明如下:
[]和test中逻辑操作符 | [[]]和(())中逻辑操作符 | 说明 |
---|---|---|
-a | && | and,与,两端都为真,则结果为真 |
-o | II | or,或 ,两端有一个为真,则结果为真 |
! | ! | not,非,两端相反,则结果为真 |
对于上述操作符,有如下提示:
- 逻辑操作符前后的表达式是否成立,一般用真假来表示。
- "!"的中文意思是反,即与一个逻辑值相反的逻辑值。
- -a的中文意思是“与”(and或&&),前后两个逻辑值都为“真”,综合返回值才为“真”,反之为“假”。
- -o的中文意思是“或”(or或||),前后两个逻辑值只要有一个为“真”,返回值就为“真”。
表达式外:&&或||可用于连接两个含[]、test或[[]]的表达式:
-
使用&&表达式时:
-
当左边为真,右边表达式才会执行。
-
当左边为假,右边表达式不会执行。
-
-
使用||的表达式时:
-
当左边为真,右边表达式不会执行。
-
当左边为假,右边表达式才会执行。
-
# 判断表达式内部:逻辑与
[ghl@server bin 17:39:26]$ file=/etc/passwd
[ghl@server bin 17:40:54]$ [ -f $file -a -r $file ] && head -1 $file
root:x:0:0:root:/root:/bin/bash
[ghl@server bin 17:41:50]$ [[ -f $file && -r $file ]] && head -1 $file
root:x:0:0:root:/root:/bin/bash# 判断表达式内部:逻辑或
[ghl@server bin 17:42:09]$ num=10
[ghl@server bin 17:42:57]$ [ $num -lt 20 -o $num -gt 1 ] && echo $num is between 1 and 20
10 is between 1 and 20
[ghl@server bin 17:43:57]$ [[ $num -lt 20 || $num -gt 1 ]] && echo $num is between 1 and 20
10 is between 1 and 20# 判断表达式内部:逻辑非
[ghl@server bin 17:44:14]$ [ ! 6 -eq 5 ] && echo 6!=5
6!=5
[ghl@server bin 17:45:06]$ [[ ! 6 -eq 5 ]] && echo 6!=5
6!=5
6.6 判断表达式 test、[]、[[]]、(())的区别总结
判断表达式符号 | [] | test | [[]] | (()) |
---|---|---|---|---|
边界是否需要空格 | 需要 | 需要 | 需要 | 不需要 |
逻辑操作符 | !、-a、-o | !、 -a、 -o | !、&&、|| | !、&&、|| |
整数比较操作符 | -eq、-ne、-gt、-ge、-lt、-le | -eq、-ne、-gt、-ge、-lt、-le | -eq、-ne、-gt、-ge、-lt、-le | =、!=、>、>=、<、<= |
字符串比较操作符 | =、== 、!= | =、== 、!= | =、== 、!= | == 、!= |
是否支持通配符匹配 | 不支持 | 不支持 | 支持(=~) | 不支持 |