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
注意變數的值均為字串型別,如果對一個沒有定義的變數取值,則值為空
- 按照慣例,Shell變數由全大寫字母加下劃線組成,有兩種型別的Shell變數:
- 檔名代換:
- 萬用字元:*、?、[]
- *:匹配0個或多個任意字元
- ?:匹配一個任意字元
- [] :匹配方括號中任意一個字元的一次出現
- 萬用字元:*、?、[]
- 命令代換:``或$()
- ``或$()也是一條命令,Shell先執行該命令,然後將輸出結果立刻代換到當前命令列中,例如:
DATE=`date` #DATE=$(date) echo $DATE
- ``或$()也是一條命令,Shell先執行該命令,然後將輸出結果立刻代換到當前命令列中,例如:
- 算術代換:$(())
- 用於算術計算,$(())中的Shell變數取值將轉換成整數,$(())中只能用+-*/和()運算子,並且只能做整數運算:
VAR=10 echo $VAR+3 #結果:10+3 echo $(($VAR+3)) #結果:13
- 用於算術計算,$(())中的Shell變數取值將轉換成整數,$(())中只能用+-*/和()運算子,並且只能做整數運算:
- 轉義字元\
- \在Shell中被用作轉義字元,用於去除緊跟其後的單個字元的特殊意義(回車除外),換句話說,緊跟其後的字元取字面值。例如:
echo $SHELL #/bin/bash echo \$SHELL #$SHELL touch \$\ \$ #建立檔名'$ $'的檔案
- \還有一種用法,在\後敲回車表示續行
- \在Shell中被用作轉義字元,用於去除緊跟其後的單個字元的特殊意義(回車除外),換句話說,緊跟其後的字元取字面值。例如:
- 單引號和雙引號
- 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
- 在Shell中用if、then、elif、else、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是一個特殊變數,在執行指令碼時自動取值為第一個命令列引數
- Shell指令碼的case可以匹配字串和Wildcard,每個匹配分支可以有若干條命令,末尾必須以;;結束,執行時找到第一個匹配的分支並執行相應的命令,然後直接跳到esac之後,esac表示case語句塊的結束。例如:
- 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
- for迴圈,示例:
- 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
- tee命令把結果輸出到標準輸出,另一個副本輸出到相應檔案。
- 檔案重定向
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
- -exec選項後面跟隨著所要執行的命令或指令碼,然後是一對兒{},一個空格和一個\,最後是一個分號。
- 命令一般形式
- 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
- awk常用的內建變數
- sed以行為單位處理檔案,awk比sed強的地方在於不僅能以行為單位還能以列為單位處理檔案。awk預設的行分隔符是換行,預設的列分隔符是連續的空格和Tab,但是行分隔符和列分隔符都可以自定義,比如/etc/passwd檔案的每一行有若干個欄位,欄位之間以:分隔,就可以重新定義awk的列分隔符為:並以列為單位處理這個檔案。awk實際上是一門很複雜的指令碼語言,還有像C語言一樣的分支和迴圈結構,但是基本用法和sed類似,awk命令列的基本形式為: