終端下工作最煩躁的就是路徑切換,沒有自動路徑切換的幫助,就像在泥裏走路;用熟練了 z / autojump / fasd 之類的工具,就像終端裏溜冰,路勁切換從此指哪打哪。

拒絕用它們的原因,首先是沒搞懂原理,不清楚它會跳到哪,想自己控制,怕它跳到不希望的地方又要跳回來麻煩。還有一些人實際用了一下,因為不熟練,感覺還沒有 cd 那麼快,其實任何東西都有一個熟悉過程,你用自己熟練了若干年的 cd 命令來比較剛用了兩分鐘的 z 命令,就像才開始學自行車,左歪一下右歪一下,你覺得沒你走路快遂放棄學車,這不是很荒謬麼?

學吉他都要手疼幾個星期呢,想提升效率,沒點代價行麼?所以想用的好用的順暢,首先花十多分鐘研究一下他們的原理,再花十多分鐘實際練習下,不超過半小時,能讓你在終端下的工作效率提升一倍以上,這個投資值不值得呢?

autojump / z 到底選哪個 ?

當然選 z ,autojump 是最早的自動路徑切換工具,好像是一個臺灣小夥開發的,自動路徑切換這個概念最早就是它提出來的,z 作為它的繼承者,有幾方面優勢:

輕量級:autojump 有十多個源文件,四千行代碼;z 只有 z.sh 一個源文件,兩百行代碼。

性能好:z 的性能是 autojump 的兩倍以上。

無依賴:autojump 使用 py 開發,可能要另安裝,z 只需要所有系統都自帶的 bash/awk。

更精準:z 的匹配演算法使用類似 FireFox 的 frecent 演算法,充分考慮訪問時間和次數。

z 做的事情和 autojump 做的差不多,但更加高效精準,更加輕量級,只有一個文件,可以附帶在你的配置文件倉庫裏,和你的其他配置文件一起部署。

autojump 不但慢,而且十分笨重,儼然是一個小項目了,只有用包管理安裝,或者克隆下來 make install。光沖這部署方式,就足夠你拋棄 autojump 切換到 z 了。

兩百多行 shell 在 github 擁有 8925 顆星,比前輩 autojump 還高,足見其受歡迎程度。

怎麼安裝呢?

建議直接把 z.sh 放到你 github 的 config 倉庫裏,隨同部署,然後 bashrc 加一行:

source /path/to/z.sh

你就可以在 bash 中用 「z」命令使用它了。別忘了它的下載地址是:

github.com/rupa/z

我在本專欄前面的文章《提高效率從編寫 init.sh 開始》裏反覆強調,把配置用 github 管理起來,有恆產才會有恆心,沒有積累怎麼可能提升效率?

工作原理:路徑收集

Bash 裏有一個 $PROMPT_COMMAND 環境變數,在你每按一次回車,顯示命令提示符 "$" 的時候,就會去執行裡面的命令。z.sh 定義了一個叫做 _z 的 shell 函數,並把命令:

_z --add "$(pwd)"

添加到 $PROMPT_COMMAND 末尾,這樣每次顯示命令提示符的時候就會被調用。zsh 中也有類似的機制,z.sh 依靠該命令不停的收集你當前的路徑,在你每敲完一條 shell 命令之後,z.sh 都會獲取最新的當前路徑,保存到 $HOME/.z 數據文件中。

數據文件 $HOME/.z 的內容為:

/home/skywind/github/docker/apache2|48|1542554142
/home/skywind/docker/gogs/data|6|1542143205
/etc/apt/sources.list.d|12|1541778322
/home/skywind/github/docker/debian/debian-jessie|2|1542461714
/home/skywind/github/docker/mysql/8.0|169|1542536861
/home/skywind/docker/apache2|81|1542537066
/home/skywind/github/docker/debian/stretch-init/build/bin|4|1542517607
/home/skywind/docker/gogs|31|1542143626

每一行代表一條記錄,格式為:path | rank | time

路徑名用於跳轉時候的字元串匹配,訪問次數(rank)和時間戳用於多個結果裏的權重比較。所謂路徑收集,就是跟蹤你去到過的所有地方,並且更新上面的數據文件,加入新數據,或者增加老路徑的訪問次數。

隨著數據越來越多,z.sh 一旦發現所有路徑的 rank 加起來超過 9000 的話,就進行 aging 操作,將資料庫裏的 rank 統一乘以 0.99,然後刪除結果小於 1 的路徑。這樣就會剔除一些很不常去的路徑,保留你最經常去的地方,如此,數據文件能夠控制在合理的大小。

每次更新路徑時,z.sh 還會剔除一些被刪除的,不存在的無效路徑,該操作很快 10000 次檢測的時間不會超過 10 豪秒。z.sh 依靠上述規則,完成資料庫文件的維護。

匹配演算法:Frecent

跳轉命令可以提供一個或者多個關鍵字:

z key1 [key2 key3 ...]

當我們使用上述命令進行路徑切換時,z.sh 首先會篩選出資料庫里路徑名匹配上述關鍵字的記錄來,匹配採用順序匹配,規則是:第一個關鍵字從路徑名字元串的開頭匹配,後面的關鍵字從上一個關鍵字被匹配到的位置之後開始匹配。也就是說假設資料庫裏有:

/home/user/github/src
/home/user/src/github

兩條記錄,你光使用 "github" 關鍵字會同時匹配到兩條數據,而使用 "github src" 來匹配的話,只會匹配到第一條,因為先要匹配到 github,再從匹配位置往後匹配 src,所以第二條路徑就不滿足規則。關鍵字可以使用正則,比如使用單個關鍵字 "src$" 也可以只匹配第一條。

所以你資料庫裏類似的路徑名很多的話,你可以多提供一兩個關鍵字,就能增加匹配效率。完成路徑名匹配後,z.sh 會篩選出一批候選路徑,然後根據所有路徑的 rank 和 time 計算一個分數 frecent:

function frecent(rank, time) {
# relate frequency and time
dx = t - time
if( dx < 3600 ) return rank * 4
if( dx < 86400 ) return rank * 2
if( dx < 604800 ) return rank / 2
return rank / 4
}

z.sh 通過管道將上面函數傳遞給 awk 進行計算,使用當前時間和時間戳的差值計算:

  • 一小時內訪問過:frecent = rank * 4
  • 一天內訪問過:frecent = rank * 2
  • 一週內訪問過:frecent = rank / 2
  • 超過一週:frecent = rank / 4

這個 frecent 的計算方法是 FireFox 匹配歷史 URL 用的,z.sh 將他移植了過來,同時兼顧了歷史路徑的訪問次數和最近訪問情況,認為訪問次數越高的路徑越重要,同時越是最近訪問過的路徑越重要,這比當純使用一個權重的 autojump 更加科學和精準。

計算完後 z.sh 選擇 frecent 最高的備選路徑,作為跳轉的目的地址。z.sh 兩百多行的代碼基本上就是在做路徑收集和匹配這兩件事情。

實際使用

光敲一個 "z" 命令,後面沒有參數,查看歷史進入過的路徑:

便於演示,我裁剪了一下我的 .z 文件,左邊是計算過的 frecent 分數,右邊是路徑名。隨便使用 cd 命令跳轉一下,再運行 z ,看看列表裡新路徑是否被加入?老路徑權重是否有變化?接著用 z 後面加一個關鍵詞就能跳到所有匹配的歷史路徑中權重最高的那個:

比如所有歷史路徑都包含 o ,那麼 z o 就會跳轉到權重最高的 ~/software 目錄中。使用:「z -l foo" 可以列出包含 foo 的所有歷史路徑:

比如你不確定某個關鍵字會匹配到哪個路徑,可以用 z -l 命令將滿足名字匹配的路徑通通列出來。比如上面列出了所有包含 c 字母的路徑,這樣看一眼避免挑錯。有時候不少路徑名十分相似,比如:

project1/src
project2/src

那麼你 z src 的時候可能並不能如你願跳轉到你想要去的路徑,那怎麼辦呢?

增加一個關鍵字即可,比如 z 1 src ,空格分隔多個關鍵字,z會先匹配出第一個來,比如1 ,然後再匹配第二個 src ,馬上鎖定 project1/src 了。這樣雖然歷史中多個路徑都帶有 src 關鍵字,但是你加入了其他關鍵字就可以精確的鎖定你要去的地方。

另一種做法是實際 cd project1/src 過去,增加它的權重,權重超過 project2/src 那麼下次 z src 的時候就會跳轉過去,你可以實時用 z -l src 查看包含 src 的所有路徑和權重。

經驗技巧

實際使用起來,一般是 z + 最後一級目錄名,比如:

$ z vim # -> /home/skywind/software/vim
$ z tmp # -> /home/skywind/tmp
$ z local # -> /home/skywind/.local

99%的時候這樣做就足夠了,當沒有按照你要求跳轉的時候,你可以再補充一下再上一級目錄的一些信息,比如 z vim/src 或者 z v src 就夠了,弄不明白會跳轉到哪裡,可以隨時用:

$ z -l key1 [key2 ... ]

查看權重。不過常使用你根本必擔心這個問題,基本上常去的地方,z 都是指哪打哪。有時候如果處在一個頂層目錄,確定要跳轉到一個子目錄,可以用 z -c 命令限制匹配當前目錄的子目錄,我們可以 alias 一下:

alias zz=z -c

這樣 zz abc 就能跳轉到子目錄中包含 abc 的路徑了。當然你也可以把 z -l 給 alias 成 zl,用起來也很方便。剛開始使用 z.sh,可以刻意將一些 cd 命令用 z 代替,跳錯了就用 z -l 觀察下原因,覺得含糊了就去讀一遍項目 README,等你摸透了習慣了,你會發現 z 越來越順手。

後 記

我現在基本不用 autojump / fasd 之流,只用 z 一個命令進行自動路徑切換,z.sh 比他們兩個性能好多了。在性能好的機器上或許不明顯,找臺性能差的或者 cygwin/msys 這種 I/O 很慢的系統下面,autojump / fasd 幾乎不能用,可以卡到每按一次回車他們兩個都要卡一秒,但是 z.sh 沒這問題,在性能很差的電腦上你都基本感覺不到它的存在。

以前有個哥麼,每次見他瘋狂的敲 tab,我就知道他又卡在 cd 上了,每次補全都有好幾個備選,敲了幾個字母以為可以繼續了,再按一次 tab,繼續出來一堆候選讓他接著輸入,他一邊狂敲 tab 一邊嘴裡罵個不停,敲一次罵一句。

自從我給他介紹了 z.sh,幾天以後,他的脾氣好了不少,鄒著的眉頭也舒展了,用之前他猶如一頭不知道出路在哪的困獸;自從用上了 z.sh ,他成了一名來去無影的劍客,整個人的精神面貌得到了極大的改觀,生活不再單調,工作充滿歡笑!腰也不酸了,腿也不疼了,一口氣完成六個需求!

--

PS:z.sh 的幾個移植版本

我移植的 z.lua 支持 windows 和 posix shell (bash, zsh, ash, dash, busybox ...) :

  • skywind3000/z.lua

還有一個 powershell 的 port:

  • JannesMeyer/z.ps

恩,咱們對 Windows cmd/powershell 也同樣不拋棄,不放棄!


推薦閱讀:
查看原文 >>
相關文章