From: http://www.mike.org.cn/articles/linux-linux-bash-command-search-mechanism/
本文假設(shè)的環(huán)境是GNU/Linux,且shell是BASH;
注意: 另外,我們討論的前提是當(dāng)你鍵入一個(gè)命令時(shí)并沒(méi)有指定該命令的路徑, 舉例來(lái)說(shuō)就是我們鍵入的命令是以commandname的形式而不是/path/commandname或./path/commandname的形式來(lái) 運(yùn)行的. 一旦我們指定了命令(或腳本或二進(jìn)制文件)相對(duì)或絕對(duì)路徑時(shí)就談不上搜索機(jī)制了.
本文講解的內(nèi)容是: 通常,我們?cè)贚inux系統(tǒng)終端提示符下鍵入如ls等命令時(shí),shell是如何找到這個(gè)命令的呢? shell下都有哪幾類(lèi)命令呢? 這些命令是如何被加載的呢?
一、Linux命令的分類(lèi):
包括:alias, keyword, function, built-in, $PATH這5類(lèi)
二、 Linux命令搜索順序:
當(dāng)我們鍵入某個(gè)命令時(shí), 那么shell會(huì)按照alias->keyword->function,->built-in->$PATH的順序進(jìn)行搜索, 本著”先到先得”的原則, 就是說(shuō)如果有如名為mycmd的命令同時(shí)存在于alias和function中的話, 那么肯定會(huì)使用alias的mycmd命令(當(dāng)然, 這不是絕對(duì)的, 下面會(huì)說(shuō)到特例).
三、相關(guān)
set +-h, hash, type, command, enable, builtin
1) hash命令:
首先, 我們來(lái)看hash這命令(和我上面說(shuō)的”不是絕對(duì)的”有關(guān)系了!), hash命令用于記錄在當(dāng)前shell環(huán)境下用戶(hù)曾經(jīng)鍵入的命令路徑記錄緩存表, 主要是為了加快命令搜尋速度. 下面看個(gè)例子:
例:我在shell下鍵入 ls, find, pwd, ls, echo “Hello, world”, mail及if共7個(gè)命令(注意, ls執(zhí)行2次), 下面是history的結(jié)果:
1 ls
2 find
3 pwd
4 ls
5 echo “Hello, world”
6 mail
7 if
那么, 現(xiàn)在我執(zhí)行hash命令, 其顯示結(jié)果為:
[ancharn@fc8 ~]$ hash
hits command
1 /bin/mail
2 /bin/ls
1 /usr/bin/find
不知大家發(fā)現(xiàn)了什么沒(méi)有? 這個(gè)hash表左邊一列表示該命令在當(dāng)前shell環(huán)境下共被使用了幾次, 右邊一列表示命令路徑. 但是我們發(fā)現(xiàn)這個(gè)hash緩存中缺少了if,pwd和echo3個(gè)命令, 為什么呢? 我們?cè)谶@兒要得出一個(gè)重要的結(jié)論就是: (1) hash不會(huì)記錄function, built-in命令(其實(shí)還包括alias), 為什么呢? 答案是因?yàn)樗麄儧](méi)有路徑, 即不會(huì)存在于某個(gè)目錄之下, 它們是隨shell而加載進(jìn)而存在于內(nèi)存中, 所以這樣的命令還有必要進(jìn)行緩存以提高搜索效率嗎?!
但是有人會(huì)說(shuō), ls不是被hash記錄下來(lái)了嗎? 沒(méi)錯(cuò), 你的觀察很細(xì)致, 通常ls在bash中是一個(gè)alias, 那么, 在這兒我們先下一個(gè)結(jié)論: (2) alias中若定義的是包含了路徑的別名命令則不會(huì)被記錄到hash中, 只有沒(méi)有指定路徑的alias才會(huì)被記錄到hash中. 情況例子:
這是我當(dāng)前shell(bash)環(huán)境下的ls別名的定義
[ancharn@fc8 //]$ alias ls
alias ls=’ls –color=auto’
(注意:后面的”ls –color=auto”沒(méi)有指定如/bin/ls這樣的路徑)
所以, 正如你看到的, 上面我鍵入了2次ls命令(是ls –color=auto的別名), 那么在hash中能夠看到被記錄; 下面看個(gè)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 這個(gè)命令沒(méi)有alias, 也就是說(shuō)當(dāng)執(zhí)行write命令時(shí)其實(shí)找到的是PATH變量中的/usr/bin/write這個(gè)二進(jìn)制文件來(lái)執(zhí)行的, 這時(shí)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
請(qǐng)看, hash表中的write的hits數(shù)還是1次; 這里要注意的是當(dāng)我們定義了write的alias后(指定路徑), PATH就不會(huì)被搜到了, 為什么呢? 很簡(jiǎn)單, 因?yàn)閣rite的alias中已經(jīng)指明了它的具體路徑了!
接著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
這次, 我們沒(méi)有指定write別名中的路徑, 當(dāng)我們定義好write的別名后去執(zhí)行write時(shí), hash表中就會(huì)增加一次hits.這里要注意的是當(dāng)我們定義了write的alias后(不指定路徑, 請(qǐng)和上面的例子比較下), PATH就會(huì)被搜到了, 所以hash的hits增加了. 請(qǐng)大家切記alias中若定義的是包含了路徑的別名命令則不會(huì)被記錄到hash中, 只有沒(méi)有指定路徑的alias才會(huì)被記錄到hash中這條結(jié)論.
另外, hash因?yàn)槭莃uilt-in命令, 所以用help hash來(lái)查看幫助. 常用的有hash -r用于清空hash表, hash -d name用于delete某個(gè)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 命令大家應(yīng)該很熟悉, 我們?cè)谶@里主要說(shuō)的是set +-h的作用: help set可以看到”-h Remember the location of commands as they are looked up.” 中文意思就是記憶命令的路徑以便于查詢(xún). 當(dāng)我們鍵入set +h后再運(yùn)行hash:
[ancharn@fc8 //]$ set +h
[ancharn@fc8 //]$ hash
-bash: hash: hashing disabled
也就是說(shuō)”set +h”用于禁用hash而”set -h”用于啟用hash.
3) type:
此命令用于列出某個(gè)命令屬于哪類(lèi). 如:
[ancharn@fc8 //]$ type -a pwd
pwd is a shell builtin
pwd is /bin/pwd
pwd屬于內(nèi)置和PATH變量中.
[ancharn@fc8 //]$ type pwd
pwd is a shell builtin
直接用type commandname可以告訴你該命令在運(yùn)行時(shí)會(huì)執(zhí)行哪一類(lèi).
4) command:
該命令的作用是: 如果你有一個(gè)命令如gcc既是一個(gè)function, 同時(shí)又是一個(gè)PATH變量中的命令, 那么如果你直接執(zhí)行g(shù)cc, 按照順序來(lái)說(shuō), 會(huì)執(zhí)行function而不是gcc的PATH變量中的命令, 而用command gcc會(huì)跳過(guò)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命令如果直接運(yùn)行則會(huì)列出當(dāng)前shell的所有built-in命令, enable -n commandname會(huì)在當(dāng)前shell下disable掉該內(nèi)置命令:
[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
用于運(yùn)行一個(gè)內(nèi)置命令. 例如:
[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既是函數(shù), 又是內(nèi)置命令, 又存在PATH變量中)
[ancharn@fc8 var]$ pwd
just a test for pwd
[ancharn@fc8 var]$ builtin pwd // (注: 此時(shí)我們就去直接執(zhí)行pwd這個(gè)內(nèi)置命令)
/var
小結(jié): 我們都知道了shell在搜索命令時(shí)的順序是alias->keyword->function,->built-in->$PATH, 那么其中還有2點(diǎn)需要注意的就是 (1) hash不會(huì)記錄function, built-in命令(其實(shí)還包括alias), (2) alias中若定義的是包含了路徑的別名命令則不會(huì)被記錄到hash中, 只有沒(méi)有指定路徑的alias才會(huì)被記錄到hash中. 另外, (3) 不要忘記, 我們討論的前提是a) 受限于具體的shell種類(lèi)b)且只在當(dāng)前shell環(huán)境有效.切記!!!
到這里, 請(qǐng)大家來(lái)思考一個(gè)問(wèn)題:
請(qǐng)看下面的執(zhí)行情況:
[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這個(gè)funtion后, 兩次定義gcc的alias時(shí)指定不指定具體的/usr/bin/gcc路徑時(shí), 執(zhí)行g(shù)cc這個(gè)命令的反應(yīng)不同呢? 按照alias->keyword->function,->built-in->$PATH 這個(gè)順序來(lái)看, 應(yīng)該執(zhí)行alias的gcc啊?! 請(qǐng)思考!
當(dāng)然, 別著急, 后面我會(huì)給出答案. 但是, 請(qǐng)您思考下!
四, 命令舉例:
* alias(別名):
alias 命令通常被設(shè)定在文件~/.bashrc和/etc/bashrc中,~/.bashrc通常用于用戶(hù)自己的環(huán)境,而/etc/bashrc用于全局定義 (即對(duì)所有用戶(hù)生效,當(dāng)然,只對(duì)用戶(hù)shell是bash生效). 具體的這兩個(gè)文件的關(guān)系及如何加載在后面有介紹.
* Shell keyword(shell關(guān)鍵字):
諸如if,while,until,case,for這些命令.
* Function(函數(shù)):
舉例:
定義個(gè)名為pwd的函數(shù), 其功能是簡(jiǎn)單地顯示”my function pwd”這句話
function pwd { echo “my function pwd”; }
定義好了之后可以用set或type -a pwd來(lái)查看,取消則用unset pwd即可。
* Shell built-in command(shell內(nèi)置命令):
命令enable可以查看所有當(dāng)前shell環(huán)境下的內(nèi)置命令; 或者用man cd(任何一個(gè)內(nèi)置命令均可)查看到的manpage的上部列出了全部的內(nèi)置命令.
* PATH variable
該變量定義在文件/etc/profile, /etc/profile.d/*.sh(POSIX), ~/.bash_profile(Bash)中.
其加載順序是: 先/etc/profile (invoke /etc/profile.d/*.sh), 然后是~/.bash_profile, 再由~/.bash_profile調(diào)用執(zhí)行 ~/.bashrc, 然后由~/.bashrc去調(diào)用執(zhí)行 ~/.bashrc, ~/.bashrc再調(diào)用執(zhí)行文件/etc/bashrc.
1) 為了查看具體的加載順序, 你可以在四個(gè)文件中的頭部和尾部分別添加兩句話, 例如:
[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”
其它的文件一樣添加, 這樣當(dāng)你用某個(gè)用戶(hù)登錄系統(tǒng)時(shí)就會(huì)看到如下的顯示, 諸如:
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
從上面的顯示你能夠清晰的看到每個(gè)文件的加載順序及相互調(diào)用執(zhí)行關(guān)系(注意查看start和end).
2) PATH變量和hash的關(guān)系
這里, 我們來(lái)看一個(gè)例子:
[ancharn@fc8 ~]$ echo $PATH
/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/ancharn/bin
我首先在/home/ancharn/bin目錄下寫(xiě)一個(gè)名為test.sh的腳本,內(nèi)容如下:
[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]$
那么, 執(zhí)行test.sh這個(gè)腳本如下:
[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目錄下建立一個(gè)同test.sh名的文件, 內(nèi)容如下:
[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
繼續(xù)執(zhí)行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
說(shuō)明什么呢?
如果按照PATH的順序即/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/ancharn /bin, 會(huì)先找/usr/bin然后再找/home/ancharn/bin, 注意, 這個(gè)前提是hash表中沒(méi)有該命令的記錄, 因此我們看到/usr/bin/test.sh腳本并沒(méi)有被執(zhí)行, 因?yàn)樵趫?zhí)行test.sh前, shell去hash表中查看了緩存, 進(jìn)而繼續(xù)執(zhí)行了/home/ancharn/bin/test.sh腳本, 所以我們看到hits數(shù)增加了一次, 而/usr/bin/test.sh不會(huì)被執(zhí)行.
現(xiàn)在, 我們清空hash, 重新執(zhí)行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
現(xiàn)在正常了. 所以一定要注意PATH和hash的這層關(guān)系.
注意: su, su-, bash –login, bash –norc這些命令的不同就在于是否執(zhí)行了login-shell, 大家可以su和su -后, 再去運(yùn)行echo $PATH看看有何不同.
好了, 回答上面的思考題, 其核心在于alias如果定義的如alias gcc=’gcc’時(shí), 其實(shí)alias->keyword->function,->built-in->$PATH 這個(gè)順序并沒(méi)有變, 但是要知道alias gcc=’gcc’這種沒(méi)有指定路徑的alias會(huì)在找到gcc這個(gè)alias后, 再去找到后面指定的’gcc’, 怎么找? 當(dāng)然到下一個(gè)了, 就是keyword->function….這個(gè)順序了. 而如果是alias gcc=’/usr/bin/gcc’這樣的指定具體路徑的定義alias的話, 那么alias執(zhí)行后就直接找到了那個(gè)具體文件而跳過(guò)了后面的所有搜索(即keyword->function,->built-in->$PATH). 請(qǐng)大家留意.
最后, 大家在做實(shí)驗(yàn)驗(yàn)證的時(shí)候可以分成2類(lèi)驗(yàn)證, 因?yàn)橐粋€(gè)命令不可能既屬于keyword又屬于built-in, 所以你可以:
1) 選擇一個(gè)keyword如while, 定義一個(gè)while的alias,function,然后編寫(xiě)一個(gè)shell腳本名為while存放于PATH變量的某個(gè)路徑下;
2) 選擇一個(gè)built-in命令如pwd來(lái)驗(yàn)證.
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
