编写快速安全 Bash 脚本的建议 | 翻译

Posted OSC开源社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编写快速安全 Bash 脚本的建议 | 翻译相关的知识,希望对你有一定的参考价值。

#点击图片报名参加武汉、长沙源创会#


昨天我和一些朋友聊起Bash,我意识到:即使我已经使用Bash十多年了,现在还有一些基础的杂项,我理解的并不是很清晰。 像往常一样,我认为我应该写一个博文。会包含下面几个方面:

  • 一些bash基础知识(“你怎么写一个for循环”)

  • 杂项事宜(“总是引用你的bash变量”)

  • bash脚本安全提示(“总是使用set -u”)

如果你编写shell脚本,并且你没有阅读这篇文章中其他任何内容,你应该知道有一个shell脚本校验工具(linter),叫做。使用它来使您的shell脚本更好!

我们会像讨论编程语言一样讨论bash,因为,怎么说呢,它就是。 这篇文章的目标不是bash编程详解。我不会在bash中做复杂的编程,也真的不计划学习如何去做。 但是,经过今天的思考之后,我认为明确整理下bash编程语言的一些基础知识是有用的。bash编程语言与我使用过的其他编程语言有着很大的不同。
我真的曾认为我已经知道这些东西了,但是通过写这篇文章我依旧学到了一些东西,也许你也会有所收获。


变量赋值

编写快速安全 Bash 脚本的建议 | 翻译


在bash中变量赋值按照下面的方式:

编写快速安全 Bash 脚本的建议 | 翻译

并且你可以使用$VARIABLE(变量名)来引用变量。需要注意的是不要在=运算符的两边放置空格符,比如VARIABLE= 2、VARIABLE = 2、或者VARIABLE =2,这并不是语法错误,但是将会做完全不需要的事情(比如试图运行一个名字为2的程序,并将环境变量VARIABLE设置为空字符串)。

Bash变量并不要求全部大写,但是通常是大写的。

大多数你所使用的bash变量都是字符串。在bash中也有一些数组变量,但我并不是完全理解它们。


编写快速安全 Bash 脚本的建议 | 翻译

使用${}引用变量

编写快速安全 Bash 脚本的建议 | 翻译


有时某些变量,内容为file.txt,并且我想这样使用它:

编写快速安全 Bash 脚本的建议 | 翻译

这段代码是无法工作的!它会去查找MYVAR__bak变量,但这并不是一个真实存在的变量。

为了避免类似问题,你需要知道的仅仅是${MYVAR}和$MYVAR是一回事。所以你可以这样运行指令:

编写快速安全 Bash 脚本的建议 | 翻译


编写快速安全 Bash 脚本的建议 | 翻译

全局变量,局部变量和环境变量

编写快速安全 Bash 脚本的建议 | 翻译


Bash有3种变量。我一般先想到(可能也是最常用)的是环境变量。

Linux上的每个进程实际上都有环境变量(您可以运行env查看当前设置的变量),但在Bash中,它们更易于访问。要查看名为MYVAR的环境变量,可以运行

编写快速安全 Bash 脚本的建议 | 翻译

要设置环境变量,您需要使用export关键字:

编写快速安全 Bash 脚本的建议 | 翻译

设置环境变量时,所有子进程将看到该环境变量。所以如果你运行export MYVAR=2; python test.py,python程序将MYVAR设置为2。

第二种变量是全局变量。同样像上面那样赋值。

编写快速安全 Bash 脚本的建议 | 翻译

在其他编程语言中他们表现得像全局变量。

还有局部变量,它们的作用域只能存在于bash函数中。 我基本上从来没有使用过这样的函数(不像我写的其他编程语言),我从来没有使用过局部变量。


编写快速安全 Bash 脚本的建议 | 翻译

for循环

编写快速安全 Bash 脚本的建议 | 翻译


以下是我在bash中编写循环的方法。 此循环将从1打印到10。

编写快速安全 Bash 脚本的建议 | 翻译

如果你想用一行代码写这个循环,可以这样写:

编写快速安全 Bash 脚本的建议 | 翻译

我觉得这是不可能记住的(你要怎么记住在 seq 1 10 之后有一个分号,但是在 do 之后却没有了),所以我不会去记它。

你也可以写while循环,但我从来没有这样写过。

有个很酷的事情是你可以遍历另一个命令的输出。seq 1 10 将数字从1到10(每行一个)打印,这个for循环只是提取该输出并遍历它。我就经常用这种方法。

您也可以使用反引号或$()来插入命令的输出。

编写快速安全 Bash 脚本的建议 | 翻译


编写快速安全 Bash 脚本的建议 | 翻译

if 语句

编写快速安全 Bash 脚本的建议 | 翻译


在 bash 中的 If 语句是相当让人讨厌去记它。你必须放在这些方括号中,而在方括号之间必须有空格,否则它不起作用。[[ 和 [ 方括号(双/单) 都工作。 这里我们真正进入 bash 的奇怪领域:[ 是一个程序 (/usr/bin/[) 但 [[ 是 bash 语法。[[ 更好。

编写快速安全 Bash 脚本的建议 | 翻译

此外,您可以检查“此文件存在”,“此目录存在”等内容。例如,您可以检查文件 /tmp/awesome.txt 是否存在,如下:

编写快速安全 Bash 脚本的建议 | 翻译

这通常是有用的,但我必须每次查找语法。
如果您想尝试用命令行,可以使用 test 命令,例如 test -e /tmp/awesome.txt 。 它成功会返回0,否则返回错误。
最后一件事是为什么[[比[好:如果你使用[[,那么你可以使用<做比较,它不会变成文件重定向。

编写快速安全 Bash 脚本的建议 | 翻译

还有一个额外的最后一件关于 if 的事:我今天学到是不需要通过[[或者[去使用 if 语句:任何有效的命令都会工作。 所以你可以这样做:

编写快速安全 Bash 脚本的建议 | 翻译


编写快速安全 Bash 脚本的建议 | 翻译

函数不是那么难

编写快速安全 Bash 脚本的建议 | 翻译


在 bash 中定义和调用函数(特别是没有参数)是非常容易的。

编写快速安全 Bash 脚本的建议 | 翻译


编写快速安全 Bash 脚本的建议 | 翻译

总是引用你的变量

编写快速安全 Bash 脚本的建议 | 翻译


另一个 bash 技巧:绝不使用一个没有引用的变量。 看看这个看似合理的 shell 脚本:

编写快速安全 Bash 脚本的建议 | 翻译

如果你尝试运行这个脚本,你会得到不可理喻的错误消息 script.sh: line 3: [: too many arguments. 什么?

Bash 解释这个 if 语句为 if [ i am awesome == i are awesome],这是6个字符串 (i, am, awesome, i, are, awesome) 无意义的 if 语句。 正确的写法是

编写快速安全 Bash 脚本的建议 | 翻译

有些情况下,只要使用 $ X 而不是 “$ X” 就可以,但是您可以知道何时可以,何时不行吗? 我肯定不能。 总是引用你的 bash 变量,你会更快乐的。


编写快速安全 Bash 脚本的建议 | 翻译

返回代码, &&, 和 `||

编写快速安全 Bash 脚本的建议 | 翻译


每个 Unix 程序都有一个“返回代码”,它是一个从0到127的整数。0表示成功,其他都意味着失败。 这在 bash 中是有作用的,因为:有时我从命令行运行一个程序,并希望仅在第一个程序成功的情况下运行第二个程序。

你可以用 && 实现!

例如:create_user && make_home_directory。 这将运行 create_user ,检查返回代码,然后仅在返回代码为0时运行 make_home_directory。

这与 create_user; make_home_directory 不同,无论 create_user 的返回代码是什么,都将运行 make_home_directory。

你也可以使用create_user || make_home_directory,只有create_user运行失败才运行make_home_directory 。 这在技术领域中非常巧妙。


编写快速安全 Bash 脚本的建议 | 翻译

后台进程

编写快速安全 Bash 脚本的建议 | 翻译


我不会在此谈及太多关于 job 控制的内容,但是:你可以像下面这样启动后台进程

编写快速安全 Bash 脚本的建议 | 翻译

如果你后来后悔将进程放到后台,并希望把它带调回前台,你可以用 fg 来做到这一点。 如果不止一个进程,您可以使用 jobs 查看所有后台进程。由于某种原因,fg 需要一个 “job ID”(这就是 jobs 打印输出的)而不是一个 PID。 谁知道 Bash 为什么这样子呢。

另外,如果你在后台运行太多的进程,内置等待命令将等到它们都返回。

说到后悔 - 如果你不小心在错误的终端启动一个进程,Nelson Elhage 有一个很棒的项目叫做  ,可以保存你的进程并将其移到屏幕会话或者某些其他东西中。


编写快速安全 Bash 脚本的建议 | 翻译

安全选项: set -e

编写快速安全 Bash 脚本的建议 | 翻译


当我编写程序时,通常我会犯错误。 在我使用的大多数编程语言中,当有些程序发生可怕的错误时,程序将退出并告诉我出了什么问题。

在 bash 脚本中,你通常运行很多程序。 有时这些程序将在退出是返回一个表示失败的返回码。 默认情况下,当程序失败时,Bash 将继续运行。

例如,在这个脚本中,Python 将失败(因为我正在尝试运行的文件不存在),然后它会愉快地继续执行并打印 “done”。

编写快速安全 Bash 脚本的建议 | 翻译

这几乎不是我想要的 - 如果我的脚本中的一个程序失败,我不希望它继续执行,做一些可能是未定义/可疑的事情! 这太可怕了。set -e 将使脚本停止,并阻止任何潜在的破坏。下面是更安全的版本:

编写快速安全 Bash 脚本的建议 | 翻译


编写快速安全 Bash 脚本的建议 | 翻译

更安全的选项: use set -u

编写快速安全 Bash 脚本的建议 | 翻译


在大多数我所使用的编程语言中,如果我尝试使用未设置的变量我会得到一个错误。这在 Bash 中并不适用!默认的,未设置的变量会被评估为空字符串。下面代码的结果:

编写快速安全 Bash 脚本的建议 | 翻译

在 $DIRECTORY 未设置的情况下,实际上会运行 rm -rf /* 。如果你使用 set -u 选项, bash 会在你尝试使用未设置变量时终止运行并返回失败。


编写快速安全 Bash 脚本的建议 | 翻译

调试选项: use set -x

编写快速安全 Bash 脚本的建议 | 翻译


set -x 将会使得 bash 在实际运行命令之前打印出每个待执行的命令。这对调试真的很有用。 

你可以通过 set -o 选项查看所有其他的 bash 选项。

我所见到的人们在实践中所使用的大量 shell 脚本都以 set -eu 或者 set -eux 打头。一切为了安全性!

如果 pipe 的部分失败了,你也可以使用 set -o pipefail 来退出。


编写快速安全 Bash 脚本的建议 | 翻译

使用 shellcheck 检查 bash 脚本

编写快速安全 Bash 脚本的建议 | 翻译


最近我得知有一个 bash linter 可以帮助检测所有这些怪异的漏洞及和更多(潜在的 bug )! 它被称为 shellcheck,你可以使用 apt-get install shellcheck 命令进行安装。

它还有一个网站! 。 这里有一个例子,你可以从中查找它所给出的错误码的详细信息。这真的很棒,我很乐意尝试。

Shellcheck 能做的比我知道的 bash 脚本最佳做法要多得多:)。当我看到那些类似我写的哪些例子时,“哇,这真是有道理的,但我从来没有这样想过”。

也有一个 shell 格式化工具,名字是 shfmt ,貌似很有用:.


编写快速安全 Bash 脚本的建议 | 翻译

bash 是怪异的,记住一些怪癖 

编写快速安全 Bash 脚本的建议 | 翻译


我认为这些都是我所知道的基本的 bash 怪癖!我想这可能是关于 bash 是一个你不应该使用的可怕的编程语言的批评,以及为什么我们没有一个更不容易混淆的 shell 编程语言,但这并不会带给我多少麻烦。
我只是试图不写非常复杂的 bash 脚本,坚持这里的一些最佳实践,而不必担心太多。无论如何,了解这些奇怪的怪癖是非常有趣的!
如果你喜欢这些,那么人们给我一大堆其他的 bash 资源,我现在就和你分享一下:

  •  防御性 bash 编程

  • 来自 Google 的 (shell 脚本编码风格指南)。

  • TLDP 的 (高级 bash 编程指南)

  • 这个  指南和扩展 

  • 一个测试你 bash 水平的

  • 再次, 和 

(如果我们谈及 shell 的替代物,那么我实际上每天使用的 shell 是 Fish,它是完美的。我喜欢 Fish,是因为它,但我依然选择用 bash 编写 shell 脚本。

感谢 Mat、Marina、Kamal、Geoffrey、Panashe、@gnomon、和 Iain,他们和我一起探讨 Bash!


编写快速安全 Bash 脚本的建议 | 翻译


推荐阅读





点击“阅读原文”查看更多精彩内容

以上是关于编写快速安全 Bash 脚本的建议 | 翻译的主要内容,如果未能解决你的问题,请参考以下文章

FlowCanvas官方文档翻译

主从故障快速修复脚本

编写更好 Bash 脚本的 8 个建议

sh 用于快速设置Golang环境的Bash脚本

sh 用于快速(呃)Laravel应用程序设置的Bash脚本

sh SSH KeyGen +复制Bash脚本(快速ssh密钥设置)