第一回 引子
GNU Emacs 是一个非常强大的编辑器,这个编辑器不仅可以用来写文章,写程序,更重要的是, 他可以和一些原本看似没有明显关系的应用程序在一起,合作创造出一些新的“不可思议”的应用。比如说可以在 GNU Emacs 里面运行你的 Shell。
通常来说人们在 Linux 或者 Unix 上面工作的时候,不论是在本机工作,还是登录到地球另一头的远端机器,都是使用各种各样的终端或者终端模拟器来运行 Shell。最常见的例如 xterm,rxvt,以及 Putty 之类的终端模拟器。与此对应,GNU Emacs 也有自己的终端模拟器,例如 ansi-term,multi-term 等等。这些终端模式,使得你可以像在在其他终端当中一样工作,甚至可以在 Emacs 的终端里面运行 Vim。
但是,今天要和大家分享的是另外一种使用方式—— Shell mode。这是一种完全不同的工作方式。这种方式和大家常用的工作方式最大的一个区别,就是在这里完全没有任何 terminal 的存在。用户实际上是工作在一个 Emacs 的文本缓冲区里面,并不直接和 Shell 进行交互。一切的命令输入都是写入到这个文本缓冲区当中,经由 comint.el从缓冲区中读取,然后转交给后台的 Shell 进程。Shell 产生的输出再由 comint.el进行收集,然后写入到用户所用的这个缓冲区当中来。这个缓冲区在 Emacs 当中叫做 Shell 缓冲区 (Shell buffer)。
启动一个 Shell 缓冲区并且进入 shell mode 的过程非常简单。只需要在 Emacs 当中按下 Meta-x 组合键(在现在的键盘上通常是 Alt-x 组合键),然后输入命令 shell 并回车,Emacs 就会启动一个 Shell 进程并且打开一个与之关联的 Shell 缓冲区。Shell 缓冲区 的名字通常会是 *shell*。具体启动什么样的 Shell 进程 通过 Emacs 配置文件里的 shell-file-name 变量指定,或者由用户的环境变量 SHELL 或 EMACSSHEL 来指定。通常的写法是
(setq shell-file-name "/bin/bash")
或者
export EMACSSHELL=/usr/bin/zsh
另外如果你希望使用一个支持 ANSI color 的 Shell 进程,那么最好在你的 Emacs 配置文件里面加入下面两行,以便在执行 ls – color=auto 命令的时候输出的色彩信息能够被 Emacs 正确解析。
(autoload 'ansi-color-for-comint-mode-on "ansi-color" nil t)
(add-hook 'shell-mode-hook 'ansi-color-for-comint-mode-on t)
说了这么多了,这种工作方式究竟能有什么好处呢?我为什么要离开熟悉的 Xterm,把我的 Shell 搬到 Emacs 当中来呢?
第二回 初识 Shell mode -- 窗口篇
下面我们就来谈谈好处。事实上不仅仅是好处,在相当程度上甚至是不可替代性。
第一个明显的好处就是多窗口的工作模式。
通常在人们的工作当中都会打开多个终端,同时进行几份工作。在这个时候就需要对这些终端窗口进行排列和管理(在这里假设你工作在图形化环境之下)。而且通常需要频繁的使用鼠标在不同的窗口之间切换焦点。为了避免窗口之间相互遮盖,你也许会通过精心编辑的 .Xdefaults文件使得两个或四个终端窗口恰到好处的平铺在整个屏幕当中。但是仍然需要使用鼠标在不同的窗口进行切换,在不同的窗口之间复制粘贴信息……这些窗口维护的工作在任务繁忙的时候会很繁重。并且如果这时候需要的不止 4 个窗口,或者你还需要进行额外的文字编辑的工作……最终窗口还是会要么被覆盖起来,要么被挤到别的虚拟桌面。
在这种时候最好来试试 GNU Emacs。GNU Emacs 天生具有完善的窗口管理功能,并且完全不依赖于 X Window。这是因为 GNU Emacs 的诞生要远远早于 X Window 的历史。在 GNU Emacs 里面你只需要按下 Ctrl-x 2 组合键就可以把当前窗口切分成上下两个等分的窗口,
+----------------------+
| |
| |
+----------------------+
| |
| |
+----------------------+
按下 Ctrl-x 3组合键又可以把当前窗口切分成左右两个等分的窗口。这些切分可以一直进行下去。
+----------+-----------+
| | |
| | |
+----------+-----------+
| |
| |
+----------------------+
输入 Ctrl-x 0可以关闭当前光标所在的窗口。
+----------------------+
| |
| |
+----------------------+
| |
| |
+----------------------+
输入 Ctrl-x 1组合键则可以关闭其他所有窗口,并使当前光标所在的窗口成为最大的窗口。
+----------------------+
| |
| |
| |
| |
| |
+----------------------+
当你使用两个或以上的窗口的时候,可以使用 Ctrl-x o(注意是小写字母 o)组合键在各个窗口进行移动。通过给 Ctrl-x o组合键加上数字前缀,例如 Ctrl-u 3 Ctr-x o 或者更加简洁的 Meta-3 Ctrl-x o 就可以在多个窗口之间快速的移动。
当然,当你启动了太多各种缓冲区的时候,总归是要把其中的一些覆盖掉的。因为保证工作窗口具有足够的可视面积才是真正有意义的事。在这种时候可以通过 Ctrl-x b 组合键在所有缓冲区之间方便的切换。或者通过 Ctrl-x Ctrl-b 组合键得到有缓冲区的列表。
这种缓冲区的切换和 X Window 窗口或者虚拟桌面之间的切换最大的不同在于——如果你有 任意两个或者多个缓冲区的工作需要相互参照(这样的需要会非常常见),甚至就是信息的复制粘贴,这个时候相关的工作窗口最好能分布在同一个屏幕上。在 GNU Emacs 当中你将很容易把这些需要参照的缓冲区切换到同一个屏幕的窗口当中去。而在图形终端的工作方式下,这些需要参照的窗口常常要么恰好是相互覆盖的,要么恰好是处在不同的虚拟桌面之中,频繁的拖拽移动将会变得非常繁琐。
还有一种情况,由于工作的原因恰好需要对同一个 Shell 进程当中的内容进行上下文参照……通常绝大多数终端都不提供这种功能。但是在 Emacs 里面,同一个缓冲区显示在两个独立的窗口里面完全不成问题。
另外如果你很喜欢多个虚拟桌面的工作方式,可以使用 make-frame 命令生成多个 frame( 也许可以叫做“窗框”),把他们放到多个虚拟桌面当中去。而且即使是在这种情况下,仍然可以使用 Ctrl-x b 组合键在任何一个 frame 中的任何一个窗口中切换到任何一个被遮盖的缓冲区。不需要进行任何 X Window 当中的窗口移动和桌面切换,包括进行上下文参照。
技巧一
如何在 GNU Emacs 当中启动多个 Shell 进程及其对应的 Shell 缓冲区?
我在上文当中提到了那么多的窗口,但是如果你在 minibuffer 当中第二次输入 Meta-x shell 命令,GNU Emacs 会把你带到已经存在的那个名叫 *shell* 的 Shell 缓冲区,而不是创建一个新的。解决的方法非常简单——你只需要使用 rename-buffer 命令为现有的 Shell 缓冲区重新安排一个名字,然后再执行 shell 命令,GNU Emacs 就会为你创建一个新的名叫 *shell* 的 Shell 缓冲区了。因为这两个命令在我的工作中用的非常频繁,所以我把它们绑定到了两个快捷键上面
(global-set-key (kbd "C-c z") 'shell)
(global-set-key (kbd "
技巧二
如何 undo 到我刚刚离开的窗口设置?
上文提到过,Ctrl-x 0, Ctrl-x 1, Ctrl-x 2, Ctrl-x 3 能够快速的更改 GNU Emacs 的窗口设置,但是如果我在用过 Ctrl-x 1 之后希望能够快速“退回”到“刚才”使用过的窗口设置,而不是把它再做一遍,有没有办法做呢?GNU Emacs 有一个叫做 winner-mode 的 minor mode 可以帮你完成这个愿望。
只需要在你的 Emacs 配置文件里面加入下面几行
(when (fboundp 'winner-mode)
(winner-mode)
(windmove-default-keybindings))
然后就可以使用 Ctrl-c ← (对,就是向左的箭头键)组合键,退回你的上一个窗口设置。
第三回 甜蜜约会 -- buffer 篇
上文描述了在 GNU Emacs 里面通过简单的窗口管理优化 Shell 工作的方法,是不是开始对 Shell 从终端里面搬到 Emacs 里面开始有了一点点的心动了呢? 别着急,这还只是个开始,目前你看到的都还只是外表。接下来让我们和 Emacs 来一个甜蜜的约会吧。
输入
我在开头的引子部分曾说过,在 Shell mode 中工作的时候,用户实际上接触的是一个文本缓冲区,实际上并没有直接的跟 Shell 进程打任何交道。这也是和通常的终端模式的工作方法的一个非常大的区别。虽然这个区别看起来似乎不是那么显著(那是因为这个 Shell 缓冲区被设计成了看起来很像一个图形终端的样子),但是实际上这点区别将会带来一些不可替代的优势。让我们来先看一个简单的例子:
让我们在 Shell 提示符前输入这样一行命令
2 : 2037 : 13:04:40 : ~
[email protected]$ cd /usr/share/emacs
2 : 2038 : 13:05:05 : /usr/share/emacs
[email protected]$ ls -1
23.1
site-lisp
site-lisp.tar
2 : 2039 : 13:05:09 : /usr/share/emacs
[email protected]$
这个时候让我们把光标移动到 23.1 的前面,输入 ls -1加空格,
2 : 2037 : 13:04:40 : ~
[email protected]$ cd /usr/share/emacs
2 : 2038 : 13:05:05 : /usr/share/emacs
[email protected]$ ls -1
ls -1 23.1
site-lisp
site-lisp.tar
2 : 2039 : 13:05:09 : /usr/share/emacs
[email protected]$
然后回车。接下来就会看到这样的输出结果出现在缓冲区里面。
2 : 2040 : 13:08:55 : /usr/share/emacs
[email protected]$ ls -1 23.1
etc
leim
lisp
site-lisp
2 : 2041 : 13:09:06 : /usr/share/emacs
[email protected]$
这是一件很有意思的事情,因为我们并没有像在终端当中常见的那样在 Shell 提示符的后面进行命令输入,而是在一个看起来非常随意的地方。神奇的是他居然被正确地执行了。事情的真相其实很简单。
因为现在我们是在一个被称作 Shell 缓冲区的文本缓冲区里面。这就是一个很普通的文本缓冲区,它具有所有其他文本缓冲区所具有的一切特性。你可以在任何时候,任何位置,对这个缓冲区里的任何文本内容进行任何编辑。因为他就是文本。直到某一刻,你在其中一个文本行上面按下了回车,这时 comint.el 就会负责把当前光标所在行的内容提取出来,发送给 Shell 去执行,然后将 Shell 执行的结果以及 一个提示符(这个提示符实际上也是由 Shell 输出给 comint.el 的)以文本的形式添加到这个缓冲区的末尾。
这个例子并不仅仅是列一个目录那么简单,事实上他提供了一个更加强大的工作方式 —— 曾经只能用来阅读的命令输出现在也可以被用来构造新的命令了。让我们再来看一个新的例子,在这个例子中我们将把这种能力与 Bash 的历史命令引用的能力结合起来
2 : 2044 : 15:16:17 : /usr/share/emacs/23.1
[email protected]$ ls -1
etc
leim
lisp
cd ../site-lisp && !!
2 : 2045 : 15:16:49 : /usr/share/emacs/23.1
[email protected]$ cd ../site-lisp && !!
cd ../site-lisp && ls -1
auctex
auctex.el
autoconf
autoconf-mode.el
autotest-mode.el
bashdb.el
bashdb.elc
bbdb
如果这时候我需要列出 auctex.el 文件的内容,我只需要在在各文件名前面输入 head,然后回车就行了
2 : 2045 : 15:16:49 : /usr/share/emacs/23.1
[email protected]$ cd ../site-lisp && !!
cd ../site-lisp && ls -1
auctex
head auctex.el
autoconf
autoconf-mode.el
autotest-mode.el
bashdb.el
bashdb.elc
bbdb
2 : 2046 : 15:17:16 : /usr/share/emacs/site-lisp
[email protected]$ head auctex.el
;;; auctex.el
;;
;; This can be used for starting up AUCTeX. The following somewhat
;; strange trick causes tex-site.el to be loaded in a way that can be
;; safely undone using (unload-feature 'tex-site).
;;
(autoload 'TeX-load-hack
(expand-file-name "tex-site.el" (file-name-directory load-file-name)))
(TeX-load-hack)
2 : 2047 : 15:23:53 : /usr/share/emacs/site-lisp
[email protected]$
这看起来已经不太像是在运行 Shell 了,倒象是在与某人合作编写一篇巨大的文章了,而 Shell 就是你的合作者。如果你真这么想的话,那就非常好了。至少你已经从枯燥乏味的日常工作当中找到些许的乐趣了。
小技巧
如果你登录在一台远程机器上工作,cat 一个文件后,需要把这个文件的内容保存到本地来,那么完全不需要启动一个 FTP session 去下载这个文件。你只需要选中缓冲区里面的文件内容,按下 Meta-x 组合键,输入 write-region 命令就可以把选中的内容保存在本地文件当中。