chapter 10:Sh 编程

发布时间 2023-09-23 23:50:07作者: 20211108俞振阳

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将其视为$10的连接,并将$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 课程创建用户帐户。