天天看點

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

  雙分支:

   else

    if-false

  多分支:

   if CONDITION1; then

   elif CONDITION2; then

    if-ture

   elif CONDITION3; then

   …

   esle

    all-false

  逐條件進行判斷,第一次遇為“真”條件時,執行其分支,而後結束;

  示例:使用者鍵入檔案路徑,腳本來判斷檔案類型;

   #!/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

  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]\t"
       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]\t"
     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

  進入條件: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]\t"
          let i++
      done
      echo
      let i=1
      let j++
  done
      

 循環控制語句(用于循環體中):

  continue [N]:提前結束第N層的本輪循環,而直接進入下一輪判斷;

   while CONDTIITON1; do

    CMD1

    …

    if CONDITION2; then

     continue

    fi

    CMDn

  break [N]:提前結束循環;     

     break

  示例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

  until false; do

  示例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`\t"
                   echo "uid: `echo $line | cut -d: -f3 `"
           fi
   done < /etc/passwd 
      

 for循環的特殊格式:

  for ((控制變量初始化;條件判斷表達式;控制變量的修正表達式)); do

  控制變量初始化:僅在運作到循環代碼段時執行一次;

  控制變量的修正表達式:每輪循環結束會先進行控制變量修正運算,而後再做條件判斷;

  示例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]\t"
           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

   ${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