Linux(BASH)命令搜尋機制

來源:互聯網
上載者:User

From:http://www.mike.org.cn/articles/linux-linux-bash-command-search-mechanism/

本文假設的環境是GNU/Linux,且shell是BASH;
  
  注意: 另外,我們討論的前提是當你鍵入一個命令時並沒有指定該命令的路徑, 舉例來說就是我們鍵入的命令是以commandname的形式而不是/path/commandname或./path/commandname的形式來 啟動並執行. 一旦我們指定了命令(或指令碼或二進位檔案)相對或絕對路徑時就談不上搜尋機制了.

  本文講解的內容是: 通常,我們在Linux系統終端提示符下鍵入如ls等命令時,shell是如何找到這個命令的呢? shell下都有哪幾類命令呢? 這些命令是如何被載入的呢?
  
  一、Linux命令的分類:
  
  包括:alias, keyword, function, built-in, $PATH這5類

  二、 Linux命令搜尋順序:

  當我們鍵入某個命令時, 那麼shell會按照alias->keyword->function,->built-in->$PATH的順序進行搜尋, 本著”先到先得”的原則, 就是說如果有如名為mycmd的命令同時存在於alias和function中的話, 那麼肯定會使用alias的mycmd命令(當然, 這不是絕對的, 下面會說到特例).

  三、相關

  set +-h, hash, type, command, enable, builtin

  1) hash命令:

  首先, 我們來看hash這命令(和我上面說的”不是絕對的”有關係了!), hash命令用於記錄在當前shell環境下使用者曾經鍵入的命令路徑記錄緩衝表, 主要是為了加快命令搜尋速度. 下面看個例子:

  例:我在shell下鍵入 ls, find, pwd, ls, echo “Hello, world”, mail及if共7個命令(注意, ls執行2次), 下面是history的結果:

  1 ls
  2 find
  3 pwd
  4 ls
  5 echo “Hello, world”
  6 mail
  7 if
  
  那麼, 現在我執行hash命令, 其顯示結果為:
  
  [ancharn@fc8 ~]$ hash
  hits command
  1 /bin/mail
  2 /bin/ls
  1 /usr/bin/find

  不知大家發現了什麼沒有? 這個hash表左邊一列表示該命令在當前shell環境下共被使用了幾次, 右邊一列表示命令路徑. 但是我們發現這個hash緩衝中缺少了if,pwd和echo3個命令, 為什麼呢? 我們在這兒要得出一個重要的結論就是: (1) hash不會記錄function, built-in命令(其實還包括alias), 為什麼呢? 答案是因為他們沒有路徑, 即不會存在於某個目錄之下, 它們是隨shell而載入進而存在於記憶體中, 所以這樣的命令還有必要進行緩衝以提高搜尋效率嗎?!

  但是有人會說, ls不是被hash記錄下來了嗎? 沒錯, 你的觀察很細緻, 通常ls在bash中是一個alias, 那麼, 在這兒我們先下一個結論: (2) alias中若定義的是包含了路徑的別名命令則不會被記錄到hash中, 只有沒有指定路徑的alias才會被記錄到hash中. 情況例子:

  這是我當前shell(bash)環境下的ls別名的定義

  [ancharn@fc8 //]$ alias ls
  alias ls=’ls –color=auto’
  
  (注意:後面的”ls –color=auto”沒有指定如/bin/ls這樣的路徑)

  所以, 正如你看到的, 上面我鍵入了2次ls命令(是ls –color=auto的別名), 那麼在hash中能夠看到被記錄; 下面看個write命令的例子:

  [ancharn@fc8 //]$ alias write
  -bash: alias: write: not found
  
  [ancharn@fc8 //]$ write
  usage: write user [tty]

  [ancharn@fc8 //]$ hash  
  hits command
  1 /usr/bin/write
  1 /bin/mail
  2 /bin/ls
  1 /usr/bin/find

  write 這個命令沒有alias, 也就是說當執行write命令時其實找到的是PATH變數中的/usr/bin/write這個二進位檔案來執行的, 這時hash記錄了write的路徑並被引用了1次, 然後我定義write別名就是write本身, 但是指定具體路徑是/usr/bin/write:

  [ancharn@fc8 //]$ alias write=’/usr/bin/write’
  [ancharn@fc8 //]$ alias write
  alias write=’/usr/bin/write’
  [ancharn@fc8 //]$ write
  usage: write user [tty]
  [ancharn@fc8 //]$ hash
  hits command
  1 /usr/bin/write
  1 /bin/mail
  2 /bin/ls
  1 /usr/bin/find

  請看, hash表中的write的hits數還是1次; 這裡要注意的是當我們定義了write的alias後(指定路徑), PATH就不會被搜到了, 為什麼呢? 很簡單, 因為write的alias中已經指明了它的具體路徑了!

  接著unalias掉write重新定義write別名:
  
  [ancharn@fc8 //]$ unalias write
  [ancharn@fc8 //]$ alias write
  -bash: alias: write: not found

  [ancharn@fc8 //]$ alias write=’write’
  [ancharn@fc8 //]$ alias write
  alias write=’write’

  [ancharn@fc8 //]$ write
  usage: write user [tty]

  [ancharn@fc8 //]$ hash
  hits command
  2 /usr/bin/write
  1 /bin/mail
  2 /bin/ls
  1 /usr/bin/find

  這次, 我們沒有指定write別名中的路徑, 當我們定義好write的別名後去執行write時, hash表中就會增加一次hits.這裡要注意的是當我們定義了write的alias後(不指定路徑, 請和上面的例子比較下), PATH就會被搜到了, 所以hash的hits增加了. 請大家切記alias中若定義的是包含了路徑的別名命令則不會被記錄到hash中, 只有沒有指定路徑的alias才會被記錄到hash中這條結論.

  另外, hash因為是built-in命令, 所以用help hash來查看協助. 常用的有hash -r用於清空hash表, hash -d name用於delete某個command. 如:

  [ancharn@fc8 //]$ hash
  hits command
  3 /usr/bin/write
  1 /bin/mail
  2 /bin/ls
  1 /usr/bin/find

  刪除具體的:
  [ancharn@fc8 //]$ hash -d ls
  [ancharn@fc8 //]$ hash
  hits command
  3 /usr/bin/write
  1 /bin/mail
  1 /usr/bin/find
  
  清空hash:
  [ancharn@fc8 //]$ hash -r
  [ancharn@fc8 //]$ hash
  hash: hash table empty

  2) set +-h:
  
  set 命令大家應該很熟悉, 我們在這裡主要說的是set +-h的作用: help set可以看到”-h Remember the location of commands as they are looked up.” 中文意思就是記憶命令的路徑以便於查詢. 當我們鍵入set +h後再運行hash:
  
  [ancharn@fc8 //]$ set +h
  [ancharn@fc8 //]$ hash
  -bash: hash: hashing disabled
 
  也就是說”set +h”用于禁用hash而”set -h”用於啟用hash.

  3) type:

  此命令用於列出某個命令屬於哪類. 如:

  [ancharn@fc8 //]$ type -a pwd
  pwd is a shell builtin
  pwd is /bin/pwd
  pwd屬於內建和PATH變數中.

  [ancharn@fc8 //]$ type pwd
  pwd is a shell builtin
  直接用type commandname可以告訴你該命令在運行時會執行哪一類.

  4) command:
  
  該命令的作用是: 如果你有一個命令如gcc既是一個function, 同時又是一個PATH變數中的命令, 那麼如果你直接執行gcc, 按照順序來說, 會執行function而不是gcc的PATH變數中的命令, 而用command gcc會跳過function的選擇.

  [ancharn@fc8 //]$ function gcc { echo “just a test for gcc”; }

  [ancharn@fc8 //]$ gcc
  just a test for gcc

  [ancharn@fc8 //]$ command gcc
  gcc: no input files

  5)enable:
  
  enable命令如果直接運行則會列出當前shell的所有built-in命令, enable -n commandname會在當前shell下disable掉該內建命令:
  
  [ancharn@fc8 ~]$ type -a pwd
  pwd is a shell builtin
  pwd is /bin/pwd

  [ancharn@fc8 ~]$ enable -n pwd
  [ancharn@fc8 ~]$ type -a pwd
  pwd is /bin/pwd

  [ancharn@fc8 ~]$ enable pwd
  [ancharn@fc8 ~]$ type -a pwd
  pwd is a shell builtin
  pwd is /bin/pwd

  6) builtin
 
  用於運行一個內建命令. 例如:
  
  [ancharn@fc8 ~]$ cd /var
  [ancharn@fc8 var]$ function pwd { echo “just a test for pwd”; }
  [ancharn@fc8 var]$ type -a pwd
  pwd is a function
  pwd ()

  {
  echo “just a test for pwd”
  }
  pwd is a shell builtin
  pwd is /bin/pwd

  (注: pwd既是函數, 又是內建命令, 又存在PATH變數中)

  [ancharn@fc8 var]$ pwd
  just a test for pwd
  [ancharn@fc8 var]$ builtin pwd // (注: 此時我們就去直接執行pwd這個內建命令)
  /var

  小結: 我們都知道了shell在搜尋命令時的順序是alias->keyword->function,->built-in->$PATH, 那麼其中還有2點需要注意的就是 (1) hash不會記錄function, built-in命令(其實還包括alias), (2) alias中若定義的是包含了路徑的別名命令則不會被記錄到hash中, 只有沒有指定路徑的alias才會被記錄到hash中. 另外, (3) 不要忘記, 我們討論的前提是a) 受限於具體的shell種類b)且只在當前shell環境有效.切記!!!

  到這裡, 請大家來思考一個問題:

  請看下面的執行情況:

  [ancharn@fc8 var]$ function gcc { echo “just a test for gcc”; }
  [ancharn@fc8 var]$ alias gcc=’gcc’

  [ancharn@fc8 var]$ gcc
  just a test for gcc

  [ancharn@fc8 var]$ /usr/bin/gcc
  gcc: no input files

  [ancharn@fc8 var]$ alias gcc=’/usr/bin/gcc’
  [ancharn@fc8 var]$ gcc
  gcc: no input files

  [ancharn@fc8 var]$

  為什麼定義了gcc這個funtion後, 兩次定義gcc的alias時指定不指定具體的/usr/bin/gcc路徑時, 執行gcc這個命令的反應不同呢? 按照alias->keyword->function,->built-in->$PATH 這個順序來看, 應該執行alias的gcc啊?! 請思考!
當然, 別著急, 後面我會給出答案. 但是, 請您思考下!

  四, 命令舉例:

  * alias(別名):
  alias 命令通常被設定在檔案~/.bashrc和/etc/bashrc中,~/.bashrc通常用於使用者自己的環境,而/etc/bashrc用於全域定義 (即對所有使用者生效,當然,只對使用者shell是bash生效). 具體的這兩個檔案的關係及如何載入在後面有介紹.

  * Shell keyword(shell關鍵字):
  諸如if,while,until,case,for這些命令.

  * Function(函數):
  
  舉例:
  
  定義個名為pwd的函數, 其功能是簡單地顯示”my function pwd”這句話
  function pwd { echo “my function pwd”; }
  定義好了之後可以用set或type -a pwd來查看,取消則用unset pwd即可。

  * Shell built-in command(shell內建命令):
  命令enable可以查看所有當前shell環境下的內建命令; 或者用man cd(任何一個內建命令均可)查看到的manpage的上部列出了全部的內建命令. 

  * PATH variable
  該變數定義在檔案/etc/profile, /etc/profile.d/*.sh(POSIX), ~/.bash_profile(Bash)中.

  其載入順序是: 先/etc/profile (invoke /etc/profile.d/*.sh), 然後是~/.bash_profile, 再由~/.bash_profile調用執行 ~/.bashrc, 然後由~/.bashrc去調用執行 ~/.bashrc, ~/.bashrc再調用執行檔案/etc/bashrc.
  
  1) 為了查看具體的載入順序, 你可以在四個檔案中的頭部和尾部分別添加兩句話, 例如:

  [ancharn@fc8 ~]$ cat ~/.bashrc
  echo “start of ~/.bashrc”
  if [ -f /etc/bashrc ] ; then
  . /etc/bashrc
  fi
  alias ll=’ls -l’
  alias cp=’cp -i’
  alias mv=’mv -i’
  alias rm=’rm -i’
  ……

  echo “end of ~/.bashrc”
  其它的檔案一樣添加, 這樣當你用某個使用者登入系統時就會看到如下的顯示, 諸如:
  start of /etc/profile
  end of /etc/profile
  start of ~/.bash_profile
  start of ~/.bashrc
  start of /etc/bashrc
  end of /etc/bashrc
  end of ~/.bashrc
  end of ~/.bash_profile

  從上面的顯示你能夠清晰的看到每個檔案的載入順序及相互調用執行關係(注意查看start和end).

  2) PATH變數和hash的關係
  這裡, 我們來看一個例子:

  [ancharn@fc8 ~]$ echo $PATH
  /usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/ancharn/bin
 
  我首先在/home/ancharn/bin目錄下寫一個名為test.sh的指令碼,內容如下:
  [ancharn@fc8 bin]$ cat /home/ancharn/bin/test.sh
  
  #!/bin/sh
  # just test for PATH and hash
  echo “This is my 1st shell script in /home/ancharn/bin directory.”

  # end
  
  [ancharn@fc8 bin]$
  那麼, 執行test.sh這個指令碼如下:
  [ancharn@fc8 /]$ echo $PATH
  /usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/ancharn/bin
  [ancharn@fc8 /]$ test.sh
  This is my 1st shell script in /home/ancharn/bin directory.
  [ancharn@fc8 /]$ hash
  hits command
  1 /home/ancharn/bin/test.sh
  接著,在/usr/bin目錄下建立一個同test.sh名的檔案, 內容如下:

  [ancharn@fc8 /]$ cat /usr/bin/test.sh
  #!/bin/sh
  # just test for PATH and hash
  echo “This is my 2nd shell script in /usr/bin directory.”

  # end
  繼續執行test.sh指令碼:

  [ancharn@fc8 /]$ test.sh
  This is my 1st shell script in /home/ancharn/bin directory.
  [ancharn@fc8 /]$ hash
  hits command
  2 /home/ancharn/bin/test.sh
 
  說明什麼呢?

  如果按照PATH的順序即/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/ancharn /bin, 會先找/usr/bin然後再找/home/ancharn/bin, 注意, 這個前提是hash表中沒有該命令的記錄, 因此我們看到/usr/bin/test.sh指令碼並沒有被執行, 因為在執行test.sh前, shell去hash表中查看了緩衝, 進而繼續執行了/home/ancharn/bin/test.sh指令碼, 所以我們看到hits數增加了一次, 而/usr/bin/test.sh不會被執行.

  現在, 我們清空hash, 重新執行test.sh指令碼:
  
  [ancharn@fc8 /]$ hash -r
  [ancharn@fc8 /]$ hash
  hash: hash table empty
  [ancharn@fc8 /]$ test.sh
  This is my 2nd shell script in /usr/bin directory.
  [ancharn@fc8 /]$ hash
  hits command
  1 /usr/bin/test.sh

  現在正常了. 所以一定要注意PATH和hash的這層關係.

  注意: su, su-, bash –login, bash –norc這些命令的不同就在於是否執行了login-shell, 大家可以su和su -後, 再去運行echo $PATH看看有何不同.

  好了, 回答上面的思考題, 其核心在於alias如果定義的如alias gcc=’gcc’時, 其實alias->keyword->function,->built-in->$PATH 這個順序並沒有變, 但是要知道alias gcc=’gcc’這種沒有指定路徑的alias會在找到gcc這個alias後, 再去找到後面指定的’gcc’, 怎麼找? 當然到下一個了, 就是keyword->function….這個順序了. 而如果是alias gcc=’/usr/bin/gcc’這樣的指定具體路徑的定義alias的話, 那麼alias執行後就直接找到了那個具體檔案而跳過了後面的所有搜尋(即keyword->function,->built-in->$PATH). 請大家留意.

  最後, 大家在做實驗驗證的時候可以分成2類驗證, 因為一個命令不可能既屬於keyword又屬於built-in, 所以你可以:
  
  1) 選擇一個keyword如while, 定義一個while的alias,function,然後編寫一個shell指令碼名為while存放於PATH變數的某個路徑下;

  2) 選擇一個built-in命令如pwd來驗證.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.