shell编程初步学习

时间:2022-06-01 19:37:09

第十三章 总结

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)的权限,然后:

绝对路径:使用/home/dmtsai/shell.sh来执行命令

相对路径:假设工作目录在/home/dmtsai/,则使用./shell.sh来执行

变量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来进行程序的调试。


参考文献: 鸟哥的Linux私房菜