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