鸟哥学习Shell Scripts

鸟哥学习Shell Scripts
引用:
    学习Shell Scripts

    如果您真得很想要走信息这条路,并且想要好好的管理好属于您的主机,那么,别说鸟哥不告诉您,Shell Scripts真的是必须要学习的一项课程!基本上,Shell scripts有点像是早期的批处理文件,亦即是将一些指令汇编起来一次执行,但是Shell scripts拥有更强大的功能,那就是,他可以进行类似程序(program)的撰写,并且,不需要经过编译(compiler)就能执行,真得很方便。加上,我们可透过shell script来简化我们日常的工作管理,而且,整个Linux环境中,一些服务(services)的启动都是透过shell script的,如果您对于script不了解,嘿嘿!发生问题时,可真是会求助无门的!所以,好好的学一学他吧!

什么是Shell scripts?
    这个有趣的问题赶紧回答看看,什么是shell script呢?shell我们在认识bash当中已经提过了,那是一个文字接口底下让我们与系统沟通的一个工具借口,那么script是啥?字面上的意义,script是[脚本、剧本]的意思。整句话是说,shell script是针对shell所写的[剧本!] 什么东西啊?呵呵!其实,shell script是利用shell的功能所写的一个[程序(program)],这个程序是使用纯文字文件,将一些shell的语法与指令写在里面,搭配正规表示法、管线命令与数据流重导向等功能,已达到我们所想要的处理目的。
    所以,简单的说,shell script就像是早期DOS年代的批处理(.bat),最简单的功能就是将许多指令汇整写在一起,让使用者很轻易的就能够one touch(执行一个档案“shell script”,就能够一次执行多个指令),而,shell script 更提供数组、循环、条件与逻辑判断等重要功能,让使用者也可以直接以shell 来撰写程序,而不必使用类似C程序语言等传统程序撰写的语法呢!
    那,这么说您可以了解了吗?是的!shell script可以简单的被看成是批处理,也可以被说成是一个程序语言,且这个程序语言由于都是利用shell与相关工具指令,所以不需要编译即可执行,且拥有不错的除错(debug)工具,所以,他可以帮助系统管理员快速的管理好主机。

---------------------------------------------------------------------------------
干吗学习shell scripts?

    这是一个好问题,我又干嘛一定要学shell script?我又不是信息人,没有写程序的概念,那我干吗还要学shell script呢?不要学可不可以啊?呵呵~如果Linux对您而言,您只是想要[会用]而已,那么,不需要学shell script也还无所谓,这部分先给他跳过去,等到有空的时候,再来好好的瞧一瞧。但是,如果您是真的想要玩清楚Linux的来龙去脉,那么shell script就不可不知,为什么呢?因为:

自动化管理的重要依据:
    不用鸟哥说您也知道,管理一部主机真不是简单的事情,每天要进行的任务就有:查询登录档、追踪流量、监控使用者使用主机状态、主机各项硬件设备状态、主机软件更新查询、更不要说得应付其它使用者的突然要求了。而这些工作,您想要自行手动处理,还是写个简单的程序来帮助您每日自动处理分析,若有问题才通知您呢?当然是让系统自动工作比较好,对吧!呵呵~这就得要良好的shell script来帮忙的啦!

追踪与管理系统的重要工作:
    虽然我们还没有提到服务启动的方法,不过,这里可以先提一下,我们Linux系统的服务(services)启动的接口,在/etc/init.d/这个目录下,所有的档案都是scriptsl;另外,包括开机(booting)过程也都是利用shell script来帮忙搜寻系统的相关设定数据,然后再代入各个服务的设定参数啊!举例来说,如果我们想要重新启动系统登录文件,可以使用:[/etc/init.d/syslogd restart],那个syslogd档案就是scripts啦!另外,我曾经在某一代的FC上面发现,启动MySQL这个数据库服务时,确实是可以启动的,但是屏幕上却老是出现[failure],后来才发现,原来启动MySQL那个script会主动地以[空的密码]去尝试去登录MySQL,但我修改过MySQL的密码喽~当然就登入失败~后来改了改script,就略去这个问题啦!如此说来,script 确实是需要学习的啊!

简单入侵侦测功能:
    当我们的系统有异状时,大多会将这些异状记录在系统记录器,也就是我们提到的[系统登录文件],那么我们可以在固定的几分钟内主动地去分析系统登录文件,若察觉有问题,就立即通报管理员,或者是立刻加强防火墙的设定规则,如此一来,您的主机可就能够达到[自我保护]的聪明学习功能啦~举例来说,我们可以通过shell script 去分析[当该封包尝试几次还是联机失败后,就予以抵挡住该IP]之类的举动,例如鸟哥写过一个关于抵挡砍站软件的shell script,就是用这个想法去达成的呢!

连续指令单一化:
    其实,对于新手而言,script最简单的功能就是:[汇整一些在command line 下达的连续指令,将他写入scripts当中,而由直接执行scripts来启动一连串的command line 指令输出入!]例如:防火墙连续规则(iptables),开机加载程序的项目(就是在/etc/rc.d/rc.local里头的数据),等等都是相似的功能啦!其实,说穿了,如果不考虑program的部分,那么scripts也可以想成,仅是帮我们把一大串的指令汇编在一个档案里面,而直接执行该档案就可以执行那一串又臭有长的指令段!就是这么简单啦!

简易的数据处理:       
    由前一章 正规表示法的awk程序说明中,您可以发现,awk可以用来处理简单的数据呢!例如薪资简单的处理啊。shell script的功能更强大,例如鸟哥曾经用shell script直接处理数据的比对啊,文字数据得处理啊等等的,撰写方便,速度又快(因为在Linux效能较佳),真的是很不错用的啦!

跨平台支持与学习历程较短:
    几乎所有的Unix Like上面都可以跑shell script,连MS Windows系列也有相关的仿真器可以用,此外,shell script的语法是相当亲和的,看都看得懂得文字,而不是机器码,很容易学习~这些都是您可以加以考虑的学习点啊!

上面这些都是您考虑学习shell script的特点~此外,shell script还可以简单的以vi来直接编写,实在是很方便的好东西!所以,还是建议您学习一下啦。

不过,虽然shell script号称是程序(program),但实际上,shell script处理数据的速度上是不太够的。因为,shell script 用的是外部的指令与bash shell 的一些预设工具,所以,他常常会去呼叫外部的函数库,因此,运算速度上面当然比不上传统的程序语言。所以,shell script用在系统管理上面是很好的一项工具,但是用在处理大量数值运算上,就不够好了,而且还很麻烦,因为:shell scripts 的速度较慢,且使用的CPU资源较多,造成主机资源的分配不良,还好,我们确实很少看到利用shell scripts在进行大量数据运算的,所以,不必担心的啦!

----------------------------------------------------------------------------------------------------
第一节 script的撰写与执行

如同前面讲到的,shell script其实就是纯文字文件(ASCII),我们可以编辑这个档案,然后让这个档案来帮我们一次执行多个命令,或者是利用一些运算与逻辑判断来帮我们达成某些功能。所以,要编辑这个档案的内容时,当然就需要具备有bash shell 指令下达的相关知识。我们说过,要下达指令需要注意的事项在bash章节内已经提过,在shell script的撰写同样要用到这些注意事项的:
如同前面bash command提到的,指令与参数间的多个空白会被忽略掉:
而空白行也被忽略掉!并且[tab]也是不会被理会的!
如果读取到一个Enter符号(CR),就尝试开始执行该行命令;
至于如果一行的内容太多,则可以使用\[Enter]来延伸至下一行;
此外,使用最多的#可做为批注!任何加在#后面的字,将全部被视为批注文字而被忽略!
如此一来,我们在script内所撰写的程序,就会被一行一行的执行,好了,那么这个程序假设文件名是shell.sh 好了,如何执行这个档案呢?很简单,可以由底下几个方法:
将shell.sh加上可读与执行(rx)的权限,然后就能够以./shell.sh来执行了;
直接以sh shell.sh的方式来直接执行即可。
反正重点就是要让那个shell.sh内的指令可以被执行的意思!那我为何需要使用./shell.sh来下达指令?还记得我们在bash 里面一直强调的,指令是否能够被执行与PATH这个环境变量有关,所以,要执行[目前这个目录下的某个档案]就需要加上 ./这个目录!另外,其实您也可以将shell.sh放在您的家目录下的~/bin这个目录中,然后利用PATH="$PATH"~/bin的设定,就能够直接执行您的script了
      那,为何sh shell.sh也可以执行呢?这是因为/bin/sh其实就是/bin/bash,使用sh shell.sh亦即告诉系统,我想要直接以bash的功能来执行shell.sh这个档案内的相关指令的意思。而我们也可以利用sh的参数,如 -n 及 -x来检查与追踪shell.sh的语法是否正确!

----------------------------------------------------------------------------------------------

撰写第一个script
不论是那个门派,要学武功要从扫地做起,那么要学程序呢?呵呵,肯定是由[秀出Hello World!]这个字眼开始的!OK!那么鸟哥就先写一个script给大家瞧一瞧:
[root@linux~]# mkdir scripts; cd scripts
[root@linux scripts]# vi sh01.sh
#!/bin/bash
# Program:
#            This program is used to show "Hello World!" in screen.
# History:
# 2005/08/23 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "Hello World!\a\n"
exit 0


在我们这个章节当中,请将所有的撰写的script放置到您家目录的~/scripts这个目录内,比较好管理啦!上面的写法当中,我主要将整个程序的撰写分成数段,大致是这样:
第一行 #!/bin/bash在宣告这个script使用的shell 名称:
因为我们使用的是bash,所以,必须要以[#!/bin/bash]来宣告这个档案内的语法使用bash的语法!那么当这个程序被执行时,他就能够加载bash的相关环境设定档,并且执行bash来使我们底下的指令能够执行!这很重要的!(在很多状况中,如果没有设定好这一行,那么该程序很可能会无法执行,因为系统可能无法判断该程序需要使用什么shell来执行!)

程序内容的宣告:
      整个script当中,除了第一行的#!是用来宣告shell的之外,其他的#就是[批注]用途!所以上面的程序当中,第二行以下就是用来说明整个程序的状态。一般来说,建议您一定要养成说明该script的:1.内容与功能;2.版本信息;3.作者与联络方式;4.建档日期;5.历史纪录等等。这将有助于未来程序的改写与debug!

主要环境变量的宣告:
      建议务必要将一些重要的环境变量设定好,鸟哥个人认为,PATH是当中最重要的!如此一来,则可让我们这支程序在进行时,可以直接下达指令,而不必写绝对路径!比较好!

主要程序部分
      就将主要的程序写好即可!在这个例子当中,就是echo那一行。

执行成果告知
      是否记得我们在bash里面要讨论一个指令的执行成功与否,可以使用$?这个变量来观察~那么我们就也可以利用exit这个指令来让程序中断,并且回传一个数值给系统,在我们这个例子当中,我使用exit 0,这代表离开script,并且回传一个0给系统,所以我执行完这个script后,若接着下达echo $?则可以得到0的值!更聪明的读者应该也知道了,呵呵!利用这个exit n的功能,我们还可以自订错误讯息,让这支程序变得更加的smart呢!
接下来执行看看结果是怎样的?
[root@linux scripts]# sh sh01.sh
Hello World!


您会看到屏幕是这样,而且应该还会听到[咚]的一声,为什么呢?还记得前一章提到的printf吧?用echo接着那些特殊的按键也可以发生同样的事情~不过,echo必须要加上 -e 的参数才行,呵呵,在您写完这个小script之后,您就可以大声地说:[我也会写程序了]!

另外,你也可以利用:[chmod a+x sh01.sh; ./sh01.sh] 来执行这个script呢!

--------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------
撰写 shell script 的良好习惯建立

一个良好习惯的养成是很重要的~大家在刚开始撰写程序的时候,最容易忽略这部分, 认为程序写出来就好了,其它的不重要。其实,如果程序的说明能够更清楚, 那么对您自己是有很大的帮助的。

举例来说,鸟哥自己为了自己的需求,曾经撰写了不少的 script 来帮我进行主机 IP 的侦测啊、 登录档分析与管理啊、自动上传下载重要 设定档啊等等的,不过,早期就是因为太懒了, 管理的主机又太多了,常常同一个程序在不同的主机上面进行更改,到最后,到底哪一支才是最新的都记不起来,  而且,重点是,我到底是改了哪里??为什么做那样的修改?都忘的一干二净~真要命~

所以,后来鸟哥在写程序的时候,通常会比较仔细的将程序的设计过程给他记录下来, 而且还会记录一些历史纪录,如此一来,好多了~ 至少很容易知道我修改了哪些数据,以及程序修改的理念与逻辑概念等等, 在维护上面是轻松很多很多的喔!

另外,在一些环境的设定上面,毕竟每个人的环境都不相同,为了取得较佳的执行环境, 我都会自行先定义好一些一定会被用到的环境变量,例如  PATH 这个玩意儿! 这样比较好啦~所以说,建议您一定要养成良好的 script 撰写习惯, 在每个 script 的文件头处记录好:
script 
的功能; 
script 
的版本信息; 
script 
的作者与联络方式; 
script 
的版权宣告方式; 
script 
 History (历史纪录) 
script 
内较特殊的指令,使用绝对路径的方式来下达; 
script 
运作时需要的环境变量预先宣告与设定。

--------------------------------------------------------------------------------
简单的 shell script 练习

在第一支 shell script 撰写完毕之后,相信您应该具有基本的撰写功力了。 接下来,在开始更深入的程序概念之前,我们先来玩一些比 较有趣的简单的小范例好了。 底下的范例中,达成结果的方式相当的多,建议您先自行撰写看看,写完之后再与鸟哥写的内容比对, 这样才能更加深概念喔! 好!不啰唆,我们就一个一个来玩吧!


--------------------------------------------------------------------------------

变量内容由使用者决定 
很多时候我们需要使用者输入一些内容,好让程序可以顺利运作。 简单的来说,大家应该都有安装过软件的经验,安装的时候,他不是会问您『要安装到那个目录去?』吗? 那个让使用者输入的数据的动作,就是让使用者输入变量内容啦。

你应该还记得在 bash 的时候,我们有学到一个 read 指令吧?忘记的话,请自行回头去阅读一番。 现在,请你以 read 指令的用 途,撰写一个 script ,他可以让使用者输入:1 first name  2. last name 最后并且在屏幕上显示: Your full name is: 』的内容:
[root@linux scripts]# vi sh02.sh
#!/bin/bash
# Program:
#  Let user keyin their first and last name, and show their full name.
# History:
#  2005/08/23 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

read -p "Please input your first name: " firstname
read -p "Please input your last name:  " lastname
echo -e "\nYour full name is: $firstname $lastname"

 
将上面这个 sh02.sh 执行一下,你就能够发现使用者自己输入的变量可以被取用的哩! 很不错吧!加油!



--------------------------------------------------------------------------------

利用 date 进行档案的建立 
想象一个状况,如果我每天要进行备份,而备份的数据又不想被覆盖掉,也就是说, 我想要将每天备份的数据放在不同的档案中。哇!这真困扰啊?难道 要我每天去修改 script  不需要啊!因为每天的『日期』并不相同,所以我可以将档名取成类似: backup.20050802  不就可以 每天一个不同档名了吗?呵呵!确实如此。好了,接下来出个例子: 我想要建立三个空的档案,档名最开头由使用者输入决定,假设使用者输入  filename 好了, 那今天的日期是  2005/08/23  ,我想要以前天、昨天、今天的日期来建立这个档案,亦即  filename_20050821, filename_20050822, filename_20050823 ,该如何是好?
[root@linux scripts]# vi sh03.sh
#!/bin/bash
# Program:
#  User can keyin filename to touch 3 new files.
# History:
#  2005/08/23 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

# 1. 
让使用者输入文件名称,并取得 fileuser 这个变量;
echo -e "I will use 'touch' command to create 3 files."
read -p "Please input the filename what you want: " fileuser

# 2. 
为了避免使用者随意按 Enter ,利用变量功能分析文件名是否有设定?
filename=${fileuser:-"filename"}

# 3. 
开始利用 date 指令来取得所需要的档名了;
date1=`date --date='2 days ago' +%Y%m%d`
date2=`date --date='1 days ago' +%Y%m%d`
date3=`date +%Y%m%d`
file1="$filename""$date1"
file2="$filename""$date2"
file3="$filename""$date3"

# 4. 
将档名建立吧!
touch $file1
touch $file2
touch $file3

 
我透过一些简单的动作,这些动作都可以在 bash 那一章里面找到, 包括小指令 (`) 的取得讯息、变量的设定功能、变量的累加以及利用  touch 指令辅助! 如果您开始执行这个 sh03.sh 之后,你可以进行两次输入,一次直接按 [Enter] 来查阅档名是啥? 一次可以输 入一些字符,这样来判断你的档案喔!关于 date 的指令应用,请 man date 吧! ^_^



--------------------------------------------------------------------------------

数值运算的方法 
各位看官应该还记得,我们可以使用 declare 来定义变量的类型吧?! 这样才能够进行加减运算啊!可惜的是, bash shell  头预设仅支持到整数的数据。 OK!那我们来玩玩看,如果我们要使用者输入两个变量,然后将两个变量的内容相乘, 最后输出相乘的结果,那可以怎么做?
[root@linux scripts]# vi sh04.sh
#!/bin/bash
# Program:
#  User can input 2 integer to cross by!
# History:
#  2005/08/23 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "You SHOULD input 2 number, I will cross they! \n"
read -p "first number:  " firstnu
read -p "second number: " secnu
total=$(($firstnu*$secnu))
echo -e "\nThe number $firstnu x $secnu is ==> $total"

 
在数字的运算上,我们可以使用『 declare -i total=$firstnu*$secnu  也可以使用上面的方式来进行!基本上,鸟哥比较建议使用这样的方式来进行运算: 
var=$((
运算内容))
不但容易记忆,而且也比较方便的多~未来您可以使用这种方式来计算的呀!至于数值运算上的处理, 则有:+, -, *, /, %等等。 那个 % 是取余数啦~举例来说, 13  3 取余数,结果是 13=4*3+1,所以余数是 1 啊!就是:
[root@linux scripts]# nu=$((13%3)); echo $nu
1

 
这样了解了吧?!多多学习与应用喔! ^_^


--------------------------------------------------------------------------------
善用判断式

 bash 章节中,我们提到过 $? 这个变量所代表的意义, 此外,也透过 &&  || 来作为前一个指令是否能够成 功进行的一个参考。 那么,如果我想要知道 /dmtsai 这个目录是否存在时,难道一定要使用 ls 来执行, 然后再以 $? 来判断执行成果吗? 呵呵!当然不需要! 我们可以透过『 test 』这个指令来侦测呢!


--------------------------------------------------------------------------------
利用 test 指令的测试功能

当我要检测系统上面某些档案或者是相关的属性时,利用 test 这个指令来工作, 真是好用得不得了,举例来说,我要检查 /dmtsai 是否存在时,使用:
[root@linux ~]# test -e /dmtsai

 
执行结果并不会显示任何讯息,但最后我们可以透过 $?  &&  || 来展现整个结果呢! 例如我们在将上面的例子改写成这样:
[root@linux ~]# test -e /dmtsai && echo "exist" || echo "Not exist"

 
最终的结果可以告知我们是『exist』还是『Not exist』呢!那我知道 -e 是测试一个『东西』在不在, 如果还想要测试一下该档名是啥玩意儿时,还有哪些标志可以来判断的呢?呵呵!有底下这些东西喔!

测试的标志 代表意义 
1. 
关于某个档名的『类型』侦测(存在与否),如 test -e filename 
-e 
该『档名』是否存在?(常用
-f 
该『档名』是否为档案(file)(常用
-d 
该『文件名』是否为目录(directory)(常用
-b 
该『档名』是否为一个 block device 装置? 
-c 
该『档名』是否为一个 character device 装置? 
-S 
该『档名』是否为一个 Socket 档案? 
-p 
该『档名』是否为一个 FIFO (pipe) 档案? 
-L 
该『档名』是否为一个连结档? 
2. 
关于档案的权限侦测,如 test -r filename 
-r 
侦测该档名是否具有『可读』的属性? 
-w 
侦测该档名是否具有『可写』的属性? 
-x 
侦测该档名是否具有『可执行』的属性? 
-u 
侦测该文件名是否具有『SUID』的属性? 
-g 
侦测该文件名是否具有『SGID』的属性? 
-k 
侦测该文件名是否具有『Sticky bit』的属性? 
-s 
侦测该档名是否为『非空白档案』? 
3. 
两个档案之间的比较,如: test file1 -nt file2 
-nt (newer than)
判断 file1 是否比 file2  
-ot (older than)
判断 file1 是否比 file2  
-ef 
判断 file2  file2 是否为同一档案,可用在判断 hard link 的判定上。 主要意义在判定,两个档案是否均指向同一个 inode 哩! 
4. 
关于两个整数之间的判定,例如 test n1 -eq n2 
-eq 
两数值相等 (equal) 
-ne 
两数值不等 (not equal) 
-gt n1 
大于 n2 (greater than) 
-lt n1 
小于 n2 (less than) 
-ge n1 
大于等于 n2 (greater than or equal) 
-le n1 
小于等于 n2 (less than or equal) 
5. 
判定字符串的数据 
test -z string 
判定字符串是否为 0 ?若 string 为空字符串,则为 true 
test -n string 
判定字符串是否非为 0 ?若 string 为空字符串,则为 false
注: -n 亦可省略 
test str1 = str2 
判定 str1 是否等于 str2 ,若相等,则回传 true 
test str1 != str2 
判定 str1 是否不等于 str2 ,若相等,则回传 false 
6. 
多重条件判定,例如: test -r filename -a -x filename 
-a (and)
两状况同时成立!例如 test -r file -a -x file,则 file 同时具有 r  x 权限时,才回传 true 
-o (or)
两状况任何一个成立!例如 test -r file -o -x file,则 file 具有 r  x 权限时,就可回传 true 
反相状态,如 test ! -x file ,当 file 不具有 x 时,回传 true 

OK
!现在我们就利用 test 来帮我们写几个简单的例子。首先,判断一下, 让使用者输入一个档名,我们判断:
这个档案是否存在,若不存在则给予一个『Filename does not exist』的讯息,并中断程序; 
若这个档案存在,则判断他是个档案或目录,结果输出『Filename is regular file』或 Filename is directory 
判断一下,执行者的身份对这个档案或目录所拥有的权限,并输出权限数据!
你可以先自行创作看看,然后再跟底下的结果讨论讨论。注意利用 test  && 还有 || 等标志!
[root@linux scripts]# vi sh05.sh
#!/bin/bash
# Program:
#  Let user input a filename, the program will search the filename
# 1.) exist? 2.) file/directory? 3.) file permissions 
# History:
#  2005/08/25 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

# 1. 
让使用者输入档名,并且判断使用者是否真的有输入字符串?
echo -e "The program will show you that filename is exist which input by you.\n\n"
read -p "Input a filename : " filename
test -z $filename && echo "You MUST input a filename." && exit 0
# 2. 
判断档案是否存在?
test ! -e $filename && echo "The filename $filename DO NOT exist" && exit 0
# 3. 
开始判断档案类型与属性
test -f $filename && filetype="regulare file"
test -d $filename && filetype="directory"
test -r $filename && perm="readable"
test -w $filename && perm="$perm writable"
test -x $filename && perm="$perm executable"
# 4. 
开始输出信息!
echo "The filename: $filename is a $filetype"
echo "And the permission are : $perm"

 
很有趣的例子吧!您可以自行再以其它的案例来撰写一下可用的功能呢!



--------------------------------------------------------------------------------
利用判断符号 [ ]

除了我们很喜欢使用的 test 之外,其实,我们还可以利用判断符号『 [ ] 』来进行数据的判断呢! 举例来说,如果我想要知道 $HOME 这个变量是否为空的,可以这样做:
[root@linux ~]# [ -z "$HOME" ]

 
但使用 [] 要特别注意的是,在上述的每个组件中间都需要有空格键来分隔,假设我空格键使用『□』来表示, 那么,在这些地方你都需要有空格键:
[  "$HOME"  ==  "$MAIL"  ]
[
"$HOME"=="$MAIL"]
 
                

 
上面的例子在说明,两个字符串 $HOME  $MAIL 是否相同的意思,相当于 test $HOME = $MAIL 的意思啦! 而如 果没有空白分隔,例如 [$HOME==$MAIL] 时,我们的 bash 就会显示错误讯息了!这可要很注意啊! 所以说,您最好要注意:
在中括号 [] 内的每个组件都需要有空格键来分隔; 
在中括号内的变量,最好都以双引号来设定; 
在中括号内的常数,最好都以单或双引号来设定。
举例来说,假如我设定了 name="VBird Tsai" ,然后这样判定:
[root@linux ~]# name="VBird Tsai"
[root@linux ~]# [ $name == "VBird" ]
bash: [: too many arguments

 
为什么呢?因为 $name 如果没有使用双引号刮起来,那么上面的判定式会变成: 
[ VBird Tsai == "VBird" ] 
而不是我们要的: 
[ "VBird Tsai" == "VBird" ] 
这可是差很多的喔!另外,中括号的使用方法与标志与 test 几乎一模一样啊~ 只是中括号比较常用在条件判断式 if ..... then ..... fi 的情况中就是了。 好,那我们也继续来做一个小案例好了:
当执行一个程序的时候,这个程序会让使用者选择 Y  N  
如果使用者输入 Y  y 时,就显示『 OK, continue  
如果使用者输入 n  N 时,就显示『 Oh, interrupt !』 
如果不是 Y/y/N/n 之内的其它字符,就显示『I don't know what is your choise
利用中括号、 &&  || 来继续吧!
[root@linux scripts]# vi sh06.sh
#!/bin/bash
# Program:
#  This program will show the user's choice
# History:
#  2005/08/25 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

read -p "Please input (Y/N): " yn
[ "$yn" == "Y" -o "$yn" == "y" ] && echo "OK, continue" && exit 0
[ "$yn" == "N" -o "$yn" == "n" ] && echo "Oh, interrupt!" && exit 0
echo "I don't know what is your choise" && exit 0

 
很有趣吧!利用这个字符串判别的方法,我们就可以很轻松的将使用者想要进行的工作分门别类呢! 接下来,我们再来谈一些其它有的没有的东西吧!

Tips:
为什么判断式里面下达等于要用 == 而不是一个 = 就好了呢?我们在前一章正规表示法里面的 awk 提到, 只有一个 = 用来给予一个变量设定其内容,逻辑判断时,则会给予两个等于, 亦即『比较』而非『设定』的意思~这里要好好的分辨一下喔! ^_^   


--------------------------------------------------------------------------------
Shell script 
的预设变数($0, $1...)

其实,当我们执行一个 shell script 时,在这个 shell script 里面就已将帮我们做好一些可用的变量了。 举例来说,在不久的将来,您就会发现,当我们要启动一个系统服务时,可能会下达类似这样的指令:
[root@linux ~]# /etc/init.d/crond restart

 
那是啥玩意儿?呵呵!就是『向 /etc/init.d/crond 这个 script 下达 restart 的指令』, 咦!我们不是都使  read 来读取使用者输入的变量内容吗?为啥我可以直接在 script 后面接上这个参数? 这是因为 shell script 帮我们设定好 一些指定的变量了!变量的对应是这样的:

/path/to/scriptname  opt1  opt2  opt3  opt4  ...
       $0             $1    $2    $3    $4   ...

 
这样够清楚了吧?!执行的文件名为 $0 这个变量,第一个接的参数就是 $1 啊~ 所以,只要我们在 script 里面善用 $1 的话, 就可以很简单的立即下达某些指令功能了! 好了,来做个例子吧~假设我要执行一个 script ,执行后,该 script 会自动列出自己的档名,  还有后面接的前三个参数,该如何是好?
[root@linux scripts]# vi sh07.sh
#!/bin/bash
# Program:
#  The program will show it's name and first 3 parameters.
# History:
#  2005/08/25 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

echo "The script naem is ==> $0"
[ -n "$1" ] && echo "The 1st paramter is ==> $1" || exit 0
[ -n "$2" ] && echo "The 2nd paramter is ==> $2" || exit 0
[ -n "$3" ] && echo "The 3th paramter is ==> $3" || exit 0

 
这支程序里面鸟哥加上了一些控制式,亦即利用 &&  || 来加以判断 $1 ~ $3 是否存在? 若存在才显示,若不存在就中断~执行结果如下:
[root@linux scripts]# sh sh07.sh theone haha quot
The script naem is ==> sh07.sh
The 1st paramter is ==> theone
The 2nd paramter is ==> haha
The 3th paramter is ==> quot

 
上面这七的例子都很简单吧?几乎都是利用 bash 的相关功能而已~ 不难啦~底下我们就要使用条件判断式来进行一些分别功能的设定了,好好瞧一瞧先~ 

--------------------------------------------------------------------------------
条件判断式:

只要讲到『程序』的话,那么条件判断式,亦即是『 if then 』这种判别式肯定一定要学习的! 因为很多时候,我们都必须要依据某些数据来 判断程序该如何进行。举例来说,我们在上头不是有练习当使用者输入 Y/N 时,必须要执行不同的讯息输出吗?简单的方式可以利用 &&   || ,但如果我还想要执行一堆指令呢? 那真的得要 if then 来帮忙啰~底下我们就来聊一聊!


--------------------------------------------------------------------------------
利用 if .... then

这个 if .... then 是最常见的条件判断式了~简单的说,就是当符合某个条件判断的时候, 就予以进行某项工作就是了。我们可以简单的这样看:
if [ 
条件判断式 ]; then
当条件判断式成立时,可以进行的指令工作内容;
fi

 
至于条件判断式的判断方法,与前一小节的介绍相同啊!较特别的是,如果我有多个条件要判别时, 除了 sh06.sh 那个案例,也就是将多个条 件写入一个中括号内的情况之外, 我还可以有多个中括号来隔开喔!而括号与括号之间,则以 &&  || 来隔开,他们的意义是: 
&& 
代表 AND  
|| 
代表 or 
所以,在使用中括号的判断式中, &&  || 就与指令下达的状态不同了。举例来说, sh06.sh 那个例子我可以改写成这样:
[root@linux scripts]# vi sh06-2.sh
#!/bin/bash
# Program:
#  This program will show the user's choice
# History:
#  2005/08/25 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

read -p "Please input (Y/N): " yn

if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
echo "OK, continue"
exit 0
fi
if [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
echo "Oh, interrupt!"
exit 0
fi
echo "I don't know what is your choise" && exit 0

 
不过,由这个例子看起来,似乎也没有什么了不起吧? sh06.sh 还比较简单呢~ 但是,如果我们考虑底下的状态,您就会知道 if then 的好处了:
if [ 
条件判断式 ]; then
当条件判断式成立时,可以进行的指令工作内容;
else
当条件判断式不成立时,可以进行的指令工作内容;
fi

 
如果考虑更复杂的情况,则可以使用这个语法:
if [ 
条件判断式一 ]; then
当条件判断式一成立时,可以进行的指令工作内容;
elif [ 
条件判断式二 ]; then
当条件判断式二成立时,可以进行的指令工作内容;
else
当条件判断式一与二均不成立时,可以进行的指令工作内容;
fi

 
那我就可以将 sh06-2.sh 改写成这样:
[root@linux scripts]# vi sh06-3.sh
#!/bin/bash
# Program:
#  This program will show the user's choice
# History:
#  2005/08/25 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

read -p "Please input (Y/N): " yn

if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
echo "OK, continue"
elif [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
echo "Oh, interrupt!"
else
echo "I don't know what is your choise"
fi

 
是否程序变得很简单,而且依序判断,可以避免掉重复判断的状况,这样真的很容易设计程序的啦! ^_^ 好了,那么如果我要侦测你所输入的参数是 否为 hello   也就是说,如果我想要知道,你在程序后面所接的第一个参数 (就是 $1 啊!是否为 hello 
如果是的话,就显示 "Hello, how are you ?" 
如果没有加任何参数,就提示使用者必须要使用的参数下达法; 
而如果加入的参数不是 hello ,就提醒使用者仅能使用 hello 为参数。
整个程序的撰写可以是这样的:
[root@linux scripts]# vi sh08.sh
#!/bin/bash
# Program:
#  Show "Hello" from $1....
# History:
#  2005/08/28 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

if [ "$1" == "hello" ]; then
echo "Hello, how are you ?"
elif [ "$1" == "" ]; then
echo "You MUST input parameters, ex> $0 someword"
else
echo "The only parameter is 'hello'"
fi

 
然后您可以执行这支程序,分别在 $1 的位置输入 hello, 没有输入与随意输入, 就可以看到不同的输出啰~是否还觉得挺简单的啊!  ^_^。事实上, 学到这里,也真的很厉害了~好了,底下我们继续来玩一些比较大一点的啰~ 我们在前一章已经学会了 grep 这个好用的玩意儿,那 么多学一个叫做 netstat 的指令, 这个指令可以查询到目前主机有开启的网络服务端口口 (service ports) 相关的功能我们会在 服务器架设篇继续介绍,这里您只要知道,我可以利用『 netstat -tuln 』来取得目前主机有启动的服务, 而且取得的信息有点像这样:
[root@linux ~]# netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address   Foreign Address    State
tcp        0      0  0.0.0 .0:199     0.0.0.0:*          LISTEN
tcp        0      0 :::80           :::*               LISTEN
tcp        0      0 :::22           :::*               LISTEN
tcp        0      0 :::25           :::*               LISTEN

 
上面的重点是特殊字体的那个部分,那些特殊字体的部分代表的就是 port 啰~ 那么每个 port 代表的意义为何呢?几个常见的 port 与相关网络服务的关系是: 
80: WWW 
22: ssh 
21: ftp 
25: mail
那我如何透过 netstat 去侦测我的主机是否有开启这四个主要的网络服务端口口呢? 我可以简单的这样去写这个程序喔:
[root@linux scripts]# vi sh09.sh
#!/bin/bash
# Program:
#  Using netstat and grep to detect WWW,SSH,FTP and Mail services.
# History:
#  2005/08/28 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

# 1. 
先作一些告知的动作而已~
echo "Now, the services of your Linux system will be detect!"
echo -e "The www, ftp, ssh, and mail will be detect! \n"

# 2. 
开始进行一些测试的工作,并且也输出一些信息啰!
testing=`netstat -tuln | grep ":80 "`
if [ "$testing" != "" ]; then
echo "WWW is running in your system."
fi
testing=`netstat -tuln | grep ":22 "`
if [ "$testing" != "" ]; then
echo "SSH is running in your system."
fi
testing=`netstat -tuln | grep ":21 "`
if [ "$testing" != "" ]; then
echo "FTP is running in your system."
fi
testing=`netstat -tuln | grep ":25 "`
if [ "$testing" != "" ]; then
echo "Mail is running in your system."
fi

 
这样又能够一个一个的检查啰~是否很有趣啊! ^_^。接下来,我们再来玩更难一点的。 我们知道可以利用 date 来显示日期与时间,也可以 利用 $((计算式)) 来计算数值运算。 另外, date 也可以用来显示自 19710101 以来的『总秒数』 (请自行查阅  man date  info date) 。那么,您是否可以撰写一支小程序,用来『计算退伍日期还剩几天?』也就是说:
先让使用者输入他们的退伍日期; 
再由现在日期比对退伍日期; 
由两个日期的比较来显示『还需要几天』才能够退伍的字样。
似乎挺难的样子?其实也不会啦,利用『 date --date="YYYYMMDD" +%s 』就能够达到我们所想要的啰~如果您已经写完了程序,对照底下的写法试看看:
[root@linux scripts]# vi sh10.sh
#!/bin/bash
# Program:
#  Tring to calculate your demobilization date at how many days 
# later...
# History:
#  2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

# 1. 
告知使用者这支程序的用途,并且告知应该如何输入日期格式?
echo "This program will try to calculate :"
echo "How many days about your demobilization date..."
read -p "Please input your demobilization date (YYYYMMDD ex>20050401): " date2

# 2. 
测试一下,这个输入的内容是否正确?利用正规表示法啰~
date_d=`echo $date2 |grep '[0-9]\{8\}'`
if [ "$date_d" == "" ]; then
echo "You input the wrong format of date...."
exit 1
fi

# 3. 
开始计算日期啰~
declare -i date_dem=`date --date="$date2" +%s`
declare -i date_now=`date +%s`
declare -i date_total_s=$(($date_dem-$date_now))
declare -i date_d=$(($date_total_s/60/60/24))
if [ "$date_total_s" -lt "0" ]; then
echo "You had been demobilization before: " $((-1*$date_d)) " ago"
else
declare -i date_h=$(($(($date_total_s-$date_d*60*60*24))/60/60))
echo "You will be demobilized after $date_d days and $date_h hours."
fi

 
瞧一瞧,这支程序可以帮您计算退伍日期呢~如果是已经退伍的朋友, 还可以知道已经退伍多久了~哈哈!很可爱吧~利用 date 算出自   1971/01/01  以来的总秒数, 再与目前的总秒数来比对,然后以一天的总秒数 (60*60*24) 为基数去计算总日数, 就能够得知两者的 差异了~瞧~全部的动作都没有超出我们所学的范围吧~ ^_^ 还能够避免使用者输入错误的数字,所以多了一个正规表示法的判断式呢~ 这个例子比较难, 有兴趣想要一探究竟的朋友,可以作一下课后练习题 关于计算生日的那一题喔!~加油!



--------------------------------------------------------------------------------
利用 case ..... esac 判断

上个小节提到的『 if .... then .... fi 』对于变量的判断中, 是以比对的方式来分辨的,如果符合状态就进行某些行为,并 且透过较多层次 ( 就是 elif ... ) 的方式来进行多个变量的程序代码撰写,譬如 sh08.sh 那个小程序,就是用这样的方式来的啰。  好,那么万一我有多个既定的变量内容,例如 sh08.sh 当中,我所需要的变量就是 "hello" 及空字符串两个, 那么我只要针对这两个变量来 设定状况就好了对吧?!那么可以使用什么方式来设计呢? 呵呵~就用 case ... in .... esac 吧~,他的语法如下:
case $
变量名称 in
  "
第一个变量内容")
程序段
;;
  "
第二个变量内容")
程序段
;;
  *)
不包含第一个变量内容与第二个变量内容的其它程序执行段
exit 1
;;
esac

 
要注意的是,这个语法是以 case 为开头,而以 esac 为结尾,啥?为何是 esac 呢?想一想,既然 if 的结尾是 fi ,那么  case 的结尾当然就是将 case 倒着写,自然就是 esac 啰~ ^_^,很好记吧~ 另外,每一个变量内容的程序段最后都需要两个分号  (;;) 来代表该程序段落的结束,这挺重要的喔! 至于为何需要有 * 这个变量内容在最后呢?这是因为,如果使用者不是输入变量内容一或二时, 我们 可以告知使用者相关的信息啊!举例来说,我们如果将 sh08.sh 改写的话, 他应该会变成这样喔!
[root@linux scripts]# vi sh08-2.sh
#!/bin/bash
# Program:
#  Show "Hello" from $1.... by using case .... esac
# History:
#  2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

case $1 in
  "hello")
echo "Hello, how are you ?"
;;
  "")
echo "You MUST input parameters, ex> $0 someword"
;;
  *)
echo "Usage $0 {hello}"
;;
esac

 
在上面这个 sh08-2.sh 的案例当中,如果你输入『 sh sh08-2.sh test 』来执行, 那么屏幕上就会出现 Usage sh08-2.sh {hello}』的字样,告知执行者仅能够使用 hello 喔~ 这样的方式对于需要某些固定字符串来执行的变量内 容就显的更加的方便呢? 这种方式您真的要熟悉喔!这是因为系统的很多服务的启动 scripts 都是使用这种写法的, 举例来说,我们 Linux  的服务启动放置目录是在 /etc/init.d/ 当中,我已经知道里头有个 syslog 的服务,我想要重新启动这个服务,可以这样做: 
/etc/init.d/syslog restart
重点是那个 restart 啦~如果您进入 /etc/init.d/syslog 就会看到他使用的是 case 语法, 并且会规定某些既 定的变量内容,你可以直接下达 /etc/init.d/syslog   script 就会告知你有哪些后续接的变量可以使用啰~方便吧!  ^_^

一般来说,使用『 case $变量 in 』这个语法中,当中的那个 $变量 大致有两种取得的方式:
直接下达式:例如上面提到的,利用『 script.sh variable  的方式来直接给予 $1 这个变量的内容,这也是在 /etc/init.d 目录下大多数程序的设计方式。 
交互式:透过 read 这个指令来让使用者输入变量的内容。
这么说或许您的感受性还不高,好,我们直接写个程序来玩玩:让使用者能够输入 one, two, three  并且将使用者的变量显示到屏幕上,如果不是 one, two, three 时,就告知使用者仅有这三种选择。
[root@linux scripts]# vi sh11.sh
#!/bin/bash
# Program:
#  Let user input one, two, three and show in screen.
# History:
#  2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

echo "This program will print your selection !"
# read -p "Input your choice: " choice
# case $choice in
case $1 in
  "one")
echo "Your choice is ONE"
;;
  "two")
echo "Your choice is TWO"
;;
  "three")
echo "Your choice is THREE"
;;
  *)
echo "Usage {one|two|three}"
;;
esac

 
此时,您可以使用『 sh sh11.sh two 』的方式来下达指令,就可以收到相对应的响应了。 上面使用的是直接下达的方式,而如果使用 的是交互式时,那么将上面第 10, 11 行的 "#" 拿掉, 并将 12 行加上批注 (#),就可以让使用者输入参数啰~这样是否很有趣啊?!



--------------------------------------------------------------------------------
利用 function 功能

什么是『函数 (function)』功能啊?简单的说,其实, 函数可以在 shell script 当中做出一个类似自订执行指令的东西, 最大的功能是, 可以简化我们很多的程序代码~举例来说,上面的 sh11.sh 当中,每个输入结果 one, two, three 其实输出的内容 都一样啊~那么我就可以使用 function 来简化了! function 的语法是这样的:
function fname() {
程序段
}

 
那个 fname 就是我们的自订的执行指令名称~而程序段就是我们要他执行的内容了。 要注意的是,在 shell script 当中,  function 的设定一定要在程序的最前面, 这样才能够在执行时被找到可用的程序段喔!好~我们将 sh11.sh 改写一下:
[root@linux scripts]# vi sh11-2.sh
#!/bin/bash
# Program:
#  Let user input one, two, three and show in screen.
# History:
#  2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

function printit(){
echo -n "Your choice is "
}

echo "This program will print your selection !"
case $1 in
  "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 的话,就表示我的  shell script 要去执行『 function printit ....  里面的那几个程序段落啰! 当然啰,上面这个例子举得太简单 了,所以您不会觉得 function 有什么好厉害的, 不过,如果某些程序代码一再地在 script 当中重复时,这个 function 可就重 要的多啰~ 不但可以简化程序代码,而且可以做成类似『模块』的玩意儿,真的很棒啦!

另外, function 也是拥有内建变量的~他的内建变量与 shell script 很类似, 函数名称代表示 $0 ,而后续接的变量 也是以 $1, $2... 来取代的~ 这里很容易搞错喔~因为『 function fname() { 程序段 } 』内的 $0, $1...  等等与 shell script  $0 是不同的。以上面 sh11-2.sh 来说,假如我下达:『 sh sh11-2.sh one   表示在 shell script 内的 $1  "one" 这个字符串。但是在 printit() 内的 $1 则与这个 one 无关。 我们 将上面的例子再次的改写一下,让您更清楚!
[root@linux scripts]# vi sh11-3.sh
#!/bin/bash
# Program:
#  Let user input one, two, three and show in screen.
# History:
#  2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

function printit(){
echo "Your choice is $1"
}

echo "This program will print your selection !"
case $1 in
  "one")
printit 1
;;
  "two")
printit 2
;;
  "three")
printit 3
;;
  *)
echo "Usage {one|two|three}"
;;
esac

 
在上面的例子当中,如果您输入『 sh sh11-3.sh one 』就会出现『 Your choice is 1 』的字样~ 为什么是  1 呢?因为在程序段落当中,我们是写了『 printit 1 』那个 1 就会成为 function 当中的 $1 喔~ 这样是否理解呢?  function 本身其实比较困难一点,如果您还想要进行其它的撰写的话。 不过,我们仅是想要更加了解 shell script 而已,所以,这 里看看即可~了解原理就好啰~ ^_^ 

--------------------------------------------------------------------------------
循环 (loop)

除了 if...then...fi 这种条件判断式之外,循环可能是程序当中最重要的一环了~ 循环可以不断的执行某个程序段落,直到使用者设定的条件达成为止。 所以,重点是那个『条件的达成』是什么。底下我们就来谈一谈:


--------------------------------------------------------------------------------
while do done, until do done

一般来说,循环最常见的就是底下这两种状态了:
while [ condition ]
do
程序段落
done

 
这种方式中, while 是『当....时』,所以,这种方式说的是『当 condition 条件成立时,就进行循环,直到 condition 的条件不成立才停止』的意思。
until [ condition ]
do
程序段落
done

 
这种方式恰恰与 while 相反,它说的是『当 condition 条件成立时,就终止循环, 否则就持续进行循环的程序段。』是否刚好相反 啊~我们以 while 来做个简单的练习好了。 假设我要让使用者输入 yes 或者是 YES 才结束程序的执行,否则就一直进行告知使用者输入字符 串。
[root@linux scripts]# vi sh12.sh
#!/bin/bash
# Program:
#  Use loop to try find your input.
# History:
#  2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

while [ "$yn" != "yes" ] && [ "$yn" != "YES" ]
do
read -p "Please input yes/YES to stop this program: " yn
done

 
上面这个例题的说明是『当 $yn 这个变量不是 "yes"  $yn 也不是 "YES" 时,才进行循环内的程序。』 而如果 $yn  "yes"  "YES" 时,就会离开循环啰~那如果使用 until 呢?呵呵有趣啰~ 他的条件会变成这样:
[root@linux scripts]# vi sh12-2.sh
#!/bin/bash
# Program:
#  Use loop to try find your input.
# History:
#  2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

until [ "$yn" == "yes" ] || [ "$yn" == "YES" ]
do
read -p "Please input yes/YES to stop this program: " yn
done

 
仔细比对一下这两个东西有啥不同喔! ^_^再来,如果我想要计算 1+2+3+....+100 这个数据呢? 利用循环啊~他是这样的:
[root@linux scripts]# vi sh13.sh
#!/bin/bash
# Program:
#  Try to use loop to calculate the result "1+2+3...+100"
# History:
#  2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

s=0
i=0
while [ "$i" != "100" ]
do
i=$(($i+1))
s=$(($s+$i))
done
echo "The result of '1+2+3+...+100' is ==> $s"

 
嘿嘿!当您执行了『 sh sh13.sh 』之后,就可以得到 5050 这个数据才对啊!这样瞭呼~ 那么让您自行做一下,如果想要让使用者 自行输入一个数字,让程序由 1+2+... 直到您输入的数字为止, 该如何撰写呢?应该很简单吧?!答案可以参考一下习题练习里面的一题喔!



--------------------------------------------------------------------------------
for...do....done

相对于 while, until 的循环方式是必须要『符合某个条件』的状态, for 这种语法,则是『 已经知道要进行几次循环』的状态!他的语法是:
for (( 
初始值限制值执行步阶 ))
do
程序段
done

 
这种语法适合于数值方式的运算当中,在 for 后面的括号内的三串内容意义为:
初始值:某个变量在循环当中的起始值,直接以类似 i=1 设定好; 
限制值:当变量的值在这个限制值的范围内,就继续进行循环。例如 i<=100 
执行步阶:每作一次循环时,变量的变化量。例如 i=i+1
值得注意的是,在『执行步阶』的设定上,如果每次增加 1 ,则可以使用类似『i++』的方式,亦即是 i 每次循环都会增加一的意思。好,我们以这种方式来进行 1 累加到 100 的循环吧!
[root@linux scripts]# vi sh14.sh
#!/bin/bash
# Program:
#  Try do calculate 1+2+....+100
# History:
#  2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

s=0
for (( i=1; i<=100; i=i+1 ))
do
s=$(($s+$i))
done
echo "The result of '1+2+3+...+100' is ==> $s"

 
一样也是很简单吧!利用这个 for 则可以直接限制循环要进行几次呢!这么好用的东西难道只能在数值方面动作? 当然不是啦~我们还可以利用底下的方式来进行非数字方面的循环运作喔!
for $var in con1 con2 con3 ...
do
程序段
done

 
以上面的例子来说,这个 $var 的变量内容在循环工作时:
第一次循环时, $var 的内容为 con1  
第二次循环时, $var 的内容为 con2  
第三次循环时, $var 的内容为 con3  
....
我们可以做个简单的练习。假设我有三种动物,分别是 dog, cat, elephant 三种, 我想每一行都输出这样:『There are dogs...』之类的字样,则可以:
[root@linux scripts]# vi sh15.sh
#!/bin/bash
# Program:
#  Using for .... loop to print 3 animal 
# History:
#  2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

for animal in dog cat elephant
do
echo "There are ""$animal""s.... "
done

 
很简单是吧! ^_^。好了,那么如果我想要让使用者输入某个目录, 然后我找出某目录内的文件名的权限呢?又该如何是好?呵呵!可以这样做啦~
[root@linux scripts]# vi sh16.sh
#!/bin/bash
# Program:
#  let user input a directory and find the whole file's permission.
# History:
#  2005/08/29 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

# 1. 
先看看这个目录是否存在啊?
read -p "Please input a directory: " dir
if [ "$dir" == "" ] || [ ! -d "$dir" ]; then
echo "The $dir is NOT exist in your system."
exit 1
fi

# 2. 
开始测试档案啰~
filelist=`ls $dir`
for filename in $filelist
do
perm=""
test -r "$dir/$filename" && perm="$perm readable"
test -w "$dir/$filename" && perm="$perm writable"
test -x "$dir/$filename" && perm="$perm executable"
echo "The file $dir/$filename's permission is $perm "
done

 
呵呵!很有趣的例子吧~利用这种方式,您可以很轻易的来处理一些档案的特性呢~ 我们循环就介绍到这里了~其它更多的应用,就得视您的需求来玩啰~。 

--------------------------------------------------------------------------------
shell script 
的追踪与 debug

scripts 
在执行之前,最怕的就是出现问题了!那么我们如何 debug 呢?有没有办法不需要透过直接执行该 scripts 就可以来判断是否有问题呢!?呵呵! 当然是有的!我们就直接以 bash 的相关参数来进行判断吧!
[root@linux ~]# sh [-nvx] scripts.sh
参数:
-n  
:不要执行 script,仅查询语法的问题;
-v  
:再执行 sccript 前,先将 scripts 的内容输出到屏幕上;
-x  
:将使用到的 script 内容显示到屏幕上,这是很有用的参数!
范例:

范例一:测试 sh16.sh 有无语法的问题?
[root@linux ~]# sh -n sh16.sh 
若语法没有问题,则不会显示任何信息!

范例二:将 sh15.sh 的执行过程全部列出来~
[root@linux ~]# sh -x sh15.sh 
+ PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/home/vbird/bin
+ export PATH
+ for animal in dog cat elephant
+ echo 'There are dogs.... '
There are dogs....
+ for animal in dog cat elephant
+ echo 'There are cats.... '
There are cats....
+ for animal in dog cat elephant
+ echo 'There are elephants.... '
There are elephants....
使用 -x 真的是追踪 script 的好方法,他可以将所有有执行的程序段在执行前列出来,
如果是程序段落,则输出时,最前面会加上 + 字号,表示他是程序代码而已,
实际的输出则与 standard output 有关啊~如上所示。

 
在上面的范例二当中,我们可以透过这个简单的参数 -x 来达成 debug 的目的,这可是一个不可多得的参数, 通常如果您执行 script 却发生问题时,利用这个 -x 参数,就可以知道问题是发生在哪一行上面了!

熟悉 sh 的用法,将可以使您在管理 Linux 的过程中得心应手!至于在 Shell scripts 的学习方法上面,需要『多看、多模 仿、并加以修改成自己的样式!』 是最快的学习手段了!网络上有相当多的朋友在开发一些相当有用的 scripts ,若是您可以将对方的  scripts 拿来,并且改成适合自己主机的样子!那么学习的效果会是最快的呢!

另外,我们 Linux 系统本来就有很多的启动 script ,如果您想要知道每个 script 所代表的功能是什么? 可以直接以  vi 进入该 script 去查阅一下,通常立刻就知道该 script 的目的了。 举例来说,我们的 Linux 里头有个文件名称为:  /etc/init.d/portmap ,这个 script 是干嘛用的? 利用 vi 去查阅最前面的几行字,他出现如下信息:
# description: The portmapper manages RPC connections, which are used by \
#              protocols such as NFS and NIS. The portmap server must be \
#              running on machines which act as servers for protocols which \
#              make use of the RPC mechanism.
# 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/bash
echo -e "Your name is ==> `whoami`"
echo -e "The current directory is ==> `pwd`"

请自行建立一支程序,该程序可以用来计算『您还有几天可以过生日』啊?? 
#!/bin/bash
read -p "Pleas input your birthday (MMDD, ex> 0709): " bir
now=`date +%m%d`
if [ "$bir" == "$now" ]; then
echo "Happy Birthday to you!!!"
elif [ "$bir" -gt "$now" ]; then
year=`date +%Y`
total_d=$(($((`date --date="$year$bir" +%s`-`date +%s`))/60/60/24))
echo "Your birthday will be $total_d later"
else
year=$((`date +%Y`+1))
total_d=$(($((`date --date="$year$bir" +%s`-`date +%s`))/60/60/24))
echo "Your birthday will be $total_d later"
fi

让使用者输入一个数字,程序可以由 1+2+3... 一直累加到使用者输入的数字为止。 
#!/bin/bash
read -p "Please input an integer number: " number
i=0
s=0
while [ "$i" != "$number" ]
do
i=$(($i+1))
s=$(($s+$i))
done
echo "the result of '1+2+3+...$number' is ==> $s"

撰写一支程序,他的作用是: 1.) 先查看一下 /root/test/logical 这个名称是否存在; 2.) 若不存在,则建立一个档 案,使用 touch 来建立,建立完成后离开; 3.) 如果存在的话,判断该名称是否为档案,若为档案则将之删除后建立一个档案,档名为  logical ,之后离开; 4.) 如果存在的话,而且该名称为目录,则移除此目录! 
#!/bin/bash
if [ ! -e logical ]; then
touch logical
echo "Just make a file logical"
exit 1
elif [ -e logical ] && [ -f logical ]; then
rm logical
mkdir logical
echo "remove file ==> logical"
echo "and make directory logical"
exit 1
elif [ -e logical ] && [ -d logical ]; then
rm -rf logical
echo "remove directory ==> logical"
exit 1
else
echo "Does here have anything?"
fi

我们知道 /etc/passwd 里面以 : 来分隔,第一栏为账号名称。请写一只程序,可以将 /etc/passwd 的第一栏取出,而且每一栏都以一行字符串『The 1 account is "root" 』来显示,那个 1 表示行数。 
#!/bin/bash
accounts=`cat /etc/passwd | cut -d':' -f1`
for account in $accounts
do
declare -i i=$i+1
echo "The $i account is \"$account\" "
done

你可能感兴趣的:(鸟哥学习Shell Scripts)