目录
前言
编程语言分类
静态和动态语言:
-
静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如Java语言、C语言
-
动态编译语言:不事先声明,可随时改变类型,如Shell语言、python语言、javascript语言、php语言
强类型和弱类型语言:
-
强类型语言:不同类型数据操作,必须经过强制转换成同一类型才能运算,如java,C#
-
弱类型语言:语言的运行时会隐式地做数据类型转换。无需指定类型,默认均为字符型;参与计算会自动进行隐式类型转换;变量无需事先定义可直接调用。如Shell语言,php,JavaScript
脚本检查工具:
yum install -y epel-release
yum install -y ShellCheck
一、Shell脚本语言的基本结构
1、Shell脚本的用途:
-
自动化常用命令
-
执行系统管理和故障排除
-
创建简单的应用程序
-
处理文本或文件
2、 Shell脚本基本结构:
Shell脚本编程:是基于过程式,解释执行的语言
编程语言的基本结构:
-
各种系统命令的组合
-
数据存储:变量,数组
-
表达式:a+b
-
控制语句:if、case、for、while
shell脚本:包含一些命令或声明,并符合一定格式的文本文件
格式要求:首行执行shebang机制
#声明后续语句是通过那种语言写的
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
3、 创建Shell脚本过程
-
使用vim创建文本文件,第一行必须包括shell声明序列:
#!/bin/bash
-
加执行权限,给予执行权限,在命令行上指定脚本的绝对或相对路径
[root@localhost ~]# chmod +x shellScript/hello.sh
-
运行脚本,直接运行解释器,将脚本作为解释器程序的参数运行。
[root@localhost ~]# /root/shellScript/hello.sh
例:
vim script1.sh
#!/bin/bash
#auth:Super ShanShan
#Use:列出系统中所有的普通文件
#Date: 2025-06-04
#version:1.0
cd /home
./script1.sh
find /home -type f
~
~
~
~
~
~
~
~
~
~
~
"script1.sh" 8L, 149C 7,12 全部
[root@bogon ~]# chmod +x script1.sh
root@bogon opt]# /root/script1.sh
/home/shanshan/.bash_logout
/home/shanshan/.bash_profile
/home/shanshan/.bashrc
4、 脚本注释规范
-
第一行一般为调用使用的语言
-
程序名,避免更改文件名为无法找到正确的文件
-
版本号
-
更改后的时间
-
作者相关信息
-
该程序的作用,及注意事项
-
最后是各版本的更新简要说明
5、 执行脚本
1、增加执行权限,执行脚本时会创建一个子shell,不影响现有的shell环境
chmod +x sh
./sh
/root/shellScript/sh
2、使用 . 或者source,执行脚本时不会创建一个子shell,会影响现有的shell环境
source sh
. sh
**注意:尽量不要使用该方式执行脚本!!!而且没有权限时运行会切换运行环境!
其它运行方式
bash
6、脚本错误
-
语法错误,会导致后续的命令不继续执行,可以用bash -n shellname检查错误
-
命令错误,后续的命令还会继续,可以使用bash -x shellname检查
-
逻辑错误,只能使用bash -x进行观察
二、Shell脚本语言的变量用法详解
1、变量
变量表示命名的内存空间,将数据放在内存空间中,通过变量名引用,获取数据
2、 变量类型
变量类型:
-
内置变量:如PS1,PATH,UID,HOSTNAME,HISTSIZE
-
用户自定义变量
-
预定义变量
-
位置变量
不同的变量存放的数据不同,决定了以下:
-
数据存储方式
-
参与的计算
-
表示的数据范围
变量数据类型:
-
字符串
-
数值:整型,浮点型(小数)、bash不支持浮点数
3、 Shell中变量命名法则
-
不能使用程序中的保留字,如:if,for
-
只能使用数字,字母及下划线,且不能以数字开头
-
见名思意,用英文名字,并体现真正含义
-
统一命名规则:驼峰命名法
-
变量名大写
-
局部变量小写
-
函数名小写
4、 变量定义和引用
变量的生效范围(变量作用域)
-
普通变量:生效范围为当前shell进程;对当前shell之外的其他shell进程,包括当前shell的子shell进程均无效
-
环境变量:生效范围为当前shell进程及其子进程
-
本地变量:生效范围为当前shell进程中某代码片段,通常指函数
变量赋值:
name="value"
value可以是以下多种类型
直接字符串:name='root'
变量引用:name="$USER"
命令应用:name=`command` || name=$(command)
通配符:FILE=/etc/* /*表示etc目录下所有的文件名*/
注意:变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存。
变量引用:
$name
${name}
例:
[root@bogon ~]# n1=1
[root@bogon ~]# echo $n1
1
弱引用和强引用:
-
“$name”:弱引用,其中的变量引用会被替换成为变量值
-
‘$name’:强引用,其中的变量引用不会被替换成变量值,而保持原字符串
[root@bogon ~]# str1=zhao
[root@bogon ~]# str1=`zhao`
[root@bogon ~]# str2=`$str1`
[root@bogon ~]# echo $str2
$str1
[root@bogon ~]# str1="zhao"
[root@bogon ~]# echo $str2
zhao
{}"的使用
[root@bogon ~]# str1=zhao
[root@bogon ~]# str4=$(str1)4444
zhao4444
5、 环境变量
环境变量:
-
可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量。
-
一旦子进程修改了从父进程继承的变量,将会传递新的值给孙子进程
-
一般只在配置文件中使用,在脚本中较少使用
课程引入:普通变量生效的范围与环境变量生效的范围
[root@localhost ~]# vim shell/father.sh
#!/bin/bash
NAME=father
echo "father.sh:NAME=$NAME"
echo "fatther is PID=$BASHPID"
shell/son.sh
[root@localhost ~]#vim shell/son.sh
#!/bin/bash
echo "son.sh:NAME=$NAME"
NAME=son
echo "son.sh:NAME=$NAME"
echo "son.sh PID is $BASHPID"
echo "son.sh father pid is $PPID"
sleep 100
[root@localhost ~]#chmod -R +x shell/*
[root@localhost ~]# ./shell/father.sh
father.sh:NAME=father
fatther is PID=12053
son.sh:NAME=
son.sh:NAME=son
son.sh PID is 12054
son.sh father pid is 12053
#子进程无法使用父进程的变量,需要自己赋值
变量声明和赋值:
export name=VALUE
declare -x name =VALUE
#或者先赋值再声明~
value可以是以下多种类型
直接字符串:name='root'
变量引用:name="$USER"
命令应用:name=`command` || name=$(command)
通配符:FILE=/etc/* /*表示etc目录下所有的文件名*/
6、只读变量
只读变量:只能声明定义,但后续不能修改和删除
声明只读变量:
readonly name
declare -r name
查看只读变量:
readonly [-p]
declare -r
7、位置变量
位置变量:在Bash Shell中内置的变量,在脚本代码中调用命令行传递给脚本的参数
$1,$2,... 对应第一个,第二个等参数,shift[n]换位置,最多9个
#预定义变量
$0 命令本身,包括路径
$* 传递给脚本的所有参数,全部参数合成一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
$# 传递给脚本的参数的个数
$? 上个命令的退出状态,或函数的返回值
$$ 当前shell进程ID。对于Shell脚本,就是这些脚本所在的进程ID
注意:$@,$*只有被双引号括起来的时候才会有差异
例:
[root@bogon ~]# vim script1.sh
#!/bin/bash
#auth:Super ShanShan
#Use:列出系统中所有的普通文件
#Date: 2025-06-04
#version:1.0
echo $1 $2 $3 $4 $5 $6 $7 $8 $9
echo $0
echo $*
echo $@
echo $#
echo $?
echo $$
[root@bogon ~]# ./script1.sh a b c d e f g h i
a b c d e f g h i
./script1.sh
a
a
a
a
a
a
a
a
a
a
9
0
56367
清空所有位置变量
set --
//写在脚本内部
[root@bogon ~]# vim script1.sh
#!/bin/bash
#auth:Super ShanShan
#Use:列出系统中所有的普通文件
#Date: 2025-06-04
#version:1.0
set --
echo $1 $2 $3 $4 $5 $6 $7 $8 $9
echo $0
echo $*
echo $@
echo $#
echo $?
echo $$
[root@bogon ~]# ./script1.sh a b c d e f g h i
./script1.sh
0
0
57123
8、 退出状态码变量
进程执行后,将使用变量 ? 保存状态码的相关数字,不同的值反应成功与失败, ?保存状态码的相关数字,不同的值反应成功与失败, ?保存状态码的相关数字,不同的值反应成功与失败,的取值范围为[0,255]
$?的值为0 代表成功
$?的值不为0 代表失败
例:
[root@bogon ~]# mkdir aaa
[root@bogon ~]# echo $?
0
用户可以在脚本中使用以下命令自定义退出状态码
exit [n]
注意:
-
脚本中一旦遇到了exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
-
如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
9.2 防止扩展
反斜线(\)会使随后的字符按原意解释
实例:
[root@bogon ~]# echo "\$a"
$a
9.3 加引号来防止扩展
单引号(' ')防止所有扩展 双引号(" ")可防止扩展,但是以下清空例外:$(美元符号)
9.4 变量扩展
``:反引号,命令替换 \:反斜线,禁止单个字符扩展 !:叹号,历史命令替换
三、 Shell字符串详解
字符串(String)就是一系列字符的组合。字符串是Shell编程中最常用的数据类型之一
字符串可以由单引号''
包围,也可以由""
包围,也可以不用引号,三种方式的区别
-
由单引号
' '
包围的字符串-
任何字符都会原样输出,在其中使用变量是无效的
-
字符串中不能出现单引号,即使对单引号进行转义也不行
-
-
由双引号
" "
包围的字符串-
如果其中包含了某个变量,那么该变量就会被解析(得到该变量的值),而不是原样输出
-
字符串中可以出现双引号,只要进行转义就行
-
-
不被引号包围的字符串
-
不被引号包围的字符串中出现变量也会被解析,这一点和双引号
""
包围的字符串一样 -
字符串中不能出现空格,否则空格后面的字符串会作为其他变量或者命令解析
-
通过代码演示一下三种形式的区别
#!/bin/bash
n=74
str1=c.biancheng.net$n
str2="shell \"Script\" $n"
str3='C语言中文网 $n'
echo $str1
echo $str2
echo $str3
# 运行结果
c.biancheng.net74
shell "Script" 74
C语言中文网 $n
str1 中包含了$n
,它被解析为变量 n 的引用。$n
后边有空格,紧随空格的是 str2;Shell 将 str2 解释为一个新的变量名,而不是作为字符串 str1 的一部分
str2 中包含了引号,但是被转义了(由反斜杠\
开头的表示转义字符)。str2 中也包含了$n
,它也被解析为变量 n 的引用
str3 中也包含了$n
,但是仅仅是作为普通字符,并没有解析为变量 n 的引用
获取字符串长度
在Shell中获取字符串长度很简单,具体方法如下:
${#string_name}
string_name:表示字符串名字
1、Shell字符串拼接
在脚本语言中,字符串的拼接(也称为字符串连接或者字符串合并)往往都非常简单,例如:
-
在
PHP
中使用.
即可连接两个字符串 -
在
JavaScript
中使用+
即可将两个字符串合并为一个
然而,在Shell中你不需要使用任何运算符,将两个字符串并排放在一起就能实现拼接
#!/bin/bash
name="shell"
url="http://c.biancheng.net/shell/"
str1=$name$url #中间不能有空格
str1=$name":"$url
str2="$name $url" #如果被双引号包围,那么中间可以有空格,也可以出现别的字符串
str3="$name:$url"
str4="${name}Script:${url}Index.html" #在变量后加上字符串,需要给变量名加上大括号
2、Shell字符串截取
Shell截取字符串通常有两种方式,从指定位置开始截取和从指定字符(子字符串)开始截取
从指定位置开始截取
这种方式需要两个参数:除了指定起始位置,还需要截取长度,才能最终确定要截取的字符串
既然需要指定起始位置,那么就要涉及到计数方向的问题,到底是从字符串左边开始计数,还是从字符串右边开始计数?答案是:Shell同时支持两种计数方式
1.从字符串左边开始计数
如果想从字符串的左边开始计数,那么截取字符串的具体格式如下:
${string:start:length}
`其中,Sting是要截取的字符串,start是起始位置(从左边开始,从0开始计数),length是要截取的长度(省略的话表示直到字符串的末尾)
例如:
url="c.biancheng.net"
echo ${url:2:9}
>结果为:biancheng
url="c.biancheng.net"
echo ${url:2} #省略length,截取到字符串末尾
>结果为:biancheng.net
2.从右边开始计数
如果想从字符串的右边开始计数,那么截取字符串的具体格式如下:
${string:0-start:length}
`同第 1) 种格式相比,第 2) 种格式仅仅多了0-,这是固定的写法,专门用来表示从字符串右边开始计数。
这里需要强调两点:
从左边开始计数时,起始数字是 0(这符合程序员思维);
从右边开始计数时,起始数字是 1(这符合常人思维)
计数方向不同,起始数字也不同。
不管从哪边开始计数,截取方向都是从左到右。
例如:
url="c.biancheng.net"
echo ${url:0-13:9}
>结果为:biancheng 从右边数:b是第13个字符
url="c.biancheng.net"
echo ${url:0-13} #省略length,直接截取到字符串末尾
>结果为:biancheng.net
从指定字符(子字符串)开始截取
这种截取方式无法指定字符串长度,只能从指定字符(子字符串)截取到字符串末尾。Shell可以截取指定字符(子字符串)右边的所有字符,也可以截取左边的所有字符
1)使用#号截取右边字符
使用#
号可以截取指定字符(或子字符串)右边的所有字符,具体格式如下:
${string#*chars}
#其中,string 表示要截取的字符,chars 是指定的字符(或者子字符串),*是通配符的一种,表示任意长度的字符串。*chars连起来使用的意思是:忽略左边的所有字符,直到遇见 chars(chars 不会被截取)
例如:
url="http://c.biancheng.net/index.html"
echo ${url#*:}
>结果为://c.biancheng.net/index.html
echo ${url#*p:}
echo ${url#*ttp:}
`如果不需要忽略chars左边的字符,那么也可以不写*
url="http://c.biancheng.net/index.html"
echo ${url#http://}
>结果为:c.biancheng.net/index.html
`注意:以上写法遇到第一个匹配的字符(子字符串)就结束了
url="http://c.biancheng.net/index.html"
echo ${url#*/}
>结果为:/c.biancheng.net/index.html。url 字符串中有三个/,输出结果表明,Shell 遇到第一个/就匹配结束了
使用##
可以直到最后一个指定字符(子字符串)再匹配结束
${string##*chars}
例如:
#!/bin/bash
url="http://c.biancheng.net/index.html"
echo ${url#*/}
# 结果为:/c.biancheng.net/index.html
echo ${url##*/}
# 结果为:index.html
str="-----aa+++aa@@@"
echo ${str#*aa}
# 结果为:+++aa@@@
echo ${str##*aa}
# 结果为:@@@
2)使用%截取左边字符
使用%
号可以截取指定字符(或者子字符串)左边的所有字符
3、汇总
格式 | 说明 |
---|---|
${string:start :length} | 从string字符串的左边第start个字符开始,向右截取length个字符。 |
${string:start} | 从string字符串的左边第start个字符开始截取,直到最后。 |
${string:0-start:length} | 从string字符串的右边第start个字符开始,向右截取length个字符。 |
${string:0-start} | 从string字符串的右边第start个字符开始截取,直到最后。 |
${string#*chars} | 从string字符串第一次出现chars的位置开始,截取chars右边的所有字符。 |
${string##*chars} | 从string字符串最后一次出现chars的位置开始,截取chars右边的所有字符。 |
${string%chars*} | 从string字符串第一次出现chars的位置开始,截取chars左边的所有字符。 |
${string%%chars*} | 从string字符串最后一次出现chars的位置开始,截取chars左边的所有字符。 |
例:
[root@bogon ~]# b=sdaadwdqwea
[root@bogon ~]# echo ${#b}
11
[root@bogon ~]# str1=abc
[root@bogon ~]# str2=def
[root@bogon ~]# echo $str1$str2
abcdef
[root@bogon ~]# str3=$str1${str2}gh
[root@bogon ~]# echo $str3
abcdefgh
[root@bogon ~]# echo ${str3:0:2}
ab
[root@bogon ~]# echo ${str3:0}
abcdefgh
[root@bogon ~]# echo ${str3:0:1}
a
[root@bogon ~]# echo ${str3:1:1}
b
[root@bogon ~]# echo ${str3:1:2}
bc
[root@bogon ~]# echo ${str3:1}
bcdefgh
[root@bogon ~]# echo ${str3:0-2}
gh
[root@bogon ~]# echo ${str3:0-0}
abcdefgh
[root@bogon ~]# echo ${str3:0-1}
h
[root@bogon ~]# echo ${str3:0-1:1}
h
[root@bogon ~]# echo ${str3#*b}
cdefgh
[root@bogon ~]# echo ${str3##*b}
cdefgh
[root@bogon ~]# echo ${str3%b*}
a
[root@bogon ~]# echo ${str3%%b*}
a
4、Shell的格式化输出printf
4.1、语法格式:
printf "指定的格式" "文本1" "文本2" .....
4.2、常用格式替换符:
替换符 | 功能 |
---|---|
%s | 字符串 |
%f | 浮点格式,保留小数点位数%.nf,n为数字 |
%b | 相对应的参数中包括转义字符时,可以使用此替换符进行替换,对应的转义字符会被转义 |
%c | ASCII字符,即显示对应参数的第一个字符 |
%d,%i | 十进制整数 |
%o | 八进制值 |
%u | 不带正负号的十进制值 |
%x | 十六进制值(a-f) |
%X | 十六进制值(A-F) |
%% | 表示%本身 |
说明:%s中的数字代表此替换符中的输出字符宽度,不足补空格,默认是右对齐,%-10s表示10个字符宽,-表示左对齐
#!/bin/bash
read -p "请输入你的姓名: " name
read -p "请输入你的年龄: " age
read -p "请输入你的性别: " sex
read -p "请输入你的电话: " phone
read -p "请输入你的体重: " weight
printf "%s 你好,你的年龄为%d, 性别 %s,电话 %d, 体重 %.2f" $name $age $sex $phone $weight
~[root@bogon ~]# ./personal.sh
请输入你的姓名: shanshan
请输入你的年龄: 1
请输入你的性别: 飞机
请输入你的电话: 12345
请输入你的体重: 1
shanshan 你好,你的年龄为1, 性别 飞机,电话 12345, 体重 1.00
4.3、常用转义字符:
转义符 | 功能 |
---|---|
\a | 警告字符,通常为ASCII的BEL字符 |
\b | 后退 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\\ | 表示\本身 |
[root@bogon ~]# echo -e "\ta"
a
[root@bogon ~]# echo -e "\ra"
a
四、Shell脚本语言的运算
4.1 算数运算
shell支持算术运算,但只支持整数,不支持小数
4.2 Bash中的算术运算
-- + 加法运算 -- - 减法运算 -- * 乘法运算 -- / 除法运算 -- % 取模,即取余数 -- ** 乘方 #乘法符号在有些场景需要转义
4.2 实现算术运算
1. let var=算术表达式 2. var=$[算术表达式] 3. var=$((算术表达式)) 4. var=$(expr arg1 arg2 arg3 ...) 5. declare -i var = 数值 6. echo '算术表达式' | bc (支持浮点数)
实例:使用bc计算小数和declare -i计算
[root@localhost ~]# echo "scale=3;20/3"|bc
6.666
[root@localhost ~]# echo "scale=3;2/3"|bc
.666
[root@localhost ~]# i=20
[root@localhost ~]# j=20
[root@localhost ~]# declare -i sum=i*j
[root@localhost ~]# echo $sum
400
[root@localhost ~]#
内建的随机数生成器变量:
$RANDOM 取值范围:0-32767
实例:生成0-49之间的随机数
[root@localhost ~]# echo $[$[$RANDOM%50]+1] 40 [root@localhost ~]# echo $[$RANDOM%50] 44 [root@localhost ~]# echo $[$[$RANDOM%50]+1] #生成1~50之间的随机数 [root@localhost ~]# echo $[RANDOM % 100 + 1]
实例:生成随机颜色字符串
[root@localhost ~]# echo -e "\033[1;$[RANDOM%7+31]mStudy\033[0m"
Study
[root@localhost ~]# echo -e "\033[1;$[RANDOM%7+31]mStudy\033[0m"
Study
[root@localhost ~]#
4.3 增强型赋值:
+=
i+=10 <==> i=1+10
-=
i-=j <==> i=i-j
*=
/=
%=
++
i++,++1 <==> i=i+1 (自增)
--
i--,--i <==> i=i-1 (自减)
格式:
let varOPERvalue
实例:自增,自减
[root@localhost ~]# let var+=1
[root@localhost ~]# echo $var
1
[root@localhost ~]# let var++
[root@localhost ~]# echo $var
2
[root@localhost ~]# let var-=1
[root@localhost ~]# echo $var
1
[root@localhost ~]# let var--
[root@localhost ~]# echo $var
0
[root@localhost ~]#
[root@localhost ~]# unset i j ;i=1;let j=i++;echo "i=$i,j=$j"
i=2,j=1
[root@localhost ~]# unset i j ;i=1;let j=++i;echo "i=$i,j=$j"
i=2,j=2
[root@localhost ~]#
# i++ 与 ++i的区别:
i++ 先赋值再运算
++i 先运算再赋值
实例:鸡兔同笼问题
[root@localhost ~]# vim shell/chicken.sh
#!/bin/bash
HEAD=35
FOOT=94
RABBIT=$(((FOOT-HEAD-HEAD)/2))
CHOOK=$[35-RABBIT]
echo "兔子的数量为:"$RABBIT
echo "鸡的数量为: "$CHOOK
[root@localhost ~]# chmod +x shell/chicken.sh
[root@localhost ~]# shell/chicken.sh
兔子的数量为::12
鸡的数量为:23
[root@localhost ~]#
# 在脚本中写入变量,让用户在命令行写入需要计算的数值
[root@localhost ~]# vim shell/chicken.sh
#!/bin/bash
HEAD=$1
FOOT=$2
RABBIT=$(((FOOT-HEAD-HEAD)/2))
CHOOK=$[35-RABBIT]
echo "兔子的数量为:"$RABBIT
echo "鸡的数量为: "$CHOOK
[root@ansible-salve1 ~]# ./chicken.sh 30 80
兔子的数量为:10
鸡的数量为: 25
[root@ansible-salve1 ~]#
面试题:计算出所有人的年龄总和
[root@ansible-salve1 shell]# vim nianling.txt
[root@ansible-salve1 shell]# cat nianling.txt
a=20
b=18
c=22
[root@ansible-salve1 shell]# cut -d"=" -f2 nianling.txt
20
18
22
[root@ansible-salve1 shell]# cut -d"=" -f2 nianling.txt | tr '\n' + | grep -Eo ".*[0-9]" | bc
60
[root@ansible-salve1 shell]# grep -Eo "[0-9]+" nianling.txt
20
18
22
[root@ansible-salve1 shell]# grep -Eo "[0-9]+" nianling.txt | tr '\n' +| grep -Eo ".*[0-9]" | bc
60
[root@ansible-salve1 shell]#
4.2 逻辑运算(了解,不用掌握)
True用数字表示1,False用数字表示0
-
与:&
1 与 1 = 1 1 与 0 = 0 0 与 1 = 0 0 与 0 = 0
-
或:|
1 或 1 = 1 1 或 0 = 1 0 或 1 = 1 0 或 0 = 0
-
非:!
!1 = 0 !True=False !0 = 1 !False=True
-
异或:^
#异或的两个值,相同为假,不同为真 1 ^ 1 =0 1 ^ 0 =1 0 ^ 1 =1 0 ^ 0 =0
4.3 短路运算(不用了解)
-
短路与
CMD1 短路与 CMD2 --第一个CMD1结果为0(假),总的结果必定为0,所以不需要执行CMD2 --第二个CMD1结果为1(真),第二个CMD2必须要参与计算,才能得到最终的结果
-
短路或
CMD1 短路或 CMD2 --第一个CMD1结果为1(真),总的结果必定为1,因此不需要执行CMD2 --第一个CMD1结果为0(假),第二个CMD2必须要参与运算,才能得到最终的结果
4.4 条件测试命令
条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测试过程,实现评估布尔声明,以便在条件性环境下进行执行。
-
若真,则状态码变量$?返回0
-
若假,则状态码变量$?返回1
扩展: [ ] 与 [[ ]] 的区别
`区别1: [ ]是符合POSIX标准的测试语句,兼容性强,几乎可以运行在所有的Shell解释器中 [[ ]]仅可运行在特定的几个Shell解释器中(如Bash) `区别2: > < 可以在[[ ]]中使用ASCII码顺序进行排序,而[]不支持 `区别3: 在[ ]中使用-a 和 -o 表示逻辑与和逻辑或,[[ ]]使用&& 和 || 表示,[[ ]]不支持-a `区别4: 在[ ]中==是字符匹配,在[[ ]]中==是模式匹配 `区别5: [ ]不支持正则匹配,[[ ]]支持用=~进行正则匹配 `区别6: [ ]仅在部分Shell中支持用()进行分组,[[ ]]均支持 `区别7: 在[ ]中如果变量没有定义,那么需要用双引号引起来,在[[ ]]中不需要
4.4.1 条件测试命令及其语法
语法1:test <测试表达式> 说明:test命令和<测试表达式>之间至少有一个空格
# 在shell中,大于用 -gt 表示,小于用 -lt 表示,大于或等于用 -ge 表示,小于或等于用 -le表示 ,不相等用-ne 表示
[root@ansible-salve1 ~]# test 1 -lt 2
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]# test 2 -lt 1
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]#
语法2:[<测试表达式>] 说明:该方法和test命令的用法一样,[]的两边和内容之间至少有一个空格
[root@ansible-salve1 ~]# [ 1 -gt 3 ]
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# [ 1 -lt 3 ]
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]#
语法3:[[ <测试表达式> ]] 说明:比test和[]更新的语法格式。[[]]的边界和内容之间至少有一个空格。[[]]中可以使用通配符等进行模式匹配
[root@ansible-salve1 ~]# [[ 1 > 3 ]]
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# [[ 1 < 3 ]]
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]#
语法4:((<测试表达式>)) 说明:一般用于if语句里,双小括号两端不需要有空格,测试对象只能是整数
[root@ansible-salve1 ~]# ((1>2))
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# ((1<2))
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]#
4.4.2 变量测试
语法规则:-v VAR 变量var是否被定义
示例:判断NAME变量是否被定义
[root@ansible-salve1 shell]# [[ -v NAME ]]
[root@ansible-salve1 shell]# echo $?
1
[root@ansible-salve1 shell]# NAME=1
[root@ansible-salve1 shell]# [[ -v NAME ]]
[root@ansible-salve1 shell]# echo $?
0
[root@ansible-salve1 shell]#
语法规则: -R VAR 变量VAR是否被引用
示例:判断NAME变量是否被引用
[root@ansible-salve1 shell]# NAME=10
[root@ansible-salve1 shell]# test -v NAME
[root@ansible-salve1 shell]# echo $?
0
[root@ansible-salve1 shell]# test -R NAME
[root@ansible-salve1 shell]# echo $?
1
[root@ansible-salve1 shell]#
4.4.3 文件测试表达式
常用的文件测试操作符 | 说明 |
---|---|
-a/-e 文件 | 文件是否存在 |
-b 文件 | 文件是否存在,且为块文件,如果文件存在且是一个块文件,则结果为0 |
-c 文件 | 文件是否存在且为字符文件,如果文件存在且是一个字符文件,则结果为0 |
-L 文件 或 -h 文件 | 文件存在且为链接文件则为真 |
-d 文件 | 文件存在且为目录则为真,即测试表达式成立 |
-f 文件 | 文件存在且为普通文件则为真,即测试表达式成立 |
-s 文件 | 文件存在且文件大小不为0则为真 |
-S 文件 | 文件是否存在且为套接字文件 |
-p 文件 | 文件是否存在且为管道文件 |
-u 文件 | 文件是否存在且拥有suid的权限,如果设置了suid,则结果为0 |
-g 文件 | 文件是否存在且拥有sgid的权限 |
-r 文件 | 文件存在且可读为真 |
-w 文件 | 文件存在且可写为真 |
-x 文件 | 文件存在且可执行则为真 |
-t fd | fd 文件描述符是否在某终端已经被打开 |
-N 文件 | 文件自从上一次读取之后是否被修改过 |
-O 文件 | 当前有效用户是否为文件属主 |
-G 文件 | 当前有效用户是否为文件属组 |
f1 -ef f2 | 文件f1是否是文件f2的硬链接 |
f1 -nt f2,nt为newerthan | 文件f1比文件f2新则为真,根据文件的修改时间来计算 |
f1 -ot f2,ot为olderthan | 文件f1比文件f2旧则为真,根据文件的修改时间来计算 |
测试文件
[root@ansible-salve1 ~]# test -a test.txt
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# test -d shell
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]# touch test.txt
[root@ansible-salve1 ~]# test -w test.txt &&echo "true"
true
[root@ansible-salve1 ~]# test -r test.txt &&echo "true"
true
[root@ansible-salve1 ~]# test -x test.txt &&echo "true"
[root@ansible-salve1 ~]#
4.4.4 字符串测试表达式
常用字符串测试操作符 | 说明 |
---|---|
-n ”字符串“ | 若字符串的长度不为0,则为真,即测试表达式成立,n可以理解为nozero |
-z ”字符串“ | 若字符串的长度为0,则为真,z可以理解为zero |
> | Ascii码是否大于Ascii码 |
“字符串1” == ”字符串2“ | 若字符串1长度等于字符串2长度,则为真 |
“字符串1” != ”字符串2“ | 若字符串1长度不等于字符串2长度,则为真 |
“字符串1” =~ “字符串2” | 左侧字符串是否能被右侧的PATTERN所匹配。注意:此表达式用于[[ ]]中:扩展的正则表达式 |
测试字符串
[root@ansible-salve1 ~]# var_test='Mike'
[root@ansible-salve1 ~]# echo $var_test
Mike
[root@ansible-salve1 ~]# [[ -n var_test ]] && echo "True"
True
# 通配符
[root@ansible-salve1 ~]# FILE=test.log
[root@ansible-salve1 ~]# [[ "$FILE" == *.log ]] && echo "True"
True
[root@ansible-salve1 ~]# FILE=test.txt
[root@ansible-salve1 ~]# [[ "$FILE" == *.log ]] && echo "True"
[root@ansible-salve1 ~]# [[ "$FILE" != *.log ]] && echo "True"
True
[root@ansible-salve1 ~]#
# 扩展的正则表达式
[root@ansible-salve1 ~]# FILE=test.log
[root@ansible-salve1 ~]# [[ "$FILE" =~ \.log$ ]] && echo "True"
True
[root@ansible-salve1 ~]# N=100
[root@ansible-salve1 ~]# [[ "$N" =~ ^[0-9]+$ ]] && echo "True"
True
[root@ansible-salve1 ~]# N=A10
[root@ansible-salve1 ~]# [[ "$N" =~ ^[0-9]+$ ]] && echo "True"
[root@ansible-salve1 ~]# IP=1.2.3.4
[root@ansible-salve1 ~]# [[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]#
# 使用正则表达式判断IP是否合法
[root@ansible-salve1 ~]# IP=1.2.3.333
[root@ansible-salve1 ~]# [[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# IP=255.255.255.255
[root@ansible-salve1 ~]# [[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]#
4.4.5 整数测试表达式
在[ ] 或 test中使用的比较符号 | 在(()) 或 [[ ]]中使用的比较符号(不用这个做数字比较) | 说明 |
---|---|---|
-eq | \== 或 = | 相等,equal |
-ne | != | 不相等,not equal |
-gt | > | 大于,greater than |
-ge | > = | 大于等于,greater equal |
-lt | < | 小于,less than |
-le | < = | 小于等于,less equal |
4.4.6 逻辑操作符
在[ ] 中使用的操作符 | 在test, [[ ]] , (( ))中使用的逻辑操作符 | 说明 |
---|---|---|
-a | && | and,与,两边都为真,则结果为真 |
-o | || | or,或,有真则真,同假则假 |
! | ! | not,非,两端相反,则结果相反 |
示例
[root@ansible-salve1 ~]# var_test=1
[root@ansible-salve1 ~]# var_t=2
[root@ansible-salve1 ~]# [ $var_test -lt 0 -a $var_t -gt 0 ]
[root@ansible-salve1 ~]# echo $?
1
[root@ansible-salve1 ~]# [ $var_test -lt 0 -o $var_t -gt 0 ]
[root@ansible-salve1 ~]# echo $?
0
[root@ansible-salve1 ~]#
4.5 关于()与 { }
( )和 { }都可以将多个命令组合再一次,批量执行,{ } 里的内容需要与两侧用空格隔开并在命令结尾加上;
-
( )会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境
[root@ansible-salve1 ~]# name=mage;(echo $name;name=wang;echo $name);echo $name mage wang mage [root@ansible-salve1 ~]# (umask 444;umask);umask 0444 0022 [root@ansible-salve1 ~]# (umask 444;touch 1.txt);umask 0022 [root@ansible-salve1 ~]# ll 1.txt --w--w--w- 1 root root 0 11月 13 22:12 1.txt [root@ansible-salve1 ~]# echo $BASHPID 1418 [root@ansible-salve1 ~]# (echo $BASHPID; sleep 3) 1501
-
{ } 不会开启子shell,在当前shell中运行,会影响当前shell环境
[root@ansible-salve1 ~]# name=mage;{ echo $name;name=wang;echo $name; };echo $name mage wang wang [root@ansible-salve1 ~]# { umask 444;touch 1.txt; };umask 0444 [root@ansible-salve1 ~]# umask 0444 [root@ansible-salve1 ~]# ll 1.txt --w--w--w- 1 root root 0 11月 13 22:23 1.txt [root@ansible-salve1 ~]# echo $BASHPID 1418 [root@ansible-salve1 ~]# { echo $BASHPID; sleep 3; } 1418 [root@ansible-salve1 ~]#