對話 UNIX: !$#@*%
現(xiàn)在,您已經(jīng)在 IBM® AIX® 上工作了一段時(shí)間了。您已經(jīng)學(xué)習(xí)了幾個(gè)基本命令,能夠在目錄結(jié)構(gòu)中移動(dòng)、創(chuàng)建和修改文件、查看正在運(yùn)行的進(jìn)程以及管理用戶和系統(tǒng)。這很不錯(cuò),但是您希望了解 Unix® 管理員輸入的命令是什么意思。這些命令中包含許多奇怪的符號(hào)。在本文中,了解 |、>、>>、<、<<、[[ 和 ]] 等符號(hào)在 UNIX 和 Linux® 中的意思,以及如何使用 &&、||、<、<= 和 != 操作符。
管道
如果您熟悉 UNIX,那么管道(或 pipe)會(huì)是每天都要接觸到的東西。管道最初是由 Malcolm McIlroy 開發(fā)的,可以使用管道把一個(gè)命令的標(biāo)準(zhǔn)輸出(stdout)定向到下一個(gè)命令的標(biāo)準(zhǔn)輸入(stdin),這樣就形成了連續(xù)執(zhí)行的命令鏈。可以在一個(gè)命令行上使用多個(gè)管道。在許多時(shí)候,一個(gè)命令的 stdout 用作下一個(gè)命令的 stdin,第二個(gè)命令的 stdout 又被重定向到另一個(gè)命令的 stdin,依此類推。
例如,在排除故障或執(zhí)行日常檢查時(shí),大多數(shù) UNIX 管理員首先做的事情之一是查看系統(tǒng)上當(dāng)前正在運(yùn)行的進(jìn)程。清單 1 演示這樣的檢查。
清單 1. 日常進(jìn)程檢查示例
# ps –ef UID PIDPPID CSTIMETTY TIME CMDroot 1 0 0 Jul 27 - 0:05 /etc/initroot 53442 151674 0 Jul 27 - 0:00 /usr/sbin/syslogdroot 57426 1 0 Jul 27 - 0:00 /usr/lib/errdemonroot 61510 1 0 Jul 27 - 23:55 /usr/sbin/syncd 60root 65634 1 0 Jul 27 - 0:00 /usr/ccs/bin/shlap64root 82002 110652 0 Jul 27 - 0:24 /usr/lpp/X11/bin/X -x abx -x dbe -x GLX -D /usr/lib/X11//rgb -T -force :0 -auth /var/dt/A:0-SfIdMaroot 86102 1 0 Jul 27 - 0:00 /usr/lib/methods/ssa_daemon -l ssa0root 106538 151674 0 Jul 27 - 0:01 sendmail: accepting connectionsroot 110652 1 0 Jul 27 - 0:00 /usr/dt/bin/dtlogin -daemonroot 114754 118854 0 Jul 27 - 20:22 dtgreetroot 118854 110652 0 Jul 27 - 0:00 dtlogin <:0>-daemonroot 131088 1 0 Jul 27 - 0:07 /usr/atria/etc/lockmgr -a /var/adm/atria/almd -q 1024 -u 256 -f 256root 147584 1 0 Jul 27 - 0:01 /usr/sbin/cronroot 155816 151674 0 Jul 27 - 0:04 /usr/sbin/portmaproot 163968 151674 0 Jul 27 - 0:00 /usr/sbin/qdaemonroot 168018 151674 0 Jul 27 - 0:00 /usr/sbin/inetdroot 172116 151674 0 Jul 27 - 0:03 /usr/sbin/xntpdroot 180314 151674 0 Jul 27 - 0:19 /usr/sbin/snmpmibdroot 184414 151674 0 Jul 27 - 0:21 /usr/sbin/aixmibdroot 188512 151674 0 Jul 27 - 0:20 /usr/sbin/hostmibdroot 192608 151674 0 Jul 27 - 7:46 /usr/sbin/muxatmdroot 196718 151674 0 11:00:27 - 0:00 /usr/sbin/rpc.mountdroot 200818 151674 0 Jul 27 - 0:00 /usr/sbin/biod 6root 213108 151674 0 Jul 27 - 0:00 /usr/sbin/nfsd 3891root 221304 245894 0 Jul 27 - 0:05 /bin/nsrexecd daemon 225402 151674 0 11:00:27 - 0:00 /usr/sbin/rpc.statdroot 229498 151674 0 11:00:27 - 0:00 /usr/sbin/rpc.lockdroot 241794 151674 0 Jul 27 - 0:51 /usr/lib/netsvc/yp/ypbindroot 245894 1 0 Jul 27 - 0:00 /bin/nsrexecdroot 253960 1 0 Jul 27 - 0:00 ./mflm_managerroot 274568 151674 0 Jul 27 - 0:00 /usr/sbin/sshd -Droot 282766 1 0 Jul 27 lft0 0:00 /usr/sbin/getty /dev/consoleroot 290958 1 0 Jul 27 - 0:00 /usr/lpp/diagnostics/bin/diagdroot 315646 151674 0 Jul 27 - 0:00 /usr/sbin/lpdroot 319664 1 0 Jul 27 - 0:00 /usr/atria/etc/albd_serverroot 340144 168018 0 12:34:56 - 0:00 rpc.ttdbserver 100083 1root 376846 168018 0 Jul 30 - 0:00 rlogindcormany 409708 569522 0 19:29:27 pts/1 0:00 -kshroot 569522 168018 0 19:29:26 - 0:00 rlogindcormany 733188 409708 3 19:30:34 pts/1 0:00 ps -efroot 749668 168018 0 Jul 30 - 0:00 rlogind
系統(tǒng)上當(dāng)前正在運(yùn)行的進(jìn)程的列表可能像 清單 1 這么簡單;但是,大多數(shù)生產(chǎn)系統(tǒng)運(yùn)行的進(jìn)程更多,這會(huì)使 ps 的輸出更長。為了把這個(gè)列表縮短到自己需要的范圍,可以使用管道把 ps –ef 的標(biāo)準(zhǔn)輸出重定向到 grep,從而搜索自己真正希望看到的結(jié)果。清單 2 把 清單 1 產(chǎn)生的進(jìn)程列表重定向到 grep,搜索字符串 “rpc 和 “ksh。
清單 2. 把進(jìn)程列表重定向到 grep
# ps –ef | grep –E "rpc|ksh"root 196718 151674 0 11:00:27 - 0:00 /usr/sbin/rpc.mountd daemon 225402 151674 0 11:00:27 - 0:00 /usr/sbin/rpc.statdroot 229498 151674 0 11:00:27 - 0:00 /usr/sbin/rpc.lockdroot 340144 168018 0 12:34:56 - 0:00 rpc.ttdbserver 100083 1cormany 409708 569522 0 19:29:27 pts/1 0:00 -kshcormany 733202 409708 0 19:52:20 pts/1 0:00 grep -E rpc|ksh
當(dāng)多次把 stdout 重定向到 stdin 時(shí),管道的使用方法可以很復(fù)雜。在下面的示例中,擴(kuò)展了前面的 ps 和 grep 示例,把它的 stdout 重定向到另一個(gè) grep,其作用是排除包含 “grep 或 “ttdbserver 的字符串。當(dāng)最后的 grep 操作完成時(shí),再次使用管道把 stdout 重定向到一個(gè) awk 語句,其作用是輸出進(jìn)程標(biāo)識(shí)符(PID)大于 200,000 的所有進(jìn)程:
# ps –ef | grep –E "rpc|ksh" | grep -vE "grep|rpc.ttdbserver" | awk -v _MAX_PID=200000 '{if ($2 > _MAX_PID) {print "PID for process",$8,"is greater than", _MAX_PID}}'PID for process /usr/sbin/rpc.statd is greater than 200000PID for process /usr/sbin/rpc.lockd is greater than 200000PID for process -ksh is greater than 200000
圖 1 通過圖形說明命令的 stdout 重定向到后續(xù)命令的 stdin 的次序。
圖 1. 管道示例
用 >、>>、< 和 << 執(zhí)行數(shù)據(jù)重定向
通過命令行界面(CLI)執(zhí)行命令的另一個(gè)重要方面是,能夠把各種輸出寫到一個(gè)設(shè)備,或者把來自另一個(gè)設(shè)備的輸入讀取到命令中。要想寫一個(gè)命令的輸出,需要在執(zhí)行的命令后面加上大于號(hào)(> 或 >>)和所需的目標(biāo)文件名或設(shè)備。如果目標(biāo)文件不存在,而且您對目標(biāo)目錄有寫權(quán)限,那么 > 和 >> 會(huì)創(chuàng)建這個(gè)文件并根據(jù)您的 umask 設(shè)置權(quán)限,然后把命令的輸出寫到剛創(chuàng)建的文件中。但是,如果這個(gè)文件存在,> 會(huì)嘗試打開文件并覆蓋整個(gè)內(nèi)容。如果希望在這個(gè)文件中追加內(nèi)容,那么只需使用 >>??梢哉J(rèn)為它的作用是把左邊命令的輸出數(shù)據(jù)流移動(dòng)到右邊的目標(biāo)文件中(即 <cmd> -> <output> -> <file>)。
下面的示例執(zhí)行 “管道 一節(jié)中的 ps –ef 示例,并把輸出重定向到文件 ps_out:
# ps –ef | grep –E "rpc|ksh" > ps_out
下面的代碼執(zhí)行前面擴(kuò)展的管道示例并把輸出重定向到同一個(gè)文件(ps_out),但是追加到當(dāng)前數(shù)據(jù)后面:
# ps –ef | grep –E "rpc|ksh" | grep -vE "grep|rpc.ttdbserver" | awk -v _MAX_PID=200000 '{if ($2 > _MAX_PID) {print "PID for process",$8,"is greater than", _MAX_PID}}' >> ps_out
清單 3 給出前兩個(gè)重定向的輸出。
清單 3. 重定向的輸出
# cat ps_outroot 196718 151674 0 11:00:27 - 0:00 /usr/sbin/rpc.mountd daemon 225402 151674 0 11:00:27 - 0:00 /usr/sbin/rpc.statdroot 229498 151674 0 11:00:27 - 0:00 /usr/sbin/rpc.lockdroot 340144 168018 0 12:34:56 - 0:00 rpc.ttdbserver 100083 1cormany 409708 569522 0 19:29:27 pts/1 0:00 -kshcormany 733202 409708 0 19:52:20 pts/1 0:00 grep -E rpc|kshPID for process /usr/sbin/rpc.statd is greater than 200000PID for process /usr/sbin/rpc.lockd is greater than 200000PID for process -ksh is greater than 200000
當(dāng)只使用 > 重定向輸出時(shí),只重定向命令的 stdout。但是,除了 stdout,還有 stderr 輸出:前者表示為 1,后者表示為 2。在 Unix 中輸出重定向沒有區(qū)別。只需在 > 前面加上所需的輸出類型(例如,1>、2>),告訴 shell 要把輸出路由到哪里。
清單 4 嘗試列出 fileA.tar.bz2 和 fileC.tar.bz2。但是,如第一個(gè)命令(ls)所示,fileC.tar.bz2 不存在。好在可以把 stdout 和 stderr 分別重定向到 ls.out 和 ls.err,這樣就能夠看到錯(cuò)誤消息。
清單 4. 列出文件 fileA.tar.bz2 和 fileC.tar.bz2
# lsfileA.tar.bz2 fileAA.tar.bz2 fileB.tar.bz2 fileBB.tar.bz2# ls fileA.tar.bz2 fileC.tar.bz2 1> ls.out 2> ls.err# cat ls.outfileA.tar.bz2# cat ls.errls: 0653-341 The file fileC.tar.bz2 does not exist.
在 AIX 中,對 stdout 和 stderr 使用 > 和 >> 時(shí)應(yīng)用相同的規(guī)則。例如,以后的測試可以使用相同的輸出文件,見 清單 5。
清單 5. 使用輸出文件進(jìn)行以后的測試
# ls fileB.tar.bz2 fileD.tar.bz2 1>> ls.out 2>> ls.err# cat ls.outfileA.tar.bz2fileB.tar.bz2# cat ls.errls: 0653-341 The file fileC.tar.bz2 does not exist.ls: 0653-341 The file fileD.tar.bz2 does not exist.
有時(shí)候,可能需要把 stdout 和 stderr 寫到同一個(gè)文件或設(shè)備。這有兩種方法。第一種方法是把 1> 和 2> 重定向到同一個(gè)文件:
# ls fileA.tar.bz2 fileC.tar.bz2 1> ls.out 2> ls.out# cat ls.outfileA.tar.bz2ls: 0653-341 The file fileC.tar.bz2 does not exist.
第二個(gè)方法更簡單更快速,有經(jīng)驗(yàn)的 Unix 用戶更喜歡采用這種方法:
# ls fileA.tar.bz2 fileC.tar.bz2 > ls.out 2>&1# cat ls.outfileA.tar.bz2ls: 0653-341 The file fileC.tar.bz2 does not exist.
我們分解這個(gè)語句。首先,執(zhí)行 ls fileA.tar.bz2 fileC.tar.bz2。然后使用 > ls.out 把 stdout 重定向到 ls.out,使用 2>&1 把 stderr 重定向到前面重定向的 stdout(ls.out)。
請記住,可以把輸出重定向到文件和其他設(shè)備??梢园褦?shù)據(jù)重定向到打印機(jī)、軟盤、終端類型(TTY)以及各種其他設(shè)備。例如,如果希望把一個(gè)消息發(fā)送給所有會(huì)話(或 TTY)上的某個(gè)用戶,那么只需循環(huán)處理 who 并把一個(gè)消息重定向到 TTY(如果您有足夠的權(quán)限的話),見 清單 6。
清單 6. 把消息重定向到一個(gè) TTY
# for _TTY in 'who | grep "cormany" | awk '{print $2}''> do> _TTY="/dev/${_TTY}"> echo "Sending message to cormany on ${_TTY}"> echo "Test Message to cormany@${_TTY}" > ${_TTY}> doneSending message to cormany on /dev/pts/13Test Message to cormany@/dev/pts/13Sending message to cormany on /dev/pts/14
stdin 而不是 stdout
盡管使用 > 和 >> 對于大多數(shù)人是一個(gè)相當(dāng)容易掌握的概念,但是有的人在使用小于號(hào)(< 和 <<)時(shí)常常有困難。在考慮 > 和 >> 時(shí),認(rèn)為它們把左邊命令的輸出數(shù)據(jù)流移動(dòng)到右邊的目標(biāo)文件中,這樣最容易理解。同樣的方法也適用于 < 和 <<。在使用 < 時(shí),本質(zhì)上是用一個(gè)已經(jīng)提供的 stdin 執(zhí)行一個(gè)命令。也就是說,把已經(jīng)提供的數(shù)據(jù)提供給左邊的命令作為 stdin(即 <cmd> <- <data>)。
例如,假設(shè)希望把一個(gè)包含 ASCII 文本文件的電子郵件發(fā)送給另一個(gè)用戶??梢允褂霉艿腊?cat 的 stdout 重定向到 mail 的 stdin(即 cat mail_file.out | mail –s "Here's your E-mail!" [email protected]),也可以把文件的內(nèi)容重定向到 mail 命令的 stdin:
# mail –s "Here's your E-mail!" [email protected] < mail_file.out
使用 <<(也稱為 here-document)可以節(jié)省格式化時(shí)間,并且使命令執(zhí)行的處理更容易。通過使用 <<,文本字符串被重定向到執(zhí)行的命令作為 stdin,但是可以繼續(xù)輸入信息,直到到達(dá)終止標(biāo)識(shí)符。只需輸入命令,輸入 << 和終止標(biāo)識(shí)符,然后輸入需要的任何內(nèi)容,最后在一個(gè)新行上輸入終止標(biāo)識(shí)符。通過使用 here-document,可以保留空格、換行等。
例如,Unix 必須單獨(dú)處理下面五個(gè) echo 語句:
# echo "Line 1"Line 1# echo "Line 2"Line 2# echo "Line 3"Line 3# echo "Line 4"Line 4# echo "Line 5"Line 5
可以用以下代碼替換多個(gè) echo 語句,UNIX 只需處理一次執(zhí)行:
# cat << EOF> Line 1> Line 2> Line 3> Line 4> Line 5> EOFLine 1Line 2Line 3Line 4Line 5
還可以使用制表符讓 shell 腳本中的內(nèi)容更整潔一點(diǎn),這只需要在 << 和終止標(biāo)識(shí)符之間放上一個(gè)連字符(-):
# cat <<- ATC>Line 1>Line 2>Line 3>Line 4>Line 5> ATCLine 1Line 2Line 3Line 4Line 5
清單 7 給出的示例演示如何結(jié)合使用本文到目前為止討論的東西。
清單 7. 組合 CLI
# cat redirect_example#!/usr/bin/kshcat <<- ATC | sed "s/^/Redirect Example => /g" >> atc.outThis is an example of how to redirectstdout to a file as well as pipe stdout into stdinof another command (i.e. sed), all done insidea here-document.Cool eh?ATC
現(xiàn)在,看看關(guān)于重定向和管道的腳本。
# ./redirect_example# cat atc.outRedirect Example => This is an example of how to redirectRedirect Example => stdout to a file as well as pipe stdout into stdinRedirect Example => of another command (i.e. sed), all done insideRedirect Example => a here-document.Redirect Example =>Redirect Example => Cool eh?
子 shell
有時(shí)候,需要一起執(zhí)行幾個(gè)命令。例如,如果希望在另一個(gè)目錄中執(zhí)行某一操作,可以使用 清單 8 中的代碼。
清單 8. 同時(shí)執(zhí)行幾個(gè)命令
# pwd/home/cormany# cd testdir# tar –cf ls_output.tar ls.out?# pwd/home/cormany/testdir
這是有效的,但是要注意,在執(zhí)行這些步驟之后,您就不再位于原來的目錄中了。通過把這些命令放在它們自己的子 shell 中,它們會(huì)作為子 shell 的實(shí)例執(zhí)行。清單 9 演示如何使用子 shell 執(zhí)行相同的代碼。
清單 9. 使用子 shell 同時(shí)執(zhí)行幾個(gè)命令
# pwd/home/cormany# (cd testdir ; tar -cf ls_output.tar ls.out?)# pwd/home/cormany
test 命令、[ ] 和 [[ ]]
在編寫 shell 腳本或用任何現(xiàn)代語言編寫程序時(shí),運(yùn)算表達(dá)式或值的能力都很重要。Unix 一直通過 test 命令提供這一功能。正如 test 的手冊頁指出的,test 命令運(yùn)算表達(dá)式參數(shù)的值,如果表達(dá)式的值是 True,就返回零(True)退出值。關(guān)于 test 的定義和所有可用條件的更多信息,請參見 test 手冊頁。
要想使用 test 命令,只需給這個(gè)命令提供適當(dāng)?shù)臉?biāo)志和文件名。當(dāng) test 運(yùn)算完表達(dá)式時(shí),返回到命令提示,可以在這里檢查返回碼,見 清單 10。
清單 10. 檢查返回碼
# ls –l-rwxr-xr-x1 cormany atc 786 Feb 22 16:11 check_file-rw-r--r--1 cormany atc 0 Aug 04 20:57 emptyfile# test -f emptyfile# echo $?0# test -f badfilename# echo $?1
根據(jù)定義,如果表達(dá)式值是 True,那么 test 返回零退出值,否則返回非零退出值(即 1)。在 清單 10 中,找到了文件 emptyfile,所以 test 返回 0;但是沒有找到文件 badfilename,所以返回 1。
使用 test 的另一種方法是把要運(yùn)算的表達(dá)式放在單層方括號(hào)([ ])中。使用 test 命令或把它替換為 [ ] 會(huì)返回相同的值:
# [ -f emptyfile ]# echo $?0# [ -f badfilename ]# echo $?1
使用單層方括號(hào)([ ])還是雙層方括號(hào)([[ ]])是個(gè)人習(xí)慣問題,實(shí)際上取決于您如何學(xué)習(xí)命令和 shell 腳本編程。但是請記住,這兩者之間有一些差異。盡管 [ ] 和 [[ ]] 在運(yùn)算期間使用相同的測試操作符,但是它們使用不同的邏輯操作符。
操作符
在 ksh(AIX 中使用的默認(rèn) shell)中,以及 Unix 和 Linux 使用的其他 shell 中,一定要知道如何使用測試、邏輯和替換操作符。
測試操作符
在編寫 shell 腳本時(shí),測試操作符對于檢查錯(cuò)誤和檢查文件狀態(tài)很重要。下面只是可以在 ksh 和其他標(biāo)準(zhǔn) UNIX shell 中使用的一部分測試操作符:
-d <file>:<file> 是一個(gè)目錄
-e <flle>:<file> 存在
-f <file>:<file> 是一個(gè)常規(guī)文件
-n <string>:<string> 不是 NULL
-r <file>:用戶對 <file> 有讀權(quán)限
-s <file>:<file> 的大小大于 0
-w <file>:用戶對 <file> 有寫權(quán)限
-x <file>:用戶對 <file> 有執(zhí)行權(quán)限
-z <string>:<string> 是 null
-L <file>:<file> 是一個(gè)符號(hào)鏈接
請記住,在 Unix 目錄中,設(shè)備、符號(hào)鏈接和其他對象都是文件,所以上面的測試操作符適用于所有類型的文件。
每個(gè)人都有自己的 shell 腳本編程風(fēng)格。無論在測試語句中使用 [[ ]] 還是 [ ],上面的測試操作符的作用是相同的。本文使用 [[ ]]。清單 11 演示如何使用上面列出的幾個(gè)測試操作符。
清單 11. 使用測試操作符
#!/usr/bin/kshwhile truedo echo "nEnter file to check: c" read _FNAME if [[ ! -e "${_FNAME}" ]] thenecho "Unable to find file '${_FNAME}'"continue fi if [[ -f "${_FNAME}" ]] thenecho "${_FNAME} is a file." elif [[ -d "${_FNAME}" ]] thenecho "${_FNAME} is a Directory." elif [[ -L "${_FNAME}" ]] thenecho "${_FNAME} is a symbolic link." elseecho "Unable to determine file type for '${_FNAME}'" fi [[ -r "${_FNAME}" ]] && echo "User ${USER} can read '${_FNAME}'" [[ -w "${_FNAME}" ]] && echo "User ${USER} can write to '${_FNAME}'" [[ -x "${_FNAME}" ]] && echo "User ${USER} can execute '${_FNAME}'" if [[ -s "${_FNAME}" ]] thenecho "${_FNAME} is NOT empty." elseecho "${_FNAME} is empty." fidone
