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

Path parsing from full to relative

by Kaplah (Initiate)
on Jun 20, 2018 at 19:56 UTC ( [id://1217048]=perlquestion: print w/replies, xml ) Need Help??

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

Greetings all. I was wondering how I would accomplish the following. Given a full file path how can I trim it down to the last folder? For instance:


C:\somedirectory\anotherDirectory\WorkingDirectory\myFile.txt
C:\somedirectory\anotherDirectory\WorkingDirectory\Logs\myLogs.txt
C:\somedirectory\anotherDirectory\WorkingDirectory\Stuff\myStuff.txt


needs to become:
\WorkingDirectory\myFile.txt
\WorkingDirectory\Logs\myLogs.txt
\WorkingDirectory\Stuff\myStuff.txt


The trick here is that the directories are not hard coded. My only life saver is that the "WorkingDirectory" has a naming standard and starts with the same three characters (for argument "ABC"). Is there a way to grab this folder once and then build the other strings? Or can I pull the reverse and subtract the unwanted beginning portion out of my path string? How would I do this?

Replies are listed 'Best First'.
Re: Path parsing from full to relative
by Discipulus (Canon) on Jun 20, 2018 at 20:21 UTC
    Hello Kaplah and welcome to the monastery and to the wonderful world of Perl!

    You can profit the File::Spec core module and specifically splitpath and splitdir but also catfile functions. Like in:

    my $path = 'C:\somedirectory\anotherDirectory\WorkingDirectory\myFile. +txt'; my ($volume,$directories,$file) = File::Spec->splitpath( $path ); my @dirs = File::Spec->splitdir( $directories ); my $partial = = File::Spec->catfile( $dirs[-1], $file );

    The above code does what you ask about "trim it down to the last folder" but your desired output is not "trim it down to the last folder"

    For this you can just use some regex like /(\\WorkingDirectory.*)$/ and find in $1 what you want.

    Or for a more robust solution modify my first code to grab one or more directories to include.

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Path parsing from full to relative
by choroba (Cardinal) on Jun 20, 2018 at 21:48 UTC
    I tend to prefer Path::Tiny to handle paths:
    #! /usr/bin/perl use warnings; use strict; use Test::More tests => 3; use Path::Tiny; my %expected; @expected{qw{ C:/somedirectory/anotherDirectory/WorkingDirectory/myFile.txt C:/somedirectory/anotherDirectory/WorkingDirectory/Logs/myLogs.txt C:/somedirectory/anotherDirectory/WorkingDirectory/Stuff/myStuff.t +xt }} = qw(WorkingDirectory/myFile.txt WorkingDirectory/Logs/myLogs.txt WorkingDirectory/Stuff/myStuff.txt); for my $path (keys %expected) { is path($path)->relative('C:/somedirectory/anotherDirectory'), $expected{$path}, $path; }

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: Path parsing from full to relative
by Laurent_R (Canon) on Jun 20, 2018 at 20:57 UTC
    Other monks have provided useful answers, but one quick comment:
    needs to become:
    \WorkingDirectory\myFile.txt \WorkingDirectory\Logs\myLogs.txt \WorkingDirectory\Stuff\myStuff.txt
    Are you sure that you really want to keep the leading backslash? A relative path should probably not include a leading backslash.

    Update: Fixed a small typo: s/are/have/

      You're correct. I probably could drop the backslash.
Re: Path parsing from full to relative
by Marshall (Canon) on Jun 20, 2018 at 20:45 UTC
    #!/usr/bin/perl use strict; use warnings; my @dirs = qw ( C:\somedirectory\anotherDirectory\WorkingDirectory\myFile.txt C:\somedirectory\anotherDirectory\WorkingDirectory\Logs\myLogs.txt C:\somedirectory\anotherDirectory\WorkingDirectory\Stuff\myStuff.txt C:\someBSdirectory\bogus.txt ); foreach my $dir (@dirs) { print "$1\n" if $dir =~ /(\\WorkingDirectory.*$)/; } __END__ prints: \WorkingDirectory\myFile.txt \WorkingDirectory\Logs\myLogs.txt \WorkingDirectory\Stuff\myStuff.txt
    Update: After seeing some other replies, I would advise to use the "/" character instead of the "\" character in delimiting program paths. In Perl using "/" instead of "\" works just fine on both Unix and Windows and I recommend it.
Re: Path parsing from full to relative
by tybalt89 (Monsignor) on Jun 20, 2018 at 20:53 UTC

    This gets the output you want by eliminating everything before the last common directory.
    I have no idea where "ABC" enters into your example :(

    #!/usr/bin/perl # https://perlmonks.org/?node_id=1217048 use strict; use warnings; local $_ = do { local $/; <DATA> }; /^(.*)\\.*\\.*\n(?:\1\\.*\\.*\n)*$/ and s/\Q$1\E//g; print; __DATA__ C:\somedirectory\anotherDirectory\WorkingDirectory\myFile.txt C:\somedirectory\anotherDirectory\WorkingDirectory\Logs\myLogs.txt C:\somedirectory\anotherDirectory\WorkingDirectory\Stuff\myStuff.txt
Re: Path parsing from full to relative
by Veltro (Hermit) on Jun 20, 2018 at 22:17 UTC

    I have found that fileparse helps me out in most situations. And since you are on Windows I recommend more checks. The following code checks if the path has a filename and checks the file suffix is correct. I've ran into trouble myself many times by not doing these simple things. The split instruction is added to get the dirname as you want it (though without the leading \)

    use strict ; use warnings ; use File::Basename qw( fileparse ) ; my $fullpath = q{C:\somedirectory\anotherDirectory\WorkingDirectory\Lo +gs\myLogs.txt} ; my ( $filename, $dirname, $suffix ) = fileparse $fullpath, qr/\.[^.]*/ + ; if ( $filename ne "" && $suffix eq ".txt" ) { ( undef, $dirname ) = split( /(?=WorkingDirectory)/, $dirname ) ; print "filename = $filename\n" ; print "dirname = $dirname\n" ; print "suffix = $suffix\n" ; } else { die "Wrong path!\n" ; } print "shortpath = $dirname$filename$suffix\n" ; __END__ filename = myLogs dirname = WorkingDirectory\Logs\ suffix = .txt shortpath = WorkingDirectory\Logs\myLogs.txt

    edit: Btw, if the split fails, dirname becomes undefined, so you can build a simple check for that as well.

Re: Path parsing from full to relative
by AnomalousMonk (Archbishop) on Jun 20, 2018 at 21:55 UTC

    Assuming that you want to exclude all directories above the lowest-level "working directory," so that for a path like
        C:\foo\bar\ABCxyz\baz\boff\ABCxyz\myStuff.txt
    you want
        \ABCxyz\myStuff.txt
    returned, and also making some assumptions about the whole  ABC thing, this might do the trick:

    c:\@Work\Perl\monks>perl -wMstrict -le "use List::Util qw(max); ;; my $rx_wordic = qr{ (?<= \\) ABC [^\\]* }xms; ;; my $maxlen = max map length, my @paths = qw( C:\somedirectory\anotherDirectory\ABCxyz\myFile.txt C:\somedirectory\anotherDirectory\ABCxyz\Logs\myLogs.txt C:\somedirectory\anotherDirectory\ABCxyz\Stuff\myStuff.txt C:\foo\bar\ABCxyz\baz\boff\ABCxyz\myStuff.txt C:\foo\bar\ABCxyz\baz\boff\ABCxyz\xyzzy.zy\a.b\myStuff.txt C:\myStuff.txt C:\totally\bogus\myStuff.txt ); ;; for my $path (@paths) { printf qq{%-*s -> }, $maxlen+2, qq{'$path'}; $path =~ s{ \A .* (?= $rx_wordic) }''xmsg; printf qq{%-s \n}, qq{'$path'}; } " 'C:\somedirectory\anotherDirectory\ABCxyz\myFile.txt' -> 'ABCxy +z\myFile.txt' 'C:\somedirectory\anotherDirectory\ABCxyz\Logs\myLogs.txt' -> 'ABCxy +z\Logs\myLogs.txt' 'C:\somedirectory\anotherDirectory\ABCxyz\Stuff\myStuff.txt' -> 'ABCxy +z\Stuff\myStuff.txt' 'C:\foo\bar\ABCxyz\baz\boff\ABCxyz\myStuff.txt' -> 'ABCxy +z\myStuff.txt' 'C:\foo\bar\ABCxyz\baz\boff\ABCxyz\xyzzy.zy\a.b\myStuff.txt' -> 'ABCxy +z\xyzzy.zy\a.b\myStuff.txt' 'C:\myStuff.txt' -> 'C:\my +Stuff.txt' 'C:\totally\bogus\myStuff.txt' -> 'C:\to +tally\bogus\myStuff.txt'

    Update: Thanks to Laurent_R for making the point about avoiding a leading \ on processed paths (update: but this feature is easy to get rid of if you don't want it).


    Give a man a fish:  <%-{-{-{-<

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others taking refuge in the Monastery: (8)
As of 2024-04-23 09:57 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found