当前位置: > shell编程 >

在Linux世界驰骋系列之Shell编程

时间:2014-04-29 02:04来源:linux.it.net.cn 作者:IT网
Linux环境中,Shell不仅是常用的命令解释程序,而且是高级编程语言。本讲介绍Shell概述、Shell变量、位置参数、特殊符号、别名、各种控制语句、函数等Shell编程知识。

    Shell是Unix/Linux系统中一个重要的层次,它是用户与系统交互作用的界面。在以前介绍Linux命令时,Shell都作为命令解释程序出现:它接收用户输入的命令,进行分析,创建子进程,由子进程实现命令所规定的功能,等子进程终止工作后发出提示符。这是Shell最常见的使用方式。

    Shell还是一种高级编程语言,它有变量、关键字,有各种控制语句,如if、case、while、for等语句,支持函数模块,有自己的语法结构。利用Shell程序设计语言可以编写出功能很强、但代码简单的程序。特别是它把相关的Linux命令有机地组合在一起,可大大提高编程的效率,充分利用Linux系统的开放性能,能够设计出适合自己要求的命令。

    Shell概述

    Shell的概念最初是在Unix操作系统中形成和得到广泛应用的。Unix的Shell有很多种类,Linux系统继承了Unix系统中Shell的全部功能,现在默认使用的是bash。

    1.Shell的特点

    Shell具有如下突出特点:

    (1)把已有命令进行适当组合构成新的命令。

    (2)提供了文件名扩展字符(通配符,如* 、 ?、 [ ]),使得用单一的字符串可以匹配多个文件名,省去键入一长串文件名的麻烦。

    (3)可以直接使用Shell的内置命令,而不需创建新的进程,如Shell中提供的cd、echo、exit、pwd、kill等命令。为防止因某些Shell不支持这类命令而出现麻烦,许多命令都提供了对应的二进制代码,从而也可以在新进程中运行。

    (4)Shell允许灵活地使用数据流,提供通配符、输入/输出重定向、管道线等机制,方便了模式匹配、I/O处理和数据传输。

    (5)结构化的程序模块,提供了顺序流程控制、条件控制、循环控制等。

    (6)Shell提供了在后台执行命令的能力。

    (7)Shell提供了可配置的环境,允许创建和修改命令、命令提示符和其它的系统行为。

    (8)Shell提供了一个高级的命令语言,能够创建从简单到复杂的程序。这些Shell程序称为Shell脚本,利用Shell脚本,可把用户编写的可执行程序与Unix命令结合在一起,当作新的命令使用,从而便于用户开发新的命令。

   

    2.常用Shell类型

    Linux系统提供多种不同的Shell以供选择。常用的有Bourne Shell(简称sh)、C-Shelll(简称csh)、Korn Shell(简称ksh)和Bourne Again Shell (简称bash)。

    (1)Bourne Shell是AT&T Bell实验室的 Steven Bourne为AT&T的Unix开发的,它是Unix的默认Shell,也是其它Shell的开发基础。Bourne Shell在编程方面相当优秀,但在处理与用户的交互方面不如其它几种Shell。

    (2)C Shell是加州伯克利大学的Bill Joy为BSD Unix开发的,与sh不同,它的语法与C语言很相似。它提供了Bourne Shell所不能处理的用户交互特征,如命令补全、命令别名、历史命令替换等。但是,C Shell与BourneShell并不兼容。

    (3)Korn Shell是AT&T Bell实验室的David Korn开发的,它集合了C Shell和Bourne Shell的优点,并且与Bourne Shell向下完全兼容。Korn Shell的效率很高,其命令交互界面和编程交互界面都很好。

    (4)Bourne Again Shell (即bash)是自由软件基金会(GNU)开发的一个Shell,它是Linux系统中一个默认的Shell。Bash不但与Bourne Shell兼容,还继承了C Shell、Korn Shell等优点。

    3. Shell脚本的建立

    Shell程序可以存放在文件中,这种被Shell解释执行的命令文件称为Shell脚本 (Shell Script)。Shell脚本可以包含任意从键盘键入的Linux命令。

    建立Shell脚本的步骤同建立普通文本文件的方式相同,利用编辑器(如vi)进行程序录入和编辑加工。例如,要建立一个名为ex1的Shell脚本,可在提示符后输入命令:

    $ vi ex1

    进入vi的插入方式后,就可录入程序行。完成编辑之后,将编辑缓冲区内容写入文件中,返回到Shell命令状态。

    4.执行Shell脚本的方式

    执行Shell脚本的方式基本上有三种:

    (1)输入定向到Shell脚本

    这种方式是用输入重定向方式让Shell从给定文件中读入命令行并进行相应处理。其一般形式是:

    $ bash

    例如:

    $ bash

    Shell从文件ex1中读取命令行,并执行它们。当Shell到达文件末尾时就终止执行,并把控制返回到Shell命令状态。此时,脚本名后面不能带参数。

    (2)以脚本名作为参数

    其一般形式是:

    $ bash 脚本名 [参数]

    例如:

    $ bash ex2 /usr/meng /usr/zhang

    其执行过程与上一种方式一样,但这种方式的好处是能在脚本名后面带有参数,从而将参数值传递给程序中的命令,使一个Shell脚本可以处理多种情况,就如同函数调用时可根据具体问题给定相应的实参。

    如果以目前Shell(以·表示)执行一个Shell脚本,则可以使用如下简便形式:

    $ · 脚本名 [参数]

    以Shell脚本作为Shell的命令行参数,这种方式可用来进行程序调试。

    (3)将Shell脚本的权限设置为可执行,然后在提示符下直接执行它。

    通常用户是不能直接执行由正文编辑器(如vi)建立的Shell脚本的,因为直接编辑生成的脚本文件没有“执行”权限。如果要把Shell脚本直接当作命令执行,就需要利用命令chmod 将它置为有“执行”权限。例如,

    $ chmod a+x ex2

    就把Shell脚本ex2置为对所有用户都有“执行”权限。然后,在提示符后输入脚本名ex2就可直接执行该文件。注意,此时该脚本所在的目录应被包含在命令搜索路径(PATH)中。例如:

    $ ex2

   

    Shell接收用户输入的命令(脚本名),并进行分析。如果文件被标记为可执行的,但不是被编译过的程序,Shell就认为它是一个Shell脚本。Shell将读取其中的内容,并加以解释执行。所以,从用户的观点看,执行Shell脚本的方式与执行一般的可执行文件的方式相似。

    因此,用户开发的Shell脚本可以驻留在命令搜索路径的目录之下(通常是“/bin”、 “/usr/bin”等),像普通命令一样使用。这样,也就开发出自己的新命令。如果打算反复使用编好的Shell脚本,那么采用这种方式就比较方便。

    Shell脚本经常被用来执行重复性的工作,例如,当进入系统时要查看有无信件、列出谁在系统中、将工作目录改到指定目录并予以显示、印出当前日期等。完成这些工作的命令是固定的。为了减少录入时间,可把这些命令建立在一个Shell脚本中,以后每次使用该文件名就可执行这些工作。

    另外,完成某些固定工作时需输入的命令很复杂,例如文件系统的安装(mount),要带多个选项和参数。此时,利用Shell脚本存放该命令,以后用时就很方便了。

    Shell变量

    Shell中也采用变量,用来存放字符串。Shell变量比C语言中的变量简单得多,没有众多存储类及类型的限制,也不需要预先定义、然后才能赋值,可以在使用时“现定义、现赋值”。

    Shell有环境变量和临时变量。环境变量是永久性变量,其值不会随Shell脚本执行结束而消失。临时变量是在Shell程序内部定义的,其使用范围仅限于定义它的程序,出了本程序就不能再用它;而且当程序执行完毕,它的值也就不存在了。

    1.用户定义的变量

    用户定义的变量是最普通的Shell变量。变量名是以字母或下线符打头的字母、数字和下线符序列,并且大小写字母意义不同。如dir与Dir是不同的变量。这与C语言中标识符的定义相同。变量名的长度不受限制。

    定义变量并赋值的一般形式是:

    变量名=字符串

    例如:

    myfile=/usr/meng/ff/m1.c

    注意,在赋值语句中,赋值号“=”的两边没有空格,否则在执行时会引起错误。

    变量的值可以改变,只须利用赋值语句重新给它赋值即可。一个未明确赋过值的变量仅含一个空字符串。

    在程序中使用变量的值时,要在变量名前面加上一个符号“$”。例如,

    $ dir=/usr/meng/ff

    $ echo $dir

    /usr/meng/ff ←——— 显示结果

    $ echo dir

    dir ←——— 显示结果

    如果在赋给变量的值中要含有空格、制表符或换行符,那么,就应该用双引号把这个字符串括起来。例如,

    names="Zhangsan Lisi Wangwu"

    可以将一个命令的执行结果赋值给变量。有两种形式的命令替换:一种是使用倒引号引用命令,其一般形式是: `命令表`。

    例如:将当前工作目录的全路径名存放到变量dir中,输入以下命令行:

    $ dir=`pwd`

    另一种形式是:$(命令表)。上面的命令行也可以改写为:

    $ dir=$(pwd)

   

    2.数组

    bash只提供一维数组,并且没有限定数组的大小。类似与C语言,数组元素的下标由0开始编号。获取数组中的元素要利用下标。下标可以是整数或算术表达式,其值应大于或等于0。用户可以使用赋值语句对数组变量赋值。对数组元素赋值的一般形式是:数组名[下标]=值例如:

    $ city[0]=Beijing

    $ city[1]=Shanghai

    $ city[2]=Tianjin

    $

    也可以用declare命令显式声明一个数组,一般形式是:

    declare -a 数组名

    读取数组元素值的一般格式是:

    ${数组名[下标]}

    例如:

    $ echo ${city[0]}

    Beijing

    一个数组的各个元素可以利用上述方式一个元素一个元素地赋值,也可以组合赋值。定义一个数组并为其赋初值的一般形式是:

    数组名=(值1 值2 ... 值n)

    其中,各个值之间以空格分开。

    例如:

    $ A=(this is an example of shell script)

    $ echo ${A[0]} ${A[2]} ${A[3]} ${A[6]}

    this an example script

    $ echo ${A[8]}

    $

    由于值表中初值共有7个,所以A的元素个数也是7。A[8]超出了已赋值的数组A的范围,就认为它是一个新元素,由于预先没有赋值,所以它的值是空串。

    若没有给出数组元素的下标,则数组名表示下标为0的数组元素,如city就等价于city[0]。

    使用*或@做下标,则会以数组中所有元素取代

或[@]。


    3.变量引用

    除了上面所介绍的变量引用方式外,在bash中还有其它的引用方式。归纳起来,有效的变量引用表达式有以下形式:

    $name ${name#pattern}

    ${name} ${name##pattern}

    ${name[n]} ${name % pattern}

    ${name

} ${name %% pattern}


    ${name [@]} ${#@}

    ${name:-word} ${$#*}

    ${name:=word} ${# name }

    ${name:?word} ${# name

}


    ${name:+word} ${#name[@]}

    (1)表达式$name表示变量name的值,若变量未定义,则用空值替换。

    (2)表达式${name}将被变量name的值替换。用花括号括起name,目的在于把变量名与后面的字符分隔开,避免出现混淆。替换后花括号被取消。

    (3)${name[n]}表示数组变量name中第n个元素的值。

    (4)表达式${name

}和${name[@]}都表示数组name中所有非空元素的值,每个元素的值用空格分开。如果用双引号把它们都括起来,那么二者的含义就有区别:对于"${name
}",它被扩展成一个词(即字符串),这个词由以空格分开的各个数组元素组成;对于"${name[@]}",它被扩展成多个词,每个数组元素是一个词。如果数组name中没有元素,则${name[@]}被扩展为空串。


    (5)表达式${name:-word}、${name:=word}、${name:+word}、${name:?word}的计算方法在下面介绍。

    (6)表达式${name#pattern}和${name##pattern},如果pattern(表示匹配模式)与name值的开头匹配,那么name的值去掉匹配部分后的结果就是该表达式的值;否则, name的值就是该表达式的值。在第一种格式中,name值去掉的部分是与pattern匹配的最少的部分;而第二种格式中,name值去掉的部分是与pattern匹配的最多的部分。

    (7)表达式${name % pattern}和${name %% pattern},如果pattern与name值的末尾匹配,那么name的值中去掉匹配部分后的结果就是该表达式的值;否则,该表达式的值就是name的值。在第一种格式中,去掉的部分是最少的匹配部分;而第二种格式中,去掉的部分是最多的匹配部分。

    (8)表达式${#@}和${#*}的值分别是由$@和$*返回的参数的个数。

    (9)表达式${#name}的值是数组name第i个元素值的长度(字符个数)。

    (10)表达式${#nane

}和${#name[@]}的值都是数组name中已经设置的元素的个数。


    4.交互输入变量值

    利用read命令可以从键盘上读取数据,然后赋给指定的变量。read命令的一般格式是:

    read 变量1 [ 变量2 …]

    例如:

    read a b c

    输入数据时,数据间以空格或制表符作为分隔符。如果变量个数与给定数据个数相同,则依次对应赋值;如果变量个数少于数据个数,则从左至右对应赋值,但最后一个变量被赋予剩余的所有数据;如果变量个数多于给定数据个数,则依次对应赋值,而没有数据与之对应的变量取空串。

    5.位置参数

    执行Unix/Linux命令或Shell 脚本时可以带有实参。相应地,在Shell脚本中应有变量。执行Shell程序时,用实参去替代这些变量。在Shell脚本中这类变量的名称很特别,分别是0、1、2……这类变量称作位置变量,因为它们与命令行上具体位置的实参相对应:命令名(脚本名)对应位置变量0,第一个实参对应位置变量1,第二个实参对应位置变量2……如果位置变量是由两个或更多个数字构成,那么,必须把它们用一对花括号括起来,如{10}、{11}。命令行实参与脚本中位置变量的对应关系如图 1所示。

   

    例如:

    $ set `pwd;ls;date`

    $ echo $1 $2 $3 $9 ${10} ${11}

    /home/mengqc bash_1 ex1 12 3 21:52:32

    如果在脚本中使用的位置参数不超过9个,那么只用$1~$9即可。但是,实际给定的命令行参数有可能多于9个,此时就需要用shift命令移动位置参数。每执行一次shift命令,就把命令行上的实参向左移一位,即相当于位置参数向右移动一个位置,如图2所示。

   

    

    图2虚线示出实参与位置参数的对应关系。可以看出,shift命令执行后新$1的值是原$2的值,新$2的值是原$3的值,依此类推。

    shift命令不能将$0移走,所以经shift右移位置参数后, $0的值不会发生变化。

    6. 预先定义的特殊变量

    在Shell中,预先定义了几个有特殊含义的Shell变量,它们的值只能由Shell根据实际情况进行赋值,而不能通过用户重新设置。下面给出这些特殊变量的表示形式及意义:

    $# 命令行上实际参数的个数,但不包含Shell脚本名。

    $?上一条命令执行后的返回值(也称作 “退出码”)。它是一个十进制数。多数Shell命令执行成功时,则返回值为0;如果执行失败,则返回非0值。

    $$ 当前进程的进程号。

    $! 上一个后台命令对应的进程号,这是一个由1~5位数字构成的数字串。

    $- 由当前Shell设置的执行标志名组成的字符串。例如:

    set -xv

    这个命令行给Shell设置了标志-x和-v(用于跟踪输出)。

    $* 表示在命令行中实际给出的所有实参字符串,它并不仅限于9个实参。

    $ @ 它与$*基本功能相同,但“$@”与“$*”不同。

    7.环境变量

    Linux环境(也称为Shell环境)由许多变量及这些变量的值组成。这些变量和变量的值决定了用户环境的外观。注意,如果要使用环境变量或其它Shell变量的值,必须在变量名之前加上一个“$”符号,不能直接使用变量名。

    常用的环境变量如下:

    HOME 用户主目录的全路径名。

    LOGNAME 即注册名,由Linux自动设置。它是与系统交互的名字或字符串。

    MAIL 系统信箱的路径。

    PATH 查找命令的目录列表。PATH变量包含带冒号分界符的字符串,这些字符串指向含有所使用命令的目录。

    PS1 Shell的主提示符。bash默认的主提示符一般为“\s-\v\$ ”。其中,\s表示Shell的名称;\v表示bash的版本号。当然,也可以随意设置PS1的值,例如:

    PS1="Enter Command> "

    则主提示符改成“Enter Command> ”。

    PWD 当前工作目录的路径,它指出目前在Linux文件系统中处在什么位置。

    SHELL 当前使用的Shell,它也指出Shell解释程序放在什么地方。

    TERM 终端类型。

    可以使用unset命令删除一个环境变量(如NAME):

    $ unset NAME

    可以创建一个新的环境变量,或者改变一个已有的环境变量的值,其形式与设置一般变量相同。如果变量值的字符串中带有空格等特殊字符,需要用引号把整个字符串括起来。利用export命令可以将这些变量导出,使它们成为公用量,如:

    export HOME HZ LOGNAME TERM

    可以利用env命令列出所有的环境变量,包括本进程及以前的“祖先进程”所导出的变量。

    8.参数置换变量

    参数置换变量是另一种为变量赋值的方式,其一般形式是:

    变量2=$ {变量1 op 字符串}

    其中,op表示操作符,它可以是下列四个操作符之一“: :-”、“: =” 、“: +” 和“: ?”。变量2的值取决于变量1(参数)是否为空串、利用哪个操作符及字符串的取值。在操作符的前后不留空格。

    命令历史

    bash提供了命令历史功能,即系统为每个用户维护一个命令历史文件(~/.bash_history),它在注册用户的主目录(用~表示)之下。该文件由编号的表格构成。每当注册后,用户输入命令并执行它时,该命令就自动地加到这个命令历史表中。

    使用命令历史机制,用户可以方便地调用或者修改以前的命令,可以把全部或部分先前命令作为新命令予以快捷执行。这一功能称为历史替换(history substitution)。

    ◆history命令可以显示命令历史表中的命令。其语法格式是:

    history [option][arg ?]

    (1)如果不带任何参数,则history命令会显示历史命令的清单(包括刚输入的history命令)。所有这些命令都被称作事件,一个事件表示一个操作已经发生,即一个命令已被执行。显示的各行命令之前的数字表示相应命令行在命令历史表中的序号,称为历史事件号。历史事件号从1开始顺序向下排,最后执行的命令的事件号最大。

    (2)如果history 后给出一个正整数,例如:

    history 50

    那么,就只显示历史表中的最后50行命令。

    (3)如果history后给出一个文件名,例如:

    history al

    那么,就把al作为历史文件名。

    ◆执行历史命令是命令替换之一,它以字符“!”开头、后随1个或多个字符来定义用户所需的某种类型的历史命令。它可以出现在输入行的任何地方,除非已在进行历史替换。如果在“!”之前加上反斜线“\”,或者在其后跟着空格、制表符、换行符、等号“=”或开括号“(”,那么“!”就作为普通字符对待,失去特殊意义。

    历史替换可以作为输入的命令行的一部分或全体。当输入行的正文中包含历史替换时,将在完成相应的替换后,在终端上显示输入的命令行,从而用户可以看到实际执行的命令(显示命令后立即执行)。

    ◆在默认方式下,bash使用用户主目录下面的文件“.bash_history”来保存命令历史。但是,用户也可以通过重新为环境变量HISTFILE赋值来改变存放历史命令的文件。例如:

    $ HISTFILE="/home/mengqc/.myhistory"

    历史文件中能够保留的命令个数有限,其默认值是500。如果用户输入的命令太多,超过限定值,那么最早输入的命令就会从历史表中删除,而新输入的命令会加到该表尾部。用户可以利用HISTSIZE变量重新设定该值。例如:

    $ HISTSIZE=600

    通常用户不必对命令历史表进行管理(如设置HISTSIZE的值等),由系统自动管理。利用命令历史功能,用户可以对先前输入的命令重新进行编辑、修改和执行,从而简化用户的操作。

    别名

    使用别名可以简化输入,方便用户。对于常用的选项或参数较多的固定命令采用别名替换,既缩短击键次数,又减少出错机率。

    定义别名要使用Shell内部命令alias,其一般语法格式为:

    alias [name[=value]]…

    如果没有指定参数,将在标准输出(屏幕)上显示别名清单,其格式为name=value,其中name是用户(或系统)定义的别名名称,value是别名所代表的内容。注意,在赋值号“=”两边不能有空格。

    例如:

    $ alias ll=' ls -l '

    定义了别名ll,它代表“ls -l”。当输入ll命令后,Shell将寻找它们所维护的别名表(放在内存中的一个内部表格)。若在该表中找到命令行的第一个字段(即“ll”)时,该命令就会被别名定义的内容所替换。定义别名时,往往用单引号将它代表的内容括起来,从而防止Shell对其中的内容产生歧义,如对空格和特殊字符另作解释。

    如果想取消先前定义的别名,可使用如下命令:

    unalias name…

    执行后,就从别名表中删除由name指定的别名。

    unalias也可以在一个命令上同时取消多个别名的定义,只须在unalias之后依次列出要取消的别名名称。也可以一次将所有的别名都从别名表中删除,使用如下命令:

    unalias -a

    

    Shell特殊字符

    Shell中除使用普通字符外,还使用了一些特殊字符,它们有特定的含义,如通配符“*”和“?”、管道线(|)及单引号、双引号等。在使用时应注意它们表示的意义和作用范围。

    1.一般通配符

    通配符用于模式匹配,如文件名匹配、路径名搜索、字符串查找等。常用的通配符有四种:

    * 匹配任意字符0次或多次出现。例如,f*可以匹配以f 打头的任意字符串。但应注意,文件名前面的圆点( . ) 和路径名中的斜线( / )必须显式匹配。

    ? 匹配任意一个字符,例如,f ?匹配f1、fa、fb等,但不能匹配 f 、fabc、 f12等。

    [ ] 其中有一个字符组,它匹配该字符组所限定的任何一个字符。该字符组可以由直接给出的字符组成,也可以由表示限定范围的起始字符、终止字符及中间一个连字符(-)组成。例如,f[a-d]与f[abcd]作用相同。

    ! 表示不在一对方括号中所列出的字符。例如,f[!1—9].c 表示以f打头,后面一个字符不是数字1至9的.c文件名,它匹配fa.c、fb.c、fm.c等。

    在一个正则表达式中,可以同时使用“*”和“?”。

    2.模式表达式

    模式表达式是那些包含一个或多个通配符的字。bash除支持一般通配符外,还提供了特有的扩展模式匹配表达式,其形式和含义如下:

    (1)*(模式表) 匹配给定模式表中“模式”的0次或多次出现,各模式之间以“|”分开。例如,file*(.c|.o)将匹配文件file、file.c、file.o、file.c.c、file.0.0、file.c.o、file.o.c等,但不匹配file.h或file.s等。

    (2)+(模式表) 匹配给定模式表中“模式”的1次或多次出现,各模式之间以“|”分开。例如,file+(.c | .o)匹配文件file.c、file.o、file.c.o、file.c.c等,但不匹配file。

    (3)?(模式表) 匹配模式表中任何一种“模式”的0次或1次出现,各模式之间以“|”分开。例如,file?(.c|.o)只匹配file、file.c和file.0,它不匹配多个模式或模式的重复出现,即不匹配file. c. c、file. c. 0等。

    (4)@(模式表) 仅匹配模式表中给定“模式”的一次出现,各模式之间以“|”分开。例如,file@(.c|.0)匹配file.c和file.0,但不匹配file、file.c.c、file.c.o等。

    (5)!(模式表) 除给定模式表中的一个“模式”之外,它可以匹配其它任何东西。

    可以看出,模式表达式的定义是递归的,每个表达式中都可以包含一个或多个模式。例如file*( .[cho]|.sh)是合法的模式表达式。但在使用时应注意,由于带“*”和“+”的表达式可以匹配给定模式的组合,若利用此种表达式去删除文件就存在危险,有可能误将系统配置文件删除。因此,必须小心使用。

    3.引号

    在Shell中引号分为三种:单引号、双引号和倒引号。

    (1)双引号

    由双引号括起来的字符,除$、倒引号(`)和反斜线(\)仍保留其特殊功能外,其余字符均作为普通字符对待。“$”表示变量替换,即用其后指定的变量的值来代替$和变量;倒引号表示命令替换;仅当“\”后面的字符是下述字符之一时,“\”才是转义字符,这些字符是:“$”、“`”、“"”、“\”或换行符。转义字符告诉Shell不要对其后面的那个字符进行特殊处理,只是当作普通字符。例如:

    $ echo "My current dir is `pwd` and logname is $LOGNAME"

    My current dir is /home/mengqc and logname is mengqc

    (2)单引号

    由单引号括起来的字符都作为普通字符出现。例如,

    $ echo 'The time is ` date ` , the file is $HOME/abc '

    The time is ` date ` , the file is $HOME/abc

    (3)倒引号

    倒引号括起来的字符串被shell解释为命令行,在执行时,Shell会先执行该命令行,并以它的标准输出结果取代整个倒引号部分。在前面示例中已经见过。例如,

    $ echo current directory is ` pwd `

    current directory is /home/mengqc

    4.注释

    Shell程序中以“#”开头的正文行表示注释。例如:

    #!/bin/bash

    # If no arguments, then listing the current directory.

    # Otherwise, listing each subdirectory.

    if test $# = 0

    then ls ·

    else

    for i

    do

    ls -l $i | grep '^d'

    done

    fi

    上面程序由if语句构成,其中else部分是for循环语句。其功能是检测位置参数个数,若等于0,则列出当前目录本身;否则,对于每个位置参数显示其所包含的子目录。

    上面代码中,第一行#!/bin/bash表示下面的脚本是用bash编写的,必须调用bash程序对它解释执行。后面两行以“ # ”开头,表示这是注释行。注释行可用来说明程序的功能、结构、算法和变量的作用等,增加程序的可读性。在执行时Shell将忽略注释行。

    命令执行操作符

    ◆多条命令可以在一行中出现。它们可以从左到右顺序执行。此时,各条命令之间应以分号( ;)隔开,如:

    pwd ; who | wc -l ; cd /usr/bin

    ◆在相邻命令间可存在逻辑关系,即逻辑“与”和逻辑“或”。

    逻辑与操作符“&&”可把两个命令联系在一起,其一般形式:

    命令1 && 命令2

    其功能是先执行命令1,如果执行成功,才执行命令2;否则,若命令1执行不成功,则不执行命令2。例如:

    cp ex1 ex10 && rm ex1

    如果成功地把文件ex1拷贝到文件ex10中,则把ex1删除。

    逻辑或操作符“||”可把两个命令联系起来,其一般形式是:

    命令1 || 命令2

    其功能是先执行命令1,如果执行不成功,则执行命令2;否则,若命令1执行成功,则不执行命令2。例如:

    cat abc || pwd

    如果不能将文件abc的内容列出来,则显示当前工作目录的路径。

    ◆在Shell中有两种方式可以将若干命令组合在一起,使其在逻辑上被视为一条命令,它们是用花括号{ }将各命令括起来和用圆括号( )括起来。

    以花括号括起来的全部命令可视为语法上的一条命令,出现在管道符的一边。成组命令的执行顺序是根据命令出现的先后次序,由左至右执行。在管道线中,成组命令把各命令的执行结果汇集在一起,形成一个输出流,这个流作为该管道线中下一个命令的输入。例如,

    { echo "User Report for ` date ` . "; who ; } | pr

    使用花括号时在格式上应注意:左括号 “{ ”后面应有一个空格;右括号“}”之前应有一个分号( ;)。

    成组命令也可以用圆括号括起来。例如:

    (echo "Current directory is ` pwd ` . "

    cd /home/mengqc ; ls -l ;

    cp m1 em1 && rm m1

    cat em1) | pr

    如上所示,在用圆括号括起成组命令时,左括号后不必有空格,右括号之前也不需加上分号。

    二者执行过程相同,但是存在重要区别:用花括号括起来的成组命令只是在本Shell内执行命令表,不产生新的进程;而用圆括号括起来的成组命令是在新的子Shell内执行,要建立新的子进程。因此,在圆括号内的命令不会改变父Shell的变量值及工作目录等。

    算术运算

    bash中执行整数算术运算的命令是let,其语法格式为:

    let arg …

    其中,arg是单独的算术表达式。这里的算术表达式使用C语言中表达式的语法、优先级和结合性。除++、--和逗号(,)之外,所有整型运算符都得到支持。此外,还提供了方幂运算符“**”。命名的参数可以在算术表达式中直接利用名称访问,不要前面带有“$”符号。当访问命名参数时,就作为算术表达式计算它的值。算术表达式按长整数进行求值,并且不检查溢出。当然,用0作除数就产生错误。

    let 命令的替代表示形式是:

    ((算术表达式))

    例如,let ″j=i*6+2″等价于((j=i*6+2))。

    如果表达式的值是非0,那么返回的状态值是0;否则,返回的状态值是1。

    当表达式中有Shell的特殊字符时,必须用双引号将其括起来。例如,let ″val=a|b″。如果不括起来,Shell会把命令行let val=a|b中的“|”看成管道符,将其左右两边看成不同的命令,因而无法正确执行。

    控制结构

    Shell具有一般高级程序设计语言所具有的控制结构和其它的复杂功能,如if语句、 case语句、循环结构、函数等。其实在Shell中,这些控制结构也称作“命令”。为了程序设计的习惯,才把它们称作语句。

    1.if语句

    if语句用于条件控制结构中,其一般格式为:

    if 测试条件

    then 命令1

    else 命令2

    fi

    其中,if、then、else和fi是关键字。例如:

    if test -f "$1"

    then echo "$1 is an ordinary file . "

    else echo "$1 is not an ordinary file . "

    fi

    

    应该注意,if语句中else部分可以缺省。另外,if 语句的else部分还可以是else—if结构,此时可以用关键字“elif”代替“else if”。

    通常,if的测试部分是利用test命令实现的。其实,条件测试可以利用一般命令执行成功与否来作判断。如果命令正常结束,则表示执行成功,其返回值为0,条件测试为真;如果命令执行不成功,其返回值不等于0,条件测试就为假。所以if的语句的更一般形式是:

    if 命令表1

    then 命令表2

    else 命令表3

    fi

    2.条件测试

    条件测试有三种常用形式:一种是用test命令,如上所示。另一种是用一对方括号将测试条件括起来。这两种形式是完全等价的。例如,测试位置参数$1是否是已存在的普通文件,可写为test -f "$1"。也可写成[ -f "$1" ]。利用一对方括号表示条件测试时,在左方括号“[”之后、右方括号“]”之前各应有一个空格。

    第三种形式是:

    [[条件表达式]]

    test命令可以和多种系统运算符一起使用。这些运算符可以分为四类:文件测试运算符(文件的属性及权限等)、字符串测试运算符(两个串是否相同及是否为空)、数值测试运算符(大小关系)和逻辑运算符(逻辑与、或、非)。

    3.case语句

    case语句允许进行多重条件选择。其一般语法形式是:

    case 字符串 in

    模式字符串1) 命令

     …

     命令;;

    模式字符串2) 命令

     …

     命令;;

     …

    模式字符串n) 命令

     …

     命令;;

    esac

    其执行过程是用“字符串”的值依次与各模式字符串进行比较,如果发现同某一个匹配,那么就执行该模式字符串之后的各个命令,直至遇到两个分号为止。如果没有任何模式字符串与该字符串的值相符合,则不执行任何命令。

    在使用case语句时应注意:

    (1)每个模式字符串后面可有一条或多条命令,其最后一条命令必须以两个分号(即;;)结束。

    (2)模式字符串中可以使用通配符。

    (3) 如果一个模式字符串中包含多个模式,那么各模式之间应以竖线(|)隔开,表示各模式是“或”的关系,即只要给定字符串与其中一个模式相配,就会执行其后的命令表。

    (4)各模式字符串应是惟一的,不应重复出现。并且要合理安排它们的出现顺序。例如,不应将“*”作为头一个模式字符串,因为“*”可以与任何字符串匹配,它若第一个出现,就不会再检查其它模式了。

    (5)case语句以关键字case开头,以关键字esac(是case倒过来写!)结束。

    (6)case的退出(返回)值是整个结构中最后执行的那个命令的退出值。若没有执行任何命令,则退出值为零。

    4. while语句

    Shell中有三种用于循环的语句,它们是while语句、for语句和until语句。

    while语句的一般形式是:

    while 测试条件

    do

    命令表

    done

    其执行过程是,先进行条件测试,如果结果为真,则进入循环体(do—done之间部分), 执行其中命令;然后再做条件测试……直至测试条件为假时才终止while语句的执行。例如:

    while [ $1 ]

    do

     if [ -f $1 ]

     then echo "display : $1 "

     cat $1

     else echo "$1 is not a file name . "

     fi

     shift

    done

    这段程序对各个给定的位置参数,首先判断其是否是普通文件,若是,则显示其内容;否则,显示它不是文件名的信息。每次循环处理一个位置参数$1,利用shift命令可把后续位置参数左移。

    测试条件部分除使用test命令或等价的方括号外,还可以是一组命令。根据其最后一个命令的退出值决定是否进入循环体执行。

    5.until语句

    until语句的一般形式是:

    until 测试条件

    do

     命令表

    done

    它与while语句很相似,只是测试条件不同:当测试条件为假时,才进入循环体,直至测试条件为真时终止循环。

    6.for语句

    for语句是最常用的建立循环结构的语句。其使用格式主要有三种,取决于循环变量的取值方式。格式一:

    for 变量 in 值表

    do

     命令表

    done

    例如:

    for day in Monday Wednesday Friday Sunday

    do

     echo $day

    done

    其执行过程是,变量day依次取值表中各字符串,即第一次将“Monday”赋给day,然后进入循环体,执行其中的命令,显示出Monday。第二次将“Wednesday”赋给day,然后执行循环体中命令,显示出Wednesday。依次处理,当day把值表中各字符串都取过一次之后,下面day的值就变为空串,从而结束for循环。因此,值表中字符串的个数就决定了for循环执行的次数。在格式上,值表中各字符串之间以空格隔开。

    格式二:

    for 变量 in 文件正则表达式

    do

     命令表

    done

    其执行过程是,变量的值依次取当前目录下(或给定目录下)与正则表达式相匹配的文件名,每取值一次,就进入循环体执行命令表,直至所有匹配的文件名取完为止,退出for循环。

    格式三:

    for i in $* 或者 for i

    do do

     命令表 命令表

    done done

    这两种形式是等价的。其执行过程是,变量i 依次取位置参数的值,然后执行循环体中的命令表,直至所有位置参数取完为止。

    7.break命令和continue命令

    break命令可以使我们从循环体中退出来。其语法格式是:

    break [ n ]

    其中,n表示要跳出几层循环。默认值是1,表示只跳出一层循环。

    continue命令跳过循环体中在它之后的语句,回到本层循环的开头,进行下一次循环。其语法格式是:

    continue [ n ]

    其中,n表示从包含continue语句的最内层循环体向外跳到第几层循环。默认值为1。循环层数是由内向外编号。

    函数

    在Shell脚本中可以定义并使用函数。其定义格式为:

    [function]函数名( )

    {

     命令表

    }

    其中,关键字function可以缺省。

    函数应先定义,后使用。调用函数时,直接利用函数名,如showfile,不必带圆括号,就像一般命令那样使用。Shell脚本与函数间的参数传递可利用位置参数和变量直接传递。变量的值可以由Shell脚本传递给被调用的函数,而函数中所用的位置参数$1、$2,等对应于函数调用语句中的实参,这一点是与普通命令不同的。下面是使用函数的示例:

    #func is a function name

    # it echos the values of variables and arguments

    func( )

    {

     echo "Let's begin now. "

     echo $a $b $c

     echo $1 $2 $3

     echo "The end. "

    }

    a=" Working directory "

    b="is"

    c=`pwd`

    func Welcome You Byby

    echo "Today is ` date ` "

    Shell中的函数把若干命令集合在一起,通过一个函数名加以调用。如果需要,还可被多次调用。执行函数并不创建新的进程,是通过Shell进程执行。

    通常,函数中的最后一个命令执行之后,就退出被调函数。也可利用return命令立即退出函数,其语法格式是:

    return [ n ]

    其中,n值是退出函数时的退出值(退出状态),即$?的值。当n值缺省时,则退出值是最后一个命令执行后的退回值。

    本讲介绍了有关Shell编程的知识和示例,从中可以体会到bash的特点和长处。很显然,对于系统管理员来说,利用bash编写脚本解决问题,可以大大减轻工作强度,有效提高编程效率。建议读者多上机练习。
(责任编辑:IT)
------分隔线----------------------------
栏目列表
推荐内容