這次先讓我們從CU Shell版的一個實例貼子來談起吧:
例中的提問是:
cd /etc/aa/bb/cc可以執行,但是把這條命令寫入shell時shell不執行!
這是什麼原因呀!
我當時如何回答暫時別去深究,先讓我們瞭解一下進程(process)的觀念好了。
首先,我們所執行的任何程序,都是由父進程(parent process)所產生出來的一個子進程(child process),子進程在結束後,將返回到父進程去。此一現像在Linux系統中被稱爲 fork。
當子進程被產生的時候,將會從父進程那裏獲得一定的資源分配、及(更重要的是)繼承父進程的環境﹗
讓我們回到上一章所談到的"環境變量"吧:
*所謂環境變量其實就是那些會傳給子進程的變量。
簡單而言,"遺傳性"就是區分本地變量與環境變量的決定性指標。
然而,從遺傳的角度來看,我們也不難發現環境變量的另一個重要特徵:
*環境變量只能從父進程到子進程單向繼承。換句話說:在子進程中的環境如何變更,均不會影響父進程的環境。
接下來,再讓我們瞭解一下命令腳本(shell script)的概念。
所謂的shell script講起來很簡單,就是將你平時在shell prompt後所輸入的多行command line依序寫入一個文件去而已。
其中再加上一些條件判斷、互動界面、參數運用、函數調用、等等技巧,得以讓script更加"聰明"的執行,但若撇開這些技巧不談,我們真的可以簡單的看成script只不過依次執行預先寫好的命令行而已。
再結合以上兩個概念(process + script),那應該就不難理解如下這句話的意思了:
*正常來說,當我們執行一個shell script時,其實是先產生一個sub-shell的子進程,然後sub-shell再去產生命令行的子進程。
然則,那讓我們回到本章開始時所提到的例子再從新思考:
cd /etc/aa/bb/cc可以執行,但是把這條命令寫入shell時shell不執行!
這是什麼原因呀!
我當時的答案是這樣的:
因爲,一般我們跑的shell script是用subshell去執行的。
從process的觀念來看,是parent process產生一個child process去執行,當child結束後,會返回parent,但parent的環境是不會因child的改變而改變的。
所謂的環境元數很多,凡舉effective id, variable, workding dir等等...
其中的workding dir ($PWD)正是樓主的疑問所在:
當用subshell來跑script的話,sub shell的$PWD會因爲cd而變更,
但當返回primary shell時,$PWD是不會變更的。
能夠了解問題的原因及其原理是很好的,但是?如何解決問題恐怕是我們更感興趣的﹗是吧?^_^
那好,接下來,再讓我們瞭解一下source命令好了。
當你有了fork的概念之後,要理解source就不難:
*所謂source就是讓script在當前shell內執行、而不是產生一個sub-shell來執行。
由於所有執行結果均於當前shell內完成,若script的環境有所改變,當然也會改變當前環境了﹗
因此,只要我們要將原本單獨輸入的script命令行變成source命令的參數,就可輕易解決前例提到的問題了。
比方說,原本我們是如此執行 script的:
./my.script
現在改成這樣即可:
source ./my.script
或:
. ./my.script
說到這裏,我想,各位有興趣看看/etc底下的衆多設定文件,應該不難理解它們被定義後,如何讓其它script讀取並繼承了吧?
若然,日後你有機會寫自己的script,應也不難專門指定一個設定文件以供不同的script一起"共享"了... ^_^
okay,到這裏,若你搞得懂fork與source的不同,那接下來再接受一個挑戰:
----那exec又與source/fork有何不同呢?
哦...要了解exec或許較爲複雜,尤其扯上File Descriptor的話...
不過,簡單來說:
* exec也是讓script在同一個進程上執行,但是原有進程則被結束了。
也就是簡而言之:原有進程會否終止,就是exec與source/fork的最大差異了。
嗯,光是從理論去理解,或許沒那麼好消化,不如動手"實作+思考"來的印像深刻哦。
下面讓我們寫兩個簡單的script,分別命令爲1.sh及2.sh:
1.sh
#!/bin/sh
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/sh
echo "PID for 2.sh: $$"
echo "2.sh get /$A=$A from 1.sh"
A=C
export A
echo "2.sh: /$A is $A"
然後,分別跑如下參數來觀察結果:
$ ./1.sh fork
PID for 1.sh before exec/source/fork:531
1.sh: $A is B
using fork by default...
PID for 2.sh:532
2.sh get $A=B from 1.sh
2.sh: $A is C
PID for 1.sh after exec/source/fork:531
1.sh: $A is B
$ ./1.sh source
PID for 1.sh before exec/source/fork:533
1.sh: $A is B
using source...
PID for 2.sh:533
2.sh get $A=B from 1.sh
2.sh: $A is C
PID for 1.sh after exec/source/fork:533
1.sh: $A is C
$ ./1.sh exec
PID for 1.sh before exec/source/fork:537
1.sh: $A is B
using exec...
PID for 2.sh:537
2.sh get $A=B from 1.sh
2.sh: $A is C
############
echo "PID for 1.sh after exec/source/fork:$$"
echo "1.sh: /$A is $A"
已經不會執行了,1.sh的進程已經沒了。
##############