shell編程之 shell問(wèn)答錄
Q1: shell如何執行“簡(jiǎn)單”命令?
A: 這里的簡(jiǎn)單命令和bash參考手冊里的含義相同,形式上一般是:命令的名稱(chēng)加上它的參數。有三種不同的簡(jiǎn)單命令:
1.內置命令(builtin)
是shell解釋程序內建的,有shell直接執行,不需要派生新的進(jìn)程。有一些內部命令可以用來(lái)改變當前的shell環(huán)境,如:
cd /path
var=value
read var
export var
...
2.外部命令(external command or disk command)
二進(jìn)制可執行文件,需要由磁盤(pán)裝入內存執行。會(huì )派生新的進(jìn)程,shell解釋程序會(huì )調用fork自身的一個(gè)拷貝,然后用exec系列函數來(lái)執行外部命令,然后外部命令就取代了先前fork的子shell。
3.shell腳本(script)
shell解釋程序會(huì )fork+exec執行這個(gè)腳本命令,在exec調用中內核會(huì )檢查腳本的第一行(如:#!/bin/sh),找到用來(lái)執行腳本的解釋程序,然后裝入這個(gè)解釋程序,由它解釋執行腳本程序。解釋程序可能有很多種,各種shell(Bourne shell,Korn shell cshell,rc及其變體ash,dash,bash,zshell,pdksh,tcsh,es...),awk,tcl/tk,expect,perl,python,等等。在此解釋程序顯然是當前shell的子進(jìn)程。如果這個(gè)解釋程序與當前使用的shell是同一種shell,比如都是bash,那么它就是當前shell的子shell,腳本中的命令都是在子shell環(huán)境中執行的,不會(huì )影響當前shell的環(huán)境。
Q2: shell腳本是否作為單獨的一個(gè)進(jìn)程執行?
A: 不是,shell腳本本身不能作為一個(gè)進(jìn)程。如上面講的,shell腳本由一個(gè)shell解釋程序來(lái)解釋、運行其中的命令。這個(gè)shell解釋程序是單獨的一個(gè)進(jìn)程,腳本中的外部命令也都作為獨立進(jìn)程依次被運行。這也就是為什么ps不能找到正在運行的腳本的名字的原因了。作為一個(gè)替代方案,你可以這樣調用腳本:
sh script-name
這時(shí)shell解釋程序“sh”作為一個(gè)外部命令被顯式地調用,而script-name作為該命令的命令行參數可以被我們ps到。
另外,如果你的系統上有pidof命令可用,它倒是可以找出shell腳本進(jìn)程(實(shí)際上應該是執行shell腳本的子shell進(jìn)程)的進(jìn)程ID:
pidof -x script-name
Q3: shell何時(shí)在子shell中執行命令?
A: 在此我們主要討論Bourne shell及其兼容shell。在許多情況下shell會(huì )在子shell中執行命令:
1.(...)結構
小括號內的命令會(huì )在一個(gè)子shell環(huán)境中執行,命令執行的結果不會(huì )影響當前的shell環(huán)境。需要注意是此時(shí)變量$$會(huì )顯示當前shell的進(jìn)程id,而不是子shell的進(jìn)程id。
參考:
{...;}結構中的命令在當前shell中執行,(內部)命令執行的結果會(huì )影響當前的shell環(huán)境。
2.后臺執行或異步執行
command
命令由一個(gè)子shell在后臺執行,當前shell立即取得控制等候用戶(hù)輸入。后臺命令和當前shell的執行是并行的,但沒(méi)有互相的依賴(lài)、等待關(guān)系,所以是異步的并行。
3.命令替換
`command`(Bourn shell及兼容shell/csh)
$(command)(在ksh/bash/zsh中可用)
將command命令執行的標準輸出代換到當前的命令行。command在子shell環(huán)境中執行,結果不會(huì )影響當前的shell環(huán)境。
4.管道(不同的shell處理不同)
cmd1|cmd2
cmd1和cmd2并行執行,并且相互有依賴(lài)關(guān)系,cmd2的標準輸入來(lái)自cmd1的標準輸出,二者是“同步”的。
對管道的處理不同的shell實(shí)現的方式不同。
在linux環(huán)境下大多數shell(bash/pdksh/ash/dash等,除了zshell例外)都將管道中所有的命令在子shell環(huán)境中執行,命令執行的結果不會(huì )影響當前的shell環(huán)境。
Korn shell的較新的版本(ksh93以后)比較特殊,管道最后一級的命令是在當前shell執行的。這是一個(gè)feature而非BUG,在POSIX標準中也是允許的。這樣就使下面的命令結構成為可能:
command|read var
由于read var(read是一個(gè)內部命令)在當前shell中執行,var的值在當前shell就是可用的。
反之bash/pdksh/ash/dash中read var在子shell環(huán)境中執行,var讀到的值無(wú)法傳遞到當前shell,所以變量var無(wú)法取得期望的值。類(lèi)似這樣的問(wèn)題在各種論壇和news group中經(jīng)常被問(wèn)到。個(gè)人認為command|read var的結構很清晰,并且合乎邏輯,所以我認為Korn shell的這個(gè)feature很不錯??上Р皇撬械膕hell都是這樣實(shí)現的。:(如開(kāi)源的pdksh就是在子shell執行管道的每一級命令。
Korn shell對管道的處理還有一個(gè)特殊的地方,就是管道如果在后臺執行的話(huà),管道前面的命令會(huì )由最后一級的命令派生,而不是由當前shell派生出來(lái)。據說(shuō)Bourne shell也有這個(gè)特點(diǎn)(標準的Bourne shell沒(méi)有測試環(huán)境,感興趣的朋友有條件的可以自行驗證)。但是他們的開(kāi)源模仿者,pdksh和ash卻不是這樣處理。
最特殊的是zshell,比較新的zshell實(shí)現(好像至少3.0.5以上)會(huì )在當前shell中執行管道中的每一級命令,不僅僅是最后一條。每一條命令都由當前shell派生,在后臺執行時(shí)也是一樣??梢?jiàn)在子sehll中執行管道命令并不是不得已的做法,大概只是因為實(shí)現上比較方便或者這樣的處理已經(jīng)成為unix的傳統之一了吧。;-)
讓我們總結一下,不同的shell對管道命令的處理可能不同。有的shell中command|read var這樣的結構是ok的,但我們的代碼出于兼容性的緣故不能依賴(lài)這一點(diǎn),最好能避免類(lèi)似的代碼。
5.進(jìn)程替換(僅bash/zsh中,非POSIX兼容)
(...)
>(...)
與管道有點(diǎn)類(lèi)似,例子:cmd1 (cmd2) >(cmd3), cmd1, cmd2, cmd3的執行是同步并行的。
(command)形式可以用在任何命令行中需要填寫(xiě)輸入文件名的地方,command的標準輸出會(huì )被該命令當作一個(gè)輸入文件讀入。
>(command)形式可以用在任何命令行中需要填寫(xiě)輸出文件的地方,該命令的輸出會(huì )被command作為標準輸入讀入。
兩種形式中的command都在子shell環(huán)境中執行,結果不會(huì )影響當前的shell環(huán)境。
6.if或while命令塊的輸入輸出重定向
在SVR4.2的Bourne shell中對此情況會(huì )fork一個(gè)子shell執行if塊和while塊中的命令;在linux下似乎其它的shell中都不這樣處理。
7.協(xié)進(jìn)程(ksh)
只有Korn shell和pdksh有協(xié)進(jìn)程的機制(其它shell中可以用命名管道來(lái)模擬)。類(lèi)似于普通的后臺命令,協(xié)進(jìn)程在后臺同步運行,所以必須在子shell中運行。協(xié)進(jìn)程與后臺命令不同的是它要和前臺進(jìn)程(使用read -p和print -p)進(jìn)行交互,而后者一般只是簡(jiǎn)單地異步運行。
Q4: 既然在當前shell中執行命令也會(huì )派生子shell,那么它與在子shell中執行命令又有什么區別呢?
A: 這種說(shuō)法不準確。
在當前shell中執行內部命令不會(huì )派生子shell,因此有些內部命令才能夠改變當前的shell執行環(huán)境。
在當前shell中執行外部命令或腳本時(shí)會(huì )派生子shell,所以這時(shí)命令的執行不會(huì )影響當前 的shell環(huán)境。注意:子shell中執行的內部命令只會(huì )改變子shell的執行環(huán)境,而不會(huì )改變當前shell(父shell)的環(huán)境。
Q5: 怎樣把子shell中的變量傳回父shell?
A: 例如(echo $a) | read b不能工作,如何找到一個(gè)替代方案?下面給出一些可能的方案:
1.使用臨時(shí)文件
...
#in subshell
a=100
echo $a>tmpfile
...
#in parent
read b
2.使用命名管道
mkfifo pipef
(...
echo $a > pipef
...)
read b
3.使用coprocess(ksh)
( echo $a |)
read -p b
4.使用命令替換
b=`echo $a`
5.使用eval命令
eval `echo b=$a`
6.使用here document
read b `echo $a`
END
7.使用here string(bash/pdksh)
read b `echo $a`
8.不用子shell,用.命令或source命令執行腳本。
即在當前shell環(huán)境下執行腳本,沒(méi)有子shell,也就沒(méi)有了子shell的煩惱。:)
解決的方法還不止于此,其它的進(jìn)程間通信手段應該也能使用,這有待于大家一起發(fā)掘了。^_^
評論