Unix系列shell程序编写从入门到精通(上)
时间:2006-01-24 来源:heavensword
*Shell 是什么 ?
任何发明都具有供用户使用的界面。 UNIX 供用户使用的界面就是 Shell(DOS 的 command 熟悉吧,但 UNIX 的要强大的多 ) 。 Shell 为用户提供了输入命令和参数并可得到命令执行结果的环境。
为了不同的需要, UNIX 提供了不同的 Shell 。现在的 UNIX 大部分都支持 BourneShell ,以下教程就以 BourneShell(Bsh) 为例,一步步的领略 UNIX Shell 的强大功能,占先其强大魅力,达到更方便灵活的管理、应用 UNIX 的目的。
1.UNIX 内核和 Shell 的交互方法
启动 UNIX 时,程序 UNIX( 内核 ) 将被调入计算机内存,并一直保留在内存中直到机器关闭。在引导过程中,程序 init 将进入后台运行一直到机器关闭。该程序查询文件 /etc/inittab ,该文件列出了连接终端的各个端口及其特征。当发现一个活动的终端时, init 程序调用 getty 程序在终端上显示 login 等登陆信息。 (username 和 passwd) ,在输入密码后, getty 调用 login 进程,该进程根据文件 /etc/passwd 的内容来验证用户的身份。若用户通过身份验证, login 进程 把用户的 home 目录设置成当前目录并把控制交给一系列 setup 程序。 setup 程序可以是指定的应用程序,通常 setup 程序 为一个 Shell 程序,如 :/bin/sh 即 Bourne Shell ( command 出来了,呵呵)。
得到控制后, Shell 程序读取并执行文件 /etc/.profile 以及 .profile 。这两个文件分别建立了系统范围内的和 该用户自己的工作环境。最后 Shell 显示命令提示符,如 $ 。 ( 这是以 bsh 为例,若是 csh, 为 .cshrc,ksh 为 .kshrc,bash 为 .bashrc 等等 )
注 :( 不妨把 /etc/.profile 和 .profile 看成 DOS 的 autoexec.bat 或 config.sys 文件 )
当 shell 退出时,内核把控制交给 init 程序 , 该程序重新启动自动登陆过程。有两种方法使 shell 退出,一是用户执行 exit 命令,二是 内核 ( 例如 root 用 kill 命令 ) 发出一个 kill 命令结束 shell 进程。 shell 退出后,内核回收用户及程序使用的资源。
用户登陆后,用户命令同计算机交互的关系为 : 命令进程 --->Shell 程序 --->UNIX 内核 ---> 计算机硬件。当用户输入一个命令,如 $ls, Shell 将定位其可执行文件 /bin/ls 并把其传递给内核执行。内核产生一个新的子进程调用并执行 /bin/ls 。当程序执行完毕后,内核取消 该子进程并把控制交给其父进程,即 Shell 程序。例如执行 :
$ps
该命令将会列出用户正在执行的进程,即 Shell 程序 ( 下来详细说说,别急现在 ) 和 ps 程序。若执行 :
$sleep 10 &
$ps
其中第一条命令将产生一个在后台执行的 sleep 子进程。 ps 命令执行时会显示出该子进程。
每当用户执行一条命令时,就会产生一个子进程。该子进程的执行与其父进程或 Shell 完全无关,这样可以使 Shell 去做其他工作。 (Shell 只是把用户的意图告诉内核,然后该干嘛干嘛 :)) 现在 windows 有个计划任务 ( 在固定的时间,日期自动执行某任务 ), 其实 UNIX 很早就有这个功能了,也就是所谓的 Shell 的自动执行。一些 UNIX 资源,如 cron 可以自动执行 Shell 程序而无需用户的参与, ( 这个功能好象在 /var/spool/crotab 目录里 ) 。
Crontab 程序对于系统管理员来说是非常有用的。 Cron 服务用于计划程序在特定时间(月、日、周、时、分)运行。我们以 root 的 crontab 为例。根用户的 crontab 文件放在 /var/spool/crontab/root 中,其格式如下:
(1) (2) (3) (4) (5) (6)
0 0 * * 3 /usr/bin/updatedb
1. 分钟 (0-60)
2. 小时 (0-23)
3. 日 (1-31)
4. 月 (1-12)
5. 星期 (1-7)
6. 所要运行的程序
2.Shell 的功能和特点
1> 命令行解释
2> 使用保留字
3> 使用 Shell 元字符 ( 通配符 )
4> 可处理程序命令
5> 使用输入输出重定向和管道
6> 维护一些变量
7> 运行环境控制
8> 支持 Shell 编程
对于命令行解释就不多说了,就是在 shell 提示符 ( 例如 :$,%,# 等 ) 后输入一行 unix 命令, Shell 将接收用户的输入。
使用保留字 :Shell 有一些具有特殊意义的字,例如在 Shell 脚本中, do,done,for 等字用来控制循环操作, if,then 等控制条件操作。 保留字随 Shell 环境的不同而不同。
通配符: * 匹配任何位置
? 匹配单个字符
[] 匹配的字符范围或列表 例如 :
$ls [a-c]*
将列出以 a-c 范围内字符开头的所有文件
$ls [a,m,t]*
将列出以 e,m 或 t 开头的所有文件
程序命令 :当用户输入命令后, Shell 读取环境变量 $path( 一般在用户自己的 .profile 中设置 ) ,该变量包含了命令可执行文件可能存在的目录列表。 shell 从这些目录中寻找命令所对应的可执行文件,然后将该文件送给内核执行。
输入输出重定向及管道 :重定向的功能同 DOS 的重定向功能:
> 重定向输出
< 重定向输入
而管道符号,是 unix 功能强大的一个地方 , 符号是一条竖线 :| ,用法 : command 1 | command 2 他的功能是把第一个命令 command 1 执行的结果作为 command 2 的输入传给 command 2 ,例如 :
$ls -s|sort -nr|pg
该命令列出当前目录中的所有文件,并把输出送给 sort 命令作为输入, sort 命令按数字递减的顺序把 ls 的输出排序。然后把排序后的 内容传送给 pg 命令, pg 命令在显示器上显示 sort 命令排序后的内容。
维护变量 : Shell 可以维护一些变量。变量中存放一些数据供以后使用。用户可以用 = 给变量赋值,如 :
$lookup=/usr/mydir
该命令建立一个名为 lookup 的变量并给其赋值 /usr/mydir, 以后用户可以在命令行中使用 lookup 来代替 /usr/mydir ,例如 :
$echo $lookup
结果显示 :/usr/mydir
为了使变量能被子进程使用 , 可用 exprot 命令,例如 :
$lookup=/usr/mydir
$export lookup
运行环境控制 :当用户登陆启动 shell 后, shell 要为用户创建一个工作的环境,如下 :
1> 当 login 程序激活用户 shell 后,将为用户建立环境变量。从 /etc/profile 和 .profile 文件中读出,在这些文件中一般都用 $TERM 变量设置终端类型,用 $PATH 变量设置 Shell 寻找可执行文件的路径。
2> 从 /etc/passwd 文件或命令行启动 shell 时,用户可以给 shell 程序指定一些参数,例如 -x ,可以在命令执行前显示该命令及其参数。后面详细介绍这些参数。
shell 编程 :本文主要介绍的内容。
shell 本身也是一种语言 (* 可以先理解为 unix 命令的组合,加上类 C 的条件,循环等程序控制语句 , 类似 dos 批处理,但要强大的多 ), 用户可以 通过 shell 编程 ( 脚本 , 文本文件 ) ,完成特定的工作。
SHELL 变量
下面我们详细的介绍 Bourne Shell 的编程 :
自从贝尔实验室设计了 Bourne Shell 。从那时起许多厂商根据不同的硬件平台设计了许多版本得 unix 。但在众多版本的 unix 中, Bourne Shell 一直保持一致。
1>Bsh 的启动:用户在登陆后,系统根据文件 /etc/passwd 中有关该用户的信息项启动 Shell 。例如某用户在 passwd 中 的信息项为 :
ice_walk:!:411:103:Imsnow ,ice_walk:/home/ice_walk:/bin/bsh
则表明,用户名是 ice_walk 等信息,在最后一项 /bin/bsh 表明用户的 sh 环境类型是 bsh, 于是系统启动之。在启动或执行 ( 包括下面我们要讲 的 shell 程序 -- 脚本)过程中可以使用以下一些参数,我们一一说明 :
-a 将所有变量输出
-c string 从 string 中读取命令
-e 使用非交互式模式
-f 禁止 shell 文件名产生
-h 定义
-i 交互式模式
-k 为命令的执行设置选项
-n 读取命令但不执行
-r 受限模式
-s 命令从标准输入读取
-t 执行一命令,然后退出 shell
-u 在替换时,使用未设置的变量将会出错
-v 显示 shell 的输入行
-x 跟踪模式,显示执行的命令
许多模式可以组合起来用 , 您可以试试了,但 -ei 好象不行,你说 why 呢?
使用 set 可以设置或取消 shell 的选项来改变 shell 环境。打开选项用 -, 关闭选项用 +, 多数 unix 允许打开或关闭 a 、 f 、 e 、 h 、 k 、 n 、 u 、 v 和 x 选项。若显示 Shell 中已经设置的选项,执行 :
$echo $-
Bsh 中每个用户的 home 目录下都有一个 .profile 文件,可以修改该文件来修改 shell 环境。为了增加一个可执行文件的路径 ( 例如 /ice_walk/bin) , 可以把下面代码加入 .profile 中
PATH=$PATH:/ice_walk/bin;exprot PATH
.profile 中 shell 的环境变量意思如下 :
CDPATH 执行 cd 命令时使用的搜索路径
HOME 用户的 home 目录
IFS 内部的域分割符,一般为空格符、制表符、或换行符
MAIL 指定特定文件 ( 信箱 ) 的路径,有 UNIX 邮件系统使用
PATH 寻找命令的搜索路径 ( 同 dos 的 config.sys 的 path)
PS1 主命令提示符,默认是 $
PS2 从命令提示符,默认是 >
TERM 使用终端类型
2>Bsh 里特殊字符及其含义
在 Bsh 中有一组非字母字符。这些字符的用途分为四类 : 作为特殊变量名、产生文件名、数据或程序控制以及引用和逃逸字符控制。他们 可以让用户在 Shell 中使用最少的代码完成复杂的任务。
*> Shell 变量名使用的特殊字符
$# 传送给命令 Shell 的参数序号
$- 在 Shell 启动或使用 set 命令时提供选项
$? 上一条命令执行后返回的值
$$ 当前 shell 的进程号
$! 上一个子进程的进程号
$@ 所有的参数,每个都用双括号括起
$* 所有参数,用双括号括起
$n 位置参数值, n 表示位置
$0 当前 shell 名
*> 产生文件名的特殊字符
包括 *,?,[] ,上面讲过,不再多说。
*> 数据或程序控制使用的特殊字符
>(file) 输出重定向到文件中 ( 没有文件则创建,有则覆盖 )
>>(file) 输出重定向到文件中 ( 没有则创建,有则追加到文件尾部 )
<(file) 输入重定向到文件
; 命令分割符
| 管道符
& 后台运行 ( 例如 :sleep 10 &)
` ` 命令替换,重定向一条命令的输出作为另一命令的参数
*> 对于引用或逃逸的特殊字符
Bsh 用单引号 ' ' 和双引号 将特殊字符或由空白分隔的字引用起来组成一个简单的数据串 . 使用单引号和双引号的区别是双引号中的内容可进行参数和变量替换 . 逃逸字符也一样 .
$echo $HOME $PATH
结果显示 $/u/ice_walk/bin:/etc:/usr/bin
而 $echo '$HOME $PATH' 结果显示 $HOME $PATH
shell 的逃逸符是一个 \\, 表示其后的字符不具有特殊的含义或不是 shell 的函数
$echo \\$HOME $PATH
结果显 $$HOME /bin:/etc:/usr/bin:
3>Bsh 的变量
前面我们在多个地方引用了变量 , 当 Shell 遇到一个 $ 符时 ( 没有被引用或逃逸 ) ,它将认为其后为一变量。不论该变量是环境变量还是用户自定义的变量,在命令行中变量名要被变量值替换。例如命令 :ls $HOME 将列出变量 HOME 对应目录下的文件。 用户可以在命令行中的任何地方进行变量替换。包括命令名本身,例如:
$dir=ls
$$dir f*
将列出以 f 开头的文件。
现在详细的介绍下 Bsh 的变量。 Bsh 中有四类变量 : 用户定义的变量、位置变量 (shell 参数 ) 、预定义变量及环境变量。
用户定义的变量:
用户定义的变量由字母和下划线组成,并且变量名的第一个字符不能为数字 (0~9) 。与其他 UNIX 名字一样,变量名是大小写敏感的。用户可以在命令行上用 = 给变量赋值,例如 :
$NAME=ice_walk
给变量 NAME 赋值为 ice_walk, 在应用变量 NAME 的时候,在 NAME 前加 $ 即可,前面已说,不再废话 ( 别说我废话多,关键是没当过老师 :() 。可以用变量和其他字符组成新的字,例如 :
$SUN=sun
$echo ${SUN}day
在应用 shell 变量时候,可以在变量名字两边 $ 后面加上 {} ,以更加清楚的显示给 shell, 哪个是真正的变量,以实现字符串的合并等功能。
结果显示 :sunday( 注意不能 echo $SUNday, 因为 SUNday 变量没定义,读者试下执行结果 ) 用户也可以在命令行上同时对多个变量赋值,赋值语句之间用空格分开 :
$X=x Y=y
注意变量赋值是从右到左进行的
$X=$Y Y=y
X 的值是 y
$X=z Y=$Z
Y 的值是空 ( 变量未赋值时, shell 不报错,而是赋值为空 )
用户可以使用 unset < 变量 > 命令清除给变量赋的值
用户使用变量时要在其前面加一 $ 符,使变量名被变量值所替换。 Bsh 可以进行变量的条件替换,即只有某种条件发生时才进行替换。替换条件放在一对大括号 {} 中,如 :
${variable: -value} variable 是一变量值, value 是变量替换使用的默认值
$echo Hello $UNAME
结果显示 :Hello
$echo Hello ${UNAME: -there}
结果显示 :Hello there
$echo $UNAME
结果显示 : ( 空 )
$UNAME=John
$echo Hello ${UNAME: -there}
结果显示 :Hello John
可以看出,变量替换时将使用命令行中定义的默认值,但变量的值并没有因此而改变。另外一种替换的方法是不但使用默认值进行替换,而且将默认值赋给该变量。其形式如下 :
${variable:=value}
该形式在变量替换后同时把值 value 符给变量 variable 。
$echo Hello $UNAME
结果显示 :Hello
$echo Hello ${UNAME:=there}
结果显示 :Hello there
$echo $UNAME
结果显示 :there
$UNAME=John
$echo Hello ${UNAME:-there}
结果显示 :Hello John
变量替换的值也可以是 ` ` 括起来的命令 :
$USERDIR={$Mydir: -`pwd`}
第三种变量的替换方法是只有当变量已赋值时才用指定值替换形式 :
${variable: +value}
只有变量 variable 已赋值时,其值才用 value 替换,否则不进行任何替换,例如 :
$ERROPT=A
$echo ${ERROPT: +Error tracking is acitive}
结果显示 :Error tracking is acitive
$ERROPT=
$echo ${ERROPT: +Error tracking is acitive}
结果显示 : ( 空 )
我们还可以使用错误检查的条件进行变量替换 :
${variable:?message}
当变量 variable 已设置时,正常替换。否则消息 message 将送到标准错误输出 ( 若此替换出现在 shell 程序中 , 那么该程序将终止 ) 。 例如:
$UNAME=
$echo $ {UNAME:?UNAME HAS NOT BEEN SET}
结果显示 :UNAME HAS NOT BEEN SET
$UNAME=Stephanie
$echo $ {UNAME:?UNAME HAS NOT BEEN SET}
结果显示 :Stephanie
当没有指定 message 时, shell 将显示一条默认的消息,例如 :
$UNAME=
$echo $ {UNAME:?}
结果显示 :sh:UNAME:parameter null or not set
4> 位置变量或 Shell 参数
在 shell 解释用户的命令时,将把命令行的第一个字作为命令,而其他的字作为参数。当命令对应的可执行文件为 Shell 程序时,这些参数将作为位置变量传送给该程序。第一个参数记为 $1, 第二个为 $2.... 第九个为 $9 。其中 1 到 9 是真正的参数名, $ 符只是用来标识变量的替换。
位置变量 $0 指命令对应的可执行文件名。在后面将详细介绍位置变量。
1. 只读变量
用户将变量赋值后,为了防止以后对该变量的修改,可以用以下命令将该变量设置为只读变量:
readonly variable
2.export 命令
shell 执行一个程序时,首先为该程序建立一个新的执行环境,称为子 shell 。在 Bourne Shell 中变量都是局部的,即他们只在创建他们的 Shell 中有意义。用户可以用 export 命令让变量被其他子 Shell 识别。但某用户的变量是没法让其他用户使用的。
当用户启动一个新 shell 时 , 该 shell 将使用默认的提示符。因为赋给变量 PS1 的值只在当前 shell 中有效。为了让子 Shell 使用当前 Shell 中定义的提示符号,可以使用 export 命令:
$PS1=Enter command:
Enter command:export PS1
Enter command:sh
Enter command:
此时变量 PS1 变成了全局变量。它可以被其子 Shell 使用。当变量被设置成全局的以后,将一直保持有效直到用户退出该变量所在的 Shell 。用户可以在文件 .profile 中给一个变量永久赋值。详见规范 Shell 。
基本语句
从本节起,我们将详细介绍 Shell 程序设计的基本知识,通过编写 Shell 脚本,用户可以根据自己的需要有条件的或者重复的执行命令。通过 Shell 程序,可以把单个的 UNIX 命令组合成一个完全实用的工具,完成用户的任务。
1> 什么是 Shell 程序
当用户在 UNIX Shell 中输入了一条复杂的命令,如 :
$ls -R /|greo myname |pg
我们可以称用户在对 Shell 编程,当把这条语句写在一个文件里,并且符给该文件可执行权限,那么该文件就是我们传统上说的 Shell 程序。
2> 简单的 Shell 程序
假设用户每天使用下述命令备份自己的数据文件 :
$cd /usr/icewalk;ls * |cpio -o > /dev/fd0
我们可以把它写在一个文件,如 :ba.sh 中 :
$cat >ba.sh
cd /usr/icewalk
ls * |cpio -o > /dev/fd0
^D (ctrl_d)
程序 ba.sh 就是 Shell 脚本,用户可以用 vi 或其他编辑工具编写更复杂的脚本。
此时用户备份文件只需要执行 Shell 程序 ba.sh, 执行时需在当前 Shell 中创建一个子 Shell:
$sh ba.sh
程序 sh 与用户登陆时执行的 Bourne Shell 相同,但当 Sh 命令带参数 ba.sh 后,它将不再是一个交互式的 Shell ,而是直接从文件 ba.sh 中读取命令。
执行 ba.sh 中命令的另一方法是给文件 ba.sh 执行权限:
$chmod +x ba.sh
此时,用户可以输入文件名 ba.sh 做为一个命令来备份自己的数据,需要注意的是,用这种方法执行命令的时候,文件 ba.sh 必须存在于环境变量 $PATH 所指定的路径上