第二十四章 Shell Script 身为 UNIX 系统管理者除了要熟悉 UNIX 指令外,我们最好学会几种 scripts 语言,例如 shell script 或 perl。学会 script 语言后,我们就可以将日常的系统管理工作写成一支执行档,如此一来,在管理系统时就可以更加灵活。 Shell script 是最基本的 script 语言,它是一堆 UNIX 指令的集合。本章将介绍 Shell script 的基本功能及语法,期望读者可以经由学习 Shell scripts 让使用 UNIX 系统时可以更加得心应手。 24.1 概论 Shell Script 是一个类似 MS Windows 中 .bat 档的东西,简单的说,Shell Script 就是将一堆 shell 中的指令放在一个文字文件中来执行。因此,为了能写出一个 shell Script,你必须先对 UNIX 指令有初步的认识。身为一个 UNIX 系统的管理者,一定要会使用 shell script 来使管理工作更加容易。 一般我们会将 Shell Script 的扩展名命名为 .sh,但并非一定要这么做,这样做只是为了要让我们更容易管理这些档案。在介绍如何 Shell Script 的内容之前,我们先来看如何写出一个 Shell Script 并执行它。假设我们要写一个名为 test.sh 的 Shell Script,首先用你习惯使用的文字编辑软件来开一个文件名为 test.sh 内容如下:
第一行是必需的,用来定义你要使用的 shell。这里我们定义要使用的是 Bourne Shell,其所在路径是 /bin/sh。在 UNIX 系统中有许多不同的 Shell 可以使用,而每个 Shell 的特性及用法都有些许的不同。因此,在写 Shell Script 时,我们会针对 Bourne Shell (sh) 来写,因为 sh 是所有 UNIX 系统中都会有的 Shell。就算你执行 Shell Script 时的环境不是使用 sh,只要加上第一行 #!/bin/sh 就可以在执行此 Shell Script 时使用 sh。而第二行的 echo 代表列出一个字符串,我们常使用它来输出信息。将 test.sh 存盘后,我们就可以用下列其中一种方式执行它: 1. 转向输入 $ sh < test.sh 2. 如果要输入参数的话,第一种方式便不适用,可以改用这种方法。<arguments> 就是我们要输入的参数,在上面的 test.sh 中并不需要输入参数: $ sh test.sh <arguments> 3.你也可以改变 test.sh 的权限,将它变成可以独立执行的档案,这样就可以只打 test.sh 来执行它: $ chmod a+x test.sh $ ./test.sh 在 Shell Script 中,你们可以使用 # 为批注,在 # 后面的字符串都将被视为批注而被式忽略。而分号 ; 则代表新的一行,例如打 ls;ls -d 代表二个指令。另外,我们可以使用变量、流程控制、甚至是副函式来使程序更加灵活。以下的各章节我们会详细加以说明。 24.2 变量的使用 24.2.1 变量的使用 我们知道 Shell Script 是使用一堆指令拼凑而成,为了方便说明及练习起见,我们不使用编辑档案的方式来执行,而改以在命令列中打我们要的指令。首先,先打 sh 来进入 Bourne Shell。 % sh $ 在打了 sh 之后,会进入 Bourne Shell,其一般使用者的提示字符为 $。以下各指令开头的 $ 表示提示字符,而 $ 之后的粗体字才是我们输入的字符串。 在 Shell Script 中,所有的变量都视为字符串,因此并不需要在定义变量前先定义变量类型。在 Shell 中定义和使用变量时有些许的差异。例如,我们定义一个变量 color 并令它的值为 red,接着使用 echo 来印出变量 color 的值: $ color=red $ echo $color red 在这里,以 color=red 来定义变量 color 的值为 red,并以 echo $color 来印出 color 这一个变数。在定义变量时,不必加 $,但是在使用它时,必须加上 $。请注意,在等号的二边不可以有空白,否则将出现错误 ,系统会误以为你要执行一个指令。 我们再介绍一个范例: $ docpath=/home/td/src/doc $ echo $docpath /home/td/src/doc $ ls $docpath abc.txt abc2.txt semmt.doc $ ls $docpaht/*.txt abc.txt abc2.txt 这里我们定义了变量 docpath 的值为 /home/td/src/doc,并印出它。接着我们使用 ls 这个指令来印出变量 docpath 目录中所有档案。再以 ls $docpath/*.txt 来印出 /home/td/src/doc/ 目录下所有扩展名为 .txt 的档案。 我们再来看一个例子,说明如何使用变量来定义变量: $ tmppath=/tmp $ tmpfile=$tmppath/abc.txt $ echo $tmpfile /tmp/abc.txt 另外,我们也可以使用指令输出成为变量,请注意这里使用的二个 ` 是位于键盘左上角的 ` ,在 shell script 中,使用 ` 包起来的代表执行该指令: $ now=`date` $ echo $now Mon Jan 14 09:30:14 CST 2002 如果在变量之后有其它字符串时,要使用下列方式来使用变量: $ light=dark $ echo ${light}blue darkblue $ echo "$light"blue darkblue 这里双引号中的字将会被程序解读,如果是使用单引号将直接印出 $light 而非 dark。 经由上面几个简单的例子,相信您对变量的使用已有初步的认识。另外有一些我们必须注意的事情: $ color=blue $ echo $color blue $ echo "$color" blue $ echo '$color' $color $ echo /$color $color $ echo one two three one two three $ echo "one two three" one two three 我们可以看到上面各个执行结果不大相同。在 Shell Script 中,双引号 " 内容中的特殊字符不会被忽略,而单引号中的所有特殊字符将被忽略。另外,/ 之后的一个字符将被视为普通字符串。 如果您希望使用者能在程序执行到一半时输入一个变量的值,您可以使用 read 这个指令。请看以下的范例:
由于 echo 指令内定会自动换行,所以我们使用 printf 这个指令来输出字符串。我们将上述内容存成档案 input.sh,接着使用下列指令来执行: $ sh input.sh Please input your name:Alex Your name is Alex 您可以看到变量 Name 已被设为您所输入的字符串了。 24.2.2 程序会自动定义的变量 在执行 Shell Script 时,程序会自动产生一些变量:
以下我们举几个例子来说明: $ ls -d /home /home $ echo $? 0 $ ls /home/aaa/bb/ccc /home/aaa/bb/cc: No such file or directory $ echo $? 2 $ echo $? 0 上面例子中的第一行是 ls,我们可以看到存在一个目录 /home,接者 echo $? 时,出现 0 表示上一次的命令正常结束。接着我们 ls 一个不存在的目录,再看 $? 这个变量变成 2,表示上一次执行离开的结果不正常。最后一个 echo $? 所得到的结果是 0,因为上一次执行 echo 正常显示 2。 如果写一个文件名为 abc.sh,内容如下:
接着以下列指令来执行该档案: $ chmod a+x abc.sh $ ./abc.sh a "b c d" e f 4:a b c d e f a b c d e f 上面最后二行即为执行结果。我们可以看到 $# 即为参数的个数,而 $1, $2, $3...分别代表了输入的参数 "a", "b c d", "e", "f",而最后的 $@ 则是所有参数。 24.2.3 系统内定的标准变量 你可以使用 set 这个指令来看目前系统中内定了哪些参数。一般而言会有 $HOME, $SHELL, $USER, $PATH 等。 $ echo $HOME /home/jack $ echo $PATH /usr/bin:/usr/sbin:/bin 24.2.4 空变量的处理 如果程序执行时,有一个变量的值尚未被给定,你可以利用下列方式来设定对于这种情形提出警告: $ echo $number one one $ set -u $ echo $number one sh: ERROR: number: Parameter not set 在 set -u 之后,如果变量尚未设定,则会提出警告。你也可以利用下列的方式来处理一些空变量及变量的代换:
我们以下面的例子来说明: $ echo $name Wang Wang $ echo ${name:-Jack} Wang Jack Wang $ echo $name Wang Wang 上面的例子中,变数 $name 并未被取代,而下面的例子中,$name 将被取代: $ echo $name Wang Wang $ echo ${name:=Jack} Wang Jack Wang $ echo $name Wang Jack Wang 24.3 运算符号 24.3.1 四则运算 在 shell 中的四则运算必须使用 expr 这个指令来辅助。因为这是一个指令,所以如果要将结果指定给变量,必须使用 ` 包起来。请注意,在 + - * / 的二边都有空白,如果没有空白将产生错误: $ expr 5 -2 3 $ sum=`expr 5 + 10` $ echo $sum 15 $ sum=`expr $sum / 3` $ echo $sum 5 还有一个要特别注意的是乘号 * 在用 expr 运算时,不可只写 *。因为 * 有其它意义,所以要使用 /* 来代表。另外,也可以用 % 来求余数。 $ count=`expr 5 /* 3` $ echo $count $ echo `expr $count % 3` 5 我们再列出更多使用 expr 指令的方式,下列表中为可以放在指令 expr 之后的表达式。有的符号有特殊意义,必须以 / 将它的特殊意义去除,例如 /*,否则必须用单引号将它括起来,如 '*':
我们针对比较复杂的文字处理部份再加以举例: $ tty ttyp0 $ expr `tty` : ".*/(../)/$" p0 $ expr `tty` : '.*/(../)$' p0 上面执行 tty 的结果是 ttyp0,而在 expr 中,在 : 右侧的表达式中,先找 .* 表示0个或一个以上任何字符,传回之后在结尾 ($) 时的二个字符 /(../)。在第一个 expr 的式子中,因为使用双引号,所以在 $ 之前要用一个 / 来去除 $ 的特殊意义,而第二个 expr 是使用单引号,在单引号内的字都失去了特殊意义,所以在 $ 之前不必加 /。 除了使用 expr 外,我们还可以使用下列这种特殊语法: $ a=10 $ b=5 $ c=$((${a}+${b})) $ echo $c 15 $ c=$((${a}*${b})) $ echo $c 50 我们可以使用 $(()) 将表达式放在括号中,即可达到运算的功能。 24.3.2 简单的条件判断 最简单的条件判断是以 && 及 || 这二个符号来表示。 $ ls /home && echo found found $ ls /dev/aaaa && echo found ls: /dev/aaaa: No such file or directory $ ls -d /home || echo not found /home $ ls /dev/aaaa && echo not found ls: /dev/aaaa: No such file or directory
24.3.3 以 test 来比较字符串及数字 我们说过 Shell Script 是一堆指令的组合,所以在比较字符串及数字时一样是经由系统指令来达成。这里我们使用 test 及 [ 来做运算,运算所传回的结果是真 (true) 或假 ( false)。我们可以将它应用在条件判断上。test 和 [ 都是一个指令,我们可以使用 test 并在其后加上下表中的参数来判断真假。或者也可以使用 [ 表达式 ] 来替代 test,要注意的是 [ ] 中的空白间隔。
我们举例来说明: $ test 5 -eq 5 && echo true true $ test abc!=cde && echo true ture $ [ 6 -lt 10 ] && echo true ture $ pwd /home $ echo $HOME /home/jack $ [ $HOME = `pwd` ] || echo Not home now Not home now 24.3.4 以 test 来处理档案 我们也可以使用 test 及 [ 来判断一个档案的类型。下表中为其参数:
我们举例来说明: $ [ -d /bin ] && echo /bin is a directory /bin is a directory $ test -r /etc/motd && echo /etc/motd is readable /etc/motd is readable 第一个指令测试 /bin 是否存在,而且是一个目录,如果是则执行 echo 传回一个字符串。第二个指令是测试 /etc/motd 是否可以被读取,如果是则执行 echo 传回一个字符串。 24.4 内建指令 在 Shell 中有一些内建的指令,这些内建的指令如流程控制及 cd 等指令是 Shell 中的必备元素。另外还有一些为了提高执行效率的指令,如 test、echo 等。有的内建指令在系统中也有同样名称不同版本的相同指令,但是如 test、echo 等在执行时会伪装成是在 /bin 中的指令。 在写 shell script 时,要注意指令是否存在。下列即为常见的内建指令:
24.5 流程控制 24.5.1 if 的条件判断 基本语法:
范例一:
说明:上面这一个程序是检查 /etc/motd 这个档案是否可以读,如果可以则印出该档案,否则印出档案不可读。 范例二: $ ee test.sh
$ chmod a+x test.sh $ ./test.sh 3 3 is between 5 and 0. 说明:这里我们建立一个档名为 test.sh 的档案,以指令 cat test.sh 来看它的内容。接着执行 ./test.sh 3,表示输入一个参数 3。test.sh 档案的内容表示依输入的参数判断参数大于 5 或介于 5 和 0 的中间,或者是小于 0。 24.5.2 while 及 until 循环 基本语法:
范例一:
说明:首先令变量 i=1,接着在循环中当 i 小于等于 5 时就印出 i 的值,每印一次 i 就加 1。直到 i 大于 5 才停止。 范例二:
说明:首先令变量 i=1,接着循环会判断,一直执行到 i 大于 5 才停止。每跑一次循环就印出 i 的值,每印一次 i 就加 1。注意 while 和 until 的判断式中,一个是 -le ,一个是 -gt。 24.5.3 for 循环 基本语法:
范例一: $ ee color1.sh
$ chmod a+x color1.sh $ ./color1.sh blue red green 说明:这个档案 color1.sh 中,会在每一次循环中将关键词 in 后面的字符串分配给变量 color,然后印出变量 color。关键词 in 让我们可以依序设定一些值并指派给变量,然而,我们也可以不使用关键词 in。如果没有关键词 in ,程序会自动读取输入的参数,并依序指派给 for 之后的变量。请看范例二。 范例二: $ ee color2.sh
$ chmod a+x color2.sh $ ./color2.sh black green yellow black green yellow 说明:在 color2.sh 这个档中,for 循环没有使用 in 这个关键词。但我们在执行它时输入三个参数,循环会自动将输入的参数指派给 for 之后的变量 color,并印出它。 24.5.4 case 判断 基本语法:
范例: $ ee num.sh
$ chmod a+x num.sh $ ./num.sh 3 8 a 3 is between 0~3 8 is 8 or 9 a is not on my list 说明:这个程序是用来判断输入的参数大小。for 循环会将每一个输入的参数指定给变量 num,而在 case 中,判断变量 num 的内容符合哪一个条件,同一个条件中的每个字用 | 分开。如果未符上面的条件则一定会符合最后一个条件 * 。每一个要执行的 list 是以 ;; 做结尾,如果有多行 list,只要在最后一行加上一个 ;; 即可。 24.6 函式的运用 在 Shell Script 中也可以使用函式 (function) 来使用程序模块化。 基本语法:
函式有几个要注意的地方:
范例: $ ee test.sh
$ chmod a+x test.sh $ ./test.sh err.log 说明: 这个程序中有二个函式:errexit 及 ok。第一行定义要将 log 档存在传给这个 Shell Script 的第一个参数。接着是二个函式,之后印出一行字,echo -n 表示印出字后游标不换行。然后再执行 ok 这个函式,如果 ok 函式执行成功则再执行 errexit 函式,并传给 errexit 函式一个字符串,最后再印出一个字符串。 在 ok 函式中,使用 read 指令来读入一个参数并指派给变数 ans。接着判断使用者输入的值是否为 Y 或 y,如果是则传回 1 代表没有成功执行,如果不是则传回 0 代表成功执行函式 ok。 如果 ok 函式传回 1 便不会执行 errexit 函式。如果是 0 则在 errexit 函式中,会先印出要传给 errexit 的参数 " Testing the errexit function",并记录在指定的档案中。
|
Top | Ths file was last modified: 2005 December 27 23:32:58. |