網上關於 zsh 的文章有不少,但其中超過 95% 的文章講如何使用和配置,寫如何用 zsh 編程的文章不多,能找到的多數也是隻言片語,不成系統。國外有幾本講 zsh 的書,其中也有不少內容是配置、使用、編寫補全腳本等等,對編程有用的篇幅佔比並很少,並且比較零散不便於查詢。至於官方文檔?那是讓即便有多年編程經驗的開發者也會抓狂的神奇存在。可讀性極差,並且基本沒有例子,不熟悉文檔結構和內容的話,很難找到本身想要的東西。但內容覆蓋很全面,洋洋灑灑近 500 頁,耐心去看總會找到的。還有一份官方「入門」文檔,上次更新時間是 2002 年,也要 300 多頁,至於可讀性,比官網文檔要稍微好一些吧,仍是有必定的參考價值的。官網上還有一些連接,裏邊內容比較零散,也能夠看看。git
不少人在 zsh 中用 bash 語法寫腳本,雖然也能夠正常運行,但這樣沒法利用 zsh 的衆多優秀特性,仍是很是遺憾的。熟悉下 zsh 下獨有的特性,對寫腳本的幫助是很大的。github
本系列文章無關 zsh 的安裝、使用、配置(若是須要配置文件,能夠參考個人 .zshrc,裏邊有比較詳細的註釋),更無 oh-my-zsh 相關內容,安裝 zsh 後無需配置便可開始學習編寫腳本。讀者不須要有 bash 的基礎(最好了解一些),但須要接觸過任何一門編程語言,對編程的一些基礎概念要有了解。shell
不少人對 zsh 的瞭解停留在界面漂亮、主題多、插件多、補全強等等,而對 zsh 的語言特性瞭解並很少。由於 zsh 基本兼容 bash,很多人使用 bash 語法寫 zsh 腳本,或者偶爾使用一些 zsh 特有的小技巧,很難體會出 zsh 做爲一門編程語言的強大之處。編程
另外有些人認爲 bash 幾乎在全部類 Unix 系統都有默認安裝,而 zsh 每每要本身安裝,爲了通用性而用 bash 寫腳本比較好。這個說法也有必定的道理,但並非對全部開發者來講都有影響。若是是開源軟件的開發者,爲了不潔癖用戶由於不想安裝他用不到的 zsh 而不使用本身的軟件,而避免使用 zsh,是有必定道理的(但如今 zsh 的用戶量也有必定的積累了)。除此以外,本身平時寫腳本、公司內部使用等多數場景,都是不須要考慮這個因素的。數組
若是在公司使用,還涉及其餘因素。bash
第一個是 zsh 的部署成本。但由於多數狀況都須要部署其餘軟件,甚至本身的腳本能夠和 zsh 打包部署(去掉用不到的文件後的 zsh 只有 1M 多),因此基本不成問題。並且若是使用系統默認的 bash 的話,還涉及版本不一樣致使的問題,好比不一樣系統的 bash 版本不同,或者系統升級後,bash 的升級致使以前的腳本掛掉等等。因此即便使用 bash,最好也是統一部署或者自帶一個特定的版本,而不是使用系統默認的,以減小沒必要要的麻煩。微信
第二個就是很是重要的學習成本。由於會寫 bash 的人不少,但會寫 zsh 的比較少,若是隻有本身會寫,那麼和別人合做會出問題。但 zsh 的學習成本並無那麼大,尤爲是對會 bash 開發者來講,要大體看懂 zsh 腳本基本只須要幾十分鐘的學習,而編寫的話,按部就班也是很天然的事情,並且想不起來的時候還能夠用 bash 的語法寫。因此學習成本沒有那麼可觀。數據結構
第三個是使用 zsh 開發的好處。若是 zsh 和 bash 相比,沒有明顯的好處,爲何要學習和使用它呢?那麼就要從 bash 痛點講起了。我想常常寫 bash 腳本的人,不多有人會舉大拇指說 bash 真好用啊。相反,我曾經屢次聽某些開發者說我寫過一個超過 2000(或者其餘行數)行的 shell(bash)腳本。但幾乎沒有人會認爲寫一個超過 2000 行的 Python 腳本是一件多麼特別的事情。蹩腳的語法(幾乎全部從任何其餘語言遷移過來的開發者,都要從新熟悉和習慣它的語法)、嚴重依賴外部命令(由於文件系統錯誤等問題,掛掉一個外部命令,腳本就休克了。命令版本不一樣會有用法上的微秒差異,調試測試困難。頻繁起新進程性能低下)、功能孱弱蹩腳(不少須要頻繁使用的功能不全面或者很差用,好比字符串處理和數組的用法)等等,讓不少開發者很是頭疼,其中有些人甚至主張禁止使用 shell 腳本,一概改用 Python 等等,但 Python 並不是適用全部場景,並且也有另外的一些問題,這樣作也是因噎廢食。Zsh 並不是將這些問題所有解決了,但和 bash 相比,有很大的改善。好比 zsh 支持多種風格的語法,開發者很容易找到親切感;對外部命令的依賴比 bash 要輕不少,多數經常使用的功能不須要使用外部命令,性能更好,調試也更加方便;功能上和 bash 相比也有比較大的提高,處理不那麼複雜的場景已經比較夠用了。編程語言
有人可能會說,不如「一步到位」,使用 Powershell。Powershell 的確比 Python 更適合做爲一種 shell 腳本語言,但使用它的話會有其餘問題。ide
首先 Powershell 的學習成本是絕對要比 zsh 高的,若是想省點事,這並非好的選擇。
其次 Linux 下的 Powershell 目前仍是 beta 版,之後會不會有不少人用也很難說,若是不多有人用,那麼生態環境就成問題。好比遇到問題後找不到解決辦法,配套的軟件和庫不完善等等。
再次 Powershell 解釋器的啓動速度很是感人,在個人機器上,Windows 下的 Powershell 空腳本要執行將近 200 毫秒,Linux 下的要更長一些(我只在 WSL 裏安裝試用過,時間翻了幾倍),而 zsh 的話,在 Linux 下不超過 5 毫秒,在 WSL 下也不超過 20 毫秒。若是寫一個簡單的腳本,運行時都要卡一下,是很是影響體驗的。
最後若是平時就使用 Powershell 做爲交互 shell,那麼雖然腳本的啓動時間問題有所緩解,但用戶體驗會差不少,並且之後也很難提高上來,很容易得不償失。
能夠經過一個例子直觀感覺下用 zsh 寫的腳本。這是一個刪除當前目錄以及全部子目錄下重複文件的腳本,經過 md5 判斷文件是否相同(不嚴謹)。熟悉 bash 的讀者能夠嘗試用 bash 完成相同的功能,而後對比一下代碼(我以前寫過一個 bash 版本的,不貼上來了),就能比較直觀地感覺到 bash 和 zsh 的區別了。
#!/bin/zsh
local files=("${(f)$(md5sum **/*(.D))}")
local files_to_delete=()
local -A md5s
for i ($files) {
local md5=$i[1,32]
if (($+md5s[$md5])) {
files_to_delete+=($i[35,-1])
} else {
md5s[$md5]=1
}
}
(($#files_to_delete)) && rm -v $files_to_delete複製代碼
對於沒有接觸過 shell 腳本的開發者或者用戶來講,有一個更重要的問題,我爲何要學習和使用 shell 腳本呢?
那麼要從 shell 腳本的使用場景提及。Shell 是一種和計算機系統交互的文本界面(CLI),簡單說就是輸入命令後返回結果(也有比較複雜的操做)。CLI 在某些場景要比圖形界面(GUI)方便和高效不少,是不可取代的(即便有一天語音識別取代了文本輸入,CLI 也會換湯不換藥地繼續存在)。那麼使用 CLI 就必須約定好指令格式,而 shell 腳本就是一種用於 CLI 交互的指令格式。
由於這個比較特別的場景,shell 腳本有一些與其餘編程語言不一樣的特色。一個很重要的特色,shell 腳本要比較簡潔,容易輸入。若是發送一條簡單指令就要打幾十個字符,那恐怕誰也沒法接受。而爲了達到能夠接受的簡潔程度,shell 腳本的語法,每每比其餘編程語言的更加怪異。
有人可能會說,這搞混了兩個事情。在 CLI 輸入命令和寫腳本文件而後執行命令是兩回事,不須要使用同一種語言,而只是在 CLI 交互中,一般是沒有必要寫複雜邏輯的,也就是說 shell 腳本基本沒有必要學習。
是兩回事不假,但兩者並非不相關的。好比有人這麼想後,決定在 shell 裏只使用最簡單的命令,不學習較爲複雜的語法,若是須要寫腳本,就用 Python 之類的語言寫。那麼有什麼問題嗎?
Python 是爲通用的場景設計的,雖然也能處理 shell 腳本所作的事情,但每每要寫出多幾倍甚至幾十倍(若是對 Python 也不甚瞭解的話)的代碼出來。而不少時候,shell 腳本作的是一次性工做,運行完就直接刪除,或者直接在一行敲完,回車便可,這樣的場景用 Python 寫成本要高出不少。並且並非一個 Python 初學者就能用 Python 實現 shell 腳本的功能的,甚至熟練的 Python 開發者也極可能一時想很差怎麼實現某個用 shell 腳本能很容易實現的功能。Shell 腳本的不少工做是和字符串和目錄文件打交道,特色是要實現的功能複雜多樣,沒有固定模式,不管用什麼語言寫,都不容易。Python 自帶的字符串和目錄文件等類庫功能很是基礎,基本只能實現功能很單一的操做,稍微複雜點的功能都須要本身寫。若是去找某些功能複雜的第三方庫,那就會涉及一堆問題,好比一樣有學習和部署成本,可能由於用戶少因此有 bug 未被發現,可能已經沒有人維護了,Python 的語法決定庫怎麼寫都不能讓語法太簡潔等等。
而初步熟悉一門 shell 腳本只須要幾十分鐘,用多了天然就熟悉了,成本收益的權衡不言而喻。
文中行首的 %
表明 zsh 的命令提示符(相似 bash 的 $
,這個是能夠自由定義的,具體是什麼不重要),行首的 >
表明此行是換行後的輸入內容,以 #
開頭的爲註釋(非 root 用戶的命令提示符,本系列文章不須要 root 用戶),其他的是命令的輸出內容。另外某些地方會貼成段的 zsh 代碼,那樣就省略開頭的 %
,比較容易分辨。
一個樣例:
# 前兩行是輸入內容,第三行是輸出內容
% echo "Hello \ > World"
Hello World複製代碼
本系列文章使用的 zsh 版本是 5.4.1(寫這篇文章時的最新版本),代碼在老版本中可能運行不了或者結果有出入,儘可能使用最新版本。
下面直接進入正題。
接觸一門新的編程語言,運行完 Hello World 後,首先要了解的基本就是如何定義和使用變量了。有了變量後能夠比較變量內容,進而能夠接觸條件、循環、分支等語句,繼而瞭解函數的用法,更高級的數據結構的使用,更多庫函數,等等。這樣就大概瞭解了一門面向過程的語言的基本用法,剩下的能夠等到用的時候再查手冊。
因此這一篇講最基本的變量和語句。
zsh 有 5 種變量:整數、浮點數(bash 不支持)、字符串、數組、哈希表(或者叫關聯數組或者字典,本系列文章統一使用「哈希表」這一名詞),另外還有一些其餘語言少有的東西,好比 alias(但主要是交互時使用,編程時基本用不到)。此篇只涉及整數、浮點數、字符串,而且不涉及數值計算和字符串處理等內容。
Zsh 的變量多數狀況不須要提早聲明或者指定類型,能夠直接賦值和使用(但哈希表是一個例外)。
# 等號兩端不能有空格
% num1=123
% num2=123.456
% str1=abcde
# 若是字符串中包含空格等特殊字符,須要加引號
% str2='abc def'
# 也能夠用雙引號,但和單引號有區別,好比雙引號裏可使用變量,而單引號不能夠
% str3="abc def $num1"
# 在字符串中可使用轉義字符,單雙引號都可
% str4="abc\tdef\ng"
# 輸出變量,也可使用 print
% echo $str1
abcde
# 簡單的數值計算
% num3=$(($num1 + $num2))
# (( 中的變量名能夠不用 $
% num3=$((num1 + num2))
# 簡單的字符串操做
% str=abcdef
# 2 和 4 都是字符在數組的位置,從 1 開始數,逗號兩邊不能有空格
% echo $str[2,4]
bcd
# -1 是最後一個字符
% echo $str[4,-1]
def複製代碼
# 比較數值
% num=123
# (( )) 用於數值比較等操做,若是爲真返回 0,不然返回 1
# && 後邊的語句在前邊的語句爲真時才執行
# 注意這裏只能使用雙等號來比較
% ((num == 123)) && echo good
good
# (( 裏邊可使用與(&&)或(||)非(!)操做符,同 c 系列語言
% ((num == 1 || num == 2)) && echo good
# 比較字符串
% str=abc
# 比較字符串要用 [[,內側要有空格,[[ 的具體用法以後會講到
# 這裏雙等號能夠替換成單等號,能夠根據本身的習慣選用
# 本系列文章統一使用雙等號,由於和 (( )) 一致,而且使用雙等號的經常使用編程語言更多些
# $str 兩側不須要加雙引號,即便 str 未定義或者 $str 中含空格和特殊符號
% [[ $str == abc ]] && echo good
good
# 能夠和空字符串 "" 比較,未定義的字符串和空字符串比較結果爲真
# [[ 裏也能夠用 && || !
% [[ $str == "" || $str == 123 ]] && echo good複製代碼
稍微瞭解下簡單變量的使用後,快速進入語句部分。
zsh 支持多種風格的語法,包括經典的 posix shell (bash 的語法和它相似,但有一些擴展,能夠歸爲一類)的,以及 csh 風格的等等。但 posix shell 的語法並很差用,咱們不必必定使用這個。我只選用一種我認爲最方便簡潔的語法,沒有 fi
、then
、do
、done
、esac
、in
等的關鍵字(雖然其中某些關鍵字其餘編程語言也有,但基本用法都各異,並且容易混淆),也不須要多餘的分號。若是不肯定語法是否符合預期,能夠定義一個函數而後使用 which
查看,內容會被轉化成原始(posix shell 風格)的樣子。熟悉 bash 而且喜歡使用 bash 語法的讀者能夠跳過這部份內容,語法的不一樣並不影響後續內容的閱讀,繼續使用 bash 風格語法寫 zsh 也是沒有問題的。
# 格式
if [[ ]] {
} elif {
} else {
}複製代碼
大括號也能夠另起一行,本系列文章統一使用這種風格,縮進爲 4 個空格。注意 elif
不可寫做 else if
。
[[ ]]
用於比較字符串、判斷文件等,功能比較複雜多樣,這裏先使用最基礎的用法。注意儘可能不要用 [[ ]]
比較數值,由於不留神的話,數值會被轉化成字符串來比較,沒有任何錯誤提示,但結果可能不符合預期,致使沒必要要的麻煩。
# 樣例
if [[ "$str" == "name" || "$str" == "value" ]] {
echo "$str"
}複製代碼
(( ))
用於比較數值,裏邊能夠調用各類數值相關的函數,格式相似 c 語言,變量前的 $
可省略。
# 格式
if (( )) {
}複製代碼
# 樣例
if ((num > 3 && num + 3 < 10)) {
echo $num
}複製代碼
{ }
用於在當前 shell 運行命令而且判斷運行結果。
# 格式
if { } {
}複製代碼
# 樣例
if {grep sd1 /etc/fstab} {
echo good
}複製代碼
( )
用於在子 shell 運行命令而且判斷運行結果,用法和 {} 相似,再也不舉例。
# 格式
if ( ) {
}複製代碼
這幾種括號能夠一塊兒使用,這樣能夠同時判斷字符串、數值、文件、命令結果等等。最好不要混合使用 &&
||
,會致使可讀性變差和容易出錯。
# 格式
if [[ ]] && (( )) && { } {
}複製代碼
# 格式
while [[ ]] {
break/continue
}複製代碼
和 if
同樣,這裏的 [[ ]]
能夠替換成其餘幾種括號,功能也是同樣的,再也不依次舉例。break
用於結束循環,continue
用於直接進入下一次循環。全部的循環語句中均可以使用 break
和 continue
,下邊再也不贅述。
# 樣例 死循環
while (( 1 )) {
echo good
}複製代碼
until
和 while
相反,不知足條件時運行,一旦知足則中止,其餘的用法和 while
相同,再也不舉例。
# 格式
until [[ ]] {
}複製代碼
for
循環主要用於枚舉,這裏的括號是 for
的特有用法,不是在子 shell 執行。括號內是字符串(可放多個,空格隔開)、數組(可放多個)或者哈希表(可放多個,哈希表是枚舉值而不是鍵)。i
是用於枚舉內容的變量名,變量名隨意。
# 格式
for i ( ) {
}複製代碼
# 樣例
for i (aa bb cc) {
echo $i
}
# 枚舉當前目錄的 txt 文件
for i (*.txt) {
echo $i
}
# 枚舉數組
array=(aa bb cc)
for i ($array) {
echo $i
}複製代碼
經典的 c 風格 for
循環。
# 格式
for (( ; ; )) {
}複製代碼
# 樣例
for ((i=0; i < 10; i++)) {
echo $i
}複製代碼
這個樣例只是舉例,實際上多數狀況不須要使用這種 for
循環,能夠這樣。
# 樣例,{1..10} 能夠生成一個 1 到 10 的數組
for i ({1..10}) {
echo $i
}複製代碼
repeat
語句用於循環固定次數,n
是一個整數或者內容爲整數的變量。
# 格式
repeat n {
}複製代碼
# 樣例
repeat 5 {
echo good
}複製代碼
分支邏輯用 if
也能夠實現,但 case
更適合這種場景,而且功能更強大。
# 格式 + 樣例
case $i {
(a)
echo 1
;;
(b)
echo 2
# 繼續執行下一個
;&
(c)
echo 3
# 繼續向下匹配
;|
(c)
echo 33
;;
(d)
echo 4
;;
(*)
echo other
;;
}複製代碼
;;
表明結束 case
語句,;&
表明繼續執行緊接着的下一個匹配的語句(再也不進行匹配),;|
表明繼續往下匹配看是否有知足條件的分支。
select
語句是用於根據用戶的選擇決定分支的語句,語法和 for
語句差很少,若是不 break
,會循環讓用戶選擇。
# 格式
select i ( ) {
}複製代碼
# 樣例
select i (aa bb cc) {
echo $i
}複製代碼
輸出是這樣的。
1) aa 2) bb 3) cc
?#複製代碼
按上邊的數字加回車來選擇。
# 格式
{
語句 1
} always {
語句 2
}複製代碼
若是語句 1 執行出錯,則執行語句 2。
if
語句的簡化版,在只有一個分支的狀況下更簡潔,功能和 if
語句相似,不贅述。
格式:
[[ ]] || {
}
[[ ]] && {
}複製代碼
最好不要連續混合使用 &&
||
,好比。
aa && bb || cc && dd複製代碼
容易致使邏輯錯誤或者誤解,能夠用 { }
把語句包含起來。
aa && { bb || { cc && dd } }複製代碼
比較複雜的判斷仍是用 if
可讀寫更好,&&
||
一般只適用於簡單的場景。
本篇簡單介紹了變量和語句的使用方法。變量部分只涉及了最基礎經常使用的部分,後續文章會詳細介紹。語句部分已經覆蓋了全部須要使用的語句,實際上這些語句都不僅有這一種語法,但本系列文章統一使用這個語法。但涉及到的幾種括號的用法比較複雜,以後的文章也會詳細介紹。
全系列文章地址:github.com/goreliu/zsh…
付費解決 Windows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua 等領域相關問題,靈活訂價,歡迎諮詢,微信 ly50247。