views:

96

answers:

4

Hey I'm wondering how I can get this code to work. Basically I want to keep the lines of $filename as long as they contain the $user in the path:

        open STDERR, ">/dev/null";
        $filename=`find -H /home | grep $file`;
        @filenames = split(/\n/, $filename);
        for $i (@filenames) {
            if ($i =~ m/$user/) {
                #keep results
            } else {
                delete  $i; # does not work.    
            }
        }
        $filename = join ("\n", @filenames);
        close STDERR;

I know you can delete like delete $array[index] but I don't have an index with this kind of loop that I know of.

+10  A: 

You could replace your loop with:

@filenames = grep /$user/, @filenames;
Jon
+2  A: 

There's no way to do it when you're using foreach loop. But nevermind. The right thing to do is to use File::Find to accomplish your task.

use File::Find 'find';

...

my @files;
my $wanted = sub {
    return unless /\Q$file/ && /\Q$user/;
    push @files, $_;
};

find({ wanted => $wanted, no_chdir => 1 }, '/home');

Don't forget to escape your variables with \Q for use in regular expressions.

BTW, redirecting your STDERR to /dev/null is better written as

{
    local *STDERR;
    open STDERR, '>', '/dev/null';
    ...
}

It restores the filehandle after exiting the block.

codeholic
thanks, added the local STDERR ref.
Flamewires
+2  A: 

If you have a find that supports -path, then make it do the work for you, e.g.,

#! /usr/bin/perl

use warnings;
use strict;

my $user = "gbacon";
my $file = "bash";

my $path = join " -a " => map "-path '*$_*'", $user, $file;
chomp(my @filenames = `find -H /home $path 2>/dev/null`);

print map "$_\n", @filenames;

Note how backticks in list context give back a list of lines (including their terminators, removed above with chomp) from the command's output. This saves you having to split them yourself.

Output:

/home/gbacon/.bash_history
/home/gbacon/.bashrc
/home/gbacon/.bash_logout
Greg Bacon
Could you explain what the my $path line is doing? sorry, bit of a noob.
Flamewires
No problem! Fully expanded, the command becomes `find -H /home -path '*gbacon*' -a -path '*bash*' 2>/dev/null`, that is, everything under `/home` that contains both `gbacon` and `bash` (the `-a` option means 'and' with `find`) somewhere in its path. The trailing `2>/dev/null` discards any error output.
Greg Bacon
+2  A: 

If you want to remove an item from an array, use the multi-talented splice function.

my @foo = qw( a b c d e f );

splice( @foo, 3, 1 ); # Remove element at index 3.

You can do all sorts of other manipulations with splice. See the perldoc for more info.


As codeholic alludes to, you should never modify an array while iterating over it with a for loop. If you want to modify an array while iterating, use a while loop instead.

The reason for this is that for evaluates the expression in parens once, and maps each item in the result list to an alias. If the array changes, the pointers get screwed up and chaos will follow.

A while evaluates the condition each time through the loop, so you won't run into issues with pointers to non-existent values.

daotoad
Ah okay, It seemed awkward to try it with that loop anyway.
Flamewires