http://www.perlmonks.org?node_id=722411

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

Dear Masters,
I have the following subdirectories:
-dir1 |_ file1.gz |_ ... |_ file1000.gz ... -dir100 |_file1.gz |_ ... |_ file300.gz
What I am trying to do is to traverse "dir*" and execute "gunzip" command on the files inside them. I tried this code, but doesn't work:
for my $sd (@dirs) { system("cd $sd"); system("gunzip *.gz"); }
Please advice what's wrong with the above construct?

---
neversaint and everlastingly indebted.......

Replies are listed 'Best First'.
Re: Traversing SubDir and Execute Unix Command
by Corion (Pope) on Nov 08, 2008 at 16:08 UTC

    system("cd $sd") doesn't behave the way you think it does. It starts the cd command, which immediately exits again. The current directory of the parent process (your Perl script) is not changed.

    To change the directory, see chdir. Also see File::Find, if you haven't already.

Re: Traversing SubDir and Execute Unix Command
by ig (Vicar) on Nov 08, 2008 at 17:59 UTC

    find2perl makes this easy. It takes the same arguments as the find command and produces a script for you. It comes with perl on unix, linux and Windows.

    Running the command

    find2perl /some/directory -name '*.gz' -exec gunzip {} \;

    produces

    #! /usr/bin/perl -w eval 'exec /usr/bin/perl -S $0 ${1+"$@"}' if 0; #$running_under_some_shell use strict; use File::Find (); # Set the variable $File::Find::dont_use_nlink if you're using AFS, # since AFS cheats. # for the convenience of &wanted calls, including -eval statements: use vars qw/*name *dir *prune/; *name = *File::Find::name; *dir = *File::Find::dir; *prune = *File::Find::prune; sub wanted; sub doexec ($@); use Cwd (); my $cwd = Cwd::cwd(); # Traverse desired filesystems File::Find::find({wanted => \&wanted}, '/some/directory'); exit; sub wanted { /^.*\.gz\z/s && doexec(0, 'gunzip','{}'); } sub doexec ($@) { my $ok = shift; my @command = @_; # copy so we don't try to s/// aliases to consta +nts for my $word (@command) { $word =~ s#{}#$name#g } if ($ok) { my $old = select(STDOUT); $| = 1; print "@command"; select($old); return 0 unless <STDIN> =~ /^y/; } chdir $cwd; #sigh system @command; chdir $File::Find::dir; return !$?; }
Re: Traversing SubDir and Execute Unix Command
by Perlbotics (Bishop) on Nov 08, 2008 at 17:17 UTC

    Is this limited to *NIX? FWIW, you can merge your system calls into one:

    for my $sd (@dirs) { system(qq{cd "$sd" && gunzip *.gz}); }
    Personally, I would add a check for system's return value and redirect gunzips output. For more robustness it might be better (slightly slower) to first scan for all *.gz files below @dirs and then decompress each file individually. That would allow to check the success of the decompression operation for each individual file - if that matters.

Re: Traversing SubDir and Execute Unix Command
by Cody Pendant (Prior) on Nov 08, 2008 at 22:50 UTC
    Just a quick note, even if "cd" was what you wanted, it wouldn't have worked after the first time because when you're in "dir001" you can't do "cd dir002" unless dir001" is the parent of "dir002".


    Nobody says perl looks like line-noise any more
    kids today don't know what line-noise IS ...
Re: Traversing SubDir and Execute Unix Command
by crusher (Acolyte) on Nov 08, 2008 at 19:16 UTC

    to change the current directory do as the following example:

    use Cwd; chdir("C:\\Perl") || die "Error changing the directory ($!)";
    my $cwd =getdcwd();

    Now cwd holds the full path to the working directory. Hope this helps...

Re: Traversing SubDir and Execute Unix Command
by mhearse (Chaplain) on Nov 08, 2008 at 21:42 UTC
    One idea would be to just use the shell...
    find top_dir/ -type f -name '*.gz' | xargs gunzip
Re: Traversing SubDir and Execute Unix Command
by Anonymous Monk on Nov 09, 2008 at 12:57 UTC