中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Emacs中Shell環境如何擴展和定制

發布時間:2021-12-18 10:00:48 來源:億速云 閱讀:190 作者:小新 欄目:系統運維

這篇文章主要為大家展示了“Emacs中Shell環境如何擴展和定制”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“Emacs中Shell環境如何擴展和定制”這篇文章吧。

進入和退出 Shell Mode

輕輕的我走了,正如我輕輕的來;我輕輕的招手,作別西天的云彩。

但是在 Emacs Shell Mode 的缺省設計里面,沒有能夠讓我們如此輕松和優雅的進入與退出。這就是在這一節當中我們要進行定制和擴展的地方。

Shell buffer 的進入

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

這是一個很不優雅的行為。這樣的細節工作應該由 Emacs 事先料理好,我所需要的只是優雅的進入。實現這個目的有兩種做法,一種是在創建 Shell  buffer 的時候就把它修改成一個獨特的名字;另外一種做法是在創建出 Shell buffer 之后,根據用戶的使用情況來自動修改 Shell buffer  的名稱。由于工作特點的關系,我選擇的是第二種方案。

在我的工作環境當中,絕大多數時間都要登錄到遠程的機器上去工作。所以我非常希望 Shell buffer  的名稱能夠被自動修改成我所登錄的目標機器的名稱,這樣在我登錄大量的機器進行操作的時候,就可以方便的通過 buffer  名稱來進行分辨。這就是我選擇第二套方案的原因。我首先接受 Emacs 創建出來的默認 buffer,然后在我登錄遠程機器的時候 Emacs  會自動為我改名。如果我沒有登錄遠程機器,那么它將保持默認的名稱,或者由我主動的修改 buffer 名稱。

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

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

現在讓我們看一看 Emacs 是如何在我登錄遠程機器的時候自動修改 Shell buffer  的名稱的。實現這樣的功能首先需要編寫一個rename-buffer-in-ssh-login函數:

清單 1. rename-buffer-in-ssh-login 函數

(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)
)
)
)

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

要讓這個函數工作,我們需要把它加入到一個 hook 變量 comint-input-filter-functions當中。

(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  進程)去執行之前,會首先運行comint-input-filter-functions hook  當中的函數,同時將輸入的命令作為參數傳遞給該中的函數。所以我們的 rename-buffer-in-ssh-login函數就可以跟蹤輸入到 buffer  當中的每一條命令,當發現有類似 ssh msg@hostA.cn.ibm.com 或者 ssh  msg@hostB這樣的命令的時候,就會執行預定的操作。同時正則表達式的設計還避免了在類似 ssh msg@hostA.cn.ibm.com ls  /opt/IBM這樣不以登錄為目的的遠程命令上面出現誤動作的機會。

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

Shell buffer 的退出

進入的問題解決了,下面讓我們來看一看退出的時候會有哪些問題。

當用戶退出 Shell 會話之后,Emacs 并不會刪除這個 Shell buffer,而是把它留在那里,等待用戶的進一步的處理。

dove@bash-4.1$exit
exit
Process shell finished

如果用戶這個時候再次執行 M-x shell命令,Emacs 會再次復用這個 buffer。

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

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

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

直到有一天在翻看 Emacs 源代碼的時候突然看到了 set-process-sentinel這個函數才找到了解決方案。  set-process-sentinel函數可以對一個特定的進程設置一個“哨兵”,當這個進程的狀態發生變化的時候(比如說進程結束的時候),“哨兵”就會通知  Emacs 調用相應的函數來完成預定的工作。有了這個方案,我們只需要把刪除 Shell buffer 的函數關聯到正確的進程上就行了。

下面就是這兩個函數:

清單 2. 兩個函數

(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的作用是刪除進程對應的 buffer; kill-shell-buffer-after-exit函數的作用就是把  kill-shell-buffer函數關聯到正確的進程上去。然后當我們把這個函數加入到 shell-mode-hook當中后,就可以在每次打開 Shell  buffer 的時候得到正確的進程信息了。

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

outline in Shell Mode

這一節我們談 outline-mode。Outline-mode 是 GNU Emacs 的一個非常好用的寫作模式。使用 outline-mode  可以輕松方便的操作結構化文檔,可以將文檔內容分級展開,或者逐級隱藏,既能總攬全局,又可深入細節。outline-mode 是如此精彩,以至于 Carsten  Dominik 教授在此基礎上開發出了強大的 orgmode。

在這一節當中我們將要討論一下如何將 outline-mode 的強大功能應用到 Shell-mode 當中。在進入細節之前,讓我們先對  Outline-mode 進行一個簡單的介紹。

Outline mode  當中,文檔中的內容被分成兩種結構,一種是“標題”,一種是“內容”。其中的“標題”又可以根據需要分成大小不同的級別。在對文檔的內容進行折疊和展開操作的時候就是以這些“標題”的級別為依據的。例如下面這段摘自  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.

當我們折疊起這段文檔的時候,分別可以折疊成這樣的形式

* 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 又有什么關系呢? 如果我們把 Shell buffer 里的 * 命令 * 看作 outline-mode  的“標題”,將命令產生的輸出看作是“內容”,那么是不是就可以像折疊起一篇普通的結構化文檔那樣將所有的 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$ ...

當我們把 Shell buffer  里面的內容全部折疊起來,我們就看到了一條時間線。既能夠于一瞥之間總覽全部的歷史,又可以隨時深入任何一條命令的細節。相比與僅能告訴我們曾經做過什么的  history命令來說,這樣的場景更像是一部“時間機器”。

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

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

設置標題以后,在 Shell mode 里面輸入 M-x outline-minor-mode就可以享受 outline-mode  帶來的便利了。例如上文示例中所示的結果使用一下三個操作就可以實現:

  • 輸入 M-x hide-body或者 M-x hide-all命令折疊起 Shell buffer 里的所有命令

  • 移動光標到 ls *.el所在的行

  • 使用 M-x show-entry或者 M-x show-subtree命令展開 ls *.el命令

Enhanced outline in Shell Mode

在上一節里面講述了通過設置 outline-regexp變量,使 outline-minor-mode可以在 shell-mode  中工作的方法,但是這樣簡單的設置很難避免會有一些負面的影響。因為 outline-regexp變量是一個全局變量,所以對  outline-regexp的值勢必改變其他模式中的outline-minor-mode的行為方式,而這肯定不是你所希望的。

所以我在工作當中實際使用的是另外一種相對復雜一些的方法:使用一個函數為每一個 buffer 設置分別的  outline-regexp,并且把outline-regexp變量修改為特定 buffer 范圍內的局部變量。下面就是這個函數:

清單 4. 設置 buffer 的函數

(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))))

這個函數首先定義了一個匿名函數,存儲在 find-regexp變量中,這個函數通過遞歸的方式遍歷一個嵌套列表,直至找到與給定模式對應的值;然后啟動  outline-minor-mode,修改 outline-regexp為局部變量,然后調用上述的匿名函數設置正確的 outline-regexp。

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

但是細心的讀者應該看到了,這個 set-outline-minor-mode-regexp函數并沒有接受任何參數,這是因為這些主模式在調用 hook  函數的時候是不會向它們傳遞任何參數的。那么我們需要的的數據從哪里來呢?顯然這里需要一個全局變量 outline-minor-mode-list來存儲  set-outline-minor-mode-regexp函數所需的所有數據。

清單 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 就可以在創建一個新的 buffer 的時候,為這個 buffer 設置正確的 outline-regexp值了。

延伸閱讀 hook

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

簡單來講,hook 就是一個存儲函數列表的 Lisp 變量,該列表里的每一個函數被稱作這個 hook 的一個 hook 函數。GNU Emacs  的很多主模式(major modes)在完成初始化之后都會嘗試尋找并調用對應該模式的 hook 變量里面的 hook 函數。因此 hook 就成為定制  Emacs 過程中一個非常重要的機制。我們可以通過添加 hook 函數的方式輕松的定制或擴展 Emacs 的行為。

最簡單的 hook 用法就是直接調用已有的 Emacs 函數,例如啟動特定的子模式(minor modes):

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

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

關于 hook 有幾個細節需要注意

  • 絕大多數普通 hook 變量的名稱都是在主模式的名稱后面加上 -hook后綴來構成的

  • 但是,并不是所有 hook 變量都是這樣命名的

  • 絕大多數普通 hook 函數被調用的時候是不會向它傳遞任何參數的,同時也不會理會函數的返回結果的

  • 但是,并不是所有 hook 函數都是這樣調用的

  • 已經裝入的 hook 函數將無法通過再次執行 add-hook來進行覆蓋或修改。實際的結果將會裝入該 hook 函數的多個版本。解決的辦法之一是清除  hook 變量,然后再次裝入:

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

以上是“Emacs中Shell環境如何擴展和定制”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

安义县| 大庆市| 汝城县| 日土县| 阿荣旗| 黄陵县| 浦城县| 齐齐哈尔市| 湟中县| 梁山县| 遵义市| 新巴尔虎左旗| 海宁市| 广河县| 固镇县| 绥阳县| 扶余县| 巩留县| 霸州市| 文成县| 阿拉善左旗| 平江县| 上林县| 商河县| 水城县| 绿春县| 沁阳市| 南通市| 平安县| 上思县| 长岭县| 积石山| 建德市| 蒙自县| 延吉市| 定结县| 永吉县| 思茅市| 台湾省| 漳州市| 龙川县|