为什么在Bash应该避免eval,我应该使用什么来代替?

前端之家收集整理的这篇文章主要介绍了为什么在Bash应该避免eval,我应该使用什么来代替?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
一次又一次,我看到Bash对Stack Overflow的答案使用eval – 和答案得到了基础,双关意图,使用这样一个“邪恶”的结构。为什么eval这么邪恶?

如果eval不能安全使用,我应该使用什么?

还有更多的问题,而不是见到眼睛。我们将从明显的开始:eval有可能执行“脏”数据。脏数据是没有被重写为安全使用情况XYZ的任何数据;在我们的例子中,它是任何字符串没有格式化,以便安全的评价。

消毒数据看起来容易一目了然。假设我们抛出一个选项列表,bash已经提供了一个很好的方法来清理单个元素,另一种方法是将整个数组作为单个字符串进行清理:

  1. function println
  2. {
  3. # Send each element as a separate argument,starting with the second element.
  4. # Arguments to printf:
  5. # 1 -> "$1\n"
  6. # 2 -> "$2"
  7. # 3 -> "$3"
  8. # 4 -> "$4"
  9. # etc.
  10.  
  11. printf "$1\n" "${@:2}"
  12. }
  13.  
  14. function error
  15. {
  16. # Send the first element as one argument,and the rest of the elements as a combined argument.
  17. # Arguments to println:
  18. # 1 -> '\e[31mError (%d): %s\e[m'
  19. # 2 -> "$1"
  20. # 3 -> "${*:2}"
  21.  
  22. println '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
  23. exit "$1"
  24. }
  25.  
  26. # This...
  27. error 1234 Something went wrong.
  28. # And this...
  29. error 1234 'Something went wrong.'
  30. # Result in the same output (as long as $IFS has not been modified).

现在我们想要添加一个选项来将输出作为参数重定向到println。当然,我们可以在每次调用重定向println的输出,但是为了举例,我们不会这样做。我们需要使用eval,因为变量不能用于重定向输出

  1. function println
  2. {
  3. eval printf "$2\n" "${@:3}" $1
  4. }
  5.  
  6. function error
  7. {
  8. println '>&2' '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
  9. exit $1
  10. }
  11.  
  12. error 1234 Something went wrong.

看起来不错,对吧?问题是,eval解析两次命令行(在任何shell)。在解析的第一遍,去除一层引用。删除引号后,将执行一些变量内容

我们可以通过让变量扩展发生在eval中来解决这个问题。我们要做的是单引号的一切,留下双引号,它们是。一个例外:我们必须在eval之前扩展重定向,所以必须保持在引号之外:

  1. function println
  2. {
  3. eval 'printf "$2\n" "${@:3}"' $1
  4. }
  5.  
  6. function error
  7. {
  8. println '&2' '\e[31mError (%d): %s\e[m' "$1" "${*:2}"
  9. exit $1
  10. }
  11.  
  12. error 1234 Something went wrong.

这应该工作。它也是安全的,只要println中的$ 1不会脏。

现在持续只是一个时刻:我使用相同的无引号的语法,我们最初使用sudo的时间!为什么它在那里工作,而不是在这里?为什么我们必须单引号? sudo有点更现代:它知道在引号中包含它接收的每个参数,虽然这是一个过度简化。 eval简单地连接一切。

不幸的是,没有用于处理像sudo这样的参数的eval的替换,因为eval是一个shell内置的;这是重要的,因为它在执行时需要周围代码的环境和范围,而不是像函数那样创建一个新的栈和范围。

eval替代

具体的用例通常有可行的替代eval。这里有一个方便的列表。命令表示通常发送给eval的内容;在任何你喜欢的替代。

无操作

在bash中的无操作中的简单冒号:

创建子shell

  1. ( command ) # Standard notation

执行命令的输出

不要依赖外部命令。你应该总是控制返回值。把这些放在自己的线上:

  1. $(command) # Preferred
  2. `command` # Old: should be avoided,and often considered deprecated
  3.  
  4. # Nesting:
  5. $(command1 "$(command2)")
  6. `command "\`command\`"` # Careful: \ only escapes $ and \ with old style,and
  7. # special case \` results in nesting.

基于变量的重定向

调用代码时,将地图& 3(或高于& 2的任何内容)

  1. exec 3<&0 # Redirect from stdin
  2. exec 3>&1 # Redirect to stdout
  3. exec 3>&2 # Redirect to stderr
  4. exec 3> /dev/null # Don't save output anywhere
  5. exec 3> file.txt # Redirect to file
  6. exec 3> "$var" # Redirect to file stored in $var--only works for files!
  7. exec 3<&0 4>&1 # Input and output!

如果是一次性调用,则不必重定向整个shell:

  1. func arg1 arg2 3>&2

在要调用函数中,重定向到& 3:

  1. command <&3 # Redirect stdin
  2. command >&3 # Redirect stdout
  3. command 2>&3 # Redirect stderr
  4. command &>&3 # Redirect stdout and stderr
  5. command 2>&1 >&3 # idem,but for older bash versions
  6. command >&3 2>&1 # Redirect stdout to &3,and stderr to stdout: order matters
  7. command <&3 >&4 # Input and output!

可变间接

情况:

  1. VAR='1 2 3'
  2. REF=VAR

坏:

  1. eval "echo \"\$$REF\""

为什么?如果REF包含双引号,这将打破并打开代码来利用。它可以清除REF,但它是浪费时间,当你有这样:

  1. echo "${!REF}"

没错,bash的版本2内置了变量间接。如果你想做更复杂的事情,它会比eval更棘手:

  1. # Add to scenario:
  2. VAR_2='4 5 6'
  3.  
  4. # We could use:
  5. local ref="${REF}_2"
  6. echo "${!ref}"
  7. # Or:
  8. ref="${REF}_2" echo "${!ref}"
  9.  
  10. # Versus the bash < 2 method,which might be simpler to those accustomed to eval:
  11. eval "echo \"\$${REF}_2\""

无论如何,新的方法更直观,虽然它可能不像经验的编程谁是用于eval。

关联数组

关联数组本质上在bash 4中实现。一个警告:它们必须使用declare创建。

  1. declare -A VAR # Local
  2. declare -gA VAR # Global
  3.  
  4. # Use spaces between parentheses and contents; I've heard reports of subtle bugs
  5. # on some versions when they are omitted having to do with spaces in keys.
  6. declare -A VAR=( ['']='a' [0]='1' ['duck']='quack' )
  7.  
  8. VAR+=( ['alpha']='beta' [2]=3 ) # Combine arrays
  9.  
  10. VAR['cow']='moo' # Set a single element
  11. unset VAR['cow'] # Unset a single element
  12.  
  13. unset VAR # Unset an entire array
  14. unset VAR[@] # Unset an entire array
  15. unset VAR[*] # Unset each element with a key corresponding to a file in the
  16. # current directory; if * doesn't expand,unset the entire array
  17.  
  18. local KEYS=( "${!VAR[@]}" ) # Get all of the keys in VAR

在旧版本的bash中,您可以使用变量间接:

  1. VAR=( ) # This will store our keys.
  2.  
  3. # Store a value with a simple key.
  4. # You will need to declare it in a global scope to make it global prior to bash 4.
  5. # In bash 4,use the -g option.
  6. declare "VAR_$key"="$value"
  7. VAR+="$key"
  8. # Or,if your version is lacking +=
  9. VAR=( "$VAR[@]" "$key" )
  10.  
  11. # Recover a simple value.
  12. local var_key="VAR_$key" # The name of the variable that holds the value
  13. local var_value="${!var_key}" # The actual value--requires bash 2
  14. # For < bash 2,eval is required for this method. Safe as long as $key is not dirty.
  15. local var_value="`eval echo -n \"\$$var_value\""
  16.  
  17. # If you don't need to enumerate the indices quickly,and you're on bash 2+,this
  18. # can be cut down to one line per operation:
  19. declare "VAR_$key"="$value" # Store
  20. echo "`var_key="VAR_$key" echo -n "${!var_key}"`" # Retrieve
  21.  
  22. # If you're using more complex values,you'll need to hash your keys:
  23. function mkkey
  24. {
  25. local key="`mkpasswd -5R0 "$1" 00000000`"
  26. echo -n "${key##*$}"
  27. }
  28.  
  29. local var_key="VAR_`mkkey "$key"`"
  30. # ...

猜你在找的Bash相关文章