第3章 Shell编程
Shell编程在Linux从业中经常要用到,而且是在Linux行业网上招聘中经常要求的技能,读者需要重点掌握。
Shell的中文意思是“外壳”,通俗地讲,Shell是一个交互编程接口,也是一个命令解释语言,还是一种命令语言解释器,可以用Shell编写各种脚本工具。解释型语言的特点是对源文件进行边识别翻译边执行,不会直接生成二进制机器码,Shell就是解释型语言。Shell脚本文件交给Shell解释器进行解析,Shell解释器把Shell脚本文件解析成一条条语句,然后调用每条语句相应的系统命令进行执行。Shell作为一种命令语言解释器,内置了大量的命令集,涵盖了当前Linux中的所有命令。Shell脚本的执行流程是Shell解释器顺序读取每一行命令,识别成一条条的Linux系统指令,然后调用Linux相应的命令接口生成执行结果。
Shell除了作为命令解释程序以外,还是一种高级程序设计语言,它有变量、关键字,有各种控制语句、支持函数模块,有自己的语法结构,利用Shell程序设计语言可以编写出功能很强但代码简单的程序。
Shell有Bourne(简称B)Shell、Korn Shell、C Shell三种类型,三种Shell的功能大同小异,用得最多的还是B Shell。Shell脚本文件头可用#! /bin/sh说明脚本用哪一种Shell执行,#!表示使用哪一种解释器执行当前文本,/bin/sh是指B Shell解释器。若文件头无#!说明用哪种解释器执行文本,系统会选择Shell环境变量作为此文本的解释器。Shell的注释以#号开头,后面接注释文字。
3.1 Shell环境变量
3.1.1 环境变量说明
环境变量用于描述该用户的操作环境下特定意义的变量,或者说是通过设置环境变量来配置用户的操作环境。Linux中的环境变量包括:用户所使用的Shell类型、工作的主目录、登录方式等。
环境变量分为系统环境变量和用户自定义环境变量。系统环境变量为在系统中有特定意义的环境变量,而且在不定义时也存在,系统环境变量可以重新进行赋值,如PATH环境变量就是系统环境变量,表明用户搜索执行码时所用到的路径。
Shell用户环境变量是每一个Linux用户定义在.profile或.bash_profile中生效的变量,同时还包括.bash_profile中包含执行脚本的环境变量。
环境变量包括定义和导出生效两部分,定义INFORMIXDIR环境变量,如INFORMIXDIR=/usr/informix,导出生效,如export INFORMIXDIR。只有导出生效的环境变量才能被引用,引用时需要在变量前面加$符号。
环境变量定义和导出有如下两种格式:
[1]name=value; export name
[2]name=value
export name
unset命令用来删除环境变量,如unset USERNAME是删除USERNAME变量。
环境变量使用alias进行变量重定义,重定义的环境变量不需要用export导出,定义方法如alias ygp=' cd ~/public/ygp' 。
3.1.2 用户常用的系统环境变量
表3-1列出了用户常用的系统环境变量,这些变量可以在.bash_profile中重新赋值,以便适应用户环境客户化的需要。
表3-1 用户常用系统环境变量表

下面是env.sh脚本,用来输出环境变量。
#! /bin/sh
echo "PWD:"$PWD
echo "path:"$PATH
echo "Logname:"$LOGNAME
echo "Shell:"$SHELL
echo "HOME:"$HOME
$ chmod u+x env.sh ——增加执行权限
$ ./env.sh ——执行,执行结果如下:
PWD:/home/zfb
path:/home/zfb/extra/bin:/home/zfb/extra/bin:/usr/lib/jdk/bin:/usr/kerberos/
bin
Logname:zfb
Shell:/bin/bash
HOME:/home/zfb
3.1.3 用户登录脚本示例
1.命令行提示符
下文约定脚本行首$为命令行的提示符。
$pwd
/home/zfb/public/ygp/shell
2.用户.bash_profile脚本部分内容
下面是一个用户下的.bash_profile脚本,后文会对此脚本进行解释说明。
INFORMIXDIR=/usr/informix
PATH=$PATH:$INFORMIXDIR/bin:$HOME/bin:.
PATH=$PATH:/opt/subversion/bin.
PS1=' Linux开发’:' $PWD>'
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
alias rm=' rm -i'
alias ygp=' cd /home/zfb/public/ygp'
export PATH INFORMIXDIR
(1)脚本具体说明如下。
上面的示例中PATH、INFORMIXDIR都是环境变量,其中,PATH是系统环境变量,INFORMIXDIR是用户自定义环境变量,环境变量必须用export命令导出才能生效。
环境变量可以递归赋值,上面示例中的PATH就进行了递归赋值。
上面示例中HOME为系统环境变量,其路径值是由增加用户时进行指定的。
PATH指出此用户下执行程序的搜索路径,不同路径名用“:”分开,以“.”结束搜索路径。当我们敲入一执行码时,./test系统会根据PATH路径去寻找该执行码。
.bash_profile可以包含其他可执行脚本,如上面.bashrc文件在当前目录存在,则解释执行。
alias是对变量进行重定义的命令。
(2).bash_profile文件何时执行?
[1]用户登录时立即执行。
[2]可以手工执行,在主目录下用. ./.bash_profile来进行重新执行操作。
3.2 Shell的符号、变量及运行
3.2.1 Shell中的符号及其含义
在Shell中,内置了许多特定符号,这些符号分别代表着特定的含义,下面是对这些符号的说明。
█ *:匹配0个和多个字符组成的串。
█ ? :匹配单个字符。
█ []:匹配的字符范围或列表。例如,$ls [a-c]*,将列出以a~c范围内字符开头的所有文件,$ls [e, m, t]*将列出以e、m或t开头的所有文件。
█ >:为重定向覆盖输出。
█ <:为重定向输入。
█ 》:为重定向添加。
█ —:管道命令,左边的输出作为右边的输入,如ls *.c—wc –l。
█ $#:传送给命令Shell的参数序号。
█ $-:在Shell启动或使用set命令时提供选项。
█ $? :上一条命令执行后返回的值。
█ $$:当前Shell的进程号。
█ $! :上一个子进程的进程号。
█ $@:所有的参数,每个都用双引号引起,以("$1""$2"...)的形式保存所有输入的命令行参数。
█ $*:所有的参数,用一个双引号引起整体,以("$1 $2..." )的形式保存所有输入的命令行参数。
█ $n:位置参数值,n表示位置。
█ $0:当前Shell名。
█ $:引用某个变量。
█ #:注释符号。
█ &:后台命令。
█ &&(布尔与)与条件符号:仅当其左边命令执行成功后,才执行其右边命令。
█ ——(布尔或)或条件符号:仅当其左边命令执行不成功时,才执行其右边命令。
█ ! (布尔非):反转命令的退出状态值。
█ ; :命令分隔符,在一个命令行中依次执行各个命令。
█ “…”:双引号表示除\、$、’和”外,由双引号引起来的字符为普通字符。
█ ‘…' :单引号引起来的字符均作为普通字符。
█ `…`:命令替换,倒引号引起来的字符串作为Shell命令执行。
█ ~:表示主目录。
█ .(内置句点):执行命令。
█ ..:表示上级目录。
█ [](内置表达式):计算算术表达式的值,相当于test。
█ {}:用来封装函数体。
█ \:表示转义字符。
█ <<:重定向输入。
█ <<-:重定向输入,输入去掉行首的Tab键。
3.2.2 “反引号命令替换
“内部整体作为Linux命令执行输出,例3-1和例3-2说明了反引号的用法。
【例3-1】 命令行的使用
$wc -l `ls test.c`
7 test.c
【例3-2】 base.sh脚本用例
(1)编写测试脚本base.sh
#! /bin/sh
#使用basename命令得到文件名
file=`basename $0`
echo "file name : $file"
#使用pwd命令得到当前路径
path=`pwd`
#将两个字符串连接起来
path=$path/$file
echo "full path : $path"
exit 0
(2)增加脚本执行权限
$chmod u+x base.sh
(3)使用./base.sh,执行结果如下:
file name : base.sh
full path : /home/zfb/public/ygp/shell/base.sh
3.2.3 Shell变量
1.变量特点
Shell中变量特点如下:
[1]无需定义,可直接使用。
[2]Shell大小写敏感。
[3]$为Shell保留字符,变量被其他变量引用时前面需要加$。
[4]变量赋值“=”两边是没有空格的,不然会带来错误。
[5]如果在赋值语句中,右边没有任何信息,那么这个变量为一个空字符串;另外仅定义声明而没有赋值的变量,默认也是一个空字符串。
[6]Shell只有两种类型,一种是整型数字,一种字符串。整型数字必须所有位都为数字,类型Shell解释器自动识别。
[7]如果一个变量中含空格、制表位、换行符,则要用双引号引起,不然会出错。
[8]字符串左右应加双引号“”。
[9]Shell内置9个位置变量,$1~$9。
2.引用变量三种方法
Shell中引用变量有如下三种方法:
[1]使用双引号引用变量
"$var"
[2]使用大括号引用变量
{$var}
[3]直接引用变量
$var
3.用户变量赋值
(1)用户变量赋值种类
用户变量赋值有如下4种方法:
[1]直接赋值
user=meng #字符串赋值
null= #空串赋值
number=12345 #数字赋值
[2]变量赋值
var1=$user
var2=$var1
[3]read读入
read 变量1[变量2]
read var1 var2
#当输入abc defa并敲回车后,变量var1和vae2就被分别赋值abc和def了
[4]参数置换方式为变量赋值
${变量:-字串} 如果变量被设定并非空,则返回是变量的值,否则是字符串的值。
${变量:+字串} 如果变量被设定并非空,则返回是字串的值,否则是变量的值(即空值)。
${变量:=字串} 如果变量被设定并非空,则返回变量的值,否则是字符串的值,同时变量被设成字符串。
${变量:?字串} 如果变量被设定并非空,则返回变量的值,否则返回报错。
(2)编写测试程序var.sh,测试观察变量变化效果:
#! /bin/sh
var1=abc
var2=${var1:-"hello"}
echo "var1="$var1 "var2="$var2
var3=
var4=${var3:-"hello"}
echo "var3="$var3 "var4="$var4
var5=abc
var6=${var5:+"hello"}
echo "var5="$var5 "var6="$var6
var8=${var7:+"hello"}
echo "var7="$var7 "var8="$var8
var9=abc
var10=${var9:="hello"}
echo "var9="$var9 "var10="$var10
var11=
var12=${var11:="hello"}
echo "var11="$var11 "var12="$var12
var13=abc
var14=${var13:? "hello"}
echo "var13="$var13 "var14="$var14
var16=${var15:? "hello"}
echo "var15xx="$var15"
echo "game over"
增加执行权限并执行:
$chmod u+x var.sh
$./var.sh
执行结果如下:
var1=abc var2=abc
var3= var4=hello
var5=abc var6=hello
var7= var8=
var9=abc var10=abc
var11=hello var12=hello
var13=abc var14=abc
./var.sh: line 27: var15: hello
4.位置变量
Shell脚本可以向脚本命令行传递参数。在Shell中,$0表示执行的程序名,$1~$9是传递的命令行参数,Shell脚本最多能传递9个参数,$1~$9称为Shell内置的位置变量。shift会让位置参数左移一位,即$2变$1、$3变$2。
(1)编写测试程序arg.sh,测试位置变量变化效果:
#! /bin/sh
echo NO.0 $0
echo NO.1 $1
echo NO.2 $2
echo NO.3 $3
echo NO.4 $4
echo NO.5 $5
echo NO.6 $6
echo NO.7 $7
echo NO.8 $8
echo NO.9 $9
shift
echo shifting
echo NO.0 $0
echo NO.1 $1
echo NO.2 $2
echo NO.3 $3
echo NO.4 $4
echo NO.5 $5
echo NO.6 $6
echo NO.7 $7
echo NO.8 $8
echo NO.9 $9
(2)增加脚本执行权限并执行:
$ chmod u+x arg.sh
$./arg.sh arg1 arg2 arg3 arg4
(3)执行结果如下:
NO.0 ./arg.sh
NO.1 arg1
NO.2 arg2
NO.3 arg3
NO.4 arg4
NO.5
NO.6
NO.7
NO.8
NO.9
shifting
NO.0 ./arg.sh
NO.1 arg2
NO.2 arg3
NO.3 arg4
NO.4
NO.5
NO.6
NO.7
NO.8
NO.9
5.字符串变量
字符串变量左右应加双引号,否则会报错。
(1)编写测试程序str.sh,测试字符串变量变化效果:
#! /bin/sh
string1=good morning #没有双引号引起字符串,执行时会报错
string2="good morning"
echo "string1:"$string1
echo "string2:"$string2
unset string2 #清空变量的值
echo "string2:"$string2
(2)执行sh str.sh,执行结果如下:
str.sh: 3: morning: not found
string1:
string2:good morning
string2:
6.表达式求值
expr命令用来对表达式进行求值,其操作符和运算符之前必须有空格隔开。在Shell脚本中,数学表达式直接运算需要用两对圆括号括起来。
【例3-3】 expr.sh脚本。
(1)编写测试程序expr.sh,测试表达式求值的效果:
#! /bin/sh
expr 3 + 9
expr 9 % 2
expr 3 \* 2 #\表示转义
sum=$((3+2))
echo sum:$sum
mod=$(( 3 % 2 ))
echo mod:$mod
mul=$(( 3 * 2 ))
echo mul:$mul
a=3
c=$(($a +8))
echo c:$c
(2)执行sh expr.sh,执行结果如下:
12
1
6
sum:5
mod:1
mul:6
c:11
【例3-4】 let.sh脚本。
在Shell中,使用let内置命令可以完成对数值的运算。
(2)编写测试程序let.sh,测试let命令的使用:
#! /bin/bash
let a=11
let a=a+5
echo "11 + 5 = $a"
let "a <<= 3" # let "a = a << 3"
echo "\"\$a\" (=16) left-shifted 3 places = $a"
let "a /= 4" # let "a = a /4"
echo "128 /4 = $a"
let "a -= 5" # let "a = a - 5"
echo "32 - 5 = $a"
let "a *= 10" # let "a = a * 10"
echo "27 * 10 = $a"
let "a %= 8" # let "a = a % 8"
echo "270 modulo 8 = $a (270 /8 = 33, remainder $a)"
exit 0
(2)执行./let.sh,执行结果如下:
11 + 5 = 16
"$a" (=16) left-shifted 3 places = 128
128 /4 = 32
32 - 5 = 27
27 * 10 = 270
270 modulo 8 = 6 (270 /8 = 33, remainder 6)
3.2.4 Shell脚本执行
Shell命名规则一般是filename.sh,结尾以.sh表示文件类型。
Shell脚本有两种执行方式:一种为sh filename.sh,第二种执行方式是对文件增加执行权限然后敲入执行码进行执行,如chmod u+x filename.sh; ./filenamel.sh。
3.2.5 Shell退出状态
1.Shell退出状态说明
图3-1展示了Shell脚本的执行流程及退出状态的保存方法。由于Shell解释器创建子进程执行Shell脚本,因此Shell进程是Shell脚本的父进程,所以Shell可以得到子进程的状态,Shell脚本内命令同理。退出状态必须是十进制数,范围必须是0~255, Shell脚本执行成功返回0,报错返回非0。
$? 是一个Shell中的内置变量,代表着最后一次运行进程的退出状态码。

图3-1 Shell退出状态图
2.Shell退出状态举例
下面的用例用来说明Shell的退出状态。
(1)编写测试程序test.c:
#include <stdio.h>
int main(void)
{
printf("test program\n") ;
return 35 ;
}
(2)在Shell命令行中编译该程序:
gcc test.c -o test
(3)编辑exit.sh脚本,脚本内容如下:
#! /bin/sh
#exit.sh 测试不同进程的退出状态码
./test #执行test程序
echo statas=$?
exit 45
(4)执行exit.sh脚本:
sh exit.sh
(5)执行结果如下:
test program
statas=35
(6)查看最后一次退出状态:
$ echo $?
45
3.3 Shell的输入和输出
3.3.1 Shell的输入
Shell用来输入的指令是read函数,其格式说明如下:
read 变量1[变量2]
利用read函数可以交互式地为变量赋值,当然也可以通过制表符或空格为多个变量赋值,使用read函数读入变量的三种情况说明如下:
[1]如果变量的个数多于输入字符串的字符个数,则依次赋值,剩下的变量取空值。
[2]如果变量的个数等于输入字符串的字符个数,则一一对应赋值。
[3]如果变量的个数小于输入字符串的字符个数,则除依次赋值外,最后一个变量接纳剩下的所有字符串。
【例3-5】 read函数的例子
read.sh源代码如下:
#! /bin/sh
echo "input your name and age:"
read name age
echo " name is :"$name
echo "age is :"$age
上述代码的意思是从终端中输入两个值,分别赋值给name和age这两个变量,然后利用echo函数将其输出,执行结果如下:
$sh read.sh
input your name and age:
sky 3000 --键盘输入
name is :sky
age is :3000
3.3.2 Shell的输出
1.echo函数介绍
echo是Shell中实现文本和变量输出的函数,能够输出提示信息,显示执行结果和报告执行状态等。
echo函数后面的各参数之间以空格隔开,以换行符终止。如果数据之间须保留多个空格,则要用双引号把它们整个给引起来,以便Shell对它们进行正确的操作。
echo函数中,还定义了一组转义字符,用于输出控制或打印无法显示的字符。在使用转义字符的时候,要加入“-e”选项。
2.输出转义字符
表3-2列出了Shell中的转义字符并对其作用进行了说明,echo函数利用这些转义字符,可以打印出无法显示的字符。
表3-2 转义字符及其作用说明表

3.4 Shell测试条件
Shell提供两种测试条件的方式,利用test命令和利用方括号形式,其定义格式如下:
test –d $dir
[-d $dir]
这两种方式完全等价,即[表达式]等价于test表达式。
1.条件测试分类
条件测试可以分为四类:字符串测试、数值测试、逻辑测试、文件属性测试。
2.字符串测试
表3-3列出了对字符串测试操作的说明,字符串测试的作用是测试字符串操作的返回值。注意使用=、! =、<、>这些符号时,两边需要加空格。
表3-3 字符串测试表

下面以test_str1.sh、test_str2.sh两个用例来说明字符串测试的使用方法。
【例3-6】 test_str1.sh用例。
(1)编写测试程序test_str1.sh:
#! /bin/sh
echo please input name:
read name
if test $name
then
echo "name is :"$name
else
echo "name is null"
fi
(2)执行sh test_str1.sh,执行结果如下:
please input name: --直接回车
name is null
【例3-7】 test_str2.sh用例。(1)编写测试程序test_str2.sh:
#! /bin/sh
str1="happy"
str2="happy"
str3=
#测试str1与str2相等
test $str1 = $str2
echo $?
#测试str3是否是空串
test -z $str3
echo $?
#测试str1与str2不相等
test $str1 ! = $str2
echo $?
#使用test另一种方式[]来实现上述三种判断
echo "using [ ] "
#测试str1与str2相等
[ $str1 = $str2 ]
echo $?
#测试str3是否是空串
[ -z $str3 ]
echo $?
#测试str1与str2不相等
[ $str1 ! = $str2 ]
echo $?
(2)执行sh test_str2.sh,执行结果如下:
0
0
1
using [ ]
0
0
1
请大家扩充Shell脚本功能,把所有参数使用一遍,进行举一反三,以下同。
3.数值测试
表3-4列出了数值测试的使用方法,数值测试主要用于两个数值之间大小的比较。
表3-4 数值测试表

下面以test_number.sh用例来说明数值的使用方法。
【例3-8】 test_number.sh用例。
(1)编写测试程序test_number.sh:
#! /bin/sh
a=1
b=3
test $a -eq $b
echo $?
test 6 -eq 6
echo $?
#使用[]方式完成上述功能
echo "using [ ] "
[ $a -eq $b ]
echo $?
[ 6 -eq 6 ]
echo $?
(2)执行sh test_number.sh,执行结果如下:
1
0
using [ ]
1
0
4.逻辑测试
表3-5列出了逻辑测试的使用方法。逻辑运算符的作用是进行逻辑语句的判断,也就是对“与”、“或”、“非”条件的判断。逻辑表达式中优先级的顺序是:“()”运算符>“! ”运算符>“-a”运算符>“-o”运算符。
表3-5 逻辑测试表

下面以test_log.sh用例来说明逻辑测试的使用方法。
【例3-9】 test_log.sh用例。
(1)编写测试脚本test_log.sh:
#! /bin/sh
[ -x $1 -a $0 ] #检查两个文件是否同时可执行,其中一个是Shell脚本本身
echo $?
[ -w $1 -o $0 ] #检查两个文件是否有一个可写,其中一个是Shell脚本本身
echo $?
(2)增加脚本执行权限:
$chmod +x test_log.sh
(3)使用ls查看文件参数:
$l test_log.sh test.c
-rw-rw-r-- 1 zfb zfb 86 12月 23 10:11 test.c
-rwxrwxr-x 1 zfb zfb 164 12月 23 19:25 test_log.sh
(4)执行./test_log.sh test.c,执行结果如下:
$./test_log.sh test.c
1
0
5.文件属性测试
表3-6列出了文件属性测试的使用方法,文件属性测试用于测试文件类型。
表3-6 文件属性测试表

下面以test_file.sh用例来说明文件属性测试的使用方法。
【例3-10】 test_file.sh用例。
(1)编写测试脚本test_file.sh:
#! /bin/sh
file=test.c
[ -r $file ] #测试读权限
echo $?
[ -w $file ] #测试写权限
echo $?
[ -x $file ] #测试执行权限
echo $?
[ -f $file ] #测试是否是文件
echo $?
[ -d $file ] #测试是否是目录
echo $?
(2)增加脚本执行权限:
$chmod +x test_file.sh
(3)使用ls查看test.c文件:
$l test_log.sh test.c
-rw-rw-r-- 1 zfb zfb 86 12月 23 10:11 test.c
(4)使用./test_file.sh查看执行结果:
0
0
1
0
1
3.5 Shell的流程控制结构
本节介绍Shell流程控制语句,Shell流程控制语句有if语句、case语句、while语句、until语句、for语句和跳转语句(break、continue、exit),下文将介绍这些语句的语法形式和使用方法。
3.5.1 if语句
if语句条件返回值为0表示条件测试为真;如果条件命令执行不成功,其返回值不等于0,条件测试就为假。
if语句的语法形式如下:
if 测试条件1
then 命令或命令表
elif 测试条件2
then 命令或命令表
else 命令或命令表
fi
其中,elif部分和else部分可以省去,一种结构可以演化为三种结构。下面以eq_str.sh、file_test.sh两个用例来说明if语句的使用方法。
【例3-11】 eq_str.sh用例。
if语句条件为文件属性测试和字符串测试时需要使用“if[[文件属性测试或字符串测试]]”这种格式。
(1)编写测试脚本eq_str.sh:
str1="happy new year"
str2="happy new year"
if [[ $str1 = $str2 ]]
then
echo "they are equal"
fi
(2)增加脚本执行权限:
$chmod u+x eq_str.sh
(3)执行 ./eq_str.sh,执行结果如下:
they are equal
【例3-12】 file_test.sh用例。
(1)编写测试脚本file_test.sh:
#! /bin/sh
if [ -f $1 ] #普通文件
then
echo "regular file"
elif [ -d $1 ] #目录文件
then
echo "dir file" #链接文件
elif [ -l $1 ]
then
echo "symlink file"
fi
(2)增加脚本执行权限:
$chmod u+x file_test.sh
(3)执行 ./file_test.sh,执行结果如下:
regular file
3.5.2 case语句
case语句是一种多重判断语句,类似于多个if-elif操作。case语句的执行原理,是将字符串与各个模式顺次匹配,若满足条件则执行,否则继续查找,如果没有匹配成功的,则不执行任何语句,直接退出。
使用case语句时,应注意以下事项:
[1]每个模式匹配后的处理语句,是以“; ; ”两个分号进行结束。
[2]模式串表达式应该有唯一性,不要出现几个模式串表达式能够相互转换的情况,这样不利于语句调试。
[3]一个模式表达式可以包含多个模式串,但要用“—”隔开,“—”在这里是“或”的关系。
case语句是一个基于模式匹配的多路分支结构,其一般语法形式如下:
case word in
pattern 1) 命令表1; ;
pattern 2) 命令表2; ;
…
*) 缺省命令表;;
esac
下面以case.sh、case_menu.sh两个用例来说明case语句的使用方法。
【例3-13】 case.sh用例。
(1)编写测试脚本case.sh:
#! /bin/sh
echo please input your name:
read name
case $name in
Tom)
echo your name is tom ; ;
Jim)
echo your name is Jim ; ;
*)
echo "sorry we don' t know your name" ; ;
esac
(2)执行sh case.sh,执行结果如下:
please input your name:
Jim --输入
your name is Jim
【例3-14】 case_menu.sh用例。
(1)编写脚本case_menu.sh:
#! /bin/sh
echo "1 save"
echo "2 load"
echo "3 exit"
echo #输出一个换行
echo "please input chioce"
read chioce
#快捷键如下
#s--存储(save)
#l--加载(load)
#e--退出(exit)
case $chioce in
1 — S — s)
echo "save"; ;
2 — L — l)
echo "load"; ;
3 — $ — s)
echo "exit"; ;
*) #其他的输入情况
echo "invalid choice"
exit 1; ;
esac
exit 0
(2)执行sh case_menu.sh,执行结果如下:
1 save
2 load
3 exit
please input chioce
s
save
3.5.3 while语句
while语句的执行过程是:先测试条件语句是否为真,若为真,则执行循环体,当执行完当前命令后,再进行条件测试,直到条件结果为假,循环结束。
这里的条件测试语句既可以是test语句,也可以是运行命令的返回值,若返回值大于0,则表示条件为真,否则条件为假。
while语句的语法形式如下:
while 测试条件
do
命令表
done
下面以ls_file.sh、read_while.sh两个用例来说明while语句的使用方法。
【例3-15】 ls_file.sh用例。
执行时每隔1秒显示test.c文件大小,使用Ctrl+C结束脚本执行。
#! /bin/sh
while true
do
sleep 1 ;
ls -l test.c
done
【例3-16】 read_while.sh用例。
(1)编写测试脚本read_while.sh:
#! /bin/sh
type="";
echo input your type:
read type
while [ $type ! = "quit" ]
do
echo "your input is :"$type
echo input your type:
read type
done
(2)执行sh read_while.sh,执行结果如下:
input your type:
why
your input is :why
input your type:
quit
3.5.4 until语句
until语句在形式上是while语句的一种变形。对于until语句中的条件测试语句,如果条件为假,则执行,否则不执行。
until语句的语法形式如下:
until 测试条件
do
命令表
done
下面以until.sh用例来说明until语句的使用方法。
【例3-17】 until.sh用例。
(1)编写测试脚本until.sh:
#! /bin/sh
type="";
echo input your type:
read type
until [ $type = "quit" ]
do
echo "your input is :"$type
echo input your type:
read type
done
(2)执行sh until.sh,执行结果如下:
input your type:
what
your input is :what
input your type:
quit
3.5.5 for语句
1.for语句统一语法形式
for语句是Shell中经常使用的循环语句,for语句统一的语法形式如下:
for 变量名 in 循环参数列表
do
命令表
done
上面统一的语法形式,可以演化成下面三种表现形式。
2.数组作为循环参数
for语句循环参数是数组表时,语法形式如下:
for 变量名 in 数组表
do
命令表
done
下面以for_array.sh用例说明for语句循环参数是数组表时的用法。
【例3-18】 for_array.sh用例。
(1)编写测试脚本for_array.sh:
#! /bin/sh
for word in Hello to you
do
echo $word
done
array="what who where"
for word in $array
do
echo $word
done
file=`ls`
for word in $file
do
echo $word
done
(2)执行sh for_array.sh,执行结果如下:
Hello
to
you
what
who
where
args.sh
case_menu.sh
3.以正则表达式作为循环参数
for语句循环参数是正则表达式时,语法形式如下:
for 变量 in 正则表达式
do
命令表
done
下面以for_regular.sh用例说明for语句循环参数是正则表达式时的用法。
【例3-19】 for_regular.sh用例。
(1)编写测试脚本for_regular.sh:
#! /bin/sh
for file in /dev/tty[1-3]
do
ls -l $file
done
(2)执行sh for_regular.sh,执行结果如下:
crw------- 1 root root 4, 1 12月 23 08:24 /dev/tty1
crw------- 1 root root 4, 2 12月 23 08:24 /dev/tty2
crw------- 1 root root 4, 3 12月 23 08:24 /dev/tty3
4.位置参数方式作为循环参数
for语句循环参数是位置参数时,语法形式如下:
for 变量 in $*
do
命令表
done
下面以for_args.sh用例说明for语句循环参数是位置参数时的用法。
【例3-20】 for_args.sh用例。
(1)编写测试脚本for_args.sh:
#! /bin/sh
for word in $*
do
echo $word
done
(2)执行sh for_args.sh arg1 arg2 arg3,执行结果如下:
arg1
arg2
arg3
3.5.6 跳转语句
1.break语句
break语句是一个退出循环的命令,主要用于多层循环的嵌套,它的一般使用形式为:
break [n]
其中,n用来表示跳出几层循环,默认值为1,即退出本次循环。
2.continue语句
continue语句与break语句有相同之处,都用于终止本次循环,区别在于break语句是退出整个循环,即不再执行剩下的循环操作,而continue语句是停止本次循环体的执行,转向循环体中的下一次循环。
continue语句的语法为:
continue [n]
其中,n用来表示跳出几次循环,默认值为1。
3.exit语句
exit语句是退出正在执行的Shell脚本,可以主动指定返回值。
exit语句的语法为:
exit [n]
其中,n是主动设定的返回值,如果未显式给定n的值,则该值默认取最后一次命令的执行状态作为返回值。
3.6 Shell数组
在Shell中可以使用数组来存储同类型的数值集合。一般Shell中支持一维数组,但不限定数组的具体大小,数组的使用方式是采用指定下标。数组中的下标往往是由0开始编号的,一般可以采用直接给出下标的方式,也可以通过算术表达式的方式指定下标。但不管采用哪种方式,都要记住一点,不能给出一个小于0,或大于数组已存元素个数的值,不然会产生越界的错误。
在数组的操作中,取值的一般方式是:
${数组名[下标值]}
相对应的数组的赋值操作的一般方式是:
数组名[下标值]=值
对于数组的赋值,可以采用单个元素逐一进行赋值的方式,也可以采用一次性赋值的方式,但要注意,一次性赋值时,值与值之间要用空格隔开,赋值方法如下:
数组名=(value1 value2 value3…)
在数组中可以使用*或@符号来代替下标,这里*或@就是上文说明的通配符。
下面以array.sh用例来说明Shell数组的使用方法。
【例3-21】 array.sh用例。
(1)编写测试脚本array.sh:
name=(tom jim jane test1)
echo "name[0] is :"${name[0]}
echo "name[1] is :"${name[1]}
echo "name[2] is :"${name[2]}
echo "name[3] is :"${name[3]}
echo "name set is :"${name[*]}
echo "name set is :"${name[@]}
(2)增加脚本执行权限:
$chmod u+x array.sh
(3)执行 ./array.sh,执行结果如下:
name[0] is :tom
name[1] is :jim
name[2] is :jane
name[3] is :test1
name set is :tom jim jane test1
name set is :tom jim jane test1
3.7 Shell函数
函数代表一种模式化的设计思想,可以将一些常用的、内聚度较高的操作,封装成函数,在需要时进行调用。
在Shell中可以按照一定的规则,定义一组命令集,组成一个过程,这个过程有过程名,在执行这个命令集时,只需要通过使用这个过程名,就能实现执行过程中相关命令的功能,这个过程就叫函数。
函数其实是一个模块化的概念,按一定功能,设定一个模块,在使用时,只须指定模块名,便能达到操作的目的。Shell中的函数一次定义,可以多次使用。执行函数操作,并不需要创建新进程,而是在当前的Shell进程中运行。
1.Shell函数定义原型
Shell中函数定义方法如下:
function 函数名()
{
语句
}
在这里,关键字function是可以不显式指定的。在使用函数时,要注意函数应先定义再使用,调用函数时,只须指定函数名,不用加后面的()。
下面以show.sh用例来说明Shell函数的使用方法。
【例3-22】 show.sh用例。
(1)编写测试脚本show.sh:
show()
{
echo $a $b $c
echo $1 $2 $3
}
a=111
b=222
c=333
d=444
f=555
e=666
echo "Function Begin"
show $d $e $f
echo "Function Finished"
(2)执行sh show.sh,执行结果如下:
Function Begin
111 222 333
444 666 555
Function Finished
2.Shell函数的参数与返回值
[1]变量传递的两种方法
变量传递有两种方法:其一为变量直接传递法,数量不限,如上文show.sh中变量a、b、c;其二为位置参数法,数量最多9个,如上文show.sh中$1、$2、$3。
[2]函数返回值
函数执行到最后一条语句后就会退出,但也可以主动调用return语句来实现提前退出。return语句的使用方式相对简单,原型为:return n,其中,n的值可以主动指定,若采用默认的方式,则退出值为最近一个命令的退出码。
3.8 I/O重定向
1.Linux文件描述
“一切皆是文件”是UNIX/Linux的基本设计哲学之一。不仅普通的文件,目录、字符设备、块设备、套接字等在UNIX/Linux中都是以文件的形式被对待的。它们虽然类型不同,但是对其提供的却是同一套操作界面。
在Linux中每一个进程都由task_struct(参见图12-4进程task_struct文件结构)数据结构来定义。task_struct数据结构的files选项指向打开文件描述符。每一个进程默认打开三个文件。这三个文件描述符是files_struct数据结构中的fd[0]、fd[1]和fd[2],即文件描述符0、1、2,分别指向标准输入(键盘)、标准输出(屏幕)、标准错误(屏幕)。
在Linux系统中,文件描述符(File Descriptor)是用一个数字来表示。表3-7列出了进程默认打开的文件描述符表。
表3-7 进程默认打开文件描述符表

2.基本I/O重定向
I/O重定向表示重新定位数据的流向,下面说明的是常见的I/O重定向方法。
cmd > file:把stdout重定向到file文件中。
cmd >> file:把stdout重定向到file文件中(追加)。
cmd 1> file:把stdout重定向到file文件中。
cmd > file 2>&1:把stdout和stderr一起重定向到file文件中。
cmd 2> file:把stderr重定向到file文件中。
cmd 2>> file:把stderr重定向到file文件中(追加)。
cmd >> file 2>&1:把stdout和stderr一起重定向到file文件中(追加)。
cmd <file>file2: cmd命令以file文件作为stdin,以file2文件作为stdout。
cmd < file:以file文件作为stdin。
cmd << delimiter:从stdin中读入,直至遇到delimiter分界符。
cmd <<- delimiter:从stdin中读入,直至遇到delimiter分界符,输入去掉行首Tab键。
3.高级I/O重定向
下面说明复杂I/O重定向的方法,较少使用,了解即可。
>&n:使用系统调用dup(2)复制文件描述符n,并把结果用做标准输出。
<&n:标准输入复制自文件描述符n。
<&-:关闭标准输入(键盘)。
>&-:关闭标准输出。
n<&-:表示将文件描述符n输入关闭。
n>&-:表示将文件描述符n输出关闭。
cmd 2>file:运行一个命令并把错误输出(文件描述符2)定向到file。
cmd 2>&1:运行一个命令并把它的标准输出和输入合并(严格地说是通过复制文件描述符1来建立文件描述符2,但效果通常是合并了两个流)。
exec 1>outfilename:打开文件outfilename作为stdout。
exec 2>errfilename:打开文件errfilename作为stderr。
exec 0<&-:关闭fd0。
exec 1>&-:关闭fd1。
exec 5>&-:关闭fd5。
3.9 Shell内置命令
1.内置命令列表
表3-8列出了Shell的内置命令及其说明,Shell的内置命令是在Shell脚本中能使用的命令。
表3-8 Shell内置命令列表

2.trap命令
trap命令用于指定在接收到信号后将要采取的行动,trap命令的一种常见用途是忽略某些信号或在脚本程序被信号中断时完成清理工作。历史上,Shell总是使用数字来代表信号,现在提倡使用信号的名字,使用信号名时需要省略SIG前缀。在命令提示符下输入命令trap –l可以查看信号编号及其关联的名称。
“信号”是指那些被异步发送到一个程序的事件。默认情况下,它们通常会终止一个程序的运行。
trap命令使用格式如下:
trap ' command' signal-list
其中trap命令的参数分为两部分,前一部分是接收到指定信号时将要采取的行动,后一部分是要处理的信号名。
(1)trap捕捉到信号之后,可以有以下三种反应方式。
[1]执行一段程序来处理这一信号。
[2]接受信号的默认操作。
[3]忽视这一信号。
(2)trap对上面三种方式提供了三种基本形式。
[1]设置信号的处理方式,使用第一种形式:
trap ' command' signal-list
trap "command" signal-list
[2]恢复信号的默认操作,使用第二种形式:
trap signal-list
[3]忽略信号,使用第三种形式:
trap " " signal-list
在第一种形式trap命令中Shell接收到signal-list清单中数值相同的信号时,将执行引号中的命令串。
使用trap时有如下注意事项:
[1]对信号11(段违例)不能捕捉,因为Shell本身需要捕捉该信号去进行内存的转存。
[2]在捕捉到signal-list中指定的信号并执行完相应的命令之后,如果这些命令没有将Shell程序终止的话,Shell程序将继续执行收到信号时所执行的命令后面的命令,这样将很容易导致Shell程序无法终止。
[3]在trap语句中,单引号和双引号是不同的,当Shell程序第一次碰到trap语句时,将把command中的命令扫描一遍,此时若command是用单引号引起来的话,那么Shell不会对command中的变量和命令进行替换。
表3-9列出了能被trap命令捕获的比较重要的信号列表及其说明。
表3-9 能够被捕获的比较重要信号列表

通常需要忽略的信号有四个,即HUP、INT、QUIT、TSTP,也就是信号1、2、3、24,使用下面的语句可以使这些信号被忽略。
trap "" 1 2 3 24或trap "" HUP INT QUIT TSTP
3.date命令
date命令的功能是显示和设置系统日期和时间。
date命令语法形式如下,查看时间时格式需要带+号。
date [选项] [+格式]
date常见的选项说明如下:
[1]-d datestr, --date datestr显示由datestr描述的日期。
[2]-s datestr, --set datestr设置datestr描述的日期。
[3]-u, --universal显示或设置通用时间。
表3-10列出date设置和显示时间时格式选项的种类及其说明。这里需要说明的是,只有超级用户才能用date命令设置时间,一般用户只能用date查看时间。
表3-10 date时间格式说明表

例1:用指定的格式显示时间。
$ date ' +This date now is =>%x , time is now =>%X , thank you ! '
This date now is =>11/12/99 , time is now =>17:53:01 , thank you !
例2:用默认格式显示当前的时间。
# date
Fri Nov 26 15:20:18 CST 1999
例3:设置时间为下午14点36分。
# date -s 14:36:00
Fri Nov 26 14:15:00 CST 1999
例4:设置时间为1999年11月28号。
# date -s 991128
Sun Nov 28 00:00:00 CST 1999
例5:按格式显示下一天的时间。
# date -d next-day +%Y%m%d
20060328
3.10 实用Shell脚本
下面三个Shell脚本是作者从业中经常使用到的脚本。file_mod.sh是去掉文件中的回车符,当Windows下的文本文件传到Linux下时,文件每行会多一个回车符,利用file_mod.sh可以批量修改文件,去掉文件尾的回车符;stopproc.sh脚本是根据进程名杀死一个或多个进程的脚本,此脚本相当于实现Linux下的pkill命令的功能;backup.sh脚本是实用数据备份脚本,这是一个技术含量很高而且非常实用的脚本,此脚本实现了按需备份(排除不需要备份的文件)和自动ftp传输功能。
1.去掉文件中回车符
【例3-23】 file_mod.sh用例。
file="aa.txt aaa.txt"
echo $file
# ^[表示ESC键,需要先输ctrl+v,再按ESC键
# ^M表示回车,需要先输ctrl+v,再按M键
for filename in $file
do
echo $filename
vi $filename<<-EOF
:%s/aaa/AAA/g^M^[
:wq^M^[
EOF
done
2.根据进程名kill进程
【例3-24】 stopproc.sh用例。
#! /bin/sh
if [ $# -lt 1 ]
then
echo "usage ./simstop.sh proc_name"
exit 1
fi
PROCESS=`ps -ef—grep $1—grep -v grep—grep -v PPID—awk ' { print $2}' `
for i in $PROCESS
do
echo "Kill the $1 process [ $i ]"
kill -9 $i
done
3.实用数据备份脚本
【例3-25】 backup.sh用例。
#! /bin/sh
FAPWORKDIR=/home/bep
cd ${FAPWORKDIR}
DATE=`date +"%Y%m%d"`
rm -f backup/bep.$DATE.tar backup/exclude.list
# 产生排除文件列表
# *.tar *.Z *.gz *.rar *.o .* 等文件都不备份
find -L include -name ' *.tar' -o -name ' *.Z' -o -name ' *.gz' -o -name ' *.rar' -o
-name ' .*' >> backup/exclude.list
find -L src -name ' *.tar' -o -name ' *.Z' -o -name ' *.gz' -o -name ' *.rar' -o
-name ' .*' -o -name ' *.o' >> backup/exclude.list
tar-chvf backup/bep.$DATE.tar-X backup/exclude.list.profile include src scr
ipts lib
compress -F backup/bep.$DATE.tar
ftp -n 192.168.31.125 <<!
user bepbak bepbak
lcd backup
cd 110_bak
bin
put bep.$DATE.tar.Z
bye
!
rm -f backup/bep.$DATE.tar backup/bep.$DATE.tar.Z backup/exclude.list