Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

rel2abs of a path with env

by ovedpo15 (Pilgrim)
on Jun 07, 2021 at 13:43 UTC ( #11133617=perlquestion: print w/replies, xml ) Need Help??

ovedpo15 has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks!
Next to my previous question, I have another corner case. I moved to use Path::Tiny module (by using path($path)->absolute) but I noticed that it does not resolve paths with env. For example:
/a/b/c/$USER /a/b/c/${USER}
Is it possible to suggest a way to get the absolute path of it? How can I resolve the env without doing it manually?

Replies are listed 'Best First'.
Re: rel2abs of a path with env
by stevieb (Canon) on Jun 07, 2021 at 14:14 UTC

    Like this?:

    use warnings; use strict; use Path::Tiny; my $path = path("/a/b/c/$ENV{USER}")->absolute; print "$path\n";

    Output:

    spek@scelia ~/scratch $ perl path.pl /a/b/c/spek

    If not, I fail to realize what it is exactly you're after.

      Yes that's exactly what I did and it didn't resolve the env. The output of path("/a/b/c/$ENV{USER}")->absolute for me is /a/b/c/$ENV{USER}. Using Perl 5.26.1.
      Edit: I misread. Sorry. Why did you replace /a/b/c/${USER} with /a/b/c/$ENV{USER}?

        All worked here (FreeBSD 13, Perl 5.32, Path::Tiny 0.118) ...

        # perl -Mstrict -Mwarnings -MPath::Tiny -E \ 'say join qq[\n], map path( $_ )->absolute(), q[~/tmp], q[~tester/tmp], qq[/tmp/$ENV{USER}] ' /home/parv/tmp /home/tester/tmp /tmp/parv

        Very odd. Thinking to step ouside of Linux, I tested this on Windows. It appears to have worked correctly for me as well.


        SKMDSK06::T:\>perl -V | findstr uname uname='Win32 strawberry-perl 5.32.0.1 #1 Sun Aug 2 19:46:04 2020 +x64'


        SKMDSK06::T:\>set user USERDOMAIN=SKMDSK06 USERDOMAIN_ROAMINGPROFILE=SKMDSK06 USERNAME=Steve USERPROFILE=C:\Users\Steve


        SKMDSK06::T:\>type test01.pl use strict; use Path::Tiny; my $path = path("/a/b/c/$ENV{USER}")->absolute; print "$path\n";


        SKMDSK06::T:\>perl test01.pl T:/a/b/c SKMDSK06::T:\>


        Not sure that this helps any, but there it is.

        Perl is not bash, and has different interpolation syntax. If you want to interpolate the value of an environment variable into a string in Perl, you say $ENV{USER}, not $USER.

        As to why you got a literal /a/b/c/$ENV{USER}, are you sure you used "/a/b/c/$ENV{USER}" and not '/a/b/c/$ENV{USER}'? The single quotes do not interpolate.

        Because env vars are accessed using $ENV{USER}, and there presumably no var named $USER in your program.

        Seeking work! You can reach me at ikegami@adaelis.com

Re: rel2abs of a path with env
by hippo (Bishop) on Jun 07, 2021 at 14:52 UTC

    Is this an XY Problem? Why would you want to use Perl to munge path expressions which contain Shell variables?


    🦛

      While this might be XY, I have written code designed to read a configuration file where I wanted to honor environment variables placed in the config file. But I'm an odd bird; not sure that's a common way of looking at the Universe.

Re: rel2abs of a path with env (updated)
by haukex (Archbishop) on Jun 07, 2021 at 15:16 UTC

    I agree with hippo that this seems to be moving into XY Problem territory. What about /a/b/c/\$USER? Or /a/b/c/\\$USER? Or malformed inputs like /a/b/c/${USER? And what other possible inputs haven't you told us about?

    Enough rope to shoot yourself in the foot:

    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;

    I wouldn't necessarily recommend doing much more (e.g. String::Interpolate), as otherwise you may end up opening a security hole.

    Update: I realized that the code I posted originally (below) would also unescape backslashes and $s in the values of variables interpolated into the string, so I rewrote the parser using m/\G/gc regexes above. It should now also be more roboust in the face of invalid inputs. The only minor downside is that now integrating String::Unescape isn't quite as easy.

Re: rel2abs of a path with env
by Fletch (Bishop) on Jun 07, 2021 at 14:12 UTC

    You're kind of getting off into the weeds now and there's probably not going to be an off the shelf module to do what you're wanting. The (na´ve, obvious) solution would be to round trip things through a shell, but that's going to have security implications.

    use feature qw( say ); use IPC::Run qw( run ); my $path = q{/a/b/c/$USER}; my $output = q{}; run( ['/bin/bash', '-c', qq{echo -n "$path"}, ], q{>}, \$output ); say qq{Output: $output};

    The safer alternative is going to be walking over your strings and using something like s{\$(?: \{? (\S+) \}? )}{$ENV{$1} // qq{UNDEF:$1}}xge; to expand things yourself.

    Edit: Obviously that's an imprecise regexp and to be more bulletproof you'd want to use Regexp::Common and be fancier pulling any curly wrapped bit(s) out with something like qr/\$ ($RE{balanced}{-parens=>q{{}}}) / and then working with that instead.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Thanks. I wrote the following regex:
      sub resolve_env_in_path { my ($path) = @_; $path =~ s/\$\{(\w+)\}/$ENV{$1}/g; $path =~ s/\$(\w+)/$ENV{$1}/g; return $path; }
      What do you think?

        One thing your version silently strips out any undefined variables; using the /e modifier lets you have an expression rather than just a quoted string so you can do a bit of logic on the RHS of the substitution ...{ $ENV{$1} // qq{MISSING:$1} }e (so you get an indication what's missing).

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11133617]
Approved by marinersk
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (2)
As of 2022-12-03 06:15 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?