为什么要EVAL避免在Bash中,和我应该怎么用呢? [英] Why should eval be avoided in Bash, and what should I use instead?

查看:112
本文介绍了为什么要EVAL避免在Bash中,和我应该怎么用呢?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

一次又一次,我看到堆栈&NBSP猛砸答案;溢评估使用 - 和答案得到撞坏,双关语意,对于使用这样的恶建设。为什么评估如此邪恶?

Time and time again, I see Bash answers on Stack Overflow using eval--and the answers get bashed, pun intended, for the use of such an "evil" construct. Why is eval so evil?

如果评估不能安全使用,我应该怎么用呢?

If eval can't be used safely, what should I use instead?

推荐答案

还有更多的这个问题比满足眼睛。我们将具有明显的启动:评估必须执行脏数据的潜力。脏数据是没有被改写为安全可直接使用的,在情况-XYZ的任何数据;在我们的例子,那就是还没有被格式化,以便评估安全的任何字符串。

There's more to this problem than meets the eye. We'll start with the obvious: eval has the potential to execute "dirty" data. Dirty data is any data that has not been rewritten as safe-for-use-in-situation-XYZ; in our case, it's any string that has not been formatted so as to be safe for evaluation.

消毒数据容易出现乍一看。假设我们扔围绕一个选项列表,庆典已经提供了一个伟大的方式来净化单个元素,而另一种方式来净化整个数组作为一个字符串:

Sanitizing data appears easy at first glance. Assuming we're throwing around a list of options, bash already provides a great way to sanitize individual elements, and another way to sanitize the entire array as a single string:

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在每次调用的输出,但例如起见,我们不打算这样做。我们需要使用评估,因为变量不能用来重定向输出。

Now say we want to add an option to redirect output as an argument to println. We could, of course, just redirect the output of println on each call, but for the sake of example, we're not going to do that. We'll need to use eval, since variables can't be used to redirect output.

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解析命令行两次(在任何壳)。关于解析引用的一层的第一遍被除去。随着引号去掉,一些变量的内容得到执行。

Looks good, right? Problem is, eval parses twice the command line (in any shell). On the first pass of parsing one layer of quoting is removed. With quotes removed, some variable content gets executed.

我们可以让变量扩展采取在评估地方解决这个问题。我们所要做的就是单引号的一切,留下双引号他们在哪里。只有一个例外:我们必须扩大之前评估重定向,从而使已留在引号之外:

We can fix this by letting the variable expansion take place within the eval. All we have to do is single-quote everything, leaving the double-quotes where they are. One exception: we have to expand the redirection prior to eval, so that has to stay outside of the quotes:

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 的println 永远不会脏。

This should work. It's also safe as long as $1 in println is never dirty.

现在坚持一会儿就好了:我使用相同的不带引号的语法,我们与须藤最初使用所有的时间!为什么它在那里工作,而不是在这里?为什么我们要单引号的一切吗? 须藤是有点更现代:它知道在引号它接收每个参数封装,虽然这是一个过于简单化。 评估简单并置的一切。

Now hold on just a moment: I use that same unquoted syntax that we used originally with sudo all of the time! Why does it work there, and not here? Why did we have to single-quote everything? sudo is a bit more modern: it knows to enclose in quotes each argument that it receives, though that is an over-simplification. eval simply concatenates everything.

不幸的是,没有下降的替代产品评估即把参数,像须藤做,因为的eval 是内置了壳;这是很重要的,因为它需要对周围code的环境和范围在执行时,而不是建立一个类似功能的新的堆栈和范围做。

Unfortunately, there is no drop-in replacement for eval that treats arguments like sudo does, as eval is a shell built-in; this is important, as it takes on the environment and scope of the surrounding code when it executes, rather than creating a new stack and scope like a function does.

具体的用例往往有可行的替代品评估。这里有一个方便的名单。 命令重新presents你通常会发送到评估;替补无论你请。

Specific use cases often have viable alternatives to eval. Here's a handy list. command represents what you would normally send to eval; substitute in whatever you please.

一个简单的冒号在bash无操作:
    

A simple colon in a no-op in bash: :

( command )   # Standard notation

执行一个命令的输出

从来不靠外部命令。你应该总是在返回值的控制权。把这些对自己的行:

Execute output of a command

Never rely on an external command. You should always be in control of the return value. Put these on their own lines:

$(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.

重定向基于变

在调用code,地图和3 (或任何比&放越高; 2 )以你的目标:

Redirection based on variable

In calling code, map &3 (or anything higher than &2) to your target:

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!

如果它是一个一次性的电话,你就不必重定向整个外壳:

If it were a one-time call, you wouldn't have to redirect the entire 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包含双引号,这将打破,并打开code战功。这是可能的消毒REF,但它是在浪费时间,当你有这样的:

Why? If REF contains a double quote, this will break and open the code to exploits. It's possible to sanitize REF, but it's a waste of time when you have this:

echo "${!REF}"

这是正确的,bash有内置的为2个版本,被比评估麻烦了一点,如果你想要做一些更复杂的变间接:

That's right, bash has variable indirection built-in as of version 2. It gets a bit trickier than eval if you want to do something more complex:

# Add to scenario:
VAR_2='4 5 6'

# We could use:
local ref="${REF}_2"
echo "${!ref}"
# Or:
ref="${REF}_2" echo "${!ref}"

# Versus the bash < 2 method, which might be simpler to those accustomed to eval:
eval "echo \"\$${REF}_2\""

无论如何,新方法更直观,尽管它可能似乎不是这样的有经验的编程谁是用来评估

关联数组在bash 4.一个需要注意的本质实现:他们必须通过创建声明

Associative arrays are implemented intrinsically in bash 4. One caveat: they must be created using 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,可以使用可变间接:

In older versions of bash, you can use variable indirection:

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"`"
# ...

这篇关于为什么要EVAL避免在Bash中,和我应该怎么用呢?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆