1. 程式人生 > 其它 >Shell指令碼程式設計

Shell指令碼程式設計

Shell歷史

  • shell:命令直譯器,作用是解釋執行使用者的命令
  • 執行命令方式:
    • 互動式:使用者輸入一條命令,Shell就解釋執行一條
    • 批處理:使用者事先編寫好一個指令碼,裡面包含多條命令,讓Shell一次性執行完這些命令
  • 版本(僅介紹3種):
    • 1、sh(Bourne Shell):最原始版本shell
    • 2、bash(Bourne Again Shell):增強版shell,是各種Linux發行版標準配置的Shell
    • 3、zsh: 命令補全功能非常強大,可以補齊路徑,補齊命令,補齊引數等
  • 內建命令:
    • cd、echo、alias、umask、exit等命令即是內建命令,凡是用which命令查不到程式檔案所在位置的命令都是內建命令。
    • 內建命令沒有單獨的man手冊,要在man手冊中檢視內建命令,應該
      man bash-builtins
    • 使用者在命令列輸入命令後,一般情況下Shell會fork一個子程序並exec該命令,然後父程序切換到後臺並等待回收子程序。但是Shell的內建命令例外,並不建立新的程序,但執行結束後也會有一個狀態碼(Exit Status),0表示成功,非0表示失敗,可以使用特殊變數$?讀取,比如:
      ls
      echo $?

執行指令碼

  • 編寫一個簡單的指令碼test.sh:
#! /bin/sh

pwd
cd ..
pwd
    • 第一行開頭#!(稱為Shebang),它表示該指令碼使用後面指定的直譯器/bin/sh解釋執行。給這個指令碼檔案加上可執行許可權(chmod a+x test.sh)後,可通過 ./test.sh執行,Shell會fork一個子程序並呼叫exec執行./test.sh這個程式。
    • 也可以通過/bin/sh test.sh去執行,這種方式不需要test.sh檔案具有可執行許可權
  • 如果將命令列下命令用()括號括起來,也會fork出一個子Shell執行小括號中的命令,一行中可以輸入由分號;隔開的多個命令,比如:
    (pwd;cd..;pwd)

    和上面shell指令碼的執行結果相同,不會改變cd ..命令改變的是子Shell的PWD,而不會影響到互動式Shell

  • 下面3種執行方式不會建立子Shell,而是直接在互動式Shell下執行,會改變互動式Shell的PWD:
    • pwd;cd..;pwd
    • source ./test.sh
    • . ./test.sh

      .和source的執行效果一樣,注意.和後面有一個空格

基本語法

  • 變數
    • 按照慣例,Shell變數由全大寫字母加下劃線組成,有兩種型別的Shell變數:
      • 環境變數
        • 環境變數可以從父程序傳給子程序,因此Shell程序的環境變數可以從當前Shell程序傳給fork出來的子程序。
        • env和printenv命令可以顯示當前Shell程序的環境變數。
      • 本地變數
        • 只存在於當前Shell程序
        • set命令可以顯示當前Shell程序中定義的所有變數(包括本地變數和環境變數)和函式
    • 定義和賦值本地變數:
      VARNAME=value
      

        注意等號兩邊都不能有空格,否則會被Shell解釋成命令和命令列引數。

    • 定義和匯出環境變數---export:
      export VARNAME=value

      也可以分兩步完成:

      VARNAME=value
      export VARNAME
    • 刪除變數
      unset VARNAME

      unset命令可以刪除已定義的環境變數或本地變數

    • 獲取變數值
      • 用${VARNAME}可以表示它的值,在不引起歧義的情況下也可以用$VARNAME表示它的值
      • 比如:
        echo $SHELL

        注意變數的值均為字串型別,如果對一個沒有定義的變數取值,則值為空

  • 檔名代換:
    • 萬用字元:*、?、[]
      • *:匹配0個或多個任意字元
      • ?:匹配一個任意字元
      • [] :匹配方括號中任意一個字元的一次出現
  • 命令代換:``或$()
    • ``或$()也是一條命令,Shell先執行該命令,然後將輸出結果立刻代換到當前命令列中,例如:
      DATE=`date`
      #DATE=$(date)
      echo $DATE
  • 算術代換:$(())
    • 用於算術計算,$(())中的Shell變數取值將轉換成整數,$(())中只能用+-*/和()運算子,並且只能做整數運算:
      VAR=10
      echo $VAR+3
      #結果:10+3
      echo $(($VAR+3))
      #結果:13
  • 轉義字元\
    • \在Shell中被用作轉義字元,用於去除緊跟其後的單個字元的特殊意義(回車除外),換句話說,緊跟其後的字元取字面值。例如:
      echo $SHELL
      #/bin/bash
      echo \$SHELL
      #$SHELL
      touch \$\ \$
      #建立檔名'$ $'的檔案
    • \還有一種用法,在\後敲回車表示續行
  • 單引號和雙引號
    • Shell指令碼中的單引號和雙引號一樣都是字串的界定符
    • 單引號用於保持引號內所有字元的字面值
    • 雙引號中如果有變數,允許變數擴充套件,與單引號不同
      DATE=$(date)
      echo '$DATE'
      # $DATE
      echo "$DATE"
      #Mon Jul 26 18:02:59 CST 2021
      echo "$DATE+10000"
      #Mon Jul 26 18:02:59 CST 2021+10000

shell指令碼語法

  • 條件測試:test或[
    • 命令test或[可以測試一個條件是否成立,如果測試結果為真,則該命令的Exit Status為0,如果測試結果為假,則命令的Exit Status為1
    • 左方括號[確實是一個命令的名字,傳給命令的各引數之間應該用空格隔開。命令test或[的引數形式是相同的,只不過test命令不需要]引數。以[命令為例,常見的測試命令如下表所示:
      [ -d DIR ]              如果DIR存在並且是一個目錄則為真
      [ -f FILE ]             如果FILE存在且是一個普通檔案則為真
      [ -z STRING ]           如果STRING的長度為零則為真
      [ -n STRING ]           如果STRING的長度非零則為真
      [ STRING1 = STRING2 ]   如果兩個字串相同則為真
      [ STRING1 != STRING2 ]  如果字串不相同則為真
      [ ARG1 OP ARG2 ]        ARG1和ARG2應該是取值為整數的變數,OP是-eq(等於)-ne(不等於)-lt(小於)-le(小於等於)-gt(大於)-ge(大於等於)之一
      [ ! EXPR ]              EXPR可以是上表中的任意一種測試條件,!表示邏輯反
      [ EXPR1 -a EXPR2 ]      EXPR1和EXPR2可以是上表中的任意一種測試條件,-a表示邏輯與
      [ EXPR1 -o EXPR2 ]      EXPR1和EXPR2可以是上表中的任意一種測試條件,-o表示邏輯或

      注意:作為一種好的Shell程式設計習慣,應該總是把變數取值放在雙引號之中。如果變數事先沒有定義,則被Shell展開為空字串,會造成測試條件的語法錯誤

    • 示例:
      python@Elite-Wang:~$ VAR=''
      python@Elite-Wang:~$ [ -z "$VAR" ]
      python@Elite-Wang:~$ echo $?
      0
      python@Elite-Wang:~$ unset VAR
      python@Elite-Wang:~$ VAR1=1
      python@Elite-Wang:~$ VAR2=2
      python@Elite-Wang:~$ test "$VAR1" -lt "$VAR2"
      python@Elite-Wang:~$ echo $?
      0
      python@Elite-Wang:~$ VAR=abc
      python@Elite-Wang:~$ [ -d downloads -a $VAR = 'abc' ]
      python@Elite-Wang:~$ echo $?
      0
      python@Elite-Wang:~$ unset VAR
      python@Elite-Wang:~$ [ -d downloads -a $VAR = 'abc' ]
      -bash: [: too many arguments
      python@Elite-Wang:~$ [ -d downloads -a "$VAR" = 'abc' ]
      python@Elite-Wang:~$ echo $?
      1
  • if/then/elif/else/fi
    • 在Shell中用if、then、elif、else、fi這幾條命令實現分支控制,例如:
      #! /bin/sh
      
      if [ -f ~/.bashrc ]; then
          . ~/.bashrc
      fi

      其實是三條命令,if [ -f ~/.bashrc ]是第一條,then . ~/.bashrc是第二條,fi是第三條。如果兩條命令寫在同一行則需要用;號隔開,一行只寫一條命令就不需要寫;號了,另外,then後面有換行,但這條命令沒寫完,Shell會自動續行,把下一行接在then後面當作一條命令處理。和[命令一樣,要注意命令和各引數之間必須用空格隔開。if命令的引數組成一條子命令,如果該子命令的Exit Status為0(表示真),則執行then後面的子命令,如果Exit Status非0(表示假),則執行elif、else或者fi後面的子命令。if後面的子命令通常是測試命令,但也可以是其它命令。Shell指令碼沒有{}括號,所以用fi表示if語句塊的結束。

    • :(冒號):是一個特殊的命令,稱為空命令,該命令不做任何事,但Exit Status總是真。此外,也可以執行/bin/true或/bin/false得到真或假的Exit Status。示例:
      #! /bin/bash
      
      if [ -f /bin/bash ]; then
          echo '/bin/bash is a file'
      else
          echo '/bin/bash is not a file'
      fi  
      if :; then
          echo 'always true'
      fi  
    • read命令:作用是等待使用者輸入一行字串,將該字串存到一個Shell變數中,示例:
      #! /bin/sh
      
      echo "Is it morning? Please answer yes or no."
      read YES_OR_NO
      if [ "$YES_OR_NO" = "yes" ]; then
        echo "Good morning!"
      elif [ "$YES_OR_NO" = "no" ]; then
        echo "Good afternoon!"
      else
        echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
      fi
  • case/esac
    • Shell指令碼的case可以匹配字串和Wildcard,每個匹配分支可以有若干條命令,末尾必須以;;結束,執行時找到第一個匹配的分支並執行相應的命令,然後直接跳到esac之後,esac表示case語句塊的結束。例如:
      #! /bin/sh
      
      echo "Is it morning? Please answer yes or no."
      read YES_OR_NO
      case "$YES_OR_NO" in
      yes|y|Yes|YES)
        echo "Good Morning!";;
      [nN]*)
        echo "Good Afternoon!";;
      *)
        echo "Sorry, $YES_OR_NO not recognized. Enter yes or no."
        exit 1;;
      esac
      exit 0
    • 使用case語句的例子可以在系統服務的指令碼目錄/etc/init.d中找到。這個目錄下的指令碼大多具有這種形式(以/etc/init.d/nfs-kernel-server為例):
      case "$1" in
          start)
              ...
          ;;
          stop)
              ...
          ;;
          reload | force-reload)
              ...
          ;;
          restart)
          ...
          *)
              log_success_msg "Usage: nfs-kernel-server {start|stop|status|reload|force-reload|restart}"
              exit 1
          ;;
      esac

      $1是一個特殊變數,在執行指令碼時自動取值為第一個命令列引數

  • for/do/done
    • for迴圈,示例:
      #! /bin/sh
      
      for FRUIT in apple banana pear; do
        echo "I like $FRUIT"
      done
      FRUIT是一個迴圈變數,第一次迴圈$FRUIT的取值是apple,第二次取值是banana,第三次取值是pear
    • 在命令列中可以寫成一行,命令間用;隔開:
      for FILENAME in `ls chap?`; do mv $FILENAME $FILENAME~; done
  • while/do/done
    • 比如一個驗證密碼的指令碼:
      #! /bin/sh
      
      echo "Enter password:"
      read TRY
      while [ "$TRY" != "secret" ]; do
        echo "Sorry, try again"
        read TRY
      done
    • 控制迴圈次數
      #! /bin/sh
      
      COUNTER=1
      while [ "$COUNTER" -lt 10 ]; do
        echo "Here we go again"
        COUNTER=$(($COUNTER+1))
      done
  • break和continue
    • break跳出整個迴圈,continue跳過本次迴圈
    • 使用者輸錯五次密碼就報錯退出:
      #! /bin/sh
      
      echo 'please input password:'
      COUNTER=0
      while :; do
          read PASSWD
          if [ "$PASSWD" = "123456" ]; then
              echo "login success!"
              break
          else
              COUNTER=$(($COUNTER+1))
              if [ "$COUNTER" -eq 5 ]; then
                  echo '5 chances are used up, login fails!'
                  break
              else
                  echo 'password error,please login again!'
              fi
          fi
  • 位置引數和特殊變數
    • 常用的位置引數和特殊變數
      $0  相當於C語言main函式的argv[0]
      $1、$2...    這些稱為位置引數(Positional Parameter)
      $#  表示引數的個數,注意這裡的#後面不表示註釋
      $@  表示引數列表"$1" "$2" ...,例如可以用在for迴圈中的in後面。
      $*  表示引數列表"$1" "$2" ...,同上
      $?  上一條命令的Exit Status
      $$  當前程序號
    • shift命令可以左移位置引數,比如shift 3表示原來的$4現在變成$1,原來的$5現在變成$2等等,原來的$1、$2、$3丟棄,$0不移動。不帶引數的shift命令相當於shift 1。例如:
      #! /bin/sh
      
      echo "$#"
      echo "$@"
      shift 2
      echo "$*"
      shift
      echo "$*"
      echo "$?"
      echo "$$"

      執行結果:

      python@Elite-Wang:~$ ./test.sh aa bb cc dd ee ff
      6
      aa bb cc dd ee ff
      cc dd ee ff
      dd ee ff
      0
      22248

Shell輸入輸出

  • echo
    • echo顯示文字行或變數,或者把字串輸入到檔案。
    • 引數:
      • -e 解析轉義字元
      • -n 不回車換行。預設情況echo回顯的內容後面跟一個回車換行。
      • 示例:
        python@Elite-Wang:~$ echo "hello world\n\n"
        hello world\n\n
        python@Elite-Wang:~$ echo -e "hello world\n\n"
        hello world
        
        
        python@Elite-Wang:~$ echo -n "hello world\n\n"
        hello world\n\npython@Elite-Wang:~$ 
  • 管道 |
    • 可以通過管道把一個命令的輸出傳遞給另一個命令做輸入。
      ls -ahl | more
      ps -aux | grep 'redis'
  • tee
    • tee命令把結果輸出到標準輸出,另一個副本輸出到相應檔案。
      ls -aux | tee test.txt
  • 檔案重定向
    cmd > file             把標準輸出重定向到新檔案中
    cmd >> file            追加
    cmd > file 2>&1        標準出錯也重定向到1所指向的file裡
    cmd >> file 2>&1
    cmd < file1 > file2    輸入輸出都定向到檔案裡
    cmd < &fd              把檔案描述符fd作為標準輸入
    cmd > &fd              把檔案描述符fd作為標準輸出
    cmd < &-               關閉標準輸入

    檔案描述符0表示標準輸入,1標準輸出,2標準錯誤

  • 函式
    • 函式定義中沒有引數列表,例如:
      #! /bin/sh
      
      foo(){ echo "Function foo is called";}
      echo "-=start=-"
      foo
      echo "-=end=-"

      注意函式體的左花括號'{'和後面的命令之間必須有空格或換行,如果將最後一條命令和右花括號'}'寫在同一行,命令末尾必須有;號。

    • Shell函式沒有引數列表並不表示不能傳引數,呼叫函式時可以傳任意個引數,在函式內同樣是用$0、$1、$2等變數來提取引數,函式中的位置引數相當於函式的區域性變數,改變這些變數並不會影響函式外面的$0、$1、$2等變數。函式中可以用return命令返回,如果return後面跟一個數字則表示函式的Exit Status。
    • 通過指令碼建立多個目錄,各目錄名通過命令列引數傳入,示例:
      #! /bin/sh
      
      is_directory()
      {
          if [ ! -d "$1" ]; then
              return 1
          else
              return 0
          fi
      }
      for DIR in "$@"; do
          if is_directory "$DIR"; then
              echo "$DIR exists"
              :
          else
              echo "create directory $DIR"
              mkdir "$DIR" > /dev/null 2>&1
              if [ $? -ne 0 ]; then
                  echo "create directory $DIR fail"
              fi
          fi
      done

      注意is_directory()返回0表示真返回1表示假。

Shell指令碼除錯

  • Shell提供了一些用於除錯指令碼的選項,如下所示:
    • -n:讀一遍指令碼中的命令但不執行,用於檢查指令碼中的語法錯誤
    • -v:一邊執行指令碼,一邊將執行過的指令碼命令列印到標準錯誤輸出
    • -x:提供跟蹤執行資訊,將執行的每一條命令和結果依次打印出來
  • 使用這些選項有三種方法:
    • 一是在命令列提供引數
      sh -x ./test.sh
    • 二是在指令碼開頭提供引數
      #! /bin/sh -x
    • 三是在指令碼中用set命令啟用或禁用引數
      #! /bin/sh
      if [ -z "$1" ]; then
        set -x
        echo "ERROR: Insufficient Args."
        exit 1
        set +x
      fi

      set -x和set +x分別表示啟用和禁用-x引數,這樣可以只對指令碼中的某一段進行跟蹤除錯。

Shell常用工具

  • grep
    • Linux系統中grep命令是一種強大的文字搜尋工具,它能使用正則表示式搜尋文字,並把匹配的行打印出來。grep全稱是Global Regular Expression Print,表示全域性正則表示式版本,它的使用許可權是所有使用者。
    • grep正則表示式的Basic規範中字元?+{}|()應解釋為普通字元,要表示上述特殊含義則需要加\轉義。如果用egrep,或者grep加上-E引數,則不需要轉義
    • 主要引數:
      grep --help
      
      [options]主要引數:
      -c:只輸出匹配行的計數。
      -i:不區分大小寫。
      -h:查詢多檔案時不顯示檔名。
      -l:查詢多檔案時只輸出包含匹配字元的檔名。
      -n:顯示匹配行及 行號。
      -s:不顯示不存在或無匹配文字的錯誤資訊。
      -v:顯示不包含匹配文字的所有行。
      --color=auto :可以將找到的關鍵詞部分加上顏色的顯示。
    • pattern正則表示式主要引數:
      \: 忽略正則表示式中特殊字元的原有含義。
      ^:匹配正則表示式的開始行。
      $: 匹配正則表示式的結束行。
      \<:從匹配正則表示式的行開始。
      \>:到匹配正則表示式的行結束。
      [ ]:單個字元,如[A]即A符合要求 。
      [ - ]:範圍,如[A-Z],即A、B、C一直到Z都符合要求 。
      .:所有的單個字元。
      *:匹配前面字元的個數,長度可以為0或多個。
    • grep命令使用簡單例項
      $ grep 'test' d*
      顯示所有以d開頭的檔案中包含 test的行。
      
      $ grep 'test' aa bb cc
      顯示在aa,bb,cc檔案中匹配test的行。
      
      $ grep '[a-z]\{5\}' aa
      顯示所有包含每個字串至少有5個連續小寫字元的字串的行。
      
      $ grep 'w\(es\)t.*\1' aa
      如果west被匹配,則es就被儲存到記憶體中,並標記為1,然後搜尋任意個字元(.*),這些字元後面緊跟著 另外一個es(\1),找到就顯示該行。如果用egrep或grep -E,就不用”\”號進行轉義,直接寫成’w(es)t.*\1′就可以了。
    • grep命令使用複雜例項
      • 明確要求搜尋子目錄
        grep -r
      • 忽略子目錄
        grep -d skip
      • 用於搜尋的特殊符號
        \< 和 \> 分別標註單詞的開始與結尾。
        例如:
        grep man * 會匹配 ‘Batman’、’manic’、’man’等
        grep ‘\<man’ * 匹配’manic’和’man’,但不是’Batman’
        grep ‘\<man\>’ 只匹配’man’,而不是’Batman’或’manic’等其他的字串
        ‘^’:指匹配的字串在行首
        ‘$’:指匹配的字串在行 尾
  • find
    • 命令一般形式
      find pathname -options [-print -exec -ok ...]
    • 命令的引數
      pathname: find命令所查詢的目錄路徑。例如用.來表示當前目錄,用/來表示系統根目錄,遞迴查詢。
      -print: find命令將匹配的檔案輸出到標準輸出。
      -exec: find命令對匹配的檔案執行該引數所給出的shell命令。相應命令的形式為'command' {  } \;,注意{   }和\;之間的空格。
      -ok: 和-exec的作用相同,只不過以一種更為安全的模式來執行該引數所給出的shell命令,在執行每一個命令之前,都會給出提示,讓使用者來確定是否執行。
    • 命令選項
      -name   按照檔名查詢檔案。
      -perm   按照檔案許可權來查詢檔案。
      -prune  使用這一選項可以使find命令不在當前指定的目錄中查詢,如果同時使用-depth選項,那麼-prune將被find命令忽略。
      -user   按照檔案屬主來查詢檔案。
      -group  按照檔案所屬的組來查詢檔案。
      -mtime -n +n 按照檔案的更改時間來查詢檔案,-n表示檔案更改時間距現在n天以內,+n表示檔案更改時間距現在n天以前。find命令還有-atime和-ctime 選項,但它們都和-m time選項。
      -nogroup 查詢無有效所屬組的檔案,即該檔案所屬的組在/etc/groups中不存在。
      -nouser 查詢無有效屬主的檔案,即該檔案的屬主在/etc/passwd中不存在。
      -newer file1 ! file2 查詢更改時間比檔案file1新但比檔案file2舊的檔案。
      -type   查詢某一型別的檔案,諸如:
          b - 塊裝置檔案。
          d - 目錄。
          c - 字元裝置檔案。
          p - 管道檔案。
          l - 符號連結檔案。
          f - 普通檔案。
      -size n:[c] 查詢檔案長度為n塊的檔案,帶有c時表示檔案長度以位元組計。
      -depth   在查詢檔案時,首先查詢當前目錄中的檔案,然後再在其子目錄中查詢。
      -fstype  查詢位於某一型別檔案系統中的檔案,這些檔案系統型別通常可以在配置檔案/etc/fstab中找到,該配置檔案中包含了本系統中有關檔案系統的資訊。
      -mount   在查詢檔案時不跨越檔案系統mount點。
      -follow  如果find命令遇到符號連結檔案,就跟蹤至連結所指向的檔案。
    • 注意以下三個的區別:
      訪問:
      -amin n 查詢系統中最後N分鐘訪問的檔案 -atime n 查詢系統中最後n*24小時訪問的檔案
      改變檔案狀態:
      -cmin n 查詢系統中最後N分鐘被改變檔案狀態的檔案 -ctime n 查詢系統中最後n*24小時被改變檔案狀態的檔案
      改變檔案資料:
      -mmin n 查詢系統中最後N分鐘被改變檔案資料的檔案 -mtime n 查詢系統中最後n*24小時被改變檔案資料的檔案
    • 使用exec或ok來執行shell命令
      • -exec選項後面跟隨著所要執行的命令或指令碼,然後是一對兒{},一個空格和一個\,最後是一個分號。
        find . -type f -exec ls -l {} \;
      • -ok是-exec選項的安全模式。它將在對每個匹配到的檔案進行操作之前提示你。
        $ find . -name "*.conf"  -mtime +5 -ok rm {  } \;
        < rm ... ./conf/httpd.conf > ? n
  • xargs
    • 在使用find命令的-exec選項處理匹配到的檔案時, find命令將所有匹配到的檔案一起傳遞給exec執行。但有些系統對能夠傳遞給exec的命令長度有限制,這樣在find命令執行幾分鐘之後,就會出現 溢位錯誤。錯誤資訊通常是“引數列太長”或“引數列溢位”。這就是xargs命令的用處所在,特別是與find命令一起使用。
    • find命令把匹配到的檔案傳遞給xargs命令,而xargs命令每次只獲取一部分檔案而不是全部,不像-exec選項那樣。這樣它可以先處理最先獲取的一部分檔案,然後是下一批,並如此繼續下去。並且,使用xargs命令只有一個程序
    • xargs前面和管道 | 聯用,後面跟上操作,相當於-exec 操作
      find . -perm -7 -print | xargs chmod o-w   #回收其它使用者的寫許可權
      find . -type f -print | xargs grep "hello"  #用grep命令在所有的普通檔案中搜索hello這個詞
    • 利用xargs批量刪除redis資料庫中的鍵
      redis-cli keys "key*" | xargs redis-cli del

      批量刪除redis資料庫中以key開頭的鍵

    • xargs加上'-i'引數後,可以用'{}'代替'|'前面的標準輸出,批量設定以"key"開頭key的過期時間,可以在終端中可以執行以下命令:
      redis-cli keys "key*" | xargs -i redis-cli expire {} 過期時間(單位:秒)
  • sed
    • 行處理工具
    • sed意為流編輯器(Stream Editor),在Shell指令碼和Makefile中作為過濾器使用非常普遍,也就是把前一個程式的輸出引入sed的輸入,經過一系列編輯命令轉換為另一種格式輸出。
    • sed命令列基本格式:
      sed option 'script' file1 file2 ...
      sed option -f scriptfile file1 file2 ...
    • 選項含義
      --version            顯示sed版本。
      --help               顯示幫助文件。
      -n,--quiet,--silent  靜默輸出,預設情況下,sed程式在所有的指令碼指令執行完畢後,將自動列印模式空間中的內容,這些選項可以遮蔽自動列印。
      -e script            允許多個指令碼指令被執行。
      -f script-file, 
      --file=script-file   從檔案中讀取指令碼指令,對編寫自動指令碼程式來說很棒!
      -i,--in-place        直接修改原始檔,經過指令碼指令處理後的內容將被輸出至原始檔(原始檔被修改)慎用!可以使用tee命令重定向到檔案中
      -l N, --line-length=N 該選項指定l指令可以輸出的行長度,l指令用於輸出非列印字元。
      --posix             禁用GNU sed擴充套件功能。
      -r, --regexp-extended  在指令碼指令中使用擴充套件正則表示式
      -s, --separate      預設情況下,sed將把命令列指定的多個檔名作為一個長的連續的輸入流。而GNU sed則允許把他們當作單獨的檔案,這樣如正則表示式則不進行跨檔案匹配。
      -u, --unbuffered    最低限度的快取輸入與輸出。
    • 以上僅是sed程式本身的選項功能說明,至於具體的指令碼指令,這裡就簡單介紹幾個指令碼指令操作作為sed程式的例子。
      a,append        追加
      i,insert        插入
      d,delete        刪除
      s,substitution  替換
    • sed命令的格式:
      /pattern/action

      其中pattern是正則表示式,action是編輯操作。sed程式一行一行讀出待處理檔案,如果某一行與pattern匹配,則執行相應的action,如果一條命令沒有pattern而只有action,這個action將作用於待處理檔案的每一行。

    • 常用的sed命令:
      /pattern/p  列印匹配pattern的行
      /pattern/d  刪除匹配pattern的行
      /pattern/s/pattern1/pattern2/   查詢符合pattern的行,將該行第一個匹配pattern1的字串替換為pattern2
      /pattern/s/pattern1/pattern2/g  查詢符合pattern的行,將該行所有匹配pattern1的字串替換為pattern2

      使用p命令需要注意,sed是把待處理檔案的內容連同處理結果一起輸出到標準輸出的,因此p命令表示除了把檔案內容打印出來之外還額外列印一遍匹配pattern的行。比如一個檔案testfile的內容是

      123
      abc
      456

      列印其中包含abc的行

      sed '/abc/p' testfile
      
      123
      abc
      abc
      456

      要想只輸出處理結果,應加上-n選項,使用d命令就不需要-n引數了,注意,sed命令不會修改原檔案,刪除命令只表示某些行不列印輸出,而不是從原檔案中刪去。

    • 使用查詢替換命令時,可以把匹配pattern1的字串複製到pattern2中,比如:
      $ sed 's/bc/-&-/' testfile
      123
      a-bc-
      456
      pattern2中的&表示原檔案的當前行中與pattern1相匹配的字串
    • 再比如:
      $ sed 's/\([0-9]\)\([0-9]\)/-\1-~\2~/' testfile
      -1-~2~3
      abc
      -4-~5~6

      pattern2中的\1表示與pattern1的第一個()括號相匹配的內容,\2表示與pattern1的第二個()括號相匹配的內容。sed預設使用Basic正則表示式規範,如果指定了-r選項則使用Extended規範,那麼()括號就不必轉義了。

    • 執行多個指令碼指令:
      $ sed  's/yes/no/;s/static/dhcp/'  ./testfile
      注:使用分號隔開指令。
      
      $ sed -e 's/yes/no/' -e 's/static/dhcp/' testfile
      注:使用-e選項。
  • awk
    • sed以行為單位處理檔案,awk比sed強的地方在於不僅能以行為單位還能以列為單位處理檔案。awk預設的行分隔符是換行,預設的列分隔符是連續的空格和Tab,但是行分隔符和列分隔符都可以自定義,比如/etc/passwd檔案的每一行有若干個欄位,欄位之間以:分隔,就可以重新定義awk的列分隔符為:並以列為單位處理這個檔案。awk實際上是一門很複雜的指令碼語言,還有像C語言一樣的分支和迴圈結構,但是基本用法和sed類似,awk命令列的基本形式為:
      awk option 'script' file1 file2 ...
      awk option -f scriptfile file1 file2 ...
    • 和sed一樣,awk處理的檔案既可以由標準輸入重定向得到,也可以當命令列引數傳入,編輯命令可以直接當命令列引數傳入,也可以用-f引數指定一個指令碼檔案,編輯命令的格式為:
      /pattern/{actions}
      condition{actions}
    • 和sed類似,pattern是正則表示式,actions是一系列操作。awk程式一行一行讀出待處理檔案,如果某一行與pattern匹配,或者滿足condition條件,則執行相應的actions,如果一條awk命令只有actions部分,則actions作用於待處理檔案的每一行。比如檔案testfile的內容表示某商店的庫存量:
      ProductA  30
      ProductB  76
      ProductC  55

      列印每一行的第二列:

      $ awk '{print $2;}' testfile
      30
      76
      55
    • 自動變數$1、$2分別表示第一列、第二列等,類似於Shell指令碼的位置引數,而$0表示整個當前行。再比如,如果某種產品的庫存量低於75則在行末標註需要訂貨:
      $ awk '$2<75 {printf "%s\t%s\n", $0, "REORDER";} $2>=75 {print $0;}' testfile
      ProductA  30    REORDER
      ProductB  76
      ProductC  55    REORDER

      可見awk也有和C語言非常相似的printf函式。awk命令的condition部分還可以是兩個特殊的condition-BEGIN和END,對於每個待處理檔案,BEGIN後面的actions在處理整個檔案之前執行一次,END後面的actions在整個檔案處理完之後執行一次。

    • awk命令可以像C語言一樣使用變數(但不需要定義變數),比如統計一個檔案中的空行數
      awk '/^ *$/ {x=x+1;} END {print x;}' testfile
    • 有些awk變數是預定義的有特殊含義的:
      • awk常用的內建變數
        FILENAME  當前輸入檔案的檔名,該變數是隻讀的
        NR  當前行的行號,該變數是隻讀的,R代表record
        NF  當前行所擁有的列數,該變數是隻讀的,F代表field
        OFS 輸出格式的列分隔符,預設是空格
        FS  輸入檔案的列分融符,預設是連續的空格和Tab
        ORS 輸出格式的行分隔符,預設是換行符
        RS  輸入檔案的行分隔符,預設是換行符

        例如列印系統中的使用者帳號列表:

        awk 'BEGIN {FS=":"} {print $1;}' /etc/passwd