Python 脚本解析 3 ---- 字符串转执行命令行

不同的字符串执行方法:exec、eval、locals、compile

eval内置函数:字符串转数据序列 

eval是Python的一个内置函数,这个函数的作用是,把字符串解析转换为执行对象,比如说一个有效的表达式,或者一条linux执行命令,并返回执行结果。

用法和执行过程

eval(expression[, globals[, locals]])

# expression : 字符串
# globals : 变量作用域,全局命名空间,如果被提供,则必须是一个dict对象。
# locals : 变量作用域,局部命名空间,如果被提供,可以是任何mapping对象。
  1. 解析 expression
  2. 将其编译为字节码
  3. 将其作为 Python 表达式进行计算
  4. 返回评估结果

举例:

实现list、dict、tuple与str之间的转化,是str()函数的逆过程,同样str函数把list,dict,tuple转为为字符串。

a = "[[1,2], [3,4]]"
b = eval(a)         # [[1, 2], [3, 4]]
type(b)             # 

a = "{0: 'a', 1: 'b'}"
b = eval(a)         #  {0: 'a', 1: 'b'}
type(b)             # 

a = "([1,2], [3,4], [5,6], [7,8], (9,0))"
b = eval(a)         #  ([1, 2], [3, 4], [5, 6], [7, 8], (9, 0))
type(b)             # 

eval("{'student':'liming','age':age}",{"age":20})          #{'student': 'liming', 'age': 20}  

age=18
eval("{'student':'liming','age':age}",{"age":20},locals())  #{'student': 'liming', 'age': 18}

当locals参数为空,globals参数不为空时,查找globals参数中是否存在变量,并计算。

当两个参数都不为空时,先查找locals参数,再查找globals参数,locals参数中同名变量会覆盖globals中的变量。

eval 后面2个参数的作用,相当于给表达式添加临时的作用域,并不会污染真正的globals(), locals()变量的值,仅仅通过字典即可完成作用域的传递。

可以与任意 Python 运算符的布尔表达式一起使用:

 * 值比较运算符:<,>,<=,>=,==,!=
 * 逻辑(布尔)运算符: and, or, not
 * 测试运算符:in ,not in
 * 身份运算:is ,is not

print(eval('a+b', {'a': 8, 'b': 9}))          # 17
print(eval('a+b', {'a':'8','b':'9'}))         # 89
d={'c':1,'b':2}
e={'a':8,'b':9}
print(eval('a+b', e, d))      # print(eval('a+b', {'a': 8, 'b': 9}, {'c': 1, 'b': 2})) # 10


def func(a, b, condition):
    if eval(condition):
        return a + b
    return a - b

func(2, 4, "a > b")        # -2
func(2, 2, "a is b")       # 4

不适用于复合语句

如果您尝试将复合语句传递给eval(),那么您将得到一个SyntaxError. 任何其他复合语句,例如if、for、while、import、def或class,都会引发错误。(处理判断的控制流语句,x循环迭代语句,异常处理程序,函数、声明语句等)

>>> x = 100
>>> eval("if x: print(x)")
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 1
    if x: print(x)
     ^
SyntaxError: invalid syntax

返回值:

  • 如果expression是一个code对象(比如:输出语句),eval()函数的返回值是None;
  • 如果expression是一个表达式,那表达式的结果就是eval()函数的返回值;
  • x = 10
    
    def func():
        y = 20
        a = eval('x + y')
        print('a: ', a)     # a: 30
        b = eval('x + y', {'x': 1, 'y': 2})
        print('b: ', b)     # b: 3
        c = eval('x + y', {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
        print('c: ', c)     # c: 4
        d = eval('print(x, y)')  # 10  20
        print('d: ', d)     # d: None
    
    func()

 与compile()联合使用

还可以将编译后的代码对象传递给 Python 的eval(). 要编译您要传递给的代码eval(),您可以使用compile(). 这是一个内置函数,可以将输入字符串编译为代码对象或AST 对象,以便您可以使用eval().

code = compile("5 + 4", "", "eval")
eval(code)                 #9

exec 内置函数:以字符串形式提供的代码。

Python2 中exec不是函数,而是一个内置语句(statement),但是Python 2中有一个 execfile() 函数。可以理解为 Python 3 把 exec 这个 statement 和 execfile() 函数的功能够整合到一个新的 exec() 函数中去了。

exec()函数用于动态执行Python 程序,该程序可以是字符串或目标代码。如果它是一个字符串,则该字符串被解析为一套Python语句,然后执行,除非发生语法错误,如果它是一个目标代码,则简单地执行它。

exec(object[, globals[, locals]])

用法:

prog = 'print("The sum of 5 and 10 is", (5+10))'
exec(prog)             #  The sum of 5 and 10 is 15
  • 单行代码或单行代码片段
  • 多行代码以分号分隔
  • 由换行符分隔的多行代码
  • 三引号字符串内的多行代码并具有适当的缩进

返回值

我们必须小心,不能在函数定义之外使用 return 语句,甚至不能在传递给 exec() 函数的代码上下文中使用。它不返回任何值,永远是None。 

与eval的差别:

eval更偏向于表达式类型的字符串的转换,处理序列并返回处理结果,不适用于复合语句。eval不支持任何形式的变量赋值操作。

exec可以动态运行代码段,可以处理复合字符串对象,也可以产生新的变量,没有返回值,永远为None。因此,如果需要获取返回执行结果,就别用exec。

>>> a = 1
>>> exec("a = 2")    # 相当于直接执行 a=2
>>> print(a)         # 2
>>> a = exec("2+3")  # 相当于直接执行 2+3,但是并没有返回值,a 应为 None
>>> print(a)         # None
>>> a = eval('2+3')  # 执行 2+3,并把结果返回给 a
>>> print(a)         # 5
>>> eval("a = 2")
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 1
    a = 2
      ^
SyntaxError: invalid syntax

与compile()联合使用

>>> string_input = """
... def sum_of_even_squares(numbers):
...     return sum(number**2 for number in numbers if number % 2 == 0)
...
... print(sum_of_even_squares(numbers))
... """

>>> compiled_code = compile(string_input, "", "exec")
>>> numbers = [2, 3, 7, 4, 8]
>>> exec(compiled_code)                    # 84

 eval() 和exec() 的安全隐患

1、需要防止恶意输入,所以永远不要将其与不受信任的输入一起使用。

除了将字符串转成表达式并执行,也可以利用执行系统命令,删除文件,读取隐私数据文件等操作。
比如:

eval("__import__('os').system('ls /Users/secret/')")       # os.system('ls /Users/secret/')

eval("__import__('os').system('cat /Users/secret/secret.txt')")  

eval("__import__('os').system('rm * -rf')")  # 这个命令惊呆了!

__import__是python中的一个内置方法,它与import语句不同。通过这个方法导入一个os模块, __import__('os')等价于import os, system方法可以接收一个字符串参数,在字符串中可以指定终端下能够执行的命令,比如上面的代码中传入的是ls,就可以查看当前目录下的情况。

针对以上例子,有一个限制的办法,即指定 globals 为 {'__builtins__': None} 或者 {'__builtins__': {}} 。

s = {'__builtins__': None}
eval("__import__('os').system('whoami')", s)

# result:
"""
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 1, in 
TypeError: 'NoneType' object is not subscriptable"""

__builtins__ 包含了内置命名空间中的名称,在控制台中输入 dir(__builtins__) ,就能发现很多内置函数、异常和其它属性的名称。在默认情况下,eval 函数的 globals 参数会隐式地携带__builtins__ ,即使是令 globals 参数为 {} 也如此,所以如果想要禁用它,就得显式地指定它的值。

上例将它映射成 None,就意味着限定了 eval 可用的内置命名空间为 None,从而限制了表达式调用内置模块或属性的能力。

().__class__.__bases__[0].__subclasses__()

().__class__.__bases__[0]将为我们提供一个object类,上述代码,提供了我们现在可以使用的所有子类的列表object

参考下:

用python继承链搞事情:https://xz.aliyun.com/t/2308

Keith Bot:https://ctftime.org/writeup/15658

将目标代码构建为字符串后,您就可以exec()像执行任何 Python 代码一样执行它们。在这种情况下,我们很难确定字符串将包含什么内容。如果在构建代码时,使用不受信任的输入源(例如用户的直接输入)这也是有隐患的。函数 eval() 和 exec() 常常会被黑客利用,成为可以执行系统级命令的入口点,进而来攻击网站。

2、会造成内存耗尽问题:

>>> eval("2 ** 999999999999999", {"__builtins__":None}, {})

解决方法:

ast 模块的 literal() 是 eval() 的安全替代,与 eval() 不做检查就执行的方式不同,ast.literal() 会先检查表达式内容是否有效合法。 exec() ,似乎还没有类似的替代方法。

大神的文章目前还没更多学习,先膜拜下。

locals、compile未完待续。。。

参考文档:

深度辨析 Python 的 eval() 与 exec():https://juejin.cn/post/6844903805931225095

Python eval(): Evaluate Expressions Dynamically – Real Python :https://realpython.com/python-eval-function/

你可能感兴趣的:(python)