shell

shell編程

Shell的做用是解釋執行用戶的命令,用戶輸入一條命令,Shell就解釋執行一條,這種方式稱爲交互式(Interactive),Shell還有一種執行命令的方式稱爲批處理(Batch),用戶事先寫一個Shell腳本(Script),其中有不少條命令,讓Shell一次把這些命令執行完,而沒必要一條一條地敲命令。Shell腳本和編程語言很類似,也有變量和流程控制語句,但Shell腳本是解釋執行的,不須要編譯,Shell程序從腳本中一行一行讀取並執行這些命令,至關於一個用戶把腳本中的命令一行一行敲到Shell提示符下執行。php

因爲歷史緣由,UNIX系統上有不少種Shell:html

1.sh(Bourne Shell):由Steve Bourne開發,各類UNIX系統都配有sh。linux

2.csh(C Shell):由Bill Joy開發,隨BSD UNIX發佈,它的流程控制語句很像C語言,支持不少Bourne Shell所不支持的功能:做業控制,命令歷史,命令行編輯。ios

3.ksh(Korn Shell):由David Korn開發,向後兼容sh的功能,而且添加了csh引入的新功能,是目前不少UNIX系統標準配置的Shell,在這些系統上/bin/sh每每是指向/bin/ksh的符號連接。git

4.tcsh(TENEX C Shell):是csh的加強版本,引入了命令補全等功能,在FreeBSD、Mac OS X等系統上替代了csh。正則表達式

5.bash(Bourne Again Shell):由GNU開發的Shell,主要目標是與POSIX標準保持一致,同時兼顧對sh的兼容,bash從csh和ksh借鑑了不少功能,是各類Linux發行版標準配置的Shell,在Linux系統上/bin/sh每每是指向/bin/bash的符號連接。雖然如此,bash和sh仍是有不少不一樣的,一方面,bash擴展了一些命令和參數,另外一方面,bash並不徹底和sh兼容,有些行爲並不一致,因此bash須要模擬sh的行爲:當咱們經過sh這個程序名啓動bash時,bash能夠僞裝本身是sh,不認擴展的命令,而且行爲與sh保持一致。shell

6.zsh 的命令補全功能很是強大,能夠補齊路徑,補齊命令,補齊參數等。apache

vim /etc/passwd
其中最後一列顯示了用戶對應的shell類型
root:x:0:0:root:/root:/bin/bash
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
syslog:x:101:103::/home/syslog:/bin/false
itcast:x:1000:1000:itcast,,,:/home/itcast:/bin/bash
ftp:x:115:125:ftp daemon,,,:/srv/ftp:/bin/false

用戶在命令行輸入命令後,通常狀況下Shell會fork並exec該命令,可是Shell的內建命令例外,執行內建命令至關於調用Shell進程中的一個函數,並不建立新的進程。之前學過的cd、alias、umask、exit等命令便是內建命令,凡是用which命令查不到程序文件所在位置的命令都是內建命令,內建命令沒有單獨的man手冊,要在man手冊中查看內建命令,應該編程

$ man bash-builtins

如export、shift、if、eval、[、for、while等等。內建命令雖然不建立新的進程,但也會有Exit Status,一般也用0表示成功非零表示失敗,雖然內建命令不建立新的進程,但執行結束後也會有一個狀態碼,也能夠用特殊變量$?讀出。ubuntu

執行腳本

編寫一個簡單的腳本test.sh:

#! /bin/sh
cd .. ls 

Shell腳本中用#表示註釋,至關於C語言的//註釋。但若是#位於第一行開頭,而且是#!(稱爲Shebang)則例外,它表示該腳本使用後面指定的解釋器/bin/sh解釋執行。若是把這個腳本文件加上可執行權限而後執行:

chmod a+x test.sh
./test.sh

Shell會fork一個子進程並調用exec執行./test.sh這個程序,exec系統調用應該把子進程的代碼段替換成./test.sh程序的代碼段,並從它的_start開始執行。然而test.sh是個文本文件,根本沒有代碼段和_start函數,怎麼辦呢?其實exec還有另一種機制,若是要執行的是一個文本文件,而且第一行用Shebang指定了解釋器,則用解釋器程序的代碼段替換當前進程,而且從解釋器的_start開始執行,而這個文本文件被看成命令行參數傳給解釋器。所以,執行上述腳本至關於執行程序

$ /bin/sh ./test.sh

以這種方式執行不須要test.sh文件具備可執行權限。

若是將命令行下輸入的命令用()括號括起來,那麼也會fork出一個子Shell執行小括號中的命令,一行中能夠輸入由分號;隔開的多個命令,好比:

$ (cd ..;ls -l) 

和上面兩種方法執行Shell腳本的效果是相同的,cd ..命令改變的是子Shell的PWD,而不會影響到交互式Shell。然而命令

$ cd ..;ls -l 

則有不一樣的效果,cd ..命令是直接在交互式Shell下執行的,改變交互式Shell的PWD,然而這種方式至關於這樣執行Shell腳本:

$ source ./test.sh 

或者

$ . ./test.sh

source或者.命令是Shell的內建命令,這種方式也不會建立子Shell,而是直接在交互式Shell下逐行執行腳本中的命令。

 

小結:

./test.sh //須要可執行權限,不會改變路徑(由於是直接執行,而且建立了子線程) /bin/sh test.sh //不須要可執行權限,不會改變路徑,(由於是當作參數傳遞給sh去執行) source test.sh //不須要可執行權限,會改變路徑,由於是內建命令 . (注意有空格)test.sh //跟source相同

 

基本語法

變量

按照慣例,Shell變量由全大寫字母加下劃線組成,有兩種類型的Shell變量:

1.環境變量

環境變量能夠從父進程傳給子進程,所以Shell進程的環境變量能夠從當前Shell進程傳給fork出來的子進程。用printenv命令能夠顯示當前Shell進程的環境變量。

2.本地變量

只存在於當前Shell進程,用set命令能夠顯示當前Shell進程中定義的全部變量(包括本地變量和環境變量)和函數。

環境變量是任何進程都有的概念,而本地變量是Shell特有的概念。在Shell中,環境變量和本地變量的定義和用法類似。在Shell中定義或賦值一個變量:

itcast$ VARNAME=value

注意等號兩邊都不能有空格,不然會被Shell解釋成命令和命令行參數。

一個變量定義後僅存在於當前Shell進程,它是本地變量,用export命令能夠把本地變量導出爲環境變量,定義和導出環境變量一般能夠一步完成:

itcast$ export VARNAME=value

也能夠分兩步完成:

itcast$ VARNAME=value
itcast$ export VARNAME

用unset命令能夠刪除已定義的環境變量或本地變量。

itcast$ unset VARNAME

若是一個變量叫作VARNAME,用${VARNAME}能夠表示它的值,在不引發歧義的狀況下也能夠用$VARNAME表示它的值。經過如下例子比較這兩種表示法的不一樣:

itcast$ echo $SHELL

注意,在定義變量時不用$,取變量值時要用$。和C語言不一樣的是,Shell變量不須要明肯定義類型,事實上Shell變量的值都是字符串,好比咱們定義VAR=45,其實VAR的值是字符串45而非整數。Shell變量不須要先定義後使用,若是對一個沒有定義的變量取值,則值爲空字符串。

文件名代換(Globbing):* ? []

這些用於匹配的字符稱爲通配符(Wildcard),具體以下:

通配符

*   匹配0個或多個任意字符
?   匹配一個任意字符
[若干字符]  匹配方括號中任意一個字符的一次出現

$ ls /dev/ttyS*
$ ls ch0?.doc
$ ls ch0[0-2].doc
$ ls ch[012]   [0-9].doc

注意,Globbing所匹配的文件名是由Shell展開的,也就是說在參數還沒傳給程序以前已經展開了,好比上述ls ch0[012].doc命令,若是當前目錄下有ch00.doc和ch02.doc,則傳給ls命令的參數其實是這兩個文件名,而不是一個匹配字符串。

命令代換:`或 $()

由'`'反引號括起來的也是一條命令,Shell先執行該命令,而後將輸出結果馬上代換到當前命令行中。例如定義一個變量存放date命令的輸出:

itcast$ DATE=`date`
itcast$ echo $DATE

命令代換也能夠用$()表示:

itcast$ DATE=$(date)

算術代換:$(())

用於算術計算,$(())中的Shell變量取值將轉換成整數,一樣含義的$[]等價例如:

itcast$ VAR=45
itcast$ echo $(($VAR+3))
$(())中只能用+-*/和()運算符,而且只能作整數運算。

$[base#n],其中base表示進制,n按照base進制解釋,後面再有運算數,按十進制解釋。

echo $[2#10+11]
echo $[8#10+11]
echo $[10#10+11]

轉義字符\

和C語言相似,\在Shell中被用做轉義字符,用於去除緊跟其後的單個字符的特殊意義(回車除外),換句話說,緊跟其後的字符取字面值。例如:

itcast$ echo $SHELL
/bin/bash
itcast$ echo \$SHELL
$SHELL
itcast$ echo \\
\

好比建立一個文件名爲「$ $」的文件能夠這樣:

itcast$ touch \$\ \$

還有一個字符雖然不具備特殊含義,可是要用它作文件名也很麻煩,就是-號。若是要建立一個文件名以-號開頭的文件,這樣是不行的:

itcast$ touch -hello
touch: invalid option -- h
Try `touch --help' for more information.

即便加上\轉義也仍是報錯:

itcast$ touch \-hello
touch: invalid option -- h
Try `touch --help' for more information.

由於各類UNIX命令都把-號開頭的命令行參數看成命令的選項,而不會看成文件名。若是非要處理以-號開頭的文件名,能夠有兩種辦法:

itcast$ touch ./-hello

或者

itcast$ touch -- -hello

\還有一種用法,在\後敲回車表示續行,Shell並不會馬上執行命令,而是把光標移到下一行,給出一個續行提示符>,等待用戶繼續輸入,最後把全部的續行接到一塊兒看成一個命令執行。例如:

itcast$ ls \
> -l
(ls -l命令的輸出)

單引號

和C語言不同,Shell腳本中的單引號和雙引號同樣都是字符串的界定符(雙引號下一節介紹),而不是字符的界定符。單引號用於保持引號內全部字符的字面值,即便引號內的\和回車也不例外,可是字符串中不能出現單引號。若是引號沒有配對就輸入回車,Shell會給出續行提示符,要求用戶把引號配上對。例如:

itcast$ echo '$SHELL'
$SHELL
itcast$ echo 'ABC\(回車)
> DE'(再按一次回車結束命令)
ABC\
DE

雙引號

被雙引號用括住的內容,將被視爲單一字串。它防止通配符擴展,但容許變量擴展。這點與單引號的處理方式不一樣

itcast$ DATE=$(date)
itcast$ echo "$DATE"
itcast$ echo '$DATE'

Shell腳本語法

條件測試:test或[

命令test或[能夠測試一個條件是否成立,若是測試結果爲真,則該命令的Exit Status爲0,若是測試結果爲假,則命令的Exit Status爲1(注意與C語言的邏輯表示正好相反)。例如測試兩個數的大小關係:

itcast@ubuntu:~$ var=2
itcast@ubuntu:~$ test $var -gt 1
itcast@ubuntu:~$ echo $?
0
itcast@ubuntu:~$ test $var -gt 3
itcast@ubuntu:~$ echo $?
1
itcast@ubuntu:~$ [ $var -gt 3 ]
itcast@ubuntu:~$ echo $?
1
itcast@ubuntu:~$

雖然看起來很奇怪,但左方括號[確實是一個命令的名字,傳給命令的各參數之間應該用空格隔開,好比,$VAR、-gt、三、]是[命令的四個參數,它們之間必須用空格隔開。命令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(大於等於)之中的一個

和C語言相似,測試條件之間還能夠作與、或、非邏輯運算:

帶與、或、非的測試命令

[ ! EXPR ]          EXPR能夠是上表中的任意一種測試條件,!表示邏輯反
[ EXPR1 -a EXPR2 ]  EXPR1和EXPR2能夠是上表中的任意一種測試條件,-a表示邏輯與
[ EXPR1 -o EXPR2 ]  EXPR1和EXPR2能夠是上表中的任意一種測試條件,-o表示邏輯或

例如:

$ VAR=abc
$ [ -d Desktop -a $VAR = 'abc' ]
$ echo $?
0

注意,若是上例中的$VAR變量事先沒有定義,則被Shell展開爲空字符串,會形成測試條件的語法錯誤(展開爲[ -d Desktop -a = 'abc' ]),做爲一種好的Shell編程習慣,應該老是把變量取值放在雙引號之中(展開爲[ -d Desktop -a "" = 'abc' ]):

$ unset VAR
$ [ -d Desktop -a $VAR = 'abc' ]
bash: [: too many arguments
$ [ -d Desktop -a "$VAR" = 'abc' ]
$ echo $?
1

if/then/elif/else/fi

和C語言相似,在Shell中用if、then、elif、else、fi這幾條命令實現分支控制。這種流程控制語句本質上也是由若干條Shell命令組成的,例如先前講過的

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語句塊的結束。見下例:

#! /bin/sh

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 

:是一個特殊的命令,稱爲空命令,該命令不作任何事,但Exit Status老是真。此外,也能夠執行/bin/true或/bin/false獲得真或假的Exit Status。再看一個例子:

    #! /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." exit 1 fi exit 0 

上例中的read命令的做用是等待用戶輸入一行字符串,將該字符串存到一個Shell變量中。

此外,Shell還提供了&&和||語法,和C語言相似,具備Short-circuit特性,不少Shell腳本喜歡寫成這樣:

test "$(whoami)" != 'root' && (echo you are using a non-privileged account; exit 1)

&&至關於「if...then...」,而||至關於「if not...then...」。&&和||用於鏈接兩個命令,而上面講的-a和-o僅用於在測試表達式中鏈接兩個測試條件,要注意它們的區別,例如,

test "$VAR" -gt 1 -a "$VAR" -lt 3

和如下寫法是等價的

test "$VAR" -gt 1 && test "$VAR" -lt 3

case/esac

case命令可類比C語言的switch/case語句,esac表示case語句塊的結束。C語言的case只能匹配整型或字符型常量表達式,而Shell腳本的case能夠匹配字符串和Wildcard,每一個匹配分支能夠有若干條命令,末尾必須以;;結束,執行時找到第一個匹配的分支並執行相應的命令,而後直接跳到esac以後,不須要像C語言同樣用break跳出。

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

啓動nfs-kernel-server服務的命令是

$ sudo /etc/init.d/nfs-kernel-server start

$1是一個特殊變量,在執行腳本時自動取值爲第一個命令行參數,也就是start,因此進入start)分支執行相關的命令。同理,命令行參數指定爲stop、reload或restart能夠進入其它分支執行中止服務、從新加載配置文件或從新啓動服務的相關命令。

for/do/done

Shell腳本的for循環結構和C語言很不同,它相似於某些編程語言的foreach循環。例如:

    #! /bin/sh

    for FRUIT in apple banana pear; do echo "I like $FRUIT" done 
FRUIT是一個循環變量,第一次循環$FRUIT的取值是apple,第二次取值是banana,第三次取值是pear。再好比,要將當前目錄下的chap0、chap一、chap2等文件名改成chap0~、chap1~、chap2~等(按慣例,末尾有~字符的文件名錶示臨時文件),這個命令能夠這樣寫:

$ for FILENAME in chap?; do mv $FILENAME $FILENAME~; done

也能夠這樣寫:

$ for FILENAME in `ls chap?`; do mv $FILENAME $FILENAME~; done

while/do/done

while的用法和C語言相似。好比一個驗證密碼的腳本:

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

Shell還有until循環,相似C語言的do...while循環。本章從略。

break和continue

break[n]能夠指定跳出幾層循環,continue跳過本次循環步,沒跳出整個循環。

break跳出,continue跳過。

習題

一、把上面驗證密碼的程序修改一下,若是用戶輸錯五次密碼就報錯退出。

位置參數和特殊變量

有不少特殊變量是被Shell自動賦值的,咱們已經遇到了$?和$1,如今總結一下:

經常使用的位置參數和特殊變量

$0  至關於C語言main函數的argv[0]
$一、$2...    這些稱爲位置參數(Positional Parameter),至關於C語言main函數的argv[1]、argv[2]...
$#  至關於C語言main函數的argc - 1,注意這裏的#後面不表示註釋
$@  表示參數列表"$1" "$2" ...,例如能夠用在for循環中的in後面。
$*  表示參數列表"$1" "$2" ...,同上
$?  上一條命令的Exit Status
$$  當前進程號

位置參數能夠用shift命令左移。好比shift 3表示原來的$4如今變成$1,原來的$5如今變成$2等等,原來的$一、$二、$3丟棄,$0不移動。不帶參數的shift命令至關於shift 1。例如:

    #! /bin/sh

    echo "The program $0 is now running" echo "The first parameter is $1" echo "The second parameter is $2" echo "The parameter list is $@" shift  echo "The first parameter is $1" echo "The second parameter is $2" echo "The parameter list is $@" 

shell輸入輸出

echo

echo顯示文本行或變量,或者把字符串輸入到文件。

echo [option] string
-e 解析轉義字符
-n 不回車換行。默認狀況echo回顯的內容後面跟一個回車換行。
echo "hello\n\n"
echo -e "hello\n\n"
echo  "hello"
echo -n "hello"

管道|

能夠經過管道把一個命令的輸出傳遞給另外一個命令作輸入。管道用豎線表示。

cat myfile | more
ls -l | grep "myfile"
df -k | awk '{print $1}' | grep -v "文件系統"
df -k 查看磁盤空間,找到第一列,去除「文件系統」,並輸出

tee

tee命令把結果輸出到標準輸出,另外一個副本輸出到相應文件。

df -k | awk '{print $1}' | grep -v "文件系統" | tee a.txt

tee -a a.txt表示追加操做。
df -k | awk '{print $1}' | grep -v "文件系統" | tee -a a.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 < &-               關閉標準輸入

函數

和C語言相似,Shell中也有函數的概念,可是函數定義中沒有返回值也沒有參數列表。例如:

    #! /bin/sh

    foo(){ echo "Function foo is called";} echo "-=start=-" foo echo "-=end=-" 

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

在定義foo()函數時並不執行函數體中的命令,就像定義變量同樣,只是給foo這個名字一個定義,到後面調用foo函數的時候(注意Shell中的函數調用不寫括號)才執行函數體中的命令。Shell腳本中的函數必須先定義後調用,通常把函數定義都寫在腳本的前面,把函數調用和其它命令寫在腳本的最後(相似C語言中的main函數,這纔是整個腳本實際開始執行命令的地方)。

Shell函數沒有參數列表並不表示不能傳參數,事實上,函數就像是迷你腳本,調用函數時能夠傳任意個參數,在函數內一樣是用$0、$一、$2等變量來提取參數,函數中的位置參數至關於函數的局部變量,改變這些變量並不會影響函數外面的$0、$一、$2等變量。函數中能夠用return命令返回,若是return後面跟一個數字則表示函數的Exit Status。

下面這個腳本能夠一次建立多個目錄,各目錄名經過命令行參數傳入,腳本逐個測試各目錄是否存在,若是目錄不存在,首先打印信息而後試着建立該目錄。

    #! /bin/sh

    is_directory() { DIR_NAME=$1 if [ ! -d $DIR_NAME ]; then return 1 else return 0 fi } for DIR in "$@"; do if is_directory "$DIR" then : else echo "$DIR doesn't exist. Creating it now..." mkdir $DIR > /dev/null 2>&1 if [ $? -ne 0 ]; then echo "Cannot create directory $DIR" exit 1 fi fi done 

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

Shell腳本的調試方法

Shell提供了一些用於調試腳本的選項,以下所示:

-n

讀一遍腳本中的命令但不執行,用於檢查腳本中的語法錯誤

-v

一邊執行腳本,一邊將執行過的腳本命令打印到標準錯誤輸出

-x

提供跟蹤執行信息,將執行的每一條命令和結果依次打印出來

使用這些選項有三種方法,一是在命令行提供參數

    $ sh -x ./script.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參數,這樣能夠只對腳本中的某一段進行跟蹤調試。

正則表達式

之前咱們用grep在一個文件中找出包含某些字符串的行,好比在頭文件中找出一個宏定義。其實grep還能夠找出符合某個模式(Pattern)的一類字符串。例如找出全部符合xxxxx@xxxx.xxx模式的字符串(也就是email地址),要求x字符能夠是字母、數字、下劃線、小數點或減號,email地址的每一部分能夠有一個或多個x字符,例如abc.d@ef.com、1_2@987-6.54,固然符合這個模式的不全是合法的email地址,但至少能夠作一次初步篩選,篩掉a.b、c@d等確定不是email地址的字符串。再好比,找出全部符合yyy.yyy.yyy.yyy模式的字符串(也就是IP地址),要求y是0-9的數字,IP地址的每一部分能夠有1-3個y字符。

若是要用grep查找一個模式,如何表示這個模式,這一類字符串,而不是一個特定的字符串呢?從這兩個簡單的例子能夠看出,要表示一個模式至少應該包含如下信息:

字符類(Character Class):如上例的x和y,它們在模式中表示一個字符,可是取值範圍是一類字符中的任意一個。

數量限定符(Quantifier): 郵件地址的每一部分能夠有一個或多個x字符,IP地址的每一部分能夠有1-3個y字符

各類字符類以及普通字符之間的位置關係:例如郵件地址分三部分,用普通字符@和.隔開,IP地址分四部分,用.隔開,每一部分均可以用字符類和數量限定符描述。爲了表示位置關係,還有位置限定符(Anchor)的概念,將在下面介紹。

規定一些特殊語法表示字符類、數量限定符和位置關係,而後用這些特殊語法和普通字符一塊兒表示一個模式,這就是正則表達式(Regular Expression)。例如email地址的正則表達式能夠寫成[a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+.[a-zA-Z0-9_.-]+,IP地址的正則表達式能夠寫成[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}。下一節介紹正則表達式的語法,咱們先看看正則表達式在grep中怎麼用。例若有這樣一個文本文件testfile:

192.168.1.1
1234.234.04.5678
123.4234.045.678
abcde

查找其中包含IP地址的行:

$ egrep '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' testfile
192.168.1.1
1234.234.04.5678

egrep至關於grep -E,表示採用Extended正則表達式語法。grep的正則表達式有Basic和Extended兩種規範,它們之間的區別下一節再解釋。另外還有fgrep命令,至關於grep -F,表示只搜索固定字符串而不搜索正則表達式模式,不會按正則表達式的語法解釋後面的參數。

注意正則表達式參數用單引號括起來了,由於正則表達式中用到的不少特殊字符在Shell中也有特殊含義(例如\),只有用單引號括起來才能保證這些字符原封不動地傳給grep命令,而不會被Shell解釋掉。

192.168.1.1符合上述模式,由三個.隔開的四段組成,每段都是1到3個數字,因此這一行被找出來了,可爲何1234.234.04.5678也被找出來了呢?由於grep找的是包含某一模式的行,這一行包含一個符合模式的字符串234.234.04.567。相反,123.4234.045.678這一行不包含符合模式的字符串,因此不會被找出來。

grep是一種查找過濾工具,正則表達式在grep中用來查找符合模式的字符串。其實正則表達式還有一個重要的應用是驗證用戶輸入是否合法,例如用戶經過網頁表單提交本身的email地址,就須要用程序驗證一下是否是合法的email地址,這個工做能夠在網頁的Javascript中作,也能夠在網站後臺的程序中作,例如PHP、Perl、Python、Ruby、Java或C,全部這些語言都支持正則表達式,能夠說,目前不支持正則表達式的編程語言實在不多見。除了編程語言以外,不少UNIX命令和工具也都支持正則表達式,例如grep、vi、sed、awk、emacs等等。「正則表達式」就像「變量」同樣,它是一個普遍的概念,而不是某一種工具或編程語言的特性。

基本語法

咱們知道C的變量和Shell腳本變量的定義和使用方法很不相同,表達能力也不相同,C的變量有各類類型,而Shell腳本變量都是字符串。一樣道理,各類工具和編程語言所使用的正則表達式規範的語法並不相同,表達能力也各不相同,有的正則表達式規範引入不少擴展,能表達更復雜的模式,但各類正則表達式規範的基本概念都是相通的。本節介紹egrep(1)所使用的正則表達式,它大體上符合POSIX正則表達式規範,詳見regex(7)(看這個man page對你的英文絕對是很好的鍛鍊)。但願讀者仿照上一節的例子,一邊學習語法,一邊用egrep命令作實驗。

字符類

字符  含義               舉例
.   匹配任意一個字符          abc.能夠匹配abcd、abc9等
[]  匹配括號中的任意一個字符  [abc]d能夠匹配ad、bd或cd
-   在[]括號內表示字符範圍    [0-9a-fA-F]能夠匹配一位十六進制數字
^   位於[]括號內的開頭,匹配除括號中的字符以外的任意一個字符  [^xy]匹配除xy以外的任一字符,所以[^xy]1能夠匹配a一、b1但不匹配x一、y1

[[:xxx:]]   grep工具預約義的一些命名字符類   [[:alpha:]]匹配一個字母,[[:digit:]]匹配一個數字

 

數量限定符

字符    含義                             舉例
?   緊跟在它前面的單元應匹配零次或一次    [0-9]?\.[0-9]匹配0.02.3、.5等,因爲.在正則表達式中是一個特殊字符,因此須要用\轉義一下,取字面值
+   緊跟在它前面的單元應匹配一次或屢次    [a-zA-Z0-9_.-]+@[a-zA-Z0-9_.-]+\.[a-zA-Z0-9_.-]+匹配email地址
*   緊跟在它前面的單元應匹配零次或屢次    [0-9][0-9]*匹配至少一位數字,等價於[0-9]+,[a-zA-Z_]+[a-zA-Z_0-9]*匹配C語言的標識符
{N} 緊跟在它前面的單元應精確匹配N次       [1-9][0-9]{2}匹配從100到999的整數
{N,}  緊跟在它前面的單元應匹配至少N次     [1-9][0-9]{2,}匹配三位以上(含三位)的整數
{,M}  緊跟在它前面的單元應匹配最多M次     [0-9]{,1}至關於[0-9]?
{N,M} 緊跟在它前面的單元應匹配至少N次,最多M次   [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}匹配IP地址

 

再次注意grep找的是包含某一模式的行,而不是徹底匹配某一模式的行。再舉個例子,若是文本文件的內容是

aaabc
aad
efg

查找a*這個模式的結果是三行都被找出來了

$ egrep 'a*' testfile 
aabc
aad
efg

a匹配0個或多個a,而第三行包含0個a,因此也包含了這一模式。單獨用a這樣的正則表達式作查找沒什麼意義,通常是把a*做爲正則表達式的一部分來用。

位置限定符

    字符  含義                舉例
    ^   匹配行首的位置        ^Content匹配位於一行開頭的Content
    $   匹配行末的位置        ;$匹配位於一行結尾的;號,^$匹配空行
    \<  匹配單詞開頭的位置    \<th匹配... this,但不匹配ethernet、tenth
    \>  匹配單詞結尾的位置    p\>匹配leap ...,但不匹配parent、sleepy
    \b  匹配單詞開頭或結尾的位置     \bat\b匹配... at ...,但不匹配cat、atexit、batch
    \B  匹配非單詞開頭和結尾的位置   \Bat\B匹配battery,但不匹配... attend、hat ...

 

位置限定符能夠幫助grep更準確地查找,例如上一節咱們用[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}查找IP地址,找到這兩行

192.168.1.1
1234.234.04.5678

若是用^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$查找,就能夠把1234.234.04.5678這一行過濾掉了。

其它特殊字符

字符  含義    舉例
\    轉義字符,普通字符轉義爲特殊字符,特殊字符轉義爲普通字符   普通字符<寫成\<表示單詞開頭的位置,特殊字符.寫成\.以及\寫成\\就看成普通字符來匹配
()   將正則表達式的一部分括起來組成一個單元,能夠對整個單元使用數量限定符    ([0-9]{1,3}\.){3}[0-9]{1,3}匹配IP地址
|    鏈接兩個子表達式,表示或的關係     n(o|either)匹配no或neither

 

以上介紹的是grep正則表達式的Extended規範,Basic規範也有這些語法,只是字符?+{}|()應解釋爲普通字符,要表示上述特殊含義則須要加\轉義。若是用grep而不是egrep,而且不加-E參數,則應該遵守Basic規範來寫正則表達式。

 

sed

sed意爲流編輯器(Stream Editor),在Shell腳本和Makefile中做爲過濾器使用很是廣泛,也就是把前一個程序的輸出引入sed的輸入,通過一系列編輯命令轉換爲另外一種格式輸出。sed和vi都源於早期UNIX的ed工具,因此不少sed命令和vi的末行命令是相同的

注意,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        直接修改源文件,通過腳本指令處理後的內容將被輸出至源文件(源文件被修改)慎用!
-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 "2a itcast" ./testfile 在輸出testfile內容的第二行後添加"itcast"。

$ sed "2,5d" testfile

sed處理的文件既能夠由標準輸入重定向獲得,也能夠當命令行參數傳入,命令行參數能夠一次傳入多個文件,sed會依次處理。sed的編輯命令能夠直接當命令行參數傳入,也能夠寫成一個腳本文件而後用-f參數指定,編輯命令的格式爲

/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選項,這種用法至關於grep命令

$ sed -n '/abc/p' testfile
abc

使用d命令就不須要-n參數了,好比刪除含有abc的行

$ sed '/abc/d' testfile
123
456

注意,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選項。

若是testfile的內容是

<html><head><title>Hello World</title></head>
<body>Welcome to the world of regexp!</body></html>

如今要去掉全部的HTML標籤,使輸出結果爲

Hello World
Welcome to the world of regexp!

怎麼作呢?若是用下面的命令

$ sed 's/<.*>//g' testfile

結果是兩個空行,把全部字符都過濾掉了。這是由於,正則表達式中的數量限定符會匹配儘量長的字符串,這稱爲貪心的(Greedy)。好比sed在處理第一行時,<.*>匹配的並非或這樣的標籤,而是

<html><head><title>Hello World</title>

這樣一整行,由於這一行開頭是<,中間是若干個任意字符,末尾是>。那麼這條命令怎麼改纔對呢?留給同窗們思考練習。

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

自動變量$一、$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

就像Shell的環境變量同樣,有些awk變量是預約義的有特殊含義的:

awk經常使用的內建變量

FILENAME  當前輸入文件的文件名,該變量是隻讀的
NR  當前行的行號,該變量是隻讀的,R表明record
NF  當前行所擁有的列數,該變量是隻讀的,F表明field
OFS 輸出格式的列分隔符,缺省是空格
FS  輸入文件的列分融符,缺省是連續的空格和Tab
ORS 輸出格式的行分隔符,缺省是換行符
RS  輸入文件的行分隔符,缺省是換行符

例如打印系統中的用戶賬號列表

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

Linux核心命令

運維內容

  • strace
  • netstat
  • perf
  • top
  • pidstat
  • mpstat
  • dstat
  • vmstat
  • slabtop
  • free
  • top
  • tcpdump
  • ip
  • nicstat
  • dtrace
  • ping
  • dtrace
  • blktrace
  • iptop
  • iostat
  • stap

文本處理類的命令:

  • wc

    wc [option] [file]...
        -l: 統計行數
        -c: 統計字節數
        -w;統計單詞數
    
  • tr

    tr: 轉換字符或刪除字符
        tr '集合1' '集合2'
        tr -d '字符集合'
    
  • cut

    This is a test line.
    -d字符:指定分隔符
    -f#: 指定要顯示字段
        單個數字:一個字段
        逗號分隔的多個數字:指定多個離散字段
        -:連續字段,如3-5;
    
  • sort

    按字符進行比較
    sort [option] file...
        -f: 忽略字符大小寫;
        -n: 比較數值大小;
        -t: 指定分隔符
        -k: 指定分隔後進行比較字段
        -u: 重複的行,只顯示一次;
    
  • uniq

    移除重複的行
    -c:顯示每行重複的次數
    -d:僅顯示重複過的行
    -u: 僅顯示未曾重複的行
    
  • 工具速查連接

    http://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/index.html
    

shell習題訓練

  1. 求2個數之和
  2. 計算1-100的和
  3. 將一目錄下全部的文件的擴展名改成bak
  4. 編譯當前目錄下的全部.c文件:
  5. 打印root可使用可執行文件數,處理結果: root's bins: 2306
  6. 打印當前sshd的端口和進程id,處理結果: sshd Port&&pid: 22 5412
  7. 輸出本機建立20000個目錄所用的時間,處理結果:

    real    0m3.367s
    user    0m0.066s
    sys     0m1.925s
    
  8. 打印本機的交換分區大小,處理結果: Swap:1024M

  9. 文本分析,取出/etc/password中shell出現的次數

    第一種方法結果:
          4 /bin/bash
          1 /bin/sync
          1 /sbin/halt
         31 /sbin/nologin
          1 /sbin/shutdown
    第二種方法結果:
            /bin/sync       1
            /bin/bash       1
            /sbin/nologin   30
            /sbin/halt      1
            /sbin/shutdown  1
    
  10. 文件整理,employee文件中記錄了工號和姓名,(提示join)

    employee.txt:
        100 Jason Smith 
        200 John Doe 
        300 Sanjay Gupta 
        400 Ashok Sharma 
        bonus文件中記錄工號和工資
    bonus.txt:
        100 $5,000 
        200 $500 
        300 $3,000 
        400 $1,250 
    要求把兩個文件合併並輸出以下,處理結果:
        400 ashok sharma $1,250
        100 jason smith  $5,000
        200 john doe  $500
        300 sanjay gupta  $3,000
    
  11. 寫一個shell腳原本獲得當前的日期,時間,用戶名和當前工做目錄。

  12. 編寫shell腳本獲取本機的網絡地址。
  13. 編寫個shell腳本將當前目錄下大於10K的文件轉移到/tmp目錄下
  14. 編寫一個名爲myfirstshell.sh的腳本,它包括如下內容。

    a) 包含一段註釋,列出您的姓名、腳本的名稱和編寫這個腳本的目的。
    b) 問候用戶。
    c) 顯示日期和時間。
    d) 顯示這個月的日曆。
    e) 顯示您的機器名。
    f) 顯示當前這個操做系統的名稱和版本。
    g) 顯示父目錄中的全部文件的列表。
    h) 顯示root正在運行的全部進程。
    i) 顯示變量TERM、PATH和HOME的值。
    j) 顯示磁盤使用狀況。
    k) 用id命令打印出您的組ID。
    m) 跟用戶說「Good bye」
    
  15. 文件移動拷貝,有m1.txt m2.txt m3.txt m4.txt,分別建立出對應的目錄,m1 m2 m3 m4 並把文件移動到對應的目錄下

  16. root用戶今天登錄了多長時間
  17. 終端輸入一個文件名,判斷是不是設備文件
  18. 統計IP訪問:要求分析apache訪問日誌,找出訪問頁面數量在前100位的IP數。日誌大小在78M左右。如下是apache的訪問日誌節選

    202.101.129.218 - - [26/Mar/2006:23:59:55 +0800] "GET /online/stat_inst.php?pid=d065 HTTP/1.1" 302 20-"-" "-" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
    
  19. 設計一個Shell程序,在/userdata目錄下創建50個目錄,即user1~user50,並設置每一個目錄的權限,其中其餘用戶的權限爲:讀;文件全部者的權限爲:讀、寫、執行;文件全部者所在組的權限爲:讀、執行。

  20. 設計一個shell程序,添加一個新組爲class1,而後添加屬於這個組的30個用戶,用戶名的形式爲stdxx,其中xx從01到30,並設置密碼爲對應的stdxx。
  21. 編寫shell程序,實現自動刪除30個帳號的功能。帳號名爲std01至std30。
  22. 用戶清理,清除本機除了當前登錄用戶之外的全部用戶
  23. 設計一個shell程序,在每個月第一天備份並壓縮/etc目錄的全部內容,存放在/root/bak目錄裏,且文件名,爲以下形式yymmdd_etc,yy爲年,mm爲月,dd爲日。Shell程序fileback存放在/usr/bin目錄下。
  24. 對於一個用戶日誌文件,每行記錄了一個用戶查詢串,長度爲1-255字節,共幾千萬行,請排出查詢最多的前100條。 日誌能夠本身構造。 (提示:awk sort uniq head)
  25. 編寫本身的ubuntu環境安裝腳本
  26. 編寫服務器守護進程管理腳本。
  27. 查看TCP鏈接狀態

    netstat -nat |awk ‘{print $6}’|sort|uniq -c|sort -rn
    
    netstat -n | awk ‘/^tcp/ {++S[$NF]};END {for(a in S) print a, S[a]}’ 或
    netstat -n | awk ‘/^tcp/ {++state[$NF]}; END {for(key in state) print key,"\t",state[key]}’
    netstat -n | awk ‘/^tcp/ {++arr[$NF]};END {for(k in arr) print k,"t",arr[k]}’
    
    netstat -n |awk ‘/^tcp/ {print $NF}’|sort|uniq -c|sort -rn
    
    netstat -ant | awk ‘{print $NF}’ | grep -v ‘[a-z]‘ | sort | uniq -c
    
  28. 查找請求數請20個IP(經常使用於查找攻來源):

    netstat -anlp|grep 80|grep tcp|awk ‘{print $5}’|awk -F: ‘{print $1}’|sort|uniq -c|sort -nr|head -n20
    
    netstat -ant |awk ‘/:80/{split($5,ip,":");++A[ip[1]]}END{for(i in A) print A[i],i}’ |sort -rn|head -n20
    
  29. 用tcpdump嗅探80端口的訪問看看誰最高

    tcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F"." ‘{print $1"."$2"."$3"."$4}’ | sort | uniq -c | sort -nr |head -20
    
  30. 查找較多time_wait鏈接

    netstat -n|grep TIME_WAIT|awk ‘{print $5}’|sort|uniq -c|sort -rn|head -n20
    
  31. 找查較多的SYN鏈接

    netstat -an | grep SYN | awk ‘{print $5}’ | awk -F: ‘{print $1}’ | sort | uniq -c | sort -nr | more
    
  32. 根據端口列進程

    netstat -ntlp | grep 80 | awk ‘{print $7}’ | cut -d/ -f1
    
  33. 得到訪問前10位的ip地址

    cat access.log|awk ‘{print $1}’|sort|uniq -c|sort -nr|head -10
    cat access.log|awk ‘{counts[$(11)]+=1}; END {for(url in counts) print counts[url], url}’
    
  34. 訪問次數最多的文件或頁面,取前20

    cat access.log|awk ‘{print $11}’|sort|uniq -c|sort -nr|head -20
    
  35. 列出傳輸最大的幾個exe文件(分析下載站的時候經常使用)

    cat access.log |awk ‘($7~/.exe/){print $10 " " $1 " " $4 " " $7}’|sort -nr|head -20
    
  36. 列出輸出大於200000byte(約200kb)的exe文件以及對應文件發生次數

    cat access.log |awk ‘($10 > 200000 && $7~/.exe/){print $7}’|sort -n|uniq -c|sort -nr|head -100
    
  37. 若是日誌最後一列記錄的是頁面文件傳輸時間,則有列出到客戶端最耗時的頁面

    cat access.log |awk ‘($7~/.php/){print $NF " " $1 " " $4 " " $7}’|sort -nr|head -100
    
  38. 列出最最耗時的頁面(超過60秒的)的以及對應頁面發生次數

    cat access.log |awk ‘($NF > 60 && $7~/.php/){print $7}’|sort -n|uniq -c|sort -nr|head -100
    
  39. 列出傳輸時間超過 30 秒的文件

    cat access.log |awk ‘($NF > 30){print $7}’|sort -n|uniq -c|sort -nr|head -20
    
  40. 統計網站流量(G)

    cat access.log |awk ‘{sum+=$10} END {print sum/1024/1024/1024}’
    
  41. 統計404的鏈接

    awk ‘($9 ~/404/)’ access.log | awk ‘{print $9,$7}’ | sort
    
  42. 統計http status

    cat access.log |awk ‘{counts[$(9)]+=1}; END {for(code in counts) print code, counts[code]}'
    cat access.log |awk '{print $9}'|sort|uniq -c|sort -rn
    
  43. 蜘蛛分析,查看是哪些蜘蛛在抓取內容。

    /usr/sbin/tcpdump -i eth0 -l -s 0 -w - dst port 80 | strings | grep -i user-agent | grep -i -E 'bot|crawler|slurp|spider'
    
  44. 建立一個用戶mandriva,其ID號爲2002,基本組爲distro(組ID爲3003),附加組爲linux;

    # groupadd linux
    # groupadd -g 3003 distro
    # useradd -u 2002 -g distro -G linux mandriva
    
  45. 建立一個用戶fedora,其全名爲Fedora Community,默認shell爲tcsh; # useradd -c "Fedora Community" -s /bin/tcsh fedora

  46. 修改mandriva的ID號爲4004,基本組爲linux,附加組爲distro和fedora;

    # usermod -u 4004 -g linux -G distro,fedora mandriva
    
  47. 給fedora加密碼,並設定其密碼最短使用期限爲2天,最長爲50天;

    # passwd fedora
    # chage -m 2 -M 50 fedora
    
  48. 調試命令

    strace -p pid
    
  49. 寫一個腳本

    一、建立一個組newgroup, id號爲4000;
    二、建立一個用戶mageedu1, id號爲3001,附加組爲newgroup;
    三、建立目錄/tmp/hellodirxyz
    四、複製/etc/fstab至上面的目錄中
    五、改變目錄及內部文件的屬主和屬組爲mageedu1;
    六、讓目錄及內部文件的其它用戶沒有任何權限;
    
            #!/bin/bash
            # Description:
            # Version:
            # Datetime:
            # Author:
    
            myGroup="newgroup1"
            myUser="mageedu2"
            myDir="/tmp/hellodirxyz1"
            myID=3002
    
            groupadd -g 4001 $myGroup
            useradd -u $myID -G $myGroup $myUser
            mkdir $myDir
            cp /etc/fstab $myDir
            chown -R $myUser:$myUser $myDir
            chmod -R o= $myDir
    
            unset myGroup myUser myID myDir
    
  50. 統計/bin、/usr/bin、/sbin和/usr/sbin等各目錄中的文件個數;

    # ls /bin | wc -l
    
  51. 顯示當前系統上全部用戶的shell,要求,每種shell只顯示一次;

    # cut -d: -f7 /etc/passwd | sort -u
    
  52. 取出/etc/passwd文件的第7行;

    # head -7 /etc/passwd | tail -1
    
  53. 顯示第3題中取出的第7行的用戶名;

    # head -7 /etc/passwd | tail -1 | cut -d: -f1
    
    # head -7 /etc/passwd | tail -1 | cut -d: -f1 | tr 'a-z' 'A-Z'
    
  54. 統計/etc目錄下以P或p開頭的文件個數;

    # ls -d /etc/[Pp]* | wc -l
    
  55. 寫一個腳本,用for循環實現顯示/etc/init.d/functions、/etc/rc.d/rc.sysinit和/etc/fstab各有多少行;

    for fileName in /etc/init.d/functions /etc/rc.d/rc.sysinit /etc/fstab; do
        wc -l $fileName
    done
    
    #!/bin/bash
    for fileName in /etc/init.d/functions /etc/rc.d/rc.sysinit /etc/fstab; do
        lineCount=`wc -l $fileName | cut -d' ' -f1`
        echo "$fileName: $lineCount lines."
    done
    
    #!/bin/bash
    for fileName in /etc/init.d/functions /etc/rc.d/rc.sysinit /etc/fstab; do
        echo "$fileName: `wc -l $fileName | cut -d' ' -f1` lines."
    done
    
  56. 寫一個腳本,將上一題中三個文件的複製到/tmp目錄中;用for循環實現,分別將每一個文件的最近一次的修改時間改成2016年12月15號15點43分;

    for fileName in /etc/init.d/functions /etc/rc.d/rc.sysinit /etc/fstab; do
        cp $fileName /tmp
        baseName=`basename $fileName`
        touch -m -t 201109151327 /tmp/$baseName
    done
    
  57. 寫一個腳本, 顯示/etc/passwd中第三、7和11個用戶的用戶名和ID號;

    for lineNo in 3 7 11; do
        userInfo=`head -n $lineNo /etc/passwd | tail -1 | cut -d: -f1,3`
        echo -e "User: `echo $userInfo | cut -d: -f1`\nUid: `echo $userInfo |cut -d: -f2`"
    done
    
  58. 顯示/proc/meminfo文件中以大小寫s開頭的行;

    # grep "^[sS]" /proc/meminfo
    # grep -i "^s" /proc/meminfo
    
  59. 取出默認shell爲非bash的用戶;

    # grep -v "bash$" /etc/passwd | cut -d: -f1
    
  60. 取出默認shell爲bash的且其ID號最大的用戶;

    # grep "bash$" /etc/passwd | sort -n -t: -k3 | tail -1 | cut -d: -f1
    
  61. 顯示/etc/rc.d/rc.sysinit文件中,以#開頭,後面跟至少一個空白字符,然後又有至少一個非空白字符的行;

    # grep "^#[[:space:]]\{1,\}[^[:space:]]\{1,\}" /etc/rc.d/rc.sysinit
    
  62. 顯示/boot/grub/grub.conf中以致少一個空白字符開頭的行;

    # grep "^[[:space:]]\{1,\}[^[:space:]]\{1,\}" /boot/grub/grub.conf
    
  63. 找出/etc/passwd文件中一位數或兩位數;

    # grep --color=auto "\<[0-9]\{1,2\}\>" /etc/passwd
    
  64. 找出ifconfig命令結果中的1到255之間的整數;

    # ifconfig | grep -E --color=auto "\<([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\>"
    
  65. 查看當前系統上root用戶的全部信息;

    # grep "^root\>" /etc/passwd
    
  66. 添加用戶bash和testbash、basher,然後找出當前系統上其用戶名和默認shell相同的用戶;

    # grep --color=auto "^\([[:alnum:]]\{1,\}\)\>.*\1$" /etc/passwd
    
  67. 找出netstat -tan命令執行的結果中以「LISTEN」或「ESTABLISHED」結尾的行;

  68. 取出當前系統上全部用戶的shell,要求:每種shell只顯示一次,且按升序顯示;
    # cut -d: -f7 /etc/passwd | sort -u
    

自動化

開機自啓動腳本

若是要添加爲開機啓動執行的腳本文件,可先將腳本複製或者軟鏈接到/etc/init.d/目錄下,而後用:

    update-rc.d xxx defaults NN命令(NN爲啓動順序),

將腳本添加到初始化執行的隊列中去。

注意若是腳本須要用到網絡,則NN需設置一個比較大的數字,如99。

1) 將你的啓動腳本複製到 /etc/init.d目錄下,如下假設你的腳本文件名爲 test。

2) 設置腳本文件的權限

    $ sudo chmod 755 /etc/init.d/test

3) 執行以下命令將腳本放到啓動腳本中去:

    $ cd /etc/init.d
    $ sudo update-rc.d test defaults 95
相關文章
相關標籤/搜索