Bash编程实例 一 通过学习如何使用 bash 脚本语言编程,将使 Linux 的日常交互更有趣和有生产力,同时还可以利用那些已熟悉和喜爱的标准 UNIX 概念(如管道和重定向)。 Bash 编程实例 第一部分 环境变量 在 bash 中定义环境变量的标准方法是: $ myvar='This is my environment variable!' 以上命令定义了一个名为 "myvar" 的环境变量,并包含字符串 "This is my environment variable!"。以上有几点注意事项:第一,在等号 "=" 的两边没有空格,任何空格将导致错误。第二个件要注意的事是:虽然在定义一个字时可以省略引号,但是当定义的环境变量值多于一个字时(包含空格或制表键),引号是必须的。第三,虽然通常可以用双引号来替代单引号,但在上例中,这样做会导致错误。因为使用单引号禁用了称为扩展的 bash 特性,其中,特殊字符和字符系列由值替换。例如,"!" 字符是历史扩展字符,bash 通常将其替换为前面输入的命令。尽管这个类似于宏的功能很便利,但我们现在只想在环境变量后面加上一个简单的感叹号,而不是宏。 另一个例子 $ echo foo$myvarbar bash会困惑,到底扩展$m、$my、$myvar、$myvarbar...在这种情况下要用显式的花括号将它括起。 $ echo foo${myvar}bar 一定要记住:当环境变量没有用空白(空格或制表键)与周围文本分开时,要使用更明确的花括号形式。 当一个环境变量被导出时,它可以自动地由以后运行的任何脚本或可执行程序环境使用。shell脚本可以使用shell的内置环境变量支持“到达”环境变量,而C程序可以使用getenv()函数调用。如下C代码示例: #include #include int main(void) { char *myenvvar=getenv("myvar"); printf("The myvar environment variable is %s ",myenvvar); } 直接执行程序,结果为: The myvar environment variable is (null) 在export myvar后,程序执行结果为: The myvar environment variable is This is my environment variable! 使用unset除去环境变量后,程序执行结果为: The myvar environment variable is (null) 另外,也可以在一行定义并到处环境变量,如: export myvar=abc 截断字符串概述 截断字符串是将初始字符串截断成较小的独立块,它是一般 shell 脚本每天执行的任务之一。 $ basename /usr/local/share/doc/foo/foo.txt foo.txt Basename是一个截断字符串的极简便工具。它的相关命令dirname返回basename丢弃的“另”一个部分路径。 $ dirname /usr/local/share/doc/foo/foo.txt /usr/local/share/doc/foo 命令替换 如何创建一个包含可执行命令结果的环境变量,可以通过如下方法: $ MYDIR=`dirname /usr/local/share/doc/foo/foo.txt` $ echo $MYDIR /usr/local/share/doc/foo 以上程序需要注意的是:在第一行,将要执行的命令以 反引号 括起。不是标准的单引号,而是键盘中通常位于Tab键之上的单引号。(注:单引号中的内容将会被强制显示,也就是说单引号中的命令不会被替换) 除了反引号``外,还可以使用$()来完成同样操作 $ MYDIR=$(dirname /usr/local/share/doc/foo/foo.txt) $ echo $MYDIR /usr/local/share/doc/foo 使用命令替换可以将任何命令或命令管道放在``或$()之间,并将其分配给环境变量。 象专业人员那样截断字符串 有时候我们需要执行更高级的字符串“截断”,如下例子: $ MYVAR=foodforthought.jpg $ echo ${MYVAR##*fo} rthought.jpg $ echo ${MYVAR#*fo} odforthought.jpg 第一个echo,bash取得MYVAR,找到从字符串"foodforthought.jpg" 开始处开始、且匹配通配符 "*fo" 的 最长 子字符串,然后将其从字符串的开始处截去。 第二个echo,bash取得MYVAR,找到从字符串"foodforthought.jpg" 开始处开始、且匹配通配符 "*fo" 的 最短 子字符串,然后将其从字符串的开始处截去。 记忆方法:当搜索最长匹配时,使用 ##(因为 ## 比 # 长)。当搜索最短匹配时,使用 #。如何记住使用"#"字符来从字符串开始部分出去?在美国键盘上,shift-4 是 "$",它是 bash 变量扩展字符。在键盘上,紧靠 "$" 左边的是 "#"。这样,可以看到:"#" 位于 "$" 的“开始处”,因此(根据我们的记忆法),"#" 从字符串的开始处除去字符。同理,使用"%"来从尾部截去字符串: $ MYFOO="chickensoup.tar.gz" $ echo ${MYFOO%%.*} chickensoup $ echo ${MYFOO%.*} chickensoup.tar 如果忘记了应该使用"#" 还是 "%",则看一下键盘上的 3、4 和 5 键,然后猜出来。 还有另一种形式的变量扩展,来选择特定子字符串。 $ EXCLAIM=cowabunga $ echo ${EXCLAIM:0:3} cow $ echo ${EXCLAIM:3:7} abunga 请注意命令替换$()和截断字符串${}的区别 应用字符串截断 下面是一个简单的shell脚本,这个脚本接受一个文件作为自变量,然后打印:该文件是否是一个tar文件。 #!/bin/bash if [ "${1##*.}" = "tar" ] then echo This appears to be a tarball. else echo At first glance, this does not appear to be a tarball. fi 看一下上例使用的"if"语句。语句中使用了一个布尔表达式。在bash中,"="比较运算符检查字符串是否相等。在bash中,所有布尔表达式都用方括号括起。 ($1是传给脚本的第一个命令行自变量,$2是第二个,以此类推。) If语句 if [ condition ] then action fi 只有当condition为真时,该语句才执行操作,否则不执行操作,并继续执行"fi"之后的语句。 if [ condition ] then action elif [ condition2 ] then action2 . . . elif [ condition3 ] then else actionx fi 以上"elif"形式将连续测试每个条件,并执行符合第一个 真 条件的操作。如果没有条件为真,则将执行"else"操作,如果有一个条件为真,则继续执行整个"if,elif,else"语句之后的行。 Bash编程实例 二 在前一篇 bash 的介绍性文章中,我们了解了脚本语言的一些基本元素和使用 bash 的原因。在本文(即第二部分)中,将继续前一篇的内容,并讲解条件 (if-then) 语句、循环和更多的 bash 基本结构。 Bash编程实例 第二部分 接收自变量 看一下如下的例子: #! /bin/bash echo name of script is $ 0 echo first argument is $ 1 echo second argument is $ 2 echo seventeenth argument is $ 17 echo number of arguments is $# Bash中将" $ 0 "扩展成从命令行调用的脚本名称,"$#"被扩展成传递给脚本的自变量数目。(请注意$ 和 0是连起来的) 在Bash编程中有时候需要一次引用 所有 命令行自变量,针对这种用途,bash实现了变量"$@",它被扩展成所有用空格分开的命令行参数。 Bash编程结构 在各种编程语言中出现的"if"语句和"for"循环等标准编程结构,Bash有自己的版本。以下几节,将介绍几种bash结构,并演示这些结构及其和其他编程语言中结构的差异。 方便的条件语句 在C语言中,要比较特定文件是否比另一个文件新必须使用两个stat()调用和两个stat结构来进行手工比较。而在bash中,因为其内置了标准文件比较运算符,因此,确定"/tmp/myfile 是否可读"与查看"$myvar 是否大于 4"一样容易。 看下面的例子: if [ -z "$myvar" ] then echo "myvar is not defined" fi 上例表示如果$myvar没有定义(即$myvar为0),则echo ".........." bash中可使用的比较运算符如下: 运算符 描述 示例 文件比较运算符 -e filename 如果 filename存在,则为真 [ -e /var/log/syslog ] -d filename 如果 filename为目录,则为真 [ -d /tmp/mydir ] -f filename 如果 filename为常规文件,则为真 [ -f /usr/bin/grep ] -L filename 如果 filename为符号链接,则为真 [ -L /usr/bin/grep ] -r filename 如果 filename可读,则为真 [ -r /var/log/syslog ] -w filename 如果 filename可写,则为真 [ -w /var/mytmp.txt ] -x filename 如果 filename可执行,则为真 [ -L /usr/bin/grep ] filename1-nt filename2 如果 filename1比 filename2新,则为真 [ /tmp/install/etc/services -nt /etc/services ] filename1-ot filename2 如果 filename1比 filename2旧,则为真 [ /boot/bzImage -ot arch/i386/boot/bzImage ] 字符串比较运算符 (请注意引号的使用,这是防止空格扰乱代码的好方法) -z string 如果 string长度为零,则为真 [ -z "$myvar" ] -n string 如果 string长度非零,则为真 [ -n "$myvar" ] string1= string2 如果 string1与 string2相同,则为真 [ "$myvar" = "one two three" ] string1!= string2 如果 string1与 string2不同,则为真 [ "$myvar" != "one two three" ] 算术比较运算符 num1-eq num2 等于 [ 3 -eq $mynum ] num1-ne num2 不等于 [ 3 -ne $mynum ] num1-lt num2 小于 [ 3 -lt $mynum ] num1-le num2 小于或等于 [ 3 -le $mynum ] num1-gt num2 大于 [ 3 -gt $mynum ] num1-ge num2 大于或等于 [ 3 -ge $mynum ] 有时,有几种不同方法来进行特定比较。如下: if [ "$myvar" -eq 3 ] then echo "myvar equals 3" fi
if [ "$myvar" = "3" ] then echo "myvar equals 3" fi 上面两个比较执行相同的功能,但是第一个使用算术比较运算符,而第二个使用字符串比较运算符。 字符串比较说明 大多数时候,虽然可以不使用括起字符串和字符串变量的双引号,但这并不是好主意。因为如果环境变量中恰巧有一个空格或制表键,bash 将无法分辨,从而无法正常工作。这里有一个错误的比较示例: if [ $myvar = "foo bar oni" ] then echo "yes" fi
在上例中,如果 myvar 等于 "foo",则代码将按预想工作,不进行打印。但是,如果 myvar 等于 "foo bar oni",则代码将因以下错误失败: [: too many arguments
在这种情况下,"$myvar"(等于 "foo bar oni")中的空格迷惑了 bash。bash 扩展 "$myvar" 之后,代码如下: [ foo bar oni = "foo bar oni" ] 因为环境变量没放在双引号中,所以 bash 认为方括号中的自变量过多。可以用双引号将字符串自变量括起来消除该问题。请记住,如果养成将所有字符串自变量用双引号括起的习惯,将除去很多类似的编程错误。"foo bar oni" 比较 应该写成: if [ "$myvar" = "foo bar oni" ] then echo "yes" fi 在调用环境变量的时候最好使用""将环境变量括起来。(注意:如果想引用环境变量的值,则不要使用单引号,因为单引号会 禁用 变量(和历史)扩展。如在上例中会只接受空格以前的字符) 循环结构:"for" OK,开始我们的for循环了:) 先看一个简单的例子: #! /bin/bash
for x in one two three four do echo number $x done
输出: number one number two number three number four 这个例子中"for x"部分定义了一个名为"$x"的新环境变量(也称为循环控制变量),它的值被依次设置为"one"、"two"、"three"和"four"。每一次赋值之后,执行一次循环体("do"和"done"之间的代码)。在循环体内,象其他环境变量一样,使用标准的变量扩展语法来引用循环控制变量"$x"。还要注意,"for"循环总是接受"in"语句之后的某种类型的字列表。在这个例子中,指定了四个英语字母,但是字列表也可以引用磁盘上的文件,甚至文件通配符。 再看一个使用标准shell通配符的例子: #! /bin/bash
for myfile in /etc/r* do if [ -d "$myfile" ] then echo "$myfile (dir)" else echo "$myfile" fi done