Bourne Shell及shell编程
作者: 何斌武 ([email][email protected][/email],大连理工大学网络中心,April 1999.)
修改: shark巨菜(原文较老,经实践后加入了部分注释)
介绍:Bourne Shell 基础及其他很多有用的特性,shell编程及组织。
主要内容:
.shell基础 基本介绍,环境,选项,特殊字符
.shell变量 用户定义变量,环境变量,位置变量(shell 参数)
.shell script编程
条件测试,循环及重复控制
.shell定制
1.shell基础知识
作者:Stephen Bourne 在Bell实验室开发
建议:man sh 查看相关UNIX上的改进或特性
(1)shell提示符及其环境
/etc/passwd文件
提示符:$
/etc/profile $HOME/.profile
(2)shell执行选项
-n 测试shell script语法结构,只读取shell script但不执行
-x 进入跟踪方式,显示所执行的每一条命令,用于调度
-a Tag all variables for export
-c "string" 从strings中读取命令
-e 非交互方式
-f 关闭shell文件名产生功能
-h locate and remember functions as defined( 找到并记住所定义的功能 )
-i 交互方式
-k 从环境变量中读取命令的参数
-r 限制方式
-s 从标准输入读取命令
-t 执行命令后退出(shell exits)
-u 在替换中如使用未定义变量为错误
-v verbose,显示shell输入行
这些选项可以联合使用,但有些显然相互冲突,如-e和-i.
(3)受限制shell(Restircted Shell)
sh -r 或 /bin/rsh
不能执行如下操作:cd, 更改PATH,指定全路径名,输出重定向,因此可以提供一个较好的控制和安全机制。通常rsh用于应用型用户及拨号用户,这些用户通常是看不到提示符的。通常受限制用户的主目录是不可写的。
不足:如果用户可以调用sh,则rsh的限制将不在起作用,事实上如果用户在vi及more程序中调用shell,而这时rsh的限制将不再起作用。
(4)用set改变 shell选项
用户可以在$提示符下用set命令来设置或取消shell的选项。使用-设置选项,+取消相应选项,大多数UNIX系统允许a,e,f,h,k,n,u,v和x的开关设置/取消。
set -xv
启动跟踪方式;显示所有的命令及替换,同样显示输入。
set -tu
关闭在替换时对未定义变量的检查。
echo $-
显示所有已设置的shell选项。Fedora9默认是himBH
(5)用户启动文件 .profile
PATH=$PATH:/usr/loacl/bin; export PATH
(6)shell环境变量
CDPATH 用于cd命令的查找路径
HOME /etc/passwd文件中列出的用户主目录
IFS Internal Field Separator,默认为空格,tab及换行符
MAIL /var/mail/$USERNAME mail等程序使用
PATH
PS1,PS2 默认提示符($)及换行提示符(>)
TERM 终端类型,常用的有vt100、ansi、vt200、xterm、linux等。
示例:
(5)用户启动文件 .profile
PATH=$PATH:/usr/loacl/bin; export PATH
(6)shell环境变量
CDPATH 用于cd命令的查找路径
HOME /etc/passwd文件中列出的用户主目录
IFS Internal Field Separator,默认为空格,tab及换行符
MAIL /var/mail/$USERNAME mail等程序使用
PATH
PS1,PS2 默认提示符($)及换行提示符(>)
TERM 终端类型,常用的有vt100、ansi、vt200、xterm、linux等。
示例:
$PS1="test:";export PS1
测试失败。
test: PS1="\$";export PS1 测试失败。
#echo $MAIL
/var/mail/username
(7)保留字符及其含义
$ 是shell变量名的开始,如$var。
| 管道,将标准输出转到下一个命令的标准输入。
# 注释开始,root登陆提示符。
& 在后台执行一个进程。
? 匹配一个字符。
* 匹配0到多个字符(与DOS不同,可在文件名中间使用,并且含。)
$- 使用set及执行时传递给shell的标志位。
$! 最后一个子进程的进程号。
$! 执行上一个背景指令的PID。--网上的信息
test: PS1="\$";export PS1 测试失败。
#echo $MAIL
/var/mail/username
(7)保留字符及其含义
$ 是shell变量名的开始,如$var。
| 管道,将标准输出转到下一个命令的标准输入。
# 注释开始,root登陆提示符。
& 在后台执行一个进程。
? 匹配一个字符。
* 匹配0到多个字符(与DOS不同,可在文件名中间使用,并且含。)
$- 使用set及执行时传递给shell的标志位。
$! 最后一个子进程的进程号。
$! 执行上一个背景指令的PID。--网上的信息
$# 传递给shell script的参数个数。
$# 这个程序的参数个数。--
网上的信息
$* 传递给shell script的参数。
$* 传递给shell script的参数。
$*
这个程序的所有参数。--网上的信息
$@ 所有参数,个别的用双引号括起来。
$@ 所有参数,个别的用双引号括起来。
$*
以"参数1 参数2 ... " 形式保存所有参数。--网上的信息
$@ 以"参数1" "参数2" ... 形式保存所有参数。--网上的信息
$? 上一个命令的返回值 (0则表示成立;1表示不成立)
$0 当前shell的名字。
$@ 以"参数1" "参数2" ... 形式保存所有参数。--网上的信息
$? 上一个命令的返回值 (0则表示成立;1表示不成立)
$0 当前shell的名字。
$0
这个程序的执行名字。--网上的信息
$n (n:1-) 位置参数。
$n (n:1-) 位置参数。
$n
这个程序的第n个参数值,n=1...9。--网上的信息
$$ 进程PID、这个程序的PID。
$$ 进程PID、这个程序的PID。
转义符及单引号:
#echo "$HOME $PATH"
/home/user /usr/local/bin:/bin:/usr/bin:/root/bin
#echo "$HOME $PATH"
/home/user /usr/local/bin:/bin:/usr/bin:/root/bin
#echo '$HOME $PATH'
$HOME $PATH
#echo \$HOME $PATH
$HOME /usr/local/bin:/bin:/usr/bin:/root/bin
重定向:
重定向:> 追加的方式重定向:>> 标准输入:0 标准输出:1 标准错误输出:2
#echo 1> filename 重定向标准输出到“filename”。
#echo 1>> filename 追加的方式重定向标准输出到“filename”。
#la 2> filename 重定向标准错误输出到“filename”,如果命令正确还会显示在标准输出上。
#la &> filename #la > filename 2>&1 重定向正确和错误的信息到“filename”。
#la > /dev/null 2>&1 这种情况会使正确和错误的信息都输出到null里面,就不会有任何返回信息。
其他:
#sleep 5; echo 5 seconds reaches (5秒延迟后显示5秒延迟)
#filename=`date +%Y%m%d`.log 注意:`是1左边的按钮,不是单引号。
2. shell变量
变量:代表某些值的符号,如$HOME。cd命令查找$HOME,在计算机语言中可以使用变量可以进行多种运算和控制。
Bourne Shell有如下四种变量:
l
用户自定义变量
l
位置变量即 shell script之参数
l
预定义变量(特殊变量)
l
环境变量(参考shell定制部分)
(1)
用户自定义变量(数据的存储)
#COUNT=1
#NAME="He Binwu"
技巧:因为大部分UNIX命令使用小写字符, 因此在shell编程中通常使用全大写变量 ,当然这并不是强制性的,但使用大写字符可以在编程中方便地识别变量。
变量的调用:在变量前加$
#echo $HOME
/home/hbwork
#WEEK=Satur
#echo Today is $WEEKday
Today is
#COUNT=1
#NAME="He Binwu"
技巧:因为大部分UNIX命令使用小写字符, 因此在shell编程中通常使用全大写变量 ,当然这并不是强制性的,但使用大写字符可以在编程中方便地识别变量。
变量的调用:在变量前加$
#echo $HOME
/home/hbwork
#WEEK=Satur
#echo Today is $WEEKday
Today is
#echo Today is $WEEK day
Today is Satur day
#echo Today is ${WEEK}day 不空格用变量的话要在$后加上大括号{}
Today is Saturday
Linux Shell/Bash从左向右赋值, Shell从右从左进行赋值。
#X=$Y Y=y
#echo $X
# (空,Shell下运行结果)
Today is Satur day
#echo Today is ${WEEK}day 不空格用变量的话要在$后加上大括号{}
Today is Saturday
Linux Shell/Bash从左向右赋值, Shell从右从左进行赋值。
#X=$Y Y=y
#echo $X
# (空,Shell下运行结果)
#A=a B=$A
#echo $B
#a (Bash下运行结果)
使用unset命令删除变量的赋值
#Z=hello
#echo $Z
hello
#unset Z
#echo $Z
有条件的命令替换
在Bourne Shell中可以使变量替换在特定条件下执行,即有条件的环境变量替换。 这种变量替换总是用大括号括起来的。
. 设置变量的默认值
在变量未赋值之前其值为空。Bourne Shell允许对变量设置默认值,其格式如下:
$ {variable: -defaultvalue} //注意这里的符号"-"
例:
#echo Hello $UNAME
Hello
#echo Hello ${UNAME:-there}
Hello there
#echo $UNAME #变量值为空,因为没有对$UNAME赋过值。
#UNAME=hbwork
#echo Hello ${UNAME:-there}
Hello hbwork #不知道there为什么不显示。
另一种情况是改变变量的值,格式如下:
${variable:=value}
例:
#echo Hello $UNAME
Hello
#echo Hello ${UNAME:=there}
Hello there
#echo $UNAME #变量值并未发生变化
there
变量替换中使用命令替换
$USERDIR=${$MYDIR:-`pwd`} #系统说是bad substitution。
在变量已赋值时进行替换 ${variable:+value} 带有错误检查的有条件变量替换
${variable:?value}
例:
#UNAME=
#echo ${UNAME:?"UNAME has not been set"}
UNAME: UNAME has not been set
#echo ${UNAME:?}
UNAME: parameter null or not set
(2)位置变量(Shell参数)
在shell script中位置参数可用$1..$9表示,$0表示内容通常为当前执行程序的文件名。
.防止变量值被替换 read-only variable(只读变量)
.使用export命令输出变量,使得变量对子shell可用,当shell执行一下程序时,shell将为其设置一个新的环境让其执行,这称之了subshell. 在Bourne Shell中变量通常被认为是本地变量,也就是说在对其赋值之外的shell环境之外是不认识此变量的。使用export对subshell可用。
例:对变量PS1的export操作,shell的提示符将发生变化。
#PS1=`hostname`$
yourhostname$
yourhostname$echo $PS1
yourhostname$ #这行就是输出的结果
yourhostname$export PS1
yourhostname$ #这行就是输出的结果
3.Shell Script编程
目的:使用UNIX所提供的最常用工具来完成所需复杂任务的强大功能。
(1)最简单的Shell 编程
每天数据的备份: #现阶段作用不大,用tar更好。
#cd /home/yourname; ls * |cpio -o > bak.cpio
书写程序的目的是一次编程,多次使用(执行)!
#cat backup.sh
cd /home/yourname
ls * | cpio -o > bak.cpio
执行:
#sh backup.sh 或:#chmod +x backup.sh #./backup.sh
技巧:在shell script中加入必要的注释,以便以后阅读及维护。
(2)shell是一个(编程)语言
同传统的编程语言一样,shell提供了很多特性,这些特性可以使你的shell script编程更为有用,如:数据变量、参数传递、判断、流程控制、数据输入和输出,子程序及以中断处理等。
. 在shell编程中使用数据变量可以将程序变量更为通用,如在上面backup.sh中:
cd $WORKDIR #在脚本中没有成功。
ls * | cpio -o > /dev/rmt/0h
. Shell编程中的注释以#开头
. 对shell变量进行数字运算,使用expr命。令格式:#expr integer operator integer
其中operator为+ - * / %(加、减、乘、除、余), 但对*的使用要用转义符\。
如:
#expr 4 \* 5
20
#int=`expr 5 + 7` #等号左右不能有空格。
#echo $int
12
(3)Shell编程的参数传递, 可通过命令行参数以及交互式输入变量(read)
restoreall.sh 对backup.sh程序的备份磁带进行恢复
$cat > restoreall.sh
cd $WORKDIR
cpio -i < /dev/rmt/0h
^D
restore1.sh:只能恢复一个文件
#restore1 --program to restore a single file
cd $WORKDIR
cpio -i $i < /dev/rmt/0h
$restore1 file1
恢复多个文件restoreany :
#restoreany --program to restore a single file
cd $WORKDIR
cpio -i $* < /dev/rmt/0h
$ restoreany file1 file2 file3
(4)条件判断
. if-then语句,格式如下:
if command_1
then
command_2
command_3
fi
command_4
意思:在if-then语句中使用了命令返回码$?,即当command_1执行成功时才执行command_2和command_3,而command_4总是执行。
示例程序unload:在备份成功时删除原始文件,带有错误检查
cd $1
#备份时未考虑不成功的情况!
ls -a | cpio -o > /dev/rmt/0h
rm -rf *
改进如下:
#当使用了管道命令时,管理命令的返回代码为最后一个命令的返回代码
if ls -a | cpio -o > /dev/rmt/0h
then
rm -rf *
fi
. if-then-else语句,格式如下:
if command_1
then
command_2
else
command_3
fi
技巧: 由于shell对命令中的多余的空格不作任何处理,一个好的程序员会用这一特性对自己的程序采用统一的缩进格式,以增强自己程序的可读性。
. test命令,格式如下:
#expr 4 \* 5
20
#int=`expr 5 + 7` #等号左右不能有空格。
#echo $int
12
(3)Shell编程的参数传递, 可通过命令行参数以及交互式输入变量(read)
restoreall.sh 对backup.sh程序的备份磁带进行恢复
$cat > restoreall.sh
cd $WORKDIR
cpio -i < /dev/rmt/0h
^D
restore1.sh:只能恢复一个文件
#restore1 --program to restore a single file
cd $WORKDIR
cpio -i $i < /dev/rmt/0h
$restore1 file1
恢复多个文件restoreany :
#restoreany --program to restore a single file
cd $WORKDIR
cpio -i $* < /dev/rmt/0h
$ restoreany file1 file2 file3
(4)条件判断
. if-then语句,格式如下:
if command_1
then
command_2
command_3
fi
command_4
意思:在if-then语句中使用了命令返回码$?,即当command_1执行成功时才执行command_2和command_3,而command_4总是执行。
示例程序unload:在备份成功时删除原始文件,带有错误检查
cd $1
#备份时未考虑不成功的情况!
ls -a | cpio -o > /dev/rmt/0h
rm -rf *
改进如下:
#当使用了管道命令时,管理命令的返回代码为最后一个命令的返回代码
if ls -a | cpio -o > /dev/rmt/0h
then
rm -rf *
fi
. if-then-else语句,格式如下:
if command_1
then
command_2
else
command_3
fi
技巧: 由于shell对命令中的多余的空格不作任何处理,一个好的程序员会用这一特性对自己的程序采用统一的缩进格式,以增强自己程序的可读性。
. test命令,格式如下:
test conditions
使用test命令进行条件测试
test在以下四种情况下使用: a. 字符比较、b.两个整数值的比较、c. 文件操作,如文件是否存在及文件的状态等、d. 逻辑操作,可以进行and/or,与其他条件联合使用。
a. 测试字符数据:
test在以下四种情况下使用: a. 字符比较、b.两个整数值的比较、c. 文件操作,如文件是否存在及文件的状态等、d. 逻辑操作,可以进行and/or,与其他条件联合使用。
a. 测试字符数据:
shell
变量通常民政部下均作为字符变量
str1 = str2 二者相同
str1 != str2 不同
-n string string不为空(长度不为零)
-z string string为空
string string不为空
例:
#str1=abcd #在含有空格时必须用引号括起来
#test $str1=abcd #输入数字也不会出错。
#echo $?
0
#str1="abcd "
#test $str1=abcd #是abcd和一个空格,不输入空格,或者输入数字都会出错。
str1 = str2 二者相同
str1 != str2 不同
-n string string不为空(长度不为零)
-z string string为空
string string不为空
例:
#str1=abcd #在含有空格时必须用引号括起来
#test $str1=abcd #输入数字也不会出错。
#echo $?
0
#str1="abcd "
#test $str1=abcd #是abcd和一个空格,不输入空格,或者输入数字都会出错。
#-bash: test: abcd: unary operator expected
#echo $?
2
Note: 在test处理含有空格的变量时最好用引号将变量括起来,否则会出现错误的结果,因为shell在处理命令行时将会去掉多余的空格,而用引号括起来则可以防止shell去掉这些空格。
例:
#str1=" "
#test $str1
#echo $?
1
#test "$str1"
#echo $?
0
#test -n $str1 #不报错,echo $str1返回0。
#echo $?
#echo $?
2
Note: 在test处理含有空格的变量时最好用引号将变量括起来,否则会出现错误的结果,因为shell在处理命令行时将会去掉多余的空格,而用引号括起来则可以防止shell去掉这些空格。
例:
#str1=" "
#test $str1
#echo $?
1
#test "$str1"
#echo $?
0
#test -n $str1 #不报错,echo $str1返回0。
#echo $?
0
#test -n "$str1"
#echo $?
0
b. 整数测试: test与expr相同,可以将字符型变量转换为整数进行操作,expr进行整数的算术运算,而test则进行逻辑运算.
表达式 说明
-------------------------------------
int1 -eq int2 相等?
int1 -ne int2 不等?
int1 -gt int2 int1 > int2 大于?
int1 -ge int2 int1 >= int2 大于等于?
int1 -lt int2 int1 < int2 小于?
int1 -le int2 int1 <= int2 小于等于?
例:
$ int1=1234
$ int2=01234 #如果int2=12345的话比较结果就是1.
$ test $int1 -eq $int2
$ echo $?
0
c. 文件测试:检查文件状态如存在及读写权限等。
-r filename 用户对文件filename有读权限?
-w filename 用户对文件filename有写权限?
-x filename 用户对文件filename有可执行权限?
-f filename 文件filename为普通文件?
-d filename 文件filename为目录?
-c filename 文件filename为字符设备文件?
-b filename 文件filename为块设备文件?
-s filename 文件filename大小不为零?
-t fnumb 与文件描述符fnumb(默认值为1)相关的设备是一个终端设备?
d. 测试条件-r –s等:
例:
#cat /dev/null > empty
#test -r empty #test后面的-r等参数的意义见man test。
#echo $?
0
#test -s empty
1
#test ! -s empty
#echo $?
0
#test -n "$str1"
#echo $?
0
b. 整数测试: test与expr相同,可以将字符型变量转换为整数进行操作,expr进行整数的算术运算,而test则进行逻辑运算.
表达式 说明
-------------------------------------
int1 -eq int2 相等?
int1 -ne int2 不等?
int1 -gt int2 int1 > int2 大于?
int1 -ge int2 int1 >= int2 大于等于?
int1 -lt int2 int1 < int2 小于?
int1 -le int2 int1 <= int2 小于等于?
例:
$ int1=1234
$ int2=01234 #如果int2=12345的话比较结果就是1.
$ test $int1 -eq $int2
$ echo $?
0
c. 文件测试:检查文件状态如存在及读写权限等。
-r filename 用户对文件filename有读权限?
-w filename 用户对文件filename有写权限?
-x filename 用户对文件filename有可执行权限?
-f filename 文件filename为普通文件?
-d filename 文件filename为目录?
-c filename 文件filename为字符设备文件?
-b filename 文件filename为块设备文件?
-s filename 文件filename大小不为零?
-t fnumb 与文件描述符fnumb(默认值为1)相关的设备是一个终端设备?
d. 测试条件-r –s等:
例:
#cat /dev/null > empty
#test -r empty #test后面的-r等参数的意义见man test。
#echo $?
0
#test -s empty
1
#test ! -s empty
#echo $?
0
e. 测试条件之逻辑运算-a And -o Or:
例:
#test -r empty -a -s empty
#echo $?
1
#echo $?
1
f. 进行test测试的标准方法
因为test命令在 shell编程中占有很重要的地位,为了使shell能同其他编程语言一样便于阅读和组织, Bourne Shell在使用test测试时使用了另一种方法: 用方括号将整个test测试括起来。
#int1=4
#[ $int1 -gt 2 ]
#echo $?
0
例: 重写unload程序,使用test测试。
#!/bin/sh
#unload - program to backup and remove files
#syntax: unload directory
#check arguments
if [ $# -ne 1 ]
then
echo "usage: $0 directory"
exit 1
fi
#check for valid directory name
if [ ! -d "$1" ]
then
echo "$1 is not a directory"
exit 2
fi
cd $1
ls -a | cpio -o > /dev/rmt/0h
if [ $? -eq 0 ]
then
rm -rf *
else
echo "A problem has occured in creating backup"
echo "The directory will not be ereased"
echo "Please check the backup device"
exit 3
fi
#end of unload
在如上示例中出现了exit, exit有两个作用:一是停止程序中其他命令的执行,二是设置程序的退出状态。
g. if嵌套及elif结构
if嵌套:
if command
then
command
else
if command
then
command
else
if command
then
command
fi
fi
fi
elif结构:
if command
then
command
elif command
then
command
elif command
then
command
fi
elif 结构同if结构类似,但结构更清淅,其执行结果完全相同。
if command
then
command
else
if command
then
command
else
if command
then
command
fi
fi
fi
elif结构:
if command
then
command
elif command
then
command
elif command
then
command
fi
elif 结构同if结构类似,但结构更清淅,其执行结果完全相同。
h.交互式从键盘读入数据
使用read语句,其格式为:read var1 var2 ... varn
read 将不作变量替换,但会删除多余的空格,直到遇到第一个换行符(回车), 并将输入值依次赋值给相应的变量。
例:
#read var1 var2 var3
Hello my friends
#echo $var1 $var2 $var3
Hello my friends
#echo $var1
Hello
#read var1 var2 var3
Hello my dear friends
#echo $var3
dear friends # 输入多余变量时,输入值余下的内容赋给最后一个变量
#read var1 var2 var3
Hello friends
#echo $var3
#var3 为空
在shell script中可使用read语句进行交互操作:
echo -n "Do you want to continue: Y or N" #echo -n message 输出结果后不换行
read ANSWER
if [ $ANSWER=N -o $ANSWER=n ]
then
exit
fi
# 不知道什么意思,输出Y、y、N、n、1等都会退出。
使用read语句,其格式为:read var1 var2 ... varn
read 将不作变量替换,但会删除多余的空格,直到遇到第一个换行符(回车), 并将输入值依次赋值给相应的变量。
例:
#read var1 var2 var3
Hello my friends
#echo $var1 $var2 $var3
Hello my friends
#echo $var1
Hello
#read var1 var2 var3
Hello my dear friends
#echo $var3
dear friends # 输入多余变量时,输入值余下的内容赋给最后一个变量
#read var1 var2 var3
Hello friends
#echo $var3
#var3 为空
在shell script中可使用read语句进行交互操作:
echo -n "Do you want to continue: Y or N" #echo -n message 输出结果后不换行
read ANSWER
if [ $ANSWER=N -o $ANSWER=n ]
then
exit
fi
# 不知道什么意思,输出Y、y、N、n、1等都会退出。
i. case 结构:结构较elif-then结构更清楚
比较if-then语句:
if [ variable1 = value1 ]
then
command
command
elif [ variable1 = value2 ]
then
command
command
elif [ variable1 = value3 ]
then
command
command
fi
相应的case结构:
case value in
pattern1)
command
command;;
pattern2)
command
command;;
...
patternn)
command;;
esac
* case 语句只执行第一个匹配模式
例:使用case语句建立一个菜单选择shell script
#Display a menu
echo _
echo "1 Restore"
echo "2 Backup"
echo "3 Unload"
echo
#Read and excute the user's selection
echo -n "Enter Choice:"
read CHOICE
case "$CHOICE" in
1) echo "Restore";;
2) echo "Backup";;
3) echo "Unload";;
*) echo "Sorry $CHOICE is not a valid choice"
exit 1 #exit 后有没有1都可以退出。
esac
case模式中也可以使用逻辑操作“|”和通配符“*”“?”“[]”,如下所示:
case "$CHOICE" in
[1-3]|r|R|test) echo "Restore";;
[4-6]|b|B) echo "Backup";;
[7-9]|u|U) echo "Unload";;
*) echo "Sorry $CHOICE is not a valid choice"
exit 1
esac
(5) 循环控制
<1> while 循环:
格式:
while command
do
command
command
command
...
done
例:计算1到5的平方
#!/bin/sh
#Filename: square.sh
int=1
while [ $int -le 5 ]
do
sq=`expr $int \* $int`
echo $sq
int=`expr $int + 1`
done
echo "Job finished"
#sh square.sh
1
4
9
16
25
Job finished
<2> until 循环结构:
格式:
until command
do
command
command
....
command
done
示例:使用until结构计算1-5的平方,加入if判断。
#!/bin/sh
#Read user's selection
echo -n "Enter a num(<=5):"
read INUM
#judgment
if [ $INUM -gt 5 ]
then
echo "Please input a num le 5,try again."
else
#square
until [ $INUM -gt 5 ]
do
SQ=`expr $INUM \* $INUM`
echo $SQ
INUM=`expr $INUM + 1`
done
echo "Job finished"
fi
<3> 使用shift对不定长的参数进行处理
在以上的示例中我们总是假设命令行参数唯一或其个数固定,或者使用$*将整个命令行参数传递给shell script进行处理。对于参数个数不固定并且希望对每个命令参数进行单独处理时则需要shift命令。使用shift可以将命令行位置参数依次移动位置,即$2->$1, $3->$2. 在移位之前的第一个位置参数$1在移位后将不在存在。
示例如下:
#!/bin/sh
#Filename: shifter
until [ $# -eq 0 ]
do
echo "Argument is $1 and `expr $# - 1` argument(s) remain"
shift
done
<3> 使用shift对不定长的参数进行处理
在以上的示例中我们总是假设命令行参数唯一或其个数固定,或者使用$*将整个命令行参数传递给shell script进行处理。对于参数个数不固定并且希望对每个命令参数进行单独处理时则需要shift命令。使用shift可以将命令行位置参数依次移动位置,即$2->$1, $3->$2. 在移位之前的第一个位置参数$1在移位后将不在存在。
示例如下:
#!/bin/sh
#Filename: shifter
until [ $# -eq 0 ]
do
echo "Argument is $1 and `expr $# - 1` argument(s) remain"
shift
done
#./shifter 1 2 3 4
Argument is 1 and 3 argument(s) remain
Argument is 2 and 2 argument(s) remain
Argument is 3 and 1 argument(s) remain
Argument is 4 and 0 argument(s) remain
Argument is 2 and 2 argument(s) remain
Argument is 3 and 1 argument(s) remain
Argument is 4 and 0 argument(s) remain
此脚本程序依次显示输入的变量,并提示还有多少个变量剩余;无shift命令程序时不停显示第一行内容。
还需实例证实。
使用shift时,每进行一次移位$#减1,使用这一特性可以用until循环对每个参数进行处理。
一个求整数和的shell脚本。如下示例:
#!/bin/sh
# sumints.sh - a program to sum a series of integers
if [ $# -eq 0 ]
then
echo "Usage: sumints integer list"
exit 1
fi
sum=0
until [ $# -eq 0 ]
do
sum=`expr $sum + $1`
shift
done
echo $sum
#sh sumints.sh 324 34 34 12 34
438
使用shift的另一个原因是Bourne Shell的位置参数变量为$1~$9, 因此通过位置变量只能访问前9个参数。但这并不等于在命令行上最多只能输入9个参数。此时如果想访问前9个参数之后的参数,就必须使用shift。
另外shift后可加整数进行一次多个移位,如:shift 3
<4>. for循环
格式:
for var in arg1 arg2 ... argn
do
command
....
command
done
示例:
#for letter in a b 1234; do echo $letter;done
a
b
1234
对当前目录下的所有文件操作:
#!/bin/bash
#!/bin/sh
# sumints.sh - a program to sum a series of integers
if [ $# -eq 0 ]
then
echo "Usage: sumints integer list"
exit 1
fi
sum=0
until [ $# -eq 0 ]
do
sum=`expr $sum + $1`
shift
done
echo $sum
#sh sumints.sh 324 34 34 12 34
438
使用shift的另一个原因是Bourne Shell的位置参数变量为$1~$9, 因此通过位置变量只能访问前9个参数。但这并不等于在命令行上最多只能输入9个参数。此时如果想访问前9个参数之后的参数,就必须使用shift。
另外shift后可加整数进行一次多个移位,如:shift 3
<4>. for循环
格式:
for var in arg1 arg2 ... argn
do
command
....
command
done
示例:
#for letter in a b 1234; do echo $letter;done
a
b
1234
对当前目录下的所有文件操作:
#!/bin/bash
for a in *
do
if [ -f $a ]
then
echo "$a is a file"
elif [ -d $a ]
do
if [ -f $a ]
then
echo "$a is a file"
elif [ -d $a ]
then
echo "$a is a directory"
fi
done
求命令行上所有整数之和:
#!/bin/sh
sum=0
for INT in $*
do
sum=`expr $sum + $INT`
done
echo $sum
<6> 从循环中退出:break和continue命令
break :立即退出循环。continue:忽略本循环中的其他命令,继续下一循环。
在shell编程中有时我们要用到进行无限循环的技巧,也就是说这种循环一直执行碰到break或continue命令。(一直执行到程序执行了break或用户强行中断时才结束循环)这种无限循环通常是使用true或false命令开始的。UNIX系统中的true总是返回0值,而false则返回非零值。
echo "$a is a directory"
fi
done
求命令行上所有整数之和:
#!/bin/sh
sum=0
for INT in $*
do
sum=`expr $sum + $INT`
done
echo $sum
<6> 从循环中退出:break和continue命令
break :立即退出循环。continue:忽略本循环中的其他命令,继续下一循环。
在shell编程中有时我们要用到进行无限循环的技巧,也就是说这种循环一直执行碰到break或continue命令。(一直执行到程序执行了break或用户强行中断时才结束循环)这种无限循环通常是使用true或false命令开始的。UNIX系统中的true总是返回0值,而false则返回非零值。
格式: #好像不是break或continue的格式。
while true
do
command
....
command
done
上面所示的循环也可以使用until false
while true
do
command
....
command
done
上面所示的循环也可以使用until false
如下:
until false
do
command
....
command
done
在如下shell脚本中同时使用了continue,break以及case语句中的正规表达式用法:
#!/bin/sh
until false
do
command
....
command
done
在如下shell脚本中同时使用了continue,break以及case语句中的正规表达式用法:
#!/bin/sh
#Interactive program to restore or backup file and directory name that in the directory you input.
#带交互功能的备份和还原输入的目录下的文件和文件夹名。
echo "Welcome to the menu driven Archive program"
while true
do
# Display a Menu
echo
echo "Make a Choice from the Menu below"
echo _________________
echo "1 Restore Archive"
echo "2 Backup directory"
echo "3 Exit"
echo
# Read the user's selection
echo -n "Enter Choice: "
read CHOICE
case $CHOICE in
[1-2] ) echo
# Read and validate the name of the directory
echo -n "What directory do you want? "
read WORKDIR
if [ ! -d "$WORKDIR" ]
then
echo "Sorry, $WORKDIR is not a directory"
continue
fi
# Make the directory the current working directory
cd $WORKDIR;;
3) :;; #:为空语句,不执行任何动作。
*) echo "Sorry, $CHOICE is not a valid choice"
break
esac
case "$CHOICE" in
1) echo "Restoring..."
cpio -i < /home/shark/bak.cpio;;
2) echo "Backuping..."
ls | cpio -o > /home/shark/bak.cpio;;
3) echo "Exited"
break
esac
#Check for cpio errors
case "$?" in
0) echo "operation complete, now exit."
break;;
*) echo "A problem has occurred during the process, now exit."
break
esac
done
(6) 结构化编程:
(6) 结构化编程:
定义函数
同其他高级语言一样shell也提供了函数功能。函数通常也称之为子过程(subroutine),其定义格式如下:
function name()
{
command
...
command; # 分号
}
定义函数之后,可以在shell中对此函数进行调用,使用函数定义可以将一个复杂的程序分为多个可管理的程序段,如下所示:
#!/bin/bash
同其他高级语言一样shell也提供了函数功能。函数通常也称之为子过程(subroutine),其定义格式如下:
function name()
{
command
...
command; # 分号
}
定义函数之后,可以在shell中对此函数进行调用,使用函数定义可以将一个复杂的程序分为多个可管理的程序段,如下所示:
#!/bin/bash
#start program
setup ()
{ command list ; }
do_data ()
{ command list ; }
cleanup ()
{ command list ; }
errors ()
{ command list ; }
setup
do_data
cleanup
#end program
技巧:
. 在对函数命名时最好能使用有含义的名字,即函数名能够比较准确的描述函数所完成的任务。
setup ()
{ command list ; }
do_data ()
{ command list ; }
cleanup ()
{ command list ; }
errors ()
{ command list ; }
setup
do_data
cleanup
#end program
技巧:
. 在对函数命名时最好能使用有含义的名字,即函数名能够比较准确的描述函数所完成的任务。
. 为了程序的维护方便,请尽可能使用注释。
使用函数的另一个好处就是可以在一个程序中的不同地方执行相同的命令序列(函数),如下所示:
askyesno()
{
while true
do
echo -n "Continue?(Y/N)"
read ANSWER
case $ANSWER in
[Yy]) return 0;;
[Nn]) return 1;;
*) echo "Answer Y or N";;
esac
done
}
这样可以在shell编程中调用askyesno确定是否继续执行,例如与前面备份恢复的脚本结合:
#!/bin/sh
#function "askyesno" ask yes or no to continue.
askyesno()
{
while true
do
echo -n "Continue?(Y/N)"
read ANSWER
case $ANSWER in
[Yy]) return 0;;
[Nn]) return 1;;
*) echo "Answer Y or N";;
esac
done
}
# Interactive program to restore or backup a directory。
echo "Welcome to the menu driven Archive program"
#中间内容同上故省略。
case "$CHOICE" in
1) echo "Restoring..."
if askyesno
then
cpio -i < /home/shark/bak.cpio
fi
break;;
2) echo "Backuping..."
if askyesno
then
ls | cpio -o > /home/shark/bak.cpio
fi
break;;
3) echo "Exited"
break
esac
#检查对错不知为何不运行了,故删除。
done
**shell 函数与shell程序非常相似,但二者有一个非常重要的差别:shell程序是由子shell执行的,而shell函数则是作为当前shell的一部分被执行的,因此在当前shell中可以改变函数的定义。此外在任意shell(包括交互式的shell)中均可定义函数。
例如:
#dr
-bash: dr: command not found
#dr () { ls -l ;}
#dr
total 12
-bash: dr: command not found
#dr () { ls -l ;}
#dr
total 12
drwxr-xr-x 2 root root 4096 Oct 31 16:47 dir
-rwxr-xr-x 1 root root 174 Nov 5 16:36 jb.sh
-rwxr-xr-x 1 root root 1118 Nov 5 17:41 script.sh
#unset dr
#dr () {
> echo "Permission Link Owner Group File_SZ LastAccess FileName"
> echo "-------------------------------------------------------"
> ls -lh $*;
> }
#dr
Permission Link Owner Group File_SZ LastAccess FileName
#unset dr
#dr () {
> echo "Permission Link Owner Group File_SZ LastAccess FileName"
> echo "-------------------------------------------------------"
> ls -lh $*;
> }
#dr
Permission Link Owner Group File_SZ LastAccess FileName
-------------------------------------------------------
total 12K
drwxr-xr-x 2 root root 4.0K Oct 31 16:47 dir
-rwxr-xr-x 1 root root 174 Nov 5 16:36 jb.sh
-rwxr-xr-x 1 root root 1.1K Nov 5 17:41 script.sh
通常情况下,shell脚本是在子shell中执行的,所以在此子shell中对变量所作的修改对父shell不起作用。点(.) 命令使用shell在不创建子shell而由当前shell读取并执行一个shell脚本, 可以通过这种方式来定义函数及变量。此外点(.)命令最常用的功能就是通过读取.profile来重新配置初始化login变量。
通常情况下,shell脚本是在子shell中执行的,所以在此子shell中对变量所作的修改对父shell不起作用。点(.) 命令使用shell在不创建子shell而由当前shell读取并执行一个shell脚本, 可以通过这种方式来定义函数及变量。此外点(.)命令最常用的功能就是通过读取.profile来重新配置初始化login变量。
如下所示:
#. .profile
(csh 相对于.命令的是source命令)。 # 新近的linux系统里面貌似没有.profile文件了。
(7)使用And/Or(&& /||)结构进行有条件的命令执行
<1> And。仅当第一个命令成功时才有执行后一个命令。如同在逻辑与表达式中如果前面的结果为真时才有必要继续运算,否则结果肯定为假后面也就不用进行运输了。
格式:command1 && command2
例如:rm $TEMPDIR/* && echo "File successfully removed"
等价于:
if rm $TEMPDIR/*
then
echo "File successfully removed"
fi
<2>Or。与AND相反,仅当前一个命令执行出错时才执行后一条命令。
例如:rm $TEMPDIR/* || echo "File not removed"
等价于:
if rm $TEMPDIR/*
then
command
else
echo "File not removed"
fi
<3> 混合命令条件执行
格式:command1 && command2 && command3
当command1, command2成功时执行command3。
格式:command1 && command2 || comamnd3
当command1成功,command2失败时执行command3。
#. .profile
(csh 相对于.命令的是source命令)。 # 新近的linux系统里面貌似没有.profile文件了。
(7)使用And/Or(&& /||)结构进行有条件的命令执行
<1> And。仅当第一个命令成功时才有执行后一个命令。如同在逻辑与表达式中如果前面的结果为真时才有必要继续运算,否则结果肯定为假后面也就不用进行运输了。
格式:command1 && command2
例如:rm $TEMPDIR/* && echo "File successfully removed"
等价于:
if rm $TEMPDIR/*
then
echo "File successfully removed"
fi
<2>Or。与AND相反,仅当前一个命令执行出错时才执行后一条命令。
例如:rm $TEMPDIR/* || echo "File not removed"
等价于:
if rm $TEMPDIR/*
then
command
else
echo "File not removed"
fi
<3> 混合命令条件执行
格式:command1 && command2 && command3
当command1, command2成功时执行command3。
格式:command1 && command2 || comamnd3
当command1成功,command2失败时执行command3。
格式:command1 || command2 && comamnd3
当command1失败,command2成功时执行command3。
格式:command1 || command2 || comamnd3
当command1,command2失败时执行command3。
当然可以根据自己的需要进行多种条件命令的组合,在此不多讲述。
(8) 使用getopts命令读取unix格式选项
UNIX 格式选项指如下格式的命令行参数:command -options parameters
使用格式为:getopts option_string variable
具体使用方法请参考getopts的在线文档(man getopts)。
当然可以根据自己的需要进行多种条件命令的组合,在此不多讲述。
(8) 使用getopts命令读取unix格式选项
UNIX 格式选项指如下格式的命令行参数:command -options parameters
使用格式为:getopts option_string variable
具体使用方法请参考getopts的在线文档(man getopts)。
例子如下:
#newdate
if [ $# -lt 1 ]
then
date
else
while getopts mdyDHMSTjJwahr OPTION
do
case $OPTION
in
m) date '+%m ';; # Month of Year
d) date '+%d ';; # Day of Month
y) date '+%y ';; # Year
D) date '+%D ';; # MM/DD/YY
H) date '+%H ';; # Hour
M) date '+%M ';; # Minute
S) date '+%S ';; # Second
T) date '+%T ';; # HH:MM:SS
j) date '+%j ';; # day of year
J) date '+%y%j ';;# 5 digit Julian date
w) date '+%w ';; # Day of the Week
a) date '+%a ';; # Day abbreviation2
h) date '+%h ';; # Month abbreviation
r) date '+%r ';; # AM-PM time
\?) echo "Invalid option $OPTION";;
esac
done
fi
运行:
#newdate -a -h -d
Mon
Jan
31
#newdate -ahd
Mon
Jan
31
示例程序:复制程序
#Syntax: duplicate [-c integer] [-v] filename
#where integer is the number of duplicate copies and -v is the verbose option
COPIES=1
VERBOSE=N
#newdate
if [ $# -lt 1 ]
then
date
else
while getopts mdyDHMSTjJwahr OPTION
do
case $OPTION
in
m) date '+%m ';; # Month of Year
d) date '+%d ';; # Day of Month
y) date '+%y ';; # Year
D) date '+%D ';; # MM/DD/YY
H) date '+%H ';; # Hour
M) date '+%M ';; # Minute
S) date '+%S ';; # Second
T) date '+%T ';; # HH:MM:SS
j) date '+%j ';; # day of year
J) date '+%y%j ';;# 5 digit Julian date
w) date '+%w ';; # Day of the Week
a) date '+%a ';; # Day abbreviation2
h) date '+%h ';; # Month abbreviation
r) date '+%r ';; # AM-PM time
\?) echo "Invalid option $OPTION";;
esac
done
fi
运行:
#newdate -a -h -d
Mon
Jan
31
#newdate -ahd
Mon
Jan
31
示例程序:复制程序
#Syntax: duplicate [-c integer] [-v] filename
#where integer is the number of duplicate copies and -v is the verbose option
COPIES=1
VERBOSE=N
while getopts vc: OPTION
do
case $OPTION
in
c) COPIES=$OPTARG;;
v) VERBOSE=Y;;
\?) echo "Illegal Option"
exit 1;;
esac
done
if [ $OPTIND -gt $# ]
then
echo "No file name specified"
exit 2
fi
shift `expr $OPTIND -1`
FILE=$1
COPY=0
while [ $COPIES -gt $COPY ]
do
COPY=`expr $COPY + 1`
cp $FILE ${FILE}${COPY}
if [ VERBOSE = Y ]
then
echo ${FILE}${COPY}
fi
done
运行:
#duplicate -v fileA
fileA1
#duplicate -c 2 -v fileB
fileB1
fileB2 #不能顺利运行,$OPTIND、$OPTARG不知道哪里来的。
4. Shell的定制
通常使用shell的定制来控制用户自己的环境,比如改变shell的外观(提示符)以及增强自己的命令。
(1)通常环境变量来定制shell
通常改变环境变量可以定制shell的工作环境。shell在处理信息时会参考这些环境变量,改变环境变量的值在一定程度上改变shell的操作方式,比如改变命令行提示符。
.使用IFS增加命令行分隔符
默认状态下shell的分隔符为空格、制表符及换行符,但可以通过改变IFS的值加入自己的分隔符。
do
case $OPTION
in
c) COPIES=$OPTARG;;
v) VERBOSE=Y;;
\?) echo "Illegal Option"
exit 1;;
esac
done
if [ $OPTIND -gt $# ]
then
echo "No file name specified"
exit 2
fi
shift `expr $OPTIND -1`
FILE=$1
COPY=0
while [ $COPIES -gt $COPY ]
do
COPY=`expr $COPY + 1`
cp $FILE ${FILE}${COPY}
if [ VERBOSE = Y ]
then
echo ${FILE}${COPY}
fi
done
运行:
#duplicate -v fileA
fileA1
#duplicate -c 2 -v fileB
fileB1
fileB2 #不能顺利运行,$OPTIND、$OPTARG不知道哪里来的。
4. Shell的定制
通常使用shell的定制来控制用户自己的环境,比如改变shell的外观(提示符)以及增强自己的命令。
(1)通常环境变量来定制shell
通常改变环境变量可以定制shell的工作环境。shell在处理信息时会参考这些环境变量,改变环境变量的值在一定程度上改变shell的操作方式,比如改变命令行提示符。
.使用IFS增加命令行分隔符
默认状态下shell的分隔符为空格、制表符及换行符,但可以通过改变IFS的值加入自己的分隔符。
如下所示:
#IFS=":"
#echo:Hello:my:Friend
Hello my Friend # 不能实现。
(2) 加入自己的命令及函数
如下程序:
#Directory and Prompt change program
#Syntax: chdir directory
if [ ! -d "$1" ]
then
echo "$1 is not a directory"
exit 1
fi
cd $1
PS1=`pwd`$
export PS1
运行:
#chdir /usr/home/test
#IFS=":"
#echo:Hello:my:Friend
Hello my Friend # 不能实现。
(2) 加入自己的命令及函数
如下程序:
#Directory and Prompt change program
#Syntax: chdir directory
if [ ! -d "$1" ]
then
echo "$1 is not a directory"
exit 1
fi
cd $1
PS1=`pwd`$
export PS1
运行:
#chdir /usr/home/test
#
但此程序在执行时系统提示符并不会改变,因为此程序是在子shell中执行的。因此其变量对当前shell并无影响,要想对当前shell起作用,最好是将此作为函数写在自己的.profile中或建立自己的个人函数文件.persfuncs。
#Personal function file persfuncs
chdir()
{
#Directory and Prompt change program
#Syntax: chdir directory
if [ ! -d "$1" ]
then
但此程序在执行时系统提示符并不会改变,因为此程序是在子shell中执行的。因此其变量对当前shell并无影响,要想对当前shell起作用,最好是将此作为函数写在自己的.profile中或建立自己的个人函数文件.persfuncs。
#Personal function file persfuncs
chdir()
{
#Directory and Prompt change program
#Syntax: chdir directory
if [ ! -d "$1" ]
then
echo "$1 is not a directory"
exit 1
fi
cd $1
PS1=`pwd`$
export PS1;
}
再执行:
#. .persfuncs
#chdir temp
/home/hbbwork/temp# # 看懂了但是没实现。
也可在自己的.profile文件中用 . .persfuncs调用.persfuncs。
说明:在bash/tcsh中已经使用别名,相对而言别名比此方法更为方便。
5. 有关shell的专门讨论
(1)shell 程序的调试
切记:程序员总是会犯错误的,而计算机是不会错的。使用-x进行跟踪执行,执行并显示每一条指令。
(2) 命令组
用()小括号将一组命令括起来,则这些命令会由子shell来完成;而{command_list;}则在当前shell中执行。这两者的主要区别在于其对shell变量的影响,子shell执行的命令不会影响当前shell中的变量。
#NUMBER=2
#(A=2;B=2;NUMBER=`expr $A + $B`; echo $NUMBER)
4
#echo $NUMBER
2
#{ A=2;B=2;NUMBER=`expr $A + $B`; echo $NUMBER; }
4
#echo $NUMBER
4
exit 1
fi
cd $1
PS1=`pwd`$
export PS1;
}
再执行:
#. .persfuncs
#chdir temp
/home/hbbwork/temp# # 看懂了但是没实现。
也可在自己的.profile文件中用 . .persfuncs调用.persfuncs。
说明:在bash/tcsh中已经使用别名,相对而言别名比此方法更为方便。
5. 有关shell的专门讨论
(1)shell 程序的调试
切记:程序员总是会犯错误的,而计算机是不会错的。使用-x进行跟踪执行,执行并显示每一条指令。
(2) 命令组
用()小括号将一组命令括起来,则这些命令会由子shell来完成;而{command_list;}则在当前shell中执行。这两者的主要区别在于其对shell变量的影响,子shell执行的命令不会影响当前shell中的变量。
#NUMBER=2
#(A=2;B=2;NUMBER=`expr $A + $B`; echo $NUMBER)
4
#echo $NUMBER
2
#{ A=2;B=2;NUMBER=`expr $A + $B`; echo $NUMBER; }
4
#echo $NUMBER
4
总结:
在本章中讲述了Bourne Shell的基本知识,使用shell变量,shell script基础。这些概念对于理解学习Korn Shell, csh以及其他script编程都是非常有用的。
很多OS都有不少语言及一些script功能,但很少有象UNIX SHELL这样灵活强大的script脚本语言能力。
对于系统管理员或程序员来说,熟练地使用shell script将对日常工作(系统维护及管理)非常有用,如果你想作一个合格的系统管理员,强烈建议你进一步深入的了解和使用shell.
另外,对于系统管理员来说,PERL也是一个必不可少的script编程语言,尤其是对于处理文本格式的各种文件,PERL具有shell, awk, sed, grep等的功能,但使用起来更为灵活,功能也更强大。大家可以参考“Perl By Examples"来学习和使用PERL。