无论您开什么车,您一定会喜欢高速的大马力汽车。没有什么车比配备五轮辐镀镁轮毂、宽轮胎、黑色油漆、大量镀铬部件和强劲的大马力引擎的 1969 年产 Dodge Charger 更给力了。它是一部杰作,就像 Rodin 的思考者一样,是一部经典之作。
UNIX® shell 也是一部经典之作。只要经过一番改造,它也可以像高速的大马力汽车一样强劲有力。您可以将这个改造过程称为 “极限 shell 改造”。我觉得这很酷!
它由活动部件组成
在闪动的提示符背后,您的 shell(比如 Bash、Z shell 或其他 shell)有许多活动部件,您可以增强、改进和调整它们。以下是对这些部件的总结(其中许多已在前面的专栏文章中详细讨论过):
- 别名(alias)是缩写的命令名和命令行短语。例如,命令 alias ll 'ls -hlt' 将一个常用的命令行缩减为两个字母,从而使要输入和要记住的东西变得更少。别名通常只是单纯的缩写。但是,与 shell 脚本或 shell 函数一样,别名也可以接受参数。命令 alias print 'lpr -h -Pps5 \!*' 可以将 \!* 替换为命令行参数。因此,print manual.ps schematic.ps form.ps 会展开为 lpr -h -Pps5 manual.ps schematic.ps form.ps。
- 环境变量跨命令(甚至是生成新进程的命令)保存设置。根据惯例,一些环境变量有特殊含义:命令和应用程序认为 PRINTER 表示首选输出设备,而 EDITOR 和 PAGER 分别表示用来修改和显示文本的文本编辑器和查看器。其他环境变量与不同的实用程序有关。PS1 就是这样一个例子,它告诉 shell 将显示哪些内容作为最初提示。另一个重要的环境变量是 shell 的 PATH,它列出了搜索可执行文件的目录。(要想了解特定应用程序或命令中可以识别哪些环境变量,请参阅其手册页中的 "Environment variables" 部分。)
- 函数也可以从命令行调用,它们填补了别名与完全成熟的脚本之间的空缺。例如,如果需要操纵参数或应用逻辑,别名很可能不够用;但是,使用完整的脚本可能太过浪费。因此可以通过组合使用函数来实现累积的效果。创建函数需要一定的编程经验,但是并不不是很麻烦。
- shell 选项控制了 shell 的行为。shell 选项通常因 shell 而异,功能丰富的 shell(比如 Bash 或 Z shell)可能有数百个可调整参数。(Z shell 有专门对配置选项进行解释的手册页 zshoptions。)例如,如果已经启用了 Z shell 选项pushd_ignore_dups,pushd 就不会将已存在的目录压入目录堆栈。您可以花几小时研究选项,寻找想要的组合,然后执行 set(Bash) 或 setopt 并将输出重定向到某个文件中。可以将捕捉到的部分或所有设置复制到启动文件中,这样就可以在每次打开 shell 时重建自己的工作空间。
除了这些部件之外,还可以改变 shell 的外观。美元符号 ($) 提示符可以显示为不同的颜色,以反映当前工作目录,甚至是天气情况。只要可以用命令捕捉到信息,就可以将它们显示在提示符中。
修改提示符
与 shell 中的其他操作一样,环境变量可以控制每次 shell 显示提示符时应显示的内容。与命令行本身一样,可以在呈现提示符内容时对变量 PS1(或 "prompt string level 1")进行解释。PS1 可以包含其他 shell 和环境变量、置换命令运算(通过反引号)和特殊的字面值。下面是一个简单的示例:
$ export PS1="\u@\h \w >"strike@nostromo ~ > whoami; hostname; pwdstrikenostromo/home/strikestrike@nostromo ~ > |
最初,提示符是简单的 $ 符号。如果将环境变量 PS1 设置(使用 export 而不是 set)为 \u@\h \w >,则会显示您的用户名 (\u)、字面值 @、主机名 (\h)、当前工作目录 (\w) 和字面字符串 >。
其他特殊的字面值包括 \t(采用 24 小时制的时间格式)、\d(采用周、月、日格式的日期)和 \!(shell 历史记录编号)。如果 shell 窗口打开的时间较长,可以用 !nnn 快速重复前面的命令,其中的 nnn 是命令提示符中显示的编号。
strike@nostromo ~ > export PS1="\! $ "776 $ find . -name ......999 $ !776find . -name ...1000 $ |
当您的行为类似于超级用户(即 root 用户)时,shell 提示符会变成 #,您可能对此感到惊奇。默认情况下,标准的提示符实际上是特殊操作符 \$。如果您的有效用户 ID 是 0,则会使用 # 作为提示符;否则会使用 $。
还有用来改变颜色的缩写。下面是一个示例:
strike@nostromo ~ > export PS1='$ '$ blue='\e[0;34m'$ none='\e[m'$ export PS1="$blue\u@\h$none\w>"<span style="color: blue;">strike@nostromo </span>~ ><br /> |
十分笨拙的缩写 \e[0;34m 表示可以启用蓝色。\e[m 可以将显示颜色恢复为 shell 窗口的默认颜色。这两个编码均放在单引号内,以防止 shell 篡改有特殊含义的字符。这个示例还说明可以在提示符中使用变量。在解释命令行时,会展开变量 blue 和 none,提示符被设置为展开变量而生成的字符串。如果希望在每次显示提示符时都动态展开变量,则必须在设置提示符时对它的解释进行转义。请参见以下示例:
<span style="color: blue;">strike@nostromo </span>~ >export PS1="\$somevar $ "$ somevar="hello"hello $ |
\$somevar 可以对美元符号进行转义,避免在设置提示符的命令中对该变量进行解释,而是在显示提示符时对其进行解释。如果其他命令改变了 somevar 的值,提示符将显示为新的值。
正如前面提到的,还可以在提示符下调用任何命令或函数。只需使用反引号 (``)。例如,在使用 Ruby Version Manager 在不同的 Ruby 语言版本和解释器之间切换时,您可能想知道当前使用了哪种 Ruby 二进制代码。有两种实现此操作的方法:
hello $ export PS1="(`which ruby`) \w $ "(/Users/strike/.rvm/rubies/ruby-1.9.2-p0/bin/ruby) ~ $ |
第一种方法使用 which 在当前的 PATH 中寻找第一个 Ruby 可执行文件。后一种方法实现相同的目标,它展示了置换命令运算可以有多么复杂。如果将提示符设置为以下字符串:
"(`rvm info | grep 'ruby:' | grep bin | cut -f2 -d: | tr \\" ' '`)" |
会得到相同的结果,尽管没必要采用这么复杂的方式。
顺便说一句,您可能想知道 PS1 为什么是 "prompt string level 1"。是否存在其他提示符级别?是的,存在针对 PS2、PS3 和 PS4 的环境变量,当打开新的块时,会出现这些提示符。下面是一个示例:
输入 for i in [A-Z]* 并按回车之后,shell 会显示 PS2 提示符(默认提示符是 >),这表明您目前位于 for 循环主体中。换句话说,您现在 “嵌套” 在循环中,处于更深的一级。如果用 done 结束循环,第一级提示符会重新出现。
$ for i in [A-Z]*> do> echo $i> doneGemfileGemfile.lockREADMERakefile$ |
实际上,只要在前面的提示符中没有完成命令行,就会出现新提示符。这解释了不匹配的单引号或双引号会导致出现新提示符的原因。shell 会提示您继续输入前面已经开始的命令行。
在提示符中嵌入信息是跟踪状态(比如当前主机、工作目录等等)的好方法。创建您喜欢的提示符之后,将 shell 启动文件分发给您的所有帐号。如果您与大多数现代用户一样,那么您的屏幕上会有许多远程 shell 窗口,提示符可以帮助您区分它们。可以创建根据主机改变提示符颜色的函数。
modder 和 rodder
一些用户将定制 shell 当作一种消遣,您可以在网上找到很多这方面的灵感和源代码。有两个软件包非常值得一提:Oh My Z Shell! 和 Bash It!。前者适用于 Z shell,后者适用于 Bash。它们都是 shell 修改的集合,包括一些补充、主题(颜色和提示符)、函数和现成的 "dot" 文件。它们都是开放源码的,可以从 github 获得它们。让我们来试一下 Oh My Z Shell!(或简称为 OMZ!)。要想使用它,您必须安装 Z shell 4.3.9 或更高版本。
可以使用 wget 安装这个包,并自动将您的 shell 改为 Z shell,参见清单 1。
清单 1. 安装 shell 并将它改为 Z shell
$ wget http://github.com/robbyrussell/o ... er/tools/install.sh -O - | sh
...
Cloning Oh My Zsh...
Cloning into /Users/strike/.oh-my-zsh...
remote: Counting objects: 1312, done.
remote: Compressing objects: 100% (750/750), done.
remote: Total 1312 (delta 796), reused 944 (delta 520)
Receiving objects: 100% (1312/1312), 153.63 KiB, done.
Resolving deltas: 100% (796/796), done.
Looking for an existing zsh config...
Using the Oh My Zsh template file and adding it to ~/.zshrc
Copying your current PATH and adding it to the end of ~/.zshrc for you.
Time to change your default shell to zsh!
Changing shell for strike.
Password for strike:
chsh: /usr/bin/env zsh: non-standard shell
|
还可以手工安装这个工具包。如果您在运行另一种 shell,只是想试试 Z shell,那么手工安装可能会更好。请使用 Git 复制这个工具包,然后运行 zsh:
$ git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh$ cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc$ zsh |
您应该会看到与图 1 相似的屏幕和提示符。OMZ! 的默认主题称为 "robbyrussell",由 OMZ! 的管理者命名。您可以将它更改为 ~/.oh-my-zsh/themes 中列出的任何主题。更改主题的方法是,打开 ~/.zshrc 文件,将 ZSH_THEME 变量设置为主题文件的基本名称。例如,要想使用 cloud.zsh-theme,则应该设置 ZSH_THEME=cloud。
图 1. 更改 shell 的主题
您可能会注意到,许多主题的输出状态信息显示在提示符中和提示符的最右边。例如,Clean 主题在最右边输出当前时间。与硬件屏幕不同,屏幕模拟器的底部通常没有状态条,但是可以使用提示符右边的区域提供动态反馈。还记得可以在提示符中使用 \e[ 编码设置颜色吗?有一组这样的 “转义” 可以将光标移动到窗口中的不同位置。另外,目前的 UNIX 系统并没有使用神秘的符号和数字,而是使用 tput 按名称或用途查找和产生转义。Z shell 使用这种方法提供 RPS1 和 RPS2,它们分别表示初始行和后续行右边的提示符。
除了通过主题控制颜色和提示符之外,OMZ! 还提供了一些插件,这些插件将同类的函数和特性集中在一起。例如,如果您使用 Git 进行源代码控制,可以启用 Git OMZ! 插件,它会在提示符中增加 Git 状态。请打开 ~/.zshrc 文件,然后编辑插件行,在其中包含 git>。现在,当切换到一个 Git 存储库时,提示符会反映该状态。例如,图 2 显示了 Clean 主题和一个存储库,其中有包含一些尚未提交的修改。
图 2. Clean 主题和存储库
提示显示了当前的分支 ("rubricmods"

和一个红色的 X,这表示当前存储库是 “脏的”,即已经修改了本地文件,但还没有提交。提交修改之后,X 会消失。Git 插件还为常用的 Git 命令组合创建别名,为 Git 选项提供上下文敏感的命令补全特性。来试一下:如果输入
git branch,然后按 Tab 键,Git 插件就会列出可用的分支,以及用于 Mac OS X、Ruby on Rails 开发、MySQL 等的其他插件。一些插件很简单,可能只提供一个函数或几个别名;其他插件的功能可能更丰富。
Dirpersist 是说明 OMZ! 插件的用途的好例子。Dirpersist 跨 shell 调用存储并恢复目录堆栈,因此可以保留重要的状态,让您能够回到原来离开的位置继续工作。要使用它,请在 ~/.zshrc 文件中添加这个插件。这个插件的源代码很简单,参见清单 2。
清单 2. OMZ! 插件源代码
#!/bin/zsh
#
# Make the dirstack more persistent
#
# Add dirpersist to $plugins in ~/.zshrc to load
#
#
$zdirstore is the file used to persist the stack
zdirstore=~/.zdirstore
dirpersistinstall () {
if grep 'dirpersiststore' ~/.zlogout > /dev/null; then
else
if read -q \?"Would you like to set up your .zlogout file for
use with dirpersist? (y/n) "; then
echo "# Store dirs stack\n\
# See ~/.oh-my-zsh/plugins/dirspersist.plugin.zsh\ndirpersiststore" >> ~/.zlogout
else
echo "If you don't want this message to appear, remove dirspersist from \$plugins"
fi
fi
}
dirpersiststore () {
dirs -p | perl -e 'foreach (reverse <STDIN> {\
chomp;s/([& ])/\\$1/g ;print "if [ -d $_ ]; then pushd -q $_; fi\n"}' > $zdirstore
}
dirpersistrestore () {
if [ -f $zdirstore ]; then
source $zdirstore
fi
}
DIRSTACKSIZE=10
setopt autopushd pushdminus pushdsilent pushdtohome pushdignoredups
dirpersistinstall
dirpersistrestore
# Make popd changes permanent without having to wait for logout
alias popd="popd;dirpersiststore"
|
这个插件包含三个函数、几个 shell 选项和 popd 的别名(为每个弹出操作保留目录堆栈)。这个插件还对其环境进行初始化,创建存储目录堆栈的位置,通过修改 Z shell 退出文件 ~/.zlogout,在用户退出时保存目录堆栈。您应该可以看出,创建新的插件很容易,还可以围绕任何 shell 命令集来构建插件。
如果您喜欢 OMZ!,但是您使用的是 Bash shell,那么试一下 Bash It!。它的灵感来源于 OMZ!,具有相似的功能。Bash It! 还为 Subversion 和 nginx 提供了插件。
发动引擎吧!
研究 OMZ! 或 Bash It! 的内部机制,了解定制 shell 的一些技巧。通过在网上搜索 "dot files",还能找到一些充满智慧的、新奇的东西:许多 UNIX 爱好者在网上公开了他们的 shell 配置供他人借鉴。还有一点值得注意:应该对 dot 文件采用某种版本控制机制。有了存储库,您便可以更放心地试验各种修改和其他 shell。如果犯了错误,可以恢复以前的版本,还可以为不同的 shell 或场景保留相应的变体。shell 非常灵活,因为一种配置不一定适合所有场景。当您有了一套很酷的 dot 文件之后,请与别人分享。公开 dot 文件虽然不能使您成为《速度与激情》的下一部影片中的明星,但很可能会提高您在爱好者中的声誉。
关于作者
![]()
Martin Streicher 是一位 Ruby on Rails 的自由开发人员和 Linux Magazine 的前任主编。Martin 毕业于 Purdue University 并获得计算机科学学位,从 1986 年起他一直从事 UNIX 类系统的编程工作。他喜欢收集艺术品和玩具。
http://www.ibm.com/developerworks/cn/aix/library/au-spunix_shellmakeover/index.html