当前位置: 首页 > Linux

Bash实例,第1部分

时间:2023-04-06 20:34:35 Linux

作者:DanielRobbins 来自:IBMDW中国您可能会问:为什么要学习Bash编程?那么,这里有一些令人信服的理由:已经在运行它如果您看一下,您可能会发现您现在正在运行bash。因为bash是标准的Linuxshell并且有多种用途,所以即使您更改了默认shell,bash也可能仍在您系统的某个地方运行。因为bash已经在运行,所以将来运行的任何bash脚本本质上都是内存高效的,因为它们与任何已经运行的bash进程共享内存。如果运行工具可以完成工作并且运行良好,为什么还要加载500K的解释器?已经在使用它您不仅在运行bash,而且实际上每天都在与bash打交道。它总是在那里,所以学习如何充分利用它是值得的。这样做将使您的bash体验更加有趣和高效。但是为什么要学习bash编程呢?很简单,因为您已经考虑过如何运行命令、CPing文件以及管道和重定向输出。为什么不学习一门语言,这样您就可以使用和利用那些您已经知道和喜爱的强大的节省时间的概念?命令shell释放了UNIX系统的潜力,而bash就是Linuxshell。它是您和机器之间的高级链接。增加您的bash知识,它会自动提高您在Linux和UNIX中的工作效率——就这么简单。Bash混淆以错误的方式学习bash可能会非常混乱。许多新手键入“manbash”来查看bash手册页,只是为了获得shell功能的非常简单和技术性的描述。其他人输入“infobash”(查看GNU信息文档),只会得到一个重新显示的手册页,或者(如果幸运的话)一个稍微友好的信息文档。虽然这对初学者来说可能有些令人沮丧,但标准的bash文档并不适合所有人,它仅适用于那些已经熟悉shell编程的人。帮助页面确实有很多优秀的技术信息,但对初学者的帮助有限。这就是本系列的内容。在本系列中,我将向您展示如何实际使用bash编程概念,以便您可以编写自己的脚本。我将用通俗易懂的语言向您解释,而不是技术描述,这样您不仅可以知道它们的作用,还可以知道何时应该使用它们。在这个由三部分组成的系列结束时,您将能够自己编写复杂的bash脚本,熟练地使用bash,并通过阅读(和理解)标准bash文档来补充您的知识。开始吧。环境变量在bash和几乎所有其他shell中,用户可以定义环境变量,这些变量存储为ASCII字符串。环境变量最方便的一点是它们是UNIX进程模型的标准部分。这意味着:环境变量不仅可以被shell脚本独占使用,也可以被编译后的标准程序使用。当您在bash中“导出”一个环境变量时,您以后运行的任何程序,无论它是否是shell脚本,都可以读取该设置。一个很好的例子是vipw命令,它通常允许root用户编辑系统密码文件。通过将EDITOR环境变量设置为您最喜欢的文本编辑器的名称,您可以将vipw配置为使用该编辑器而不是vi,如果您习惯了xemacs而真的不喜欢vi,这会很方便。在bash中定义环境变量的标准方法是:$myvar='Thisismyenvironmentvariable!'上面的命令定义了一个名为“myvar”的环境变量,并包含字符串“Thisismyenvironmentvariable!”。上面有几点需要注意:首先,等号“=”两边不能有空格,任何空格都会报错(试试看)。第二个需要注意的是,虽然在定义单词时可以省略引号,但是在定义包含多个单词(包括空格或制表符)的环境变量时,需要使用引号。引用详细信息有关如何在bash中使用引号的详细信息,请参阅bash帮助页面的“引用”部分。用其他值“扩展”(替换)特殊字符序列确实使bash中字符串的处理变得复杂。本系列将仅涵盖最常用的引用函数。第三,虽然您通常可以使用双引号代替单引号,但在上面的示例中这样做会导致错误。为什么?因为使用单引号会禁用称为扩展的bash功能,其中特殊字符和字符系列会被值替换。例如,“!”character是bash通常用先前输入的命令替换的历史扩展字符。(本系列文章不会涉及历史扩展,因为它在bash编程中并不常用。有关历史扩展的更多信息,请参阅bash手册页的“历史扩展”部分。)虽然这个类似宏的函数非常方便,但我们现在只想附加一个带有简单感叹号的环境变量,而不是宏。现在,让我们看看如何实际使用环境变量。下面是一个例子:$echo$myvar这是我的环境变量!通过在环境变量前加上$,您可以使bash将其替换为myvar的值。这在bash术语中称为“变量扩展”。但是如何做:$echofoo$myvarbarfoo我们期望回显“fooThisismyenvironmentvariable!bar”,但它没有。怎么了?简而言之,bash变量扩展工具陷入了困境。它不知道扩展哪个变量:$m、$my、$myvar、$myvarbar等。如何更清楚地告诉bash引用了哪个变量?试试这个:$echofoo${myvar}barfooThisismyenvironmentvariable!bar如您所见,当环境变量与周围的文本没有明确分隔时,可以将其括在花括号中。虽然$myvar的输入速度更快并且在大多数情况下都能正常工作,但${myvar}几乎在所有情况下都能正确解析。除此之外,它们是相同的,您将在本系列的其余部分看到这两种形式的变量扩展。请记住:当环境变量未通过空格(空格或制表符)与周围文本分隔时,请使用更明确的大括号形式。回想一下,我们还提到过可以“导出”变量。导出环境变量时,它会自动被稍后运行的任何脚本或可执行环境使用。Shell脚本可以使用Shell的内置环境变量支持“获取”环境变量,而C程序可以使用getenv()函数调用。以下是一些C代码示例,导入并编译它们——这将帮助我们从C的角度理解环境变量:myvar.c——示例环境变量C程序#include#includeintmain(void){char*myenvvar=getenv("EDITOR");printf("Theeditorenvironmentvariableissetto%s\n",myenvvar);}将上面的代码保存到文件myenv.c中并发出下面的命令编译:$gccmyenv.c-omyenv现在会有一个目录中的可执行程序,运行时将打印EDITOR环境变量的值(如果它有值)。下面是我在我的机器上运行它时发生的情况:$./myenv编辑器环境变量设置为(null)啊……C程序得到一个空字符串,因为EDITOR环境变量没有设置为任何值。让我们尝试将其设置为特定值:$EDITOR=xemacs$./myenv编辑器环境变量设置为(null)虽然期望myenv打印值“xemacs”,但它不起作用,因为环境变量还没有已经出口了。好好工作。这次让它正常工作:$exportEDITOR$./myenv编辑器环境变量设置为xemacs现在,正如您自己所见:没有导出环境变量,另一个进程(在本例中为示例C程序)无法看到到环境变量。顺便说一句,如果您愿意,可以在一行中定义和导出环境变量,如下所示:$exportEDITOR=xemacs这与两行版本的效果相同。现在演示如何使用unset删除环境变量:$unsetEDITOR$./myenv编辑器环境变量设置为(null)dirname和basename注意:dirname和basename不是磁盘上的文件或目录,它们只是字符串操作命令。截断字符串概述截断字符串是将初始字符串截断为更小的独立块,这是常见的shell脚本每天执行的任务之一。很多时候shell脚本需要采用完全限定路径并找到结束文件或目录。虽然用bash编写代码是可能的(而且很有趣),但标准的basenameUNIX可执行文件可以很好地完成这项工作:一个非常方便的截断字符串的工具。其相关命令dirname返回basename丢弃的路径的“其他”部分。$dirname/usr/local/share/doc/foo/foo.txt/usr/local/share/doc/foo$dirname/usr/home/drobbins//usr/home命令替换需要知道一个方便的操作:如何创建一个包含可执行命令结果的环境变量。这很简单:$MYDIR=`dirname/usr/local/share/doc/foo/foo.txt`$echo$MYDIR/usr/local/share/doc/foo上面所做的称为“命令替换”。这个例子有几点需要指出。在第一行,只需将要执行的命令括在反引号中即可。这不是标准的撇号,而是通常位于键盘上Tab键上方的撇号。同样可以用bash备用命令替换语法来完成:$MYDIR=$(dirname/usr/local/share/doc/foo/foo.txt)$echo$MYDIR/usr/local/share/doc/foo你可以看到,bash提供了多种方法来做完全相同的事情。使用命令替换在$()之间放置任何命令或命令管道,并将其分配给环境变量。太方便了!这是一个显示如何在命令替换中使用管道的示例:MYFILES=$(ls/etc|greppa)bash-2.03$echo$MYFILESpam.dpasswd像专业人士一样截断字符串,尽管basename和dirname是很好的工具,但有时它可能有必要执行比标准路径名操作更高级的字符串“截断”。当需要更有说服力时,您可以利用bash的内置变量扩展功能。已使用标准类型变量扩展,如${MYVAR}。但是bash本身也可以执行一些方便的字符串截断。看看这些例子:$MYVAR=foodforthought.jpg$echo${MYVAR##*fo}rthought.jpg$echo${MYVAR#*fo}odforthought.jpg在第一个例子中,输入了${MYVAR##*fo}。这到底是什么意思?基本上,在${}中输入环境变量名称,两个##,然后是一个通配符(“fo”)。然后,bash使用MYVAR,找到从字符串“foodforthought.jpg”开始的与通配符“fo”匹配的最长子字符串,并从字符串的开头截断它。一开始可能有点难以理解,所以为了感受这个特殊的“##”选项是如何工作的,让我们一步一步地看看bash是如何完成这个扩展的。首先,它在“foodforthought.jpg”的开头搜索与“*fo”通配符匹配的子字符串。以下是检查的子字符串:ffoMATCHES*fofoofoodfoodffoodfoMATCHES*fofoodforfoodfortfoodforthfoodforthofoodforthoufoodforthugfoodforthoughtfoodforthought.jfoodforthought.jpfoodforthought.jpg在搜索匹配的字符串后,您可以看到bash找到了两个匹配项。它选择最长的匹配项,将其从原始字符串的开头删除,然后返回结果。上面显示的第二种变量扩展形式看起来与第一种相同,但它只使用一个“#”——而bash执行几乎相同的过程。它查看与第一个示例相同的一系列子字符串,但bash从原始字符串中删除最短的匹配项并返回结果。因此,一旦找到“fo”子字符串,它就会从字符串中删除“fo”并返回“odforthought.jpg”。说到这里可能会很混乱,所以让我们简单地记住一下这个特性。搜索最长的匹配项时,使用##(因为##比#长)。搜索最短匹配时,使用#。看,不难记!等等,你怎么记得应该使用'#'字符从字符串的开头剥离?这很容易!注意:在美式键盘上,shift-4是“$”,这是bash变量扩展字符。在键盘上,紧靠“$”左边的是“#”。于是,可以看到:“#”是“$”的“开头”,所以(根据我们的助记符),“#”去掉了字符串开头的字符。您可能会问:如何从字符串末尾去除字符。如果您猜到我们在美式键盘上使用紧靠“$”("%)右侧的字符,那么您猜对了。以下是一些如何截断字符串末尾的简单示例:$MYFOO="chickensoup.tar.gz"$echo${MYFOO%%.*}chickensoup$echo${MYFOO%.*}chickensoup.tar如您所见,%和%%变量扩展选项的工作方式与#和##相同。注意:如果要从末尾删除特定的子字符串,则不必使用“*”字符:本例中的MYFOOD="chickensoup"$echo${MYFOOD%%soup}chicken,没关系您是否使用“%%”或“%”,因为只能匹配一个。还要记住:如果您忘记应该使用“#”还是“%”,请查看键盘键上的3、4和5,然后猜吧。另一种形式的变量扩展可用于根据特定字符偏移量和长度选择特定子字符串。尝试在bash中键入以下行:$EXCLAIM=cowabunga$echo${EXCLAIM:0:3}cow$echo${EXCLAIM:3:7}abunga这种形式的字符串截断非常简单,只需指定起始字符和子字符串长度,用冒号分隔。.我们的脚本将接受一个文件作为参数,并打印:Isthefileatarfile.要确定它是否是tar文件,我们将以查找模式“.tar”结束文件。它看起来像这样:mytar.sh--一个简单的脚本#!/bin/bashif["${1##*.}"="tar"]thenecho这似乎是一个tarball.elseecho乍一看,这似乎不是tarball.fi要运行此脚本,请将其输入文件mytar.sh,然后键入“chmod755mytar.sh”以生成可执行文件。然后,按如下方式做一个tar文件实验:$./mytar.shthisfile.tarThisappearstoatarball.$./mytar.shthatfile.gz乍一看,这似乎不是tarball。嗯,运行成功,但是不太实用。在使其更实用之前,先看一下上面使用的“if”语句。语句中使用了布尔表达式。在bash中,“=”比较运算符检查字符串是否相等。在bash中,所有布尔表达式都包含在方括号中。但是布尔表达式实际上测试的是什么?让我们看看左边。根据前面学习的字符串截断知识,“${1##.}”会去掉最长的“.”从环境变量“1”所含字符串的开头开始匹配,返回结果。这将返回最后一个“。”之后的所有内容。在文件中。显然,如果文件以“.tar”结尾,结果将是“tar”,并且条件为真。您可能想知道:开头的“1”环境变量是什么。很简单——$1是传递给脚本的第一个命令行参数,$2是第二个,依此类推。好的,现在我们已经回顾了功能,让我们首先看一下“if”语句。If语句像大多数语言一样,bash有自己的条件形式。使用时,按照上面的格式;也就是说,将“if”和“then”放在不同的行中,并将“else”和末尾所需的“fi”与它们水平对齐。这将使代码易于阅读和调试。除了“if,else”形式外,还有其他形式的“if”语句:if[condition]thenactionfi只有当条件为真时,语句才执行动作,否则不执行动作,继续执行在那之后的任何一行执行“fi”。if[condition]thenactionelif[condition2]thenaction2...elif[condition3]thenelseactionxfi上面的“elif”形式会依次测试每一个条件,并执行匹配第一个为真的条件的动作。如果没有一个条件为真,则执行“else”操作,如果有一个条件为真,则在整个“if,elif,else”语句之后的行继续执行。下一次我们已经介绍了bash功能的基础知识,是时候加快步伐准备编写一些实际的脚本了。在下一篇中,您将介绍循环概念、函数??、名称空间和其他重要主题。然后,您将准备好编写一些更复杂的脚本。在第三部分中,我将重点介绍一些非常复杂的脚本和函数,以及几个bash脚本设计选项。再见!参考资料您可以在developerWorks全球站点上看到这篇英文文章。阅读developerWorks上的介绍性bash文章“Bashinaction,第1部分”。访问GNU的bash主页以获得bash在线参考手册