级别: 初级
Matt Chapman, 软件工程师, IBM Hursley
2001 年 2 月 01 日
根据 Matt Chapman 的观点,Z shell 可以提高 shell 的交互效率。现在正是将这个秘密公开的时候了!在本文中,不仅介绍了 Z shell,而且还探讨了比其它 shell(尤其是 Bash)优越的地方。
提高交互式 UNIX shell 的体验
大多数 Linux 的开发人员和用户都或早或晚地接触过 UNIX shell。其中比较典型的是 Bash shell,有时也会是 C shell,或 Tcsh,Korn shell(IBM AIX 操作系统的缺省 shell)。但它们都很少能象 Z shell 一样可以提高 shell 交互效率并节省您的输入!对于那些不愿意学习“全新 shell”的人而言,值得注意的是 Zsh 是最接近所有其它 shell 的超集,所以您可以直接开始了。
运行 Z shell
Z shell ("Zsh") 命令通常在系统中的 /bin/zsh 中。如果在安装 Linux 时没有安装它,那么可以在大多数发行版的安装磁盘中找到它。当然也可以从 http://www.zsh.org 下载。顺便提一下,它并不只限于 Linux - 其源代码可以在大多数 UNIX 平台(包括 AIX)上成功地编译,并且还有许多二进制文件。
在本文中涉及的所有 shell 命令和设置选项都可以放在 ~/.zshrc 文件中,这样就可以在每次启动交互式 shell 时使用它们了。您也可以建立一个既可以在交互式 shell 中使用又可以在非交互式 shell 中使用的 ~/.zshenv 文件(例如 shell 脚本)。欲了解 Zsh 启动过程详情,请参阅 Zsh 文档。
命令行编辑
可以缺省地使用光标和退格键来移动和编辑输入行,就象在 Bash shell 中一样。也可以使用命令 "bindkey -e" 来启用 Emacs 绑定。这些键顺序的示例包括 Control-A(跳至行首)和 Control-K(删除到行尾)。当然也可以使用 "bindkey -v" 命令来绑定 Vi。
Zsh 具有多行编辑模式,这特别适合于短小的 shell 脚本,如下例所示:
zsh% for x in 1 2 3
for> do
[we're in the middle of a "for" command so
for> echo $x
Zsh changes the prompt to remind us]
for> done
[after entering this, Zsh knows we're done
1
so it can run the command]
2
3
zsh% <Cursor-Up>
[or Control-P with Emacs bindings]
zsh% for x in 1 2 3
[the whole four line script appears, and
do
you can then move all through it, editing
echo $x
as required]
done
|
在这里将不再详述 shell 脚本,但值得一提的是,对于单行的 "for" 循环,事实上可以忽略 "do" 和 "done" 部分。
所键入的命令可以储存在历史记录中,而且有很多方法可以访问它,例如在上面示例中的 Cursor-Up,它就允许您滚动浏览以前输入的命令(当然用 Cursor-Down 也可以的)。另外一些有用的小窍门是 "!!",它可执行前一命令,和 "^old^new",它将前一个命令中的 "old" 替换成 "new" 之后再执行该命令。这尤其适用于当您输错时,或仅仅想稍微修改一下命令 -- 这要比回想此命令再直接编辑它快得多。
Zsh 甚至还有一个可以校正各种错误的拼写检查器。但如果它把您的命令 "纠正" 成您事实上并不想要的意思的话就会很危险,所以在它检测校正后总是会提示您。另外此功能缺省设置为关闭,您必须用 "setopt CORRECT" 启用它。以下是一个示例:
zsh% setopt CORRECT
[enable option]
zsh% chomd u+x file.sh
[we've mistyped "chmod" here]
zsh: correct 'chomd' to 'chmod' [nyae]? y
[Zsh detects this and asks if we want
to correct it]
zsh%
[if yes, then the corrected command
is executed]
|
TAB 键的魔力
最能干的 shell 用户是一个“懒惰”的家伙!您应该用尽可能少的输入来完成您想要的东西。通过本文您会发现 Zsh 对此很擅长。
文件名的完成并不是一个新的概念;许多 shell 可提供基本的完成格式,例如 Bash shell 可使用 TAB 键,如下例所示:
bash$ ls
another_file
this_is_a_file_with_a_long_name
bash$ more th<TAB>
[press the TAB key]
bash$ more this_is_a_file_with_a_long_name
[the shell magically fills in
the rest!]
|
按下 TAB 键,shell 会为您完成文件名。考虑一下您刚才所节省的所有输入!(或者对于用鼠标从以前列表目录中剪切和粘贴文件名 - 考虑一下将手挪向鼠标然后再放回键盘所节约的时间。)
当然,它一点也不神奇,只是减少一些烦琐的工作 - 您正在输入一个以 "th" 开头的文件名,在当前目录下仅有一个文件是以 "th" 开头的, 因而这有可能就是您要的那一个。所以,让 shell 为您做这件事吧!
以上的示例是用 Bash shell, 当然 Zsh 也可以做到这一点!而且还可以做到更多。让我们来看一下另外一个示例:
bash$ ls
another_file
this_is_a_file_with_a_long_name
this_is_another_file
bash$ more th<TAB>
bash$ more this_is_a<TAB>
[beeps, try pressing TAB again ]
bash$ more this_is_a
this_is_a_file_with_a_long_name
[the shell gives you a list of
this_is_another_file
possible completions]
bash$ more this_is_an<TAB>
[type more of the required filename]
bash$ more this_is_another_file
[the shell can then complete it]
|
正如您从这个示例中看到的,当有不止一个匹配的时候,它就会变得复杂一些。Bash shell 将尽可能多的完成文件名,然后停止。然后您就得输入文件名较多的字符,这样,在将完成它之前就是唯一的了。Zsh 缺省的情况与之很类似,但是通过设置几个选项,我们可以使之友好些。我建议将 "setopt AUTO_LIST AUTO_MENU" 放到您的 ~/.zshrc 文件中,或者在命令行中输入它。我们可以用 Zsh 尝试下面的示例:
zsh% more th<TAB>
zsh% more this_is_a
[the shell completes as much as it can
this_is_a_file_with_a_long_name
AND displays all the matching names]
this_is_another_file
zsh% more this_is_a<TAB>
[press TAB again]
zsh% more this_is_a_file_with_a_long_name
[completes to the first match]
zsh% more this_is_a_file_with_a_long_name<TAB>
[press TAB to rotate through all matches]
zsh% more this_is_another_file
|
shell 在您第一次按下 TAB 时就显示所有的匹配,然后,您就可以不断地按 TAB 键来进行选择。您可以象在 Bash 的示例中那样输入较多的文件名字母,但是如果匹配的数量很少,则循环浏览它们,要比计算出需要输入哪个或哪些字符以使文件名唯一要更容易。
Zsh 还可以提供可编程的完成。这表示它完成的不仅仅是个文件名 -- 事实上是任何事,从命令到别名到环境变量都可以。同时所有这些可编程的内容大多数都作为缺省启用的,这样您就不需要做任何配置就可从中受益(尽管我们也会尝试几个简单的定制)。
让我们从命令完成开始,它也可以节省输入,在您不能准确记起命令名称的时候确实非常有用。简单地输入命令的第一部分,然后按下 TAB 键,您就可以得到尽可能完整的列表就象文件名一样。shell 知道输入的第一部分将是命令而不是文件名,所以它将对照着命令,包括内部命令和外部命令 (使用 PATH 变量定位),还有 shell 函数和别名来完成。值得一提的是 "which-command" 函数在这里很有用。在完成之后,或者输入命令后,运行缺省设置绑定到 Esc-?(按下然后松开 Escape,然后按下 "?")的函数。它简单地告诉您命令在哪里,就好象您在命令后输入 "which" 一样。这里有一个示例:
zsh% xf<TAB>
[complete against commands]
xf86config xfd xfindproxy xfontsel xfs xfwp
zsh% xfo<TAB>
[type another letter to make it
unique, or TAB repeatedly to cycle]
through completions]
zsh% xfontsel
[command has now been completed]
zsh% xfontsel<Esc-?>
[use Esc-? shortcut]
zsh% which-command xfontsel
[this shows expansion of shortcut]
/usr/X11R6/bin/xfontsel
zsh% xfontsel
[afterwards we're back to the command itself]
|
另外一个真正对您完成有用的是环境变量。任何时候您都可以使用 "$" 符号来引用变量,shell 将参照此范围内所有变量来完成。所以如果您先执行 "echo $D",然后按下 TAB, 则如果您没有其它也可与之匹配的变量,它将以 "$DISPLAY" 完成,(如果有的话,您会得到一个和前面一样完整的列表 -- 无论您匹配什么,“完成功能”都以同样的方式工作)。
正如前面所提及的,您可以完全用编程来完成,以确定完成什么及何时完成。这都是由 "compctl" 命令控制的。(Zsh 的新 beta 版本是建立在先前的 3.1.6 版本上,也包括一个新的我们在此不会涉及的机制,因为它们的使用并不广泛。)这是很复杂的领域,但是大多数都可以通过几个短的 "compctl" 行来得到。
首先,也是 "compctl" 最有用的功能之一,是基于上下文对文件名的完成进行过滤。在给定的情形下,我们可以减少相关匹配的数量,从而可增加唯一匹配的机会,因此我们就可以完整得到整个输入的内容。让我们以 Java 的使用为例。当我们想编译 Java 源文件时,我们通常会执行 "javac source.java"。源文件总是有一个 ".java" 的扩展名,所以我们可以使用此信息来限制 Zsh 在此情况下所做的匹配。以下是实现方法:
zsh% ls
MyPackage TestProgram.class TestProgram.html TestProgram.java
zsh% javac T<TAB>
[we want to recompile source file]
TestProgram.class TestProgram.html TestProgram.java
zsh% javac TestProgram.
[only matches as far as this]
zsh% javac TestProgram.j<TAB>
[so we have to type another letter]
zsh% javac TestProgram.java
[filename is finally complete]
zsh% compctl -g '*.java' javac
[restrict matches when command is javac]
zsh% javac T<TAB>
zsh% javac TestProgram.java
[only one match now, completes immediately!]
zsh% ls MyPackage
[what if we have a package directory?]
Main.class Main.java
zsh% javac M<TAB>
zsh% javac M<TAB>
[oh dear, no match for directory]
zsh% compctl -g '*.java' + -g '*(-/)' javac
[expand command to match directories too]
zsh% javac M<TAB>
zsh% javac MyPackage/
[directory now completes]
zsh% javac MyPackage/<TAB>
zsh% javac MyPackage/Main.java
[only one match inside directory]
|
上面的示例显示我们得如何精简我们的 "compctl" 行来说明目录,否则在我们试图完成目录名称的时候,shell 就会发出蜂鸣声。但是我们就会有个功效强大的机制 -- 可以仅仅按下三个键:"M",然后按 TAB 键两次,就能够输入 "MyPackage/Main.java"。
这里有一些更多有关 ~/.zshrc 文件中 "compctl" 行的示例,它可以使您入门:
compctl -g '*.gz' + -g '*(-/)' gunzip gzcat
compctl -g '*(-*)' + -g '*(-/)' strip
compctl -g '*.ps *.eps' + -g '*(-/)' gs ghostview psnup psduplex ps2ascii
compctl -g '*.dvi' + -g '*(-/)' xdvi dvips
compctl -g '*.xpm *.xpm.gz' + -g '*(-/)' xpmroot sxpm pixmap xpmtoppm
compctl -g '*.fig' + -g '*(-/)' xfig
compctl -g '*(-/) .*(-/)' cd
compctl -g '(^(*.o|*.class|*.jar|*.gz|*.gif|*.a|*.Z))' more less vi
compctl -g '*.html' + -g '*(-/)' appletviewer
|
这些行中的大多数都无需解释,因为它们与 "javac" 示例很相似,尽管您可能对这些语法不太熟悉,如与可执行文件相匹配的 "*(-*)" 和逻辑非操作 "(^..)"-- 在上面的行里,命令中的许多扩展名,象 "more" 和 "vi" 都可以取消,因为这些通常只是文本文件中的操作。
这里有个略微复杂些的示例,它使用定制函数来生成该完成。回到我们使用过的 "javac",在编译完源代码之后我们可能想用命令 "java TestProgram" 来运行它。如果使用缺省的文件名完成,我们只会得到 "java TestProgram." 和一个匹配的列表。然后我们除去 "." 就可以了。但是 Zsh 甚至可以比这做得还要好。您给 "compctl" 一个可返回匹配列表的函数名,匹配列表在名为 "reply" 的变量里,在必要时也可调用该函数,并将其输出作为匹配列表。所以我们在当前目录里可以定义列出所有 ".class" 文件的函数,然后除去 ".class" 的扩展名,返回这个新列表。这里有一个这样的实现及其用法:
zsh% ls
MyPackage TestProgram.class TestProgram.html TestProgram.java
zsh% echo $(ls *.class)
[use command substitution to generate
TestProgram.class
list of .class files]
zsh% echo ${$(ls *.class)%.class}
[combine with parameter expansion to
TestProgram
remove .class extension]
zsh% myclasslist () { reply=(${$(ls *.class)%.class}) }
[define our own shell function]
zsh% compctl -K myclasslist java
[use this function with the java command]
zsh% java <TAB>
[now we can type "java", followed by a
space, then press the TAB key...]
zsh% java TestProgram
[...there is only one .class file in
the current directory, so everything is
completed for us!]
|
Shell 提示
在到目前为止的示例中,我们一直在使用没有包含一些有用信息的 "zsh%" 提示符。无需多说,Zsh 是一个可完全配置并可传达多种有用信息的提示符。缺省的提示符是由 PROMPT 变量控制的。这里有一些示例:
zsh% PROMPT='%/> '
[displays the current working directory]
/home/matt/tmp> cd /usr/bin
/usr/bin> cd
/home/matt>
/home/matt> PROMPT='%~> '
[use short form of current directory]
~> cd /tmp
[~ is used for home directory]
/tmp> cd
~> cd /usr/local/bin
/usr/local/bin> LBIN=/usr/local/bin
[you can give directories names
to shorten the prompt]
~LBIN> pwd
/usr/local/bin
~LBIN> cd
~> PROMPT='%m> '
[the first part of the hostname
(use %M for the full hostname)]
kheldar> PROMPT='[%!]> '
[current history event number]
[232]> pwd
/home/matt
[233]> cd /tmp
[234]> !232
[the event number is useful for
pwd
using history commands]
/tmp
[235]>
|
Zsh 同样支持右边提示符,它使用支持相同语法的 RPROMPT 变量。一些人发现可变长的左边提示符(例如显示路径名的时候)经常更改令人心烦,因为它意味着您开始键入命令的位置经常变化。但是能够一眼看出当前的目录是很有用的 -- 它总是实时保存输入的 "pwd"。所以这使得对于 RPROMPT 来说,'%~' 是个理想的设置。在提示符中有当前目录的另外一个问题,是当路径名变得很长的时候,就没有足够的空间供您输入命令。但您会很高兴地看到 Zsh 很有效地处理这种情况。如果您的命令与右边提示符十分相似,则提示符会自动消失,以留下多余的空间供您输入命令。另外,如果您再删除一定数量的命令时,提示符又会再次出现。
提示符的另外一个很有用的地方是使用有强烈的视觉效果的颜色。现在大多数的终端仿真器只支持有限的颜色(可以通过使用控制代码来控制这些颜色)。通常的换码序列是 "ESC-[31m",其中 "1" 指定前景颜色,可以是任何从 0(缺省)到 9 的任何数字。如果使用回显命令,换码可以使用 "\033" 进行输入,或在 Escape 键后按下 Control-V 键(引用 - 插入功能)直接将其插入 shell 中。它随后会作为 "^[" 出现,而您可以将光标移到它上面来检查它是否是单个字符 -- 它会一个一个地跳到下一个字符上。另一个需要注意的是需要告诉 Zsh 在提示符中哪些字符构成换码序列,否则就会假设它们都是可打印字符,光标的位置就会出错。这通过使用 "{%...%}" 语法来封装所有换码序列来完成。让我们用下面这个示例来说明以上内容:
zsh% echo '\033[31m hello \033[30m'
[see if we can change color -
"1" is red for us (actual colors may
hello
vary), and "0" is the default]
zsh% echo '^[[31m hello ^[[30m'
[try with escape sequences -
use Control-V, then Escape to
hello
give "^[" character]
zsh% PROMPT='^[[31mzsh%% ^[[30m'
zsh% pwd
[prompt has changed color
/home/matt
but cursor positioning is wrong]
zsh> PROMPT='%{^[[31m%}zsh%% %{^[[30m%}'
[use "%{...%}" to enclose each sequence]
zsh% pwd
[positioning is then correct]
/home/matt
|
如果您想象这样设置自己的缺省提示符,则需要进入 ~/.zshrc 文件中。有必要插入实际的换码 "^[",而不是这两个字符 "^"" 和 "["。在 Emacs/XEmacs 中可以使用 Control-Q, 然后 Escape,而在 vi/vim 中您可以使用 Control-V,然后是 Escape -,和 Zsh 一样。当然也可以使用八进制格式的控制码,"\033",但是 PROMPT 设置并不能够理解这个语法本身,所以您得使用 "echo" 和命令替换,如下例所示:
以这种方法使用左边和右边提示符组合使用颜色,可以提供很多可改善交互的视觉效果。使用左边提示符可帮助您专注于您接下来要输入命令的那部分屏幕,而且它还可以将每一个命令与输出分离。有这两种提示符就可以显示更多的信息,这样您就不需要输入一些常用的命令,例如 "pwd"。就我个人而言,我把短的主机名作为我的左边提示符,因为对于一个给定的登录,它有固定的宽度,它能够清晰地区分出连接到不同主机的终端,我把当前的工作目录作为右边提示符,并以不同的颜色标识。下例说明了这一点:
zsh% PROMPT=$(echo '%{\033[31m%}%m>%{\033[30m%}')
kheldar>RPROMPT=$(echo '%{\033[32m%}%~%{\033[30m%}')
kheldar>cd /usr/X11R6/bin ~
kheldar>
/usr/X11R6/bin
|
结束语
希望您喜欢这篇简短的 Zsh 教程。请亲自尝试这些示例来真正体会 Zsh。当然,我们仅是浅尝辄止而已。Zsh 已经开发了很多年(现在仍在继续),所以它的潜力几乎是无止境的。即使您仅使用了本文所涉及的一些功能,我希望您能够体会到 UNIX shell 环境的友好之处……
如果您已经被 Zsh 所折服,您可以通过设置 Linuxconf 面板中的 "User accounts",使之成为您缺省的 shell(或者如果您坚持己见,也可以编辑 /etc/passwd 来实现)。
参考资料
欲知 Zsh 的详情,请参阅它的帮助页面和浏览以下网站:
关于作者
|
|
|
Matt Chapman 于 1996 年毕业于 Warwic 大学的计算机科学系,他持有理科学士学位。他是位于英国 Hursley 的 IBM Java 技术中心的软件工程师,五年多来一直是 Linux 和 Zsh 的忠实用户。可以通过 mchapman@uk.ibm.com 与他联系。
|
|