shell脚本

文章目录

shell脚本

脚本开头

一个规范的 Shell 脚本在第一行会指出由哪个程序(解释器)来执行脚本中的内容,这一行内容在Linux bash 的编程一般为:

bash 复制代码
#!/bin/bash

bash 复制代码
#!/bin/sh

其中,开头的"#!"字符又称为幻数(其实叫什么都无所谓,知道它的作用就好),在执行bash脚本的时候,内核会根据"#!"字符后的解释器来确定该用哪个程序解释这个脚本中的内容。

注意,这一行必须位于每个脚本顶端的第一行,如果不是第一行则为脚本注释行,

例如下面的例子:

bash 复制代码
[yang@server ~ 14:44:15]$ vim script.sh
#!/bin/bash
echo "Hello World !"

CentOS 和 RHEL下默认的 Shell 均为 bash。因此,在写 Shell 脚本的时候,脚本的开头即使不加幻数,也会交给bash解释。如果写脚本不希望使用系统默认的 Shell 解释,那么就必须要指定解释器了,否则脚本文件执行后的结果可能就不是你所要的。

建议读者养成好的编程习惯,不管采用什么脚本,最好都加上相应的开头解释器语言标识,遵守 Shell 编程规范。

脚本注释

在 Shell 脚本中,跟在# 后面的内容表示注释,用来对脚本进行注释说明,注释部分不会被当作程序来执行,仅仅是给开发者和使用者看的,系统解释器是看不到的,更不会执行。

注释可自成一行,也可以跟在脚本命令的后面与命令在同一行。

开发脚本时,如果没有注释,那么团队里的其他人就会很难理解脚本对应内容的用途,而且若时间长了,自己也会忘记。因此,我们要尽量养成为所开发的 Shell 脚本书写关键注释的习惯,书写注释不光是为了方便别人,更是为了方便自己,避免影响团队的协作效率,以及给后来接手的人带来维护困难。

特别提示一下,注释尽量不要用中文,在脚本中最好也不要有中文。

bash 与 sh 的区别

早期的bash与sh稍有不同,但大多数脚本都可以不加修改地在sh上运行。

sh为bash的软链接,大多数情况下,脚本的开头使用 #!/bin/bash#!/bin/sh 是没有区别的,但更规范的写法是在脚本的开头使用 #!/bin/bash

Shell 脚本的执行

当 Shell 脚本运行时,它会先查找系统环境变量ENV,该变量指定了环境文件,再加载了上述环境变量文件后, Shell 就开始执行 Shell 脚本中的内容。

Shell 脚本是从上至下、从左至右依次执行每一行的命令及语句的,即执行完了一个命令后再执行下一个,如果在 Shell 脚本中遇到子脚本(即脚本嵌套)时,就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句。通常情况下,在执行 Shell 脚本时,会向系统内核请求启动一个新的进程,以便在该进程中执行脚本的命令及子 Shell 脚本,基本流程如下。

Shell 脚本的执行通常可以采用以下几种方式:

  1. bash script-name 或sh script-name:这是当脚本文件本身没有可执行权限(即文件权限属性x位为-号)时常使用的方法,或者脚本文件开头没有指定解释器时需要使用的方法。

    bash 复制代码
    [yang@server ~ 14:45:15]$ bash script.sh
    Hello World!
  2. /path/script-name 或 ./script-name:指在当前路径下执行脚本(脚本需要有执行权限),需要将脚本文件的权限先改为可执行,然后通过脚本绝对路径或相对路径就可以直接执行脚本了。

    bash 复制代码
    [yang@server ~ 14:45:21]$ chmod +x script.sh
    
    # 绝对路径
    [yang@server ~ 14:45:27]$ /home/laoma/script.sh
    
    # 相对路径
    [yang@server ~ 14:45:34]$ ./script.sh
    Hello World !

    在企业生产环境中,不少运维人员在写完 Shell 脚本之后,由于忘记为该脚本设置执行权限,然后就直接应用了,结果导致脚本没有按照自己的意愿手动或定时执行,对于这一点,避免出现该问题的方法就是用第1种方法替代第2种。

  3. source script-name 或 . script-name :这种方法通常是使用 source. 点号读入或加载指定的 Shell 脚本文件,然后,依次执行指定的 Shell 脚本文件中所有语句。这些语句将在当前父 Shell 脚本进程中运行(其他几种模式都会启动新的进程执行子脚本)。因此,**使用 source 可以将子脚本中的变量值、函数值等传递到当前父 Shell 脚本中使用。**这是它和其他几种方法最大的区别,也是值得读者特别注意的地方。

    bash 复制代码
    [yang@server ~ 14:45:48]$ source script.sh
    Hello World !
    # 等效于
    [yang@server ~ 14;45;48]$ . script.sh
    Hello World !
  4. bash < script-name 或 cat scripts-name|sh:同样适用于bash,这种用法也很常见。

    bash 复制代码
    [yang@server ~ 14:45:52]$ bash < script.sh
    Hello World !
    
    [yang@server ~ 14:45:59]$ cat script.sh | bash
    Hello World !

什么是 Shell 变量

什么是变量

简单地说,变量名是用一个固定的字符串(字符、数字和下划线的组合,不能以数字开头)代替更多、更复杂的内容,该内容里可能还会包含变量、路径、字符串等其他的内容。

变量 是暂时存储数据的地方及数据标记,所存储的数据存在于内存空间中,通过正确地调用内存空间中变量的名字就可以取出与变量对应的数据。使用变量的最大好处就是使程序开发更为方便,当然,在编程中使用变量也是必须的,否则就很难完成相关的程序开发工作。

变量的赋值方式为:先写变量名称,紧接着是这个字符,最后是值,中间无任何空格。

通过 echo 命令加上 $username 即可输出 username 变量的值。变量的内容一般要加双引号,以防止出错,特别是当值里的内容之间有空格时。

Shell 变量的特性

默认情况下,在bash Shell 中是不会区分变量类型的 。例如:常见的变量类型为整数、字符串、小数等都当做字符串变量。这和其他强类型语言(例如:Java/C语言)是有区别的,当然,如果需要指定 Shell 变量的类型,也可以使用 declare 命令定义变量的类型,但在一般情况下没有这个需求。

变量类型

变量根据范围可分为两类:

  • 全局变量 ,在创建它们的 Shell 及其派生出来的任意子进程 Shell 中使用

  • 局部变量,只能在创建它们的 Shell 函数或 Shell 脚本中使用。

变量根据是否是用户自定义也可分为两类:

  • 普通变量:也称为常规变量,由开发者在开发脚本程序时创建。
  • 环境变量:定义shell 执行环境。环境变量又可分为自定义环境变量和bash内置的环境变量。

环境变量

环境变量一般是指用export内置命令导出的变量,用于定义 Shell 的运行环境,保证 Shell 命令的正确执行。 Shell 通过环境变量来确定登录用户名、命令路径、终端类型、登录目录等,所有的环境变量都是系统全局变量,可用于所有子进程中,这包括编辑器、 Shell 脚本和各类应用。

环境变量可以在命令行中设置和创建,但用户退出命令行时这些变量值就会丢失,因此,如果希望永久保存环境变量,可在用户家目录下的 .bash_profile.bashrc(非用户登录模式特有,例如远程SSH)文件中,或者全局配置 /etc/bashrc (非用户登录模式特有,例如远程SSH)或 /etc/profile 文件中定义。在将环境变量放入上述的文件中后,每次用户登录时这些变量都将被初始化。

按照系统规范,所有环境变量的名字均采用大写形式。在将环境变量应用于用户进程程序之前,都应该用 export 命令导出定义。有一些环境变量,比如HOMEPATHUSER等,在用户登录之前就已经被/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 的所有参数配置信息。

环境变量初始化与对应文件的生效顺序

在登录 Linux 系统并启动一个bash shell 时,默认情况下 bash 会在若干个文件中查找环境变量的设置。这些文件可统称为系统环境文件。bash 检查的环境变量文件的情况取决于系统运行 Shell 的方式。

系统运行 Shell 的方式一般有3种:

  1. 通过系统用户登录后默认运行的 Shell 。
  2. 非登录交互式运行 Shell 。
  3. 执行脚本运行非交互式 Shell 。

当用户登录 Linux 系统时, Shell 会作为登录 Shell 启动。此时的登录 Shell 加载环境变量的顺序如下图所示。

  1. 用户登录系统后首先会加载 /etc/profile 全局环境变量文件,这是Linux系统上默认的 Shell 主环境变量文件。系统上每个用户登录都会加载这个文件。

  2. 当加载完/etc/profile文件后,才会执行/etc/profile.d目录下的脚本文件,这个目录下的脚本文件有很多,例如:系统的字符集设置等。

  3. 之后开始运行 $HOME/.bash_profile。在这个文件中,又会去找$HOME/.bashrc:如果有,则执行;如果没有,则不执行。在HOME/.bashrc文件中又会去找/etc/bashrc(全局环境变量文件),如果有,则执行,如果没有,则不执行。

  4. 如果用户的 Shell 不是登录时启动的,比如手动敲下bash时启动,那么这种非登录 Shell 只会加载$HOME/.bashrc(用户环境变量文件),并会去找/etc/bashrc(全局环境变量文件)。

    因此如果希望在非登录 Shell 下也可读到设置的环境变量等内容,就需要将变量设定等写入$HOME/.bashrc或者/etc/bashrc,而不是$HOME/.bash_profile/etc/profile

变量定义技巧总结

可以多学习和模仿操作系统自带的 /etc/init.d/functions 函数库脚本的定义思路,多学习Linux系统脚本中的定义,有经验的读者最终应形成一套适合自己的规范和习惯。

  1. 变量名及变量内容定义小结。

    • 变量名只能包含字母、数字或下划线,只能以字母或下划线开头。

    • 变量名的定义要有一定的规范,并且要见名知意。

      示例:

bash 复制代码
[yang@server bin 10:20:34]$ username=zhangsan
[yang@server bin 10:34:54]$ first_name=zhang
[yang@server bin 10:35:00]$ last_name=san
[yang@server bin 10:35:09]$ 1_name=zhangsan
-bash: 1_name=zhangsan: 未找到命令
[yang@server bin 10:35:16]$ echo $first_name $last_name 
zhang san
[yang@server bin 10:35:44]$ echo $first_name$last_name 
zhangsan
[yang@server bin 10:35:48]$ echo $first_name_$last_name 
san
[yang@server bin 10:35:57]$ echo ${first_name}_$last_name 
zhang_san
[yang@server bin 10:36:09]$ echo $(first_name)_$last_name 
-bash: first_name: 未找到命令
_san

创建第一个 Shell 脚本

bash 复制代码
[yang@server bin 09:48:16]$ vim gather_os_info.sh 
#!/bin/bash
#收集系统快设备信息
echo =============系统块设备信息====================
lsblk
echo
#收集文件系统信息
echo =============文件系统信息====================
df -h | grep -v tmpfs
echo
#收集cpu和内存使用情况
echo =============cpu和内存使用情况====================
top -b -n 1 | head -n 5
echo
# 收集系统中cpu和内存使用情况 使用top命令
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 '/^Tasks/ {print $10}')
echo 系统总内存为: $(cat ${os_info_file} | awk 'NR==4 {print $4}') K
echo 系统可用内存为: $(cat ${os_info_file} | awk 'NR==4 {print $6}') K

[yang@server bin 10:14:53]$ ./gather_os_info.sh 
=============系统块设备信息====================
NAME                MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda                   8:0    0   50G  0 disk 
├─sda1                8:1    0    1G  0 part /boot
└─sda2                8:2    0   49G  0 part 
  ├─centos_c01-root 253:0    0   47G  0 lvm  /
  └─centos_c01-swap 253:1    0    2G  0 lvm  [SWAP]
sr0                  11:0    1  4.4G  0 rom  

=============文件系统信息====================
文件系统                     容量  已用  可用 已用% 挂载点
/dev/mapper/centos_c01-root   47G  1.6G   46G    4% /
/dev/sda1                   1014M  139M  876M   14% /boot

=============cpu和内存使用情况====================
top - 10:15:14 up  1:21,  6 users,  load average: 0.06, 0.10, 0.07
Tasks: 185 total,   1 running, 184 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  4026116 total,  3659360 free,   194136 used,   172620 buff/cache
KiB Swap:  2097148 total,  2097148 free,        0 used.  3619096 avail Mem 

==================== CPU Memory tasks 信息 ====================
系统总任务数量: 185
系统正在运行的进程数量: 1
系统中僵尸进程数量: 0
系统总内存为: 4026116 K
系统可用内存为: 3659152 K

Shell 位置参数变量

在 Shell 中存在一些特殊且重要的变量,例如:$0$1,我们称之为位置参数变量。要从命令行、函数或脚本执行等处传递参数时,就需要在 Shell 脚本中使用位置参数变量。

部分位置参数变量如下:

  1. $0 ,获取当前执行的 Shell 脚本的文件名,如果执行脚本包含了路径,那么就包括脚本路径。
  2. ** n ∗ ∗ ,获取当前执行的 S h e l l 脚本的 ∗ ∗ 第 n 个参数值 ∗ ∗ 。如果 n 大于 9 ,则用大括号括起来,例如 n**,获取当前执行的 Shell 脚本的**第n个参数值**。如果n大于9,则用大括号括起来,例如 n∗∗,获取当前执行的Shell脚本的∗∗第n个参数值∗∗。如果n大于9,则用大括号括起来,例如{10},接的参数以空格隔开。
  3. $# ,获取当前执行的 Shell 脚本后面接的参数数量
  4. ** ∗ ∗ ∗ ,获取当前 S h e l l 脚本所有传参的参数,不加引号和 ' ***,获取当前 Shell 脚本所有传参的参数,不加引号和` ∗∗∗,获取当前Shell脚本所有传参的参数,不加引号和'@相同;如果给 ∗ ' 加上双引号,例如: ' " *`加上双引号,例如:`" ∗'加上双引号,例如:'"*",则表示将所有的参数视为单个字符串,相当于1 2 $3`。
  5. ** @ ∗ ∗ ,获取当前 S h e l l 脚本所有传参的参数,不加引号和 @**,获取当前 Shell 脚本所有传参的参数,不加引号和 @∗∗,获取当前Shell脚本所有传参的参数,不加引号和*相同;如果给 @ 加上双引号,例如: ' @加上双引号,例如:` @加上双引号,例如:'@,则表示将所有的参数视为不同的独立字符串,相当于"1"、"2"、"$3"...`,这是将多参数传递给其他程序的最佳方式,因为它会保留所有的内嵌在每个参数里的任何空白。
bash 复制代码
[yang@server bin 11:15:20]$ vim ssh_ctl
[yang@server bin 11:18:22]$ cat ssh_ctl 
#!/bin/bash
systemctl $1 sshd
[yang@server bin 11:14:13]$ chmod +x ssh_ctl 
[yang@server bin 11:15:59]$ ssh_ctl status
● sshd.service - OpenSSH server daemon
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
   Active: active (running) since 四 2025-09-25 09:53:01 CST; 2 weeks 0 days ago
     Docs: man:sshd(8)
           man:sshd_config(5)
 Main PID: 997 (sshd)
   CGroup: /system.slice/sshd.service
           └─997 /usr/sbin/sshd -D
[yang@server bin 11:16:20]$ systemctl status sshd
● sshd.service - OpenSSH server daemon
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
   Active: active (running) since 四 2025-09-25 09:53:01 CST; 2 weeks 0 days ago
     Docs: man:sshd(8)
           man:sshd_config(5)
 Main PID: 997 (sshd)
   CGroup: /system.slice/sshd.service
           └─997 /usr/sbin/sshd -D
[yang@server bin 11:16:57]$ ssh_ctl stop
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to manage system services or units.
Authenticating as: root
Password: 
==== AUTHENTICATION COMPLETE ===
[yang@server bin 11:17:21]$ ssh_ctl status
● sshd.service - OpenSSH server daemon
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
   Active: inactive (dead) since 四 2025-10-09 11:17:21 CST; 5s ago
     Docs: man:sshd(8)
           man:sshd_config(5)
  Process: 997 ExecStart=/usr/sbin/sshd -D $OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 997 (code=exited, status=0/SUCCESS)
[yang@server bin 11:17:26]$ ssh_ctl start
==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to manage system services or units.
Authenticating as: root
Password: 
==== AUTHENTICATION COMPLETE ===

Shell 进程中的特殊状态变量

$?

作用:获取执行上一个指令的执行状态返回值:0为成功,非零为失败,这个变量最常用。

bash 复制代码
[yang@server bin 11:29:55]$ vim show_args.sh 
#!/bin/bash

echo $1
echo $2
echo $#
[yang@server bin 11:30:22]$ ./show_args.sh {1..9}
1
2
9

[yang@server bin 11:30:36]$ ls
gather_os_info.sh  hello.sh  service_ctl  show_args.sh  ssh_ctl
[yang@server bin 11:31:38]$ echo $?
0
[yang@server bin 11:31:44]$ ls daidh
ls: 无法访问daidh: 没有那个文件或目录
[yang@server bin 11:31:49]$ echo $?
2
read

从标准输入读取字符串等信息, 传给 Shell 程序内部定义的变量。

bash 复制代码
[root@server bin 13:38:10]# vim useradd.sh
#!/bin/bash

#提供用户名
read -p "请输入用户名:" name

#提供用户性别
read -p "请输入用户性别:" sex

#提供用户年龄
read -p "请输入用户年龄:" age

#提供用户密码
read -sp "请输入用户密码:" pass
echo

#添加用户
useradd ${name}

#设置用户密码
echo ${pass} | passwd --stdin ${name} 

#将用户信息存储到/etc/users.info
echo ${name}:${sex}:${age}: >> /etc/users.info
[root@server bin 13:53:20]# bash useradd.sh 
请输入用户名:zhangsan
请输入用户性别:male
请输入用户年龄:18
请输入用户密码:
更改用户 zhangsan 的密码 。
passwd:所有的身份验证令牌已经成功更新。
[root@server bin 13:54:12]# cat /etc/users.info 
zhangsan:male:18:

算术运算符

如果要执行算术运算,就会离不开各种运算符号,和其他编程语言类似,Shell 也有很多算术运算符。

下面就给大家介绍一下常见的 Shell 算术运算符:

  • +、-一元正号和负号。
  • +、-,加法和减法。
  • *、/、%,乘法、除法、取余(取模)。
  • **,幂运算。
  • ++、--,增加及减少,可前置也可放在变量结尾。
  • !、&&、||,逻辑非(取反)、逻辑与(and)、逻辑或(or)。
  • <、<=、>、>=,比较符号(小于、小于等于、大于、大于等于)。
  • ==、!=、=,比较符号(相等、不相等,对于字符串也可以表示相当于)。
  • <<、>>,向左移位、向右移位。
  • ~、|、&、^,按位取反、按位异或、按位与、按位。
  • =、+=、-=、*=、/=、%= ,赋值运算符,例如 a+=1 相当于 a=a+1a-=1 相当于 a=a-1

Shell 中常见的算术运算命令:

  • (()),用于整数运算的常用运算符,效率很高。
  • let ,用于整数运算,类似于(())
  • expr,可用于整数运算,但还有很多其他的额外功能。
  • bc,Linux下的一个计算器程序(适合整数及小数运算)。
  • $[],用于整数运算。
  • awk,awk 既可以用于整数运算,也可以用于小数运算。
  • declare,定义变量值和属性,-i参数可以用于定义整形变量,做运算。

(()) 双小括号数值运算命令

双小括号 (()) 的作用是进行数值运算与数值比较,它的效率很高,用法灵活,是企业场景运维人员经常采用的运算操作符。

(()) 双小括号数值运算的基础语法

双小括号 (()) 的操作方法:

  • ((i=i+1)),此种书写方法为运算后赋值法,即将i+1的运算结果赋值给变量i。

    注意 :不能用 echo ((i=i+l))输出表达式的值,可以用echo $((i=i+l))输出其值。

  • **i= ( ( i + 1 ) ) ∗ ∗ ,可以在 ' ( ( ) ) ' 前加 ' ((i+1))**,可以在 `(())` 前加 ` ((i+1))∗∗,可以在'(())'前加'` 符,表示将表达式运算后赋值给i。

  • (( 8>7 && 5==5)),可以进行比较操作,还可以加入逻辑与和逻辑或,用于条件判断。

  • **echo ( ( 2 + 1 ) ) ∗ ∗ ,需要直接输出运算表达式的运算结果时,可以在 ' ( ( ) ) ' 前加 ' ((2+1))**,需要直接输出运算表达式的运算结果时,可以在 `(())` 前加 ` ((2+1))∗∗,需要直接输出运算表达式的运算结果时,可以在'(())'前加'` 符。

bash 复制代码
[root@server bin 14:37:47]# echo $[1+2]
3
[root@server bin 14:38:27]# echo  ${$(echo {1..100} | sed 's/ /+/g')}
-bash: 未预期的符号 `)' 附近有语法错误
[root@server bin 14:39:23]# echo  $[$(echo {1..100} | sed 's/ /+/g')]
5050
[root@server bin 14:39:38]# echo  $(($(echo {1..100} | sed 's/ /+/g')))
5050
[root@server bin 14:39:48]# echo $((1+2))
3
[root@server bin 14:40:08]# let 1+2
[root@server bin 14:40:21]# let sum=1+2
[root@server bin 14:40:32]# echo sum
sum
[root@server bin 14:40:36]# echo $sum
3
[root@server bin 14:41:00]# echo $[1.3+2.7]
-bash: 1.3+2.7: 语法错误: 无效的算术运算符 (错误符号是 ".3+2.7")
[root@server bin 14:41:10]# echo 1.3+2.7 | bc
4.0

Shell 脚本的条件测试

条件测试方法综述

通常,在 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 的算术运算符(())

test 条件测试的简单语法及示例

test条件测试的语法格式为: test <判断表达式>

bash 复制代码
[root@server bin 15:19:03]# 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.

文件判断表达式

bash 复制代码
[yang@server ~ 14:44:15]$ test ! -d /tmp/zhangsan && mkdir /tmp/zhangsan
[yang@server ~ 15:02:44]$ echo $?
0
[yang@server ~ 15:02:52]$ test ! -d /tmp/zhangsan && mkdir /tmp/zhangsan
[yang@server ~ 15:02:54]$ echo $?
1
[yang@server ~ 15:03:50]$ test  -d /tmp/zhangsan && echo /tmp/zhangsan is exit.
/tmp/zhangsan is exit.

字符串判断表达式

字符串测试操作符

字符串测试操作符的作用包括:比较两个字符串是否相同、测试字符串的长度是否为零、字符串是否为NULL等。

常用字符串测试操作符:

  • -n "字符串",若字符串的长度不为0,则为真,即判断表达式成立,n可以理解为no zero。
  • -z "字符串",若字符串的长度为0,则为真,即判断表达式成立,z可以理解为zero的缩写。
  • "串1" = "串2",若字符串1等于字符串2,则为真,即判断表达式成立,可使用"=="代替"="。
  • "串1" != "串2",若字符串1不等于字符串2,则为真,即判断表达式成立,但不能用"!=="代替"!="。

以下是针对字符串测试操作符的提示:

  • 对于字符串的测试,一定要将字符串加双引号之后再进行比较 ,如 [ -n "$myvar" ],特别是使用[] 的场景。
  • 比较符号(例如=和!=)的两端一定要有空格。

-z 选项,判断变量值是否为空,如果为空,则为真值,反之为假值。

bash 复制代码
[root@server bin 15:54:41]# unset string
[root@server bin 15:56:31]# [ -z "$string" ] && echo null string || echo $string
null string
[root@server bin 15:56:40]# string="hello world"
[root@server bin 15:56:46]# [ -z "$string" ] && echo null string || echo $string
hello world

-n 选项,判断变量值是否为非空,如果为非空,则为真值,反之为假值。

bash 复制代码
[root@server bin 15:56:49]# unset string
[root@server bin 15:57:21]# [ -n "$string" ] && echo $string || echo null string
null string
[root@server bin 15:57:34]# string="hello world"
[root@server bin 15:57:39]# [ -n "$string" ] && echo $string || echo null string
hello world

[[]]中=~操作符

语法: [[ "$str" =~ pattern ]]

**作用:**使用 =~ 操作符时,其右边的字符串被认为是一个扩展正则表达式。扩展之后跟左边字符串进行比较,看左边字符串是否包含右边模式,也就是判断右边的模式是否为左边字符串的子字符串,而不是判断右边的模式是否完全等于左边字符串。

=~ 操作符右边的字符串不能使用引号括起来,无论是双引号、还是单引号,否则表示匹配这个字符串自身的内容,不再解析成正则表达式。

bash 复制代码
# 判断变量值是否包涵数字
[root@server bin 15:57:45]# str=123abc
[root@server bin 15:59:27]# [[ "$str" =~ [0-9]+ ]] && echo str is a string with num || echo str is a string without any num
str is a string with num
[root@server bin 15:59:34]# str=abcdef
[root@server bin 15:59:39]# [[ "$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

# 判断变量值是否只包涵数字
[root@server bin 15:59:43]# str=123;[[ "$str" =~ ^[0-9]+$ ]] && echo str is a num || echo str is not a num
str is a num

# 加引号对比
[root@server bin 15:59:50]# str=123;[[ "$str" =~ [0-9]+ ]] && echo true || echo false 
true
[root@server bin 15:59:55]# str=123;[[ "$str" =~ "[0-9]+" ]] && echo true||echo false 
false

整数二元比较操作符

整数二元比较操作符介绍

整数二元比较操作符使用说明如下:

[]和test中比较符号 (())和[[]]中比较符号 说明
-eq ==或= 相等,全拼为 equal
-ne != 不相等,全拼为 not equal
-gt > 大于,全拼为 greater than
-ge >= 大于等于,全拼为 greater equal
-It < 小于,全拼为less than
-le <= 小于等于,全拼为 less equal

以下是针对上述符号的特别说明:

  • =!= 也可在[]中做比较使用,但在[]中使用包含 >< 的符号时,需要用反斜线转义,有时不转义虽然语法不会报错,但是结果可能会不对。
  • 也可以在[[]]中使用包含-gt-lt 的符号,但是不建议这样使用。
  • 比较符号两端也要有空格。
bash 复制代码
[root@server bin 15:52:23]# vim test_num.sh
[root@server bin 15:54:21]# 
[root@server bin 15:54:22]# bash test_num.sh 
请输入一个数字:56
56 > 10
[root@server bin 15:54:33]# cat test_num.sh
#!/bin/bash

read -p "请输入一个数字:" num
# 大于 10
(( $num>10 )) && echo "$num > 10"
# 小于 10
(( $num<10 )) && echo "$num < 10"
# 等于 10
(( $num==10 )) && echo "$num = 10"

[]、[[]]、(())用法的小结:

  • 整数加双引号的比较是对的。
  • [[]]中用类似-eq等的写法是对的,[[]]中用类似>、<的写法也可能不对,有可能会只比较第一位,逻辑结果不对。
  • []中用类似>、<的写法在语法上虽然可能没错,但逻辑结果不对,可以使用=、!=正确比较。
  • (())中不能使用类似-eq等的写法,可以使用类似>、<的写法。

if 条件语句

if条件语句是Linux运维人员在实际生产工作中使用得最频繁也是最重要的语句,因此,请务必重视if条件语句的知识,并牢固掌握。

1. 单分支结构

第一种语法:

bash 复制代码
if <条件表达式>
  then 
  指令
fi

第二种语法:

bash 复制代码
if <条件表达式>;then
  指令
fi

上文的"<条件表达式>"部分可以是test、口、[[]]、(())等条件表达式,甚至可以直接使用命令作为条件表达式。每个if条件语句都以if开头,并带有then,最后以fi结尾。

第二种语法中的分号相当于命令换行,上面的两种语法含义是相同的,读者可根据习惯自行选择。

本书主要使用第二种语法格式。

在所有编程语言里,if条件语句几乎是最简单的语句格式,且用途最广。当if后面的<条件

表达式>成立时(真),就会执行then后面的指令或语句;否则,就会忽略then后面的指令或语句,

转而执行fi下面的程序。

if单分支语句执行流程逻辑图如下:

条件语句还可以嵌套(即if条件语句里面还有if条件语句),注意每个if条件语句中都要有一个与之对应的fi(if反过来写),每个if和它下面最近的fi成对搭配,语法示例如下:

bash 复制代码
if <条件表达式>;then
  if <条件表达式>;then
    指令
  fi
fi

提示:通常在书写Shell条件语句时,要让成对的条件语句关键字的缩进相对应,以便于阅读浏览。

前文曾讲解过的文件条件表达式:

bash 复制代码
[root@server bin 16:00:01]# [ ! -d /tmp/yang ] && mkdir /tmp/yang

等价于下面的if条件语句:

bash 复制代码
[root@server bin 16:54:35]# if [ ! -d /tmp/yang ];then
> mkdir  /tmp/yang
> fi

2. 双分支结构

if条件语句的双分支结构主体则为:"如果...,那么...,否则..."。

if条件语句的双分支结构语法为:

bash 复制代码
if <条件表达式>;then
  指令集1
else
  指令集2
fi

if 双分支语句执行流程逻辑图如下:

前文的文件测试条件表达式:

bash 复制代码
[root@server bin 16:55:07]# [ -d /tmp/yang ] && echo /tmp/yang is exist || mkdir /tmp/yang
/tmp/yang is exist

就相当于下面的双分支的if条件语句:

bash 复制代码
[root@server bin 16:56:10]# if [ -d /tmp/yang ];then
> echo /tmp/yang is exist
> else
> mkdir /tmp/yang
> fi
/tmp/yang is exist

3.多分支结构

if条件语句多分支结构的主体为:"如果...,那么...,否则如果...,那么,否则如果...,那么...,否则..."。

if条件语句多分支语法为:

bash 复制代码
if <条件表达式1>;then
  指令1
elif <条件表达式2>;then
  指令2
else
  指令3
fi

多个elif

bash 复制代码
if <条件表达式1>;then
  指令
elif <条件表达式2>;then
  指令
elif <条件表达式3>;then
  指令
else
  指令
fi

提示:

  1. 注意多分支elif的写法,每个elif都要带有then。
  2. 最后结尾的else后面没有then。

if 条件语句实践

示例:每3分钟检查一次系统可用内存,如果空闲内存低于100M时给root用户发邮件。

1.获取可用内存大小。

bash 复制代码
[root@server bin 16:59:05]# free -m
              total        used        free      shared  buff/cache   available
Mem:           3931         195        3554          11         182        3521
Swap:          2047           0        2047
[root@server bin 17:00:21]# free -m | awk 'NR==2 { print $4}'
3553

2.编写Shell脚本:monitor_mem.sh。

bash 复制代码
[root@server bin 16:59:17]# vim monitor_mem.sh
#!/bin/bash
FreeMem=$(free -m | awk 'NR==2 { print $4}')
if [ $FreeMem -lt 100 ];then
  echo  "Mem is lower than 100M" | mail -s "FreeMem is ${FreeMem}M" root@localhost
fi
[root@server bin 16:59:44]# chmod +x monitor_mem.sh

3.加入到计划任务

bash 复制代码
[root@server bin 16:59:44]# chmod +x monitor_mem.sh
[root@server bin 16:59:50]# crontab -e
no crontab for root - using an empty one
crontab: no changes made to crontab
[root@server bin 17:00:06]# crontab -l
*/3 * * * * /home/yang/monitor_mem.sh
相关推荐
嗨丶王哪跑2 小时前
网络安全审计技术原理与应用
运维·网络·安全·web安全
斯普信专业组2 小时前
Skywalking从部署集成到动态调优(上)
运维·skywalking·动态调优
斯普信专业组2 小时前
Skywalking从部署集成到动态调优(下)
运维·skywalking·动态调优
努力努力再努力wz2 小时前
【C++进阶系列】:万字详解智能指针(附模拟实现的源码)
java·linux·c语言·开发语言·数据结构·c++·python
QMCY_jason3 小时前
ubuntu 24.04 FFmpeg编译 带Nvidia 加速记录
linux·ubuntu·ffmpeg
matlab的学徒3 小时前
Kubernetes(K8S)全面解析:核心概念、架构与实践指南
linux·容器·架构·kubernetes
Fcy6483 小时前
初识Linux和Linux基础指令详细解析及shell的运行原理
linux·服务器·ubuntu·centos
gb42152874 小时前
linux系统中如何在root用户中将某个文件夹目录的权限赋值给其它用户(主要说的是 方法 1)
linux