I was unhappy with the various answers, needing a little more sophistication to handle more edge cases such as arbitrary numbers of backslashes and ${} style variables, but not wanting to pay the cost of a bash eval. Here is my regex based solution:
#!/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"