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"