天天看點

Shell程式設計基礎

1 文法基本介紹

1.1 開頭

  程式必須以下面的行開始(必須方在檔案的第一行):  

<a href="http://my.oschina.net/itblog/blog/204410#">?</a>

1

<code>#!/bin/sh</code>

  符号#!用來告訴系統它後面的參數是用來執行該檔案的程式。在這個例子中我們使用/bin/sh來執行程式。 

  當編輯好腳本時,如果要執行該腳本,還必須使其可執行。 

  要使腳本可執行:

  編譯 chmod +x filename 這樣才能用./filename 來運作

1.2 注釋 

  在進行shell程式設計時,以#開頭的句子表示注釋,直到這一行的結束。我們真誠地建議您在程式中使用注釋。

如果您使用了注釋,那麼即使相當長的時間内沒有使用該腳本,您也能在很短的時間内明白該腳本的作用

及工作原理。

1.3 變量 

  在其他程式設計語言中您必須使用變量。在shell程式設計中,所有的變量都由字元串組成,并且您不需要對變量

進行聲明。要指派給一個變量,您可以這樣寫: 

2

3

<code>#!/bin/sh </code>

<code>#對變量指派: </code>

<code>a=</code><code>"hello world"</code>

  #對變量進行指派以後,可以使用unset 變量名來清除該變量的值。

  # 現在列印變量a的内容: 

<code>echo</code> <code>"a is:"</code> 

<code>echo</code> <code>$a</code>

  #現在清除a的值: 

<code>unset</code> <code>a</code>

  #此時再顯示變量a的内容:

<code>echo</code> <code>"a is:"</code>

  echo $a  #這一行将不會有任何值顯示

  有時候變量名很容易與其他文字混淆,比如: 

<code>num=2 </code>

<code>echo</code> <code>"this is the $numnd"</code>

  這并不會列印出"this is the 2nd",而僅僅列印"this is the ",因為shell會去搜尋變量numnd的值,

但是這個變量時沒有值的。可以使用花括号來告訴shell我們要列印的是num變量: 

<code>echo</code> <code>"this is the ${num}nd"</code>

  這将列印: this is the 2nd 

1.4 環境變量

  由export關鍵字處理過的變量叫做環境變量。我們不對環境變量進行讨論,因為通常情況下僅僅在登入

腳本中使用環境變量。 

1.5 shell指令和流程控制

  在shell腳本中可以使用三類指令: 

1.5.1 unix 指令: 

  雖然在shell腳本中可以使用任意的unix指令,但是還是由一些相對更常用的指令。這些指令通常是用來

進行檔案和文字操作的。 

常用指令文法及功能 

  echo "some text": 将文字内容列印在螢幕上 

  ls: 檔案清單 

  wc –l filewc -w filewc -c file: 計算檔案行數計算檔案中的單詞數計算檔案中的字元數 

  cp sourcefile destfile: 檔案拷貝 

  mv oldname newname : 重命名檔案或移動檔案 

  rm file: 删除檔案 

  grep 'pattern' file: 在檔案内搜尋字元串比如:grep 'searchstring' file.txt 

  cut -b colnum file: 指定欲顯示的檔案内容範圍,并将它們輸出到标準輸出裝置比如:輸出

  每行第5個到第9個字元cut -b 5-9 file.txt,千萬不要和cat指令混淆,這是兩個完全不同的指令 

  cat file.txt: 輸出檔案内容到标準輸出裝置(螢幕)上 

  file somefile: 得到檔案類型 

  read var: 提示使用者輸入,并将輸入指派給變量 

  sort file.txt: 對file.txt檔案中的行進行排序 

  uniq: 删除文本檔案中出現的行列比如: sort file.txt | uniq 

  expr: 進行數學運算:例如:計算 2+3,可以這樣寫:expr 2 "+" 3 

  find: 搜尋檔案比如:根據檔案名搜尋find . -name filename -print 

  tee: 将資料輸出到标準輸出裝置(螢幕) 和檔案比如:somecommand | tee outfile 

  basename file: 傳回不包含路徑的檔案名比如: basename /bin/tux将傳回 tux 

  dirname file: 傳回檔案所在路徑比如:dirname /bin/tux将傳回 /bin 

  head file: 列印文本檔案開頭幾行 

  tail file : 列印文本檔案末尾幾行 

  sed: sed是一個基本的查找替換程式。可以從标準輸入(比如指令管道)讀入文本,并将

  結果輸出到标準輸出(螢幕)。該指令采用正規表達式(見參考)進行搜尋。 

  不要和shell中的通配符相混淆。比如:将linuxfocus 替換為 linuxfocus :

  cat text.file | sed 's/linuxfocus/linuxfocus/' &gt; newtext.file 

  awk: awk 用來從文本檔案中提取字段。預設地,字段分割符是空格,可以使用-f指定其他分割符。

      cat file.txt | awk -f, '{print 1","3 }'這裡我們使用,作為字段分割符,同時列印

  第一個和第三個字段。請注意-f後面的逗号不能省略。如果該檔案内容如下: 

  adam bor, 34, indiakerry miller, 22, usa,那麼指令輸出結果為:adam bor, indiakerry miller

1.5.2 概念: 管道, 重定向和 backtick 

  這些不是系統指令,但是他們真的很重要。

  管道 (|) 将一個指令的輸出作為另外一個指令的輸入。 

<code>grep</code> <code>"hello"</code> <code>file</code><code>.txt | </code><code>wc</code> <code>-l</code>

  在file.txt中搜尋包含有”hello”的行并計算其行數。 

  在這裡grep指令的輸出作為wc指令的輸入。當然您可以使用多個指令。 

  重定向:将指令的結果輸出到檔案,而不是标準輸出(螢幕)。

  &gt; 寫入檔案并覆寫舊檔案 

  &gt;&gt; 加到檔案的尾部,保留舊檔案内容。 

      反短斜線 

  使用反短斜線可以将一個指令的輸出作為另外一個指令的一個指令行參數。

  指令: 

<code>find</code> <code>. -mtime -1 -</code><code>type</code> <code>f -print</code>

  用來查找過去24小時(-mtime –2則表示過去48小時)内修改過的檔案。如果您

  想将所有查找到的檔案打一個包,則可以使用以下腳本: 

<code># the ticks are backticks (`) not normal quotes ('): </code>

<code>tar</code> <code>-zcvf lastmod.</code><code>tar</code><code>.gz `</code><code>find</code> <code>. -mtime -1 -</code><code>type</code> <code>f -print`</code>

1.5.3 流程控制 

1.if 

  "if" 表達式 如果條件為真則執行then後面的部分: 

4

5

6

7

8

9

<code>if</code> <code>....; </code><code>then</code> <code>(</code>

<code>    </code><code>注意</code><code>if</code><code>判斷條件後面的分号</code>

<code>)</code>

<code>elif</code> <code>....; </code><code>then</code> <code>(</code>

<code>    </code><code>elif</code><code>是</code><code>else</code> <code>if</code><code>的簡寫</code>

<code>else</code> 

<code>    .... </code>

<code>fi</code>

  大多數情況下,可以使用測試指令來對條件進行測試。比如可以比較字元串、判斷檔案

是否存在及是否可讀等等… 

  通常用" [ ] "來表示條件測試。注意這裡的空格很重要。要確定方括号的空格。 

  [ -f "somefile" ] :判斷是否是一個檔案 

  [ -x "/bin/ls" ] :判斷/bin/ls是否存在并有可執行權限 

  [ -n "var"]:判斷var變量是否有值 

  [ "a"="b" ] :判斷a和b是否相等 

  執行man test可以檢視所有測試表達式可以比較和判斷的類型。 

  直接執行以下腳本:   

<code>if</code> <code>[ </code><code>"$shell"</code> <code>= </code><code>"/bin/bash"</code> <code>]; </code><code>then</code> 

<code> </code><code>echo</code> <code>"your login shell is the bash (bourne again shell)"</code> 

<code> </code><code>echo</code> <code>"your login shell is not bash but $shell"</code> 

  變量$shell包含了登入shell的名稱,我們和/bin/bash進行了比較。 

快捷操作符 

  熟悉c語言的朋友可能會很喜歡下面的表達式: 

  [ -f "/etc/shadow" ] &amp;&amp; echo "this computer uses shadow passwors" 

  這裡 &amp;&amp; 就是一個快捷操作符,如果左邊的表達式為真則執行右邊的語句。

  您也可以認為是邏輯運算中的與操作。上例中表示如果/etc/shadow檔案存在

  則列印” this computer uses shadow passwors”。同樣或操作(||)在shell程式設計中也是

  可用的。這裡有個例子:  

<code>mailfolder=</code><code>/var/spool/mail/james</code> 

<code>[ -r </code><code>"mailfolder"</code> <code>]</code><code>' '</code><code>{ </code><code>echo</code> <code>"can not readmailfolder"</code> <code>; </code><code>exit</code> <code>1; } </code>

<code>echo</code> <code>"$mailfolder has mail from:"</code> 

<code>grep</code> <code>"^from "</code> <code>$mailfolder</code>

  該腳本首先判斷mailfolder是否可讀。如果可讀則列印該檔案中的"from" 一行。如果不可讀

  則或操作生效,列印錯誤資訊後腳本退出。這裡有個問題,那就是我們必須有兩個指令: 

  -列印錯誤資訊 

  -退出程式 

  我們使用花括号以匿名函數的形式将兩個指令放到一起作為一個指令使用。一般函數将在下文提及。 

  不用與和或操作符,我們也可以用if表達式作任何事情,但是使用與或操作符會更便利很多。

1.6 case

  case :表達式可以用來比對一個給定的字元串,而不是數字。 

  case ... in 

  這裡是可能出現的情況...) do something here ;; #case後面是兩個分号

  esac  #case開始,并将case倒過來寫以結束case語句

  先看一個例子:

10

11

<code>#!/bin/bash</code>

<code>echo</code> <code>"please input yes/y/y/yes or no/n/n/no:"</code><code>  </code>

<code>read</code> <code>string  </code>

<code>case</code> <code>$string </code><code>in</code> <code>    </code>

<code>    </code><code>yes</code><code>|y|y|yes)      </code>

<code>        </code><code>echo</code> <code>"you input yes!"</code><code>;;    </code>

<code>    </code><code>no|n|n|no)      </code>

<code>        </code><code>echo</code> <code>"you input no!"</code><code>;;    </code>

<code>    </code><code>*)</code>

<code>        </code><code>echo</code> <code>"error!"</code><code>;;</code>

<code>esac</code>

  這個腳本提示輸入資料,然後根據輸入顯示判定結果。*)表示其他所有沒有列舉的類型,和java的case

  中的default的作用相同。

  讓我們再看一個例子。 file指令可以辨識出一個給定檔案的檔案類型,比如:

<code>file</code> <code>lf.gz</code>

  這将傳回:  

<code>lf.gz: </code><code>gzip</code> <code>compressed data, deflated, original filename, </code>

<code>last modified: mon aug 27 23:09:18 2001, os: unix</code>

  我們利用這一點寫了一個叫做smartzip的腳本,該腳本可以自動解壓bzip2, gzip 和zip 類型的壓縮檔案:  

<code>ftype=`</code><code>file</code> <code>"$1"</code><code>` </code>

<code>case</code> <code>"$ftype"</code> <code>in</code> 

<code>"$1: zip archive"</code><code>*) </code>

<code>  unzip </code><code>"$1"</code> <code>;; </code>

<code>"$1: gzip compressed"</code><code>*) </code>

<code>  gunzip </code><code>"$1"</code> <code>;; </code>

<code>"$1: bzip2 compressed"</code><code>*) </code>

<code>  bunzip2 </code><code>"$1"</code> <code>;; </code>

<code>*) </code><code>echo</code> <code>"file $1 can not be uncompressed with smartzip"</code><code>;; </code>

  您可能注意到我們在這裡使用了一個特殊的變量$1。該變量包含了傳遞給該程式的第一個參數值。

  也就是說,當我們運作: 

<code>smartzip articles.zip</code>

  $1 就是字元串 articles.zip 

1.7 selsect

  select 表達式是一種bash的擴充應用,尤其擅長于互動式使用。使用者可以從一組不同的值中進行選擇。 

<code>select</code> <code>var </code><code>in</code> <code>... ; </code>

<code>do</code>

<code> </code><code>break</code>

  done #這裡的do和done相當于普通程式中的一對花括号,表示一個語句塊

  .... now $var can be used .... 

  下面是一個例子: 

<code>echo</code> <code>"what is your favourite os?"</code> 

<code>select</code> <code>var </code><code>in</code> <code>"linux"</code> <code>"gnu hurd"</code> <code>"free bsd"</code> <code>"other"</code><code>; </code><code>do</code> 

<code>  </code><code>break</code> 

<code>done</code> 

<code>echo</code> <code>"you have selected $var"</code>

  下面是該腳本運作的結果: 

<code>what is your favourite os? </code>

<code>1) linux </code>

<code>2) gnu hurd </code>

<code>3) free bsd </code>

<code>4) other </code>

<code>#? 1 </code>

<code>you have selected linux</code>

1.8 loop

1.8.1 while loop: while-loop的文法如下: 

<code>while</code> <code>...; </code><code>do</code> 

<code>  .... </code>

<code>done</code>

  while-loop 将運作直到表達式測試為假。will run while the expression that we test for is true. 

  關鍵字"break" 用來跳出循環。而關鍵字”continue”用來不執行餘下的部分而直接跳到下一個循環。 

  例如:i=0, 循環輸出整數i的值,每次循環後i+1,當i&gt;3後停止循環:

<code>i=0  </code><code>#定義變量i并指派。注意,等号兩邊都不能有空格!  </code>

<code>while</code> <code>[ i -</code><code>le</code> <code>3 ]; </code><code>do</code>  <code>#le表示less than,相當于數學中的 "&lt;"       </code>

<code>    </code><code>echo</code> <code>"i =i"</code><code>   </code><code>#為變量指派時,不加""符号,擷取變量的值時,加上""符号    </code>

<code>    </code><code>i=`</code><code>expr</code> <code>$i + 1`  </code><code>#這裡使用了前面提到的"`"符号,這裡将表達式的結果重新指派給i  </code>

1.8.2 for loop:表達式檢視一個字元串清單 (字元串用空格分隔) 然後将其賦給一個變量: 

<code>for</code> <code>var </code><code>in</code> <code>....; </code><code>do</code> 

<code>  .... </code>

  在下面的例子中,将分别列印abc到螢幕上:  

<code>for</code> <code>var </code><code>in</code> <code>a b c ; </code><code>do</code> 

<code>  </code><code>echo</code> <code>"var is $var"</code> 

  下面是一個更為有用的腳本showrpm,其功能是列印一些rpm包的統計資訊: 

12

<code># list a content summary of a number of rpm packages </code>

<code># usage: showrpm rpmfile1 rpmfile2 ... </code>

<code># example: showrpm /cdrom/redhat/rpms/*.rpm </code>

<code>for</code> <code>rpmpackage </code><code>in</code> <code>$*; </code><code>do</code> 

<code> </code><code>if</code> <code>[ -r </code><code>"$rpmpackage"</code> <code>];</code><code>then</code> 

<code>  </code><code>echo</code> <code>"=============== $rpmpackage =============="</code> 

<code>  rpm -qi -p $rpmpackage </code>

<code> </code><code>else</code> 

<code>  </code><code>echo</code> <code>"error: cannot read file $rpmpackage"</code> 

<code> </code><code>fi</code> 

  這裡出現了第二個特殊的變量$*,該變量包含了所有輸入的指令行參數值。

  如果您運作showrpm openssh.rpm w3m.rpm webgrep.rpm 

  此時 $* 包含了 3 個字元串,即openssh.rpm, w3m.rpm and webgrep.rpm. 

  再看一個例子:循環輸出1~5之間的整數:

<code>#for loop test  </code>

<code>for</code> <code>i </code><code>in</code> <code>(seq15);</code><code>do</code><code>    </code>

<code>    </code><code>echoi  </code>

1.8.3 until loop: until loop的寫法和while loop的寫法差不多。

  看一個例子:

<code>a=2 </code>

<code>b=2</code>

<code>until</code> <code>[ a -gt 5 ] &amp;&amp; [b -gt 5 ]; </code><code>do</code>

<code>    </code><code>echo</code> <code>`</code><code>expr</code> <code>a+b`</code>

<code>    </code><code>a=`</code><code>expr</code> <code>a+1`</code>

<code>    </code><code>b=`</code><code>expr</code> <code>b + 1`</code>

  輸出結果為: 

<code>4</code>

<code>6</code>

<code>8</code>

<code>10</code>

  其中,until語句塊中的指派語句也可以寫成:a=((a+1))b=((b+1)),就像下面這樣:

  例:求1+2+3+...+100的和

<code>#!/bin/bash  </code>

<code>i=0  </code>

<code>sum</code><code>=0  </code>

<code>while</code> <code>[ i -</code><code>le</code> <code>100 ]; </code><code>do</code>   <code>    </code>

<code>    </code><code>#sum=`exprsum + i`       </code>

<code>    </code><code>#i=`expri + 1`    </code>

<code>    </code><code>sum</code><code>=((</code><code>sum</code><code>+i))    </code>

<code>    </code><code>i=((i+1))  </code>

<code>done</code><code>  </code>

<code>echo</code> <code>$</code><code>sum</code>

2. 引号 

  在向程式傳遞任何參數之前,程式會擴充通配符和變量。這裡所謂擴充的意思是程式會把通配符

(比如*)替換成合适的檔案名,它變量替換成變量值。為了防 止程式作這種替換,您可以使用

引号:讓我們來看一個例子,假設在目前目錄下有一些檔案,兩個jpg檔案, mail.jpg 和tux.jpg。 

  編譯shell腳本 

  #ch#!/bin/sh mod +x filename 

   cho *.jpg ∪緩螅梢醞&amp;uuml;淙耄?./filename 來執行您的腳本。 

  這将列印出"mail.jpg tux.jpg"的結果。 

    引号 (單引号和雙引号) 将防止這種通配符擴充: 

<code>echo</code> <code>"*.jpg"</code> 

<code>echo</code> <code>'*.jpg'</code>

  這将列印"*.jpg" 兩次。 

    單引号更嚴格一些。它可以防止任何變量擴充。雙引号可以防止通配符擴充但允許變量擴充。  

<code>echo</code> <code>$shell </code>

<code>echo</code> <code>"$shell"</code> 

<code>echo</code> <code>'$shell'</code>

  運作結果為: 

<code>/bin/bash</code> 

<code>/bin/bash</code> <code>#雙引号的值被替換掉 </code>

<code>$shell </code><code>#單引号的值沒有被替換</code>

    最後,還有一種防止這種擴充的方法,那就是使用轉義字元——反斜杆: 

<code>echo</code> <code>*.jpg </code>

<code>echo</code> <code>$shell</code>

  這将輸出:  

<code>*.jpg </code>

<code>$shell</code>

3 函數

  在shell腳本中,可以把公共的腳本提出來作為一個程式塊,用函數來包裝,這類寫法和javascript的寫法差不多,隻是花括号中為shell指令而已:

<code>function</code> <code>name() {</code>

<code>  </code><code>#do something here</code>

<code>}</code>

  需要注意的是,函數的調用必須在函數的聲明之後。

  例如:寫一個将兩個數相加的函數,并調用:

<code>function</code> <code>add() {</code>

<code>  </code><code>echo</code> <code>"this is function add's body."</code>

<code>  result=`</code><code>expr</code> <code>1+2`</code>

<code>  </code><code>return</code> <code>$result</code>

<code>}  </code>

<code>echo</code> <code>"now call this function"</code>

<code>add 1 2</code>

<code>echo</code> <code>"$?"</code>

  結果将輸出: 

<code>now call this </code><code>function</code>

<code>3</code>

  你可能注意到,這裡使用了"$?",這表示上次shell腳本執行的傳回值。

4 linux中shell變量#,@,0,1,$2的含義解釋: 

  變量說明: 

$$ 

shell本身的pid(processid) 

$! 

shell最後運作的背景process的pid 

$? 

最後運作的指令的結束代碼(傳回值) 

$- 

使用set指令設定的flag一覽 

$* 

所有參數清單。如"$*"用「"」括起來的情況、以"$1 $2 … $n"的形式輸出所有參數。 

$@ 

所有參數清單。如"$@"用「"」括起來的情況、以"$1" "$2" … "$n" 的形式輸出所有參數。 

$# 

添加到shell的參數個數 

$0 

shell本身的檔案名 

$1~$n 

添加到shell的各參數值。$1是第1參數、$2是第2參數…。

  大家可以自己在終端輸入指令看看結果。

繼續閱讀