ZShell

配置

zsh 的配置文件是 $ZDOTDIR 目录下的 .zshenv.zprofile.zshrc.zlogin.zlogout 这几个文件。如果 ZDOTDIR 没有设置,那么就使用默认 的 HOME 目录。这几个文件的用途都是不言自明的了,其中主要的配置会在用于 非登录 shell 的 .zshrc 里面进行,而 .zshenv 则用于设置搜索路径以及其他 一些重要的变量。需要注意的是, .zshenv 里面不应该包含产生文本输出的命 令,也不应该随意假设执行的时候 shell 已经绑定到了一个 tty 上。

提示符

可以使用 prompt 变量来配置 zsh 的提示符,提示符可以显示的信息非常丰富,详 细可以参考 zsh 的文档,我使用如下设置来让他的提示符更像 bash :

prompt='%1/ %(!.#.$) '

另外,在这里可以找到 Phil 的 zsh 的提示符设置,这是一个很 cool 的提示 符,他还详细讲解了他设置这个提示符的脚本,仔细看还可以学到很多 zsh 相 关的知识。

与 TRAMP 配合使用

Emacs 里面通过 TRAMP 登录到 zsh 的机器上的时候会挂起,因为无法正确 地识别出命令行提示符。

一种解决办法

zsh 有额外的默认打开的功能,让 TRAMP 不能正确处理。额外的功能由 prompt_crprompt_sp 两个变量控制。 prompt_cr 打开的话,在输出提示符 之前会输出回车符,这是多行编辑所需要的,但是这样有时候如果上一个命令的 输出最后一行没有换行的话就会被“吃掉”,例如:

$ setopt prompt_cr
$ setopt No_prompt_sp
$ echo -n "foobar"
$

看到 "foobar" 已经被吃掉了,但是如果同时打开了 prompt_sp 变量,则会在 未换行的那一行末尾打印一个反色粗体的 % (或者是 #)并打印适量的空格让提 示符延伸到下一行。这个功能本来是不错的,但是在 Emacs 里面用 M-x shell 得到的 shell 里面不好用,而且多余的 % (或者 #) 会影响 TRAMP 识别提示 符,造成 TRAMP 不能正常工作。要解决这个问题的办法也是比较简单的,因为 EmacsTRAMP 使用的 TERM 变量都是 dumb ,只要在 TERMdumb 的时候 关掉这两个选项就好了,在 ~/.zshrc 里面加入:

可是这样仍然无法使用 TRAMP 的 sudo 方式来访问本地文件,也不知道是怎么 一回事情,这个是很郁闷的。这个问题暂时还没有解决,只有先用 bash 作为登 录 shell ,然后查看 TERM 变量,如果是 linux 或者 screen 的话就运行 zsh ,因为 linux 是在虚拟终端里面的 TERM 值,而 screen 是我在 X 下运行 urxvt 里面 Screen 里的 TERM 值,其他情况下通常都不需要 zsh 。

另外,在 Emacs 里面用 M-x shell 得到的终端会设置 EMACS 变量 ,在这里面 可以把 zle (Z-Shell Line Editor) 禁用掉。

~/.bashrc 里面写

~/.zshrc 里面写

更好的解决办法

后来我在 Emacs 的邮件列表里面问了,原来 zsh 同时使用左右两个提示符,所 以会造成问题,现在登录 shell 仍然使用 zsh ,只需要在 ~/.zshrc 里面加入

就可以了。

单词分界

zsh 的命令行编辑器默认用起来不是很方便,特别是在编辑路径的时候,比如输 入 /tmp/foo/bar ,再用 Alt+Backspace 来删除一个词的时候会把整个路径全 部都删除掉,这是因为 / 被当作单词的一部分的原因,而通常我们更习惯的是 每次删除一个目录名。这可以通过修改 WORDCHARS 变量来实现,这个变量是一 个表示除了字母和数字之外还有哪些符号是一个单词可以包含的,默认值是

?*_-.[]~=/&;!#$%^(){}<>

可以看到里面包含了 / ,只要定制为自己需要的值就行了,例如,从中去掉 /. 要显得更习惯一点。

WORDCHARS='*?_-[]~=&;!#$%^(){}<>'

键绑定

zsh 可以使用 bindkey 命令来进行键绑定,快捷键的语法里面一些常用的特殊 字符的表示方法是:

\e\E Escape
\M[-]X 就类似于 EmacsM-X
\C[-]X^X 类似于 EmacsC-X

例如,zsh 的命令行编辑器也实现了 Emacs 的 kill-ring 的功能,我习惯于把 set-mark-command 绑定到 M-<SPACE> 上面,在 zsh 里面为了迎合这个习惯, 我可以这样写(我一直使用 Alt 作为 Meta 键):

bindkey '\e ' set-mark-command

这样就很舒服了,可惜 zsh 里面不能像 Emacs 那样高亮显示当前的 region , 不过仍然有 Emacs 的那个 C-x C-x 交换 mark 和 pointer 的功能,因此查看 当前 region 还是很方便的。

交互使用

通配符

除了支持经典的通配符之外,如果设置了 EXTENDEDGLOB ,那么 zsh 还支持一 些扩展的通配符:

否定匹配

^ 可以用于取接下来的模式的否定,例如:

% ls -d ^*.c
Makefile   file.pro   link       morestuff  run2       run303
bar.o      foo        main.h     pipe       run234     stuff
file.h     foo.o      main.o     run123     run240     sub
% ls -d ^*.*
Makefile   link       pipe       run2       run240     stuff
foo        morestuff  run123     run234     run303     sub

整数范围匹配

<x-y> 可以匹配在 xy 范围内的整数:

% ls run<200-300>
run234 run240
% ls run<-200>
run 123 run2
% ls run<->
run123 run2 run234 run240 run303

分组功能

% ls (foo|bar).*
bar.o foo.c foo.o
% ls *.(c|o|pro)
bar.o file.pro foo.c foo.o main.o q.c

递归匹配

**/ 用于打开递归匹配。

% ls -d **/f*bar
footbar  stuff/fbar  stuff/fbar/foobar  stuff/xxx/foobar
% ls -d stuff/**/f*bar
stuff/fbar  stuff/fbar/foobar  stuff/xxx/foobar
% ls -d fo*bar
footbar
% ls -d **/fo*bar
footbar  stuff/fbar/foobar  stuff/xxx/foobar

去除特例

使用 ~ 可以从模式中去除不想要的匹配。

% ls *.c
foo.c foob.c bar.c
% ls *.c~bar.c
foo.c foob.c
% ls *.c~f*
bar.c

类型限定

在模式后面可以使用圆括号加上限定符限定匹配的类型,限定符包括:

他们可以组合使用,还可以哟个 ^ 来表示否定,例如 *(W^@)*(x^@/) 等。

命令替换

如同 bash 一样,zsh 也支持 ``$() 这样的命令替换用于使用程序的输出 作为命令行。对应于 bash 那方便的 <() 命令,也可以在 zsh 中使用:

grep foo <(ls -l)

就是把 ls -l 的输出作为一个命名管道,再把这个管道的名字(事实上,是指向 这个管道的一个符号链接的名字)作为参数传递给 grep 程序,不过,这样也有 一些不足的地方,例如下面的命令就不能成功:

mail -f <(zcat ~/mail/oldzshmail.Z)

因为 mail 不能把一个管道打开作为邮件箱。不过,zsh 还提供了另外一个类似 的命令 =() ,它不是创建管道,而是创建一个临时文件,并在程 序运行结束之后删掉临时文件。

别名

zsh 有和其他 shell 一样的别名,除此之外,zsh 还有一种“global alias”, 它可以出现在命令行的任何地方,通常用于为一些常用的比较长的名字、地址等:

alias -g gg='http://www.google.com'
w3m gg

不过这个功能不能被滥用了,否则会造成一片混乱的局势。

历史

zsh 可以使用 csh 那样的历史替换功能。另外,还有一个内置的 fc 命令可以 对历史里面的命令行进行编辑之后再执行,默认的编辑器是 vi ,不过可以通过 变量 FCEDIT 来更改。另外,给 fc 传递 -e 选项可以指定 ename 参数用于覆 盖 FCEDIT 的值,如果 ename- 的话,则不调用编辑器编辑,这个功能是很 常用的,所以还有一个内置命令 r ,就等价于 fc -e -

$ echo foobar
foobar
$ echo hello world
hello world
$ r world=zshell
echo hello zshell
hello zshell
$ fc -l
    1  echo foobar
    2  echo hello world
    3  r world=zshell
    4  echo hello zshell
$ r bar=baz 1
echo foobaz
foobaz

重定向

zsh 支持一种扩展的重定向语法,由 multios 选项控制(默认是打开的)。这样 可以避免使用 tee 程序,例如,在通常的是 shell 里面需要这样来完成重定向 到多处的任务:

echo foo | tee file1 > file2

现在在 zsh 里面只需要这样

echo foo > file1 > file2

另外,对于输入重定向也有这个功能,例如

prog < file1 < file2

等价于

cat file1 file2 | prog

另外,管道也相当于一个重定向,并且管道的输入先于输入重定向,从下面这个 测试可以看出来:

echo then this > testfile
echo this first | cat < testfile

命令行编辑

zsh 有一个强大的命令行编辑器,可以模拟 Emacs 或者 vi 的快捷键方式,还 支持多行编辑。

另外,还有一些非常人性化的功能,比如,你正输入了一个命令,突然忘记了要 用哪些文件名作为参数,这个时候你不想直接退出,因为这样你下一次就要再把 这个命令整个输入一遍,虽然是不完整的命令,这个时候可以使用 ESC-Q ,把当 前输入的不完整的命令放入 buffer stack 中,在下一次命令提示符显示出来的 时候这个不完整的命令将会被显示出来,处于待编辑状态。

更特殊的情况是你忘记了命令的参数,需要 man 一下,这个时候直接使用=ESC-H= 即可把当前命令放入 buffer stack 再运行 run-help prog 命令( run-helpman 的一个别名)。

还有一个有趣的命令是 ESC-A ,他直接执行当前命令,并将其放入 buffer stack 中,这对于执行一系列的类似的命令非常有用。

另外, ESC-' 命令用于创建当前命令行的引用,有时候是非常方便的,例如, 你要输出一句

don't do that; type 'rm -rf \*', with a \ before the *.

只需要直接输入这一句话,然后按下 ESC-' ,就会自动转换成

'don'\"t do that; type '\"rm -rf \*'\", with a \ before the *.'

再跳到行首,输入 echo 就可以了。

脚本编程

使用 Emacs 编辑 zsh 脚本

Emacs 已经支持 zsh 脚本的编辑,会自动识别 #!/bin/zsh 并进入 shell-script-mode 。在 shell-script-mode 里面可以使用 C-c : zsh 来得到 针对 zsh 的编辑功能支持。