詳解Bash命令列處理(轉)

gugu99發表於2007-08-10
詳解Bash命令列處理(轉)[@more@]

  前言

  我看很多兄弟寫指令碼或命令時出現錯誤的主要原因,是因為不瞭解bash的命令列處理。我在這裡總結了一下,大家可以參考一下。其中也涉及到雙引號,單引號以及eval的技巧,我會一一講述。

  Shell從標準輸入或指令碼中讀取的每行稱為一個管道行,它包含一個或多個由0個或多個管道字元(|)分隔的命令。對每一個管道行,進行12個步驟的處理。

  一、bash命令處理的12個步驟;

  +-------------+      單引號

  |-------------------------&gt|       |--------------------------|

  | -----------------------&gt| 1.分隔成記號|---- ---------------|   |

  | |  -------------------&gt|       |   雙引號    |   |

  | |  |          +-------------+          |   |

  | |  |             ||             |   |

  | |  |讀取下一個命令      /             |   |

  | |  |   +-------------------------------------------+  |   |

  | |  |   |          2.           |  |   |

  | |  ------|       檢驗第一個記號        |  |   |

  | |     |開放的關鍵字          其他關鍵字 |  |   |

  | |     |        非關鍵字          |  |   |

  | |     +-------------------------------------------+  |   |

  | |               ||             |   |

  | |               /             |   |

  | |      +-----------------------------+        |   |

  | | 擴充套件別名 |      3. 檢驗第一個記號 |        |   |

  | |------------| 別名            |        |   |

  |        |       不是別名    |        |   |

  |        +-----------------------------+        |   |

  |                 ||             |   |

  |                 /             |   |

  |              +--------------+         |   |

  |              | 4.大括號擴充套件 |         |   |

  |              +--------------+         |   |

  |                 ||             |   |

  |                 /             |   |

  |              +--------------+         |   |

  |              | 5.~符號擴充套件 |         |   |

  |              +--------------+         |   |

  |                 ||             |   |

  |                 /             |   |

  |              +--------------+    雙引號   |   |

  |              | 6.引數擴充套件 |

  |              +--------------+            |

  |                 ||                |

  |                 /                |

  |          +------------------------------+        |

  |          | 7.命令替換(巢狀命令列處理) |        |

  |          +------------------------------+        |

  |                 ||                |

  |                 /                |

  |              +--------------+   雙引號      |

  |              | 8.算術擴充套件 |------------------|   |

  |              +--------------+         |   |

  |                 ||             |   |

  |                 /             |   |

  |              +--------------+         |   |

  |              | 9.單詞分割 |         |   |

  |              +--------------+         |   |

  |                 ||             |   |

  |                 /             |   |

  |              +--------------+         |   |

  |              | 10.路徑名擴充套件|         |   |

  |              +--------------+         |   |

  |                 ||             |   |

  |                 /             |   |

  |        +----------------------------------------+  |   |

  |        | 11.命令查尋:函式,內建命令,可執行檔案|  |        +----------------------------------------+

  |                 ||

  |                 /

  |將引數帶入下一個命令    +-------------+

  |----------eval--------------| 12.執行命令 |

                 +-------------+

  結合上面的插圖,這裡給出命令列的12個步驟。

  1、將命令列分成由固定元字符集分隔的記號;

  SPACE, TAB, NEWLINE, ; , (, ), , |, &

  記號型別包括單詞,關鍵字,I/O重定向符和分號。

  2、檢測每個命令的第一個記號,檢視是否為不帶引號或反斜線的關鍵字。

  如果是一個開放的關鍵字,如if和其他控制結構起始字串,function,{或(,則命令實際上為一複合命令。shell在內部對複合命令進行處理,讀取下一個命令,並重復這一過程。如果關鍵字不是複合命令起始字串(如then等一個控制結構中間出現的關鍵字),則給出語法錯誤訊號。

  3、依據別名列表檢查每個命令的第一個關鍵字;

  如果找到相應匹配,則替換其別名定義,並退回第一步;否則進入第4步。該策略允許遞迴別名,還允許定義關鍵字別名。如alias procedure=function

  4、執行大括號擴充套件,例如a{b,c}變成ab ac

  5、如果~位於單詞開頭,用$HOME替換~。

  使用usr的主目錄替換~user。

  6、對任何以符號$開頭的表示式執行引數(變數)替換;

  7、對形式$(string)的表示式進行命令替換;

  這裡是巢狀的命令列處理。

  8、計算形式為$((string))的算術表示式;

  9、把行的引數,命令和算術替換部分再次分成單詞,這次它使用$IFS中的字元做分割符而不是步驟1的元字符集;

  10、對出現*, ?, [ / ]對執行路徑名擴充套件,也稱為萬用字元擴充套件;

  11、按命令優先順序表(跳過別名),進行命令查尋;

  12、設定完I/O重定向和其他操作後執行該命令。

  二、關於引用

  1、單引號跳過了前10個步驟,不能在單引號裡放單引號

  2、雙引號跳過了步驟1~5,步驟9~10,也就是說,只處理6~8個步驟。

  也就是說,雙引號忽略了管道字元,別名,~替換,萬用字元擴充套件,和透過分隔符分裂成單詞。

  雙引號裡的單引號沒有作用,但雙引號允許引數替換,命令替換和算術表示式求值。可以在雙引號裡包含雙引號,方式是加上轉義符"",還必須轉義$, `, 。

  三、eval的作用;

  eval的作用是再次執行命令列處理,也就是說,對一個命令列,執行兩次命令列處理。這個命令要用好,就要費一定的功夫。我舉兩個例子,拋磚引玉。

  1、例子1:用eval技巧實現shell的控制結構for

  用eval技巧實現shell的控制結構for。

  [root@home root]# cat myscript1

#!/bin/sh

evalit(){

    if [ $cnt = 1 ];then

        eval $@

        return

    else

        let cnt=cnt-1

        evalit $@

    fi

    eval $@

}

cnt=$1

echo $cnt | egrep "^[1-9][0-9]*$" >/dev/null

if [ $? -eq 0 ]; then

    shift

    evalit $@

else

    echo 'ERROR!!! Check your input!'

fi

[root@home root]# ./myscript1 3 hostname

home

home

home

[root@home root]# ./myscript1 5 id |cut -f1 -d' '

uid=0(root)

uid=0(root)

uid=0(root)

uid=0(root)

uid=0(root)

  注意:bash裡有兩個很特殊的變數,它們儲存了引數列表。

  $*,儲存了以$IFS指定的分割符所分割的字串組。

  $@,原樣儲存了引數列表,也就是"$1""$2"...

  這裡我使用了函式遞迴以及eval實現了for結構。

  當執行eval $@時,它經歷了步驟如下:

  第1步,分割成eval $@

  第6步,擴充套件$@為hostname

  第11步,找到內建命令eval

  重複一次命令列處理,第11步,找到hostname命令,執行。

  注意:也許有人想當然地認為,何必用eval呢?直接$@來執行命令就可以了嘛。

  例子2:一個典型錯誤的例子

  錯誤!這裡給個典型的例子大家看看。

  [root@home root]# a="id | cut -f1 -d' '"

[root@home root]# $a

id:無效選項 -- f

請嘗試執行‘id --help’來獲取更多資訊。

[root@home root]# eval $a

uid=0(root)

  如果命令列復雜的話(包括管道或者其他字元),直接執行$a字串的內容就會出錯。分析如下。

  $a的處理位於第6步──引數擴充套件,也就是說,跳過了管道分析,於是"|", "cut", "-f1", "-d"都變成了id命令的引數,當然就出錯啦。

  但使用了eval,它把第一遍命令列處理所得的"id", "|", "cut", "-f1", "-d"這些字串再次進行命令列處理,這次就能正確分析其中的管道了。

  總而言之:要保證你的命令或指令碼設計能正確透過命令列處理,跳過任意一步,都可能造成意料外的錯誤!

  例子3:設定系統的ls色彩顯示

  eval $(dircolors -b /etc/dircolors)

  eval語句通知shell接受eval引數,並再次透過命令列處理的所有步驟執行它們。

  它使你可以編寫指令碼隨意建立命令字串,然後把它們傳遞給shell執行;

  $()是命令替換,返回命令的輸出字串。

  其中dircolors命令根據/etc/dircolors配置檔案生成設定環境變數LS_COLORS的bash程式碼,內容如下

  [root@localhost root]# dircolors -b > tmp

[root@localhost root]# cat tmp

LS_COLORS='no=00:fi=00:di=01;34:ln=01; ......

export LS_COLORS

#這裡我沒有指定配置檔案,所以dircolors按預置資料庫生成程式碼。

其輸出被eval命令傳遞給shell執行。

  eval是對Bash Shell命令列處理規則的靈活應用,進而構造"智慧"命令實現複雜的功能。

  上面提及的命令是eval其中一個很普通的應用,它重複了1次命令列引數傳遞過程,純粹地執行命令的命令。

  其實它是bash的難點,是高階bash程式設計師的必修之技。

  四、命令優先順序表

  1、別名

  2、關鍵字

  3、函式

  4、內建命令

  5、指令碼或可執行程式($PATH)

  五、鑑於一些學習中會遇到的困惑,我再給出一些有趣的命令。

  1、command builtin enable

  上面的命令列提及過,第11步會進行命令查詢,那它的具體過程如何呢?

  它的預設查詢次序為函式,內部命令,指令碼和可執行程式碼。我們往往要在實際程式設計中跳過一些查詢項以滿足一定的功能需求。這時候就要用到這三個命令來施展魔法~~

  2、command

  跳過別名和函式的查詢,換句話說,它只查詢內部命令以及搜尋路徑中找到的指令碼或可執行程式。

  這裡舉個有趣的例子。

  [root@home root]# type -all pwd

pwd is a shell builtin

pwd is /bin/pwd

[root@home root]# cat myscript2

#!/bin/sh

pwd(){

    echo "This is the current directory."

    command pwd

}

pwd

[root@home root]# ./myscript2

This is the current directory.

/root

  我用pwd()函式取代了內建命令pwd以及外部命令/bin/pwd,然後在指令碼里執行內建命令pwd。在這裡我們為什麼要用command呢?是為了避免函式陷入遞迴迴圈,因為函式名與內建命令同名,而函式的優先順序比內建命令高。

  3、builtin

  顧名思義,它只查詢內建命令。這個命令很簡單,就不多說了。

  4、enable

  與builtin相反,它遮蔽一個內建命令,允許執行一個shell指令碼或同名的可執行程式碼而無須給出完全路徑名。

  舉個例子吧。

  pwd命令有兩個,一個是shell內建的,一個是可執行程式。

  當執行一些奇怪的路徑名後,shell內建的pwd會列印出"錯誤資訊",但外部的pwd會列印出當前目錄的"原來面目"。請看下面:

  [root@home root]# cd //

[root@home //]# pwd

//

[root@home //]# type -all pwd

pwd is a shell builtin

pwd is /bin/pwd

[root@home //]# /bin/pwd

/

[root@home //]# enable -n pwd

[root@home //]# pwd

/

  這樣,用enable -n遮蔽內建pwd命令後,就可以用外部pwd列印出正確的路徑名了。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-939811/,如需轉載,請註明出處,否則將追究法律責任。

相關文章