Bash脚本编程总结

bash脚本编程之用户交互: 

read [option]… [name …]
  -p ‘PROMPT’
  -t TIMEOUT
 bash -n /path/to/some_script
  检测脚本中的语法错误
 bash -x /path/to/some_script
  调试执行
 示例:  

#!/bin/bash
  # Version: 0.0.1
  # Author: mrlapulga
  # Description: read testing
  read -p "Enter a disk special file: " diskfile
  [ -z "$diskfile" ] && echo "Fool" && exit 1
  if fdisk -l | grep "^Disk $diskfile" &> /dev/null; then
      fdisk -l $diskfile
  else
      echo "Wrong disk special file."
      exit 2
  fi

bash脚本编程1: 

 CONDITION:
  bash命令:
   用命令的执行状态结果;
    成功:true
    失败:flase
   成功或失败的意义:取决于用到的命令;
 if语句:
  单分支:
   if CONDITION; then
    if-true
   fi
  双分支:
   if CONDITION; then
    if-true
   else
    if-false
   fi
  多分支:
   if CONDITION1; then
    if-true
   elif CONDITION2; then
    if-ture
   elif CONDITION3; then
    if-ture
   …
   esle
    all-false
   fi
  逐条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束;
  示例:用户键入文件路径,脚本来判断文件类型;
   #!/bin/bash
   #
   read -p "Enter a file path: " filename
   if [ -z "$filename" ]; then
       echo "Usage: Enter a file path."
       exit 2
   fi
   if [ ! -e $filename ]; then
       echo "No such file."
       exit 3
   fi
   if [ -f $filename ]; then
       echo "A common file."
   elif [ -d $filename ]; then
       echo "A directory."
   elif [ -L $filename ]; then
       echo "A symbolic file."
   else
       echo "Other type."
   fi


  注意:if语句可嵌套;
 循环:for
  循环体:要执行的代码;可能要执行n遍;
   进入条件:
   退出条件:
  for循环:
   for 变量名  in 列表; do
    循环体
   done
   执行机制:
    依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束;
   示例:添加10个用户, user1-user10;密码同用户名;

    #!/bin/bash
    #
    if [ ! $UID -eq 0 ]; then
        echo "Only root."
        exit 1
    fi
    for i in {1..10}; do
        if id user$i &> /dev/null; then
       echo "user$i exists."
        else
         useradd user$i
     if [ $? -eq 0 ]; then
         echo "user$i" | passwd --stdin user$i &> /dev/null
                echo "Add user$i finished."
            fi
        fi
    done


   列表生成方式:
    (1) 直接给出列表;
    (2) 整数列表:
     (a) {start..end}
     (b) $(seq [start [step]] end)
    (3) 返回列表的命令;
     $(COMMAND)
    (4) glob
    (b) 变量引用;
     $@, $*
   示例:判断某路径下所有文件的类型 

    #!/bin/bash
    #
    for file in $(ls /var); do
        if [ -f /var/$file ]; then
     echo "Common file."
        elif [ -L /var/$file ]; then
     echo "Symbolic file."
        elif [ -d /var/$file ]; then
     echo "Directory."
        else
     echo "Other type."
        fi
    done


   示例:

    #!/bin/bash
    #
    declare -i estab=0
    declare -i listen=0
    declare -i other=0
    for state in $( netstat -tan | grep "^tcp>" | awk '{print $NF}'); do
        if [ "$state" == 'ESTABLISHED' ]; then
     let estab++
        elif [ "$state" == 'LISTEN' ]; then
     let listen++
        else
     let other++
        fi
    done
    echo "ESTABLISHED: $estab"
    echo "LISTEN: $listen"
    echo "Unkown: $other"

bash脚本编程2:

 编程语言:
  数据结构
  顺序执行
  选择执行
   条件测试
    运行命令或[[ EXPRESSION ]]
     执行状态返回值;
   if
   case
  循环执行
   将某代码段重复运行多次;
   重复运行多少次?
    循环次数事先已知:
    循环次数事先未知;
    必须有进入条件和退出条件:
   for, while, until
  函数:结构化编程及代码重用;
   function
 for循环语法:
  for NAME in LIST; do
   循环体
  done
  列表生成方式:
   (1) 整数列表
    {start..end}
    $(seq start [[step]end])
   (2) glob
    /etc/rc.d/rc3.d/K*
   (3) 命令
  示例:通过ping命令探测172.16.250.1-254范围内的所有主机的在线状态;
   #!/bin/bash
   #
   net='172.16.250'
   uphosts=0
   downhosts=0
   for i in {1..20}; do
       ping -c 1 -w 1 ${net}.${i} &> /dev/null
       if [ $? -eq 0 ]; then
    echo "${net}.${i} is up."
           let uphosts++
       else
    echo "${net}.${i} is down."
           let downhosts++
       fi
   done
       
   echo "Up hosts: $uphosts."
   echo "Down hosts: $downhosts."


 while循环:
  while CONDITION; do
   循环体
  done
  CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;
   条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环;
   因此:CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正;
  示例:求100以内所有正整数之和;

   #!/bin/bash
   #
   declare -i sum=0
   declare -i i=1
   while [ $i -le 100 ]; do
       let sum+=$i
       let i++
   done
   echo "$i"
   echo "Summary: $sum."
  练习:添加10个用户
   user1-user10
   #!/bin/bash
   #
   declare -i i=1
   declare -i users=0
   while [ $i -le 10 ]; do
       if ! id user$i &> /dev/null; then
    useradd user$i
      echo "Add user: user$i."
           let users++
       fi
       let i++
   done
   echo "Add $users users."
  练习:通过ping命令探测172.16.250.1-254范围内的所有主机的在线状态;(用while循环)
   #!/bin/bash
   #
   declare -i i=1
   declare -i uphosts=0
   declare -i downhosts=0
   net='172.16.250'
   while [ $i -le 20 ]; do
       if ping -c 1 -w 1 $net.$i &> /dev/null; then
     echo "$net.$i is up."
     let uphosts++
       else
     echo "$net.$i is down."
     let downhosts++
       fi
       let i++
   done
   echo "Up hosts: $uphosts."
   echo "Down hosts: $downhosts."
  练习:打印九九乘法表;(分别使用for和while循环实现)
   #!/bin/bash
   #
   for j in {1..9}; do
       for i in $(seq 1 $j); do
     echo -e -n "${i}X${j}=$[$i*$j]	"
       done
       echo
   done   
   #!/bin/bash
   #
   declare -i i=1
   declare -i j=1
   while [ $j -le 9 ]; do
       while [ $i -le $j ]; do
     echo -e -n "${i}X${j}=$[$i*$j]	"
     let i++
       done
       echo
       let i=1
       let j++
   done
    练习:利用RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大者和最小者;
   #!/bin/bash
   #
   declare -i max=0
   declare -i min=0
   declare -i i=1
   while [ $i -le 9 ]; do
       rand=$RANDOM
       echo $rand
       if [ $i -eq 1 ]; then
     max=$rand
     min=$rand
       fi
       if [ $rand -gt $max ]; then
     max=$rand
       fi
       if [ $rand -lt $min ]; then
     min=$rand
       fi
       let i++
   done
   echo "MAX: $max."
   echo "MIN: $min."

bash脚本编程3:

 while CONDITION; do
  循环体
 done
  进入条件:CONDITION为true;
  退出条件:false
 until CONDITION; do
  循环体
 done
  进入条件:false
  退出条件:true
 示例:求100以内所正整数之和:
  #!/bin/bash
  #
  declare -i i=1
  declare -i sum=0
  until [ $i -gt 100 ]; do
      let sum+=$i
      let i++
  done
  echo “Sum: $sum” 
 示例:打印九九乘法表
  #!/bin/bash
  #
  declare -i j=1
  declare -i i=1
  until [ $j -gt 9 ]; do
      until [ $i -gt $j ]; do
    echo -n -e "${i}X${j}=$[$i*$j]	"
          let i++
      done
      echo
      let i=1
      let j++
  done


 循环控制语句(用于循环体中):
  continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;
   while CONDTIITON1; do
    CMD1
    …
    if CONDITION2; then
     continue
    fi
    CMDn
    …
   done
  break [N]:提前结束循环;     
   while CONDTIITON1; do
    CMD1
    …
    if CONDITION2; then
     break
    fi
    CMDn
    …
   done
  示例1:求100以内所有偶数之和;要求循环遍历100以内的所正整数;

   #!/bin/bash
   #
   declare -i i=0
   declare -i sum=0
   until [ $i -gt 100 ]; do
       let i++
       if [ $[$i%2] -eq 1 ]; then
     continue
       fi
       let sum+=$i
   done
   echo "Even sum: $sum"


 创建死循环:
  while true; do
   循环体
  done
  until false; do
   循环体
  done
  示例2:每隔3秒钟到系统上获取已经登录的用户的信息;如果docker登录了,则记录于日志中,并退出;

   #!/bin/bash
   #
   read -p "Enter a user name: " username
   while true; do
       if who | grep "^$username" &> /dev/null; then
     break
       fi
       sleep 3
   done
   echo "$username logged on." >> /tmp/user.log      
   第二种实现:
   #!/bin/bash
   #
   read -p "Enter a user name: " username
   until who | grep "^$username" &> /dev/null; do
       sleep 3
   done
   echo "$username logged on." >> /tmp/user.log


 while循环的特殊用法(遍历文件的每一行):
  while read line; do
   循环体
  done < /PATH/FROM/SOMEFILE
  依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line:
  示例:找出其ID号为偶数的所有用户,显示其用户名及ID号;

   #!/bin/bash
   #
   while read line;do
           if [ $[`echo $line | cut -d: -f3` % 2] -eq 0 ];then
                   echo -e -n "username: `echo $line | cut -d: -f1`	"
                   echo "uid: `echo $line | cut -d: -f3 `"
           fi
   done < /etc/passwd 


 for循环的特殊格式:
  for ((控制变量初始化;条件判断表达式;控制变量的修正表达式)); do
   循环体
  done
  控制变量初始化:仅在运行到循环代码段时执行一次;
  控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断;
  示例1:求100以内所正整数之和;

   #!/bin/bash
   #
   declare -i sum=0
   for ((i=1;i<=100;i++)); do
       let sum+=$i
   done
   echo "Sum: $sum."
  示例2:打印九九乘法表;
   #!/bin/bash
   #
   for((j=1;j<=9;j++));do
           for((i=1;i<=j;i++))do
               echo -e -n "${i}X${j}=$[$i*$j]	"
           done
           echo
   done


  练习:写一个脚本,完成如下任务
   (1) 显示一个如下菜单:
    cpu) show cpu information;
    mem) show memory information;
    disk) show disk information;
    quit) quit
   (2) 提示用户选择选项;
   (3) 显示用户选择的内容;

    #!/bin/bash
    #
    cat << EOF
    cpu) show cpu information;
    mem) show memory information;
    disk) show disk information;
    quit) quit
    ============================
    EOF
    read -p "Enter a option: " option
    while [ "$option" != 'cpu' -a "$option" != 'mem' -a "$option" != 'disk' -a "$option" != 'quit' ]; do
        read -p "Wrong option, Enter again: " option
    done
    if [ "$option" == 'cpu' ]; then
        lscpu
    elif [ "$option" == 'mem' ]; then
        cat /proc/meminfo
    elif [ "$option" == 'disk' ]; then
        fdisk -l
    else
        echo "Quit"
        exit 0
    fi


   进一步地:
    用户选择,并显示完成后不退出脚本;而是提示用户继续选择显示其它内容;直到使用quit方始退出;
 条件判断:case语句
  case 变量引用 in
  PAT1)
   分支1
   ;;
  PAT2)
   分支2
   ;;
  …
  *)
   默认分支
   ;;
  esac
  case支持glob风格的通配符:
   *: 任意长度任意字符;
   ?: 任意单个字符;
   []:指定范围内的任意单个字符;
   a|b: a或b
  示例:

   #!/bin/bash
   #
   cat << EOF
   cpu) show cpu information;
   mem) show memory information;
   disk) show disk information;
   quit) quit
   ============================
   EOF
   read -p "Enter a option: " option
   while [ "$option" != 'cpu' -a "$option" != 'mem' -a "$option" != 'disk' -a "$option" != 'quit' ]; do
       read -p "Wrong option, Enter again: " option
   done
   case "$option" in
   cpu)
    lscpu 
    ;;
   mem)
    cat /proc/meminfo
    ;;
   disk)
    fdisk -l
    ;;
   *)
    echo "Quit..."
    exit 0
    ;;
   esac

bash脚本编程4:

 function:函数
  过程式编程:代码重用
   模块化编程
   结构化编程
  语法一:
   function f_name {
    …函数体…
   }
  语法二:
   f_name() {
    …函数体…
   }
  调用:函数只有被调用才会执行;
   调用:给定函数名
    函数名出现的地方,会被自动替换为函数代码;
   函数的生命周期:被调用时创建,返回时终止;
    return命令返回自定义状态结果;
     0:成功
     1-255:失败
  示例:
   #!/bin/bash
   #
   function adduser {
      if id $username &> /dev/null; then
           echo "$username exists."
           return 1
      else
           useradd $username
           [ $? -eq 0 ] && echo "Add $username finished." && return 0
      fi
   }
   for i in {1..10}; do
       username=myuser$i
       adduser
   done


  示例:服务脚本

   #!/bin/bash
   #
   # chkconfig: - 88 12
   # description: test service script
   #
   prog=$(basename $0)
   lockfile=/var/lock/subsys/$prog
   start() {
       if [ -e $lockfile ]; then
    echo "$prog is aleady running."
    return 0
       else
    touch $lockfile
    [ $? -eq 0 ] && echo "Starting $prog finished."
       fi
   }
   stop() {
       if [ -e $lockfile ]; then
    rm -f $lockfile && echo "Stop $prog ok."
       else
    echo "$prog is stopped yet."
       fi
   }
   status() {
       if [ -e $lockfile ]; then
    echo "$prog is running."
       else
    echo "$prog is stopped."
       fi
   }
   usage() {
       echo "Usage: $prog {start|stop|restart|status}"
   }
   if [ $# -lt 1 ]; then
       usage
       exit 1
   fi   
   case $1 in
   start)
    start
    ;;
   stop)
    stop
    ;;
   restart)
    stop
    start
    ;;
   status)
    status
    ;;
   *)
    usage
   esac


  函数返回值:
   函数的执行结果返回值:
    (1) 使用echo或print命令进行输出;
    (2) 函数体中调用命令的执行结果;
   函数的退出状态码:
    (1) 默认取决于函数体中执行的最后一条命令的退出状态码;
    (2) 自定义退出状态码:
     return
  函数可以接受参数:
   传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;例如“testfunc arg1 arg2 …”
   在函数体中当中,可使用$1, $2, …调用这些参数;还可以使用$@, $*, $#等特殊变量;
  示例:添加10个用户

   #!/bin/bash
   #
   function adduser {
      if [ $# -lt 1 ]; then
    return 2
    # 2: no arguments
      fi
      if id $1 &> /dev/null; then
    echo "$1 exists."
    return 1
      else 
    useradd $1
    [ $? -eq 0 ] && echo "Add $1 finished." && return 0
      fi
   }
   for i in {1..10}; do
       adduser myuser$i
   done


  变量作用域:
   本地变量:当前shell进程;为了执行脚本会启动专用的shell进程;因此,本地变量的作用范围是当前shell脚本程序文件;
   局部变量:函数的生命周期;函数结束时变量被自动销毁;
    如果函数中有局部变量,其名称同本地变量;

   在函数中定义局部变量的方法:
    local NAME=VALUE
  函数递归:
   函数直接或间接调用自身;
    N!=N(n-1)(n-2)...1
    n(n-1)! = n(n-1)(n-2)!
   
    #!/bin/bash
    #
    fact() {
        if [ $1 -eq 0 -o $1 -eq 1 ]; then
      echo 1
        else
      echo $[$1*$(fact $[$1-1])]
        fi
    }
    fact 5


   练习:求n阶斐波那契数列;

    #!/bin/bash
    #
    fab() {
        if [ $1 -eq 1 ]; then
     echo 1
        elif [ $1 -eq 2 ]; then
     echo 1
        else
     echo $[$(fab $[$1-1])+$(fab $[$1-2])]
        fi
    }
    fab 7

bash脚本编程5:

 数组:
  变量:存储单个元素的内存空间;
  数组:存储多个元素的连续的内存空间;
   数组名
   索引:编号从0开始,属于数值索引;
    注意:索引也可支持使用自定义的格式,而不仅仅是数值格式;
       bash的数组支持稀疏格式;
   引用数组中的元素:${ARRAY_NAME[INDEX]}
  声明数组:
   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
  引用数组元素:${ARRAY_NAME[INDEX]}
   注意:省略[INDEX]表示引用下标为0的元素;
  数组的长度(数组中元素的个数):${#ARRAY_NAME[*]}, ${#ARRAY_NAME[@]}
   示例:生成10个随机数保存于数组中,并找出其最大值和最小值;
    #!/bin/bash
    #
    declare -a rand
    declare -i max=0
    for i in {0..9}; do
        rand[$i]=$RANDOM
        echo ${rand[$i]}
        [ ${rand[$i]} -gt $max ] && max=${rand[$i]}
    done
    echo "Max: $max"


   练习:写一个脚本
    定义一个数组,数组中的元素是/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." 


  引用数组中的元素:
   所有元素:${ARRAY[@]}, ${ARRAY[*]}
   数组切片:${ARRAY[@]:offset:number}
    offset: 要跳过的元素个数
    number: 要取出的元素个数,取偏移量之后的所有元素:${ARRAY[@]:offset};
  向数组中追加元素:
   ARRAY[${#ARRAY[*]}]
  删除数组中的某元素:
   unset ARRAY[INDEX]
  关联数组:
   declare -A ARRAY_NAME
   ARRAY_NAME=([index_name1]=’val1′ [index_name2]=’val2′ …)
 bash的字符串处理工具:
  字符串切片:
   ${var:offset:number}
   取字符串的最右侧几个字符:${var: -lengh}
    注意:冒号后必须有一空白字符;
  基于模式取子串:
   ${var#*word}:其中word可以是指定的任意字符;功能:自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符之间的所有字符;
   ${var##*word}:同上,不过,删除的是字符串开头至最后一次由word指定的字符之间的所有内容;
    file=”/var/log/messages”
    ${file##*/}: messages
   ${var%word*}:其中word可以是指定的任意字符;功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符之间的所有字符;
    file=”/var/log/messages”
    ${file%/*}: /var/log
   ${var%%word*}:同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符;
   示例:url=http://www.mrlapulga.com:80
    ${url##*:}
    ${url%%:*}
  查找替换:
   ${var/pattern/substi}:查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substi替换之;
   ${var//pattern/substi}: 查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substi替换之;

   ${var/#pattern/substi}:查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substi替换之;
   ${var/%pattern/substi}:查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substi替换之;
  查找并删除:
   ${var/pattern}:查找var所表示的字符串中,删除第一次被pattern所匹配到的字符串
   ${var//pattern}:
   ${var/#pattern}:
   ${var/%pattern}:
  字符大小写转换:
   ${var^^}:把var中的所有小写字母转换为大写;
   ${var,,}:把var中的所有大写字母转换为小写;
  变量赋值:
   ${var:-value}:如果var为空或未设置,那么返回value;否则,则返回var的值;
   ${var:=value}:如果var为空或未设置,那么返回value,并将value赋值给var;否则,则返回var的值;
   ${var:+value}:如果var不空,则返回value;
   ${var:?error_info}:如果var为空或未设置,那么返回error_info;否则,则返回var的值;
 为脚本程序使用配置文件:
  (1) 定义文本文件,每行定义“name=value”
  (2) 在脚本中source此文件即可
 命令:
  mktemp命令:
   mktemp [OPTION]… [TEMPLATE]
    TEMPLATE: filename.XXX
     XXX至少要出现三个;
    OPTION:
     -d: 创建临时目录;
     –tmpdir=/PATH/TO/SOMEDIR:指明临时文件目录位置;
  install命令:
        install [OPTION]… [-T] SOURCE DEST
        install [OPTION]… SOURCE… DIRECTORY
        install [OPTION]… -t DIRECTORY SOURCE…
        install [OPTION]… -d DIRECTORY…
          选项:
           -m MODE
           -o OWNER
           -g GROUP
原文地址:https://www.cnblogs.com/iuskye/p/6821068.html