Linux初入门扉 ( 三 )
Shell脚本
什么是 Shell
Shell 是 Linux 的命令解释程序,是用户和内核之间的接口。除了作为命令解释程序外,Shell 同时还提供了一个可支持强大脚本语言的程序环境。
Bash
Bourne Shell (sh),是 Unix 系统的默认 Shell,简单轻便,脚本编程功能强,但交互性差。
Bourne Again Shell,即 Bash,是 GNU 开发的一个 Shell,也是大部分 Linux 系统的默认 Shell,是 Bourne Shell 的扩展。
许多 Bash 脚本会在文件首行加上 #!/bin/bash 。这里 #! 符号的名称是 shebang(也叫 sha-bang,即 sharp # 与 bang !)。当一个文本文件首行有 shebang,且以可执行模式执行时,shebang 后的内容会看作这个脚本的解释器和相关参数,系统会执行解释器命令,并将脚本文件的路径作为参数传递给该命令。
Bash 也支持在同一个行中安排多个命令:
| 分隔符 | 说明 |
|---|---|
| ; | 按命令出现的先后,顺序执行 |
| && | 先执行前面的命令,若成功,才接着执行后面命令;若失败,不执行后面命令 |
| || | 先执行前面的命令,若成功,不执行后面命令;若失败,才执行后面命令 |
| 后缀 & | 后台方式执行命令 |
组命令:
使用 { 命令1; 命令2; … },组命令在 shell 内执行,不会产生新的进程,注意花括号和命令之间的空格。
使用 (命令1; 命令2; …),组命令会建立独立的 shell 子进程来执行组命令,这里的圆括号周围并不需要空格。
shell 变量
像大多数程序设计语言一样,shell 也允许用户在程序中使用变量。但 shell 不支持数据类型,它将任何变量值都当作字符串。但从赋值形式上看,可将 shell 变量分成四种形式:用户自定义、环境变量、位置变量和预定义特殊变量。
用户自定义变量
变量定义:name=串,其中 = 两边不允许有空格。如果字串中含空格,就要用双引号括起。在引用时,使用 $name 或 ${name},后者花括号是为了帮助解释器识别变量边界。
已定义的变量可以通过 unset name 来删除。
环境变量
每个用户登录系统后,Linux 都会为其建立一个默认的工作环境,由一组环境变量定义,用户可以通过修改这些环境变量,来定制自己工作环境。在 Bash 中,可用 env 命令列出所有已定义的环境变量。通常,用户最关注的几个变量是:
HOME:用户主目录,一般情况下为 /home/用户名。
LOGNAME:登录用户名。
PATH:命令搜索路径,路径以冒号分割。当我们输入命令名时,系统会在 PATH 变量中从前往后逐个搜索对应的程序是否在目录中。
PWD:用户当前工作目录路径。
SHELL:默认 shell 的路径名。
TERM:使用的终端名。
可以使用 export 命令来定义环境变量。在同一个 shell 中使用 export 定义之后,这个环境变量会一直保留,直到这个 shell 退出
此外,也可以在命令前加上环境变量的定义。此时只有这一条命令的环境变量出现变化。
位置变量
Shell 解释用户的命令时,把命令程序名后面的所有字串作为程序的参数。分别对应 $1、$2、$3、……、$9,程序名本身对应 $0。
可用 shift
命令,丢弃开头的 n 个位置变量,改变 $1、$2、$3 等的对应关系。 可用 set 命令,重置整个位置变量列表,从而给 $1、$2、$3 等赋值。
例子:
1 | $ set one two three |
特殊变量
Shell 中还有一组有 shell 定义和设置的特殊变量,用户只能引用,而不能直接改变或重置这些变量。
| 特殊变量 | 说明 |
|---|---|
| $# | 命令行上的参数个数,不包括 $0 |
| $? | 最后命令的退出代码,0 表示成功,其它值表示失败 |
| $$ | 当前进程的 PID |
| $! | 最近一个后台运行进程的进程号 |
| $* | 命令行所有参数构成的一个字符串 |
| $@ | 用双引号括起的命令行各参数拼接构成的一个字符串 |
特殊字符
反斜杠,消除单个字符的特殊含义。
- 包含空白字符(空格和制表符)、反斜杠本身、各种引号,以及 $、! 等。
- 与其他语言不同,shell 中反斜杠不会将普通字符转义为其他含义(例如 \n 不会被视作换行符)。
使用双引号包裹字符串可以消除空白字符切分参数的特殊含义,但是很多其他特殊字符的特殊含义仍然保留。双引号也被称为「弱引用」。
单引号,能消除所有特殊字符的特殊含义,包括反斜杠,因此单引号字符串中不能使用反斜杠转义单引号本身。单引号也被称为「强引用」。
反引号(`)括起的字符串,被 shell 解释为命令,执行时用命令输出结果代替整个反引号对界限部分。
与反引号相同的语法是 $(command),它的好处是界限更明确,且可以嵌套。因此编写新脚本时,更建议使用此语法。
特殊字符示例
1 | $ ls /mnt/c/Program Files/ |
变量输入与输出
输入
可以使用 read 命令读取用户输入,并将输入的内容赋值给变量。其中 -p 参数后可以设置输出的提示信息。
1 | $ name="" |
在使用 read 时,建议加上 -r 参数,否则 \ 会被视为转义符号。
1 | $ message="" |
输出
可以使用 echo 命令输出变量信息。其中 -n 参数不会在结尾输出换行符,而 -e 参数会解析文本中的转义字符(例如 \n)。
1 | $ echo -n "hello" |
此外,printf 命令也可以用来输出,它的使用方法类似于 C 中的 printf() 函数。
1 | $ name="linux" |
算术运算
在 Bash 中进行算术运算,需要使用 expr 计算算术表达式值或 let 命令赋值表达式值到变量。基本运算符是 +、-、* (转义)、/、%。在 expr 中,运算符两边与操作数之间必须有空格,小括号要转义;但 let 则没有这个要求,运算符前后有无空格均可,小括号不需转义,但 = 前后不能有空格。
另外,所有标准的 shell 都支持另一种语法 (( 表达式 )),其中 表达式 是一个 C 风格的数学表达式,可以计算,也可以赋值。(( 表达式 )) 是一条完整的命令,命令的返回值为 0 或 1。若表达式的结果非零,那么 (( 表达式 )) 命令返回零,而当表达式结果为零时命令返回 1。
expr 和 let 使用示例
1 | $ expr length "ustclug" |
条件表达式
条件表达式写成 test 条件表达式,或 [ 条件表达式 ],注意表达式与方括号之间有空格。
字符串比较
| 表达式 | 含义 |
|---|---|
| string1 = string2 | 如果两个串相等,则结果为真(true: 0) |
| string1 != string2 | 如果两个串不相等,则结果为真 |
| string 或 -n string | 如果字符串 string 长度不为 0,则结果为真 |
| -z string | 如果字符串 string 长度为 0,则结果为真 |
数值比较
表达式:int1 [option] int2,其中的参数可以用下列替换。
| 参数 | 说明 |
|---|---|
| -eq | 等于 |
| -ne | 不等于 |
| -gt | 大于 |
| -ge | 大于等于 |
| -lt | 小于 |
| -le | 小于等于 |
文件状态
| 表达式 | 含义 |
|---|---|
| -r file | 文件存在且可读 |
| -w file | 文件存在且可写 |
| -x file | 文件存在且可执行 |
| -f file | 文件存在且为普通文件 |
| -d file | 文件存在且为目录 |
| -s file | 文件存在且长度大于 0 |
复合逻辑表达式
| 表达式 | 含义 |
|---|---|
| ! expr | 否运算 |
| expr1 –a expr2 | 与运算 |
| expr1 –o expr2 | 或运算 |
流程控制
条件分支:if
序列中可嵌套 if 语句,在 shell 中也允许有多个 elif ,但 shell 的流程控制不可为空。末尾的 fi 就是 if 倒过来写,后面还会遇到类似的结束符。
1 | if condition1 |
按值选择:case
选项值必须以右括号 ) 结尾,若匹配多个离散值,用 | 分隔。这里的 esac 也是 case 倒着写。
1 | case <variable> in |
枚举循环:for
for 循环可以对一个列表中的每个值都执行一系列命令。
1 | for var in list |
除此之外,用于流程控制的还有在 C 语言中同样常见的 break 和 continue。与 C 语言不同的是,它们还接受一个数字作为参数,即 break n 和 continue n,其中参数 n 均表示跳出或跳过 n 层循环。
函数
与其他编程语言类似,shell 也可以定义函数。其定义格式为:
1 | # POSIX syntax |
其中函数的参数可以像命令一样通过 $1、$2、$3 等获取,返回值可以显式用 return 返回,或以最后一条命令运行结果作为返回值。在函数中使用 return 会结束本次函数执行,而使用 exit 会直接结束退出包含函数的当前脚本程序。
函数在使用前必须定义,因此应将函数定义放在脚本开始的部分。在调用函数时仅使用其函数名即可。
