作者:DanielRobbins 来自:IBMDW中国让我们看看处理命令行参数的简单技术,然后看看基本的bash编程结构。接收参数在介绍性文章的示例程序中,我们使用环境变量“$1”来引用第一个命令行参数。同样,“$2”、“$3”等可用于指代传递给脚本的第二个和第三个参数。下面是一个例子:#!/usr/bin/envbashechonameofscriptis$0echofirstargumentis$1echosecondargumentis$2echo第十七参数是$17echonumberofargumentsis$#除了以下两个细节,这个例子不需要解释。首先,“$0”将扩展为从命令行调用的脚本的名称,“$#”将扩展为传递给脚本的参数数量。通过传递不同类型的命令行参数来试验上面的脚本,看看它是如何工作的。有时需要一次引用所有命令行参数。为此,bash实现了变量“$@”,它扩展为所有由空格分隔的命令行参数。您将在本文后面的“for”循环部分看到使用此变量的示例。Bash编程结构如果您使用过过程化语言(如C、Pascal、Python或Perl)进行编程,那么您就会熟悉标准编程结构,例如“if”语句和“for”循环。对于大多数这些标准结构,Bash都有自己的版本。在接下来的几节中,我将介绍几种bash结构,并演示它们与您已经熟悉的其他编程语言的结构有何不同。如果您以前编程不多,请不要担心。我已经提供了足够的信息和示例来让您了解本文的最新信息。方便的条件语句如果您曾经用C语言编写过与文件相关的代码,您就会知道:比较一个特定文件是否比另一个文件新是一项繁重的工作。那是因为C没有任何内置语法来进行这种比较,您必须使用两个stat()调用和两个stat结构手动完成。相反,bash具有内置的标准文件比较运算符,因此确定“/tmp/myfile是否可读”就像查看“$myvar是否大于4”一样容易。下表列出了最常用的bash比较运算符。还有如何正确使用每个选项的示例。跟在“如果”后面的例子。示例:if[-z"$myvar"]thenecho"myvarisnotdefined"fi运算符描述示例文件比较运算符-e文件名如果文件名存在则为真[-e/var/log/syslog]-d文件名如果文件名是一个目录[-d/tmp/mydir]-f文件名如果文件名是一个普通文件则为真[-f/usr/bin/grep]-L文件名如果文件名是一个符号链接则为真[-L/usr/bin/grep]-r文件名如果文件名可读则为真[-r/var/log/syslog]-w文件名如果文件名可写则为真[-w/var/mytmp.txt]-x文件名如果文件名可执行则为真[-L/usr/bin/grep]filename1-ntfilename2如果filename1比filename2新[/tmp/install/etc/services-nt/etc/services]filename1-otfilename2如果filename1比filename2旧则为真[/boot/bzImage-otarch/i386/boot/bzImage]字符串比较运算符(注意引号的使用,这是防止空格混淆代码的好方法)-zstring如果字符串长度为零则为真[-z"$myvar"]-n如果字符串的长度非零,则字符串为真[-n"$myvar"]string1=string2如果string1与string2相同如果相同则为真["$myvar"="onetwothree"]string1!=string2如果string1不同则为真fromstring2["$myvar"!="onetwothree"]算术比较运算符num1-eqnum2等于[3-eq$mynum]num1-nenum2不等于[3-ne$mynum]num1-ltnum2小于[3-lt$mynum]num1-lenum2小于或等于[3-le$mynum]num1-gtnum2大于[3-gt$mynum]num1-genum2大于orequalto[3-ge$mynum]有时有几种不同的方法来进行特定的比较例如,以下两个片段做同样的事情:if["$myvar"-eq3]thenecho"myvarequals3"fiif["$myvar"="3"]thenecho"myvarequals3"fi上面两个比较执行相同的功能,但是第一个使用算术比较运算符,而第二个使用字符串比较运算符。字符串比较表明,大多数时候,虽然可以省略字符串和字符串变量周围的双引号,但这不是一个好主意。为什么?因为如果环境变量中恰好有空格或制表符,bash将无法分辨,因此无法工作。下面是一个错误比较的例子:if[$myvar="foobaroni"]thenecho"yes"fi在上面的例子中,如果myvar等于"foo",代码按预期工作并且不打印。但是,如果myvar等于“foobaroni”,代码将失败并出现以下错误:[:toomanyarguments在这种情况下,“$myvar”(等于“foobaroni”)中的空格会混淆bash。bash展开"$myvar"后,代码如下:[foobaroni="foobaroni"]由于环境变量没有用双引号引起来,bash认为方括号中的参数太多。您可以通过将字符串参数括在双引号中来消除此问题。请记住,如果您养成将所有字符串参数括在双引号中的习惯,您将消除许多此类编程错误。"foobaroni"比较应该写成:if["$myvar"="foobaroni"]thenecho"yes"fi更多引用细节如果要扩展环境变量,必须用双引号括起来,而不是单引号上升。单引号禁用变量(和历史)扩展。上面的代码将按预期工作,没有任何不愉快的意外。循环结构:“for”好了,既然我们已经介绍了条件语句,是时候探索bash循环结构了。我们将从标准的“for”循环开始。这是一个简单的例子:#!/usr/bin/envbashforxinonetwothreefourdoechonumber$xdone输出:numberonenumber2numberthreenumber4发生了什么事?“for”循环的“forx”部分定义了一个名为“$x”的新环境变量(也称为循环控制变量),它被设置为值“一”、“二”、“三”“”和“四”。每次赋值后,循环体(“do”和“done”之间的代码)被执行。在循环体内,循环控制变量“$x”像任何其他环境变量一样被引用使用标准变量扩展语法。还要注意,“for”循环总是在“in”语句之后接收某种类型的单词列表。在这个例子中,指定了四个英语单词,但单词列表也可以引用磁盘上的文件,甚至文件通配符。看看下面的示例,它演示了如何使用标准shell通配符:#!/usr/bin/envbashformyfilein/etc/r*doif[-d"$myfile"]thenecho"$myfile(dir)"elseecho"$myfile"fidoneOutput:/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替换它.一旦进入循环,“-d”条件运算符用于根据myfile是否为目录执行两个不同的操作。如果它是目录,则“(dir)”将附加到输出行。您还可以在单??词列表中使用多个通配符,甚至可以使用环境变量:forxin/etc/r--?/var/lo*/home/drobbins/mystuff/*/tmp/${MYPATH}/*docp$x/mnt/mydirdoneBash将在所有正确的地方执行通配符和环境变量扩展,并且可能会创建一个很长的单词列表。虽然所有通配符扩展示例都使用绝对路径,但也可以使用相对路径,如下所示:forxin../*mystuff/*doecho$xisasillyfiledone目录执行通配符扩展就像在命令行上使用相对路径一样。查看通配符扩展。您会注意到,如果您在通配符中使用绝对路径,bash会将通配符扩展为绝对路径列表。否则,bash将在后续单词列表中使用相对路径。如果仅引用当前工作目录中的文件(例如,如果输入“forxin*”),生成的文件列表将不会以路径信息为前缀。请记住,“basename”可执行文件可用于去除前面的路径信息,如下所示:forxin/var/log/*doecho`basename$x`isafilelivingin/var/logdoneofcourse,对脚本的命令行参数执行循环通常很方便。下面是如何使用本文开头提到的“$@”变量的示例:#!/usr/bin/envbashforthingin"$@"doechoyoutyped${thing}。done输出:$allargshello你傻了,你输入了hello。你在那里打字。是的,没错:您可以使用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]do语句done通常使用“While”语句循环一定次数,例如下面的例子会循环10次:myvar=0while[$myvar-ne10]doecho$myvarmyvar=$(($myvar+1))done可以看到,上面的例子使用了一个算术表达式使得条件最终为假并导致循环终止。“Until”语句提供与“while”语句相反的功能:只要某个条件为假,它们就会重复。这是一个“until”循环,在功能上等同于前面的“while”循环:myvar=0until[$myvar-eq10]doecho$myvarmyvar=$(($myvar+1))doneCase语句TheCasestatement是另一个方便的条件结构。这是一个示例片段:case"${x##*.}"ingz)gzunpack${SROOT}/${x};;bz2)bz2unpack${SROOT}/${x};;*)echo“存档格式无法识别。”出口;;esac在上面的例子中,bash首先展开“${x##*.}”。在代码中,“$x”是文件名,“${x##.*}”删除文件中除最后一个句点之后的文本之外的所有文本。然后,bash将结果字符串与“)”左侧列出的值进行比较。在此示例中,“${x##.}”与“gz”进行比较,然后是“bz2”,最后是“”。如果“${x##.}”匹配这些字符串或模式中的任何一个,则执行紧接在“)”之后的行,直到“;;”,然后bash继续执行终止符“esac”之后的行OK。如果没有匹配到模式或字符串,则不执行任何代码行,并且在该特定代码片段中至少执行一个代码块,因为任何不匹配“gz”或“bz2”的字符串都将与“”模式匹配匹配。函数和命名空间在bash中,甚至可以定义类似于其他过程语言(如Pascal和C)的函数。在bash中,函数甚至可以像脚本接受命令行参数一样接受参数。让我们在继续之前先看一个示例函数定义:tarview(){echo-n"Displayingcontentsof$1"if[${1##*.}=tar]thenecho"(uncompressedtar)"tartvf$1elif[${1##*.}=gz]然后echo"(gzip-compressedtar)"tartzvf$1elif[${1##*.}=bz2]然后echo"(bzip2-compressedtar)"cat$1|bzip2-d|tartvf-fi}另一种情况可以使用“case”语句来编写上面的代码。你知道怎么写吗?上面我们定义了一个名为“tarview”的函数,它接受一个参数,一个某种类型的tar文件。执行函数时,它会确定参数是哪种tar文件类型(未压缩、gzip压缩或bzip2压缩),打印一条信息行,然后显示tar文件的内容。应按如下方式调用上述函数(在键入、粘贴或找到该函数后从脚本或命令行调用):$tarviewshorten.tar.gzajr/abbot01999-02-2716:17shorten-2.3a/-rw-r--r--ajr/abbot11431997-09-0404:06shorten-2.3a/Makefile-rw-r--r--ajr/abbot11991996-02-0412:24shorten-2.3a/INSTALL-rw-r--r--ajr/abbot8391996-05-2900:19shorten-2.3a/LICENSE...以交互方式使用它们不要忘记,您可以将函数(如上面的函数)放在~/.bashrc或~/.bash_profile中,这样您就可以随时在bash中使用它们。如您所见,可以使用与命令行参数相同的机制在函数定义中引用参数。此外,“$#”宏将被扩展以包含参数的数量。唯一可能不完全相同的是变量“$0”,它将扩展为字符串“bash”(如果该函数是从shell交互式运行的)或调用该函数的脚本的名称。命名空间通常需要在函数内创建环境变量。尽管可能,您还应该注意一个技术细节。在C等大多数编译型语言中,当在函数内部创建变量时,变量会被放置在单独的本地命名空间中。因此,如果在C中定义一个名为myfunction的函数,并在该函数中定义一个名为“x”的参数,则任何名为“x”的全局变量(函数外的变量)都不会受到Its印象的影响,从而消除了负面影响.这在C中是正确的,但在bash中则不然。在bash中,无论何时在函数内部创建环境变量,它都会被添加到全局命名空间中。这意味着该变量将覆盖函数外部的全局变量并在函数退出后继续存在:#!/usr/bin/envbashmyvar="hello"myfunc(){myvar="onetwothree"forxin$myvardoecho$xdone}myfuncecho$myvar$x当你运行这个脚本时,它会输出“一二三三”,它显示了函数中定义的“$myvar”如何影响全局变量“$myvar”,以及循环控制变量“$x”在函数退出后仍然存在(如果“$x”全局变量存在,它也会受到影响)。在这个简单的例子中,很容易找到错误并通过使用另一个变量名来更正它。但这不是正确的方法,解决这个问题最好的方法是通过使用“local”命令在第一时间防止影响全局变量的可能性。当使用“local”在函数内部创建变量时,它们将被放置在本地命名空间中,不会影响任何全局变量。以下是如何实现上述代码,以便不覆盖全局变量:#!/usr/bin/envbashmyvar="hello"myfunc(){localxlocalmyvar="onetwothree"forxin$myvardoecho$xdone}myfuncecho$myvar$x这个函数会输出“hello”——在不覆盖全局变量“$myvar”的情况下,“$x”不会继续存在于myfunc之外。在函数的第一行,我们创建了稍后要用到的局部变量x,在第二个例子(localmyvar="onetwothree"")中,我们创建了局部变量myvar并为其赋值。在使用中第一种形式在将循环控制变量定义为局部变量时很方便,因为不允许这样说:“forlocalxin$myvar”。该函数不影响任何全局变量,鼓励您将所有函数设计为这种形式方式。只有当您明确希望修改全局变量时才应使用“local”。结束语我们已经了解了最基本的bash功能,现在我们要看看如何基于bash开发整个应用程序。下一部分就要讲了。再见!参考资料您可以在developerWorks全球站点上查看本文的原始英文版本。阅读developerWorks上的介绍性bash文章“BashbyExample,第1部分”。访问GNU的bash主页以获得原始的bash在线参考手册:http://www.ibm.com/developerw…
