一次又一次,我看到Bash在Stack
Overflow上使用eval
了答案,而答案被猛烈抨击了,旨在使用这种“邪恶的”构造。为什么eval
这么邪恶?
如果eval
不能安全使用,我应该怎么用呢?
这个问题比眼前的问题还重要。我们将从显而易见的内容开始:eval
具有执行“脏”数据的潜力。脏数据是指尚未重写为XYZ的任何数据;在我们的例子中,它是未格式化的任何字符串,以确保评估安全。
乍看之下,对数据进行消毒似乎很容易。假设我们要抛出一个选项列表,bash已经提供了一种清除单个元素的好方法,以及另一种将整个数组作为单个字符串进行清理的方法:
function println
{
# Send each element as a separate argument, starting with the second element.
# Arguments to printf:
# 1 -> "$1\n"
# 2 -> "$2"
# 3 -> "$3"
# 4 -> "$4"
# etc.
printf "$1\n" "${@:2}"
}
function error
{
# Send the first element as one argument, and the rest of the elements as a combined argument.
# Arguments to println:
# 1 -> '\e[31mError (%d): %s\e[m'
# 2 -> "$1"
# 3 -> "${*:2}"
println '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
exit "$1"
}
# This...
error 1234 Something went wrong.
# And this...
error 1234 'Something went wrong.'
# Result in the same output (as long as $IFS has not been modified).
现在说我们要添加一个选项以将输出重定向为println的参数。当然,我们可以只在每次调用时重定向println的输出,但是出于示例的目的,我们不会这样做。我们需要使用eval
,因为变量不能用于重定向输出。
function println
{
eval printf "$2\n" "${@:3}" $1
}
function error
{
println '>&2' '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
exit $1
}
error 1234 Something went wrong.
看起来不错吧?问题是,eval解析了两次命令行(在任何shell中)。在第一次解析时,将删除一层报价。删除引号后,将执行一些可变内容。
我们可以通过在中进行变量扩展来解决此问题eval
。我们要做的就是将所有内容都单引号,将双引号留在原处。一个例外:我们必须在之前扩展重定向eval
,因此必须将其排除在引号之外:
function println
{
eval 'printf "$2\n" "${@:3}"' $1
}
function error
{
println '&2' '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
exit $1
}
error 1234 Something went wrong.
这应该工作。只要$1
in println
从不脏,它也是安全的。
现在稍等片刻:我一直使用与我们一直使用的相同的 未引用 语法sudo
!为什么在这里而不是在这里工作?为什么我们必须将所有内容都单引号?
sudo
有点现代:它知道将收到的每个参数都用引号引起来,尽管这过于简单了。 eval
简单地将所有内容连接在一起。
不幸的是,没有内置的替代品可以eval
像实参一样处理参数sudo
,eval
内置的shell
也不例外。这很重要,因为它在执行时会考虑周围代码的环境和范围,而不是像函数那样创建新的堆栈和范围。
特定的用例通常可以替代eval
。这是一个方便的清单。 command
代表您通常发送给的内容eval
;随便你。
一个简单的冒号在bash中是不可操作的:
:
( command ) # Standard notation
切勿依赖外部命令。您应该始终控制返回值。将它们放在自己的行上:
$(command) # Preferred
`command` # Old: should be avoided, and often considered deprecated
# Nesting:
$(command1 "$(command2)")
`command "\`command\`"` # Careful: \ only escapes $ and \ with old style, and
# special case \` results in nesting.
在调用代码时,将&3
(或高于的任何内容&2
)映射到您的目标:
exec 3<&0 # Redirect from stdin
exec 3>&1 # Redirect to stdout
exec 3>&2 # Redirect to stderr
exec 3> /dev/null # Don't save output anywhere
exec 3> file.txt # Redirect to file
exec 3> "$var" # Redirect to file stored in $var--only works for files!
exec 3<&0 4>&1 # Input and output!
如果是一次调用,则不必重定向整个shell:
func arg1 arg2 3>&2
在调用的函数内,重定向到&3
:
command <&3 # Redirect stdin
command >&3 # Redirect stdout
command 2>&3 # Redirect stderr
command &>&3 # Redirect stdout and stderr
command 2>&1 >&3 # idem, but for older bash versions
command >&3 2>&1 # Redirect stdout to &3, and stderr to stdout: order matters
command <&3 >&4 # Input and output!
场景:
VAR='1 2 3'
REF=VAR
坏:
eval "echo \"\$$REF\""
为什么?如果REF包含双引号,这将中断并打开代码以供利用。可以对REF进行消毒,但是当您使用以下代码时会浪费时间:
echo "${!REF}"
没错,bash从版本2开始就内置了变量间接寻址。与eval
您想做更复杂的事情相比,它有点棘手:
# Add to scenario:
VAR_2='4 5 6'
# We could use:
local ref="${REF}_2"
echo "${!ref}"
# Versus the bash < 2 method, which might be simpler to those accustomed to eval:
eval "echo \"\$${REF}_2\""
无论如何,新方法都更直观,尽管对于习惯了这种编程的经验丰富的程序员来说似乎不是那样eval
。
关联数组在bash 4中内部实现。一个警告:它们必须使用创建declare
。
declare -A VAR # Local
declare -gA VAR # Global
# Use spaces between parentheses and contents; I've heard reports of subtle bugs
# on some versions when they are omitted having to do with spaces in keys.
declare -A VAR=( ['']='a' [0]='1' ['duck']='quack' )
VAR+=( ['alpha']='beta' [2]=3 ) # Combine arrays
VAR['cow']='moo' # Set a single element
unset VAR['cow'] # Unset a single element
unset VAR # Unset an entire array
unset VAR[@] # Unset an entire array
unset VAR[*] # Unset each element with a key corresponding to a file in the
# current directory; if * doesn't expand, unset the entire array
local KEYS=( "${!VAR[@]}" ) # Get all of the keys in VAR
在较早版本的bash中,可以使用变量间接寻址:
VAR=( ) # This will store our keys.
# Store a value with a simple key.
# You will need to declare it in a global scope to make it global prior to bash 4.
# In bash 4, use the -g option.
declare "VAR_$key"="$value"
VAR+="$key"
# Or, if your version is lacking +=
VAR=( "$VAR[@]" "$key" )
# Recover a simple value.
local var_key="VAR_$key" # The name of the variable that holds the value
local var_value="${!var_key}" # The actual value--requires bash 2
# For < bash 2, eval is required for this method. Safe as long as $key is not dirty.
local var_value="`eval echo -n \"\$$var_value\""
# If you don't need to enumerate the indices quickly, and you're on bash 2+, this
# can be cut down to one line per operation:
declare "VAR_$key"="$value" # Store
echo "`var_key="VAR_$key" echo -n "${!var_key}"`" # Retrieve
# If you're using more complex values, you'll need to hash your keys:
function mkkey
{
local key="`mkpasswd -5R0 "$1" 00000000`"
echo -n "${key##*$}"
}
local var_key="VAR_`mkkey "$key"`"
# ...
本文向大家介绍为什么在Bash中应该避免eval,我应该用什么来代替呢?,包括了为什么在Bash中应该避免eval,我应该用什么来代替呢?的使用技巧和注意事项,需要的朋友参考一下 eval是Bash shell的内置命令,它将其参数连接为单个字符串。然后,它将参数与空格连接起来,然后将该字符串作为bash命令执行。以下是其工作方式的示例。 eval示例 在下面的示例中,我们使用一个字符串,该字符串
对于与PropertyValueFactory相关的问题,许多回答(和评论)建议避免使用该类和其他类似类。使用这个类有什么问题?
问题内容: 之间有什么区别: 和 我知道JPanel是GUI组件的容器,但我确实看不到使用它的实用程序。当然,我错了,但我是从Swing开始的,所以…为什么我应该使用JPanel?真正的目的是什么? 问题答案: 为什么我应该使用JPanel? 您可以使用JPanel获得以下一项或多项好处: 将组件分组在一起。 为了更好地组织您的组件。 为了使我们能够使用 多种布局 并组合其效果。(例如,用于数字键
问题内容: 看看这个: 我运行了一个快速的Google搜索,但找不到答案- 我应该用什么代替? 问题答案: 由于django 1.7 引入的迁移系统而被弃用。 现在,您可以使用 跟踪 更改。这会将您的模型更改转换为python代码,以使其可部署到另一个数据库。当您需要对数据库进行进一步的修改时,可以使用数据迁移。 创建迁移后,您必须 应用 它们:。 因此,除了使用之外,您还应该使用然后。 更改模型
问题内容: 看看这个: 问题答案: 由于django 1.7引入的迁移系统而被弃用。 现在,你可以使用跟踪更改。这会将你的模型更改转换为python代码,以使其可部署到另一个数据库。当你需要对数据库进行进一步的修改时,可以使用数据迁移。 创建迁移后,你必须应用它们:。 因此,除了使用之外,你还应该使用然后。 更改模型中的某些内容后,开发工作流程如下: 在你的生产系统上: 奖励:你无需migrate
为什幺应该使用流 在node中,I/O都是异步的,所以在和硬盘以及网络的交互过程中会涉及到传递回调函数的过程。你之前可能会写出这样的代码: var http = require('http'); var fs = require('fs'); var server = http.createServer(function (req, res) { fs.readFile(__dirname