SHELL编程看这一篇就够了
文章目录
- 4.1.2 Shell 进程中的特殊状态变量
- 4.2 Shell 内置变量命令
- 4.2.1 `echo`
- 4.2.2 `eval`
- 4.2.3 `read`
- 4.2.4 `exec`
- 4.2.5 `shift`
- 4.3 Shell 变量子串知识
- 4.4 Shell 特殊扩展变量
- 5. 变量的数值计算实践
- 5.2 `(())` 双小括号数值运算
- 5.3 `let` 命令
- 5.4 `expr` 命令
- 5.5 `bc` 命令
- 5.6 `awk` 实现计算
- 5.7 `$[]` 运算
- 5.8 综合练习:计算 1+2+...+10
- 6. Shell 脚本的条件测试
1. 为什么要学习 Shell 编程?
1.1 什么是 Shell
Shell 是一个命令解释器,它的作用是解释执行用户输入的命令及程序。用户每输入一条命令, Shell 就解释执行一条。这种从键盘一输入命令,就可以立即得到回应的对话方式,称为交互的方式。
Shell 存在于操作系统的外层,负责与用户直接对话,把用户的输入解释给操作系统,并处理各种各样的操作系统的输出结果,然后输出到屏幕返回给用户。输入系统用户名和密码并登录到 Linux 后的所有操作都是由 Shell 解释与执行的。
Shell 的英文是壳的意思,命令解释器( Shell )就像壳一样包住了系统核心。
在常用的操作系统中: Linux 下默认的 Shell 是 Bourne Again shell(bash)。 Solaris 和 FreeBSD 下默认的是 Bourne shell(sh)。 AIX 下默认的是 Kom Shell (ksh)。 这里重点讲 Linux 系统环境下的 Bourne Again shell(bash)。
1.2 什么是 Shell 脚本
理解了 Shell 之后,再理解 Shell 脚本就简单了。当命令或程序语句不在命令行下执行,而是通过一个程序文件来执行时,该程序就被称为 Shell 脚本。
如果在 Shell 脚本里内置了很多条命令、语句及循环控制,然后将这些命令一次性执行完毕,这种通过文件执行脚本的方式称为非交互的方式。
Shell 脚本类似于 DOS 系统下的批处理程序(早期扩展名一般为*.bat)。用户可以在 Shell 脚本中敲入一系列的命令及命令语句组合。这些命令、变量和流程控制语句等有机地结合起来,就形成了一个功能强大的 Shell 脚本。
1.3 为什么要学习 Shell 编程
Shell 脚本语言很适合用于处理纯文本类型的数据,而 Linux 系统中几乎所有的配置文件、日志文件(如 NFS、Rsync、Httpd、Nginx、LVS、MySQL 等),以及绝大多数的启动文件都是纯文本类型的文件。因此,学好 Shell 脚本语言,就可以利用它在 Linux 系统中发挥巨大的作用。
Shell 脚本语言是实现 Linux/UNIX 系统管理及自动化运维所必备的重要工具,Linux/UNIX 系统的底层及基础应用软件的核心大都涉及 Shell 脚本的内容。每一个合格的 Linux 系统管理员或运维工程师,都需要能够熟练地编写 Shell 脚本语言,并能够阅读系统及各类软件附带的 Shell 脚本内容。只有这样才能提升运维人员的工作效率,适应日益复杂的工作环境,减少不必要的重复工作,从而为个人的职场发展奠定较好的基础。
例如:
- 检查多台服务器运行状态
- 自动化部署、升级各种服务器
1.4 学好 Shell 编程所需的基础知识
本节首先来探讨一下在学习 Shell 编程之前需要掌握的基础知识,需要说明的是,并不是必须具备这些基础知识才可以学习 Shell 编程,而是,如果具备了这些基础知识,那么就可以把 Shell 编程学得更好,领悟得更深。如果只是想简单地了解 Shell 脚本语言,那么就无须掌握太多的系统基础知识,只需要会一些简单的命令行操作即可。
学好 Shell 编程并通过 Shell 脚本轻松地实现自动化管理企业生产系统的必备基础如下:
- 能够熟练使用 vim 编辑器,熟悉 SSH 终端及
.vimrc等的配置。在 Linux 下开发 Shell 脚本最常使用的编辑器是 vim,因此如果能够熟练使用并配置好 vim 的各种高级功能设置,就可以让开发 Shell 脚本达到事半功倍的效果。 - 要有一定的 Linux 命令基础,掌握 Linux 常用命令,并能够熟练使用它们。
- 要熟练掌握 Linux 正则表达式及三剑客命令(grep、sed、awk)。
- 熟悉常见的 Linux 网络服务部署、优化、日志分析及排错。
1.5 脚本语言的种类
Shell 脚本语言是弱类型语言(无须定义变量的类型即可使用)。
在 Unix/Linux 中 主要有两大类 Shell : Bourne shell,包括 Bourne shell(sh)、Kom shell(ksh)、Bourne Again Shell (bash)三种类型。 C shell,包括 csh、tcsh 两种类型。
csh 由 Berkeley 大学开发,随 BSD UNIX 发布,它的流程控制语句很像 C 语言,支持很多 Bourne shell 所不支持的功能,例如:作业控制、别名、系统算术、命令历史命令行编辑等。
tcsh 是 csh 的增强版,加入了命令补全等功能,在 FreeBSD、MacOSX 等系统上替代了 csh。
2. 创建第一个 Shell 脚本
2.1 Shell 脚本
2.1.1 脚本开头
一个规范的 Shell 脚本在第一行会指出由哪个程序(解释器)来执行脚本中的内容,这一行内容在 Linux bash 的编程一般为:
#!/bin/bash
或
#!/bin/sh
其中,开头的#!字符又称为幻数,在执行 bash 脚本的时候,内核会根据#!字符后的解释器来确定该用哪个程序解释这个脚本中的内容。
注意,这一行必须位于每个脚本顶端的第一行,如果不是第一行则为脚本注释行。
示例:
[zlf@server ~ 16:20:01]$ vim script.sh
#!/bin/bash
echo "Hello World !"
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。
[zlf@server ~ 16:22:10]$ ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 11月 2 17:33 /bin/sh -> bash
2.2 Shell 脚本的执行
当 Shell 脚本运行时,它会先查找系统环境变量 ENV,该变量指定了环境文件,再加载了上述环境变量文件后, Shell 就开始执行 Shell 脚本中的内容。
Shell 脚本是从上至下、从左至右依次执行每一行的命令及语句的,即执行完了一个命令后再执行下一个,如果在 Shell 脚本中遇到子脚本(即脚本嵌套)时,就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句。通常情况下,在执行 Shell 脚本时,会向系统内核请求启动一个新的进程,以便在该进程中执行脚本的命令及子 Shell 脚本。
Shell 脚本的执行通常可以采用以下几种方式:
bash script-name或sh script-name:这是当脚本文件本身没有可执行权限(即文件权限属性 x 位为-号)时常使用的方法,或者脚本文件开头没有指定解释器时需要使用的方法。
[zlf@server ~ 16:24:20]$ bash script.sh
Hello World!
/path/script-name或./script-name:指在当前路径下执行脚本(脚本需要有执行权限),需要将脚本文件的权限先改为可执行,然后通过脚本绝对路径或相对路径就可以直接执行脚本了。
[zlf@server ~ 16:25:05]$ chmod +x script.sh
# 绝对路径
[zlf@server ~ 16:25:30]$ /home/zlf/script.sh
# 相对路径
[zlf@server ~ 16:25:45]$ ./script.sh
Hello World !
在企业生产环境中,不少运维人员在写完 Shell 脚本之后,由于忘记为该脚本设置执行权限,然后就直接应用了,结果导致脚本没有按照自己的意愿手动或定时执行,对于这一点,避免出现该问题的方法就是用第 1 种方法替代第 2 种。
source script-name或. script-name:这种方法通常是使用 source 或.点号读入或加载指定的 Shell 脚本文件,然后,依次执行指定的 Shell 脚本文件中所有语句。这些语句将在当前父 Shell 脚本进程中运行(其他几种模式都会启动新的进程执行子脚本)。因此,使用 source 可以将子脚本中的变量值、函数值等传递到当前父 Shell 脚本中使用。这是它和其他几种方法最大的区别,也是值得特别注意的地方。
[zlf@server ~ 16:26:10]$ source script.sh
Hello World !
# 等效于
[zlf@server ~ 16:26:25]$ . script.sh
Hello World !
bash < script-name或cat scripts-name|sh:同样适用于 bash,这种用法也很常见。
[zlf@server ~ 16:27:00]$ bash < script.sh
Hello World !
[zlf@server ~ 16:27:15]$ cat script.sh | bash
Hello World !
- 将脚本复制到环境变量 PATH 所包含的路径中。
[zlf@server ~ 16:28:00]$ echo $PATH
/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/zlf/.local/bin:/home/zlf/bin
[zlf@server ~ 16:28:20]$ cp script.sh ~/bin
# 脚本需要具备执行权限。
[zlf@server ~ 16:28:40]$ script.sh
2.3 Shell 脚本开发的基本规范及习惯
Shell 脚本的开发规范及习惯非常重要,虽然这些规范不是必须要遵守的,但有了好的规范和习惯,可以大大提升开发效率,并能在后期降低对脚本的维护成本。
当多人协作开发时,大家有一个互相遵守的规范就显得更重要了。即使只是一个人开发,最好也采取一套固定的规范,这样脚本将会更易读、更易于后期维护,最重要的是要让自己养成一个一出手就很专业和规范的习惯。
下面来看看有哪些规范: Shell 脚本的第一行是指定脚本解释。
#!/bin/bash
Shell 脚本的开头加版本、版权等信息。
#!/bin/bash
# Date:16:29 2022-3-30
# Author:Created by zlf
# Description:This script is used to...
# Version:2.0
可修改 ~/.vimrc 配置文件配置 vim 编辑文件时自动加上以上信息的功能。
在 Shell 脚本中尽量不用中文(不限于注释)。 尽量用英文注释,防止本机或切换系统环境后中文乱码的困扰。如果非要加中文,请根据自身的客户端对系统进行字符集调整,如:export LANG="zh_CN.UTF-8",并在脚本中,重新定义字符集设置,和系统保持一致。
Shell 脚本的命名应以.sh为扩展名。例如:script-name.sh。 Shell 脚本应存放在固定的路径下。
以下则是 Shell 脚本代码书写的良好习惯: 成对的符号应尽量一次性写出来,然后退格在符号里增加内容,以防止遗漏。 这些成对的符号包括: {} [] '' `` ""
中括号([]) 两端至少要有 1 个空格,因此,键入中括号时即可留出空格[],然后再退格键入中间的内容,并确保两端都至少有一个空格,即先键入一对中括号,然后退 1 格,输入两个空格,再退 1 格,双中括号([[]]) 的写法也是如此。
对于流程控制语句,应一次性将格式写完,再添加内容。 比如,一次性完成 if 语句的格式,应为:
if 条件内容;then
内容
fi
一次性完成 for 循环语句的格式,应为:
for
do
内容
done
提示:while 和 until,case 等语句也是一样。
通过缩进让代码更易读,比如:
if 条件内容;then
内容
fi
对于常规变量的字符串定义变量值应加引号,并且等号前后不能有空格,需要强引用的(指所见即所得的字符引用),则用单引号(''),如果是命令的引用,则用反引号(```)。
例如:
zlf_file="test.txt"
week_day="$(date +%A)"
welcome_string='******'
脚本中的单引号、双引号及反引号必须为英文状态下的符号,其实所有的 Linux 字符及符号都应该是英文状态下的符号,这点需要特别注意。
说明:好的习惯可以让我们避免很多不必要的麻烦,提升工作效率。
3. Shell 变量基础知识
3.1 什么是 Shell 变量
3.1.1 什么是变量
简单地说,变量名是用一个固定的字符串(字符、数字和下划线的组合,不能以数字开头)代替更多、更复杂的内容,该内容里可能还会包含变量、路径、字符串等其他的内容。
变量是暂时存储数据的地方及数据标记,所存储的数据存在于内存空间中,通过正确地调用内存空间中变量的名字就可以取出与变量对应的数据。使用变量的最大好处就是使程序开发更为方便,当然,在编程中使用变量也是必须的,否则就很难完成相关的程序开发工作。
下面是定义变量和打印变量的示例:
# 定义变量,名字为username,对应的内容zlf
[zlf@server ~ 16:30:10]$ username="zlf"
# 打印变量的值
[zlf@server ~ 16:30:25]$ echo $username
zlf
变量的赋值方式为:先写变量名称,紧接着是=字符,最后是值,中间无任何空格。 通过 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,历史命令个数。
- HISTTIMEFORMAT,history 历史命令显示格式。
- PS1,命令行提示符。
- LANG,Shell 环境语言。
- PATH,命令搜素路径。
- HOME,当前用户家目录。
- USER,当前用户。
- PWD,当前 Shell 路径。
- IFS,字符串分隔符。
在查看设置的变量时,有 3 个命令可以显示变量的值:
- set 命令,输出所有的变量,包括全局变量和局部变量。
- env 命令,只显示全局变量,包括 shell 的环境。
- declare 命令,输出所有的变量、函数、整数和已经导出的变量。
set -o命令显示 bash Shell 的所有参数配置信息。
3.2.1 设置环境变量
- 设置环境变量 如果想要设置环境变量,就要在给变量赋值之后或在设置变量时使用 export 命令,具体设置见下文的示例。其实,除了 export 命令,带
-x选项的 declare 内置命令也可以完成同样的功能(注意:此处不要在变量名前面加$)。
export 命令和 declare 命令的格式如下:
export 变量名=value
变量名=value ; export 变量名
declare -x 变量名=value
示例:
export NAME=zlf
declare -x NAME=zlf
NAME=zlf;export NAME
下面来看看让环境变量永久生效的常用设置文件。 用户的环境变量配置文件:~/.bash_profile 和 ~/.bashrc。推荐在(~/.bashrc)文件中设置。 全局环境变量的配置文件:/etc/profile、/etc/bashrc、/etc/profile.d/。推荐在 /etc/bashrc 文件中设置。若要在登录后初始化或显示加载内容,则把脚本文件放在/etc/profile.d/下即可(无须加执行权限)。
- 设置登录后提示信息 第一种是在
/etc/motd里增加提示的字符串,如下:
[zlf@server ~ 16:35:00]$ vim /etc/motd
welcome to zlf linux Shell training.
#登录后效果
Welcome to zlf linux Shell training.
Last login: Sat Dec 3 12:38:23 2022 from 10.1.8.1
第二种是在 /etc/profile.d/ 下面增加如下脚本。
[zlf@server ~ 16:36:10]$ sudo vim /etc/profile.d/zlf.sh
echo Welcome to zlf 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 生存期的脚本中使用。例如,本地变量 zlf 的定义为 bingbing, 这个值只在用户当前 Shell 生存期中有意义。如果在 Shell 中启动另一个进程或退出,那么变量 zlf 的值将会无效。
3.3.1 普通变量定义
为普通变量的定义赋值,一般有以下 3 种写法:
变量名=value,赋值时不加引号。变量名='value',赋值时加单引号。变量名="value",赋值时加双引号。
3.3.2 变量名及变量值要求
变量名:一般是由字母、 数字 、下划线组成的,只可以以字母或下划线开头,例如:zlf、 zlf_123、 zlf_training。
变量值:可以用单引号或双引号引起来,也可不加引号,但是这三者的含义是不同的。
3.3.3 普通变量的定义及输出示例
采用不同的方式对普通变量进行定义,并打印输出。
示例 1:
[zlf@server ~ 16:40:00]$ a=192.168.1.2
[zlf@server ~ 16:40:10]$ b='192.168.1.2'
[zlf@server ~ 16:40:20]$ c="192.168.1.2"
[zlf@server ~ 16:40:30]$ echo "a=$a"
a=192.168.1.2
[zlf@server ~ 16:40:40]$ echo "b=$b"
b=192.168.1.2
[zlf@server ~ 16:40:50]$ echo "c=${c}"
c=192.168.1.2
可见, 将连续的普通字符串的内容赋值给变量,不管用不用引号,或者不管用什么引号,它的内容是什么,打印变量时就会输出什么。
示例 2:
[zlf@server ~ 16:41:00]$ a=192.168.1.2-$a
[zlf@server ~ 16:41:10]$ echo "a=$a"
a=192.168.1.2-192.168.1.2
[zlf@server ~ 16:41:20]$ b='192.168.1.2-$a'
[zlf@server ~ 16:41:30]$ echo "b=$b"
b=192.168.1.2-$a
[zlf@server ~ 16:41:40]$ c="192.168.1.2-$a"
[zlf@server ~ 16:41:50]$ echo "c=${c}"
c=192.168.1.2-192.168.1.2-192.168.1.2
变量定义的基本技巧总结 a=192.168.1.2-$a, 定义 a 变量的方式是不加任何引号直接定义变量的内容, 当内容为简单连续的数字、字符串、路径名时,可以这样用,例如: a=1,b=zlf等。不加引号时,值里有变量的会被解析后再输出,上述变量定义中因为$a的值被解析为192.168.1.2,因此新的 a 值就是192.168.1.2-192.168.1.2。
b='192.168.1.2-$a',定义 b 变量的方式是通过单引号定义。 这种定义方式的特点是:输出变量内容时单引号里是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。 这种方式比较适合于定义显示纯字符串的情况,即不希望解析变量 、命令等的场景,因此, 对于这里的 b 的值,定义时看到的是什么就输出什么,即192.168.1.2-$a。
c="192.168.1.2-$a",定义 c 变量的方式是通过双引号定义变量。 这种定义方式的特点是:输出变量内容时引号里的变量及命令会经过解析后再输出内容,而不是把双引号中的变量名及命令 (命令需要反引起来)原样输出。 这种方式比较适合于字符串中附带有变量及命令且想将其解析后再输出的变量定义。
3.3.4 把命令的结果作为变量的内容赋值
常见方法有两种,以 ls 命令为例:
变量名=command``,把命令用反引号引起来,不推荐使用这种方法,因为容易和单引号混淆。变量名=$(command),把命令用$()括起来 ,推荐使用这种方法。
[zlf@server ~ 16:43:00]$ ls
script.sh
[zlf@server ~ 16:43:10]$ CMD1=`ls`
[zlf@server ~ 16:43:20]$ echo $CMD1
script.sh
[zlf@server ~ 16:43:30]$ CMD2=$(pwd)
[zlf@server ~ 16:43:40]$ echo $CMD2
/home/zlf
[zlf@server ~ 16:43:50]$ date +%A
Friday
[zlf@server ~ 16:44:00]$ echo Today is $(date +%A).
Today is Friday.
生产场景中把命令的结果作为变量的内容进行赋值的方法在脚本开发时很常见。
3.4 变量定义技巧总结
可以多学习和模仿操作系统自带的 /etc/init.d/functions 函数库脚本的定义思路,多学习 Linux 系统脚本中的定义,有经验的读者最终应形成一套适合自己的规范和习惯。
变量名及变量内容定义小结。 变量名只能包含字母、数字或下划线,只能以字母或下划线开头。 变量名的定义要有一定的规范,并且要见名知意。 示例:
# 每个单词的首字母大写的写法
LaomaAge=1
# 单词之间用_
zlf_age=1
# 驼峰语法:首个单词的首字母小写,其余单词首字母大写
zlfAge=1
zlfSex=man
# 单词全大写
ZLFAGE=1
一般的变量定义、赋值常用双引号;简单连续的字符串可以不加引号;希望原样输出时使用单引号。
希望变量的内容是命令的解析结果时,要用反引号```,或者用$()把命令括起来再赋值。
Shell 定义变量 a=1 里等号是赋值的意思;比较变量是否相等时也可以用=或==。 打印输出及使用变量 打印输出或使用变量时,变量名前要接 $ 符号;变量名后面紧接其他字符的时候,要用大括号将变量部分单独括起来;在 unset、export、(())等场景中使用但不打印变量时不加$,这个有些例外。 打印输出或使用变量时,一般加双引号或不加引号;如果是字符串变量,最好加双引号;希望原样输出时使用单引号。
4. Shell 变量进阶知识
4.1 Shell 中特殊变量
4.1.1 Shell 位置参数变量
在 Shell 中存在一些特殊且重要的变量,例如:$0、$1,我们称之为位置参数变量。要从命令行、函数或脚本执行等处传递参数时,就需要在 Shell 脚本中使用位置参数变量。
部分位置参数变量如下:
$0,获取当前执行的 Shell 脚本的文件名,如果执行脚本包含了路径,那么就包括脚本路径。$n,获取当前执行的 Shell 脚本的第 n 个参数值。如果 n 大于 9,则用大括号括起来,例如${10},接的参数以空格隔开。$#,获取当前执行的 Shell 脚本后面接的参数数量。$*,获取当前 Shell 脚本所有传参的参数,不加引号和$@相同;如果给$*加上双引号,例如:"$*",则表示将所有的参数视为单个字符串,相当于$1 $2 $3。$@,获取当前 Shell 脚本所有传参的参数,不加引号和$*相同;如果给$@加上双引号,例如:"$@",则表示将所有的参数视为不同的独立字符串,相当于"$1"、"$2"、"$3"…,这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在每个参数里的任何空白。
示例 1:showargs.sh 内容如下
#!/bin/bash
echo $0
echo $1
echo $2
echo $10
echo ${10}
echo $#
echo $*
echo $@
echo "$@"
[zlf@server ~ 16:48:00]# bash showargs.sh {a..z}
showargs.sh
a
b
a0
j
26
a b c d e f g h i j k l m n o p q r s t u v w x y z
a b c d e f g h i j k l m n o p q r s t u v w x y z
a b c d e f g h i j k l m n o p q r s t u v w x y z
示例 2:ssh_ctl.sh
#!/bin/bash
systemctl $1 sshd
执行效果如下:
[zlf@server ~ 16:50:00]# bash ssh_ctl.sh stop
[zlf@server ~ 16:50:30]# bash ssh_ctl.sh status
● sshd.service - OpenSSH server daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
Active: inactive (dead) since 五 2026-10-09 11:38:00 CST; 5s ago
Docs: man:sshd(8)
man:sshd_config(5)
Process: 947 ExecStart=/usr/sbin/sshd -D $OPTIONS (code=exited, status=0/SUCCESS)
Main PID: 947 (code=exited, status=0/SUCCESS)
10月 08 13:38:54 server.zlf.cloud systemd[1]: Starting Ope...
10月 08 13:38:54 server.zlf.cloud sshd[947]: Server listen...
10月 08 13:38:54 server.zlf.cloud sshd[947]: Server listen...
10月 09 09:50:54 server.zlf.cloud sshd[12993]: Accepted pa...
10月 09 11:38:00 server.zlf.cloud systemd[1]: Stopping Ope...
10月 09 11:38:00 server.zlf.cloud sshd[947]: Server listen...
10月 09 11:38:00 server.zlf.cloud systemd[1]: Stopped Open...
Hint: Some lines were ellipsized, use -l to show in full.
[zlf@server ~ 16:51:00]# bash ssh_ctl.sh restart
[zlf@server ~ 16:51:20]# bash ssh_ctl.sh status
● sshd.service - OpenSSH server daemon
Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
Active: active (running) since 五 2026-10-09 11:38:17 CST; 6s ago
Docs: man:sshd_config(5)
Main PID: 13368 (sshd)
CGroup: /system.slice/sshd.service
└─13368 /usr/sbin/sshd -D
10月 09 11:38:17 server.zlf.cloud systemd[1]: Starting Ope...
10月 09 11:38:17 server.zlf.cloud sshd[13368]: Server list...
10月 09 11:38:17 server.zlf.cloud sshd[13368]: Server list...
10月 09 11:38:17 server.zlf.cloud systemd[1]: Started Open...
Hint: Some lines were ellipsized, use -l to show in full.
4.1.2 Shell 进程中的特殊状态变量
$?
作用:获取上一个指令的执行状态返回值。 0 表示成功,非零表示失败,这是最常用的状态变量。
[zlf@server ~ 16:43:10]$ ls
hello script.sh showargs.sh
[zlf@server ~ 16:43:12]$ echo $?
0
[zlf@server ~ 16:43:14]$ ls /root
ls: 无法打开目录/root: 权限不够
[zlf@server ~ 16:43:16]$ echo $?
2
查看 ls 退出码含义:
[zlf@server ~ 16:43:18]$ man ls
......
Exit status:
0 if OK,
1 if minor problems (e.g., cannot access subdirectory),
2 if serious trouble (e.g., cannot access command-line argument).
$$
作用:获取当前 Shell 脚本或当前 Shell 进程的进程号(PID)。 这个变量不常用,了解即可。
[zlf@server ~ 16:43:20]$ echo $$
2447
[zlf@server ~ 16:43:22]$ kill -9 $$
Connection closed by foreign host.
Disconnected from remote host(10.1.8.88) at 16:43:24.
Type `help' to learn how to use Xshell prompt.
$!
作用:获取上一个后台进程的进程号(PID)。 这个变量不常用,了解即可。
[zlf@server ~ 16:43:26]$ md5sum /dev/zero &
[1] 2611
[zlf@server ~ 16:43:28]$ echo $!
2611
[zlf@server ~ 16:43:30]$ ps o pid,%cpu,%mem,command $!
PID %CPU %MEM COMMAND
2611 99.1 0.0 md5sum /dev/zero
[zlf@server ~ 16:43:32]$ kill $!
[1]+ Terminated md5sum /dev/zero
$_
作用:获取上一条命令的最后一个参数。 这个变量不常用,了解即可。
[zlf@server ~ 16:43:34]$ ls /etc/hosts /etc/fstab /etc/hostname
/etc/fstab /etc/hostname /etc/hosts
[zlf@server ~ 16:43:36]$ cat $_
zlf-server
[zlf@server ~ 16:43:38]$ cat /etc/hostname
zlf-server
4.2 Shell 内置变量命令
Bash Shell 包含一些内置命令。这些内置命令在目录列表里是看不见的,它们由 Shell 本身提供。常用的内置命令有:echo、eval、exec、read、shift 等。
下面简单介绍几个最常用的内置命令的格式和功能。
4.2.1 echo
echo 命令用于输出内容。
常用选项:
-n:不换行输出-e:解析转义字符
常用转义字符:
表格
| 转义字符 | 说明 |
|---|---|
\n | 换行 |
\t | 制表符(Tab) |
\b | 退格 |
\r | 回车到行首 |
[zlf@server ~ 16:43:40]$ echo -n "zlf ";echo zhangsan
zlf zhangsan
[zlf@server ~ 16:43:42]$ echo -e "zlf\nzhangsan"
zlf
zhangsan
[zlf@server ~ 16:43:44]$ echo -e "zlf\tzhangsan"
zlf zhangsan
[zlf@server ~ 16:43:46]$ echo -e "1\b23"
23
[zlf@server ~ 16:43:48]$ echo -e "123\b"
123
[zlf@server scripts 16:43:50]$ echo -ne "123\b";echo haha
12haha
[zlf@server ~ 16:43:52]$ echo -e "hello world\r123"
123lo world
4.2.2 eval
eval 会把参数组合成一条新命令,然后交给 Shell 执行。
示例 1:
[zlf@server ~ 16:43:54]$ vim noeval.sh
[zlf@server ~ 16:43:56]$ cat noeval.sh
echo \$$#
[zlf@server ~ 16:43:58]$ bash noeval.sh hello world
$2
[zlf@server ~ 16:44:00]$ vim eval.sh
[zlf@server ~ 16:44:02]$ cat eval.sh
eval "echo \$$#"
[zlf@server ~ 16:44:04]$ bash eval.sh hello world
world
示例 2:
[zlf@server ~ 16:44:06]$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-LmBsuYheypEC/agent.1589; export SSH_AUTH_SOCK;
SSH_AGENT_PID=1590; export SSH_AGENT_PID;
echo Agent pid 1590;
[zlf@server ~ 16:44:08]$ eval $(ssh-agent)
Agent pid 1598
等价写法:
[zlf@server ~ 16:44:10]$ ssh-agent > /tmp/env
[zlf@server ~ 16:44:12]$ source /tmp/env
[zlf@server ~ 16:44:14]$ rm -f /tmp/env
4.2.3 read
read 用于从标准输入读取内容,并保存到变量中。
[zlf@server ~ 16:44:16]$ cat read.sh
#!/bin/sh
read -p "输入你想要说的话:" str
echo "你想要说的话是:$str"
[zlf@server ~ 16:44:18]$ bash read.sh
输入你想要说的话:hello world
你想要说的话是:hello world
不显示输入内容:
[zlf@server ~ 16:44:20]$ read -s -p "请设置用户密码: " password
请设置用户密码:
[zlf@server ~ 16:44:22]$ echo $password
redhat
设置用户密码示例:
[root@server ~ 16:44:24]# cat set_password_zlf.sh
#!/bin/bash
read -sp '请输入用户新的密码: ' password
echo $password | passwd --stdin zlf
4.2.4 exec
exec 可以在不创建新子进程的情况下执行指定命令。 命令执行完成后,原来的 Shell 进程会终止。
[zlf@server ~ 16:44:26]$ sudo -i
[root@server ~ 16:44:28]# ps $$
PID TTY STAT TIME COMMAND
1741 pts/0 S 0:00 -bash
[root@server ~ 16:44:30]# exec sleep 10
[zlf@server ~ 16:44:40]$
另一个窗口查看进程:
[zlf@server ~ 16:44:42]$ ps 1741
PID TTY STAT TIME COMMAND
1741 pts/0 S+ 0:00 sleep 10
读取文件内容示例:
[zlf@server ~ 16:44:44]$ cat exec.sh
#!/bin/bash
seq 5 > /tmp/seq.log
exec < /tmp/seq.log
while read line
do
echo $line
done
[zlf@server ~ 16:44:46]$ bash exec.sh
1
2
3
4
5
4.2.5 shift
shift 会让位置参数左移:$2 变成 $1,$3 变成 $2,依次类推。 每执行一次 shift,参数总数 $# 减 1。
示例 1:
[zlf@server ~ 16:44:48]$ cat shift.sh
#!/bin/sh
echo $1
shift
echo $1
[zlf@server ~ 16:44:50]$ bash shift.sh hello world
hello
world
示例 2:处理带选项的参数:
[zlf@server ~ 16:44:52]$ cat shift.sh
#!/bin/sh
if [ "$1" = "-c" ];then
shift
fi
command="$1"
echo $command
[zlf@server ~ 16:44:54]$ bash shift.sh -c date
date
4.3 Shell 变量子串知识
4.3.1 变量子串介绍
Shell 变量子串可以用来:
- 取变量内容
- 计算长度
- 截取子串
- 删除匹配内容
- 替换匹配内容
常见写法:
表格
| 表达式 | 说明 |
|---|---|
${parameter} | 返回变量内容 |
${#parameter} | 返回变量长度 |
${parameter:offset} | 从 offset 位置开始取到结尾 |
${parameter:offset:length} | 从 offset 位置开始取 length 个字符 |
${parameter#word} | 开头删除最短匹配 |
${parameter##word} | 开头删除最长匹配 |
${parameter%word} | 结尾删除最短匹配 |
${parameter%%word} | 结尾删除最长匹配 |
${parameter/pattern/string} | 替换第一个匹配 |
${parameter//pattern/string} | 替换所有匹配 |
4.3.2 变量子串实践
[zlf@server ~ 16:44:56]$ str="abc123abc123"
[zlf@server ~ 16:44:58]$ echo ${str}
abc123abc123
[zlf@server ~ 16:45:00]$ echo ${#str}
12
[zlf@server ~ 16:45:02]$ echo ${str} | wc -L
12
[zlf@server ~ 16:45:04]$ expr length "${str}"
12
[zlf@server ~ 16:45:06]$ echo "$str" | awk '{ print length($0)}'
12
[zlf@server ~ 16:45:08]$ echo ${str:3}
123abc123
[zlf@server ~ 16:45:10]$ echo ${str:3:4}
123a
[zlf@server ~ 16:45:12]$ echo ${str#b*a}
abc123abc123
[zlf@server ~ 16:45:14]$ echo ${str#a*c}
123abc123
[zlf@server ~ 16:45:16]$ echo ${str##a*c}
123
[zlf@server ~ 16:45:18]$ echo ${str%a*c}
abc123abc123
[zlf@server ~ 16:45:20]$ echo ${str%c*3}
abc123ab
[zlf@server ~ 16:45:22]$ echo ${str%%c*3}
ab
[zlf@server ~ 16:45:24]$ echo ${str/abc/def}
def123abc123
[zlf@server ~ 16:45:26]$ echo ${str//abc/def}
def123def123
4.3.3 生产场景应用案例
示例 1:替换文件名中的字符串
[zlf@server ~ 16:45:28]$ touch stu-202212-snap.jpg
[zlf@server ~ 16:45:30]$ ls stu-*
stu-202212-snap.jpg
[zlf@server ~ 16:45:32]$ file="stu-202212-snap.jpg"
[zlf@server ~ 16:45:34]$ mv $file ${file/2022/2021}
[zlf@server ~ 16:45:36]$ ls stu-*
stu-202112-snap.jpg
示例 2:删除文件名中的字符串
[zlf@server ~ 16:45:38]$ touch stu-202212-snap.jpg
[zlf@server ~ 16:45:40]$ ls stu-*
stu-202212-snap.jpg
[zlf@server ~ 16:45:42]$ file="stu-202212-snap.jpg"
[zlf@server ~ 16:45:44]$ mv $file ${file/-snap/}
[zlf@server ~ 16:45:46]$ ls stu-*
stu-202212.jpg
4.4 Shell 特殊扩展变量
4.4.1 特殊扩展变量介绍
表格
| 表达式 | 说明 |
|---|---|
${parameter:-word} | 变量为空或未定义时返回 word |
${parameter:=word} | 变量为空或未定义时设为 word 并返回 |
${parameter:?word} | 变量为空或未定义时输出错误信息 |
${parameter:+word} | 变量有值时返回 word,否则返回空 |
4.4.2 特殊扩展变量实践
[zlf@server ~ 16:45:48]$ unset test
[zlf@server ~ 16:45:50]$ echo ${test:-UNSET}
UNSET
[zlf@server ~ 16:45:52]$ echo $test
[zlf@server ~ 16:45:54]$ test=hello
[zlf@server ~ 16:45:56]$ echo ${test:-UNSET}
hello
[zlf@server ~ 16:45:58]$ unset SHELL
[zlf@server ~ 16:46:00]$ echo ${SHELL:=/bin/bash}
/bin/bash
[zlf@server ~ 16:46:02]$ echo $SHELL
/bin/bash
[zlf@server ~ 16:46:04]$ echo ${SHELL:=/bin/sh}
/bin/bash
[zlf@server ~ 16:46:06]$ echo $SHELL
/bin/bash
[zlf@server ~ 16:46:08]$ unset test
[zlf@server ~ 16:46:10]$ echo ${test:?UNSET}
-bash: test: UNSET
[zlf@server ~ 16:46:12]$ test=hello
[zlf@server ~ 16:46:14]$ echo ${test:?UNSET}
hello
[zlf@server ~ 16:46:16]$ unset test
[zlf@server ~ 16:46:18]$ echo ${test:+UNSET}
[zlf@server ~ 16:46:20]$ test=hello
[zlf@server ~ 16:46:22]$ echo ${test:+UNSET}
UNSET
4.4.3 生产场景应用案例
示例 1:Apache 启动脚本中的默认值
HTTPD_LANG=${HTTPD_LANG:-"C"}
httpd=${HTTPD:-/usr/sbin/httpd}
pidfile=${PIDFILE:-/var/run/httpd.pid}
lockfile=${LOCKFILE:-/var/lock/subsys/httpd}
示例 2:防止路径变量未定义导致误删
#!/bin/bash
find ${path:-/tmp} -name "*.tar.gz" -type f -mtime +7 | xargs rm -f
5. 变量的数值计算实践
5.1 算术运算符
Shell 支持常见算术运算:
表格
| 运算符 | 说明 | ||
|---|---|---|---|
+ - | 加法、减法 | ||
* / % | 乘法、除法、取余 | ||
** | 幂运算 | ||
++ -- | 自增、自减 | ||
&& ` | !` | 逻辑与、或、非 | |
< <= > >= == != | 比较 | ||
<< >> | 左移、右移 | ||
& ` | ^` `~` | 按位与、或、异或、取反 | |
= += -= *= /= %= | 赋值运算 |
常用运算命令:
表格
| 命令 | 说明 |
|---|---|
(()) | 整数运算,效率高 |
let | 整数运算 |
expr | 整数运算,也可处理字符串 |
bc | 支持小数运算 |
$[] | 整数运算 |
awk | 支持整数和小数 |
5.2 (()) 双小括号数值运算
5.2.1 基础语法
((i=i+1))
i=$((i+1))
((8>7 && 5==5))
echo $((2+1))
5.2.2 (()) 实践
[zlf@server ~ 16:46:24]$ echo $((1+1))
2
[zlf@server ~ 16:46:26]$ echo $((6*3))
18
[zlf@server ~ 16:46:28]$ ((i=5))
[zlf@server ~ 16:46:30]$ ((i=i*2))
[zlf@server ~ 16:46:32]$ echo $i
10
[zlf@server ~ 16:46:34]$ ((a=1+2**3-4%3))
[zlf@server ~ 16:46:36]$ echo $a
8
[zlf@server ~ 16:46:38]$ b=$((a=1+2**3-4%3))
[zlf@server ~ 16:46:40]$ echo $b
8
[zlf@server ~ 16:46:42]$ echo $((a=1+2**3-4%3))
8
[zlf@server ~ 16:46:44]$ a=$((100*(100+1)/2))
[zlf@server ~ 16:46:46]$ echo $a
5050
[zlf@server ~ 16:46:48]$ echo $((100*(100+1)/2))
5050
[zlf@server ~ 16:46:50]$ a=8;echo $((a+=1))
9
[zlf@server ~ 16:46:52]$ echo $((a**2))
81
[zlf@server ~ 16:46:54]$ ((3<8))
[zlf@server ~ 16:46:56]$ echo $?
0
[zlf@server ~ 16:46:58]$ echo $((3<8))
1
[zlf@server ~ 16:47:00]$ ((3>8))
[zlf@server ~ 16:47:02]$ echo $?
1
[zlf@server ~ 16:47:04]$ echo $((3>8))
0
[zlf@server ~ 16:47:06]$ echo $((3==3))
1
[zlf@server ~ 16:47:08]$ if ((8>7 && 5==5));then echo yes;fi
yes
[zlf@server ~ 16:47:10]$ a=10
[zlf@server ~ 16:47:12]$ echo $((a++))
10
[zlf@server ~ 16:47:14]$ echo $a
11
[zlf@server ~ 16:47:16]$ echo $((--a))
10
[zlf@server ~ 16:47:18]$ echo $a
10
[zlf@server ~ 16:47:20]$ num=99
[zlf@server ~ 16:47:22]$ echo $((num+1))
100
[zlf@server ~ 16:47:24]$ num=$((num+1))
[zlf@server ~ 16:47:26]$ echo $num
100
5.3 let 命令
let 和 (()) 类似,用于整数运算。
[zlf@server ~ 16:47:28]$ i=2
[zlf@server ~ 16:47:30]$ i=i+8
[zlf@server ~ 16:47:32]$ echo $i
i+8
[zlf@server ~ 16:47:34]$ i=2
[zlf@server ~ 16:47:36]$ let i=i+8
[zlf@server ~ 16:47:38]$ echo $i
10
5.4 expr 命令
5.4.1 基本用法
[zlf@server ~ 16:47:40]$ expr 2+2
2+2
[zlf@server ~ 16:47:42]$ expr 2 + 2
4
[zlf@server ~ 16:47:44]$ expr 2 * 2
expr: syntax error
[zlf@server ~ 16:47:46]$ expr 2 \* 2
4
5.4.2 企业级实战案例
判断是否为整数
[zlf@server ~ 16:47:48]$ i=5
[zlf@server ~ 16:47:50]$ expr $i + 5
10
[zlf@server ~ 16:47:52]$ echo $?
0
[zlf@server ~ 16:47:54]$ i=a
[zlf@server ~ 16:47:56]$ expr $i + 5
expr: non-integer argument
[zlf@server ~ 16:47:58]$ echo $?
2
判断文件扩展名
[zlf@server ~ 16:48:00]$ file=stu-202212.jpg
[zlf@server ~ 16:48:02]$ expr "$file" : ".*\.jpg$"
14
[zlf@server ~ 16:48:04]$ echo $?
0
[zlf@server ~ 16:48:06]$ expr "$file" : ".*\.pub$"
0
[zlf@server ~ 16:48:08]$ echo $?
1
计算字符串长度
[zlf@server ~ 16:48:10]$ str="abc123abc123"
[zlf@server ~ 16:48:12]$ expr length "$str"
12
打印长度不大于 6 的单词
[zlf@server ~ 16:48:14]$ cat str.sh
#!/bin/bash
strings="This is Linux World Welcome to our classroom"
for word in $strings
do
if [ $(expr length "$word") -le 6 ];then
echo $word
fi
done
[zlf@server ~ 16:48:16]$ bash str.sh
This
is
Linux
World
to
our
5.5 bc 命令
bc 是 Linux 下的计算器,支持小数运算。
[zlf@server ~ 16:48:18]$ 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+3*4-6/3^3%4
13
scale=4
1/3
.3333
quit
[zlf@server ~ 16:48:20]$ echo '1+3*4-6/3^3%4' | bc
13
[zlf@server ~ 16:48:22]$ echo 'scale=4;1/3' | bc
.3333
5.6 awk 实现计算
[zlf@server ~ 16:48:24]$ echo 7.7 3.8 | awk '{ print $1-$2 }'
3.9
[zlf@server ~ 16:48:26]$ echo 358 113 | awk '{ print ($1-3)/$2 }'
3.14159
5.7 $[] 运算
[zlf@server ~ 16:48:28]$ echo $[1+1]
2
[zlf@server ~ 16:48:30]$ echo $[4-2]
2
[zlf@server ~ 16:48:32]$ echo $[2*2]
4
[zlf@server ~ 16:48:34]$ echo $[4/2]
2
[zlf@server ~ 16:48:36]$ echo $[5/2]
2
[zlf@server ~ 16:48:38]$ echo $[5%2]
1
[zlf@server ~ 16:48:40]$ count=3;echo $[(count+1)*3]
12
[zlf@server ~ 16:48:42]$ count=3;echo $[ ++count + 3 ]
7
[zlf@server ~ 16:48:44]$ count=3;echo $[ count++ + 3 ]
6
[zlf@server ~ 16:48:46]$ count=3;echo $[ --count + 3 ]
5
[zlf@server ~ 16:48:48]$ count=3;echo $[ count-- + 3 ]
6
5.8 综合练习:计算 1+2+…+10
[zlf@server ~ 16:48:50]$ echo $(seq -s + 10) | bc
1+2+3+4+5+6+7+8+9+10
55
[zlf@server ~ 16:48:52]$ echo $(( $(echo {1..10} | tr ' ' '+' ) ))
55
[zlf@server ~ 16:48:54]$ echo $[ $(echo {1..10} | tr ' ' '+' ) ]
55
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 <判断表达式>
[zlf@server ~ 17:24:10]$ help test
test: test [表达式]
Evaluate conditional expression.
Exits with a status of 0 (true) or 1 (false) depending on
the evaluation of EXPR. Expressions may be unary or binary. Unary
expressions are often used to examine the status of a file. There
are string operators and numeric comparison operators as well.
The behavior of test depends on the number of arguments. Read the
bash 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 to
modification 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 STRING
STRING True if string is not empty.
STRING1 = STRING2
True if the strings are equal.
STRING1 != STRING2
True if the strings are not equal.
STRING1 < STRING2
True if STRING1 sorts before STRING2 lexicographically.
STRING1 > STRING2
True 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-equal
than ARG2.
Exit Status:
Returns success if EXPR evaluates to true; fails if EXPR evaluates to
false or an invalid argument is given.
**示例1:**判断文件是否存在
[root@server ~ 15:08:55]# test -f file && echo true ||echo false
该语句表示如果file文件存在,则输出true,否则输出false。这里的&&是逻辑与,||是逻辑或。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&后面的命令,而||后面的命令是test命令执行失败之后(为假)所执行的命令。
test命令判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个&&或||)来测试,示例如下。
[root@server ~ 15:09:34]# test -f file && echo true
[root@server ~ 15:09:41]# test -f file || echo false
false
另外,逻辑操作符&&和||的两端既可以有空格,也可以无空格。带空格看起来会更美观一些,建议使用带空格的格式。
示例2:-z 选项判断变量值是否为空,如果为空,则为真值,反之为假值。
[root@server ~ 15:09:59]# unset string
[root@server ~ 15:10:21]# test -z "$string" && echo null string || echo $string
null string
[root@server ~ 15:11:09]# string="hello world"
[root@server ~ 15:11:15]# test -z "$string" && echo null string || echo $string
hello world
结论:test命令测试的功能很强大,但是和[]、[[]]的功能有所重合,因此,在实际工作中选择一种适合自己的语法就好了。对于其他的语法,能读懂别人写的脚本就可以了。
6.1.3 [](中括号)条件测试语法及示例
[]条件测试的语法格式为:[ < 判断表达式> ]
注意:中括号内部的两端要有空格,[]和test等价,即test的所有判断选项都可以直接在[]里使用。
**示例1:**判断文件是否存在
[root@server ~ 15:11:47]# [ -f file ] && echo true || echo false
false
该语句表示如果file文件存在,则输出true,否则输出false。这里的&&是逻辑与,||是逻辑或。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&后面的命令,而||后面的命令是test命令执行失败之后(为假)所执行的命令。
[]条件判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个&&或||)来测试,示例如下。
[root@server ~ 15:13:05]# [ -f file ] && echo true
[root@server ~ 15:13:18]# [ -f file ] || echo false
false
逻辑操作符
&&和||的两端既可以有空格,也可以无空格。带空格看起来会更美观一些,建议使用带空格的格式。
6.1.4 [[]](双中括号)条件测试语法及示例
[[]]条件测试的语法格式为: [[ < 判断表达式> ]]。
注意:双中括号里的两端也要有空格。
**示例1:**判断文件是否存在
[root@server ~ 15:13:32]# [[ -f file ]] && echo true || echo false
false
该语句表示如果file文件存在,则输出true,否则输出false。这里的&&是逻辑与,||是逻辑或。test的-f参数用于测试文件是否为普通文件,test命令若执行成功(为真),则执行&&后面的命令,而||后面的命令是test命令执行失败之后(为假)所执行的命令。
[]条件判断表达式的逻辑也可以用上述表达形式的一半逻辑(即仅有一个&&或||)来测试,示例如下。
[root@server ~ 15:14:13]# [[ -f file ]] && echo ture
[root@server ~ 15:14:43]# [[ -f file ]] && echo falsee
逻辑操作符
&&和||的两端既可以有空格,也可以无空格。带空格看起来会更美观一些,建议使用带空格的格式。
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 文件判断表达式举例
[root@server ~ 15:17:44]# data_path=/tmp/zlf
[root@server ~ 15:18:13]# mkdir ${data_path}
[root@server ~ 15:18:57]# [ -d ${data_path} ] && echo ${data_path} is exist
/tmp/zlf is exist
[root@server ~ 15:19:29]# rmdir ${data_path}
[root@server ~ 15:20:22]# [ -d ${data_path} ] || echo ${data_path} is not exist
/tmp/zlf is not exist
[zlf@server ~ 15:21:58]$ [ -r /etc/shadow ] && cat /etc/shadow || echo hello
hello
[zlf@server ~ 15:22:35]$ [ -w ~/.bashrc ] && echo "alias pa='ps axu'" >>~/.bashrc
[zlf@server ~ 15:23:35]$ tail -1 ~/.bashrc
alias pa='ps axu'
6.2.3 特殊条件判断表达式案例
以下写法适用于所有的条件判断表达式,是工作中比较常用的替代if语句的方法。判断条件判断表达式的条件成立或不成立后,还需要继续执行多条命令语句的语法。
例如,当条件1成立时,同时执行命令1、命令2、命令3。
# 格式1
[ 条件1 ] && {
命令1
命令2
命令3
}
# 格式2
[[ 条件1 ]] && {
命令1
命令2
命令3
}
# 格式3
test 条件1 && {
命令1
命令2
命令3
}
示例:
[zlf@server ~ 15:23:47]$ [ -w ~/.bashrc ] && {
echo "alias sa='ssh root@servera'" >> ~/.bashrc
echo "alias sb='ssh root@serverb'" >> ~/.bashrc
echo "alias sc='ssh root@serverc'" >> ~/.bashrc
}
[zlf@server ~ 15:36:26]$ tail -3 ~/.bashrc
alias sa='ssh root@servera'
alias sb='ssh root@serverb'
alias sc='ssh root@serverc'
# 一行写法
[zlf@server ~ 15:36:38]$ [ -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 选项,判断变量值是否为空,如果为空,则为真值,反之为假值。
[zlf@server ~ 15:37:11]$ unset string
[zlf@server ~ 15:38:02]$ [ -z "$string" ] && echo null string || echo $string
null string
[zlf@server ~ 15:38:35]$ string="hello world"
[zlf@server ~ 15:40:59]$ [ -z "$string" ] && echo null string || echo $string
hello world
示例2:-n 选项,判断变量值是否为非空,如果为非空,则为真值,反之为假值。
[zlf@server ~ 15:41:08]$ unset string
[zlf@server ~ 15:41:42]$ [ -n "$string" ] && echo $string || echo null string
null string
[zlf@server ~ 15:42:11]$ string="hello world"
[zlf@server ~ 15:42:58]$ [ -n "$string" ] && echo $string || echo null string
hello world
**示例3:**判断是否相对
[zlf@server ~ 15:43:03]$ [ "$string" = "hi" ]&& echo hi
[zlf@server ~ 15:44:30]$ [ "$string" = "ha" ]&& echo hi
[zlf@server ~ 15:44:48]$ [ "$string" != "hi" ]&& echo hi
hi
[zlf@server ~ 15:44:57]$ [ "$string" != "ha" ]&& echo ha
ha
6.3.2 字符串测试生产案例
示例: /etc/init.d/network 部分内容
[zlf@server ~ 15:45:01]$ sed -n '30,31p' /etc/init.d/network
# Check that networking is up.
[ "${NETWORKING}" = "no" ] && exit 6
6.3.3 [[]] 中=~操作符
语法:[[ "$str" =~ pattern ]]
**作用:**使用 =~ 操作符时,其右边的字符串被认为是一个扩展正则表达式。扩展之后跟左边字符串进行比较,看左边字符串是否包含右边模式,也就是判断右边的模式是否为左边字符串的子字符串,而不是判断右边的模式是否完全等于左边字符串。
=~ 操作符右边的字符串不能使用引号括起来,无论是双引号、还是单引号,否则表示匹配这个字符串自身的内容,不再解析成正则表达式。
示例:
# 判断变量值是否包涵数字
[zlf@server ~ 15:46:59]$ str=123abc
[zlf@server ~ 15:48:23]$ [[ "$str" =~ [0-9]+ ]] && echo str is a string with num || echo str is a string without any num
str is a string with num
[zlf@server ~ 15:46:39]$ str=abcdef
[zlf@server ~ 15:46:57]$ [[ "$str" =~ [0~9]+ ]] && echo str is a string with num || echo str is a string without any num
str is a string without any num
# 判断变量值是否只包涵数字
[zlf@server ~ 15:48:26]$ str=123;[[ "$str" =~ ^[0-9]+$ ]] && echo str is a num || echo str is not a num
str is a num
# 加引号对比
[zlf@server ~ 15:50:31]$ str=123;[[ "$str" =~ [0-9]+ ]] && echo true || echo false
true
[zlf@server ~ 15:51:09]$ str=123;[[ "$str" =~ "[0-9]+" ]] && echo true || echo false
false
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:
[zlf@server ~ 17:04:45]$ [ 2 -lt 3 ] && echo true || echo false
true
[zlf@server ~ 17:05:07]$ [ 2 -gt 3 ] && echo true || echo false
false
[zlf@server ~ 17:05:14]$ [ 2 -le 3 ] && echo true || echo false
true
[zlf@server ~ 17:05:21]$ [ 2 -ge 3 ] && echo true || echo false
false
[zlf@server ~ 17:05:27]$ [ 3 -ge 3 ] && echo true || echo false
true
[zlf@server ~ 17:05:38]$
示例2: 错误的示例
[zlf@server ~ 17:05:38]$ echo [ 2 > 1 ] && echo true
true
# 事实是2<3
[zlf@server ~ 17:07:44]$ echo [ 2 > 3 ] && echo true
true
# 转义>符号
[zlf@server ~ 17:09:30]$ [ 2 \> 3 ] && echo true || echo false
false
[]、[[]]、(())用法的小结:
- 整数加双引号的比较是对的。
[[]]中用类似-eq等的写法是对的,[[]]中用类似>、<的写法也可能不对,有可能会只比较第一位,逻辑结果不对。[]中用类似>、<的写法在语法上虽然可能没错,但逻辑结果不对,可以使用=、!=正确比较。(())中不能使用类似-eq等的写法,可以使用类似>、<的写法。
6.4.2 整数变量测试实践示例
示例1:
[zlf@server ~ 17:09:54]$ a=98;b=99
[zlf@server ~ 17:11:24]$ [ $a -eq $b ] && echo true || echo false
false
[zlf@server ~ 17:11:56]$ [ $a -ne $b ] && echo true || echo false
true
[zlf@server ~ 17:12:36]$ [ $a -gt $b ] && echo true || echo false
false
[zlf@server ~ 17:12:45]$ [ $a -lt $b ] && echo true || echo false
true
示例2:
[zlf@server ~ 17:09:54]$ a=98;b=99
[zlf@server ~ 17:12:55]$ [[ $a = $b ]] && echo ture || echo false
false
[zlf@server ~ 17:13:59]$ [[ $a != $b ]] && echo ture || echo false
ture
[zlf@server ~ 17:14:12]$ (( $a >= $b )) && echo ture || echo false
false
[zlf@server ~ 17:14:26]$ (( $a <= $b )) && echo ture || echo false
ture
6.5 逻辑操作符
6.5.1 逻辑操作符介绍
表达式中逻辑操作符使用说明如下:
| []和test中逻辑操作符 | [[]]和(())中逻辑操作符 | 说明 |
|---|---|---|
| -a | && | and,与,两端都为真,则结果为真 |
| -o | II | or,或 ,两端有一个为真,则结果为真 |
| ! | ! | not,非,两端相反,则结果为真 |
对于上述操作符,有如下提示:
- 逻辑操作符前后的表达式是否成立,一般用真假来表示。
- "!"的中文意思是反,即与一个逻辑值相反的逻辑值。
- -a的中文意思是“与”(and或&&),前后两个逻辑值都为“真”,综合返回值才为“真”,反之为“假”。
- -o的中文意思是“或”(or或||),前后两个逻辑值只要有一个为“真”,返回值就为“真”。
表达式外:&&或||可用于连接两个含[]、test或[[]]的表达式:
-
使用&&表达式时:
-
当左边为真,右边表达式才会执行。
-
当左边为假,右边表达式不会执行。
-
-
使用||的表达式时:
-
当左边为真,右边表达式不会执行。
-
当左边为假,右边表达式才会执行。
-
6.5.2 逻辑操作符实践示例
# 判断表达式内部:逻辑与
[zlf@server ~ 17:14:32]$ file=/etc/passwd
[zlf@server ~ 17:15:14]$ [ -f $file -a -r $file ] && head -1 $file
root:x:0:0:root:/root:/bin/bash
[zlf@server ~ 17:18:01]$ [[ -f $file && -r $file ]] && head -1 $file
root:x:0:0:root:/root:/bin/bash
# 判断表达式内部:逻辑或
[zlf@server ~ 17:18:14]$ num=10
[zlf@server ~ 17:18:28]$ [ $num -lt 20 -o $num -gt 1 ] && echo $num is between 1 and 20
10 is between 1 and 20
[zlf@server ~ 17:19:17]$ [[ $num -lt 20 || $num -gt 1 ]] && echo $num is between 1 and 20
10 is between 1 and 20
# 判断表达式内部:逻辑非
[zlf@server ~ 17:20:34]$ [ ! 6 -eq 5 ] && echo 6!=5
6!=5
[zlf@server ~ 17:21:06]$ [[ ! 6 -eq 5 ]] && echo 6!=5
6!=5
6.5.3 逻辑操作符企业案例
[laoma@shell ~]$ data_path=/tmp/laoma
[laoma@shell ~]$ mkdir ${data_path}
[laoma@shell ~]$ [ -d ${data_path} ] && echo ${data_path} is exist
/tmp/laoma is exist
[zlf@server ~ 17:22:39]$ rmdir ${data_path}
[zlf@server ~ 17:23:10]$ [ -d ${data_path} ] || echo ${data_path} is not exist
/tmp/zlf is not exist
[zlf@server ~ 17:23:19]$ [ -d ${data_path} ] ||mkdir ${data_path}
[zlf@server ~ 17:23:52]$ ls -d ${data_path}
/tmp/zlf
6.6 判断表达式对比
| 判断表达式符号 | [] | test | [[]] | (()) |
|---|---|---|---|---|
| 边界是否需要空格 | 需要 | 需要 | 需要 | 不需要 |
| 逻辑操作符 | !、-a、-o | !、 -a、 -o | !、&&、|| | !、&&、|| |
| 整数比较操作符 | -eq、-ne、-gt、-ge、-lt、-le | -eq、-ne、-gt、-ge、-lt、-le | -eq、-ne、-gt、-ge、-lt、-le | ==、!=、>、>=、<、<= |
| 字符串比较操作符 | =、== 、!= | =、== 、!= | =、== 、!= | = 、!= |
| 是否支持通配符匹配 | 不支持 | 不支持 | 支持(=~) | 不支持 |
6.7 设定密码操作实例
[zlf@server ~ 16:45:25]$ vim set_password.sh
#!/bin/bash
echo "请提供一个包含数字字母特殊字符的密码"
read -p "请输入符合要求的字符串:" string
echo $string |grep -q '[a-Z]'|| {
echo 密码需包含字母数字特殊字符
exit
}
echo $string |grep -q '[0-9]'||{
echo 密码需包含字母数字特殊字符
exit
}
[ -n "${string//[0-9a-Z]/}" ] ||{
echo 密码需包含字母数字特殊字符
exit
}
[ "${#string}" -ge 12 ] ||{
echo 密码长度需要大于等于12
exit
}
echo $string | passwd --stdin zlf
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)