bash脚本编程

case选择分支结构
case 词 in [模式 [| 模式]...) 命令 ;;]... esac
  在脚本中使用的case结构:
    case $(VAR-NAME) in
      PATTERN1)
        COMMAND
        ...
        ;;
      PATTERN2)
        COMMAND
        ...
        ;;
      ...
    esac

  PATTERN可以是下列几类字符:
    1.普通文本字符
    2.GLOBBING风格的通配符
      *:任意长度的任意字符
      ?:任意单个字符
      []:指定范围内的任意单个字符
      [^]:指定范围以外的任意单个字符
    3. |  或字符

eg: 写一个脚本:
    判断用户利用脚本删除某文件时,是否执行删除操作;
#!/bin/bash
#
if [ -e $1 ] ; then
  echo -e "\033[5;1;31mDanger!\033[1;31mAre you sure to delete it? [yes or no] \033[0m"
  read -t 5 CHOICE
  [ -z $CHOICE ] && CHOICE=no
  case $CHOICE in
    yes)
      rm -rf $1
      ;;
    no)
      echo "Right choice."
      ;;
  esac
else
  echo "$1 does not exist."
fi

#!/bin/bash
#
if [ -e $1 ] ; then
  echo -e "\033[5;1;31mDanger!\033[0m\033[1;31mAre you sure to delete it? [yes or no] \033[0m"
  read -t 5 CHOICE
  [ -z $CHOICE ] && CHOICE=no
  if [ "$CHOICE" == 'yes' ] ; then
    rm -rf $1
  elif [ "$CHOICE" == 'no' ] ; then
    echo "Right choice."
  fi
else
  echo "$1 does not exist."
fi




if的多分支结构和case的选择分支结构的异同:
  相同点:
    1.判断的条件为真时才会执行对应分支中的语句;条件为假则不执行;
    2.都可以设置默认分支语句;即:所有给定的条件的判断结果都为假时,执行的语句;
  不同点:
    1.if是根据命令的执行状态返回值的真假来判断是否执行某个分支中的语句;
      case是根据某个变量中保存的值与指定模式匹配的结果为真假来判断是否执行某个分支中的语句;
    2.if的每个分支中无需单独的结束标记,case的每个分支都必须以;;结束;


编写管理用户账户的脚本,第四版,利用case语句+for循环,同时接受创建和删除用户的操作;

#!/bin/bash
#
if [ $# -lt 1 ] ; then
  echo -e "Usage: $(basename $0) options... USERLIST\n"
  echo -e "  Options: "
  echo -e "    -a, --add: \vAdd some users from USERLIST."
  echo -e "    -d, --delete: \vDelete some users from USERLIST."
  echo -e "    -h, --help: \vPrint help informationn."
  echo -e "    -v, --verbose: \vPrint more informationn about manage users."
  echo
  echo -e "  USERLIST FORMAT: "
  echo -e "    USERNAME1,USERNAME2,...,USERNAMEN"
  exit 5
fi

ADDUSER=0
DELUSER=0
DEBUG=0

for I in $(seq $#) ; do
  if [ $# -ne 0 ] ;then
    case $1 in
      -h|--help)
        echo -e "Usage: $(basename $0) options... USERLIST\n"
        echo -e "  Options: "
        echo -e "    -a, --add: \vAdd some users from USERLIST"
        echo -e "    -d, --delete: \vDelete some users from USERLIST"
        echo -e "    -h, --help: \vPrint help informationn"
        echo -e "    -v, --verbose: \vPrint more informationn about manage users."
        echo
        echo -e "  USERLIST FORMAT: "
        echo -e "    USERNAME1,USERNAME2,...,USERNAMEN"
        exit 0
        ;;
      -v|--verbose)
        DEBUG=1
        shift
        ;;
      -a|--add)
        ADDUSERLIST=$2
        ADDUSER=1
        shift 2
        ;;
      -d|--delete)
        DELUSERLIST=$2
        DELUSER=1
        shift 2
        ;;
      *)
        echo -e "Usage: $(basename $0) options... USERLIST\n"
        echo -e "  Options: "
        echo -e "    -a, --add: \vAdd some users from USERLIST"
        echo -e "    -d, --delete: \vDelete some users from USERLIST"
        echo -e "    -h, --help: \vPrint help informationn"
        echo -e "    -v, --verbose: \vPrint more informationn about manage users."
        echo
        echo -e "  USERLIST FORMAT: "
        echo -e "    USERNAME1,USERNAME2,...,USERNAMEN"
        exit 6
        ;;
    esac
  fi
done

if [ $ADDUSER -eq 1 ] ; then
  for J in $(echo $ADDUSERLIST | tr ',' ' ') ; do
    if ! id $J &> /dev/null ; then
      useradd $J &> /dev/null
      echo $J | passwd --stdin $J &> /dev/null
      [ $DEBUG -eq 1 ] && echo "Create user $J successfully."
    else
      echo "$J exist already."
    fi
  done
fi

if [ $DELUSER -eq 1 ] ; then
  for J in $(echo $DELUSERLIST | tr ',' ' ') ; do
    if id $J &> /dev/null ; then
      userdel -r $J &> /dev/null
      [ $DEBUG -eq 1 ] && echo "Delete user $J finished."
    else
      echo "$J does not exist yet."
    fi
  done
fi


while循环结构
while 命令; do 命令; done
在脚本中可以写成下列结构:
  while CONDITION  ; do
    COMMAND
  done

while循环进入循环的条件:CONDITION为真;
while循环退出循环的条件:CONDITION为假;



until循环结构
until 命令; do 命令; done
在脚本中可以写成下列结构:
  until CONDITION  ; do
    COMMAND
  done
until循环进入循环条件:CONDITION为假
until循环推出循环条件:CONDITION为真


注意:
  1.while CONDITION  相当于  until !CONDITION
  2.while和until循环结构中,没有变量自增或自减的变化方法;因此需要使用语句手动给出变量的变化方式;


写一个脚本,使用while或until循环,计算100以内整数的和;
#!/bin/bash
#
declare -i I=0
until [ $I -eq 100 ] ; do
  let I++
  let SUM+=$I
done

echo $SUM

#!/bin/bash
#
declare -i I=0
while [ $I -lt 100 ] ; do
  let I++
  let SUM+=$I
done

echo $SUM


循环控制语句:
  continue:
   continue [n]
    继续 for、while、或 until 循环。
    提前结束第N层当前循环,直接进入下一轮条件判断,如果条件判断结果仍然满足循环进入条件,则开启下一轮循环;
  break
    break [n]
    退出 for、while、或 until 循环
    提前结束第N层循环,且不再继续后续循环


while和until的两种特殊循环使用方法:
  1.无限循环方法
  while true ; do
    COMMAND
  done

  until  false ; do
    COMMAND
  done


猜数字游戏:
#!/bin/bash
#
NUMBER=$[RANDOM%100+1]
while true ; do
  read -p "Input a number: " INPTNUM
  if [ $INPTNUM -gt $NUMBER ] ; then
    echo "Too big"
  elif [ $INPTNUM -lt $NUMBER ] ; then
    echo "Too small"
  else
    echo "Yes! you WIN. That's $NUMBER."
    break
  fi
done

#!/bin/bash
#
NUMBER=$[RANDOM%100+1]
until false ; do
  read -p "Input a number: " INPTNUM
  if [ $INPTNUM -gt $NUMBER ] ; then
    echo "Too big"
  elif [ $INPTNUM -lt $NUMBER ] ; then
    echo "Too small"
  else
    echo "Yes! you WIN. That's $NUMBER."
    break
  fi
done


注意:在此类循环结构中需要适当的添加continue或break,使无限循环可控;
 
 
  2.实现遍历功能的while和until循环结构
  while  read  LINES  ;  do
    COMMADN
  done <  /PATH/FORM/SOMEFILES

  until  ! read  LINES  ;  do
    COMMADN
  done <  /PATH/FORM/SOMEFILES

注意:在做遍历循环时建议使用for;

select循环结构
 select NAME [in 词语 ... ;] do 命令; done
 select循环也是一种遍历列表的方式创建一个可视化菜单,每个列表中项都有一个数字编号与之对应,供用户选择使用,而用户只需要选择编号即可;
 select是一种默认无限循环结构;因此必须在循环体中卫select提供退出循环条件,通常可以使用break或exit命令实现;
 通常情况下,select循环会和case一起使用,以进行合理的取值判断;

 在脚本中实现格式:
  select  VAR-NAME in  LIST ; do
    COMMAND
  done
  写一个脚本,显示以/bin/bash为默认shell的用户的ID信息;
#!/bin/bash
#
select I in $(awk -F : '/\/bin\/bash$/{print $1}' /etc/passwd) quit ; do
  case $I in
  quit)
    exit
    ;;
  *)
    echo "The UID of $I is $(id -u $I)"
    ;;
  esac
done


bash脚本编程--函数
对于bash来说,函数就是由命令和语句结构构成的能够实现特定功能集合;

为什么要用函数?
在bash脚本编写过程中,有可能会出现重复且不做任何改变的代码内容,如果这类内容完全依靠原始代码书写的话,不易于排错和优化;因此,可以选择将此类代码封装在函数中,在适当的场景中可以重复调用执行;

像此类被封装起来的代码块,通常称其为模块,也可以称为函数;

注意:1.想要使用函数,必须在使用前先定义;
          2.如果在某个bash脚本中包含了函数体,默认函数体中的各命令和语句不会被执行的;只有在调用函数名的时候,才会执行函数体中的命令和语句;
          3.通常需要重复执行的代码块或命令集,可以封装成函数;
          4.被调用的函数只能在调用函数的shell中被执行;

定义函数的方法:
  函数由两部分组成:
      函数名 + 函数体
      函数名:调用函数时所使用的字符串标识;在一个执行环境中,函数名不允许重复定义;
      函数体:能够实现特定独立功能的shell命令或结构化语句块;

  定义的语法:
    语法一:
      function  function-name {
      func-body
      }
    语法二:
      func-name() {
      func-bady
      }
     注意:在语法二的格式中,func-name和()之间绝对不能有空格;

  注意:函数可以在脚本中定义,也可以在当前shell中通过交互式环境定义;


函数的使用:
  函数在定义的时候,其函数体中包含的所有命令或结构化语句都不会被执行;只有在函数被调用时,才能执行其函数体中的各命令和语句;

  调用方式:在命令行或脚本中,通过直接给出函数名的方式进行函数调用;

  通常可以将常用的函数存放在专门用于保存函数的文件中;如果想调用这个文件中已经被定义保存的函数时;只需要在命令行或脚本中使用source命令(.命令)加载文件内容到当前shell中,然后再直接使用函数名调用函数即可;

  函数的撤销:unset命令
  格式:# unset func-name
  注意:可以使用set命令查看当前已经定义生效的函数;


  函数的返回值:
    两种返回值:
      函数的执行结果返回值:
        1.在函数体中所添加的命令有标准输出;
        2.在函数体中使用echo或printf命令强制输出返回信息;
      函数的执行状态返回值:
        1.默认情况下,其状态返回值为函数体中最后一条命令的状态返回值;
        2.自定义状态返回值(退出码):
          return命令
          return [n]
          从一个 shell 函数返回。            n: 0-255(1.2.127)为系统保留的状态码;尽量不用;
          注意:在函数被调用执行时,一旦遇到return命令,则不会再继续执行函数体中其他的后续命令,立刻结束次此函数的调用执行;

      函数的生命周期:
        一般来讲,从函数被调用时开始,直到函数体中所有的命令和结构化语句全部执行完成,或者遇到return命令,则函数的调用结束;

      函数的实参:
        对于bash的函数来说,没有形参,只有实参;
        bash函数的实参是使用$1,$2...位置变量提供数据的;
        可以使用$@,$*表示全部的参数列表;
        可以使用$#计算参数的个数;

        注意:为函数提供参数时使用的位置变量,是调用函数名时在函数名后面的对应位置上的参数信息;与脚本位置参数不是一回事;

    变量:函数被调用时必须在某特定的shell中被调用,因此,函数中可以继承并识别出环境变量和由调用函数shell定义的本地变量;

        在函数中还可以定义一类局部变量:而局部变量仅在函数的生命周期内有效,因此在结束函数执行之前,应该撤销所有该函数定义的局部变量;

        局部变量的定义方式:
          local VAR-NAME=值


    变量的替换方式:
      前提:定义环境变量
#!/bin/bash
#
testvar() {
  local MYVAR=chinalink
  echo "Internal function: $MYVAR"
}
echo "Global variables: $MYVAR"
MYVAR=link
echo "External function, $MYVAR"
testvar


      函数的递归调用:
        广义:在一个函数中调用另一个函数;
        狭义:在函数体中调用函数自身;
          直接调用:func1(){
                      func1
                    }
          间接调用:func2(){
                           func1
                           }
                           func1(){
                          func2
                           }
        
  函数直接递归调用示例1:
  计算某个数字的阶乘;
利用for循环:
#!/bin/bash
#
fact=1
if [ $1 -eq 0 ] ; then
  echo "0! is $fact"
elif [ $1 -eq 1 ] ; then
  echo "1! is $fact"
else
  for I in $(seq $1) ; do
    let fact=$[fact*$I]
  done
  echo "${1}! is $fact"
fi

利用函数递归调用:
#!/bin/bash
#
fact(){
  if [ $1 -eq 0 ] || [ $1 -eq 1 ] ; then
    echo 1
  else
    echo "$[$1*$(fact $[$1-1])]"
  fi
}

echo "${1}! is $(fact $1)"

示例2:
  斐波那契数列(黄金分割数列)

  1 1 2 3 5 8 13 21 34 55 ...

  N=N-1 + N-2

#!/bin/bash
#
fabonacci(){
  if [ $1 -eq 1 ] || [ $1 -eq 2 ] ; then
    echo -n "1 "
  else
    echo -n "$[$(fabonacci $[$1-1])+$(fabonacci $[$1-2])] "
  fi
}

for I in $(seq $1) ; do
  fabonacci $I
done
echo

示例三:
  汉诺塔

#!/bin/bash
#
step=0
move(){
  let step++
  echo "$step: move disk $1 $2 --> $3"
}

hanoi(){
  if [ $1 -eq 1 ] ; then
    move $1 $2 $4
  else
    hanoi "$[$1-1]" $2 $4 $3
    move $1 $2 $4
    hanoi "$[$1-1]"  $3 $2 $4
  fi
}

hanoi $1 A B C