這篇文章主要為大家展示了“Emacs中Shell環(huán)境如何擴展和定制”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Emacs中Shell環(huán)境如何擴展和定制”這篇文章吧。
憑借整站使用H5網(wǎng)站設(shè)計的創(chuàng)新體驗、定制設(shè)計、設(shè)計團隊積累與透明式的服務(wù)過程,符合行業(yè)特點,專屬顧問根據(jù)企業(yè)產(chǎn)品,消費群體屬性,準(zhǔn)確定位;設(shè)計師以目標(biāo)客戶為中心,以突出品牌官網(wǎng)特性為宗旨,定制專屬網(wǎng)站建設(shè)設(shè)計方案。
進入和退出 Shell Mode
輕輕的我走了,正如我輕輕的來;我輕輕的招手,作別西天的云彩。
但是在 Emacs Shell Mode 的缺省設(shè)計里面,沒有能夠讓我們?nèi)绱溯p松和優(yōu)雅的進入與退出。這就是在這一節(jié)當(dāng)中我們要進行定制和擴展的地方。
Shell buffer 的進入
首先是進入。在本文的***部分有一個小技巧,介紹了在 GNU Emacs 中如何打開多個 Shell buffer —— 我們需要將現(xiàn)有的 Shell buffer 重命名,然后才能再次打開一個叫做 *shell*的 Shell buffer。這是 Emacs 創(chuàng)建 Shell buffer 時使用的默認(rèn)名稱。
這是一個很不優(yōu)雅的行為。這樣的細節(jié)工作應(yīng)該由 Emacs 事先料理好,我所需要的只是優(yōu)雅的進入。實現(xiàn)這個目的有兩種做法,一種是在創(chuàng)建 Shell buffer 的時候就把它修改成一個獨特的名字;另外一種做法是在創(chuàng)建出 Shell buffer 之后,根據(jù)用戶的使用情況來自動修改 Shell buffer 的名稱。由于工作特點的關(guān)系,我選擇的是第二種方案。
在我的工作環(huán)境當(dāng)中,絕大多數(shù)時間都要登錄到遠程的機器上去工作。所以我非常希望 Shell buffer 的名稱能夠被自動修改成我所登錄的目標(biāo)機器的名稱,這樣在我登錄大量的機器進行操作的時候,就可以方便的通過 buffer 名稱來進行分辨。這就是我選擇第二套方案的原因。我首先接受 Emacs 創(chuàng)建出來的默認(rèn) buffer,然后在我登錄遠程機器的時候 Emacs 會自動為我改名。如果我沒有登錄遠程機器,那么它將保持默認(rèn)的名稱,或者由我主動的修改 buffer 名稱。
接受默認(rèn)的 buffer 名還有一個附加的好處——當(dāng)你打開大量的 buffer 進行工作的時候,如果要回到這個默認(rèn)的 Shell buffer,你不必在長長的 buffer 列表里面進行切換,只需要執(zhí)行一個打開 Shell 的命令,也就是 M-x shell,Emacs 就會立刻把你帶到這個默認(rèn)的 Shell buffer 中來。為了能夠更加方便的打開 Shell,我把這個命令綁定到了 C-c z組合鍵上:
(global-set-key (kbd "C-c z") (quote shell))
現(xiàn)在讓我們看一看 Emacs 是如何在我登錄遠程機器的時候自動修改 Shell buffer 的名稱的。實現(xiàn)這樣的功能首先需要編寫一個rename-buffer-in-ssh-login函數(shù):
清單 1. rename-buffer-in-ssh-login 函數(shù)
(defun rename-buffer-in-ssh-login (cmd) "Rename buffer to the destination hostname in ssh login" (if (string-match "ssh [-_a-z0-9A-Z]+@[-_a-z0-9A-Z.]+[ ]*[^-_a-z0-9-A-Z]*$" cmd) (let (( host (nth 2 (split-string cmd "[ @\n]" t) ))) (rename-buffer (concat "*" host)) ; (add-to-list 'shell-buffer-name-list (concat "*" host)); (message "%s" shell-buffer-name-list) ) ) )
這個函數(shù)會分析提供給它的命令。如果匹配預(yù)先定義的正則表達式,則截取 @字符后面的機器名,然后使用 rename-buffer命令修改當(dāng)前 buffer 的名稱。另外,由于在 GNU Emacs 的默認(rèn)約定里將 Shell buffer 看作是一種臨時 buffer,而臨時 buffer 的名稱通常會以一個 *字符開頭,在這里仍然遵循這樣的命名約定,在機器名稱的前面添加一個了 *前綴。
要讓這個函數(shù)工作,我們需要把它加入到一個 hook 變量 comint-input-filter-functions當(dāng)中。
(add-hook 'comint-input-filter-functions 'rename-buffer-in-ssh-login)
comint-input-filter-functions是一個 comint-mode 的 hook。Shell-mode 實際上是由 comint-mode 派生出來的,所以 comint-mode 的 hook 在 Shell-mode 里面也能夠工作。
comint-mode 或者 Shell-mode 在將輸入到 buffer 中的命令傳遞給后臺進程(在這里是 Shell 進程)去執(zhí)行之前,會首先運行comint-input-filter-functions hook 當(dāng)中的函數(shù),同時將輸入的命令作為參數(shù)傳遞給該中的函數(shù)。所以我們的 rename-buffer-in-ssh-login函數(shù)就可以跟蹤輸入到 buffer 當(dāng)中的每一條命令,當(dāng)發(fā)現(xiàn)有類似 ssh msg@hostA.cn.ibm.com 或者 ssh msg@hostB這樣的命令的時候,就會執(zhí)行預(yù)定的操作。同時正則表達式的設(shè)計還避免了在類似 ssh msg@hostA.cn.ibm.com ls /opt/IBM這樣不以登錄為目的的遠程命令上面出現(xiàn)誤動作的機會。
看到這里細心的讀者也許注意到了一個細節(jié),就是上面的代碼里面被注釋掉了兩行內(nèi)容。尤其是其中的***行將截取下來的機器名加入到了一個 shell-buffer-name-list的列表里面。實際上這段代碼的存在是為了跟蹤 Shell buffer 名稱的變化過程,然后配合另外一個函數(shù) rename-buffer-in-ssh-exit,在退出每一次 ssh 登錄的時候?qū)?Shell buffer 的名稱再改回來原來的樣子。但是由于實際應(yīng)用的復(fù)雜性,目前為止還沒有找到一個十分滿意的實現(xiàn)方案。有興趣的讀者可以嘗試自己實現(xiàn)這個函數(shù)。
Shell buffer 的退出
進入的問題解決了,下面讓我們來看一看退出的時候會有哪些問題。
當(dāng)用戶退出 Shell 會話之后,Emacs 并不會刪除這個 Shell buffer,而是把它留在那里,等待用戶的進一步的處理。
dove@bash-4.1$exit exit Process shell finished
如果用戶這個時候再次執(zhí)行 M-x shell命令,Emacs 會再次復(fù)用這個 buffer。
dove@bash-4.1$ dove@bash-4.1$exit exit Process shell finished dove@bash-4.1$
首先這其實是一個非常正確的設(shè)計。因為 Shell buffer 里面的內(nèi)容通常是非常重要的。甚至于有些時候我會在結(jié)束一天的工作之后把某一些 Shell buffer 保存成文件,以備日后查閱。這里面不僅僅有這一天以來執(zhí)行過的所以命令的記錄,還有所有這些命令的輸出信息,甚至當(dāng)我先后登錄了幾臺不同的機器進行了不同的操作,所有這些工作也都記錄在這個 Shell buffer 當(dāng)中,可以說這個 buffer 就是我這一天以來所有足跡的記錄。試想想,還有什么地方能夠提供這么完整、詳細的工作記錄?另外還有什么地方能夠提供如此方便的搜索功能?甚至連命令的輸出信息都可以隨意搜索?
但是,很快我就習(xí)慣了正確處理我的 Shell buffer。對于主要的 buffer 我已經(jīng)習(xí)慣在退出之前就把它保存好了,那么這個時候是不是可以告訴 Emacs 不用這么拘謹(jǐn)了呢?事實上這個事情還真不好辦。我曾經(jīng)試圖用 comint-output-filter-functionshook 去捕捉Process shell finished這樣的信息,但是這樣的信息是在 comint-mode 已經(jīng)退出以后才由 Emacs 輸出的,因此在這個 hook 里面完全捕捉不到。
直到有一天在翻看 Emacs 源代碼的時候突然看到了 set-process-sentinel這個函數(shù)才找到了解決方案。 set-process-sentinel函數(shù)可以對一個特定的進程設(shè)置一個“哨兵”,當(dāng)這個進程的狀態(tài)發(fā)生變化的時候(比如說進程結(jié)束的時候),“哨兵”就會通知 Emacs 調(diào)用相應(yīng)的函數(shù)來完成預(yù)定的工作。有了這個方案,我們只需要把刪除 Shell buffer 的函數(shù)關(guān)聯(lián)到正確的進程上就行了。
下面就是這兩個函數(shù):
清單 2. 兩個函數(shù)
(defun kill-shell-buffer(process event) "The one actually kill shell buffer when exit. " (kill-buffer (process-buffer process)) ) (defun kill-shell-buffer-after-exit() "kill shell buffer when exit." (set-process-sentinel (get-buffer-process (current-buffer)) #'kill-shell-buffer) )
其中 kill-shell-buffer的作用是刪除進程對應(yīng)的 buffer; kill-shell-buffer-after-exit函數(shù)的作用就是把 kill-shell-buffer函數(shù)關(guān)聯(lián)到正確的進程上去。然后當(dāng)我們把這個函數(shù)加入到 shell-mode-hook當(dāng)中后,就可以在每次打開 Shell buffer 的時候得到正確的進程信息了。
(add-hook 'shell-mode-hook 'kill-shell-buffer-after-exit t)
outline in Shell Mode
這一節(jié)我們談 outline-mode。Outline-mode 是 GNU Emacs 的一個非常好用的寫作模式。使用 outline-mode 可以輕松方便的操作結(jié)構(gòu)化文檔,可以將文檔內(nèi)容分級展開,或者逐級隱藏,既能總攬全局,又可深入細節(jié)。outline-mode 是如此精彩,以至于 Carsten Dominik 教授在此基礎(chǔ)上開發(fā)出了強大的 orgmode。
在這一節(jié)當(dāng)中我們將要討論一下如何將 outline-mode 的強大功能應(yīng)用到 Shell-mode 當(dāng)中。在進入細節(jié)之前,讓我們先對 Outline-mode 進行一個簡單的介紹。
Outline mode 當(dāng)中,文檔中的內(nèi)容被分成兩種結(jié)構(gòu),一種是“標(biāo)題”,一種是“內(nèi)容”。其中的“標(biāo)題”又可以根據(jù)需要分成大小不同的級別。在對文檔的內(nèi)容進行折疊和展開操作的時候就是以這些“標(biāo)題”的級別為依據(jù)的。例如下面這段摘自 GNU Emacs Manual 的示例:
* Food This is the body, which says something about the topic of food. ** Delicious Food This is the body of the second-level header. ** Distasteful Food This could have a body too, with several lines. *** Dormitory Food * Shelter Another first-level topic with its header line.
當(dāng)我們折疊起這段文檔的時候,分別可以折疊成這樣的形式
* Food... * Shelter...
或者這樣的形式
* Food... ** Delicious Food... ** Distasteful Food * Shelter...
或者我們又可以將 Delicious Food單獨展開
* Food... ** Delicious Food This is the body of the second-level header. ** Distasteful Food * Shelter...
那么這些示例和 Shell mode 又有什么關(guān)系呢? 如果我們把 Shell buffer 里的 * 命令 * 看作 outline-mode 的“標(biāo)題”,將命令產(chǎn)生的輸出看作是“內(nèi)容”,那么是不是就可以像折疊起一篇普通的結(jié)構(gòu)化文檔那樣將所有的 Shell 命令都折疊起來呢?就像下面這個示例所展示的這樣:
清單 3. 示例
dove@bash-4.1$ cd ~/org... 2 : 2001 : 11:23:10 : ~/org dove@bash-4.1$ ls *.el calendar-setup.el dove-ext.el org-mode.el settings.el color-theme.el keybindings.el plugins.el dove@bash-4.1$ ee work.org &... dove@bash-4.1$ Waiting for Emacs... dove@bash-4.1$ ls... dove@bash-4.1$ ee settings.el &... dove@bash-4.1$ Waiting for Emacs... dove@bash-4.1$ cd~/... dove@bash-4.1$ ls... dove@bash-4.1$ ...
當(dāng)我們把 Shell buffer 里面的內(nèi)容全部折疊起來,我們就看到了一條時間線。既能夠于一瞥之間總覽全部的歷史,又可以隨時深入任何一條命令的細節(jié)。相比與僅能告訴我們曾經(jīng)做過什么的 history命令來說,這樣的場景更像是一部“時間機器”。
那么該怎樣實現(xiàn)這樣的夢想呢?其中的關(guān)鍵就是要讓 outline-mode 能夠認(rèn)出我們的“標(biāo)題”。在 outline-mode 里面缺省的“標(biāo)題”是一個 *,這個 *從文本行的***個字符開始匹配,匹配上的,就是“標(biāo)題”,匹配不上的,就是“內(nèi)容”,匹配的次數(shù)越多,“標(biāo)題”的級別越低。我們可以通過設(shè)置 outline-regexp變量的值來定義我們自己的“標(biāo)題”。在 Shell mode 里面一個可行的辦法就是將 Shell 提示符的內(nèi)容定義為“標(biāo)題”。如同下面的示例這樣:
(setq outline-regexp ".*[bB]ash.*[#\$]")
設(shè)置標(biāo)題以后,在 Shell mode 里面輸入 M-x outline-minor-mode就可以享受 outline-mode 帶來的便利了。例如上文示例中所示的結(jié)果使用一下三個操作就可以實現(xiàn):
輸入 M-x hide-body或者 M-x hide-all命令折疊起 Shell buffer 里的所有命令
移動光標(biāo)到 ls *.el所在的行
使用 M-x show-entry或者 M-x show-subtree命令展開 ls *.el命令
Enhanced outline in Shell Mode
在上一節(jié)里面講述了通過設(shè)置 outline-regexp變量,使 outline-minor-mode可以在 shell-mode 中工作的方法,但是這樣簡單的設(shè)置很難避免會有一些負(fù)面的影響。因為 outline-regexp變量是一個全局變量,所以對 outline-regexp的值勢必改變其他模式中的outline-minor-mode的行為方式,而這肯定不是你所希望的。
所以我在工作當(dāng)中實際使用的是另外一種相對復(fù)雜一些的方法:使用一個函數(shù)為每一個 buffer 設(shè)置分別的 outline-regexp,并且把outline-regexp變量修改為特定 buffer 范圍內(nèi)的局部變量。下面就是這個函數(shù):
清單 4. 設(shè)置 buffer 的函數(shù)
(defun set-outline-minor-mode-regexp () "" (let ((find-regexp (lambda (lst mode) "" (let ((innerList (car lst))) (if innerList (if (string= (car innerList) mode) (car (cdr innerList)) (progn (pop lst) (funcall find-regexp lst mode)))))))) (outline-minor-mode 1) (make-local-variable 'outline-regexp) (setq outline-regexp (funcall find-regexp outline-minor-mode-list major-mode))))
這個函數(shù)首先定義了一個匿名函數(shù),存儲在 find-regexp變量中,這個函數(shù)通過遞歸的方式遍歷一個嵌套列表,直至找到與給定模式對應(yīng)的值;然后啟動 outline-minor-mode,修改 outline-regexp為局部變量,然后調(diào)用上述的匿名函數(shù)設(shè)置正確的 outline-regexp。
要讓這個函數(shù)能夠工作,我們就需要把他加入到各個主模式的 hook 之中,如同下面的示例所示:
清單 5. 示例
(add-hook 'shell-mode-hook 'set-outline-minor-mode-regexp t ) (add-hook 'sh-mode-hook 'set-outline-minor-mode-regexp t ) (add-hook 'emacs-lisp-mode-hook 'set-outline-minor-mode-regexp t ) (add-hook 'perl-mode-hook 'set-outline-minor-mode-regexp t )
但是細心的讀者應(yīng)該看到了,這個 set-outline-minor-mode-regexp函數(shù)并沒有接受任何參數(shù),這是因為這些主模式在調(diào)用 hook 函數(shù)的時候是不會向它們傳遞任何參數(shù)的。那么我們需要的的數(shù)據(jù)從哪里來呢?顯然這里需要一個全局變量 outline-minor-mode-list來存儲 set-outline-minor-mode-regexp函數(shù)所需的所有數(shù)據(jù)。
清單 6. 全局變量 outline-minor-mode-list
(setq outline-minor-mode-list (list '(emacs-lisp-mode "(defun") '(shell-mode ".*[bB]ash.*[#\$] ") '(sh-mode "function .*{") '(perl-mode "sub ") ))
有了這些擴展,Emacs 就可以在創(chuàng)建一個新的 buffer 的時候,為這個 buffer 設(shè)置正確的 outline-regexp值了。
延伸閱讀 hook
一些讀者可能注意到,在本文的敘述中多次提到了 hook 這一概念,那么 hook 究竟是什么東西?他在 Emacs 里面有起到什么作用呢?在這里我給大家做一個簡要的介紹。
簡單來講,hook 就是一個存儲函數(shù)列表的 Lisp 變量,該列表里的每一個函數(shù)被稱作這個 hook 的一個 hook 函數(shù)。GNU Emacs 的很多主模式(major modes)在完成初始化之后都會嘗試尋找并調(diào)用對應(yīng)該模式的 hook 變量里面的 hook 函數(shù)。因此 hook 就成為定制 Emacs 過程中一個非常重要的機制。我們可以通過添加 hook 函數(shù)的方式輕松的定制或擴展 Emacs 的行為。
最簡單的 hook 用法就是直接調(diào)用已有的 Emacs 函數(shù),例如啟動特定的子模式(minor modes):
(add-hook 'shell-mode-hook 'outline-minor-mode t)
更加復(fù)雜的用法就如上文所示,編寫自己的 hook 函數(shù)。
關(guān)于 hook 有幾個細節(jié)需要注意
絕大多數(shù)普通 hook 變量的名稱都是在主模式的名稱后面加上 -hook后綴來構(gòu)成的
但是,并不是所有 hook 變量都是這樣命名的
絕大多數(shù)普通 hook 函數(shù)被調(diào)用的時候是不會向它傳遞任何參數(shù)的,同時也不會理會函數(shù)的返回結(jié)果的
但是,并不是所有 hook 函數(shù)都是這樣調(diào)用的
已經(jīng)裝入的 hook 函數(shù)將無法通過再次執(zhí)行 add-hook來進行覆蓋或修改。實際的結(jié)果將會裝入該 hook 函數(shù)的多個版本。解決的辦法之一是清除 hook 變量,然后再次裝入:
(setq 'shell-mode-hook nil) (add-hook 'shell-mode-hook 'outline-minor-mode t)
以上是“Emacs中Shell環(huán)境如何擴展和定制”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!