本文示例以 Bash 为准。sh 不一定指向 Bash,数组、let、字符串替换等 Bash 写法不能直接当成通用 sh 语法。

格式

打开文本编辑器(如vi或vim ),创建一个后缀名为sh的文件;开头第一行使用 shebang 指定解释器,如#!/bin/bash。用 ./test.sh 直接执行时,系统会据此选择解释器;用 bash test.sh 时,则由命令中指定的 Bash 解释脚本。

例如编写一个名为test.sh的脚本:

1
2
#!/bin/bash
echo "test" #echo命令用于输出(打印)信息

使用方式

bash + 脚本名运行

1
2
3
4
5
6
7
[kaka@localhost ~]$ cat test.sh 
#!/bin/bash
a='hello world'
echo "$a"

[kaka@localhost ~]$ bash test.sh
hello world

给执行权限后直接运行

直接运行前需要为脚本赋予执行权限(chmod +x <脚本的文件名>)

1
2
3
4
5
6
7
8
9
10
11
[kaka@localhost ~]$ cat test.sh 
#!/bin/bash
a='hello world'
echo "$a"

[kaka@localhost ~]$ ./test.sh
bash: ./test.sh: 权限不够

[kaka@localhost ~]$ chmod +x test.sh
[kaka@localhost ~]$ ./test.sh
hello world

使用 source 或 . 运行

使用 source ./a.sh 或 . ./a.sh,会在当前 Shell 中执行脚本。因此变量赋值、cd 等操作会影响当前 Shell,也不需要给脚本执行权限。source 是 Bash 支持的写法,. 是 POSIX 写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
[kaka@localhost ~]$ cat a.sh 
#!/bin/bash
A=5
echo $A

[kaka@localhost ~]$ bash a.sh
5
[kaka@localhost ~]$ echo $A

[kaka@localhost ~]$ source a.sh
5
[kaka@localhost ~]$ echo $A
5

变量

自定义变量、环境变量、位置变量、预定义变量

变量名要求

  • 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头
  • 中间不能有空格,可以使用下划线 _
  • 不能使用标点符号
  • 建议避开 Bash 保留字,也不要随意覆盖 PATH 等已有变量,以免给自己整点新活

如何使用

在变量名前加美元符号($)即可

为了帮助解释器识别到变量的边界,可以在变量名外加入花括号({})

自定义变量

普通赋值创建的是当前 Shell 中的变量,默认不会传给子进程;它不等同于函数里的 local 局部变量。

格式:<变量名>=<变量值>,注意等号两边不需要空格

如果变量值存在空格,需要用双引号("")或单引号('')框起来

使用unset <变量名>撤销该自定义变量

环境变量

使用 export 变量名 或 export 变量名=值,可以将变量导出给之后启动的子进程。它不会自动同步到其他已打开的终端。

例如 /etc/profile 是登录 Shell 会读取的初始化文件之一,可以在其中配置环境变量;个人配置通常放在 ~/.bash_profile 等文件中,具体读取规则取决于 Shell 和启动方式。

预定义变量

下面列出的是特殊参数(包括位置参数),由 Shell 提供,不是普通的环境变量或局部变量。

变量 含义
$n 位置变量
n为数字,$0代表该脚本名称
$1代表第1个参数(以此类推),但从第十个开始要用花括号({})包含
$# 获取所有输入参数的个数
$* 使用 "$*" 时,将所有参数合成一个字符串(以 IFS 的首字符分隔)
$@ 使用 "$@" 时,每个原始参数保持为独立的一项
$? 获取上一条命令的退出状态;0 通常表示成功,非零状态的具体含义由命令决定

创建一个名为advance.sh的文件,并写入以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
echo '==========$n=========='
echo "$0"
echo "$1"
echo "$2"
echo '==========$#=========='
echo $#
echo '==========$*=========='
for i in "$*"; do
echo "$i"
done
echo '==========$@=========='
for i in "$@"; do
echo "$i"
done
echo '==========$?=========='
echo $?

使用bash ./advance.sh 1 2 3 4 5命令,结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
==========$n==========
./advance.sh
1
2
==========$#==========
5
==========$*==========
1 2 3 4 5
==========$@==========
1
2
3
4
5
==========$?==========
0

💡提示:"$*" 和 "$@" 的区别需要配合双引号理解;不加引号可能再次发生单词分割和文件名展开。另外,最后的 echo $? 获取的是前一条命令的状态,不是整个脚本的状态。

只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变、不能被撤销

赋值

  • 显示变量的值使用双引号

  • 单引号中的 $变量名 会按普通文本保留,不做变量展开

  • 反引号与 $(命令) 都用于命令替换:先执行命令,再使用其标准输出,末尾的换行会被移除。嵌套时建议使用 $(...)。

直接赋值

格式:<变量名>=<变量值>,注意等号两边不需要空格

1
2
3
#!/bin/bash
num=10086 #直接赋值
echo $num

键盘读取赋值

格式:read -r -p "提示信息" 变量名,可省略 -p "提示信息"

获取输入的单行数据,并赋值给变量名

1
2
3
#!/bin/bash
read -r INPUT #将键盘输入的单行数据赋值给INPUT变量名
echo "$INPUT"

通过命令行参数赋值

即通过位置变量进行调用

1
2
#!/bin/bash
echo "$1" "$2"

利用命令的输出结果赋值

在赋值语句中使用反引号,或推荐的 $(命令)。

1
2
3
#!/bin/bash
DATE=`date +%F` #将当前年月日期(结果)赋值到DATE变量上
echo $DATE

这里的 `date +%F` 等价于 $(date +%F),可以写成 DATE=$(date +%F);直接执行 date +%F 则是输出日期,不是赋值。

从文件中读取数据赋值

这种方式适用于处理大批量的数据,直接把相应的数据写入文件中。这种方式通常是循环一行行读入数据,即每循环一次,就从文件中读入一行数据,直到文件的结尾。下面约定当前目录至少有一个 .sh 文件,且文件名不含换行。

1
2
3
4
5
6
#!/bin/bash
printf '%s\n' *.sh > execfile #列举后缀名为sh的文件重定向至execfile
while IFS= read -r LINE || [ -n "$LINE" ] #保留空白和反斜杠,也处理末行无换行的情况
do
printf '%s\n' "$LINE" #输出每行内容
done < execfile

运算符

Bash 变量默认保存文本;在 $((...))、let 等算术上下文中可以做整数运算。它们不直接支持浮点数,整数除法会舍去小数部分。

基本语法

$((运算式));旧写法 $[运算式] 已不推荐使用。

1
2
3
4
5
[kaka@localhost ~]$ a=10
[kaka@localhost ~]$ b=20
[kaka@localhost ~]$ sum=$((a + b))
[kaka@localhost ~]$ echo $sum
30

使用expr计算

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

1
2
3
4
[kaka@localhost ~]$ a=10
[kaka@localhost ~]$ b=20
[kaka@localhost ~]$ expr $a + $b
30

使用let计算

1
2
3
4
5
[kaka@localhost ~]$ a=10
[kaka@localhost ~]$ b=20
[kaka@localhost ~]$ let sum=a+b
[kaka@localhost ~]$ echo $sum
30

算术运算符

运算符 说明 举例
+ 加法 expr $a + $b
- 减法 expr $a - $b
* 乘法 expr $a \* $b
此处需要用\转义,防止被当成文件名通配符展开
/ 除法 expr $b / $a
% 取余 expr $b % $a
= 赋值 a=$b
把变量 b 的值赋给 a
== 相等
在算术条件中比较两个整数
(( a == b ))
!= 不相等
在算术条件中比较两个整数
(( a != b ))

注意:使用 [ ] 测试时,两侧必须有空格,例如 [ "$a" -eq "$b" ] 比较整数,[ "$a" = "$b" ] 比较字符串。(( a == b )) 是 Bash 的算术条件写法。

替删改

替换

语法 含义
${变量名/旧模式/新字符串} 将第一个匹配旧模式的部分替换成新字符串
${变量名//旧模式/新字符串} 将全部匹配旧模式的部分替换成新字符串

例如将www.abca.com中的第一个a替换成d

1
2
var=www.abca.com
echo "${var/a/d}"

例如将114514中的1全部替换成9

1
2
num=114514
echo "${num//1/9}" #输出994594,数字也能整点活

删除

语法 含义
${变量名#模式} 删除匹配模式的最短前缀
${变量名##模式} 删除匹配模式的最长前缀
${变量名%模式} 删除匹配模式的最短后缀
${变量名%%模式} 删除匹配模式的最长后缀

这里的模式使用 Shell 通配符,不是正则表达式;例如 */ 匹配以 / 结尾的前缀,匹配到的 / 也会一起删除。

创建一个名为del.sh的文件,并写入以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
file=/dir1/dir2/abc.file

echo "输出名为file变量"
echo ${file}

echo '删除第一个 / 前面的内容(从前往后匹配)'
echo ${file#*/}

echo "删除最后一个 / 前面的内容(从前往后匹配)"
echo ${file##*/}

echo "删除第一个 / 后面的内容(从后往前匹配)"
echo ${file%/*}

echo "删除最后一个 / 后面的内容(从后往前匹配)"
echo ${file%%/*}

执行结果

1
2
3
4
5
6
7
8
9
输出名为file变量
/dir1/dir2/abc.file
删除第一个 / 前面的内容(从前往后匹配)
dir1/dir2/abc.file
删除最后一个 / 前面的内容(从前往后匹配)
abc.file
删除第一个 / 后面的内容(从后往前匹配)
/dir1/dir2
删除最后一个 / 后面的内容(从后往前匹配)

默认值

使用 ${变量名-默认值}:变量未设置时使用默认值,已经设置时使用原值。这只影响展开结果,不会修改原变量。

1
2
3
4
5
6
[kaka@localhost ~]$ unset num
[kaka@localhost ~]$ echo ${num-1919}
1919
[kaka@localhost ~]$ num=10010
[kaka@localhost ~]$ echo ${num-1919}
10010

💡提示:${num:-1919} 会在变量未设置或为空时使用默认值;${num:=1919} 则会同时给普通变量赋值。冒号虽然小,作用可不小。