@@ -335,17 +335,23 @@ def expanduser(path):
335335# XXX With COMMAND.COM you can use any characters in a variable name,
336336# XXX except '^|<>='.
337337
338+ _varpattern = r"'[^']*'?|%(%|[^%]*%?)|\$(\$|[-\w]+|\{[^}]*\}?)"
339+ _varsub = None
340+ _varsubb = None
341+
338342def expandvars(path):
339343 """Expand shell variables of the forms $var, ${var} and %var%.
340344
341345 Unknown variables are left unchanged."""
342346 path = os.fspath(path)
347+ global _varsub, _varsubb
343348 if isinstance(path, bytes):
344349 if b'$' not in path and b'%' not in path:
345350 return path
346- import string
347- varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii')
348- quote = b'\''
351+ if not _varsubb:
352+ import re
353+ _varsubb = re.compile(_varpattern.encode(), re.ASCII).sub
354+ sub = _varsubb
349355 percent = b'%'
350356 brace = b'{'
351357 rbrace = b'}'
@@ -354,94 +360,44 @@ def expandvars(path):
354360 else:
355361 if '$' not in path and '%' not in path:
356362 return path
357- import string
358- varchars = string.ascii_letters + string.digits + '_-'
359- quote = '\''
363+ if not _varsub:
364+ import re
365+ _varsub = re.compile(_varpattern, re.ASCII).sub
366+ sub = _varsub
360367 percent = '%'
361368 brace = '{'
362369 rbrace = '}'
363370 dollar = '$'
364371 environ = os.environ
365- res = path[:0]
366- index = 0
367- pathlen = len(path)
368- while index < pathlen:
369- c = path[index:index+1]
370- if c == quote: # no expansion within single quotes
371- path = path[index + 1:]
372- pathlen = len(path)
373- try:
374- index = path.index(c)
375- res += c + path[:index + 1]
376- except ValueError:
377- res += c + path
378- index = pathlen - 1
379- elif c == percent: # variable or '%'
380- if path[index + 1:index + 2] == percent:
381- res += c
382- index += 1
383- else:
384- path = path[index+1:]
385- pathlen = len(path)
386- try:
387- index = path.index(percent)
388- except ValueError:
389- res += percent + path
390- index = pathlen - 1
391- else:
392- var = path[:index]
393- try:
394- if environ is None:
395- value = os.fsencode(os.environ[os.fsdecode(var)])
396- else:
397- value = environ[var]
398- except KeyError:
399- value = percent + var + percent
400- res += value
401- elif c == dollar: # variable or '$$'
402- if path[index + 1:index + 2] == dollar:
403- res += c
404- index += 1
405- elif path[index + 1:index + 2] == brace:
406- path = path[index+2:]
407- pathlen = len(path)
408- try:
409- index = path.index(rbrace)
410- except ValueError:
411- res += dollar + brace + path
412- index = pathlen - 1
413- else:
414- var = path[:index]
415- try:
416- if environ is None:
417- value = os.fsencode(os.environ[os.fsdecode(var)])
418- else:
419- value = environ[var]
420- except KeyError:
421- value = dollar + brace + var + rbrace
422- res += value
423- else:
424- var = path[:0]
425- index += 1
426- c = path[index:index + 1]
427- while c and c in varchars:
428- var += c
429- index += 1
430- c = path[index:index + 1]
431- try:
432- if environ is None:
433- value = os.fsencode(os.environ[os.fsdecode(var)])
434- else:
435- value = environ[var]
436- except KeyError:
437- value = dollar + var
438- res += value
439- if c:
440- index -= 1
372+
373+ def repl(m):
374+ lastindex = m.lastindex
375+ if lastindex is None:
376+ return m[0]
377+ name = m[lastindex]
378+ if lastindex == 1:
379+ if name == percent:
380+ return name
381+ if not name.endswith(percent):
382+ return m[0]
383+ name = name[:-1]
441384 else:
442- res += c
443- index += 1
444- return res
385+ if name == dollar:
386+ return name
387+ if name.startswith(brace):
388+ if not name.endswith(rbrace):
389+ return m[0]
390+ name = name[1:-1]
391+
392+ try:
393+ if environ is None:
394+ return os.fsencode(os.environ[os.fsdecode(name)])
395+ else:
396+ return environ[name]
397+ except KeyError:
398+ return m[0]
399+
400+ return sub(repl, path)
445401
446402
447403# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
0 commit comments