`

Linux 脚本编写基础

 
阅读更多

1. Linux 脚本编写基础
1.1 语法基本介绍
1.1.1 开头
程序必须以下面的行开始(必须方在文件的第一行):

#!/bin/sh

  符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行程序。
  当编辑好脚本时,如果要执行该脚本,还必须使其可执行。
  要使脚本可执行:
编译 chmod +x filename 这样才能用./filename 来运行
1.1.2 注释
  在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。我们真诚地建议您在程序中使用注释。
  如果您使用了注释,那么即使相当长的时间内没有使用该脚本,您也能在很短的时间内明白该脚本的作用及工作原理。
1.1.3 变量
  在其他编程语言中您必须使用变量。在shell编程中,所有的变量都由字符串组成,并且您不需要对变量进行声明。要赋值给一个变量,您可以这样写:
#!/bin/sh

#对变量赋值:
a="hello world"
# 现在打印变量a的内容:
echo "A is:"
echo $a

有时候变量名很容易与其他文字混淆,比如:
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

1.1.4 环境变量
由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因为通常情况下仅仅在登录脚本中使用环境变量。
1.1.5 Shell命令和流程控制
在shell脚本中可以使用三类命令:
1)Unix 命令:
  虽然在shell脚本中可以使用任意的unix命令,但是还是由一些相对更常用的命令。这些命令通常是用来进行文件和文字操作的。
常用命令语法及功能
  echo "some text": 将文字内容打印在屏幕上
  ls: 文件列表
  wc –l filewc -w filewc -c file: 计算文件行数计算文件中的单词数计算文件中的字符数
  cp sourcefile destfile: 文件拷贝
  mv oldname newname : 重命名文件或移动文件
  rm file: 删除文件
  grep 'pattern' file: 在文件内搜索字符串比如:grep 'searchstring' file.txt
  cut -b colnum file: 指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出每行第5个到第9个字符cut -b5-9 file.txt千万不要和cat命令混淆,
这是两个完全不同的命令
  cat file.txt: 输出文件内容到标准输出设备(屏幕)上
  file somefile: 得到文件类型
  read var: 提示用户输入,并将输入赋值给变量
  sort file.txt: 对file.txt文件中的行进行排序
  uniq: 删除文本文件中出现的行列比如: sort file.txt | uniq
  expr: 进行数学运算Example: add 2 and 3expr 2 "+" 3
  find: 搜索文件比如:根据文件名搜索find . -name filename -print
  tee: 将数据输出到标准输出设备(屏幕) 和文件比如:somecommand | tee outfile
  basename file: 返回不包含路径的文件名比如: basename /bin/tux将返回 tux
  dirname file: 返回文件所在路径比如:dirname /bin/tux将返回 /bin
  head file: 打印文本文件开头几行
  tail file : 打印文本文件末尾几行
  sed: Sed是一个基本的查找替换程序。可以从标准输入(比如命令管道)读入文本,并将
结果输出到标准输出(屏幕)。该命令采用正则表达式(见参考)进行搜索。不要和shell中的通配符相混淆。比如:将linuxfocus 替换为LinuxFocus :
cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file

  awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F指定其他分割符。
cat file.txt | awk -F, '{print $1 "," $3 }'
这里我们使用,作为字段分割符,同时打印第一个和第三个字段。如果该文件内容如下: Adam Bor, 34, IndiaKerry Miller, 22, USA命令输出结果为:Adam Bor, IndiaKerry Miller, USA
2) 概念: 管道, 重定向和 backtick
  这些不是系统命令,但是他们真的很重要。
  管道 (|) 将一个命令的输出作为另外一个命令的输入。
grep "hello" file.txt | wc -l

  在file.txt中搜索包含有”hello”的行并计算其行数。
  在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。
  重定向:将命令的结果输出到文件,而不是标准输出(屏幕)。
  > 写入文件并覆盖旧文件
  >> 加到文件的尾部,保留旧文件内容。

反短斜线
 使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。
命令:
find . -mtime -1 -type f -print

  用来查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。如果您想将所有查找到的文件打一个包,则可以使用以下脚本:
#!/bin/sh
# The ticks are backticks (`) not normal quotes ('):
tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print`

3) 流程控制
1.if
  "if" 表达式 如果条件为真则执行then后面的部分:
if ....; then
  ....
elif ....; then
  ....
else
  ....
fi

大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读等等…
  通常用" [ ] "来表示条件测试。注意这里的空格很重要。要确保方括号的空格。
[ -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进行了比较。

快捷操作符
熟悉C语言的朋友可能会很喜欢下面的表达式:
[ -f "/etc/shadow" ] && echo "This computer uses shadow passwors"

  这里 && 就是一个快捷操作符,如果左边的表达式为真则执行右边的语句。
您也可以认为是逻辑运算中的与操作。上例中表示如果/etc/shadow文件存在则打印” This computer uses shadow passwors”。同样或操作(||)在shell编程中也是可用的。这里有个例子:
#!/bin/sh
mailfolder=/var/spool/mail/james
[ -r "$mailfolder" ]
'  '{ echo "Can not read $mailfolder" ; exit 1; }
echo "$mailfolder has mail from:"
grep "^From " $mailfolder

该脚本首先判断mailfolder是否可读。如果可读则打印该文件中的"From" 一行。如果不可读则或操作生效,打印错误信息后脚本退出。这里有个问题,那就是我们必须有两个命令:
  -打印错误信息
  -退出程序
  我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。一般函数将在下文提及。
  不用与和或操作符,我们也可以用if表达式作任何事情,但是使用与或操作符会更便利很多。
2.case
case :表达式可以用来匹配一个给定的字符串,而不是数字。
case ... in
...) do something here ;;
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" ;;
*) echo "File $1 can not be uncompressed with smartzip";;
esac

  您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含了传递给该程序的第一个参数值。
也就是说,当我们运行:
smartzip articles.zip

$1 就是字符串 articles.zip
3. selsect
select 表达式是一种bash的扩展应用,尤其擅长于交互式使用。用户可以从一组不同的值中进行选择。
select var in ... ; do
 break
done
.... now $var can be used ....

下面是一个例子:
#!/bin/sh
echo "What is your favourite OS?"
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do
    break
done
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

4.loop
loop表达式:
while ...; do
....
done

  while-loop 将运行直到表达式测试为真。will run while the expression that we test for is true.
关键字"break" 用来跳出循环。而关键字”continue”用来不执行余下的部分而直接跳到下一个循环。
  
for-loop表达式查看一个字符串列表 (字符串用空格分隔) 然后将其赋给一个变量:
for var in ....; do
  ....
done

在下面的例子中,将分别打印ABC到屏幕上:
#!/bin/sh
for var in A B C ; do
  echo "var is $var"
done

下面是一个更为有用的脚本showrpm,其功能是打印一些RPM包的统计信息:
#!/bin/sh
# list a content summary of a number of RPM packages
# USAGE: showrpm rpmfile1 rpmfile2 ...
# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm
for rpmpackage in $*; do
 if [ -r "$rpmpackage" ];then
  echo "=============== $rpmpackage =============="
  rpm -qi -p $rpmpackage
 else
  echo "ERROR: cannot read file $rpmpackage"
 fi
done

  这里出现了第二个特殊的变量$*,该变量包含了所有输入的命令行参数值。
如果您运行showrpm openssh.rpm w3m.rpm webgrep.rpm
此时 $* 包含了 3 个字符串,即openssh.rpm, w3m.rpm and webgrep.rpm.
5. 引号
在向程序传递任何参数之前,程序会扩展通配符和变量。这里所谓扩展的意思是程序会把通配符(比如*)替换成合适的文件名,它变量替换成变量值。为了防止程序作这种替换,您可以使用引号:让我们来看一个例子,假设在当前目录下有一些文件,两个jpg文件, mail.jpg 和tux.jpg。
#!/bin/sh
echo *.jpg

  这将打印出"mail.jpg tux.jpg"的结果。
  引号 (单引号和双引号) 将防止这种通配符扩展:
#!/bin/sh
echo "*.jpg"
echo '*.jpg'
  这将打印"*.jpg" 两次。
  单引号更严格一些。它可以防止任何变量扩展。双引号可以防止通配符扩展但允许变量扩展。
#!/bin/sh
echo $SHELL
echo "$SHELL"
echo '$SHELL'

  运行结果为:
/bin/bash
/bin/bash
$SHELL

  最后,还有一种防止这种扩展的方法,那就是使用转义字符——反斜杠:
echo *.jpg
echo $SHELL

  这将输出:
*.jpg
$SHELL

6. 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命令进行重命名。
4)函数
如果您写了一些稍微复杂一些的程序,您就会发现在程序中可能在几个地方使用了相同的代码,并且您也会发现,如果我们使用了函数,会方便很多。一个函数是这个样子的:
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
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系统的惯例,首先输入的应该是包含减号的参数.
第2部分 实例
现在我们来讨论编写一个脚本的一般步骤。任何优秀的脚本都应该具有帮助和输入参数。并且写一个伪脚本(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`
}
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()
{
  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编程简介 
Bourne Shell 
介绍: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 defind 
-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选项。 

(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等
 
示例:
$PS1="test:";export PS1 
test: PS1="\$";export PS1 
$echo $MAIL 
/var/mail/username 

(7)保留字符及其含义 
$ shell变量名的开始,如$var 
| 管道,将标准输出转到下一个命令的标准输入 
# 注释开始 
& 在后台执行一个进程 
? 匹配一个字符 
* 匹配0到多个字符(与DOS不同,可在文件名中间使用,并且含.) 
$- 使用set及执行时传递给shell的标志位 
$! 最后一个子进程的进程号 
$# 传递给shell script的参数个数 
$* 传递给shell script的参数 
$@ 所有参数,个别的用双引号括起来 
$? 上一个命令的返回代码 
$0 当前shell的名字 
$n (n:1-) 位置参数 
$$ 进程标识号(Process Identifier Number, PID) 
>file 输出重定向 
`command` 命令替换,如 filename=`basename /usr/local/bin/tcsh` 
>>fiile 输出重定向,append 
转义符及单引号: 
$echo "$HOME $PATH" 
/home/hbwork /opt/kde/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin: 
$echo '$HOME $PATH' 
$HOME $PATH 
$echo \$HOME $PATH 
$HOME /opt/kde/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/hbwork/bin 

其他: 
$dir=ls 
$$dir 
$alias dir ls 
$dir 
ls > filelist 
ls >> filelist 
wc -l < filelist 
wc -l filelist 
sleep 5; echo 5 seconds reaches; ls -l 
ps ax |egrep inetd 
find / -name core -exec rm {} \; & 
filename=`date "+%Y%m%d"`.log 

2. shell变量 
变量:代表某些值的符号,如$HOME,cd命令查找$HOME,在计算机语言中可以使用变量可以进行多种运算和控制。 
Bourne Shell有如下四种变量: 
.用户自定义变量 
.位置变量即 shell script之参数 
.预定义变量(特殊变量) 
.环境变量(参考shell定制部分) 
(1)用户自定义变量(数据的存储) 
$ 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 Saturday 

Shell变量赋值从右从左进行(Linux Shell/bash从左向右赋值!) 
$ X=$Y Y=y 
$ echo $X 

$ Z=z Y=$Z 
$ echo $Y 

使用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=hbwork 
$ echo Hello ${UNAME:-there} 
Hello hbwork 

.另一种情况:改变变量的值,格式如下: 
${variable:=value}
 
例: 
$ echo Hello $UNAME 
Hello 
$ echo Hello ${UNAME:=there} 
Hello there 
$ echo $UNAME #变量值并未发生变化 
there 
$
 
.变量替换中使用命令替换 
$USERDIR=${$MYDIR:-`pwd`} 

.在变量已赋值时进行替换 
${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表示内容通常为当前执行程序的文件名。 
.防止变量值被替换 readonly variable 
.使用export命令输出变量,使得变量对子shell可用,当shell执行一下程序时,shell将为其设置一个新的环境让其执行,这称之了subshell. 在Bourne Shell中变量通常被认为是本地变量,也就是说在对其赋值之外的shell环境之外是不认识此变量的。使用export对subshell可用。 
例:对变量PS1的export操作,shell的提示符将发生变化。 
$ PS1=`hostname`$ 
peony$sh 
$ echo $PS1 
$ <-输出结果 
$ exit 
peony$export PS1 
peony$sh 
peony$ echo $PS1 
peony$ <-输出结果 
peony$
 

3.Shell Script编程 
目的:使用UNIX所提供的最常用工具来完成所需复杂任务的强大功能。 
(1)最简单的Shell 编程 
$ls -R / |grep myname |more
 
每天数据的备份: 
$ cd /usr/yourname; ls * |cpio -o > /dev/rmt/0h 

书写程序的目的是一次编程,多次使用(执行)! 
$ cat > backup.sh 
cd /home/hbwork 
ls * | cpio -o > /dev/rmt/0h 
^D
 
执行: 
$ 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命令进行进行条件测试 
格式: test conditions 
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 $? 

$ str1="abcd " 
$ test $str1=abcd 
$ echo $? 

Note: 在test处理含有空格的变量时最好用引号将变量括起来,否则会出现错误的结果, 
因为shell在处理命令行时将会去掉多余的空格,而用引号括起来则可以防止 
shell去掉这些空格. 
例: 
$ str1=" " 
$ test $str1 
$ echo $? 

$ test "$str1" 
$ echo $? 

$ test -n $str1 
test: argument expected 
$ test -n "$str1" 
$ echo $? 

$
 
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 
$ test $int1 -eq $int2 
$ echo $? 

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. 测试条件之否定,使用! 
例: 
$ cat /dev/null > empty 
$ test -r empty 
$ echo $? 

$ test -s empty 

$ test ! -s empty 
$ echo $? 

e. 测试条件之逻辑运算 
-a And 
-o Or 
例: $ test -r empty -a -s empty 
$ echo $? 

f. 进行test测试的标准方法 
因为test命令在 shell编程中占有很重要的地位,为了使shell能同其他编程语言一样 
便于阅读和组织, Bourne Shell在使用test测试时使用了另一种方法:用方括号将整个 
test测试括起来: 
$ int1=4 
$ [ $int1 -gt 2 ] 
$ echo $? 

例: 重写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 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 message 输出结果后不换行 
echo -n "Do you want to continue: Y or N" 
read ANSWER 
if [ $ANSWER=N -o $ANSWER=n ] 
then 
exit 
fi 
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 
esac 
在上例中,*指默认匹配动作。此外,case模式中也可以使用逻辑操作,如下所示: 
pattern1 | pattern2 ) command 
command ;; 
这样可以将上面示例程序中允许用户输入数字或每一个大写字母。 
case "$CHOICE" in 
1|R) echo "Restore";; 
2|B) echo "Backup";; 
3|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 completed" 
$ sh square.sh 



16 
25 
Job completed 
<2> until循环结构: 
格式: 
until command 
do 
command 
command 
.... 
command 
done 
示例:使用until结构计算1-5的平方 
#!/bin/sh 
int=1 
until [ $int -gt 5 ] 
do 
sq=`expr $int \* $int` 
echo $sq 
int=`expr $int + 1` 
done 
echo "Job completed" 
<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 

使用shift时,每进行一次移位,$#减1,使用这一特性可以用until循环对每个参 
数进行处理,如下示例中是一个求整数和的shell script: 
#!/bin/sh 
# sumints - 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 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 c d e; do echo $letter;done 





对当前目录下的所有文件操作: 
$ for i in * 
do 
if [ -f $i ] 
then 
echo "$i is a file" 
elif [ -d $i ] 
echo "$i 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命令。这种无限循环通常是使用true或false命令开始的。UNIX 
系统中的true总是返加0值,而false则返回非零值。如下所示: 
#一直执行到程序执行了break或用户强行中断时才结束循环 
while true 
do 
command 
.... 
command 
done 
上面所示的循环也可以使用until false, 如下: 
until false 
do 
command 
.... 
command 
done 
在如下shell script中同时使用了continue,break以及case语句中的正规表达式用法: 
#!/bin/sh 
# Interactive program to restore, backup, or unload 
# a directory 
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 Unload directory" 
echo "4 Quit" 
echo 
# Read the user's selection 
echo -n "Enter Choice: " 
read CHOICE 
case $CHOICE in 
[1-3] ) 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;; 
4) :;; # :为空语句,不执行任何动作 
*) echo "Sorry, $CHOICE is not a valid choice" 
continue 
esac 
case "$CHOICE" in 
1) echo "Restoring..." 
cpio -i 
2) echo "Archiving..." 
ls | cpio -o >/dev/rmt/0h;; 
3) echo "Unloading..." 
ls | cpio -o >/dev/rmt/0h;; 
4) echo "Quitting" 
break;; 
esac 
#Check for cpio errors 
if [ $? -ne 0 ] 
then 
echo "A problem has occurred during the process" 
if [ $CHOICE = 3 ] 
then 
echo "The directory will not be erased" 
fi 
echo "Please check the device and try again" 
continue 
else 
if [ $CHOICE = 3 ] 
then 
rm * 
fi 
fi 
done 

(6)结构化编程:定义函数 
同其他高级语言一样,shell也提供了函数功能。函数通常也称之为子过程(subroutine), 
其定义格式如下: 
funcname() 

command 
... 
command; #分号 

定义函数之后,可以在shell中对此函数进行调用,使用函数定义可以将一个复杂的程序分 
为多个可管理的程序段,如下所示: 
# start program 
setup () 
{ command list ; } 
do_data () 
{ command list ; } 
cleanup () 
{ command list ; } 
errors () 
{ command list ; } 
setup 
do_data 
cleanup 
# end program 
技巧: 
. 在对函数命名时最好能使用有含义的名字,即函数名能够比较准确的描述函数所完成 
的任务。 
. 为了程序的维护方便,请尽可能使用注释 

使用函数的另一个好处就是可以在一个程序中的不同地方执行相同的命令序列(函数), 
如下所示: 
iscontinue() 

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编程中调用iscontinue确定是否继续执行: 
if iscontinue 
then 
continue 
else 
break 
fi 

** shell函数与shell程序非常相似,但二者有一个非常重要的差别:shell程序是由子shell 
执行的,而shell函数则是作为当前shell的一部分被执行的,因此在当前shell中可以改 
变函数的定义。此外在任意shell(包括交互式的shell)中均可定义函数,如: 
$ dir 
dir: not found 
$ dir () { ls -l ;} 
$ dir 
total 5875 
-rw-r--r-- 1 hbwork 100 Nov 10 23:16 doc 
-rw-r--r-- 1 hbwork 2973806 Nov 10 23:47 ns40docs.zip 
-rw-r--r-- 1 hbwork 1715011 Nov 10 23:30 ns840usr.pdf 
-rw-r--r-- 1 hbwork 1273409 Sep 23 1998 radsol21b6.tar.Z 
-rw-r--r-- 1 hbwork 7526 Nov 10 23:47 wget-log 
-rw-r--r-- 1 hbwork 1748 Nov 13 21:51 wget-log.1 
$ unset dir 
$ dir () { 
> echo "Permission Link Owner Group File_SZ LastAccess FileName" 
> echo "-----------------------------------------------------------" 
> ls -l $*; 
> } 
$ dir 
Permission Link Owner Group File_SZ LastAccess FileName 
----------------------------------------------------------- 
total 5875 
-rw-r--r-- 1 hbwork 100 Nov 10 23:16 doc 
-rw-r--r-- 1 hbwork 2973806 Nov 10 23:47 ns40docs.zip 
-rw-r--r-- 1 hbwork 1715011 Nov 10 23:30 ns840usr.pdf 
-rw-r--r-- 1 hbwork 1273409 Sep 23 1998 radsol21b6.tar.Z 
-rw-r--r-- 1 hbwork 7526 Nov 10 23:47 wget-log 
-rw-r--r-- 1 hbwork 1748 Nov 13 21:51 wget-log.1 
通常情况下,shell script是在子shell中执行的,困此在此子shell中对变量所作的 
修改对父shell不起作用。点(.) 命令使用shell在不创建子shell而由当前shell读取 
并执行一个shell script, 可以通过这种方式来定义函数及变量。此外点(.)命令最 
常用的功能就是通过读取.profile来重新配置初始化login变量。如下所示: 
$ . .profile 
(csh相对于.命令的是source命令). 
(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 
当然可以根据自己的需要进行多种条件命令的组合,在此不多讲述。 

(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 abbreviation 
h) date '+%h ';; # Month abbreviation 
r) date '+%r ';; # AM-PM time 
\?) echo "Invalid option $OPTION";; 
esac 
done 
fi 
$ newdate -J 
94031 
$ 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 3 -v fileB 
fileB1 
fileB2 
fileB3 

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/teresa 

但此程序在执行时系统提示符并不会改变,因为此程序是在子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) 

$ echo $NUMBER 

$ { A=2;B=2;NUMBER=`expr $A + $B`; echo $NUMBER; } 

$ echo $NUMBER 


总结: 
在本章中讲述了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。
分享到:
评论

相关推荐

    linux脚本编写基础

    linux脚本编写基础 linux脚本编写基础 linux脚本编写基础 linux脚本编写基础 linux脚本编写基础

    Linux脚本编写基础

    Linux脚本编写基础

    Linux脚本编写基础.pdf

    Linux脚本编写基础.pdf

    linux脚本编写基础[参考].pdf

    linux脚本编写基础[参考].pdf

    Linux shell脚本编写基础

    Linux shell脚本编写基础 在进行linux测试时编写脚本是必不可少的,Shell脚本的名称可以随便定义,也不要什么后缀名, 例如可以写abc,smartzip这类名称,运行时只要键入 ./smartzip就能运行脚本了。。 每行命令开头...

    Linux 脚本编写

    1. Linux 脚本编写基础 1.1 语法基本介绍 1.1.1 开头 程序必须以下面的行开始(必须方在文件的第一行): #!/bin/sh  符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行...

    Linux 下脚本编写基础

    Linux脚本编写基础,还不错,保证你收获不小! Linux脚本编写基础,还不错,保证你收获不小!

    shell脚本编写基础

    第1部分. Linux 脚本编写基础 1.1 语法基本介绍 在进行linux测试时编写脚本是必不可少的,Shell脚本的名称可以随便定义,也不要什么后缀名,例如可以写abc,smartzip这类名称,运行时只要键入

    Linux Shell脚本编写基础

    Linux Shell脚本编写基础

    Linuxshell脚本编写基础

    Linuxshell脚本编写基础 讲解linux 脚本的编写 适合自学Linux的同学

    Linux脚本编写教程

    完美的Linux脚本教程,从基础的注释、回显、流程控制到高级的函数。带有几个实例,如遍历当前目录下的文件。

Global site tag (gtag.js) - Google Analytics