引用:
学习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