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

I'm searching for the parent directory containing a specific file type using File::Find. I then want to ls -l on that directory and redirect the output to APPEND into a named file somewhere.

The problem... I could create a string with the command for each directory (and that does the redirect), but... Some of the parent directory names contain shell meta chars (sometimes).

So if I read the system help correctly, I must use the args as an array to prevent shell meta char interpretation. Well that works. But now my redirect is broken. Now ls complains it can't find a file called '>>'

What do I do to fix this ?

# use File::Find; # find(\&get_this,"."); # sub get_this { if( -d $_ ) { opendir(DIZ,$_); DIREL: while( $del = readdir(DIZ) ) { if( $del =~ m/\.xxx$/ ) { print "$File::Find::name\n" ; $File::Find::prune = 1; system("echo \"----------------------------------------------- +--\" >> \"/Volumes/Expansion\ Drive/stuffTGZ/list_xxx_dirs.txt\""); system("echo \"- $File::Find::name --------------------------- +--\" >> \"/Volumes/Expansion\ Drive/stuffTGZ/list_xxx_dirs.txt\""); my @syscmd = ("/bin/ls","-l","$File::Find::name",">>","/Volume +s/Expansion\ Drive/stuffTGZ/list_xxx_dirs.txt") ; system(@syscmd); last DIREL; } } closedir(DIZ); } }

BTW, does it make any difference bieng on iMac OSX ? thanks

Replies are listed 'Best First'.
Re: Another system redirect problem
by ikegami (Patriarch) on Jun 18, 2011 at 05:44 UTC

    As an anonymous monk explained, you can't execute a shell command (of which ">>" is a part) without running a shell!

    IPC::Open3 is a somewhat low-level way of doing it, but it's an option.

    use IPC::Open3 qw( open3 ); open(local *OUT_FH, '>>', '/Volumes/Expansion Drive/stuffTGZ/list_xxx_ +dirs.txt') or die $!; print(OUT_FH "-------------------------------------------------\n"); print(OUT_FH "- $File::Find::name -----------------------------\n"); # Gets closed by open3. open(local *CHILD_STDIN, '<', '/dev/null') or die $!; my $pid = open3( '<&CHILD_STDIN', '>&OUT_FH', '>&STDERR', "/bin/ls" => ( "-l", $File::Find::name ), ); waitpid($pid, 0);

    You can reuse OUT_FH for multiple calls to open3, but you need to reopen CHILD_STDIN each time.

      I believe IPC::Run provides a solution
      # Can do I/O to sub refs and filenames, too: run \@cmd, '<', "in.txt", \&out, \&err or die "cat: $?" run \@cat, '<', "in.txt", '>>', "out.txt", '2>>', "err.txt";

        From what I gathered from the docs and a lot of testing, it would look like

        use IPC::Run qw( run ); open(my $fh, '>>', '/Volumes/Expansion Drive/stuffTGZ/list_xxx_dirs.tx +t') or die $!; print($fh "-------------------------------------------------\n"); print($fh "- $File::Find::name -----------------------------\n"); run [ "/bin/ls" => ( "-l", $File::Find::name ) ], '<', \"", '>', $fh;

        Notes:

        • Lexical file handles work, but I didn't see it in the documentation.
        • «'<', \""» appears to open /dev/null (desirable) as opposed to closing STDIN as documented (undesirable).
        • The child appears to inherit the parent's STDERR if 2> isn't specified. That's probably documented, but I didn't see it.
Re: Another system redirect problem
by jwkrahn (Abbot) on Jun 18, 2011 at 07:13 UTC

    It looks like you want something like this (UNTESTED):

    # use File::Find; # open LIST_FH, '>>', '/Volumes/Expansion Drive/stuffTGZ/list_xxx_dirs.t +xt' or die "Cannot open '/Volumes/Expansion Drive/stuffTGZ/list_xxx_d +irs.txt' because: $!"; find( sub { return unless /\.xxx$/; print "$File::Find::name\n"; print LIST_FH "-------------------------------------------------"; print LIST_FH "- $File::Find::name -----------------------------"; open LS_PIPE, '-|', '/bin/ls' ,'-l', $File::Find::name or die "Can +not open pipe from '/bin/ls' because: $!"; print LIST_FH <LS_PIPE>; close LS_PIPE or warn $! ? "Error closing '/bin/ls' pipe: $!" : "Exit status $? from '/bin/ls'"; }, '.' );

      Almost there, a little mistake by copying my stuff over.

      open LS_PIPE, '-|', '/bin/ls' ,'-l', $File::Find::name or die "Cannot open pipe from '/bin/ls' because: $!";

      Becomes ...

      open LS_PIPE, '-|', '/bin/ls' ,'-l', $_ or die "Cannot open pipe from +'/bin/ls' because: $!";

      Because if the found directory is at a depth greater than 1 ls reports that it cannot find the path obtained by using $File::Find::name (this is because find (by default) cd's into each directory but $File::Find::name returns the path from the 'root' of the search). But $_ returns the found directory name only, (local to the current directory)

Re: Another system redirect problem
by Anonymous Monk on Jun 18, 2011 at 05:31 UTC
    So if I read the system help correctly, I must use the args as an array to prevent shell meta char interpretation. Well that works. But now my redirect is broken. Now ls complains it can't find a file called '>>'

    It is the shell which does the redirection , redirection is a feature of the shell.

    See IPC::Run,IPC::Run3 but not String::ShellQuote :)

Re: Another system redirect problem
by trendle (Novice) on Jun 18, 2011 at 18:11 UTC

    Well thanks to all who posted.

    You exposed a flaw in my thought process... I was thinking of system(@xyz) as only suspending metachar expansion, but of course if I can't already do that in the shell why would it work in perl. I saw '>>' as part of the shell's command expansion. But I didn't realise that system(@abc) executes the called program directly, so does no shell expansion.

    I hadn't realised that it would be so complicated for a late night 'simple' task.

    I think that all the solutions posted (correct me if I'm wrong (again)) are effectively variations on using a pipe and capturing the output into the wanted file. For my comfort zone I'll stick with the core perl solution rather than the lower level IPC stuff.

    Thanks for your time, I hope this question saves a few others from the confusion I created for myself

      I think that all the solutions posted (correct me if I'm wrong (again)) are effectively variations on using a pipe and capturing the output into the wanted file.

      Neither of my solutions use pipes. They just do output redirection like the shell would.

      The other solution given does use a pipe, which means the output isn't going to a file. It is placed in the file by the script which gets it from the pipe.

        Yes, sorry, you are correct. It's a problem of a little knowledge being not enough to to express myself succinctly. I'm not quite there with IPC utilities yet, they seem a bit intimidating.