学习 Shell Scripts (二)
用来『计算退伍日期还剩几天?』也就是说:先让使用者输入他们的退伍日期;再由现在日期比对退伍日期;由两个日期的比较来显示『还需要几天』才能够退伍的字样。似乎挺难的样子?其实也不会啦,利用『date--date="YYYYMMDD"+%s』就能够达到我们所想要的啰~如果您已经写完了程序,对照底下的写法试看看:[root@linuxscripts]#vish10.sh#!/bin/bash#Program:#Tringtocalculateyourdemobilizationdateathowmanydays#later...#History:#2005/08/29VBirdFirstreleasePATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexportPATH#1.告知使用者这支程序的用途,并且告知应该如何输入日期格式?echo"Thisprogramwilltrytocalculate:"echo"Howmanydaysaboutyourdemobilizationdate..."read-p"Pleaseinputyourdemobilizationdate(YYYYMMDDex>20050401):"date2#2.测试一下,这个输入的内容是否正确?利用正规表示法啰~date_d=`echo$date2|grep'[0-9]\{8\}'`if["$date_d"==""];thenecho"Youinputthewrongformatofdate...."exit1fi#3.开始计算日期啰~declare-idate_dem=`date--date="$date2"+%s`declare-idate_now=`date+%s`declare-idate_total_s=$(($date_dem-$date_now))declare-idate_d=$(($date_total_s/60/60/24))if["$date_total_s"-lt"0"];thenecho"Youhadbeendemobilizationbefore:"$((-1*$date_d))"ago"elsedeclare-idate_h=$(($(($date_total_s-$date_d*60*60*24))/60/60))echo"Youwillbedemobilizedafter$date_ddaysand$date_hhours."fi瞧一瞧,这支程序可以帮您计算退伍日期呢~如果是已经退伍的朋友,还可以知道已经退伍多久了~哈哈!很可爱吧~利用date算出自1971/01/01以来的总秒数,再与目前的总秒数来比对,然后以一天的总秒数(60*60*24)为基数去计算总日数,就能够得知两者的差异了~瞧~全部的动作都没有超出我们所学的范围吧~^_^还能够避免使用者输入错误的数字,所以多了一个正规表示法的判断式呢~这个例子比较难,有兴趣想要一探究竟的朋友,可以作一下课后练习题关于计算生日的那一题喔!~加油!--------------------------------------------------------------------------------利用case.....esac判断上个小节提到的『if....then....fi』对于变量的判断中,是以比对的方式来分辨的,如果符合状态就进行某些行为,并且透过较多层次(就是elif...)的方式来进行多个变量的程序代码撰写,譬如sh08.sh那个小程序,就是用这样的方式来的啰。好,那么万一我有多个既定的变量内容,例如sh08.sh当中,我所需要的变量就是"hello"及空字符串两个,那么我只要针对这两个变量来设定状况就好了对吧?!那么可以使用什么方式来设计呢?呵呵~就用case...in....esac吧~,他的语法如下:case$变量名称in"第一个变量内容")程序段;;"第二个变量内容")程序段;;*)不包含第一个变量内容与第二个变量内容的其它程序执行段exit1;;esac要注意的是,这个语法是以case为开头,而以esac为结尾,啥?为何是esac呢?想一想,既然if的结尾是fi,那么case的结尾当然就是将case倒着写,自然就是esac啰~^_^,很好记吧~另外,每一个变量内容的程序段最后都需要两个分号(;;)来代表该程序段落的结束,这挺重要的喔!至于为何需要有*这个变量内容在最后呢?这是因为,如果使用者不是输入变量内容一或二时,我们可以告知使用者相关的信息啊!举例来说,我们如果将sh08.sh改写的话,他应该会变成这样喔![root@linuxscripts]#vish08-2.sh#!/bin/bash#Program:#Show"Hello"from$1....byusingcase....esac#History:#2005/08/29VBirdFirstreleasePATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexportPATHcase$1in"hello")echo"Hello,howareyou?";;"")echo"YouMUSTinputparameters,ex>$0someword";;*)echo"Usage$0{hello}";;esac在上面这个sh08-2.sh的案例当中,如果你输入『shsh08-2.shtest』来执行,那么屏幕上就会出现『Usagesh08-2.sh{hello}』的字样,告知执行者仅能够使用hello喔~这样的方式对于需要某些固定字符串来执行的变量内容就显的更加的方便呢?这种方式您真的要熟悉喔!这是因为系统的很多服务的启动scripts都是使用这种写法的,举例来说,我们Linux的服务启动放置目录是在/etc/init.d/当中,我已经知道里头有个syslog的服务,我想要重新启动这个服务,可以这样做:/etc/init.d/syslogrestart重点是那个restart啦~如果您进入/etc/init.d/syslog就会看到他使用的是case语法,并且会规定某些既定的变量内容,你可以直接下达/etc/init.d/syslog,该script就会告知你有哪些后续接的变量可以使用啰~方便吧!^_^一般来说,使用『case$变量in』这个语法中,当中的那个$变量大致有两种取得的方式:直接下达式:例如上面提到的,利用『script.shvariable』的方式来直接给予$1这个变量的内容,这也是在/etc/init.d目录下大多数程序的设计方式。交互式:透过read这个指令来让使用者输入变量的内容。这么说或许您的感受性还不高,好,我们直接写个程序来玩玩:让使用者能够输入one,two,three,并且将使用者的变量显示到屏幕上,如果不是one,two,three时,就告知使用者仅有这三种选择。[root@linuxscripts]#vish11.sh#!/bin/bash#Program:#Letuserinputone,two,threeandshowinscreen.#History:#2005/08/29VBirdFirstreleasePATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexportPATHecho"Thisprogramwillprintyourselection!"#read-p"Inputyourchoice:"choice#case$choiceincase$1in"one")echo"YourchoiceisONE";;"two")echo"YourchoiceisTWO";;"three")echo"YourchoiceisTHREE";;*)echo"Usage{one|two|three}";;esac此时,您可以使用『shsh11.shtwo』的方式来下达指令,就可以收到相对应的响应了。上面使用的是直接下达的方式,而如果使用的是交互式时,那么将上面第10,11行的"#"拿掉,并将12行加上批注(#),就可以让使用者输入参数啰~这样是否很有趣啊?!--------------------------------------------------------------------------------利用function功能什么是『函数(function)』功能啊?简单的说,其实,函数可以在shellscript当中做出一个类似自订执行指令的东西,最大的功能是,可以简化我们很多的程序代码~举例来说,上面的sh11.sh当中,每个输入结果one,two,three其实输出的内容都一样啊~那么我就可以使用function来简化了!function的语法是这样的:functionfname(){程序段}那个fname就是我们的自订的执行指令名称~而程序段就是我们要他执行的内容了。要注意的是,在shellscript当中,function的设定一定要在程序的最前面,这样才能够在执行时被找到可用的程序段喔!好~我们将sh11.sh改写一下:[root@linuxscripts]#vish11-2.sh#!/bin/bash#Program:#Letuserinputone,two,threeandshowinscreen.#History:#2005/08/29VBirdFirstreleasePATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexportPATHfunctionprintit(){echo-n"Yourchoiceis"}echo"Thisprogramwillprintyourselection!"case$1in"one")printit;echo$1|tr'a-z''A-Z';;"two")printit;echo$1|tr'a-z''A-Z';;"three")printit;echo$1|tr'a-z''A-Z';;*)echo"Usage{one|two|three}";;esac以上面的例子来说,我做了一个函数名称为printif,所以,当我在后续的程序段里面,只要执行printit的话,就表示我的shellscript要去执行『functionprintit....』里面的那几个程序段落啰!当然啰,上面这个例子举得太简单了,所以您不会觉得function有什么好厉害的,不过,如果某些程序代码一再地在script当中重复时,这个function可就重要的多啰~不但可以简化程序代码,而且可以做成类似『模块』的玩意儿,真的很棒啦!另外,function也是拥有内建变量的~他的内建变量与shellscript很类似,函数名称代表示$0,而后续接的变量也是以$1,$2...来取代的~这里很容易搞错喔~因为『functionfname(){程序段}』内的$0,$1...等等与shellscript的$0是不同的。以上面sh11-2.sh来说,假如我下达:『shsh11-2.shone』这表示在shellscript内的$1为"one"这个字符串。但是在printit()内的$1则与这个one无关。我们将上面的例子再次的改写一下,让您更清楚![root@linuxscripts]#vish11-3.sh#!/bin/bash#Program:#Letuserinputone,two,threeandshowinscreen.#History:#2005/08/29VBirdFirstreleasePATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexportPATHfunctionprintit(){echo"Yourchoiceis$1"}echo"Thisprogramwillprintyourselection!"case$1in"one")printit1;;"two")printit2;;"three")printit3;;*)echo"Usage{one|two|three}";;esac在上面的例子当中,如果您输入『shsh11-3.shone』就会出现『Yourchoiceis1』的字样~为什么是1呢?因为在程序段落当中,我们是写了『printit1』那个1就会成为function当中的$1喔~这样是否理解呢?function本身其实比较困难一点,如果您还想要进行其它的撰写的话。不过,我们仅是想要更加了解shellscript而已,所以,这里看看即可~了解原理就好啰~^_^--------------------------------------------------------------------------------循环(loop)除了if...then...fi这种条件判断式之外,循环可能是程序当中最重要的一环了~循环可以不断的执行某个程序段落,直到使用者设定的条件达成为止。所以,重点是那个『条件的达成』是什么。底下我们就来谈一谈:--------------------------------------------------------------------------------whiledodone,untildodone一般来说,循环最常见的就是底下这两种状态了:while[condition]do程序段落done这种方式中,while是『当....时』,所以,这种方式说的是『当condition条件成立时,就进行循环,直到condition的条件不成立才停止』的意思。until[condition]do程序段落done这种方式恰恰与while相反,它说的是『当condition条件成立时,就终止循环,否则就持续进行循环的程序段。』是否刚好相反啊~我们以while来做个简单的练习好了。假设我要让使用者输入yes或者是YES才结束程序的执行,否则就一直进行告知使用者输入字符串。[root@linuxscripts]#vish12.sh#!/bin/bash#Program:#Uselooptotryfindyourinput.#History:#2005/08/29VBirdFirstreleasePATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexportPATHwhile["$yn"!="yes"]&&["$yn"!="YES"]doread-p"Pleaseinputyes/YEStostopthisprogram:"yndone上面这个例题的说明是『当$yn这个变量不是"yes"且$yn也不是"YES"时,才进行循环内的程序。』而如果$yn是"yes"或"YES"时,就会离开循环啰~那如果使用until呢?呵呵有趣啰~他的条件会变成这样:[root@linuxscripts]#vish12-2.sh#!/bin/bash#Program:#Uselooptotryfindyourinput.#History:#2005/08/29VBirdFirstreleasePATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexportPATHuntil["$yn"=="yes"]||["$yn"=="YES"]doread-p"Pleaseinputyes/YEStostopthisprogram:"yndone仔细比对一下这两个东西有啥不同喔!^_^再来,如果我想要计算1+2+3+....+100这个数据呢?利用循环啊~他是这样的:[root@linuxscripts]#vish13.sh#!/bin/bash#Program:#Trytouselooptocalculatetheresult"1+2+3...+100"#History:#2005/08/29VBirdFirstreleasePATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexportPATHs=0i=0while["$i"!="100"]doi=$(($i+1))s=$(($s+$i))doneecho"Theresultof'1+2+3+...+100'is==>$s"嘿嘿!当您执行了『shsh13.sh』之后,就可以得到5050这个数据才对啊!这样瞭呼~那么让您自行做一下,如果想要让使用者自行输入一个数字,让程序由1+2+...直到您输入的数字为止,该如何撰写呢?应该很简单吧?!答案可以参考一下习题练习里面的一题喔!--------------------------------------------------------------------------------for...do....done相对于while,until的循环方式是必须要『符合某个条件』的状态,for这种语法,则是『已经知道要进行几次循环』的状态!他的语法是:for((初始值;限制值;执行步阶))do程序段done这种语法适合于数值方式的运算当中,在for后面的括号内的三串内容意义为:初始值:某个变量在循环当中的起始值,直接以类似i=1设定好;限制值:当变量的值在这个限制值的范围内,就继续进行循环。例如i$s"一样也是很简单吧!利用这个for则可以直接限制循环要进行几次呢!这么好用的东西难道只能在数值方面动作?当然不是啦~我们还可以利用底下的方式来进行非数字方面的循环运作喔!for$varincon1con2con3...do程序段done以上面的例子来说,这个$var的变量内容在循环工作时:第一次循环时,$var的内容为con1;第二次循环时,$var的内容为con2;第三次循环时,$var的内容为con3;....我们可以做个简单的练习。假设我有三种动物,分别是dog,cat,elephant三种,我想每一行都输出这样:『Therearedogs...』之类的字样,则可以:[root@linuxscripts]#vish15.sh#!/bin/bash#Program:#Usingfor....looptoprint3animal#History:#2005/08/29VBirdFirstreleasePATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexportPATHforanimalindogcatelephantdoecho"Thereare""$animal""s...."done很简单是吧!^_^。好了,那么如果我想要让使用者输入某个目录,然后我找出某目录内的文件名的权限呢?又该如何是好?呵呵!可以这样做啦~[root@linuxscripts]#vish16.sh#!/bin/bash#Program:#letuserinputadirectoryandfindthewholefile'spermission.#History:#2005/08/29VBirdFirstreleasePATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/binexportPATH#1.先看看这个目录是否存在啊?read-p"Pleaseinputadirectory:"dirif["$dir"==""]||[!-d"$dir"];thenecho"The$dirisNOTexistinyoursystem."exit1fi#2.开始测试档案啰~filelist=`ls$dir`forfilenamein$filelistdoperm=""test-r"$dir/$filename"&&perm="$permreadable"test-w"$dir/$filename"&&perm="$permwritable"test-x"$dir/$filename"&&perm="$permexecutable"echo"Thefile$dir/$filename'spermissionis$perm"done呵呵!很有趣的例子吧~利用这种方式,您可以很轻易的来处理一些档案的特性呢~我们循环就介绍到这里了~其它更多的应用,就得视您的需求来玩啰~。--------------------------------------------------------------------------------shellscript的追踪与debugscripts在执行之前,最怕的就是出现问题了!那么我们如何debug呢?有没有办法不需要透过直接执行该scripts就可以来判断是否有问题呢!?呵呵!当然是有的!我们就直接以bash的相关参数来进行判断吧![root@linux~]#sh[-nvx]scripts.sh参数:-n:不要执行script,仅查询语法的问题;-v:再执行sccript前,先将scripts的内容输出到屏幕上;-x:将使用到的script内容显示到屏幕上,这是很有用的参数!范例:范例一:测试sh16.sh有无语法的问题?[root@linux~]#sh-nsh16.sh#若语法没有问题,则不会显示任何信息!范例二:将sh15.sh的执行过程全部列出来~[root@linux~]#sh-xsh15.sh+PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/home/vbird/bin+exportPATH+foranimalindogcatelephant+echo'Therearedogs....'Therearedogs....+foranimalindogcatelephant+echo'Therearecats....'Therearecats....+foranimalindogcatelephant+echo'Thereareelephants....'Thereareelephants....#使用-x真的是追踪script的好方法,他可以将所有有执行的程序段在执行前列出来,#如果是程序段落,则输出时,最前面会加上+字号,表示他是程序代码而已,#实际的输出则与standardoutput有关啊~如上所示。在上面的范例二当中,我们可以透过这个简单的参数-x来达成debug的目的,这可是一个不可多得的参数,通常如果您执行script却发生问题时,利用这个-x参数,就可以知道问题是发生在哪一行上面了!熟悉sh的用法,将可以使您在管理Linux的过程中得心应手!至于在Shellscripts的学习方法上面,需要『多看、多模仿、并加以修改成自己的样式!』是最快的学习手段了!网络上有相当多的朋友在开发一些相当有用的scripts,若是您可以将对方的scripts拿来,并且改成适合自己主机的样子!那么学习的效果会是最快的呢!另外,我们Linux系统本来就有很多的启动script,如果您想要知道每个script所代表的功能是什么?可以直接以vi进入该script去查阅一下,通常立刻就知道该script的目的了。举例来说,我们的Linux里头有个文件名称为:/etc/init.d/portmap,这个script是干嘛用的?利用vi去查阅最前面的几行字,他出现如下信息:#description:TheportmappermanagesRPCconnections,whichareusedby\#protocolssuchasNFSandNIS.Theportmapservermustbe\#runningonmachineswhichactasserversforprotocolswhich\#makeuseoftheRPCmechanism.#processname:portmap简单的说,他是被用在NFS与NIS上面的一个启动RPC的script,然后我们再利用http://www.google.com.tw去搜寻一下NFS,NIS与RPC,立刻就能够知道这个script的功能啰~所以,下次您发现不明的script时,如果是系统提供的,那么利用这个检查的方式,一定可以约略了解的啦!加油的啰~^_^另外,本章所有的范例都可以在http://linux.vbird.org/linux_basic/0340bashshell-scripts/scripts.tgz里头找到喔!加油~--------------------------------------------------------------------------------本章习题练习(要看答案请将鼠标移动到『答:』底下的空白处,按下左键圈选空白处即可察看)请建立一支script,当你执行该script的时候,该script可以显示:1.你目前的身份(用whoami)2.你目前所在的目录(用pwd)#!/bin/bashecho-e"Yournameis==>`whoami`"echo-e"Thecurrentdirectoryis==>`pwd`"请自行建立一支程序,该程序可以用来计算『您还有几天可以过生日』啊??#!/bin/bashread-p"Pleasinputyourbirthday(MMDD,ex>0709):"birnow=`date+%m%d`if["$bir"=="$now"];thenecho"HappyBirthdaytoyou!!!"elif["$bir"-gt"$now"];thenyear=`date+%Y`total_d=$(($((`date--date="$year$bir"+%s`-`date+%s`))/60/60/24))echo"Yourbirthdaywillbe$total_dlater"elseyear=$((`date+%Y`+1))total_d=$(($((`date--date="$year$bir"+%s`-`date+%s`))/60/60/24))echo"Yourbirthdaywillbe$total_dlater"fi让使用者输入一个数字,程序可以由1+2+3...一直累加到使用者输入的数字为止。#!/bin/bashread-p"Pleaseinputanintegernumber:"numberi=0s=0while["$i"!="$number"]doi=$(($i+1))s=$(($s+$i))doneecho"theresultof'1+2+3+...$number'is==>$s"撰写一支程序,他的作用是:1.)先查看一下/root/test/logical这个名称是否存在;2.)若不存在,则建立一个档案,使用touch来建立,建立完成后离开;3.)如果存在的话,判断该名称是否为档案,若为档案则将之删除后建立一个档案,档名为logical,之后离开;4.)如果存在的话,而且该名称为目录,则移除此目录!#!/bin/bashif[!-elogical];thentouchlogicalecho"Justmakeafilelogical"exit1elif[-elogical]&&[-flogical];thenrmlogicalmkdirlogicalecho"removefile==>logical"echo"andmakedirectorylogical"exit1elif[-elogical]&&[-dlogical];thenrm-rflogicalecho"removedirectory==>logical"exit1elseelseecho"Doesherehaveanything?"fi我们知道/etc/passwd里面以:来分隔,第一栏为账号名称。请写一只程序,可以将/etc/passwd的第一栏取出,而且每一栏都以一行字符串『The1accountis"root"』来显示,那个1表示行数。#!/bin/bashaccounts=`cat/etc/passwd|cut-d':'-f1`foraccountin$accountsdodeclare-ii=$i+1echo"The$iaccountis\"$account\""done