【问题标题】:How to expand environment variables in python as bash does?如何像bash一样在python中扩展环境变量?
【发布时间】:2015-08-24 10:39:00
【问题描述】:

使用os.path.expandvars,我可以在字符串中扩展环境变量,但需要注意的是:“格式错误的变量名称和对不存在变量的引用保持不变”(强调我的)。此外,os.path.expandvars 也扩展了转义的\$

我想以类似 bash 的方式扩展变量,至少在这两点上。比较:

import os.environ
import os.path
os.environ['MyVar'] = 'my_var'
if 'unknown' in os.environ:
  del os.environ['unknown']
print(os.path.expandvars("$MyVar$unknown\$MyVar"))

这给了my_var$unknown\my_var

unset unknown
MyVar=my_var
echo $MyVar$unknown\$MyVar

这给了my_var$MyVar,这就是我想要的。

【问题讨论】:

    标签: python environment-variables


    【解决方案1】:

    以下实现与os.path.expandvars 完全兼容,但通过可选参数允许更大的灵活性:

    import os
    import re
    
    def expandvars(path, default=None, skip_escaped=False):
        """Expand environment variables of form $var and ${var}.
           If parameter 'skip_escaped' is True, all escaped variable references
           (i.e. preceded by backslashes) are skipped.
           Unknown variables are set to 'default'. If 'default' is None,
           they are left unchanged.
        """
        def replace_var(m):
            return os.environ.get(m.group(2) or m.group(1), m.group(0) if default is None else default)
        reVar = (r'(?<!\\)' if skip_escaped else '') + r'\$(\w+|\{([^}]*)\})'
        return re.sub(reVar, replace_var, path)
    

    以下是一些调用示例:

    >>> expandvars("$SHELL$unknown\$SHELL")
    '/bin/bash$unknown\\/bin/bash'
    
    >>> expandvars("$SHELL$unknown\$SHELL", '')
    '/bin/bash\\/bin/bash'
    
    >>> expandvars("$SHELL$unknown\$SHELL", '', True)
    '/bin/bash\\$SHELL'
    

    【讨论】:

    • 我对此进行了测试,但它并不是 100% 等同于您从 bash 扩展中获得的结果。在 bash 中,“\$IGNORE”将消耗反斜杠并返回“$IGNORE”。而这个 python 实现会将其保留为“\$IGNORE”。只是指出这一点,以防有人正在寻找一对一的 bash 扩展
    【解决方案2】:

    试试这个:

    re.sub('\$[A-Za-z_][A-Za-z0-9_]*', '', os.path.expandvars(path))
    

    正则表达式应该匹配任何有效的变量名,如this answer,并且每个匹配项都将替换为空字符串。

    编辑:如果您不想替换转义变量(即\$VAR),请在正则表达式中使用否定的lookbehind 断言:

    re.sub(r'(?<!\\)\$[A-Za-z_][A-Za-z0-9_]*', '', os.path.expandvars(path))
    

    (表示匹配项不应以\ 开头)。

    编辑 2:让我们把它变成一个函数:

    def expandvars2(path):
        return re.sub(r'(?<!\\)\$[A-Za-z_][A-Za-z0-9_]*', '', os.path.expandvars(path))
    

    检查结果:

    >>> print(expandvars2('$TERM$FOO\$BAR'))
    xterm-256color\$BAR
    

    变量$TERM 被扩展为它的值,不存在的变量$FOO 被扩展为空字符串,\$BAR 没有被触及。

    【讨论】:

    • 我本来想说这也会扩展转义的\$,我不希望这样,但后来我意识到os.path.expandvars 也是如此。也许我必须修改问题...
    • 我不关注。如果 VAR 是现有的环境变量,expandvars 已经将 $VAR 替换为其值。如果 VAR 不是 env 变量,结果只能包含更多的 $VAR 出现,你说你想用空字符串替换它,就像 bash 一样。
    • 我现在已经修改并扩展了这个问题,希望它更清楚。您的解决方案将从结果中删除 $unknown,但将 \my_var 留在我想要的位置 $MyVar(未展开,因为美元被转义了)。
    • 但现在我的问题是os.path.expandvars 已经扩展了\$MyVar 我不想扩展! (如果它是转义的反斜杠,则正则表达式将不起作用,如\\$unknown)。
    • 您使用的是哪个 python 版本?在我的机器上print(os.path.expandvars("$MyVar$unknown\$MyVar")) 打印my_var$unknown\$MyVar(查看我更新的答案)
    【解决方案3】:

    正如@HuStmpHrrrr 所指出的那样,替代解决方案是让bash 评估您的字符串,这样您就不必在python 中复制所有想要的bash 功能。

    效率不如我提供的其他解决方案,但它非常简单,这也是一个不错的功能:)

    >>> from subprocess import check_output
    >>> s = '$TERM$FOO\$TERM'
    >>> check_output(["bash","-c","echo \"{}\"".format(s)])
    b'xterm-256color$TERM\n'
    

    附:当心"\ 的转义:在调用check_output 之前,您可能希望在s 中将\ 替换为\\"\"

    【讨论】:

    • 我认为这样更好。正则表达式很难维护和调试。我怀疑是否有人真的会感觉到产生调用 shell 的进程的效率低下等。一个人会调用多少次(使用不同的s!!)这个?如果您一遍又一遍地使用相同的s 调用,缓存是解决方案,而不是正则表达式。
    【解决方案4】:

    这是一个使用原始expandvars 逻辑的解决方案:暂时将os.environ 替换为使未知变量为空字符串的代理对象。请注意,defaultdict 不起作用,因为 os.environ

    对于您的转义问题,您可以将r'\$' 替换为一些保证不在字符串中且不会被扩展的值,然后将其替换回来。

    class EnvironProxy(object):
        __slots__ = ('_original_environ',)
    
        def __init__(self):
            self._original_environ = os.environ
    
        def __enter__(self):
            self._original_environ = os.environ
            os.environ = self
            return self
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            os.environ = self._original_environ
    
        def __getitem__(self, item):
            try:
                return self._original_environ[item]
            except KeyError:
                return ''
    
    
    def expandvars(path):
        replacer = '\0'  # NUL shouldn't be in a file path anyways.
        while replacer in path:
            replacer *= 2
    
        path = path.replace('\\$', replacer)
    
        with EnvironProxy():
            return os.path.expandvars(path).replace(replacer, '$')
    

    【讨论】:

      【解决方案5】:

      我遇到过同样的问题,但我会提出一种不同且非常简单的方法。

      如果我们看一下“转义字符”的基本含义(就像它们在打印机设备中开始的那样),其目的是告诉设备“对接下来发生的事情做一些不同的事情”。这是一种离合器。在我们的特殊情况下,我们遇到的唯一问题是当我们在一个序列中包含两个字符 '\' 和 '$' 时。

      不幸的是,我们无法控制标准的 os.path.expandvars,因此字符串通过 lock、stock 和 barrel。然而,我们可以做的是欺骗函数,使其在这种情况下无法识别“$”!最好的方法是将 $ 替换为任意“实体”,然后将其转换回来。

      def expandvars(value):
          """
          Expand the env variables in a string, respecting the escape sequence \$
          """
          DOLLAR = r"\&#36;"
          escaped = value.replace(r"\$", r"\%s" % DOLLAR)
          return os.path.expandvars(escaped).replace(DOLLAR, "$")
      

      我使用了 HTML 实体,但任何合理不可能的序列都可以(随机序列可能更好)。我们可以想象这种方法会产生不良副作用的情况,但它们的可能性应该很小,可以忽略不计。

      【讨论】:

      • 为确保不发生冲突,您可以执行以下操作:if DOLLAR in value: DOLLAR = DOLLAR + '\0' * len(value)
      • 非常感谢!既然你指出了这一点,这似乎很明显。我唯一的问题是len(value) 背后的基本原理。它是否比另一种方法更可取,例如循环测试(如果价值为美元)并逐步添加填充物?
      【解决方案6】:

      我对各种答案不满意,需要更复杂一点来处理更多边缘情况,例如任意数量的反斜杠和 ${} 样式变量,但不想支付 bash 评估的成本。这是我基于正则表达式的解决方案:

      #!/bin/python
      
      import re
      import os
      
      def expandvars(data,environ=os.environ):
          out = ""
          regex = r'''
                   ( (?:.*?(?<!\\))                   # Match non-variable ending in non-slash
                     (?:\\\\)* )                      # Match 0 or even number of backslash
                   (?:$|\$ (?: (\w+)|\{(\w+)\} ) )    # Match variable or END
              '''
      
          for m in re.finditer(regex, data, re.VERBOSE|re.DOTALL):
              this = re.sub(r'\\(.)',lambda x: x.group(1),m.group(1))
              v = m.group(2) if m.group(2) else m.group(3)
              if v and v in environ:
                  this += environ[v]
              out += this
          return out
      
      
      # Replace with os.environ as desired
      envars = { "foo":"bar", "baz":"$Baz" }
      
      tests = { r"foo": r"foo",
                r"$foo": r"bar",
                r"$$": r"$$",                 # This could be considered a bug
                r"$$foo": r"$bar",            # This could be considered a bug
                r"\n$foo\r": r"nbarr",        # This could be considered a bug
                r"$bar": r"",
                r"$baz": r"$Baz",
                r"bar$foo": r"barbar",
                r"$foo$foo": r"barbar",
                r"$foobar": r"",
                r"$foo bar": r"bar bar",
                r"$foo-Bar": r"bar-Bar",
                r"$foo_Bar": r"",
                r"${foo}bar": r"barbar",
                r"baz${foo}bar": r"bazbarbar",
                r"foo\$baz": r"foo$baz",
                r"foo\\$baz": r"foo\$Baz",
                r"\$baz": r"$baz",
                r"\\$foo": r"\bar",
                r"\\\$foo": r"\$foo",
                r"\\\\$foo": r"\\bar",
                r"\\\\\$foo": r"\\$foo" }
      
      for t,v in tests.iteritems():
          g = expandvars(t,envars)
          if v != g:
              print "%s -> '%s' != '%s'"%(t,g,v)
              print "\n\n"
      

      【讨论】:

      • 不幸的是,正确的处理不仅需要处理转义的美元符号,还需要引用问题。例如,语法$var1" "$var2' '$var3'$var4' 应该扩展,使得引号消失。并且$var4 应该逐字保留。
      • 我们想要的是wordexp POSIX C 库函数的接口,或多或少。
      猜你喜欢
      • 1970-01-01
      • 2012-06-06
      • 1970-01-01
      • 2010-09-11
      • 1970-01-01
      • 2018-07-23
      • 1970-01-01
      • 1970-01-01
      • 2017-12-18
      相关资源
      最近更新 更多