Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

Is this directory a subdirectory of another directory?

by mt2k (Hermit)
on Mar 15, 2002 at 06:37 UTC ( [id://151919]=perlquestion: print w/replies, xml ) Need Help??

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

Okay, I believe that this is a weird question. Not the question itself, but how I'm going to explain it! I think the easiest way to start is to give an example of a directory structure (I'm using Win32, so I'll use Win32 paths):

  • Directory C:/Apache/www-root
    • file1.txt
    • file2.txt
    • Directory C:/Apache/www-root/images
      • image1.gif
      • image2.gif
      • Directory C:/Apache/www-root/images/bgs
        • bgimg1.gif
        • bgimg2.gif

Now, say that the string "C:/Apache/www-root/images/bgs" is passed to the CGI script and stored in $input{'directory'}. Let's say that $input{'directory'} is the name of the directory I want to copy to another directory somewhere on the system, specified as $input{'copy_to'}. Here's the code that accomplishes this:

use File::NCopy; File::NCopy::copy (\1, "$input{'directory'}", "$input{'copy_to'}");

But there is a small problem with this code! If $input{'directory'} is "C:/Apache/www-root/images" and $input{'copy_to'} is "C:/Apache/www-root/images/bgs", the copy does a horrible thing. It continually, over and over again, copies the C:/Apache/www-root/images directory deeper and deeper into directory C:/Apache/www-root/images/bgs, so that one gets a filesystem such as this:

  • Directory C:/Apache/www-root
    • file1.txt
    • file2.txt
    • Directory C:/Apache/www-root/images
      • image1.gif
      • image2.gif
      • Directory C:/Apache/www-root/images/bgs
        • bgimg1.gif
        • bgimg2.gif
        • Directory C:/Apache/www-root/images/bgs/images
          • image1.gif
          • image2.gif
          • Directory C:/Apache/www-root/images/bgs/images/bgs
            • bgimg1.gif
            • bgimg2.gif

And that pattern continues on for whoever knows how many times. So in the end I have a directory C:/Apache/www-root/images/bgs/ images/bgs/ images/bgs/ images/bgs/ images/bgs/ images/bgs/ images/bgs/ images/bgs/ images/bgs/ images/bgs/images (minus the spaces) and that is DEFINITELY not what I want to ever happen. So to finally get to the actual question: How can I make sure that $input{'copy_to'} is not a subdirectory of $input{'directory'}?? Here is some more code. I believe that I know where the missing code has to go. It is colored in red. This is what I use to display a list of options to the web user of which directories s/he can copy to:

print qq|<select name="blah">\n|;

find(\&{ sub {
     if (-d "$File::Find::dir/$_"){
          if ($count++ == 0) {
               push @dirs, $File::Find::dir;
          } else {
               push @dirs, $File::Find::dir . "/" . $_;
          }
     }

   }
}, "C:/Apache/www-root");

foreach (sort { lc($a) cmp lc($b) } @dirs) {
if (something needs to go here) { print qq|<option>$_\n|; }
}

print qq|</select>|;

So what code has to go in that red if statement (or maybe somewhere in that find sub) to make sure that $input{'copy_to'} is not a subdirectory of $input{'directory'}??

Let me sum this up in one sentence:
"How do I make sure that a directory is not copied to a subdirectory of the directory I'm copying?"

Sorry for all the head scratching and confusion with this question. That was hard to describe...

Edit Petruchio Fri Mar 15 06:46:11 UTC 2002 - Fixed typo in title on author's behalf

Edit by tye as long path string made page render very wide in some browsers

Replies are listed 'Best First'.
(crazyinsomniac) Re: Is this directory a subdirectory of another directory?
by crazyinsomniac (Prior) on Mar 15, 2002 at 07:41 UTC
Re: Is this directory a subdirectory of another directory?
by gmax (Abbot) on Mar 15, 2002 at 09:06 UTC
    crazyinsomniac gave the quickest solution, IMO.
    However, since Ryszard provided the theory, here is a bit of practice. :)
    Someone would say that it is overkill, but knowing this method might be useful in a broader context.
    For this exercise, I created a "testtree" directory, with some subdirs as you can see at the end of the script. You can either recreate this tree or provide real names.
    #!/usr/bin/perl -w use strict; use File::Find; use Tree::DAG_Node; my $count =0; my $rootname="/home/perl/"; # or your favourite dir my $dirname= "testtree"; my $root = new Tree::DAG_Node; $root->name($dirname); my $node = $root; find(\&{ sub { if (-d "$File::Find::dir/$_"){ my $dir = $File::Find::dir; $dir .= "/$_" if $count++; $dir =~ s/^$rootname//; my $ancestor = $node; while ($ancestor) { my $name = $ancestor->name; if ((length($dir) > length($name)) && $dir =~ /^$name/) +{ my $nnode = new Tree::DAG_Node; $ancestor->add_daughter($nnode); $nnode->name($dir); $node = $nnode; last; } $ancestor = $ancestor->mother; } } } }, "$rootname$dirname"); sub is_below { my $node = shift; my $dir = shift; return scalar grep {$dir eq $_->name} $node->ancestors; } sub is_above { my $node = shift; my $dir = shift; return scalar grep {$dir eq $_->name} $node->self_and_descendants; } sub find_node { my $node = shift; my $dir = shift; my ($descendant) = grep {$dir eq $_->name} $node->self_and_descendants; return $descendant; } my $source = "testtree/a/k"; my $dest = "testtree/a"; my $n = find_node($root,$source) or die "can't find dir\n"; if (is_below($n,$dest)) { print "[$source] is below [$dest]\n"; } elsif (is_above($n,$dest)) { print "[$source] is above [$dest]\n"; } print "\nthe tree\n\n"; print $root->dump_names,"\n"; print "the same, fancier\n"; print map "$_\n", @{$root->draw_ascii_tree}; __END__ output: [testtree/a/k] is below [testtree/a] the tree 'testtree' 'testtree/b' 'testtree/b/y' 'testtree/b/x' 'testtree/a' 'testtree/a/k' 'testtree/a/k/m' 'testtree/a/k/l' 'testtree/a/j' the same, fancier (simplified, for posting purposes) | <testtree> /-----------------------\ | | <b> <a> /---------\ /-------\ | | | | <b/y> <b/x> <a/k> <a/j> /--------\ | | <a/k/m> <a/k/l>
    This script will build a directory tree using Tree::DAG_Node.
    Using standard methods, you can find out if a node is below or above another one. is_above and is_below are shortcuts to achieve our purposes through Tree::DAG_Node methods.

    update (1)
    This script is not CGI safe.
    If you need to use it in a CGI environment, some sanitizing will be necessary.
    update (2)
    Adjusted the code to shorten it.
     _  _ _  _  
    (_|| | |(_|><
     _|   
    
Re: Is this directory a subsirectory of another directory?
by Juerd (Abbot) on Mar 15, 2002 at 06:43 UTC

    Hmmm, interesting problem. Please never use real paths in CGI input - that's asking for trouble. Please never use "$foo", because that's (in a way) equal to saying "" . $foo . "". Just use the scalar you want directly: $foo. (Note: except for when a subroutine usually modifies data, but you only want the returned value - using interpolation is a very nice way to cheat in those circumstances.)

    As for your problem, you could split the directory names on \ and then compare the two arrays, or use one of the directory names as a regex (\Q!) for the other. Windows doesn't have symlinks and such, so I think a simple minded approach like that might just work.

    U28geW91IGNhbiBhbGwgcm90MTMgY
    W5kIHBhY2soKS4gQnV0IGRvIHlvdS
    ByZWNvZ25pc2UgQmFzZTY0IHdoZW4
    geW91IHNlZSBpdD8gIC0tIEp1ZXJk
    

        Not for nothing, but it's NTFS that supports hard (not symbolic) links, not NT/2K/XP, per se.

        1. NT's own CLI (CMD.EXE) doesn't even support them directly.
        2. You need a separate console mode program (ln.exe, from the POSIX subsystem -- or MKS Toolkit, Cygwin, U/WIN, et al) to create links.
        3. The standard Windows Explorer is not even (apparently) link aware.

        I think Juerd's point was true if you assume typical Windows users (i.e., not everyone knows how to create multiple links to a file).

Re: Is this directory a subdirectory of another directory?
by Ryszard (Priest) on Mar 15, 2002 at 06:55 UTC
    Interesting problem.

    How about building a directory tree at run time and assigning values for each level of the tree.

    You'd then compare $input{'directory'} and $input{'copy_to'} and branch accordingly.

    Having provided that interesting bit of theory, I'm not really sure how you'd go about doing it.. :-)

Re: Is this directory a subdirectory of another directory?
by strat (Canon) on Mar 15, 2002 at 13:09 UTC
    I hope I understand your question correctly.

    1. what if you first search for the filenames in one step and prepare the copy-commands file by file, and then i a second step execute the copy-commands?

    2. Otherwise:

    if ($dir1 !~ /^\Q$dir2/){ print "$dir1 is no subdir of $dir2\n"; }

    Best regards,
    perl -le "s==*F=e=>y~\*martinF~stronat~=>s~[^\w]~~g=>chop,print"

Re: Is this directory a subdirectory of another directory?
by mt2k (Hermit) on Mar 15, 2002 at 19:30 UTC
    Okay, in the end I used strat's line of code within a loop to test for each directory.
    I tried crazyinsomniac's code, which only printed out one filename in a directory not even included in the path. I probably used it wrong!
    So anyway, thanks a lot for your help (and that includes everyone that posted).
    I would post the code I now use, but it's quite long... And sorry about the use of HTML there... I noticed that it shrunk the size of the nodelets after I posted it.

    Update: Okay, I tried crazyinsomniac's code again, and it worked for the most part, but it still left valid directories out of the options, so I went back with strat's code with a small modification. Here is the code I use now:

    my $root_path = "C:/Apache/www-root"; my $count = 0; my $dir = ""; my @dirs find(\&{ sub { if (-d "$File::Find::dir/$_"){ if (++$count == 1) { push @dirs, $File::Find::dir; } else { push @dirs, $File::Find::dir . "/" . $_; } } } }, "$root_path"); foreach $dir (@dirs) { my $tmp_dir = $input{'file' . $i}; my $tmp_dir =~ s/(.*)\/(.*)/$1/; if ($test_this_directory !~ /^\Q$full_file_path/ && $dir ne $tmp_ +dir) { #valid directory - add it to cpoy options } }

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (6)
As of 2024-04-19 18:03 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found