常常有人問我說用 command line 下指令的方式到底比用滑鼠操作 GUI(Graphical User Interface) 圖形介面 好在哪裡,那通常我會說用下指令的方式效率高很多,其中一個原因就是 alias
我可以把我常用的指令進行縮寫,譬如說在我的電腦上 gst
就是 git status
的意思,ga
則是 git add
,用滑鼠開資料夾、按按鈕的時間已經夠我下很多個指令,效率自然高得多
因此我認為 alias 是 Shell 中必不可少的功能,接下來幾天就要實作他
在 Linux 中要建立一個 alias 可以使用 alias name='command -args'
,譬如說 alias gst='git status'
,這樣下 gs
就會得到 git status
的效果
如果要取消 alias 則是用 unalias name
,這點跟 unset 環境變數很像,當然取消了之後就沒辦法再使用
在真的開始實作之前必須先克服一個問題,大家還記得在 Day06-執行指令(二) 時我們寫了這些扣,當時為了把指令中的參數分開,以空白分割字串把 ps aux
切成 [ps, aux]
// 把使用者的輸入切割成 Array
// "ps aux" -> ["ps", "aux"]
args := strings.Split(input, " ")
但現在問題來了,因為 alias 的指令中也含有空白 ,譬如說 alias gst='git status'
,這樣引號中的指令就會被切開,變成 [alias, gst='git, status']
但 git status
不應該被切開,我們想要的是切成 [alias, gst='git status']
,所以今天要來改造一下切割指令的部分
針對這個問題我想到一個很不通用的爛方法(時間不多允許我偷懶一下XD),就是針對 alias
指令做特殊的切割,其他指令的話還是用舊的切割方式
alias
開頭的指令若指令是 alias
開頭,那就只切割第一個 Space,譬如說 alias gst='git status'
就切成 [alias, 'git status']
其他指令還是照舊,把所有的 Space 都切開,譬如說 ls -l -a -h
就切成 [ls, -l, -a, -h]
strings.HasPrefix(s, prefix string)
用來判斷某字串 s
是不是以字串 prefix
為開頭,等等會用來判斷使用者輸入的指令是不是 alias
開頭
strings.SplitN
他跟之前介紹過的 strings.Split
很類似,Split
可以用來切開所有空白,而 SplitN
則是可以設定 最多切成幾段 ,超過他就不再切了,剛好適用於目前的情境
// paresArgs 就是上圖的 parseArgs
// 這邊要根據上面的演算法來實作他
func parseArgs(input string) []string {
// 如果 input 是 "alias" 開頭,那最多就切成兩段
// 也就是 ["alias", "ooo xxx ooo xxx"]
// 後面的空白不會被切到
if strings.HasPrefix(input, "alias") {
return strings.SplitN(input, " ", 2)
}
// 如果不是 "alias" 開頭
// 那就用原本的方法,把所有空白都切開
// "ls -l -a" -> ["ls", "-l", "-a"]
return strings.Split(input, " ")
}
在 executeInput
中把剛寫好的 parseArgs
拿來用
func executeInput(input string) error {
// ...
args := parseArgs(input) // <---- HERE
if args[0] == "cd" {
err := os.Chdir(args[1])
return err
}
// ...
}
因為今天只是實作 function 而不是完成了一個 feature,所以沒什麼好 Demo 的
為了檢驗剛剛寫的 parseArgs
有沒有正常運作,來寫個測試在 parseArgs_test.go
import "testing"
// 比較 a, b 兩個 Array 的內容是否一樣
func equal(a, b []string) bool {
// 實作很簡單,有興趣到 Github commit 上看
}
func TestAbs(t *testing.T) {
// 測試 "ls -l -a" 有沒有被正確切成 ["ls", "-l", "-a"]
input := "ls -l -a"
ans := []string{"ls", "-l", "-a"}
// 如果 parse 出來的結果跟答案不一樣就是失敗
if !equal(parseArgs(input), ans) {
t.Fail()
}
// 第二組 input 跟答案
// 測試有沒有被正確被切成 ["alias", "gst='git status'"]
input = "alias gst='git status'"
ans = []string{"alias", "gst='git status'"}
// 如果 parse 出來的結果跟答案不一樣就是失敗
if !equal(parseArgs(input), ans) {
t.Fail()
}
}
接著下 go test
指令,Go 就會自動執行所有 _test.go
結尾的檔案,包括剛剛寫的 parseArgs_test.go
如果錯誤的話就會跑出 Fail
今天除了寫扣外還順便講了測試的部分,這種針對單一 function 的測試叫做 單元測試(Unit Test),如果是在一個團隊裡面開發的話,通常都會搭配簡單的 Unit Test 以防 腦殘 同事不小心把程式改壞XD,在部署前先用自動化測試檢查一遍大家也比較安心
今天寫的扣比較多一點,完整的實作放在這裡,有問題的話歡迎在下面提出來,沒問題的話就明天見囉