第三十九章 Shell高级编程

一、Shell函数
1、语法格式
(1)简单语法

fun_name(){

指令。。。

return n

}

(2)规范语法

function fun_name(){

指令。。。

return n

}

2、函数的执行
(1)方法一:直接执行函数名

a、格式

fun_name

b、注意事项

(i)不要带小括号。

(ii)函数定义和函数体必须在要执行的函数名的前面,因为shell的执行是从上向下执行的。

(2)方法二:带参数执行

a、格式

fun_name 参数1  参数2  。。。

b、注意事项

(i)在函数体中可以使用位置参数($1,$2..$n,$#,$*,$?,$@)做为函数的参数。

(ii)父脚本的参数则临时被函数的参数所掩盖或变隐藏。

(iii)位置参数$0比较特殊,仍然表示父脚本的名称。

(iv)当函数执行完成后,原来的命令行参数会恢复。

(v)return的功能和工作方式与shell中的exit相同,用于跳出函数。

(vi)return会返回一个退出给调用的程序。

(vii)在函数体中使用exit会跳出shell,终止整个shell脚本。

3、示例
(1)示例1:基本调用的注意事项

a、只定义函数,不调用,则无结果。

vi /services/scripts/myFun01.sh

#!/bin/sh

myFun01(){

    echo "I amLinux"

}

sh /services/scripts/myFun01.sh

#<--结果为空,没有任何输出

b、函数调用在前,定义在后,则报错commandnot found。

vi /services/scripts/myFun01.sh

#!/bin/sh

myFun01

myFun01(){

    echo "I amLinux"

}

sh /services/scripts/myFun01.sh

myFun01.sh: line 2: myFun01: command not found

c、调用函数加小括号,则报错syntaxerror: unexpected end of file。

vi /services/scripts/myFun01.sh

#!/bin/sh

myFun01(){

    echo "I amLinux"

}

myFun01()

sh /services/scripts/myFun01.sh

myFun01.sh: line 6: syntax error: unexpected end of file

d、正确调用。

vi /services/scripts/myFun01.sh

#!/bin/sh

myFun01(){

    echo "I amLinux"

}

myFun01

sh /services/scripts/myFun01.sh

I am Linux

(2)示例2:函数定义和函数执行分离于不同脚本

vi /services/scripts/fun_defined.sh

#!/bin/sh

myFun01(){

    echo "I ammyFun01"

}

myFun02(){

    echo "I ammyFun02"

}

vi /services/scripts/fun_exec.sh

#!/bin/sh

[ -f fun_defined.sh ] && . ./fun_defined.sh || exit 1

myFun01

myFun02

sh fun_exec.sh

I am myFun01

I am myFun02

(3)示例3:函数传参

vi /services/scripts/myFun03.sh

#!/bin/sh

myFun03(){

    echo "I am Linux。You are $1"

}

myFun03 abc

sh /services/scripts/myFun03.sh

I am Linux。Youare abc

(4)示例4:函数获取脚本传参

vi /services/scripts/myFun04.sh

#!/bin/sh

myFun04(){

    echo "I am myFun04。You are $1"

}

myFun04 $1

sh /services/scripts/myFun04.sh Tom

I am myFun04。Youare Tom

(5)示例5:通过函数传参、脚本传参实现对任意指这URL的异常判断

vi /service/scripts/fun_check_web.sh

#!/bin/sh

httpcode=0

getHttpCode(){

httpcode=`curl -I -s -w "%{http_code}" -o /dev/null $1`

if [ $http_code -ne 200 ]

  then

echo " Web is error."

return 1

else

    echo " Web isOK."

fi

return 0

}

getHttpCode $1

ehco $?

echo $httpcode

sh fun_check_web.sh http://www.baidu.com

Web is OK.

0

200

sh fun_check_web.sh http://www.baidu.com123

Web is error.

1

000

(6)示例6:开发MySQL单实例或多实例启动脚本

a、思路

(i)MySQL启动命令:

单实例:mysqld_safe --user-mysql &

多实例:mysqld_safe--defaults-file=/data/3306/my.cnf &

(ii)MySQL停止命令

单实例:mysqladmin -uroot -p'123456'shutdown

多实例:mysqladmin -uroot -p'123456' -S/data/3306/mysql.sock shutdown

b、要求

一个start函数调用启动命令,一个stop函数调用停止命令,一个restart函数调用stop函数和start函数。

c、单实例启动脚本

vi /services/scripts/singleDBStart.sh

#!/bin/sh

. /etc/init.d/functions

basedir=/application/mysql/

datadir=/application/mysql/data/

 

function usage(){

    echo "$0{start|stop|restart}"

    exit 1

}

[ $# -ne 1 ] && usage

 

function start_mysql(){

    $basedir/bin//mysqld_safe--datadir="$datadir" --user=mysql &

    if [ $? -eq 0 ]

      then

        action "startmysql" /bin/true

    else

        action "startmysql" /bin/false

    fi

}

 

function stop_mysql(){

    mysqladmin -uroot-p'123456' shutdown

    if [ $? -eq 0 ]

      then

        action "stopmysql" /bin/true

    else

        action "stopmysql" /bin/false

    fi

}

 

if [ "$1" == "start" ]

  then

    start_mysql

elif [ "$1" == "stop" ]

  then

    stop_mysql  

elif [ "$1" == "restart" ]

  then

    stop_mysql

    start_mysql

else

    usage

fi

sh singleDBStart.sh start

start mysql                                               [  OK  ]

sh singleDBStart.sh stop

stop mysql                                                 [  OK  ]

mysql -uroot -p

mysql>

d、多实例启动脚本:查看多实例/data/3306/mysql

cat /data/3306/mysql

#!/bin/sh

 

#init

port=3306

mysql_user="root"

mysql_pwd="wddgmy"

CmdPath="/application/mysql/bin"

mysql_sock="/data/${port}/mysql.sock"

#startup function

function_start_mysql()

{

    if [ ! -e"$mysql_sock" ];then

      printf "StartingMySQL...\n"

      /bin/sh${CmdPath}/mysqld_safe --defaults-file=/data/${port}/my.cnf 2>&1 >/dev/null &

    else

      printf "MySQL is running...\n"

      exit

    fi

}

 

#stop function

function_stop_mysql()

{

    if [ ! -e"$mysql_sock" ];then

       printf "MySQL isstopped...\n"

       exit

    else

       printf "StopingMySQL...\n"

       ${CmdPath}/mysqladmin-u ${mysql_user} -p${mysql_pwd} -S /data/${port}/mysql.sock shutdown

   fi

}

 

#restart function

function_restart_mysql()

{

    printf "RestartingMySQL...\n"

    function_stop_mysql

    sleep 2

    function_start_mysql

}

 

case $1 in

start)

    function_start_mysql

;;

stop)

    function_stop_mysql

;;

restart)

    function_restart_mysql

;;

*)

    printf "Usage:/data/${port}/mysql {start|stop|restart}\n"

esac

(7)示例7:将开发MySQL单实例启动脚本通过chkconfig进行管理启动

cp /services/scripts/singleDBStart.sh /etc/init.d/sigleMySQL

vi /etc/init.d/sigleMySQL

#!/bin/sh

# chkconfig: 2345 21 60

# description: sigleMySQL start and mysql stop scripts

. /etc/init.d/functions

basedir=/application/mysql/

datadir=/application/mysql/data/

......

chkconfig --add sigleMySQL

chkconfig sigleMySQL on

chkconfig --list sigleMySQL

sigleMySQL          0:off   1:off   2:on   3:on    4:on    5:on   6:off

二、case结构条件句
1、语法格式

case "字符串变量" in

值1) 指令1...

;;

值2|值3|值4) 指令2...

;;

*) 指令...

esac

2、示例
(1)示例1:根据用户的选择输入,判断是哪种水果并加上不同颜色

a、给字符串加颜色

vi /services/scripts/echo-color01.sh

echo -e "\033[30m BlackFont : this is blackfont  \033[0m"

echo -e "\033[31m RedFont   : this is redfont    \033[0m"

echo -e "\033[32m GreenFont : this is greenfont  \033[0m"

echo -e "\033[33m YellowFont : this is yellowfont  \033[0m"

echo -e "\033[34m BlueFont  : this is bluefont   \033[0m"

echo -e "\033[35m PurpleFont : this is purplefont  \033[0m"

echo -e "\033[36m SkyBlueFont: this is skybluefont\033[0m"

echo -e "\033[37m WhiteFont : this is whitefont   \033[0m"

vi /services/scripts/echo-color02.sh

#!/bin/sh

RED_COLOR='\E[1;31m'

GREEN_COLOR='\E[1;32m'

YELLOW_COLOR='\E[1;33m'

BLUE_COLOR='\E[1;34m'

PINK_COLOR='\E[1;35m'

RES='\E[0m'

echo -e "${RED_COLOR}========red color=========${RES}"

echo -e "${YELLOW_COLOR}======yellow color=========${RES}"

echo -e "${BLUE_COLOR}========blue color=========${RES}"

echo -e "${GREEN_COLOR}=======green color=========${RES}"

echo -e "${PINK_COLOR}========pink color=========${RES}"

b、完整脚本

vi /services/scripts/fruitmenu.sh

#!/bin/sh

RED_COLOR='\E[1;31m'

GREEN_COLOR='\E[1;32m'

YELLOW_COLOR='\E[1;33m'

BLUE_COLOR='\E[1;34m'

RES='\E[0m'

 

menu(){

cat <<END

==========================

    1  apple

    2  pear

3 banana

4 exit

=============================

END

}

while true

do

menu

read -p "please ipunt your choice: " -t 10 fruit

case "$fruit" in

    1)

      echo -e"${RED_COLOR} apple ${RES}"

      ;;

    2)

      echo -e"${GREEN_COLOR} pear ${RES}"

      ;;

    3)

      echo -e"${YELLOW_COLOR} banana ${RES}"

      ;;

    4)

      exit 0

      ;;

 

    *)

      echo "no fruit youchoose"

      ;;

esac

done

(2)示例2:开发一个给指定内容加指定颜色的脚本

a、要求

(i)使用read或传参实现

(ii)以传参为例,在脚本命令行传2个参数,第1个参数是内容,第2个参数是颜色

b、传参脚本

vi /services/scripts/textcolor.sh

#!/bin/sh

RED_COLOR='\E[1;31m'

GREEN_COLOR='\E[1;32m'

YELLOW_COLOR='\E[1;33m'

BLUE_COLOR='\E[1;34m'

RES='\E[0m'

 

if [ $# -ne 2 ];then

    echo "Usage $0content {red|yellow|blue|green}"

    exit

fi

case "$2" in

    red|RED)

       echo -e"${RED_COLOR} $1 ${RES}"

       ;;

    green|GREEN)

       echo -e"${GREEN_COLOR} $1 ${RES}"

       ;;

    yellow|YELLOW)

       echo -e"${YELLOW_COLOR} $1 ${RES}"

       ;;

    blue|BLUE)

       echo -e"${BLUE_COLOR} $1 ${RES}" 

       ;; 

    *)

       echo "you inputcolor is not exits"

       echo "Usage $0content {red|yellow|blue|green}"

       ;;

esac

sh textcolor.sh apple red

 apple

sh textcolor.sh apple green

 apple

sh textcolor.sh apple yellow

 apple

sh textcolor.sh apple blue 

 apple

c、函数脚本

vi /services/scripts/textcolor_fun.sh

#!/bin/sh

plus_color(){

RED_COLOR='\E[1;31m'

GREEN_COLOR='\E[1;32m'

YELLOW_COLOR='\E[1;33m'

BLUE_COLOR='\E[1;34m'

RES='\E[0m'

 

case "$2" in

    "red")

       echo -e"${RED_COLOR} $1 ${RES}"

       ;;

    "green")

       echo -e"${GREEN_COLOR} $1 ${RES}"

       ;;

    "yellow")

       echo -e"${YELLOW_COLOR} $1 ${RES}"

       ;;

    "blue")

       echo -e"${BLUE_COLOR} $1 ${RES}" 

       ;;

    *)

       echo "you inputcolor is not exits"

       echo  $1 

       ;;

esac

}

plus_color $1 $2

sh textcolor_fun.sh apple red

 apple

(3)示例3:利用case来开发类似系统启动rsync服务的脚本(可参考系统的rpcbind和nfs脚本)

a、手动启动rsync服务进行测试

lsof -i :873

rsync --daemon

Failed to parse config file: /etc/rsyncd.conf

touch /etc/rsyncd.conf

rsync --daemon       

lsof -i :873         

COMMAND  PID USER   FD  TYPE DEVICE SIZE/OFF NODE NAME

rsync   2837 root    4u IPv4  15548      0t0 TCP *:rsync (LISTEN)

rsync   2837 root    5u IPv6  15549      0t0 TCP *:rsync (LISTEN)

pkill rsync

lsof -i :873

b、思路

使用pkill rsync命令来停止rsync并不好,也不规范,最好是通过pid文件来停止rsync服务。同时,如果使用端口或进程进行判断程序是否启动或关闭时,在放到/etc/init.d/下或chkconfig进行管理时,容易出现错误,无法正确执行。

vi /etc/rsyncd.conf

pid file = /var/run/rsyncd.pid

lock file = /var/run/rsyncd.lock

log file = /var/log/rsyncd.log

pkill rsync

lsof -i :873

rsync --daemon     

cat /var/run/rsyncd.pid

3026

c、完整脚本

vi /services/scripts/rsyncd.sh

#!/bin/sh

. /etc/init.d/functions

pidfile="/var/run/rsyncd.pid"

start_rsync(){ 

    if [ -f $pidfile ];then

        echo "rsync isrunning"

    else

        rsync --daemon

        action "rsyncstart" /bin/true

    fi

}

stop_rsync(){

    if [ -f $pidfile ];then

        kill -USR2 `cat$pidfile`

        rm -f ${pidfile}

        action "rsync isstopped"  /bin/true

    else

        action "rsync notrunning, already been stopped." /bin/false

    fi

}

case "$1" in

    start)

        start_rsync

        RETVAL=$?

        ;;

    stop)

        stop_rsync

        RETVAL=$?

        ;;

    restart)

        stop_rsync

        sleep 2

        start_rsync

        RETVAL=$?

        ;;

    *)

        echo "USAGE: $0{start|stop|restart}"

        exit 1

        ;;

esac

exit $RETVAL

scripts]# sh rsyncd.sh stop

rsync not running, already been stopped.            [ FAILED  ]

sh rsyncd.sh stop

rsync not running

sh rsyncd.sh start

rsync start                                                [  OK  ]

sh rsyncd.sh start

rsync is running, already been stopped.            [ FAILED  ]

sh rsyncd.sh stop

rsync is stopped                                              [  OK  ]

sh rsyncd.sh restart

rsync not running, already been stopped.            [ FAILED  ]

rsync start                                               [  OK  ]

d、加入chkconfig进行管理

cp /services/scripts/rsyncd.sh /etc/init.d/rsyncd

chmod +x /etc/init.d/rsyncd

vi /etc/init.d/rsyncd

#!/bin/sh

# chkconfig: 2345 31 61

# description: rsync start and stop scripts

. /etc/init.d/functions

pidfile="/var/run/rsyncd.pid"

start_rsync(){ 

...

esac

exit $RETVAL

chkconfig --add rsyncd

chkconfig rsyncd on

chkconfig --list rsyncd

rsyncd           0:off   1:off  2:on    3:on    4:on   5:on    6:off

(4)示例4:将前面shell函数的示例6:MySQL启动脚本由函数改为case来实现

cp /services/scripts/singleDBStart.sh/services/scripts/MySQLStart_case.sh

vi /services/scripts/MySQLStart_case.sh

#!/bin/sh

. /etc/init.d/functions

basedir=/application/mysql/

datadir=/application/mysql/data/

 

function usage(){

    echo "$0{start|stop|restart}"

    exit 1

}

[ $# -ne 1 ] && usage

 

function start_mysql(){

    $basedir/bin//mysqld_safe--datadir="$datadir" --user=mysql &

    if [ $? -eq 0 ]

      then

        action "startmysql" /bin/true

    else

        action "startmysql" /bin/false

    fi

}

 

function stop_mysql(){

    mysqladmin -uroot-p'123456' shutdown

    if [ $? -eq 0 ]

      then

        action "stopmysql" /bin/true

    else

        action "stopmysql" /bin/false

    fi

}

case "$1" in

start)

start_mysql

;;

stop)

stop_mysql

;;

restart)

    stop_mysql

start_mysql

;;

*)

    usage

;;

esac

sh MySQLStart_case.sh start

start mysql                                                [ OK  ]

sh MySQLStart_case stop

stop mysql                                                [  OK  ]

mysql -uroot -p

mysql>

三、while循环和until条件句
1、语法格式
(1)while语法

while 条件

do

指令。。。

done

(2)until语法(应用场合不多见,了解就行)

until条件

do

指令。。。

done

2、while读取文件内容
(1)方法一

cat a.log | while read line

do

done

(2)方法二

while read line

do

done < a.log

(3)方法三

exec < a.log

while read line

do

done

3、示例
(1)示例1:(while)每隔2秒记录一次系统负载情况,屏幕输出

vi /services/scripts/while01.sh

#!/bin/sh

while true

do

    uptime

    sleep 2   #休眠2秒,usleep 1000000也是休眠1秒,单位是微秒

done

sh while01.sh

22:19:01 up  3:34,  3 users, load average: 0.01, 0.07, 0.12

22:19:03 up  3:34,  3 users, load average: 0.01, 0.07, 0.12

(2)示例2:使用while语句计算1+2+..+100的值

a、方法一

vi /services/scripts/while02.sh

#!/bin/sh

i=1

sum=0

while (( i < 101 ))

do

    (( $sum + $i ))

    (( i++ ))

done

echo "sum = " $sum

sh  while02.sh

sum =  5050

b、方法二

vi /services/scripts/while02_02.sh

#!/bin/sh

i=1

sum=0

while [ $i -le 100 ]

do

    let sum=sum+i

    let i=i+1

done

echo "sum = " $sum

sh  while02_02.sh

sum =  5050

(3)示例3:使用while语句竖向打印10,9,8,...1

vi /services/scripts/while03.sh

#!/bin/sh

i=10

while ((i>0))

do

    echo $i

    ((i--))

done

sh while03.sh

10

9

8

7

6

5

4

3

2

1

(4)示例4:手机充值10元,每发一条短信扣1角5分,当余额低于1角5分不能发短信,提示余额不足,请充值。用while实现

a、思路

单位换算,统一单位,尽量用整数来计算。所以10元=1000分,1角5分=15分。

b、脚本

vi /services/scripts/while04.sh

#!/bin/sh

sum=1000

while ((sum >= 15))

do

    ((sum=sum-15))

    echo "yu e is$sum"

done

echo "yu e bu zhu, qing chong zi"

(5)示例5:计算nginx日志中的所有行的访问字节数的总和

a、nginx日志内容

cat /application/nginx/logs/bbs_access.log

192.168.158.111 - - [18/Mar/2017:04:52:39 +0800] "GET /HTTP/1.1" 200 12 "-" "Mozilla/5.0 (Windows NT6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2824.2Safari/537.36" "-"

192.168.158.111 - - [18/Mar/2017:04:52:39 +0800] "GET/favicon.ico HTTP/1.1" 404 570 "http://bbs.abc.org/""Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/54.0.2824.2 Safari/537.36" "-"

192.168.158.111 - - [18/Mar/2017:04:52:39 +0800] "GET/favicon.ico HTTP/1.1" 404 - "http://bbs.abc.org/""Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (

KHTML, like Gecko) Chrome/54.0.2824.2 Safari/537.36""-"

b、脚本

vi /services/scripts/while05.sh

#!/bin/sh

sum=0

i=0

while read line

do

    i=$(echo $line|awk '{print$10}')

    if expr $i + 0&>/dev/null

      then

        ((sum=sum+i))

    fi

done < /application/nginx/logs/bbs_access.log

echo $sum

sh while05.sh    

582

sh -x  while05.sh

+ sum=0

+ i=0

+ read line

++ echo 192.168.158.111 - - '[18/Mar/2017:04:52:39' '+0800]''"GET' / 'HTTP/1.1"' 200 12 '"-"' '"Mozilla/5.0''(Windows' NT '6.1)' AppleWebKit/537.36 '(KHTML,' like 'Gecko)'Chrome/54.0.2824.2 'Safari/537.36"' '"-"'

++ awk '{print $10}'

+ i=12

+ expr 12 + 0

+ (( sum=sum+i ))

+ read line

++ echo 192.168.158.111 - - '[18/Mar/2017:04:52:39' '+0800]''"GET' /favicon.ico 'HTTP/1.1"' 404 570'"http://bbs.abc.org/"' '"Mozilla/5.0' '(Windows' NT '6.1)'AppleWebKit/537.36 '(KHTML,' like 'Gecko)' Chrome/54.0.2824.2'Safari/537.36"' '"-"'

++ awk '{print $10}'

+ i=570

+ expr 570 + 0

+ (( sum=sum+i ))

+ read line

++ echo 192.168.158.111 - - '[18/Mar/2017:04:52:39' '+0800]''"GET' /favicon.ico 'HTTP/1.1"' 404 -'"http://bbs.abc.org/"' '"Mozilla/5.0' '(Windows' NT '6.1)'AppleWebKit/537.36 '('

++ awk '{print $10}'

+ i=-

+ expr - + 0

+ read line

++ echo KHTML, like 'Gecko)' Chrome/54.0.2824.2'Safari/537.36"' '"-"'

++ awk '{print $10}'

+ i=

+ expr + 0

+ read line

+ echo 582

582

(6)示例6:以守护进程方式,每10秒,实现一次rsync mysqlbinlog推送

a、思路

crond定时任务,只能精确到分钟,无法到秒级,所以只能通过循环来实现。脚本完成后,放入rc.local文件中,让脚本开机自启动运行。

b、脚本

vi /services/scripts/while06.sh

#!/bin/sh

while true

do

    rsync -az/data/3306/mysql-bin* rsync_backup@192.168.1.8::backup--password-file=/etc/rsync.password

    sleep 10

done

sh while06.sh &

c、开机自启动

echo "/bin/sh /services/scripts/while06.sh &" >>/etc/rc.local

4、条件句小结

(1)while循环的特长是执行守护进程以及希望循环不退出,用于频率小于1分钟的循环处理。其它的while循环几乎都可以用for循环替代。

(2)case语句可以用if语句替换,一般在系统启动脚本传入少量固定规则字符串时用case,其它判断多用if。

(3)if和for语句是最常用的,其次是while(守护进程),再就是case(服务启动脚本)

四、for循环
1、语法格式
(1)for循环结构

for 变量名 in 变量取值列表

do

指令。。。

done

说明:“in 变量取值列表”可以省略,当省略时,默认为$@$@表示获取当前执行的shell的所有参数)。也就是for i 相当于for i in "$@"

(2)C语言型语法的for循环结构

for((exp1;exp2;exp3))

do

指令。。。

done

2、示例
(1)示例1:通过for循环打印5 4 3 2 1

vi /services/scripts/for01.sh

#!/bin/sh

for n in 5 4 3 2 1      # 也可以用for n in {5..1}

do

    echo $n

done

(2)示例2:通过for循环只保留crond、sshd、rsyslog、network,关闭其它开机自启动,

vi /services/scripts/for02_autoStart.sh

#!/bin/sh

LANG=en

for name in `chkconfig --list | grep 3:on | awk '{print $1}'`

do

    chkconfig $name off

done

 

for name in sshd crond rsyslog network sysstat

do

    chkconfig $name on

done

(3)示例3:通过for循环计算1+2+..+100

vi /services/scripts/for03_add.sh

#!/bin/sh

sum=0

for i in {1..100}   # 也可以for ((i=0;i<=100;i++))

do

    ((sum=sum+i))

done

echo $sum

(4)示例4:创建10个文件,文件名中包含oldboy,结尾是1到10,扩展名为html

vi /services/scripts/for04_createFiles.sh

#!/bin/sh

for ((i=1;i<11;i++))

do

    touch oldboy_${i}.html

done

sh for04_createFiles.sh

ll

-rw-r--r-- 1 root root  0Jun  4 22:26 oldboy_1.html

-rw-r--r-- 1 root root  0Jun  4 22:26 oldboy_10.html

-rw-r--r-- 1 root root  0Jun  4 22:26 oldboy_2.html

-rw-r--r-- 1 root root  0Jun  4 22:26 oldboy_3.html

-rw-r--r-- 1 root root  0Jun  4 22:26 oldboy_4.html

-rw-r--r-- 1 root root  0Jun  4 22:26 oldboy_5.html

-rw-r--r-- 1 root root  0Jun  4 22:26 oldboy_6.html

-rw-r--r-- 1 root root  0Jun  4 22:26 oldboy_7.html

-rw-r--r-- 1 root root  0Jun  4 22:26 oldboy_8.html

-rw-r--r-- 1 root root  0Jun  4 22:26 oldboy_9.html

(5)示例5:通过for循环将上述文件名中的oldboy全部改为linux,扩展名改为大写HTML

a、方法一

vi /services/scripts/for05_rename.sh

#!/bin/sh

cd /services/scripts/for

for n in `seq 10`

do

    mv oldboy_${i}.htmllinux_${i}.HTML

done

b、方法二

vi /services/scripts/for05_rename.sh

#!/bin/sh

cd /services/scripts/for

for f in `ls *.html`

do

#mv $f `echo $f | sed 's#oldboy#linux#g' |sed 's#html#HTML#g'`

    mv $f `echo $f | sed's#oldboy\(.*\).html#linux\1.HTML#g'`

done

(6)示例6:通过for循环批量创建10个系统账号oldboy01到oldboy10,密码不能相同

a、思路和技术点

(i)数字前面加0

数字前面加0,有多种方法。可参参考http://oldboy.blog.51cto.com/2561410/788422

最常用的是seq的-w参数

seq -w 10

01

02

03

04

05

06

07

08

09

10

(ii)无交互式设置密码

echo aaa | passwd --stdin test

b、脚本

vi /services/scripts/for06_createUsers01.sh

#!/bin/sh

for n in `seq -w 10`

do

useradd oldboy$n  && echo "root$n" | passwd--stdin oldboy$n

#useradd oldboy$n -p "root$n" #也可以这样创建用户时直接设置密码但/etc/shadow中为明文

done

(7)示例7:通过for循环批量创建10个系统账号oldboy01到oldboy10,密码为随机8位字符串

a、随机数

(i)echo $RAMDOM:一般为5位数以下

echo $RAMDOM

1634

echo $RANDOM

13621

echo $RANDOM

18892

echo $RANDOM

620

(ii)RAMDOM加上一个8位数10000000:echo$((RANDOM+10000000))

echo $((RANDOM+10000000))

10030175

echo $((RANDOM+10000000))

10012580

echo $((RANDOM+10000000))

10016722

(iii)echo $RAMDOM后md5sum生成的字符串取任意8位:

echo $RANDOM | md5sum | cut -c 1-8

785c4f64

echo "`date`$RANDOM" | md5sum | cut -c 1-8  #以时间为种子取随机数

adba4d85

b、脚本

vi /services/scripts/for07_createUsers02.sh

#!/bin/sh

for n in `seq -w 10`

do

pass=`echo $RANDOM | md5sum | cut -c 1-8`

useradd oldboy$n  && echo "$pass" | passwd--stdin oldboy$n

echo -e "oldboy$n\t $pass ">> pass.txt

done

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

五、随机数的6种获取方法
1、方法一:RANDOM

echo $RANDOM

18682

2、方法二:openssl

openssl rand -base64 8

QFN20JhyWCI=

openssl rand -base64 10

YJzbf9BJjm+4lg==

3、方法三:date

date +%s%N

1496592770592461116

4、方法四:urandom

head /dev/urandom | cksum

1232898784 2017

5、方法五:uuid

cat /proc/sys/kernel/random/uuid

9e1960ac-69c9-4917-bbe5-d1926bafaae0

6、方法六:expect的mkpasswd方法

mkpasswd -l 8

So5;a9i

mkpasswd --help

mkpasswd [args] [user]

 where arguments are:

   -l #      (length of password,default = 9)

   -d #      (min # of digits,default = 2)

   -c #      (min # of lowercasechars, default = 2)

   -C #      (min # of uppercasechars, default = 2)

   -s #      (min # of special chars,default = 1)

   -v        (verbose, show passwdinteraction)

   -p prog   (program to setpassword, default = passwd)

六、循环的控制
1、break、continue、exit和return的对比

命令

说明

break n

n表示跳出循环的层数,如果省略n则表示跳出整个循环

continue n

n表示退出到第n层继续循环,如果省略n则表示跳过本次循环,忽略剩余代码,进入下一次循环

exit n

退出当前shell程序,n为返回值。n也可以省略,再下一个shell里通过$?来接收这个n的值

return n

用于在函数里做为函数的返回值,用于判断函数执行是否正确

2、示例
(1)示例1:break跳出整个循环体,执行循环体外后面的语句

vi break01.sh

#!/bin/sh

for ((i=0;i<=5;i++))

do

   if [ $i -eq 3 ];then

       break

   fi

   echo $i

done

echo "OK"

sh break01.sh

0

1

2

OK

(2)示例2:continue当前循环,执行下一次循环

vi continue01.sh

#!/bin/sh

for ((i=0;i<=5;i++))

do

   if [ $i -eq 3 ];then

       continue

   fi

   echo $i

done

echo "OK"

sh break01.sh

0

1

2

4

5

OK

(3)示例3:exit退出shell,不在执行程序体中任何语句

vi exit01.sh

#!/bin/sh

for ((i=0;i<=5;i++))

do

   if [ $i -eq 3 ];then

       exit

   fi

   echo $i

done

echo "OK"

sh break01.sh

0

1

2

(4)示例4:用shell脚本给服务器配置临时别名IP,并可以随进撤销配置的所有IP。IP段为192.168.2.1-192.168.2.16,其中192.168.2.10不能配置。

vi ipconfig.sh

#!/bin/sh

for ((i=1;i<17;i++))

do

    if [ $i -eq 10 ];then

        continue

    fi

    ifconfig eth0:$i 192.168.2.$i/24 up

done

(5)示例5:判断当前局域网网络里,当前在线用户的IP有哪些?

vi ping01.sh

#!/bin/sh

for n in {1..254}

do

   ping -c1 192.168.1.$n >/dev/null 2>&1

   if [ $? -eq 0 ]

     then

         echo "192.168.1.$n is up " >> /tmp/uplist.log

   else

         echo "192.168.1.$n is down " >> /tmp/downlist.log

   fi

done

(6)示例6:判断是否有DDOS攻击

vi ddos.sh

#!/bin/sh

while true

do

awk '{print $1}' access.log|grep -v"^$"|sort|uniq -c >/tmp/tmp.log

exec </tmp/tmp.log

while read line

do

ip=`echo $line|awk '{print $2}'`

count=`echo $line|awk '{print $1}'`

if [ $count -gt 3 ] && [ `iptables-L -n |grep "$ip"|wc -l` -lt 1 ]

then

iptables -I INPUT -s $ip -j DROP

echo "$line is dropped" >>/tmp/droplist.log

fi

done

sleep 5

done

七、一键优化脚本
1、一键优化的思维思想
(1)要知道在哪些优化点
(2)每个优化点,如何优化?
(3)针对每个优化点,在命令行完成单条测试
2、示例1:一键优化Linux系统的脚本
(1)优化点

a、安装系统时精简安装包(最小化安装)

b、配置yum源

c、禁用开机不需要的启动的服务

d、优化系统内核参数/etc/sysctl.conf

e、增加系统文件描述符、堆栈等配置

f、禁止root远程登陆,修改SSH端口为特殊端口,禁止DNS,禁止空密码

g、有外网IP的要配置防火墙,公对外开启需提供服务的端口,配置或关闭SELinux

h、清除无用的默认用户和组(非必须),添加运维成员用户

i、锁定敏感文件,如/etc/passwd(非必须)

j、配置时间同步

k、配置sudo,对普通用户权限精细控制

 

3、示例2:检查一键优化是否成功的脚本

 

八、Shell数组
1、定义
(1)方法一(常用)

array=(value1 value2 value3 ...)

(2)方法二(常用)

array=($(ls等命令))

(3)方法三

array=([key1]=value1 [key2]=value2...)

(4)方法四

array[下标1]=value1

array[下标2]=value2

(5)方法五

declare -a array

2、数据的操作
(1)获取数组长度

echo ${#array[@]}或echo ${#array[*]}

(2)打印单个数组元素:数组下标从0开始

echo ${array[下标]}

(3)打印数组全部元素

echo ${array[@]}或echo${array[*]}  #与获取数组长度相比,没有#

(4)数组的赋值

数组的赋值是直接通过数据名[下标]就可以对其进行引用赋值。如果下标不存在,则会自动添加一个新的数组元素,如查存在就覆盖原来的值。

array[下标]=value4

(5)数组的删除

通过unset 数组名[下标]就可以清除对应的元素,如不带下标,则删除整个数组。就相当于unset 变量,可以理解为把数组名当变量在使用。

unset array[1]

unset array

(6)数组的截取(与变量子串的替换相似)

a、截取指定长度的子串

array[@或*]:开始下标: 截取长度

b、从指定下标截取到结尾

array[@或*]:开始下标

(7)数组的替换(与变量子串的替换相似,该操作临时生效,不会改就原数组内容,类似sed)

array[@或*]/需替换的内容/替换内容

3、示例
(1)示例1:定义数组

array=(1 2 3)

(2)示例2:获取数组array的长度

echo ${#array[@]}

3

echo ${#array[*]}

3

(3)示例3:打印数组array中单个的元素

echo ${array[0]}

1

echo ${array[1]}

2

echo ${array[2]}

3

(4)示例4:打印数组array中所有的元素

echo ${array[*]}

1 2 3

echo ${array[@]}

1 2 3

(5)示例5:数组赋值

a、为数组array新增一个元素

array[3]=4

b、查看数组所有元素

echo ${array[@]}

1 2 3 4

c、修改数组第一个元素的值

array[0]=test

d、查看数组所有元素

echo ${array[*]}

test 2 3 4

(6)示例6:删除数组

a、删除数组array第一个元素

unset array[0]

echo ${array[*]}

2 3 4

echo ${#array[*]}

2   # <--数组长度为2

echo ${array[0]}

    # <--下标为0的元素,已经不存在了,下标从1开始了

echo ${array[1]}

2

b、删除数组array

unset array

echo ${array[*]}

# <--为空

(7)示例7:数组的截取

array=(1 2 3 4 5)

echo ${array[@]:2:2}

3 4

echo ${array[@]:2} 

3 4 5

(8)示例8:数组的替换

array=(1 2 3 4 5)

echo ${array[@]/2/test}

1 test 3 4 5

echo ${array[@]}

1 2 3 4 5

array1=(${array[@]/2/test})

echo ${array1[@]}         

1 test 3 4 5

(9)示例9:方法二方式定义数组的相关操作

pwd

/services/scripts

ls

a1.log  a2.log  a3.log a4.log  a5.log

array2=($(ls))

echo ${#array2[*]}

5

echo ${array2[0]}

a1.log

echo ${array2[1]}

a2.log

for f in ${array2[*]};do echo $f;done

a1.log

a2.log

a3.log

a4.log

a5.log

(10)示例10:定义数组并打印

array=(

    aa

    bb

    cc

)

for ((i=0;i<${#array[*]};i++))

do

    echo "This is num $i,then content is ${array[$i]}"

done

4、数组小结:常用功能
(1)数组定义

array=(1 3 3)

array=($(ls))

(2)数组打印

a、打印所有元素

${array[*]}

b、打印数组长度

${#array[*]}

c、打印单个元素

${array[i]}

九、运维要求必会的脚本开发

1、各类监控脚本。如:内存、磁盘、端口、URL监控报警

2、如果监控网站目录文件是否被篡改,以及站点目录被篡改后如何批量恢复

3、如何开发各类服务rsync、nginx、mysql等启动及停止专业脚本

4、如何开发MySQL主从同步监控报警以及自动化处理不同步的脚本

5、一键配置mysql多实例,一键配置mysql主从同步,一键部署脚本

6、监控http/MySQL/rsync/nfs等服务是否正常生产脚本

7、一键软件安装以及优化,lamp、lnmp、一键安装,一键安装数据库,优化配置主从

8、MySQL多实例启动脚本,分库,分表自动化备份脚本

9、监控网络连接数,以及根据webPV分IP脚本

10、监控网站pv以及流量。并对流量信息进行统计

11、监控web服务器URL地址的脚本,可以批量以及通用

12、系统的基础优化,一键优化脚本

13、清理系统垃圾文件(过期备份)脚本

14、tcp连接状态统计

15、批量创建用户并设置随机8位密码

16、获取服务器的信息,批量分文件

十、Shell脚本的调试
1、常见脚本错误
(1)if条件句的条件表达式中中括号[]两边缺少空格

cat if.sh

#!/bin/bash

if [$1-lt$2]#<--[$1之间缺少空格

then

echo "Yes,$1islessthan$2"

exit

fi

shif.sh12

if.sh:line2:[1:commandnotfound#<--[1当成一个命令了,提示命令没有找到

(2)if条件句的条件表达式中应成对出现的符号,缺失一个

cat if.sh

#!/bin/bash

if [ $1-lt$2#<--[]应成对出现,但缺少右边的]

then

echo "Yes,$1islessthan$2"

exit

fi

shif.sh12

if.sh:line2:[:missing`]'#<--但缺少右边的]

(3)if条件句缺少结尾的fi

cat if.sh

#!/bin/bash

if[$1-lt$2]

then

echo "Yes,$1islessthan$2"

exit

#<--缺少if结束的fi

shif.sh12

if.sh:line6:syntaxerror:unexpectedendoffile#<--语法错误,未预期的文件结尾。

2、调试技巧
(1)调试要求

a、重视书写习惯、开发规范和开发制度,尽量减少脚本调试的难度,提升开发效率

b、基本语法要熟悉,才能利用好调试

c、开发时,思路要清晰,将复杂的脚本简单化,分段实现

d、将重复的内容,最好写成函数

(2)技巧1:使用dos2unix命令处理来自windows下开发的脚本

从windows编辑的脚本到linux下时,有时候会有格式问题,存在一些不可见字符,导致脚本运行时报错。由于是不可见字符,用眼睛看代码时,又不容易发现问题。所以,从windows迁移到linux上的脚本或者不是自已写的脚本,一律要用dos2unix命令格式化处理后再运行。例:下面脚本是从windows开发的,在linux中显示的结果,每行结尾处多了^M

cat -vwhile-debug-test.sh

#!/bin/sh^M

i=1^M

sum=0^M

while((i<=100))^M

do^M

((sum=sum+i))^M

((i++))^M

done^M

echo $sum^M

dos2unix while-debug-test.sh

dos2unix:convertingfilewhile-debug-test.shtoUNIXformat..

(3)技巧2:使用echo命令调试

在指定位置插入echo命令,进行输出,并在语句后面加上exit命令,不执行echo后面的语句,这样就可以逐段排查了(写法:echo$var;exit)。如下例:

vi/services/scripts/webSiteCheck.sh

#!/bin/sh

path=/server/scripts

while true

do

f=`md5sum -c $path/md5sum.db2>/dev/null|grepFAILED|wc-l`

n=`cat $path/check_site_filenumber.log|wc-l`

findtext=`find /application/nginx/html/-type f>$path/new_site.log`

log=/tmp/check.log

[ !-f$log ]&&touch$log

if[ $f-ne0 ]||[ `cat$path/new_site.log|wc-l`-ne$n ]

then

echo "`md5sum-c md5sum.db2>/dev/null|grepFAILED`">$log

echo "`md5sum -c md5sum.db2>/dev/null|grepFAILED`"#调试,echo 输出

exit#<--调试,退出,不执行后面的语句

diff $path/check_site_filenumber.log$path/new_site.log>>$log

mail -s "siteistampered$(date)"aaa@abc.com<$log

fi

sleep180

done

3、bash的调试参数
(1)-n参数:不执行脚本,仅查询脚本是否存在语法问题,如果有错误,则给出错误提示

sh-n while01.sh

#<--正确,无任何提示,脚本也未执行

sh-n if.sh

if.sh:line6:syntaxerror:unexpectedendoffile#<--有错误提示,脚本也未执行

(2)-v参数:在执行脚本时,先将脚本的内容输出到屏幕上,然后再执行脚本,如果有错误,则给出错误提示

sh-v while02.sh

#!/bin/sh

i=1

sum=0

while(($i<101))

do

sum=$(($sum+$i))

i=$(($i+1))

done

echo "sum="$sum#<--文件内容

sum=5050#<--文件执行的结果

(3)-x参数:将执行的脚本内容和输出显示到屏幕上(最常用的参数)

sh -x while01.sh

+true

+uptime

+sleep2

4、使用set命令调试部分脚本内容(缩小调试的作用域)

bash-x将会调试脚本全部内容,但有时,我们不想调试全部脚本,只想调试部分脚本,这就需要set命令了。如脚本调用了系统函数,但系统函数不会有错误,我们不需要调试,这时就可以用set-x和set+x来指定要调试的作用域了。

(1)参数

-n:读命令,但不执行

-v:显示读取的所有行

-x:显示所有命令及其参数

(2)示例

viwhile02.sh

#!/bin/sh

i=1

sum=0

set -x#<--调试开始位置

while(($i<101))

do

sum=$(($sum+$i))

i=$(($i+1))

done

set +x#<--调试结束位置

echo "sum="$sum

sh while02.sh#<--不需要用sh-x

Logo

更多推荐