我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Emacs中Shell环境如何扩展和定制

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Emacs中Shell环境如何扩展和定制

这篇文章主要为大家展示了“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$exitexitProcess shell finished

如果用户这个时候再次执行 M-x shell命令,Emacs 会再次复用这个 buffer。

dove@bash-4.1$dove@bash-4.1$exitexitProcess shell finisheddove@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 的示例:

* FoodThis is the body,which says something about the topic of food.** Delicious FoodThis is the body of the second-level header.** Distasteful FoodThis could havea body too, withseveral lines.*** Dormitory Food* ShelterAnother first-level topic with its header line.

当我们折叠起这段文档的时候,分别可以折叠成这样的形式

* Food...* Shelter...

或者这样的形式

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

或者我们又可以将 Delicious Food单独展开

* Food...** Delicious FoodThis 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 : ~/orgdove@bash-4.1$ ls *.elcalendar-setup.el dove-ext.el org-mode.el settings.elcolor-theme.el keybindings.el plugins.eldove@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环境如何扩展和定制”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注编程网行业资讯频道!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Emacs中Shell环境如何扩展和定制

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Emacs中Shell环境如何扩展和定制

这篇文章主要为大家展示了“Emacs中Shell环境如何扩展和定制”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Emacs中Shell环境如何扩展和定制”这篇文章吧。进入和退出 Shell Mo
2023-06-16

如何扩展和定制 PHP 函数库?

可以扩展和定制 php 函数库来满足特定需求,通过以下步骤实现:使用 create_function 创建新函数并将其添加到现有函数库。使用 filter_var 注册过滤器以修改现有函数的行为。如何扩展和定制 PHP 函数库?PHP 函
如何扩展和定制 PHP 函数库?
2024-04-27

Solr搜索的扩展性与定制化开发实践(如何扩展和定制Solr搜索功能?)

Solr是一款可扩展、可定制的高级搜索平台。通过扩展其架构,包括复制、分片和集合,以及扩展其功能,如自定义分词器、过滤器和查询处理器,可以提升Solr的固有能力。此外,定制化的开发实践,如利用SolrJ、Velocity模板和自定义请求处理器,可进一步增强Solr的功能。通过遵循最佳实践,例如性能优化、可扩展性规划、代码重用和单元测试,可以确保扩展和定制的可靠性。这些实践使开发者能够创建强大的、灵活的搜索解决方案,以满足各种应用程序需求。
Solr搜索的扩展性与定制化开发实践(如何扩展和定制Solr搜索功能?)
2024-04-02

PHP扩展开发:如何将自定义函数加载到PHP运行环境中?

需要将自定义函数加载到php运行环境中,可以通过编写php扩展来实现。步骤如下:1. 使用c语言或汇编语言编写扩展模块,包含自定义函数的实现;2. 创建声明文件,声明函数列表和配置选项;3. 在php.ini中添加扩展加载路径;4. 重新加
PHP扩展开发:如何将自定义函数加载到PHP运行环境中?
2024-05-15

Sphinx搜索的扩展插件与定制化开发实践(如何为Sphinx开发扩展插件和定制功能?)

Sphinx扩展插件和定制化开发指南,详解扩展插件的创建和安装,以及定制化开发的实践。开发者可以通过扩展Sphinx的核心功能和直接修改源代码来增强其搜索功能,以满足特定需求。扩展插件相对易开发,而定制化开发则需要对Sphinx源代码有深入了解。根据具体要求,可选择适合的开发方式,优先考虑扩展插件,仅在必需时进行定制化开发。
Sphinx搜索的扩展插件与定制化开发实践(如何为Sphinx开发扩展插件和定制功能?)
2024-04-02

ElasticSearch的映射在Python中如何定义和使用?(Python环境下,如何定义和管理ElasticSearch的映射?)

本指南详细介绍了如何在Python中定义和使用ElasticSearch映射。映射定义了索引中每个字段的类型和属性。使用mapping字典和put_mapping方法可以创建映射。使用bulk方法可索引文档,然后使用搜索查询根据映射的字段进行搜索。管理映射包括获取、更新和删除映射。最佳实践包括考虑数据类型、添加属性、使用嵌套类型、更新映射和利用工具。
ElasticSearch的映射在Python中如何定义和使用?(Python环境下,如何定义和管理ElasticSearch的映射?)
2024-04-02

MySQL的视图在Python中如何定义和使用?(Python环境下如何定义和使用MySQL的视图?)

Python中定义MySQL视图可以使用CREATEVIEW语句或SQLAlchemy。CREATEVIEW语句从现有表中创建虚拟视图,而SQLAlchemy使用MetaData和create_view()方法。从视图中检索数据可以使用SELECT语句或SQLAlchemy的Table对象和select()方法。视图简化查询、抽象数据、提高维护性和安全性。但需要注意,视图不是物理表,不能直接更新,其性能受底层表结构和查询复杂度影响。
MySQL的视图在Python中如何定义和使用?(Python环境下如何定义和使用MySQL的视图?)
2024-04-02

如何在phpcms中构建自定义的表单和数据收集系统?(phpcms环境下,如何构建个性化的表单以收集用户数据?)

在PHPCMS中构建自定义表单和数据收集系统包括:创建表结构、设计表单布局、编写表单处理程序、集成到PHPCMS、验证和存储数据。通过创建表格、设计表单、编写处理脚本,并使用模板函数将表单与PHPCMS集成,您可以收集和管理用户数据。此外,自定义模板、第三方表单库和PHPCMS插件可以增强表单功能和数据处理。
如何在phpcms中构建自定义的表单和数据收集系统?(phpcms环境下,如何构建个性化的表单以收集用户数据?)
2024-04-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录