第十三章 总结
shellscript
shell script是利用shell的功能所写的一个“程序”(program),这个程序是使用纯文本文件,将一些shell的语法与命令(含外部命令)写在里面,搭配正则表达式,管道命令与数据流重定向等功能,以达到我们所想要的处理目的。
shell script就像早期DOS年代的批处理文件(.bat),最简单的功能就是将许多命令写在一起,让用户轻易就能够一下子处理复杂的操作(执行一个文件“shell script”,就能够一次执行多个命令)。而且shell script更提供数组、循环、条件与逻辑判断等重要功能,让用户也可以直接以shell来编写程序,而不必使用类似C程序语言等传统程序编写的语法。
shell script可以简单被看成是批处理文件,也可以被说成是一个程序语言,且这个程序语言由于都是利用shell与相关工具命令,所以不需要编译即可执行,且拥有不错的排错工具,所以它可以帮助系统管理员快速管理好主机。
学习shellscript的原因
² 自动化管理的重要依据
² 追踪与管理系统的重要工作
² 简单入侵检测功能
² 连续命令单一化
² 简易的数据处理
² 跨平台支持与学习历程较短
shellscript用在系统管理上面是很好的一项工具,但是用在处理大量数值运算上,就不够好了,因为shell script的速度较慢,且使用的CPU资源较多,造成主机资源的分配不良。
script的编写与执行
shell script是纯文本文件,可以编辑这个文件,然后让这个文件来帮我们一次执行多个命令,或者是利用一些运算与逻辑判断来帮助我们达成某些功能。故要编辑这个文件的内容时,就需要具备bash命令执行的相关知识。
在shell script的编写中,需要注意以下事项:
² 命令的执行是从上而下的、从左而右地分析与执行
² 命令的执行就如同前面提到的:命令,参数间的多个空白都会被忽略掉
² 空白行也将被忽略掉,并且[Tab]按键所得到的空白同样视为空格键
² 如果读取到一个Enter符号(CR),就尝试开始执行该行(或该串)命令
² 至于如果一行的内容太多,则可以使用”\Enter“来扩展至下一行
² “#”可作为批注。任何加在#后面的数据将全部被视为批注文件而忽略。
综上,在script内所编写的程序就会被一行一行执行。那么shell如何执行,其实很简单(以/home/dmtsai/shell.sh为例):
² 直接命令执行:shell.sh文件必须具备可读与可执行(rx)的权限,然后:
n 绝对路径:使用/home/dmtsai/shell.sh来执行命令
n 相对路径:假设工作目录在/home/dmtsai/,则使用./shell.sh来执行
n 变量PATH功能:将shell.sh放在PATH指定的目录内,如~:/bin/。
² 以bash进程来执行:通过bashshell.sh或sh shell.sh来执行
重点是让那个shell.sh内的命令可以被执行。若shell.sh在~/bin内具有rx的权限,那就直接输入shell.sh即可执行该脚本程序。
sh shell.sh可以执行原因:/bin/sh其实就是/bin/bash(连接文件),使用shshell.sh即告诉系统,要直接以bash的功能来执行shell.sh这个文件内的相关命令,故此时shell.sh只要有r的权限即可被执行。也可以利用sh的参数,如-n及-x来检查与追踪shell.sh的语法是否正确。
编写第一个script mkdir scripts; cd scripts vi sh01.sh 输入内容如下: #!/bin/bash # Program: # This program shows "hello world!" in your screen. # History: # 2016/07/03 yzhang 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
将所编写的scripts放置在主文件的~/scripts目录下,便于管理。下面对程序分段进行说明:
² 第一行#!/bin/bash声明这个script使用的shell命令
因为使用的是bash,故必须要以”#!/bin/bash”来声明这个文件内的语法使用bash的语法。当程序被执行时,他就能够加载bash的相关环境配置文件(一般来说就是non-login shell的~/.bashrc),并且执行bash来使下面的命令能够执行。这很重要(在很多情况中,如果没有设置好这一行,那么改程序很可能无法执行,因为系统可能无法判断改程序需要使用什么shell来执行)。
² 程序内容的说明
在scripts中,除了第一行的#!是用来声明shell的之外,其他的#都是批注的用途。上面程序中,第二行以下使用来说明整个程序的基本数据。一般来说,建议一定要养成说明该script的内容与功能,版本信息,作者与联系方式,建立日期,历史记录等习惯。这有助于将来程序的改写与调试。
² 主要环境变量的声明
建议务必要将一些重要的环境变量设置好。PATH与LANG是其中最重要的。如此,程序在进行时可以直接执行一些外部命令,而不必写绝对路径。
² 主要程序部分
就是主要的程序。
² 告知执行结果
可以使用$?来查看命令是否正确执行。也可以利用exit这个命令来让程序中断,并且回传一个数值给系统。在scripts中,可以使用exit回传一个数值给系统。利用exit n(n是数字)的功能,可以自定义错误信息,让程序变得更加聪明。
执行shell脚本,可以使用sh sh01.sh来实现。此外,也可以使用chmod a+x sh01.sh; ./sh01.sh来执行。
编写shellscript的良好习惯
在每个script的文件头处记录好:
² Script的功能
² Script的版本信息
² Script的作者与联系方式
² Script的版权声明方式
² Script的History(历史记录)
² Script内较特殊的命令,使用绝对路径的方式来执行
² Script执行时需要的环境变量预先声明与设置。
² 此外,建议特殊的程序代码,加上批注信息。程序编写最好使用嵌套方式,最好能以tab按键的空格缩排,这样程序代码会漂亮有条理。此外,编写script的工具最好使用vim而不是vi,因为vim会有额外的语法检查机制,能够在第一阶段编写时就发现语法方面的问题。
简单shell编写
交互式脚本:变量内容由用户决定
很多时候需要用户输入一些内容,好让程序可以顺利执行。这就体现了交互式编程。下面以read命令的用途,编写一个script,它可以让用户输入first name与last name,并且在屏幕上显示“”Your full name is:”的内容:
!#/bin/bash # program: # user inputs his first name and last name. Program shows his full name. # History: # 2016/07/03 Yzhang 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" #结果由屏幕输出
执行上面脚本,就能够发现用户自己输入的变量可以让程序所使用,并且显示到屏幕上。
随日期变化:利用日期进行文件的创建
假设服务器内有数据库,数据库每天的数据都不太一样,备份时,希望将每天的数据都备份成不同的文件名,此时利用日期保存文件很重要。举例如下:假设想创建三个空的文件(通过touch)文件名最开头由用户输入决定,假定用户输入filename好了,那今天的日期是2016/7/3,想以前天,昨天,今天的日期来创建这些文件,即filename_20160702,filename_21-0160703,filename_2016/07/04,其实现如下:
#!/bin/bash # Program: # Program creates three files, which named by user's input and data commond. # History: # 2016/07/03 Yzhang First Release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH # 1.让用户输入文件名,并取得fileuser这个变量 echo -e "I will user 'touch' command to create 3 files." #纯粹显示信息 read -p "Please input your filename: " 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"
上面的script中用到了命令$(command)取得信息、变量的设置功能,变量的累加以及利用touch命令辅助等。
数值运算:简单的减少乘除
可以使用declare来定义变量的类型。当变量定义成为整数后才能够进行加减运算。此外,可以用$((计算式))来进行数值运算。bash shell里头默认仅支持到整数的数据而已。下面,通过用户输入两个变量,然后对其进行相乘输出,实现如下:
#!/bin/bash #program: # User inputs 2 integer numbers; program will cross these two numbers. # History: # 20160703 Yzhang First Release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH echo -e "You should input 2 numbers, I will cross them! \n" read -p "first number: " firstnu read -p "second number: " secnu total=$(($firstnu*$secnu)) echo -e "\nThe result of $firstnu * $secnu is ==> $total"
可以使用”declare–I total=$firstnu*secnu”,也可以使用上面的方式进行。不过建议使用var=$((运算内容)),原因是容易记忆,也较方便。计算13与3的余数:echo $((13%3))
Script的执行方式区别
不同的script执行方式会造成不一样的结果,脚本的执行除了上面介绍的,还可以用source或小数点( . )来执行。
利用直接执行的方式来执行script
当使用前面提到的直接命令执行(不论是绝对路径/相对路径还是$PATH内),或者是利用bash(或sh)来执行脚本时,该script都会使用一个新的bash环境来执行脚本内的命令。即使用这种执行方式时,其实script是在子进程的bash内执行的。当子进程完后,子进程内的各项变量或操作将会结束而不会传回到父进程中。
上面运行sh02.sh时,用到的变量firstname和lastname,在父进程中不能执行,如执行echo $firstname和$lastname将显示两个变量不存在。
利用source来执行脚本:在父进程中执行
如果用source执行命令,则不一样。
如source sh02.sh
此时如输入用户名,当执行完成后,在shell下执行echo $firname $lastname则有数据产生,为输入的名字值。
当不注销系统而要让某些写入~/.bashrc设置生效时,需要使用source ~/.bashrc而不能使用bash~/.bashrc。
善用判断式
可以使用S?等来进行判断,但是可以通过test进行更简单的判断。
利用test命令的测试功能
当要检测系统上面某些文件或者是相关的属性时,利用test这个命令来工作真是好用得不得了,如检查/dmtsai是否存在时,使用:test –e /dmtsai
上面的执行结果并不会显示任何信息,但最后可以通过$?或&&及||来显示整个结果。
test –e /dmtsai && echo “exist” ||echo “Not exist”
最终结果可以显示exist还是not exist。-e是测试一个东西存在不存在。常用的测试命令如下:
测试的标志 |
代表意义 |
关于某个文件名的“文件类型”判断,如test – e filename表示存在否 |
|
-e |
该文件名是否存在 |
-f |
该文件名是否存在且为文件(file) |
-d |
该文件名是否存在且为目录(directory) |
-b |
该文件名是否存在且为一个block device设备 |
-c |
该文件名是否存在且为一个character device设备 |
-S |
该文件名是否存在且为一个Socket文件 |
-p |
该文件名是否存在且为一个FIFO(pipe)文件 |
-L |
该文件名是否存在且为一个连接文件 |
关于文件的权限检测,如test –r filename表示可读否(但root权限常有例外) |
|
-r |
检测该文件名是否存在且具有“可读”的权限 |
-w |
检测该文件名是否存在且具有“可写”的权限 |
-x |
检测该文件名是否存在且具有“可执行”的权限 |
-u |
检测该文件名是否存在且具有“SUID”的属性 |
-g |
检测该文件名是否存在且具有“SGID“的属性 |
-k |
检测该文件名是否存在且具有“Sticky bit”的属性 |
-s |
检测该文件名是否存在且具有“非空白文件” |
两个文件之间的比较,如test file1 –nt file2 |
|
-nt |
(newer than)判断file1是否比file2新 |
-ot |
(older than)判断file1是否比file2旧 |
-ef |
判断file1与file2是否为同一文件,可用在判断hard link的判定上。主要意义在于判定两个文件是否均指向同一个inode |
关于两个整数之间的判定吗,如test n1 –eq n2 |
|
-eq |
两数值相等(equal) |
-ne |
两数值不等(not equal) |
-gt |
N1大于n2(greate than) |
-lt |
N1小于n2(less than) |
-ge |
N1大于等于n2(greater than or equal) |
-le |
N1小于等于n2(less than or equal) |
判定字符串的数据 |
|
test –z string |
判定字符串是否为0,若string为空字符串,则为true |
test –n string |
判定字符串是否非为0,若string为空字符串,则为false |
test str1 = str2 |
判定str1是否等于str2,若相等,则回传true |
test str1 != str2 |
判定str1是否不等于str2,若相等,则回传false |
多重条件判定,若test –r filename –a –x filename |
|
-a |
两个条件同时成立!如test –r file –a –x file,则file同时具有r与x权限时,才回传true |
-o |
任何一个条件成立!如test –r file –o –x file,则file具有r或x权限时,就可回传true |
! |
反向状态,如test ! –x file,但file不具有x时,回传true |
下面利用test编写简单例子。首先判断一下,让用户输入一个文件名,判断如下:
² 这个文件是否存在,若不存在则给予一个“Filename does not exist”的信息,并中断程序
² 若文件存在,则判断它是个文件或目录,结果输出“Filename isregular file”或“Filename is directory”
² 判断一下,执行者的身份对这个文件或目录所拥有的权限,并输出权限数据。
#!/bin/bash # Program: # User input a filename, program will check the following: # 1) exist? 2) file/directory? 3) file permissions # History: # 2016/07/03 Yzhang First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH # 1.让用户输入文件名,并且判断用户是否真的输入字符串 echo -e "Please input a filename, I will check the filename's type and permission. \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 $filetype" echo "And the permissions are : $perm"
注意:由于root在很多权限的限制上面都是无效的,所以使用root执行这个脚本时,常常会发现与ls –l观察到的结果并不相同。建议使用一般用户执行此脚本。
利用判断符号[]
除了利用test外,还可以使用判断符号[]来进行数据的判断。例如想知道$HOME这个变量是否为空,可以执行如下命令:
[ -z "$HOME" ] ; echo $?
使用中括号必须要特别注意,因为中括号用在很多地方,包括统配符和正则表达式等,所以如果要在bash的语法当中使用中括号作为shell的判断式时,必须要注意中括号的两端需要有空格符来分割。
在bash当中,=号与==号的结果一样。
判断两个字符串是否相等,可以使用如下命令
[ “$HOME” == “$MAIL” ] 相当于test$HOME=SMAIL。如果没有空格,则bash将会显示错误信息。故注意如下:
² 在中括号[]没的每个组件都需要有空格键来分割;
² 在中括号内的变量,最好都以双引号括起来
² 在中括号内的变量,最好都以单或双引号括起来。
中括号使用的方法与test几乎一模一样。只是中括号比较常用在条件表达式if then fi的情况中。举例如下:
² 执行一个程序时,程序让用户选择Y或N
² 如果用户输入Y或y时,显示“OK, continue”
² 如果用户输出N或n时,显示“Ok, interrupt!”
² 如果用户输入不是Y,y,N,n之内的其他字符,显示“Idon’t know“,利用中括号,&&和||实现。
#!/bin/bash # Program: # This programs shows the user's choice # History: # 2016/07/04 Yzhang 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 your choice is" && exit 0
其中的-o(或)连接两个判断。
Shellscript的默认变量($0, $1, …)
重新启动系统注册表文件的功能,使用如下命令
file /etc/init.d/syslog 使用file来查询后,系统告知这个文件是个bash的可执行script、
/etc/init.d/syslog restart
上面中,restart是重新启动之意,可以重新启动/etc/init.d/syslog这个程序。
script针对参数设置好的一些变量名称如下:
/path/to/scriptname opt1 opt2 opt3 opt4
$0 $1 $2 $3 $4
执行的脚本名为$0这个变量,第一个参数接的就是$1。在script中善用$1,就可以简单执行某些命令的功能。此外,还有一些较为特殊的变量可以在script内使用来调用这些参数。
² $#:代表后接的参数“个数”
² $@:代表”$1” “$2” “$3” “$4”之意,每个变量是独立的(用双引号括起来)
² $*:代表“”$1c$2c$3c$4“”,其中c为分割字符,默认为空格键,所以本例代表““$1 $2 $3 $4””之意。
其中,$@与$*基本上还是有所不同。不过,一般情况下可以直接记忆$@即可。举例:假设执行一个可以携带参数的script,执行该脚本后屏幕显示如下数据:
² 程序的文件名
² 共有几个参数
² 若参数的个数小于2则告知用户参数太少
² 全部的参数内容
² 第一个参数
² 第二个参数
#!/bin/bash # Program: # Program shows the script name, parameters... # History: # 2016/07/04 Yzhang First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH echo "The script name is ===> $0" echo "Total paramter number is ===> $#" [ "$#" -lt 2 ] && echo "The number of parameter is less than 2. Stop here." && exit 0 echo "Your whole parameter is ===> '$@'" echo "The 1st parameter ===> $1" echo "The 2st parameter ===> $2"
脚本执行如下:
shsh07.sh theone haha quot
shift:造成参数变量号码偏移
改上面程序如下:
#!/bin/bash # Program: # Program shows the script name, parameters... # History: # 2016/07/04 Yzhang First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH echo "Total paramter number is ===> $#" echo "Your whole parameter number is ===> '$@'" shift #进行第一次“一个变量的shift” echo "Total paramter number is ===> $#" echo "Your whole parameter number is ===> '$@'" shift 3 #进行第二次“三个变量的shift” echo "Total paramter number is ===> $#" echo "Your whole parameter is ===> '$@'"
程序执行如下:
shsh07.sh one two three four five six
shift会移动变量,而且shift后面可以接数字,代表拿掉最前面的几个参数。执行结果,第一次shift后,显示情况是twothree four fix six,第二次拿掉三个后,显示为five six。
条件判断式
利用if…then
这个if…then是最常见的条件判断式。简单说,就是当符合某个条件判断的时候,就进行某些工作。If…then的判断还有多层次的情况。分别如下:
单层、简单条件判断式
如果只有一个判断式要进行,则可以简单这样:
if [条件判断式]; then
当条件判断成立时,可以进行的命令功能内容
fi 结束if之意。
其中条件判断的方法与上面介绍的相同。当有多个判断时,多个条件写入一个中括号内,也可以多个中括号,括号之间则以&&或||来隔开,意义:
&&代表AND
||代表or
如[ “$yn” == ”Y” –o “$yn” == ”y”] 可以写成 [ “$yn == ”Y”]|| [ “$yn” == ”y”]
使用条件实现程序如下:
#!/bin/bash # Program: # This programs shows the user's choice # History: # 2016/07/04 Yzhang 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" exit 0 fi if [ "$yn" == "N" ] ||[ "$yn" == "n" ]; then echo "Oh interrupt" exit 0 fi echo "I don't know what your choice is" && exit 0
多重、复杂条件判断式
在一个程序中,如果使用多重条件判断,可以使用如下语法:
#一个条件判断
if [ 条件判断式 ]; then
当条件判断式成立时,可以进行的命令工作内容;
else
当条件判断式不成立时,可以进行的命令工作内容;
fi
#多个条件判断(if … elif …else)分多种不同情况执行
if [条件判断式一 ]; then
当条件判断式一成立时,可以进行的命令工作内容
elif [ 条件判断式二 ] ; then
当条件判断式二成立时,可以进行的命令工作内容
else
当条件判断式一与二均不成立时,可以进行的命令工作内容;
fi
注意:elif也是个判断式,因此出现elif后面都接then来处理。但是else已经是最后的没有成立的结果了,所以else后面并没有then。
#!/bin/bash # Program: # This programs shows the user's choice # History: # 2016/07/04 Yzhang 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" elif [ "$yn" == "N" ] ||[ "$yn" == "n" ]; then echo "Oh interrupt" else echo "I don't know what your choice is" && exit 0 fi
上面程序变得简单,且依序判断,可以避免掉重复判断的状况,且容易设计程序。
如果不希望用户由键盘输入额外的数据时,可以使用上一节提到的参数功能($1)让用户在执行命令时就将参数代进去。现想让用户输入hello这个关键字,利用参数的方法可以这样依序设计:
1) 判断$1是否为hello,如果是的话,显示“hello”
2) 如果没有加任何参数,就提示用户必须要使用的参数;
3) 而如果加入的参数不是hello,就提醒用户仅能使用hello为参数。
整个程序的编写可以如下:
#!/bin/bash # Program: # Check $1 is equal to "hello" # History: # 2016/07/04 Yzhang First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH if [ "$1" == "hello" ]; then echo "hello" elif [ "$1" == "" ]; then echo "Your must input parameters, ex> {$0 someword}" else echo "The only parameter is ‘hello', ex> {$0 hello}" fi
netstat命令,可以查询到目前主机打开的网络服务端口(service ports),可以使用netstat –tuln来取得目前主机有启动的服务。
IP地址为127.0.0.1是针对本机开放,而0.0.0.0则代表对整个Internet开发。每个端口(port)都有其特定的网络服务,常见的port现骨干网络服务关系如下:
80:www
22:ssh
21:ftp
25:mail
111:RPC(远程过程调用)
631:CUPS(打印服务功能)
编写脚本,实现检测常见的端口(21,22,80)服务开启情况。具体如下:
#!/bin/bash # Program: # Using netstat and grep to detect www,ssh,ftp and mail services. # History: # 2016/07/04 Yzhang First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH # 1. 先做一些告知的操作而已~ echo "Now, I will detect your Linux server's services" 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" fi testing=$(netstat -tuln | grep ":22") if [ "$testing" != "" ]; then echo "ssh is running" fi testing=$(netstat -tunl | grep ":21") if [ "$testing" != "" ]; then echo "ftp is running" fi
中国当兵时公民应尽的义务,不过,在当兵的是否总想退伍,可以编写脚本计算退伍时间.
1) 想让用户输入退伍时间
2) 在由现在日期对比退伍日期
计算所需天数。#!/bin/bash # Program: # You input demobilization date, I calculate how many days before you demobilize. # History: # 2016/07/04 Yzhang First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/urs/local/sbin:~/bin export PATH # 1 告知用户程序的用途,并且告知应该如何输入日期格式 echo "This program will try to calculate :" echo "How many days before your demobilization date..." read -p "Please input your demobolization date (YYYYMMDD ex>20160704): " date2 # 2 利用正则表达式测试一下这个输入内容是否正确 date_d=$(echo $date2 | grep '[0-9]\{8\}') if [ "$date_d" == "" ]; then echo "input wrong" 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 need demobilization before $((-1*$date_d)) ageo" else declare -i date_h=$(($(($date_total_s-$date_d*60*60*24))/60/60)) echo "you will demobilize after $date_d days and $date_h hours." fi
利用case…esac判断
case是针对两个变量,多种情况进行的判断,其语法如下:
case $环境名称 in < == 关键字符为case,还有变量前有$
“第一个变量内容”) < == 每个变量内容建议用双引号括起来,关键字则为小括号)
程序段
;; < == 每个类型结尾使用两个连续的分号来处理
“第二个变量内容“”)
程序段
;;
*) < == 最后一个变量内容都会用*来代表所有其他值
不包含第一个变量内容与第二个变量内容的其他程序程序执行段
exit1
;;
esac
注意:case语法中,是以case为开头的,而其结束为esac(反写)。此外,对每个变量内容的程度段最后都需要两个分号(;;)来代表该程序段落的结束。至于这个变量内容的最后使用*,是因为如果用户输入第一个或第二个变量内容时,可以告知用户相关信息。
举例如下:
#!/bin/bash # Program: # Show "Hello" from $1... by using case ... esac # History: # 2016/07/04 Yzhang 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
上面的程序编写很简单明了,系统的很多服务的启动script都是使用这种写法的。例如/etc/init.d中的syslog服务,重启这个服务使用下列命令:/etc/init.d/syslog restart,其中重点是restart,可以使用less/etc/init.d/syslog来查阅,其中就用到了case的语法,并且规定某些既定的变量内容,可以直接执行/etc/init.d/syslog,此script就会告知后续变量可以使用情况。
一般来说,使用“case $变量in”这个语法中,当中的那个”$变量”大致有两种取得的方式:
² 直接执行式:如上面的就直接使用$1这个变量的内容,这也是在/etc/init.d目录下大多数程序的设计方式。
² 交互式:通过read这个命令来让用户输入变量的内容。举例如下:
#!/bin/bash # Program: # This script only accept the flowing parameter: one two or three. # History: # 2016/07/04 Yzhang 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 $0 {one | two | three}" ;; esac
上面程序使用的是非交互式,要使用交互式,则把其中#取消,把下面的case $1 in进行注释,即可实现。
利用function功能
什么是函数(function)功能?简单地说,其实,函数就是可以在shell script当中做出一个类似自定义命令的东西,最大的功能是,可以简化很多程序代码。Function的语法如下:
function fname()
{
程序段
}
fname是自定义的执行命令名称,程序段是要执行的内容。注意:因为shell script的执行方式是由上而下、由左而右,因此在shell script当中的function的设置一定要在程序的最前面,这样才能够在执行时被找到可用的程序段。下面是程序的编写:
#!/bin/bash # Program: # This script only accept the flowing parameter: one two or three. # History: # 2016/07/04 Yzhang 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 $0 {one | two | three}" ;; esac
上面的程序执行中,在case的one,two,three情况下,调用printit函数。
另外,function也是拥有内置变量的。它的内置变量与shell script很类似,函数名称表示$0,而后续接的变量也是以$1,$2…来替代的。
#!/bin/bash # Program: # This script only accept the flowing parameter: one two or three. # History: # 2016/07/04 Yzhang 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 $1 " #这个$1必须要参数下面命令的执行 } echo "This program will print your selection!" case $1 in "one") printit 1 #请注意,printit命令后面还有接参数 ;; "two") printit 2 ;; "three") printit 3 ;; *) echo "Usage $0 {one | two | three}" ;; esac
循环(loop)
循环可以不断地执行某个程序段落,直到用户设置的条件达成为止。其重点是条件的完成是什么。除了这种依据判断式达成与否的不定循环之外,还有另外一种已经固定要跑多少次的循环,可称为固定循环状态。
whiledo done, until do done(不定循环)
一般来说,补丁循环最常见的状态就是下面两种:
while [condition] < === 中括号内的状态就是判断式
do < === 循环的开始
程序段落
done < === 循环的结束
这种循环,当condition条件成立时,就进行循环,直到condition的条件不成立才停止。
不定循环
until [condition]
do
程序段
done
这种方式与while相反,当condition条件成立时,就终止循环,否则就持续进行循环的程序段。
while循环
#!/bin/bash # Program: # This script only accept the flowing parameter: one two or three. # History: # 2016/07/04 Yzhang First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH while [ "$yn" != "yes" -a "$yn" != "YES" ] do read -p "Please input yes/YES to stop this program: " yn done echo "OK! you input the correct answer."
until循环
#!/bin/bash # Program: # This script only accept the flowing parameter: one two or three. # History: # 2016/07/04 Yzhang First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH until [ "$yn" == "yes" -o "$yn" == "YES" ] do read -p "Please input yes/YES to stop this program: " yn done echo "OK! you input the correct answer."
计算1+2+…+100
#!/bin/bash # Program: # This script only accept the flowing parameter: one two or three. # History: # 2016/07/04 Yzhang 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"
for…do…done(固定循环)
相对于while,until的循环方式是必须要“符合某个条件”的状态,for这种语法则是“已经知道要进行几次循环”的状态。语法如下:
for var in con1 con2 con3…
do
程序段
done
#!/bin/bash # Program: # This script only accept the flowing parameter: one two or three. # History: # 2016/07/04 Yzhang 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 "Three are ${animal}s..." done
利用循环处理获取/etc/passwd并使用循环处理的方法。
#!/bin/bash # Program: # This script only accept the flowing parameter: one two or three. # History: # 2016/07/04 Yzhang First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH users=$(cut -d ':' -f1 /etc/passwd) for username in $users do id $usrname echo $username done
使用ping命令,判断网络状态。进行网络状态的实际检测时,要检测的域是本机所在的192.168.1.1~192.168.1.100,由于有100台主机,可以利用循环来实现,具体如下:
#!/bin/bash # Program: # This script only accept the flowing parameter: one two or three. # History: # 2016/07/04 Yzhang First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH network="192.168.1" for sitenu in $(seq 1 100) do ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0 || result=1 if [ "$result" == 0 ]; then echo "Server ${network}.${sitenu} is UP." else echo "Server ${network}.${sitenu} is Down." fi done
实现判断式上循环功能。让用户输入某个目录文件名,然后找出某目录内的文件名的权限,其实现如下:
#!/bin/bash # Program: # This script only accept the flowing parameter: one two or three. # History: # 2016/07/04 Yzhang 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" == "" -o ! -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 -e "$dir/$filename" && perm="$perm executable" echo "The file $dir/$filename's permission is $perm" done
for…do…done的数值处理
for ((初始值; 限制值; 执行步长))
do
程序段
done
这种语法适合于数值方式的运算当中,在for后面的括号内的三串内容意义为:
² 初始值:某个变量在循环当中的初始值,直接以类似i=1设置好;
² 限制值:当变量的值在这个限制值的范围内,就继续进行循环,例如,i<=100;
² 执行步长:没做一次循环时变量的变化量。如i=i+1。
#!/bin/bash # Program: # This script only accept the flowing parameter: one two or three. # History: # 2016/07/04 Yzhang First release PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATH read -p "Please input a number, I will count for 1+2+...+your_input: " nu s=0 for((i=1; i<=$nu; i++)) do s=$(($s+$i)) done echo "The result of '1+2+3+...+$nu' is ====> $s"
shellscript的追踪与调试
script在执行之前,最怕的就是出现语法错误问题。可以使用bash相关的参数进行判断语法问题。
sh [-nvx] scripts.sh
参数:
-n:不要执行script,仅查询语法的问题;
-v:在执行script前,先将script的内容输出到屏幕上
-x:将使用到的script内容显示到屏幕上,这是很有用的信息。
测试是否存在语法问题,可以使用如下命令:
sh –n sh16.sh
将sh15.sh的执行过程全部列出来
sh –x sh15.sh
执行上述命令后,输出信息中,在加号后面的数据其实都是命令串,由sh –x的方式来将命令执行过程也显示出来,如此用户可以判断程序代码执行到哪一段时会出现相关的信息。
重点
² shell script是利用shell的功能所写的一个程序(program),这个程序使用纯文本文件,将一些shell的语法与命令(含外部命令)写在里面,搭配正则表达式、管道命令与数据流重定向等功能,以达到我们所想要的处理目的。
² shell script用在系统管理上面是很好的一项工具,但是用在处理大量数值运算上就不够好了,因为shell script的速度较慢,且使用的CPU资源较多,造成主机资源的分配不良。
² 在shell script的文件中,命令是从上而下、从左而右地分析与执行。
² shell script的执行至少需要有r的权限,若需要直接命令执行,则需要拥有r和x的权限。
² 在良好的编程习惯中,第一行要声明shell(#!/bin/bash),第二行以后则声明程序用途、版本、作者等。
² 对交互式脚本可以read命令达成
² 要创建每次执行脚本都有不同结果的数据,可使用date命令利用日期达成。
² script的执行若以source来执行时,代表在父进程的bash内执行之意。
² 若需要进行判断式,可以使用test或中括号([])来处理。
² 在script内,$0, $1, $2,…, $@是有特殊意义的。
² 条件判断式可使用if…then来判断,若是固定变量内容的情况下,可使用case $var in easc来处理。
² 循环主要分为不定循环(while, until)以及固定循环(for),配合do,done来达成所需任务。
² 可以使用sh –x script.sh来进行程序的调试。