shell 從標(biāo)準(zhǔn)輸入或腳本中讀取的每一行稱為管道,它包含了一個(gè)或多個(gè)命令,這些命令被一個(gè)或多個(gè)管道字符 (|)
隔開(kāi)。
事實(shí)上還有很多特殊符號(hào)可用來(lái)分割單個(gè)的命令:分號(hào) (;)
,管道 (|)
,&
,邏輯 AND(&&)
,邏輯 OR(||)
。對(duì)于每一個(gè)地區(qū)的管道,shell 都會(huì)將命令分割,為管道設(shè)置 I/O,并且對(duì)每一個(gè)命令依次執(zhí)行下面的操作。
http://wiki.jikexueyuan.com/project/learn-shell/images/1.png" alt="" />
看起來(lái)很復(fù)雜,但是每一個(gè)步驟都是在 shell 的內(nèi)存里發(fā)生的,shell 不會(huì)真的把每個(gè)步驟的發(fā)生演示給我們看。所以這是我們分析 shell 內(nèi)存的情況,從而知道每個(gè)階段的命令行是如何被轉(zhuǎn)換的。
案例:
mkdir /tmp/x #建立臨時(shí)性目錄
cd /tmp/x #切換到該目錄
touch f1 f2 #建立文件
f=f y=”a b” #賦值兩個(gè)變量
echo ~+/${f}[12] $y $(echo cmd subst) $((3+2))>out #忙碌的命令
上述命令的執(zhí)行步驟:
1 命令一開(kāi)始會(huì)根據(jù) shell 語(yǔ)法而分割為 token。最重要的一點(diǎn)是:I/O 重定向 >out
在這里是被識(shí)別的,并存儲(chǔ)供稍后使用。流程繼續(xù)處理下面這行,其中每個(gè) token 的范圍顯示于命令下方的行上:
echo ~+/${f}[12] $y $(echo cmd subst) $((3 + 2))
| 1 | |----- 2 ----| |3 | |-------- 4----------| |----5-----|
2 堅(jiān)持第一個(gè)單詞 (echo)
是否為關(guān)鍵字,例如 if 或 for。在這里不是,所以命令行不變繼續(xù)執(zhí)行。
3 堅(jiān)持第一個(gè)單詞 (依然是 echo) 是否為別名。這里不是,所以命令行不變繼續(xù)處理。
4 掃描所有單詞是否需要波浪號(hào)展開(kāi)。在這里 ~+
等同于 $PWD
,也就是當(dāng)前目錄。token2 將被修改,處理繼續(xù)如下:
echo /tmp/x/${f}[12] $y $(echo cmd subst) $((3 + 2))
| 1 | |------- 2 -------| |3 | |-------- 4----------| |----5-----|
5 變量展開(kāi):token2 與 3 都被修改。產(chǎn)生:
echo /tmp/x/${f}[12] a b $(echo cmd subst) $((3 + 2))
| 1 | |------- 2 -------| | 3 | |-------- 4----------| |----5-----|
6 處理命令替換。注意,這里可遞歸引用列表里的所有步驟! 在此例中,因?yàn)槲覀円噲D讓所有的東西容易理解,因此命令修改了 token4,結(jié)果:
echo /tmp/x/${f}[12] a b cmd subst $((3 + 2))
| 1 | |------- 2 -------| | 3 | |--- 4 ----| |----5-----|
7 執(zhí)行算術(shù)替換 。修改 token5,結(jié)果:
echo /tmp/x/${f}[12] a b cmd subst 5
| 1 | |------- 2 -------| | 3 | |--- 4 ----| |5|
8 前面所有的展開(kāi)產(chǎn)生的結(jié)果,都將再一次被掃描,看看是否有 $IFS
字符。如果有,則他們是作為分隔符,產(chǎn)生額外的單詞。例如,兩個(gè)字符 $y
原來(lái)是組成一個(gè)單詞,但展開(kāi)式 “a 空格 b”,在此階段被切分為兩個(gè)單詞:a 與 b。相同方式也應(yīng)用于命令替換 $(echo cmd subst)
的結(jié)果上。先前的 token3 變成了 token3 與 4。先前的 token4 則成了 token5 與 6。結(jié)果:
echo /tmp/x/${f}[12] a b cmd subst 5
| 1 | |------- 2 -------| 3 4 |-5-| |- 6 -| 7
9 通配符展開(kāi)。token2 變成了 token2 與 token3:
echo /tmp/x/$f1 /tmp/x/$f2 a b cmd subst 5
| 1 | |---- 2 ----| |---- 3 ----| 4 5 |-6-| |- 7 -| 8
10 這時(shí),shell 已經(jīng)準(zhǔn)備好了要執(zhí)行最后的命令了,他會(huì)去尋找 echo。正好 ksh93 與 bash 的 echo 都內(nèi)建到 shell 中了
11 shell 實(shí)際執(zhí)行命令。首先執(zhí)行 >out
的 I/O 重定向,在調(diào)用內(nèi)部的 echo 版本,顯示最后的參數(shù)。
最后的結(jié)果:
$cat out
/tmp/x/f1 /tmp/x/f2 a b cmd subst 5
eval 語(yǔ)句
shell 中的 eval
這個(gè)命令很神奇,他能把字符串當(dāng)做命令來(lái)執(zhí)行。PS:這個(gè)字符串必須是可執(zhí)行的 bash 命令才可以。
案例:
eval "ls" #輸出當(dāng)前目錄的所有文件
語(yǔ)法:eval [參數(shù)]
補(bǔ)充說(shuō)明:eval 可讀取一連串的參數(shù),然后再依慘呼本身的特性來(lái)執(zhí)行。
參數(shù):不限數(shù)目,彼此之間用分號(hào)隔開(kāi)。
案例:我有一個(gè)文件 test.txt
命令:cat test.txt
輸出:hello world
命令:myfile="cat test.txt"
命令:echo $myfile
輸出:cat test.txt
命令:eval $myfile
輸出:hello world
從 eval $myfile
這條命令可以看出,eval 進(jìn)行了變量替換,將字符串中屬于 bash
的命令執(zhí)行了。把拼接起來(lái)的字符串當(dāng)作命令執(zhí)行,這就是 eval 的神奇之處。
subShell 與代碼塊
subShell 是一群被括在圓括號(hào)里的命令,這些命令會(huì)在另外的進(jìn)程中執(zhí)行。當(dāng)你需要讓一小組的命令在不同的目錄下執(zhí)行時(shí),這種方式可以讓你不必修改主腳本的目錄,直接處理這種情況。
例如:tar -cf -.| (cd /tmp;tar -xpf -)
左邊的 tar
命令會(huì)產(chǎn)生當(dāng)前目錄的 tar 打包文件,將他傳送給標(biāo)準(zhǔn)輸出。這份打包文件會(huì)通過(guò)管道傳遞給走遍的 subShell 里的命令。開(kāi)頭的 cd
命令會(huì)先切換到新目錄,也就是讓大寶文件在此目錄下解開(kāi)。然后,走遍的 tar 將從打包文件中解開(kāi)文件。注意,執(zhí)行此管道的 shell(或腳本) 并未更改他的目錄。
代碼塊概念上與 subShell 雷同,只不過(guò)他不會(huì)建立新的進(jìn)程。代碼塊里的命令以花括號(hào) ({})
括起來(lái),且對(duì)主腳本的狀態(tài)會(huì)造成影響 (例如他的當(dāng)前目錄)。一般來(lái)說(shuō),花括號(hào)被視為 shell 關(guān)鍵字,意即他們只有出現(xiàn)在命令的第一個(gè)符號(hào)時(shí)會(huì)被識(shí)別。實(shí)際上:這表示你必須將結(jié)束花括號(hào)放置在換行字符或分號(hào)之后。例如:
cd /home/directory||{
echo could not change to /home/directory!>&2
echo you lose !>&2
exit1
}
IO 重定向也可以套用 subShell 與代碼塊里。在該情況下,所有的命令會(huì)從重定向來(lái)源讀取它們的輸入或傳送他們的輸出。
subShell 與代碼塊
結(jié)構(gòu)
定界符
認(rèn)可的位置
另外的進(jìn)程
SubShell
()
行上的任何位置
是
代碼塊
{}
在換行字符,分號(hào)或關(guān)鍵字之后
否
注意:代碼塊里的 exit 會(huì)終止整個(gè)腳本。
我們通常在 shell 中運(yùn)行一個(gè)腳本只需要簡(jiǎn)單的調(diào)用 ./[script_name]
即可,這種方式下,shell 會(huì)啟動(dòng)一個(gè)子進(jìn)程來(lái)運(yùn)行該腳本,稱為 subShell,當(dāng) subShell 運(yùn)行完成,子進(jìn)程結(jié)束。父進(jìn)程的環(huán)境不會(huì)有任何改變。
案例:bash 代碼
\#!/bin/bash
cd /var/cache
testname="fine"
分別在 shell 中運(yùn)行
./test.sh;echo $testname
會(huì)發(fā)現(xiàn)還是位于原來(lái)的目錄中,$testname
的值書粗話為 null。source ./test.sh;echo $testname
這里就不一樣了,現(xiàn)在你位于 /var/cache
中,$testname
的值也變成了 fine用 source
命令來(lái)運(yùn)行腳本,不會(huì)產(chǎn)生子進(jìn)程,腳本在 shell 的進(jìn)程空間中執(zhí)行,所以運(yùn)行重定義的變量,執(zhí)行的操作,都會(huì)在 shell 的運(yùn)行環(huán)境中保留下來(lái)。