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

I have a snippet of code here
foreach(readdir(PRINT)) { #do work }
the loop sets up all documents of the appropriate type to be printed in a certain way. PRINT is the directory handle. This works; but to skip the . and .. entries I have to add this as the first line inside the loop
#Skip . and .. if(($_ eq ".") or ($_ eq "..")) {next;}
What I am wanting to do is slice the list returned by readdir. The problem is, I don't know the size of the list. Is there a way to say.. I want so slice from element [3..$end_of_list] ie slice from element 3 indefinitely until the end of the list. I'm trying to avoid that line of code by using a slice on the anonymous(not sure if the correct terminology) list returned by readdir. Is there a way to pulls this off?

Replies are listed 'Best First'.
•Re: question regarding slices
by merlyn (Sage) on Nov 06, 2003 at 19:14 UTC
    There is no promise that dot and dot-dot are always the first two entries returned, although it is rare to see them misordered (usually from a disk fsck repair).

    The best solution is to leave your check in your loop as you already have. Stop optimizing for things that will break eventually.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      How to do it with not just 1 slice but 2 slices, and at the same time avoid Merlyn's issue of . and .. not always coming at the start of the list
      @l = readdir(PRINT) ; %h{@l} = (1) x @l ; delete @h{'.', '..'} ; for( keys %h ) { # do work }
      This is probably not what was desired in the first place, since it adds more lines of code, but I thought this would be interesting.

      Nuts and Bolts

    • @l = readdir gets the list of files
    • %h{@l} = (1) x @l converts the list into a hash, since all elements in a directory are unique
    • delete @h{'.', '..'} removes the . and .. entries from the hash at whatever position they're hiding in.
    • for( keys %h) { . and .. free looping
        Besides being very likely both longer to type and longer to run, you've also made it less portable, because delete @hash{@list} wasn't added until perl 5.6, if I recall. It might have been 5.5, but it's still a "relatively recent invention" as I often say.

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

      thanks merlyn.. had no clue that they wouldn't always be the first two.. I'll leave as is

      edit:Here's the final version for anyone interested. I'm printing the items using TextPad because I like its formatting. I realize its proprietary but I just wrote it for personal use. I could have use the win 32 print shell command but its formatting was ugly so i decided to use textpad. It could easily adapted to provide max compatibility
Re: question regarding slices
by davido (Cardinal) on Nov 06, 2003 at 18:32 UTC
    If the list is named @array, you can find the last element with $#array.

    Consider the following code:

    my @array = qw/this that the other/; local $, = ", "; print @array[1..$#array], "\n"; __OUTPUT__ that, the, other

    An unnamed true list (one that isn't held within a scalar in any way, nor referenced by an array-ref) can't be indexed in such a way as to construct a 0..end-of-list, but you do have a couple of other options.

    my $last_item = (this, that, the, other)[-1];

    Or the more useful...

    my $list = [qw/this that the other/]; local $, = ", "; print @{$list}[0..$#{$list}];

    This last alternative uses an anonymous list referred to by an array-ref scalar.


    Dave


    "If I had my life to live over again, I'd be a plumber." -- Albert Einstein
      the list has no name..the list is returned by readdir... which you can put into an array like so my @files = readdir(PRINT). However I'm trying to expand my foo and learn to ninja-code it in such a way that doesnt require a variable declaration. Probably shoulda been more clear about that in the original question. Thank you for your information though. I normally use that or just simply the array in a scalar context which will return the size as well.
Re: question regarding slices
by tcf22 (Priest) on Nov 06, 2003 at 18:44 UTC
    You could use grep, like this
    foreach(grep ! /^\.{1,2}$/, readdir(PRINT)){ #do work }
    but it loops through the list twice, which may be a little inefficient, so you could use glob which removes the . and .. automatically.
    my $dir = '/path/to/dir'; foreach(glob('$dir/*')){ #do work }
    I prefer using glob().

    As for retrieving all but the first 2 elements of the list, it is a little akward, so you could assign them into an array and do this
    my @array = readdir(PRINT); foreach(@array[2..$#array]){ #do work }

    - Tom

      Don't use grep to filter out these entries. Unless you know what you are doing, you open yourself to security problems (missing a directory you should traverse). And if you do know what you are doing, it looks strange to someone who doesn't, and so they might want to "fix" it and hence break it.

      More discussion about this issue can be found at:

      And in case the OP isn't aware of them, this might be the right moment to mention File::Find and File::Find::Rule.

Re: question regarding slices
by QM (Parson) on Nov 06, 2003 at 22:32 UTC
    Ignoring for the moment Merlyn's comments as to the order of . and .. from readdir...

    I feel so slow for not finding this sooner:

    foreach ( splice( @{[readdir(PRINT)]}, 2 ) {...}
    Because as perldoc -f splice reports:
    ...If LENGTH is omitted, removes everything from OFFSET onward...
    Update: and since splice returns what it removes, this returns all but the first 2 elements, which is a variation on splice I had forgotten.

    -QM
    --
    Quantum Mechanics: The dreams stuff is made of

Re: question regarding slices
by jweed (Chaplain) on Nov 06, 2003 at 18:38 UTC
    Unfortunately, that can't be done straight from the readdir list beacuse any such try would be clunky and require two calls, like this:
    foreach(readdir(PRINT)[2...scalar(readdir(PRINT)-1])
    Instead, try naming your array and then saying
    foreach(@array[2..$#array]) ...
    This will skip . and .. for you.

    UPDATE: Whoops, I was beat. Sorry for reposting old info.
    UPDATE 2 : DO NOT USE the method described above. As merlyn so expertly states, . and .. are not always the first files. The glob solution is particuarly pleasing, IMHO.
Re: question regarding slices
by TomDLux (Vicar) on Nov 06, 2003 at 23:08 UTC

    Using glob() is the best solution, since it doesn't include . and .. in the solution set. But, in general, if there are things you want to skip over, I find the NEXT statement with conditional modifier is the clearest solution:

    foreach ( readdir PRINT ) { next if ( ( $_ eq '.' ) or ( $_ eq '..' ) ); # or, alternately ... next if /^..?$/; }

    The first line is explicitly clear and is fast. Regex cannot be faster than straightfoward string comparison, but in this case the small number of characters and the idiomatic nature makes the code clear to undnerstand.

    --
    TTTATCGGTCGTTATATAGATGTTTGCA

      Your RE will skip any one- or two-letter entries, not just . and .., since . in an RE means "any character." I think you meant /^\.\.?$/.
Re: question regarding slices
by duct_tape (Hermit) on Nov 06, 2003 at 18:46 UTC

    I can't think of how to do this, since it is a list instead of an array... it might not be possible. With lists you can use -1 to get the last element, but 2 .. -1 doesn't seem to work.

    What about using grep to avoid the check for . and .. within the foreach?

    foreach (grep { !/^\.\.?$/ } readdir(PRINT)) { # stuff }

    I know this isn't what you asked for, but it is another solution. Personally I prefer having the check within the foreach loop like this:

    next if (/^\.\.?$/);

    Hope that helps.
    Brad

Re: question regarding slices
by Art_XIV (Hermit) on Nov 10, 2003 at 14:40 UTC

    I'd check out grep and/or the file test operators before proceeding with filter solution that you propose. Slicing may work, but it's a bit of an off-handed way of doing it.

    The file test operators (-e, -s, etc..) should let you select the canidate files that are actually readable or that have readable content.

    Doing a grep on your readdir should let you select the files with names/extensions that you want to read. You can even combine the filetests with a grep.

    Hanlon's Razor - "Never attribute to malice that which can be adequately explained by stupidity"