第六章 使用shell

    主要内容

          1.什么是shell

          2.shell的基本操作

          3.通配符与文件名匹配

          4.输入输出重定向

          5.shell变量

          6.shell中的其他常用转义字符

          7.命令的别名

          8.定制用户环境 6.1 什么是Shell 1.shell的历史

    终端(Terminals):

    UNIX是可以在许多种机器上运行的操作系统,但人们又如何使用这些机器呢?过去,他们是通过哑终端来连接到这些机器,也就是用键盘、显示器及足够的电子元件组成的机器与中央计算机相连。那么,现在这些东西都在哪儿?为什么我们使用UNIX/Linux时见不到“终端”呢?这是因为终端的厂家无法达成一项最终标准,这导致每种牌子的终端都有各自的键盘布局、各自的在屏幕上显示字符的方法、发送或接收什么信号表示什么字符、控制代码,等等。为了避免这些混乱,就创建了一个含有所有不同终端特性的(capability)文件,这就是“termcap”。打开文件/etc/termcap就可以看到。Linux终端大多数用“vt100”或“Linux”作为终端类型。

    xterms:

    在20世纪80年代初期,产生了一个UNIX的图形子系统——the X Window System。90年代早期,为了更好地实现基于Intel的UNIX类系统上(如FreeBSD、NetBSD、Linux)的应用,产生了一个系统分支—XFree86。

    X Window中一个很大的好处是可以运行多个虚拟(virtual)终端。甚至在X Window下就有这么个应用程序——“xterm”。您将发现“xterm”和“virtual terminal”在很多情况下都是一样的。有的地方说“打开一个xterm”,其实您不是非要用“xterm”程序,其他的终端模拟器(terminal emulator),如rxvt、konsole、aterm、eterm、wterm等等,一样有效。

    终端模拟器(又称为虚拟终端)通过伪(pseudo)tty设备——pty与系统相连,并且使用自己的显示标准——xterm。这导致不同的终端模拟器可能在一些按键或程序上存在细小的差别,这取决于模拟器多大程度上遵守了“xterm”的显示标准。

    2.shell的作用

    为了在终端中运进程序,需要shell。shell是操作系统的一部分,用来与用户打交道,并且可以用来协调各个命令。

    shell的作用主要有:

    (1)shell是系统用户界面,提供了用户与操作系统内核进行交互操作的接口。

    (2)shell是命令解释器,解释用户输入的命令,把它们送到内核去执行。

    (3)shell是脚本编程语言环境。

    Linux中的shell的主要作用是用户与操作系统内核之间的一个接口,功能是解释用户的命令并且将命令送到内核中去执行。通过shell,可以同时在后台运行多个应用程序,并且把需要与用户交互的程序放在前台运行。通过在多条命令的序列中使用变量和流程控制,shell可以作为一名复杂的编程语言。第一个真正的UNIX shell——“sh”,亦称为“Bourne shell”,诞生于1975年,作者是Steve Bourne。很快,出现了其他shell,如基于原始“Bourne shell”的“ksh”、“zsh”,后者常用作专属Unixes系统中的标准shell。也有一些从C语言中衍生出来的shell,如“csh”或“tcsh”。发现到现在,shell主要有:csh,tcsh,pdksh,ash,sash,zsh,bash等。Linux的默认shell为bash(Bourne Again Shell)。这个shell功能非常强大。

    虽然Linux发展到现在,普通用户可以在图形界面下作很多事情,但是,对于高级用户和系统管理员面言,很多功能必须在shell中才能完成。而Linux强大的功能往往在shell中才能体现出来。 6.2 shell基本操作 Linux中的shell命令行有许多非常实用的功能,如:自动补齐、命令行的历史记录和编辑命令行。下面分别讨论。 一、自动补齐 如何用“cd”(改变目录,change directory)最快地从您当前所在的home目录跳到“/ usr/src/redhat/”呢?

    cd/u<TAB>sr<TAB>r<TAB>

    这称为“命令行自动补齐”(automatic command line completion),这在平常应用中是不可缺少的。让我们仔细看看这个例子:

    cd/u<TAB>

    扩展成了cd/usr/,下面的

    cd/u<TAB>sr<TAB>

    扩展为cd/usr/src/。如果只敲了cd/us,“/usr”下匹配的(“cd/u/s”)三个子目录将列出供您选择:“/usr/sbin”、“/usr/share”和“/usr/src”。

    因此,<tab>键可以很方便地用于根据前几个字母,来查找匹配的文件或子目录。比如,ls/usr/bin/zip将列出所有“/usr/bin”下面,以字符串“zip”开头的文件或子目录。当然,完成这类任务还有更厉害的命令,但这个方法确实很管用。

    另外,碰到长文件名时就显得特别方便。假设您要安装一个名为“boomshakalak-whizbang-4.6.4.5-i586.rpm”的RPM包,您输入rpm-i boom,如果目录下没有其他文件能够匹配,那shell就会自动帮忙补齐。这种补齐对命令也有效:

    [tom@belbo tom]$gre<TAB>

    grecord grefer grep

    [tom@belbo tom]$gre

    在这里shell将列出所有以字符串“gre”开头的已知命令。 二、命令行的历史记录 通过按向上方向键,用户可以向后遍历近来在该控制台下输入的命令。用向下方向键可以向前遍历。与Shift键连用的话,还可以遍历以往在该控制台中的输出。也可以通过光标和功能键(Home、End等键)编辑旧的命令,然后再运行。 6.3 通配符与文件名匹配 shell在处理用户发出的命令时,如果遇到一些特殊的字符,就会作出特别的解释,这些有特殊含义的字符叫做转义字符(metacharacters)。例如:<>|;!?[]$\“`~(){}。下面分别讨论一下shell中常用的转义字符。 一、通配字符(wildcards)与文件名匹配 通配符(wildcards)使得用户在进行文件名匹配时不必一一写出名称,就可以指定多个文件。常用到的通配符主要有:?[][-][!]等。

    假设用户想用“rm”命令删除目录下所有以字符串“.bak”结尾的文件。除了在“rm”后跟上所有文件名作为参数,还可以用通配符“*”:

    rm*.bak

    ”可匹配一个或多个字符。在本例中,您告诉shell将命令“rm”的参数扩展到“所有以‘.bak’结尾的文件”,shell就将扩展后的参数告诉“rm”命令。

    再比如有个目录,其中含文件“124.bak”、“346.bak”及“583.bak”。您想只保留文件“583.bak”,可以用:

    rm4.bak

    shell就将“4.bak”扩展成“所有含“4”并以“.bak”结尾的字符串”。

    注意到rm 4*.bak无法工作,因为这匹配的是以“4”开头的文件。由于目录中没有这样的文件,shell将这个模式扩展为空的字符串,故“rm”将返回出错信息:

    rm:cannot remove‘4*.bak’:No such file or directory

    如果您想保留文件“345.bak”,而删除“124.bak”和“583.bak”。这看起来有些难度,因为被删文件的名称除了后缀其他都不同。但幸运的是,您可以用不含有来指定文件:

    rm*[!6].bak

    这将被读为:除了以“6.bak”结尾的文件,删除其他所有以“.bak”结尾的文件。您必须将取反号(negation sign)与取反字符(这里是6)放到括号中,不然的话,shell会将惊叹号(exclamation mark)解释成历史记录替换的开始(the beginning of a history substitution)。取反号在本篇介绍的所有匹配模式中都有效。

    请注意:通配符“*”与取反号连用,很容易产生问题。

    rm[!6].bak

    表示什么?这个命令将删除所有文件,甚至包括名称中包含“6”的文件。如果您将通配符“*”放到了取反号前面和后面,实际上取反号将失效,因为shell将其解释为“所有名称中任何位置都不含该字符的文件”。在我们的例子里,只有文件“666.bak”不符合该模式。

    第二个通配符是问号(question mark):“?”。在匹配时,一个问号只能代表一个字符。为了示范其用途,我们在上例的假设中添加两个新文件:“311.bak~”和“some.text”。现在,列出所有在点号后有四个字符的文件:

    ls*.????

    问号通配符能够有效地避免上面提到的“取反号陷阱”(negation trap):

    rm[!4]?.

    将扩展成“所有除了点号前倒数第二个字符为4的文件”,也就是只保留文件“346.bak”。

    其实也可以这样:

    ls[13]*

    将列出所有以字符“1”或“3”开头的文件。在我们的例子中,文件“124.bak”、“311.bak~”和“346.bak”匹配。注意到您必须用中括号将匹配的模式括起来,否则模式只匹配以字符串“13”开头的文件。

    再比如,ls[3-8]?.

    将列出所有点号前倒数第二个字符落在“3”到“8”范围的文件。在我们的例子中,匹配的文件是“346.bak”和“583.bak”。 二、引用shell的特殊字符 但是,上面的那些机制存在一个缺点:shell总在命令执行前,试着进行扩展。有时候,会变得很棘手:

    (1)文件名包含特殊字符。假设您在那个目录中还有一个名为“!56.bak”的文件。下面试图进行模式匹配:

    rm!*

    rm

    rm:too few arguments

    shell将“!*”解释成历史记录的替换(加入前一个命令的所有参数),而不是匹配方式。

    (2)命令本身带特殊字符作参数。一些Linux下的命令行工具,比如(e)grep、sed、awk、find及locate,都使用自己的正则表达式(regular expressions)。这些表达式与模式匹配看起来惊人的相似,但在某些地方又有所不同。

    但为了使这些特殊命令生效,shell就不能先将其当作模式匹配来解释:

    find.-name[1-9]*-print

    find:paths must precede expression

    应该是:

    find.-name“[1-9]*”-print

    ./346.bak

    ./124.bak

    ./583.bak

    ./311.bak~

    可以通过反斜线(back slash)来引用特殊字符,比如!、$、?或空格:

    ls\!*

    !56.bak

    或者用(单)引号:

    ls“!”*

    !56.bak

    请注意,要看清楚引号应该放在什么位置。命令ls!将查找名为“!”的文件,这是由于通配符也在引号间,所以只能依照字面来解释。 6.4 输入输出重定向 Linux的理念是汇集许多小程序,每个程序都有特殊的专长。复杂的任务不是由大型软件完成,而是运用shell的机制,组合许多小程序共同完成。重定向就在其中发挥着重要的作用。

    系统定义三个标准文件:标准输入(stdin)、标准输出(stdout)、标准错误输出(stderr)。其中标准输入缺省是键盘,标准输出和标准错误输出缺省是显示器。但是,可以通过重定向改变命令的输入和输出。

    1.输出重定向

    有时,您希望将命令的输出结果保存到文件中,或以文件内容作为命令的参数。这可以通过“>”来实现。

    command>file 将command的输出保存到file中,如果file文件不存在,就产生一个新文件file,如果file文件已经存在,将覆盖file中的内容:

    例:ls>dirlist 将当前目录的内容保存到“dirlist”文件。

    另一种操作符是“>>”,这将输出添加到已存在的文件中:

    echo“string”>>file

    如果file文件不存在,就产生一个新文件file,如果file文件已经存在,将string加到文件file的尾部。

    2.输入重定向

    command<file

    将file内容作为command的输入:

    sort<dirlist>sdirlist

    将文件“dirlist”的内容送到命令“sort”,然后再将排序后的结果送到文件“sdirlist”。当然,您也可以一步到位。

    3.错误输出重定向

    如果在执行一条命令的过程中,系统有错误信息,如:

    cat filea

    shell给出如下的错误提示:

    cat:filea:No such file or directory

    如果想将这些错误提示保存在文件errfile中,可以写为

    cat filea 2>errfile

    这时如果errfile已经存在,则被覆盖,如果只是想追加到文件尾,则可以用

    cat filea 2>>errfile

    另外一种常见的重定向错误输出的场合是直接丢弃这些错误提示,如:

    cat filea 2>/dev/null

    在一条命令中,可以同时使用输入重定向、输出重定向和错误输出重定向,如:

    command<inputfile>outputfile 2>errfile

    4.管道

    管道符:”|”,将前一个命令的输出转成下一个命令的输入。

    语法是:command1|command2|command3等等。

    管道经常将一个程序的输出送到“more”或“less”来阅读。

    ls-l|less

    其中,第一个命令提供目录内容,第二个则将其以翻页的方式显示。更复杂的例子如:

    rpm-qa|grep^x|less

    第一个命令给出所有已安装的RPM包,第二个则将其过滤(filter:“grep”),只剩下以“x”开头的包,第三个命令则将结果以翻页的方式显示。

    5.过滤器(filters)

    过滤器用来接收标准输入,经过一定的转化,再写到标准输出。所以,过滤器一般放在管道符中间。

    常用的过滤器如下:expand,sed,awk,fmt,tac,tr,grep,nl,pr。

    tee命令接收标准输入并将数据输出到标准输出和一个文件内。 6.5 shell变量 shell变量(Variables)又叫做环境变量,可以定制用户本身的工作环境。使用变量可以保存有用信息,使系统获知用户相关设置。变量也用于保存暂时信息。shell变量分两类,一类是用户自定义的变量,一类是系统预定义的变量。作为惯例,所有系统预定义的环境变量名都是大写。当然,用户可以自己定义一些变量,如“$path”、“$pAtH”,但shell不会理睬这些变量。使用set命令查看当时系统中定义的变量。 一、用户自定义变量 用户自定义变量时,在命令行上打入:变量名=变量值,可以定义变量。Shell变量没有类型的区分,都是字符串型。在定义变量时,“=”两边不能加空格。

    例如:

    $var1=2

    $var2=“hello world”

    使用echo$变量名 查看变量值。

    例如:

    $echo$var1

    在一个shell中定义的变量,是不能直接传递它的子进程或子shell使用的。如果在父shell中定义的变量想传递给子进程,则需要export命令。通过export命令,父shell将变量“复制”给子进程,子进程中对变量的任何修改,不会带回父shell。

    例1:

    $xy=hello     ;在父shell中定义变量xy,值为hello

    $bash;启动子shell

    $echo$xy      ;在子shell中查看变量xy的值,为空

    例2:

    $export xy=hello  ;在父shell中定义变量xy,值为hello,并将变量export

    $bash;启动子shell

    $echo$xy      ;在子shell中查看变量xy的值,为hello hello

    $xy=goodbye    ;在子shell中修改变量xy的值为goodbye

    $exit        ;返回父shell

    $echo$xy      ;在父shell中查看变量xy的值,仍为hello hello

    使用unset取消对变量的定义。例:

    $unset xy 二、系统预定义变量 系统定义了一些特殊的变量。

    $TERM(当前终端类型)

    $PATH(命令的搜索路径)

    $MANPATH(手册页存放的目录)

    $HOME(用户主目录)

    $LANG(当前使用语言)

    $PS1,$PS2(命令提示符)

    $SHELL(当前shell名)。

    ……

    下面介绍一些常用的系统预定义变量。

    1.命令提示符

    每次当用户打开一个控制台(console)或xterm时,最先看到的就是提示符(prompt),类似于:

      account@hostname~$

    在默认设置下,提示符将显示用户名、主机名(默认是“localhost”)、当前所在目录。

    按照传统,最后一个字符可以标识该用户是普通用户($),还是“root”(#)。命令:

      echo$PS1

    将显示当前的设定。

    可以通过$PS1变量来设置提示符。其中可用字符的含义在man bash的“PROMPTING”部分,对这些参数(parameter)有详细说明。用户可以加入一些小玩意,如不同格式的当前时间,命令的历史记录号,甚至不同的颜色。

    例如:PS1=“\u:\w\$”

    这样,提示符就变成:

    user_name:/usr/bin$

    可以通过命令export来测试不同的设置(比如,export PS1=“\u:\w\$”)。如果找到了适合的提示符,就将设置放到您的“.bashrc”中。这样,每次打开控制台或终端窗口时,都会生效。

    如果一条命令太长,那么可以在输入完一行后加“\”表示另起一行继续输入,下一行的提示符变为“>”。这个提示符叫做第二提示符,可能通过变量$PS2来查看或更改。

    2.PATH

    “$PATH”与“$PS1”一样,也是环境变量。输入

    set

    将列出所有当前定义的环境变量。

    环境变量在shell的配置文件中定义,可能是用户自己的配置文件,也可能是由“root”通过“/etc”下面的系统级文件定义的。如果您使用X,更多的一些变量将由X、您的窗口管理器或桌面环境的启动文件配置。

    如果对这些设置不很清楚,最好不要随便改动。了解如何改变$PATH变量很有用,因为这个变量决定了shell将到哪些目录中寻找命令或程序。如果要执行的命令的目录在$PATH中,就不必输入这个命令的完整路径,直接输入命令就可以了。一些第三方软件没有将可执行文件放到Linux的标准目录中。因此,将这些非标准的安装目录添加到$PATH是一种解决的办法。

    处理$PATH变量要注意的是:不能只替换变量,而是要将新的字符串添加到原来的值中。在大多数情况下,不能用“PATH=/some/directory”,因为这将删除$PATH中其他的所有目录,这样用户在该终端运进程序时,就不得不给出完整路径。所以,只能作添加:

    PATH=$PATH:/some/directory

    这样,PATH被设成当前的值(以$PATH来表示)+新添的目录。

    请不要在“.bashrc”中设置PATH,否则会导致PATH中目录的意外增长。您每次打开一个新的shell,“.bashrc”都会作用。所以如果在该文件中添加目录,您每次打开一个终端,目录又会被添加。这将导致PATH变量由于目录复制,不断地增长。 6.6 shell中的其他常用转义字符 除了前面介绍通配字符外,还有几个字符在shell中也有着特殊的含义。下面做一个小结。

    1.反引号`和$()

    被反引号引起来的部分首先被执行,其结果放在外面的命令中。如:

    $echo Now is`date`

    Now is Sat Dec 31 15:02:31 CST 2005

    $()与`作用相同。如

    $echo There are$(ps aux|wc-l)processes running

    There are 226 processes running

    2.单引号’

    如果命令中的某部分被单引号包含,那么其中的所有字符均不转义,而是保留原来的样子。如:

    $xy=hello

    $echo$xy world

    hello world

    $echo‘$xy world’

    $xy world

    3.双引号”

    双引号与单引号类似,但是有三个字符例外,它们是反引号`,反斜杠\和$。如:

    $xy=hello

    $echo$xy world

    hello world

    $echo“$xy world”

    hello world

    4.反斜杠\

    反斜杠表示下面一个字符不需要转义,如:

    $echo US\$

    US$

    另外,在一行命令的结尾处加\,表示本行没有结束,下行是续行。 6.7 命令的别名 记住所有的命令及各自带的可选项,然后每次一一输入,这确实有点枯燥。但幸运的是,用户可以为常用命令定义快捷方式。这些快捷方式可以用较简单的命令别名(alias),或复杂一些的shell函数的语法来定义。

    命令的别名

    定义别名的语法是:

    alias shortcut=command

    例如,经常用到的命令ls-l,就可以设置一个别名:

    alias ll=ls-l

    以后,就可以使用命令ll了,作用与ls-l相同。

    命令中有空格的话,就需要用引号(如在命令与可选项间就有空格)。请注意,您可以用单引号或双引号,但它们是有区别的。

    单引号将剥夺其中的所有字符的特殊含义,而双引号中的“$”(参数替换)和“`”(命令替换)是例外。这意味着,如果您想在别名中应用变量或命令的替换,就得用双引号。

    您可以用“alias”在命令行快速地创建别名,或将命令放到各自的“~/.bashrc”,或放到系统级的“/etc/profile.d/alias.sh”中(而在Mandrake Linux 8以前的版本里,用的是“/etc/bashrc”)。要删除一个别名,只要输入:unalias alias。运行alias将列出您系统中所有定义的别名。 6.8 定制用户环境 用户在登录shell时,会依次执行一系列的脚本配置文件。这些配置文件是用来定制用户环境的。登录BASH时,用户将依次执行一系列的脚本:/etc/profile,$HOME/.bash_profile(如果没有,执行$HOME/.bash_login,还没有,执行$HOME/.profile)。注销时,会自动执行$HOME/.bash_logout。使用redhat的用户,登录时除了这两个文件以外,还会自动执行$HOME/.bashrc,这个文件又会再执行/etc/bashrc。

    bash配置文件

    在用户的home目录下,有一些以.bash开头的隐藏文件。

    .bash_history:记录了您以前输入的命令,

    .bash_logout:当您退出shell时,要执行的命令,

    .bash_profile:当您登入shell时,要执行的命令,

    .bashrc:每次打开新的shell时,要执行的命令。

    请注意后两个的区别:“.bash_profile”只在会话开始时被读取一次,而“.bashrc”则每次打开新的终端(如新的xterm窗口)时,都要被读取。按照传统,您得将定义的变量,如PATH,放到“.bash_profile”中,而像aliases(别名)和函数之类,则放在“.bashrc”。但由于“.bash_profile”经常被设置成先读取“.bashrc”的内容,您如果图省事的话,就把所有配置都放进“.bashrc”。

    这些文件是每一位用户的设置。系统级的设置存储在“/etc/profile”、“/etc/ bashrc”及目录“/etc/profile.d”下的文件中。但您得习惯用各自的配置文件:编辑不需要“root”权限,还可以使您的设置更有个性。当系统级与用户级的设置发生冲突时,将采用用户的设置。

    上面的这些文件是每位用户的设置,系统级的设置存储在“/etc/profile”、“/etc/ bashrc”及目录“/etc/profile.d”下的文件中。您最好习惯使用各自的配置文件:编辑不需要“root”权限,还可以使您的设置更具个性。当系统级与用户级的设置发生冲突时,将优先采用用户的设置。

    shell、转义字符、通配符、重定向、环境变量、别名

    1.常用的shell有哪几种?Linux系统中默认的shell是什么?

    2.shell的主要功能是什么?bash有什么特点?

    3.说明三种引号的作用有什么区别。

    4.别名的作用是什么?如何使用?

    5.如何定制用户环境?