• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
第9章 Shell 編程
 

第9章 Shell 編程

on

  • 2,535 views

 

Statistics

Views

Total Views
2,535
Views on SlideShare
2,522
Embed Views
13

Actions

Likes
0
Downloads
16
Comments
0

4 Embeds 13

http://irondogstock.blogspot.com 8
http://www.slideshare.net 2
http://irondogstock.blogspot.tw 2
http://highspeed.homelinux.net 1

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    第9章 Shell 編程 第9章 Shell 編程 Presentation Transcript

    • 第 9 章 shell 编程 9.1 shell 概述 9.2 shell 的基本功能 9.3 shell 启动及其命令 9.4 shell 命令的集成 9.5 shell 变量 9.6 shell 的控制结构 9.7 shell 的运行环境 9.8 shell 应用实例 9.9 小结 习题
    • shell 是一种命令语言,同时又是一种程序设计语言。 shell 的语言处理能力,使得用户能够方便地定义各种变量、参数,并使用各种控制语句编写复杂的命令程序完成多种工作。本章介绍 shell 、 shell 程序设计语言、 shell 的运行环境及 shell 程序的调试。
    • 9.1 shell 概述 9.1.1 什么是 shell shell 是一种命令解释程序 ( 命令解释器 ) , shell 解释用户输入的命令行,提交系统内核处理,并将结果返回给用户。与 Linux 命令一样都是实用程序,但两者又有区别。一旦用户注册到系统后, shell 就被系统装入内存,并一直运行到用户退出系统之止;而一般命令仅当被调用时,才由系统装入内存执行。 shell 本身也是一种可编程的程序设计语言。用 shell 写的程序 (shell 脚本 ) 相当于 dos/windows 下的批处理文件,它可以简单到只有一条命令,也可以复杂到包括大量循环、条件语句、数学运算、控制结构,也可以是介于两者之间的程序。
    • 9.1.2 shell 程序语言的特点 shell 允许通过编程来完成复杂的功能处理,但其作为语言与高级语言相比较具有不同的特点: (1) shell 是解释性的,多数高级语言是编译性的; (2) shell 语言与高级语言处理的对象不同; (3) shell 与系统有密切的关系; (4) shell 易编写、调试、灵活性较强,但速度低; (5) shell 作为命令级语言,命令组合功能很强。
    • 9.1.3 shell 的版本 shell 有两种主要语法类型: Bourne shell 和 C shell ,彼此不兼容。 Bourne shell 家族: sh ksh bash psh zsh ; C shell 家族: csh tcsh 。 其中 bash 和 zsh 在不同程度上支持 csh 的语法。 这里,我们再着重介绍一下 bash 的特点: (1) 自动补全功能 假设要输入的命令很长,或者命令后面要给的文件名很长。这个时候只要按一个 Tab 键, bash 就会在可能的命令或文件名里面找寻匹配的命令,找到的话就会自动帮你补齐。
    • (2) 命令行编辑程序 bash 的命令行编辑是在提示符下,可对未执行的命令字符任意地修改。 (3) 命令历史 (command history) 所谓的命令历史就是把曾经输入过的命令记录起来,方便日后的查询与使用。只要按向上键就可以调出前一个命令,再按一次向上键就可以调出更前一个命令,依此类推,用向下键可以回到下个命令,所以用上、下键就可以选择以前输入过的命令。
    • 9.2 shell 的基本功能 9.2.1 程序的运行 当用户输入一行命令后, shell 负责解释、分析输入的内容,并且决定做什么,同时替系统内核删除命令行中不必要的信息。命令是一个可执行的 Linux 命令、程序、工具或 shell 脚本。 例 1: $ ls -l file1 file2 file3 例 2: $ echo ′Welcome to Tsinghua University.′ 例 3: $ echo Welcome to Tsinghua University. 注意 : 例 2 和例 3 的结果是完全一样的。
    • 9.2.2 使用保留字和元字符 shell 有一些具有特殊意义的字 ( 保留字 ) ,如在 shell 脚本中,像 do 、 done 、 for 、 while 等保留字控制循环操作, if 、 then 、 else 等保留字进行条件控制。保留字随 shell 的不同而不同。 在 Linux 系统里,有一组特殊意义字符,这就是所谓的元字符 ( 通配符 ) 。 现在列出一些常用的通配符的意义,供参考。 .. 上一层目录,与 cd 命令配合用得比较多 . 目前工作的目录 * 任意长度的字符 ? 长度为一个的任意字符
    • [ .. ] 括号内的一个字符 \ m 等于某个通配符,如 * 、?等 [ a-z ] * 小写字母开头的所有字符串 \ 转义符号,用以解除特殊字符的特殊意义 ~ 用户目录 ; 分隔符,当命令行有多个命令时,做分隔用 $ BourneShell 的提示符,同时也作为 shell 语言的位置变量参数 # 做注释用 | 建立一个管道,使一命令的输出作为另一个命令的输入 & 将命令以后台方式执行
    • > 将命令的输出重导入文件中 < 将命令的输入流指定为由文件中加载,和 > 相反 >> 将命令的输出加在一个已经存在的文件后面 {..} 括号内的一个字符串 例: $ ls -x t* 显示当前目录下以 t 打头的所有文件。
    • 9.2.3 变量、文件名的替换 1. 变量的替换 shell 允许对变量赋值。 shell 一旦在命令行中发现$变量名时,将在$变量名的位置上用以前赋给该变量的值替代$变量名。 例: $ myhome=/usr/app1 $ echo $ myhome /usr/app1 $ ls -x $ myhome file1 file2 file3 file4
    • 2. 文件名的替换 shell 在命令行中将文件名进行替换。事实上, shell 在确定要执行的程序名和它的自变量之前,要对命令行扫描,找出元字符进行相应的文件名的替换。 例: $ ls -x file1 file2 file3 file4 $ echo* file1 file2 file3 file4
    • 9.2.4 输入输出重定向 ( 改向 ) shell 处理命令行的输入输出重定向,它扫描命令行中特殊改向字符“ >” 、“ <” 、“ >>” 、“ <<” ,将输入或输出改向到指定的文件中。 例: $ echo “Please call me : 62781849” > msg $ cat msg Please call me : 62781849 注意: 就程序或命令本身而言,它并不知道其标准输出已被改向,只是简单地按照自身的方式向标准输出输送信息。
    • 正如 shell 扫描命令行查找重定向字符一样,它也查找管道字符“ |” 。对于所发现的每个管道字符,它将管道字符前面的命令的标准输出连接到管道字符后面的命令的标准输入中,然后启动两个程序的执行。 例: $ w|wc -l( 假设有 8 个用户上机 ) 8
    • 9.2.5 运行环境的控制 当用户登录到 Linux 系统中,系统启动一个交互式的 shell 命令解释器 ( 称为注册 shell) 。该 shell 为此用户创建工作环境。 shell 提供了一定的命令,允许用户对自己运行的环境进行控制,即对运行环境实现客户化。用户的运行环境包括:主目录、终端类型、输入命令提示符、查找命令的路径名、以及其他全局变量等。例如, Bourne again Shell 、 Bourne Shell 、 Korn Shell 和 C Shell 的环境文件分别为 .bash-profile 、 .profile 、 .kshrc 和 .cshrc 。
    • 9.2.6 支持 shell 的编程 shell 除了具有命令解释器的功能外,它本身就是一种程序设计语言,这种语言也由 shell 解释执行的。用户可以在文件中编写一组 shell 命令,该文件称为 shell 脚本或 shell 程序。通过把命令、变量赋值及条件控制语句结合起来,用户就获得了一个强大的编程工具。需要说明的是,在 Linux 系统本身就存在大量的 shell 程序,用于各种管理和应用。
    • 9.3 shell 启动及其命令 9.3.1 shell 的启动 1. 系统在用户登录时启动 shell 在 Linux 系统引导过程中 , 首先启动 init 进程以查询终端的各个端口及其特性,当发现活动的终端时,调用 getty 进程。接着 getty 进程在接受用户名和口令后,调用 login 进程。 login 进程负责验证用户身份,验证后把控制权交给 shell 程序。 shell 根据环境文件建立系统范围内的工作环境和该用户自己的工作环境,最后显示命令提示符 (# 、$、 %) 。 在 /etc/passwd 文件中指定要启动的 shell ,如下所示:
    • root:x:0:0:root:/root:/bin/bash john:x:201:Certer′starf:/home:/bin/sh 2. 命令行状态下的交互 shell 启动 在系统中可能有多种版本的 shell 存在,可通过相应的命令来启动,如: $ bsh $ ksh $ sh 3. 执行用户命令时启动 shell( 子 shell) 由系统生成新的子 shell 来执行该命令。
    • 9.3.2 命令的查询 环境变量 PATH 的构成决定了寻找 shell 命令和其他程序的途径,是影响 shell 程序效率的主要因素之一。 PATH 变量设置要考虑以下几点:在 PATH 变量中,命令使用得多的程序目录应放在前面,极少用目录不设在 PATH 变量中。在 shell 程序内用得较多的程序目录应加在变量 PATH 中。并且 PATH 应尽可能地短,不要出现重复的目录。还要尽量避免查询大目录,如需设置,将其路径放在 PATH 路径的最后位置。 例: PATH=/bin:/usr/bin:/etc:.
    • 9.3.3 shell 常用的命令 Linux 系统通常提供了许多命令以方便用户与系统的交互对话。这些命令的选项较多,并且可利用命令的集成 ( 如输入输出改道、管道机制 ) 及程序设计功能组合成许多命令。 Linux 有丰富的 shell 命令,大致划分为以下几类:目录操作与管理、文件操作与管理命令、系统管理与维护、用户管理与维护、系统状态、进程管理、通讯命令、其他命令。例如, awk cat chmod chown cp cron date df diff du echo expr file find grep init kill ln login ls mail make man mkdir mv nohup pg ps pwd read rm wc
    • 通常情况下,在用户输入一个命令 ( 非内部命令 ) 时,注册 shell 首先通过查询路径查找该命令,然后生成出一个副本 ( 称为该 shell 的子 shell) ,由子 shell 来负责解释执行这个命令。在命令执行过程中,注册 shell( 父 shell) 等待子 shell 的执行而进入睡眠态,一旦子 shell 执行完毕,子 shell 将唤醒注册 shell( 父 shell) ,而自己的生命周期到此结束。
    • 9.3.4 shell 的内部命令 内部命令构造在 shell 的内部。内部命令比非内部命令的执行速度要快。因此,编写 shell 程序时应尽量地使用 shell 的内部命令。 shell 常用的内部命令有: # 注释命令, # 后面的内容作为注释信息。 : 空命令,通常放在一行的最左边,实际不做任何命令,只返回出口代码 0 。 其他命令有: cd,eval,set,unset,exec,exit,if,else,for,case,while,until,continue,break 等。
    • 9.4 shell 命令的集成 9.4.1 元字符和文件名生成 1. UNIX 元字符 ( 通配符 ) 的定义 * 匹配任何字符串,包括空字符串; ? 匹配任何单个字符; [ .,-,! ] 按照范围、列表或不匹配等形式匹配指定的字符; \ 转意符,使元字符失去其特殊的含义。 例: [ a-d , x , y ] 匹配字符 a 、 b 、 c 、 d 、 x 、 y ; z* 匹配以字符 z 开始的任何字符串;
    • x?y 匹配以 x 开始、以 y 结束、中间为任何单个字符的字符串; [ !Z ] 匹配不为 Z 的单个字符。 2. 元字符作为文件扩展名的使用 例: [ a-f ] * 匹配字符 a 到字符 f 开头的文件名,如 abc , d2 , e3.c , f.dat ; *z 匹配以字符 z 结尾的任何字符串,如 win.z , core.zz , a-c-5z ; rc?.d 匹配以 rc 开始、以 .d 结束、中间为任何单个字符的文件名,如 rc0.d , rc2.d , rcS.d ; * [ !o ] 匹配不以 o 结尾的文件名。
    • 9.4.2 管道和命令表 在 shell 中有两种结构类型:管道线和命令表。当 shell 检测到一个管道操作符时,就建立一个系统管道文件,这是一个先进先出的数据结构,它允许在同一时刻对管道线上的命令或程序进行读和写,即允许两个无关的命令通过管道连接交换信息。 1. 管道的概念 管道:是一个命令的标准输出与另一个命令的标准输入之间的连接,不经过任何中间文件; 管道线:是由管道操作符分隔的一个命令序列,最简单的管道线是一个简单命令; 管道操作符:用符号“ |” 表示。
    • 例: w|wc -l ps aux|grep ftp 2. 命令表的概念 命令表:一串管道线构成了一个命令表,最简单的命令表是一个管道线,一个命令表送回的值是该命令表中最后一个管道线的出口状态。 管道线分隔符:分隔命令表元素,确定管道线执行的条件。各分隔符含义如下: ; 表示按顺序执行管道线; && 表示根据条件 (true) ,执行其后面的管道线; ‖ 表示根据条件 (false) ,执行其后面的管道线;
    • & 表示前面的管道线在后台 ( 异步 ) 执行。 例 1 : 四个管道线构成一个命令表 ls -l /tmp /root w|wc -l ps 例 2 : 与例 1 等价 ls -l /tmp /root ; w|wc -l ; ps 例 3 : sys-account &
    • 9.4.3 命令组合 命令组合有两种形式: { 命令表 } 和 ( 命令表 ) ,前者只在本 shell 中执行,不产生新的子进程;后者要产生新的子进程来执行命令表。 例 1 : { cd mydoc ; rm junk ; } 该命令表只能在当前 shell 下执行,先进入目录 mydoc ,然后执行 rm 命令,执行完毕后,当前目录已改变为 mydoc 。 例 2 : (cd mydoc ; rm junk ; ) 当前 shell 要生成一个子 shell 进程,由该子 shell 来执行命令表。子 shell 完成操作后,自然消亡,而其父 shell 进程的当前路径并没有变化。
    • 9.4.4 命令替换 当一个字符串被括在反撇号“ ` ” 中时,该字符串将作为命令被 shell 解释执行,即用命令的执行结果替换这个字符串本身。要注意反撇号与单引号的区别。 例 1 :$ now=′date′ $ echo $ now date $ now=`date` $ echo $ now 1998 年 10 月 28 日 星期三 17 时 51 分 56 秒 CST 例 2 :$ count=10 $ count=`expr $ count+1` $ echo $ count 11
    • 9.4.5 输入、输出重定向 1. 使用标准改向符进行重定向 ( 改向 ) < 输入改向 << 追加输入改向 > 输出改向 >> 追加输出改向 2. 使用标准文件描述字进行重定向 ( 改向 ) 在 Linux 系统中,定义了用于输入、输出的标准文件,其文件描述字 0 为进程的标准输入、文件描述字 1 为标准输出、文件描述字 2 为标准错误输出。 3. 标准错误输出的改向 (> 、 >>) 格式为:
    • command 2 >file command 2 >>file 例 1 : 将 myfile1 作为 sort 的输入。 sort <myfile1 例 2 : 将 date 的输出转向到 myfile2 文件中。 date >myfile2 例 3 : 将 ls |l 的输出追加到 myfile3 文件中。 ls -l>>myfile3 例 4 :将错误输出改向到 err-file 文件。 $ myprog 2>err-file 例 5 :将标准输出和错误输出改向 out 文件。 $ myprog >out 2>>out $ myprog >out 2>>&1
    • 9.5 shell 变量 9.5.1 shell 变量描述 shell 实际上是基于字符串的程序设计语言,但也有变量。 shell 变量能够而且只能存储正文字符串,即它只有一种类型的变量即串变量。但从赋值的形式上看,则可以分成四种类型的变量或变量形式。变量的名字必须以字母或下划线开头,可以包括字母、数字和下划线。 9.5.2 用户自定义变量 用户自定义变量语法格式: name=string ,赋值号“ =” 两边不允许有空白符。 例:
    • nodehost=beijing.UUCP path=/bin:/usr/bin:/etc/bin count=10 允许多个赋值操作,按从右到左的顺序进行。 例: $ A= $ BB=abcC=″OK″ $ echo $ A $ B $ C abc abc OK 当引用一个未设置的变量时,其隐含值为空。 例: $ echo ″ $ mail is path of mailbox″ is path of mailbox
    • 如果用双引号“”将值括起来,则括起来的字符串允许出现空格、制表符和换行符等特殊字符,而且允许有变量替换。 例 1 :$ MAIL=/var/mail/fk $ var=″ $ MAIL is path of mailbox″ $ echo $ var /var/mail/fk is path of mailbox 例 2 : $ str=″This is \ n a book″ $ echo $ str This is a book
    • 如果用单引号‘’将值括起来,则括起来的字符串允许出现空格、制表符和换行符的特殊字符,但不允许有变量替换。 例 3 : $ BOOK=″English book″ $ MSG=′ $ BOOK′ $ echo $ MSG $ BOOK 例 4 : $ msg=′ Today is Sunday′ $ echo $ msg Today is Sunday
    • 引用变量的值时,可以用花括号 {} 将变量名称括起来,使变量名称与它的后续字符分隔开,如果紧跟在变量名称后面的字符是字母、数字或下划线时,必须要使用花括号。 例 5 : $ str=′This is a string′ $ echo ″ $ {str}ent test of variables″ This is a stringent test of variables $ echo ″ $ strent test of variables″ test of variables
    • 可将变量设置为只读形式,格式为, readonly 变量名 1 变量名 2 例 6 : $ ux=UNIX.SUN $ readonly ux $ ux=UNIX.SCO ux: is read only 查看只读形式的变量,格式为 readonly 例 7 : $ readonly readonly ux
    • 9.5.3 位置变量 位置变量顾名思义是与变量所在位置有关的变量,这是一种特殊的变量。当一个 shell 过程被调用时, shell 隐含地为它建立一系列的位置变量。这种位置变量是系统预定义好的 , 可以直接引用。如命令行的 shell 过程名本身被指定为位置变量$ 0 ,第一个命令参数为$ 1 ,……,第九个命令参数为$ 9 。 例: ls / /bin /etc /usr/bin /dev $ 0 $ 1 $ 2 $ 3 $ 4 $ 5
    • 1. 内部命令 shift 的作用 当位置变量个数超出 9 时,就不能直接引用位置大于 9 的位置变量了,必须用 shift 命令存取。每执行一次 shift 命令,删除$ 1 位置变量,并使其他的所有位置变量向左移动一个位置。 例: $ 0 $ 1 $ 2 $ 3 $ 4 $ 5 $ 6 $ 7 $ 8 $ 9 myprog a b c d e f g h i j shift b c d e f g h i j shift c d e f g h i j shift d e f g h i j
    • 2. 用 set 命令进行强制性赋值 位置变量可以使用 set 命令进行强制性赋值。 例: set Sun Mon Tue Wed Thu Fri Sat 结果$ 1 $ 2 $ 3 $ 4 $ 5 $ 6 $ 7 注意 : $ 0 是不能用这种方法赋值的。
    • 9.5.4 环境变量 shell 执行环境由一系列环境变量组成,这些变量是由 shell 维护和管理的。所有这些变量都可被用户重新设置,变量名由大写字母或数字组成。 CDPATH 执行 cd 命令时使用的搜索路径; HOME 用户的 home 目录; PATH 寻找命令或可执行文件的搜索路径; PS1 主命令提示符,默认为“$”; PS2 从命令提示符,默认为“ >” ; TERM 使用的终端类型。
    • 9.5.5 预定义的特殊变量 在 shell 中有一组特殊的变量,其变量名和变量值只有 shell 本身才可以设置。 “ $ #” 记录传递给 shell 的自变量个数。 例 1 : myprog a b c 则 $ # 的值为 3 例 2 : if test $ # -lt 2 then echo ″ two or more args required ″ exit fi
    • “ $ ?” 取最近一次命令执行后的退出状态:执行成功返回码为 0 ,执行失败返回码为 1 。 例: $ test -r my-file( 假设 my-file 文件不可读 ) $ echo $ ? 1 “ $$”记录当前 shell 的进程号。
    • 9.5.6 变量替换 shell 在遇到未设置的变量时,将其值作为空串处理。而在实际应用中,对于未设置的变量,用户可以根据需要采用不同的处理方式,这可通过变量替换来实现。变量替换提供了三种功能:允许替换未设置变量的隐含值;允许对未设置变量赋值;在访问未设置变量时,提示出错信息。格式为, $ {var:-word} 例 1 : 假设$ PARM 未设置 $ echo ″ The value of PARM is $ {PARM:-undefined}″ The value of PARM is undefined $ echo $ PARM -
    • 例 2 :假设$ PARM 未设置 $ arg= $ {PARM:- ″ not defined ″} ( 注意 : 双引号 ) $ echo ′ $ arg : ′ $ arg $ arg : not defined 例 3 :对未设置变量赋值 $ cat use.d sel= $ 1 : $ {sel∶=main}( 注意 : 此处的“ :” 为空命令 ) echo ″ Your selection is $ sel ″ $ use.d programming Your selection is programming
    • 注意 : 变量替换的这种形式不允许使用位置变量,若要使用位置变量,则必须先将位置变量赋值给一中间变量,然后再对中间变量进行这种形式的替换。 例 4 :将例 3 中的空命令用中间变量替换。 $ cat use.d sel= $ 1 my-sel= $ {sel∶=main} echo ″ Your selection is $ my-sel ″ $ use.d programming Your selection is programming
    • 例 5 : 测试环境变量 TERM 是否设置。 $ cat check.env : $ {TERM:? ″ the TERM varible should be set ″ } 注意 : 出错信息以一行长为限,且输出到标准错误输出上。
    • 9.5.7 特殊字符的引用 在 shell 中所使用的许多特殊符号也可为其他目的所使用,因此当不需要引用这些字符的特殊含义时,就必须进行删除。消除特殊字符的含义有 3 种常用的方法,分别是转义符、单引号和双引号。 1. 转义符 ( \ ) 的引用 使用转义符消除单个字符的特殊含义,即将紧跟在转义符后面的单个字符按字符本身的实际含义解释。 例:转义符具有续行功能 $ cat back.sh echo ″Enter your name: \\ c″ read name echo Hello $ name
    • $ back.sh Enter your name: john Hello john 2. 单引号 (‘’) 的引用 使用单引号消除被括在单引号中的所有特殊字符的含义,即单引号表示内容照原样不动。 例 1 : $ echo ′type a $ * please′ type a $ * please 例 2 : 在指定的目录中,查找名字为 *.zh 或 $ .sh 文件。 $ cat test.sh grep ′*.zh | $ .sh′ $ 1
    • 3. 双引号 (“”) 的引用 使用双引号能消除被括在双引号中的大部分特殊字符的含义,不能消除的字符有:$、 ` 、″、 \。 例 1 :$ echo ″ Type a \$ *, please ″ Type a $ *, please 例 2 : vdate= ″`date` is the system maintenance time ! ″ 例 3 : $ cat share-file mkdir /tmp/fk chmod 755 /tmp/fk cp ″ $ @″ /tmp/fk chmod 777 /tmp/fk/*
    • 4. 特殊字符串引用的例外 引用双引号、单引号和转意符都不能消除对 echo 命令有特殊功能的控制字串的特殊含义。这些控制字串是: \ b 退格 \ c 显示后不换行 \ f 在终端上屏幕的开始处显示 \ n 换行 \ r 回车 \ t 制表符 \ v 垂直制表符 \ 反斜杠
    • 9.6 shell 的控制结构 9.6.1 条件与 test 命令 在程序设计语言中,作出决策的结构称为条件。在高级语言中决策的作出是依赖于基本运算的结果;在 shell 语言中,作出决策所依赖的条件是所执行命令的“出口状态”。 当 shell 命令或程序执行成功时,它返回一个“零”出口状态 ( 即$ ? 为零 ) ;如果执行的命令或程序出错时,则返回一个“非零”的出口状态 ( 即$ ? 不为零 ) 。 除了一般命令的出口状态外, UNIX 还提供了两个命令 true 和 false 的出口状态。 true 的出口状态为零;而 false 的出口状态为非零。 shell 将 true 和 false 命令作为恒真和恒假条件使用。
    • 例: 判断所给出的参数是否为一个目录。 $ cat check.dir test -d $ 1 && echo ″ $ 1 is a dictory ″ && exit 0 echo ″ $ 1 is not a dictroy ″ exit 1 在上例中,使用了 exit 命令。它是一个 shell 的内部命令,用于终止 shell 程序的执行。该命令可带一个定义出口状态值的自变量。 例: exit(0): 终止程序的执行,并返回零值; exit(1) 或 exit(x): 终止程序的执行,并返回非零值; 将命令返回零出口状态称为返回“真值”; 将命令返回非零出口状态称为返回“假值”。
    • 1. test 命令 test 命令是 shell 程序设计的条件判断中最常用的测试命令,它有两种等价的格式: test expression 或[ expression ] ( 注意:[ ]中的空格 ) 其中 expression 就是要测试的条件。如果 test 计算 expression 的结果为真,则返回“零”出口状态,否则返回“非零”出口状态。 test 命令可用于对字符串、整数及文件进行各类测试。
    • 2. test 字符串测试表达式 例 1 : 两个字符串进行比较 $ user=smith $ test ″ $ user″= smith $ echo $ ? 0 $ test ″ $ user″ = tom $ echo $ ? 1
    • 例 2 : 带有空格的字符串比较 $ month=″January ″ $ test ″ $ month″ = January $ echo $ ? 1 $ test $ month = January $ echo $ ? 0 注意: shell 在处理变量时,遇到有双引号将保留其内容,而省略双引号时,将滤去空格。
    • 例 3 : 带有空格的字符串比较 $ a=″testing string″ $ test ″ $ a″ = ″testing string″ $ echo $ ? 0 $ test $ a = ″testing string″ test: unknown operator string shell 处理变量 $ a 时,将其进行变量替换,然后将结果 (testing string) 传递给 test ,而 test 将 string 作为操作符来处理,因此出错。
    • 例 4 : 带有空串 ( 或未设置的字符串比较 ) $ name= $ test ″ $ name″ = smith $ echo $ ? 1 $ test $ name = smith test: argument expected shell 处理变量$ name 时,双引号将其括起的内容作为一个“位置持有者”来保留,并把该值传递给 test ,保证处理的正常执行。
    • 例 5 : 带有空串的字符串比较 $ blanks=″″ $ test $ blanks $ echo $ ? 1 $ test ″ $ blanks″ $ echo $ ? 0 shell 处理变量$ blanks 时,将空格滤去,使其变为空串传递给 test ;而双引号保留 “位置持有者”的位置,其值为一个空格 ( 空白符 ) ,传递给 test 。
    • 例 6 : 带有算符的字符串比较 $ symvar=″=″ $ echo ″ $ symvar″ = $ test -z ″ $ symvar″ test: argument expected 出错的原因是“ =” 运算符比“ -z” 运算符的优先级要高,因此, test 命令期望在等号之后要有一个自变量。为避免上述问题发生,可用下面命令形式替换: $ test x″ $ symvar″ = x $ echo $ ? 1
    • 3. 使用 test 测试字符串时要注意的问题 (1) 向 test 传递的各自变量之间必须有空白字符,如果“ =” 与任一自变量之间没有空白字符就会产生错误。 (2) 在使用变量的值替换的自变量上,用双引号括起来十分必要,以保证 test 在变量的值为空时也能接收到该自变量,这是一种良好的 shell 程序设计风格。 (3) 在 test 命令的测试中,空格和引号是 shell 程序设计中经常发生错误的焦点,要特别引起注意。 (4) test 命令对它的自变量非常讲究,在比较的串中出现运算符时,要考虑到运算符优先级的问题。
    • 4. test 命令可用于整数比较 首先要搞清楚整数比较的两个概念: shell 并不区分放在 shell 变量中的值的类型,就变量本身而言,它存放的仅仅是一组字符串,即 shell 只有一种类型的变量——串变量;当使用整数比较操作符时,是 test 命令来解释存放在变量中的整数值,而不是 shell 。 命令格式为, test 整数测试表达式
    • 例 1 : $ x1= ″ 005 ″ $ x2= ″ 10″ $ test ″ $ x1 ″ = 5 $ echo $ ? 1 例 2 : $ test ″ $ x1 ″ -eq 5 $ echo $ ? 0 例 3 : $ test ″ $ x2 ″ -eq 10 $ echo $ ? 0
    • 5. test 用于文件的测试 test 可用于文件各种特性的检查。这些文件的操作符自然是一原操作符,意味着它们要求其后跟随一个自变量。在所有的情况中,这个自变量是一个文件或目录名。常用的文件测试表达式示例如下: 例 1 : 检查指定的文件是否存在并且可读 test -f /usr/fk/message 例 2 : 检查指定的文件是否为目录 test -d /usr/src/local/sendmail 例 3 : 检查指定的出错文件是否为空,如不空则列出该文件的内容 test -s $ errfile && {echo ″Errors found:″ ;\ cat $ errfile}
    • 6. 表达式的逻辑运算 在实际应用中常需要将多个表达式用逻辑运算符组合起来,构成比较复杂的条件。逻辑运算符包括: ! : 逻辑非单目运算符,可放置在任何其他 test 表达式之前,求得表达式运算结果的非值。 -a : 逻辑与运算符,执行两个表达式的逻辑与运算,并且仅当两者都为真时,才返回真值。 -o : 逻辑或运算符,执行两个表达式的逻辑或运算,并只要当两者之一为真时,就返回真值。 逻辑运算符优先级 ( 由高到低 ) 的排列顺序如下: ( ) ! -a -o
    • 逻辑运算符优先级要比字符串操作符、数字比较操作符、文件操作符的优先级低。 例 1 : 当指定的文件不可读时为真。 test ! -r /usr/fk/message 例 2 : 当指定的文件均存在,且 message 为可读、$ mailfile 指定的文件为普通文件时,返回真。 test -r /usr/fk/message -a -f ″ $ mailfile ″ 例 3 : 当变量值大于等于 0 并且小于 10 时为真。 test ″ $ count ″ -ge 0 -a ″ $ count ″ -lt 10 例 4 : 当变量$ a 等于 0 或者$ b 大于 5 ,并且$ c 小于等于 8 时为真。 test \ (″ $ a″ -eq 0 -o ″ $ b″ -gt 5 \ )-a ″ $ c″ -le 8
    • 9.6.2 if 结构 1. if 的简单结构 格式为, if command then command command ... fi
    • 2. if 的完整结构 格式为, if command then command command ... else command command ... fi
    • 3. if 的连用结构 格式 1 if command then commands else if command then commands else … if command then commands fi ... fi fi
    • 格式 2 if command then commands elif command then commands elif … elif commands then commands else commands fi
    • 9.6.3 case 结构 格式为, case value in pattern1) command ... command;; pattern2) command ... command;; ... patternn) command ... command;; esac
    • 9.6.4 for 结构 格式为, for variable in arg1 arg2 ... argn do command ... command done
    • 9.6.5 while 结构 格式为, while command do command ... command done
    • 9.6.6 until 结构 格式为, until command do command ... command done
    • 9.6.7 循环体中其他命令 1. break 命令 格式为, break 或 break n break 是 shell 的内部命令,用于在循环体中根据命令运行的返回条件,直接终止循环体内命令的执行。当执行 break 命令时,控制流从循环体中转移到 done 之后的第一条命令上。当执行 break n 命令时,则终止最内层的 n 个循环的执行。 2. continue 命令 格式为, continue 或 continue n
    • continue 是 shell 的内部命令,用于在循环体中根据命令运行的返回条件,直接进入下一次循环命令的执行。当执行 continue 命令时,控制流直接转到本循环体中第一条命令上。当执行 continue n 命令时,则跳过最里层的 n 次循环体的执行,即开始第 n 个 ( 从内向外数 ) 循环的下一个循环过程。 3. 循环中的输入输出改向 对整个循环命令也可实施 shell 命令的输入输出改向。循环输入的改向将作用于循环体中从标准输入读数据的所有命令;而循环输出改向将作用于循环体中向标准输出写数据的所有命令。在循环体内也可以进行输入输出改向,且这种改向独立于整个循环体命令的输入输出改向。
    • 例: while test ″ $ count ″ -lt 20 do ... echo ″ error: $ count ″ ... echo ″ right: $ count ″ >/dev/tty ... done 2 >error.out
    • 4. 循环体与管道 循环命令也能通过管道与其他的 shell 命令联接在一起使用。一个命令的输出通过管道可以作为循环命令的输入,而整个循环命令的输出也可以通过管道作为另一个命令的输入。 例: $ for i in a b c d >do >echo $ i >done|wc -l 4
    • 9.6.8 函数的定义和使用 函数实际上是由若干 shell 命令组成,因此它与 shell 的命令文件形式上是相似的。不同的只是 shell 函数常驻于内存之中。它们不再是一个单独的进程,而是成了 shell 的一部分。 格式为, Function Name(){ command … command }
    • 函数仅存在于定义它的 shell 环境中,不能把它们传递给子 shell 。函数是在当前 shell 中被执行的,在函数体中变量值和当前工作目录的改变,将影响当前 shell 的运行环境。函数一旦定义就被存入内存, shell 执行函数时不需要在磁盘中进行查找,所以执行速度要比把函数中的命令放入到一个文件中的执行要快。使用函数时,命令行中列在函数名后的自变量传递给函数体中的位置参数。函数执行返回时也有一个出口状态。函数体内执行 exit 命令将结束函数的执行,同时也结束调用函数的 shell 程序的运行。而 return 命令用于结束函数的执行,如省略 return 时,则以最后一条命令的出口状态作为函数的出口状态。
    • 例: # The test codes for function definition GetYesOrNo() { while echo ″ $ *(Y/N)? \ c ″ > &2 do read reply RestData case ″ $ reply ″ in [ yY ] ) return 0 ;; [ nN ] )return 1 ;; *)echo ″ Please enter Y or N ! ″ >&2 ;; esac done 执行$ GetYesOrNo ″ Do you wish to continue ″ || exit 显示 Do you wish to continue(Y/ N)?
    • 9.7 shell 的运行环境 9.7.1 局部变量 当用户注册之后,用户就拥有了注册 shell 的运行环境,其运行环境主要由 shell 变量值决定。注册 shell 在接受到用户输入的命令 ( 非内部命令 ) 后,通常派生出一个子 shell ,由此子 shell 负责解释执行该命令。子 shell 有自己的运行环境和局部变量,局部变量仅在特定的环境下才能使用。 shell 的运行环境是可以改变的,但子 shell 不能存取由父 shell 设置的局部变量,也不能改变父 shell 的变量值。
    • 例 1 : $ cat var-test echo : $ x: $ x=100 $ var-test :: 例 2 : $ cat var-test2 x=60 echo : $ x: $ x=10 $ var-test2 :60: $ echo $ x 10
    • 9.7.2 全局变量 全局变量是一种特殊的变量,可以被任何运行的子 shell 来引用。全局变量通过 export 命令来定义,格式如下: export variables 其中 variables 是定义全局变量的变量表名。 一旦变量被定义为全局变量,则对于以后的所有子 shell 来说这些都是全局变量。子 shell 中无法改变全局变量的值。若在子 shell 中改变全局变量的值,实际是对全局变量的副本进行更改,不影响全局变量值。子 shell 中局部变量的使用优先于全局变量。
    • 例: $ export g-var $ g-var=″GLOBAL″ $ cat test-var export g-var l-var g-var=″sub-shell:g-var″ l-var=″sub-shell:l-var″ echo $ g-var $ l-var $ test-var sub-shell:g-var sub-shell:l-var $ echo $ g-var : $ l-var: GLOBAL :: $
    • 9.7.3 局部变量和全局变量作用域 任何没有用 export 命令定义过的变量是局部变量,子 shell 不能存取父 shell 的局部变量。子 shell 中可以存取和修改父 shell 的全局变量,但这种修改对于父 shell 全局变量没有任何影响。在子 shell 中用 export 命令定义的全局变量和对此变量的修改对父 shell 变量没有影响。全局变量保持它的全局性,不仅能直接传递给它的子 shell ,而且子 shell 还能将它传递给子 shell 的子 shell 。在对变量赋值之前和之后的任何时候可以将该变量转换成全局变量。
    • 9.7.4 程序调试 shell 提供了多种工具以便在调试 shell 程序时使用,这些工具允许观察一个 shell 程序的执行,还允许观察一个程序在不做任何实际处理的情况下是如何被“执行”的。提供的测试方式有: 1. shell 程序的详细跟踪 shell 提供的详细跟踪特性允许用户观察一个 shell 程序的读入和执行,如果在读入命令行时发现语法错误,则终止程序的执行。命令行被读入后, shell 按读入时的形式在标准错误输出中显示该命令行,然后执行命令行。详细跟踪 shell 程序的执行有两种方式:整个程序的详细跟踪和局部程序的详细跟踪。
    • 整个程序的跟踪执行格式 : sh -v 文件名用来实现对整个文件的脚本进行跟踪。 局部程序的跟踪执行格式 :set -v—— 设置跟踪标志 set +v—— 关闭跟踪标志用来实现对文件中的部分脚本进行跟踪。
    • 例: $ cat traced date echo $ PATH|wc -c $ traced 1998 年 11 月 05 日 星期四 17 时 29 分 59 秒 CST 45 $ sh -v traced date 1998 年 11 月 05 日 星期四 17 时 30 分 08 秒 CST echo $ PATH|wc -c 45
    • 2. shell 程序的跟踪执行 此功能允许用户观察一个 shell 程序的执行,它使命令行在执行前完成所有替换之后,在标准错误输出中显示每一个被替换后的命令行,并且在行前加上前缀符号“ +”( 但变量赋值语句不加“ +” 符号 ) ,然后执行命令。对 shell 程序的跟踪执行也有两种方式:整个程序的跟踪执行和局部程序的跟踪执行。 整个程序的跟踪执行格式为, sh -x 文件名 用来实现对整个文件脚本的跟踪执行。 局部程序的跟踪执行格式为, set -x—— 设置跟踪标志 set +x—— 关闭跟踪标志
    • 用来实现对文件中部分脚本的跟踪执行。 例: $ cat traced date echo $ PATH|wc -c $ sh -x traced +date 1998 年 11 月 05 日 星期四 17 时 30 分 08 秒 CST +echo /bin:/usr/bin:/usr/fk/bin /bin:/usr/bin:/usr/fk/bin +wc -c 25
    • 3. 详细跟踪与跟踪执行的组合 整个程序的跟踪执行格式为, sh -vx 文件名 局部程序的跟踪执行格式为, set -vx—— 设置跟踪标志 set +vx—— 关闭跟踪标志 详细跟踪与跟踪执行的区别在于在执行命令行之前,详细跟踪打印出命令行的原始内容,而跟踪执行却打印出经过替换后的命令行内容,循环结构和管道线在两种跟踪方式中是根本不同的。由于 shell 对这些结构只读取一次,因此,详细跟踪方式也仅显示它们一次。但这类结构的命令行可能要被重复执行多次,所以,跟踪执行方式就可能将同样的命令行显示一次以上。
    • 4. 非执行跟踪 shell 的非执行任选项提供的功能允许用户观察一个 shell 程序,而程序中的命令行不被执行。非执行通常与详细跟踪结合使用,查看在不实际执行一个 shell 程序时,程序是如何被“执行”的。其格式为, sh -n 文件名 或 set -n
    • 5. 未置变量退出跟踪 shell 的未置变量退出特性允许对所有的变量进行检查,如果引用了一个未赋值的变量就终止 shell 程序执行。 shell 通常允许未置变量的使用,在此种情况下,变量的值为空。但如果设置了未置变量退出的任选项,则一旦使用了未置的变量就显示出错信息,并终止程序的运行。其格式为, sh -u 文件名 或 set -u—— 设置标志 set +u—— 清除标志
    • 6. 立即退出跟踪 shell 的立即退出特性允许在程序内的任何命令以非零状态退出时,立即终止程序的运行。其格式为, sh -e 文件名 或 set -e—— 设置标志 set +e—— 清除标志
    • 9.7.5 shell 程序的应用 1. shell 程序的定义 若一个文件包含了一串简单命令串时,称为命令文件;若一个文件包含了一组更为复杂的命令组合 ( 常常使用了 shell 的条件命令、控制结构和其他高级特性 ) 时,就称之为 shell 程序 ( 或称 shell 过程 ) 。通常情况下,两者的称呼并没有明确的区别。 在 shell 程序设计语言里的基本操作是 Linux 系统命令,它是 Linux 系统中最有生气的部分,而且对于 Linux 系统的使用极为重要。
    • 2. 何时使用 shell 程序设计语言 当一个问题的解决方法需要使用多个 Linux 系统的标准命令时,可使用 shell 命令文件。如果处理的问题比较复杂,可以使用 shell 程序设计语言,即在命令文件中定义各种参数和变量,使用 shell 的条件命令、控制结构和其他高级特性。
    • 9.8 shell 应用实例 下面这段程序取自 /etc/rc.d/rc.sysinit 文件中的一部分, /etc/rc.d/rc.sysinit 文件是 Linux 系统启动过程中需要执行的 shell 程序。这段程序的功能主要是配置网络环境、检查文件系统和加载应用模块。要注意,在 shell 程序中注释语句是以 # 开头,但以 # !开头的语句并不是注释语句。如果想执行该程序,可以用一般用户登录系统后,直接执行 /etc/rc.d/rc.sysinit 。
    • # 第一条语句先指定本 shell 程序采用哪个 shell 执行。 #! /bin/sh # 以 # 开始的语句是注释行。 # /etc/rc.d/rc.sysinit - run once at boot time # # Taken in part from Miquel van Smoorenburg′s bcheckrc. # # 下面语句是定义变量 PATH ,并把 PATH 变量声明为全局变量。 # Set the path PATH=/bin:/sbin:/usr/bin:/usr/sbin export PATH
    • # 下面命令读取配置数据,这里用到一个简单的 IF 语句。 # 判断 /etc/sysconfig/network 文件存在否,如果该文件存在,就执行它。 # 否则,给 NETWORKING 变量赋值,给 HOSTNAME 变量赋值。 # Read in config data. if [ -f /etc/sysconfig/network ] ; then . /etc/sysconfig/network else NETWORKING=no HOSTNAME=localhost fi
    • # 下面语句的功能是启用交换,执行 shell 命令 swapon 。 # Start up swapping. echo ″Activating swap partitions″ swapon |a # 以下语句设置主机名。 # 下面语句引用 hostname 变量,执行 hostname 命令。 # Set the hostname. hostname $ {HOSTNAME} echo hostname: `hostname`
    • # 下面语句设置 NIS 域名,其中引用$ NISDOMAIN 变量,执行 shell 命令 domainname 。 # Set the NIS domain name if [ -n ″ $ NISDOMAIN″ ] ; then domainname $ NISDOMAIN else domainname ″″ fi # 下面是一段简单的 if 语句,判断 /fsckoptions 文件是否存在, # 并定义 fsckoptions 变量。 if [ -f /fsckoptions ] ; then fsckoptions=`cat /fsckoptions` else fsckoptions=′′ fi
    • # 下面是一段简单的 if 语句,判断 /fastboot 文件是否不存在。 # 其中,引用 fsckoptions 变量,执行 shell 语句 fsck 。 # 并引用系统变量$ ? ,定义变量 rc ,获取上一个 shell 语句的执行结果。 if [ ! -f /fastboot ] ; then echo ″Checking root filesystems.″ fsck -V -a $ fsckoptions / rc= $ ? # 以下这段语句是检查文件系统的执行结果, # 如果$ rc 大于 1 ,说明文件系统有严重问题,并显示信息。 # 其中,给环境变量 PS1 赋值,执行 shell 命令 sulogin , # 执行 shell 命令 umount a ,执行 shell 命令 reboot 。 # A return of 2 or higher means there were serious problems. if [$ rc -gt 1 ] ; then echo echo echo ″*** An error occurred during the file system check.″ echo ″*** Dropping you to a shell; the system will reboot″ echo ″*** when you leave the shell.″
    • PS1=″(Repair filesystem) #″; export PS1 sulogin echo ″Unmounting file systems″ umount a mount -n -o remount, ro/ echo ″Automatic reboot in progress.″ reboot # 下面语句是嵌套的复合条件 if 语句,执行 shell 命令 quotacheck elif [″$ rc″=″1″ -a -x/sbin/quotacheck ] ; then echo ″Checking root filesystem quotas″ /sbin/quotacheck -v/ fi fi
    • # 下面语句对根系统实行磁盘空间限定,执行 shell 命令 quotaon 。 if [ -x/sbin/quotaon ] ; then echo ″Turning on user and group quotas for root filesystem″ /sbin/quotaon/ fi # 下面命令是设置 PNP ,首先检查参数,执行 shell 命令 mount 。 # 在 if 语句中采用输出重定向,定义变量 PNP 。 # check for arguments mount -t proc/proc/proc if grep -i nopnp/proc/cmdline>/dev/null; then PNP= else PNP=yes fi
    • # 以下语句设置 PNP, 先使用一个简单 if 语句 , 复合条件,并引用变量 PNP 。 # 执行 shell 命令 isapnp 。 # set up PNP if [ -x /sbin/isapnp -a -f /etc/isapnp.conf ] ; then if [ -n ″ $ PNP″ ] ; then echo ″Setting up ISA PNP devices″ /sbin/isapnp/etc/isapnp.conf else echo ″Skipping ISA PNP configuration at users request″ fi fi
    • # 下面命令重新 mount 根文件系统,执行 shell 命令 mount # 如果 /etc/HOSTNAME 不存在,则把$ {HOSTNAME} 变量的值输出到 /etc/HOSTNAME 文件中。 # Remount the root filesystem read-write. echo ″Remounting root filesystem in read-write mode.″ mount -n -o remount,rw / if [ ! -f /etc/HOSTNAME ] ; then echo $ {HOSTNAME} > /etc/HOSTNAME fi # 清空 / etc/mtab 文件 # clear mtab >/etc/mtab
    • # 执行 shell 命令,把 / 和 /proc 文件系统加载上。 # Enter root and /proc into mtab. mount -f / mount -f /proc # 如果 /proc/ksyms 文件存在 , 定义变量 USEMODULES 为 y 。 if [ -f /proc/ksyms ] ; then USEMODULES=y else USEMODULES= fi
    • # 下面命令是获取要执行的模块。 # 先执行 shell 命令 rm 删除文件 , 引用变量 USEMODULES , # 并用 set 给位置变量强制赋值,引用系统变量$ # ,引用位置变量$ 1, # 在 if 语句中使用管道命令 , 使用输出重定向。 # Get the modules ready to go -- we use awk here as cut is in /usr/bin rm -f /lib/modules/preferred if [ -n $ USEMODULES ] ; then set `cat /proc/cmdline` while [$ # -gt 0 ] ; do if echo $ 1|grep ′^BOOT-IMAGE=′ > /dev/null ; then # # # 中间略去部分语句 # # 下面语句初始化串口 , 执行 shell 命令 rc.serial 。 # Initialize the serial ports. if [ -f /etc/rc.d/rc.serial ] ; then . /etc/rc.d/rc.serial fi
    • # 下面语句加载模块 , 执行 shell 命令 rc.modules 。 # Load modules (for backward compatibility with VARs) if [ -f /etc/rc.d/rc.modules ] ; then /etc/rc.d/rc.modules fi # 下面语句是检查 SCSI 磁带设备。 # If a SCSI tape has been detected, load the st module unconditionally # since many SCSI tapes don′t deal well with st being loaded and unloaded # 这里用到带复合条件的 if 语句,并使用管道命令和重定向命令。 if [ -f /proc/scsi/scsi ] && cat /proc/scsi/scsi|grep -q ′Type:Sequential-Access′ 2>/dev/null ; then if cat /proc/devices|grep -qv ′ 9 st′ ; then if [ -n ″ $ USEMODULES″ ] ; then # Try to load the module. If it fails, ignore it... modprobe st 2>/dev/null fi fi fi
    • # 下面执行 shell 命令 dmesg ,显示系统配置信息, # 并把输出结果重定向到 /var/log/dmesg 中。 # Now that we have all of our basic modules loaded and the kernel going, # let′s dump the syslog ring somewhere so we can find it later dmesg > /var/log/dmesg # 执行 shell 命令 random start 。 # Feed entropy into the entropy pool /etc/rc.d/init.d/random start
    • 9.9 小结 本章主要介绍了什么是 shell 、 shell 变量、 shell 命令以及 shell 语言控制结构 , 并着重讲述了如何使用 shell 语言编写 shell 程序并调试运行。大家要充分理解 shell 变量,通过编写 shell 程序并运行,来理解 shell 程序在整个 Linux 系统所起的巨大作用。
    • 习题 9-1 什么是 shell ?什么是重定向?什么是管道? 9-2 shell 变量怎么用 ? 常用的预定义变量和环境变量都有哪些?常用的位置变量和系统自动赋值有哪些? 9-3 往你的主目录下随便拷贝 5 个文件,用 mv 命令将这些文件名都变成大写,现在请你编个 shell 程序,将这些文件名都变成小写。 9-4 编写一个 shell 程序,它能够将指定目录及其子目录中的包含字符串 root 的文本文件找出来。