shell脚本实战(第2版)(txt+pdf+epub+mobi电子书下载)


发布时间:2020-05-24 14:52:07

点击下载

作者:(美)戴夫·泰勒,布兰登·佩里

出版社:人民邮电出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

shell脚本实战(第2版)

shell脚本实战(第2版)试读:

前言

自从2004年本书第1版问世以来,Unix系统管理领域已经发生了不小的改变。当时鲜有普通的计算机用户会去碰类Unix操作系统,但随着像Ubuntu这种对新手颇为友好的Linux桌面发行版日渐流行,情况开始不一样了。接着出现了苹果公司新一代基于Unix的操作系统OS X以及大量基于iOS的技术。如今,类Unix操作系统的应用比以往任何时候都要广泛。如果把安卓智能手机也算在内的话,这可以说是世界上用户数量最为庞大的操作系统了。

尽管变化颇多,但作为流行于Unix用户之间的系统shell,Bourne-again shell(bash)至今仍旧屹立不倒。无论是系统管理员、工程师还是业余爱好者,如何充分发挥出bash脚本的威力都是当务之急。本书可以为你带来什么

本书重点关注编写可移植的自动化脚本(例如构建软件或提供业务流程)时经常面对的那些难题,为此我们的做法是先实现一些常见任务的自动化。但如果想从本书中获得最大收益,就要将已形成的解决方案推广应用到其他类似的问题上。比如本书第1章创建了一个小型的包装器脚本,实现了echo的可移植版。尽管很多系统管理员都能受益于这个脚本,但其中真正的价值在于,创建包装器脚本这种一般性的解决方案能够确保跨平台行为的一致性。书中随后的章节将深入研究bash脚本编程中一些酷炫的特性以及Unix系统常见的实用工具,为你传授各种绝招。目标读者

bash仍旧是类Unix服务器或工作站用户的主要工具,这些用户包括Web开发人员(很多人都是在OS X上做开发,然后部署到Linux服务器)、数据分析师、移动应用程序开发人员以及软件工程师,等等。除此之外,大量的业余爱好者也在自己的开源微型计算机(例如树莓派)上运行着Linux,实现智能家庭的自动化。shell脚本是这类用途的不二之选。

无论是对于在bash技艺上追求精益求精的老手,还是那些偶尔用一下终端或shell脚本的用户,书中所展现的脚本都大有裨益。后一阵营中的用户可能希望温习一些技巧或是学点bash的高级概念,给自己再充充电。

本书并非教程!其目标是通过(基本上)简短紧凑的脚本教会你bash脚本编程的实用技术以及常见工具的用法,但不会去逐行解释脚本。本书只讲解每个脚本的核心内容,有经验的shell脚本用户通过阅读代码就能明白其余的部分。你大可放开手脚,把脚本拆解开来,根据自己的需要修改,借此达到融会贯通的目的。书中的脚本旨在解决那些三天两头就会碰上的麻烦事,比如Web管理或是文件同步,不管用的是什么工具,每个技术专家都得应付这类问题。内容组织

本书第2版对上一版中共计12章内容做了与时俱进的更新,另外还增添了3章全新的内容。每一章都会演示新的特性或shell脚本用例,两者相互结合,共同展示了各种可用于提高Unix日常使用效率的shell脚本用法。OS X用户请放心:书中大部分脚本在Linux或OS X下都没有问题。如有例外,我们会特别指出。第0章 shell脚本速成

第2版中新添加的这一章为Unix新用户快速地介绍了bash脚本的语法及其用法。从“什么是shell脚本”这种最基本的问题到构建并执行简单的shell脚本,本章内容简明扼要,毫无废话,旨在帮助你尽快进入状态,为接下来的第1章奠定基础。第1章 遗失的代码库

Unix环境中的编程语言,尤其是C、Perl和Python,都包含了各式各样的函数与实用工具库,可用于验证数字格式、计算日期偏差以及执行其他的任务。和shell打交道时,有大量的工作需要我们自己来完成,因此这一章关注的是那些能够简化shell脚本的工具以及鲜为人知的技巧。无论是对于本书中的脚本还是你自己编写的脚本,在这里所学到的知识都能够派上用场,其中包括各种输入验证函数、一个简约却不简单的脚本化bc前端、可以快速插入逗号并提高多位数可读性的工具、一种能够解决某些Unix版本不支持echo命令-n选项的技术,以及一个支持在脚本中使用ANSI颜色序列的脚本。第2章 改进用户命令第3章 创建实用工具

这两章介绍了一些可用于拓展Unix功能的新命令。Unix的一个出色之处在于其自身总是在不断地成长和演进。我们在此给出了多个脚本,其中包括一个易用的交互式计算器、反删除工具、两个提示/事件跟踪系统、locate命令的重制版、多时区date命令,以及增强了目录列表功能的ls新版本。第4章 Unix调校

这么说可能显得骇人听闻,但在经历了数十年的发展之后,Unix的某些方面看起来已经支离破碎了。如果你辗转尝试过不同的Unix版本,尤其是开源的Linux发行版和商业版Unix,比如像OS X、Solaris或是Red Hat,就会注意到这些版本之间所存在的选项缺失、命令缺失、命令不一致等类似问题。为了提高Unix命令的友好性或一致性,这一章重写了一些命令并为其加上了前端,其中就包括为非GNU命令增添GNU风格的全字(full-word)命令选项。另外,这一章还给出了两个精巧的脚本,大大降低了与各式文件压缩工具打交道时的难度。第5章 系统管理:用户管理第6章 系统管理:系统维护

如果你翻开了这本书,那么可以推测你可能负责着不止一个Unix系统的管理任务,哪怕只是个人版的Ubuntu或BSD盒子(BSD box)。这两章中给出了大量能够提高管理工作效率的脚本,其中包括磁盘用量分析工具、自动向超额用户发送电子邮件的磁盘配额系统、killall命令的重制版、crontab验证程序、日志文件轮替(rotation)工具以及若干备份工具。第7章 Web与Internet用户

这一章给出了一些令人拍案叫绝的shell脚本技巧,展示了Unix命令行在处理网络资源时所用到的那些既简单又神奇的方法,其中包括URL提取工具、天气预报跟踪程序、电影数据库搜索器以及能够自动发送邮件提醒的网站变更跟踪程序。第8章 网站管理员绝招

也许你还在自家的Unix系统或网络共享服务器上运行并管理着网站,这一章提供了一些值得一试的脚本工具,可以帮助你动态创建Web页面、生成基于Web的相册,甚至是记录Web搜索日志。第9章 Web与Internet管理第10章 Internet服务器管理

对于搭建在Internet上的服务器,管理员总是面对着各种挑战,这两章就是来处理这些问题的,其中包括了两个Web服务器流量日志分析脚本、网站无效链接识别工具,以及一个可用于简化 .htaccess文件维护任务的Apache密码管理工具,除此之外,还研究了目录及全站镜像技术。第11章 OS X脚本

在将Unix融入用户友好型操作系统的道路上,OS X取得的巨大进步得益于其富有吸引力并在商业上获得成功的图形用户界面。更重要的是,由于在华丽的界面背后,OS X包含了一套完整的Unix系统,因此出现了大量为其编写的实用脚本,这也正是这一章要为读者讲解的内容。除了自动化抓屏工具,我们还研究了保存iTunes音乐库的方法、如何更改Terminal的窗口标题,以及如何强化open命令。第12章 shell脚本趣用与游戏

一本编程书里怎么能少了游戏?这一章结合书中多种复杂的技术及设计思路,创造出了6个既有趣又不失挑战性的游戏。尽管这一章旨在娱乐,但这些游戏的代码同样很值得学习。尤其值得一提的是猜词游戏(hangman game),该游戏在实现过程中运用了一些精妙的编码技术以及shell脚本技巧。第13章 与云共舞

自本书第1版发行以来,Internet在我们的日常生活中占据了越来越重要的地位,很多时候大家都在忙于通过云服务(例如iCloud、Dropbox和Google Drive)同步设备和文件。这一章包含了一些能够充分利用云服务备份/同步文件和目录的shell脚本,另外还演示了在脚本中运用与照片或文本-语音转换相关的OS X特性。第14章 ImageMagick及图像处理

命令行的应用并不仅限于文本处理。这一章专门讲述了如何利用开源软件ImageMagick中的图像处理工具集识别并处理图片。无论是判断图像类型,还是为图像添加边框和水印,一些常见的任务都可以使用这一章中的脚本搞定。第15章 天数与日期

最后一章简化了与日期和预约相关的烦人细节,其中包括:计算两个日期之间的时长、某一天是星期几以及距离指定日期还有多久。这些问题全都可以利用便捷的shell脚本来解决。附录A 在Windows 10中安装bash

第2版撰写期间,微软开始大力改善自己在开源界的形象,于2016年在Windows 10中发布了一个完整的bash环境。尽管我们并没有针对该版本的bash测试过书中的例子,但是很多概念和解决方法应该同样适用。本附录介绍了在Windows 10中安装bash的方法,这样你就可以在自己的Windows机器上尝试动手编写一些令人刮目相看的脚本了!附录B 免费福利

每一名优秀的童子军都知道不能没有B计划!就本书而言,我们得确保在写作期间备份所有的脚本,避免可能出现的意外情况。现在本书既然已经出版,也就用不着再备份了,不过好友之间还是应该开诚布公。本附录包含了3个额外的脚本:批量文件重命名、批量运行命令以及查找月相(phase of the moon)。既然先前都已经为你准备了101个脚本,那么剩下这几个也不用再藏着掖着了。在线资源

所有shell脚本的源文件都可以在图灵社区本书主页(http://ituring.com.cn/book/2485)下载,其中还包括了在脚本中用到那些例子的资源文件,比如脚本#84中用于猜词游戏的单词列表,脚本#27中节选自《爱丽丝梦游仙境》的摘录。最后的话

希望你会喜欢这本shell脚本经典著作的更新版。乐趣是学习过程中不可缺少的一部分,之所以在书中挑选这些例子,正是因为它们不仅写起来好玩,而且摆弄起来也很有意思。我们很享受本书的撰写过程,希望你也能够展卷愉悦。同乐!电子书

扫描如下二维码,即可购买本书电子版。

致谢

第1版致谢

很多人都为本书的创作和出版做出了贡献,其中尤为值得一提的是我一开始的技术评审员和即时通信软件中从未消失的伙伴Dee-Ann LeBlanc,还有技术编辑和脚本专家Richard Blum,他对本书中的大部分脚本都给出了富有意义且重要的意见。Nat Torkington促进了书中脚本的组织性和健壮性。其他在撰稿阶段给予宝贵帮助的人包括Audrey Bronfin、Martin Brown、Brian Day、Dave Ennis、Werner Klauser、Eugene Lee、Andy Lester和John Meister。MacOSX.com论坛功不可没(还是一个很酷的在线闲逛之所),而AnswerSquad.com团队的很多妙点子实在是让人回味无穷。最后,如果没有Bill Pollock的鼎力支持以及Hillel Heinstein、Rebecca Pepper和Karol Jurado的编排润色,本书断难成形。感谢整个No Starch Press团队!

我要感谢我可爱的孩子们——Ashley、Gareth和Kiana——还有家里那些小动物们的支持。

 Dave Taylor

 第2版致谢

过去十几年中,本书第1版已经证明了它对喜欢bash脚本编程或是想要学习更高级技术的读者来说是一本令人鼓舞的实用读物。在更新第2版时,我和Dave希望给本书带来点不一样的东西,再开启探索shell脚本的另一个新十年。如果没有众人的支持,那么添加新脚本和完善脚本讲解的工作肯定不可能完成。

感谢我的喵咪Sam,它在我想要工作的时候经常卧在我的笔记本电脑上,我完全明白这小家伙的心思,它觉得自己是在帮忙。我的家人和朋友对于我在长达数月间只讨论bash脚本表现出了百分之百的支持和理解。No Starch Press团队一直会给那些除了高中论文和博客文章之外没有写过任何东西的人提供机会,万分感谢Bill Pollock、Liz Chadwick、Laurel Chun和No Starch团队的其他成员。衷心感谢Jordi Gutiérrez Hermoso在本书技术和代码方面提供的宝贵意见。

 Brandon Perry第 0 章shell脚本速成bash(以及通常意义上的shell脚本编程)出现的日子可是不短了,每天都有新手通过bash见识到shell脚本编程和系统自动化的威力。随着微软公司在Windows 10中发布了交互式的bash shell以及Unix子系统,现在已是更适合了解shell脚本所能实现的简洁和高效的时候了。0.1 什么是shell脚本

从计算机出现的早期开始,shell脚本就一直在帮助系统管理员和程序员完成费时费力的枯燥工作。那么,什么是shell脚本?为什么你要关心它?shell脚本就是包含一组可运行的特定shell命令(在本书中是bash shell)的文本文件,命令的执行顺序与其出现在脚本中的顺序一致。shell以命令行的形式提供了可用的操作系统命令库的接口。

shell脚本其实就是为使用shell环境中的命令所编写的小型程序,可用于自动化那些通常没人愿意手动完成的任务,例如Web爬取、磁盘用量跟踪、天气数据下载、文件更名,等等。你甚至能够用shell脚本制作一些初级的游戏!脚本中可以加入简单的逻辑,例如在其他语言中出现的if语句,不过你很快就会看到,脚本的形式甚至可以更简单。

OS X、BSD以及Linux操作系统中可用的命令行shell有很多种,包括tcsh、zsh和广受欢迎的bash。本书关注的是Unix环境中的主流:bash。每种shell都有自己的特性和功能,但是多数人在Unix中最先熟悉的就是bash。在OS X中,Terminal(终端)应用会打开一个bash shell窗口(如图0-1)。在Linux中,有各种各样的shell控制台程序,其中最常见的是gnome-terminal(GNOME)或konsole(KDE)。这些应用可以修改自身的配置来使用其他类型的shell,不过默认选用的都是bash。实际上,不管你用的是哪种类Unix操作系统,打开Terminal应用,得到的都是bash。

图0-1 OS X中的Terminal应用,其中显示了bash的版本号注意 2016年8月,微软发布了针对Windows 10周年版(Windows 10 Anniversary)的bash,所以如果你用的是Windows,照样可以使用bash shell。附录A给出了在Windows 10中安装bash的操作方法,不过本书假设你运行的是像OS X或Linux这样的类Unix操作系统。你完全可以在Windows 10下实验书中的脚本,但结果怎样就不保证了,因为我们自己没有在Windows中测试过。不过bash的美妙之处就在于它的可移植性,所以多数脚本基本上应该没问题。

使用终端与系统交互可能看起来是件艰巨的任务。但随着时间的推移,打开终端,快速对系统做出更改,会变得比在一个又一个的菜单中移动鼠标,找到要更改的选项更加自然。0.2 执行命令

bash的核心功能是执行系统命令。来看一个简单的“Hello World”的例子。在bash shell中,echo命令可以在屏幕上显示文本,例如:$ echo "Hello World"

在命令行中输入上面的内容,你就会看到Hello World出现在屏幕上。这行代码执行了bash标准命令echo。bash用来搜索标准命令的目录被保存在环境变量PATH中。你可以使用echo查看PATH变量的内容,如代码清单0-1所示。

代码清单0-1 输出当前环境变量PATH$ echo $PATH/Users/bperry/.rvm/gems/ruby-2.1.5/bin:/Users/bperry/.rvm/gems/ruby-2.1.5@global/bin:/Users/bperry/.rvm/rubies/ruby-2.1.5/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/usr/local/MacGPG2/bin:/Users/bperry/.rvm/bin注意 如果代码清单中既显示了输入命令,也显示了输出内容,输入命令会以粗体显示并以$作为起始,以此同输出内容区分开来。

输出中的各个目录之间以冒号分隔。当需要运行程序或命令时,bash会检查所有这些目录。如果命令没有在其中,bash就无法执行。另外要注意的是,bash是以这些目录在PATH中出现的顺序依次检查的。顺序在这里很重要,如果有两个同名的命令分别位于PATH中两个不同的目录中,可能会由于目录出现的先后顺序产生不同的结果。如果在查找特定命令时碰到了麻烦,可以用which命令查看待查找命令在PATH中的位置,如代码清单0-2所示。

代码清单0-2 使用which在PATH中查找命令$ which ruby/Users/bperry/.rvm/rubies/ruby-2.1.5/bin/ruby$ which echo/bin/echo

知道了这些信息,你可以把需要测试的文件移动或复制到echo $PATH所列出的某个目录中(如代码清单0-1所示),然后就能执行命令了。本书中使用了which来确定命令的完整路径。在调试有问题的PATH环境变量时,which是一个有用的工具。0.3 配置登录脚本

我们在书中会编写一些随后用于其他脚本中的脚本,因此能够轻松调用新脚本就显得很重要了。你可以配置PATH环境变量,使得在启动新的命令shell时,可以像其他命令那样自动调用定制脚本。新命令shell启动后的第一件事是读取用户主目录(在OS X和Linux中分别是/Users/和/home/)中的登录脚本并执行其中的定制命令。根据你所用的系统,登录脚本可以是.login、.profile、.bashrc或.bash_profile。要想知道具体是哪个,像下面这样在这些文件中各加入一行:echo this is .profile

将最后一个单词调整为对应的文件名,然后登录。这行内容应该会出现在终端窗口顶端,指明登录时运行的是哪个脚本。如果你打开终端,看到的是this is .profile,那么说明你的shell环境载入的是.profile文件;如果看到的是this is .bashrc,则说明是.bashrc文件,以此类推。依赖于你所用的shell,这种行为也会有变化。

你可以修改登录脚本,让它帮助在PATH变量中加入其他目录。另外也可以在登录脚本中完成其他bash设置,例如修改bash提示符外观、定制PATH等。让我们用cat命令来看一个定制好的.bashrc登录脚本。cat命令接受文件名作为参数,然后将文件内容输出到控制台屏幕,如代码清单0-3所示。

代码清单0-3 定制过的.bashrc文件,其中将RVM加入了PATH$ cat ~/.bashrcexport PATH="$PATH:$HOME/.rvm/bin" # 将RVM添加到PATH中。

该代码显示出了.bashrc文件的内容,可以看到PATH获得了一个新的值,允许本地RVM (Ruby version manager,Ruby版本管理器)管理已安装的各种Ruby版本。因为.bashrc在每次启动新的命令shell时都会设置PATH,所以RVM在系统中总是默认可用。

你可以用类似的方法让自己的shell脚本也默认可用。首先,在主目录中创建一个开发目录,将编写的所有脚本都保存到这个目录。然后在登录脚本中将该目录添加到PATH变量中,这样就可以更方便地引用新写的脚本了。

命令echo $HOME可以在终端中打印出主目录的路径。进入主目录,然后创建开发目录(建议将其命名为scripts)。接着用文本编辑器打开登录脚本,在脚本顶端添加以下代码(把/path/to/scripts/替换成开发目录的具体路径),将开发目录加入PATH。export PATH="/path/to/scripts/:$PATH"

在此之后,你保存到开发目录中的任何脚本都可以像其他shell命令那样调用了。0.4 运行shell脚本

我们到目前为止已经用到了几个命令,例如echo、which和cat,但是只是单独使用,并没有把它们放到shell脚本中。让我们来写一个可以连续执行这些命令的shell脚本,如代码清单0-4所示。这个脚本先打印出Hello World,然后输出shell脚本neqn的路径,neqn应该位于PATH默认的目录中。接着用该路径将neqn的内容打印到屏幕上。(neqn的内容是什么目前并不重要,这里只是作为一个例子而已。)这是利用shell脚本按顺序执行命令序列的一个很好的例子,在这里我们查看了文件的完整系统路径并快速检查了文件内容。

代码清单0-4 我们的第一个shell脚本echo "Hello World"echo $(which neqn)cat $(which neqn)

打开你自己惯用的文本编辑器(Linux上的Vim或gedit、OS X上的TextEdit都是很流行的编辑器),输入代码清单0-4中的代码。然后将shell脚本保存到开发目录中并命名为intro。shell脚本对文件扩展名没有特别要求,用不用都行(如果喜欢的话,可以用.sh作为扩展名,但不是必须的)。第一行代码使用echo命令打印出文本Hello World。第二行代码稍微复杂点,使用which命令找出neqn的位置,然后将其用echo命令在屏幕上打印出来。为了像这样将一个命令作为另外一个命令的参数来运行两个命令,bash使用子shell(subshell)来执行第二个命令并将其输出作为第一个命令的参数。在上面的例子中,子shell执行的是which命令,该命令返回neqn脚本的完整路径。这个路径再被用作echo的参数,结果就是在屏幕上显示出neqn的路径。最后,还是用子shell将neqn的路径传给cat命令,在屏幕上打印出neqn脚本的内容。

保存好文件之后,就可以在终端运行脚本了。代码清单0-5显示了执行结果。

代码清单0-5 运行我们的第一个shell脚本 $ sh intro❶ Hello World❷ /usr/bin/neqn❸ #!/bin/sh # Provision of this shell script should not be taken to imply that use of # GNU eqn with groff -Tascii|-Tlatin1|-Tutf8|-Tcp1047 is supported. GROFF_RUNTIME="${GROFF_BIN_PATH=/usr/bin}:" PATH="$GROFF_RUNTIME$PATH" export PATH exec eqn -Tascii ${1+"$@"} # eof $

运行这个脚本的时候,使用sh命令并将intro脚本作为参数。sh命令会依次执行脚本中的每行代码,就好像这些命令是在终端上敲入的一样。你可以看到先是打印出了Hello World❶,然后是neqn的路径❷,最后输出neqn的文件内容❸,也就是shell脚本neqn的源代码(至少在OS X上是这样的,Linux版本也许略有不同)。0.5 让shell脚本用起来更自然

不使用sh命令也可以执行脚本。如果在shell脚本intro中多加一行,然后修改脚本的权限,就可以像其他bash命令那样直接调用脚本了。在文本编辑器中更新intro脚本:❶ #!/bin/bash echo "Hello World" echo $(which neqn) cat $(which neqn)

我们在脚本顶端加上了一行/bin/bash❶。这行叫作shebang1。shebang允许你指定用哪个程序来解释脚本。这里选择将文件作为bash脚本。你可能还碰到过其他shebang,例如针对Perl(#!/usr/bin/perl)或Ruby(#!/usr/bin/env ruby)的。1shebang这个词其实是两个字符名称sharp-bang的简写。在Unix的行话里,用sharp或hash(有时候是mesh)来称呼字符“#”,用bang来称呼惊叹号“!”,因而shebang合起来就代表了这两个字符。详情请参考:http://en.wikipedia.org/wiki/Shebang_(Unix)。(本书除脚本#40之脚注为原书注,其余均为译者注。)

有了这行,还得设置文件权限才能像其他程序那样直接运行shell脚本。在终端中的操作方法如代码清单0-6所示。

代码清单0-6 将脚本intro的权限修改为可执行❶ $ chmod +x intro❷ $ ./intro Hello World /usr/bin/neqn #!/bin/sh # Provision of this shell script should not be taken to imply that use of # GNU eqn with groff -Tascii|-Tlatin1|-Tutf8|-Tcp1047 is supported. GROFF_RUNTIME="${GROFF_BIN_PATH=/usr/bin}:" PATH="$GROFF_RUNTIME$PATH" export PATH exec eqn -Tascii ${1+"$@"} # eof $

我们用到了权限修改命令chmod❶并将+x作为命令参数,该参数可以将随后指定的文件设置为可执行权限。权限设置好之后,不用调用bash就可以直接运行shell脚本❷。这是一种很好的shell脚本编程实践,在你以后精进技艺的过程中就会发现它的作用了。本书中大部分脚本都要像intro脚本这样设置执行权限。

这只是一个简单的例子,告诉你如何运行shell脚本,如何使用shell脚本运行其他的shell脚本。书中很多地方都用到了这种方法,在今后编写shell脚本的时候,你也会看到更多的shebang。0.6 为什么要用shell脚本

你也许疑惑为什么偏要选择bash shell脚本,而不去用那些漂亮的新语言,比如Ruby或Go。尽管这些语言都试图在多种系统上实现可移植性,但它们通常并没有被默认安装。原因很简单:所有Unix机器上都已经有了一个基本的shell,而且绝大多数用的都是bash shell。本章开头也提到过,微软最近在Windows 10中也加入了多数Linux发行版和OS X中采用的bash shell。这意味着你的shell脚本几乎不需要做什么额外的工作,就拥有了比以往更好的可移植性。相较于其他语言,shell脚本能够更准确、更轻松地完成系统维护及其他任务。bash并非十全十美,但你可以在本书中学会如何弥补其中一些不足之处。

代码清单0-7中展示了一个方便的微型shell脚本(没错,只有一行),完全可移植。该脚本可以统计出OpenOffice文档目录中的文档共有多少页,这对于作者特别有用。

代码清单0-7 统计OpenOffice文档目录中文档页面数量的bash脚本#!/bin/bashecho "$(exiftool *.odt | grep Page-count | cut -d ":" -f2 | tr '\n' '+')""0" | bc

我们不会深究这个脚本的工作细节,毕竟才刚上路嘛!不过概括地讲,脚本从各个文档中提取出页数信息,使用加号将页数拼接在一起,然后通过管道将算式传给命令行计算器,计算出最终的页面总数。所有这一切全在这一行代码中完成。全书中还有更多像这样的酷炫脚本,做过一些练习之后,这个脚本的含义就一目了然了!0.7 开始动手吧

如果你之前没有接触过shell脚本编程,那么现在对此应该有了基本的了解。创建小型脚本完成特定的任务是Unix哲学的核心。搞清楚如何编写脚本并根据个人需要扩展Unix系统,这样才能成长为一名高级用户。这一章只是道开胃菜,真正酷炫的shell脚本还在后面!第 1 章遗失的代码库Unix最强有力的一点在于它允许你用全新的方式组合现有命令,从而创建出新命令。尽管Unix包含了数百条命令以及数以千计的命令组合方式,你还是会碰到无法完全解决问题的情况。本章将重点放在了那些能够帮助你在shell脚本编程世界中编写出更精巧、更复杂程序的跳板上。

有些事得先解决:shell脚本编程环境不像真正的编程环境那么复杂。Perl、Python、Ruby,甚至是C都拥有能够提供额外功能的语言结构和库,而shell脚本更多的是一个你自己动手搭建的世界。本章中的脚本将会带领你走进这片天地。这些可作为构建模块的脚本能够帮助你编写出书中后续出现的那些酷炫的shell脚本。

脚本编写过程中的不少难题也源自不同的Unix流派以及各种GNU/Linux发行版之间的差异。尽管IEEE POSIX标准意在为Unix实现奠定通用的功能基础,但如果在Red Hat GNU/Linux环境中待过一年后再使用OS X系统,仍会让人困惑不已。命令不同,命令的位置也不同,命令选项也经常有细微的差异。这些不一致性使得脚本编写很棘手,不过我们会学习一些技巧来避开此类麻烦。什么是POSIX

早期的Unix可谓是“狂野的西部”,各个公司不断推陈出新,将操作系统引往不同的发展方向,同时还向客户保证所有这些新版本都彼此兼容,和其他Unix没什么两样。电气和电子工程师协会(IEEE)介入其中并通过各大Unix厂商的巨大努力,为Unix创建了一套名为“可移植操作系统接口”(portable operating system interface,POSIX)的标准定义,所有的商业和开源Unix实现都要以其作为衡量标准。POSIX操作系统本身是买不到的,不过你使用的Unix或GNU/Linux通常都兼容POSIX(尽管在GNU/Linux已成为事实标准的情况下,关于是否还需要POSIX标准的问题仍尚存争议)。

同时,即便是兼容POSIX的Unix实现之间也不尽相同。本章随后讨论的一个例子中就涉及了echo命令。某些版本的echo支持-n选项,该选项会禁用这个命令正常输出的末尾换行符(trailing newline)。其他版本的echo支持使用转义序列\c作为“不包含换行符”的一种特殊提示,还有部分版本根本无法阻止输出末尾出现的换行符。更有意思的是,有些Unix系统内建的shell命令echo会忽略-n和\c,而独立的可执行程序/bin/echo则理解这两者。这使得在shell脚本中提示输入变得很难办,因为脚本应该尽可能在多种Unix系统中表现一致。对于功能性脚本而言,重要的是让echo命令在不同的系统中以相同的方式工作。在随后的脚本#8中,我们将会看到如何在shell脚本中包装echo命令,从而创建出该命令的标准化版本。注意 本书中有些脚本利用了bash的某些特性,可能并非所有的POSIX兼容shell都支持这些特性。

好了,背景知识也了解够了,来看看我们的shell脚本库中都有哪些工具吧!脚本#1 在PATH中查找程序

使用环境变量(例如MAILER和PAGER)的shell脚本都有一个隐藏的危险:有些设置指向的程序可能并不存在。如果你以前没有碰到过这种环境变量,那么应该将MAILER设置成你喜欢的电子邮件程序(例如/usr/bin/mailx),将PAGER设置成可以分屏浏览长文档的程序。假如你为了实现灵活性,打算使用PAGER设置代替系统默认的分页程序(通常是more或less程序)来显示脚本输出,你该怎样确保环境变量PAGER的值是一个有效的程序?

第一个脚本解决的问题正是如何测试能否在用户的环境变量PATH中找到指定的程序。该脚本也很好地演示了包括脚本函数和变量切分(variable slicing)在内的各种shell脚本编写技术。代码清单1-1显示了如何验证路径是否有效。代码

代码清单1-1 shell脚本函数inpath #!/bin/bash # inpath -- 验证指定程序是否有效,或者能否在PATH目录列表中找到。 in_path() { # 尝试在环境变量PATH中找到给定的命令。如果找到,返回0; # 如果没有找到,则返回1。注意,该函数会临时修改IFS(内部字段分隔符), # 不过在函数执行完毕时会将其恢复原状。 cmd=$1 ourpath=$2 result=1 oldIFS=$IFS IFS=":" for directory in $ourpath do if [ -x $directory/$cmd ] ; then result=0 # 如果执行到此处,那么表明我们已经找到了该命令。 fi done IFS=$oldIFS return $result } checkForCmdInPath() { var=$1 if [ "$var" != "" ] ; then❶ if [ "${var:0:1}" = "/" ] ; then❷ if [ ! -x $var ] ; then return 1 fi❸ elif ! in_path $var "$PATH" ; then return 2 fi fi }

就像第0章中说过的那样,我们建议你在主目录中创建一个名为scripts的子目录,然后将该目录的完整路径添加到环境变量PATH中。使用echo $PATH查看PATH的当前值,然后编辑登录脚本的内容(.login、.profile、.bashrc、.bash_profile,具体是哪个脚本取决于所使用的shell)来修改PATH。更多细节参见0.3节。注意 如果你在终端中用ls命令列出文件,有些特殊文件(例如.bashrc或.bash_profile)可能一开始不会显示出来。这是因为像.bashrc这样以点号开始的文件名被文件系统认为是“隐藏文件”。(在Unix的最初期,这有点算是一个由bug转变成的特性。)要想列出目录中包括隐藏文件在内的所有文件,可以使用ls命令的-a选项。

值得重申一次的是,我们假设你使用bash作为所有脚本的shell。这个脚本的第一行(称为shebang)明确调用了/bin/bash。很多系统也支持使用/usr/bin/env bash在脚本运行时刻设置shebang。关于注释对于是否要详细讲解每个脚本的工作原理,我们也是费尽心思。有时候,我们会在代码之后解释一些不容易理解的代码片段,但一般会使用代码注释现场作解。可以在代码中查找以符号#起始的行,或是行中出现在#之后的部分。 你以后少不了要读别人写的脚本(当然不是我们的),练练通过阅读注释来弄明白脚本的来龙去脉是有好处的。在编写你自己的脚本时,写注释也是一个应该养成的良好习惯,这能够帮助你总结特定的代码片段所要实现的效果。工作原理

checkForCmdInPath能够正常工作的关键在于,区分只包含程序名的变量(例如echo)与包含程序完整路径和文件名的变量(例如/bin/echo)。它的做法是检查给定值的第一个字符是否为/。因此,我们需要把第一个字符与变量值的其余部分分离开。

注意,❶处的变量切分语法${var:0:1}是一种可以在字符串中指定子串的简写法,从偏移处开始,按照给定长度截取(如果没有提供长度,则返回余下的全部字符串)。例如,表达式${var:10}将会从第11个字符开始返回变量$var余下的值,而${var:10:5}则返回第11个到第15个字符。具体的含义可通过下面的代码来观察:$ var="something wicked this way comes..."$ echo ${var:10}wicked this way comes...$ echo ${var:10:6}wicked$

在代码清单1-1中,这个语法只是用来查看指定路径是否以斜线起始。只要确定传入脚本的路径包含起始斜线,就检查是否能在文件系统中找到该路径。如果路径开头是/,则假定给出的是绝对路径,然后使用bash操作符-x ❷检查其是否存在。否则,将该值交给函数inpath❸,看看能否在默认的环境变量PATH的各个目录中找到。运行脚本

要想以独立程序的形式运行这个脚本,首先需要在脚本底部加上一小段代码。这段代码负责获取用户输入并将其传给相应的函数。if [ $# -ne 1 ] ; then echo "Usage: $0 command" >&2 exit 1ficheckForCmdInPath "$1"case $? in 0 ) echo "$1 found in PATH" ;; 1 ) echo "$1 not found or not executable" ;; 2 ) echo "$1 not found in PATH" ;;esacexit 0

添加上面的代码之后,就可以直接调用脚本了,如接下来的“运行结果”所示。使用脚本完成工作之后,记得要把这段代码删除或注释掉,不然随后将其作为库函数使用时就乱套了。运行结果

在测试该脚本的时候,我们使用3个程序名来调用inpath:一个存在的程序、一个虽然存在但没有列入PATH中的程序,以及一个不存在但包含完整的合格文件名和路径的程序。代码清单1-2显示了测试结果。

代码清单1-2 测试inpath脚本$ inpath echoecho found in PATH$ inpath MrEchoMrEcho not found in PATH$ inpath /usr/bin/MrEcho/usr/bin/MrEcho not found or not executable

脚本中最后添加的那段代码将函数in_path的结果转换成了更易于阅读的文字,所以现在我们可以很容易地看到每种情况都按照预期得以处理。精益求精

如果你想在第一个脚本中就化身为代码忍者,可以将表达式${var:0:1}换成更为复杂的${var%${var#?}},后者是POSIX的变量切分写法。从外表上来看,这种写法嵌套了两个字符串切分。内部的${var#?}会提取变量var中除第一个字符之外的其余所有内容,其中#表示删除指定模式的第一处匹配,?是正则表达式,只匹配单个字符。

接下来,${var%pattern}会产生一个子串,其值为将指定模式从变量var中删除后所剩下的部分1。在这个例子中,被删除的模式正是内部字符串切分的结果,所以最后剩下的就是整个字符串的第一个字符。1准确地说,应该是从变量var的右侧开始,删除指定的模式。

如果POSIX写法看起来太吓人,大多数shell(包括bash、ksh和zsh)也支持我们在该脚本中采用的${varname:start:size}这种形式。

当然,如果这两种方法你都不喜欢,还可以调用$(echo $var | cut -c1)。在bash编程中,解决问题的手段不止一种,可以通过不同的方式从系统中提取、转换或载入数据。重要的是要意识到并理解“殊途同归”并不意味着不同的方法之间存在优劣之分。

如果你想创建一种能够区分自己是独立运行还是被其他函数所调用的脚本,可以考虑在脚本开始部分加上一个条件测试:if [ "$BASH_SOURCE" = "$0" ]

亲爱的读者,我们把它留给你作为一个练习,等做过一些实验后再编写剩余的片段。注意 脚本#47和这个脚本紧密相关。它会验证PATH中的各个目录以及用户登录环境中的环境变量。脚本#2 验证输入:仅限字母数字

用户总是无视操作指南,输入一些不一致、格式错误或语法有问题的数据。作为一名shell脚本开发人员,你得在这些错误引发问题之前将其找出并标记出来。

一种典型的情况涉及文件名和数据库键名。你的程序要提示用户输入的字符应该仅限字母数字,只能包含大写字母、小写字母和数字,不能有标点符号、特殊字符和空格。用户输入的字符串是否有效?这正是代码清单1-3要测试的。代码

代码清单1-3 脚本validalnum #!/bin/bash # validAlphaNum -- 确保输入内容仅限于字母和数字。 validAlphaNum() { # 返回值:如果输入内容全部都是字母和数字,那么返回0;否则,返回1。 # 删除所有不符合要求的字符。❶ validchars="$(echo $1 | sed -e 's/[^[:alnum:]]//g')"❷ if [ "$validchars" = "$1" ] ; then return 0 else return 1 fi } # 主脚本开始 -- 如果要将该脚本包含到其他脚本之内,那么删除或注释掉本行以下的所有内容。 # ================= /bin/echo -n "Enter input: " read input # 输入验证。 if ! validAlphaNum "$input" ; then echo "Your input must consist of only letters and numbers." >&2 exit 1 else echo "Input is valid." fi exit 0工作原理

这个脚本的逻辑直截了当。首先,使用基于sed的转换移除输入中所有的无效字符,创建一个新版本的输入信息❶。然后,将结果与最初的输入相比较❷。如果两者相同,那么皆大欢喜;如果不同,则说明在转换过程中被移除的属于不可接受的字符(字母和数字),输入无效。

这种做法奏效的原因在于sed会删除所有不在[:alnum:]中的字符,[:alnum:]是POSIX的正则表达式简写,代表所有的字母数字字符。如果转换后的值和最初的输入不一致,就意味着输入字符串中存在非字母数字字符,从而表明输入无效。该函数会返回非0值表明这一情况。记住,我们只接受ASCII文本。运行脚本

这是一个独立的脚本。它提示用户输入,然后告诉用户输入是否有效。函数validAlphaNum更为典型的用法是将其复制并粘贴到其他shell脚本的顶部,或是像脚本#12那样以库的形式引用该函数。

validalnum也很好地演示了一种通用的shell脚本编程技术。编写函数,然后在将其并入更大、更复杂的脚本之前先进行测试。这样做能为你免去不少麻烦。运行结果

shell脚本validalnum用起来很简单,它会让用户输入要验证的字符串。代码清单1-4展示了该脚本是如何处理有效输入和无效输入的。

代码清单1-4 测试validalnum脚本$ validalnumEnter input: valid123SAMPLEInput is valid.$ validalnumEnter input: this is most assuredly NOT valid, 12345Please enter only letters and numbers.精益求精

因为灵活性好,所以这种“删除有问题的字符,然后看看剩下什么”的方法很受用,特别要记得将输入变量和匹配模式(或是干脆没有模式)放入双引号中,以避免空输入错误。空模式在脚本编程中一直都是个问题,因为它会使有效的条件测试变成一个产生错误信息的不完整语句。零长度的引用字符串和空白是不一样的,牢记这句话,你肯定不会吃亏。如果除了大写字母之外,还需要空格、逗号和点号,该怎么办?修改❶处的替换模式就可以了:sed 's/[^[:upper:] ,.]//g'

你也可以像下面这样验证电话号码输入(允许整数值、空格、括号和连字符,但是不允许前导空格或连续多个空格):sed 's/[^- [:digit:]\(\)]//g'

但如果你想限制输入仅为整数值,一定要小心陷阱。例如,你可能会这样做:sed 's/[^[:digit:]]//g'

这行代码处理正数没有问题,但要是你还想处理负数呢?如果你仅在有效字符集中加入一个负号,那-3-4也算是有效输入了,但它显然不是一个合法的整数。脚本#5讨论了如何处理负数。脚本#3 规范日期格式

shell脚本开发存在的一个问题是各种不一致的数据格式。规范数据格式的难度可小可大。数据格式算是其中最有挑战性的工作之一,这是因为指定日期的方法各种各样。哪怕是提示过特定的格式,例如按照“月-日-年”,照样有可能得到不一致的输入:月份没有采用数字,而是用了月份名称或月份名称缩写,甚至还有全部是大写字母的月份全称。有鉴于此,一个能够规范日期的函数,尽管本身很基础,却能在后续的脚本编写工作中帮上大忙,尤其是在脚本#7中。代码

代码清单1-5可以对符合一组简单要求的日期进行规范:月份要么采用名称,要么采用1至12之间的值;年份必须采用4位数的值。规范后的日期由月份名称(3个字母的缩写)、天数以及4位数的年份组成。

代码清单1-5 shell脚本normdate #!/bin/bash # normdate -- 将月份规范成3个字母,首字母大写。 # 该脚本随后将作为脚本#7的辅助函数。 # 如果没有错误,那么以0值退出。 monthNumToName() { # 将变量month设置为相应的值。 case $1 in 1 ) month="Jan" ;; 2 ) month="Feb" ;; 3 ) month="Mar" ;; 4 ) month="Apr" ;; 5 ) month="May" ;; 6 ) month="Jun" ;; 7 ) month="Jul" ;; 8 ) month="Aug" ;; 9 ) month="Sep" ;; 10) month="Oct" ;; 11) month="Nov" ;; 12) month="Dec" ;; * ) echo "$0: Unknown numeric month value $1" >&2 exit 1 esac return 0 } # 主脚本开始 -- 如果要将该脚本包含到其他脚本之内,那么删除或注释掉本行以下的所有内容。 # ================= # 输入验证。 if [ $# -ne 3 ] ; then echo "Usage: $0 month day year" >&2 echo "Formats are August 3 1962 and 8 3 1962" >&2 exit 1 fi if [ $3 -le 99 ] ; then echo "$0: expected 4-digit year value." >&2 exit 1 fi # 输入的月份是否为数字?❶ if [ -z $(echo $1|sed 's/[[:digit:]]//g') ]; then monthNumToName $1 else # 规范前3个字母,首字母大写,其余小写。❷ month="$(echo $1|cut -c1|tr '[:lower:]' '[:upper:]')"❸ month="$month$(echo $1|cut -c2-3 | tr '[:upper:]' '[:lower:]')" fi echo $month $2 $3 exit 0工作原理

注意脚本中的第三个条件语句❶。它移除了第一个输入字段中的所有数字,然后使用-z检测结果是否为空。如果为空,则意味着该字段中只有数字,可以使用函数monthNumToName将其直接映射成月份名称,该函数还会验证数字代表的月份是否有效。否则,我们假定字段中包含的是月份字符串,这得借助两个子shell(包含在$(和)之间的命令序列,其中的命令以及$()会被命令输出所替换),通过复杂的cut和tr管道来实现规范化。

第一个子shell❷只提取出输入的首个字符,然后使用tr将其转换成大写(命令序列echo $1|cut -c1也可以写成POSIX形式的${1%${1#?}})。第二个子shell❸提取出后两个字符并将其转换成小写,这样就生成了首字母大写的月份缩写(长度为3个字符)。注意,这种字符串操作方法并没有检查输入的月份是否有效,这一点和月份为数字时的处理并不一样。运行脚本

为了确保以后引入了normdate功能的脚本拥有最大的灵活性,这个脚本被设计成以3个命令行参数的形式接收输入,如代码清单1-6所示。如果你只打算将其用于交互模式,那就得提示用户输入这些参数,不过这样一来,要想在其他脚本中调用normdate就更困难了。运行结果

代码清单1-6 测试normdate脚本$ normdate 8 3 62normdate: expected 4-digit year value.

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载