• 编写可靠shell


    前言

    作为互联网从业者,经常需要和Linux打交道,当然不可避免的也要写一些shell,无论是进行CI/CD流水线搭建、数据处理、抑或是进行系统管理,随处可见大量shell脚本的影子。shell有一个很大的特点,也不知道该称之为优点还是缺点,就是它的语法相当灵活,100个人中就有100种写法或是代码风格,究竟该怎么写,可能会逼死强迫症。

    为什么是“可靠shell”呢?大家可能都有过shell语言在某些情况下造成的“灾难”,也许是因为我们使用了非预期的变量、产生了非预期的返回,却没有及时判断处理导致了不可预知的问题。

    于是花了些功夫查阅资料,给团队做了一次分享,始有此文。在这里,笔者无视那些众多的shell解释器,就以Linux标准的bash为例,斗胆整理了了一份关于shell的部分编写建议作为自己写shell时的参考指南,还请各位读者抱着批判性思维来审阅。

    开头指定bash

    指定bash的方式有很多,不过建议大家使用下面两种中的一种:

    如何编写可靠shell

     

    有几点说明:

    1、运行./a.sh时,当没有指定shebang时,就会默认用$SHELL指定的解释器,否则就会用shebang指定的解释器

    2、#!/bin/bash 的方式限制了代码注入的可能,在某些情况下更安全

    3、#!/usr/bin/env bash 的方式通过添加env中间层,使得可以在$PATH中搜索bash,提供灵活性、适应性

    用双引号包围变量

    考虑如下代码段:

    如何编写可靠shell

     

    运行会报错,因为等号前后字符串个数不一致。正确的做法是如下代码:

    如何编写可靠shell

     

    要小心命令行参数中的空格。如果变量要放到if语句中,最好用双引号包围,其他情况下,包围变量也是一个不错的实践。当然,在双引号中继续用{}大括号包围变量,比如"${filename}" ,也是推荐的写法。

    全部代码进函数

    建议除公共部分外,所有的代码都封装进函数,即使只有一个函数,也定义一个main函数。

    如何编写可靠shell

     

    最常见到的不规范的写法就是大家的shell全都不在函数中,一条条命令顺序执行(笔者以前也经常这么写)。

    定义函数有几种方式:

    如何编写可靠shell

     

    建议使用标准写法(第一种)。Shell函数在定义时不能指明参数,只在调用时可以传递参数,且传递什么则接收什么。上面的"$@"则是接收命令行参数的写法。

    使用readonly定义常量

    如何编写可靠shell

     

    使用readonly修饰的变量定义会变成只读变量,无法在脚本中被修改,更加安全。

    关注变量作用域

    如何编写可靠shell

     

    1、Shell中默认变量作用域为全局(无论定义在外层还是函数中)

    2、强烈建议定义变量时用local、readonly修饰(定义在函数内),有充足理由时可以使用declare(如需定义整型变量)

    3、如果必须定义全局变量,则建议全局变量大写

    警惕未被初始化的变量

    如何编写可靠shell

     

    如果运行上面的脚本,参数为空的话,你的根目录的data目录就被删掉了。可以使用nounset标志来防止这种意外情况的发生:

    如何编写可靠shell

     

    1、set –o nounset的另一种表达方式:set -u

    2、当使用了未初始化的变量时,设置set -o nounset,可以让程序强制退出

    当然,上面例子只是为说明问题,脚本可不能这么写,太危险。

    让代码执行可追踪

    不多说,使用set -o xtrace可达到该目的,将每行的执行命令输出。也可以简写为set -x。常用于调试场景,也可以在执行shell时使用sh -x 的方式调试脚本。

    防止错误滚雪球

    假如这么写shell(假设a.txt不存在):

    如何编写可靠shell

     

    那么仍然会尝试删除a.txt。

    如果我们想判断下上一步的执行结果再决定下一步行动,常用的做法可能是这样:

    如何编写可靠shell

     

    我们通过$?的值来判断上一步的状态,进而决定是继续删除还是直接退出。

    还有另外一种方式也可以达到目的:

    如何编写可靠shell

     

    1、set -o errexit的另一种表达方式:set -e

    2、使用set -o errexit,一但有任何一个语句返回非0值,则退出bash,从而尽早捕获错误

    3、此时无法使用$?获取命令执行状态,因为bash无法获得任何非0返回值

    4、如果需要让程序即使出错也继续执行,可以在可能出错的语句追加" || true"

    学会查路径

    强烈建议在脚本开头定义基础目录,如下:

    如何编写可靠shell

     

    1、基于当前脚本执行路径,指定其他路径;

    2、在每个脚本前设置当前工作区、脚本名、工程根目录的只读变量是一个好习惯

    3、让脚本在任何目录下都可以正常执行(脚本中所有位置全部使用决定路径,尽量少使用相对路径;)

    巧用shift

    如何编写可靠shell

     

    上面的func2使用了shift,使得所有命令行参数都可以通过$1读取。再举个更实用的例子:

    如何编写可靠shell

     

    假设该文件命名为test.sh,我们运行时使用:sh test.sh --file a.txt --module module_a 的方式,程序就可以精确获取到每个参数,用于后续的逻辑处理。

    封装一些常用指令

    假如我们经常需要检查命令执行状态,就可以封装一个函数:

    如何编写可靠shell

     

    以后在需要的地方调用该函数即可。

    提供help信息

    不多说,脚本最好提供一个help函数,当用户输入参数异常时能够及时给出反馈。

    切换目录的几种方式

    假如我们需要临时在某个路径下执行一些指令,为了不改变主程序的执行路径,可以有几种方式:

    如何编写可靠shell

     

    巧用trap信号

    如何编写可靠shell

     

    trap func EXIT允许在脚本结束时调用函数,用它注册清理函数。

    让脚本可以单独运行任意一个函数

    如何编写可靠shell

     

    如上编写的脚本(假设test.sh),我们在运行的时候,可以使用sh test.sh --eval start 来单独运行start方法。

    一些额外的小tip

    1、在条件判断时,尽量使用双中括号"[["而非单中括号"["。单中括号是一个Linux命令,每次使用都会fork一个子进程。双中括号是shell关键字,更加强大,可完全替代单中括号

    2、判断时,有个小技巧:[[ "z${var}" = "z" ]] 加入任意前导字符(此处是z)可以防止var变量为空时脚本报错

    3、利用/dev/null过滤不需要的输出信息:$ command > /dev/null 2>&1

    4、变量可以习惯性使用{}包围,以防意外情况,且用双引号包围是个好习惯,如 "${var}"

    5、把then,do等和if、while或者for写在同一行,不换行

    6、一行太长时使用 进行换行,换行原则是整齐美观

    7、禁止直接操作$1、$2等参数,除非这些变量只用一次

    8、整数运算使用$(()),如 echo $((3+4)) ;小数运算使用bc计算器,如 echo "scale=2; 5/3" |bc

    9、尽量使用$()将命令结果赋值给变量,而非使用反引号

    10、尽量使用绝对路径,不易出错

    11、shell脚本main函数接收参数时,尽量使用main "$@" 的形式。以下是各种形式传参的结果:

    ……

    如何编写可靠shell

     

    你的脚本可以这样开始

    如何编写可靠shell

     转自https://www.toutiao.com/i6847025428117324300/?timestamp=1594257392&app=news_article&group_id=6847025428117324300&use_new_style=1&req_id=202007090916320100140631681D6FE111

  • 相关阅读:
    《PHP程序员面试笔试宝典》——如何回答非技术性问题?
    《PHP程序员面试笔试宝典》——如何回答技术性的问题?
    《PHP程序员面试笔试宝典》——如何巧妙地回答面试官的问题?
    我的新书——《PHP程序员面试笔试宝典》
    小程序"errcode":41002错误问题如何解决?
    Apache虚拟主机的搭建及相关问题解决
    PHP面试题详解
    Mysql的基本操作知识
    图片压缩
    JS点击复制
  • 原文地址:https://www.cnblogs.com/cangqinglang/p/13271810.html
Copyright © 2020-2023  润新知