世上本沒有shell,命令多了,也就成了shell。
——Pope
要說shell,就不能不談起Unix的Keep It Simple Stupid理念。Unix社區崇尚簡潔、專注,鼓勵一個工具只做一件事,做好這件事。當你需要完成一項復雜的工作時,分解,細化,再交給相應的小工具去執行。這樣就需要提供一種機制把各個工具連接起來。所以,在我開來,學習shell重在掌握任務分解和命令間的互聯。
當然,你還會發現,shell的用法很奇怪,畢竟它是個很古老的東西了,包容各種歷史遺留也是無奈之選。
其實,本文是一篇Learning the Bash Shell (3rd)的讀書筆記。
shell是一種用戶與類Unix操作系統之間以文字方式進行交互的媒介。還有其他的交互形式,比如圖形界面。
shell命令一般是祈使句 如:去 給老子 拿瓶水來。
shell命令先是一個動詞,緊接著若干副詞(option)和名詞(argument)(也可能沒有)。
操作的對象大多是文件(Unix里幾乎所有東西都是文件)。文件分為普通文件(文本),可執行文件和目錄。
目錄可以包括若干子目錄,包括本目錄(.)和父目錄(..)。目錄被組織成樹形結構,用路徑來標示。
可以明確指定shell操作的對象的絕對路徑,如果不說就默認是在當前目錄下。當前目錄雖然被稱為目錄卻沒有物理對應,是一個概念上的浮動地址,可以通過pwd
(print the working directory,你看多怪的名字,不知道的還以為是password呢)命令來查看,還可以用cd
命令(change the working directory)來修改。此外還有主目錄(~
)和操作記錄中的上一目錄(-
)。
shell的操作對象可以是具體名詞(如某個文件名),也可以是抽象名詞(用來描述某一特征,需要用到通配符(wildcard))。
通配符有三種基本模式:
匹配任一字符、匹配任意字符、匹配字符集。
!, \*, […], [!…], {…}
Unix將I/O設備統一看作文件,并把I/O看作處理任意長的字節流。
Unix把I/O流簡化為標準輸入輸入(standard input)、標準輸出(standard output)和標準錯誤輸出(standard error output)三個端口。輸入流默認為鍵盤,輸出和錯誤流默認為顯示屏。如果需要,可以將這些端口映射到其他設備或文件上(重定向)。
Unix下的小工具往往只專注于某項處理。數據被不同的小工具一道道篩選、加工,最后得到想要的結果。正是因為專注,讓它們更容易組合起來構建復雜的工具。
奇怪的命名
雖然shell命令的命名也不是無章可循,但總歸是很奇怪的。以下是幾個常用的工具:
但比起那些預設變量,這還算好的。
比如位置參數,人家名字直接叫1, 2, ??要想取到它們的值,前面加$($var
其實是${var}
的簡寫),即$1
, $2
, ??。還有0(腳本文件的名字)、*(所有位置參數構成的字符串,用IFS(Internal Field Separator)的首字符作分隔符)、@("$1" "$2" ... "$N")、#(位置參數的個數)??
不帶參數的cat就像while true { echo input }
有管道還不夠嗎
cat
是用來解釋重定向的絕佳例子: cat < file1 > file2
等價于cp file1 file2
。command < filename
,表示command
從某個文件而不是標準輸入拿到數據; command > filename
,表示command
將數據輸出到某個文件而不是標準輸出。
我覺得這個設計很好,但記法太屎。我寧愿多敲幾個字,把從哪輸入,輸出到哪明確清楚了(比如: file1 > cat > file2
)而不是把<
和>
作為前綴貼到操作對象前面。
除了重定向輸出到某個文件,你還可以把輸出作為其他命令的輸入直接導入(管道pipe,用|
表示。這個記法也很屎,體現不出方向來,雖然知道是從左到右。為什么不能和重定向統一起來呢?!比如,$ cut -d: -f1 < /etc/passwd | sort | lp
寫成/etc/passwd > cut -d: -f1 > sort > lp
的形式。可能是于當初把命令設計成前綴形式有關吧,如果當初設計成中綴形式并把命令也看成文件(命令本來就是內存里的一個文件),也許會方便很多)。
文本編輯原來這么陽春白雪
bash的emacs-mode對文本編輯提供很精細的控制:對字符的操作(^B, ^F, ^D, ?)、對單詞的操作(?B, ?F, ?D, ??)、對整句的操作(^A, ^E, ^K, Y(找回前一次被刪掉的東西)),甚至是對歷史記錄的操作(P(相當于↑), ^N(相當于↓), ^R)。
但emacs-mode最強大的功能還得是文本補全(?)。當你敲入幾個字符并按下?,可能會出現四種情況:
- 沒有以這些字符開頭的詞,shell會小叫一聲beep;
- 找到一個唯一的匹配,shell會補全它,并附帶一個空格方便你敲其他字符;
- 如果找到一個唯一匹配的目錄,shell會補全文件名,并附上一個斜杠(/);
- 如果有多個匹配可能,shell會補上這些可能匹配的最長前綴。
如果你想知道都有哪些可能的選擇,敲兩下?(或者按?-?,它會顯示所有可能選擇)。
shell提供了很多快捷鍵來補全不同類型的名字(比如,?-/ 專門補文件名,?-~專補用戶名,?-!專補命令名??)
既然提到了emacs-mode,不用想也能知道肯定有個vi-mode。
vi最著名的就是用h, j, k, l作為←↓↑→。這得追溯到當初Bill Joy用ADM-3a開發vi,這種機器的h, j, k, l鍵上分別有四個←↓↑→小箭頭,所以后面你知道的。
vi-mode和vi一樣,也有兩種模式(輸入模式和控制模式)。在輸入模式下,你可以鍵入、刪除(?, ^W)。按下 ? 可以進入控制模式。這個模式下,可以用單鍵來控制移動(h, l, b, w, 0, $)(很喜歡這種單鍵控制的方式,其實很多軟件都可以借鑒。如果你不用編輯文字,干嘛要用control、shift這類的輔助鍵。這就好像有名字空間你不用,非得都搞成全家變量一樣)。你不光可以移動一個字符(h, l)、一個單詞(b, w),甚至還可以移動到某個特定的字符上(F-×, f-×,×表示任意字符)按下i(或a, I(等效于0i), A (等效于$a), R (與r略有不同,r只替換一個字符而且不會進入輸入模式,即你不能再鍵入其他字符))切回輸入模式。控制模式下還有個不得不提的命令——撤銷(u, undo)。另外“.”也很有用,它反復執行上一次的操作。
vi也有文本補全,不過不是 ?,而是進入控制模式按反斜杠(/)。
一直覺得vi很有意思,設計很精巧,像一門微型語言。比如刪除操作,按下d
(delete)之后不會有什么變化,還要指定刪除的方向(向前還是向后)和長度(字符還是單詞還是整句,是當前光標位置到行首還是到行尾)。這不是v. + adv. + n. 結構么!而且一個動作可以有多個說法(比如刪除整句,可以用dd
,也可以用0d$
)。讓你感覺它不是一堆互不相干的快捷鍵,而是一句意義明確的指示。
命令行工具一個總的印象就是太費腦子,這里指的是要記的東西太多,不如GUI直觀。但好處就在于它能控制得很精細,不像GUI,界面設計成什么樣就只有哪些功能。
到底0是真是假
在流程控制中,shell用返回狀態作為邏輯判斷的結果。這樣,0(在[...]中)就表示真,其他返回碼均表示假。但在算術表達式($((...))
或$[...]
)中,0卻是假,1才是真。而算術式的另一種形式((...))
卻用0作為真,1為假
丑陋的語法
如果說if...then...fi是bash的特色的話,bash的case這是巨丑陋:
case expression in
pattern1 )
statements ;;
pattern2 )
statements ;;
...
esac
你就不能正常一點?!至少對稱一點,別弄出半邊括號來好不。
還有,按理說shell的語法是不依賴空格或是換行的。可你看看賦值語句,多了空格它不干,再看看[ ... ]少了空格它也不干。而在for, while, select里do如果新起一行就不用打分號。
shell語法看似統一,其實很混亂。這貨不是一門語言,而是一堆微型語言,雖然每個都很簡單,但要想全都記住卻有不小難度。
P.S. Learning the bash Shell似乎沒有繼承Learning系列簡單明了的衣缽,看似很全,其實毫無系統。示例晦澀,講解不清,比如對getopts里OPTSTRING(Mac下好像是這個,書中是OPTARG)的說明,OPTSTRING是一個由option及其所帶參數構成的一個序列,而OPTIND是OPTSTRING里下一個要處理的option的下標(不敢舉例,怕自己理解有誤)。
我想要的shell
首先互聯功能必須很強大;
靈活的語法(不依賴空格神馬的,不限定格式);
統一的語法;
??
(cont.)