自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

大話Emacs Shell Mode:當(dāng)Shell遇見Emacs

系統(tǒng) Linux
在《大話Emacs Shell Mode:讓工作更輕松的技巧》里面介紹了一些 Shell 環(huán)境下的日常操作如何在 GNU Emacs 的Shell-mode 模式下變得輕松愉快在接下來(lái)的這個(gè)部分里面,我將介紹一些針對(duì) Shell 環(huán)境的擴(kuò)展和定制。通過(guò)對(duì) Emacs 的擴(kuò)展和定制,將會(huì)使 Emacs 當(dāng)中的 Shell 操作變成一種更加舒適的享受。

 在《大話Emacs Shell Mode:讓工作更輕松的技巧》里面介紹了一些 Shell 環(huán)境下的日常操作如何在 GNU Emacs 的 Shell-mode 模式下變得輕松愉快。在接下來(lái)的這個(gè)部分里面,我將介紹一些針對(duì) Shell 環(huán)境的擴(kuò)展和定制。通過(guò)對(duì) Emacs 的擴(kuò)展和定制,將會(huì)使Emacs當(dāng)中的Shell 操作變成一種更加舒服的享受。

第四回 我愛我家 —— 裝修篇

進(jìn)入和退出 Shell Mode

輕輕的我走了,正如我輕輕的來(lái);我輕輕的招手,作別西天的云彩。

但是在 Emacs Shell Mode 的缺省設(shè)計(jì)里面,沒有能夠讓我們?nèi)绱溯p松和優(yōu)雅的進(jìn)入與退出。這就是在這一節(jié)當(dāng)中我們要進(jìn)行定制和擴(kuò)展的地方。

Shell buffer 的進(jìn)入

首先是進(jìn)入。在本文的***部分有一個(gè)小技巧,介紹了在 GNU Emacs 中如何打開多個(gè) Shell buffer —— 我們需要將現(xiàn)有的 Shell buffer 重命名,然后才能再次打開一個(gè)叫做 *shell*的 Shell buffer。這是 Emacs 創(chuàng)建 Shell buffer 時(shí)使用的默認(rèn)名稱。

這是一個(gè)很不優(yōu)雅的行為。這樣的細(xì)節(jié)工作應(yīng)該由 Emacs 事先料理好,我所需要的只是優(yōu)雅的進(jìn)入。實(shí)現(xiàn)這個(gè)目的有兩種做法,一種是在創(chuàng)建 Shell buffer 的時(shí)候就把它修改成一個(gè)獨(dú)特的名字;另外一種做法是在創(chuàng)建出 Shell buffer 之后,根據(jù)用戶的使用情況來(lái)自動(dòng)修改 Shell buffer 的名稱。由于工作特點(diǎn)的關(guān)系,我選擇的是第二種方案。

在我的工作環(huán)境當(dāng)中,絕大多數(shù)時(shí)間都要登錄到遠(yuǎn)程的機(jī)器上去工作。所以我非常希望 Shell buffer 的名稱能夠被自動(dòng)修改成我所登錄的目標(biāo)機(jī)器的名稱,這樣在我登錄大量的機(jī)器進(jìn)行操作的時(shí)候,就可以方便的通過(guò) buffer 名稱來(lái)進(jìn)行分辨。這就是我選擇第二套方案的原因。我首先接受 Emacs 創(chuàng)建出來(lái)的默認(rèn) buffer,然后在我登錄遠(yuǎn)程機(jī)器的時(shí)候 Emacs 會(huì)自動(dòng)為我改名。如果我沒有登錄遠(yuǎn)程機(jī)器,那么它將保持默認(rèn)的名稱,或者由我主動(dòng)的修改 buffer 名稱。

接受默認(rèn)的 buffer 名還有一個(gè)附加的好處——當(dāng)你打開大量的 buffer 進(jìn)行工作的時(shí)候,如果要回到這個(gè)默認(rèn)的 Shell buffer,你不必在長(zhǎng)長(zhǎng)的 buffer 列表里面進(jìn)行切換,只需要執(zhí)行一個(gè)打開 Shell 的命令,也就是 M-x shell,Emacs 就會(huì)立刻把你帶到這個(gè)默認(rèn)的 Shell buffer 中來(lái)。為了能夠更加方便的打開 Shell,我把這個(gè)命令綁定到了 C-c z組合鍵上:

(global-set-key (kbd "C-c z") (quote shell))

現(xiàn)在讓我們看一看 Emacs 是如何在我登錄遠(yuǎn)程機(jī)器的時(shí)候自動(dòng)修改 Shell buffer 的名稱的。實(shí)現(xiàn)這樣的功能首先需要編寫一個(gè)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)
)
)
)

這個(gè)函數(shù)會(huì)分析提供給它的命令。如果匹配預(yù)先定義的正則表達(dá)式,則截取 @字符后面的機(jī)器名,然后使用 rename-buffer命令修改當(dāng)前 buffer 的名稱。另外,由于在 GNU Emacs 的默認(rèn)約定里將 Shell buffer 看作是一種臨時(shí) buffer,而臨時(shí) buffer 的名稱通常會(huì)以一個(gè) *字符開頭,在這里仍然遵循這樣的命名約定,在機(jī)器名稱的前面添加一個(gè)了 *前綴。

要讓這個(gè)函數(shù)工作,我們需要把它加入到一個(gè) hook 變量 comint-input-filter-functions當(dāng)中。

(add-hook 'comint-input-filter-functions 'rename-buffer-in-ssh-login)

comint-input-filter-functions是一個(gè) comint-mode 的 hook。Shell-mode 實(shí)際上是由 comint-mode 派生出來(lái)的,所以 comint-mode 的 hook 在 Shell-mode 里面也能夠工作。

comint-mode 或者 Shell-mode 在將輸入到 buffer 中的命令傳遞給后臺(tái)進(jìn)程(在這里是 Shell 進(jìn)程)去執(zhí)行之前,會(huì)首先運(yùn)行comint-input-filter-functions hook 當(dāng)中的函數(shù),同時(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這樣的命令的時(shí)候,就會(huì)執(zhí)行預(yù)定的操作。同時(shí)正則表達(dá)式的設(shè)計(jì)還避免了在類似 ssh msg@hostA.cn.ibm.com ls /opt/IBM這樣不以登錄為目的的遠(yuǎn)程命令上面出現(xiàn)誤動(dòng)作的機(jī)會(huì)。

看到這里細(xì)心的讀者也許注意到了一個(gè)細(xì)節(jié),就是上面的代碼里面被注釋掉了兩行內(nèi)容。尤其是其中的***行將截取下來(lái)的機(jī)器名加入到了一個(gè) shell-buffer-name-list的列表里面。實(shí)際上這段代碼的存在是為了跟蹤 Shell buffer 名稱的變化過(guò)程,然后配合另外一個(gè)函數(shù) rename-buffer-in-ssh-exit,在退出每一次 ssh 登錄的時(shí)候?qū)?Shell buffer 的名稱再改回來(lái)原來(lái)的樣子。但是由于實(shí)際應(yīng)用的復(fù)雜性,目前為止還沒有找到一個(gè)十分滿意的實(shí)現(xiàn)方案。有興趣的讀者可以嘗試自己實(shí)現(xiàn)這個(gè)函數(shù)。

Shell buffer 的退出

進(jìn)入的問題解決了,下面讓我們來(lái)看一看退出的時(shí)候會(huì)有哪些問題。

當(dāng)用戶退出 Shell 會(huì)話之后,Emacs 并不會(huì)刪除這個(gè) Shell buffer,而是把它留在那里,等待用戶的進(jìn)一步的處理。

dove@bash-4.1$exit
exit
Process shell finished

如果用戶這個(gè)時(shí)候再次執(zhí)行 M-x shell命令,Emacs 會(huì)再次復(fù)用這個(gè) buffer。

dove@bash-4.1$
dove@bash-4.1$exit
exit
Process shell finished
dove@bash-4.1$

首先這其實(shí)是一個(gè)非常正確的設(shè)計(jì)。因?yàn)?Shell buffer 里面的內(nèi)容通常是非常重要的。甚至于有些時(shí)候我會(huì)在結(jié)束一天的工作之后把某一些 Shell buffer 保存成文件,以備日后查閱。這里面不僅僅有這一天以來(lái)執(zhí)行過(guò)的所以命令的記錄,還有所有這些命令的輸出信息,甚至當(dāng)我先后登錄了幾臺(tái)不同的機(jī)器進(jìn)行了不同的操作,所有這些工作也都記錄在這個(gè) Shell buffer 當(dāng)中,可以說(shuō)這個(gè) buffer 就是我這一天以來(lái)所有足跡的記錄。試想想,還有什么地方能夠提供這么完整、詳細(xì)的工作記錄?另外還有什么地方能夠提供如此方便的搜索功能?甚至連命令的輸出信息都可以隨意搜索?

但是,很快我就習(xí)慣了正確處理我的 Shell buffer。對(duì)于主要的 buffer 我已經(jīng)習(xí)慣在退出之前就把它保存好了,那么這個(gè)時(shí)候是不是可以告訴 Emacs 不用這么拘謹(jǐn)了呢?事實(shí)上這個(gè)事情還真不好辦。我曾經(jīng)試圖用 comint-output-filter-functionshook 去捕捉Process shell finished這樣的信息,但是這樣的信息是在 comint-mode 已經(jīng)退出以后才由 Emacs 輸出的,因此在這個(gè) hook 里面完全捕捉不到。

直到有一天在翻看 Emacs 源代碼的時(shí)候突然看到了 set-process-sentinel這個(gè)函數(shù)才找到了解決方案。 set-process-sentinel函數(shù)可以對(duì)一個(gè)特定的進(jìn)程設(shè)置一個(gè)“哨兵”,當(dāng)這個(gè)進(jìn)程的狀態(tài)發(fā)生變化的時(shí)候(比如說(shuō)進(jìn)程結(jié)束的時(shí)候),“哨兵”就會(huì)通知 Emacs 調(diào)用相應(yīng)的函數(shù)來(lái)完成預(yù)定的工作。有了這個(gè)方案,我們只需要把刪除 Shell buffer 的函數(shù)關(guān)聯(lián)到正確的進(jìn)程上就行了。

下面就是這兩個(gè)函數(shù):

清單 2. 兩個(gè)函數(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的作用是刪除進(jìn)程對(duì)應(yīng)的 buffer; kill-shell-buffer-after-exit函數(shù)的作用就是把 kill-shell-buffer函數(shù)關(guān)聯(lián)到正確的進(jìn)程上去。然后當(dāng)我們把這個(gè)函數(shù)加入到 shell-mode-hook當(dāng)中后,就可以在每次打開 Shell buffer 的時(shí)候得到正確的進(jìn)程信息了。

(add-hook 'shell-mode-hook 'kill-shell-buffer-after-exit t)

#p#

outline in Shell Mode

這一節(jié)我們談 outline-mode。Outline-mode 是 GNU Emacs 的一個(gè)非常好用的寫作模式。使用 outline-mode 可以輕松方便的操作結(jié)構(gòu)化文檔,可以將文檔內(nèi)容分級(jí)展開,或者逐級(jí)隱藏,既能總攬全局,又可深入細(xì)節(jié)。outline-mode 是如此精彩,以至于 Carsten Dominik 教授在此基礎(chǔ)上開發(fā)出了強(qiáng)大的 orgmode。

在這一節(jié)當(dāng)中我們將要討論一下如何將 outline-mode 的強(qiáng)大功能應(yīng)用到 Shell-mode 當(dāng)中。在進(jìn)入細(xì)節(jié)之前,讓我們先對(duì) Outline-mode 進(jìn)行一個(gè)簡(jiǎn)單的介紹。

Outline mode 當(dāng)中,文檔中的內(nèi)容被分成兩種結(jié)構(gòu),一種是“標(biāo)題”,一種是“內(nèi)容”。其中的“標(biāo)題”又可以根據(jù)需要分成大小不同的級(jí)別。在對(duì)文檔的內(nèi)容進(jìn)行折疊和展開操作的時(shí)候就是以這些“標(biāo)題”的級(jí)別為依據(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)我們折疊起這段文檔的時(shí)候,分別可以折疊成這樣的形式

* Food...
* Shelter...

或者這樣的形式

* Food...
** Delicious Food...
** Distasteful Food
* Shelter...

或者我們又可以將 Delicious Food單獨(dú)展開

* 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 命令都折疊起來(lái)呢?就像下面這個(gè)示例所展示的這樣:

清單 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)容全部折疊起來(lái),我們就看到了一條時(shí)間線。既能夠于一瞥之間總覽全部的歷史,又可以隨時(shí)深入任何一條命令的細(xì)節(jié)。相比與僅能告訴我們?cè)?jīng)做過(guò)什么的 history命令來(lái)說(shuō),這樣的場(chǎng)景更像是一部“時(shí)間機(jī)器”。

那么該怎樣實(shí)現(xiàn)這樣的夢(mèng)想呢?其中的關(guān)鍵就是要讓 outline-mode 能夠認(rèn)出我們的“標(biāo)題”。在 outline-mode 里面缺省的“標(biāo)題”是一個(gè) *,這個(gè) *從文本行的***個(gè)字符開始匹配,匹配上的,就是“標(biāo)題”,匹配不上的,就是“內(nèi)容”,匹配的次數(shù)越多,“標(biāo)題”的級(jí)別越低。我們可以通過(guò)設(shè)置 outline-regexp變量的值來(lái)定義我們自己的“標(biāo)題”。在 Shell mode 里面一個(gè)可行的辦法就是將 Shell 提示符的內(nèi)容定義為“標(biāo)題”。如同下面的示例這樣:

(setq outline-regexp ".*[bB]ash.*[#\$]")

設(shè)置標(biāo)題以后,在 Shell mode 里面輸入 M-x outline-minor-mode就可以享受 outline-mode 帶來(lái)的便利了。例如上文示例中所示的結(jié)果使用一下三個(gè)操作就可以實(shí)現(xiàn):

  • 輸入 M-x hide-body或者 M-x hide-all命令折疊起 Shell buffer 里的所有命令
  • 移動(dòng)光標(biāo)到 ls *.el所在的行
  • 使用 M-x show-entry或者 M-x show-subtree命令展開 ls *.el命令

#p#

Enhanced outline in Shell Mode

在上一節(jié)里面講述了通過(guò)設(shè)置 outline-regexp變量,使 outline-minor-mode可以在 shell-mode 中工作的方法,但是這樣簡(jiǎn)單的設(shè)置很難避免會(huì)有一些負(fù)面的影響。因?yàn)?outline-regexp變量是一個(gè)全局變量,所以對(duì) outline-regexp的值勢(shì)必改變其他模式中的outline-minor-mode的行為方式,而這肯定不是你所希望的。

所以我在工作當(dāng)中實(shí)際使用的是另外一種相對(duì)復(fù)雜一些的方法:使用一個(gè)函數(shù)為每一個(gè) buffer 設(shè)置分別的 outline-regexp,并且把outline-regexp變量修改為特定 buffer 范圍內(nèi)的局部變量。下面就是這個(gè)函數(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))))

這個(gè)函數(shù)首先定義了一個(gè)匿名函數(shù),存儲(chǔ)在 find-regexp變量中,這個(gè)函數(shù)通過(guò)遞歸的方式遍歷一個(gè)嵌套列表,直至找到與給定模式對(duì)應(yīng)的值;然后啟動(dòng) outline-minor-mode,修改 outline-regexp為局部變量,然后調(diào)用上述的匿名函數(shù)設(shè)置正確的 outline-regexp。

要讓這個(gè)函數(shù)能夠工作,我們就需要把他加入到各個(gè)主模式的 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 )

但是細(xì)心的讀者應(yīng)該看到了,這個(gè) set-outline-minor-mode-regexp函數(shù)并沒有接受任何參數(shù),這是因?yàn)檫@些主模式在調(diào)用 hook 函數(shù)的時(shí)候是不會(huì)向它們傳遞任何參數(shù)的。那么我們需要的的數(shù)據(jù)從哪里來(lái)呢?顯然這里需要一個(gè)全局變量 outline-minor-mode-list來(lái)存儲(chǔ) 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 ")
))

有了這些擴(kuò)展,Emacs 就可以在創(chuàng)建一個(gè)新的 buffer 的時(shí)候,為這個(gè) buffer 設(shè)置正確的 outline-regexp值了。

延伸閱讀 hook

一些讀者可能注意到,在本文的敘述中多次提到了 hook 這一概念,那么 hook 究竟是什么東西?他在 Emacs 里面有起到什么作用呢?在這里我給大家做一個(gè)簡(jiǎn)要的介紹。

簡(jiǎn)單來(lái)講,hook 就是一個(gè)存儲(chǔ)函數(shù)列表的 Lisp 變量,該列表里的每一個(gè)函數(shù)被稱作這個(gè) hook 的一個(gè) hook 函數(shù)。GNU Emacs 的很多主模式(major modes)在完成初始化之后都會(huì)嘗試尋找并調(diào)用對(duì)應(yīng)該模式的 hook 變量里面的 hook 函數(shù)。因此 hook 就成為定制 Emacs 過(guò)程中一個(gè)非常重要的機(jī)制。我們可以通過(guò)添加 hook 函數(shù)的方式輕松的定制或擴(kuò)展 Emacs 的行為。

最簡(jiǎn)單的 hook 用法就是直接調(diào)用已有的 Emacs 函數(shù),例如啟動(dòng)特定的子模式(minor modes):

(add-hook 'shell-mode-hook 'outline-minor-mode t)

更加復(fù)雜的用法就如上文所示,編寫自己的 hook 函數(shù)。

關(guān)于 hook 有幾個(gè)細(xì)節(jié)需要注意

  • 絕大多數(shù)普通 hook 變量的名稱都是在主模式的名稱后面加上 -hook后綴來(lái)構(gòu)成的
  • 但是,并不是所有 hook 變量都是這樣命名的
  • 絕大多數(shù)普通 hook 函數(shù)被調(diào)用的時(shí)候是不會(huì)向它傳遞任何參數(shù)的,同時(shí)也不會(huì)理會(huì)函數(shù)的返回結(jié)果的
  • 但是,并不是所有 hook 函數(shù)都是這樣調(diào)用的
  • 已經(jīng)裝入的 hook 函數(shù)將無(wú)法通過(guò)再次執(zhí)行 add-hook來(lái)進(jìn)行覆蓋或修改。實(shí)際的結(jié)果將會(huì)裝入該 hook 函數(shù)的多個(gè)版本。解決的辦法之一是清除 hook 變量,然后再次裝入:
(setq 'shell-mode-hook nil)
(add-hook 'shell-mode-hook 'outline-minor-mode t)

原文:http://www.ibm.com/developerworks/cn/linux/l-cn-emacs-shell2/index.html?ca=drs-

【編輯推薦】

 

  1. Emacs和Vim:神的編輯器和編輯器之神
  2. Linux服務(wù)器安全初始化Shell腳本
  3. 省時(shí)省力 Shell小技巧一則
  4. 大話Emacs Shell Mode:讓工作更輕松的技巧
責(zé)任編輯:黃丹 來(lái)源: IBMDW
相關(guān)推薦

2011-07-19 10:16:55

2019-06-17 08:50:49

Emacs shell命令Windows

2019-03-12 21:00:15

WindowsEmacsShell

2011-09-16 10:13:02

Emacs

2011-09-16 09:38:19

Emacs

2023-02-16 08:03:06

2020-03-09 12:31:08

WindowsWindows 10GNU Emacs

2012-02-03 14:06:34

Node.js

2009-10-26 11:11:33

linux Emacs

2012-09-21 17:14:47

開源云計(jì)算

2021-04-01 10:16:01

EmacsJavaScript elisp

2020-03-16 10:05:13

EmacsGUDLinux

2019-09-29 16:42:35

straceEmacsLinux

2020-01-14 15:54:20

邊緣計(jì)算區(qū)塊鏈

2019-04-29 16:14:43

Emacs記筆記應(yīng)用

2011-09-16 13:30:23

Emacs

2022-06-16 10:14:51

LinuxEmacs編輯器

2017-08-03 16:20:42

深度學(xué)習(xí)文本摘要遞歸神經(jīng)網(wǎng)絡(luò)

2017-05-04 13:18:18

深度學(xué)習(xí)知識(shí)圖譜

2014-11-04 16:30:52

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)