-- 我们的技术是你的Q?a target="_blank">www.icwin.net
1. Linux 脚本~写基础 1.1 语法基本介绍 1.1.1 开?br />E序必须以下面的行开始(必须方在文g的第一行)Q? #!/bin/sh W号#!用来告诉pȝ它后面的参数是用来执行该文g的程序。在q个例子中我们?bin/sh来执行程序? 当编辑好脚本Ӟ如果要执行该脚本Q还必须使其可执行? 要脚本可执行: ~译 chmod +x filename q样才能?/filename 来运?br /> 1.1.2 注释 在进行shell~程Ӟ?开头的句子表示注释Q直到这一行的l束。我们真诚地您在E序中用注释?br />如果您用了注释Q那么即使相当长的时间内没有使用该脚本,您也能在很短的时间内明白该脚本的作用 及工作原理?br /> -- 我们的技术是你的Q?a target="_blank">www.icwin.net
1.1.3 变量 在其他编E语a中您必须使用变量。在shell~程中,所有的变量都由字符串组成,q且您不需要对变量 q行声明。要赋值给一个变量,您可以这样写Q? #!/bin/sh #对变量赋| a="hello world" # 现在打印变量a的内容: echo "A is:" echo $a 有时候变量名很容易与其他文字hQ比如: num=2 echo "this is the $numnd" qƈ不会打印?this is the 2nd"Q而仅仅打?this is the "Q因为shell会去搜烦变量numnd的| 但是q个变量时没有值的。可以用花括号来告诉shell我们要打印的是num变量Q? num=2 echo "this is the ${num}nd" q将打印Q?this is the 2nd
1.1.4 环境变量 由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因ؓ通常情况下仅仅在d 脚本中用环境变量?
1.1.5 Shell命o和流E控?br />在shell脚本中可以用三cd令: 1)Unix 命o: 虽然在shell脚本中可以用Q意的unix命oQ但是还是由一些相Ҏ常用的命令。这些命令通常是用?br />q行文g和文字操作的? 常用命o语法及功? echo "some text": 文字内Ҏ印在屏幕? ls: 文g列表 wc –l filewc -w filewc -c file: 计算文g行数计算文g中的单词数计文件中的字W数 cp sourcefile destfile: 文g拯 mv oldname newname : 重命名文件或Ud文g rm file: 删除文g grep 'pattern' file: 在文件内搜烦字符串比如:grep 'searchstring' file.txt cut -b colnum file: 指定Ʋ显C的文g内容范围Qƈ它们输出到标准输出讑֤比如Q输?br />每行W?个到W?个字Wcut -b5-9 file.txt千万不要和cat命ohQ?br />q是两个完全不同的命? cat file.txt: 输出文g内容到标准输备(屏幕Q上 file somefile: 得到文gcd read var: 提示用户输入Qƈ输入赋值给变量 sort file.txt: 对file.txt文g中的行进行排? uniq: 删除文本文g中出现的行列比如Q?sort file.txt | uniq expr: q行数学q算Example: add 2 and 3expr 2 "+" 3 find: 搜烦文g比如Q根据文件名搜烦find . -name filename -print tee: 数据输出到标准输出讑֤(屏幕) 和文件比如:somecommand | tee outfile basename file: q回不包含\径的文g名比如: basename /bin/tux返?tux dirname file: q回文g所在\径比如:dirname /bin/tux返?/bin head file: 打印文本文g开头几? tail file : 打印文本文g末尾几行 sed: Sed是一个基本的查找替换E序。可以从标准输入Q比如命令管道)d文本Qƈ?br />l果输出到标准输出(屏幕Q。该命o采用正则表达式(见参考)q行搜烦? 不要和shell中的通配W相h。比如:linuxfocus 替换? LinuxFocus Qcat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file awk: awk 用来从文本文件中提取字段。缺省地Q字D分割符是空|可以使用-F指定其他分割W?br />cat file.txt | awk -F, '{print $1 "," $3 }'q里我们使用Q作为字D分割符Q同时打?br />W一个和W三个字Dc如果该文g内容如下Q?Adam Bor, 34, IndiaKerry Miller, 22, USA 命o输出l果为:Adam Bor, IndiaKerry Miller, USA 2) 概念: 道, 重定向和 backtick q些不是pȝ命oQ但是他们真的很重要?br /> 道 (|) 一个命令的输出作ؓ另外一个命令的输入? grep "hello" file.txt | wc -l 在file.txt中搜索包含有”hello”的行ƈ计算其行数? 在这里grep命o的输Z为wc命o的输入。当然您可以使用多个命o? 重定向:命令的l果输出到文Ӟ而不是标准输出(屏幕Q?br /> > 写入文gq覆盖旧文g >> 加到文g的尾部,保留旧文件内宏V?
反短斜线 使用反短斜线可以一个命令的输出作ؓ另外一个命令的一个命令行参数?br />命oQ? find . -mtime -1 -type f -print 用来查找q去24时Q?mtime ?则表C?8时Q内修改q的文g。如果您 惛_所有查扑ֈ的文件打一个包Q则可以使用以下脚本Q? #!/bin/sh # The ticks are backticks (`) not normal quotes ('): tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print` 3) 程控制 1.if "if" 表达?如果条g为真则执行then后面的部分: if ....; then .... elif ....; then .... else .... fi 大多数情况下Q可以用测试命令来Ҏ件进行测试。比如可以比较字W串、判断文?br />是否存在及是否可ȝ{? 通常? [ ] "来表C条件测试。注意这里的I格很重要。要保ҎLI格? [ -f "somefile" ] Q判断是否是一个文? [ -x "/bin/ls" ] Q判?bin/ls是否存在q有可执行权? [ -n "$var" ] Q判?var变量是否有? [ "$a" = "$b" ] Q判?a?b是否相等 执行man test可以查看所有测试表辑ּ可以比较和判断的cd? 直接执行以下脚本Q? #!/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的名Uͼ我们?bin/bashq行了比较? 快捷操作W? 熟悉C语言的朋友可能会很喜Ƣ下面的表达式: [ -f "/etc/shadow" ] && echo "This computer uses shadow passwors" q里 && 是一个快h作符Q如果左边的表达式ؓ真则执行双的语句?br />您也可以认ؓ是逻辑q算中的与操作。上例中表示如果/etc/shadow文g存在 则打印?This computer uses shadow passwors”。同h操作(||)在shell~程中也?br />可用的。这里有个例子: #!/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是否可读。如果可d打印该文件中?From" 一行。如果不可读 则或操作生效Q打印错误信息后脚本退出。这里有个问题,那就是我们必L两个命oQ? -打印错误信息 -退出程? 我们使用花括号以匿名函数的Ş式将两个命o攑ֈ一起作Z个命令用。一般函数将在下文提及? 不用与和或操作符Q我们也可以用if表达式作M事情Q但是用与或操作符会更便利很多?br /> 2.case
case :表达式可以用来匹配一个给定的字符Ԍ而不是数字? case ... in ...) do something here ;; esac 让我们看一个例子?file命o可以辨别Z个给定文件的文gcdQ比如: file lf.gz q将q回Q? lf.gz: gzip compressed data, deflated, original filename, last modified: Mon Aug 27 23:09:18 2001, os: Unix 我们利用q一点写了一个叫做smartzip的脚本,该脚本可以自动解压bzip2, gzip 和zip cd的压~文Ӟ #!/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 您可能注意到我们在这里用了一个特D的变量$1。该变量包含了传递给该程序的W一个参数倹{?br />也就是说Q当我们q行Q? smartzip articles.zip $1 是字符?articles.zip
3. selsect select 表达式是一Ubash的扩展应用,其擅长于交互式使用。用户可以从一l不同的gq行选择? 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" 下面是该脚本q行的结果: 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”用来不执行余下的部分而直接蟩C一个@环? for-loop表达式查看一个字W串列表 (字符串用I格分隔) 然后其赋给一个变量: for var in ....; do .... done 在下面的例子中,分别打印ABC到屏q上Q? #!/bin/sh for var in A B C ; do echo "var is $var" done 下面是一个更为有用的脚本showrpmQ其功能是打C些RPM包的l计信息Q? #!/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 q里出现了第二个Ҏ的变?*Q该变量包含了所有输入的命o行参数倹{?br />如果您运行showrpm openssh.rpm w3m.rpm webgrep.rpm 此时 $* 包含?3 个字W串Q即openssh.rpm, w3m.rpm and webgrep.rpm.
5. 引号 在向E序传递Q何参C前,E序会扩展通配W和变量。这里所谓扩展的意思是E序会把通配W?br />Q比?Q替换成合适的文g名,它变量替换成变量倹{ؓ了防 止程序作q种替换Q您可以使用 引号Q让我们来看一个例子,假设在当前目录下有一些文Ӟ两个jpg文gQ?mail.jpg 和tux.jpg? 1.2 ~译SHELL脚本 #ch#!/bin/sh mod +x filename cho *.jpg ∪缓螅梢酝ÉA淙?./filename 来执行您的脚本? q将打印?mail.jpg tux.jpg"的结果? 引号 (单引号和双引? 防止这U通配W扩展: #!/bin/sh echo "*.jpg" echo '*.jpg' q将打印"*.jpg" 两次? 单引h严格一些。它可以防止M变量扩展。双引号可以防止通配W扩展但允许变量扩展? #!/bin/sh echo $SHELL echo "$SHELL" echo '$SHELL' q行l果为: /bin/bash /bin/bash $SHELL 最后,q有一U防止这U扩展的ҎQ那是使用转义字符——反斜杆Q? echo *.jpg echo $SHELL q将输出Q? *.jpg $SHELL
6. Here documents -- 我们的技术是你的Q?a target="_blank">www.icwin.net
当要几行文字传递给一个命令时Qhere documentsQ译者注Q目前还没有见到q对该词适合的翻译) 一U不错的Ҏ。对每个脚本写一D帮助性的文字是很有用的,此时如果我们四有那个 here documents ׃必用echo函数一行行输出?一?"Here document" ?<< 开_后面接上一个字W串Q这个字W串 q必d现在here document的末。下面是一个例子,在该例子中,我们对多个文件进行重命名Qƈ?br />使用here documents打印帮助Q? #!/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 q是一个复杂一些的例子。让我们详细讨论一下。第一个if表达式判断输入命令行参数?br />否小??(Ҏ变量$# 表示包含参数的个? 。如果输入参数小?个,则将帮助文字传?br />lcat命oQ然后由cat命o其打印在屏q上。打印帮助文字后E序退出?如果输入参数{?br />于或大于3个,我们将W一个参数赋值给变量OLDQ第二个参数赋值给变量NEW。下一步,?br />们用shift命o第一个和W二个参C 参数列表中删除,q样原来的第三个参数成为参 数列?*的第一个参数。然后我们开始@环,命o行参数列表被一个接一个地被赋值给变量$file?br />接着?们判断该文g是否存在Q如果存在则通过sed命o搜烦和替换来产生新的文g名。然?br />反短斜U内命ol果赋值给newfile。这h们就辑ֈ了我们的?的:得到了旧文g名和?br />文g名。然后用mv命oq行重命名? 4)函数 如果您写了一些稍微复杂一些的E序Q您׃发现在程序中可能在几个地方用了相同的代码, q且您也会发玎ͼ如果我们使用了函敎ͼ会方便很多。一个函数是q个样子的: functionname() { # inside the body $1 is the first argument given to the function # $2 the second ... body } 您需要在每个E序的开始对函数q行声明?
下面是一个叫做xtitlebar的脚本,使用q个脚本您可以改变终端窗口的名称?br />q里使用了一个叫做help的函数。正如您可以看到的那Pq个定义的函数被使用了两ơ? #!/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" #
在脚本中提供帮助是一U很好的~程习惯Q这h便其他用P和您Q用和理解脚本? 命o行参? 我们已经见过$* ?$1, $2 ... $9 {特D变量,q些Ҏ变量包含了用户从命o 行输入的参数。迄今ؓ止,我们仅仅了解了一些简单的命o行语法(比如一些强制性的 参数和查看帮助的-h选项Q?但是在编写更复杂的程序时Q您可能会发现您需要更多的 自定义的选项。通常的惯例是在所有可选的参数之前加一个减P后面再加上参数?( 比如文g?? 有好多方法可以实现对输入参数的分析,但是下面的用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" 您可以这栯行该脚本Q? cmdparser -l hello -f -- -somefile1 somefile2 q回的结果是Q? opt_f is 1 opt_l is hello first arg is -somefile1 2nd arg is somefile2 q个脚本是如何工作的呢?脚本首先在所有输入命令行参数中进行@环,输入参?br />与case表达式进行比较,如果匚w则设|一个变量ƈ且移除该参数。根据unixpȝ的惯例, 首先输入的应该是包含减号的参? W?部分 实例 现在我们来讨论编写一个脚本的一般步骤。Q何优U的脚本都应该h帮助和输入参数。ƈ且写一个伪脚本Qframework.shQ,该脚本包含了大多数脚本都需要的框架l构Q是一个非怸错的L。这时候,在写一个新的脚本时我们只需要执行一下copy命oQ? cp framework.sh myscript 然后再插入自q函数? 让我们再看两个例子: 二进制到十进制的转换 脚本 b2d 二q制?(比如 1101) 转换为相应的十进制数。这也是一个用expr命oq行数学q算的例子: #!/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" 该脚本用的法是利用十q制和二q制数权?(1,2,4,8,16,..)Q比如二q制"10"?br />以这栯{换成十进Ӟ 0 * 1 + 1 * 2 = 2 Z得到单个的二q制数我们是用了lastchar 函数。该函数使用wc –c计算字符个数Q?br />然后使用cut命o取出末尾一个字W。Chop函数的功能则是移除最后一个字W?
文g循环E序 或许您是惛_所有发出的邮g保存C个文件中的h们中的一员,但是在过了几个月 以后Q这个文件可能会变得很大以至于对该文g的访问速度变慢。下面的 脚本rotatefile 可以解决q个问题。这个脚本可以重命名邮g保存文gQ假设ؓoutmailQؓoutmail.1Q?br />而对于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 q个脚本是如何工作的呢?在检用h供了一个文件名以后Q我们进行一??的@环。文?被命名ؓ10Q文?重命名ؓ9{等。@环完成之后,我们原始文件命名ؓ文g1同时建立一个与原始文g同名的空文g? 调试 //--------------------------------------- //-- www.icwin.net //--------------------------------------- 最单的调试命o当然是用echo命o。您可以使用echo在Q何怀疑出错的地方打印M变量倹{这也是l大多数的shellE序员要p80%的时间来调试E序的原因。ShellE序的好处在于不需要重新编译,插入一个echo命o也不需要多时间? shell也有一个真实的调试模式。如果在脚本"strangescript" 中有错误Q您可以q样来进行调试: sh -x strangescript q将执行该脚本ƈ昄所有变量的倹{? shellq有一个不需要执行脚本只是检查语法的模式。可以这样用: sh -n your_script q将q回所有语法错误?-- 我们的技术是你的Q?a target="_blank">www.icwin.net
|