写在前面:

    博客书写牢记5W1H法则:What,Why,When,Where,Who,How。


本篇主要内容:

● case语句

● function函数

● 数组

● bash内置字符串处理

● glob模式匹配扩展

● 脚本信号捕捉

● bash中使用ASCII颜色

dialog实现窗口化编程



case语句

   case $WORD in 

      pattern1 [| pattern1_1] ... )

         list1

         ;;

      pattern2 [| pattern2_1] ... )

         list2

         ;;

      * )

         default

         ;;

   esac

   补充:case语句支持glob通配符:

      *:任意长度任意字符;

      ?:任意单个字符;

      []:范围内任意单个字符;

      a|b:a或b;


function函数

   语法一:

      function f_name {

         ...函数体...

      }

         

   语法二:

      f_name() {

         ...函数体...

      }

   意义:

      代码重用,模块化、结构化编程。

   特点:

      调用执行,但需要定义被调用语句前部,否则可能无法被调用。建议将函数放到脚本首部

   生命周期:

      每次被调用时创建、返回终止。

   运行状态返回值:

      最后一条命令的执行结果;

      return #重定义的值;

   运行返回值:

      函数体内各种形式的输出语句。

   函数位置参数:

      函数可以接收位置参数变量,在函数调用时在函数名后面直接跟参数即可。

      在函数内使用$1,$2...引用函数位置参数。

      在函数内使用$*或$@引用所有参数

      在函数内使用$#引用参数个数。

   局部变量:

      作用域为函数生命周期,在函数结束时被自动销毁。

      定义方法:

         local VAR=VALUE

   函数递归:

      函数直接或间接的调用自身。如:

# 求10!。已知10!=10*9!=10*9*8!=...
fact () {
   if [ $1 -eq 0 -o $1 -eq 1 ];then
      echo 1
   else
      fact $[ $1 * $(fact $[$1-a]) ]
   fi
}
fact $1

  注意:函数递归会占用大量内存空间,影响性能,不建议使用。

#!/bin/bash
#求$1的阶乘。已知10!=10*9!=10*9*8!... 注意:0!=1 1!=1

num=$1
declare -i res=1

for i in `seq 0 $num`;do
   if [ $i -eq 0 ];then
      res=1
   elif [ $i -eq 1 ];then
      res=1
   else
      let res*=$i
   fi
done

echo "${1}! = $res"


数组:

   变量是存储单个元素内存空间;而数组是存储多个元素的连续内存空间。

   数组名:

      整个数组诸多元素共有一个数组名。

   数组索引:

      引用数组中各个元素的值:数组名[索引],索引从0开始

   注意:bash-4.x及以后版本支持自定义索引格式,而不必仅仅是数字,此种数字称为“关联数组”,如:

      week[sun]="sunday" week[we]="wendy"

   声明:

      declare -a ARR:声明索引数组;

      declare -A ARR:声明关联数组;

   数组赋值:

      (1)单个元素赋值:

         ARR[INDEX]=VALUE

         ARR=([0]="VALUE0" [5]="VALUE4" ...)

      (2)为整个数组赋值:

         ARR=("VALUE0" "VALUE1" ...)

      (3)使用read命令赋值

         read -a ARR

            各数组元素会以空格为分隔符。

   数组元素引用:

      ${ARR[INDEX]}:引用第INDEX+1个数组元素

      ${ARR[@]}:引用数组中所有元素

      ${ARR[*]}:引用数组中所有元素

      ${ARR[@]:OFFSET:NUM} 跳过前OFFSET个元素,取出其后的NUM个元素

      ${ARR[@]:OFFSET} 跳过前OFFSET个元素,取出其后所有元素

   数组长度:

      ${#ARR[@]}

      ${#ARR[*]}

   列出数组索引:

      ${!ARR[@]}

      ${!ARR[#]}

   删除数组中元素:

      unset ARR[INDEX]

   数组中追加元素:

      ARR[${#ARR[@]}]=VALUE


bash内置字符串处理:

   (详细参考man bash的Parameter Expansion章节,1000行左右)

   变量长度:

      ${#VAR} :取$VAR的字符串长度

      ${#ARR[INDEX]} :取数组ARR的索引为INDEX的元素值的字符串长度

      ${#ARR[@]} :取数组ARR的元素个数

      ${#ARR[*]} :取数组ARR的元素个数

[root@www test]# str="How are you?"
[root@www test]# declare -a arr=(1 2 3 4 5)
[root@www test]# echo ${#str}
12
[root@www test]# echo ${#arr[*]}
5
[root@www test]# echo ${#arr[@]}
5
[root@www test]# echo ${#arr[0]}
1


   变量字符串切片:

      ${VAR:OFFSET} :$VAR中,跳过前OFFSET个字符,取其后的所有字符

      ${VAR:OFFSET:LENGTH} :$VAR中,跳过前OFFSET个字符,取其后的LENGTH个

      ${VAR: -LENGTH} :$VAR中,取最后LENGTH个字符(注意空格)

[root@www test]# echo $str
How are you?
[root@www test]# echo ${str:4}
are you?
[root@www test]# echo ${str:4:3}
are
[root@www test]# echo ${str: -4}
you?


   基于模式取变量中字符:

      PATTERN仅支持glob形式模式匹配

      ${VAR#PATTERN}:$VAR中,从左到右,最短匹配PATTERN,取剩余部分

      ${VAR##PATTERN}:$VAR中,从左到右,最长匹配PATTERN,取剩余部分

      ${VAR%PATTERN}:$VAR中,从右向左,最短匹配PATTERN,取剩余部分

      ${VAR%%PATTERN}:$VAR中,从右向左,最长匹配PATTERN,取剩余部分

[root@www test]# file="/usr/bin/ls"
[root@www test]# echo ${file#*/}
usr/bin/ls
[root@www test]# echo ${file##*/}
ls
[root@www test]# echo ${file%/*}
/usr/bin
[root@www test]# echo ${file%%/*}


   变量判断赋值:

      ${parameter:-word}:返回默认值。当$parameter为空或未定义,返回word;否则返回$parameter;

      ${parameter:=word}:设置默认值。当$parameter为空或未定义,设置parameter的值为word并返回;否则返回$parameter;

      ${parameter:?word}:返回错误信息。当$parameter为空或未定义,将word以标准错误返回;否则返回$parameter;

      ${parameter:+word}:替换返回值。当$parameter为空或未定义,返回空;否则,返回word;

  #返回默认值
[root@www test]# unset file
[root@www test]# echo ${file:-"/usr/bin/ls"}
/usr/bin/ls
[root@www test]# echo $file
  #设置默认值
[root@www test]# unset file
[root@www test]# echo ${file:="/usr/bin/ls"}
/usr/bin/ls
[root@www test]# echo $file
/usr/bin/ls
[root@www test]# echo ${file:="/sbin/ss"}
/usr/bin/ls
[root@www test]# echo $file
/usr/bin/ls
  #返回错误信息
[root@www test]# unset file
[root@www test]# echo ${file:?"file is unset"}
-bash: file: file is unset
[root@www test]# echo $?
1
[root@www test]# file="/sbin/ss"
[root@www test]# echo ${file:?"file is unset"}
/sbin/ss
  #替换返回值
[root@www test]# unset file
[root@www test]# echo ${file:+"/sbin/ss"}

[root@www test]# file="/usr/bin/ls"
[root@www test]# echo ${file:+"/sbin/ss"}
/sbin/ss
[root@www test]# echo $file
/usr/bin/ls


   变量字符串替换与删除:

      ${VAR/PATTERN} :$VAR中,匹配模式PATTERN,将第一个匹配到的字符删除

      ${VAR/PATTERN/STRING} :$VAR中,匹配模式PATTERN,将第一个匹配到的字符替换为STRING

      ${VAR//PATTERN} :$VAR中,匹配模式PATTERN,将所有匹配到的字符删除

      ${VAR//PATTERN/STRING} :$VAR中,匹配模式PATTERN,将所有匹配到的字符替换为STRING

      ${VAR/#PATTERN} :$VAR中,锚定行首,匹配模式PATTERN,将第一个匹配到的字符删除

      ${VAR/#PATTERN/STRING} :$VAR中,锚定行首,匹配模式PATTERN,将第一个匹配到的字符替换为STRING

      ${VAR/%PATTERN} :$VAR中,锚定行尾,匹配模式PATTERN,将第一个匹配到的字符删除

      ${VAR/%PATTERN/STRING} :$VAR中,锚定行尾,匹配模式PATTERN,将第一个匹配到的字符替换为STRING

[root@www test]# echo $file
/usr/bin/ls
[root@www test]# echo ${file/\/usr/}
/bin/ls
[root@www test]# echo ${file/\/usr\//\/s}
/sbin/ls
[root@www test]# echo ${file/%ls/cp}
/usr/bin/cp


   字符串大小写转换:

      ${VAR^^}:把VAR中的所有小写字符转换为大写;

      ${VAR,,}:把VAR中的所有大写字符转换为小写;

      拓展:

         ${VAR^PATTERN}:第一个被PATTERN到的字符转换为大写;

         ${VAR^^PATTERN}:所有被PATTERN到的字符转换为大写;

         ${VAR,PATTERN}:第一个被PATTERN到的字符转换为小写;

         ${VAR,,PATTERN}:所有被PATTERN到的字符转换为小写;

[root@www test]# echo $file
/usr/bin/ls
[root@www test]# echo ${file^^}
/USR/BIN/LS
[root@www test]# file=${file^^}
[root@www test]# echo $file
/USR/BIN/LS
[root@www test]# echo ${file,,}
/usr/bin/ls


glob模式匹配扩展:

   pattern-list可以使用“|”分隔多个

   ?(pattern-list) :pattern-list出现0次或一次

   *(pattern-list) :pattern-list出现0次或多次

   +(pattern-list) :pattern-list出现1次或多次

   @(pattern-list) :pattern-list内任意元素出现一次(注意这里的各元素是以"|"分隔的!区别于[] )

   !(pattern-list) :排除pattern-list内元素外的其他元素

实例:

[root@www test]# touch {,file,filefile}{,10,20,0}
[root@www test]# ls
0  10  20  file  file0  file10  file20  filefile  filefile0  filefile10  filefile20
[root@www test]# ls ?(file)10
10  file10
[root@www test]# ls ?(file|1)0
0  10  file0
[root@www test]# ls +(file)0
file0  filefile0
[root@www test]# ls *(file)0
0  file0  filefile0
  #与[]不同的是,()中的元素会被当做一个整体!
[root@www test]# touch 120
[root@www test]# ls @(12)0
120
[root@www test]# ls @(file|1|2)0
10  20  file0
[root@www test]# ls !(file)10
10  filefile10


信号捕捉:

   列出信号:

      trap -l

      kill -l

      man 7 signal

   设置信号捕捉及处理:

      trap "COMMAND或FUNCTIONZ_NAME" SIGNALS 设置捕捉到信号后执行的命令或函数

   常用被捕捉的信号:

      HUPINT

   实例:

  #睡眠30秒,在睡眠期间如果用户按了Ctrl+C则显示本目录内容

[root@www shell]# cat trap 
#!/bin/bash
#

trap "ls" INT
sleep 30
[root@www shell]# ./trap 
^C0.sh         days.sh


  #ping 172.18.X.1,X为1-255,若能ping通则返回up,否则返回down,脚本运行期间,用户键入Ctrl+C则退出脚本

[root@www shell]# cat ping.sh 
#!/bin/bash
#

trap "user_exit" INT

user_exit () {
   echo -e "\b\b >>> Script stopd. <<< "
   exit 1
}

IP_HEAD="172.18."
IP=""
for (( i=1; i<=255; i++ ));do
   IP="${IP_HEAD}${i}.1"
   ping -W 1 -c 1 $IP &> /dev/null && echo " $IP is up." || echo " $IP is down."
done

[root@www shell]# ./ping.sh 
 172.18.1.1 is up.
 172.18.2.1 is down.
 >>> Script stopd. <<<


bash中使用ACSII颜色:

   \033[31mHELLO WROLD\033[0m

      输出HELLO WORLD

   \033[42;31;5mHELLO WORLD\033[0m

      输出HELLO WORLD并闪烁

   含义:

      \033[  为固定字符,表示要设置颜色信息

      ##m 

         第1个#,设置为3表示前景色,4为背景色

         第2个#,颜色类别

            1,2,3,4,5,6,7

      #m

         加粗、闪烁等功能

      补充:各项之间以“;”分隔


详细参考:http://www.jb51.net/article/48844.htm


dialog

   命令可实现窗口化编程;

   各窗体控件使用方式;

   如何获取用户选择或键入的内容?

   默认,其输出信息被定向到了错误输出流;


实例:

写一个脚本,完成如下功能

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

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

   (3) 复制命令至某目标目录(例如/mnt/sysroot,即把此目录当作根)下的对应的路径中

      bash,  /bin/bash  ==> /mnt/sysroot/bin/bash

      useradd, /usr/sbin/useradd  ==>  /mnt/sysroot/usr/sbin/useradd

   (4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下;

      /lib64/ld-linux-x8664.so.2  ==>  /mnt/sysroot/lib64/ld-linux-x8664.so.2

   进一步:

      每次复制完成一个命令后,不要退出,而是提示用户继续输入要复制的其它命令,并重复完成如上所描述的功能;直到用户输入“quit”退出脚本;

[root@www shell]# cat sysroot.sh
#!/bin/bash
# 说明:用户给出一个命令,将命令文件和命令所依赖的函数库拷贝到指定目录,这里设置为/mnt/sysroot
# exit 4: command not found

#定义用户命令变量comm,生成临时文件,设置并创建目标目录
declare comm=""
sysroot="/mnt/sysroot/"
mkdir -p /mnt/sysroot &> /dev/null
script_name=`basename $0`
workingdir="`pwd`/"
tempfile_ldd_print=`mktemp ${script_name}.tempfile.XXXXX`
tempfile_ldd_des=`mktemp ${script_name}.tempfile.XXXXX`


#定义函数,将用户命令传值过来进行处理,完成cp命令和库文件操作
cp_command () {
   local com="$1"
   local com_path=`which $com --skip-alias`
   #获取命令cp到的目的路径,并判断是否存在,不存在,则添加
   local com_des_dir="$sysroot`dirname $com_path | cut -c 2-`"
   ! [ -d $com_des_dir ] && mkdir -p $com_des_dir && echo "$com_dis_dir not exist, created."
   cp -p $com_path $com_des_dir && echo " -- copy $com_path ==> $com_des_dir"

   #将ldd的输出过滤出包含路径的行并写入到临时文件,等待进一步处理
   ldd $com_path | grep '/' > $tempfile_ldd_print
   #循环语句,对ldd输出的临时文件的每一行进行处理
   > $tempfile_ldd_des
   while read line;do
      #去除行中/前的字符,写入到新的文件
      echo "${line#*/}" >> $tempfile_ldd_des
   done < $tempfile_ldd_print
   #循环语句,对上一步处理得到的临时文件继续处理,以得到路径,cp库文件到指定目录 #由于得到的路径首部/被去除,所以需要先cd到/
   cd /
   while read line;do
      #去除(后面的内容,得到的内容即为库文件路径,写入数组
      lib_path=${line%(*}
      des_file="$sysroot$lib_path"
      #得到lib文件cp到的目的路径,检查是否存在,不存在则建立
      des_path=`dirname $des_file`
      ! [ -d $des_path ] && mkdir -p $des_path && echo "$des_path not exist, created."
      cp -f $lib_path $des_path && echo " -- copy /$lib_path ==> $des_path"
   done < $workingdir$tempfile_ldd_des
   echo "-->>> SUCCESS! <<<-- \"$comm\" command copy complite."
   echo "---------------------------------------"
}

#定义函数,清除创建的临时文件
clear_tempfile () {
   rm -f $workingdir$tempfile_ldd_print
   rm -f $workingdir$tempfile_ldd_des
}

#循环提示用户输入命令,并完成操作,直到用户输入quit退出
while true;do
   read -p "Please input command [\"quit\" to exit]: " comm
   echo $comm | grep "[[:space:]]" > /dev/null && echo " >>> Only 1 args shoud be gaven,if there is a space in your command,please use \"\"." && continue
   [[ "$comm" == "quit" ]] && clear_tempfile && exit 0
   ! which $comm --skip-alias &> /dev/null && echo " >>> command \"$comm\" not found,please check" && continue
   cp_command $comm
done
[root@www shell]# ./sysroot.sh 
Please input command ["quit" to exit]: cp
 -- copy /usr/bin/cp ==> /mnt/sysroot/usr/bin
 -- copy /lib64/libselinux.so.1  ==> /mnt/sysroot/lib64
 -- copy /lib64/libacl.so.1  ==> /mnt/sysroot/lib64
 -- copy /lib64/libattr.so.1  ==> /mnt/sysroot/lib64
 -- copy /lib64/libc.so.6  ==> /mnt/sysroot/lib64
 -- copy /lib64/libpcre.so.1  ==> /mnt/sysroot/lib64
 -- copy /lib64/liblzma.so.5  ==> /mnt/sysroot/lib64
 -- copy /lib64/libdl.so.2  ==> /mnt/sysroot/lib64
 -- copy /lib64/ld-linux-x86-64.so.2  ==> /mnt/sysroot/lib64
 -- copy /lib64/libpthread.so.0  ==> /mnt/sysroot/lib64
-->>> SUCCESS! <<<-- "cp" command copy complite.
---------------------------------------
Please input command ["quit" to exit]: sdlkjf
 >>> command "sdlkjf" not found,please check
Please input command ["quit" to exit]: cp ls mv        
 >>> Only 1 args shoud be gaven,if there is a space in your command,please use "".
Please input command ["quit" to exit]: quit
[root@www shell]#