1 什么是Eshell?

 Eshell是Emacs完全用Elisp实现的类UNIX shell. 由于它完全是由Elisp实现的,因此它具有与Emacs相同的可移植性,而且它可以很自然的与Elisp代码相结合. 事实上,你完全可以在Eshell下运行lisp代码

2 Eshell与普通shell有什么不同?

  • Eshell支持输出重定向但不支持输入重定向

  • Eshell没有job control功能,它不支持多进程的后台切换

  • Eshell没办法输入C-z,因为C-z会被emacs所捕获.

  • Eshell可以识别更多类型的参数

    • 字符串

    • 数字

    • Lisp列表

    • Lisp符号

    • Emacs buffer

    • Emacs process handles


  • Eshell类似于unix的shell,因此若你是在windows下运行Eshell,你会发现像dir,copy这一类cmd.exe执的内置命令无法使用了,取而代之的是ls,cp这一类的unix命令

  • Eshell并不直接调用exec这样的内核函数,它把输入的命令转换成一个可执行的Lisp代码形式,然后执行这段代码.

           要查看输入的命令被转成什么样的Lisp代码,可以输入`eshell-parse-command "echo hello"`


  • Eshell的多开比较复杂,若你已经打开了一个Eshell,再允许M-x eshell,只会跳转到那个eshell而已. 解决的办法有:

    • 先用rename-buffer把打开的那个*eshell* buffer重命名为其他命令,再调用M-x eshell

    • 使用C-u 数字n M-x eshell会创建一个编号为n的的eshell窗口


3 命令

3.1 命令提示符

  你可以通过变量`eshll-prompt-function`自定义eshell的命令提示符. 该变量可以是一个匿名函数或一个函数名称,该函数的返回值被显示为Eshell的命令提示符号.

  当你改了自己的命令提示符后,为了让Eshell能够识别出一行输出的那个部分是命令提示符,你还需要冲定义变量`eshll-prompt-regexp`,符合该正则的部分被识别为命令提示符

3.2 Eshell中查找命令的顺序为(以cp命令为例)

  1. 输入的是完整路径(/bin/cp),则直接调用该路径的命令

  2. 查询命令前缀,若为*(由变量`eshell-explicit-command-char`决定),并且能够在search path中查找到命令,则执行查找到的命令

  3. 查询定义的alias

  4. 在search path,$PATH中查询该命令

  5. 查询同名的Lisp函数cp或eshell定义的命令eshell/cp

  但若变量`eshell-prefer-lisp-functions`设置为t,则会最优先搜索elisp函数和eshell定义的命令.

  你可以用which命令来查看命令的指向. 例如

 ~ $ which ls
eshell/ls is a compiled Lisp function in `em-ls.el'  

  如果你想调用外部的同名命令,可以在命令前加星号*. 例如

 ~ $ which *ls
/bin/ls

3.3 内置命令

  eshell自己实现了许多的内置命令,例如ls. 但通常这些内置命令只是实现了最常用的那些功能. 但是Eshell足够聪明,当你输入了一个内部命令不支持的参数时,eshell会自动调用外部命令来执行

  大多数的eshell内置命令都提供–help选项,输出帮助信息

  • addpath 路径/路径列表

                     把路径加入到PATH环境变量中,如果不加参数,则输出当前的PATH变量值


  • alias 别名 定义

        定义命令别名,通过alias增加/删除的别名会自动记录在变量`eshell-aliases-file`指代的文件中(默认为~/.eshell/alias)

        定义可以用单引号'括起来,这时可以使用$1表示第一个参数,$2表示第二个参数,以此类推,$*表示所有参数

        此外Eshell还提供一种叫`auto-correcting aliasing`的东西. 若你输入一个错误的命令超过一定次数(由变量`eshell-bad-command-tolerance`决定,默认为3),则Eshell会提示你把它定义为某个正确命令的别名


  • cd 路径

             Eshell的cd命令很强大. 你可以

    • cd -        回到上一个目录

    • cd -n        回到上n个目录

    • cd=REGEXP        回到上一个满足正则匹配的目录

    • cd =       显示目录跳转的历史,每个历史项都有一个编号

    • cd =数字       跳转到指定编号的历史项

      • cd 远程目录           使用tramp格式的远程目录



  • cal-eval EXPR

        使用Emacs calculator及损EXPR


  • date

            该命令与GNU的date命令类似,只有很少一点区别


  • define

            定义变量别名


  • dired 目录

             用dired打开目录


  • diff

            使用Emacs的内置diff(不要与ediff搞混)


  • ediff-files 文件1 文件2

        使用ediff比较文件1和文件2


  • find-file 文件

        用Emacs打开文件,可以用tramp格式打开远程文件


  • grep / agrep / egrep /fgrep /glimpse

            Emacs的内置grep命令与GNU grep相兼容,但会弹出一个名为*grep*的buffer,将结果显示出来


  • info

            与外置的info命令一样,但是是使用Emacs自身的info阅读器来阅读


  • kill

            类似kill命令,但它不仅可以接进程id,还能接process object


  • jobs

            列出Emacs的所有子进程(不仅仅是在eshell中调用的). 它实际上是执行一个名为`list-processes`的Lisp函数,会弹出一个*process list* buffer


  • listify

            功能同lisp函数`list`,它将后面的参数组合为elisp中的list. 例如

    e:/我的笔记 $ listify a b c 1 2 3
    ("a" "b" "c" 1 2 3)    
    


  • listify 参数列表

        将参数列表转换成Elisp的list形式


  • locate

            目前该命令只是调用外部的locate命令,并返回结果


  • make

                     调用Emacs的compile命令


  • occur

            Emacs的occur的别名


  • printnl

            打印各个参数,每个参数一行


  • su / sudo

            Uses TRAMP’s su or sudo method to run a command via su or sudo(即是说,当目录为远程目录时,该命令作用于登录远程系统的用户)


  • whoami

            返回当前用户,如果是在远程目录时执行该命令,则返回登录远程系统的用户


  • upcase 字符串 /downcase 字符串

        转换字符串为大/小写形式


  • unset 环境变量

        取消指定的环境变量


  • vc-dir 目录

        显示目录的版本控制信息


3.4 命令交互中的一些有用的keybind

keybinddescription
C-c M-b插入buffer名称
C-c M-i插入子进程名
C-c M-v插入环境变量名
C-d M-d若一些程序不能正确处理带缓冲的输入,则按这个键序列切换

3.5 通配符

  eshell下的通配符与zsh下的通配符很类似,它也提供了predicate和modifier的功能. 具体的扩展规则可以通过执行命令eshell-display-predicate-help 和 eshell-display-modifier-help来查看帮助文档

  所谓predicate,是指对输出结果进行过滤的一种表示,它的格式为($predicate). 例如

 $ echo *(^/)     #显示非目录
("bar" "foo")  

  所谓modifier,是指对输出结果进行修改的一种表示,它的格式为(:\(modifier).

\) echo *(:U)            #把结果转换成大些形式
("BAR" "BIN/" "DEV/" "ETC/" "FOO" "HOME/" "LIB/" "TMP/" "USR/" "VAR/")  

$ echo ("foo" "bar" "baz" "foo")(:gs/foo/blarg/)    #可以进行替换操作
("blarg" "bar" "baz" "blarg")

  通过修改变量`eshell-predicate-alist`和`eshell-modifier-alist`的值可以新增自己的predicate和modifier. 例如

(add-to-list 'eshell-modifier-alist '(?X . '(lambda (lst) (mapcar 'rot13 lst))))

4 命令历史

 history命令会显示一张历史命令列表,每个命令前面都带有一个编号. eshell最多存储的历史命令个数由变量`eshell-history-size`决定.

 使用`!!`运行上一个命令

   使用`!n`命令可以运行历史命令表中第n个命令,若n为负数,则是从历史表的尾部往回数

 使用`!foo`命令或运行最后执行的以foo开头的命令,而`!?foo`会运行最后执行的包含`foo`的命令. 若要运行最后执行的第n个参数为foo的命令,用`!foo:n`

 这张历史命令表会自动记录在一个文件中,该文件路径由变量`eshell-history-file-name`决定. eshell每次启动/关闭时都会读取/写入命令历史表到该文件中.

 eshell还提供了一些查询/遍历历史命令的操作符,如下:

  • M-r / M-s

           向前/先后正则搜索匹配的命令


  • M-p / M-n

           上一个/下一个执行过的命令. 若你输入了一些命令后再执行这两个操作,则会查询以已输入为开头的历史命令


4.1 相关配置项

  • eshell-hist-ignoredups

        决定是否忽视重复的命令


  • eshell-input-filter

        该值为一个函数名,每个输入的命令都会作为参数传递给该参数,若函数返回t则保存历史列表中,否则不保存


  • eshell-history-size

        决定了保存多少条历史命令


5 补全

5.1 使用补全

  在eshell中按<TAB>键会自动开始补全. eshell不仅仅能补全命令,文件,还能补全lisp代码.

5.2 自定义补全

  eshell使用名为pcomplete(programmable completion的缩写)的库来进行补全, 你可以为自己的命令自定义补全函数.

  补全函数的命名规则为`pcomplete/命令名`或`pcomplete/MAJOR模式/命令名`. 下面是个例子

;; 首先获取补全可选项,这里获取当前符合名称的软件的列表
(defun pcmpl-package-cache (name)  
  "return a list of packages in cache"  
  (unless (equal name "")  
    (split-string (shell-command-to-string  
                   (concat "apt-cache pkgnames " name " 2> /dev/null")))))  
;; 定义补全函数
(defun pcomplete/sai ()  
  "completion for `sai'"  
  (while  
      (pcomplete-here  
       (pcmpl-package-cache (pcomplete-arg 'last)))))  

5.3 相关配置项

  • eshell-cmpl-ignore-case

             补全时是否忽略大小写


  • eshell-cmpl-cycle-completions

        是否循环补全


6 脚本

 在eshell可以用source命令来执行eshell脚本. 也可以在emacs的任何地方用函数`eshell-source-file`来调用eshell脚本

6.1 启动脚本

  Eshell也支持login和profile/rc启动脚本,它们的路径分别由变量`eshell-login-scrip`和变量`eshell-rc-scrip`决定,默认放在`~/.eshell`目录下

6.2 内置变量

  eshell的变量与Emacs共享一个作用域.

     此外eshell还定义了一些内置变量

  • $+

            当前的工作目录


  • $-

            前一个工作目录


  • $_

            最后一个命令的最后一个参数


  • $$

            最后一个内置命令的结果,如果上一个命令是外部命令,则为t或者nil


  • $?

            最后一个命令的退出码,0或1


6.3 $扩展

  eshell不提供字符串操作的expansion操作,这是因为Elisp library已经提供了大量的类似功能.

  • $var

                     扩展为变量var的值


  • $#var

                     扩展为变量var的值的长度


  • $(lisp)

                     扩展为lisp表达式的运行结果,它与(lisp)一样,但是可以嵌入到字符串中


  • ${command}

                     扩展为command的输出结果


  • $var[i]

            扩展为变量var的值中的第i个元素,如果变量var的值为字符串,它会以空格为分隔符把它分割为list


  • $var[: i]

            类似上面,但是以:为分割符号


  • $var[: i j]

            类似上面,但是会返回一个list包含第i和第j元素值. 若该返回值内插入一个字符串中,则会转换为以空格连接各元素值的字符串.


  • $var["\\\\" i]

                     以反斜杠作为分隔符.

            事实上,第一个参数可以是任何的正则表达式. 例如如果你想以数字作为分隔符可以用`$var["[0-9]+" 10 20]`


  • $var[hello]

            返回var数组中索引为hello的值


  • $@var[hello]

                     Returns the length of the cdr of the element of var who car is equal to "hello".


6.4 for循环

  for var in 列表 { 执行语句 }

  这里的列表是以空格分割的一系列的值,也可以是某个命令的输出结果

e:/git-svn/server/pub/src $ for va in 2 3 4 {echo $va}
2
3
4  

7 Input/Output

   当在eshell下运行那些非line-oriented的程序(比如使用了ncurses库的程序)时,显示会异常.

 要解决这个问题,需要手工添加这个命令到列表`eshell_visual_commands`中

 eshell能够支持输出重定向和管道,但是目前还不支持输入重定向.

7.1 重定向到虚拟设备

  所谓虚拟设备可以认为是ELisp函数的一个别名,任何对虚拟设备的重定向操作都会调用对应的Elisp函数.

  虚拟设备与Elisp函数的对应关系存储在变量`eshell-virtual-targets`中. 默认情况下eshell自带了两个虚拟设备:

  • /dev/kill会把输出流存入emacs kill ring

  • /dev/clip会把输出流存入clipboard

  • /dev/eshell会把输出流直接在eshell上输出

  • /dev/null会吧输出流丢弃

  如果你想新增自己的虚拟设备,你可以添加格式为("/dev/name" function mode)的list到变量`eshell-virtual-targets`中. 其中:

  • /dev/name是虚拟设备的名称.

  • function是一个lambda函数或者函数名称.

            若mode为nil则该函数作为output function; 若mode非nil,则该函数需要接收一个mode并返回一个output function.

             function的参数接收的mode有三个可能值'overwrite,'append和'insert

        output function接收一个字符串作为参数. eshell对输出重定向的每一行都会调用该output function来处理,直到输出完毕则传递参数nil.


  • mode与function函数配合,用来指示第二个function的类型

7.2 重定向到buffer

  此外你还可以通过`ls >> #<buffer buffer名称>`这样的方式来将输出重定向到emacs的某个buffer中

7.3 重定向到Elisp变量

  你还可以使用>>#'变量名来将输出重定向到emacs lisp的某个变量中

~ $ echo a b c >#'a
~ $ echo $a
("a" "b" "c")