我应该在我的 shell 脚本中避免使用 bash -c、sh -c 和其他 shell 的等效项吗?

should I avoid bash -c, sh -c, and other shells' equivalents in my shell scripts?

考虑以下代码:

#!/bin/bash -x
VAR='1 2 3'
bash -c "echo "$$VAR""
eval "echo "$$VAR""
bash -c "echo \"$$VAR\""
eval "echo \"$$VAR\""

输出:

+ VAR='1 2 3'
+ bash -c 'echo ' 2 3
3
+ eval 'echo ' 2 3
++ echo 2 3
2 3
+ bash -c 'echo " 2 3"'
 2 3
+ eval 'echo " 2 3"'
++ echo ' 2 3'
 2 3

似乎 evalbash -c 都以相同的方式解释代码,即 "echo "$$VAR""'echo ' 2 3"echo \"$$VAR\""'echo " 2 3"'

我似乎注意到的唯一区别是 bash -c 打开了一个子 shell,因此结果与 eval 不同。例如,在

bash -c 'echo ' 2 3

2和3是子shell的位置参数。另一方面,在

eval 'echo ' 2 3

它们只是 echo 的另一个论点。

所以我的问题是,-c 选项(bash -csh -c 或其他 shell 的等效项)可以安全使用还是像 eval?

据我所知,没有理由使用 bash shell 中的 bash -c。使用它会启动一个新的进程,这是昂贵的。

您可以使用 eval,它不会启动新进程。如果你想要一个子shell(例如,为了保护环境)你可以使用括号。

通常,bash -c(或其他带 -c 的 shells)用于从另一个非 shell 环境(或者可能是 [=解释的 DSL)执行命令=20=]) 其中需要对参数进行 shell 扩展。例如,在过去,您可能会在 C 程序中将它与 execvp 一起使用。如今,在大多数环境中通常有 运行 使用 shell 命令的方法。

是的,您应该避免在 shell 脚本中使用 sh -c 和等效项,就像您避免使用 eval.

一样
eval "$foo"

...归根结底,这是一种安全风险,因为它将数据视为代码,从一开始就重新启动解析过程(因此,运行宁扩展,重定向等)。此外,由于在此过程中考虑了引用上下文,因此正在评估的数据中的内容能够转义引号、终止命令,并以其他方式试图逃避在安全方面所做的任何努力。

sh -c "$foo"

做同样的事情——运行宁扩展、重定向等——仅在全新的shell(不共享非导出变量或其他状态)中。

这两者都意味着 $(rm -rf /) 之类的内容很容易被扩展,除非非常小心,而不是确保——通常情况下——数据只会永远被视为数据,这是编写安全代码的基本要素。


现在,令人高兴的是,当您使用 bash(或 zsh 或 ksh93)而不是 sh 时,您 可以 几乎在所有情况下都避免使用 eval

例如:

value=$(eval "echo $$varname")

...可以替换为:

value=${!varname}

...在 bash,

eval "$varname="'$value'

...可以替换为...

printf -v varname %s "$value"

...等等(ksh93 和 zsh 与后者有直接的等价物);涉及关联映射等的更高级公式最好通过新的 bash 4.3(和 ksh93)namevar 支持来解决。


值得注意的是,bash -c 不会 在上面的大多数示例中有效地替换 eval,因为它不会 运行相同的上下文:当子进程退出时,对 shell 状态所做的更改将被丢弃;因此,bash -c 不仅不能买到安全性,而且 也不能 作为 eval 的替代品。