1.了解shell scripts
1.1 为什么要学习shell script?
自动化管理
我们管理主机不是一件简单的事情:查询登录档,追踪流量,监控用户使用主机状态,主机各项硬设状态,主机软件更新查询等等。这些工作其实可以用shell scripts自动完成。
追踪与管理系统的重要工作
我们开机自启的接口是在/etc/init.d目录下,目录下所有文件都是scripts,开机过程、参数设定也是。
简单入侵检测
当系统有异状时,一般这些异状都记录在"系统注册表"里,我们可以在固定的时间段内主动去分析"系统注册表"。
连续指令一体化
script最简单的功能就是汇集一些下达的连续指令,将它写入script中。
1.5 简单的数据处理
就如前一章正规表示法的awk程序说明,可以发现awk用来处理数据。撰写方便,速度又快。
1.6 跨平台支持
几乎所有的Unix Like上面都可以跑shell scripts的。
注意:shell scripts用的是外部的指令与bash shell的一些默认工具,经常去调用外部的函数库,所以处理数据的速度上是不太够的。shell scripts用在系统管理上面是很好用的一项工具,但是在处理大量数值运算上就不好用了。
1.2 scripts的撰写
shell scripts的注意事项:
1.指令的执行是从上而下、从左到右的分析与执行;
2.指令、选项与参数间的多个空白都会被忽略掉;
3.空白行也会被忽略掉,并且tab键的空白也同样视为空格键;
4.如果读取到一个"Enter"符号(CR),就尝试开始执行该行或者该串命令;
5.如果一行的内容太多,可以使用"\Enter"来延伸到下一行;
6."#" 可以作为批注;
假设写的程序文件名是shell.sh,那么如何执行这个文件?
直接下达指令:shell.sh文件必须要具备可读与可执行的权限;
以bash程序来执行:通过"bash shell.sh"或"sh shell.sh"执行;
shell scripts 的编写
[~]$ vim hello.sh
#!/bin/bash
# Program:
# This program shows hello world in your screen
# History:
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e 'hello world! \a \n'
exit 0
解释:
第一行: #!/bin/bash在声明scripts使用的shll名称;
因为我们使用的是bash,所以必须要以"#!/bin/bash"来声明文件内的语法使用bash的语法。
程序内容说明:
建议一定要养成说明的习惯:1.标注内容与功能;2.版本信息;3.作者与联络方式;4.建档日期等等。
主要环境变量的声明。
主要程序部分
返回执行结果:
一个指令的执行成功与否,可以使用$?这个变量来观察。那么我们也可以利用"exit"指令让程序中断,并且回传一个数值给系统。"exit 0"表示离开script并回传一个0给系统,所以在执行完script后,可以使用"echo $?"来得到0的值。
2.简单的shell scripts练习
2.1 简单示例
对谈式脚步:变量内容由用户决定
[~]$ vim showname.sh
#!/bin/bash
# Program:
# User inputs his first name and last name. Program shows his full name.
read -p "Please input your first name: " firstname
read -p "Please input your last name: " lastname
echo -e "\nYour full name is: " ${firstname} ${lastname}
随日期变化,利用date进行文件的建立
[~]$ vim create_3_file.sh
#!/bin/bash
# Program:
# Program creates three files,which named by user's input and date command.
# 1.让使用者输入文件名,并取得fileuser这个变量
echo -e "I will use 'touch' command to create three 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} ${file2} ${file3}"
数值运算:简单的加减乘除
[~]$ vim multiplying.sh
#!/bin/bash
# Program:
# User inputs 2 integer numbers:program will cross these two numbers.
echo -e "You SHOULD input 2 numbers,i will multiplying them! \n"
read -p "first number:" firstnum
read -p "second numbser:" secondnum
total=$((${firstnum}*${secondnum}))
echo -e "\n The result of ${firstnum} x ${secondnum} is ==> ${total}"
在数值运算上,可以使用"declare -i total={secondnum}"实现,也可以使用上面的方式进行:var=$((运算内容))。
数值运算:通过bc计算pi
[~]$ vim cal_pi.sh
#!/bin/bash
# Program:
# User input a scale number to calculate pi number
echo -e "This program will calculate pi value.\n"
echo -e "You should input a float number to calculate pi value.\n"
read -p "The scale number(10-10000)?" checking
num=${checking:-"10"} # 开始判断是否有输入数值
echo -e "Starting calculate pi value.Be patient."
time echo "scale=${num};4*a(1)" | bc -lq
上面部分4*a(1)是bc提供的计算pi的函数,scale就是表示bc计算的pi有几位小数点的意思。
2.2script的执行方式差异(source,sh script,./script)
利用直接执行的方式来执行script
我们前面使用bash(sh)执行脚步时,script是在子程序的bash内执行的。重点是"当子程序完成后,在子程序内的各项变量或动作将会结束而不会传回到父程序中"。
例如上面的showname.sh,设定好的变量在退出showname.sh后,使用"echo ${firstname}"是无效的。因此你的firstname变量其实是在子程序的bash内执行的。当showname.sh执行完后,子程序bash内的所有数据会被移除。
利用source执行脚本:在父程序中执行
使用source执行的化,在退出showname.sh后,使用"echo ${firstname}"还是会得到数据的!
3.判断式的使用
3.1 利用test指令的测试功能
看一个示例:
[~]$ test -e showname.sh
## 什么都没显示,但是可以通过$?、&&、||来展现效果!
[~]$ test -e showname.sh && echo "exist" || echo "not exist"
功能简介:
1.关于某个档名的"文件类型"判断:
-e :该"档名"是否存在?(常用)
-f :该"档名"是否存在切为文件(file)?(常用)
-d :该"文件名"是否存在且为目录(directory)?(常用)
-b :该"档名"是否存在且为一个block device装置?
-c :该"档名"是否存在且为一个character device装置?
-S :该"档名"是否存在且为一个socket文件?
-p :该"档名"是否存在且为一个FIFO文件?
-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 :判断file1和file2是否为同一个文件。主要是判断是否均指向同一个inode;
4.关于两个整数之间的判定,例如test n1 -eq n2
-eq :两数值是否相等;
-ne :两数值是否不想等;
-gt :n1大于n2?
-lt :n1小于n2?
-ge :n1大于等于n2?
-le :n1小于等于n2?
5.判断字符串的数据
test -z str :判断字符串是否为空字符串;
test -n str :判断字符串是否为非空字符串;
test str1 == str2 :判断str1是否等于str2;
test str1 != str2 :判断str1是否不等于str2;
6.多重条件判定,例如:test -r filename -a -x filename
-a :相当于&&,file同时具有r和x权限时为true;
-o :相当于||,file具有r或x权限时为true;
! :反向,如test ! -x file,表示当file不具有x权限是为true;
3.2 判断符号[]的使用
假设我们想判断一个${HOME}是否为空可以这样做:
[~]$ [ -z "${HOME}" ] ; echo $?
需要注意的地方;
1.在中括号[]内的每个部分都需要有空格键来分隔;
2.在中括号内的变量,最好都以双引号括起来;
3.在中括号内的常量,最好都以单引号括起来;
3.3 Shell script的默认变量(1.....)
在我们执行"/etc/inti.d/network restart"时,就可以重启网络服务了。那么script是怎样获取我们指定的命令呢?
其实,script针对参数已经有设定好的一些变量名称了,对应如下:
/path opt1 opt2 opt3
$0 $1 $2 $3
$# :表示后面接参数的"个数",以上面为例值为"3"。
$@ :表示"$1" "$2" "$3",每个变量是独立的;
$* :表示"$1[c]$2[c]$3",其中[c]为分隔符,默认为空格键;
shift:参数变量号码偏移
#!/bin/bash
...
shift n ##n为数字
...
执行脚本中写入shift n后,表示拿掉最前面的n个数。
4.条件判断
4.1 利用if...then
单层、简单条件判断
if [ 条件判断 ] ; then
...................
fi
多重、复杂条件判断
if [ 条件判断 ] ; then
.......................
else
.......................
fi
或者
if [ 条件一 ] ; then
.......
elif [ 条件二 ] ;then
.......
else
.......
fi
下面练习一个示例:假设我们要检测端口号为:22、21、25、80的是否开启。
[~]$ vim netstat.sh
#!/bin/bash
#Program:
# Using netstat and grep to detect WWW,SSH,FTP,MAIL services.
testfile=netstat_check.txt
netstat -tuln > ${testfile}
str=$(grep ':80' ${testfile})
if [ "${str}"!="" ] ; then
echo "WWW is running"
fi
str=$(grep ':21' ${testfile})
if [ "${str}"!="" ] ; then
echo "FTP is running"
fi
str=$(grep ':22' ${testfile})
if [ "${str}"!="" ] ; then
echo "SSH is running"
fi
str=$(grep ':25' ${testfile})
if [ "${str}"!="" ] ; then
echo "Mail is running"
fi
4.2 case...esac的使用
case $变量名称 in
"第一个变量内容")
程序段
;;
"第二个变量内容")
程序段
;;
*)
不包含前面条件所执行的程序段
exit 1
;;
esac
接下来看一个示例:
[~]$ vim hello-2.sh
#!/bin/bash
# Program:
# Show "hello" from $1
case ${1} in
"hello")
echo '你好!'
;;
"")
echo '请输入字符!'
;;
*)
echo ${0} '{的参数必须是hello}'
exit 1
;;
esac
4.3利用function功能
语法:
fuction fname(){
代码段.....
}
需要注意的是:Shell Script的执行方式是由上而下,从左到右,所以,function的设定一定要在程序的最前面。
看下面一个示例:
[~]$ vim show123.sh
#!/bin/bash
# Program:
# Use function to repeat information.
function printit(){
echo -n "Your choice is" # 加上 -n 可以不断行继续在同一行显示
}
echo 'This program will print your selecton.'
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
另外,function也拥有内建的变量,和shell script类似。函数名称代表1,$2.....,看看下面一个例子:
[~]$ vim show123-2.sh
#/bin/bash
# Program:
# Use funciton to repeat information.
function printit(){
echo "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
5.循环(loop)
5.1 while do done,until do done(不定循环)
一般来说,不定循环就是以下两种语法:
while [ condition ] ## 括号内是条件,达到条件继续循环
do
程序段落
done
或者
until [ condition ] ## 括号内是条件,达到条件时终止循环
do
程序段落
done
假设我们要让使用者输入yes/YES才结束执行,否则就一致进行告知用户输入字符串。
[~]$ vim yes_to_stop.sh
#!/bin/bash
# Program:
# Repeat question until user input correct answer.
while [ "${str}" != "yes" ] && [ "${str}" != "YES" ]
do
read -p 'Please input yes to stop looping! ' str
done
echo 'program is shutdown!'
5.2 for...do...don(固定循环)
语法:
for var in con1 con2 con3 ...
do
程序段
done
假设有三个动物:狗、猫、猪,我们想每一行都输出她们怎么做?
[~]$ vim show_animals.sh
#!/bin/bash
# Program:
# Using for ... loop to print 3 animals.
for animal in dog cat pig
do
echo "this is a" ${animal}
done
再假设局域网有192.168.1.1~192.168.1.100,100台主机,我们想要进行网络检测是否可以连接:
[~]$ vim pingip.sh
#!/bin/bash
# Program:
# Use ping command to check the network's PC state
network=192.168.4
for site in $(seq 1 100)
do
ping -c 1 -W 1 ${network}.${site} &> /dev/null && result=0 || result=1
if [ ${result} == 0 ] ; then
echo ${network}.${site} 'UP'
else
echo ${network}.${site} 'DOWN'
fi
done
5.3 for...do...done的数值处理
for (( 初始值; 限制值; 执行步骤 ))
do
程序段
done
5.4 搭配随机数与数组
假设我们要写个脚本来决定你们团队今天中午吃什么菜怎么做?
[~]$ vim lunch.sh
eat[1]="汉堡"
eat[2]="鸡翅"
eat[3]="可乐"
eat[4]="冰糕"
eat[5]="烤肉"
eat[6]="火锅"
eat[7]="便当"
eat[8]="爆米花"
eat[9]="薯条"
eat[10]="雪花"
eatnum=10
eated=0
while [ "${eated}" -lt 3 ]
do
check=$(( ${RANDOM} * ${eatnum} / 32767 + 1 ))
mycheck=0
if [ ${eated} -ge 1 ] ; then
for i in $(seq 1 ${eated})
do
if [ ${eatedcon[$i]} == ${check} ] ; then
mycheck=1
fi
done
fi
if [ ${mycheck} == 0 ] ; then
echo 'you may eat' ${eat[${check}]}
eated=$((${eated}+1))
eatedcon[${eated}]=${check}
fi
done
6.shell script 的追踪与debug
scripts执行前,如何debug呢?我们就以bash相关参数入手:
[~]$ sh [ -nvx ] scripts.sh
选项与参数:
-n :不要执行script,仅查询语法问题;
-v :在执行script前,先将script的内容输出到屏幕上;
-x :将使用到的script内容显示到屏幕上;
7.重点
shell script是利用shell的功能所写的程序,这个程序使用纯文本文件,将一些shell的语法和指令写在里面,搭配正规表示法、管线命令与数据流重导向等功能;
shell script用在系统管理上面是很好的工具,但用在处理大量数值运算上,就不好用了!因为shell script 速度较慢,且使用CPU资源多;
shell script的执行至少要有r的权限,若下达指令,需要有r和x权限;
良好的程序编写习惯,第一行要有声明shell(#!/bin/bash),第二行则声明程序用途等等;
要建立每次执行脚本都有不同结果的数据,可使用date指令利用日期达成;
script的执行若以source来执行时,代表在父程序的bash内执行之意;
若要进行判断式,可用test或中括号 [] 处理;