8 Tips for Writing Reliable Shell Scripts

http://codebay.cn/post/945.html?1486690383042

These eight suggestions are derived from some of the experience and lessons key people have written shell scripts over the years. In fact, at the beginning of writing, there were more than these few articles. After thinking about it again and again, I removed a few irrelevant ones, and finally there were eight articles left. It’s no exaggeration to say that each one is handpicked, although a few are clichés.

1. Specify bash

The first line of the shell script, what should be after #!? If you ask this question to others, the answer may vary from person to person.

I've seen /usr/bin/env bash, and I've seen /bin/bash, and /usr/bin/bash, and /bin/sh, and /usr/bin/env sh. This can be regarded as the "four ways of writing ''Fen'" in the programming world.

In most cases, the above five ways of writing are equivalent. However, anyone who has written programs knows that there are often unexpected pits hidden in "a few cases".

What if the default shell of the system is not bash? For example, in a certain version of a Linux distribution, the default sh is not bash.

What if the system's bash is not in /usr/bin/bash?

I recommend using /usr/bin/env bash and /bin/bash. The former adds an intermediate layer through env, allowing env to search for bash in $PATH; the latter is officially endorsed, the conventional bash location, /usr/bin/bash is just a symbolic link to it.

2. set -e and set -x

OK, after some discussion, the first line is now settled. Now it's time to start writing the second line, right?

Wait a minute! Insert a line of set -e and a line of set -x before you start to conceive and write down the specific code logic.

set -x will output the content of execution when executing each line of shell script. It allows you to see the current execution, and the variables involved will be replaced with actual values.

set -e will end the program on execution error, just like "throw exception" in other languages. (To be precise, not all errors will end the program, see note below)

Note: The conditions for set -e to end the program are more complicated. In man bash, a paragraph is used to describe various scenarios. Most executions exit on error, unless the shell command is in the following conditions:

  1. A non-ending part of a pipeline, such as error | ok
  2. The non-ending part of a compound statement, such as ok && error || other
  3. A non-ending part of a sequence of statements, such as error; ok
  4. In the judgment statement, including test, if, while, etc.

The combination of these two can save you a lot of time when debugging. For defensive programming reasons, it is necessary to insert them before writing the first specific line of code. Ask yourself, when writing code, how many times can you get it right at once? Most code, before committing, usually undergoes a process of iterative debugging and modification. Rather than introducing these two configurations when you are in a rush, it is better to leave room for debugging at the beginning. After the code is finally ready to commit, it's not too late to consider whether to keep them.

 

3. Bring shellcheck

Well, now I have three lines of (boilerplate) code, and I haven't written a single line of specific business logic. Is it time to start writing?

Wait a minute! If a worker wants to do a good job, he must first sharpen his tools. This time, I will introduce a shell scripting artifact:

shellcheck

Ashamed to say, although I wrote shell scripts for several years, I still can't remember some syntax. At this time, it is necessary to rely on shellcheck to give pointers. In addition to alerting syntax problems, shellcheck can also check out bad code common to shell scripting. Originally, some of my N suggestions were about these bad codes, but considering that shellcheck can fully discover these problems, I reluctantly excluded them all. Without a doubt, using shellcheck has given me a huge leap in my shell writing skills.

The so-called "standing on the shoulders of giants", although we newbies, our skills are not as strong as the veterans, but we can catch up with each other in equipment! Just install it, you can get acquainted with a "teacher" who is persuasive and persuasive, why not do it?

By the way, shellcheck is actually written in haskell. Who said haskell can only be used for coercion?

 

4. Variable expansion

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

 

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 脚本在处理复杂的逻辑上力不从心。

autosize

 

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

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326363306&siteId=291194637