`
wangminshe89
  • 浏览: 669321 次
文章分类
社区版块
存档分类
最新评论

Bash 实例,第 2 部分

 
阅读更多

Bash 实例,第 2 部分
更多的 bash 基本编程

Daniel Robbins
总裁兼 CEO,Gentoo Technologies, Inc.
2000 年 4 月

<!-- END title and author lines --><!-- Begin Table of Contents --><!-- Begin Table of Contents -->
目录:
接收自变量
Bash 编程结构
方便的条件语句
字符串比较说明
循环结构
Shell 算术
更多的循环结构
Case 语句
函数与名称空间
名称空间
结束
参考资料
关于作者


<!-- End Table of Contents --><!-- Paste your formatted paper here --><!-- ABSTRACT -->
在前一篇 bash 的介绍性文章中,Daniel Robbins 为您讲解了脚本语言的一些基本元素和使用 bash 的原因。在本文(即第二部分)中,Daniel 继续前一篇的内容,并讲解条件 (if-then) 语句、循环和更多的 bash 基本结构。
<!-- END ABSTRACT --><!-- BODY -->

我们先看一下处理命令行自变量的简单技巧,然后再看看 bash 基本编程结构。

接收自变量
介绍性文章中的样本程序中,我们使用环境变量 "$1" 来引用第一个命令行自变量。类似地,可以使用 "$2"、"$3" 等来引用传递给脚本的第二和第三个自变量。这里有一个例子:

 #!/usr/bin/env 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 $#


除以下两个细节之外,此例无需说明。第一,"$0" 将扩展成从命令行调用的脚本名称,"$#" 将扩展成传递给脚本的自变量数目。试验以上脚本,通过传递不同类型的命令行自变量来了解其工作原理。

有时需要一次引用所有命令行自变量。针对这种用途,bash 实现了变量 "$@",它扩展成所有用空格分开的命令行参数。在本文稍后的 "for" 循环部分中,您将看到使用该变量的例子。

Bash 编程结构
如果您曾用过如 C、Pascal、Python 或 Perl 那样的过程语言编程,则一定熟悉 "if" 语句和 "for" 循环那样的标准编程结构。对于这些标准结构的大多数,Bash 有自己的版本。在下几节中,将介绍几种 bash 结构,并演示这些结构和您已经熟悉的其它编程语言中结构的差异。如果以前编程不多,也不必担心。我提供了足够的信息和示例,使您可以跟上本文的进度。

方便的条件语句
如果您曾用 C 编写过与文件相关的代码,则应该知道:要比较特定文件是否比另一个文件新需要大量工作。那是因为 C 没有任何内置语法来进行这种比较,必须使用两个 stat() 调用和两个 stat 结构来进行手工比较。相反,bash 内置了标准文件比较运算符,因此,确定“/tmp/myfile 是否可读”与查看“$myvar 是否大于 4”一样容易。

下表列出最常用的 bash 比较运算符。同时还有如何正确使用每一选项的示例。示例要跟在 "if" 之后。例如:

 if [ -z "$myvar" ]
then 
    echo "myvar is not defined"
fi


运算符 描述 示例
文件比较运算符
-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 如果 filename1filename2 新,则为真 [ /tmp/install/etc/services -nt /etc/services ]
filename1 -ot filename2 如果 filename1filename2 旧,则为真 [ /boot/bzImage -ot arch/i386/boot/bzImage ]
字符串比较运算符 (请注意引号的使用,这是防止空格扰乱代码的好方法)
-z string 如果 string 长度为零,则为真 [ -z "$myvar" ]
-n string 如果 string 长度非零,则为真 [ -n "$myvar" ]
string1 = string2 如果 string1string2 相同,则为真 [ "$myvar" = "one two three" ]
string1 != string2 如果 string1string2 不同,则为真 [ "$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"
好了,已经讲了条件语句,下面该探索 bash 循环结构了。我们将从标准的 "for" 循环开始。这里有一个简单的例子:

 
 #!/usr/bin/env bash
 
 for x in one two three four
 do
     echo number $x
 done
 
 输出:
 
 number one
 number two 
 number three 
 number four


发生了什么?"for" 循环中的 "for x" 部分定义了一个名为 "$x" 的新环境变量(也称为循环控制变量),它的值被依次设置为 "one"、"two"、"three" 和 "four"。每一次赋值之后,执行一次循环体("do" 和 "done" 之间的代码)。在循环体内,象其它环境变量一样,使用标准的变量扩展语法来引用循环控制变量 "$x"。还要注意,"for" 循环总是接收 "in" 语句之后的某种类型的字列表。在本例中,指定了四个英语单词,但是字列表也可以引用磁盘上的文件,甚至文件通配符。看看下面的例子,该例演示如何使用标准 shell 通配符:

 #!/usr/bin/env 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 替换。一旦进入循环,根据 myfile 是否为目录,"-d" 条件运算符用来执行两个不同操作。如果是目录,则将 "(dir)" 附加到输出行。

还可以在字列表中使用多个通配符、甚至是环境变量:

 
 for x in /etc/r??? /var/lo* /home/drobbins/mystuff/* /tmp/${MYPATH}/*
 do
     cp $x /mnt/mydir
 done


Bash 将在所有正确位置上执行通配符和环境变量扩展,并可能创建一个非常长的字列表。

虽然所有通配符扩展示例使用了绝对路径,但也可以使用相对路径,如下所示:

 
 for x in ../* mystuff/*
 do
     echo $x is a silly file
 done


在上例中,bash 相对于当前工作目录执行通配符扩展,就象在命令行中使用相对路径一样。研究一下通配符扩展。您将注意到,如果在通配符中使用绝对路径,bash 将通配符扩展成一个绝对路径列表。否则,bash 将在后面的字列表中使用相对路径。如果只引用当前工作目录中的文件(例如,如果输入 "for x in *"),则产生的文件列表将没有路径信息的前缀。请记住,可以使用 "basename" 可执行程序来除去前面的路径信息,如下所示:

 
 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


您已经熟悉如何执行数学操作,现在该介绍其它两种 bash 循环结构 "while" 和 "until" 了。

更多的循环结构:"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 中,甚至可以定义与其它过程语言(如 Pascal 和 C)类似的函数。在 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
 }




我们在上面定义了一个名为 "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
 ....  




如您所见,可以使用与引用命令行自变量同样的机制来在函数定义内部引用自变量。另外,将把 "$#" 宏扩展成包含自变量的数目。唯一可能不完全相同的是变量 "$0",它将扩展成字符串 "bash"(如果从 shell 交互运行函数)或调用函数的脚本名称。

名称空间
经常需要在函数中创建环境变量。虽然有可能,但是还有一个技术细节应该了解。在大多数编译语言(如 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"。

结束语
我们已经学习了最基本的 bash 功能,现在要看一下如何基于 bash 开发整个应用程序。下一部分正要讲到。再见!

参考资料



关于作者
Daniel Robbins 居住在新墨西哥州的 Albuquerque。他是 Gentoo 项目的总设计师,Gentoo Technologies, Inc. 的 CEO,Linux Advanced Multimedia Project (LAMP) 的顾问,Macmillan 书籍 Caldera OpenLinux UnleashedSuSE Linux UnleashedSamba Unleashed 的作者。Daniel 自小学二年级起就与计算机结下不解之缘,那时他首先接触的是 Logo 程序语言,并沉溺于 Pac-Man 游戏中。这也许就是他至今仍担任 SONY Electronic Publishing/Psygnosis 首席图形设计师的原因所在。Daniel 喜欢与妻子 Mary 一起共渡时光,他们的孩子将在今年春天诞生。可通过 drobbins@gentoo.org 与 Daniel Robbins 取得联系。

<!-- END BODY --><!-- End paper -->
分享到:
评论

相关推荐

    BASH中文手册加实例.pdf

    Again Shell),几乎所有的Linux 和绝大部分的UNIX 都可以使用Bash。作为系统与用户之间 的交互接口,shell 几乎是你在UNIX 工作平台上最亲密的朋友,因此,学好shell,是学习 Linux/UNIX 的的开始,并且它会始终伴随...

    Linux高级bash编程

    第一部分. 热身 1. 为什么使用shell编程 2. 带着一个Sha-Bang出发(Sha-Bang指的是#!) 2.1. 调用一个脚本 2.2. 初步的练习 第二部分. 基本 3. 特殊字符 4. 变量和参数的介绍 4.1. 变量替换 4.2. 变量...

    Advanced Bash-Scripting Guide <>

    第一部分. 热身 1. 为什么使用shell 编程 2. 带着一个Sha-Bang 出发(Sha-Bang 指的是#!) 2.1. 调用一个脚本 2.2. 初步的练习 第二部分. 基本 3. 特殊字符 4. 变量和参数的介绍 4.1. 变量替换 4.2. 变量赋值 4.3. ...

    经典书籍:高级Bash脚本编程指南

    高级Bash脚本编程指南 这本书假定你没有任何关于脚本或一般程序的编程知识, 但是如果你具备相关的知识, 那么你将很容易就能够达到中高级的水平. . . 所有这些只是UNIX®浩瀚知识的一小部分. 你可以把本书作为教材, ...

    Shell_经典实例

    Shell_经典实例,UNIX/Linux最重要的软件之一就是shell,目前最流行的shell被称为Bash(Bourne Again Shell),几乎所有的Linux和绝大部分的UNIX都可以使用Bash。作为系统与用户之间的交互接口,shell几乎是你在UNIX...

    mysqldumpx:bash脚本的集合简化了MySQL数据库的部分备份

    MySQLDumpX bash脚本的集合简化了MySQL数据库的部分备份。介绍随着Web应用程序变得越来越复杂,其数据库中表的数量也在增加。 除了内容和配置之外,许多当前的Web应用程序还将临时数据或聚合数据存储到数据库中。 ...

    shell基础编程实例

    大量的shell实例,前面都是非常之基础的,例子肯定够你需求,后面的相对加深,是对前面的实际应用,有300来页,是我在本网找不到而在网上找来的资源,下面只给出第一页例子 Example 2-1 清除:清除/var/log下的log...

    shell编程及实例

    UNIX/Linux最重要的软件之一就是shell, 目前最流行的shell被称为Bash(Bourne Again Shell), 几乎所有的 Linux和绝大部分的 UNIX 都可以使用 Bash。下面介绍了shell编程及实例。

    Bash 脚本实现每次登录到 Shell 时可以查看 Linux 系统信息

    以前我们出于不同的目的需要写很多个 bash 脚本。 现在我们写一个新的 shell 脚本,在每次登录到 shell 时显示需要的系统信息。 这个j脚本有 6 部分,细节如下: 通用系统信息 CPU/内存当前使用情况 硬盘使用率...

    Linux编程入门(基础+实例)

    这本被人称为abs的书,这本书介绍了bash大量的细节和广阔的范围,我遇到的绝大部分的技术问题 --无论是我忘记的或是以前没有发现的--都可以在这本书里找到答案。这本使用大量的例子详细地介绍 了Bash的语法,各种...

    aws-ec2-ebs-snapshot-check-bash:Amazon Web Services (AWS) 的 EBS 快照验证检查 (Bash)。 由 Casey Labs 和 Bleeding Edge Solutions 创建

    aws-ec2-ebs-snapshot-check-bash ####Amazon Web Services EBS 快照验证 - Bash 脚本由[Casey Labs Inc.] ( )和[Bleeding Edge Solutions] ( )撰写,联系我们以满足您的所有亚马逊网络服务咨询需求! =============...

    入门学习Linux常用必会60个命令实例详解doc/txt

    hda1中的“1”代表hda的第一个硬盘分区 (partition),hda2代表hda的第二主分区,第一个逻辑分区从hda5开始,依此类推。此外,可以直接检查 /var/log/messages文件,在该文件中可以找到计算机开机后系统已辨认出来的...

    remove-snap:一个bash脚本,用于从ubuntu中删除快照

    我将其放在github中,以便在打包程序构建的一部分中可以在aws ec2 ubuntu实例上调用它,而不必将脚本的内容复制到各处并最终丢失它。 用法bash -c " $( curl -fsSL ...

    边干边学Linux__第二版_doc格式

    第一部分 Linux 操作环境 第1章 Linux基础 1.1 登录Linux系统 1.2 Linx的shell 1.3 shell的一些基本命令 第2章 文本编辑 2.1 vi文本编辑器 2.2 emacs文本编辑器 第3章 Linux文件系统操作 3.1 文件类型 3.2 文件系统...

    Shell脚本专家指南

    第2部分 系统交互与高级技术 第11章 shell中的数学 11.1 expr 11.2 bc 11.3 de 第12章 cron 12.1 crontab条目 12.2 环境问题 12.3 输出重定向 第13章 自链接脚本 第14章 对并行进程的数量控制 14.1 用ksh实现并行...

    HydraPlay:基于快照和多个Mopidy实例的多房间音频播放器UI

    HydraPlay是一种多房间音频播放器,可以控制由快照安装控制的多个Mopidy实例。 该项目是使用版本7.3.6生成的。 这个项目的灵感来自我在看到的一个项目,我在上找到了一些Authors(Ryan Detzel)代码。 我在项目中...

    Shell脚本专家英文版

    全书分为3个部分:脚本技术基础、系统交互和高级技术、有用的脚本实例。主要内容包括如何使小到中型的系统管理任务自动化,分析系统数据并编辑配置文件,使用bash和ksh等编写Linux、Unix和OS X应用程序的脚本文件等...

    [Shell脚本专家指南]高清300dpi

    全书分为3个部分:脚本技术基础、系统交互和高级技术、有用的脚本实例。主要内容包括如何使小到中型的系统管理任务自动化,分析系统数据并编辑配置文件,使用bash和ksh等编写Linux、Unix和OS X应用程序的脚本文件等...

Global site tag (gtag.js) - Google Analytics