学习bash第二版-第三章 定制用户环境

  环境是一种概念的集合,用以表达一个计算机系统或其他工具集设计的用于理解和继承且外观舒适的内容。例如,用户的桌面就是一种环境。桌面工作包含的内容通常有备忘录、电话、信件、表格等。桌面上用来处理这些内容的工具有纸张、订书钉、信封、钢笔、电话、计算器等。它们每个都有一个表达如何使用的特性集,比如从这些工具在桌面上或抽屉里的位置到电话上记忆按钮设置的号码等高级特性。结合在一起,这些特性就行成了桌面的外观。
  通过把钢笔放在最容易拿到的位置,为电话按钮编程等就可以定制桌面环境的外观。通常,你的环境做的定制越多,就越会满足自己的需要,也因而越具有实用性。
  类似的,UNIX shell也给出这样的概念,如文件、目录和标准输入/输出。UNIX本身给出使用这些特性的工具,如文件操作命令、文本编辑器和打印队列。用户UNIX环境的外观由其键盘、显示器以及如何建立用户目录和如何给文件、目录、命令命名决定,在目录中可以放入各种文件。另外还有一些定制用户shell环境的高级方式。
  本章将介绍bash为定制用户环境所提供的4个最重要的特性。
  特殊文件
    当用户登录和退出一个新的shell时,文件.bash_profile、.bash_logout、.bashrc被bash所读取。
  别名
    为方便起见,用户对命令或命令字符串所定义的同义词。
  选项
    对用户环境各方面的控制,可以打开和关闭它们。
  变量
    用名字引用的可变值。shell和其他程序可依据保存在变量中的值修改其行为。
  虽然这些特性不是唯一可用的特性,但它们形成了高级定制的基础。它们也是UNIX上各种shell的通用特性。后面几章会介绍高级shell特性,如shell编程功能。
**.bash_profile、.bash_logout和.bashrc文件
  用户主目录下的这些文件对bash有特殊含义。它们在用户登录或调用另一bash shell时给出了一种自动建立其登录账号环境的方式,并允许退出时执行各种命令。这些文件存在于用户主目录下,其位置依赖于系统管理员对用户账号的设置。如果这些文件不存在,用户登录使用默认系统文件/etc/profile。可以使用流行的文本编辑器轻易的创建自己的bash文件。如果你对UNIX下可用的文本编辑器不熟悉,建议你在深入本章讲述的各种技术前最好熟悉其中一种,如vi或emacs。
  最重要的bash文件是.bash_profile,它在用户每次登录到系统时被读取,其中包含的命令被bash执行。如果你查看一下自己的.bash_profile,可能会发现类似于下面的行:
  PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
  SHELL=/bin/bash
  MANPATH=/usr/man:/usr/X11/man
  EDITOR=/usr/bin/vi
  PS1='\h:\w\$ '
  PS2='> '
  export EDITOR
  这些行定义了用户登录账号的基本环境。如果你还没有理解其中的含义,最好保留这些行。可编辑自己的.bash_profile,在已存在行的后面加入新行。
  注意,直到退出并再次登录,该文件被重新读取后,你向.bash_profile键入的内容才会生效。或者,可以使用source命令,例如:
  source .bash_profile
  source执行指定文件里的命令,这里是.bash_profile,包括加入的任意命令。
  bash允许有.bash_profile的两个同义文件:来源于C shell文件.login的.bash_login以及来源于Bourne shell和Korn shell文件.profile的.profile。登录时三者中只有一个被读取,如果用户根目录下.bash_profile不存在,则bash查找.bash_login,如果它也不存在,则查找.profile。
  bash查找这些同义文件的好处是,如果曾使用过Bourne shell,你可以保留它了。如果需要加入特定的bash命令,可以将它们放入.bash_profile中并在后面跟一条命令source .profile。登录时,所有特定的bash命令均被执行,然后bash将会调用.profile,执行其保留的命令。即使决定仍使用Bourne shell,也不必修改已存在的文件。类似方法也可用于.bash_login和C shell的.login,但由于这些shell基本语法的差异性,这不是一个好主意。
  .bash_profile只被登录shell读取并执行。如果你通过在命令行上键入bash启动一个新的bash,它就会试图读取.bashrc中的命令。这种策略给出将登录时需要的启动命令和运行一个子shell所需的命令分离开的灵活性。如果你需要在登录shell和启动一个子shell命令时进行一样的操作,可以在.bash_profile中使用source命令执行.bashrc。如果.bashrc不存在,那么启动一个子shell时就没有命令被执行。
  文件.bash_logout在每次登录shell退出时被读取并执行。它提供了定制用户环境的功能。如果要执行诸如删除账号内临时文件或记录登录系统所花时间等命令,则可将这些命令放在.bash_logout内。该文件不必一定存在于账号内——如果不存在,则退出时不再执行其他命令。
**别名
  使用UNIX一段时间后,你就会注意到有许多可用命令的名字很模糊。有时最常用的命令只有一个选项字符串,其参数需要指定。最好是存在一个特性使你可以为这样的命令重新命名,或允许你键入一些更简单的参数而不是一大堆选项。bash给出了这样的特性:别名。
  别名可在命令行上、用户的.bash_profile中或用户的.bashrc中定义,格式如下:
  alias name=command
  该语法指定name是command的别名。当将name作为一个命令键入时,bash在执行该行时用command替换它。注意,在符号=两边没有空格,此为严格语法。
  有一些使用别名的基本方式。首先,最简单的是作为一个已有命令的更易于记忆的名字。许多常用UNIX命令的名字不好记,因此用别名来替代它们,典型例子如下:
  alias search=grep
  UNIX文件搜索实用程序grep的意义是“通用正则表达式分析器(Generalized Regular Expression Parser)”,此语义对计算机科学家才有意义,但对办公室管理人员,如果要用它来找到电话号码表上的Fred,可以将grep的别名定义为search,键入:
  $ search Fred phonelist
  一些人,特别是不擅长打字的人,很喜欢使用别名,以避免犯错误。例如:
  alias emcas=emacs
  alias mali=mail
  alias gerp=grep
  这可能很方便,但我们认为最好还是保证键入的拼写正确以避免错误信息。另一常用方式是将别名作为长的命令字符串的缩写。例如,有一个需要经常进入的目录,它在用户目录结构中很深的层次里,因此需要设置一个别名,以便于用户无需键入整个路径名(即使记得)就可以进入该目录:
  alias cdvoy='cd sipp/demo/animation/voyager'
  注意,引号包含了整个cd命令;如果作为别名的字符串由多个单词组成,这是必须的。
  另一个例子,ls命令的一个可用选项是-F:它在目录文件后加斜线/,在可执行文件后加星号*。键入一个斜线后跟一个大写字母很不方便,许多人将其定义为:
  alias lf='ls -F'
  关于别名有些事情很重要。首先,bash对别名的文字用其原始文字替换。可以将其想像为bash在解释和执行命令之前将命令传给文本编辑器或字处理器,并执行“更换”或“替换”命令。当别名被扩展时任意特殊字符都会被shell正确解释。例如,要更加容易的打印用户目录下所有文件,可以定义别名:
  alias printall='pr * | lpr'
  第二,记住别名是可递归的,这意味着可以用别名定义另一别名。前面例子中客观存在的事实是该别名太长,没有节省键入,如果要保留此别名,但要加入一个缩写,可定义:
  alias pa=printall
  有了递归别名,就有可能创建出一个无限循环:
  alias ls='ls -l'
  bash确保该循环不会发生,因为只有替换文本中的第一个单词会被检查以确认是否可以进一步别名;如果该单词与要扩展的别名一样,它不会被扩展第二次。上述命令工作正常(键入ls,结果为带有权限、大小、所有者的长列表),当给出无意义的别名时,如:
  alias listfile=ls
  alias ls=listfile
  别名listfile将被忽略。
  别名只可用于命令字符串的开始——但也有例外。在上面cd的例子中,你也许想单独为目录名定义别名,而不是对整个命令。但如果定义:
  alias anim=sipp/demo/animation/voyager
  然后键入cd anim,bash可能会打印出信息:anim: No such file or directory。
  bash别名功能的一个含糊特性(在类似的C shell特性中没有)为解决该问题提供了一个办法。如果别名取值(等号右边值)以空格结束,则bash试图对命令行上的下一单词进行别名替换。要使别名取值以一个空格结束,需要将其用引号包围起来:
  alias cd='cd '
  这使得bash搜索cd的目录名参数的别名,在前面的例子中别名anim可以被正确扩展。
  定义cd命令使用的目录变量的另一方式是使用环境变量cdable_vars,本章后面会讨论它。
  最后,基础alias命令还有一些有用的助手。如果键入alias name,并且不带等号和取值,shell将打印别名的取值,如果其未定义,则将给出alias name not found。如果键入alias没有任何参数,则给出所有定义的别名列表。命令unalias name根据参数删除其别名定义。
  别名对创建舒适的环境很有益处,但它们基本不被shell脚本和函数(将在下一章介绍)所提倡。这里介绍的就是别名的全部内容。如果你对它们很精通,就会发现根本不需要什么别名。然而,别名对于对UNIX有畏惧感的新手来说很理想。第四章给出了当别名和函数名字相同时处理它们的优先次序。
**选项
  别名使你可以为命令创建方便的名字,它们实际上并不改变shell的行为。选项则不然。shell选项就是取值为“on”或“off”的设置。有些选项与编程者感兴趣的内部shell特性有关,这里介绍的是对所有用户都有用的部分。
  和选项有关的基本命令是set -o optionname和set +o optionname。可以使用一个set命令改变多个选项,方式是在每个optionname前加一个-o或+o。加号和减号的使用是违反直觉的,-符号打开命令的选项,而+符号关闭它。这样使用的原因是,符号-是为命令指定选项的传统UNIX方式。而加号+则是之后的补充。
  大多数选项都有一个字母的缩写,可用于替代set -o命令。例如,set -o noglob可缩写为set -f。这些缩写来源于Bourne shell。像其他“特别”的bash特性一样,它们是向上兼容的;否则,就不会得到应用。
  表3-1列出了一般UNIX用户使用的选项。除了特别注明,默认均为关闭。
  
  表3-1  基本shell选项
  选项       说明
  emacs      进入emacs编辑模式(默认情况)
  ignoreeof  不允许单独使用CTRL-D退出的用法。使用exit命令可立即退出。其作用等同于设置shell变量IGNOREEOF=10
  noclobber  不允许输出重定向(>)覆盖已存在文件
  noglob     不允许扩展文件名通配符如*和?(通配符扩展有时称为globbing)
  nounset    试图使用未定义变量时给出错误
  vi         进入vi编辑模式
  
  还有另外一些选项(共21个;附录二参考列表中给出)。要检验选项状态,可键入set -o,bash将打印所有选项及其设置的列表。
**shopt
  bash 2.0引入配置shell行为的新内置命令:shopt。此内置命令替换原始的使用环境变量和set命令所完成的选项配置功能。
  shopt -o功能是set命令部分功能的复制,以使shopt功能完备,同时通过set内的包含性保持向后兼容。
  此命令格式是shopt 选项 - 选项名。表3-2列出了shopt选项。
  
  表3-2  shopt选项
  选项   含义
  -p     显示可设置选项及其当前取值的列表
  -s     设置每一选项名
  -u     使每一选项名失效
  -q     抑制正常输出:返回状态指出变量是否被设置
  -o     允许选项名取值通过set命令的-o选项定义
  
  默认行为是使已命名选项失效(关闭)。如果未给出选项和参数,或使用了-p选项,shopt显示可设置选项及其当前取值的列表。如果还给定了-s或-u选项,列表分别被限定在已设置或未设置的选项上。
  最常见的选项名列表在表3-3中给出。完整列表在附录二中给出。
  
  表3-3  shopt选项名
  选项         含义
  cdable_vars  如果被设置,且cd内置命令的参数不是一个目录,则假定该参数为取值为转换到的目录的变量名
  checkhash    如果被设置,bash在试图执行命令前检验哈希表中是否存在命令。如果一个哈希表中的命令不存在,则执行正常路径搜索
  cmdhist      如果被设置,bash试图在同一历史入口中保存所有的多行命令
  dotglob      如果被设置,bash在路径名扩展结果中包含以.(点号)开始的文件名
  execfail     如果被设置,非交互shell在不能执行指定为exec命令参数的文件时不会退出。如果exec失败,则交互式shell不会退出
  histappend   如果被设置,shell退出时,历史列表被附加到HISFILE变量指定的文件后,而不是覆盖该文件
  lithist      如果被设置,并且使能了cmdhist选项,多行命令被保存到具有嵌入新行的历史文件中,而不是使用分号分隔符将其分隔
  mailwarn     如果被设置,且bash检查到邮件文件在最后一次检查之后被存取过,则显示一条消息“mailfile中的邮件被读过”
  
  本章后面将介绍各种选项的用法。
**shell变量
  用户环境中有一些特性需要定制但不能表示为开/关的选项。此类特性在shell变量中指定。shell变量可以指定从提示符字符串到shell检查新邮件的频率等各种设置。
  像别名一样,shell变量也是一个拥有取值的名字。bash有一些内置的shell变量。shell编程者可以加入自己的变量。按惯例,内置变量名均为大写。然而,bash有两个例外。定义变量的语法有些类似于别名的语法:
  varname=value
  在等号两边必须没有空格。如果取值多于一个单词,必须用引号括起来。要在一个命令中使用变量值,在其名字前加符号$。
  可以用命令unset varname删除变量。正常情况下不用这样做,因为所有不存在的变量均被假定为null,亦即空字符串""。但如果使用选项nonuset,将使shell在遇到未定义变量时指出错误,可这能是你对unset感兴趣的地方。
  最简单的检验变量值方法是使用echo 内置命令。所有echo的功能都是打印其参数,但要在shell对其进行评估之后。意思是取出变量值,在后面附加上通配符。因此,如果变量wonderland的取值为alice,键入
  $ echo "$wonderland"
  shell会简单打印出alice。如果变量未定义,shell会打印空行。一个更详细的方式是:
  $ echo "The value of \$varname  is \"$varname \"."
  第一个美元符号和内部的双引号被加上了反斜线转义(亦即加上了\,以便shell不会试图解释它;详细内容请参见第一章),这样的输出中会显示出文字。上述示例的结果为:
  The value of $wonderland is "alice".
**变量和引用
  注意,上述echo例子中使用双引号包围了变量(及包含它们的字符串)。在第一章中,曾说过双引号中的某些特殊字符也会被解释,而单引号内的却不会。
  使双引号起作用的是美元符号——它的意思是变量被评估。没有双引号可能也可以。例如,可以这样写上述echo命令:
  $ echo The value of \$varname  is \"$varname \".
  但双引号一般更正确,下面介绍了原因。假定执行:
  $ fred='Four spaces between these    words.'
  然后输入命令echo $fred,结果为:
  Four spaces between these words.
  对多余的空格是如何处理的呢?没有双引号,shell会在替换变量值后将字符串分成单词,就像正常情况下处理命令一样。双引号将这部分处理包围了起来(通过使shell认为整个被引起来的字符串是一个单词)。
  因此命令echo "$fred"结果为:
  Four spaces between these    words.
  当处理包含用户或文件输入的变量时,单引号和双引号之间的区别就变得特别重要。
  双引号也允许其他特殊字符生效。第四章、第六章和第七章中会加以介绍。但目前,我们将第一章的规则“不清楚时,使用单引号”后再加上“除非字符串包含变量,如果包含就该使用双引号”。
**内置变量
  像选项一样,某些内置变量对一般的UNIX用户有实际意义,也有一些很神秘。这里介绍最常用的一部分,后面几章再介绍不常用的内置变量。另外,附录二给出了一个完整列表。
**编辑模式变量
  前面几章介绍了和命令行编辑模式相关的一些shell变量,这里在表3-4中列出。
  
  表3-4    编辑模式变量
  变量         含义
  HISTCMD      当前命令的历史编号
  HISTCONTROL  如果设置取值为ignorespace,以空格开始的行不被输入到历史列表中,如果设置为ignoredups,匹配最后历史行的一行不被输入。设置取值为ignoreboth将使能两个选项
  HISTIGNORE   模式列表。由分号分隔,用于决定那些命令行保存在历史列表中。模式被认为从命令行行首开始且必须完全指定该行。例如,没有隐含附加的通配符(*)。HISTCONTROL应用后模式才被检查。&符号匹配前面行。可通过在前面加反斜线转义显示&
  HISTFILE     保存命令历史的历史文件名,默认为~/bash_history
  HISTFILESIZE 保存在历史文件中的最大行数。默认为500。设置该变量值后,如果必要,历史文件被截取为给定行数
  HISTSIZE     在历史文件中记忆的最大命令数。默认为500
  FCEDIT       fc命令使用的编辑器路径名
  
  前面一章中,介绍了bash如何为命令编号。要在一个交互式shell中找到当前命令的编号,可以使用HISTCMD。注意,一旦使HISTCMD失效,将会失去其特殊含义,即使后面再设置它也没有用。
  上一章介绍了bash如何在内存中保存历史列表,并在退出shell会话时将其保存到文件。变量HISTFILESIZE和HISTSIZE允许你设置shell保存在历史文件中的最大行数以及在历史列表中要记住的最大行数,亦即使用history命令显示的行数。
  假定要在用户的主目录下维护一个小的历史文件,可通过设置HISTFILESIZE为100实现。历史文件允许的行数马上就变成100.如果该文件已经比你指定的行数大,将会被截取。
  HISTSIZE工作方式相同,但只针对当前shell在内存中保存的历史命令。退出一个交互式shell时,HISTSIZE仍是保存在历史文件中的最大行数。如果设置的HISTFILESIZE比HISTSIZE小,保存的列表将被截取。
  还可以削减历史文件和历史列表的大小,方法是使用HISTCONTROL变量。如果设置为ignorespace,键入的任何以空格开始的命令都不会出现在历史中。ignoredups选项更有用。它会丢弃历史列表中相同的连续项。当一个文件被创建时,假设你要用ls命令监视它的大小。一般情况下,每次键入ls,它都会出现在你的历史列表中,设置HISTCONTROL为ignoredups后,只有第一个ls出现在历史中。
  bash 2.0引入了历史控制变量的一种更具灵活性的新类型。HISTIGNORE允许你指定模式列表,依据此列表检验命令行。如果命令行匹配模式之一,它就不进入历史列表。还可以使用模式&请求它忽略重复。
  例如,假设不想要任意以l开始的命令,也不要任何重复出现在历史中。设置HISTIGNORE为l*:&就可以了。就像其他模式匹配一样,l后通配符*将匹配以该字母开始的任意命令行。
**mail变量
  因为mail程序不总是运行,没有方式可以通知你何时获得新邮件。因此可以用shell来完成此功能。shell实际上不能检验进入的邮件,但它可以周期性查看邮件文件并判断上次检验后该文件是否被修改。表3-5中的变量列表列出了如何控制其工作方式。
  
  表3-5    邮件变量
  变量       含义
  MAIL       检查收到的邮件的文件名
  MAILCHECK  以秒计算检验新邮件的时间频率(默认为60秒)
  MAILPATH   冒号分隔的文件名列表,用以检验收到的邮件
  
  最简单的情况是,可以使用标准UNIX mail程序,邮件文件类似于/usr/mail/yourname。这里,如果要检验邮件,只需设置变量MAIL为该文件名:
  MAIL=/usr/mail/yourname
  如果系统管理员没有做这个设置,可以在自己的.bash_profile中放入类似的一行。
  然而,某些人使用的是不标准的邮件程序,它使用了多个邮件文件;可以使用MAILPATH来处理此类情况。bash将使用MAIL取值作为要检验的文件名,除非你设置了MAILPATH。在后一种情况下,shell将检验MAILPATH中列出的每个文件,来处理新邮件。可以使用此机制使shell打印每个邮件文件的不同信息:在MAILPATH中的每个邮件文件名后面加一个问号再加上要显示的信息。
  例如,假设用户有一个邮件系统自动将邮件分类到发送者用户名对应的文件中。邮件文件名为/usr/mail/you/martin,/usr/mail/you/geoffm,/usr/mail/you/paulr等,可定义MAILPATH如下:
  MAILPATH=/usr/mail/you/martin:/usr/mail/you/geoffm:/usr/mail/you/paulr
  如果从Martin Lee处得到新邮件,文件/usr/mail/you/martin将发生变化。bash在一分钟内会注意到此变化,打印出消息:
  You have new mail in /usr/mail/you/martin
  如果正在运行一个命令,shell会等待到命令结束(或挂起)后再打印信息。要进一步定制,可以定义MAILPATH为:
  MAILPATH="\
  /usr/mail/you/martin?You have mail from Martin.:\
  /usr/mail/you/geoffm?Mail from Geoff has arrived.:\
  /usr/mail/you/paulr?There is new mail from Paul."
  每行后面的反斜线允许你在下一行继续命令。但要注意:不能缩进后续行。如果现在得到来自Martin的邮件,shell将打印:
  You have mail from Martin.
  还可以在信息中使用变量$_打印当前邮件文件的名字。例如:
  MAILPATH='/usr/mail/you?You have some new mail in $_'
  新邮件到达时,就会打印如下行:
  You have some new mail in /usr/mail/you
  接收邮件通知的功能可通过使用shopt命令的mailwarn选项开启和关闭。
**提示符变量
  如果你在工作中见过经验丰富的UNIX用户,你可能会意识到shell的提示符不是一成不变的。许多用户在其提示符下有各种编码字符。可以在提示符下放入有用的信息,比如日期和当前目录。这里给出如何修改给出这些信息,其余的在下一章讨论。
  实际上,bash使用4个提示符字符串。它们保存在变量PS1,PS2,PS3和PS4里。第一个称为基本提示符字符串,亦即通常的shell提示符,默认取值为"\s-\v\$ "。许多人喜欢将基本提示符设置为包含用户名。方式如下:
  PS1="\u—> "
  \u告诉bash将当前用户名插入到提示符字符串。如果用户名为alice,则提示符为“alice—> ”。如果你是一个C shell用户,且像其他人一样,习惯在提示符字符串下给出历史编号,bash也有这种类似C shell的功能;如果在提示符字符串下使用序列\!,bash将以历史编号替代。因而,如果定义提示符字符串为:
  PS1="\u \!—> "
  则提示符为 alice 1—> ,alice 2—> 等。
  但设置提示符字符串最有用的方式也许是使其包含当前目录。对这种方式,无需键入pwd给出当前位置,如下:
  PS1="\w—> "
  表3-6列出了可用的提示符定制。
  
  表3-6    提示符字符串定制
  命令  含义
  \a    ASCII响铃字符(007)
  \d    “星期 月 天”格式日期
  \e    ASCII转义字符(033)
  \H    主机名
  \h    主机名加.
  \n    回车和换行
  \s    shell名字
  \T    12小时HH:MM:SS格式当前时间
  \t    HH:MM:SS格式当前时间
  \@    12小时am/pm格式当前时间
  \u    当前用户的用户名
  \v    bash版本(例如2.00)
  \V    bash发布;版本和补丁(例如,2.00.0)
  \w    当前工作目录
  \W    当前工作目录的基名
  \#    当前命令的命令编号
  \!    当前命令的历史编号
  \$    如果有效UID为0,则打印#,否则打印$
  \nnn  八进制字符编码
  \\    打印一个反斜线
  \[    开始非打印字符的序列,如终端控制序列
  \]    结束非打印字符序列
  
  PS2称为二级提示符字符串;其默认取值为>。当键入一个不完全行,并按RETURN后将显示它,表示必须完成该命令。例如,假定开始输入一个引号引起来的字符串,但并未结束引号,然后如果按RETURN,shell会提示>,并等待你完成该字符串:
  $ echo "This is a long line,  # 该命令的PS1
  > which is terminated down here"     # 续行的PS2
  $                                   # 下一命令的PS1
  PS3和PS4与shell编程和调试有关。在第五章和第九章里会介绍。
**命令搜索路径
  另一重要的变量是PATH,它帮助shell找到输入的命令。
  你可能知道,所用的每个命令实际上都是机器里运行的包含代码的一个文件。这些文件称为可执行文件,它们包含在各种目录下。某些目录在所有UNIX系统上都是标准的,像/bin或/usr/bin;也有一些依赖于所使用的UNIX特定版本;还有一些对机器是独有的;如果你是程序员,它们就可能为你所有。运行一个命令时,你要知道其可执行文件在什么位置。
  这就是PATH的功能。它的值是一个目录列表,每次你输入一个命令时,shell会搜索它;目录名由冒号(:)分隔;就像MAILPATH里的文件一样。
  例如,如果键入echo $PATH,结果如下:
  /bin:/usr/bin:/usr/local/bin:/usr/X386/bin
  为什么要关心用户的路径呢?原因有两个。第一,在度过本书后面章节后,你会编写自己的shell程序,要测试它们并最终为其留出一个目录。第二,可能建立的用户系统使得某些限定命令的可执行文件保存的目录在PATH中没有列出。例如,可能存在目录/usr/games,其中的可执行文件在正规工作期间是被禁止的。
  因此,需要向PATH加入目录。假定你在登录的目录/home/you下创建了一个bin目录,在其中加入了自己的shell脚本和程序。要向PATH加入此目录以使得每次登录时系统自动确定该目录位置,应在文件.bash_profile加入行:
  PATH=$PATH":/home/you/bin"
  该行先设置PATH为以前的PATH,然后紧跟分号和/home/you/bin。
  这种方式很安全。当你输入命令时,shell依PATH中目录出现的次序进行搜索直到发现一个可执行文件为止。因此,如果有一个shell脚本或程序,其名字和存在的命令一样,shell会执行那个存在的程序——除非你键入命令的完整路径名。例如,如果你在上例目录中创建了自己的more命令版本,PATH设置如上,就需要键入/home/you/bin/more(或~/bin/more)才能得到你的版本。
  重置用户路径的一种比较无理的方式是将自己的目录放在其他目录前:
  PATH="/home/you/bin:"$PATH
  这样做很不安全,因为这样你就得坚信自己的more版本会工作正常。但由于一个更重要的原因这样做也不安全:即系统安全。如果你的PATH这样设置,就为计算机黑客和制造恶作剧的人留下了一个漏洞。他们可以安装“特洛依木马”以及其他内容偷取你的文件或进行破坏(细节见第十章)。因此,除非你对使用该系统的每个人有完全的控制权并且自信,才可以使用这种方法加入自己的命令目录。
  如果需要知道命令所在的目录,不必查看PATH里的目录。shell内置命令type在你将命令作为参数时会打印出该命令的完整路径,或是打印出命令名和类型,如果它是一个内置命令(如cd)、别名或函数(见第四章)。
**命令缓存
  你也许认为在一个大的列表范围内找一个命令要花很长时间,这种想法是有道理的。bash为提高效率使用了哈希表。
  每次shell找到搜索路径下的命令时,它首先将其放入哈希表。然后如果再次使用该命令时,bash首先检查表,查看命令是否在里面。如果在,使用表中给出的路径,执行该命令;否则,进入搜索路径下查找。
  可以使用hash命令查看当前哈希表的内容:
  $ hash
  hits    command
     2    /bin/cat
     1    /usr/bin/stat
     2    /usr/bin/less
     1    /usr/bin/man
     2    /usr/bin/apropos
     2    /bin/more
     1    /bin/ln
     3    /bin/ls
     1    /bin/ps
     2    /bin/vi
  这里不但给出了缓存中的命令,还有它们在当前登录会话期间被执行的次数。
  可以向hash提供一个命令名参数强制shell在搜索路径下查找命令,并将其放入哈希表中。还可以使用hash的-r选项使bash“忘掉”哈希表中内容。另一选项-p允许你输入命令到哈希表,即使命令不存在也可以。
  可用set命令的hashall选项打开和关闭命令缓存,但通常不需要关闭。
  不要太在意哈希过程的细节。命令缓存和查找在你不知道的情况下就由bash完成了。
**目录搜索路径和变量
  CDPATH变量取值类似于PATH,也是冒号分隔的目录列表。其目的是为cd内置命令提供参数。
  默认情况下,CDPATH没有设置(即为null),当键入cd dirname时,shell会在当前目录下查找名为dirname的子目录。如果设置了CDPATH,则向shell提供了查找dirname的位置列表。列表可能包含也不能不包含当前目录。
  例如,考虑本章前面给出的长cd命令的别名:
  alias cdvoy='cd sipp/demo/animation/voyager'
  现在假定此目录下有许多用户经常进入的目录,为src、bin和doc。可如下定义CDPATH:
  CDPATH=:~/sipp/demo/animation/voyager
  换句话说,定义CDPATH为空字符串(亦即当前目录)后跟~/sipp/demo/animation/voyager。
  设置成功后,如果键入cd doc,shell会查找当前目录下名为doc的子目录。假定没有找到,就会在~/sipp/demo/animation/voyager下查找。在那里shell找到了doc目录,因此就进入了该目录。
  如果工作中的特定项目要经常进入一特定目录组,可以使用CDPATH快速进入。注意,此特性只有在每当你的工作习惯改变后就更新它时才有用。
  bash提供另一引用目录的快捷机制。如果使用shopt设置shell的cdable_vars选项,则向cd命令提供的非目录参数假定为变量。
  可以定义anim为~/sipp/demo/animation/voyager。如果设置了cdable_vars,然后键入:
  cd anim
  当前目录变成~/sipp/demo/animation/voyager。
**杂项变量
  这里给出了对定制来说重要的变量,也有一些用作状态提示和各种杂项目的。其含义相对很直接,表3-7中总结了这些基本变量。
  
  表3-7    状态变量
  变量           含义
  HOME           主目录名
  SECONDS        调用shell的秒数
  BASH           正在运行的shell实例的路径名
  BASH_VERSION   正在运行的shell版本号
  BASH_VERSINFO  正在运行的shell的版本信息数组
  PWD            当前目录
  OLDPWD         最后一个cd命令前的目录
  
  shell设置这些变量值,除了HOME(它由登录过程设置:login、rshd等)。前5个在登录时设置,最后2个在改变目录时设置。虽然可以像其他变量一样设置这些变量值,但很难想象出该设置哪些值。对SECONDS,如果设置为一个新值,它将从赋值开始计数,但如果要清除SECONDS,它将失去其特殊含义。即使后来再次对其赋值也不行。
**定制和子进程
  上面讨论的一些变量可被运行时的命令使用——这一点与shell刚好相反——这样它们就可以对用户环境进行判断。然而,其中大部分在shell外都是未知的。
  这样就存在一个问题:哪些在shell外是已知的,而哪些又只在内部可知呢?该问题是对shell和shell编程产生误解的核心。在回答之前,我们再问细一些:哪些shell事物对子进程是已知的?当输入一个命令时,就是告诉shell要在一个子进程中运行该命令;进一步说,某些复杂的程序也是从其子进程开始的。
  答案不像想象的那么简单。有的事物对子进程是已知的,但反之却并不如此。子进程从不使这些事物对创建它们的进程已知。
  哪些事物已知要依赖于子进程是一个bash程序(见第四章)还是一个交互式shell。如果子进程是一个bash程序,那么它就可能获得本章讲过的几乎所有类型的事物——选项和变量——以及后面讲到的内容。
**环境变量
  默认情况下,只有一种事物对所有子进程已知,即称为环境变量的特定类型的shell变量。书中介绍的一些内置变量实际上就是环境变量:HOME、MAIL、PATH和PWD。
  它们和另一些变量对所有子进程已知的原因很清楚。例如,文本编辑器(如vi和emacs)需要知道用户正在使用的终端类型;环境变量TERM就是其判断方式。
  另一例子是,大部分UNIX邮件程序允许你使用流行文本编辑器编辑信息。mail如何知道所使用的编辑器呢?通过EDITOR的值(有时是VISUAL)。
  任何变量都可能变成一个环境变量。首先它必须像平常那样被定义,然后必须使用下面命令将其导出:
  export varnames
  (varnames可为空格分隔的变量名列表)。可以将变量赋值和导出结合到一个语句中:
  export wonderland=alice
  也可以定义变量为特定子进程内的变量,方式是在命令前加上变量赋值,如下:
  varname=value command
  在目标命令前可加入许多赋值。例如,假定你正在使用emacs编辑器,使用它和终端进行工作时有些问题,因此你想试试TERM的各种取值。可以输入如下命令轻易实现:
  TERM=trythisone emacs filename
  emacs知道了TERM取值为trythisone。但用户shell内该环境变量的值仍为以前的设置值(如果存在)。该语法非常有用,但并不被广泛使用。在本书其余部分也不会出现更多这样的情况。
  环境变量都很重要。大部分.bash_profile文件都包含了环境变量的定义。本章前面的内置的.bash_profile包含了6个这样的定义:
  PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/bin
  SHELL=/bin/bash
  MANPATH=/usr/man:/usr/X11/man
  EDITOR=/usr/bin/vi
  PS1='\h:\w\$ '
  PS2='> '
  export EDITOR
  可以发现哪些是环境变量——通过键入不带参数的export命令或使用选项-p打印出它们的值。
  某些环境变量名被很多应用程序使用,以至于变成了许多shell环境的标准。这些变量没有内置到bash,但某些shell,如Korn shell则将其内置。表3-8列出了最常用的一些。
  
  表3-8   标准变量
  变量    含义
  COLUMNS 显示器的列数
  EDITOR  文本编辑器的路径名
  LINES   显示器的行数
  SHELL   正在运行的shell的路径名
  TERM    正在使用的终端的类型
  
  你会发现在自己的环境中它们已经存在,其中大部分来自系统/etc/profile文件(见第十章)。可以在自己的.bash_profile中定义它们,并向前面一样导出。
**终端类型
  变量TERM对使用整个屏幕或窗口的任何程序都很重要,如文本编辑器。这些程序包括了所有的屏幕编辑器(如vi和emacs)、more和许多第三方应用程序。
  因为用户在程序上花费的时间越来越多,而在shell上花费的越来越少,因此正确的设置TERM非常重要。这实际上是系统管理员的工作(或为你而做),但有时需要自己动手。下面是其基本思路。
  TERM的值必须是在terminfo数据库中以文件名出现的小写字母组成的缩写字符串。该数据库是根目录/usr/lib/terminfo下两层的文件目录。该目录包含了单个字符的子目录。它们依次包含了其名字以该字符开始的所有终端的信息文件。每个文件描述如何通知终端处理像定位屏幕上的光标、进入反向视频、滚动、插入文本等问题。描述是二进制形式的(亦即不可读)。
  终端描述文件的名字和被描述的终端的名字一样,有时候使用缩写形式。例如,DEC VT100的描述在文件/usr/lib/terminfo/v/vt100。X视窗系统下xterm终端窗口的描述文件为/usr/lib/terminfo/x/xterm。
  有时UNIX系统软件设置了不正确的TERM,这通常发生在X终端和基于PC的UNIX系统上。因此,你需要在做进一步工作前通过键入echo $TERM检查TERM的取值。如果发现UNIX系统中该值设置不正确(特别是如果终端和计算机上的设置不同),就需要自己找出正确的TERM值。
  查找TERM取值最好的方式——如果你找不到本地的系统管理员来做这件事——那就猜测terminfo的名字,通过使用ls查找/usr/lib/terminfo下的文件名。例如,如果终端是Hewlett-Packard 70092,可以试试:
  $ cd /usr/lib/terminfo
  $ ls 7/7*
  如果成功,就会看到:
  70092   70092A  70092a
  这里,有3个名字最有可能与终端描述一样,因此可以使用其中任意一个作为TERM值。换句话说,把下面3行中的任意一行放在.bash_profile中:
  TERM=70092
  TERM=70092A
  TERM=70092a
  如果不成功,ls会给出错误信息,你可以再试试另一个。如果发现terminfo不包含与终端类似的文件,它们都不对,就要查看自己终端的帮助页观察该终端是否可以仿效一个流行模式。这样做成功的几率会很高。
  反过来说,terminfo可能有和用户终端相关的几个接口,针对子模式、特殊模式等的接口。如果你可以选择几个接口作为TERM取值,最好对文本编辑器或任何其他面向屏幕的程序试试所有可能的选择,并从中选择一个最合适的。
  如果你使用的是一个窗口系统,过程相对简单一些。这种情况下,用户终端是屏幕的逻辑部分而不是物理设备,可以编写与操作系统无关的软件来控制终端窗口。如果你知道如何处理窗口大小调整和复杂光标移动等操作,就有能力处理类似TERM的简单事物。例如,在一个xterm终端窗口中X视窗系统自动设置TERM值为xterm。
**其他常用变量
  某些程序需要知道要使用的编辑器类型,如mail。大多数情况下它们默认为类似ed的常用编辑器,除非设置了EDITOR变量为流行编辑器的路径,并将其导入到.bash_profile文件中。
  一些程序在其运行时将shell当做子进程(例如,mail程序和emacs编辑器的shell模式);按惯例,它们使用SHELL变量判断所用的shell。SHELL通常由调用登录shell的进程设置,通常为login或类似rshd(如果选择远程登录的话)。bash只有在它仍未被设置时才会设置它。
  你可能注意到,SHELL取值看起来和BASH一样。这两个变量目的有点不同。BASH设置为当前shell的路径名,不管它是否是交互式shell;而SHELL被设置为登录shell名字,它可以是一个完全不同的shell。
  COLUMNS和LINES被类似vi的面向屏幕的编辑器使用。大多数情况下,如果未定义,则默认可用。但如果面向屏幕的应用程序显示出现了问题,就需要检查这些变量的正确性。
**环境文件
  虽然环境变量总是对子进程已知,仍然必须通知shell哪些其他变量、选项、别名等和子进程通信。方式是将所有的定义放入环境文件中。bash的默认环境文件是.bashrc文件,在本章开始时我们简单介绍过它。
  记住,如果采用.bash_profile外的定义并将其放入.bashrc里,就必须在.bash_profile中结尾处加入source .bashrc一行。这样该定义才对登录shell可用。
  环境文件的思路来源于C shell的.cshrc文件,.bashrc的名称反映了这一点。初始化文件的rc后缀在UNIX中特别普遍。
  作为一条一般规则,应该将尽可能少的定义放在.bash_profile中,而将尽可能多的定义放在环境文件中,因为这些定义是加入到环境而不是移走,那么对于子进程的工作,出错的几率就会大大减小(例外情况是过分使用别名引起的名称冲突)。
  .bash_profile里唯一不能少的是环境变量和它们的导出,以及那些不是定义但实际上是登录时运行或产生输出的命令。选项和别名定义都可放入环境变量。实际上,许多bash用户的.bash_profile文件都很小,例如:
  stty stop ^S intr ^C erase ^? 
  date
  source .bashrc
  虽然该文件很小,但其用户的环境文件可能会很巨大。
**定制提示
  本章介绍的技术应该很易于理解。最好的策略是在登录会话期间在shell中键入它们进行测试;然后如果决定要使其成为环境的永久部分,则将其加入到自己的.bash_profile。
  一种无需进入文本编辑器而向.bash_profile添加环境文件的最好方式是利用echo命令以及bash的一种编辑模式。如果键入一个定制命令,以后决定要将其加入到.bash_profile中,可以通过CTRL-P或CTRL-R(emacs模式下)或j,-,?(vi模式下)调出它。如下面的语句:
  PS1="\u \!—> "
  调出它之后,编辑该行以便在前面加上echo命令,并把它用单引号引起来,后面跟着一个I/O重定向符(第七章中将介绍)将输出附加到文件.bash_profile。
  $ echo 'PS1="\u \!—> " ' >> ~/.bash_profile
  记住,单引号很重要,因为它阻止shell解释美元符号、双引号和惊叹号。另外应使用两个大于号(>>),一个大于号表示覆盖而不是附加。
 

猜你喜欢

转载自blog.csdn.net/chenzhengfeng/article/details/81558731