Linux Shell Script 基础教程
本文翻译自LEARN UNIX,博主在原文的基础上添加了一些内容。如果没有Linux 机器,推荐使用该网站 https://www.tutorialspoint.com/execute_ksh_online.php 作为shell在线demo的环境。
1. Shell 是什么
Shell为您提供了与Unix系统的接口。它收集您的输入,并根据该输入执行程序。程序完成执行后,将显示该程序的输出。 Shell 是可以运行我们的命令,程序,shell 脚本的环境。Shell 有不同的类型,每种Shell都有它自己的命令和功能。
1.1 Shell 类型
Linux 系统中,主要有两种类型的Shell:
Bourne shell
- 如果使用Bourne类型的shell,默认的提示符就是$
,可以通过修改环境变量PS1
来更改你的提示符。C shell
- C 类型的shell,默认的提示符是%
。
Bourne Shell 又有下面几种类型:
- Bourne shell (sh)
- Korn shell (ksh)
- Bourne Again shell (bash)
- POSIX shell (sh)
C 类型 的Shell包括以下两种:
- C shell (csh)
- TENEX/TOPS C shell (tcsh)
在大多数 Linux 版本中,Bourne shell 通常都安装为/bin/sh
。由于这个原因,它是不同版本的Linux 的首选 Shell。在本文中,我们将介绍大多数基于Bourne Shell的Shell概念。
1.2 Shell 脚本
Shell脚本的基本概念是命令列表,按执行顺序列出命令。
Shell 脚本文件以 .sh 结尾,例如 test.sh 。Shell 脚本的内容以 #!
开头,告诉系统接下来的命令将会被 Bourne Shell 执行。我们来创建一个Shell脚本,并往脚本中添加一点注释,注释以 #
开头:
#!/bin/bash
# Author: xueqiang.chen
# Script follows here:
pwd
ls
保存命令并执行脚本,可以看到以下输出内容:
$ chmod +x test.sh
$ ./test.sh
/home/centos
go.sh test.sh
Shell 脚本可以有很复杂的结构,毕竟,shell是一种真正的编程语言,它包括变量,控制结构等。无论脚本变得多么复杂,它仍然只是顺序执行的命令的列表。
2. Shell 变量
变量是我们为其分配值的字符串,变量的值包括数字,文本,文件名,设备,或是任意的数据类型。变量只是实际数据的一个指针,我们可以创建,赋值,删除变量。
2.1 变量类型
当Shell运行时,存在三种主要类型的变量:
- 局部变量:局部变量是存在于Shell当前实例中的变量。它不适用于由 Shell 启动的程序。它们在命令提示符下设置。
- 环境变量:环境变量可用于Shell的任何子进程。某些程序需要环境变量才能正常运行。通常,shell脚本仅定义其运行的程序所需的那些环境变量。
- Shell变量:Shell变量是Shell设置的特殊变量,shell要求Shell变量才能正常运行。这些变量中的一些是环境变量,而另一些是局部变量。
2.2 变量名
变量名称只能包含字母(a到z或A到Z),数字(0到9)或下划线字符(_)。按照约定,Unix shell变量将以大写字母命名。 下面的例子是一些有效和无效的变量名:
#!/bin/bash
# valid variable names
_ALL
TOKEN_A
VAR_1
VAR_2
# invaild variable names
2_VAR
-VARIABLE
VAR1-VAR2
VAR_A!
在shell 中,!, *, -
是含有特殊意义的,因此不能在变量名中使用。
2.3 定义变量
变量的定义方式如下:
variable_name=variable_value
注意,=
号两边不能有空格。
#!/bin/bash
NAME="Zara Ali"
这个例子中定义了一个变量,并给这个变量赋值,这种变量的类型称为标量, 标量只一次只能有一个值。
2.4 使用变量
Shell 中通过 $
符号获取变量的值。例如,下面的例子将获取变量NAME
的值并将其打印在标准输出中:
#!/bin/bash
NAME="Zara Ali"
echo $NAME
2.5 只读变量
Shell 允许通过 readonly
命令将一个变量变成只读变量。当变量成为只读变量后,它的值就不能更改了。
#!/bin/bash
NAME="Zara Ali"
readonly NAME
NAME="Qadiri"
上面的脚本会发生错误。
/bin/sh: NAME: This variable is read only.
2.6 重置变量
重置或删除变量将指示Shell程序从其跟踪的变量列表中删除该变量。重置变量后,将无法访问该变量中的存储值。
以下是使用 unset
命令取消定义的变量的语法:
unset variable_name
上面的命令重置定义的变量的值,下面的这个例子简单说明这个命令是如何工作的:
#!/bin/bash
NAME="Zara Ali"
unset NAME
echo $NAME
上面的例子不会打印出任何内容,你可以使用 unset
命令来重置被标记为 readonly
的变量。
3. 特殊变量
在上一节中,我们了解了在变量名称中使用某些非字母数字字符时应注意的事项。这是因为这些字符用在特殊的Unix变量的名称中。这些变量保留用于特定功能。
下面的表格列出了可以在我们的脚本中使用的特殊变量:
变量 | 说明 |
---|---|
$0 | 当前脚本的文件名 |
$n | 这个变量对应于调用脚本的参数。其中参数 n 是正整数,代表参数的位置。例如第一个参数就是 $1 , 第二个参数就是 $2 , 以此类推。 |
$# | 脚本参数的数量 |
$* | 输出整个参数列表,将整个列表作为一个参数,且之间使用空格隔开。 |
$@ | 与$* 的作用是一致的,不同的是该变量输出时会将参数列表分成单独的参数。 |
$$ | 当前shell的进程号。对于Shell脚本,这是它们执行时的进程ID。 |
$! | 最后一个后台命令的进程号。 |
以下脚本使用与命令行相关的各种特殊变量: |
#!/bin/sh
echo "File Name: $0"
echo "First Parameter : $1"
echo "Second Parameter : $2"
echo "Quoted Values: $@"
echo "Quoted Values: $*"
echo "Total Number of Parameters : $#"
运行结果:
$./test.sh Zara Ali
File Name : ./test.sh
First Parameter : Zara
Second Parameter : Ali
Quoted Values: Zara Ali
Quoted Values: Zara Ali
Total Number of Parameters : 2
3.1 错误状态
$?
变量表示上一个命令的退出状态。
退出状态是每个命令完成后返回的数值。通常,如果大多数命令成功,则返回退出状态0;如果不成功,则返回1。
某些命令出于特殊原因会返回其他退出状态。例如,某些命令区分错误的种类,并将根据故障的特定类型返回各种退出值。
下面的这个例子返回的是成功命令的状态:
$./test.sh Zara Ali
File Name : ./test.sh
First Parameter : Zara
Second Parameter : Ali
Quoted Values: Zara Ali
Quoted Values: Zara Ali
Total Number of Parameters : 2
$echo $?
0
$
4. 数组
Shell变量足以容纳单个值。这些变量称为标量变量。
Shell支持另一种类型的变量,称为数组变量。它可以同时保存多个值。数组提供了一种对一组变量进行分组的方法。数组的命名参考变量的命名规则。
4.1 定义数组
数组变量和标量变量之间的差异可以解释如下。 假设您尝试将各个学生的姓名表示为一组变量。每个单独的变量都是标量变量,如下所示:
#!/bin/bash
NAME01="Zara"
NAME02="Qadir"
NAME03="Mahnaz"
NAME04="Ayan"
NAME05="Daisy"
我们可以使用单个数组来存储所有上述名称。以下是创建数组变量的最简单方法。
array_name[index]=value
这里 array_name 是数组的名字,index 是要设置的数组中的索引, value 就是你要为该元素设置的值。如下所示:
#!/bin/bash
NAME[0]="Zara"
NAME[1]="Qadir"
NAME[2]="Mahnaz"
NAME[3]="Ayan"
NAME[4]="Daisy"
如果使用的是 bash shell ,也可以通过以下这种方法进行数组初始化:
array_name=(value1 ... valuen)
4.2 使用数组
为变量赋值之后,访问变量可以使用以下方式:
${array_name[index]}
这里 array_name 是数组名, index 是要访问那个数组项的索引。具体的例子如下:
#!/bin/bash
NAME[0]="Zara"
NAME[1]="Qadir"
NAME[2]="Mahnaz"
NAME[3]="Ayan"
NAME[4]="Daisy"
echo "First Index: ${NAME[0]}"
echo "Second Index: ${NAME[1]}"
echo "First Method: ${NAME[*]}"
echo "Second Method: ${NAME[@]}"
运行结果:
$./test.sh
First Index: Zara
Second Index: Qadir
First Method: Zara Qadir Mahnaz Ayan Daisy
Second Method: Zara Qadir Mahnaz Ayan Daisy
上面的示例中通过 *
和 @
来获取整个数组的值。
${array_name[*]}
${array_name[@]}
5. 运算符
每个Shell都支持不同的运算符,这里我们主要讨论的是 bash shell 的运算符。
5.1 算术运算符
Bourne shell 最初没有任何执行简单算术运算的机制,它使用 awk 或 expr 来进行计算。如下所示:
#!/bin/sh
val=`expr 2 + 2`
echo "Total value : $val"
运行结果:
Total value : 4
上面的例子我们需要注意的有两点:
- 表达式和运算符之间必须用空格隔开,例如
2+2
是错误的,应该写成2 + 2
- 整个表达式要用反引号 `` 来包起来。
假设变量 a 等于 10, 变量 b 等于 20, 我们来看一下 bash shell 支持的算术运算符是如何计算这两个值得:
运算符 | 示例 |
---|---|
+ | expr $a + $b = 30 |
- | expr $a - $b = -10 |
* | expr $a \* $b = 200 |
/ | expr $b / $a = 2 |
% | expr $b % $a = 0 |
= | a = $b 将b的值赋给a |
== | [ $a == $b ] 将会返回 false |
!= | [ $a != $b ] 将会返回 true |
注意:[ $a == $b ]
不能写成 [$a==$b]
。
5.2 关系运算符
Bash 支持以下特定于数值的关系运算符。 假设变量a = 10,变量b = 20,
运算符 | 描述 | 例子 |
---|---|---|
-eq | 检查运算符两边的值是否相等,相等返回 true | [ $a -eq $b ] is not true. |
-ne | 检查运算符两边的值是否相等,不相等返回 true | [ $a -ne $b ] is true |
-gt | gt 是 greater than 的缩写,检查运算符左边的值是否大于右边,是的话返回 true | [ $a -gt $b ] is not true |
-lt | lt 是 less than 的缩写,检查运算符左边的值是否小于右边,是的话返回true | [ $a -lt $b ] is true |
-ge | ge 是 greater than or equal 的缩写,检查运算符左边的值是否大于或等于右边的值,是的话返回true | [ $a -ge $b ] is not true |
-le | le 是 less than or equal 的缩写,检查运算符左边的值是否小于或等于右边的值,是的话返回true | [ $a -le $b ] is true |
注意:所有条件表达式应放在方括号内并在其周围留有空格。
5.3 布尔运算符
Bash 支持以下布尔运算符。假设变量 a 的值是 10, 变量 b 的值是20:
运算符 | 描述 | 示例 |
---|---|---|
! | 逻辑否。这会将真实条件转换为错误条件,反之亦然。 | [ ! false ] is true. |
-o | 逻辑或。如果运算符两边之一为真,则条件为真。 | [ $a -lt 20 -o $b -gt 100 ] is true. |
-a | 逻辑与。如果运算符两边都是真的,则条件为真。 | [ $a -lt 20 -a $b -gt 100 ] is false. |
5.4 字符串运算符
Bash 运算符支持以下操作。 假设变量 a 的值为 “abc”,变量b的值为 “efg”:
运算符 | 描述 | 示例 |
---|---|---|
= | 检查运算符两边的值是否相等;如果是,则条件变为真。 | [ $a = $b ] is not true. |
!= | 检查运算符两边的值是否相等;如果值不相等,则条件为真 | [ $a != $b ] is true. |
-z | 检查给定的字符串操作数大小是否为零;如果长度为零,则返回true。 | [ -z $a ] is not true. |
-n | 检查给定的字符串操作数大小是否为非零;如果长度非零,则返回true。 | [ -n $a ] is not false. |
str | Checks if str is not the empty string; if it is empty, then it returns false. | [ $a ] is not false. |
5.5 文件测试运算符
我们有一些运算符可用于测试与文件相关的各种属性。 假设有个文件变量 file 的值为一个存在的名为 “test” 的文件,该文件的大小为100bytes,并且有读写和执行的权限。
运算符 | 描述 | 示例 |
---|---|---|
-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。 |
-t file | 检查文件描述符是否打开并与终端关联;如果是,则条件变为真。 | [ -t $file ] is 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。 |
5.6 其他shell的运算符
- C Shell 运算符:C Shell Operators
- Korn Shell 运算符:Korn Shell Operators
6. 条件表达式
在编写shell脚本时,可能需要从给定的两个路径中采用一个路径。因此,您需要使用条件语句,这些条件语句允许您的程序做出正确的决定并执行正确的操作。
7. 循环
循环是功能强大的编程工具,使您能够重复执行一组命令。在本章中,我们将研究以下可供Shell程序员使用的循环类型-
- while 循环
- for 循环
- until 循环
- select 循环
您将根据情况使用不同的循环。
7.1 嵌套循环
所有循环都支持嵌套概念,这意味着您可以将一个循环放入另一个类似的或不同的循环中。根据您的要求,此嵌套最多可以无限次。
这是嵌套while循环的示例。其他循环可以根据编程要求以类似的方式嵌套-
可以将while循环用作另一个while循环主体的一部分。 语法:
while command1 ; # this is loop1, the outer loop
do
Statement(s) to be executed if command1 is true
while command2 ; # this is loop2, the inner loop
do
Statement(s) to be executed if command2 is true
done
Statement(s) to be executed if command1 is true
done
示例:
#!/bin/sh
a=0
while [ "$a" -lt 10 ] # this is loop1
do
b="$a"
while [ "$b" -ge 0 ] # this is loop2
do
echo -n "$b "
b=`expr $b - 1`
done
echo
a=`expr $a + 1`
done
运行结果:
0
1 0
2 1 0
3 2 1 0
4 3 2 1 0
5 4 3 2 1 0
6 5 4 3 2 1 0
7 6 5 4 3 2 1 0
8 7 6 5 4 3 2 1 0
9 8 7 6 5 4 3 2 1 0