Dennis Williamson
Dennis Williamson

Reputation: 359905

Protect against null environment variables when using os.path.expandvars

How can I protect against Python's os.path.expandvars() treatment of null/unset environment variables?

From os.path:

Malformed variable names and references to non-existing variables are left unchanged.

>>> os.path.expandvars('$HOME/stuff')
'/home/dennis/stuff'
>>> os.path.expandvars('foo/$UNSET/bar')
'foo/$UNSET/bar'

I could perform this step separately from other path processing (expanduser(), realpath(), normpath(), etc.) instead of chaining them all together and check to see if the result is unchanged, but that is normal when there are no variables present - so I would also have to parse the string to see if it has any variables. I fear that may not be robust enough.

The issue comes into play when creating a file using the result. I end up with a file with the variable name as a literal part of the file's name. I want to instead reject the input with an exception.

Upvotes: 2

Views: 790

Answers (2)

Roland Puntaier
Roland Puntaier

Reputation: 3511

Extending on Jason's:

def expand_user_vars(s, variants='$%s ${%s} %%%s%%'):
    '''Return a string expanded for both leading "~/" or "~username/" and
    environment variables in the forms given by variants.


    >>> s = "~roland/.local/%XYZ%$XYZ${XYZ}"
    >>> expand_user_vars(s)
    '/home/roland/.local/'
    >>> s = "$HOME/.local/%XYZ%$XYZ${XYZ}"
    >>> expand_user_vars(s)
    '/home/roland/.local/'
    >>> s = "$EDITOR"
    >>> 'EDITOR' not in expand_user_vars(s)
    True

    '''
    s = os.path.expanduser(s)
    #python2 does not have KeyError in str(e)
    remx = re.compile(r"(?:KeyError:)?\s*'(\w+)'")
    while True:
        try:
            s = string.Template(s).substitute(os.environ)
            break
        except KeyError as e:
            reme = str(e)
            remxo = remx.match(reme)
            if remxo:
                g1 = remxo.group(1)
                for v in variants.split():
                    s = s.replace(v%g1,'')
                continue
    return s

Else, echo is there on Linux and MacOS and Windows. Replace below example with ...'echo '+s...:

import subprocess
netrc_file = subprocess.check_output('echo ${NETRC:-~/.netrc}',shell=True)

Upvotes: 0

Jason S
Jason S

Reputation: 13779

You could use string.Template, which uses a similar dollar-sign syntax for interpolation of variables but will raise KeyError if something doesn't exist rather than leaving it in.

import os
from string import Template
print(Template('$HOME/stuff').substitute(os.environ))

Upvotes: 4

Related Questions