shell脚本编程精讲
1. shell介绍
1.1. 常用操作系统默认shell
Linux是Bourne Again Shell (bash)
Solaris和FreeBSD是Bourne Shell (sh)
AIX是Korn Shell(ksh)
HP-UX缺省的是POSIX shell(sh)
1.2. shell脚本建立和执行
脚本开头(第一行)
一个规范的shell脚本的第一行会指出由哪个程序(解释器)来执行脚本中的内容,linux中一般为
#!/bin/bash或#!/bin/sh
sh和bash的区别:sh为bash的软链接,推荐标准写法:#!/bin/bash。
shell脚本执行的三种方法:
(1) bash script-name或sh script-name
(2) path/script-name或./script-name
(3) source script-name或 . script-name
说明: 第一种方法是当脚本本身没有可执行权限时常用的方法。推荐用bash执行。
第二种方法是需先将脚本文件的权限改为可执行(加x位,chmod u+x script-name或chmod 755 script-name),然后通过脚本路径就可以执行了。
第三种方法所执行的脚本中(如sun.sh)的语句会作为父shell脚本(如father.sh)进程的一部分执行,因此可将sun.sh自身脚本中的变量函数等返回值传递到当前父shell脚本father.sh中使用。
[root@test ~]# echo "userdir=`pwd`">test.sh
[root@test ~]# cat test.sh
userdir=/root
[root@test ~]# sh test.sh
[root@test ~]# echo $userdir #此处为空值
[root@test ~]#
[root@test ~]# source test.sh
[root@test ~]# echo $userdir
/root
通过source或.点号执行过的脚本,在脚本执行结束后的变量(包括函数)值在当前shell中依然存在,而sh和bash则不行。
1.3. shell脚本开发基本规范及习惯
(1) 开头指定脚本解释器
#!/bin/bash或#!/bin/bash
(2) 开头加版本版权等信息
#Date:
#Author:
#Mail:
#Funtion:
#Version:
提示:可配置vim编辑器后自动加上以上信息
(3) 脚本中不用中文注释
(4) 脚本以.sh为扩展名
(5) 代码书写优秀习惯
成对的内容一次写出来
[]中括号两端要有空格,书写时即可留出空格[ ]
流程控制一次书写完,再添加内容。
2. shell变量基础及深入
2.1. 变量类型
两类:环境变量(全局变量)和局部变量
2.2. 环境变量
环境变量定义Shell运行的环境。可用于所有子进程,这包括编辑器、脚本和应用。
环境变量用于所有用户进程(经常称为子进程)。登录进程称为父进程。s h e l l中执行的用户进程均称为子进程。不像本地变量(只用于现在的s h e l l)环境变量可用于所有子进程,这包括编辑器、脚本和应用。
环境变量可以在命令行中设置,但用户注销时这些值将丢失,因此最好在. profile文件中定义。系统管理员可能在/etc/profile文件中已经设置了一些环境变量。将之放入profile文件意味着每次登录时这些值 都将被初始化。
传统上,所有环境变量均为大写。环境变量应用于用户进程前,必须用export命令导出。
2.3. 自定义环境变量
如果想设置环境变量,就要在给变量赋值之后或设置变量时使用export命令,带-x选项的declare内置命令也可完成同样的功能。
格式: (1) export 变量名=value
(2) 变量名=value; export 变量名
(3) declare -x 变量名=value
2.4. 显示或取消环境变量
#echo显示
[root@test ~]# echo $HOME
/root
[root@test ~]# echo $UID
0
[root@test ~]# echo $PWD
/root
[root@test ~]# echo $SHELL
/bin/bash
[root@test ~]# echo $USER
root
#env或set显示系统默认的环境变量
[root@test ~]# env
HOSTNAME=test
TERM=vt100
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=192.168.65.1 50140 22
SSH_TTY=/dev/pts/3
USER=root
LS_COLORS=no=00:fi=00:di=01;34:ln=01;36:pi=40;33:so=01;35:bd=40;33;01:cd=40;33;01:or=01;05;37;41:mi=01;05;37;41:ex=01;32:*.cmd=01;32:*.exe=01;32:*.com=01;32:*.btm=01;32:*.bat=01;32:*.sh=01;32:*.csh=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.gz=01;31:*.bz2=01;31:*.bz=01;31:*.tz=01;31:*.rpm=01;31:*.cpio=01;31:*.jpg=01;35:*.gif=01;35:*.bmp=01;35:*.xbm=01;35:*.xpm=01;35:*.png=01;35:*.tif=01;35:
MAIL=/var/spool/mail/root
PATH=/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/mysql/bin:/root/bin
INPUTRC=/etc/inputrc
PWD=/root
LANG=en_US.UTF-8
SHLVL=1
HOME=/root
LOGNAME=root
CVS_RSH=ssh
SSH_CONNECTION=192.168.65.1 50140 192.168.65.151 22
LESSOPEN=|/usr/bin/lesspipe.sh %s
G_BROKEN_FILENAMES=1
_=/bin/env
[root@test ~]# set
BASH=/bin/bash
BASH_ARGC=()
BASH_ARGV=()
BASH_LINENO=()
BASH_SOURCE=()
BASH_VERSINFO=([0]="3" [1]="2" [2]="25" [3]="1" [4]="release" [5]="i686-redhat-linux-gnu")
BASH_VERSION='3.2.25(1)-rele
…省略…
使用unset取消本地变量和环境变量
[root@test ~]# echo $USER
root
[root@test ~]# unset USER
[root@test ~]# echo $USER
[root@test ~]#
2.5. 本地变量
本地变量在用户当前的Shell生存期的脚本中使用。
普通字符串变量的定义:
name=value
name='value'
name="value"
[root@test ~]# a=192.168.1.1
[root@test ~]# b='192.168.1.1'
[root@test ~]# c="192.168.1.1"
[root@test ~]# echo "a=$a"
a=192.168.1.1
[root@test ~]# echo "b=$b"
b=192.168.1.1
[root@test ~]# echo "c=${c}"
c=192.168.1.1
[root@test ~]# a=192.168.1.1-$a
[root@test ~]# b='192.168.1.1-$a'
[root@test ~]# c="192.168.1.1-$a"
[root@test ~]# echo "a=$a"
a=192.168.1.1-192.168.1.1
[root@test ~]# echo "b=$b"
b=192.168.1.1-$a
[root@test ~]# echo "c=${c}"
c=192.168.1.1-192.168.1.1-192.168.1.1
注意单引号、双引号、不加引号的区别。
(1)单引号: 将单引号内的内容原样输出,或者描述为单引号里面看到的是什么就会输出什么。
(2)双引号: 把双引号内的内容输出出来;如果内容中有命令、变量等,会先把变量、命令解析出结果,然后在输出最终内容来。
(3)不加引号: 不会将含有空格的字符串视为一个整体输出, 如果内容中有命令、变量等,会先把变量、命令解析出结果,然后再输出最终内容来。
如果字符串中带有空格等特殊字符,则不能完整的输出,需要改加双引号,一般连续的字符串,数字,路径等可以用。
自定义变量的建议:
(1) 纯数字(不带空格),定义可以不加引号
(2) 没特殊情况,字符串一般用双引号定义,特别是多个字符串中间有空格时。
(3) 变量内容需要原样输出,要用单引号。
变量命名规范:
(1) 命名要统一,使用全部大写字母,如APACHE_ERR_NUM,语义要清晰,能够正确表达变量内容的含义,过长单词可用前几个单词代替。多个单词连接可用"_"号连接,最好以${APACHE_ERR_NUM}或"${APACHE_ERR_NUM}"引用变量。
(2) 避免无意义字符或数字。
(3) 全局变量命名要用大写,使用时使用{}大括号括起来。
(4) 局部变量要以local方式进行声明(如local i),使之在函数作用域内有效,防止变量在函数中的命名与变量外部程序中变量重名造成程序异常。
(5) 变量合并:当某些变量或配置项要组合起来才有意义时,如文件路径和文件名称,建议要将组合的变量合并到一起赋值给一个新的变量,这样既方便之后的调用,也为以后进行修改提供了方便。
VERSION="2.2.22"
SOFTWARE_NAME="httpd"
SOFTWARE_FULLNAME="${SOFTWARE_NAME}-${VERSION}.tar.gz"
(6) 变量定义总结:多学习系统自带的/etc/init.dfunction函数库脚本定义思路。
2.6. shell特殊变量
(1) 位置变量
$0 当前执行的脚本名字
$n 当前脚本的第n个参数,n=1..9,如果n>9,用大括号括起来:${10}
$* 当前shell的所有参数 $1 $2 $3 …
$# 当前shell参数的总个数
$@ 程序的所有参数"$1" "$2" "$3" "…"
(2) 进程状态变量
$$ 获取当前shell的进程号(PID)
$! 执行上一个指令的PID
$? 获取执行上一个指令的返回值(0成功,非零为失败)
$_ 在此之前执行的命令或脚本的最后一个参数。
[root@test ~]# cat var.sh
echo '$0 :' $0
echo '$n :' '$1'=$1 '$2'=$2 '$3'=$3
echo '$* :' $*
echo '$# :' $#
echo '$$ :' $$
sleep 2 &
echo '----'
echo '$! :' $!
echo '$? :' $?
echo '$@ :' $@
echo '$_ :' $_
[root@test ~]# sh var.sh p1 p2 p3
$0 : var.sh
$n : $1=p1 $2=p2 $3=p3
$* : p1 p2 p3
$# : 3
$$ : 17124
----
$! : 17125
$? : 0
$@ : p1 p2 p3
$_ : p3
portmap使用实例
[root@test ~]# cat /etc/init.d/portmap
#! /bin/sh
#
# portmap Start/Stop RPC portmapper
#
# chkconfig: 345 13 87
# 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
# This is an interactive program, we need the current locale
[ -f /etc/profile.d/lang.sh ] && . /etc/profile.d/lang.sh
# We can't Japanese on normal console at boot time, so force LANG=C.
if [ "$LANG" = "ja" -o "$LANG" = "ja_JP.eucJP" ]; then
if [ "$TERM" = "linux" ] ; then
LANG=C
fi
fi
# Source function library.
. /etc/init.d/functions
# Get config.
if [ -f /etc/sysconfig/network ]; then
. /etc/sysconfig/network
else
echo $"Networking not configured - exiting"
exit 1
fi
prog="portmap"
# Check that networking is up.
if [ "$NETWORKING" = "no" ]; then
exit 0
fi
[ -f /sbin/portmap ] || exit 0
[ -f /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
RETVAL=0
start() {
echo -n $"Starting $prog: "
daemon portmap $PMAP_ARGS
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/portmap
return $RETVAL
}
stop() {
echo -n $"Stopping $prog: "
killproc portmap
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/portmap
return $RETVAL
}
restart() {
pmap_dump > /var/run/portmap.state
stop
start
pmap_set < /var/run/portmap.state
rm -f /var/run/portmap.state
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status portmap
;;
restart|reload)
restart
;;
condrestart)
[ -f /var/lock/subsys/portmap ] && restart || :
;;
*)
echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}"
exit 1
esac
exit $?
2.7. bash内部变量
有些内部命令在目录列表时是看不见的,它们由shell本身提供,常用的内部命令有:echo,eval,exec,export,readonly,read,shift,wait,exit和点(.)。
(1) echo arg
在屏幕上显示出由arg指定的字串。
(2) eval args
当Shell程序执行到eval语句时,Shell读入参数args,并将它们组合成一个新的命令,然后执行。
(3) exec 命令参数
当Shell执行到exec语句时,不会去创建新的子进程,而是转去执行指定的命令,当指定的命令执行完时,该进程(也就是最初的 Shell)就终止了,所以Shell程序中exec后面的语句将不再被执行。
(4) export 变量名=value
Shell可以用export把它的变量向下带入子Shell,从而让子进程继承父进程中的环境变量。但子Shell不能用export把它 的变量向上带入父Shell。
注意:不带任何变量名的export语句将显示出当前所有的export变量。
(5) readonly 变量名
将一个用户定义的Shell变量标识为不可变。不带任何参数的readonly命令将显示出所有只读的Shell变量。
(6) read 变量名表
从标准输入设备读字符串,传给指定变量。
(7) shift 语句
shift语句按如下方式重新命名所有的位置参数变量,即$2成为$1,$3成为$2…在程序中每使用一次shift语句,都使所有的位置参 数依次向左移动一个位置,并使位置参数$#减1,直到减到0为止。
2.8. 变量子串的常用操作
${#string} 返回$string的长度
${string:position} 在$string中,从位置$position开始提取子串
${string:position:length} 在$string中,从位置$position开始提取长度为$length的子串
${string#substring} 从变量$string的开头,删除最短匹配$substring的子串
${string##substring} 从变量$string的开头,删除最长匹配$substring的子串
${string%substring} 从变量$string的结尾,删除最短匹配$substring的子串
${string%%substring} 从变量$string的结尾,删除最长匹配$substring的子串
${string/substring/replacement} 使用$replacement,来代替第一个匹配的$substring
${string//substring/replacement} 使用$replacement,代替所有匹配的$substring
${string/#substring/replacement} 如果$string的前缀匹配$substring,那么就用$replacement来代替匹配到的$substring
${string/%substring/replacement} 如果$string的后缀匹配$substring,那么就用$replacement来代替匹配到的$substring
[root@test ~]# OLDBOY="I am oldboy"
[root@test ~]# echo ${OLDBOY}
I am oldboy
[root@test ~]# echo ${#OLDBOY} #显示长度
11
[root@test ~]# echo ${OLDBOY:2} #取第二个字符以后的内容
am oldboy
[root@test ~]# echo ${OLDBOY:2:2} #取第二个字符以后取两个字符的内容
am
[root@test ~]# echo ${OLDBOY#I am} #删除最短匹配的I am 字串
oldboy
[root@test ~]# echo ${OLDBOY#I am old}
boy
[root@test ~]# echo ${OLDBOY##I am old}
boy
[root@test ~]# echo ${OLDBOY%oldboy}
I am
[root@test ~]# echo ${OLDBOY%%boy}
I am old
[root@test ~]#
补充:
(1) 条件变量替换:
${value:-word}
当变量未定义或为空时,返回word的内容,否则返回变量的内容。
[root@test ~]# result=${test:-UNSET}
[root@test ~]# echo $result
UNSET
[root@test ~]# test=''
[root@test ~]# result=${test:-UNSET}
[root@test ~]# echo $result
UNSET
[root@test ~]# test='123'
[root@test ~]# result=${test:-UNSET}
[root@test ~]# echo $result
123
(2) ${value:=word}
与前者类似,只是若变量未定义或者值为空时,在返回word的值的同时将word赋值给value
(3) ${value:?message}
若变量以赋值的话,正常替换.否则将消息message送到标准错误输出(若此替换出现在Shell程序中,那么该程序将终止运行)
2.9. 变量的数值计算
数值计算常用命令:(()), let, expr, bc, $[]
(1) (())的用法(此法很常用)
执行简单的整数运算,只需将特定的算术表达式用"$(("和"))"括起。
[root@test ~]# ((a=1+2**3-4%3))
[root@test ~]# echo $a
8
[root@test ~]# a=$((1+2))
[root@test ~]# echo $a
3
[root@test ~]# echo $((1+2+3))
6
[root@test ~]# echo ((1+2+3))
-bash: syntax error near unexpected token `('
[root@test ~]#
[root@test ~]# echo $a
3
[root@test ~]# echo $((a+=1))
4
[root@test ~]# echo $((a++))
4
[root@test ~]# echo $((3>2))
1
[root@test ~]# echo $((3<2))
0
[root@test ~]# echo $((3==2))
0
[root@test ~]# echo $((3!=2))
1
提示:
1.**为幂运算: %为取模运算(就是除法当中取余数),加减乘除我就不细说了吧。
2.上面涉及到的参数必须为整数(整型)。不能是小数(符点数)或者字符串。bc命令可以进行浮点数运算,但一般较少用到,下文会提到。提醒下,你可以直接在shell脚本中使用上述命令进行计算。
3.echo $((a++))和echo $((a--)) 表示先输出a自身的值,然后在进行++ --的运算,echo $((++a)) 和echo $((--a))表示先进行++ --的运算,在输出a自身的值。
[root@test ~]# a=2
[root@test ~]# b=3
[root@test ~]# echo " a+b=$(($a+$b))"
a+b=5
(2) let用法
let 表达式
[root@test ~]# i=2
[root@test ~]# i=i+8
[root@test ~]# echo $i
i+8
[root@test ~]# i=2
[root@test ~]# let i=i+8
[root@test ~]# echo $i
10
提示:let i=i+8 等同于((i=i+8)),但后者效率更高。
(3) expr- evaluate expressions命令用法
[root@test ~]# expr 2 + 2
4
[root@test ~]# expr 2+2
2+2
[root@test ~]# expr 2*2
2*2
[root@test ~]# expr 2 * 2
expr: syntax error
[root@test ~]# expr 2 \* 2
4
[root@test ~]# expr 2\*2
2*2
[root@test ~]# expr 2 % 2
0
[root@test ~]# expr 2%2
2%2
提示:
运算符左右都有空格;
使用*时必须用反斜线屏蔽其特定含义。
#循环时自增
[root@test ~]# i=0
[root@test ~]# i=`expr $i + 1`
[root@test ~]# echo $i
1
#expr $[$a+$b]表达式
[root@test ~]# expr $[2+3]
5
[root@test ~]# expr $[2*3]
6
[root@test ~]# a=3
[root@test ~]# b=5
[root@test ~]# expr $[$a+$b]
8
[root@test ~]# expr $[$a*$b]
15
(4) bc命令用法
[root@test ~]# echo 3.5+5|bc
8.5
[root@test ~]# echo 3.5*5.2|bc
18.2
[root@test ~]# echo 3.52*5.21|bc
18.33
[root@test ~]# echo "3.52*5.21"|bc
18.33
[root@test ~]# echo "scale=1;3.52*5.21"|bc #精度只对除法有效
18.33
[root@test ~]# echo "13.52/5.21"|bc
2
[root@test ~]# echo "scale=1;13.52/5.21"|bc
2.5
[root@test ~]# echo "obase=2;2" | bc
10
[root@test ~]# echo "obase=16;20" | bc #10进制20转换成16进制
14
直接使用整数计算:
[root@test ~]# typeset -i a=1 b=3
[root@test ~]# a=a+b
[root@test ~]# echo $a
4
(5) $[]运算
[root@test ~]# echo $[2+3]
5
[root@test ~]# echo $[2*3]
6
Shell变量的输入
Read语法格式:
read [参数] [变量名]
常用参数:
-p prompt:设置提示信息
-t timeout 设置输入等待的时间,单位默认为秒。
[root@test ~]# read -p "please input two number:" a1 a2
please input two number:3 4
[root@test ~]# echo $a1 $a2
3 4
[root@test ~]#
[root@test ~]# read -t 5 -p "please input two number:" a1 a2 #5秒不输入则退出
please input two number:[root@test ~]#
3. 条件测试
3.1. 测试语句
3.1.1. 条件测试语法
语法说明:
格式1:test <测试表达式>
格式2:[ <测试表达式> ]
格式3:[[ <测试表达式> ]]
说明:
格式1和格式2是等价的
格式3为扩展的test命令,有网友推荐格式3,老男孩习惯使用格式2。
提示:
1. 在[[]]中可以使用通配符进行模式匹配。
2. &&、||、>、<等操作符可以应用于[[]]中,但不能应用于[]中。
3. 对整数进行关系运算,也可以使用shell的算术运算符(())。
语法举例:
格式1:test <测试表达式>
[root@test ~]# test -f file && echo true || echo false
false
[root@test ~]# touch file
[root@test ~]# test -f file && echo true || echo false
true
#测试!用法
[root@test ~]# test ! -f file && echo true || echo false
false
格式2:[ <测试表达式> ]
[root@test ~]# rm -f file
[root@test ~]# [ -f file ] && echo 1 || echo 0
0
[root@test ~]# touch file
[root@test ~]# [ -f file ] && echo 1 || echo 0
1
[root@test ~]# [ ! -f file ] && echo 1 || echo 0
0
格式3:[[ <测试表达式> ]]
[root@test ~]# [[ ! -f file ]] && echo 1 || echo 0
0
[root@test ~]# [[ -f file ]] && echo 1 || echo 0
1
[root@test ~]# [[ -f file && -f folder ]] && echo 1 || echo 0
0
[root@test ~]# ls -l file
-rw-r--r-- 1 root root 0 Sep 8 16:52 file
[root@test ~]# [[ -f file && -f file2 ]] && echo 1 || echo 0
0
[root@test ~]# [ -f file && -f file2 ] && echo 1 || echo 0
-bash: [: missing `]'
0
[root@test ~]# [ -f file -a -f file2 ] && echo 1 || echo 0
0
3.1.2. 文件测试操作符
常用文件测试操作符
文件测试操作符 说明
-d pathname 当pathname 存在并且是一个目录时返回真
-e pathname 当由pathname 指定的文件或目录存在时返回真
-f filename 当filename 存在并且是普通文件时返回真
-r pathname 当由pathname 指定的文件或目录存在并且可读时返回真
-s filename 当filename 存在并且文件大小大于0 时返回真
-w pathname 当由pathname 指定的文件或目录存在并且可写时返回真
-x pathname 当由pathname 指定的文件或目录存在并且可执行时返回真
file1 -nt file2 file1 比file2 新时返回真
file1 -ot file2 file1 比file2 旧时返回真
3.1.3. 字符串测试操作符
操作符 说明
-z string 字符串string 为空串(长度为0)时返回真
-n string 字符串string 为非空串时返回真
str1 = str2 字符串str1 和字符串str2 相等时返回真
str1 == str2 同 =
str1 != str2 字符串str1 和字符串str2 不相等时返回真
str1 < str2 按字典顺序排序,字符串str1 在字符串str2 之前
str1 > str2 按字典顺序排序,字符串str1 在字符串str2 之后
if[ "$a"="$b" ],其中$a最好用""括起来。当然最好的方法就是if[ "${a}"="${b}" ]
3.1.4. 整数二元比较操作符
在[]中使用的操作符 在(())和[[]]中使用的操作符 说明
-eq == 等于则返回真
-ne != 不等于,则返回真
-lt > 小于,则返回真
-le >= 小于等于,则返回真
-gt <= 大于,则返回真
-ge <= 大于等于,则返回真
经过实践,"="和"!="在[]中使用不需要转义,包含">"和"<"的符号在[]中使用需要转义,对于数字不转义的结果未必会报错,但是结果可能不会对。
if[[ "$a"<"$b" ]], if[ "$a"\<"$b" ]
if[[ "$a">"$b" ]], if[ "$a"\>"$b" ]
if[[ "$a"="$b" ]], if[ "$a"="$b" ]
举例:二元数字比较
[root@test ~]# [ 2>1 ] && echo 1 || echo 0
0
[root@test ~]# [ 2<1 ] && echo 1 || echo 0
0
[root@test ~]# [ 2 > 1 ] && echo 1 || echo 0
1
[root@test ~]# [ 2 < 1 ] && echo 1 || echo 0 #不报错,但结果不对
1
[root@test ~]# [ 2 \< 1 ] && echo 1 || echo 0
0
[root@test ~]# [ 2 = 1 ] && echo 1 || echo 0
0
[root@test ~]# [ 1 = 1 ] && echo 1 || echo 0
1
[root@test ~]# [[ 2 -gt 1 ]] && echo 1 || echo 0
1
[root@test ~]# [[ 2 -lt 1 ]] && echo 1 || echo 0
0
举例:二元字符比较
[root@test ~]# [ "a" > "bc" ] && echo 1 || echo 0
1
[root@test ~]# [ "a" < "bc" ] && echo 1 || echo 0
1
#上面条件不一样,但结果一样。虽然没报语法错误,显然结果不对。
[root@test ~]# [ "a" \> "bc" ] && echo 1 || echo 0
0
[root@test ~]# [ "a" \< "bc" ] && echo 1 || echo 0
1
#转义后结果正确了
3.1.5. 逻辑操作符
在[]中使用的操作符 在[[]]中使用的操作符 说明
-a && 逻辑与,操作符两边均为真,结果为真,否则为假。
-o || 逻辑或,操作符两边一边为真,结果为真,否则为假。
! ! 逻辑否,条件为假,结果为真。
3.2. 条件测试举例
[ -f "$file" ] && echo 1 || echo 0
if [ -f "$file" ] ; then echo 1; else echo 0; fi
#上面功能是等价的
#变量$file加了双引号,这是编程的好习惯,可以防止很多意外的发生。
3.2.1. 文件测试举例
[root@test ~]# file1=/etc/services ; file2=/etc/rc.local
[root@test ~]# echo $file1 $file2
/etc/services /etc/rc.local
单条件文件测试:
[root@test ~]# [ -f "$file1" ] && echo 1 || echo 0 #文件存在且是普通文件
1
[root@test ~]# [ -d "$file1" ] && echo 1 || echo 0 #文件是不是目录
0
[root@test ~]# [ -s "$file1" ] && echo 1 || echo 0 #文件大小是否为0
1
[root@test ~]# [ -e "$file1" ] && echo 1 || echo 0 #文件是否存在
1
#如果变量不加双引号,结果可能不正确
[root@test ~]# echo $file7
[root@test ~]# [ -f $file7 ] && echo 1 || echo 0 #file7不存在还返回1
1
[root@test ~]# [ -f "$file7" ] && echo 1 || echo 0
0
#文件换成实体,加不加引号都是正确的
[root@test ~]# [ -f /etc/service ] && echo 1 || echo 0
0
[root@test ~]# [ -f /etc/services ] && echo 1 || echo 0
1
[root@test ~]# [ -f "/etc/service" ] && echo 1 || echo 0
0
[root@test ~]# [ -f "/etc/services" ] && echo 1 || echo 0
1
[root@test ~]# more /etc/init.d/nfs
…省略…
# Source networking configuration.
[ -f /etc/sysconfig/network ] && . /etc/sysconfig/network
# Check for and source configuration file otherwise set defaults
[ -f /etc/sysconfig/nfs ] && . /etc/sysconfig/nfs
…省略…
多条件文件测试:
-a 和 && , -o 和 || 、非(!)
[root@test ~]# [ -f "$file1" -o -e "$file2" ] && echo 1 || echo 0
1
[root@test ~]# [ -f "$file1" -a -e "$file2" ] && echo 1 || echo 0
1
[root@test ~]# [ -f "$file1" -a -e "$file3" ] && echo 1 || echo 0
0
[root@test ~]# [ -f "$file1" && -e "$file3" ] && echo 1 || echo 0
-bash: [: missing `]'
0
#-a 和 -o 用于[]
#&& 和 || 用于[[]]
[root@test ~]# [ 3 -ne 3 ] || {
> echo "a"
> echo "b"
> echo "c"
> }
a
b
c
[root@test ~]# [ 3 -ne 3 ] || { echo "a"; echo "b"; echo "c"; }
a
b
c
3.2.2. 字符串测试举例
单条件字符串测试
[root@test ~]# [ -n "$str" ] && echo 1 || echo 0
0
[root@test ~]# [ -z "$str" ] && echo 1 || echo 0
1
[root@test ~]# [ -z "$file1" ] && echo 1 || echo 0
0
[root@test ~]# [ -n "$file1" ] && echo 1 || echo 0
1
多条件字符串测试(略)
3.2.3. 整数测试举例
[root@test ~]# a1=10; a2=13
[root@test ~]# echo $a1 $a2
10 13
[root@test ~]# [ $a1 -eq $a2 ] && echo 1 || echo 0
0
[root@test ~]# [ $a1 -gt $a2 ] && echo 1 || echo 0
0
[root@test ~]# [ $a1 -lt $a2 ] && echo 1 || echo 0
1
[root@test ~]# [ $a1 -le $a2 ] && echo 1 || echo 0
1
[root@test ~]# [ $a1 -ge $a2 ] && echo 1 || echo 0
0
[root@test ~]# [ $a1 -ne $a2 ] && echo 1 || echo 0
1
[root@test ~]# a=0001
[root@test ~]# b=10
[root@test ~]# [ "$a" -ne "$b" ] && echo 1 || echo 0
1
[root@test ~]# [ "$a" -gt "$b" ] && echo 1 || echo 0
0
[root@test ~]# [ "$a" -lt "$b" ] && echo 1 || echo 0
1
#直接通过运算符比较
[root@test ~]# [[ $a1 = $a2 ]] && echo 1 || echo 0
0
[root@test ~]# [[ $a1 == $a2 ]] && echo 1 || echo 0
0
[root@test ~]# (( $a1 > $a2 )) && echo 1 || echo 0
0
3.2.4. test命令测试的用法
[root@test ~]# echo $file1
/etc/services
[root@test ~]# test -z "$file1" || echo 0
0
[root@test ~]# test -z "$file3" || echo 0
[root@test ~]# test -z $a || echo 0
0
[root@test ~]# test -z $a $$ echo 1 || echo 0
-bash: test: too many arguments
0
[root@test ~]# test -z $a && echo 1 || echo 0
0
[root@test ~]# echo $a
0001
[root@test ~]# unset a
[root@test ~]# test -z $a && echo 1 || echo 0
1
[root@test ~]# test 1 -eq 1 && echo 1 || echo 0
1
[root@test ~]# test dd = ff && echo 1 || echo 0
0
[root@test ~]# test dd != ff && echo 1 || echo 0
1
3.2.5. 逻辑操作符举例
read m n
第一种写法:
if [ ${m} -eq 1 ] && [ ${n} -eq 2 ];then
echo "good"
else
echo "bad"
fi
第二种写法:
if [ ${m} -eq 1 -a ${n} -eq 2 ];then
echo "good"
else
echo "bad"
fi
第三种写法:
if [[ ${m} == 1 && ${n} == 2 ]];then
echo "good"
else
echo "bad"
fi
if [[ ${m} -eq 1 && ${n} -eq 2 ]];then
echo "good"
else
echo "bad"
fi
&& 与 -a 与逻辑符
[root@test ~]# read m n
1 2
[root@test ~]# echo $m $n
1 2
[root@test ~]# if [ ${m} -eq 1 ] && [ ${n} -eq 2 ];then
> echo "good"
> else
> echo "bad"
> fi
good
[root@test ~]# if [ ${m} -eq 1 -a ${n} -eq 2 ];then
> echo "good"
> else
> echo "bad"
> fi
good
[root@test ~]# if [[ ${m} == 1 && ${n} == 2 ]];then
> echo "good"
> else
> echo "bad"
> fi
good
[root@test ~]# if [[ ${m} -eq 1 && ${n} -eq 2 ]];then
> echo "good"
> else
> echo "bad"
> fi
good
4. 分支与循环结构
4.1. if条件
4.1.1. if条件语法
(1) 单分支结构
if [条件]
then
指令集
fi
或
if [条件];then
指令集
fi
提示:分号相当于命令换行。
特殊写法:if [ -f "$file1" ];then echo 1;fi
(2) 双分支结构
if 条件
then
指令集
else
指令集
fi
特殊写法:if [ -f "$file1" ];then echo 1 ;else echo 0;fi
(3) 多分支结构
if 条件
then
指令集
elif 条件
then
指令集
elif 条件
then
指令集
……
else
指令集
fi
4.1.2. if条件举例
(1) 单分支结构
[root@test shell]# cat if-single.sh
#!/bin/bash
if [ 10 -lt 12 ]
then
echo "Yes, 10 is less than 12"
fi
echo "1.--------"
if [ "10" -lt "12" ]
then
echo "Yes, 10 is less than 12"
fi
echo "2.--------"
if [[ 10 < 12 ]];then
#if [[ 10<12 ]];then #error
echo "Yes, 10 is less than 12"
fi
[root@test shell]# sh if-single.sh
Yes, 10 is less than 12
1.--------
Yes, 10 is less than 12
2.--------
Yes, 10 is less than 12
(2) 双分支结构
[root@test shell]# cat if-double1.sh
#!/bin/bash
a=3
b=1
if [ $a -lt $b ]
then
echo "$a < $b"
else
echo "$a >= $b"
fi
[root@test shell]# sh if-double1.sh
3 >= 1
(3) 多分支结构
[root@test shell]# cat if-judenum1.sh
#!/bin/bash
if [ $1 -gt $2 ]
then
echo "$1 > $2"
elif [ $1 -eq $2 ]
then
echo "$1 = $2"
else
echo "$1 < $2"
fi
[root@test shell]# sh if-judenum1.sh 1 2
1 < 2
[root@test shell]# sh if-judenum1.sh 11 11
11 = 11
[root@test shell]# sh if-judenum1.sh 11 12
11 < 12
[root@test shell]# sh if-judenum1.sh
>
#加了判断
[root@test shell]# cat if-judenum2.sh
#!/bin/bash
print_error(){
printf "input error!\n"
echo -e "use eg. $0 num1 num2 "
exit 1
}
if [[ $# != 2 ]]
then
print_error
fi
[ -n "`echo $1|sed 's/[0-9]//g'`" -a -n "`echo $2|sed 's/[0-9]//g'`" ] && \
echo "two args must be number" && exit 1
[ -n "`echo $1|sed 's/[0-9]//g'`" ] && echo "first args must be number" && exit 1
[ -n "`echo $2|sed 's/[0-9]//g'`" ] && echo "second args must be number" && exit 1
if [ $1 -gt $2 ]
then
echo "$1 > $2"
elif [ $1 -eq $2 ]
then
echo "$1 = $2"
else
echo "$1 < $2"
fi
[root@test shell]# sh if-judenum2.sh
input error!
use eg. if-judenum2.sh num1 num2
[root@test shell]# sh if-judenum2.sh 1 2
1 < 2
[root@test shell]# sh if-judenum2.sh 11 b
second args must be number
[root@test shell]# sh if-judenum2.sh 11a 22c
two args must be number
[root@test shell]# sh if-judenum2.sh 11a 22
first args must be number
扩展:判断字符串是否为数字?
方法1:sed加正则表达式
[ -n "`echo $1|sed 's/[0-9]//g'`" -a -n "`echo $2|sed 's/[0-9]//g'`" ] && \
echo "two args must be number" && exit 1
[ -n "`echo $1|sed 's/[0-9]//g'`" ] && echo "first args must be number" && exit 1
#普通变量
[ -n "`echo $num|sed 's/[0-9]//g'`" ] && {
echo "参数必须为数字"
exit 1
}
方法2:变量字串替换加正则表达式
#不为0则是数字
[ -z "`echo "${num//[0-9]/}"`" ] && echo 1 || echo 0
方法3: 变量字串替换加正则表达式
#删除非数字之后,看是否等于本身
[ -n "$num" -a "$num" = "${num//[^0-9]/}" ] && echo "it is num"
(4) 判断mysql是否启动,未启动则自动启动mysql
思路1:如果mysql端口和进程同时存在,即认为mysql服务正常!
[root@mysql-A shell]# cat if-judge-db0.sh
#!/bin/bash
MYSQL_STARTUP="/data/3306/mysql"
db_process_count=`ps -ef|grep mysql|grep -v grep|wc -l`
db_port_count=`netstat -lnt|grep 3306|wc -l`
if [ ${db_process_count} -eq 2 ] && [ ${db_port_count} -eq 1 ]
then
echo "mysql is running! "
else
${MYSQL_STARTUP} start >/tmp/mysql.log
sleep 10;
if [ ${db_process_count} -ne 2 ] || [ ${db_port_count} -ne 1 ]
then
killall mysqld >/dev/null 2>&1
sleep 5
killall mysqld >/dev/null 2>&1
sleep 5
[ ${db_port_count} -eq 0 ] && ${MYSQL_STARTUP} start >>/tmp/mysql.log
[ $? -eq 0 ] && echo "mysql is started"
fi
mail -s "mysql restarted" qxl_work@163.com < /tmp/mysql.log
fi
思路2:模拟web服务器,通过mysql账户连接mysql,根据返回状态或返回内容确认mysql服务是否正常!(推荐)
另外:通过web服务的url访问数据库来判断也可以。
手动检查:
[root@mysql-A shell]# mysql -uroot -p'123456' -S /data/3306/mysql.sock -e "select version();"
+------------+
| version() |
+------------+
| 5.1.65-log |
+------------+
脚本:
[root@mysql-A shell]# cat if-judge-db1.sh
#!/bin/bash
MYSQL_STARTUP="/data/3306/mysql"
mysql -uroot -p'123456' -S /data/3306/mysql.sock -e "select version();" >/dev/null 2>&1
if [ $? -eq 0 ]
then
echo "mysql is running! "
else
${MYSQL_STARTUP} start >/tmp/mysql.log
sleep 10;
mysql -uroot -p'123456' -S /data/3306/mysql.sock -e "select version();" >/dev/null 2>&1
if [ $? -eq 0 ]
then
killall mysqld >/dev/null 2>&1
sleep 5
killall mysqld >/dev/null 2>&1
sleep 5
${MYSQL_STARTUP} start >>/tmp/mysql.log
[ $? -eq 0 ] && echo "mysql is started"
fi
mail -s "mysql restarted" qxl_work@163.com < /tmp/mysql.log
fi
思路3:更专业的生产脚本解决方案
#!/bin/bash
# this script is created by oldboy.
# e_mail:xxx@qq.com
# function: xxxx
# version:1.1
################################################
MYUSER=root
MYPASS="oldboy"
MYSOCK=/data/3306/mysql.sock
MySQL_STARTUP="/data/3306/mysql"
LOG_PATH=/tmp
LOG_FILE=${LOG_PATH}/mysqllogs_`date +%F`.log
MYSQL_PATH=/usr/local/mysql/bin
MYSQL_CMD="$MYSQL_PATH/mysql -u$MYUSER -p$MYPASS -S $MYSOCK"
$MYSQL_CMD -e "select version();" >/dev/null 2>&1
if [ $? -eq 0 ]
then
echo "MySQL is running! "
exit 0
else
$MySQL_STARTUP start >$LOG_FILE
sleep 5;
$MYSQL_CMD -e "select version();" >/dev/null 2>&1
if [ $? -ne 0 ]
then
for num in `seq 5`
do
killall mysqld >/dev/null 2>&1
[ $? -ne 0 ] && break;
sleep 1
done
$MySQL_STARTUP start >>$LOG_FILE
fi
$MYSQL_CMD -e "select version();" >/dev/null 2>&1 && Status="restarted" || Status="unknown"
mail -s "MySQL status is $Status" xxx@qq.com < $LOG_FILE
fi
exit $RETVAL
思考:完成异地模拟web服务器通过账号连接完成mysql服务监控的脚本。
(5) 监控apache服务
apachemon-1.sh
#!/bin/bash
. /etc/init.d/functions
LOG_FILE=/tmp/httpd.log
apachectl="/application/apache/bin/apachectl"
HTTPPORTNUM=`netstat -lnt|grep 80|grep -v grep|wc -l`
HTTPPRONUM=`ps -ef|grep http|grep -v grep|wc -l`
wget --quiet --spider http://10.0.0.179:8000 && RETVAL=$?
[ ! -f $LOG_FILE ] && touch $LOG_FILE
if [ "$RETVAL" != "0" -o "$HTTPPORTNUM" -lt "1" -o "$HTTPPRONUM" \< "1" ] ;then
#echo $RETVAL $HTTPPORTNUM $HTTPPRONUM
#exit
action "httpd is not running" /bin/false
echo -e "httpd is not running\n" >$LOG_FILE
echo "Preparing start apache..."
for num in `seq 10`
do
killall httpd >/dev/null 2>&1
[ $? -ne 0 ] && {
echo "httpd is killed" >$LOG_FILE
break;
}
sleep 2
done
$apachectl start >/dev/null 2>&1 && Status="started" || Status="unknown"
[ "$Status" = "started" ] && action "httpd is started" /bin/true||\
action "httpd is started" /bin/false
mail -s "`uname -n`'s httpd status is $Status" xxx@qq.com <$LOG_FILE
exit
else
action "httpd is running" /bin/true
exit 0
fi
提示:以上对端口,进程,url同时检测的方法,在生产环境使用的不多,比较专业的做法是监控url更准确,端口和进程都是辅助手段。
一个常用简单版本:
apachemon-2.sh
#!/bin/bash
. /etc/init.d/functions
LOG_FILE=/tmp/httpd.log
apachectl="/application/apache/bin/apachectl"
wget --quiet --spider http://10.0.0.179:8000 && RETVAL=$?
[ ! -f $LOG_FILE ] && touch $LOG_FILE
if [ "$RETVAL" != "0" ]
then
echo -e "Httpd is not running\n" >$LOG_FILE
for num in `seq 10`
do
killall httpd >/dev/null 2>&1
[ $? -ne 0 ] && {
echo "Httpd is killed" >>$LOG_FILE
break;
}
sleep 2
done
$apachectl restart >/dev/null 2>&1 && Status="restarted" || Status="unknown"
[ "$Status" = "restarted" ] && action "Httpd is restarted" /bin/true||\
action "Httpd is restarted" /bin/false
mail -s "`uname -n`'s httpd status is $Status" xxx@qq.com <$LOG_FILE
exit
else
action "httpd is running" /bin/true
exit 0
fi
监控端通过端口来监控
check_httpd_port.sh
#!/bin/bash
ip_add="$1"
port="$2"
print_usage(){
echo -e "$0 ip port"
exit 1
}
#judge para num
if [ $# -ne 2 ]
then
print_usage
fi
PORT_COUNT=`nmap $ip_add -p $port|grep open|wc -l`
#echo -e "\n" |telnet $ip_add $port||grep Connected
#echo -e "\n"|telnet 10.0.0.179 8000|grep Connected
[[ $PORT_COUNT -ge 1 ]] && echo "$ip_add $port is ok." || echo "$ip_add $port is unknown."
4.2. case结构条件
4.2.1. case条件语法
case "字符串变量" in
值1) 指令…
;;
值2) 指令…
;;
*) 指令…
esac
案例1:简单实例
[root@mysql-A case]# cat case1.sh
#!/bin/bash
read -p "Please input a number:" num
case "$num" in
1)
echo "input 1"
;;
2)
echo "input 2"
;;
[3-9])
echo "input is $num"
;;
*)
echo "default , input is $num"
exit;
;;
esac
[root@mysql-A case]# sh case1.sh
Please input a number:1
input 1
[root@mysql-A case]# sh case1.sh
Please input a number:23
default , input is 23
[root@mysql-A case]# sh case1.sh sda
Please input a number:sd
default , input is sd
案例2:判断用户输入了哪种水果?
[root@mysql-A case]# cat case2.sh
#!/bin/bash
read -p "Please input the fruit name:" fruit
case "$fruit" in
apple|APPLE)
echo "the fruit name is alpple"
;;
banana|BANANA)
echo "the fruit name is banana"
;;
pear|PEAR)
echo "the fruit name is pear"
;;
*)
echo "default ,fruit name is $fruit "
exit;
;;
esac
[root@mysql-A case]# sh case2.sh
Please input the fruit name:d
default ,fruit name is d
[root@mysql-A case]# sh case2.sh
Please input the fruit name:apple
the fruit name is alpple
[root@mysql-A case]# sh case2.sh apple
Please input the fruit name:APPLE
the fruit name is alpple
案例3:apache启动脚本
httpdctl-case-3.sh
#!/bin/bash
#author:xxx
# Source function library.
. /etc/rc.d/init.d/functions
httpd="/application/apache/bin/httpd"
case "$1" in
start)
$httpd -k $1 >/dev/null 2>&1
[ $? -eq 0 ] && action "启动 httpd:" /bin/true ||\
action "启动 httpd:" /bin/false
;;
stop)
$httpd -k $1 >/dev/null 2>&1
if [ $? -eq 0 ]
then
action "停止 httpd:" /bin/true
else
action "停止 httpd:" /bin/false
fi
;;
restart)
$httpd -k $1 >/dev/null 2>&1
[ $? -eq 0 ] && action "重起 httpd:" /bin/true||\
action "重起 httpd:" /bin/false
;;
*)
echo "Format error!"
echo $"Usage: $0 {start|stop|restart}"
exit 1
;;
esac
优化后脚本:
httpdctl-case4-1.sh
#!/bin/bash
#author:xxx
# Source function library.
[ -f /etc/rc.d/init.d/functions ] && . /etc/rc.d/init.d/functions
RETVAL=0
httpd="/application/apache/bin/httpd"
start() {
$httpd -k start >/dev/null 2>&1
RETVAL=$?
[ $RETVAL -eq 0 ] && action "启动 httpd:" /bin/true ||\
action "启动 httpd:" /bin/false
return $RETVAL
}
stop() {
$httpd -k stop >/dev/null 2>&1
[ $? -eq 0 ] && action "停止 httpd:" /bin/true ||\
action "停止 httpd:" /bin/false
return $RETVAL
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
sh $0 stop
sh $0 start
;;
*)
echo "Format error!"
echo $"Usage: $0 {start|stop|restart}"
exit 1
;;
esac
exit $RETVAL
案例4:学习系统apache httpd脚本
#!/bin/bash
#
# httpd Startup script for the Apache HTTP Server
#
# chkconfig: - 85 15
# description: Apache is a World Wide Web server. It is used to serve \
# HTML files and CGI.
# processname: httpd
# config: /etc/httpd/conf/httpd.conf
# config: /etc/sysconfig/httpd
# pidfile: /var/run/httpd.pid
# Source function library.
. /etc/rc.d/init.d/functions
if [ -f /etc/sysconfig/httpd ]; then
. /etc/sysconfig/httpd
fi
# Start httpd in the C locale by default.
HTTPD_LANG=${HTTPD_LANG-"C"}
# This will prevent initlog from swallowing up a pass-phrase prompt if
# mod_ssl needs a pass-phrase from the user.
INITLOG_ARGS=""
# Set HTTPD=/usr/sbin/httpd.worker in /etc/sysconfig/httpd to use a server
# with the thread-based "worker" MPM; BE WARNED that some modules may not
# work correctly with a thread-based MPM; notably PHP will refuse to start.
# Path to the apachectl script, server binary, and short-form for messages.
apachectl=/usr/sbin/apachectl
httpd=${HTTPD-/usr/sbin/httpd}
prog=httpd
pidfile=${PIDFILE-/var/run/httpd.pid}
lockfile=${LOCKFILE-/var/lock/subsys/httpd}
RETVAL=0
# check for 1.3 configuration
check13 () {
CONFFILE=/etc/httpd/conf/httpd.conf
GONE="(ServerType|BindAddress|Port|AddModule|ClearModuleList|"
GONE="${GONE}AgentLog|RefererLog|RefererIgnore|FancyIndexing|"
GONE="${GONE}AccessConfig|ResourceConfig)"
if LANG=C grep -Eiq "^[[:space:]]*($GONE)" $CONFFILE; then
echo
echo 1>&2 " Apache 1.3 configuration directives found"
echo 1>&2 " please read /usr/share/doc/httpd-2.2.3/migration.html"
failure "Apache 1.3 config directives test"
echo
exit 1
fi
}
# The semantics of these two functions differ from the way apachectl does
# things -- attempting to start while running is a failure, and shutdown
# when not running is also a failure. So we just do it the way init scripts
# are expected to behave here.
start() {
echo -n $"Starting $prog: "
check13 || exit 1
LANG=$HTTPD_LANG daemon --pidfile=${pidfile} $httpd $OPTIONS
RETVAL=$?
echo
[ $RETVAL = 0 ] && touch ${lockfile}
return $RETVAL
}
# When stopping httpd a delay of >10 second is required before SIGKILLing the
# httpd parent; this gives enough time for the httpd parent to SIGKILL any
# errant children.
stop() {
echo -n $"Stopping $prog: "
killproc -p ${pidfile} -d 10 $httpd
RETVAL=$?
echo
[ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile}
}
reload() {
echo -n $"Reloading $prog: "
if ! LANG=$HTTPD_LANG $httpd $OPTIONS -t >&/dev/null; then
RETVAL=$?
echo $"not reloading due to configuration syntax error"
failure $"not reloading $httpd due to configuration syntax error"
else
killproc -p ${pidfile} $httpd -HUP
RETVAL=$?
fi
echo
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status -p ${pidfile} $httpd
RETVAL=$?
;;
restart)
stop
start
;;
condrestart)
if [ -f ${pidfile} ] ; then
stop
start
fi
;;
reload)
reload
;;
graceful|help|configtest|fullstatus)
$apachectl $@
RETVAL=$?
;;
*)
echo $"Usage: $prog {start|stop|restart|condrestart|reload|status|fullstatus|graceful|help|configtest}"
exit 1
esac
exit $RETVAL
案例5:学习系统portmap脚本
#! /bin/sh
#
# portmap Start/Stop RPC portmapper
#
# chkconfig: 345 13 87
# 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
# This is an interactive program, we need the current locale
[ -f /etc/profile.d/lang.sh ] && . /etc/profile.d/lang.sh
# We can't Japanese on normal console at boot time, so force LANG=C.
if [ "$LANG" = "ja" -o "$LANG" = "ja_JP.eucJP" ]; then
if [ "$TERM" = "linux" ] ; then
LANG=C
fi
fi
# Source function library.
. /etc/init.d/functions
# Get config.
if [ -f /etc/sysconfig/network ]; then
. /etc/sysconfig/network
else
echo $"Networking not configured - exiting"
exit 1
fi
prog="portmap"
# Check that networking is up.
if [ "$NETWORKING" = "no" ]; then
exit 0
fi
[ -f /sbin/portmap ] || exit 0
[ -f /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
RETVAL=0
start() {
echo -n $"Starting $prog: "
daemon portmap $PMAP_ARGS
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/portmap
return $RETVAL
}
stop() {
echo -n $"Stopping $prog: "
killproc portmap
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/portmap
return $RETVAL
}
restart() {
pmap_dump > /var/run/portmap.state
stop
start
pmap_set < /var/run/portmap.state
rm -f /var/run/portmap.state
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status portmap
;;
restart|reload)
restart
;;
condrestart)
[ -f /var/lock/subsys/portmap ] && restart || :
;;
*)
echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}"
exit 1
esac
exit $?
备注:要掌握的系统标杆脚本
这里留个作业:请大家阅读并对下面脚本进行详细注释:
/etc/init.d/functions
/etc/init.d/nfs
/etc/init.d/portmap
/etc/rc.d/rc.sysinit
/etc/init.d/httpd
提示:此类脚本网上有人注释过的,可以参考他们的去理解。
4.3. while循环与util循环
4.3.1. 语法
while 条件
do
指令…
done
util 条件
do
指令…
done
#util应用场合不多见,了解即可
4.3.2. 范例
范例1:每隔2秒记录一次系统负载情况
方法1:
[root@mysql-A while]# cat while1.sh
#!/bin/bash
while true
do
uptime
sleep 2
done
[root@mysql-A while]# sh while1.sh
12:21:02 up 4:35, 1 user, load average: 0.00, 0.02, 0.00
12:21:04 up 4:35, 1 user, load average: 0.00, 0.02, 0.00
12:21:06 up 4:35, 1 user, load average: 0.00, 0.02, 0.00
方法2:
[root@mysql-A while]# cat while1-1.sh
#!/bin/bash
while [ 1 ]
do
uptime >>./uptime.log
usleep 1000000
done
[root@mysql-A while]# sh while1-1.sh &
[2] 9195
[root@mysql-A while]# tail -f uptime.log
13:13:41 up 5:28, 1 user, load average: 0.00, 0.00, 0.00
13:13:41 up 5:28, 1 user, load average: 0.00, 0.00, 0.00
13:13:41 up 5:28, 1 user, load average: 0.00, 0.00, 0.00
脚本后台执行知识拓展:
功能 用途
sh while1-1.sh & 把脚本放到后台执行
ctrl+c 停止执行当前脚本或任务
ctrl+z 暂停执行当前脚本或任务
bg 把当前脚本或任务放到后台执行
fg 当前脚本或任务拿到前台执行,如果有多个任务,可以fg加任务编号调用,如fg 1
jobs 查看执行的脚本或任务
资料: Linux 技巧:让进程在后台可靠运行的几种方法 http://www.ibm.com/developerworks/cn/linux/l-cn-nohup/
范例2:计算从1加到100之和
[root@mysql-A while]# cat sum.sh
#!/bn/bash
i=1
sum=0
while ((i<=100))
do
((sum=sum+i))
((i=i+1))
done
echo "sum=$sum"
[root@mysql-A while]# sh sum.sh
sum=5050
4.3.3. 相关高级生产实战范例拓展
范例3:生产环境判断url是否正常的简单脚本
方法1:根据http code status判断
#!/bin/sh
while true
do
status=`curl -I -s --connect-timeout 10 $1|head -1|cut -d " " -f 2`
if [ "$status" = "200" ] ;then
echo "this url is good"
else
echo " this url is bad"
fi
sleep 2
done
方法2:多条件组合判断
#!/bin/sh
while true
do
status=`curl -I -s --connect-timeout 10 $1|head -1| awk '{print $2}'`
ok=`curl -I -s --connect-timeout 10 $1|head -1|cut -d " " -f 3`
if [ "$status" = "200" ] && [ "$ok"="OK" ];then
echo "this url is good"
else
echo " this url is bad"
fi
sleep 3
done
方法3:更专业的生产检查脚本(shell数组方法)
#!/bin/bash
# this script is created by xxx.
# function:case example
# version:1.1
. /etc/init.d/functions
url_list=(
http://xxxx.org
)
function wait()
{
echo -n '3秒后,执行该操作.';
for ((i=0;i<3;i++))
do
echo -n ".";sleep 1
done
echo
}
function check_url()
{
wait
echo 'check url...'
for ((i=0; i<`echo ${#url_list[*]}`; i++))
do
judge=($(curl -I -s ${url_list[$i]}|head -1|tr "\r" "\n"))
if [[ "${judge[1]}" == '200' && "${judge[2]}"=='OK' ]]
then
action "${url_list[$i]}" /bin/true
else
action "${url_list[$i]}" /bin/false
fi
done
}
check_url
范例4:实战分析apache日志例子
问题1:计算apache一天的日志access-2012-12-08.log中所有行的日志各元素的访问字节数的总和。
while-6.sh
exec <$1
sum=0
while read line
do
num=`echo $line|awk '{print $10}'`
[ -n "$num" -a "$num" = "${num//[^0-9]/}" ] || continue
((sum=sum+$num))
done
echo "${1}:${sum} bytes =`echo $((${sum}/1024))`KB"
[root@stu412 logs]# sh while-6.sh access_log
access_log:2314225 bytes =2259KB
范例5:分析图片服务日志,把日志排(每个图片访问次数*图片大小的总和)行,取top10,也就是计算每个url的总访问的大小。
[root@stu412 logs]# awk '{array_num[$7]++;array_size[$7]+=$10}END{for(x in array_num){print array_size[x],array_num[x],x}}' access_2010-12-8.log | sort -rn -k1 | head -10
57254 1 /static/js/jquery-jquery-1.3.2.min.js
46232 1 /?=
44286 1 //back/upload/course/2010-10-25-23-48-59-048-18.jpg
33897 3 /static/images/photos/2.jpg
11809 1 /back/upload/teacher/2010-08-30-13-57-43-06210.jpg
10850 1 /back/upload/teacher/2010-08-06-11-39-59-0469.jpg
6417 1 /static/js/addToCart.js
4460 1 /static/js/web_js.js
3583 2 /static/flex/vedioLoading.swf
2686 1 /static/js/default.js
[root@stu412 logs]#
范例6:把大文件按行分割成小文件。
split-file.sh
#!/bin/sh
#mother file
FILE_PATH=$1
#linenum of each file
FILE_NUM=$2
i=1
sub=1
#母文件总行数
totalline=`wc -l ${FILE_PATH} | awk '{print $1}'`
#单个文件行数
((inc=totalline/FILE_NUM))
cat ${FILE_PATH} | while read line
do
if ((i<=inc))
then
((i++))
else
((sub++))
i=2
fi
echo ${line} >>${FILE_PATH}.${sub}
done
4.4. for循环
4.4.1. 语法
(1) for循环结构
for 变量名 in 变量取值列表
do
指令…
done
提示:" in 变量取值列表"可省略,省略时相当于in "$@",使用for i相当于for i in "$@"
(2) c语言型for循环结构
for ((exp1;exp2;exp3))
do
指令…
done
4.4.2. 范例
范例1:打印变量列表所有元素, 打印5,4,3,2,1
方法1:直接列出元素
[root@mysql-A for]# cat for-1.sh
#!/bin/bash
for num in 5 4 3 2 1
do
echo $num
done
[root@mysql-A for]# sh for-1.sh
5
4
3
2
1
方法2:大括号方法
[root@mysql-A for]# cat for-2.sh
#!/bin/bash
for num in {5..1}
do
echo $num
done
[root@mysql-A for]# sh for-2.sh
5
4
3
2
1
[root@mysql-A for]# echo {10..2}
10 9 8 7 6 5 4 3 2
[root@mysql-A for]# echo {a..f}
a b c d e f
方法3:seq用法
[root@mysql-A for]# cat for-2-1.sh
#!/bin/bash
for num in `seq 5 -1 1`
do
echo $num
done
范例2:获取当前目录中的的有文件的文件名作为变量输出
[root@mysql-A for]# cat for-3.sh
#!/bin/bash
for filename in `ls`
do
echo $filename
done
范例3: 批量修改文件名
方法1:
#!/bin/sh
for file in `ls ./*.jpg`
do
mv $file `echo $file|sed 's/_finished//g'`
done
方法2:
#!/bin/sh
for file in `ls ./*.jpg`
do
/bin/mv $file `echo "${file%_finished*}.jpg"`
done
方法3:
ls |awk -F 'finished' '{print "mv "$0" "$1$2" "}'|/bin/bash
方法4:
rename "finished" "" *
范例4: 打印9x9乘法表
#!/bin/bash
for a in `seq 1 9`
do
for b in `seq 1 9`
do
if [ $a -ge $b ]
then
echo -en "$a x $b = $(expr $a \* $b) "
fi
done
echo " "
done
#!/bin/bash
for a in `seq 9`
do
for b in `seq 9`
do
[ $a -ge $b ] && echo -en "$a x $b = $(expr $a \* $b) "
done
echo " "
done
范例5: 清理开机启动的服务
范例6: 生产环境批量检查web服务是否正常并且发送邮件或手机报警
check_web_service.sh
#!/bin/bash
# this script is created by oldboy.
# version:1.1
################################################
#set -x
RETVAL=0
SCRIPTS_PATH="/server/scripts"
MAIL_GROUP="xxx@qq.com xxx@qq.com"
## web detection function
LOG_FILE="/tmp/web_check.log"
function Get_Url_Status(){
FAILCOUNT=0
for (( i=1 ; $i <= 3 ; i++ ))
do
wget -T 20 --tries=2 --spider http://${HOST_NAME} >/dev/null 2>&1
if [ $? -ne 0 ]
then
let FAILCOUNT+=1;
fi
done
#if 3 times then send mail.
if [ $FAILCOUNT -eq 3 ]
then
RETVAL=1
NOW_TIME=`date +"%m-%d %H:%M:%S"`
SUBJECT_CONTENT="http://${HOST_NAME} service is error,${NOW_TIME}."
for MAIL_USER in $MAIL_GROUP
do
echo "send to :$MAIL_USER ,Title:$SUBJECT_CONTENT" >$LOG_FILE
mail -s "$SUBJECT_CONTENT " $MAIL_USER <$LOG_FILE
done
else
RETVAL=0
fi
return $RETVAL
}
#func end.
[ ! -d "$SCRIPTS_PATH" ] && {
mkdir -p $SCRIPTS_PATH
EOF
}
[ ! -f "$SCRIPTS_PATH/domain.list" ] && {
cat >$SCRIPTS_PATH/domain.list<<EOF
www.etiantian.org
blog.etiantian.org
EOF
}
#service check
for HOST_NAME in `cat $SCRIPTS_PATH/domain.list`
do
echo -n "checking $HOST_NAME: "
#Get_Url_Status
Get_Url_Status && echo ok||echo no
done
范例7: 批量添加100个用户并设置指定密码(密码不能相同)
#!/bin/sh
#author:xxx
userchars="test"
passfile="/tmp/user.log"
for num in `seq 3`
do
useradd $userchars$num
passwd="`echo "date $RANDOM"|md5sum|cut -c3-11`"
echo "$passwd"|passwd --stdin $userchars$num
echo -e "user:$userchars$num\tpasswd:$passwd">>$passfile
#sleep 1
done
echo ------this is xxx contents----------------
cat $passfile
4.5. break,continue,exit
命令 说明
break n n 表示跳出循环的层数,如果省略n表示跳出整个循环。
continue n n 表示退到第n层继续循环,如果省略n表示跳出本次循环,忽略本次循环的剩余代码,进入循环的下一次循环。
exit n 退出当前shell程序,并返回n。n也可以省略。
5. shell函数
5.1. 函数语法
函数名() {
指令…
return n
}
function 函数名() {
指令…
}
5.2. 函数执行
调用函数:
(1) 直接执行函数名即可。注意,不需要带小括号。
函数名
(2) 带参数的函数执行方法:
函数名 参数1 参数2
说明:
函数体中位置参数($1,$2,…,$#,$*,$?,$@)都可以是函数的参数;
父脚本的参数则临时被函数参数所掩盖或隐藏。
$0比较特殊,他仍然是父脚本的名称。
当函数完成时,原来的命令行参数会恢复。
在shell函数里面,return命令的功能与工作方式与exit相同,用于跳出函数。
在shell函数体里使用exit会终止整个shell脚本。
return语句会返回一个退出值给调用的程序。
5.3. 范例(略)
6. shell脚本调试
6.1. 常见错误范例
(1) if条件句缺少if结尾关键字
(2) 循环体结构缺少关键字
(3) 成对的符号[],(),{},"",''等不匹配导致错误
(4) 中括号[]两端没空格导致错误
6.2. shell脚本调试技巧
(1) 使用dos2unix处理脚本
(2) 使用echo命令进行调试
(3) 使用bash命令进行调试
sh [-nvx] script.sh
参数:
-n 不会执行该脚本,仅查询脚本语法是否有问题,并给出错误提示
-v 在执行程序时,先将脚本的内容输出到屏幕上,如果有错误,也会给出错误提示。
-x 将使用的脚本内容显示到屏幕上,这是对调试很有用的参数。
特别说明:参数-x是一个不可多得的参数。如果执行脚本有问题,一般利用-x参数,就可以知道问题出在哪一行了。
(4) 使用set命令调试部分脚本内容
set命令可辅助脚本测试
set -n 读命令但并不执行
set -v 显示读取的所有行
set -x 显示所有命令及其参数
提示:
同bash的功能
开启调试功能set -x 命令,而关闭调试功能set +x
(5) 扩展:使用Bash专用调试器 bashdb
http://bashdb.sourceforge.net/