在前一篇 bash 的介绍性文章中,Daniel Robbins 为您讲解了脚本语言的一些基本元素和使用 bash 的原因。在本文(即第二部分)中,Daniel 继续前一篇的内容,并讲解条件 (if-then) 语句、循环和更多的 bash 基本结构。

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

接收自变量

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

 #!/usr/bin/env bashecho name of script is $0echo first argument is $1echo second argument is $2echo seventeenth argument is $17echo 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"
fiif [ "$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 bashfor x in one two three fourdoecho number $xdone输出:number onenumber two number three number four

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

 #!/usr/bin/env bashfor myfile in /etc/r*doif [ -d "$myfile" ]
then echo "$myfile (dir)"
elseecho "$myfile"
fidone输出:/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}/*docp $x /mnt/mydirdone

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

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

 for x in ../* mystuff/*doecho $x is a silly filedone

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

 for x in /var/log/*doecho `basename $x` is a file living in /var/logdone

当然,在脚本的命令行自变量上执行循环通常很方便。这里有一个如何使用本文开始提到的 "$@" 变量的例子:

 #!/usr/bin/env bashfor thing in "$@"doecho you typed ${thing}.done输出:$ allargs hello there you sillyyou 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 $myvar57

回页首

更多的循环结构:"while" 和 "until"

只要特定条件为真,"while" 语句就会执行,其格式如下:

 while [ condition ]dostatementsdone

通常使用 "While" 语句来循环一定次数,比如,下例将循环 10 次:

 myvar=0while [ $myvar -ne 10 ]doecho $myvarmyvar=$(( $myvar + 1 ))done

可以看到,上例使用了算术表达式来使条件最终为假,并导致循环终止。

"Until" 语句提供了与 "while" 语句相反的功能:只要特定条件为 ,它们就重复。下面是一个与前面的 "while" 循环具有同等功能的 "until" 循环:

 myvar=0until [ $myvar -eq 10 ]doecho $myvarmyvar=$(( $myvar + 1 ))done

回页首

Case 语句

Case 语句是另一种便利的条件结构。这里有一个示例片段:

 case "${x##*.}" ingz)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 $1elif [ ${1##*.} = gz ]
then echo "(gzip-compressed tar)"tar tzvf $1elif [ ${1##*.} = bz2 ]
then echo "(bzip2-compressed tar)"cat $1 | bzip2 -d | tar tvf -
fi}
另一种情况

可以使用 "case" 语句来编写上面的代码。您知道如何编写吗?

我们在上面定义了一个名为 "tarview" 的函数,它接收一个自变量,即某种类型的 tar 文件。在执行该函数时,它确定自变量是哪种 tar 文件类型(未压缩的、gzip 压缩的或 bzip2 压缩的),打印一行信息性消息,然后显示 tar 文件的内容。应该如下调用上面的函数(在输入、粘贴或找到该函数后,从脚本或命令行调用它):

 $ tarview shorten.tar.gzDisplaying 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....
交互地使用它们

别忘了,可以将函数(如上面的函数)放在 ~/.bashrc 或 ~/.bash_profile 中,以便在 bash 中随时使用它们。

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


回页首

名称空间

经常需要在函数中创建环境变量。虽然有可能,但是还有一个技术细节应该了解。在大多数编译语言(如 C)中,当在函数内部创建变量时,变量被放置在单独的局部名称空间中。因此,如果在 C 中定义一个名为 myfunction 的函数,并在该函数中定义一个名为 "x" 的自变量,则任何名为 "x" 的全局变量(函数之外的变量)将不受它的印象,从而消除了负作用。

在 C 中是这样,但在 bash 中却不是。在 bash 中,每当在函数内部创建环境变量,就将其添加到 全局名称空间。这意味着,该变量将重写函数之外的全局变量,并在函数退出之后继续存在:

 #!/usr/bin/env bashmyvar="hello"myfunc() {myvar="one two three"for x in $myvardoecho $xdone}myfuncecho $myvar $x

运行此脚本时,它将输出 "one two three three",这显示了在函数中定义的 "$myvar" 如何影响全局变量 "$myvar",以及循环控制变量 "$x" 如何在函数退出之后继续存在(如果 "$x" 全局变量存在,也将受到影响)。

在这个简单的例子中,很容易找到该错误,并通过使用其它变量名来改正错误。但这不是正确的方法,解决此问题的最好方法是通过使用 "local" 命令,在一开始就预防影响全局变量的可能性。当使用 "local" 在函数内部创建变量时,将把它们放在 局部名称空间中,并且不会影响任何全局变量。这里演示了如何实现上述代码,以便不重写全局变量:

 #!/usr/bin/env bashmyvar="hello"myfunc() {local xlocal myvar="one two three"for x in $myvardoecho $xdone}myfuncecho $myvar $x

此函数将输出 "hello" -- 不重写全局变量 "$myvar","$x" 在 myfunc 之外不继续存在。在函数的第一行,我们创建了以后要使用的局部变量 x,而在第二个例子 (local myvar="one two three"") 中,我们创建了局部变量 myvar, 同时 为其赋值。在将循环控制变量定义为局部变量时,使用第一种形式很方便,因为不允许说:"for local x in $myvar"。此函数不影响任何全局变量,鼓励您用这种方式设计所有的函数。只有在明确希望要修改全局变量时,才 应该使用 "local"。


回页首

结束语

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

参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文.
  • 阅读介绍性的 bash 文章, developerWorks上的“ Bash 实例,第 1 部分”
  • 访问 GNU's bash 主页
  • 查看 bash online reference manual

关于作者

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 取得联系。

转载于:https://www.cnblogs.com/licheng/archive/2008/08/05/1261057.html

Bash 实例,第 2 部分相关推荐

  1. Bash 实例,第 3 部分

    Bash 实例,第 3 部分 来源:http://www.ibm.com/developerworks/cn/linux/shell/bash/bash-3/index.html 探讨 ebuild ...

  2. 从脚本本身获取Bash脚本的源目录

    如何获取其中的目录路径的Bash脚本所在,该脚本里面 ? 例如,假设我要使用Bash脚本作为另一个应用程序的启动器. 我想将工作目录更改为Bash脚本所在的目录,以便可以对该目录中的文件进行操作,如下 ...

  3. Linux下Bash入门学习笔记

    学习好shell编程是很有用的,可以使用shell脚本轻巧地完成有趣的工作. 本文地址:http://www.cnblogs.com/yhLinux/p/4047516.html 1. Bash实例, ...

  4. Linux bash总结(一) 基础部分(适合初学者学习和非初学者参考)

    第一部分 bash简介     -- 对bash进行简要介绍 第二部分 bash示例和书写流程     -- 以一个简单的bash为例,说明书写.执行bash的流程 第三部分 bash基础语法     ...

  5. linux bash函数里面调用命令行,Linux-在gnome-terminal -x中运行bash函数

    您可以将其与export -f一起使用,就像@kojiro的上面的注释中指出的那样. # Define function. my_func() { // Do cool stuff } # Expor ...

  6. Linux 技巧: Bash 参数和参数扩展 (Shell)

    现在,很多 Linux® 和 UNIX® 系统上都有 bash shell,它是 Linux 上常见的默认 shell.通过本文,您将了解到如何在 bash 脚本中处理参数和选项,以及如何使用 she ...

  7. Linux 技巧: Bash 测试和比较函数 (shell编程)

    Bash shell 在当今的许多 Linux® 和 UNIX® 系统上都可使用,是 Linux 上常见的默认 shell.Bash 包含强大的编程功能,其中包括丰富的可测试文件类型和属性的函数,以及 ...

  8. Linux杀100个进程,在linux bash中杀死一个进程子树

    我正在键入一个小的bash脚本,它应该克隆一个git存储库,签出一个特定的硬编码分支并监听一些新的提交.如果发现新的提交,脚本应该杀死正在运行的'MyApp'实例,执行git pull并最终使用gra ...

  9. Linux中bash文档翻译

    BASH(1)通用命令手册BASH(1) 名称 ​ bash - GNU Bourne-Again SHell 概要 ​ bash [选项] [文件] 版权 ​ Bash是自由软件基金会版权所有(C) ...

最新文章

  1. python损失函数实现_pytorch 实现cross entropy损失函数计算方式
  2. 初三女生学计算机专业,江西中专初三毕业学计算机专业适宜女生吗
  3. 全球所有货币币种汇总
  4. 从小害怕数学的他,却成为了科普数学教育的数学家
  5. 一步步编写操作系统 6 启动bochs
  6. Java基础夺命连环16问
  7. labelimg如何调整框的颜色_PS学习之旅:如何更好调整画面明暗?明度、色阶详细介绍,收藏...
  8. 词嵌入和网络在NLP中贡献
  9. Linux 命令(46)—— read 命令(builtin)
  10. 单分子荧光原位杂交(smFISH)
  11. 网站快速收录-网站快速收录工具下载免费
  12. 华成英-模拟电子技术P9 静态工作点的稳定 笔记
  13. 如果命运是一条孤独的河流,谁会是你的灵魂摆渡人
  14. U盘装系统工具哪个好用?
  15. code405是什么意思_HTTP协议状态码详解(HTTP Status Code)
  16. 开源公告|腾讯代码安全指南开源,涉及C/C++、Go等六门编程语言
  17. ERROR: No Jack server running
  18. 输入多组字符数组c语言,c语言怎样能连续输入多个一维数组
  19. 机房监控系统的主要功能及监控内容!
  20. 艺术饭+!真.艺术饭计划少儿版 ArtFin谈另一种家族资产

热门文章

  1. Mysql数据库的基本概念
  2. 消息队列 策略_太狠了!京东T8架构师建议吃透这40W字消息队列文档,涨薪15K不是梦...
  3. HDLC 和 PPP封装简介
  4. FreeRTOS内核详解(1) —— 临界段保护原理
  5. VMware下安装的Mac OS X如何修改显示分辨率
  6. php no input file specified.,nginx+php出现No input file specified解决办法
  7. hive外部表改为内部表_hive内部表外部表介绍
  8. ccs船级社认证费用多少_亚马逊UL507认证是什么?办理费用是多少?
  9. 哈希运算python实现_一致性哈希算法 python实现
  10. mysql binlog过期策略_MySQL binlog日志优化方案