来源:良许Linux
ID:liangxuxiansheng
无论是系统运维,还是应用运维,均可分为“纯手工”—> “脚本化”—> “自动化”—>“智能化”几个阶段,其中自动化阶段,主要是将一些重复性人工操作和运维经验封装为程序或脚本,一方面避免重复性操作及风险,另一方面提高执行效率。在自动化运维的转变过程中,经常使用的可能就是shell脚本了,今天主要分享下shell脚本开发在运维工作中的一些经验总结。
小脚本有大智慧,别小看几十行代码,夹杂着系统设计、代码规范和操作经验等等细节,在建设自动化运维的工作中,还是很值得我们研究学习的,下面总结这些也都是源于各位脚本达人和我们在自身工作中“遇到的坑”、“摔过的跟头”和“排过的雷”,与大家共享。
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDEClUMYGR8MLGyd8DHHQd5xmhQkgAKBzBY7QnRl6Zl2d8jumUYQLbkA/640?wx_fmt=png)
这里主要介绍并参考我行已经形成的一些shell编写规范,编写时严格遵守这些规范,不仅使编写人受益,同时也能提高使用者的执行效率。
1)脚本开头部分应有脚本功能说明、参数使用说明、作者姓名、创建/修改日期、版本信息,格式为:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzD2n7hkbHpYESSSicsiaYFKGFtTib62MOCGHTiaLxGT5POYAYcAbX4E8TOCA/640?wx_fmt=png)
2)脚本编写时,注意格式对齐,如所有的循环或者判断语句前后的语句进行对齐,以及case的选取完全,如:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDujFMDZB89iblxtVOGIqzHyRjrfr8otpvicokjgsw3vMyRHdBw35aRXsw/640?wx_fmt=png)
3)脚本开头执行时,执行如下命令,在执行过程中若遇到使用了未定义的变量或命令返回值为非零,将直接报错退出:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDmZOkPXqAia4ctlBAcYDzuFNTnoXZGHTkreaDIjgpibaBM5NB0ZqCxia4Q/640?wx_fmt=png)
4)建议将命令行的每个参数放在单引号、双引号中,特别是rm、mv等可能对生产现有数据造成修改的操作,建议使用垃圾箱策略:rm操作转意为mv操作,制定文件保存目录,以防回退,并定期清理:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDDDQBzWmRZCfxDwCayyBLWM2qoePY6xqCibsknALfFSUTbcjVr4AGjRA/640?wx_fmt=png)
5)命令行中参数需要使用‘*’、‘?’通配符的,应依据最精确匹配原则,如能确定文件、目录名称的前缀、后缀、扩展名及其他可识别关键字的,须在参数中包含该信息,如能确定文件、目录的长度应使用‘?’通配符,不得使用‘*’,推荐的使用方式:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDRqpN5H9iamPIobYQE8a1hOlVKgDjVVWia0XyWx6uU57ibXImnEX1OknSg/640?wx_fmt=png)
不推荐使用的方式:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDwzCpkUPyBEDfm0HJicAFth7pJ0UqujrGk3NKjDKV9j8uMwLn4waEsKg/640?wx_fmt=png)
禁止使用的方式:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDhnk4SzTnALibEiaHhL0uvUH2hnD6ZiapibsOqMEeRMdiawej47zwASpQMhw/640?wx_fmt=png)
6)给数值型变量的赋值后,需由手段保证变量的值为数值型,避免在后续的处理中出现异常:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDzM9Jzh0NjU3mVsB3ez8F70N4ltXYcYRNKQ3D0GW1ndD5dWFGZj5kfQ/640?wx_fmt=png)
7)在判断条件中使用的变量,必须包含在双引号中,如:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDvpHbQzBLnIXHT9WMmmTLGibibZJUwWdCu9SKTFILqddFGNIBFx2qpjUA/640?wx_fmt=png)
禁止使用的方式:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDGDtmMRmKbkvmicjv9r9DuanBdCcEOzTfCN9r17WankkKwfqXzIa5iapA/640?wx_fmt=png)
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDic2ic8RSX2WjO3b8k6Cp4jgPqV6Q6hCjgFnIhWfUAicYicOQxNI3kA6sZg/640?wx_fmt=png)
8)对文件进行打包备份时,必须使用相对路径进行打包,如:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDwfhmsKUq2x8aLrrqtx8abCPHsA0bZGTj3Cfic2JV2fz9drpSDwgez4A/640?wx_fmt=png)
严禁将全路径打入tar包, 如:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDED1iaATlvzZlsrAyibuj2KREoHmiadp0blJ5YZ4WLX1iaNS3UC9yqjZOVA/640?wx_fmt=png)
9)对于打包后还需进行压缩的文件,建议使用管道进行处理,如:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDqBhvlUzib3UpSViaeSjCZSLTXrSC51EClpJnzZLoqpn4lfVW4GwiaOMGA/640?wx_fmt=png)
不建议两部分分开执行:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDSsqcud57EhWdP9EEkjqfWp0R2ZonGxrg3rLia4EVO2T7gxuKn7COOEw/640?wx_fmt=png)
10)使用ps命令筛选进程时,如能确定进程所属用户,必须在参数中指定用户名称,如其输出作为kill命令的输入,则必须指定进程所属用户,如:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzD2qIek35iaXicWzOOoqOOPzl5V3lcj2N1ibStM3ApXSib3ibeiaMQklUc5Z8w/640?wx_fmt=png)
这里介绍的主要是日常shell编写中遇到比较隐蔽或看似简单,却难以发现的“坑”,编写中应尽量避免使用,使用更优的方法避免重蹈覆辙。
1)更新文件使用>不用cp
使用>修改和回退文件时,保留原文件的属组和权限,避免使用cp时权限属组被修改。
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDFMmq2iaMDHMAAVCxMX6ZzD7icPezHnjPEcAxPLNibMYHzhsSWYpktDaLg/640?wx_fmt=png)
2)使用kill前确认
关键字用-w 精确匹配字段;
kill前后都保留现场, 两次ps -ef|grep -w 关键字|grep -v grep >>/tmp/kill_进程名_.backup;
删除前要校验,获取进程号是否唯一,避免多杀或误杀的情况。
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDYs9dORsB2VTx4hq1wjDibxCicqHEs0PyleECld0qYHnltqDJQRiaiaKzfg/640?wx_fmt=png)
3)使用rm前确认
删除前备份删除对象信息,避免使用变量,直接使用文件和目录名;
如果必须使用时,删除前,建议检查避免误删,删除目录和文件信息保留:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDic1o2ZaOfIaooqX0sPRucp2nYEtYibXA78dy0RNAJeJK7qGDKQSIHp1w/640?wx_fmt=png)
建议禁用find遍历根目录进行查找,同时删除前进行确认,避免多删或误删的情况。
4)For循环的坑
for循环的
in条件按空格来区分,避免进入不正确或死循环。
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDqrU6c6yFjicGJPCTIiapllzib8zLib92EvNF2VrVZuOzuuqanDMrokRSsQ/640?wx_fmt=png)
5)while循环的禁忌
如果还想使用循环中的变量,不要while结合管道使用。
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDYyXNibXSxydNLZcB8Cvzn6vcjLmjwGHVic4fia9owb4PQ1N1QbDWxgCjg/640?wx_fmt=png)
6)慎用cp
这句话基本上正确,但同样有空格分词的问题。所以应当用双引号:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDMYkpykQLbDhhLBZ7ojOicHqerzRImUr5x75UfS9wK4PibxN7e56sicfug/640?wx_fmt=png)
但是如果凑巧文件名以 - 开头,这个文件名会被 cp 当作命令行选项来处理。
可以试试下面这个:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzD4bLAbYCSRkNMRvBUMBDvVtibk4EflOawcfs1zGfbvsP4ezBia2fQBotg/640?wx_fmt=png)
但也可能再碰上一个不支持 -- 选项的系统,所以最好用下面的方法:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDnNN2WLW7NU5pAej4hdYTS9WDMadqZzZ6rTWZibibxWRPibj2jSFhzT7cQ/640?wx_fmt=png)
7)慎用cd
避免使用cd到操作目录再操作的方式,可能导致进入目录失败,误删除,如:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDicVzWZb9JGp8ATB3bialTn3eAqvPkeROCHxBF6E9w4vzTgiagS9KGIykA/640?wx_fmt=png)
建议如下:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDHKQO0ib4wE9BcwGm4BXDgrltrqyzaC4h3lmxllotlHPKDONMiaicAfmBw/640?wx_fmt=png)
8)
用[[ ]]代替[ ]
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzD1u8Xh6vtLFSDtXEuVyq1tx5FOy5JXxKCILicVlLwTQNAo2WvicfR9dEg/640?wx_fmt=png)
当$var为空时,上面的命令就变成了[ ="bar" ]
类似地,当$var包含空格时:
[ space words here = "var" ]两者都会出错。所以应当用双引号将变量括起来:
[ "$var" = var ] 几乎完美了。
但是,当$var以 - 开头时依然会有问题。在较新的bash中你可以用下面的方法来代替,[[ ]]关键字能正确处理空白、空格、带横线等问题。
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzD0PHIcC48daKmgW0n8u4icfjQvo4aGibplSNYudomPJ6ahEpXyIVB0SqA/640?wx_fmt=png)
另注意,[[适用于字符串,如果是数值,要用如:(( $var > 8 ))
9)管道操作中不要同时读写文件
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDUt6IeBHuByzPvJvhTckKRXyvtQ8tdCvXpSlAOW4Xibs538kicbiblWvDg/640?wx_fmt=png)
你不能在同一条管道操作中同时读写一个文件。根据管道的实现方式,file要么被截断成0字节,要么会无限增长直到填满整个硬盘。如果想改变原文件的内容,只能先将输出写到临时文件中再用mv命令。
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDxRmNofsDrluCJ7hibqJvKNz5kJ3hiadRRJO5ht01Ggs5LsnTPhsAtsaw/640?wx_fmt=png)
10)cd的易错问题
cd 有可能会出错,导致要执行的命令就会在你预想不到的目录里执行了。所以一定要记得判断cd的返回值。
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDquFV25ibYvibaVdc2NuBXHrDxAkaia2XmLYLLRmicXW1KNQn4kSGZT1MOg/640?wx_fmt=png)
如果你要根据cd的返回值执行多条命令,可以用 ||。
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDNq7LBibT3Y931iaicwHWY0qiaE68TL9N6JhexPjm4wkzEfVOJnT3DTjFzw/640?wx_fmt=png)
关于目录的一点题外话,假设你要在shell程序中频繁变换工作目录,如下面的代码:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzD2RlKePf7vopKBfdFS2t0FK3MZ7CdCzsWtxYWj2M0heiaXc1eJzyuFHA/640?wx_fmt=png)
不如这样写:
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDYic6IVZtCDGxwK3IeE7lKFXAzYNn01Al2JVhnIO4u1hT7J8fyNYIfrQ/640?wx_fmt=png)
括号会强制启动一个子shell,这样在这个子shell中改变工作目录不会影响父shell(执行这个脚本的shell),就可以省掉cd - 的麻烦。
![](http://mmbiz.qpic.cn/mmbiz_png/MR8pzzoKXjb7tNH6ZyYHUgIcwhYDMAzDCL7dToIb261VoOluCQD2V1n8FcLw0WV8GYG16uHl1CicQ0pNKlBibiaLg/640?wx_fmt=png)
目前行里自动化工具越来越多,无论是应用的MAOP或系统的SMDB,自动化实现都还是日常运维脚本的调用,结合日常运维的一些经验,脚本中就更需要考虑周全和控制风险。这里介绍一些结合运维场景的脚本应用,希望规避以前犯过的错,重点在控制风险。
1) 支持交互式脚本的应用
很多脚本中需要进行交互,在规避风险的同时,需要通过自动化工具发布来支持交互,可以使用expect,示例如下: