use warnings; use strict; sub interpolate { local $_ = shift; my $vars = shift; my @out; pos=undef; while (1) { if ( m{\G ( [^\\\$]+ ) }xmsgc ) { push @out, $1 } elsif ( m{\G \$ (?| (\w+) | \{(\w+)\} ) }xmsgc ) { push @out, $vars->{$1} } elsif ( m{\G \\ (.) }xmsgc ) { $1 eq "\\" or $1 eq "\$" or die "unexpected backslash escape"; push @out, $1; } else { last } } die "parse of '$_' failed at pos ".(pos//0) if !defined pos || pos!=length; return join '', @out; } use Test::More; sub exception (&) { eval { shift->(); 1 } ? undef : ($@ || die) } my %env = ( USER => "foobar", quz => "\$baz \\\\ \\\$" ); is interpolate("USER", \%env), "USER"; like exception { interpolate("\\USER"), \%env }, qr/\bunexpected backslash\b/; is interpolate("\\\\USER", \%env), "\\USER"; is interpolate("\$USER", \%env), "foobar"; is interpolate("\${USER}", \%env), "foobar"; is interpolate("\\\$USER", \%env), "\$USER"; is interpolate("\\\\\$USER", \%env), "\\foobar"; is interpolate("\\\\\\\$USER", \%env), "\\\$USER"; is interpolate("\\\\\\\\\$USER", \%env), "\\\\foobar"; is interpolate("\\\\\\\\\${USER}", \%env), "\\\\foobar"; is interpolate("\$quz", \%env), "\$baz \\\\ \\\$"; like exception { interpolate("USER\$"), \%env }, qr/\bparse of 'USER\$' failed\b/; like exception { interpolate("\${USER"), \%env }, qr/\bparse of '\$\{USER' failed\b/; done_testing; #### use warnings; use strict; use Test::More; sub exception (&) { eval { shift->(); 1 } ? undef : ($@ || die) } my %_interp_map = ( '\\'=>'\\', '$'=>'$' ); # or use String::Unescape sub interpolate { my $str = shift; $str =~ s{ (?