如何像bash一样在python中扩展环境变量呢?

时间:2015-06-09 14:27:48

标签: python environment-variables

使用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,这就是我想要的。

6 个答案:

答案 0 :(得分:3)

以下实现与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'

答案 1 :(得分: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

答案 2 :(得分:1)

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

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

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

P.S。谨防逃离"\:您可能希望将\替换为\\,将"替换为\" s在致电check_output

之前

答案 3 :(得分:1)

这是一个使用原始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, '$')

答案 4 :(得分:0)

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

如果我们看一下&#34;逃避角色的基本含义&#34; (因为它们是在打印机设备中启动的),目的是告诉设备&#34;做一些不同的事情来接下来的事情&#34;。这是一种离合器。在我们的特殊情况下,我们唯一的问题是当我们有两个角色时,我们会遇到这样的问题。和&#39; $&#39;在序列中。

不幸的是,我们无法控制标准的os.path.expandvars,因此字符串传递了lock,stock和barrel。然而,我们可以做的是愚弄这个功能,以便它无法识别&#39; $&#39;在这种情况下!最好的方法是用一些任意的&#34;实体&#34;替换$。然后将其转换回来。

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实体,但任何合理的不可能的序列都可以(随机序列可能更好)。我们可能会想象这种方法会产生不必要的副作用的情况,但它们应该不太可能忽略不计。

答案 5 :(得分:0)

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

#!/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"