shell 是一个用 C 编写的程序,是用户与系统内核之间的桥梁。其提供了一个界面,用户通过该界面访问操作系统的内核服务。Ken Thompson 的 sh 是第一种 Unix Shell,Windows Explorer 是一个典型的图形界面 Shell。其实,shell 也是一种解释性的程序设计语言,用户通过编写 shell 脚本在 Linux 上进行自动运维等。常见的 Linux shell 种类很多,如 Bourne Shell(/usr/bin/sh或/bin/sh)、Bourne Again Shell(/bin/bash)、C Shell(/usr/bin/csh)、K Shell(/usr/bin/ksh)、Shell for Root(/sbin/sh)等,国内常用的是 /bin/bash,也是大多数 Linux 系统默认的 shell。在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash.

脚本格式

shell 脚本一般需要

  1. #!/bin/bash 开头,#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序;
  2. 脚本命名常以 .sh 结尾;
  3. 脚本需要可执行权限.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 利用 vim 创建脚本
vim hello.sh

# 添加脚本内容
#!/bin/bash
echo "hello world" # 打印输出 hello world

# 增加可执行权限
chmod u+x hello.sh

# 执行脚本
./hello.sh # 相对路径执行
/home/jinzhongxu/hello.sh # 绝对路径执行
bash hello.sh # 如果没有增加可执行权限,可用该方法执行
source hello.sh
bash < hello.sh

#! /bin/bash#! /usr/bin/env bash (或者 #! /usr/local/miniconda/bin/python#! /usr/bin/env python)称为 shebang,是告知操作系统按照指定的解释器执行脚本程序。前一种是指定解释器绝对路径(/bin/bash/usr/local/miniconda/bin/python),后一种是灵活的指定解释器路径,可以跨系统使用(/usr/bin/env 是当前系统环境变量)。

执行脚本程序的方法:

  1. /bin/bash script.sh, bash script.sh, sh script.sh。这种方式是推荐的。当脚本未赋予执行权限且没有设置shebang时,都可以执行成功;
  2. /codes/script.sh(绝对路径), ./script.sh(相对路径)。需要脚本具有执行权限,可以不设置 shebang;
  3. source script.sh, . script.sh。不需要脚本具有执行权限,可以不设置 shebang;
  4. bash < script.sh, sh < script.sh 很少用。不需要脚本具有执行权限,可以不设置 shebang。

推荐:设置 shebang: #! /usr/bin/env bash(python 建议使用解释器绝对路径设置 shebang,因为不同环境有不用的依赖包),同时赋予脚本执行权限: sudo chmod +x script.sh,执行命令:/bin/bash /codes/script.sh

对于 python,如果给脚本增加了 shebang,且赋予脚本可执行权限,可以使用绝对路径执行,前面可以不用再写 python 解释器:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 利用 vim 创建脚本
vim /codes/script.py

# 添加脚本内容,注意设置 shebang
#! /usr/local/miniconda/bin/python

print("hello world") # 打印输出 hello world

# 增加可执行权限
chmod u+x /codes/script.py

# 直接使用绝对路径执行 python 脚本
/codes/script.py

shell 变量

shell 变量分为系统变量和用户自定义变量。

系统变量

系统变量常见的有:$HOME,$PWD,$SHELL,$USER 等等,查看某个系统变量值可使用命令echo $HOME 完成,当想查看所有系统变量时可使用命令:set 完成。

用户自定义变量

变量名称一般习惯为大写,不过小写也能通过。建议大写,遵守规范。

变量名的命名须遵循如下规则:

  • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头
  • 中间不能有空格,可以使用下划线 _
  • 不能使用标点符号
  • 不能使用bash里的关键字(可用help命令查看保留关键字)

注意,变量赋值等号两端不要有空格。用 $ 获取变量值。

自定义变量常使用如下方法:

1
2
3
4
# 变量=值
A=3
echo A=$A
echo "A=$A"

撤销变量,撤销后无法使用该变量:

1
2
# unset 变量
unset A

声明静态变量,这种变量不能被 unset

1
2
3
# readonly 变量
readonly B=2
echo "B=$B"

注意:echo 后面如果跟双引号,能够正常解析出变量值;但是,如果跟单引号,则只会作为字符串处理返回。比如:

1
2
3
# 使用双引号
readonly B=2
echo "B=$B"

结果如下:

1
B=2

但使用单引号时:

1
2
3
# 使用单引号
readonly B=2
echo "B=$B"

结果如下:

1
B=$B

将命令返回值赋值给变量

1
2
A=`date`
A=$(date)

预定义变量

预定义变量指 shell 设计者预先定义好的变量,可以直接在 shell 脚本中使用

  1. $$ (功能:当前进程的进程号 PID);
  2. $! (功能:后台运行的最后一个进程的进程号 PID);
  3. $? (功能:最后依次执行的命令的返回状态,如果为 0,表示正确执行;如果非 0(具体值有命令决定),表示执行错误);
  4. 一些获取位置参数的变量,下面介绍。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 创建脚本
vim predefine_test.sh

# 添加如下内容
#!/bin/bash

echo "当前程序进程ID=$$"

# 后台执行最后一个程序,获取进程号
date &
echo "最后一个后台方式运行的程序进程ID=$!"
echo "最后一个后台方式运行的程序结果(如果是0表示执行正确,否则错误)=$?"

# 执行
bash predefine_test.sh

# 结果
当前程序进程ID=43085
最后一个后台方式运行的程序进程ID=43086
最后一个后台方式运行的程序结果(如果是0表示执行正确,否则错误)=0
2022年 04月 18日 星期一 13:38:49 CST

设置环境变量

基本语法

  1. export 变量=值 (功能:将 shell 变量输出为环境变量,直接在命令行运行,则直接生效但退出当前 shell 时失效。可以写入到配置文件 ~/.bashrc/etc/profile 中);
  2. source 配置文件 (功能:让配置文件立即生效);
  3. echo $变量 (功能:查看环境变量是否失效)。

shell 注释

单号注释,在当前行开头添加 #

1
# A=3

多行注释,方法如下:

1
2
3
4
:<<!
A=3
B=2
!

把需要注释的内容放在 :<<!! 之间,建议注释符分别占一行。

位置参数

当我们执行一个 shell 脚本时,如果希望获取到命令行的参数信息,需要位置参数,如下

1
2
# 将位置参数 100 和 99 供 hello.sh 使用
./hello.sh 100 99

脚本中如何使用位置参数:

  1. $n (功能:n 为自然数,$0 表示命令本身,如 ./hello.sh$1 表示第 1 个参数,如 100,依次类推,对于超过 9 个的位置参数,需增加大括号,如 ${10};
  2. $* (功能:表示命令行中所有的参数,把所有参数看成一个整体,解译为 “$1c$2c$3c$4“,其中 c 为分隔字符,默
    认为空白键,所以为 “$1 $2 $3“);
  3. $@ (功能:表示命令行中所有的参数,不过不是看作一个整体,而是一个”数组”,可以按顺序读取,解译为 “$1“ “$2“ “$3“,每个变量是独立的(用双引号括起来),一般使用中建议采用该方式);
  4. $# (功能:表示命令行中所有参数的个数)。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 编写脚本
vim arg_test.sh

# 添加如下内容
#!/bin/bash
# \$ 表示转义$
echo "\$0=$0"
echo "\$1=$1"
echo "\$*=$*"
echo "\$@=$@"
echo "\$#=$#"

echo "==========="
for i in $@
do
echo "$i"
done

# 执行
bash arg_test.sh 100 99

# 结果
$0=arg_test.sh
$1=100
$*=100 99
$@=100 99
$#=2
===========
100
99

序列

1
2
3
4
for i in ${seq 5}
do
echo $i
done

结果为

1
2
3
4
5
1
2
3
4
5

运算式

像其他语言一样,shell 中也可以进行运算操作,方法如下:

  1. $[运算式] # 注意中括号和运算式之间可有一个空格,也可不要;
  2. $(( 运算式 )) # 注意内层小括号和运算式之间可有一个空格,也可不要;
  3. expr 运算式 # expr 与运算式之间有一个空格,运算式中运算符与运算数之间有一个空格。常见运算有:expr \*, /, % 分别表示乘,除,取余;
  4. 如果想将运算结果赋值给变量,需添加 ``,注意使用的是反引号 ` 而不是单引号 ‘,如 A=$[ 2 + 3 ] 或 A=`expr 2 + 3`

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 编写脚本
vim operator_test.sh

# 添加如下内容
#!/bin/bash

A=$(( 2+3 ))
echo "A=$A"
B=$[ 2+3 ]
echo "B=$B"
C=`expr 2 + 3`
echo "C=$C"

# 执行
bash operator_test.sh

# 结果
A=5
B=5
C=5

运算符

运算符包括算术运算符、关系运算符、布尔运算符、字符串运算符和文件测试运算符。

算术运算符

下表列出了常用的算术运算符,假定变量 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: 检测文件是否存在并且是一个符号链接。

条件判断和流程控制

基本语法如下:

1
[ condition ] 

注意,condition 与中括号之间有一个空格;当 condition 为非空时,如 [ A ],返回 true;当为空 [ ] 时,返回 false.

应用实例,如当某个程序执行正确时,执行某子程序,判断为 [ $? ],0 为 true,大于 1 为 false.

if … else …

实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# 创建脚本
vim operator2_test.sh

# 添加如下代码
#!/bin/bash

# 判断两个字符串是否相等
if [ "a" = "b" ]
then
echo "a=b"
elif [ "a" = "c" ]
then
echo "a=c"
else
echo "a既不等于b也不等于c"
fi

# 判断两个整数关系
if [ 3 -ge 2 ]
then
echo "3大于等于2"
fi

# 判断文件是否是常规文件
if [ -f /etc/passwd ]
then
echo "存在文件"
fi

# 判断文件是否是常规文件
FILE="/etc/passwd"
if [ -f $FILE ]
then
echo "存在文件"
fi

# 添加执行权限
chmod u+x operator2_test.sh

# 执行
./operator2_test.sh

# 结果
a既不等于b也不等于c
3大于等于2
存在文件
存在文件

case

基本语法

1
2
3
4
5
6
7
8
9
10
11
case $变量 in
"v1")
如果变量值等于v1,则执行该段程序
;;
"v2")
如果变量值等于v2,则执行该段程序
;;
*)
如果上面的值都不是变量的取值,则执行该段程序
;;
esac

for

基本语法1

1
2
3
4
for 变量 in v1 v2 vn
do
执行该段程序
done

基本语法2

1
2
3
4
for (( 初始值; 循环控制条件; 变量变换))
do
执行该段程序
done

实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 编写脚本
vim for_test.sh

# 添加内容
#!/bin/bash

echo "### $* ###"
for i in "$*"
do
echo "$i"
done

echo "### $* ###"
for i in $*
do
echo "$i"
done

echo "### $@ ###"
for i in "$@"
do
echo "$i"
done

echo "### 计算1到100整数和 ###"
SUM=0
for (( i=1; i<=100; i++ ))
do
SUM=$[ $SUM+$i ]
done
echo "SUM=$SUM"

# 赋予执行权限
chmod u+x for_test.sh

# 执行
./for_test.sh 100 200

# 结果
### 100 200 ###
100 200
### 100 200 ###
100
200
### 100 200 ###
100
200
### 计算1到100整数和 ###
SUM=5050

seq

生成序列,用法如下:

1
2
3
4
5
6
7
8
seq [选项] 尾数
seq [选项] 首数 尾数
seq [选项] 首数 增量 尾数

选项:
-f, --formate=格式,表示输出格式,如 "%.精度f""%.3f" 保留3为精度。默认为 "%g"
-s, --separator=格式,表示输出分隔符,如 "\n",默认为空格
-w, --equal-width,表示输出相同宽度,不足部分前面增加0,如:echo $(seq -w -s "\n" 1 2 19)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(base) ➜  / echo $(seq 2)
1 2
(base) ➜ / echo $(seq -1 2)
-1 0 1 2
(base) ➜ / echo $(seq -1 2 2)
-1 1
(base) ➜ / echo $(seq -f "%.2f" -1 2 2)
-1.00 1.00
(base) ➜ / echo $(seq -f "%.2f" -s "\n" -1 2 2)
-1.00
1.00
(base) ➜ / echo $(seq -w 1 2 19)
01 03 05 07 09 11 13 15 17 19
(base) ➜ / echo `seq 5`
1 2 3 4 5
(base) ➜ / echo {1..5}
1 2 3 4 5

while

基本语法

1
2
3
4
while [ condition ]
do
执行该段程序
done

实例程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 编写脚本
vim while_test.sh

# 添加内容
#!/bin/bash

# 计算1到100所有整数和
SUM=0
i=0
while [ $i -le 100 ]
do
SUM=$[ $SUM + $i ]
i=$[ $i + 1 ]
done

echo "1+...+100=$SUM"

# 添加执行权限
chmod u+x while_test.sh

# 执行
./while_test.sh

# 结果
1+...+100=5050

until 循环

until 循环执行一系列命令直至条件为 true 时停止。

until 循环与 while 循环在处理方式上刚好相反。

一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。

until 语法格式:

condition 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。

1
2
3
4
until condition
do
command
done

实例代码

1
2
3
4
5
6
7
8
9
#!/bin/bash
# 打印 0 ~ 9
a=0

until [ ! $a -lt 10 ]
do
echo $a
a=`expr $a + 1`
done

跳出循环

在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell 使用两个命令来实现该功能:break 和 continue。

break命令

break命令允许跳出所有循环(终止执行后面的所有循环)。

下面的例子中,脚本进入死循环直至用户输入数字大于 5。要跳出这个循环,返回到shell提示符下,需要使用 break 命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
while :
do
echo -n "输入 1 到 5 之间的数字:"
read aNum
case $aNum in
1|2|3|4|5) echo "你输入的数字为 $aNum!"
;;
*) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
break
;;
esac
done

continue

continue 命令与 break 命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。

对上面的例子进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
while :
do
echo -n "输入 1 到 5 之间的数字: "
read aNum
case $aNum in
1|2|3|4|5) echo "你输入的数字为 $aNum!"
;;
*) echo "你输入的数字不是 1 到 5 之间的!"
continue
echo "游戏结束"
;;
esac
done

运行代码发现,当输入大于 5 的数字时,该例中的循环不会结束,语句 echo “游戏结束” 永远不会被执行。

read 读取控制台输入

基本语法

1
2
3
4
5
6
read (选项) (参数)
选项:
-p: 指定读取时的提示符
-t: 指定读取等到时间(单位为秒),超时则直接向下执行
参数:
一般为变量

实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 编写脚本
vim read_test.sh

# 添加内容
#!/bin/bash

read -p "请输入一个整数:" NUM
echo "输入的整数是 $NUM"

read -p "请输入一个整数(只等待5秒钟):" -t 5 NUM2
echo "输入的整数是 $NUM2"

# 添加执行权限
chmod u+x read_test.sh

# 执行
./read_test.sh

# 结果
请输入一个整数:2
输入的整数是 2
请输入一个整数(只等待5秒钟):3
输入的整数是 3

函数

shell 中有自带的系统函数,用户也可以自定义函数。

系统函数

系统函数有很多,比如 basename, dirname 等

1
2
3
4
5
6
7
8
9
10
# basename [文件路径] [扩展名]
# 返回文件名
basename /home/jinzhongxu/test.sh .sh
# 返回结果
test

# 不指定扩展名,则返回带扩展名的文件名
basename /home/jinzhongxu/test.sh
# 返回结果
test.sh
1
2
3
4
5
# dirname [文件路径]
# 返回文件路径
dirname /home/jinzhongxu/test.sh
# 返回结果
/home/jinzhongxu

自定义函数

基本语法

1
2
3
4
5
6
7
8
[ function ] funcname [()]
{
action;
[return int;]
}

# 调用
funcname [值]

实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 编写脚本
vim func_test.sh

# 添加内容
#!/bin/bash

function add() {
SUM=$[$1+$2]
echo "$1+$2=$SUM"
}

read -p "请输入一个数:" n1
read -p "请输入另一个数:" n2

add $n1 $n2

# 添加执行权限
chmod u+x func_test.sh

# 执行
./func_test.sh

# 结果
请输入一个数:2
请输入另一个数:3
2+3=5

数组

数组中可以存放多个值。Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小(与 PHP 类似)。

与大部分编程语言类似,数组元素的下标由 0 开始。

Shell 数组用括号来表示,元素用”空格”符号分割开,语法格式如下:

1
array_name=(value1 value2 ... valuen)

实例

1
2
3
#!/bin/bash

my_array=(A B "C" D)

我们也可以使用下标来定义数组:

1
2
3
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2

读取数组元素

读取数组元素值的一般格式是:

1
${array_name[index]}

实例

1
2
3
4
5
6
7
8
#!/bin/bash

my_array=(A B "C" D)

echo "第一个元素为: ${my_array[0]}"
echo "第二个元素为: ${my_array[1]}"
echo "第三个元素为: ${my_array[2]}"
echo "第四个元素为: ${my_array[3]}"

获取数组长度

1
2
3
4
5
6
7
8
9
#!/bin/bash

my_array[0]=A
my_array[1]=B
my_array[2]=C
my_array[3]=D

echo "数组元素个数为: ${#my_array[*]}"
echo "数组元素个数为: ${#my_array[@]}"

字符串操作

shell 中有一些字符串操作方法,速度比较快

字符串截取

  1. 获取字符串长度:

    1
    2
    3
    4
    ${#string}
    # 或
    ${expr length $string}
    # 后者速度稍慢

    示例:

    1
    2
    s=abc123cba
    echo ${#s}

    结果为

    1
    9
  2. 从左边截取子字符串:${string:postion:length}

    1
    2
    s=abc123cba
    echo ${s:5:3}

    结果为

    1
    3cb
  3. 从右边截取子字符串:${string:空格-length}${string:num1-num2} 这里 num2 大于 num1 且为正数

    1
    2
    echo ${s:0-4}
    echo ${s:1-5}

    结果为

    1
    3cba

删除子字符串

  1. 从左边匹配到第一个并删除左边: ${string#match_substring},这里 match_substring 为匹配子字符串,也可以为正则表达式
    1
    2
    3
    4
    s="C:\Windows\win.ini"
    echo ${s#*\\} # 这里 \\ 表示转义 \,* 表示匹配所有,所以 *\\ 表示匹配第一个\及其前面的所有内容
    echo ${s#*W}
    echo ${s#*w}
    结果为
    1
    2
    3
    Windows\win.ini
    indows\win.ini
    s\win.ini
  2. 从左边匹配到最后一个并删除左边:${string##match_substring}
    1
    echo ${s##*\\}
    结果为
    1
    win.ini
  3. 从右侧匹配到第一个并删除右边:${string%match_substring}
    1
    echo ${s%\\*}
    结果为
    1
    C:\Windows
  4. 从右侧匹配到最后一个并删除右边:${string%%match_substring}
    1
    echo ${s%%\\*}
    结果为
    1
    C:

替换子字符串

  1. 替换第一个:${string/match_substring/replace_substring}string 中的 match_substringreplace_substring
    1
    2
    3
    s=abc123321cba
    echo ${s/123/456}
    echo ${s/c/C}
    结果为
    1
    2
    abc456321cba
    abC123321cba
  2. 替换所有:${string//match_substring/replace_substring}
    1
    echo ${s//1/I}
    结果为
    1
    abcI2332Icba

set 命令

Linux set 命令用于设置 shell。具体的,set 命令能设置所使用 shell 的执行方式,可依照不同的需求来做设置。往往该命令被忽视,导致脚本安全性和维护性降低。Bash 在执行脚本的时候,会自动创建一个新的 shell,如

1
bash hello.sh

会自动创建一个新 shell,hello.sh 在该 shell 中执行,bash 默认为该 shell 执行环境配置各种参数,set 命令可以用来修改这个新 shell 执行环境的允许参数, 默认环境参考下面语法部分。

语法

1
2
set [--abefhkmnptuvxBCEHPT] [-o option-name] [argument …]
set [+abefhkmnptuvxBCEHPT] [+o option-name] [argument …]

参数说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-e  等价于 -o errexit,若指令传回值不等于 0,即脚本出现一处错误,则立即退出 shell;
-n  只读取指令,而不实际执行;
-u  等价于 -o nounset,当执行时使用到未定义过的变量,则显示错误信息,而不是忽略它。当遇到为定义变量后不再执行后面语句;
-x  等价于 -o xtrace,执行指令后,会先显示该指令及所下的参数。便于复杂代码调试;

-a  标示已修改的变量,以供输出至环境变量;
-b  使被中止的后台程序立刻回报执行状态;
-C  转向所产生的文件无法覆盖已存在的文件;
-d  Shell 预设会用杂凑表记忆使用过的指令,以加速指令的执行。使用-d参数可取消;
-f 取消使用通配符;
-h 自动记录函数的所在位置;
-H Shell 可利用"!"加<指令编号>的方式来执行history中记录的指令;
-k  指令所给的参数都会被视为此指令的环境变量;
-l  记录 for 循环的变量名称;
-m  使用监视模式;
-p  启动优先顺序模式;
-P  启动 -P 参数后,执行指令时,会以实际的文件或目录来取代符号连接;
-t  执行完随后的指令,即退出 shell;
-v  显示 shell 所读取的输入值;
+<参数>  取消某个 set 曾启动的参数。

实例
对于 hello.sh,脚本内容如下:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
set -u # 遇到不存在的变量,报错
echo $x
echo hello
set -x # 执行命令先打印命令
echo world
set +e # 以下命令错误或返回值非0不退出shell,即关闭 -e 选项
echo $(foo + 2)
set -e # 以下命令错误或返回值非0则立即退出shell,即打开 -e 选项
echo foo2
echo $(foo + 2) || true # 失败也会继续执行,跳过 -e 选项。即默认该命令总是会执行成功

除了上面的情况,set -e 有一个例外情况,就是不适用于管道命令。所谓管道命令,就是多个子命令通过管道运算符(|)组合成为一个大的命令。Bash 会把最后一个子命令的返回值,作为整个命令的返回值。也就是说,只要最后一个子命令不失败,管道命令总是会执行成功,因此它后面命令依然会执行,set -e 就失效了。对于管道命令,可以使用参数:set -eo pipefail

可以组合使用各参数,如:

1
2
3
4
5
6
# 写法一
set -euxo pipefail

# 写法二
set -eux
set -o pipefail

或者在执行脚本时传入:

1
bash -euxo pipefail hello.sh

查看当前 shell 的设置,可直接通过如下命令查看:

1
set

参考文献

  1. Shell 教程
  2. 掌握Shell编程,一篇就够了
  3. 【小白入门 通俗易懂】2021韩顺平 一周学会Linux
  4. Linux set命令
  5. Bash 脚本 set 命令教程
  6. 4.3.1 The Set Builtin
  7. linux shell 字符串操作(长度,查找,替换)详解
  8. shell 字符串操作 ${} 的截取,删除,和 替换