標(biāo)準(zhǔn)化您的 UNIX 命令行工具
本文介紹用于標(biāo)準(zhǔn)化界面以簡(jiǎn)化在不同 Unix® 系統(tǒng)之間移動(dòng)的方法。如果您管理多種 UNIX 系統(tǒng)(特別是在異構(gòu)環(huán)境中),則最艱巨的任務(wù)可能是在不同環(huán)境之間切換并執(zhí)行不同的任務(wù),同時(shí)還必須考慮系統(tǒng)之間的所有差異。本文并不介紹特定的差異,而是研究能夠提供兼容層(或包裝)以支持一致環(huán)境的方法。
關(guān)于本系列
典型的 UNIX® 管理員擁有一套經(jīng)常用于輔助管理過程的關(guān)鍵實(shí)用工具、訣竅和系統(tǒng)。存在各種用于簡(jiǎn)化不同過程的關(guān)鍵實(shí)用工具、命令行鏈和腳本。其中一些工具來(lái)自于操作系統(tǒng),而大部分的訣竅則來(lái)源于長(zhǎng)期的經(jīng)驗(yàn)積累和減輕系統(tǒng)管理員工作壓力的要求。本系列文章主要專注于最大限度地利用各種 UNIX 環(huán)境中可用的工具,包括簡(jiǎn)化異構(gòu)環(huán)境中的管理任務(wù)的方法。
差異和問題
如果您使用多種 UNIX 主機(jī),特別是每種主機(jī)都支持不同的 UNIX 風(fēng)格(Berkeley Software Distribution (BSD)、UNIX System Release 4 (VSVR4) 等)或版本,您也許發(fā)現(xiàn)自己要花大量的時(shí)間來(lái)檢查和確定自己所在的主機(jī)類型,以便能夠適應(yīng)系統(tǒng)操作方式的變化。
例如,ps 命令在基于 BSD 和基于 SVR4 的 UNIX 主機(jī)上,分別需要不同的命令行選項(xiàng)來(lái)獲得大致相同的信息(有關(guān)更多細(xì)節(jié),請(qǐng)參閱 系統(tǒng)管理員工具包: 進(jìn)程管理技巧)。平臺(tái)之間還存在更廣泛的差異。有時(shí),這種差異是命令名稱發(fā)生了更改;Linux® 提供 adduser 命令,而 Solaris 則提供 useradd 命令。
就標(biāo)準(zhǔn)化而言,有多種方法可供您采用。
您可以選擇對(duì)主要平臺(tái)(例如 Solaris)進(jìn)行標(biāo)準(zhǔn)化,并在其他平臺(tái)上提供等效命令的包裝以匹配 Solaris 標(biāo)準(zhǔn)。 也可以選擇對(duì)為所使用的任務(wù)提供最佳組合的命令集進(jìn)行標(biāo)準(zhǔn)化,挑選您喜歡的命令并為特定平臺(tái)上不存在的命令構(gòu)建包裝。 您可以創(chuàng)建自己的一套執(zhí)行特定任務(wù)的腳本(包括您自己用于 ls、ps 等常用工具的替代腳本),以便它們生成您想要的信息。這樣做有點(diǎn)危險(xiǎn),原因是它意味著您可能從未使用原始命令,從而可能在您的腳本不可用時(shí)導(dǎo)致潛在的問題。如何具體實(shí)現(xiàn)各個(gè)命令的包裝以提供一個(gè)兼容或唯一的層,這取決于您是嘗試簡(jiǎn)單地為功能相同的替代命令提供一個(gè)公認(rèn)名稱,還是需要構(gòu)建一個(gè)或多個(gè)命令的包裝以獲得等效的結(jié)果。可能的解決方案有三種:
別名——這種解決方案僅在某些外殼中受支持——別名提供了將給定的字符串展開為特定命令的簡(jiǎn)單方法。 外殼函數(shù)——大多數(shù)現(xiàn)代外殼都支持這種解決方案——外殼函數(shù)使您能夠創(chuàng)建更復(fù)雜的序列,但是由于它們作為內(nèi)置函數(shù)運(yùn)行,在差異相當(dāng)小時(shí)可能更為實(shí)用。 外殼腳本——當(dāng)您要構(gòu)建的包裝特別復(fù)雜時(shí),更好的解決方案是使用外殼腳本,您可以代替原始命令調(diào)用這些腳本。使用外殼腳本,您可以更創(chuàng)造性地處理替代,甚至為另一個(gè)命令提供完全由外殼腳本驅(qū)動(dòng)的替代。讓我們研究一下每種可能的解決方案和一些可通過此方法來(lái)進(jìn)行模擬的示例命令。
使用別名
別名在 Korn (ksh)、Bourne-Again SHell (bash)、TENEX C shell (tcsh) 和 Z shell (zsh) 外殼中受支持,當(dāng)您希望設(shè)置命令的特定選項(xiàng),同時(shí)仍然支持其他選項(xiàng)時(shí),別名提供了也許是最簡(jiǎn)單的方法。顧名思義,您可以將一個(gè)命令用作另一個(gè)命令的別名,或者為帶有附加選項(xiàng)的同一個(gè)命令提供別名。別名從您鍵入的內(nèi)容展開為其展開形式。
例如,一個(gè)常用的別名是 ll,它調(diào)用等效的 ls -l(ll 通常稱為長(zhǎng)清單 (long listing))。每當(dāng)用戶鍵入 ll,就會(huì)直接將其替換為展開形式,因此:$ ll a* 在執(zhí)行前展開為:$ ls -l a*。
命令行選項(xiàng)也仍然有效,換句話說(shuō),$ ll -a 展開為:$ ls -l -a。
還可以為現(xiàn)有命令設(shè)置別名;假設(shè)將 -F 選項(xiàng)添加到所有 ls 命令,這樣,$ ls 將展開為:$ ls -F。
要設(shè)置別名,請(qǐng)使用內(nèi)置的外殼 alias 語(yǔ)句,并在引號(hào)中指定所需的展開形式。例如,要設(shè)置前面詳細(xì)描述的 ll 的展開形式,可使用:$ alias ll='ls -l'。
別名在以下情況下最為有用:您希望使用 base 命令并容易地指定附加選項(xiàng),同時(shí)仍然允許設(shè)置特定于平臺(tái)的選項(xiàng)。
一個(gè)很好的例子就是 ps 命令,它在基于 SVR4 和基于 BSD 的 Unix 主機(jī)上是不同的。在本系列的第一篇文章中,請(qǐng)參閱 系統(tǒng)管理員工具包: 進(jìn)程管理技巧 ——這篇文章解釋了如何使用 ps 的選項(xiàng)來(lái)獲得相似的清單。您可以結(jié)合別名使用那些選項(xiàng),而不會(huì)影響您指定附加選項(xiàng)的能力。例如,在 BSD 上,您將如清單 1 所示指定別名。
清單 1. 在 BSD 上指定別名
$ alias ps='ps -o pid,ppid,command'而在 SVR4 主機(jī)上,您將如清單 2 所示創(chuàng)建別名。
清單 2. 在 SVR4 上指定別名
$ alias ps='ps -opid,ppid,cmd現(xiàn)在,在這兩個(gè)系統(tǒng)對(duì) ps 的不同操作方式的限制下,您獲得了 ps 產(chǎn)生的標(biāo)準(zhǔn)輸出。和前面一樣,您可以繼續(xù)添加更多選項(xiàng);例如,在安裝了該別名的任一個(gè)平臺(tái)上請(qǐng)求所有進(jìn)程,添加 -A 選項(xiàng)就是這樣一種情況。這會(huì)在 BSD(在此示例中為 Mac OS X)上產(chǎn)生類似于清單 3 的輸出。
清單 3. 在 BSD 上使用 -A 選項(xiàng)
$ ps -A PID PPID COMMAND1 0 /sbin/launchd 23 1 /sbin/dynamic_pager -F /private/var/vm/swapfile 27 1 kextd 32 1 /usr/sbin/KernelEventAgent 33 1 /usr/sbin/mDNSResponder -launchdaemon 34 1 /usr/sbin/netinfod -s local 35 1 /usr/sbin/syslogd 36 1 /usr/sbin/cron 37 1 /usr/sbin/configd 38 1 /usr/sbin/coreaudiod 39 1 /usr/sbin/diskarbitrationd...SVR4 系統(tǒng)(Gentoo Linux 主機(jī))會(huì)顯示同樣的列,如清單 4 所示。
清單 4. 在 SVR4 上使用 -A 選項(xiàng)
$ ps -A PID PPID CMD1 0 init [3]2 1 [migration/0]3 1 [ksoftirqd/0]4 1 [watchdog/0]5 1 [migration/1]6 1 [ksoftirqd/1]7 1 [watchdog/1]8 1 [events/0]9 1 [events/1] 10 1 [khelper] 11 1 [kthread] 1411 [kblockd/0] 1511 [kblockd/1] 1611 [kacpid]...另一個(gè)選項(xiàng)或多或少地鏡像了本文其他地方給出的腳本和函數(shù)解決方案。該選項(xiàng)是為給定命令的特定輸出創(chuàng)建別名,這些別名采用同一方法來(lái)提供相同的格式化輸出。同樣以 ps 為例,您可以創(chuàng)建別名 ps-all 來(lái)輸出所有進(jìn)程列表,并根據(jù)需要為每種平臺(tái)設(shè)置相應(yīng)的展開形式。
設(shè)置這些別名的最佳位置是在登錄期間執(zhí)行的外殼初始化腳本中,例如 .ksh、.profile 或 .bashrc。您可以在這些腳本中執(zhí)行同樣的系統(tǒng)檢查,以驗(yàn)證要啟用哪些別名。如果希望提供適用于所有用戶的全局解決方案,則應(yīng)將別名定義放在公開可用的文件中(例如放在 /etc or /usr/local 中),并設(shè)置用戶初始化腳本以獲得別名定義來(lái)源。
別名機(jī)制最適合于您希望設(shè)置單個(gè)命令的命令行選項(xiàng)的情況,雖然也可以使用它們來(lái)將給定的命令展開為一組命令或管道。這樣削弱了為展開形式中除最后一個(gè)命令以外的其他任何命令指定附加參數(shù)的能力。對(duì)于處理此類包裝,外殼中的內(nèi)聯(lián)函數(shù)可能更為適合。
使用內(nèi)聯(lián)外殼函數(shù)
大多數(shù)外殼都支持函數(shù),這些函數(shù)本質(zhì)上是微型腳本,您可以在其中放置命令和其他外殼腳本元素以執(zhí)行特定的任務(wù)。由于它們是主外殼定義中的函數(shù),因此使用起來(lái)方便快捷,同時(shí)仍然支持許多完整外殼腳本所具有的相同功能,如命令行參數(shù)。
對(duì)于支持別名無(wú)法在其中工作的某些命令和組合,對(duì)命令行參數(shù)的支持非常關(guān)鍵。例如,killall 命令最基本的功能是終止所有與特定字符串匹配的命令。該命令并非在所有平臺(tái)上都可用,但是一旦您了解了它,就會(huì)希望在其他環(huán)境中使用它。
在 Solaris 上,killall 命令存在,但是將其用作關(guān)閉過程的一部分以終止所有進(jìn)程。設(shè)想在 Solaris 主機(jī)上意外調(diào)用 killall 命令以關(guān)閉所有 Apache 進(jìn)程,沒想到卻實(shí)際上關(guān)閉了系統(tǒng)!
提供替代——在所有主機(jī)上使用相同的名稱或使用不同的名稱——可以實(shí)現(xiàn)按名稱終止進(jìn)程的預(yù)期結(jié)果,并消除不希望的和可能代價(jià)高昂的錯(cuò)誤,同時(shí)擴(kuò)展本身并不支持該選項(xiàng)的系統(tǒng)的功能。
該命令的關(guān)鍵部分是能夠識(shí)別正在運(yùn)行的進(jìn)程,提取與給定字符串匹配的進(jìn)程,并使用 kill 命令將 KILL 信號(hào)發(fā)送到每個(gè)匹配進(jìn)程。在命令行上,您可以通過一系列管道實(shí)現(xiàn)等效的功能(使用 KILL 信號(hào)),如清單 5 所示。
清單 5. 提供 killall 命令的替代
$ ps -ef|grep gcc|awk '{ print $2; }'|xargs kill -9該命令的關(guān)鍵部分是提供給 grep(在此示例中為 gcc)的字符串和 ps 輸出中包含所需進(jìn)程 ID 的列。上面的例子對(duì) Solaris 主機(jī)和大多數(shù) SVR4 Unix 變種有效。
別名在此示例中無(wú)法工作,因?yàn)槟M軌虿迦朊钪械男畔⒉辉诮Y(jié)尾;別名所實(shí)現(xiàn)的是一種展開方法。然而,內(nèi)聯(lián)外殼函數(shù)正好適合這種情況。
在支持 Bourne 語(yǔ)法(bash 和 zsh)的外殼中,您可以使用清單 6 所示的以下語(yǔ)法來(lái)定義函數(shù)。
清單 6. 定義函數(shù)
function NAME(){# do stuff here}調(diào)用函數(shù)時(shí),函數(shù)參數(shù)作為 $1、$2 等形式來(lái)提供,就像在典型的外殼腳本中一樣。因此,您可以定義一個(gè)函數(shù),使其執(zhí)行與 killall 相同的基于字符串的信號(hào)發(fā)送功能(請(qǐng)參見清單 7)。
清單 7. 定義一個(gè)執(zhí)行與 killall 相同的信號(hào)發(fā)送功能的函數(shù)
function killall(){ps -ef|grep $1|awk '{ print $2; }'|xargs kill -9}請(qǐng)注意,該函數(shù)的 awk 部分中的 $2 不會(huì)展開,因?yàn)槟呀?jīng)對(duì) awk 腳本定義使用了單引號(hào),這樣阻止了展開,并且在此示例中會(huì)挑選第二列。
與別名一樣,指定外殼函數(shù)的最佳位置是在外殼的初始化腳本中。函數(shù)的局限性在于,它們依賴外殼提供支持能力,而這并不總是可能或可用。
雖然可以隨心所欲地使內(nèi)聯(lián)外殼函數(shù)變得任意長(zhǎng),但在許多情況下,外殼函數(shù)并不理想。例如,在模擬更復(fù)雜的命令或提供命令包裝的超長(zhǎng)序列中,您需要分析選項(xiàng)并提供本地化的等效命令,此時(shí)內(nèi)聯(lián)函數(shù)就沒有多大用處了。在這種情況下,外殼腳本可能更為適合。
使用腳本
構(gòu)建一致環(huán)境的最容易和最兼容的方法,是創(chuàng)建可用作實(shí)際命令的包裝的外殼腳本,這樣考慮了您希望支持的各種選項(xiàng)和設(shè)置。
例如,useradd 和 adduser 命令在設(shè)置參數(shù)(如用戶 ID 或組成員資格)時(shí)支持同樣的單字母命令行選項(xiàng),因此 Linux 上的 $ adduser -u 1000 -G sales,marketing mcbrown 等效于 Solaris 上的 $ useradd -u 1000 -G sales,marketing mcbrown。
然而,Linux 版本還支持?jǐn)U展命令選項(xiàng),例如,--uid 和 --groups 等效于上面的命令行選項(xiàng)。這些擴(kuò)展選項(xiàng)在 Solaris 上不受支持,但是,如果創(chuàng)建一個(gè)名為 adduser 的外殼腳本,您就可以模擬 Linux 版本,然后用適當(dāng)?shù)倪x項(xiàng)運(yùn)行實(shí)際的 Solaris useradd 命令。
清單 8 是用作 adduser 或 useradd 命令的包裝的示例外殼腳本。
清單 8. 用作包裝的示例外殼腳本
#!/bin/bash# -*- shell-script -*-for i in $*do case $i in --uid|-u) OPT_UID=$2; shift 2;; --groups|-G) OPT_GROUPS=$2; shift 2;; --gid|-g) OPT_GROUP=$2; shift 2;; --home-dir|-d) OPT_HOMEDIR=$2; shift 2;; --shell|-s) OPT_SHELL=$2;shift 2;; --non-unique|-o) OPT_NONUNIQUE=1;shift 2;; --comment|-c) OPT_COMMENT=$2;shift 2;; esacdoneOPTS=""if [ -n "$OPT_$HOMEDIR" ]thenOPTS="$OPTS -d $OPT_HOMEDIR"fiif [ -n "$GROUP" ]thenOPTS="$OPTS -g $OPT_GROUP"fiif [ -n "$OPT_GROUPS" ]thenOPTS="$OPTS -G $OPT_GROUPS"fiif [ -n "$OPT_SHELL" ]thenOPTS="$OPTS -s $OPT_SHELL"fiif [ -n "$OPT_UID" ]thenOPTS="$OPTS -u $OPT_UID"fiif [ -n "$OPT_COMMENT" ]thenOPTS="$OPTS -c "$OPT_COMMENT""fiif [ -n "$OPT_NOUNIQUE" ]thenOPTS="$OPTS -o"fiCMD=adduserUNAME=`uname`case $UNAME inSolaris) CMD=useradd;break;;esac$CMD $OPTS $*該腳本的關(guān)鍵是 foreach 循環(huán),它遍歷所提供的命令行參數(shù)(在 $* 中提供)。對(duì)于每個(gè)選項(xiàng),case 語(yǔ)句會(huì)嘗試識(shí)別該選項(xiàng)——使用短格式或長(zhǎng)格式并設(shè)置一個(gè)變量。命令行開關(guān)為 $1。如果該選項(xiàng)后面正常地跟著一個(gè)值(例如,用戶 ID),您可以將 $2 當(dāng)作該值來(lái)進(jìn)行訪問,并使用它將該值賦于某個(gè)變量。
識(shí)別出某個(gè)選項(xiàng)后,shift 語(yǔ)句從 $* 變量列表中移動(dòng)一個(gè)位置(若指定了數(shù)字,則移動(dòng)指定數(shù)目的位置),以便已經(jīng)識(shí)別出的命令行參數(shù)在循環(huán)的下一次迭代中不再在 $* 變量中。
識(shí)別并提取出可能的參數(shù)以后,您所需做的就是構(gòu)建新的選項(xiàng)來(lái)提供給最終要使用的命令。由于 useradd/adduser 都支持短格式的參數(shù),所以可在此基礎(chǔ)上構(gòu)建新的命令選項(xiàng)字符串。這是通過檢查對(duì)應(yīng)的變量是否已設(shè)置并將該選項(xiàng)添加到命令行來(lái)實(shí)現(xiàn)的。請(qǐng)注意雙引號(hào)的使用,它確保了原始命令中引用的參數(shù)被保留并得到正確識(shí)別。
將該腳本安裝在支持任一種原始命令的平臺(tái)上以后,您現(xiàn)在可以添加用戶并指定所要的選項(xiàng),包括對(duì)參數(shù)進(jìn)行混合和匹配(請(qǐng)參見清單 9)。
清單 9. 添加用戶
$ adduser.sh --homedir /etc -g wheel --shell /bin/bash -c "New user" mcbrown同樣的基本原理也可以用于構(gòu)建其他命令的包裝,甚至更改參數(shù)名稱和選項(xiàng),或者提供等效的表達(dá)式。
如果希望用原始名稱安裝該腳本——例如,adduser——并將其放在某個(gè)目錄中(例如,/usr/local/compat),您必須確保該目錄在 PATH 中出現(xiàn)在實(shí)際命令的目錄之前。下面是假設(shè)將兼容性腳本放在 /usr/local/compat 目錄中的一個(gè)例子:$ PATH=/usr/local/compat:$PATH。
使用單個(gè)源
無(wú)論您是使用多個(gè)腳本還是單個(gè)配置腳本/別名來(lái)支持統(tǒng)一的環(huán)境,您也許都希望使用單獨(dú)一組腳本來(lái)支持系統(tǒng)。因此,設(shè)置新系統(tǒng)以使用標(biāo)準(zhǔn)化腳本(無(wú)論它們是獨(dú)立腳本還是安裝外殼函數(shù)和別名)非常簡(jiǎn)單,只需將它們復(fù)制到新系統(tǒng)即可。
通過使用命令行工具和外殼流控制(如 if 或 case)的組合,您可以使用單個(gè)源來(lái)選擇各種要使用的選項(xiàng)。有兩個(gè)工具在這種情況下很有用:一個(gè)工具識(shí)別主機(jī)(如 hostname 或 uname),另一個(gè)工具識(shí)別平臺(tái) (uname)。
uname 產(chǎn)生的缺省輸出是基本操作系統(tǒng)名稱,如 Linux 或 Solaris。例如,可以按照前一部分中的 ps 示例,將該命令與 case 語(yǔ)句結(jié)合使用以選擇正確的別名,如清單 10 所示。
清單 10. uname 的輸出
UNAME='uname'case "$UNAME" inFreeBSD|NetBSD|Darwin)alias ps='ps -o pid,ppid,command'break;;Solaris|Linux)alias ps='ps -o pid,ppid,cmd'break;;esac也可以在腳本中使用同樣的基本過程來(lái)選擇特定的序列。
在使用內(nèi)聯(lián)外殼函數(shù)時(shí),與在每次使用函數(shù)時(shí)才做出決定相比,使用類似如此的包裝來(lái)選擇正確的函數(shù)定義通常更容易,因?yàn)檫@樣做會(huì)更加高效。
總結(jié)
規(guī)范化環(huán)境對(duì)于簡(jiǎn)化管理大有幫助。它減輕了您的負(fù)擔(dān),幫助您確定所在的系統(tǒng)類型,以及哪個(gè)命令和/或選項(xiàng)集最適合于獲取所需信息或執(zhí)行所需操作。為每個(gè)命令選擇正確的機(jī)制完全取決于該命令和您要嘗試達(dá)到的目的。
在您希望調(diào)用命令行選項(xiàng)的單個(gè)命令上,最好使用別名機(jī)制。內(nèi)聯(lián)函數(shù)最適合于您希望容易地將其嵌入當(dāng)前腳本環(huán)境的更復(fù)雜操作和序列,而完整的單獨(dú)腳本則最適合于麻煩的多步驟操作,或您希望為命令(或選項(xiàng))提供支持而不更改外殼環(huán)境的場(chǎng)合。
盡管有這些明顯的優(yōu)點(diǎn),但是務(wù)必要記住,如果將自己過于屏蔽在基礎(chǔ)的系統(tǒng)之外,當(dāng)發(fā)生故障而您無(wú)法訪問腳本時(shí),您可能處于無(wú)準(zhǔn)備的狀態(tài)——您應(yīng)該尋求擴(kuò)展和擴(kuò)充,而不是替代。
