對話 UNIX:第 2 部分: 做得多不如做得巧
每種技術性勞動都有其自身的秘密,這些小竅門、技巧和工具甚至可以處理最復雜的任務。例如,我的鄰居是一個熟練的木匠。他僅憑眼睛就可以非常精確地測量和改變角度、無縫地進行斜接,并且他所完成的作品為他在當地報紙上贏得了贊譽。
但更神奇的是(至少對于我這樣一個肯定會出錯的外行來說),他可以相當輕松地完成工作。他干這一行大約有 20 多年了,并且掌握了所有的快捷方法。通過這些快捷方法,可以在這里節(jié)省一點點時間,在那里節(jié)省一點點勞動,然而對于像進行切割、錘釘子和組裝框架這樣的重復性任務,這樣的節(jié)省最終加起來真的不少。
程序員、系統(tǒng)管理員和其他的 Unix® 計算機專業(yè)人員都有他們自己專門的工具:
CPU
RAM
操作系統(tǒng)
應用程序
Shell
就像一個經驗豐富的木匠,了解一些竅門并應用相應的工具可以節(jié)省大量的時間和精力。第 1 期的對話 UNIX 介紹了 UNIX 命令行的強大功能。本文向您介紹一些有用的 Shell 快捷方法,它們有助于您更好地掌握 Shell 提示符。
讓您的手指稍事休息,不要讓它們過于疲勞
正如第 1 部分所介紹的,UNIX 命令行的強大功能是無與倫比的。只需按一些鍵并使用一些句法粘結劑,包括管道 (|)、tee 和重定向,您就可以在 Shell 提示符中即興組裝自己的數據轉換器。
例如,下面的命令將在您的 home 目錄中查找所有包含單詞 Monthly Report 的文本文件:
$ find /home/joe -type f -name '*.txt' -print | xargs grep -l "Monthly Report"
該命令將搜索整個 home 目錄 (find /home/joe) 以查找所有的常規(guī)文件 (-type f) 中具有后綴 .txt 的文件,然后運行 grep 命令來搜索字符串 Monthly Report。如果找到匹配項,-l 選項將打印出相應的文件名。因此,該命令的輸出是匹配文件的列表。
盡管上面的命令很有用,但是要記住這個命令并重新輸入,這樣做很費勁,尤其是在您需要經常使用這個命令的情況下。而且,當命令行作為使用電子郵件、文件、工具(如編輯器、編譯器、監(jiān)視器)和遠程系統(tǒng)的主要接口時,您在命令行中所節(jié)省的時間和精力可以更好地用于手頭上的其他任務。畢竟,這些短的時間加起來真的不少。
為了處理這些重復性的任務,Unix Shell 提供了各種有用的快捷方法,具體包括:
符號
通配符
命令歷史
環(huán)境變量
別名
啟動文件
例如,您可以使用符號 ~(波浪符號)引用您的 home 目錄。您還可以使用 $HOME 環(huán)境變量引用您的 home 目錄,如清單 1 所示。
清單 1. UNIX Shell 中的快捷方法
$ whoamistrike$ echo ~/Users/strike$ echo $HOME/Users/strike$ !!echo $HOME/Users/strike
最后一個命令 !!(兩個感嘆號),可能看起來有些奇怪,但它是一種命令歷史符號,可以一字不差地重復前面的命令。(許多 Shell 還允許您使用向上箭頭鍵或按 Control+P 來瀏覽以前的命令列表。)
讓我們更仔細地研究 Shell 中的各種快捷方法。本文主要介紹 Z Shell(zsh,請參見參考資料部分),它通常安裝在 /bin/zsh 目錄中。(如果您的系統(tǒng)中沒有 Z Shell,可以請求系統(tǒng)管理員安裝它。)Z Shell 具有一些特別的特性,另外,這里所介紹的示例適用于所有主流的 UNIX Shell。
Shell 符號
針對許多頻繁使用的命令行參數,Shell 提供了相應的符號 或記號作為簡寫。您只需輸入這些符號來代替相應的參數。
如上所述,~ 表示您的 home 目錄。與之類似的簡寫形式 ~username 表示 username 的 home 目錄。例如,~joe 表示 joe 的 home 目錄,所以,要將文件從 joe 的 doc 目錄復制到您的 info 目錄,您可以輸入下面的命令:
$ cp ~joe/doc/report.txt ~/info
假設 joe 的 home 目錄位于 /guests,而您的 home 目錄為 /staff/bobr,~joe 將由 /guests/joe 替換,而 ~ 則變成 /staff/bobr,最后產生命令 cp /guests/joe/doc/report.txt /staff/bobr/info。(請參見側欄“檢驗您的工作以了解如何預覽命令行。)
另一個有用的符號是 ..(兩個點號),這是當前目錄的父目錄的簡寫。使用 .. 和 .(當前工作目錄的簡寫符號),您可以引用文件系統(tǒng)中相對于當前工作目錄的文件和目錄。
例如,如果您的當前工作目錄為 ~/jane/projects/lambda,那么簡寫 ../.. 表示向上兩級目錄的目錄,即 ~/jane。要表示包含 ~/jane 的目錄,您可以使用 ../../../(“向上三級目錄)或路徑 ~jane/../。后面的這個路徑表示從 ~jane 開始,然后轉到上一級目錄。
要將文件復制到您的當前目錄,不需要指定目標目錄,可以直接使用 .(“點)來表示:
$ cp -pr /path/to/lots/of/stuff .
前面的命令將 /path/to/lots/of/stuff 目錄遞歸地復制到您的當前目錄,并保持其原始的時間和日期戳。引用 .. 和 . 的路徑名稱為相對路徑名。以 /(正斜杠)或 ~(波浪符號)開頭的路徑名稱為絕對路徑名,因為您是從文件系統(tǒng)的頂端、或從一個目錄層次結構的頂端開始來引用相應的文件。
通配符和模式
使用符號,可以節(jié)省輸入的時間,并且可以快速和精確地引用特定的目錄。通配符 是另一種簡寫形式,用來引用目錄中的內容。
例如,假設您的某個目錄中包含了 100 個文件。有些是以 .c 為后綴的 C 源代碼文件,其他一些是以 .o 為后綴的目標文件,還有一些是文本文件 (.txt)、腳本 (.sh) 和可執(zhí)行文件(具有執(zhí)行權限的文件)。要僅列出其中的 C 文件,只需輸入:
$ ls *.c
通配符 *(通常稱為 star 而不是 asterisk)表示匹配任何字符序列。.c 文件擴展名是一種文本模式,它僅匹配點號加小寫字母 c 的情況。所以,*.c 表示任何字符序列加上點號和小寫字母 c。在給定了 *.c 之后,Shell 將查看當前目錄(除非您提供一個起始絕對或相對路徑名),找出所有匹配這個模式的文件名,將 *.c 擴展為文件名列表,然后將這個列表作為參數傳遞給 ls 命令。
清單 2 基于 wget 的源代碼文件演示了 *.c 的使用,wget 是一種命令行的下載實用工具。
單 2. 使用通配符在目錄中查找 C 源代碼文件
$ ls *.calloca.cansi2knr.ccmpt.cconnect.cconvert.c...
將通配符展開為匹配文件名列表的過程稱為通配符匹配 (globbing),并且 Unix Shell 具有各種各樣的通配符匹配操作符(所謂的 glob),以便幫助您描述所要查找的內容:
通配符匹配操作符 *(星號)匹配任何字符或字符序列,包括空序列。
通配符匹配操作符 ?(問號)匹配任何單個的字符。
通配符匹配操作符 [ ](方括號)匹配任何括起來的字符。在方括號中,通過使用 -(連字符),比如 [a-z] 或者所有的小寫字母,您可以引用某個范圍的字符。
(Z Shell 具有許多獨特的通配符匹配操作符。有關 Z Shell 通配符匹配操作符的更多信息,請參見側欄。)
您還可以根據需要重復使用通配符匹配操作符。清單 3 提供了一些其他示例。
在清單 3 中,命令 1 顯示了該目錄中所有的條目,包括長列表中那些以 .(點)開頭的條目。(-a 選項顯示了所謂的點文件;-1 選項表示在一列中列出所有的內容;而 -F 選項分別使用 /(正斜杠)和 *(星號)突出表示目錄和可執(zhí)行文件。)
命令 2 查找名稱以點號開頭的條目(即 .*)。第 3 個命令僅查找那些單字母后綴的項目。
第 4 個命令僅查找那些 4 個字母后跟點號和單個字符的項目。最后,命令 5 查找這樣的項目:以小寫字母 a、b 或 c 開頭,后面至少跟一個字母,然后可以是任何內容,接著是點號和任何后綴。正如所看到的,您可以根據實際情況重復使用這些通配符匹配操作符。
清單 3. 通配符示例
1 $ ls -1 -a -F./libsChangeLogChangeLog-branches/MakefileMakefile.inalloca.cansi2knr.ccmpt.ccmpt.oconfig.hconfig.h.inconnect.cconnect.hconnect.oconvert.cconvert.hconvert.o...wget*2 $ ls -a -F .*./lib3 $ ls -1 *.?alloca.cansi2knr.ccmpt.ccmpt.oconfig.hconnect.cconnect.hconnect.oconvert.cconvert.hconvert.o...4 $ ls -1 ????.?cmpt.ccmpt.o5 $ ls [a-c]?*.*alloca.cansi2knr.ccmpt.ccmpt.oconfig.hconfig.h.inconnect.cconnect.hconnect.oconvert.cconvert.hconvert.ocookIEs.ccookies.hcookies.o
那么,ls *.z 將會產生什么樣的結果呢(假設不存在這樣的文件)?它將產生一條有用的錯誤消息:
$ ls *.zzsh: no matches found: *.z
關于(命令)歷史
到目前為止,您已經了解了如何指定路徑和選擇相應的文件。您可以在命令行中描述需要完成的任務。然而,即使所有的命令行都很短并且很簡單,但您仍然有可能對反反復復地輸入這些相同的內容而感到厭煩。尤其是,您可能厭倦了輸入冗長的、復雜的命令行,其中可能包含大量的選項、或者參數的順序有嚴格的要求。幸運的是,大多數 Shell 都維護了以前命令的歷史。要再次運行一個命令,只需從這個歷史列表中找到相應的條目,然后再次運行它。與 Shell 中其他的部分一樣,通過快捷方法可以快速和輕松地進行引用。
要在 Z Shell 中啟用命令歷史,可以輸入:
$ HISTSIZE=500$ SAVEHIST=500
這里的命令指定了 Shell 和持久化歷史文件應該保留最后的 500 條命令。(在缺省情況下,Z Shell 僅保存最后的 30 條命令。)有關如何捕獲和保存命令歷史的信息,請查看您的 Shell 文檔。
在 Shell 中進行了一段時間的工作之后,您只需輸入 history 就可以查看命令歷史:
$ history...781 /bin/ls -d */782 /bin/ls -F *(/)783 /bin/ls -d -F *(/)784 /bin/ls -d -F */785 /bin/ls -d */
您所運行的每個命令都會分配到一個順序的數值標識符。您可以使用這個標識符,如 782,來引用完整的命令和命令中的某些部分。要再次運行一個命令,可以輸入 !(感嘆號)加上命令對應的數值:
$ !785ChangeLog-branches/ doc/ po/ src/ util/ Windows/
如果您希望從一個歷史命令中獲得特定的參數,可以使用 !(感嘆號)來引用這個命令,并提供 :N,其中 0 表示命令名,1 表示第 1 個參數,依此類推。例如,要提取歷史日志中命令 782 的第二個參數,可以輸入清單 4 中所示的代碼。
清單 4. 提取命令 782 的第二個參數
$ echo !782:2 echo *(/)ChangeLog-branches doc po src util windows$ ls AUTHORS COPYING INSTALL MacHINESAUTHORS COPYING INSTALL MACHINES$ echo !!:3echo INSTALL$ history -2788 ls AUTHORS COPYING INSTALL MACHINES789 echo INSTALL$ echo !788^echo AUTHORSAUTHORS$ echo !788$echo MACHINESMACHINES
命令 history -2 打印出前兩個命令。作為快捷方法,您可以使用 ^(脫字符號)引用命令的第一個參數(而不是命令名本身),并且您可以使用 $(美元符號)引用歷史命令的最后一個參數。您還可以使用范圍符號來引用某個范圍的參數,如清單 5 所示。
清單 5. 范圍符號
$ echo AUTHORS COPYING INSTALL MacHINESAUTHORS COPYING INSTALL MACHINES$ echo !!:1-2echo AUTHORS COPYINGAUTHORS COPYING
還有其他的更直接的方法可以用來再次調用歷史命令。其中一種方法是搜索歷史命令:
$ ls I*$ ls M*$ echo !?Mls INSTALL
結構 !?M 尋找最近的包含大寫字母 M 的歷史命令行。
環(huán)境變量
流暢地表達命令行 任務,這是一種基本的 Unix 技能。但是與 UNIX 進行對話不僅僅只是使用 Shell 提示符,您還必須與各種各樣的 UNIX 實用工具進行通信。在 UNIX 中,環(huán)境變量保存了 Shell 中的相關設置,并允許您將首選項傳播到從命令行啟動的所有實用工具中。
有些環(huán)境變量稱為 Shell 變量,Shell 僅使用這些變量控制其自身的行為。例如,只有 Z Shell 使用 $HISTSIZE 和 $SAVEHIST 管理命令歷史,如上所述??梢詫?Shell 變量看作相應的設置。
需要對其他的環(huán)境變量進行導出、或使得它們全局可用,并將它們復制到從命令行中啟動的每個命令的進程空間(即環(huán)境)。例如,$HOME 是一個特殊的環(huán)境變量,它保存了您的 home 目錄的位置。UNIX 登錄序列將設置 $HOME(以及其他的環(huán)境變量),然后啟動 Shell,而 Shell 反過來使用 $HOME 查找所有的 Shell 啟動文件。您所啟動的其他應用程序,如 SSH 和 FTP,引用 $HOME 查找 .netrc 文件(用于存儲機密的、遠程訪問的密碼)。有些環(huán)境變量,如 $HOME、$PATH 和 $SHELL,會被所有應用程序使用。其他的環(huán)境變量可能專門針對某個應用程序。
要查看當前所有的環(huán)境變量,可以輸入 printenv,如清單 6 所示。(根據系統(tǒng)管理員對系統(tǒng)所進行的配置,您系統(tǒng)中的環(huán)境變量可能會比本文中所介紹的更多或更少。)
清單 6. 查看環(huán)境變量
$ printenvPATH=/Users/strike/bin:/Applications/xampp/xamppfiles/bin:/Users/strike/bin:/usr/bin:/bin:/usr/sbin:/sbinHOME=/Users/strikeSHELL=/bin/zshUSER=strikeTERM=xterm-colorLOGNAME=strikeSHLVL=1PWD=/Local/src/versions/wget/wget-1.9OLDPWD=/Local/src/versions/wget/wget-1.9/srcPERL5LIB=/Applications/xampp/xamppfiles/lib/perl5/site_perl/5.8.7:/Projects/IGSP/srcCLICOLOR=trueMANPATH=/Local/root/share/man:/usr/share/man:/opt/local/share/manINFOPATH=/opt/local/share/infoLESS=-n
您可能認識其中大多數的變量,而其他一些可能是新出現的。Shell 級別($SHLVL)顯示您所處的 Shell 的深度。1 表示登錄 Shell,2 表示您從登錄 Shell 中啟動了另一個 Shell,依此類推。您可以使用 $SHLVL 的值來更改后續(xù) Shell(嵌套 Shell)的提示符。$TERM 反映了您的終端(可能是終端模擬程序)設置,對于確保正確地呈現文本、顏色以及對按鍵進行正確的解釋,這是非常重要的信息。$PWD 是您的當前工作目錄,而 $OLDPWD 是上一次的工作目錄。您可以使用這兩個變量實現在兩個目錄之間的快速切換,如清單 7 所示。
清單 7. 在目錄之間進行切換
$ echo $PWD/Users/strike$ echo $OLDPWD/Local/src/versions/wget/wget-1.9$ cd $OLDPWD$ echo $PWD/Local/src/versions/wget/wget-1.9$ echo $OLDPWD/Users/strike
上面列表中剩下的環(huán)境變量都是應用程序特定的。每個環(huán)境變量保存了相應的首選項設置,當您啟動了與之關聯的應用程序后,它可以用于控制該應用程序的工作方式。$PERL5LIB 是 Perl 查找自定義庫的搜索路徑。ls 命令使用 $CLICOLOR 通過不同的顏色呈現不同類型的文件(目錄為藍色、可執(zhí)行文件為綠色,等等)。程序的 man 頁面中通常包含對自定義應用程序環(huán)境變量的說明。
設置環(huán)境變量與設置 Shell 變量的方法相同。然而,您必須導出該變量,以使得它全局可用:
$ MYVARIABLE=$HOME/projectX$ export TMPDIR=/tmp/projectX
前一個命令設置了名為 $MYVARIABLE 的 Shell 變量。(開頭的美元符號是 Shell 提示符。您在設置變量時,不用提供這個 $ 符號。然而,當您使用這個變量時,必須使用美元符號,比如 $MYVARIABLE。)$MYVARIABLE 僅對 Shell 是可見的,因為沒有將其導出。要查看所有 Shell 變量的列表,可以輸入 set。set 的輸出包括環(huán)境變量,因為它們對 Shell 來說也是可用的。
在后面的一個命令中,設置并導出了 $TMPDIR,因此它對于從 Shell 中啟動的所有應用程序都是可用的。GNU Compiler Collection (GCC) 編譯器是一個使用 $TMPDIR 的應用程序。$TMPDIR 中所存儲的值表示 GCC 用來存放生成的臨時文件的位置。
如果您要刪除一個環(huán)境變量,只需輸入 unset 加上變量名即可,如清單 8 所示。
清單 8. 刪除環(huán)境變量
$ setHOME=/Users/strikeMYVARIABLE=/Users/strike/projectXTMPDIR=/tmp/projectX...$ unset MYVARIABLE TMPDIR$ setHOME=/Users/strike....
別名和啟動文件
前面的部分主要關注的是如何減少在命令行中的輸入。當然,還有許多內容需要學習,因為 Shell 環(huán)境非常豐富。然而請記住,功能越強大,生產能力就越大(要對蜘蛛俠說聲抱歉,因為修改了原話)。
為了保留以前輸入的內容和保存以前的所有設置,Unix Shell 分別提供了別名和啟動文件。別名 是您所創(chuàng)建的快捷方法。每次 Shell 啟動時都會讀取啟動文件,這是保存(和共享)所有 Shell 設置的理想的地方,如 Shell 變量(選項)、環(huán)境變量和別名。
別名是一個簡短的序列,您可以使用它來代替一個較長的命令。您可以把別名看作是一個命令行的縮寫。無需輸入:
$ find /home/joe -type f -name '*.txt' -print | xargs grep -l "Monthly Report"
在命令提示符處,您可以輸入已經創(chuàng)建的別名:
$ findreports
Shell 減少了工作的復雜程度,它會將 findreports 替換成其擴展形式。要創(chuàng)建 findreports 別名,可以輸入:
alias findreports='find $HOME -type f -name "*.txt" -print | xargs grep -l "Monthly Report"'
必須使用單引號確定每個別名的界限。如果您需要在別名中使用引號,那么可以使用雙引號。Z Shell 別名可以包含許多 Shell 基本單位,包括變量、管道、重定向、其他別名和其他 Shell 操作數,如清單 9 所示。
清單 9. Z Shell 基本單位
$ alias ll='/bin/ls -l'$ ll -d 2002*drwxrwxr-x 2 www-data www-data4096 Jan 16 2002 2002-02drwxrwxr-x 2 www-data www-data4096 Jan 22 2002 2002-03drwxrwxr-x 2 www-data www-data4096 Apr 15 2002 2002-04drwxrwxr-x 2 www-data www-data4096 Apr 19 2002 2002-05...$ alias lt='ll -t'$ lt -d 2002*drwxrwxr-x 2 www-data www-data 4096 Apr 19 2002 2002-05drwxrwxr-x 2 www-data www-data 4096 Apr 15 2002 2002-04drwxrwxr-x 2 www-data www-data 4096 Jan 22 2002 2002-03drwxrwxr-x 2 www-data www-data 4096 Jan 16 2002 2002-02$ alias m='pinky | grep mstreicher'$ mmstreicher Martin Streicher ...$ alias snap='pinky >> ~/.pinky'$ snap$ snap$ cat ~/.pinkyLoginNameTTY Idle When Wheremstreicher Martin Streicherpts/0Jun 18 16:40 cpe-071-065-224-025.nc.res.rr.comLoginNameTTY Idle When Wheremstreicher Martin Streicherpts/0Jun 18 16:40 cpe-071-065-224-025.nc.res.rr.com
