规范Shell
获取UNIX类型的选项:
unix有一个优点就是标准UNIX命令在执行时都具有相同的命令行格式:
command -options parameters
如果在执行Shell程序也采用上述格式,Bourne Shell中提供了一条获取和处理命令行选项的语句,即getopts语句。该语句的格式为:
getopts option_string variable
其中option_string中包含一个有效的单字符选项。若getopts命令在命令行中发现了连字符,那么它将用连字符后面的字符同option_string相比较。若有匹配,则把变量variable的值设为该选项。若无匹配,则variable设为?。当getopts发现连字符后面没有字符,会返回一个非零的状态值。Shell程序中可以利用getopts的返回值建立一个循环。
下面代码说明了date命令中怎么使用getopts命令处理各种选项,该程序除了完成unix的标准命令date的功能外,还增加了许多新的选项。
#新date程序
if [ $# -lt 1 ]
then
date
else
while getopts mdyDHMSTJjwahr OPTION
do
case $OPTION
in
m)date '+%m';;
d)date '+%d';;
y)date '+%y';;
D)date '+%D';;
H0date '+%H';;
M)date '+%M';;
S)date '+%S';;
T)date '+%T';;
j)date '+%j';;
J)date '+%y%j';;
w)date '+%w';;
a)date '+%a';;
h)date '+%h';;
r)date '+%r';;
/?)echo "无效的选项!$OPTION";;
esac
done
fi
有时侯选项中还带一个值,getopts命令同样也支持这一功能。这时需要在option_string中选项字母后加一个冒号。当getopts命令发现冒号后,会从命令行该选项后读取该值。若该值存在,那么将被存在一个特殊的变量OPTARG中。如果该值不存在,getopts命令将在OPTARG中存放一个问号,并且在标准错误输出上显示一条消息。
下面的例子,实现拷贝一个文件,并给文件赋一个新的名字。-c选项指定程序拷贝的次数,-v选项要求显示新创建文件的文件名。
#--拷贝程序
COPIES=1
VERBOSE=N
while getopts vc:OPTION
do
case $OPTION
in
c)COPIES=$OPTARG;;
v)VERBOSE=Y;;
/?)echo "无效参数!"
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
规范Shell:
我们知道环境变量PS1是提示符,看下面程序chdir:
if [ ! -d "$!" ]
then
echo "$1 is not a directory"
exit 1
fi
cd $1
PS1="'pwd'>"
export PS1
我们执行:
$chdir /usr/ice666
结果提示符号变成/usr/ice666>了吗?没有,为什么?
原因在于:chdir在子Shell中执行,变量PS1的修改在当前Shell中也不会起作用,若要chdir完成意想中的功能,必须在当前Shell中执行该命令。最好的方法就是把其改成一个函数并且在.profile文件中定义。但若要把函数放到单个文件中并在当前Shell中执行,则需要使用. 命令,并将chdir重写成一个函数,把其中的exit改写成return。下面代码是 .ice_ps的内容:
#--提示符
chdir()
{
if [ !-d "$1" ]
then
echo " $1 is not a directory"
return
fi
cd $1
PS1="'pwd'>"
export PS1;
}
然后我们在.profile文件中加入下面语句
.ice_ps
然后在切换目录的时候,我们用chdir命令,结果是什么呢,自己实验好了!
调试Shell程序
1>调试shell程序
用户刚编写完Shell程序中,不可避免的会有错误,这时我们可以利用Bsh中提供的跟踪选项,该选项会显示刚刚执行的命令及参数。用户可以通过set命令打开-x选项或在启动Shell使用-x选项将Shell设置成跟踪模式。例如有下面代码ice_tx:
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 -x ice_tx 2 3 4
结果显示:
+[ 3 -eq 0 ]
+sum=0
+[ 3 -eq 0 ]
+expr 0+2
+sum=2
+shift
+[ 2 -eq 0 ]
+expr 2+3
+sum=5
+shift
+[ 1 -eq 0 ]
+expr 5+4
+sum=9
+[ 0 -eq 0 ]
+echo 9
9
从上面可以看出,跟踪模式下Shell显示执行的每一条命令以及该命令使用的变量替换后的参数值。一些控制字如if、then、until等没显示。
2>命令分组
Shell中若干命令可以组成一个单元一起执行。为了标识一组命令,这些命令必须放到"()"或"{}"中。放在"()"中的命令将在子Shell中运行,而放在"{}"中的命令将在当前Shell中运行。子Shell中运行的命令不影响当前Shell的变量。当前Shell中运行的命令影响当前Shell的变量。
$NUMBER=2
$(A=2;B=2;NUMBER='expr $A+$B';echo $NUMBER)
结果为:4
$echo $NUMBER
结果为:2
如果把上面的()变成{},结果会是怎么样的呢?
3>使用Shell分层管理器shl
UNIX是一个多道程序设计的操作系统,一些UNIX系统利用这一特性提供了Shell层次管理器shl。使用shl用户一次可以打开多个层次的Shell,其中活跃的Shell可以从终端上获得输入。但所有Shell的输出都可在终端上显示,除非显示被禁止。
多个Shell中有一个为shl,当用户在某个Shell中工作时,可以通过使用特殊字符(一般为Ctrl+z)返回shl。为了同其他Shell区别,shl中提示符为">>>"。当用户工作在Shell层次管理器中时,可以创建、激活和删除Shell,下面是shl中使用的命令。
create name 产生名为name的层次
delete name 删除名为name的层次
block name 禁止名为name的层次的输出
unblock name 恢复名为name的层次的输出
resume name 激活名为name的层次
toggle 激活近来经常使用的层次
name 激活名为name的层次
layers [-l] name 对于表中的每个层次,显示其正在运行的进程的进程号,-l选项要求显示详细信息。
help 显示shl命令的帮助信息
quit 退出shl以及所有被激活的层次
总结
在前面我们主要介绍了sh的变量、基本语法、程序设计等。如果掌握了这些内容,在学习其他UNIX下编程语言的时候,相信有一定的好处,我们说了,在大多数的UNIX中都提供BournShell,而且很少有象sh这样强大的脚本编辑语言了,是系统管理员和程序员的一笔财富,并且不需要额外的软件环境,对文件等处理借助unix命令,实现起来比c实现还要简单。
获取UNIX类型的选项:
unix有一个优点就是标准UNIX命令在执行时都具有相同的命令行格式:
command -options parameters
如果在执行Shell程序也采用上述格式,Bourne Shell中提供了一条获取和处理命令行选项的语句,即getopts语句。该语句的格式为:
getopts option_string variable
其中option_string中包含一个有效的单字符选项。若getopts命令在命令行中发现了连字符,那么它将用连字符后面的字符同option_string相比较。若有匹配,则把变量variable的值设为该选项。若无匹配,则variable设为?。当getopts发现连字符后面没有字符,会返回一个非零的状态值。Shell程序中可以利用getopts的返回值建立一个循环。
下面代码说明了date命令中怎么使用getopts命令处理各种选项,该程序除了完成unix的标准命令date的功能外,还增加了许多新的选项。
#新date程序
if [ $# -lt 1 ]
then
date
else
while getopts mdyDHMSTJjwahr OPTION
do
case $OPTION
in
m)date '+%m';;
d)date '+%d';;
y)date '+%y';;
D)date '+%D';;
H0date '+%H';;
M)date '+%M';;
S)date '+%S';;
T)date '+%T';;
j)date '+%j';;
J)date '+%y%j';;
w)date '+%w';;
a)date '+%a';;
h)date '+%h';;
r)date '+%r';;
/?)echo "无效的选项!$OPTION";;
esac
done
fi
有时侯选项中还带一个值,getopts命令同样也支持这一功能。这时需要在option_string中选项字母后加一个冒号。当getopts命令发现冒号后,会从命令行该选项后读取该值。若该值存在,那么将被存在一个特殊的变量OPTARG中。如果该值不存在,getopts命令将在OPTARG中存放一个问号,并且在标准错误输出上显示一条消息。
下面的例子,实现拷贝一个文件,并给文件赋一个新的名字。-c选项指定程序拷贝的次数,-v选项要求显示新创建文件的文件名。
#--拷贝程序
COPIES=1
VERBOSE=N
while getopts vc:OPTION
do
case $OPTION
in
c)COPIES=$OPTARG;;
v)VERBOSE=Y;;
/?)echo "无效参数!"
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
规范Shell:
我们知道环境变量PS1是提示符,看下面程序chdir:
if [ ! -d "$!" ]
then
echo "$1 is not a directory"
exit 1
fi
cd $1
PS1="'pwd'>"
export PS1
我们执行:
$chdir /usr/ice666
结果提示符号变成/usr/ice666>了吗?没有,为什么?
原因在于:chdir在子Shell中执行,变量PS1的修改在当前Shell中也不会起作用,若要chdir完成意想中的功能,必须在当前Shell中执行该命令。最好的方法就是把其改成一个函数并且在.profile文件中定义。但若要把函数放到单个文件中并在当前Shell中执行,则需要使用. 命令,并将chdir重写成一个函数,把其中的exit改写成return。下面代码是 .ice_ps的内容:
#--提示符
chdir()
{
if [ !-d "$1" ]
then
echo " $1 is not a directory"
return
fi
cd $1
PS1="'pwd'>"
export PS1;
}
然后我们在.profile文件中加入下面语句
.ice_ps
然后在切换目录的时候,我们用chdir命令,结果是什么呢,自己实验好了!
调试Shell程序
1>调试shell程序
用户刚编写完Shell程序中,不可避免的会有错误,这时我们可以利用Bsh中提供的跟踪选项,该选项会显示刚刚执行的命令及参数。用户可以通过set命令打开-x选项或在启动Shell使用-x选项将Shell设置成跟踪模式。例如有下面代码ice_tx:
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 -x ice_tx 2 3 4
结果显示:
+[ 3 -eq 0 ]
+sum=0
+[ 3 -eq 0 ]
+expr 0+2
+sum=2
+shift
+[ 2 -eq 0 ]
+expr 2+3
+sum=5
+shift
+[ 1 -eq 0 ]
+expr 5+4
+sum=9
+[ 0 -eq 0 ]
+echo 9
9
从上面可以看出,跟踪模式下Shell显示执行的每一条命令以及该命令使用的变量替换后的参数值。一些控制字如if、then、until等没显示。
2>命令分组
Shell中若干命令可以组成一个单元一起执行。为了标识一组命令,这些命令必须放到"()"或"{}"中。放在"()"中的命令将在子Shell中运行,而放在"{}"中的命令将在当前Shell中运行。子Shell中运行的命令不影响当前Shell的变量。当前Shell中运行的命令影响当前Shell的变量。
$NUMBER=2
$(A=2;B=2;NUMBER='expr $A+$B';echo $NUMBER)
结果为:4
$echo $NUMBER
结果为:2
如果把上面的()变成{},结果会是怎么样的呢?
3>使用Shell分层管理器shl
UNIX是一个多道程序设计的操作系统,一些UNIX系统利用这一特性提供了Shell层次管理器shl。使用shl用户一次可以打开多个层次的Shell,其中活跃的Shell可以从终端上获得输入。但所有Shell的输出都可在终端上显示,除非显示被禁止。
多个Shell中有一个为shl,当用户在某个Shell中工作时,可以通过使用特殊字符(一般为Ctrl+z)返回shl。为了同其他Shell区别,shl中提示符为">>>"。当用户工作在Shell层次管理器中时,可以创建、激活和删除Shell,下面是shl中使用的命令。
create name 产生名为name的层次
delete name 删除名为name的层次
block name 禁止名为name的层次的输出
unblock name 恢复名为name的层次的输出
resume name 激活名为name的层次
toggle 激活近来经常使用的层次
name 激活名为name的层次
layers [-l] name 对于表中的每个层次,显示其正在运行的进程的进程号,-l选项要求显示详细信息。
help 显示shl命令的帮助信息
quit 退出shl以及所有被激活的层次
总结
在前面我们主要介绍了sh的变量、基本语法、程序设计等。如果掌握了这些内容,在学习其他UNIX下编程语言的时候,相信有一定的好处,我们说了,在大多数的UNIX中都提供BournShell,而且很少有象sh这样强大的脚本编辑语言了,是系统管理员和程序员的一笔财富,并且不需要额外的软件环境,对文件等处理借助unix命令,实现起来比c实现还要简单。