Shell脚本编写的八条可靠建议(值得收藏)

这八个建议,来源于键者几年来编写 shell 脚本的一些经验和教训。事实上开始写的时候还不止这几条,后来思索再三,去掉几条无关痛痒的,最后剩下八条。毫不夸张地说,每条都是精挑细选的,虽然有几点算是老生常谈了。

1. 指定bash

shell 脚本的第一行,#!之后应该是什么?如果拿这个问题去问别人,不同的人的回答可能各不相同。

我见过/usr/bin/env bash,也见过/bin/bash,还有/usr/bin/bash,还有/bin/sh,还有/usr/bin/env sh。这算是编程界的“'茴'字四种写法”了。

在多数情况下,以上五种写法都是等价的。但是,写过程序的人都知道:“少数情况”里往往隐藏着意想不到的坑。

如果系统的默认 shell 不是 bash 怎么办?比如某 Linux 发行版的某个版本,默认的 sh 就不是 bash。

如果系统的 bash 不是在 /usr/bin/bash 怎么办?

我推荐使用 /usr/bin/env bash 和 /bin/bash。前者通过env添加一个中间层,让env在$PATH中搜索bash;后者则是官方背书的,约定俗成的 bash 位置,/usr/bin/bash不过是指向它的一个符号链接。

2. set -e 和 set -x

OK,经过一番讨论,现在第一行定下来了。接下来该开始写第二行了吧?

且慢!在你开始构思并写下具体的代码逻辑之前,先插入一行set -e和一行set -x。

set -x会在执行每一行 shell 脚本时,把执行的内容输出来。它可以让你看到当前执行的情况,里面涉及的变量也会被替换成实际的值。

set -e会在执行出错时结束程序,就像其他语言中的“抛出异常”一样。(准确说,不是所有出错的时候都会结束程序,见下面的注)

注:set -e结束程序的条件比较复杂,在man bash里面,足足用了一段话描述各种情景。大多数执行都会在出错时退出,除非 shell 命令位于以下情况:

一个 pipeline 的非结尾部分,比如 error | ok

一个组合语句的非结尾部分,比如 ok && error || other

一连串语句的非结尾部分,比如 error; ok

位于判断语句内,包括test、if、while等等。
这两个组合在一起用,可以在 debug 的时候替你节省许多时间。出于防御性编程的考虑,有必要在写第一行具体的代码之前就插入它们。扪心自问,写代码的时候能够一次写对的次数有多少?大多数代 码,在提交之前,通常都经历过反复调试修改的过程。与其在焦头烂额之际才引入这两个配置,不如一开始就给 debug 留下余地。在代码终于可以提交之后,再考虑是否保留它们也不迟。

3. 带上shellcheck

好了,现在我已经有了三行(样板)代码,具体的业务逻辑一行都没写呢。是不是该开始写了?

且慢!工欲善其事,必先利其器。这次,我就介绍一个 shell 脚本编写神器:shellcheck

说来惭愧,虽然写了几年 shell 脚本,有些语法我还是记不清楚。这时候就要依仗 shellcheck 指点一下了。shellcheck 除了可以提醒语法问题以外,还能检查出 shell 脚本编写常见的 bad code。本来我的N条建议里面,还有几条是关于这些 bad code 的,不过考虑到 shellcheck 完全可以发掘出这些问题,于是忍痛把它们都剔除在外了。毫无疑问,使用 shellcheck 给我的 shell 编写技能带来了巨大的飞跃。

所谓“站在巨人的肩膀上”,虽然我们这些新兵蛋子,技能不如老兵们强,但是我们可以在装备上赶上对方啊!动动手安装一下,就能结识一个循循善诱的“老师”,何乐而不为?
顺便一提,shellcheck 居然是用 haskell 写的。谁说 haskell 只能用来装逼?

4. 变量展开

在 shell 脚本中,偶尔可以看到这样的做法:echo $xxx | awk/sed/grep/cut... 。看起来大张形势的样子,其实不过是想修改一个变量的值。杀鸡何必用牛刀?bash内建的变量展开机制已经足以满足你各种需求!还是老方法, read the f**k manaul! man bash 然后搜索Parameter Expansion,下面就是你想要的技巧。

5. 注意local

随着代码越写越多,你开始把重复的逻辑提炼成函数。有可能你会掉到bash的一个坑里。在bash,如果不加 local 限定词,变量默认都是全局的。变量默认全局——这跟 js 和 lua 相似;但相较而言,很少有 bash 教程一开始就告知你这个事实。在顶级作用域里,是否是全局变量并不重要。但是在函数里面,声明一个全局变量可能会污染到其他作用域(尤其在你根本没有注意 到这一点的情况下)。所以,对于在函数内声明的变量,请务必记得加上 local 限定词。

6. trap信号

如果你写过稍微复杂点的在后台运行的程序,应该知道 posix 标准里面“信号”是什么一回事。如果不知道,直接看下一段。像其他语言一样,shell 也支持处理信号。trap sighandler INT可以在接收到 SIGINT 时调用 sighandler 函数。捕获其他信号的方式以此类推。

不过 trap 的主要应用场景可不是捕获哪个信号。trap 命令支持“捕获”许多不同的流程——准确来说,允许用户给特定的流程注入函数调用。其中最为常用的是trap func EXIT和trap func ERR。

trap func EXIT允许在脚本结束时调用函数。由于无论正常退出抑或异常退出,所注册的函数都能得以调用,在需要调用一个清理函数的场景下,我都是用它注册清理函数,而不是简单地在脚本结尾调用清理函数。

trap func ERR允许在运行出错时调用函数。一个常用的技法是,使用全局变量ERROR存储错误信息,然后在 注册的函数中根据存储的值完成对应的错误报告。把原本四分五裂的错误处理逻辑集中到一处,有时候会起奇效。不过要记住,程序异常退出时,既会调用EXIT 注册的函数,也会调用ERR注册的函数。

7. 三思后行

以上几条都是具体的建议,剩下两条比较务虚。

这条建议的名字叫“三思而行”。其实无论写什么代码,哪怕只是一个辅助脚本,都要三思而行,切忌粗心大意。不,写脚本的时候更要记住这点。毕竟许多 时候,一个复杂的脚本发端于几行小小的命令。一开始写这个脚本的人,也许以为它只是一次性任务。代码里难免对一些外部条件有些假定,在当时也许是正常的, 但是随着外部环境的变化,这些就成了隐藏的暗礁。雪上加霜的是,几乎没有人会给脚本做测试。除非你去运行它,否则不知道它是否还能正常使用。

要想减缓脚本代码的腐烂速度,需要在编写的时候辨清哪些是会变的依赖、哪些是脚本正常运行所不可或缺的。要有适当的抽象,编写可变更的代码;同时要有防御性编程的意识,给自己的代码一道护城河。

8. 扬长避短

有些时候,使用 shell 写脚本就意味着难以移植、难以统一地进行错误处理、难以利索地处理数据。
虽然使用外部的命令可以方便快捷地实现各种复杂的功能,但作为硬币的反面,不得不依靠grep、sed、awk等各种工具把它们粘合在一起。

如果有兼容多平台的需求,还得小心规避诸如BSD和GNU coreutils,bash版本差异之类奇奇怪怪的陷阱。

由于缺乏完善的数据结构以及一致的API,shell 脚本在处理复杂的逻辑上力不从心。

解决特定的问题要用合适的工具。知道什么时候用 shell,什么时候切换到另外一门更通用的脚本语言(比如ruby/python/perl),这也是编写可靠 shell 脚本的诀窍。如果你的任务可以组合常见的命令来完成,而且只涉及简单的数据,那么 shell 脚本就是适合的锤子。如果你的任务包含较为复杂的逻辑,而且数据结构复杂,那么你需要用ruby/python之类的语言编写脚本。

以上所述是小编给大家介绍的Shell脚本编写的8 个可靠建议(值得收藏),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • shell脚本编写的俄罗斯方块游戏代码

    粘贴以下代码到一个空的Shell脚本文件中,并在Bash 中运行即可! 复制代码 代码如下: #!/bin/bash # Tetris Game# 10.21.2003 xhchen<[email]xhchen@winbond.com.tw[/email]> #APP declarationAPP_NAME="${0##*[\\/]}"APP_VERSION="1.0" #颜色定义cRed=1cGreen=2cYellow=3cBlue=4cFuchsi

  • CentOS下编写shell脚本来监控MySQL主从复制的教程

    目的:定时监控MySQL主从数据库是否同步,如果不同步,记录故障时间,并执行命令使主从恢复同步状态 1.创建脚本文件 vi /home/crontab/check_mysql_slave.sh #编辑,添加下面代码 #!/bin/sh # check_mysql_slave status ip=eth0 #网卡名称 mysql_binfile=/usr/local/mysql/bin/mysql mysql_user=root #MySQL数据库账号 mysql_pass=123456 #密码

  • Shell脚本编写Nagios插件监控程序资源占用

    一般情况下,我们只需要监控程序进程在没在就可以了.但是这次遭遇了这样的事,公司开发的程序,程序进程还在,但是死锁了.导致大范围的影响,更要命的是根本不知道问题出在哪里,还是别的测试部同事帮忙发现的,真是丢尽运维的脸了- 为避免下次再遭遇到这样的情况,分析了这次进程死锁的现象,发现死锁会占用100%的cpu,正常情况下只占用10%以内.决定编写nagios插件,用来监控程序占用的资源,包括cpu,内存等. 一.shell脚本需求分析: 能设置cpu,mem的阈值,资源占用超过阈值就报警.    要

  • 一步步教你如何编写Shell脚本

    问题: 请求在线api, 获取json格式的返回结果 解析json里面的一个值 将该值存储到文件 开发准备 1.shell 快速入门: #!/bin/sh cd ~ mkdir shell_tut cd shell_tut for ((i=0; i<10; i++)); do touch test_$i.txt done 创建一个文件夹, 并在里面新建10个文件 2.curl 使用curl请求网络, 并获取返回值 curl基本用法: $ curl www.sina.com 直接返回网页内容 $

  • Shell脚本编写的八条可靠建议(值得收藏)

    这八个建议,来源于键者几年来编写 shell 脚本的一些经验和教训.事实上开始写的时候还不止这几条,后来思索再三,去掉几条无关痛痒的,最后剩下八条.毫不夸张地说,每条都是精挑细选的,虽然有几点算是老生常谈了. 1. 指定bash shell 脚本的第一行,#!之后应该是什么?如果拿这个问题去问别人,不同的人的回答可能各不相同. 我见过/usr/bin/env bash,也见过/bin/bash,还有/usr/bin/bash,还有/bin/sh,还有/usr/bin/env sh.这算是编程界的

  • shell脚本实现多彩进度条

    先贴代码 #!/bin/bash i=0; str="" arr=("|" "/" "-" "\\") while [ $i -le 100 ] do let index=i%4 let indexcolor=i%8 let color=30+indexcolor printf "\e[0;$color;1m[%-100s][%d%%]%c\r" "$str" &qu

  • 分享9个实战及面试常用Linux Shell脚本编写

    注意事项 1)开头加解释器:#!/bin/bash 2)语法缩进,使用四个空格:多加注释说明. 3)命名建议规则:变量名大写.局部变量小写,函数名小写,名字体现出实际作用. 4)默认变量是全局的,在函数中变量local指定为局部变量,避免污染其他作用域. 5)有两个命令能帮助我调试脚本:set -e 遇到执行非0时退出脚本,set-x 打印执行过程. 6)写脚本一定先测试再到生产上. 1 获取随机字符串或数字 获取随机8位字符串: 获取随机8位数字: cksum:打印CRC效验和统计字节 2 定

  • shell脚本编写ping包及arp的监控并发送短信功能

    下面给大家分享shell脚本编写ping包及arp的监控并发送短信功能,具体代码如下所示: #!/bin/bash NOW="`date +%Y%m%d-%H:%M:%S`" PHONES=15134567893 IP=10.100.8.78 GATEWAY=10.100.30.1 #ping失误的短信内容 content1="host is failed" #arp主机网关失误的短信内容 content2="GW is failed" #pin

  • shell脚本编写俄罗斯方块

    网上看到的一个用linux的shell脚本写的俄罗斯方块.共享一下. 原作者信息在脚本的注释中有. 下载地址:点击下载 代码: #!/bin/bash # Tetris Game # 10.21.2003 xhchen<[email]xhchen@winbond.com.tw[/email]> #APP declaration APP_NAME="${0##*[\\/]}" APP_VERSION="1.0" #颜色定义 cRed=1 cGreen=2

  • vue中组件通信的八种方式(值得收藏!)

    前言 之前写了一篇关于vue面试总结的文章, 有不少网友提出组件之间通信方式还有很多, 这篇文章便是专门总结组件之间通信的 vue是数据驱动视图更新的框架, 所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通信的呢? 首先我们需要知道在vue中组件之间存在什么样的关系, 才更容易理解他们的通信方式, 就好像过年回家,坐着一屋子的陌生人,相互之间怎么称呼,这时就需要先知道自己和他们之间是什么样的关系. vue组件中关系说明: 如上图所示, A与B.A与C.B与D.C与E组件之间

  • 详解shell脚本的编写规范

    编写shell脚本的一些规范 解释器 shell脚本一般选择bash作为解释器,脚本开头应为 #!/bin/bash 或 #!/bin/sh 添加脚本版本和注释功能 在脚本的开头加注释,说明脚本作者.编写时间.脚本功能,最好可以加上脚本的版本号.shell中脚本注释用#,注释语句和#号之间有一个空格 #!/bin/bash # 脚本版本 # 脚本作者 # 脚本时间 # 脚本功能 添加脚本调试 通过set命令添加脚本调试,如果报错,脚本直接退出,不继续执行,对于管道错误也可以直接退出 #!/bin

  • Shell脚本入门之编写格式与执行方式的实现

    目标 1.掌握shell脚本编写规范 2.掌握执行shell脚本文件的3种方式与区别 Shell脚本文件编写规范 脚本文件后缀名规范 shell脚本文件就是一个文本文件, 后缀名建议使用 .sh 结尾 首行格式规范 首行需要设置Shell解析器的类型, 语法 #!/bin/bash 含义: 设置当前shell脚本文件采用bash解析器运行脚本代码 注释格式 单行注释, 语法 # 注释内容 多行注释, 语法 :<<! # 注释内容1 # 注释内容2 ! shell脚本HelloWord入门案例

随机推荐