作者:Daniel Robbins 来自:IBM DW中国

咱们先看一下解决命令行自变量的简略技巧,而后再看看 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 等于 "foobar oni",则代码将因以下谬误失败:

 [: too many arguments

在这种状况下,"$myvar"(等于 "foo bar oni")中的空格蛊惑了bash。bash 扩大 "$myvar" 之后,代码如下:

 [ foo bar oni = "foo bar oni" ]

因为环境变量没放在双引号中,所以 bash认为方括号中的自变量过多。能够用双引号将字符串自变量括起来打消该问题。请记住,如果养成将所有字符串自变量用双引号括起的习惯,将除去很多相似的编程谬误。"foobar 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

更多的循环构造:"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 }

另一种状况

能够应用 "case" 语句来编写下面的代码。您晓得如何编写吗?

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

交互地应用它们

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

如您所见,能够应用与援用命令行自变量同样的机制来在函数定义外部援用自变量。另外,将把"$#" 宏扩大成蕴含自变量的数目。惟一可能不完全相同的是变量"$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 threethree",这显示了在函数中定义的 "$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, 同时 为其赋值。在将循环控制变量定义为局部变量时,应用第一种模式很不便,因为不容许说:"forlocal x in$myvar"。此函数不影响任何全局变量,激励您用这种形式设计所有的函数。只有在明确心愿要批改全局变量时,才 应该应用 "local"。

结束语

咱们曾经学习了最根本的 bash 性能,当初要看一下如何基于 bash开发整个应用程序。下一部分正要讲到。再见!

参考资料

  • 您能够参阅本文在 developerWorks 寰球站点上的 英文原文.
  • 浏览介绍性的 bash 文章, developerWorks上的“ Bash 实例,第 1 局部”
  • 拜访 GNU's bash 主页
  • 查看 bash online reference manual

原文:http://www.ibm.com/developerw...