shell 的历史
UNIX shell 至今存在的时间已超过 35 年,它现在仍然在发展壮大!它创始于 1971 年,这一年 AT&T 贝尔实验室的 Ken Thompson 创建了第一个 UNIX shell,它具有贴切的名称 Thompson shell。虽然 Thompson shell 缺少人们日常使用的 UNIX 所具备的一些重要内置功能,如管道 (|
)、编写 shell 脚本的能力和 if
条件语句,但是该 shell 的基础功能(如数据重定向)仍存在于现今使用的 shell 中。
后来,在 1997 年,Thompson shell 被 Bourne shell(即 sh)所取代。Bourne shell 是由 AT&T 贝尔实验室的 Stephen Bourne 创建的,它成为 UNIX 第 7 版 (V7) 的缺省 shell。Bourne shell 使 UNIX 的发展向前迈进了一大步。到这时,用户可以编写 shell 脚本,在变量中存储信息和导出信息,控制文件描述符,控制信号处理,使用 for
循环和 case
语句,以及其他大量功能。虽然 Bourne shell 创建至今已有 30 多年时间,目前它仍在大量的 UNIX 系统中广为使用,并且是当今许多 UNIX 系统的超级用户 —root— 的缺省 shell。
在过去三十年中,UNIX shell 获得了许多改进和增强。结果,出现了多种不同的 shell。图1显示了少数 UNIX shell 的家族树。虽然该图并不完整,但它显示了衍生出其他较次要 shell 的那些主要 shell。
图 1. UNIX shell 家族树
Korn shell
1982 年,AT&T 贝尔实验室的 David Korn 开发了 Korn shell(即 ksh)。Korn shell 与许多其他 shell 类似,向后兼容 Bourne shell (sh)。在超过 25 年的发展历程中,它已进化为强健、稳定和非常可靠的 shell。IBM 在 AIX 中使用 Korn shell 作为其缺省 shell。Korn shell 共有两个版本,它们均包含在 AIX 中。
第一种是 AIX 普通用户的缺省 shell,即标准 ksh shell。Korn shell 符合计算机环境的可移植操作系统接口标准 (POSIX),这是操作系统的国际标准。
AIX 提供的第二种 Korn shell 是增强 Korn shell,称为 ksh93。除标准 Korn shell 所拥有的所有强大功能外,增强 Korn shell 还包含如下功能:
- 算术增强
- 复合变量
- 复合赋值
- 关联数组
- 变量名引用
- 参数扩展
- 规程函数
- 函数环境
- PATH 搜索规则
- shell 历史记录
- 其他内置命令
有关 ksh93 增强功能以及 ksh 与 ksh93 之间差异的完整列表,请参阅参考资料。
使用 ksh 设置命令行环境
在介绍如何使用 ksh 编辑命令行之前,必须设置您的环境。根据您的喜好来设置 Korn shell 相当简单:登录到 ksh 下时,使用带有 -o
开关的 set
命令来查看您的当前设置:
Current option settings are:
allexport off
bgnice on
emacs off
errexit off
gmacs off
ignoreeof on
interactive on
keyword off
markdirs off
monitor on
noexec off
noclobber off
noglob off
nolog off
notify off
nounset off
privileged off
restricted off
trackall off
verbose off
vi off
viraw on
xtrace off
下面简单介绍各项设置。(您也可以通过运行 man set
找到这些说明。)
allexport
:自动导出所有定义的后续变量。bgnice
:在后台以更低的优先级运行所有进程。emacs
:编辑输入的命令行文本时,使用 emacs 风格的行内编辑器。errexit
:如果某个命令产生非 0(零)的退出状态并且设置了 ERR 陷阱,则执行 ERR 陷阱并退出。gmacs
:编辑输入的命令行文本时,使用 gmacs 风格的行内编辑器。ignoreeof
:忽略文件结束字符,并且不退出 shell。如果用户想要退出,则必须键入exit
命令或按 11 次 Ctrl-D。keyword
:此选项将命令的所有参数放入环境中,而不仅仅放入命令之前的参数,使用set
命令可以查看这些参数。markdirs
:在来自文件名替换的所有目录的末尾添加一个正斜杠 (/
)。monitor
:将所有后台进程作为独立进程运行,并在进程结束时向 stdout 输出一行信息以通知用户。noexec
:不执行命令。仅检查语法错误。注意:此参数在交互式 shell 中无效。
noclobber
:当输出重定向到现有文件时,此标记可阻止截断这些文件。然而,启用此选项后,如果使用大于号加管道符号 (>|
),则仍然会发生截断。noglob
:禁用文件名替换。nolog
:如果使用此选项,则函数定义不会存储在历史文件中。nounset
:如果执行替换,则所有未设置参数都作为错误返回。restricted
:运行受限制的 shell。用户无法执行下列操作:更改目录;更改 SHELL、ENV 或 PATH 变量;执行在文件名中包含正斜杠 (/
) 的命令;或重定向输出。trackall
:每个命令在最初运行时都作为被跟踪的别名。verbose
:当 shell 读取输入行时,向 stdout 显示所有这些行。vi
:编辑输入的命令行文本时,使用 vi 风格的行内编辑器。viraw
:键入字符时,将每个字符处理成在 vi 编辑器下输入的原样。xtrace
:执行命令时,向 stdout 显示所有命令和参数。
若要开启内置命令集选项,请使用 -o
开关。相反,如果改变主意,可以使用 +o
开关关闭这些选项。
本文将重点讲述的主要选项是行内编辑器开关。根据个人喜好不同,人们往往偏爱 vi、emacs 或 gmacs 文件编辑器中的某一个。Korn shell 包含所有这三种编辑器。然而,我将重点介绍 vi 行内编辑器。将行内编辑器选项设置为 vi
十分简单。只需在用于查看所有当前设置的命令后加上选项 vi:
大功告成!若要验证该设置,可以再次查看您的当前设置:
# set -oCurrent option settings are:
allexport off
bgnice on
emacs off
errexit off
gmacs off
ignoreeof on
interactive on
keyword off
markdirs off
monitor on
noexec off
noclobber off
noglob off
nolog off
notify off
nounset off
privileged off
restricted off
trackall off
verbose off
vi on
viraw on
xtrace off
使用 Korn shell vi 行内编辑器
既然已将您的 shell 配置为使用 vi 行内编辑器,现在应该开始进行测试了。
在命令行上修改文本
现在,当您在命令行上键入时,将其视为处于 vi 编辑器中的插入模式。如果出错或者需要向要执行的命令添加一些内容,只需按 Esc 键退出插入模式并切换回命令模式。
例如,您当前所在的工作目录包含如下文件:
# lsfileA fileAA fileAAA fileAB fileABA fileABB fileB fileBAA fileBB fileBBB
您希望查找以 fileAA 开头的文件并删除它们,于是输入以下命令:
# find . -name "fileAB*" -exec rm {} \; 在执行您输入的行之前,您发现自己犯了一个错误,意外地将 fileAA
误输入为 fileAB
。不必担心。只需退出插入模式并切换到命令模式,将光标移动到错误字符并进行替换,所有这些操作都使用 vi 命令完成。下面详细说明命令序列,此时仍然处于行内编辑器的插入模式:
- 按 Esc 切换到命令模式。
- 使用 vi 风格的移动命令向左移动光标,以突出显示字符串“fileAB*”中的 B。(H 键可向左移动光标。)
注意:如果您习惯于在 vi 中使用箭头键,那么明智的做法是了解键盘上用于移动光标的实际字母键,因为不同类型的终端上的箭头键不一定能产生预想中的结果:
- h:向左
- l:向右
- k:向上
- j:向下
- 使用 vi 风格的“替换单个字符”命令将 B 替换为 A(即单击 R,然后键入
A
)。
经过检查确认输入无误后,按 Enter 键执行命令:
# find . -name "fileAA*" -exec rm {} \;# ls
fileA fileAB fileABA fileABB fileB fileBAA fileBB fileBBB
文件名完成
Korn shell 中 vi 行内编辑器的另一个十分有用的操作是文件名完成。执行命令时,经常会遇到这样一种情况,您用作 stdin、stdout 或 stderr 参数的文件将被写入到某个文件中。文件名可能很长,也可能有多个文件具有类似的文件名,或者您就是无法记起完整的文件名。这时就会用到文件名完成功能。在 键入文件名时,如果完成了一部分,那么只需先按 Esc 键,再按反斜杠 (\
)键。这样做省时省力!
例如,我要查看 AIX 上的 /etc/filesystems 文件,但我忘记了完整文件名。我知道它位于 /etc 下,并且该文件以 file 开头,仅此而已。我只需键入 view /etc/file
并按 Esc-\,哇!ksh 已经为您完成了这行内容。现在命令行显示为 view /etc/filesystems
。
对于目录结构也可以执行同样的操作,因为它们实际上也可算作文件名。
查看和修改命令历史记录
在您的 UNIX 系统上监视进程或执行其他一些功能时,您需要多次反复键入相同的命令。为了避免反复重新输入的麻烦,Korn shell 提供了内置的命令历史记录供您查看。如果您也将行内编辑器设为 vi
,ksh 将允许您提取用户所执行命令的历史记录(有时仅限该会话,具体取决于您的系统配置),并按照您在命令行中输入的其他文本修改命令。
如果您在变量 HISTFILE 中定义了一个文件名,ksh 允许用户从其历史记录中提取和修改命令,或仅仅再次执行原始命令。例如,以下是示例 $HISTFILE 显示的最后 10 个命令:
# tail -10 $HISTFILEls
cd ~cormany/testdir/dirA
./fileA 1>fileA.out 2>fileA.errors
pwd
ps –fu cormany
df –k .
ps –fu cormany
find . –name “fileA.out” –ls
find . –name “fileA.errors” –ls
tail -10 $HISTFILE
在命令行中,只需按 Esc 键即可进入 vi 行内编辑器的命令模式,然后按 K 键提取上次执行的命令。由于您仍处于命令模式,您可以继续按 K 键向上移动所执行命令的历史记录,或按 J 键向下移动列表。
若要帮助简化命令模式下的光标移动,当您在命令提示符处按 Esc 键时,请想象您加载的 $HISTFILE 为 vi 中的普通文件。在 vi 编辑器中,K 键向上移动一行,而 J 键向下移动一行。如果按 Esc-J 键并使用示例 $HISTFILE,想象正在编辑 $HISTFILE,并且光标开始位于文件底部。该行将显示 tail -10 $HISTFILE
。如果再次按 J 键,正在编辑的 $HISTFILE 将向上移动一行,显示为 find . -name "fileA.errors" –ls
。
图2提供了小型的“备忘单”,将常规 vi 命令模式光标移动与 ksh vi 行内编辑器命令模式移动进行比较。
图 2. vi 命令模式备忘单
命令行与 shell 脚本
有时会用到 shell 脚本,其他一些时间则要用到命令行。如果需要定期执行某项任务,或者任务非常复杂、需要进行数据处理,同时不需要用户反复键入各种命令,则适合使用 shell 脚本。有时,某些任务只需要执行一次并且相对简单,则使用命令行可以很好地完成这类任务。
例如,假设存在下列字典列表:
# lsfileA.tar.gz fileAA.tar.gz fileB.tar.gz fileBB.tar.gz
如果只需要解压缩文件,并使用 bzip2 重新压缩,然后将它们传送到 ATC-AIX2 上,那么与其键入 shell 脚本,不如使用命令行。可以将 shell 脚本视为一次输入多个命令行条目,因为从某种意义上来说,这就是实际情况。在命令行上键入命令时,就像将它们输入到脚本中,然后执行脚本。
您希望遍历目录中以 gz 结尾的文件,解压这些文件,然后使用 bzip2 重新压缩它们,再对这些文件使用 scp
命令,将它们复制到目标 ATC-AIX 服务器上。循环在命令行中的工作效果与在脚本中同样出色。当开始 loop…if
条件语句、case
switch 语句,或其他代码块语句时,您正在运行的 ksh 只会将光标移动到下一行,但提示符将更改为 $PS2。当代码块完成时,将执行代码块并使用户返回 $PS1 提示符。
也就是说:
- $PS1 提示符:等待下一个命令
- $PS1 提示符:代码块开始
- $PS2 提示符:代码块继续
- $PS2 提示符:代码块继续
- $PS2 提示符:代码块结束
- 代码块执行
- $PS1 提示符:等待下一个命令
变量 PS2 的缺省值为 >
。返回前一个解压缩后重新压缩的功能,您只需在 ksh 命令行中键入以下内容:
> do
> gzip -d ${_FNAME}
> bzip2 ${_FNAME%*.gz}
> scp ${_FNAME%*.gz}.bz2 cormany@ATC-AIX2:/home/cormany
> done
完成代码块(也就是说,使用 done
结束循环)后按 Enter 键,将开始执行循环。在命令行上键入的循环将搜索当前工作目录下以 .gz 结尾的所有文件,将这些文件解压缩,然后使用 bzip2 重新压缩它们,最后将它们复制到 ATC-AIX2 上的 /home/cormany 目录中。就是如此简单。
--End--