這次讓我們從 CU shell 版的一個(gè)實(shí)例帖子來談起吧: (論壇改版后,原鏈接已經(jīng)失效)
例中的提問原文如下:
帖子提問:
cd /etc/aa/bb/cc 可以執(zhí)行 但是把這條命令放入 shell 腳本后,shell 腳本不執(zhí)行! 這是什么原因?
意思是:運(yùn)行 shell 腳本,并沒有移動(dòng)到 / etc/aa/bb/cc 目錄。
我當(dāng)時(shí)如何回答暫時(shí)別去深究,先讓我們了解一下進(jìn)程 (process) 的概念好了。
首先,我們所執(zhí)行的任何程序,都是父進(jìn)程 (parent process) 產(chǎn)生的一個(gè) 子進(jìn)程 (child process), 子進(jìn)程在結(jié)束后,將返回到父進(jìn)程去。 此現(xiàn)象在 Linux 中被稱為fork。
(為何要稱為 fork 呢? 嗯,畫一下圖或許比較好理解...^_^)
當(dāng)子進(jìn)程被產(chǎn)生的時(shí)候,將會(huì)從父進(jìn)程那里獲得一定的資源分配、及 (更重要的是) 繼承父進(jìn)程的環(huán)境。
讓我們回到上一章所談到的 "環(huán)境變量" 吧: 所謂環(huán)境變量其實(shí)就是那些會(huì)傳給子進(jìn)程的變量。 簡單而言, "遺傳性" 就是區(qū)分本地變量與環(huán)境變量的決定性指標(biāo)。 然而,從遺傳的角度來看,我們不難發(fā)現(xiàn)環(huán)境變量的另一個(gè)重要特征: 環(huán)境變量只能從父進(jìn)程到子進(jìn)程單向傳遞。 換句話說:在子進(jìn)程中環(huán)境如何變更,均不會(huì)影響父進(jìn)程的環(huán)境。
接下來,在讓我們了解一下 shell 腳本 (shell script) 的概念. 所謂 shell script 講起來很簡單,就是將你平時(shí)在 shell prompt 輸入的多行 command line, 依序輸入到一個(gè)文件文件而已。
再結(jié)合以上兩個(gè)概念 (process + script),那應(yīng)該不難理解如下的這句話的意思了: 正常來說,當(dāng)我們執(zhí)行一個(gè) shell script 時(shí),其實(shí)是先產(chǎn)生一個(gè) sub-shell 的子進(jìn)程, 然后 sub-shell 再去產(chǎn)生命令行的子進(jìn)程。 然則,那讓我們回到本章開始時(shí),所提到的例子在重新思考:
帖子提問:
cd/etc/aa/bb/cc可以執(zhí)行 但是把這條命令放入 shell 腳本后,shell 腳本不執(zhí)行! 這是什么原因?
意思是:運(yùn)行 shell 腳本,并沒有移動(dòng)到 /etc/aa/bb/cc 目錄。
我當(dāng)時(shí)的答案是這樣的:
因?yàn)椋覀円话闩艿?shell script 是用 sub-shell 去執(zhí)行的。 從 process 的概念來看,是 parent process 產(chǎn)生一個(gè) child process 去執(zhí)行, 當(dāng) child 結(jié)束后,返回 parent, 但 parent 的環(huán)境是不會(huì)因 child 的改變而改變的。 所謂的環(huán)境變量元數(shù)很多,如 effective id(euid),variable, working dir 等等... 其中的 working dir($PWD) 正是樓主的疑問所在: 當(dāng)用 sub-shell 來跑 script 的話,sub-shell 的 $pwd 會(huì)因?yàn)?cd 而變更, 但返回 primary shell 時(shí),$PWD 是不會(huì)變更的。
能夠了解問題的原因及其原理是很好的,但是? 如何解決問題,恐怕是我們更應(yīng)該感興趣的是吧?
那好,接下來,再讓我們了解一下source命令好了。 當(dāng)你有了fork的概念之后,要理解soruce就不難:
所謂source,就是讓 script 在當(dāng)前 shell 內(nèi)執(zhí)行、 而不是產(chǎn)生一個(gè) sub-shell 來執(zhí)行。 由于所有執(zhí)行結(jié)果均在當(dāng)前 shell 內(nèi)執(zhí)行、而不是產(chǎn)生一個(gè) sub-shell 來執(zhí)行。
因此, 只要我們?cè)締为?dú)輸入的 script 命令行,變成source命令的參數(shù), 就可輕而易舉地解決前面提到的問題了。
比方說,原本我們是如此執(zhí)行 script 的:
$ ./my_script.sh
現(xiàn)在改成這樣既可:
$ source ./my_script.sh
或者:
$ . ./my_script.sh
說到這里,我想,各位有興趣看看/etc底下的眾多設(shè)定的文件, 應(yīng)該不難理解它們被定義后,如何讓其他 script 讀取并繼承了吧?
若然,日后,你有機(jī)會(huì)寫自己的 script, 應(yīng)也不難專門指定一個(gè)設(shè)定的文件以供不同的 script 一起 "共用" 了... ^_^
okay, 到這里,若你搞懂fork與source的不同, 那接下來再接受一個(gè)挑戰(zhàn):
那
exec又與source/fork有何不同呢?
哦... 要了解exec或許較為復(fù)雜,尤其是扯上File Decscriptor的話... 不過,簡單來說:
exec也是讓 script 在同一個(gè)進(jìn)程上執(zhí)行,但是原有進(jìn)程則被結(jié)束了。 簡言之,原有進(jìn)程能否終止,就是exec與source/fork的最大差異了。
嗯,光是從理論去理解,或許沒那么好消化, 不如動(dòng)手 "實(shí)踐 + 思考" 來得印象深刻哦。
下面讓我們?yōu)閮蓚€(gè)簡單的 script,分別命名為 1.sh 以及 2.sh
1.sh
#!/bin/bash
A=B
echo "PID for 1.sh before exec/source/fork:$$"
export A
echo "1.sh: \$A is $A"
case $1 in
exec)
echo "using exec..."
exec ./2.sh ;;
source)
echo "using source..."
. ./2.sh ;;
*)
echo "using fork by default..."
./2.sh ;;
esac
echo "PID for 1.sh after exec/source/fork:$$"
echo "1.sh: \$A is $A"
2.sh
#!/bin/bash
echo "PID for 2.sh: $$"
echo "2.sh get \$A=$A from 1.sh"
A=C
export A
echo "2.sh: \$A is $A"
然后分別跑如下參數(shù)來觀察結(jié)果:
$ ./1.sh fork
$ ./1.sh source
$ ./1.sh exec
好了,別忘了仔細(xì)比較輸出結(jié)果的不同及背后的原因哦... 若有疑問,歡迎提出來一起討論討論~~~~
happy scripting! ^_^