续Shell脚本学习 Day2

字符串截取补充

通配符

1
a*z

表示匹配a开头,中间任意字符,z结尾

使用案例

1
2
3
4
#   从头匹配最短字符串
## 从头匹配最长字符串
% 从尾匹配最短字符串
%% 从尾匹配最长字符串

字符串截取应用案例

删除文件名

首先,我们创建一些演示文件

1
2
3
4
mkdir sub_str #创建一个sub_str目录
cd sub_str #切换到目录下
touch demo_{1..3}_finished.jpg #生成三个.jpg文件
touch demo_{1..3}_finished.png #生成三个.png文件

要求:去除文件中的_finished字符

法一 一个一个直接修改

1
2
3
mv demo_1_finished.jpg demo_1.jpg
mv demo_2_finished.jpg demo_2.jpg
...

法二 使用字符串替换

1
2
3
f=demo_1_finished.jpg
mv demo_1_finished.jpg ${f//_finished/} #不使用反引号
mv $f `echo ${f//_finished/}`#使用反引号

法三 使用字符串替换批量处理

1
2
for file_name in `ls *fin*jpg *fin*png `;do mv $file_name ${file_name//_finished/};done #不使用反引号
for file_name in `ls *fin*jpg *fin*png `;do mv $file_name `echo ${file_name//_finished/}`;done #使用反引号

这里用到了通配符的知识

特殊shell拓展变量

  1. 当变量值为返回word字符串;当变量值非空,返回变量值
1
${vars:-word}

演示

1
2
3
4
5
6
[root@localhost sub_str]# vars=
[root@localhost sub_str]# result=${vars:-word}
[root@localhost sub_str]# echo $result
word
[root@localhost sub_str]# echo $vars
#null
  1. 当变量值为返回word字符串,将word赋值给变量;当变量值非空,返回变量值
1
${vars:=word}

演示

1
2
3
4
5
6
7
[root@localhost sub_str]# vars=		#未赋值
[root@localhost sub_str]# echo $vars
#null
[root@localhost sub_str]# echo ${vars:=word}
word
[root@localhost sub_str]# echo $vars
word
  1. 当变量为返回word字符串作为stderr;当变量值非空,返回变量值
1
${vars:?word}

演示

1
2
3
[root@localhost sub_str]# vars=
[root@localhost sub_str]# echo ${vars:?"vars是空值!!"}
-bash: vars: vars是空值!!
  1. 当变量为什么也不做;当变量非空返回word字符串
1
${vars:+word}

演示

1
2
3
4
5
6
[root@localhost sub_str]# vars=			#空
[root@localhost sub_str]# echo $vars #空
#null
[root@localhost sub_str]# vars=123 #非空
[root@localhost sub_str]# echo ${vars:+word}
word #非空

实际应用

数据备份、删除过期数据的脚本

使用的命令及参数:

1
2
3
4
5
6
find xargs 搜索且删除
find <需要搜索的目录>
-name <要搜索的文件名>
-type <文件类型>
-mtime +7 #超过七天的
xargs rm -f #交给xargs处理

del_data.sh脚本,删除某个数据文件夹的备份文件,文件日期超过7天

1
find ${dir_path:=/xxx/xxx/} -name '*.tar.gz' -type f -mtime +7|xargs rm -f

解释

1
2
使用:=是为了解决dir_path不存在时的情况
若不存在,则会在当前目录下进行搜索删除!

父子shell拓展

父shell概念

pstree看到如下结果,就是父shell环境

1
2
pstree
├─sshd───sshd───bash───pstree

ps进程管理命令

1
2
3
4
ps -ef
#-f 显示UID、PID、PPID
#-e 列出所有进程信息,如同-A选项
#--forest

创建进程列表(创建子shell)

在小括号中使用命令

实例

1
2
3
4
5
6
7
[root@localhost ~]# ls;cd /tmp/;pwd
anaconda-ks.cfg blog hello.sh node-v16.16.0-linux-x64 sub_str
/tmp
###
[root@localhost ~]# (ls;cd /tmp/;pwd)
anaconda-ks.cfg blog hello.sh node-v16.16.0-linux-x64 sub_str
/tmp

通过观察发现,使用不使用小括号和使用小括号执行的结果并没有区别,那么创建进程列表有什么作用呢?

↓↓↓请结合下一节↓↓↓

检测是否在子shell环境中

Linux默认有关shell的变量BASH_SUBSHELL

1
BASH_SUBSHELL

取值

  • 0 表示当前处于父shell
  • 1 表示当前处于子shell1,可以理解成儿子
  • 2 表示当前处于子shell2,可以理解成孙子
  • 以此类推

注意理解,这里所指的父shell、子shell是相对的!!

举个例子说明

首先创建若干个子shell

1
2
3
4
5
bash
bash
bash
sh
ps -ef f #查看进程树
1
2
3
4
5
6
root       1565   1038  0 21:22 ?        Ss     0:00  \_ sshd: root@pts/0
root 1572 1565 0 21:22 pts/0 Ss 0:00 | \_ -bash
root 1806 1572 0 22:42 pts/0 S 0:00 | \_ bash
root 1817 1806 0 22:42 pts/0 S 0:00 | \_ bash
root 1828 1817 0 22:42 pts/0 S 0:00 | \_ sh
root 1832 1828 0 22:42 pts/0 R+ 0:00 | \_ ps

观察进程树结果,按照我们的理解,按理来说目前处在sh子shell中,变量BASH_SUBSHELL的值应该为非0,即处在子shell中,但是我们在sh中打印变量值发现

1
2
sh-4.2# echo $BASH_SUBSHELL
0

结果表明,当前我们处在父shell

!!!!what the hell??

这就是所谓相对当前环境来说的含义把!有点难理解。

如何让BASH_SUBSHELL取值为非0?

这时候就用到上面“创建进程列表”的知识了!

1
(ls;cd /tmp/;pwd;echo $BASH_SUBSHELL)
1
2
3
4
sh-4.2# (ls;cd /tmp/;pwd;echo $BASH_SUBSHELL)
anaconda-ks.cfg blog hello.sh node-v16.16.0-linux-x64 sub_str
/tmp
1

我们可以通过小括号的嵌套实现多个子shell

1
2
3
4
5
6
7
8
9
sh-4.2# (ls;cd /tmp/;pwd;(echo $BASH_SUBSHELL))
anaconda-ks.cfg blog hello.sh node-v16.16.0-linux-x64 sub_str
/tmp
2
################
sh-4.2# (ls;(cd /tmp/;pwd;(echo $BASH_SUBSHELL)))
anaconda-ks.cfg blog hello.sh node-v16.16.0-linux-x64 sub_str
/tmp
3

内置命令、外置命令

内置命令:系统启动时就载入内存,常驻内存,执行效率高,但占用资源

外置命令:系统从硬盘中读取程序文件,再读入内存加载

使用type命令可以查询命令类型

1
type <命令>

使用实例

1
2
3
4
5
6
[root@localhost ~]# which cd
/usr/bin/cd
[root@localhost ~]# type ls
ls 是 `ls --color=auto' 的别名
[root@localhost ~]# type pwd
pwd 是 shell 内嵌

外置命令

外置命令,一般为自己下载的文件系统命令,处于bash shell之外的程序

特点:一定会开启子进程进行执行

一般外置命令所处目录为

1
2
3
4
/bin
/usr/bin
/sbin
/usr/sbin

注意:并非说内置命令不能存储在以上目录中(使用which <命令> 进行查询命令所处文件夹)

如:cd命令也存储在/usr/bin/目录下

1
2
3
4
[root@localhost ~]# type cd
cd 是 shell 内嵌
[root@localhost ~]# which cd
/usr/bin/cd

内置命令

内置命令与shell是一体的

特点:内置命令不会产生子进程进行执行

查询所有内置命令的命令

1
compgen -b

复习与内容补充

如何在脚本中输出“$”不被识别?

法一

使用转义符“\”

实例

1
2
3
4
[root@localhost ~]# echo "$123"
23
[root@localhost ~]# echo "\$123"
$123

法二

使用单引号

实例

1
2
[root@localhost ~]# echo '$123'
$123

shell特性

能够在命令执行结果中,再次提出结果

shell特殊符号区分

1
2
3
4
5
${vars}	#获取vars值
$(vars) #执行括号中的命令,并输出结果值
() #开启子shell执行命令,并输出结果值
`` #执行反引号中的结果,并输出结果值
$vars #获取vars值

Shell 算术运算符一览表

算术运算符 说明/含义
+、- 加法(或正号)、减法(或负号)
*、/、% 乘法、除法、取余(取模)
** 幂运算
++、– 自增和自减,可以放在变量的前面也可以放在变量的后面
!、&&、|| 逻辑非(取反)、逻辑与(and)、逻辑或(or)
<、<=、>、>= 比较符号(小于、小于等于、大于、大于等于)
==、!=、= 比较符号(相等、不相等;对于字符串,= 也可以表示相当于)
<<、>> 向左移位、向右移位
~、|、 &、^ 按位取反、按位或、按位与、按位异或
=、+=、-=、*=、/=、%= 赋值运算符,例如 a+=1 相当于 a=a+1,a-=1 相当于 a=a-1

shell数值计算

Shell 和其他编程语言一样,支持多种运算符,包括:

  • 算数运算符
  • 关系运算符
  • 布尔运算符
  • 字符串运算符
  • 文件测试运算符

原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。

shell的一些基础命令,只支持整数运算,小数的计算需要如bc的命令才能支持!

expr 是一款表达式计算工具,使用它能完成表达式的求值操作。

例如,两个数相加(注意使用的是反引号 *`* 而不是单引号 *‘*):

算术运算符

实现数值运算的方法

双小括号操作

运算操作符/运算命令 说明
((a=10+66))((b=a-15)) ((c=a+b)) 这种写法可以在计算完成后给变量赋值。以 ((b=a-15)) 为例,即将 a-15 的运算结果赋值给变量 c。 注意,使用变量时不用加$前缀,(( )) 会自动解析变量名。
a=$((10+66) b=$((a-15)) c=$((a+b)) 可以在 (( )) 前面加上$符号获取 (( )) 命令的执行结果,也即获取整个表达式的值。以 c=$((a+b)) 为例,即将 a+b 这个表达式的运算结果赋值给变量 c。 注意,类似 c=((a+b)) 这样的写法是错误的,不加$就不能取得表达式的结果。
((a>7 && b==c)) (( )) 也可以进行逻辑运算,在 if 语句中常会使用逻辑运算。
echo $((a+10)) 需要立即输出表达式的运算结果时,可以在 (( )) 前面加$符号。
((a=3+5, b=a+10)) 对多个表达式同时进行计算。

expr 命令操作

下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明 举例
+ 加法 expr $a + $b 结果为 30。
- 减法 expr $a - $b 结果为 -10。
* 乘法 expr $a \* $b 结果为 200。
/ 除法 expr $b / $a 结果为 2。
% 取余 expr $b % $a 结果为 0。
= 赋值 a=$b 把变量 b 的值赋给 a。
== 相等。用于比较两个数字,相同则返回 true。 [ $a == $b ] 返回 false。
!= 不相等。用于比较两个数字,不相同则返回 true。 [ $a != $b ] 返回 true。

注意:条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 **[ $a == $b ]**。

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明 举例
-eq 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。
-ne 检测两个数是否不相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
-lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false。
-le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true。

布尔运算符

下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明 举例
! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true。
-o 或运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 与运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false。

逻辑运算符

以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明 举例
&& 逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
|| 逻辑的 OR [[ $a -lt 100 || $b -gt 100 ]] 返回 true

字符串运算符

下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”:

运算符 说明 举例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否不相等,不相等返回 true。 [ $a != $b ] 返回 true。
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否不为 0,不为 0 返回 true。 [ -n “$a” ] 返回 true。
$ 检测字符串是否不为空,不为空返回 true。 [ $a ] 返回 true。

文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。

属性检测描述如下:

操作符 说明 举例
-b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false。
-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false。
-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false。
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true。
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ] 返回 false。
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ] 返回 false。
-p file 检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ] 返回 false。
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ] 返回 false。
-r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true。
-w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true。
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true。

其他检查符:

  • -S: 判断某文件是否 socket。
  • -L: 检测文件是否存在并且是一个符号链接。