Linux-Shell脚本编程进阶

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-Shell脚本编程进阶相关的知识,希望对你有一定的参考价值。

技术分享图片

本章内容

       条件判断

       循环

       信号捕捉

       函数

       数组

       高级字符串操作

       高级变量

       Expect

      

流程控制

       过程式编程语言:

              顺序执行

              选择执行

              循环执行

      

条件选择if语句

       选择执行:

       注意:if语句可嵌套

       单分支

              if 判断条件;then

                     条件为真的分支代码

              fi

       双分支

              if 判断条件; then

                     条件为真的分支代码

              else

                     条件为假的分支代码

              fi

      

if 语句

       多分支

              if 判断条件1; then

                     条件为真的分支代码

              elif 判断条件2; then

                     条件为真的分支代码

              elif 判断条件3; then

                     条件为真的分支代码

              else

                     以上条件都为假的分支代码

              fi

       逐条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句

      

If示例

       根据命令的退出状态来执行命令

       if ping -c1 -W2 station1 &> /dev/null; then

              echo 'Station1 is UP'

       elif grep "station1" ~/maintenance.txt &> /dev/null

       then

              echo 'Station1 is undergoing maintenance‘

       else echo 'Station1 is unexpectedly DOWN!' exit 1

       fi

      

条件判断:case语句

       case 变量引用 in

       PAT1)

              分支1

              ;;

       PAT2)

              分支2

              ;;

       ...

       *)

              默认分支

              ;;

       esac

 

       case支持glob风格的通配符:

              *: 任意长度任意字符

              ?: 任意单个字符

              []:指定范围内的任意单个字符

              a|b: ab

      

练习

       1、编写脚本/root/bin/createuser.sh,实现如下功能:使用一个用户名做为参数,如果指定参数的用户存在,就显示其存在,否则添加之;显示添加的用户的id号等信息

       答:

       #!/bin/bash

       #FileName:  createuser.sh

       [ $# -ne 1 ] && echo "1 user" && exit

       useradd $1 &> /dev/null

       if [ $? -ne 0 ];then

               echo "user $1 existed"

       else

               echo "user $1 added successfully"

               getent passwd $1                                                                      

       fi

       执行结果如下:

       [[email protected] bin]#./createuser.sh user1

       user user1 added successfully

       user1:x:1021:1022::/home/user1:/bin/bash

       [[email protected] bin]#./createuser.sh user1

       user user1 existed

      

       2、编写脚本/root/bin/yesorno.sh,提示用户输入yesno,并判断用户输入的是yes还是no,或是其它信息

       答:

       #!/bin/bash

       #FileName:  yesorno.sh

       read -p "Do you agree? yes or no: " ANS

       if [[ $ANS =~ ^([Yy]([Ee][Ss])?)$ ]];then

               echo "your name is yes"

       elif [[ $ANS =~ ^([Nn][Oo]?)$ ]]:then

               echo "your answer is no"

       else

               echo "Please input yes or no"

       fi

       执行结果如下

       [[email protected] bin]#./yesorno.sh

       Do you agree? yes or no: y

       your name is yes

       [[email protected] bin]#./yesorno.sh

       Do you agree? yes or no: n

       your answer is no

       [[email protected] bin]#./yesorno.sh

       Do you agree? yes or no: a

       Please input yes or no

      

       3、编写脚本/root/bin/filetype.sh,判断用户输入文件路径,显示其文件类型(普通,目录,链接,其它文件类型)

       答:

       #!/bin/bash

       #FileName:  filetype.sh

       read -p "Please input a filepath: " path

       ls -ld $path &> /dev/null

       if [ $? -ne 0 ];then

               echo "The file is not existed"

       else

               type=`ls -ld $path | head -c1`

       fi

       case $type in

       -)

               echo "$path is a regular file"

               ;;

       d)

               echo "$path is a directory"

               ;;

       b)

               echo "$path is a block device file"

               ;;

       c)

               echo "$path is a character device file"

               ;;

       l)

               echo "$path is a symbolic link"

               ;;

       p)

               echo "$path is a named pipe"

               ;;

       s)

               echo "$path is a local socket file"

               ;;

       *)

               echo "error"

       esac

       执行结果如下:

       [[email protected] bin]#./filetype.sh

       Please input a filepath: /etc/fstab

       /etc/fstab is a regular file

       [[email protected] bin]#./filetype.sh

       Please input a filepath: /root

       /root is a directory

       [[email protected] bin]#./filetype.sh

       Please input a filepath: /dev/sda

       /dev/sda is a block device file

       [[email protected] bin]#./filetype.sh

       Please input a filepath: /dev/zero

       /dev/zero is a character device file

       [[email protected] bin]#./filetype.sh

       Please input a filepath: /bin

       /bin is a symbolic link

      

       4、编写脚本/root/bin/checkint.sh,判断用户输入的参数是否为正整数

       #!/bin/bash

       #FileName:  checkint.sh

       read -p "Please input a positive integer: " INT

       if [[ $INT =~ ^[0-9]+$ ]];then

               echo "\"$INT\" is a positive integer"

       else

               echo "\"$INT\" is not a positive integer"                                             

       fi

       执行结果如下:

       [[email protected] bin]#./checkint.sh

       Please input a positive integer: 23

       "23" is a positive integer

       [[email protected] bin]#./checkint.sh

       Please input a positive integer: -16

       "-16" is not a positive integer

       [[email protected] bin]#./checkint.sh

       Please input a positive integer: abc

       "abc" is not a positive integer

      

循环

       循环执行

              将某代码段重复运行多次

              重复运行多少次

                     循环次数事先已知

                     循环次数事先未知

              有进入条件和退出条件

       for, while, until

      

for循环

       for 变量名 in 列表;do

              循环体

       done

       执行机制:

              依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束

      

列表生成方式:

              (1) 直接给出列表

              (2) 整数列表:

                     (a) {start..end}

                     (b) $(seq [start [step]] end)

              (3) 返回列表的命令

                     $(COMMAND)

              (4) 使用glob,如:*.sh

              (5) 变量引用;

                     [email protected], $*

       例:for file in `ls /boot`; do echo The filename is $file; done

      

练习:for实现

       1、判断/var/目录下所有文件的类型

       #!/bin/bash

       #FileName:  filetype_var.sh

       for path in `ls -d /var/*`;do

               type=`ls -ld $path | head -c1`

               case $type in

               -)

                       echo "$path is a regular file"

                       ;;

               d)

                       echo "$path is a directory"

                       ;;

               b)

                       echo "$path is a block device file"

                       ;;

               c)

                       echo "$path is a character device file"

                       ;;

               l)

                       echo "$path is a symbolic link"

                       ;;

               p)

                       echo "$path is a named pipe"

                       ;;

               s)

                       echo "$path is a local socket file"

                       ;;

               *)

                       echo "error"

                       ;;

       esac

       done

       执行结果如下:

       [[email protected] bin]#./filetype_var.sh

       ./filetype_var.sh: line 12: [: too many arguments

       /var/account is a directory

       /var/adm is a directory

       /var/cache is a directory

       /var/crash is a directory

       /var/db is a directory

       /var/empty is a directory

       /var/games is a directory

       /var/gopher is a directory

       /var/kerberos is a directory

       /var/lib is a directory

       /var/local is a directory

       /var/lock is a symbolic link

       /var/log is a directory

       /var/mail is a symbolic link

       /var/nis is a directory

       /var/opt is a directory

       /var/preserve is a directory

       /var/run is a symbolic link

       /var/spool is a directory

       /var/target is a directory

       /var/tmp is a directory

       /var/www is a directory

       /var/yp is a directory

      

       2、添加10个用户user1-user10,密码为8位随机字符

       #!/bin/bash

       #FileName:  createusers.sh

       for num in {1..10};do

               useradd user$num &> /dev/null

               if [ $? = 0 ];then

                       echo user$num created successfully

                       echo `openssl rand -base64 6` | passwd --stdin user$num &> /dev/null

               else

                       echo user$num existed

               fi

       done

       执行结果如下:

       [[email protected] bin]#./createusers.sh

       user1 created successfully

       user2 created successfully

       user3 created successfully

       user4 created successfully

       user5 created successfully

       user6 created successfully

       user7 created successfully

       user8 created successfully

       user9 created successfully

       user10 created successfully

      

       3/etc/rc.d/rc3.d目录下分别有多个以K开头和以S开头的文件;分别读取每个文件,以K开头的输出为文件加stop,以S开头的输出为文件名加start,如K34filename stop S66filename start

       #!/bin/bash

       #FileName:  rc3.d_KS.sh

       for i in `ls /etc/rc.d/rc3.d`;do

               if [[ $i =~ ^K.*$ ]];then

                       echo $i stop;

               elif [[ $i =~ ^S.*$ ]];then

                       echo $i start;

               else

                       :

               fi

       done

       [[email protected] bin]#./rc3.d_KS.sh

       K50netconsole stop

       S10network start

      

       4、编写脚本,提示输入正整数n的值,计算1+2++n的总和

       #!/bin/bash

       #FileName:  sum_n.sh

       while true; do

               read -p "Please input a positive integer: " n

               [[ $n =~ ^[1-9][0-9]*$ ]] && break

       done

      

       sum=0

       for i in `seq 1 $n`;do

               sum=$[sum+i]

       done

       echo "n=$n"

       echo "1+2+...+$n=$sum"

       执行结果如下:

       [[email protected] bin]#./sum_n.sh

       Please input a positive integer: 100

       n=100

       1+2+...+100=5050

      

       5、计算100以内所有能被3整除的整数之和

       #!/bin/bash

       #FileName:  sum_%3=0.sh

       sum=0

       for i in {0..100};do

               if [ $[i%3] -eq 0 ];then

                       sum=$[sum+i]

               else

                       :

               fi

       done

       echo sum=$sum

       执行结果如下:

       [[email protected] bin]#./sum_%3\=0.sh

       sum=1683

      

       6、编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态

       #!/bin/bash

       #FileName:  scanip.sh

       # 提示输入网络地址,用正则表达式判断网段合法性

       while true; do

               read -p "Please input an ip net, such as 192.168.0.0, the script will scan ip net 192.168.0.0/24: " address

               if [[ $address =~ ^(([1-9]?[0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$ ]];then

                       echo -e "ip is legal, start scanning...\n"

                       break

               else

                       echo -e "wrong ip address\n"

               fi

       done

       # 开始扫描

       net=`echo $address | sed -r 's/^(.*)\..*$/\1/'`

       for i in {1..254};do {

               if ping -c1 -w1 $net.$i &> /dev/null ;then

                       echo "$net.$i is up"

               else

                       echo "$net.$i is down"

               fi } &

       done

       wait

       执行结果如下:

       [[email protected] bin]#./scanip.sh

       Please input an ip net, such as 192.168.0.0, the script will scan ip net 192.168.0.0/24: 192.168.0.0

       ip is legal, start scanning...

      

       192.168.0.1 is up

       192.168.0.103 is up

       192.168.0.104 is up

       192.168.0.113 is up

       192.168.0.114 is up

       192.168.0.5 is down

       192.168.0.2 is down

       ……

       ……

       192.168.0.177 is down

       192.168.0.245 is down

       192.168.0.232 is down

       192.168.0.228 is down

      

       7、打印九九乘法表

       #!/bin/bash

       #Filename:  9×9.sh

       for i in {1..9};do

               for j in `seq 1 $i`;do

                       result=$[j*i]

                       echo -e "${j}×${i}=$result\t\c"

               done

               echo

       done

       执行结果如下:

      技术分享图片

      

       8、在/testdir目录下创建10html文件,文件名格式为数字N(从110)加随机8个字母,如:1AbCdeFgH.html

       #!/bin/bash

       #Filename:  touch_Nrand.html.sh

       mkdir /testdir/ &> /dev/null                                                                     

       rand=0

       for i in {1..10};do

               rand=`openssl rand -base64 20 | tr -dc [[:alpha:]] | head -c8`

               touch "/testdir/$i$rand.html"

       done

       执行结果如下:

       [[email protected] bin]#./touch_Nrand.html.sh

       [[email protected] bin]#cd /testdir

       [[email protected] testdir]#ls

       10InrYzLKS.html  3rlecXfCR.html  6MjgPCXtX.html  9qQGRIxir.html

       1maGPaaSn.html   4LZssBMjM.html  7ImLLSRih.html

       2vjaOmtIO.html   5goBpJYRs.html  8OXhFdlTZ.html

      

       9、打印等腰三角形

       #!/bin/bash

       #Filename:  triangle.sh

       while true;do

               read -p "Please input the high of the triangle: " high

               [[ $high =~ ^[0-9]+$ ]] && break

       done

      

       for i in `seq $high`;do

               for j in `seq $[high-i]`;do echo -e " \c";done                                           

               for k in `seq $[2*i-1]`;do echo -e "*\c";done

               echo

       done

       执行结果如下:

  技术分享图片

      

while循环

       while CONDITION; do

              循环体

       done

       CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环

       因此:CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正

       进入条件:CONDITIONtrue

       退出条件:CONDITIONfalse

      

练习:用while实现

       1、编写脚本,求100以内所有正奇数之和

       #!/bin/bash

       #FileName:  while_sum.sh

       i=0

       sum=0

       while [ $i -le 100 ] ;do

               if [ $[i%2] -eq 1 ];then

                       sum=$[sum+i]                                        

               fi

               let i++

       done

       echo sum=$sum

       执行结果如下:

       [[email protected] bin]#./while_sum.sh

       sum=2500

      

       2、编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态,并统计在线和离线主机各多少

       #!/bin/bash

       #FileName:  scanup.sh

       # 提示输入网络地址,用正则表达式判断网段合法性

       while true; do

               read -p "Please input an ip net, such as 192.168.0.0, the script will scan

       ip net 192.168.0.0/24: " address

               if [[ $address =~ ^(([1-9]?[0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([1-

       9]?[0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$ ]];then

                       echo -e "ip is legal, start scanning...\n"

                       break

               else

                       echo -e "wrong ip address\n"

               fi

       done

       # 开始扫描

       net=`echo $address | sed -r 's/^(.*)\..*$/\1/'`

       i=1

       touch ./scanup_tmp

       while [ $i -le 254 ];do {

               if ping -c1 -w1 $net.$i &> /dev/null ;then

                       echo "$net.$i is up" | tee -a ./scanup_tmp

               else

                       echo "$net.$i is down" | tee -a ./scanup_tmp

               fi } &

               let i++

       done

       wait

       up=`grep up ./scanup_tmp | wc -l`

       down=`grep down ./scanup_tmp | wc -l`

       rm -f ./scanup_tmp &> /dev/null

       echo

       echo up=$up

       echo down=$down

       # 扫描结束

       执行结果如下:

       [[email protected] bin]#./scanup.sh

       Please input an ip net, such as 192.168.0.0, the script will scan ip net 192.168.0.0/24: 192.168.0.0

       ip is legal, start scanning...

      

       192.168.0.1 is up

       192.168.0.104 is up

       192.168.0.103 is up

       192.168.0.5 is down

       ……

       192.168.0.243 is down

       192.168.0.251 is down

       192.168.0.247 is down

      

       up=6

       down=248

      

       3、编写脚本,打印九九乘法表

       #!/bin/bash

       #FileName:  9×9_while.sh

       declare -i i=1

       while [ $i -le 9 ];do

               declare -i j=1

               while [ $j -le $i ];do

                       result=$[j*i]

                       echo -e "${j}×${i}=$result\t\c"

                       let j++

               done

               echo

               let i++

       done

       执行结果如下:

  技术分享图片

      

       4、编写脚本,利用变量RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大值和最小值

       #!/bin/bash

       #FileName:  10_random_num.sh

       declare -i rand min max i=1                                                                        

       echo "10 random numbers: "

       while [ $i -le 10 ]; do

               rand=$RANDOM

               echo $i $rand

               [ $i -eq 1 ] && { min=$rand; max=$rand; }

               [ "$rand" -lt "$min" ] && min=$rand

               [ "$rand" -gt "$max" ] && max=$rand

               let i++

       done

       echo min $min

       echo max $max

       执行结果如下:

       [[email protected] bin]#./10_random_num.sh

       10 random numbers:

       1 1309

       2 23514

       3 21965

       4 6954

       5 25446

       6 19996

       7 16356

       8 4571

       9 32208

       10 8522

       min 1309

       max 32208

      

       5、编写脚本,实现打印国际象棋棋盘

       #!/bin/bash

       #FileName:  chess.sh

       # 给国际象棋棋盘选2种颜色

       # 打印8种颜色

       echo "Please select 2 colour for the chessboard: "

       for i in {0..7};do

               echo -e "$[i+1]\033[1;4${i}m  \033[0m    \c"

       done

       echo -e "\n"

       # 选择颜色1

       while true;do

               read -p "Select the first colour (1-8): " C1

               [[ "$C1" =~ ^[1-8]$ ]] && break

       done

       echo -e "The first colour is $C1\033[1;4$[C1-1]m  \033[0m\n"

       # 选择颜色2

       while true;do

               read -p "Select the second colour (1-8): " C2

               [[ "$C2" =~ ^[1-8]$ ]] && break

       done

       echo -e "The second colour is $C2\033[1;4$[C2-1]m  \033[0m\n"

       # 选择棋盘大小

       while true;do

               read -p "Now select the size of the chessboard (1-9): " S

               [[ "$S" =~ ^[1-9]$ ]] && break

       done

       # 打印棋盘

       echo  "Print the chessboard: "

       echo

       for i in `eval echo {0..$[8*S-1]}`;do

               for j in `eval echo {0..$[8*S-1]}`;do

                       if [ $[(i/S+j/S)%2] -eq 0 ];then

                               echo -e "\033[1;4$[C1-1]m  \033[0m\c"

                       else

                               echo -e "\033[1;4$[C2-1]m  \033[0m\c"

                       fi

               done

               echo

       done

       # 打印结束

       执行结果如下:

      技术分享图片

      

       6、后续六个字符串:efbaf275cd4be9c40b8b44b2395c46f8c8873ce0b902c16c8bad865d2f63是通过对随机数变量RANDOM随机执行命令: echo $RANDOM|md5sum|cut -c1-10 后的结果,请破解这些字符串对应的RANDOM

       #!/bin/bash

       #FileName:  rand_md5sum.sh

       declare -i i=0

       declare md5

       while [ $i -le 32767 ]; do {

               md5=`echo $i | md5sum | cut -c1-10`

               case $md5 in

                       efbaf275cd)

                               echo "efbaf275cd ---> $i" | tee -a ./md5sum.txt

                               ;;

                       4be9c40b8b)

                               echo "4be9c40b8b ---> $i" | tee -a ./md5sum.txt

                               ;;

                       44b2395c46)

                               echo "44b2395c46 ---> $i" | tee -a ./md5sum.txt

                               ;;

                       f8c8873ce0)

                               echo "f8c8873ce0 ---> $i" | tee -a ./md5sum.txt

                               ;;

                       b902c16c8b)

                               echo "f8c8873ce0 ---> $i" | tee -a ./md5sum.txt

                               ;;

                       ad865d2f63)

                               echo "f8c8873ce0 ---> $i" | tee -a ./md5sum.txt

                               ;;

                       *)

                               ;;

               esac } &

               let i++

       done

       wait

       cat ./md5sum.tx

       脚本结束,输出结果如下:

       f8c8873ce0 ---> 1000

       f8c8873ce0 ---> 3000

       f8c8873ce0 ---> 6000

       44b2395c46 ---> 9000

       4be9c40b8b ---> 12000

       efbaf275cd ---> 15000

      

until循环

       until CONDITION; do

              循环体

       done

       进入条件: CONDITION false

       退出条件: CONDITION true

      

循环控制语句continue

       用于循环体中

       continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1

       while CONDTIITON1; do

              CMD1

              ...

              if CONDITION2; then

                     continue

              fi

              CMDn

              ...

       done

      

循环控制语句break

       用于循环体中

       break [N]:提前结束第N层循环,最内层为第1

       while CONDTIITON1; do

              CMD1

              ...

              if CONDITION2; then

                     break

              fi

              CMDn

              ...

       done

      

循环控制shift命令

       shift [n]

       用于将参量列表 list 左移指定次数,缺省为左移一次。

       参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift

       ./doit.sh a b c d e f g h

       ./shfit.sh a b c d e f g h

      

示例:doit.sh

       #!/bin/bash

       # Name: doit.sh

       # Purpose: shift through command line arguments

       # Usage: doit.sh [args]

       while [ $# -gt 0 ] # or (( $# > 0 ))

       do

              echo $*

              shift

       done

      

示例:shift.sh

       #!/bin/bash

       #step through all the positional parameters

       until [ -z "$1" ]

       do

              echo "$1"

              shift

       done

       echo

      

创建无限循环

       while true; do

              循环体

       done

 

       until false; do

              循环体

       Done

      

练习

       1、每隔3秒钟到系统上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记录于日志/var/log/login.log,并退出脚本

       #!/bin/bash

       #FileName:  hacker_login.sh

       until false; do

               if who | grep "^hacker\ " &> /dev/null; then                                              

                       who | grep "^hacker\ " >> /var/log/login.log

                       break

               fi

               sleep 3

       done

       如果想让脚本在后台运行,可输入nohup ./hacker_login.sh &> /dev/null &

       hacker用户登录后脚本退出,执行结果如下:

       [[email protected] bin]#cat /var/log/login.log

       hacker   pts/1        2018-05-10 16:20 (192.168.30.1)

      

       2、随机生成10以内的数字,实现猜字游戏,提示比较大或小,相等则退出

       #!/bin/bash

       #FileName:  guess.sh

       num=$[RANDOM%11]

       while true;do

               read -p "Please guess the number between 0 and 10: " guess

               if [[ ! $guess =~ [[:digit:]]+ ]];then

                       :

               elif [ $guess == $num ];then

                       echo "You are right! The number is ${num}!"

                       break

               elif [ $guess -gt $num ];then

                       echo "too large"

               elif [ $guess -lt $num ];then

                       echo "too small"

               else

                       :

               fi

       done

       执行结果如下:

       [[email protected] bin]#./guess.sh

       Please guess the number between 0 and 10: aaa

       Please guess the number between 0 and 10: 5

       too large

       Please guess the number between 0 and 10: 3

       too large

       Please guess the number between 0 and 10: 1

       You are right! The number is 1!

      

       3、用文件名做为参数,统计所有参数文件的总行数

       #!/bin/bash

       #FileName:  sum_line.sh

       [ $# -eq 0 ] && echo "No args" && exit

       declare sum=0

       until [ -z $1 ]; do

               if [ -e $1 ]; then

                       sum=$[sum+`cat $1 | wc -l`]

               else

                       echo "file '$1' is not existed"; exit                                             

               fi

               shift

       done

       echo "There are all $sum cows."

       执行结果如下:

       [[email protected] bin]#./sum_line.sh /etc/fstab /etc/passwd

       There are all 70 cows.

      

       4、用二个以上的数字为参数,显示其中的最大值和最小值

       #!/bin/bash

       #FileName:  max_and_min.sh

       [ $# -lt 2 ] && echo "at least 2 args" && exit

       min=$1

       max=$1

       until [ -z $1 ]; do

               [[ ! $1 =~ ^-?[0-9]+$ ]] && echo $1 is not a number && exit                               

               [ $1 -lt $min ] && min=$1

               [ $1 -gt $max ] && max=$1

               shift

       done

       echo min number is $min

       echo max number is $max

      

特殊用法

       while循环的特殊用法(遍历文件的每一行):

              while read line; do

                     循环体

              done < /PATH/FROM/SOMEFILE

       依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line

       练习

       扫描/etc/passwd文件每一行,如发现GECOS字段为空,则填充用户名和单位电话为62985600,并提示该用户的GECOS信息修改成功

      

       双小括号方法,即(())格式,也可以用于算术运算

       双小括号方法也可以使bash Shell实现C语言风格的变量操作

              I=10

              ((I++))

       for循环的特殊格式:

              for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))

              do

                     循环体

              done

       控制变量初始化:仅在运行到循环代码段时执行一次

       控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断

      

       例:

       for (( i=1,sum=0;i<=100;i++ ));do

               let sum+=i

       done

       echo sum=$sum

      

select循环与菜单

       select variable in list

              do

                     循环体命令

              done

       select 循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示 PS3 提示符,等待用户输入

        用户输入菜单列表中的某个数字,执行相应的命令

        用户输入被保存在内置变量 REPLY

      

selectcase

       select 是个无限循环,因此要记住用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c 退出循环

       select 经常和 case 联合使用

       for 循环类似,可以省略 in list,此时使用位置参量

      

       例:

       #!/bin/bash

       #FileName:  select_menu.sh

       PS3="Please choose the menu (1-4):"

       select menu in baoyu yanwo renshen jitang;do

               case $menu in

               baoyu)

                       echo $REPLY:$menu price is 1000

                       break

                       ;;

               yanwo)

                       echo $REPLY:$menu price is 2000

                       break

                       ;;

               renshen)

                       echo $REPLY:$menu price is 3000

                       break

                       ;;

               jitang)

                       echo $REPLY:$menu price is free

                       break

                       ;;

               *)

                       echo $REPLY:$menu is empty

                       ;;

               esac

       done

      

信号捕捉trap

       trap '触发指令' 信号

              自定义进程收到系统发出的指定信号后,将执行触发指令,而不会执行原操作

       trap '' 信号

              忽略信号的操作

       trap '-' 信号

              恢复原信号的操作

       trap -p

              列出自定义信号操作

      

trap示例

       #!/bin/bash

       trap 'echo “signal:SIGINT"' int

       trap -p

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

       do

              sleep 1

              echo $i

       done

       trap '' int

       trap -p

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

       do

              sleep 1

              echo $i

       done

       trap '-' int

       trap -p

       for((i=21;i<=30;i++))

       do

              sleep 1

              echo $i

       done

      

函数介绍

       函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程

       它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分

       函数和shell程序比较相似,区别在于:

              Shell程序在子Shell中运行

              Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改

      

定义函数

       函数由两部分组成:函数名和函数体

       help function

       语法一:

              f_name (){

                     ...函数体...

              }

       语法二:

              function f_name {

                     ...函数体...

              }

       语法三:

              function f_name () {

                     ...函数体...

              }

      

       函数的优先级比别名、外部命令和内部命令都高

       一般生产中函数名可定义为func_name ()

       local var可使变量var只在本函数中生效

       declare -i var定义的var默认自带local属性,除非加个选项declare -g,但-g选项在CentOS 6上不兼容

      

函数使用

       函数的定义和使用:

              可在交互式环境下定义函数

              可将函数放在脚本文件中作为它的一部分

              可放在只包含函数的单独文件中

       调用:函数只有被调用才会执行

              调用:给定函数名

              函数名出现的地方,会被自动替换为函数代码

       函数的生命周期:被调用时创建,返回时终止

      

函数返回值

       函数有两种返回值:

       函数的执行结果返回值:

              (1) 使用echo等命令进行输出

              (2) 函数体中调用命令的输出结果

       函数的退出状态码:

              (1) 默认取决于函数中执行的最后一条命令的退出状态码

              (2) 自定义退出状态码,其格式为:

              return 从函数中返回,用最后状态命令决定返回值

              return 0 无错误返回。

              return 1-255 有错误返回

      

交互式环境下定义和使用函数

       示例:

              dir() {

              > ls -l

              > }

       定义该函数后,若在$后面键入dir,其显示结果同ls -l的作用相同

              dir

       dir函数将一直保留到用户从系统退出,或执行了如下所示的unset命令

              unset dir

      

在脚本中定义及使用函数

       函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用

       调用函数仅使用其函数名即可

       示例:

       cat func1

       #!/bin/bash

       # func1

       hello()

       {

       echo "Hello there today's date is `date +%F`"

       }

       echo "now going to the function hello"

       hello

       echo “back from the function”

      

使用函数文件

       可以将经常使用的函数存入函数文件,然后将函数文件载入shell

       文件名可任意选取,但最好与相关任务有某种联系。例如:functions.main

       一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数

       若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件

      

创建函数文件

       函数文件示例:

       cat functions.main

       #!/bin/bash

       #functions.main

       findit()

       {

              if [ $# -lt 1 ] ; then

                     echo "Usage:findit file"

              return 1

              fi

              find / -name $1 -print

       }

      

载入函数

       函数文件已创建好后,要将它载入shell

       定位函数文件并载入shell的格式

              . filename source filename

       注意:此即<> <空格> <文件名>

              这里的文件名要带正确路径

       示例:

              上例中的函数,可使用如下命令:

              . functions.main

      

检查载入函数

       使用set命令检查函数是否已载入。set命令将在shell中显示所有的载入函数

       示例:

       set

       findit=( )

       {

              if [ $# -lt 1 ]; then

              echo "usage :findit file";

              return 1

              fi

              find / -name $1 -print

       }

      

      

执行shell函数

       要执行函数,简单地键入函数名即可

       示例:

              findit groups

              /usr/bin/groups

              /usr/local/backups/groups.bak

      

删除shell函数

       现在对函数做一些改动后,需要先删除函数,使其对shell不可用。使用unset命令完成删除函数

       命令格式为:

              unset function_name

       示例:

              unset findit

              再键入set命令,函数将不再显示

       环境函数

              使子进程也可使用

              声明:export -f function_name

              查看:export -f declare -xf

      

函数参数

       函数可以接受参数:

              传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;例如“testfunc arg1 arg2 ...

              在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用[email protected], $*, $#等特殊变量

      

函数变量

       变量作用域:

              环境变量:当前shell和子shell有效

              局部变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,局部变量的作用范围是当前shell脚本程序文件,包括脚本中的函数

              本地变量:函数的生命周期;函数结束时变量被自动销毁

       注意:如果函数中有本地变量,如果其名称同局部变量,使用本地变量

       在函数中定义局部变量的方法

              local NAME=VALUE

      

函数递归示例

       函数递归:

              函数直接或间接调用自身

              注意递归层数

       递归实例:

              阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语

              一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有0的阶乘为1,自然数n的阶乘写作n!

              n!=1×2×3×...×n

              阶乘亦可以递归方式定义:0!=1n!=(n-1)!×n

              n!=n(n-1)(n-2)...1

              n(n-1)! = n(n-1)(n-2)!

      

       示例:fact.sh

       #!/bin/bash

       #

       fact() {

              if [ $1 -eq 0 -o $1 -eq 1 ]; then

                     echo 1

              else

                     echo $[$1*$(fact $[$1-1])]

              fi

       }

       fact $1

      

fork×××

       fork×××是一种恶意程序,它的内部是一个不断在fork进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源

       函数实现

              :(){ :|:& };:

              bomb() { bomb | bomb & }; bomb

       脚本实现

              cat Bomb.sh

              #!/bin/bash

              ./$0|./$0&

      

练习

       编写函数,实现OS的版本判断

       release () {

               ver=`sed -r 's/^.* ([0-9]+)\..*/\1/' /etc/redhat-release`

               echo $ver

       }

      

       编写函数,实现取出当前系统eth0IP地址

       eth0_IP () {

               IP=`ifconfig eth0 | sed -nr 's/^.*inet addr:([^ ]+) .*$/\1/p'`

               echo $IP

       }

      

       编写函数,实现打印绿色OK和红色FAILED

       OK_FAILED () {

               if [[ $1 =~ OK ]];then

                       echo -e "[  \033[32mOK\033[0m  ]"

               elif [[ $1 =~ FAILED ]];then

                       echo -e "[\033[31mFAILED\033[0m]"                                        

               else

                       :

               fi

       }

       # OK_FAILED OK打印绿色OKOK_FAILED FAILED打印红色FAILED

      

       编写函数,实现判断是否无位置参数,如无参数,提示错误

       args () {

               if [ -z $1 ]; then

                       echo "No args"

               fi

       }

       args $1

      

练习

       编写服务脚本/root/bin/testsrv.sh,完成如下要求

       (1) 脚本可接受参数:start, stop, restart, status

       (2) 如果参数非此四者之一,提示使用格式后报错退出

       (3) 如是start:则创建/var/lock/subsys/SCRIPT_NAME, 并显示“启动成功”

       考虑:如果事先已经启动过一次,该如何处理?

       (4) 如是stop:则删除/var/lock/subsys/SCRIPT_NAME, 并显示“停止完成”

       考虑:如果事先已然停止过了,该如何处理?

       (5) 如是restart,则先stop, start

       考虑:如果本来没有start,如何处理?

       (6) 如是status, 则如果/var/lock/subsys/SCRIPT_NAME文件存在,则显示“SCRIPT_NAME is running...

       如果/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“SCRIPT_NAME is stopped...

       其中:SCRIPT_NAME为当前脚本名

       (7)在所有模式下禁止启动该服务,可用chkconfig service命令管理

      

       注:

       /etc/(rc.d/)init.d/functions中有一个action函数,机器启动时各服务启动OK或者FAILED就是用这个函数打印的,可用于testsrv脚本的编写

       例:

       [[email protected] ~]#action "rm -rf /*"

       rm -rf /*                               [  OK  ]

       [[email protected] ~]#action "rm -rf /*" /bin/false

       rm -rf /*                               [FAILED]

      

       答:

       # /etc/rc.d/init.d/下创建如下脚本,然后就可以用service testsrv start|stop|status|restart命令来管理了。输命令chkconfig add testsrv,即可用chkconfig来管理了,chkconfig --list testsrv查看testsrv状态,chkconfig testsrv off即可禁止在所有模式下启动testsrv

       #!bin/bash

       #chkconfig: 2345 96 3

       #discription: test service

       #filename: testsrv

       source "/etc/init.d/functions"

       case $1 in

       start)

               [ -f /var/lock/subsys/testsrv ] && echo "testsrv is already started:" && exit

               touch /var/lock/subsys/testsrv

               action "Starting testsrv:"

               ;;

       stop)

               [ ! -f /var/lock/subsys/testsrv ] && echo "testsrv is already stopped:" && exit

               rm -f /var/lock/subsys/testsrv

               action "Stopping testsrv:"

               ;;

       status)

               [ -f /var/lock/subsys/testsrv ] && echo "testsrv is running..." || echo "testsrv is stopped"

               ;;

       restart)

               [ -f /var/lock/subsys/testsrv ] && action "Stopping testsrv:" || action "Stopping testsrv:" /bin/false

               rm -f /var/lock/subsys/testsrv                                                                                     

               touch /var/lock/subsys/testsrv

               action "Starting testsrv:"

               ;;

       *)

               echo "usage: service testsrv start|stop|restart|status"

               ;;

       esac

       执行结果如下:

       技术分享图片

 

练习

       编写脚本/root/bin/copycmd.sh

       (1) 提示用户输入一个可执行命令名称

       (2) 获取此命令所依赖到的所有库文件列表

       (3) 复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下

       如:/bin/bash ==> /mnt/sysroot/bin/bash

       /usr/bin/passwd ==> /mnt/sysroot/usr/bin/passwd

       (4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下: 如:/lib64/ld-linux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2

       (5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述功能;直到用户输入quit退出

       #!/bin/bash

       #FileName:  copycmd.sh

       input () {

       while true; do

               read -p "Please input a command or quit: " cmd

               [[ $cmd =~ ^quit$ ]] && exit

               which $cmd &> /dev/null && break

               echo "command $cmd not found"

       done

       }

       while true; do

               input

               path=`which --skip-alias $cmd`

               des=/mnt/sysroot

               tmp=`mktemp ./tmpXXX`

               ldd $path | sed -rn '[email protected][^\/]*(\/[^ ]+) .*@\[email protected]' &> $tmp

               mkdir -p "$des${path%/*}"

               cp $path "$des${path%/*}"

               while read line; do

                       mkdir -p "$des${line%/*}" &> /dev/null

                       cp $line "$des${line%/*}" &> /dev/null

               done < ./$tmp

               echo "copy command $cmd successfully!"

               rm -f ./$tmp

       done

      

练习

       1、编写函数实现两个数字做为参数,返回最大值

       # 定义并载入函数后,输F_max $1 $2即可返回$1$2的最大值

       F_max () {

       if [ $# -ne 2 ]; then

               echo "Needs two args" && return 1

       elif [[ ! $1 =~ ^-?[0-9]+$ ]]; then

               echo "$1 is not an integer" && return 2

       elif [[ ! $2 =~ ^-?[0-9]+$ ]]; then

               echo "$2 is not an integer" && return 2

       elif [ $1 -gt $2 ]; then

               echo max number is $1

       else

               echo max number is $2

       fi

       }

       执行结果如下:

       [[email protected] bin]#. F_max.sh

       [[email protected] bin]#F_max -6 -15

       max number is -6

      

       2、斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0112358132134、……,斐波纳契数列以如下被以递归的方法定义:F0=0F1=1Fn=F(n-1)+F(n-2)n2

       利用函数,求n阶斐波那契数列

       # 斐波那契数列用函数递归调用构建的话,时间复杂度会达到2^n次方,在此不用递归,改用迭代

       #!/bin/bash

       #FileName:  Fibonacci.sh

       while true; do

               read -p "Please input a positive integer: " n

               [[ $n =~ ^[0-9]+$ ]] && break

       done

       echo 'F(0),F(1),...,F(n-1),F(n)= '

       declare -a Fi

       if [ $n -eq 0 ];then

               Fi=(0)

       elif [ $n -eq 1 ];then

               Fi=(0 1)

       else

               Fi=(0 1)

               for i in `seq 2 $n`; do                                                      

                       Fi[$i]=$[(Fi[$i-1])+(Fi[$i-2])]

               done

       fi

       echo ${Fi[*]}

       执行结果如下:

       [[email protected] bin]#./Fibonacci.sh

       Please input a positive integer: 20

       F(0),F(1),...,F(n-1),F(n)=

       0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765

      

       3、汉诺塔(又称河内塔)问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤

       #!/bin/bash

       #FileName:  hanoi.sh

       while true; do

               read -p "Please input the number of the plates: " n

               [[ $n =~ ^[1-9][0-9]?$ ]];break

       done

       sum=0

       move() {

               let sum++

               echo "$sum:  move disk $1 from $2 ---> $3"

       }

       hanoi() {

               [ $1 -eq 1 ] && { move $1 $2 $4;return; }

               hanoi $[$1-1] $2 $4 $3                                                       

               move $1 $2 $4

               hanoi $[$1-1] $3 $2 $4

       }

       hanoi $n A B C

       执行结果如下:

       [[email protected] bin]#./hanoi.sh

       Please input the number of the plates: 4

       1:  move disk 1 from A ---> B

       2:  move disk 2 from A ---> C

       3:  move disk 1 from B ---> C

       4:  move disk 3 from A ---> B

       5:  move disk 1 from C ---> A

       6:  move disk 2 from C ---> B

       7:  move disk 1 from A ---> B

       8:  move disk 4 from A ---> C

       9:  move disk 1 from B ---> C

       10:  move disk 2 from B ---> A

       11:  move disk 1 from C ---> A

       12:  move disk 3 from B ---> C

       13:  move disk 1 from A ---> B

       14:  move disk 2 from A ---> C

       15:  move disk 1 from B ---> C

      

       4、打印杨辉三角

       # 杨辉三角用函数递归调用构建的话,时间复杂度会达到2^n次方,在此不用递归,改用迭代

       #!/bin/bash

       #FileName:  yanghui.sh

       while true; do

               read -p "Please input the height of the triangle: " n

               [[ $n =~ ^[1-9][0-9]?$ ]];break

       done

       n=$[n-1]

       declare -A C

               C=([00]=1 [10]=1 [11]=1)

       if [ $n -ge 2 ];then

               for i in `seq 2 $n`; do

                       C[${i}0]=1

                               for j in `seq 1 $[i-1]`; do

                                       C[$i$j]=$[(C[$[i-1]$[j-1]])+(C[$[i-1]$j])]

                               done

                       C[$i$i]=1

               done

       fi

       for i in `seq 0 $n`;do

               for k in `seq 0 $[n-i]`;do echo -e "  \c";done

               for j in `seq 0 $i`;do

                       if [ $j -eq $i ];then

                               echo ${C[$i$j]}

                       else

                               echo -e "${C[$i$j]}  \c"

                       fi

               done

       done

       执行结果如下:

      技术分享图片

      

数组

       变量:存储单个元素的内存空间

       数组:存储多个元素的连续的内存空间,相当于多个变量的集合

       数组名和索引

              索引:编号从0开始,属于数值索引

              注意:索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持

              bash的数组支持稀疏格式(索引不连续)

       声明数组:

              declare -a ARRAY_NAME

              declare -A ARRAY_NAME: 关联数组

              注意:两者不可相互转换

      

数组赋值

       数组元素的赋值

       (1) 一次只赋值一个元素

       ARRAY_NAME[INDEX]=VALUE

       weekdays[0]="Sunday"

       weekdays[4]="Thursday"

       (2) 一次赋值全部元素

       ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)

       (3) 只赋值特定元素

       ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)

       (4) 交互式数组值对赋值

       read -a ARRAY

       显示所有数组:declare -a

      

       例:

       只要能生成列表,就能对数组赋值

              filename=( /boot/* )

              filename=(`ls /boot/`)

              filename=( f{1,2,3}.{txt,log} )

       例:

              [[email protected] bin]#read -a title

              boss ceo cto

              [[email protected] bin]#echo ${title[*]}

              boss ceo cto

       关联数组由于下标可以任意指定,因此可以实现一些巧妙的运用

      

引用数组

       引用数组元素:

              ${ARRAY_NAME[INDEX]}

              注意:省略[INDEX]表示引用下标为0的元素

       引用数组所有元素:

              ${ARRAY_NAME[*]}

              ${ARRAY_NAME[@]}

       数组的长度(数组中元素的个数)

              ${#ARRAY_NAME[*]}

              ${#ARRAY_NAME[@]}

       删除数组中的某元素:导致稀疏格式

              unset ARRAY[INDEX]

       删除整个数组:

              unset ARRAY

      

数组数据处理

       引用数组中的元素:

              数组切片:${ARRAY[@]:offset:number}

                     offset: 要跳过的元素个数

                     number: 要取出的元素个数

                     取偏移量之后的所有元素 ${ARRAY[@]:offset}

       向数组中追加元素:

              ARRAY[${#ARRAY[*]}]=value

       关联数组:

              declare -A ARRAY_NAME

              ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)

              注意:关联数组必须先声明再调用

      

示例

       生成10个随机数保存于数组中,并找出其最大值和最小值

       #!/bin/bash

       declare -i min max

       declare -a nums

       for ((i=0;i<10;i++));do

              nums[$i]=$RANDOM

              [ $i -eq 0 ] && min=${nums[$i]} && max=${nums[$i]}&& continue

              [ ${nums[$i]} -gt $max ] && max=${nums[$i]}

              [ ${nums[$i]} -lt $min ] && min=${nums[$i]}

       done

       echo “All numbers are ${nums[*]}”

       echo Max is $max

       echo Min is $min

      

示例

       编写脚本,定义一个数组,数组中的元素是/var/log目录下所有以.log结尾的文件;统计出其下标为偶数的文件中的行数之和

       #!/bin/bash

       #

       declare -a files

       files=(/var/log/*.log)

       declare -i lines=0

       for i in $(seq 0 $[${#files[*]}-1]); do

              if [ $[$i%2] -eq 0 ];then

                     let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1)

              fi

       done

       echo "Lines: $lines."

      

练习

       1、输入若干个数值存入数组中,采用冒泡算法进行升序或降序排序

       #!/bin/bash

       #FileName:  bubble_sort.sh

       while true; do

               read -p "Please input the length for the array: " n

               [[ $n =~ ^[1-9][0-9]?+$ ]] && break

       done

       echo "create a random array: "

       declare -a arr

       for i in `seq 1 $n`;do

               arr[$i]=$RANDOM

               echo $i: ${arr[$i]}

       done

      

       while true; do

               read -p "Please input a or d to ascending sort or descending sort: " opt

               [ $opt == a -o $opt == d ] && break

       done

       m=$n

       while [ $m -ge 2 ]; do

               for j in `seq 2 $m`; do

                       if [ ${arr[$j]} -lt ${arr[$[j-1]]} ];then

                               tmp=${arr[$j]}

                               arr[$j]=${arr[$[j-1]]}

                               arr[$[j-1]]=$tmp

                       fi

               done

               let m--

       done

       if [ $opt == a ];then

               echo ascending sort:

               for i in `seq 1 $n`; do

                       echo $i: ${arr[$i]}

               done

       elif [ $opt == d ];then

               echo descending sort:

               declare -a d_arr

               for i in `seq 1 $n`; do

                       d_arr[$i]=${arr[$[n-i+1]]}

                       echo $i: ${d_arr[$i]}

               done

       else

               :

       fi

       输出结果如下:

       [[email protected] bin]#./bubble_sort.sh

       Please input the length for the array: 8

       create a random array:

       1: 13912

       2: 31881

       3: 4133

       4: 3167

       5: 12977

       6: 6632

       7: 2506

       8: 16781

       Please input a or d to ascending sort or descending sort: d

       descending sort:

       1: 31881

       2: 16781

       3: 13912

       4: 12977

       5: 6632

       6: 4133

       7: 3167

       8: 2506

      

       2、将下图所示,实现转置矩阵matrix.sh

       1 2 3     1 4 7

       4 5 6 ===> 2 5 8

       7 8 9     3 6 9

       #!/bin/bash

       #FileName:  matrix.sh

       # 提示输入矩阵大小n

       declare -A matrix

       while true; do

               read -p "Please input the size of the matrix: " n

               [[ $n =~ ^[1-9][0-9]?+$ ]] && break

       done

       n=$[n-1]

       # 生成并打印矩阵

       for i in `eval echo {0..$n}`; do

               for j in `eval echo {0..$n}`; do

                       matrix[$i$j]=$[i*(n+1)+j+1]

                       echo -e "${matrix[$i$j]} \c"

               done

               echo

       done

       # 开始转置矩阵

       echo -e "\nswitch the matrix: \n"

       declare tmp

       for i in `eval echo {1..$n}`; do

               for j in `eval echo {0..$[i-1]}`; do

                       tmp=${matrix[$i$j]}

                       matrix[$i$j]=${matrix[$j$i]}

                       matrix[$j$i]=$tmp

               done

       done

       for i in `eval echo {0..$n}`; do

               for j in `eval echo {0..$n}`; do

                       echo -e "${matrix[$i$j]} \c"

               done

               echo

       done

       输出结果如下:

       [[email protected] bin]#./matrix.sh

       Please input the size of the matrix: 3

       1 2 3

       4 5 6

       7 8 9

       switch the matrix:

       1 4 7

       2 5 8

       3 6 9

      

字符串切片

       ${#var}:返回字符串变量var的长度

       ${var:offset}:返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,offset的取值在0 ${#var}-1 之间(bash4.2后,允许为负值)

       ${var:offset:number}:返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分

       ${var: -length}:取字符串的最右侧几个字符

              注意:冒号后必须有一空白字符

       ${var:offset:-length}:从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容

       ${var: -length:-offset}:先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容

              注意:-length前空格

       注:这段代码有版本要求,7上有效,6上无效

      

字符串处理

       基于模式取子串

              ${var#*word}:其中word可以是指定的任意字符

              功能:自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符之间的所有字符

              ${var##*word}:同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容

       示例:

              file=“var/log/messages”

              ${file#*/}: log/messages

              ${file##*/}: messages

      

${var%word*}:其中word可以是指定的任意字符

              功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符之间的所有字符

              file="/var/log/messages"

              ${file%/*}: /var/log

       ${var%%word*}:同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符;

       示例:

              url=http://www.51cto.com:80

              ${url##*:} 80

              ${url%%:*} http

      

查找替换

              ${var/pattern/substr}:查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之

              ${var//pattern/substr}: 查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之

              ${var/#pattern/substr}:查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之

              ${var/%pattern/substr}:查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之

      

查找并删除

              ${var/pattern}:删除var所表示的字符串中第一次被pattern所匹配到的字符串

              ${var//pattern}:删除var所表示的字符串中所有被pattern所匹配到的字符串

              ${var/#pattern}:删除var所表示的字符串中所有以pattern为行首所匹配到的字符串

              ${var/%pattern}:删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串

       字符大小写转换

              ${var^^}:把var中的所有小写字母转换为大写

              ${var,,}:把var中的所有大写字母转换为小写

      

变量赋值

      

      

高级变量用法-有类型变量

       Shell变量一般是无类型的,但是bash Shell提供了declaretypeset两个命令用于指定变量的类型,两个命令是等价的

       declare [选项] 变量名

              -r 声明或显示只读变量

              -i 将变量定义为整型数

              -a 将变量定义为数组

              -A 将变量定义为关联数组

              -f 显示已定义的所有函数名及其内容

              -F 仅显示已定义的所有函数名

              -x 声明或显示环境变量和函数

              -l 声明变量为小写字母 declare -l var=UPPER

              -u 声明变量为大写字母 declare -u var=lower

             

eval命令

       eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量.该命令对变量进行两次扫描

       示例:

              [[email protected] ~]# CMD=whoami

              [[email protected] ~]# echo $CMD

              whoami

              [[email protected] ~]# eval $CMD

              root

              [[email protected] ~]# n=10

              [[email protected] ~]# echo {0..$n}

              {0..10}

              [[email protected] ~]# eval echo {0..$n}

              0 1 2 3 4 5 6 7 8 9 10

             

间接变量引用

       如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用

       variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获得变量值value的行为

       variable1=variable2

       variable2=value

      

       bash Shell提供了两种格式实现间接变量引用

              eval tempvar=\$$variable1

              tempvar=${!variable1}

       示例:

              [[email protected] ~]# N=NAME

              [[email protected] ~]# NAME=q

              [[email protected] ~]# N1=${!N}

              [[email protected] ~]# echo $N1

              q

              [[email protected] ~]# eval N2=\$$N

              [[email protected] ~]# echo $N2

              q

      

创建临时文件

       mktemp命令:创建并显示临时文件,可避免冲突

       mktemp [OPTION]... [TEMPLATE]

              TEMPLATE: filenameXXX

                     X至少要出现三个

       OPTION

              -d: 创建临时目录

              -p DIR--tmpdir=DIR:指明临时文件所存放目录位置

       示例:

              mktemp /tmp/testXXX

              tmpdir=`mktemp -d /tmp/testdirXXX`

              mktemp --tmpdir=/testdir testXXXXXX

      

安装复制文件

       install命令:

              install [OPTION]... [-T] SOURCE DEST 单文件

              install [OPTION]... SOURCE... DIRECTORY

              install [OPTION]... -t DIRECTORY SOURCE...

              install [OPTION]... -d DIRECTORY...创建空目录

       选项:

              -m MODE,默认755

              -o OWNER

              -g GROUP

       示例:

              install -m 700 -o wang -g admins srcfile desfile

              install -m 770 -d /testdir/installdir

      

expect介绍

       expect 是由Don Libes基于Tcl Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助Expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率

      

expect命令

       expect 语法:

              expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]

       选项

              -c:从命令行执行expect脚本,默认expect是交互地执行的

              示例:expect -c 'expect "\n" {send "pressed enter\n"}'

              -d:可以输出输出调试信息

              示例:expect -d ssh.exp

       expect中相关命令

              spawn:启动新的进程

              send:用于向进程发送字符串

              expect:从进程接收字符串

              interact:允许用户交互

              exp_continue 匹配多个字符串在执行动作后加此命令

      

expect

       expect最常用的语法(tcl语言:模式-动作)

       单一分支模式语法:

              expect "hi" {send "You said hi\n"}

              匹配到hi后,会输出“you said hi”,并换行

       多分支模式语法:

              expect "hi" { send "You said hi\n" } \

              "hehe" { send "Hehe yourself\n" } \

              "bye" { send "Good bye\n" }

              匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:

              expect {

              "hi" { send "You said hi\n"}

              "hehe" { send "Hehe yourself\n"}

              "bye" { send "Good bye\n"}

              }

      

示例

       #!/usr/bin/expect

       spawn scp /etc/fstab 192.168.8.100:/app

       expect {

                     "yes/no" { send "yes\n";exp_continue }

                     "password" { send “111111\n" }

       }

       expect eof

      

示例

       #!/usr/bin/expect

       spawn ssh 192.168.8.100

       expect {

                     "yes/no" { send "yes\n";exp_continue }

                     "password" { send “111111\n" }

       }

       interact

       #expect eof

      

示例:变量

       #!/usr/bin/expect

       set ip 192.168.8.100

       set user root

       set password 111111

       set timeout 10

       spawn ssh [email protected]$ip

       expect {

                     "yes/no" { send "yes\n";exp_continue }

                     "password" { send "$password\n" }

       }

       interact

      

示例:位置参数

       #!/usr/bin/expect

       set ip [lindex $argv 0]

       set user [lindex $argv 1]

       set password [lindex $argv 2]

       spawn ssh [email protected]$ip

       expect {

                     "yes/no" { send "yes\n";exp_continue }

                     "password" { send "$password\n" }

       }

       interact

       #./ssh3.exp 192.168.8.100 root 111111

      

示例:执行多个命令

       #!/usr/bin/expect

       set ip [lindex $argv 0]

       set user [lindex $argv 1]

       set password [lindex $argv 2]

       set timeout 10

       spawn ssh [email protected]$ip

       expect {

                     "yes/no" { send "yes\n";exp_continue }

                     "password" { send "$password\n" }

       }

       expect "]#" { send "useradd haha\n" }

       expect "]#" { send "echo 111111 |passwd --stdin haha\n" }

       send "exit\n"

       expect eof

       #./ssh4.exp 192.168.8.100 root 111111

      

示例:shell脚本调用expect

       #!/bin/bash

       ip=$1

       user=$2

       password=$3

       expect <<EOF

       set timeout 10

       spawn ssh [email protected]$ip

       expect {

                     "yes/no" { send "yes\n";exp_continue }

                     "password" { send "$password\n" }

       }

       expect "]#" { send "useradd hehe\n" }

       expect "]#" { send "echo 111111 |passwd --stdin hehe\n" }

              expect "]#" { send "exit\n" }

              expect eof

       EOF

       #./ssh5.sh 192.168.8.100 root 111111

      

实验:批量自动创建用户

       #!/bin/bash

       net=192.168.0

       user=root

       password=111111

       for i in {1..100}; do {                                                      

              expect <<-EOF

              spawn ssh [email protected]$net.$i

              expect {

                     "yes/no" { send "yes\n";exp_continue }

                     "password" { send "$password\n" }

             }

             expect "]#" { send "useradd haha\n" }

             expect "]#" { send "echo 111111 | passwd --stdin haha\n" }

             expect "]#" { send "exit\n" }

             expect eof

              EOF

              } &

       done

       wait      


以上是关于Linux-Shell脚本编程进阶的主要内容,如果未能解决你的问题,请参考以下文章

linux-shell编程

Linux-shell脚本基础

Linux-shell编程

Linux-Shell编程

Linux-shell编程入门

linux-shell编程基础