Shell Script语言教程~

Shell Script语言教程
《目录》
□前言
□将文字档设为可执行的Shell Script
□Script的基本结构及观念
□Bourne Shell
一、变量
二、执行命令
三、流程控制
□C Shell
一、变量
二、执行命令
三、流程控制
□附录A expr命令
□附录B test命令
为什么要进行shell编程
一般而言,Shell Script的地位和其它的可执行文件(或命令)是完全相同的,只不过Shell Script是以文
字档的方式储存,而非二进位档。而执行Shell Script时,必须有一个程式将其内容转成一道道的命令执
行,而这个程式其实就是Shell ,这也就是为什麽我们叫做Shell Script的原因(往後我们称为Script)。
不同Shell 的Script基本上会有一些差异,所以我们不能将写给A shell 的Script用B shell 执行。而在
UNIX中大家最常使用Bourne Shell以及C Shell ,所以这堂课就介绍这两种Script的写法。
下面,让我们一起来看看shell是如何工作的:
□将文字档设为可执行的Shell Script
如果我们已经写好Script,如何将其设成可执行文件呢?因为Script其实是一个可执行文件,所以必须将其
存取权设定成可执行。我们可以使用下列命令更改存取权:
chmod u+x filename 只有自己可以执行,其它人不能执行
chmod ug+x filename 只有自己以及同一群可以执行,其它人不能执行
chmod +x filename 所有人都可以执行
而我们如何指定使用那一个Shell 来解释所写的Script呢?几种基本的指定方式如下所述:
1. 如果Script的第一个非空白字元不是"#",则它会使用Bourne Shell。
2. 如果Script的第一个非空白字元是"#"时,但不以"#!"开头时,则它会使用C Shell。
3. 如果Script以"#!"开头,则"#!"後面所写的就是所使用的Shell,而且要将整个路径名称指出来。
这里建议使用第三种方式指定Shell ,以确保所执行的就是所要的。Bourne Shell的路
径名称为/bin/sh ,而C Shell 则为/bin/csh。
<eg>
1. 使用Bourne Shell
┌——————————┐ ┌——————————┐
│echo enter filename │ │#!/bin/sh │
│ . │ or │ . │
│ . │ │ . │
│ . │ │ . │
└——————————┘ └——————————┘
2. 使用C Shell
┌——————————┐ ┌——————————┐
│# C Shell Script │ │#!/bin/csh │
│ . │ │ . │
│ . │ │ . │
│ . │ │ . │
└——————————┘ └——————————┘
3. 使用/etc/perl
┌——————————┐
│#! /etc/perl │
│ . │
│ . │
│ . │
Shell Script语言教程
└——————————┘
除了在Script内指定所使用的Shell 外,你也可以在命令列中强制指定。比如你要
用C Shell 执行某个Script,你可以下这个命令:
csh filename
此时的Script的存取权就不一定要为可执行文件,其内部所指定的Shell 也会无效,详细
的情形後面会讨论。
您可以通过输入: ./filename 来执行您的脚本
□Script的基本结构及观念
Script是以行为单位,我们所写的Script会被分解成一行一行来执行。而每一行可以是命令、注解、或是流
程控制指令等。如果某一行尚未完成,可以在行末加上"\" ,这个时候下一行的内容就会接到这一行的後
面,成为同一行,如下
┌———————————┐
│echo The message is \ │
│too long so we have \ │
│to split it into \ │
│several lines │
└———————————┘
当Script中出现"#" 时,再它後面的同一行文字即为注解,Shell 不会对其翻译。
在Script中要执行一个命令的方法和在命令列中一样,你可以前景或背景执行,执行命令时也会需要设定一
些环境变量。
Script的流程控制和一般高阶语言的流程控制没有什麽两样,也和高阶语言一样有副程式。这些使得Script
的功能更加强大。
为了达到与高阶语言相同的效果,我们也可以在Script中设定变量,如此使Script成为一个名付其实的高阶
语言。
□Bourne Shell
一、变量
Bourne Shell的变量型态只有字串变量,所以要使用数值运算则必须靠外部命令达成目的。而其变量种类有
下列几种:
1. 使用者变量
这是最常使用的变量,我们可以任何不包含空白字元的字串来当做变量名称。
设定变量值时则用下列方式:
var=string
取用变量时则在变量名称前加上一"$" 号。
<eg>
┌———————┐
│name=Tom │
│echo name │
│echo $name │
└———————┘
结果如下:
name
Tom
有时候变量名很容易与其他文字混淆,比如:
num=2
echo "this is the $numnd"
这并不会打印出"this is the 2nd",而仅仅打印"this is the ",因为shell会去搜索变量numnd的值,但是
这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量:
num=2
echo "this is the ${num}nd"
这将打印: this is the 2nd
2. 系统变量(环境变量)
和使用者变量相似,只不过此种变量会将其值传给其所执行的命令。要将一使用者变量设定为系统变量,只
要加上:
export var
<eg>
┌———————┐
│name=Tom │
│export name │
└———————┘
以下是使用者一进入系统之後就已设定好的系统变量:
$HOME 使用者自己的目录
$PATH 执行命令时所搜寻的目录
Shell Script语言教程
$TZ 时区
$MAILCHECK 每隔多少秒检查是否有新的信件
$PS1 在命令列时的提示号
$PS2 当命令尚未打完时,Shell 要求再输入时的提示号
$MANPATH man 指令的搜寻路径
3. 唯读的使用者变量
和使用者变量相似,只不过这些变量不能被改变。要将使用者变量设成唯读的,只要加上:
readonly var
而若只打readonly则会列出所有唯读的变量。还有一点,系统变量不可以设定成唯读的。
<eg>
┌———————┐
│name=Tom │
│readonly name │
│echo $name │
│name=John │
│readonly │
└———————┘
结果如下:
Tom
name: is read only
readonly name
readonly ......
4. 特殊变量
有些变量是一开始执行Script时就会设定,并且不以加以修改,但我们不叫它
唯读的系统变量,而叫它特殊变量(有些书会叫它唯读的系统变量),因为这
些变量是一执行程式时就有了,况且使用者无法将一般的系统变量设定成唯读
的。以下是一些等殊变量:
$0 这个程式的执行名字
$n 这个程式的第n个参数值,n=1..9
$* 这个程式的所有参数
$# 这个程式的参数个数
$$ 这个程式的PID
$! 执行上一个背景指令的PID
$? 执行上一个指令的返回值
当你执行这个程式时的参数数目超过9 个时,我们可以使用shift 命令将参数
往前移一格,如此即可使用第10个以後的参数。除此之外,吾人可以用set 命
令改变$n及$*,方法如下:
set string
如此$*的值即为string,而分解後则会放入$n。如果set 命令後面没有参数,
则会列出所有已经设定的变量以及其值。
<eg>
档名:ex1 参数:this is a test
┌———————————┐
│echo Filename: $0 │
│echo Arguments: $* │
│echo No. of args.: $# │
│echo 2nd arg.: $2 │
│shift │
│echo No. of args.: $# │
│echo 2nd arg.: $2 │
│set hello, everyone │
│echo Arguments: $* │
│echo 2nd arg.: $2 │
└———————————┘
结果如下:
Filename: ex1
Arguments: this is a test
No. of args.: 4
2nd arg.: is
No. of args.: 3
2nd arg.: a
Arguments: hello, everyone
2nd arg.: everyone
值得一提的是,当你想从键盘输入一变量值时,你可以使用下面的命令:
read var1 var2.....
这时read会将一个字分给一个变量。如果输入的字比变量还多,最後一个变量会将
Shell Script语言教程
剩下的字当成其值。如果输入的字比变量还少,则後面的变量会设成空字串。
如果需要处理数值运算,我们可以使用expr命令。其参数及输出列於附录A。
二、执行命令
在Bourne Shell中有五种方法执行一个命令,而这五种方式所产生的果有些许的不同。
1. 直接下命令
这个方式和在命令列中直接下命令的效果一样。
2. 使用sh命令
sh command
这个档案必须是Bourne Shell的Script,但这个档案并不一定要设成可执行。除此之外和直接下命令的方式
一样。
3. 使用"."命令
. command
这时和使用sh命令相似,只不过它不像sh一般会产生新的process ,相反地,它会在原有的process 下完成
工作。
4. 使用exec命令
exec command
此时这个Script将会被所执行的命令所取代。当这个命令执行完毕之後,这个Script也会随之结束。
5. 使用命令替换
这是一个相当有用的方法。如果想要使某个命令的输出成为另一个命令的参数时,就一定要使用这个方法。
我们将命令列於两个"`" 号之间,而Shell 会以这个命令执行後的输出结果代替这个命令以及两个"`" 符
号。
<eg>
str='Current directory is '`pwd`
echo $str
结果如下:
Current directory is /users/cc/mgtsai
这个意思是pwd 这个命令输出"/users/cc/mgtsai",而後整个字串代替原来的`pwd` 设定str 变量,所以str
变量的内容则会有pwd 命令的输出。(pwd两边的符号是TAB键上方的键输入的)
<eg>
number=`expr $number + 1`
这就是先前所提要作数值运算的方法,基本上expr命令只将运算式解,而後输出到标准输出上。如果要将某
变量设定成其值,非得靠命令替换的方式不可。这个例子是将number变量的值加1 後再存回number变量。
三、流程控制
在介绍流程控制之前,我们先来看看test命令。test命令的参数是条件判断式,当
条件为真时则传回非零值,而条件为伪时则传回零。在所有的流程控制都必须用到
test命令来判断真伪。而test命令的使用方法则列於附录B。
<eg>
test $# = 0
如果执行这个程式没有参数时,会传回非零值代表"$# = 0"这个条件成立。反之则会传回零。
大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读
等等…
通常用" [ ] "来表示条件测试。注意这里的空格很重要。要确保方括号的空格。
[ -f "somefile" ] :判断是否是一个文件
[ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限
[ -n "$var" ] :判断$var变量是否有值
[ "$a" = "$b" ] :判断$a和$b是否相等
执行man test可以查看所有测试表达式可以比较和判断的类型。
直接执行以下脚本:
#!/bin/sh
if [ "$SHELL" = "/bin/bash" ]; then
echo "your login shell is the bash (bourne again shell)"
else
echo "your login shell is not bash but $SHELL"
fi
变量$SHELL包含了登录shell的名称,我们和/bin/bash进行了比较。
三、流程控制
在介绍流程控制之前,我们先来看看test命令。test命令的参数是条件判断式,当
Shell Script语言教程
条件为真时则传回非零值,而条件为伪时则传回零。在所有的流程控制都必须用到
test命令来判断真伪。而test命令的使用方法则列於附录B。 <eg>
test $# = 0
如果执行这个程式没有参数时,会传回非零值代表"$# = 0"这个条件成立。反
之则会传回零。 以下介绍各种流程控制:
1. if then
语法以及流程图如下 语法以及流程图如下
│ FALSE
if (condition) <condition>—┐
then │TRUE │
then-commands then-commands │
fi ├————┘

condition 是一个test命令。往後所介绍的各种流程中的condition 都是test命令。
<eg>
档名:chkarg
┌———————————┐
│if (test $# != 0) │
│ then │
│ echo Arg1: $1 │
│fi │
└———————————┘
$ chkarg Hello
Arg1: Hello
$ chkarg
$
2. if then else
语法以及流程图如下
│ FALSE
if (condition) <condition>—————┐
then │TRUE │
then-commands then-commands else-commands
else ├————————┘
Shell Script语言教程
else-commands │
fi 3. if then elif
语法以及流程图如下
│ FALSE
if (condition1) <condition1>—┐
then │TRUE │ FALSE
commands1 commands1 <condition2>—┐
elif (condition2) │ │TRUE │
then │ commands2 commands3
commands2 ├—————┴————┘
else │
commands3 commands3
fi
<eg>
echo 'word 1: \c'
read word1
echo 'word 2: \c'
read word2
echo 'word 3: \c'
read word3
if (test "$word1" = "$word2" -a "$word2" = "$word3")
then
echo 'Match: words 1, 2, & 3'
elif (test "$word1" = "$word2")
then
echo 'Match: words 1 & 2'
elif (test "$word1" = "$word3")
then
echo 'Match: words 1 & 3'
elif (test "$word2" = "$word3")
then
echo 'Match: words 2 & 3'
else
Shell Script语言教程
echo 'No match'
fi
4. for in
语法以及流程图如下
│ FALSE
for var in arg-list ┌—<arg-list还有东西吗?>—┐
do │ │TRUE │
commands │ 从arg-list取得一项 │
done │ 放到变数var │
│ │ │
│ commands │
<eg> └——————┘ │
┌———————————┐ ┌———————┘
│for a in xx yy zz │ │
│ do │
│ echo $a │
│done │
└———————————┘
结果如下:
xx
yy yy
zz
5. for
语法以及流程图如下
│ FALSE
for var ┌—<参数中还有东西吗?>—┐
do │ │TRUE │
commands │ 从参数中取得一项 │
done │ 放到变数var │
│ │ │
│ commands │
<eg> └—————┘ │
档名:lstarg ┌———————┘
Shell Script语言教程
┌———————————┐ │
│for a │
│ do │
│ echo $a │
│done │
└———————————┘
$lstarg xx yy zz
xx
yy yy
zz
6. while语法以及流程图如下
│ FALSE
while (condition) ┌—<condition>—┐
do │ │TRUE │
commands │ commands │
done └————┘ │
┌————┘

<eg>
┌———————————————┐
│number=0 │
│while (test $number -lt 10) │
│ do │
│ echo "$number\c" │
│ number=`expr $number + 1` │
│done │
│echo │
└———————————————┘
结果如下:
0123456789
7. until语法以及流程图如下
│ TRUE
until (condition) ┌—<condition>—┐
Shell Script语言教程
do │ │FALSE │
commands │ commands │
done └————┘ │
┌————┘

它和while 的不同只在於while 是在条件为真时执行回圈,而until 是在条件为假时执行回圈。
8. break及continue
这两者是用於for, while, until 等回圈控制下。break 会跳至done後方执行,而continue会跳至
done执行,继续执行回圈。
9. case语法以及流程图如下
│ TRUE
case str in <str=pat1>————commands1—┐
pat1) commands1;; │FALSE TRUE │
pat2) commands2;; <str=pat2>————commands2—┤
pat3) commands3;; │FALSE TRUE │
esac <str=pat3>————commands3—┤
│FALSE │
├————————————┘

而pat 除了可以指定一些确定的字串,也可以指定字串的集合,如下
* 任意字串
? 任意字元
[abc] a, b, 或c三字元其中之一
[a-n] 从a到n的任一字元
| 多重选择 <eg>
┌———————————————┐
│echo 'Enter A, B, or C: \c' │
│read letter │
│case $letter in │
│ A|a) echo 'You entered A.';;│
│ B|b) echo 'You entered B.';;│
│ C|c) echo 'You entered C.';;│
│ *) echo 'Not A, B, or C';; │
Shell Script语言教程
│esac │
└———————————————┘
让我们看一个例子。 file命令可以辨别出一个给定文件的文件类型,比如:
file lf.gz
这将返回:
lf.gz: gzip compressed data, deflated, original filename,
last modified: Mon Aug 27 23:09:18 2001, os: Unix
我们利用这一点写了一个叫做smartzip的脚本,该脚本可以自动解压bzip2, gzip 和zip 类型的压缩文件:
#!/bin/sh
ftype=`file "$1"`
case "$ftype" in
"$1: Zip archive"*)
unzip "$1" ;;
"$1: gzip compressed"*)
gunzip "$1" ;;
"$1: bzip2 compressed"*)
bunzip2 "$1" ;;
*) error "File $1 can not be uncompressed with smartzip";;
esac 您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含了传递给该程序的第一个参数值。
也就是说,当我们运行:
smartzip articles.zip
$1 就是字符串 articles.zip
10. 函数
格式如下 function-name()
{
commands
} 而要呼叫此函数,就像在命令列下直接下命令一般。
11.select 表达式是一种bash的扩展应用,尤其擅长于交互式使用。用户可以从一组不同的值中进行选
择。
select var in ... ; do
break
done
.... now $var can be used ....
下面是一个例子:
#!/bin/sh
echo "What is your favourite OS?"
elect var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do
break
one
echo "You have selected $var"
下面是该脚本运行的结果:
What is your favourite OS?
1) Linux
2) Gnu Hurd
3) Free BSD
4) Other
? 1
You have selected Linux
引号
在向程序传递任何参数之前,程序会扩展通配符和变量。这里所谓扩展的意思是程序会把通配符(比如*)替
换成合适的文件名,它变量替换成变量值。为了防止程序作这种替换,您可以使用引号:让我们来看一个例
子,假设在当前目录下有一些文件,两个jpg文件, mail.jpg 和tux.jpg。
#!/bin/sh
echo *.jpg
这将打印出"mail.jpg tux.jpg"的结果。
引号 (单引号和双引号) 将防止这种通配符扩展:
#!/bin/sh
echo "*.jpg"
echo '*.jpg'
这将打印"*.jpg" 两次。
Shell Script语言教程
单引号更严格一些。它可以防止任何变量扩展。双引号可以防止通配符扩展但允许变量扩展。
#!/bin/sh
echo $SHELL
echo "$SHELL"
echo '$SHELL'
运行结果为:
/bin/bash
/bin/bash
$SHELL
最后,还有一种防止这种扩展的方法,那就是使用转义字符——反斜杆:
echo *.jpg
echo $SHELL
这将输出:
*.jpg
$SHELL
Here documents
当要将几行文字传递给一个命令时,here documents(译者注:目前还没有见到过对该词适合的翻译)一种
不错的方法。对每个脚本写一段帮助性的文字是很有用的,此时如果我们四有那个here documents就不必用
echo函数一行行输出。 一个 "Here document" 以 << 开头,后面接上一个字符串,这个字符串还必须出现
在here document的末尾。下面是一个例子,在该例子中,我们对多个文件进行重命名,并且使用
here documents打印帮助:
#!/bin/sh
# we have less than 3 arguments. Print the help text:
if [ $# -lt 3 ] ; then
cat < ren -- renames a number of files using sed regular expressions
USAGE: ren 'regexp' 'replacement' files...
EXAMPLE: rename all *.HTM files in *.html:
ren 'HTM$' 'html' *.HTM
HELP
exit 0
fi
OLD="$1"
NEW="$2"
# The shift command removes one argument from the list of
# command line arguments.
shift
shift
# $* contains now all the files:
for file in $*; do
if [ -f "$file" ] ; then
newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"`
if [ -f "$newfile" ]; then
echo "ERROR: $newfile exists already"
else
echo "renaming $file to $newfile ..."
mv "$file" "$newfile"
fi
fi
done
这是一个复杂一些的例子。让我们详细讨论一下。第一个if表达式判断输入命令行参数是否小于3个 (特殊变
量$# 表示包含参数的个数) 。如果输入参数小于3个,则将帮助文字传递给cat命令,然后由cat命令将其打
印在屏幕上。打印帮助文字后程序退出。 如果输入参数等于或大于3个,我们就将第一个参数赋值给变量
OLD,第二个参数赋值给变量NEW。下一步,我们使用shift命令将第一个和第二个参数从参数列表中删除,这
样原来的第三个参数就成为参数列表$*的第一个参数。然后我们开始循环,命令行参数列表被一个接一个地
被赋值给变量$file。接着我们判断该文件是否存在,如果存在则通过sed命令搜索和替换来产生新的文件
名。然后将反短斜线内命令结果赋值给newfile。这样我们就达到了我们的目的:得到了旧文件名和新文件
名。然后使用mv命令进行重命名。
函数
Shell Script语言教程
如果您写了一些稍微复杂一些的程序,您就会发现在程序中可能在几个地方使用了相同的代码,并且您也会
发现,如果我们使用了函数,会方便很多。一个函数是这个样子的:
functionname()
{
# inside the body $1 is the first argument given to the function
# $2 the second ...
body
}
您需要在每个程序的开始对函数进行声明。
下面是一个叫做xtitlebar的脚本,使用这个脚本您可以改变终端窗口的名称。这里使用了一个叫做help的函
数。正如您可以看到的那样,这个定义的函数被使用了两次。
#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
cat < xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole
USAGE: xtitlebar [-h] "string_for_titelbar"
OPTIONS: -h help text
EXAMPLE: xtitlebar "cvs"
HELP
exit 0
}
# in case of error or if -h is given we call the function help:
[ -z "$1" ] && help
[ "$1" = "-h" ] && help
# send the escape sequence to change the xterm titelbar:
echo -e "33]0;$107"
#
在脚本中提供帮助是一种很好的编程习惯,这样方便其他用户(和您)使用和理解脚本。
命令行参数
我们已经见过$* 和 $1, $2 ... $9 等特殊变量,这些特殊变量包含了用户从命令行输入的参数。迄今为
止,我们仅仅了解了一些简单的命令行语法(比如一些强制性的参数和查看帮助的-h选项)。但是在编写更
复杂的程序时,您可能会发现您需要更多的自定义的选项。通常的惯例是在所有可选的参数之前加一个减
号,后面再加上参数值 (比如文件名)。
有好多方法可以实现对输入参数的分析,但是下面的使用case表达式的例子无遗是一个不错的方法。
#!/bin/sh
help()
{
cat < This is a generic command line parser demo.
USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2
HELP
exit 0
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;; # function help is called
-f) opt_f=1;shift 1;; # variable opt_f is set
-l) opt_l=$2;shift 2;; # -l takes an argument -> shift by 2
--) shift;break;; # end of options
-*) echo "error: no such option $1. -h for help";exit 1;;
*) break;;
esac
done
Shell Script语言教程
echo "opt_f is $opt_f"
echo "opt_l is $opt_l"
echo "first arg is $1"
echo "2nd arg is $2"
您可以这样运行该脚本:
cmdparser -l hello -f -- -somefile1 somefile2
返回的结果是:
opt_f is 1
opt_l is hello
first arg is -somefile1
2nd arg is somefile2
这个脚本是如何工作的呢?脚本首先在所有输入命令行参数中进行循环,将输入参数与case表达式进行比
较,如果匹配则设置一个变量并且移除该参数。根据unix系统的惯例,首先输入的应该是包含减号的参数。
实例
一般编程步骤
现在我们来讨论编写一个脚本的一般步骤。任何优秀的脚本都应该具有帮助和输入参数。并且写一个伪脚本
(framework.sh),该脚本包含了大多数脚本都需要的框架结构,是一个非常不错的主意。这时候,在写一
个新的脚本时我们只需要执行一下copy命令:
cp framework.sh myscript
然后再插入自己的函数。
让我们再看两个例子:
二进制到十进制的转换
脚本 b2d 将二进制数 (比如 1101) 转换为相应的十进制数。这也是一个用expr命令进行数学运算的例子:
#!/bin/sh
# vim: set sw=4 ts=4 et:
help()
{
cat < b2h -- convert binary to decimal
USAGE: b2h [-h] binarynum
OPTIONS: -h help text
EXAMPLE: b2h 111010
will return 58
HELP
exit 0
}
error()
{
# print an error and exit
echo "$1"
exit 1
}
lastchar()
{
# return the last character of a string in $rval
if [ -z "$1" ]; then
# empty string
rval=""
return
fi
# wc puts some space behind the output this is why we need sed:
numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
# now cut out the last char
rval=`echo -n "$1" | cut -b $numofchar`
Shell Script语言教程
}
chop()
{
# remove the last character in string and return it in $rval
if [ -z "$1" ]; then
# empty string
rval=""
return
fi
# wc puts some space behind the output this is why we need sed:
numofchar=`echo -n "$1" | wc -c | sed 's/ //g' `
if [ "$numofchar" = "1" ]; then
# only one char in string
rval=""
return
fi
numofcharminus1=`expr $numofchar "-" 1`
# now cut all but the last char:
rval=`echo -n "$1" | cut -b 0-${numofcharminus1}`
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;; # function help is called
--) shift;break;; # end of options
-*) error "error: no such option $1. -h for help";;
*) break;;
esac
done
# The main program
sum=0
weight=1
# one arg must be given:
[ -z "$1" ] && help
binnum="$1"
binnumorig="$1"
while [ -n "$binnum" ]; do
lastchar "$binnum"
if [ "$rval" = "1" ]; then
sum=`expr "$weight" "+" "$sum"`
fi
# remove the last position in $binnum
chop "$binnum"
binnum="$rval"
weight=`expr "$weight" "*" 2`
done
echo "binary $binnumorig is decimal $sum"
#
该脚本使用的算法是利用十进制和二进制数权值 (1,2,4,8,16,..),比如二进制"10"可以这样转换成十进
制:
0 * 1 + 1 * 2 = 2
为了得到单个的二进制数我们是用了lastchar 函数。该函数使用wc –c计算字符个数,然后使用cut命令取
出末尾一个字符。Chop函数的功能则是移除最后一个字符。
文件循环程序
或许您是想将所有发出的邮件保存到一个文件中的人们中的一员,但是在过了几个月以后,这个文件可能会
变得很大以至于使对该文件的访问速度变慢。下面的脚本rotatefile 可以解决这个问题。这个脚本可以重命
名邮件保存文件(假设为outmail)为outmail.1,而对于outmail.1就变成了outmail.2 等等等等...
#!/bin/sh
# vim: set sw=4 ts=4 et:
ver="0.1"
help()
Shell Script语言教程
{
cat < rotatefile -- rotate the file name
USAGE: rotatefile [-h] filename
OPTIONS: -h help text
EXAMPLE: rotatefile out
This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1
and create an empty out-file
The max number is 10
version $ver
HELP
exit 0
}
error()
{
echo "$1"
exit 1
}
while [ -n "$1" ]; do
case $1 in
-h) help;shift 1;;
--) break;;
-*) echo "error: no such option $1. -h for help";exit 1;;
*) break;;
esac
done
# input check:
if [ -z "$1" ] ; then
error "ERROR: you must specify a file, use -h for help"
fi
filen="$1"
# rename any .1 , .2 etc file:
for n in 9 8 7 6 5 4 3 2 1; do
if [ -f "$filen.$n" ]; then
p=`expr $n + 1`
echo "mv $filen.$n $filen.$p"
mv $filen.$n $filen.$p
fi
done
# rename the original file:
if [ -f "$filen" ]; then
echo "mv $filen $filen.1"
mv $filen $filen.1
fi
echo touch $filen
touch $filen
这个脚本是如何工作的呢?在检测用户提供了一个文件名以后,我们进行一个9到1的循环。文件9被命名为
10,文件8重命名为9等等。循环完成之后,我们将原始文件命名为文件1同时建立一个与原始文件同名的空文
件。
调试
最简单的调试命令当然是使用echo命令。您可以使用echo在任何怀疑出错的地方打印任何变量值。这也是绝
大多数的shell程序员要花费80%的时间来调试程序的原因。Shell程序的好处在于不需要重新编译,插入一个
echo命令也不需要多少时间。
shell也有一个真实的调试模式。如果在脚本"strangescript" 中有错误,您可以这样来进行调试:
sh -x strangescript
这将执行该脚本并显示所有变量的值。
shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用:
sh -n your_script
这将返回所有语法错误。
Shell Script语言教程
我们希望您现在可以开始写您自己的shell脚本,希望您玩得开心。
□C Shell
C Shell 有些特性和Bourne Shell一样,但有些不相同。这里介绍与Bourne Shell
不相同的地方。
一、变数
1. 字串变数
这个部分和Bourne Shell的变数一样,只不过在设定变数值时不能使用Bourne
Shell的方式,而必须打:
set var=value
2. 数字运算
基本上C Shell 没有数字变数,但C Shell 却有简单的方法处理数字运算:
@ var operator expression
operator可以是C 语言中的=, +=, -=,......,而expression则是运算式。运
算式的运算子如下:
A. () 改变计算的顺序
~@
B. ~ 位元NOT运算
@~~
! 逻辑否定
C. % 取馀数
C. % 取馀数
/ 除
* 乘
- 减
+ 加
D. >> 右移
<< 左移
E. > 大於
< 小於
>= 大於等於
<= 小於等於
!= 不等於
== 等於
F. & 位元AND运算
^ 位元XOR运算
| 位元OR 运算
G. && 逻辑AND
|| 逻辑OR
除此之外,我们也可以检验一个档案的状态,如下
-n filename
而-n可为下列之一
-d 档案是一个目录档案
-e 档案存在
-f 档案为一般的档案
-o 使用者拥有这个档案
-r 使用者可以读取这个档案
-w 使用者可以写入这个档案
-x 使用者可以执行这个档案
-z 档案长度为0
<eg>
@ count = count + 1
@ flag = -e /users/cc/mgtsai/mail && -e /usr/spool/mail
3. 阵列
在C Shell 中,我们可以宣告阵列变数,方式如下
set var=(val1 val2 ......)
而var[1]之值为val1,var[2]之值为val2......。而$var代表整个阵列。我们
可以用$#var 来计算阵列个数,也可以用$?var 来检查某个变数是否已宣告。
4. 特殊变数
$argv 和Bourne Shell的$*相似,只不过这是一个阵列。
$argv[n] 和Bourne Shell的$n相同,但不受个数限制。
$#argv 和Bourne Shell的$#相同
Shell Script语言教程
$home 和Bourne Shell的$HOME相同
$path 和Bourne Shell的$PATH相似,只不过这是一个阵列
$prompt 和Bourne Shell的$PS1相同
$shell Shell的路径名称
$status 和Bourne Shell的$?相同
$$ 和Bourne Shell的$$相同
$< 键盘输入
二、执行命令
基本上和Bourne Shell相同,只有一点在Bourne Shell中的"." 命令在C Shell 中
则为"source"命令。
三、流程控制
在C Shell 中流程控制不像Bourne Shell中一般需要使用test命令。相反地,它和
C 语言类似只要在条件中写出运□式即可。当运算结果不为零时,其值为真,为零
时其值为伪。以下是C Shell的流程控制
1. if
语法如下
if (expression) simple-command
2. goto
语法如下
goto label
这时程式会跳至以l"label:"开头的那一行执行
<eg>
if ($#argv == 2) goto goodargs
echo 'Please use two arguments.'
exit
goodrags:
...
3. if then else
这和Bourne Shell的if then, if then else, if then elif 相似。语法如下
A. if (expression) then
commands
endif
B. if (expression) then
commands
else
commands
endif
C. if (expression) then
commands
else if (expression) then
commands
else
commands
endif
4. foreach
这和Bourne Shell的for in相似。语法如下
foreach var (arg-list)
commands
end
5. while
这和Bourne Shell的while相似。语法如下
while (expression)
commands
end
6. break及continue
这和Bourne Shell的break 及continue相似,是用来中断foreach 及while 回
圈。
7. switch
这和Bourne Shell的case相似。语法如下
Shell Script语言教程
switch (string)
case pat1:
commands1
breaksw
case pat2:
commands2
breaksw
case pat3:
commands3
breaksw
endsw
□附录A expr命令
命令格式
expr expression
叙述
expression是由字串以及运算子所组成,每个字串或是运算子之间必须用空白隔开
。下表是运算子的种类及功能,而优先顺序则以先後次序排列,我们可以利用小括
号来改变运算的优先次序。其运算结果则输出至标准输出上。
: 字串比较。比较的方式是以两字串的第一个字母开始,而以第二个字串的
字母结束。如果相同时,则输出第二个字串的字母个数,如果不同时则传
回0 。
* 乘法
/ 除法
% 取馀数
+ 加法
- 减法
< 小於
<= 小於等於
= 等於
!= 不等於
>= 大於等於
> 大於
& AND运算
| OR运算
当expression中含有"*", "(", ")" 等符号时,必须在其前面加上"\" ,以免被
Shell 解释成其它意义。
<eg> expr 2 \* \( 3 + 4 \) 其输出为14
□附录B test命令
命令格式
test expression
叙述
expression中包含一个以上的判断准则以作为test评诂的标准。两准则间用"-a"代
表逻辑AND 运算,"-o"代表逻辑OR运算,而在准则前放置一"!" 代表NOT 运算。如
果没有括号,则优先权则为"!" > "-a" > "-o" 。和expr命令相同,相使用左右括
号时,必须在其前面加上"\" 。以下是有关准则的叙述(合叙述时传回真,否则传
回伪):
string string不为空白字串
-n string string的长度大於0
-z string string的长度等於0
string1=string2 string1等於string2
string1!=string2 string1不等於string2
int1 -gt int2 int1大於int2
int1 -ge int2 int1大於等於int2
int1 -eq int2 int1等於int2
int1 -ne int2 int1不等於int2
int1 -le int2 int1小於等於int2
int1 -lt int2 int1小於int2
-r filename 档案可读取
-w filename 档案可写入
-x filename 档案可执行
Shell Script语言教程
-f filename 档案为一般档
-d filename 档案为目录
-s filename 档案为非空的一般档
<eg> test -r "$filename" -a -s "$filename"

你可能感兴趣的:(shell,String,脚本,File,command,语言)