Linux/Unix技术丛书
跟老男孩学Linux运维:
Shell编程实战
老男孩 著
图书在版编目(CIP)数据
跟老男孩学Linux运维:Shell编程实战 / 老男孩著. —北京:机械工业出版社,2017.1
(Linux/Unix技术丛书)
ISBN 978-7-111-55607-7
I. 跟… II. 老… III. Linux操作系统 IV. TP316.85
中国版本图书馆CIP数据核字(2016)第313248号
跟老男孩学Linux运维:Shell编程实战
出版发行:机械工业出版社(北京市西城区百万庄大街22号 邮政编码:100037)
责任编辑:杨绣国 张梦玲 责任校对:董纪丽
印 刷:北京文昌阁彩色印刷有限责任公司 版 次:2017年1月第1版第1次印刷
开 本:186mm×240mm 1/16 印 张:26.25
书 号:ISBN 978-7-111-55607-7 定 价:89.00元
凡购本书,如有缺页、倒页、脱页,由本社发行部调换
客服热线:(010)88379426 88361066 投稿热线:(010)88379604
购书热线:(010)68326294 88379649 68995259 读者信箱:hzit@hzbook.com
版权所有 ? 侵权必究
封底无防伪标均为盗版
本书法律顾问:北京大成律师事务所 韩光/邹晓东
前言
为什么要写这本书
目前全球正处于互联网+的时代,越来越多的传统企业都在通过互联网提供产品和服务,比如,互联网+教育、互联网+金融、互联网+电商、互联网+出租车、互联网+保险等,可以看到,几乎所有的产品、服务都能在网上找到。而支撑互联网的幕后英雄其实就是Linux(包括移动互联网在内),掌握Linux运维技术已经成为每一个IT技术人员的必备技能!
互联网+的时代下企业的网站流量呈爆炸式增长,如果你是运维人员,很可能要面对几十台、几百台、上千台甚至上万台的服务器设备,而对于企业来说,如何提高IT运维的管理效率、降低成本也成了最大问题。要解决这个问题,必须在Linux运维工作中,做好运维服务的标准化、规范化、流程化和自动化,而这里面的前三项其实是在为最后一项“IT运维自动化”做铺垫。
要实现IT运维自动化就需要学会编程语言,目前Linux系统下最流行的运维自动化语言就是Shell和Python(Python相关图书,作者正在写作中)。在这两者之中,Shell又几乎是所有IT企业都必须使用的运维自动化编程语言,特别是在运维工作中的服务监控、业务快速部署、服务启动停止、数据备份及处理、日志分析等环节里,Shell必不可少。当然Python也是一门很好的自动化编程语言,它和Shell是互补的,Shell更适合系统底层,而Python则更适合处理复杂的业务逻辑,以及开发复杂的运维软件工具,实现通过Web访问等。
在长期的运维工作以及深度教学中,老男孩发现很多Linux入门人员很害怕Shell编程,觉得Shell不好掌握,甚至是已经工作的企业运维人员对Shell编程也是一知半解,不能熟练运用。而市面上的Shell图书大多如出一辙,理论多,实战少。因此在众多学员和网友的关注和提议下,老男孩决定写一本比较与众不同的偏重实战的Shell编程书籍,相信本书一定会让众多读者受益,提升个人在企业工作中的效率,达到加薪升职的目的。
本书是老男孩Linux运维实战系列的第二本书,第一本是《跟老男孩学习Linux运维:Web集群实践》(已由机械工业出版社出版),第三本是《跟老男孩学习Linux运维:三剑客命令实战》(预计书名),此书将在几个月后和大家见面。更多Linux运维实战系列的图书在持续写作中,敬请期待。
读者对象
热衷于IT运维自动化的人员
Linux系统管理员和运维工程师
互联网网站开发及数据库管理人员
网络管理员和项目实施工程师
Linux相关售前售后技术工程师
开设Linux相关课程的大中专院校
对Linux及Shell编程感兴趣的人员
如何阅读本书
本书是一本较完整的Shell编程实战型图书,并非大而全,但处处可以体现实战二字,大多内容取于企业实战,并结合老男孩十几年的运维工作和教学工作进行了梳理。本书从脉络上可分为五大部分:
第一部分为Shell编程基础篇(第1章~第4章),着重介绍新手如何学好Shell编程,涉及的内容包括Shell编程的入门介绍、基础知识、运行原理、编程语法、编程习惯、变量知识以及变量的深入实践。读者学完此部分,将会具备一个学好Shell编程的坚实基础。
第二部分为初中级的实战知识和技能篇(第5章~第8章),着重讲解变量的多种数值运算、条件测试与比较、if条件判断语句、Shell函数等相关的知识,并给出了企业实战技巧和案例。本部分是学好Shell编程的重中之重,读者必须掌握。
第三部分为Shell中高级实战知识和技能篇(第9章~第13章),着重讲解case条件语句、while循环和until型循环、for循环和select循环、条件与循环控制及状态返回值、Shell数组等知识,以及相应的实战技巧和案例。本部分同样是学好Shell编程的重中之重,读者必须掌握。
第四部分为高效Shell编程必备知识篇(第14章~第16章),着重讲解Shell脚本开发规范与编码习惯、Shell脚本的调试知识和技巧、Shell脚本开发环境的配置调整和优化等。
第五部分为Shell特殊应用及企业面试、实战案例篇(第17章~第19章),着重讲解Linux信号及trap命令的企业应用实践、Expect自动化交互式程序的应用实践,以及能体现全书所讲技术的面试题和企业实战案例,让真正的Shell全自动化运维成为可能。
最后一章补充讲解了大家易感困惑的子Shell知识及应用实践内容。
勘误和支持
由于作者所授的培训课程排期很紧,课程较多,全书内容基本上都是利用早晨和夜里的时间完成写作的。限于作者的水平和能力,加之编写的时间仓促,书中难免有疏漏和不当之处,恳请读者批评指正。你可以将书中的错误发布在专门为本书准备的博客地址评论处(http://oldboy.blog.51cto.com/2561410/1865956或微博http://weibo.com/oldboy8)。同时不管你遇到何种问题,都可以加入我为本书提供的QQ交流群204041129(验证信息:Shell书籍),我将尽力为你提供最满意的解答。书中所需的工具及源文件也将发布在的博客网站上(书中大部分章节结尾都给出了相关网址及二维码),我也会将相应功能的更新及时发布出来。如果你有更多的宝贵意见,也欢迎发送邮件至邮箱oldboy@oldboyedu.com,很期待能够听到你们的真挚反馈。
致谢
感谢犹金毅、何清等为本书贡献第20章的重要底稿内容及对本书的写作给予的支持。
感谢孔令飞为本书第19章贡献有趣的girlLove案例内容及对本书的写作给予的支持。
感谢老男孩IT教育的每一位在校学员,是你们自觉努力的学习,使得我有较多的时间持续写作,特别是运维30-31期150位学员参与了本书的校稿。感谢你们对老男孩老师的支持。
感谢老男孩IT教育里每一个班级的助教、班主任、班长及班干部,感谢你们替我分担老男孩IT教育众多学员的答疑、辅导、批改作业及班级管理工作。
感谢我的同事——老男孩教育Python学院的Alex老师、武老师,云计算与自动化架构班的赵班长老师,Linux+Python高薪运维班的李泳谊、张耀等老师,以及其他未提及名字的众多老师,正是你们辛勤努力的工作,让我得以有时间完成此书。
一如既往地感谢中网志腾的郭威总经理和数码创天的王斐总经理及梁露女士,感谢你们提供优质的DELL服务器资源,使得本书得以高效顺利地完成!
感谢森华易腾的陆锦云女士及其同事,感谢你们提供的优质IDC机房带宽支持,使得本书得以顺利完成!
感谢机械工业出版社华章公司的编辑杨绣国,感谢你的支持、包容和鼓励,正是你的鼓励和帮助引导我顺利完成全部书稿。
感谢没有提及名字的所有学生、网友以及关注老男孩的每一位友人、朋友。
最后要感谢我的父母、家人,正是你们的支持和体谅,让我有无限信心和力量去写作,并最终完成此书!
谨以此书,献给支持老男孩IT教育的每一位朋友、学员及众多热爱Linux运维技术的朋友。
老男孩老师
北京,2016年11月
目录
前 言
第1章 如何才能学好Shell编程 / 1
1.1 为什么要学习Shell编程 / 1
1.2 学好Shell编程所需的基础知识 / 1
1.3 如何才能学好Shell编程之“老鸟”经验谈 / 3
1.4 学完本书后可以达到何种Shell编程高度 / 5
第2章 Shell脚本初步入门 / 6
2.1 什么是Shell / 6
2.2 什么是Shell脚本 / 7
2.3 Shell脚本在Linux运维工作中的地位 / 8
2.4 脚本语言的种类 / 9
2.4.1 Shell脚本语言的种类 / 9
2.4.2 其他常用的脚本语言种类 / 10
2.4.3 Shell脚本语言的优势 / 11
2.5 常用操作系统默认的Shell / 11
2.6 Shell脚本的建立和执行 / 12
2.6.1 Shell脚本的建立 / 12
2.6.2 Shell脚本的执行 / 15
2.6.3 Shell脚本开发的基本规范及习惯 / 19
第3章 Shell变量的核心基础知识与实践 / 22
3.1 什么是Shell变量 / 22
3.2 环境变量 / 23
3.2.1 自定义环境变量 / 26
3.2.2 显示与取消环境变量 / 28
3.2.3 环境变量初始化与对应文件的生效顺序 / 30
3.3 普通变量 / 31
3.3.1 定义本地变量 / 31
3.3.2 变量定义及变量输出说明 / 35
3.4 变量定义技巧总结 / 40
第4章 Shell变量知识进阶与实践 / 41
4.1 Shell中特殊且重要的变量 / 41
4.1.1 Shell中的特殊位置参数变量 / 41
4.1.2 Shell进程中的特殊状态变量 / 47
4.2 bash Shell内置变量命令 / 52
4.3 Shell变量子串知识及实践 / 55
4.3.1 Shell变量子串介绍 / 55
4.3.2 Shell变量子串的实践 / 56
4.3.3 变量子串的生产场景应用案例 / 59
4.4 Shell特殊扩展变量的知识与实践 / 60
4.4.1 Shell特殊扩展变量介绍 / 60
4.4.2 Shell特殊扩展变量的实践 / 61
4.4.3 Shell特殊扩展变量的生产场景应用案例 / 63
第5章 变量的数值计算实践 / 65
5.1 算术运算符 / 65
5.2 双小括号“(())”数值运算命令 / 66
5.2.1 双小括号“(())”数值运算的基础语法 / 66
5.2.2 双小括号“(())”数值运算实践 / 66
5.3 let运算命令的用法 / 73
5.4 expr命令的用法 / 75
5.4.1 expr命令的基本用法示例 / 75
5.4.2 expr的企业级实战案例详解 / 76
5.5 bc命令的用法 / 81
5.6 awk实现计算 / 83
5.7 declare(同typeset)命令的用法 / 83
5.8 $[]符号的运算示例 / 83
5.9 基于Shell变量输入read命令的运算实践 / 84
5.9.1 read命令基础 / 84
5.9.2 以read命令读入及传参的综合企业案例 / 87
第6章 Shell脚本的条件测试与比较 / 92
6.1 Shell脚本的条件测试 / 92
6.1.1 条件测试方法综述 / 92
6.1.2 test条件测试的简单语法及示例 / 93
6.1.3 [](中括号)条件测试语法及示例 / 94
6.1.4 [[]]条件测试语法及示例 / 95
6.2 文件测试表达式 / 97
6.2.1 文件测试表达式的用法 / 97
6.2.2 文件测试表达式举例 / 97
6.2.3 特殊条件测试表达式案例 / 101
6.3 字符串测试表达式 / 102
6.3.1 字符串测试操作符 / 102
6.3.2 字符串测试生产案例 / 104
6.4 整数二元比较操作符 / 105
6.4.1 整数二元比较操作符介绍 / 105
6.4.2 整数变量测试实践示例 / 107
6.5 逻辑操作符 / 108
6.5.1 逻辑操作符介绍 / 108
6.5.2 逻辑操作符实践示例 / 110
6.5.3 逻辑操作符企业案例 / 112
6.6 测试表达式test、[]、[[]]、(())的区别总结 / 120
第7章 if条件语句的知识与实践 / 121
7.1 if条件语句 / 121
7.1.1 if条件语句的语法 / 121
7.1.2 if条件语句多种条件表达式语法 / 125
7.1.3 单分支if条件语句实践 / 126
7.1.4 if条件语句的深入实践 / 130
7.2 if条件语句企业案例精讲 / 132
7.2.1 监控Web和数据库的企业案例 / 132
7.2.2 比较大小的经典拓展案例 / 142
7.2.3 判断字符串是否为数字的多种思路 / 143
7.2.4 判断字符串长度是否为0的多种思路 / 145
7.2.5 更多的生产场景实战案例 / 145
第8章 Shell函数的知识与实践 / 151
8.1 Shell函数的概念与作用介绍 / 151
8.2 Shell函数的语法 / 152
8.3 Shell函数的执行 / 152
8.4 Shell函数的基础实践 / 153
8.5 利用Shell函数开发企业级URL检测脚本 / 155
8.6 利用Shell函数开发一键优化系统脚本 / 158
8.7 利用Shell函数开发rsync服务启动脚本 / 166
第9章 case条件语句的应用实践 / 169
9.1 case条件语句的语法 / 169
9.2 case条件语句实践 / 171
9.3 实践:给输出的字符串加颜色 / 176
9.3.1 给输出的字符串加颜色的基础知识 / 176
9.3.2 结合case语句给输出的字符串加颜色 / 177
9.3.3 给输出的字符串加背景颜色 / 180
9.4 case语句企业级生产案例 / 181
9.5 case条件语句的Linux系统脚本范例 / 187
9.6 本章小结 / 191
第10章 while循环和until循环的应用实践 / 192
10.1 当型和直到型循环语法 / 192
10.1.1 while循环语句 / 192
10.1.2 until循环语句 / 193
10.2 当型和直到型循环的基本范例 / 194
10.3 让Shell脚本在后台运行的知识 / 195
10.4 企业生产实战:while循环语句实践 / 206
10.5 while循环按行读文件的方式总结 / 210
10.6 企业级生产高级实战案例 / 211
10.7 本章小结 / 215
第11章 for和select循环语句的应用实践 / 217
11.1 for循环语法结构 / 217
11.2 for循环语句的基础实践 / 219
11.3 for循环语句的企业级案例 / 222
11.4 for循环语句的企业高级实战案例 / 230
11.5 Linux系统产生随机数的6种方法 / 239
11.6 select循环语句介绍及语法 / 241
11.7 select循环语句案例 / 242
第12章 循环控制及状态返回值的应用实践 / 249
12.1 break、continue、exit、return的区别和对比 / 249
12.2 break、continue、exit功能执行流程图 / 249
12.3 break、continue、exit、return命令的基础示例 / 251
12.4 循环控制及状态返回值的企业级案例 / 253
第13章 Shell数组的应用实践 / 260
13.1 Shell数组介绍 / 260
13.1.1 为什么会产生Shell数组 / 260
13.1.2 什么是Shell数组 / 260
13.2 Shell数组的定义与增删改查 / 261
13.2.1 Shell数组的定义 / 261
13.2.2 Shell数组的打印及输出 / 262
13.3 Shell数组脚本开发实践 / 265
13.4 Shell数组的重要命令 / 267
13.5 Shell数组相关面试题及高级实战案例 / 268
13.6 合格运维人员必会的脚本列表 / 277
第14章 Shell脚本开发规范 / 279
14.1 Shell脚本基本规范 / 279
14.2 Shell脚本变量命名及引用变量规范 / 281
14.3 Shell函数的命名及函数定义规范 / 282
14.4 Shell脚本(模块)高级命名规范 / 283
14.5 Shell脚本的代码风格 / 283
14.5.1 代码框架 / 283
14.5.2 缩进规范 / 284
14.6 Shell脚本的变量及文件检查规范 / 285
第15章 Shell脚本的调试 / 286
15.1 常见Shell脚本错误范例 / 286
15.1.1 if条件语句缺少结尾关键字 / 286
15.1.2 循环语句缺少关键字 / 287
15.1.3 成对的符号落了单 / 287
15.1.4 中括号两端没空格 / 288
15.1.5 Shell语法调试小结 / 289
15.2 Shell脚本调试技巧 / 289
15.2.1 使用dos2unix命令处理在Windows下开发的脚本 / 289
15.2.2 使用echo命令调试 / 290
15.2.3 使用bash命令参数调试 / 291
15.2.4 使用set命令调试部分脚本内容 / 294
15.2.5 其他调试Shell脚本的工具 / 296
15.3 本章小结 / 296
第16章 Shell脚本开发环境的配置和优化实践 / 297
16.1 使用vim而不是vi编辑器 / 297
16.2 配置文件.vimrc的重要参数介绍 / 298
16.3 让配置文件.vimrc生效 / 304
16.4 使用vim编辑器进行编码测试 / 304
16.4.1 代码自动缩进功能 / 304
16.4.2 代码颜色高亮显示功能说明 / 304
16.5 vim配置文件的自动增加版权功能 / 305
16.6 vim配置文件的代码折叠功能 / 305
16.7 vim编辑器批量缩进及缩进调整技巧 / 305
16.8 其他vim配置文件功能说明 / 307
16.9 vim编辑器常用操作技巧 / 307
第17章 Linux信号及trap命令的企业应用实践 / 310
17.1 信号知识 / 310
17.1.1 信号介绍 / 310
17.1.2 信号列表 / 310
17.2 使用trap控制信号 / 311
17.3 Linux信号及trap命令的生产应用案例 / 313
第18章 Expect自动化交互式程序应用实践 / 317
18.1 Expect介绍 / 317
18.1.1 什么是Expect / 317
18.1.2 为什么要使用Expect / 317
18.2 安装Expect软件 / 318
18.3 小试牛刀:实现Expect自动交互功能 / 318
18.4 Expect程序自动交互的重要命令及实践 / 319
18.4.1 spawn命令 / 320
18.4.2 expect命令 / 320
18.4.3 send命令 / 323
18.4.4 exp_continue命令 / 324
18.4.5 send_user命令 / 324
18.4.6 exit命令 / 325
18.4.7 Expect常用命令总结 / 325
18.5 Expect程序变量 / 326
18.5.1 普通变量 / 326
18.5.2 特殊参数变量 / 326
18.6 Expect程序中的if条件语句 / 327
18.7 Expect中的关键字 / 329
18.7.1 eof关键字 / 329
18.7.2 timeout关键字 / 329
18.8 企业生产场景下的Expect案例 / 330
18.8.1 批量执行命令 / 330
18.8.2 批量发送文件 / 332
18.8.3 批量执行Shell脚本 / 334
18.8.4 自动化部署SSH密钥认证+ansible的项目实战 / 337
18.9 本章小节 / 339
第19章 企业Shell面试题及企业运维实战案例 / 340
19.1 企业Shell面试题案例 / 340
19.1.1 面试题1:批量生成随机字符文件名 / 340
19.1.2 面试题2:批量改名 / 341
19.1.3 面试题3:批量创建特殊要求用户 / 342
19.1.4 面试题4:扫描网络内存活主机 / 342
19.1.5 面试题5:解决DOS攻击 / 343
19.1.6 面试题6:MySQL数据库分库备份 / 344
19.1.7 面试题7:MySQL数据库分库分表备份 / 344
19.1.8 面试题8:筛选符合长度的单词 / 344
19.1.9 面试题9:MySQL主从复制异常监控 / 344
19.1.10 面试题10:比较整数大小 / 344
19.1.11 面试题11:菜单自动化软件部署 / 344
19.1.12 面试题12:Web及MySQL服务异常监测 / 345
19.1.13 面试题13:监控Memcached缓存服务 / 345
19.1.14 面试题14:开发脚本实现入侵检测与报警 / 346
19.1.15 面试题15:开发Rsync服务启动脚本 / 349
19.1.16 面试题16:开发MySQL多实例启动脚本 / 349
19.1.17 面试题17:开发学生实践抓阄脚本 / 351
19.1.18 面试题18:破解RANDOM随机数 / 353
19.1.19 面试题19:批量检查多个网站地址是否正常 / 354
19.1.20 面试题20:单词及字母去重排序 / 355
19.1.21 面试题21:开发脚本管理服务端LVS / 357
19.1.22 面试题22:LVS节点健康检查及管理脚本 / 359
19.1.23 面试题23:LVS客户端配置脚本 / 360
19.1.24 面试题24:模拟keepalived软件高可用 / 361
19.1.25 面试题25:编写正(或长)方形图形 / 362
19.1.26 面试题26:编写等腰三角形图形字符 / 363
19.1.27 面试题27:编写直角梯形图形字符 / 364
19.1.28 面试题28:51CTO博文爬虫脚本 / 365
19.1.29 面试题29:Nginx负载节点状态监测 / 366
19.2 Shell经典程序案例:哄老婆和女孩的神器 / 369
19.2.1 功能简介 / 369
19.2.2 使用方法 / 369
19.2.3 girlLove工具内容模板 / 370
19.2.4 girlLove工具的Shell源码注释 / 371
19.2.5 girlLove最终结果展示 / 376
第20章 子Shell及Shell嵌套模式知识应用 / 377
20.1 子Shell的知识及实践说明 / 377
20.1.1 什么是子Shell / 377
20.1.2 子Shell的常见产生途径及特点 / 378
20.2 子Shell在企业应用中的“坑” / 383
20.2.1 使用管道与while循环时遭遇的“坑” / 383
20.2.2 解决while循环遭遇的“坑” / 385
20.3 Shell调用脚本的模式说明 / 386
20.3.1 fork模式调用脚本知识 / 386
21.3.2 exec模式调用脚本 / 386
21.3.3 source模式调用脚本 / 387
20.4 Shell调用脚本的3种不同实践方法 / 387
20.4.1 开发测试不同模式区别的Shell脚本 / 387
20.4.2 对比fork模式与source模式的区别 / 390
20.4.3 对比exec模式与source模式的区别 / 391
20.5 Shell调用脚本3种不同模式的应用场景 / 391
附 录 Linux重要命令汇总 / 393
第1章
如何才能学好Shell编程
1.1 为什么要学习Shell编程
Shell脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具,Linux/UNIX系统的底层及基础应用软件的核心大都涉及Shell脚本的内容。每一个合格的Linux系统管理员或运维工程师,都需要能够熟练地编写Shell脚本语言,并能够阅读系统及各类软件附带的Shell脚本内容。只有这样才能提升运维人员的工作效率,适应日益复杂的工作环境,减少不必要的重复工作,从而为个人的职场发展奠定较好的基础。那么,Shell脚本编程的学习是否容易呢?学习Shell编程到底需要什么样的Linux基础呢?
1.2 学好Shell编程所需的基础知识
本节首先来探讨一下在学习Shell编程之前需要掌握的基础知识,需要说明的是,并不是必须具备这些基础知识才可以学习Shell编程,而是,如果具备了这些基础知识,那么就可以把Shell编程学得更好,领悟得更深。如果只是想简单地了解Shell脚本语言,那么就无须掌握太多的系统基础知识,只需要会一些简单的命令行操作即可。
学好Shell编程并通过Shell脚本轻松地实现自动化管理企业生产系统的必备基础如下:
1)能够熟练使用vim编辑器,熟悉SSH终端及“.vimrc”等的配置。
在Linux下开发Shell脚本最常使用的编辑器是vim,因此如果能够熟练使用并配置好vim的各种高级功能设置,就可以让开发Shell脚本达到事半功倍的效果。这部分内容在本书的第16章有相应的讲解,读者在开始编写脚本之前可以考虑先看看第16章并搭建出高效的Shell开发环境。
说明: 在本书的第16章讲解Shell脚本开发环境的配置调整和优化时,提到了高效搭建Shell开发环境的方法,之所以把这部分内容安排在第16章,是希望读者能体验一下比较原始的Shell开发过程,然后再来掌握搭建高效的开发环境的方法,老男孩从教学的角度认为这是一个比较好的过程,读者可以根据自身的情况来决定要不要提前学习第16章,搭建好高效的Shell开发环境。
2)要有一定的Linux命令基础,至少需要掌握80个以上Linux常用命令,并能够熟练使用它们(Linux系统的常用命令请参见本书的附录)。
和其他的开发语言(例如Python)不同,Shell脚本语言很少有可以直接使用的外部函数库,老男孩就将Linux系统的命令看作Shell的函数库,因此,对Linux系统常用命令的掌握程度就直接决定了运维人员对Shell脚本编程的掌握高度。一些Shell类图书在开篇花费大量章节来讲解Linux基础命令也许就是因为这点,本书主要侧重于Shell编程企业案例实战讲解,因此不会进行大且全的介绍,也不会过多地讲解Linux的常用命令,而是采用小而美的实战策略,本书结尾会以附录的形式给出常用的Linux基础命令的相关知识。此外,如果读者想学习Linux基础命令,可以关注老男孩即将出版的新书——《跟老男孩学习Linux运维:常用命令实战》,或者其他相关图书。
3)要熟练掌握Linux正则表达式及三剑客命令(grep、sed、awk)。
Linux正则表达式及三剑客命令(grep、sed、awk)是Linux系统里所有命令中最核心的3个命令,每个命令加上正则表达式的知识后,功能都会变得异常强大。如果能够掌握它们,就可以在编写Shell脚本时轻松很多。如读者想学习这部分知识,可以关注老男孩即将出版的新书——《跟老男孩学习Linux运维:三剑客命令实战》,或者其他相关图书。
4)熟悉常见的Linux网络服务部署、优化、日志分析及排错。
学习Shell编程最直接的目的就是在工作中对系统及服务等进行自动化管理,因此,如果不熟悉工作中的网络服务,就会很难使用Shell编程处理这些服务;如果不掌握网络服务等知识,就会让Shell开发者的能力大打折扣,甚至学习到的仅仅是Shell的语法及简单的基础,那么想要学好Shell编程的想法也就落空了。需要掌握的基础网络服务包括但不限于:Crond、Rsync、Inotify、Nginx、PHP、MySQL、Keepalived、Memcached、Redis、NFS、Iptables、SVN、Git,老男孩IT教育的老师在教学的过程中也是先讲解Linux常用命令和系统网络服务,然后再讲解Shell编程,目的就是不要让学员仅仅掌握Shell的语法皮毛,而是让他们能在学完Shell编程之后,自动搭建中型集群架构等,有关基础网络服务的知识可以参考机械工业出版社的《跟老男孩学习Linux运维:Web集群实战》一书,或者其他相关图书。
1.3 如何才能学好Shell编程之“老鸟”经验谈
学好Shell编程的核心:多练→多思考→再练→再思考,坚持如此循环即可!
从老男孩IT教育毕业的一名学生曾在工作多年后返校分享了一篇“如何学好Shell编程”的讲稿,经过老男孩的整理后和读者分享如下。
(1)掌握Shell脚本基本语法的方法
最简单有效的方法就是将语法敲n+1遍。为什么不是n遍呢?因为这里的n指的是你刚开始为掌握语法而练习的那些天(21天法则),而1则是指在确定掌握语法后每天都要写一写、想一想,至少是要看一看,保持一个与Shell脚本接触的热度。
(2)掌握Shell脚本的各种常见语法
要掌握各类条件表达式、if多种判断、for循环的不同用法、While多种读文件的循环等,这样做不是为了什么都学会,而是为了能够看懂别人写的代码。掌握常见的各种语法,也就是要经常写,而且要持续写一段时间(让动作定型,在大脑和肌肉里都打上深刻烙印),各种语法都要用。
(3)形成自己的脚本开发风格
当掌握了各种常见的语法之后,就要选定一种适合自己的语法,形成自己的开发风格,例如:if语句的语法就只用一种,条件表达式的语法只用一种,函数的写法也只用一种,有些语法需要根据场景去选择,除非你是像师傅(老男孩)一样要教学育人。否则,没有必要什么语法都掌握。在解决问题的前提下,掌握一种语法,然后将其用精、用透就是最好的,切记横向贪多,要多纵深学习。
(4)从简单做起,简单判断,简单循环
初学者一定要从简单做起,最小化代码学习,简单判断,简单循环,简单案例练习,所有的大程序都是由多个小程序组成的,因此,一开始没必要写多大的程序,免得给自己带来过多的挫败感,形成编程恐惧症。可先通过小的程序培养兴趣及成就感,到碰到大的程序时,即使遇到困难也能坚持下去了。
(5)多模仿,多放下参考资料练习,多思考
多找一些脚本例子来仔细分析一下,或者是系统自带的,或者是别人写的(本书就包含大量例子),不要只看,看着会并不是真的会。当你闭上眼睛的时候,还能完整地回忆起来,甚至还能完整口述或手写出来才是真的会。
(6)学会分析问题,逐渐形成编程思维
在编写程序或脚本时,先将需求理解透,对大的需求进行分解,逐步形成小的程序或模块,然后再开发,或者先分析最终需求的基础实现,最后逐步扩展批量实现。例如师傅(老男孩)在编写批量关闭不需要自启动服务的脚本时,就采用了这种分析方法,思路如下:
1)掌握关闭一个服务的命令,即“chkconf?ig服务名off”。
2)批量处理时,会有多个服务名,那么就要用到多条以上的命令。
3)仔细分析以上命令,会发现需要处理的所有命令中,只有“服务名”不同,其他地方都一样,那么自然就会想到用循环语句来处理。
如果是你,能想到这些吗?若是想到了,则表示你已经形成了初级的编程思维了,恭喜你。
如果你能够通过分析将一个大的需求细分为各个小的单元,然后利用函数、判断、循环、命令等实现每一个小的单元,那么最后把所有程序组合起来就是一个大的脚本程序了。
如果达到了上述的水平,你就算会编程了,对于领导提出的需求,就能够进行合理的分解,只要在机器上多进行调试,相信一定能写出来。
(7)编程变量名字要规范,采用驼峰语法表示
oldboyAgeName用的就是驼峰表示法。记住,在学习的初期,不要去看大的脚本,要从小问题和小的方面着手,当你觉得小的判断、循环等在你的脑子里瞬间就能出来时,再开始去看和写大的脚本,进行深入练习。
师傅(老男孩)常说,新手初期最好的学习方法就是多敲代码,并针对问题进行分解练习,多敲代码就是让自己养成一个编程习惯,使肌肉、视觉和思维形成记忆,分解问题实际上就是掌握软件的设计和实现思想。
对于最高的编程境界,我个人的理解是:能把大问题进行完整的分析、分解且高效解决。
完整性:就是指预先考虑到各种可能性,将问题分解后,合理模块化并实现。
高效率:例如,在求“1+2+3...+100”的和时,考虑使用算法“(1+100)×100/2”,而不是逐个去加。
(8)不要拿来主义,特别是新手
好多网友看书或学习视频时,喜欢要文档、要代码,其实,这是学习的最大误区。
有了文档和代码,你会变得非常懒惰,心里面会觉得已经学会了,而实际上并没有学会。因此无论是看书还是学习视频,都要自己完成学习笔记及代码的书写,这本身就是最重要的学习过程,在学习上要肯于花时间和精力,而不是投机取巧。如果你至今都没有学好Linux运维,那么可以想一想是不是也犯了这个错误?
1.4 学完本书后可以达到何种Shell编程高度
如果读者具备了前文提到Linux基础知识,认真地阅读并按照书中的内容去勤加练习,相信很快便可熟练掌握Shell编程,搞定企业场景中的绝大多数Shell编程问题,本书介绍了大量的核心互联网运维场景企业案例,相信对大家的工作会很有帮助。
如果再配合老男孩的Shell脚本教学视频,定能使你如虎添翼,相关视频一共有14部(数百课时),观看地址为:http://edu.51cto.com/pack/view/id-546.html,读者也可以扫描下面的二维码,注册付费后开始学习。
第2章
Shell脚本初步入门
在解释“Shell脚本”这个名词之前,我们先来看看什么是Shell。
2.1 什么是Shell
Shell是一个命令解释器,它的作用是解释执行用户输入的命令及程序等,用户每输入一条命令,Shell就解释执行一条。这种从键盘一输入命令,就可以立即得到回应的对话方式,称为交互的方式。
Shell存在于操作系统的最外层,负责与用户直接对话,把用户的输入解释给操作系统,并处理各种各样的操作系统的输出结果,然后输出到屏幕返回给用户。输入系统用户名和密码并登录到Linux后的所有操作都是由Shell解释与执行的。
图2-1针对命令解释器Shell在操作系统中所处的位置给出了基本图解。
图2-1 Shell在操作系统中所处位置的基本图解
提示: Shell的英文是贝壳的意思,从图2-1中可以看出,命令解释器(Shell)就像贝壳一样包住了系统核心。
2.2 什么是Shell脚本
理解了Shell之后,再理解Shell脚本就简单了。当命令或程序语句不在命令行下执行,而是通过一个程序文件来执行时,该程序就被称为Shell脚本。如果在Shell脚本里内置了很多条命令、语句及循环控制,然后将这些命令一次性执行完毕,这种通过文件执行脚本的方式称为非交互的方式。Shell脚本类似于DOS系统下的批处理程序(早期扩展名一般为“*.bat”)。用户可以在Shell脚本中敲入一系列的命令及命令语句组合。这些命令、变量和流程控制语句等有机地结合起来,就形成了一个功能强大的Shell脚本。
下面是在Windows下利用bat批处理程序开发的备份企业网站及数据库数据的脚本范例。
范例2-1:在Windows下利用bat批处理程序备份网站及数据库数据的脚本。
@echo off
set date=%date:~0,4%-%date:~5,2%-%date:~8,2% #<==定义时间变量。
mysqldump -uroot -poldboy -A -B > D:\bak\"%date%".sql #<==备份数据库数据。
rar.exe a -k -r -s -m1 D:\bak\"%date%".sql.rar D:\bak\"%date%".sql
#<==打包备份出来的数据库数据。
del D:\bak\*.sql #<==删除未打包的无用数据库数据。
rar.exe a -k -r -s -m1 D:\bak\"%date%"htdocs.rar D:\work\PHPnow\htdocs
#<==打包站点目录下的数据。
范例2-2:清除/var/log下messages日志文件的简单命令脚本。
把所有命令放在一个文件里,堆积起来后就形成了脚本,下面就是一个由最简单的命令堆积而成的Shell脚本。需要注意的是,必须使用root身份来运行这个脚本。
# 清除日志脚本, 版本 1。
cd /var/log
cat /dev/null>messages
echo "Logs cleaned up."
提示: /var/log/messages是Linux系统的日志文件,很重要。
范例2-2所示的脚本其实是有一些问题的,具体如下:
1)如果不是root用户,则无法执行脚本清理日志,并且会提示系统的权限报错信息。
2)没有任何流程控制语句,简单地说就是只进行顺序操作,没有成功判断和逻辑严密性。
范例2-3:写一个包含命令、变量和流程控制的语句来清除/var/log下messages日志文件的Shell脚本。
# !/bin/bash
# 清除日志脚本, 版本 2
LOG_DIR=/var/log
ROOT_UID=0 #<==$UID为0的用户,即root用户
# 脚本需要使用root用户权限来运行,因此,对当前用户进行判断,对不合要求的用户给出友好提示,并终止程序运行。
if [ "$UID" -ne "$ROOT_UID" ] #<==如果当前用户不是root,则不允许执行脚本。
then
echo "Must be root to run this script." #<==给出提示后退出。
exit 1 #<==退出脚本。
fi
# 如果切换到指定目录不成功,则给出提示,并终止程序运行。
cd $LOG_DIR || {
echo "Cannot change to necessary directory."
exit 1
}
# 经过上述两个判断后,此处的用户权限和路径应该就是对的了,只有清空成功,才打印成功提示。
cat /dev/null>messages && {
echo "Logs cleaned up."
exit 0 # 退出之前返回0表示成功,返回1表示失败。
}
echo "Logs cleaned up fail."
exit 1
初学者如果想要快速掌握Shell脚本的编写方法,最有效的思路就是采用电子游戏中过关的方式,比如,对于范例2-3的脚本可以设计成如下几关:
第一关,必须是root才能执行脚本,否则给出友好提示并终止脚本运行。
第二关,成功切换目录(cd /var/log),否则给出友好提示并终止脚本运行。
第三关,清理日志(cat /dev/null > messages),若清理成功,则给出正确提示。
第四关,通关或失败,分别给出相应的提示(echo输出)。
2.3 Shell脚本在Linux运维工作中的地位
Shell脚本语言很适合用于处理纯文本类型的数据,而Linux系统中几乎所有的配置文件、日志文件(如NFS、Rsync、Httpd、Nginx、LVS、MySQL等),以及绝大多数的启动文件都是纯文本类型的文件。因此,学好Shell脚本语言,就可以利用它在Linux系统中发挥巨大的作用。
图2-2形象地展示了Shell脚本在运维工作中的地位。
图2-2 Shell脚本在运维工作中的地位形象图
2.4 脚本语言的种类
2.4.1 Shell脚本语言的种类
Shell脚本语言是弱类型语言(无须定义变量的类型即可使用),在Unix/Linux中主要有两大类Shell:一类是Bourne shell,另一类是C shell。
1.?Bourne shell
Bourne shell又包括Bourne shell(sh)、Korn shell(ksh)、Bourne Again Shell(bash)三种类型。
Bourne shell(sh)由AT&T的Steve Bourne开发,是标准的UNIX Shell,很多UNIX系统都配有sh。
Korn shell(ksh)由David Korn开发,是Bourne shell(sh)的超集合,并且添加了csh引入的新功能,是目前很多UNIX系统标准配置的Shell,这些系统上的/bin/sh往往是指向/bin/ksh的符号链接。
Bourne Again Shell(bash)由GNU项目组开发,主要目标是与POSIX标准保持一致,同时兼顾对sh的兼容,bash从csh和ksh借鉴了很多功能,是各种Linux发行版默认配置的Shell,Linux系统上的/bin/sh往往是指向/bin/bash的符号链接。尽管如此,bash和sh还是有很多的不同之处:一方面,bash扩展了一些命令和参数;另一方面,bash并不完全和sh兼容,它们有些行为并不一致,但在大多数企业运维的情况下区别不大,特殊场景可以使用bash替代sh。
2.?C shell
C shell又包括csh、tcsh两种类型。
csh由Berkeley大学开发,随BSD UNIX发布,它的流程控制语句很像C语言,支持很多Bourne shell所不支持的功能,例如:作业控制、别名、系统算术、命令历史、命令行编辑等。
tcsh是csh的增强版,加入了命令补全等功能,在FreeBSD、Mac OS X等系统上替代了csh。
以上介绍的这些Shell中,较为通用的是标准的Bourne shell(sh)和C shell(csh)。其中Bourne shell(sh)已经被Bourne Again shell(bash)所取代。
可通过以下命令查看CentOS 6系统的Shell支持情况。
[root@oldboy ~]# cat /etc/shells
/bin/sh #<==这是Linux里常用的Shell,指向/bin/bash。
/bin/bash #<==这是Linux里常用的Shell,也是默认使用的Shell。
/sbin/nologin #<==这是Linux里常用的Shell,用于禁止用户登录。
/bin/dash
/bin/tcsh
/bin/csh
Linux系统中的主流Shell是bash,bash是由Bourne Shell(sh)发展而来的,同时bash还包含了csh和ksh的特色,但大多数脚本都可以不加修改地在sh上运行,如果使用了sh后发现结果和预期有差异,那么可以尝试用bash替代sh。
2.4.2 其他常用的脚本语言种类
1.?PHP语言
PHP是网页程序语言,也是脚本语言。它是一款更专注于Web页面开发(前端展示)的语言,例如:wordpress、dedecms、discuz等著名的开源产品都是用PHP语言开发的。用PHP程序语言也可以处理系统日志、配置文件等,还可以调用Linux系统命令,但是,很少有人这么用。
2.?Perl语言
Perl脚本语言比Shell脚本语言强大很多,在2010年以前很流行,它的语法灵活、复杂,在实现不同的功能时可以用多种不同的方式,缺点是不易读,团队协作困难,但它仍不失为一种很好的脚本语言,存世的大量相关程序软件(比如,xtrabackup热备工具、MySQL MHA集群高可用软件等)中都有Perl语言的身影。当下的Linux运维人员几乎不需要了解Perl语言了,最多可了解一下Perl语言的安装环境。当然了想要二次开发用Perl编写软件人员例外,Perl语言已经成为历史了。
3.?Python语言
Python是近几年非常流行的语言,它不但可以用于脚本程序开发,也可以实现Web页面程序开发(例如:CMDB管理系统),甚至还可以实现软件的开发(例如:大名鼎鼎的OpenStack、SaltStack都是Python语言开发的)、游戏开发、大数据开发、移动端开发。
现在越来越多的公司都要求运维人员会Python自动化开发。老男孩IT教育持续引领着国内Linux培训界的风向标,早在2012年以前就已经开设了Python自动化运维开发实战课程(课程表见http://oldboy.blog.51cto.com/2561410/1123127),并于2015年开设了Python全栈开发工程师课程,课程表见http://oldboy.blog.51cto.com/2561410/1749122。Python语言目前是全球第四大开发语言,未来的发展前景很好,每一个运维人员在掌握了Shell编程之后,都应该深入学习Python语言,以提升职场竞争力。
2.4.3 Shell脚本语言的优势
Shell脚本语言的优势在于处理偏操作系统底层的业务,例如:Linux系统内部的很多应用(有的是应用的一部分)都是使用Shell脚本语言开发的,因为有1000多个Linux系统命令为它做支撑,特别是Linux正则表达式及三剑客grep、awk、sed等命令。
对于一些常见的系统脚本,使用Shell开发会更简单、更快速,例如:让软件一键自动化安装、优化,监控报警脚本,软件启动脚本,日志分析脚本等,虽然PHP/Python语言也能够做到这些,但是,考虑到掌握难度、开发效率、开发习惯等因素,它们可能就不如Shell脚本语言流行及有优势了。对于一些常规的业务应用,使用Shell更符合Linux运维简单、易用、高效的三大基本原则。
PHP语言的优势在于小型网站系统的开发;Python语言的优势在于开发较复杂的运维工具软件、Web界面的管理工具和Web业务的开发(例如:CMDB自动化运维平台、跳板机、批量管理软件SaltStack、云计算OpenStack软件)等。我们在开发一个应用时应根据业务需求,结合不同语言的优势及自身擅长的语言来选择,扬长避短,从而达到高效开发及易于自身维护等目的。
2.5 常用操作系统默认的Shell
在常用的操作系统中,Linux下默认的Shell是Bourne Again shell(bash);Solaris和FreeBSD下默认的是Bourne shell(sh);AIX下默认的是Korn Shell(ksh)。
这里重点讲Linux系统环境下的Bourne Again shell(bash)。
下面来看一个企业面试题:CentOS Linux系统默认的Shell是什么?这题的答案就是bash。
通过以下两种方法可以查看CentOS Linux系统默认的Shell。
方法1:
[root@oldboy ~]# echo $SHELL
/bin/bash
方法2:
[root@oldboy ~]# grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
提示: 结尾的/bin/bash就是用户登录后的Shell解释器。
注意: 本书写作的环境为Linux系统,具体版本为CentOS 6.x x86_64,绝大部分已写好的脚本程序都不需要经过任何修改,就可以直接应用于其他的Linux系统中。对于一些UNIX系统,因为默认不是bash解释器,所以需要根据解释器版本进行调整,本书的全部内容都是以bash及和bash兼容的sh解释器为基础编写的。
2.6 Shell脚本的建立和执行
2.6.1 Shell脚本的建立
在Linux系统中,Shell脚本(bash Shell程序)通常是在编辑器vi/vim中编写的,由UNIX/Linux命令、bash Shell命令、程序结构控制语句和注释等内容组成。这里推荐用Linux自带的功能更强大的vim编辑器来编写,可以事先做一个别名alias vi='vim',并使其永久生效,这样以后习惯输入vi的读者也就可以直接调用vim编辑器了,设置方法如下:
[root@oldboy ~]# echo "alias vi='vim'" >>/etc/profile
[root@oldboy ~]# tail -1 /etc/profile
alias vi='vim'
[root@oldboy ~]# source /etc/profile
1.?脚本开头(第一行)
一个规范的Shell脚本在第一行会指出由哪个程序(解释器)来执行脚本中的内容,这一行内容在Linux bash的编程一般为:
#!/bin/bash
或
#!/bin/sh #<==255个字符以内。
其中,开头的“#!”字符又称为幻数(其实叫什么都无所谓,知道它的作用就好),在执行bash脚本的时候,内核会根据“#!”后的解释器来确定该用哪个程序解释这个脚本中的内容。
注意,这一行必须位于每个脚本顶端的第一行,如果不是第一行则为脚本注释行,例如下面的例子。
[oldboy@oldboy ~]$ cat test.sh
#!/bin/bash
echo "oldboy start"
#!/bin/bash #<==写到这里就是注释了。
#!/bin/sh #<==写到这里就是注释了。
echo "oldboy end"
2.?bash与sh 的区别
早期的bash与sh稍有不同,它还包含了csh和ksh的特色,但大多数脚本都可以不加修改地在sh上运行,比如:
[root@oldboy ~]# ll /bin/sh
lrwxrwxrwx. 1 root root 4 3月 19 20:54 /bin/sh -> bash
[root@oldboy ~]# ll /bin/bash
-rwxr-xr-x 1 root root 940416 10月 16 21:56 /bin/bash
提示: sh为bash的软链接,大多数情况下,脚本的开头使用“#!/bin/bash”和“#!/bin/sh”是没有区别的,但更规范的写法是在脚本的开头使用“#!/bin/bash”。
下面的Shell脚本是系统自带的软件启动脚本的开头部分。
[root@oldboy ~]# head -1 /etc/init.d/sshd
#!/bin/bash
[root@oldboy ~]# head -1 /etc/init.d/ntpd
#!/bin/bash
[root@oldboy ~]# head -1 /etc/init.d/crond
#!/bin/sh
提示: 如果使用/bin/sh执行脚本出现异常,那么可以再使用/bin/bash试一试,但是一般不会发生此类情况。
一般情况下,在安装Linux系统时会自动安装好bash软件,查看系统的bash版本的命令如下。
[root@oldboy ~]# cat /etc/redhat-release
CentOS release 6.8 (Final) #<==这里显示的是作者写作的Linux的环境版本。
[root@oldboy ~]# bash --version
GNU bash, version 4.1.2(1)-release (x86_64-redhat-linux-gnu)
#<==这里显示的是bash的版本。
Copyright (C) 2009 Free Software Foundation, Inc.
#<==下面几行是自由软件提示的相关信息。
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
如果读者使用的是较老版本的Shell,那么建议将其升级到最新版本的Shell,特别是企业使用,因为近两年老版本的bash被暴露出存在较严重的安全漏洞。
例如:bash软件曾经爆出了严重漏洞(破壳漏洞),凭借此漏洞,攻击者可能会接管计算机的整个操作系统,得以访问各种系统内的机密信息,并对系统进行更改等。任何人的计算机系统,如果使用了bash软件,都需要立即打上补丁。检测系统是否存在漏洞的方法为:
[root@oldboy ~]# env x='() { :;}; echo be careful' bash -c "echo this is a test"
this is a test
如果返回如下两行,则表示需要尽快升级bash了,不过,仅仅是用于学习和测试就无所谓了。
be careful
this is a test
升级方法为:
[root@oldboy ~]# yum -y update bash
[root@oldboy ~]# rpm -qa bash
bash-4.1.2-40.el6.x86_64
提示: 如果没有输出be careful,则不需要升级。
下面是Linux中常用脚本开头的写法,不同语言的脚本在开头一般都要加上如下标识内容:
1 #!/bin/sh
2 #!/bin/bash
3 #!/usr/bin/awk
4 #!/bin/sed
5 #!/usr/bin/tcl
6 #!/usr/bin/expect #<==expect解决交互式的语言开头解释器。
7 #!/usr/bin/perl #<==perl语言解释器。
8 #!/usr/bin/env python #<==python语言解释器。
CentOS和Red Hat Linux下默认的Shell均为bash。因此,在写Shell脚本的时候,脚本的开头即使不加“#!/bin/bash”,它也会交给bash解释。如果写脚本不希望使用系统默认的Shell解释,那么就必须要指定解释器了,否则脚本文件执行后的结果可能就不是你所要的。建议读者养成好的编程习惯,不管采用什么脚本,最好都加上相应的开头解释器语言标识,遵守Shell编程规范。
如果在脚本开头的第一行不指定解释器,那么就要用对应的解释器来执行脚本,这样才能确保脚本正确执行。例如:
如果是Shell脚本,就用bash test.sh执行test.sh。
如果是Python脚本,就用python test.py执行test.py。
如果是expect脚本,就用expect test.exp执行test.exp。
提示: 其他的脚本程序大都是类似的执行方法。
3.?脚本注释
在Shell脚本中,跟在#后面的内容表示注释,用来对脚本进行注释说明,注释部分不会被当作程序来执行,仅仅是给开发者和使用者看的,系统解释器是看不到的,更不会执行。注释可自成一行,也可以跟在脚本命令的后面与命令在同一行。开发脚本时,如果没有注释,那么团队里的其他人就会很难理解脚本对应内容的用途,而且若时间长了,自己也会忘记。因此,我们要尽量养成为所开发的Shell脚本书写关键注释的习惯,书写注释不光是为了方便别人,更是为了方便自己,避免影响团队的协作效率,以及给后来接手的人带来维护困难。特别提示一下,注释尽量不要用中文,在脚本中最好也不要有中文。
2.6.2 Shell脚本的执行
当Shell脚本运行时,它会先查找系统环境变量ENV,该变量指定了环境文件(加载顺序通常是/etc/profile、~/.bash_prof?ile、~/.bashrc、/etc/bashrc等),在加载了上述环境变量文件后,Shell就开始执行Shell脚本中的内容(更多Shell加载环境变量的知识请见第3章)。
Shell脚本是从上至下、从左至右依次执行每一行的命令及语句的,即执行完了一个命令后再执行下一个,如果在Shell脚本中遇到子脚本(即脚本嵌套)时,就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句。
通常情况下,在执行Shell脚本时,会向系统内核请求启动一个新的进程,以便在该进程中执行脚本的命令及子Shell脚本,基本流程如图2-3所示。
图2-3 Shell脚本的基本执行流程
特殊技巧: 设置Linux的crond任务时,最好能在定时任务脚本中重新定义系统环境变量,否则,一些系统环境变量将不会被加载,这个问题需要注意!
Shell脚本的执行通常可以采用以下几种方式。
1)bash script-name或sh script-name:这是当脚本文件本身没有可执行权限(即文件权限属性x位为-号)时常使用的方法,或者脚本文件开头没有指定解释器时需要使用的方法。这也是老男孩推荐使用的方法。
2)path/script-name或./script-name:指在当前路径下执行脚本(脚本需要有执行权限),需要将脚本文件的权限先改为可执行(即文件权限属性加x位),具体方法为chmod +x script-name。然后通过脚本绝对路径或相对路径就可以直接执行脚本了。
在企业生产环境中,不少运维人员在写完Shell脚本之后,由于忘记为该脚本设置执行权限,然后就直接应用了,结果导致脚本没有按照自己的意愿手动或定时执行,对于这一点,避免出现该问题的方法就是用第1种方法替代第2种。
3)source script-name或. script-name:这种方法通常是使用source或“.”(点号)读入或加载指定的Shell脚本文件(如san.sh),然后,依次执行指定的Shell脚本文件san.sh中的所有语句。这些语句将在当前父Shell脚本father.sh进程中运行(其他几种模式都会启动新的进程执行子脚本)。因此,使用source或“.”可以将san.sh自身脚本中的变量值或函数等的返回值传递到当前父Shell脚本father.sh中使用。这是它和其他几种方法最大的区别,也是值得读者特别注意的地方。
source或“.”命令的功能是:在当前Shell中执行source或“.”加载并执行的相关脚本文件中的命令及语句,而不是产生一个子Shell来执行文件中的命令。注意“.”和后面的脚本名之间要有空格。
如果读者学过PHP开发就会明白,source或“.”相当于include的功能。HTTP服务软件Apache、Nginx等配置文件里都支持这样的用法。
4)sh<script-name或cat scripts-name|sh:同样适用于bash,不过这种用法不是很常见,但有时也可以有出奇制胜的效果,例如:不用循环语句来实现精简开机自启动服务的案例,就是通过将所有字符串拼接为命令的形式,然后经由管道交给bash操作的案例(见《跟老男孩学习Linux运维:Web集群实战》第3章)。
范例2-4:创建模拟脚本test.sh,并输入如下内容。
[oldboy@oldboy ~]$ cat >test.sh #<==编辑test.sh脚本文件。
echo 'I am oldboy'
输入“echo 'I am oldboy'”内容后按回车键,然后再按Ctrl+d组合键结束编辑。此操作为特殊编辑方法,这里是作为cat用法的扩展知识(通过使用来记忆是个好习惯)。
现在使用第1种方法实践,命令如下:
[oldboy@oldboy ~]$ cat test.sh
echo 'I am oldboy'
[oldboy@oldboy ~]$ sh test.sh #<==使用第1种方式的sh命令执行test.sh脚本文件。
I am oldboy
[oldboy@oldboy ~]$ bash test.sh #<==使用第1种方式的bash命令执行test.sh脚本文件。
I am oldboy
这里使用第1种方法的bash和sh,均可以执行脚本并得到预期的结果。
使用第2种方法实践,命令如下:
[oldboy@oldboy ~]$ ls -l test.sh
-rw-rw-r-- 1 oldboy oldboy 19 Apr 30 02:46 test.sh
[oldboy@oldboy ~]$ ./test.sh #<==使用第2种方式“./”在当前目录下执行test.sh脚本文件,细心的读者可以发现,这个地方无法自动补全,这是因为没有权限所导致的。
-bash: ./test.sh: Permission denied #<==提示:强制执行会提示权限拒绝,此处是
因为没有执行权限。
虽然没有权限的test.sh脚本不能直接被执行,但是可以用source或“.”(点号)来执行,如下:
[oldboy@oldboy ~]$ . test.sh #<==请注意开头的 “.”后面有空格。
I am oldboy
[oldboy@oldboy ~]$ source test.sh
I am oldboy
提示: “.”或source命令的功能相同,都是读入脚本并执行脚本。
给test.sh添加可执行权限,命令如下:
[oldboy@oldboy ~]$ chmod u+x test.sh
[oldboy@oldboy ~]$ ./test.sh
I am oldboy
可以看到,给test.sh加完可执行权限后就能执行了。前面也提到了,这种方法在使用前每次都需要给定执行权限,但容易被忘记,且多了一些步骤,增加了复杂性。
使用第3种方法实践时,会将source或“.”执行的脚本中的变量值传递到当前的Shell中,如下:
[oldboy@oldboy ~]$ echo 'userdir=`pwd`' >testsource.sh #<==第一行的内容通常用
echo处理更方便
[oldboy@oldboy ~]$ cat testsource.sh
userdir=`pwd` #<==定义了一个命令变量,内容是打印当前路径。注意,打印命令用反引号
[oldboy@oldboy ~]$ sh testsource.sh #<==采用sh命令执行脚本
[oldboy@oldboy ~]$ echo $userdir
#<==此处为空,并没有出现当前路径/home/oldboy的输出,这是为什么
根据上面的例子可以发现,通过sh或bash命令执行过的脚本,若在脚本结束之后,在当前Shell窗口中查看userdir变量的值,会发现值是空的。现在以同样的步骤改用source或“.”执行,然后再看看userdir变量的值:
[oldboy@oldboy ~]$ source testsource.sh #<==采用source执行同一脚本
[oldboy@oldboy ~]$ echo $userdir
/home/oldboy #<==此处输出了当前路径/home/oldboy,这又是为什么呢
来了解一下系统NFS服务的脚本是如何使用“.”的:
# Source function library.
. /etc/init.d/functions #<==通过"."加载系统函数库functions
说明: 操作系统及服务自带的脚本是我们学习的标杆和参考(虽然有时感觉这些脚本也不是十分规范)。
结论:通过source或“.”加载执行过的脚本,由于是在当前Shell中执行脚本,因此在脚本结束之后,脚本中的变量(包括函数)值在当前Shell中依然存在,而sh和bash执行脚本都会启动新的子Shell执行,执行完后退回到父Shell。因此,变量(包括函数)值等无法保留。在进行Shell脚本开发时,如果脚本中有引用或执行其他脚本的内容或配置文件的需求时,最好用“.”或source先加载该脚本或配置文件,处理完之后,再将它们加载到脚本的下面,就可以调用source加载的脚本及配置文件中的变量及函数等内容了。
以下采用第4种方法来实践:
[root@oldboy ~]# ls -l oldboy.sh
-rw-r--r--. 1 root root 28 Nov 18 15:52 oldboy.sh
[root@oldboy ~]# cat oldboy.sh
echo "I am oldboy teacher."
[root@oldboy ~]# sh<oldboy.sh #<==尽量不要使用这种方法。
I am oldboy teacher.
[root@oldboy ~]# cat oldboy.sh|bash #<==这种方法在命令行拼接字符串命令后,需
?要执行时就会用到
I am oldboy teacher.
提示: 代码中提到的两种执行方法相当于sh scripts-name,效率很高,但是初学者用得少。
范例2-5:已知如下命令及返回结果,请问echo $user的返回的结果为()。
[oldboy@test ~]$ cat test.sh
user=`whoami`
[oldboy@test ~]$ sh test.sh
[oldboy@test ~]$ echo $user
参考的选择项如下:
a)当前用户
b)oldboy
c)空(无内容输出)
这是某互联网公司Linux运维职位的笔试题。在这里c)是正确答案,原因前面已经讲过了,即使用sh执行脚本会导致当前Shell无法获得变量值。
通过上述面试题可得出如下的结论:
儿子Shell脚本会直接继承父亲Shell脚本的变量、函数(就好像是儿子随父亲姓,基因也会继承父亲的)等,反之则不可以。
如果希望反过来继承(就好像是让父亲随儿子姓,让父亲的基因也继承儿子的),就要用source或“.”在父亲Shell脚本中事先加载儿子Shell脚本。
2.6.3 Shell脚本开发的基本规范及习惯
Shell脚本的开发规范及习惯非常重要,虽然这些规范不是必须要遵守的,但有了好的规范和习惯,可以大大提升开发效率,并能在后期降低对脚本的维护成本。当多人协作开发时,大家有一个互相遵守的规范就显得更重要了。即使只是一个人开发,最好也采取一套固定的规范,这样脚本将会更易读、更易于后期维护,最重要的是要让自己养成一个一出手就很专业和规范的习惯。下面来看看有哪些规范,这些规范在第14章也会提及,以便于大家进一步巩固。
1)Shell脚本的第一行是指定脚本解释器,通常为:
#!/bin/bash
或
#!/bin/sh
2)Shell脚本的开头会加版本、版权等信息:
# Date: 16:29 2012-3-30
# Author: Created by oldboy
# Blog:http://oldboy.blog.51cto.com
# Description:This scripts function is.....
# Version: 1.1
说明: 以上两点在Linux系统场景中不是必需的,只属于优秀规范和习惯,第16章有自动加载此内容的方法,读者可以做进一步了解。
可修改“~/.vimrc”配置文件配置vim编辑文件时自动加上以上信息的功能。
3)在Shell脚本中尽量不用中文(不限于注释)。
尽量用英文注释,防止本机或切换系统环境后中文乱码的困扰。如果非要加中文,请根据自身的客户端对系统进行字符集调整,如:export LANG="zh_CN.UTF-8",并在脚本中,重新定义字符集设置,和系统保持一致。
4)Shell脚本的命名应以.sh为扩展名。
例如:script-name.sh
5)Shell脚本应存放在固定的路径下。
例如:/server/scripts
以下则是Shell脚本代码书写的良好习惯。
1)成对的符号应尽量一次性写出来,然后退格在符号里增加内容,以防止遗漏。这些成对的符号包括:
{}、[]、''、``、""
说明: 这部分也可以配置.vimrc实现自动添加,但是老男孩不推荐这样做,因为养成良好的习惯很重要。
2)中括号([])两端至少要有1个空格,因此,键入中括号时即可留出空格[ ],然后再退格键入中间的内容,并确保两端都至少有一个空格,即先键入一对中括号,然后退1格,输入两个空格,再退1格,双中括号([[]])的写法也是如此。
3)对于流程控制语句,应一次性将格式写完,再添加内容。
比如,一次性完成if语句的格式,应为:
if 条件内容
then
内容
fi
一次性完成for循环语句的格式,应为:
for
do
内容
done
提示: while和until,case等语句也是一样。
4)通过缩进让代码更易读,比如:
if条件内容
then
内容
fi
5)对于常规变量的字符串定义变量值应加双引号,并且等号前后不能有空格,需要强引用的(指所见即所得的字符引用),则用单引号(' '),如果是命令的引用,则用反引号(` `)。例如:
OLDBOY_FILE="test.txt"
6)脚本中的单引号、双引号及反引号必须为英文状态下的符号,其实所有的Linux字符及符号都应该是英文状态下的符号,这点需要特别注意。
说明: 好的习惯可以让我们避免很多不必要的麻烦,提升工作效率。
有关Shell开发规范及习惯的更多内容,感兴趣的读者可参考本书的第14章。很多开发习惯也可以通过配置vim的功能来实现,例如实现自动缩进、自动补全成对符号、自动加入起始解释器及版权信息等,这部分的知识可参考本书第16章。对于一些开发规范和习惯,在新手入门学习期间,我们不建议将其搞得太傻瓜化、智能化,这会让我们产生惰性,所以有关Shell的开发规范及习惯的知识放在第14章来讲解,有关搭建高效的Shell开发环境的知识放在第16章讲解!