Bash编程实例 二
时间:2005-12-08 来源:sdemon915
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
输出:
/etc/rc.d (dir)
/etc/resolv.conf
/etc/resolv.conf~
/etc/rpc
以上代码列出在 /etc 中每个以 "r" 开头的文件。要做到这点,bash 在执行循环之前首先取得通配符 /etc/r*,然后扩展它,用字符串 /etc/rc.d /etc/resolv.conf /etc/resolv.conf~ /etc/rpc 替换。(也即循环语句被替换成for myfile in /etc/rc.d /etc/resolv.conf /etc/resolv.conf~ /etc/rpc )一旦进入循环,根据 myfile 是否为目录,"-d" 条件运算符用来执行两个不同操作。如果是目录,则将 "(dir)" 附加到输出行。
还可以在字列表中使用多个通配符、甚至是环境变量:
for x in /etc/r--? /var/lo* /home/drobbins/mystuff/* /tmp/${MYPATH}/*
do
cp $x /mnt/mydir
done
虽然所有通配符扩展示例使用了 绝对路径,但也可以使用相对路径,如下所示:
for x in ../* mystuff/*
do
echo $x is a silly file
done
再看个例子:
for x in /var/log/*
do
echo `basename $x` is a file living in /var/log
done
看看如何使用"$@"的:
#!/usr/bin/env bash
for thing in "$@"
do
echo you typed ${thing}.
done
输出:
$ allargs hello there you silly
you typed hello.
you typed there.
you typed you.
you typed silly.
Shell 算术
在学习另一类型的循环结构之前,最好先熟悉如何执行 shell 算术。可以使用 shell 结构来执行简单的整数运算。只需将特定的算术表达式用 "$((" 和 "))" 括起,bash 就可以计算表达式。这里有一些例子:
$ echo $(( 100 / 3 ))
33
$ myvar="56"
$ echo $(( $myvar + 12 ))
68
$ echo $(( $myvar - $myvar ))
0 $ myvar=$(( $myvar + 1 ))
$ echo $myvar
57
更多的循环结构:"while" 和 "until"
只要特定条件为真,"while" 语句就会执行,其格式如下:
while [ condition ]
do
statements
done
通常使用 "While" 语句来循环一定次数,比如,下例将循环 10 次:
myvar=0
while [ $myvar -ne 10 ]
do
echo $myvar
myvar=$(( $myvar + 1 ))
done
可以看到,上例使用了算术表达式来使条件最终为假,并导致循环终止。
"Until" 语句提供了与 "while" 语句相反的功能:只要特定条件为 假 ,它们就重复。下面是一个与前面的 "while" 循环具有同等功能的 "until" 循环:
myvar=0
until [ $myvar -eq 10 ]
do
echo $myvar
myvar=$(( $myvar + 1 ))
done
Case 语句
Case 语句是另一种便利的条件结构。这里有一个示例片段:
case "${x##*.}" in
gz)
gzunpack ${SROOT}/${x}
;;
bz2)
bz2unpack ${SROOT}/${x}
;;
*)
echo "Archive format not recognized."
exit
;;
esac
在上例中,bash 首先扩展 "${x##*.}"。在代码中,"$x" 是文件的名称,"${x##.*}" 除去文件中最后句点后文本之外的所有文本。然后,bash 将产生的字符串与 ")" 左边列出的值做比较。在本例中,"${x##.*}" 先与 "gz" 比较,然后是 "bz2",最后是 "*"。如果 "${x##.*}" 与这些字符串或模式中的任何一个匹配,则执行紧接 ")" 之后的行,直到 ";;" 为止,然后 bash 继续执行结束符 "esac" 之后的行。如果不匹配任何模式或字符串,则不执行任何代码行,在这个特殊的代码片段中,至少要执行一个代码块,因为任何不与 "gz" 或 "bz2" 匹配的字符串都将与 "*" 模式匹配。
函数与名称空间
在bash中也可以定义函数。函数甚至可以使用与脚本接受命令行自变量类似的方式来接受自变量。
tarview() {
echo -n "Displaying contents of $1 "
if [ ${1##*.} = tar ]
then
echo "(uncompressed tar)"
tar tvf $1
elif [ ${1##*.} = gz ]
then
echo "(gzip-compressed tar)"
tar tzvf $1
elif [ ${1##*.} = bz2 ]
then
echo "(bzip2-compressed tar)"
cat $1 | bzip2 -d | tar tvf -
fi
}
(echo -n 不换行)
上面定义了一个名为 "tarview" 的函数,它接收一个自变量,即某种类型的 tar 文件。在执行该函数时,它确定自变量是哪种 tar 文件类型(未压缩的、gzip 压缩的或 bzip2 压缩的),打印一行信息性消息,然后显示 tar 文件的内容。
$ tarview shorten.tar.gz
Displaying contents of shorten.tar.gz (gzip-compressed tar)
drwxr-xr-x ajr/abbot 0 1999-02-27 16:17 shorten-2.3a/
-rw-r--r-- ajr/abbot 1143 1997-09-04 04:06 shorten-2.3a/Makefile
-rw-r--r-- ajr/abbot 1199 1996-02-04 12:24 shorten-2.3a/INSTALL
-rw-r--r-- ajr/abbot 839 1996-05-29 00:19 shorten-2.3a/LICENSE
....
名称空间
经常需要在函数中创建环境变量。虽然有可能,但是还有一个技术细节应该了解。在大多数编译语言(如 C)中,当在函数内部创建变量时,变量被放置在单独的局部名称空间中。因此,如果在 C 中定义一个名为 myfunction 的函数,并在该函数中定义一个名为 "x" 的自变量,则任何名为 "x" 的全局变量(函数之外的变量)将不受它的印象,从而消除了负作用。
在 C 中是这样,但在 bash 中却不是。在 bash 中,每当在函数内部创建环境变量,就将其添加到 全局名称空间。这意味着,该变量将重写函数之外的全局变量,并在函数退出之后继续存在:
#!/usr/bin/env bash
myvar="hello"
myfunc() {
myvar="one two three"
for x in $myvar
do
echo $x
done
}
myfunc
echo $myvar $x
运行此脚本时,它将输出 "one two three three",这显示了在函数中定义的 "$myvar" 如何影响全局变量 "$myvar",以及循环控制变量 "$x" 如何在函数退出之后继续存在(如果 "$x" 全局变量存在,也将受到影响)。
在这个简单的例子中,很容易找到该错误,并通过使用其它变量名来改正错误。但这不是正确的方法,解决此问题的最好方法是通过使用 "local" 命令,在一开始就预防影响全局变量的可能性。当使用 "local" 在函数内部创建变量时,将把它们放在 局部名称空间中,并且不会影响任何全局变量。这里演示了如何实现上述代码,以便不重写全局变量:
#!/usr/bin/env bash
myvar="hello"
myfunc() {
local x
local myvar="one two three"
for x in $myvar
do
echo $x
done
}
myfunc
echo $myvar $x
此函数将输出 "hello" -- 不重写全局变量 "$myvar","$x" 在 myfunc 之外不继续存在。在函数的第一行,我们创建了以后要使用的局部变量 x,而在第二个例子 (local myvar="one two three"") 中,我们创建了局部变量 myvar, 同时 为其赋值。在将循环控制变量定义为局部变量时,使用第一种形式很方便,因为不允许说:"for local x in $myvar"。此函数不影响任何全局变量,鼓励您用这种方式设计所有的函数。只有在明确希望要修改全局变量时,才 不应该使用 "local"。 创建局部环境变量时最好使用"local"。
原文出处:
http://www-128.ibm.com/developerworks/cn/linux/shell/bash/bash-2/index.html