前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【翻译】请停止编写 shell 脚本

【翻译】请停止编写 shell 脚本

作者头像
保持热爱奔赴山海
发布2022-09-26 15:35:21
2.3K0
发布2022-09-26 15:35:21
举报
文章被收录于专栏:饮水机管理员饮水机管理员

原文: ​​https://pythonspeed.com/articles/shell-scripts/​

作者:​​Itamar Turner-Trauring​​最后更新于 2022 年 3 月 24 日,最初创建于 2022 年 3 月 22 日

当您自动化某些任务时,例如为 Docker 打包您的应用程序时,您经常会发现自己正在编写 shell 脚本。您可能有一个bash脚本来驱动打包过程,另一个脚本作为容器的入口点。随着您的包装变得越来越复杂,您的 shell 脚本也越来越复杂。

一切正常。

然后,有一天,你的 shell 脚本做了一些完全错误的事情。

那是你意识到你的错误的时候:​​bash​​和一般的 shell 脚本语言,在默认情况下大多是被破坏的。除非您从第一天开始就非常小心,否则几乎可以保证任何超过一定复杂度级别的 shell 脚本都是错误的……并且改进正确性功能非常困难。

shell脚本的问题

bash作为一个具体的例子,我们重点来看看。

问题 #1:错误不会停止执行

考虑以下 shell 脚本:

#!/bin/bash
touch newfile
cp newfil newfile2  # Deliberate typo
echo "Success"

当我们运行它时,你认为会发生什么?

$ bash bad1.sh 
cp: cannot stat 'newfil': No such file or directory
Success

即使命令失败,脚本也会继续运行!将此与 Python 进行比较,其中异常会阻止以后的代码运行。

您可以通过添加set -e到 shell 脚本的顶部来解决此问题:

#!/bin/bash
set -e
touch newfile
cp newfil newfile2  # Deliberate typo, don't omit!
echo "Success"

现在:

$ bash bad1.sh 
cp: cannot stat 'newfil': No such file or directory

问题 #2:未知变量不会导致错误

接下来让我们考虑以下脚本,它尝试将目录添加到PATH环境变量中。 PATH是如何找到可执行文件的位置。

#!/bin/bash
set -e
export PATH="venv/bin:$PTH"  # Typo is deliberate
ls

当我们运行它时:

$ bash bad2.sh 
bad2.sh: line 4: ls: command not found

它找不到ls,因为我们有一个错字,写PTH而不是PATH——并且bash没有抱怨未知的环境变量。在 Python 中你会得到一个NameError例外;在编译语言中,代码甚至无法编译。在bash脚本中只是继续运行;会出什么问题?解决方案是set -u:

#!/bin/bash
set -eu
export PATH="venv/bin:$PTH"  # Typo is deliberate
ls

现在 bash 发现了错字:

$ bash bad2.sh
bad2.sh: line 3: PTH: unbound variable

问题 #3:管道不会捕获错误

我们认为我们用 解决了失败的命令问题set -e,但我们并没有解决所有情况:

#!/bin/bash
set -eu
nonexistentprogram | echo
echo "Success!"

当我们运行它时:

$ bash bad3.sh 
bad3.sh: line 3: nonexistentprogram: command not found

Success! 

解决方案是set -o pipefail

#!/bin/bash
set -euo pipefail
nonexistentprogram | echo
echo "Success!"

现在:

$ bash bad3.sh 
bad3.sh: line 3: nonexistentprogram: command not found

至此,我们已经实现了(大部分)​​非官方的bash严格模式​​。但这还不够。

问题 #4:子shell 很奇怪

注意:本文的早期版本包含有关子shell 的错误信息。感谢 Loris Lucido 指出我的错误。

使用该$()语法,您可以启动一个子shell:

#!/bin/bash
set -euo pipefail
export VAR=$(echo hello | nonexistentprogram)
echo "Success!"

当我们运行它时:

$ bash bad4.sh 
bad4.sh: line 3: nonexistentprogram: command not found
Success!

这是怎么回事?如果子shell 中的错误是命令参数的一部分,则它们不会被视为错误。这意味着 subshell 的错误会被丢弃。

一个例外是直接设置变量,所以我们需要这样编写代码:

#!/bin/bash
set -euo pipefail
VAR=$(echo hello | nonexistentprogram)
export VAR
echo "Success!"

现在我们的程序运行正常:

$ bash good4.sh 
good4.sh: line 3: nonexistentprogram: command not found

这可能是对​​bash​​的不良行为的充分证明,但肯定不是完整的证明。

使用 shell 脚本的一些不好的理由

无论如何,您可能想要使用 shell 脚本的一些原因是什么?

不好的原因#1:它总是在那里!

几乎每个 Unix-y 计算环境都会有一个基本的 shell。因此,如果您正在编写一些打包或启动脚本,那么很容易使用您知道会出现的工具。

问题是,如果你正在打包一个 Python 应用程序,你几乎可以保证开发环境、CI 和运行时环境都安装了 Python。那么为什么不使用默认情况下实际处理错误的编程语言呢?

更广泛地说,几乎每一种具有相当规模用户群的编程语言都会有某种面向脚本的库或习语。例如,Rust 也有​​xshell​​, 和其他库。因此,在大多数情况下,您可以使用您选择的编程语言而不是 shell 脚本。

不好的原因 #2:只需编写正确的代码!

理论上,如果您知道自己在做什么,并且保持专注并且不会忘记任何样板文件,那么您可以编写正确的 shell 脚本,甚至是非常复杂的脚本。你甚至可以编写单元测试。

在实践中:

  • 你可能不是一个人工作;您团队中的每个人都不太可能拥有相关专业知识。
  • 每个人都会感到疲倦,心烦意乱,否则最终会犯错误。
  • 我见过的几乎每个复杂的 shell 脚本都缺少

set -euo pipefail

  • 调用,而且事后添加它非常困难(通常是不可能的)。
  • 我不确定我是否见过针对 shell 脚本的自动化测试。我确信它们存在,但它们非常罕见。

不好的原因 #3:Shellcheck 将捕获所有这些错误!

如果你正在编写 shell 程序,​​shellcheck​​这是一个非常有用的捕捉 bug 的方法。不幸的是,仅靠它是不够的。

考虑以下程序:

#!/bin/bash
echo "$(nonexistentprogram | grep foo)"
export VAR="$(nonexistentprogram | grep bar)"
cp x /nosuchdirectory/
echo "$VAR $UNKNOWN_VAR"
echo "success!"

如果我们运行这个程序,它会打印“成功!”,即使它有 4 个单独的问题(至少):

$ bash bad6.sh 
bad6.sh: line 2: nonexistentprogram: command not found

bad6.sh: line 3: nonexistentprogram: command not found
cp: cannot stat 'x': No such file or directory
 
success!

怎么shellcheck做?它会解决一些问题……但不是全部:

  1. 如果你运行​​shellcheck​​,它会指出问题所在​​export​
  2. 如果您运行​​shellcheck -o all​​它会运行所有检查,它也会指出​​echo "$(nonexistentprogram ...)"​​ 也就是说,假设您使用的是 2021 年 11 月发布的 v0.8。旧版本没有此检查,因此任何早于的 Linux 发行版都会为您提供​​shellcheck​​不会遇到该问题的版本。
  3. 它不建议​​set -euo pipefail​

如果您依赖​​shellcheck​​​我强烈建议升级并确保您使用​​-o all​​.

停止编写 shell 脚本

Shell 脚本在某些情况下很好:

  • 对于您手动监督的一次性脚本,您可以采用更宽松的做法。
  • 有时你真的不能保证另一种编程语言可用,你需要使用 shell 来让事情顺利进行。
  • 对于足够简单的情况,只需按顺序运行几个命令,没有子shell、条件逻辑或循环​​set -euo pipefail​​ 就足够了(并确保使用​​shellcheck -o all​​)。

一旦你发现自己做了除此之外的任何事情,你最好使用一种不易出错的编程语言。鉴于大多数软件往往会随着时间的推移而增长,你最好的选择是从不那么坏的东西开始。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-08-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • shell脚本的问题
    • 问题 #1:错误不会停止执行
      • 问题 #2:未知变量不会导致错误
        • 问题 #3:管道不会捕获错误
          • 问题 #4:子shell 很奇怪
          • 使用 shell 脚本的一些不好的理由
            • 不好的原因#1:它总是在那里!
              • 不好的原因 #2:只需编写正确的代码!
                • 不好的原因 #3:Shellcheck 将捕获所有这些错误!
                • 停止编写 shell 脚本
                相关产品与服务
                容器服务
                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档