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

问题描述 投票:0回答:8

使用

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
8个回答
6
投票

以下实现与

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'

5
投票

试试这个:

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

正则表达式应该匹配任何有效的变量名称,按照这个答案,并且每个匹配都将替换为空字符串。

编辑:如果您不想替换转义的变量(即

\$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
未被触及。


3
投票

有一个名为

expandvars
的 pip 包正是这样做的。

pip3 install expandvars
from expandvars import expandvars

print(expandvars("$PATH:${HOME:?}/bin:${SOME_UNDEFINED_PATH:-/default/path}"))
# /bin:/sbin:/usr/bin:/usr/sbin:/home/you/bin:/default/path

它的优点是实现默认值语法(即

${VARNAME:-default}
)。


2
投票

另一种解决方案 - 正如 @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'

附注谨防

"
\
的转义:在调用
\
 之前,您可能需要将 
\\
 替换为 
"
,将 
\"
 替换为 
s
 中的 
check_output


2
投票

这里有一个使用原始

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, '$')

1
投票

我对各种答案不满意,需要更复杂一点来处理更多边缘情况,例如任意数量的反斜杠和 ${} 样式变量,但不想支付 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"

0
投票

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

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

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


0
投票

你可以试试这个。受到 Seth Robertson 的回答的启发

def expand_vars(input_str: str, env_dict: Optional[dict[str, str]] = None) -> str:
    """
    Method to expand variables in given string data.

    Args:
        input_str (str): String data in which variables need to be expanded.
        env_dict (dict, optional): Environment variables dict. Defaults to os.environ.

    Returns:
        str: Expanded string data.
    """
    if env_dict is None:
        env_dict = dict(os.environ)

    def replacer(match: re.Match) -> str:
        prefix = match.group(1)
        var_name = match.group(2) or match.group(3)
        if var_name in env_dict:
            return prefix + env_dict[var_name]
        return prefix + (f"${{{var_name}}}" if match.group(3) else f"${var_name}")

    pattern = r"(.*?)(?:\$(\w+)|\$\{(\w+)\})"
    # Expand variables until no more changes occur
    previous_str = None
    while previous_str != input_str:
        previous_str = input_str
        input_str = re.sub(pattern, replacer, input_str)
    return input_str
© www.soinside.com 2019 - 2024. All rights reserved.