适用于 ubuntu 20.04
ubuntu 20.04 是 “西柚云” 主要使用的操作系统 西柚云官网
前言
shell 是一个命令解释程序,解释用户输入的命令,Linux 系统最常用的 shell 是 bash,不同的 shell 程序会有细微的差别,这里我们学习的也是 bash。
推荐教程:阮一峰老师的《Bash 脚本教程》:https://wangdoc.com/bash/intro
1 | # 查看当前系统中包含的 shell 程序 |
变量
变量是一串字符,它代表一个值,可以通过 “$变量名” 的方式获得变量的值。如上文中的 $SHELL,SHELL 是一个变量,可以通过$SHELL的方式获得它代表的值。
变量命名规则:
- 只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线_。
- 不能使用标点符号。
- 不能使用bash里的关键字(可用help命令查看保留关键字)
0.echo(单引号、双引号、转义)
使用 echo 可以输出字符串或变量。
好习惯是使用 echo 输出时给要输出的字符串打上引号,这样看起来更直观。
单引号和双引号是不同的,单引号中的特殊字符不会被解释(保持原样输出),双引号中的部分特殊字符会被解释。
特殊字符 | 作用 |
---|---|
$ | 读取变量的值 |
`` | 读取命令的输出 |
$() | 读取命令的输出 |
$(()) | 读取数学运算的结果(只支持整数) |
转义字符 | 含义 |
---|---|
\n | 换行 |
\t | 制表符 |
\\ | 不解释转义字符 |
1 | # 输出字符串(不加引号) |
1.环境变量(USER, PATH, PWD, HOME, printenv, env)
环境变量是一种可以在当前 bash 环境和在当前 bash 环境中创建的 bash 子环境中都能访问到的变量。我们使用的终端就是一个 bash 环境,在终端中通过命令运行的其他 bash 进程都是它的子进程。
1 | # 输出环境变量 |
2.自定义变量(=, declare, export)
1 | # 定义一个普通变量,注意“=”两边不能有空格 |
普通变量和环境变量的区别是环境变量可以在当前 bash 和当前 bash 创建的子 bash 中被读取到,而普通变量只能在当前的 bash 中被读取。
在当前的 bash 环境中创建 1 个子 bash,两者是父进程和子进程的关系。
1 | # 查看进程树 |
1 | echo $USER |
3.删除一个变量(unset)
删除一个变量后,就不能通过 $+变量 的方式获得变量代表的值了。
1 | # 删除一个变量,可以是普通变量,也可以是环境变量 |
4.变量的数据类型
我们来看看 declare 的参数。
-a
:声明数组变量。-i
:声明整数变量。-l
:声明变量为小写字母。-r
:声明只读变量。-u
:声明变量为大写字母。-x
:该变量输出为环境变量。
虽然它能够声明这么多类型的变量,但总的来说变量的类型只有两类:字符串和数组。
字符串
1
2
3
4xiyouyun='西柚云'
xiyou=西柚
yun="云"
echo $xiyouyun $xiyou $yun数组
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# 创建数组
a=(1 2 3 4 5)
b=("1" "2" "3" "4" "5")
c=('x' 'i' 'o' 'u')
# 读取数组单个元素,数组的序号是从 0 开始的
echo "${a[0]}"
# 读取数组的全部元素
echo "${c[*]}"
echo "${c[@]}"
# 获取数组的长度
echo "${#c[*]}"
# 获取数组的序号
echo "${!c[*]}"
# 数组切片:从序号 1 开始,截取一个长度为 3 的序列
echo ${a[@]:1:3}
# 在数组的末尾添加几个元素
c+=("y" "u" "n")
# 删除数组对应的变量
unset a b c关联数组
数组其实有点像关联数组的另一种形式,关联数组是将一个字符串与另一个字符串关联起来,构成的一个序列。
数组是将一个序号与一个字符串关联起来。
我们将关联字符串和被关联的字符串分别称为 key 和 value。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21# 创建关联数组
declare -A map
map=(["xiyou"]="西柚" ["xiyouyun"]="西柚云")
# 根据 key 获得 value 的值
echo "${map["xiyou"]}" "${map["xiyouyun"]}"
# 读取关联数组的 value 序列
echo "${map[*]}"
echo "${map[@]}"
# 获取关联数组的长度
echo "${#map[*]}"
# 获取关联数组的 key 的序列
echo "${!map[*]}"
# 向关联数组中追加元素
map+=(["hello"]="你好")
# 删除关联数组
unset map
补充内容(变量)
一条命令输出多行内容
怎么判断命令是否支持标准输入?
- 在终端输入命令并按回车后,终端会暂停并等待输入(cat,md5sum)
- 支持使用管道符(|)将标准输出数据流入为标准输入的命令(grep)
1 | # << 可以给支持标准输入的命令提供字符串参数 |
脚本
脚本是一个包含代码的文件。
1.脚本的执行原理
脚本的执行原理就是让解释程序去解释脚本的内容,让计算机做某种操作。
解释程序:bash,python,perl,Rscript ……
1 | # 执行 1 个 bash 脚本 |
Shebang 和注释
Shebang:代码的“#!”,为 Shebang,它帮助 bash 定位到解释该文件的程序,它只有在脚本以不显式声明解释程序的情况下执行才有用。
1
2
3
4
5
6
#! /bin/python
# 推荐使用这种方式定位到解释程序的位置会更加灵活,它会让 env 去解释在当前的 bash 环境中 python 对应的是哪个可执行程序
#! /bin/env python
#! /bin/env “perl” -w注释:以’#’开头的行,这些行不会被 bash 解释,不会执行。
1
2
3
4
5
6
7cat > xiyou.sh << _xiyou_
# pwd
# ls
echo "hello, xiyouyun"
_xiyou_
# 不会执行脚本中被注释的内容
bash xiyou.sh脚本的返回值和 $?
在终端中输出的内容不是脚本的返回值,脚本的返回值保存在特殊变量 $? 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14# 命令执行成功了
cd /tmp
# 输出 0 表示上一条命令被正确执行
echo $?
# 将工作目录切换到一个在系统中不存在的目录
cd /not_exists_path
# 输出 1 表示上一条命令执行失败
echo $?
echo "cd /not_exists_path " > xiyou.sh
# 这也可以看做一条命令,所以我们可以通过 $? 来判断脚本是否能够正确被解释
bash xiyou.sh
echo $?脚本执行后的返回值可以通过一个特殊的变量$?获取到
$? 含义 0 脚本/命令内容被 bash 正确解释了 其他 脚本/命令执行出错
2.bash脚本的执行方式
无论执行方式是怎样,都是使用 bash 去解释脚本文件内容。
source(在当前的 bash 环境中执行)
1
2
3
4
5
6
7
8
9
10
11# 这个文件中通常配置了很多环境变量,因此我们想要它们在当前的 bash 环境中生效。
source ~/.bashrc
echo "export hello='hello, xiyouyun'" >> xiyouyun.sh
# 在 bash 子环境中定义一个环境变量,这样是不能被当前的 bash 环境读到的
bash xiyouyun.sh
env | grep hello
# 使用 source 执行会让环境变量在当前的环境中可读
source xiyouyun.sh
env | grep hellobash xx.sh
用户只要保证对 bash 具有可执行权限,并且用户具有 xx.sh 的可读权限就可以执行脚本了,不需要让 xx.sh 文件本身就有可执行权限。
1
2
3
4
5
6
7
8
9
10# 查看 bash 可执行程序的位置
which bash
# 查看 bash 的文件权限
ls -l $(which bash)
# 去除文件的可执行权限
chmod u-x xiyou.sh
# 查看文件的权限
ls -l xiyou.sh
# 执行脚本
bash xiyou.sh./xx.sh 配合 Shebang 可以执行所有类型的脚本。
使用这种方式执行脚本如果使用 Shebang 指定了解释的程序,则根据这个程序来解释脚本的内容。否则默认使用 bash 来解释脚本的内容。
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# 向文件中写入脚本内容
echo "echo hello xiyouyun" >> xiyou.sh
# 使用 ./xx.sh 的方式执行脚本,会报错
./xiyou.sh
# 给脚本赋予可执行权限,在Linux课程的《文件的权限和属性》讲到过
chmod u+x xiyou.sh
./xiyou.sh
# 如果不指定 Shebang,它默认被 bash 解释
cat > xiyouyun.sh << _xiyou_
#! /bin/env python
print("hello, xiyouyun")
_xiyou_
# 如果指定错误的 Shebang,执行时会报错
cat > bad.sh << _xiyou_
#! /a_not_exists_interpreter
print("hello, xiyouyun")
_xiyou_
chmod u+x xiyouyun.sh bad.sh
# 执行该脚本会通过 Shebang 找到 python 程序,然后使用 python 程序来解释脚本内容
./xiyouyun.sh
./bad.sh
# 可以看到 Linux 中程序的执行只跟文件内容和用来解释文件的程序有关,而跟文件后缀无关
bash xiyouyun.sh
# 当然,我们可以改一下文件后缀,方便知道文件应该让哪个程序来解释
mv xiyouyun.sh xiyouyun.py
如果将拥有可执行权限的脚本所在的目录加入到 PATH 变量中,这样就可以直接使用文件名来执行文件了,否则需要使用文件的相对路径或者绝对路径来执行脚本。
1 | which jupyter |

3.bash 脚本的参数($0)
执行脚本时,我们可以给脚本传入参数,然后在脚本内部读取传入的
特殊变量(参数) | 含义 |
---|---|
$0 | 脚本文件名称 |
$1 ~ $9 | 脚本的第1~9个参数 |
$# | 参数的总数 |
$@ | 全部参数,使用空格分割 |
$* | 全部参数,使用 $IFS 分割,IFS是一个环境变量 |
1 | echo "echo -e \" \$1 \$2 \$3 \$4 \$5 \$6 \\n \$@ \\n \$* \"" > xiyou.sh |
我们见到的很多命令的参数前面通常是有名称的,这可以帮助脚本和使用者更好地理解各个参数的含义,比如说 wget。那么我们怎么通过指定不同名称的参数来影响脚本的执行过程和结果呢?
这里就涉及到一些逻辑判断的问题。
如果指定了 o 参数,就将跟在其后的一段字符解释为保存结果的文件路径。
如果指定了 d 参数,就打印 debug 信息。
我们下个视频讲如何在脚本中使用这种逻辑判断。
1 | wget --help |
语法
1.命令的执行方式(&&,||, ;)
1 | # 在一行执行多条命令,几条命令执行成功与否并无关联 |
2.条件判断(if,test)
三类条件判断:
- 对字符串进行条件判断
- 对文件进行条件判断
- 对命令的返回值进行条件判断
语法规则:
- if 和 fi 包裹了整个条件判断的代码,以 if、elif、else 作为分隔符,分割后的代码块最多只有 1 个块会被执行。
- if 和 fi 是必须要有的,elif 和 else 都不是必须的,如果同时有 elif 和 else,else要放在最后
使用条件判断的 3 种代码形式:
- []
- [[]] 支持正则表达式
- test
graph TB A(执行代码) --> B(if) B(if) --> E(结束 fi) A(执行代码) --> C(elif) C(elif) --> E(结束 fi) A(执行代码) --> F(elif) F(elif) --> E(结束 fi) A(执行代码) --> G(...) G(...) --> E(结束 fi) A(执行代码) --> D(else) D(else) --> E(结束 fi)
1 | # 字符串判断 |
条件 | 值为真的情况 |
---|---|
-z 字符串 | 字符串的长度为0时 |
字符串1 = 字符串 2 | 两个字符串相同时 |
字符串1 ‘<’ 字符串 2 | 按照字典顺序字符串1 < 字符串2: a < b ab < b abcdefgg < acdefgh |
字符串1 -lt 字符串2 | 字符串1对应的数学值比字符串2对应的数学值小(lower than) “1” < “2” |
字符串1 -gt 字符串2 | 字符串1对应的数学值比字符串2对应的数学值大(greater than) |
字符串1 -ge 字符串2 | greater or equal |
字符串1 -le 字符串2 | lower or equal |
字符串1 -ne 字符串2 | not equal |
-a file | file 对应的文件存在 |
-d file | file 对应的目录存在 |
-h file | file 存在,并且是一个软链接或硬链接 |
-r file | file 可读 |
-w file | file 可写 |
-x file | file 可执行 |
`command` | 如果命令的返回值为 0 ,则为真 |
逻辑表达式 | 值为真的情况 |
---|---|
expression | 条件成立,则表达式的值为真 |
! expression | 当表达式的值为假时 |
expression1 && expression2 | 当表达式1和表达式2的值都为真时 |
expression1 && expression2 | 当表达式1或表达式2的值为真时 |
3.循环(for…in, while, for, break, continue)
1 | # while 循环 |
4.函数(定义,参数)
函数是可以重复使用的代码片段。
在函数中可以使用参数,调用函数时可以传递参数到函数中。
函数的定义
函数的参数
函数的返回值和脚本的返回值
函数中的局部变量和全局变量(推荐在函数中使用局部变量)
1 | # 定义一个函数 |
$1
~`$9`:函数的第1个到第9个参数。$0
:函数所在的脚本名。$#
:函数的参数总数。$@
:函数的全部参数,参数之间使用空格分隔。$*
:函数的全部参数,参数之间使用变量$IFS
值的第一个字符分隔,默认为空格