Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Recursively walk up a directory tree

by 1nickt (Canon)
on Feb 10, 2018 at 15:18 UTC ( [id://1208898]=perlquestion: print w/replies, xml ) Need Help??

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

Greetings all,

I need to walk up the directory tree from where I am until I find a directory containing a certain marker file. (This is to locate the base directory of an installation, which is then used to add the project's /lib to @INC.)

Is there anything more elegant than this?

use strict; use warnings; use Path::Tiny; sub find_base { -f "$_[0]/.marker" ? $_[0] : find_base($_[0]->parent) +} my $base = find_base( path(__FILE__)->realpath->parent ); unshift @INC, "$base/lib"; use Some::Class; ...
(I am aware that this does not handle the file not being found.)

Thanks for any suggestions!


The way forward always starts with a minimal test.

Replies are listed 'Best First'.
Re: Recursively walk up a directory tree
by tybalt89 (Monsignor) on Feb 10, 2018 at 16:17 UTC

    Elegant can be very subjective...

    #!/usr/bin/perl # http://perlmonks.org/?node_id=1208898 use strict; use warnings; use Cwd; cwd =~ m#^(.*)(?:/|$)(??{! -f "$1/.marker"})# or die ".marker not foun +d"; print "found in $1\n";

    Update: Small fix for the problem mentioned by AnomalousMonk in Re^3: Recursively walk up a directory tree

    cwd =~ m#^(.*)(?=/|$)(??{! -f "$1/.marker"})# or die ".marker not foun +d";

      Thanks for the suggestion! I see the (??{ ... }) code-as-match, but can't see where it recurses or loops. Could you please explain?


      The way forward always starts with a minimal test.

        The looping is done by the regex engine, trying to find the longest match, and if it fails, the next longest, etc.

        Try adding a print statement inside the code block and you can see each directory before the test for .marker.

        Like so:

        cwd =~ m#^(.*)(?:/|$)(??{print "testing $1\n"; ! -f "$1/.marker"})# or + die " .marker not found";

        The  cwd() function gives the full path of the... well, CWD. The match looks for a file at the end (rightmost part) of the path. If the file is not found, the regex engine effectively backtracks up a level in the directory.

        The  ! -f "$1/.marker" code in the  (??{ CODE }) construct evaluates to  '' (empty string) if the file exists at a given level, and this is interpolated into the regex pattern and always matches, hence the file is "found" at that level.

        If the file is not found, the negated file test evaluates to  '1' and this will probably not be found at the beginning of a directory name. However, this can fail (debug print added to show progress):

        use strict; use warnings; use Cwd; my $marker = '.marker'; cwd =~ m#^(.*)(?:/|$)(??{ print "at level '$1' \n"; ! -f "$1/$marker" +})# or die "'$marker' not found"; print "found '$marker' in '$1' \n";
        Output (with no  .marker file present anywhere):
        c:\@Work\Perl\monks\1nickt\two\one\zero>perl find_marker_1.pl at level 'c:/@Work/Perl/monks/1nickt/two/one/zero' at level 'c:/@Work/Perl/monks/1nickt/two/one' at level 'c:/@Work/Perl/monks/1nickt/two' at level 'c:/@Work/Perl/monks/1nickt' at level 'c:/@Work/Perl/monks' found '.marker' in 'c:/@Work/Perl/monks'
        If you have Perl version 5.10+, there's a variation that avoids this problem:
        use 5.010; # needs (?(?{ CODE })yes-pattern) regex extension use strict; use warnings; use Cwd; my $marker = '.marker'; cwd =~ m{ \A (.*) (?: / | \z) # (??{ print "at level '$1' \n"; ! -f "$1/$marker"}) (?(?{ print "at level '$1' \n"; ! -f "$1/$marker" }) (*FAIL)) }xms or die "'$marker' not found"; print "found '$marker' in '$1' \n";
        Output (still no  .marker file):
        c:\@Work\Perl\monks\1nickt\two\one\zero>perl find_marker_2.pl at level 'c:/@Work/Perl/monks/1nickt/two/one/zero' at level 'c:/@Work/Perl/monks/1nickt/two/one' at level 'c:/@Work/Perl/monks/1nickt/two' at level 'c:/@Work/Perl/monks/1nickt' at level 'c:/@Work/Perl/monks' at level 'c:/@Work/Perl' at level 'c:/@Work' at level 'c:' '.marker' not found at find_marker_2.pl line 62.
        Both versions of code behave the same if a  .marker is present at some level, although the first version will fail if the  .marker file is present above the level of, say, the  1nickt directory.


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

Re: Recursively walk up a directory tree
by choroba (Cardinal) on Feb 10, 2018 at 22:41 UTC
    No need for recursion, a loop is just fine:
    #!/usr/bin/perl use warnings; use strict; use feature qw{ say }; use Path::Tiny; my $path = 'Path::Tiny'->cwd; $path = path("$path/..")->realpath until -e "$path/.marker" || '/' eq $path; say -e "$path/.marker" ? 'F' : 'Not f', 'ound';

    ($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,

      Thanks choroba, I knew there must be a way with until! Maybe even more elegant as:

      my $path = Path::Tiny->cwd; $path = $path->parent until -f "$path/.marker" or $path->is_rootdi +r;


      The way forward always starts with a minimal test.

      Isn't relying on cwd less robust than using __FILE__? I guess I'm having trouble coming up with what might chdir/cd on/under the script, so maybe not.

Re: Recursively walk up a directory tree
by Anonymous Monk on Feb 10, 2018 at 16:46 UTC
    Its called a module

      I believe you actually meant "distribution" if I've understood what you meant correctly. Semantics, I know, but an important distinction.

        I believe you actually meant "distribution" if I've understood what you meant correctly. Semantics, I know, but an important distinction.

        Hehe,

        OP posts some code and asks Is there anything more elegant than this?

        Yeah, its called a module.

        Instead of doing ... 20 lines to modify global variable ... do use HideTwentyLines; and

        elegance achieved

          A reply falls below the community's threshold of quality. You may see it by logging in.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (4)
As of 2024-04-20 02:08 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found