chapter 10:Sh 编程
摘要
本章主要内容是sh编程,解释了sh脚本和不同版本的sh。它将sh脚本与C程序进行了比较,并指出了解释型语言和编译型语言之间的区别。
10.1 sh脚本
- sh脚本是一个包含sh语句的文本文件,用于执行命令解释器sh的命令
- sh脚本的第一行通常以#!开头,这被称为shebang,指定了脚本所针对的程序名称
- 使用chmod +x命令将脚本文件设为可执行
- sh脚本可以执行各种命令和内置命令,使用echo命令可以在脚本中输出文本
10.2 sh脚本与C程序的比较
- sh脚本和C程序在语法形式和用法上有一些相似之处,但根本上是不同的
sh | C |
---|---|
INTERPRETER: read & execute | COMPILE-LINKED to a.out |
mysh a b c d | a.out a b c d |
$0 $1 $2 $3 $4 | main(int argc, char *argv[ ]) |
- sh脚本是解释执行的,逐行读取并执行命令,而C程序需要先编译为二进制可执行文件后再运行
- 在C程序中,变量有类型,而在sh脚本中,一切都是字符串
- C程序需要有一个main()函数作为入口点,而sh脚本的入口点是脚本中的第一个可执行语句
10.3 命令行参数
- sh脚本可以通过命令行参数来调用,参数可以通过
$0
、$1
、$2
等位置参数来访问 - 前10个命令行参数可以直接通过
$0
到$9
的形式访问,其他参数需要使用${10}
到${n}
的形式引用 - 内置变量
$#
和$*
可以用于计算和显示命令行参数的数量和所有参数的值 - 其他与命令执行相关的内置变量有
$$
表示正在执行sh的进程的PID,$?
表示上次命令执行的退出状态
示例:
假设以下mysh脚本按如下方式运行:
mysh abc D E F G H I J K L M N
# 1 2 3 4 5 6 7 8 9 10 11 12
该脚本的内容如下:
1. #! /bin/bash
2. echo \$# = $# # $# = 12
3. echo \$* = $* # $* = abc D E F G H I J K L M N
4. echo $1 $9 $10 # abc K abc0 (注意:$10变成了abc0)
5. echo $1 $9 ${10} # abc K L (注意:${10}是L)
6. shift # replace $1、$2..with $2、$3...
7. echo $1 $9 ${10} # D L M
- 通过实例展示了sh脚本中使用命令行参数的方法
- 特殊字符
$
表示替换,若要原样使用$
,需要使用单引号或通过\
进行转义 - 注意在脚本中处理
$10
时的情况,sh将其视为$1
和0
的连接,并将$1
替换成abc
,导致$10
变成abc0
- 使用
shift
命令可以将位置参数左移一位,使得$2=$1
、$3=$2
,依此类推
10.4 内置变量
Shell具有许多内置变量,例如PATH、HOME、TERM等。用户还可以使用任何符号作为Shell变量,无需声明。所有的Shell变量值都是字符串。未分配值的Shell变量是空字符串。
变量的赋值
Shell变量可以通过以下方式设置或分配值:
变量名=字符串 # 注意:令牌之间不能有空格
如果A是一个变量,则$A是它的值。
echo A ==> A
echo $A # 如果变量A未设置,打印 (null)
A="this is fun" # 设置 A 的值
echo $A ==> this is fun
B=A # 将A分配给B
echo $B ==> A(B被分配了字符串"A")
B=$A # B取A的值
echo $B ==> this is fun
10.5 引号
Shell中有许多特殊字符,如$、/、*、>、<等,为了使用它们作为普通字符,需要使用\或单引号将它们引起来。在单引号内无法使用转义符或变量,但是在双引号内可以使用变量。
A=xyz
echo \$A ==> $A # 转义$字符
echo '$A' ==> $A # 单引号内无法使用转义符或变量
echo "see $A " ==> see xyz # 在双引号内可以使用变量
10.6 语句执行
Shell语句包括所有Unix /Linux命令,具有可能的I/O重定向。此外,Shell编程语言也支持条件测试、循环和case等语句,以控制Shell程序的执行。
ls
ls > outfile
date
cp f1 f2
mkdir newdir
cat < filename
10.7.1 内置命令
sh 有许多内置命令,它们在执行时不会派生出新的进程。以下是一些常用的内置命令:
. file
: 读取并执行文件。break [n]
: 退出最接近的第 n 层嵌套循环。cd [dirname]
: 改变当前目录。continue [n]
: 重新开始最接近的第 n 层嵌套循环。eval [arg …]
: 对参数进行一次求值并让 sh 执行结果命令。exec [arg …]
: 让 sh 执行命令并退出当前进程。exit [n]
: 让 sh 以指定的状态码 n 退出。export [var .. ]
: 导出变量给后续的命令。read [var … ]
: 从标准输入读取一行并将其分割成标记,将标记值赋给列出的变量。set [arg … ]
: 设置执行环境中的变量。shift
: 将位置参数 $2 $3 … 重命名为 $1 $2 … 。trap [arg] [n]
: 接收到信号 n 时执行 arg 。umask [ddd]
: 设置umask为八进制数字ddd。wait [pid]
: 等待进程 pid ,如果 pid 没有被给定,则等待所有活动子进程。
read 命令:
当 sh 执行 read
命令时,它会等待从标准输入中获取输入行。它将输入行分割成标记,并将这些标记分配给指定变量。一个常见的用法是通过 read
命令来让用户与执行的 sh
进程进行交互。例如:
echo -n "enter yes or no : " # 等待标准输入输入行
read ANS # 读取标准输入中的一行
echo $ANS # 输出用户的输入
在获取到输入之后,sh 可以根据输入字符串来决定下一步该做什么。
10.7.2 Linux 命令
sh 可以执行所有的 Linux 命令。其中,一些命令已经成为了 sh 的常用部分,因为它们在 sh 脚本中被广泛使用。以下列出并解释了其中的一些命令。
echo 命令
echo
命令会将参数字符串作为行输出到标准输出。除非被引用,它通常将连续的空格缩减为一个空格。
示例:
echo This is a line # 输出 This is a line
echo "This is a line" # 输出 This is a line
echo -n hi # 输出 hi,但不换行
echo there # 输出 hi there
expr 命令
由于所有的 sh 变量都是字符串,因此我们无法直接将它们作为数字值更改。例如,下面的语句:
I=123 # I 被赋值为字符串 "123"
I=I + 1 # I 被赋值为字符串 "I + 1"
与其将 I
的数值增加 1,上述语句只是将 I
更改为字符串 "I + 1",这肯定不是我们所希望的(即将 I
更改为 "124")。间接地改变 sh 变量的(数值)值可以通过 expr
命令来完成。expr
是一个程序,它的运行方式如下:
expr string1 OP string2 # OP 为任何二元数字运算符
它首先将两个参数字符串转换为数字,执行(二元)运算符 OP,然后将结果数字转换回字符串。因此,执行下面的操作:
I=123
I=$(expr $I + 1)
将 I
从 "123" 更改为 "124"。同样地,expr
还可用于对 sh 变量执行其他算术运算,其值是由数字 digit
字符串构成的。
管道命令
sh 脚本中经常使用管道来充当过滤器。例如:
ps –ax | grep httpd
cat file | grep word
实用程序命令
除了上述的 Linux 命令,sh 还使用许多其他实用程序程序作为命令。这些工具包括:
awk
: 数据操作程序。cmp
: 比较两个文件。comm
: 选择两个已排序文件中的相同行。grep
: 在一组文件中匹配模式。diff
: 查找两个文件之间的差异。join
: 通过相同键连接记录来比较两个文件。sed
: 流或行编辑器。sort
: 排序或合并文件。tail
: 输出文件的最后 n 行。tr
: 单向字符转换。uniq
: 从文件中删除连续的重复行。
10.8 命令替代
在sh中,使用$A代替A的值。同样,当sh看到cmd
(在反引号中)或$(cmd)时,它首先执行cmd,然后将$(cmd)替换为执行结果字符串。
示例
```shell echo $(date) # 显示date命令的结果字符串 echo $(ls dir) # 显示ls dir命令的结果字符串 ```10.9 sh控制语句
sh是一种编程语言。它支持许多执行控制语句,与C语言类似。
10.9.1 if-else-fi语句
if-else-fi语句的语法为:
if [ condition ]
then
statements
else
statements
fi
- 每个语句必须单独一行,但是如果它们由分号;分隔,那么sh允许在同一行上分隔多个语句。
- sh允许使用-eq,-ne,-lt,-gt等运算符将参数作为整数进行比较。
- TEST程序还可以测试文件类型和文件属性,在文件操作中经常需要。
10.9.2 for语句
sh中的for语句与C中的for循环行为相同。
其语法是:
for VARIABLE in string1 string2 .... stringn
do
commands
done
示例
for FRUIT in apple orange banana cherry
do
echo $FRUIT # 打印apple orange banana cherry每一行
done
for NAME in $*
do
echo $NAME # 列出所有的命令行参数字符串
if [ -f $NAME ]; then
echo $NAME is a file
elif [ -d $NAME ]; then
echo $NAME is a DIR
fi
done
10.9.3 while语句
sh中的while语句与C中的while循环相似。
其语法为:
while [ condition ]
do
commands
done
当条件为true时,sh将重复执行do-done关键字中的命令。
示例
以下代码段创建目录dir0、dir1 ... dir10000:shell
I=0 # 将I设置为 "0" (STRING)
while [ $I != 10000 ] # 作为字符串进行比较;或者作为数字运行while [ $I \< 1000 ]
do
echo $I # echo current $I value
mkdir dir$I # 创建目录dir0、dir1等
I=$(expr $I + 1) # 使用expr将I的值加1
done
10.9.4 until-do语句
sh的until-do语句与C语言中的do-until语句类似。
其语法为:
until [ $ANS = "give up" ]
do
echo -n "enter your answer : "
read ANS
done
sh将重复执行do-done关键字中的命令,直到条件为真为止。
10.9.5 case语句
sh的case语句与C语言中的case语句类似,但在sh编程中很少使用。
其语法为:
case $variable in
pattern1) commands;; # 注意双分号;;
pattern2) command;;
patternN) command;;
esac
10.9.6 continue和break语句
与C语言中一样,continue重启最近循环的下一次迭代,而break退出最近的循环,它们在sh中与C中完全相同。
10.10 I/O重定向
- I/O重定向可以将输入和输出从默认的stdin、stdout、stderr重定向到其他文件。
- 使用" > file"将stdout重定向到文件中,如果文件不存在,则创建该文件。
- 使用" >> file"将stdout追加到文件中。
- 使用" < file"将文件作为stdin输入,文件必须存在且有读取权限。
- 使用" << word"从"here"文件获取输入,直到出现只包含"word"的行。
10.11 嵌入文档
- 嵌入文档允许将stdin的输入输出到stdout,直到遇到预先设定的关键字。
- 可以使用echo和预先设定的关键字来创建Here文档。
- 嵌入文档通常用于生成长的描述性文本块,而无需逐行回显。
10.12 sh函数
- sh函数的定义方式为"func(){ }"。
- 所有函数必须在可执行语句之前定义。
- 函数可以通过"$0"、"$1"到"$n"来引用传递的参数。
- 函数执行完成后,可以通过"$?"获取其退出状态。
10.13 sh中的通配符
- "*"通配符可以扩展为当前目录中的所有文件。
- "?"通配符用于查询文件名中的字符。
- "[]"通配符用于查询文件名中的一对"[]"中的字符。
10.14 命令分组
- 可以使用"{ }"或"()"将命令分组在一起。
- "{ }"在同一环境中执行命令,并可用于重定向I/O。
- "()"由子进程执行,可以更改工作目录和分配变量,不会影响父进程。
10.15 eval语句
- eval是sh内置命令,用于将输入参数字符串连接为单个字符串。
- eval会执行变量和命令替换,并将生成的字符串作为sh的输入执行。
10.16 调试sh文本
- 使用子 sh 进行调试,子 sh 需要使用 -x 选项进行调试,例如
bash -x mysh
,会显示每个要执行的 sh 命令(包括变量和命令替换)执行命令之前。 - 如果出现错误,sh 将停在错误行并显示错误消息。
10.17 sh 脚本的应用
- sh 脚本最常用于执行包含一长串命令的常规工作。
- 例子 1:Linux 安装包是用 sh 脚本编写的,用户在安装过程中,可以与该 sh 脚本交互,查找硬盘、对硬盘分区和格式化、从安装媒介中提取文件并安装到目录中。
- 例子 2:登录过程会执行一系列的 sh 脚本,如 .login、.profile、.bashrc 等,来自动配置用户进程的执行环境。
- 例子 3:编译链接任务,可以通过包含编译和链接命令的 sh 脚本来完成简单的编译链接任务,而不是使用 Makefiles。
- 例子 4:为 Linux 机器上的 CS 课程创建用户帐户。