in reply to Keeping only the $n newest files/directories in a diretory?

sub purge { my $n = shift; # 100, for example my @newest_first = map $_->[0], sort { $a->[1] <=> $b->[1] } map [$_, -M], </var/script/proc/*>; system "/bin/rm", "-rf", @newest_first[$n..$#newest_first] if @newest_first > $n; }

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

  • Comment on •Re: Keeping only the $n newest files/directories in a diretory?
  • Download Code

Replies are listed 'Best First'.
Re^2: Keeping only the $n newest files/directories in a diretory?
by Aristotle (Chancellor) on Apr 19, 2003 at 14:01 UTC
    use File::Path; sub purge { my $n = shift; # 100, for example my @newest_first = do { my @dir = grep -d, </var/script/proc/*>; my @age = map -M, @dir; @dir[ sort { $age[$a] <=> $age[$b] } 0 .. $#dir ]; }; splice @newest_first, 0, $n; rmtree \@newest_first, 0, 1 if @newest_first; }

    Makeshifts last the longest.

Re: &bull;Re: Keeping only the $n newest files/directories in a diretory?
by Juerd (Abbot) on Apr 19, 2003 at 13:05 UTC

    (ST)

    Is the ST really worth the effort here? On my system, in a directory with 20000 files, there is no noticable difference between

    my @newest_first = map $_->[0], sort { $a->[1] <=> $b->[1] } map [$_, -M], <*>;
    and
    my @newest_first = sort { -M $a <=> -M $b } <*>;
    while the first one took me much longer to type in.

    Most directories on my systems don't even have twenty thousand files :)

    Juerd
    - http://juerd.nl/
    - spamcollector_perlmonks@juerd.nl (do not use).
    

      Is the ST really worth the effort here?

      I can understand the "premature optimization" argument here, but I can also understand it if merlyn (Randal Schwartz) types the top two lines of this optimization in his sleep, or at least with a single keystroke. ;)

      While the /proc filesystem is not actually a filesystem as such, and a clone in /var would probably not have -M issues, either, the same pruning script would be useful in many different circumstances. If trying to run it on a slower link, such as an SMB mounted share across the corporate campus, on a per-hour cron job, I'd definitely want such an optimization.

      --
      [ e d @ h a l l e y . c c ]

        I can also understand it if merlyn (Randal Schwartz) types the top two lines of this optimization in his sleep

        So do I. Which is the reason I commented. Yesterday someone said the same thing to me about another ST: is it really worth the effort and memory usage? What he said made sense: an ST uses a lot of memory and in many cases the win is too small to be relevant.

        If trying to run it on a slower link, such as an SMB mounted share across the corporate campus

        You may be right about this one. I don't know how SMB is implemented and whether any client side caching is (can be) done.

        a per-hour cron job

        In a per-hour cron job it doesn't matter. Even in a per-minute cron job, I'd argue that the difference between half a second and half a second is very small :)

        Juerd
        - http://juerd.nl/
        - spamcollector_perlmonks@juerd.nl (do not use).
        

Re: &bull;Re: Keeping only the $n newest files/directories in a diretory?
by Anonymous Monk on Apr 19, 2003 at 11:47 UTC

    I think this code is golfing at its best ;). It works but I don't understand it...

    Could someone please explain the following part of merlyn's code:

    my @newest_first = map $_->[0], sort { $a->[1] <=> $b->[1] } map [$_, -M], </var/script/proc/*>;

    Thanks

      The Schwartzian Transform is a technique to optimize the sorting of complex data structures in Perl.

      The simplest case would be to do a single line sort:

      my @newest_first = sort { -M $a <=> -M $b } </var/script/proc/*>;

      However, to sort, one must compare pairs of items, often comparing the same $a to many different $bs, and vice versa. All those -M checks take a lot of time.

      Read a Schwartzian Transform like this starting with the last line.

    • <.../*> is a shorthand call to glob() that returns a list of files.
    • The map above that takes each filename and pairs it to its own -M check results. So you get a list of "foo.blah", timestamp pairs. The square brackets keep each pair in their own little referenced array.
    • The sort line sorts these pairs numerically descending by their second (index 1) element, the timestamps.
    • The map at the top will turn the sorted list of pairs into a list of filenames again, keeping the sorted order.

      --
      [ e d @ h a l l e y . c c ]

        All those -M checks take a lot of time.

        On what antique system is that? Many simple filesystem operations are cached by the filesystem. System calls have overhead, but so does creating an array. And unless you have millions of files, you will probably not notice any difference in speed :)

        Juerd
        - http://juerd.nl/
        - spamcollector_perlmonks@juerd.nl (do not use).
        

      Ok, reading it from the bottom up,
      </var/script/proc/*>
      Makes a list of files in that directory. It's then used as the second argument to
      map [$_, -M]
      which, from that list, generates a 2nd-order list, each element of which is a list containing in field one the filename, and in field two the modification time (-M is a test operator giving that -- it's default parameter is $_). If you're a C Programmer, think something like the following for each entry in that 2nd order list:
      struct { char filename[FNSIZE]; int mtime; /* The date field -- modification time */ }
      That structure is then used as an argument to
      sort { $a->[1] <=> $b->[1] }
      which sorts the first index of the 2nd-order list by the date field. The results are then used as the 2nd argument to
      map $_->[0]
      which returns a 1d list containing just the filenames (although they're sorted this time). The results are then saved in @newest_first. Code like the above can be intimidating unless you know the map operator well. It's an extremely powerful tool once you do though, and is one of the ways in which Perl's expressiveness borrows from Lisp. Hope this helps.

      That's the Schwartzian Transform. I goes something like this:

      </var/script/proc/*> will glob and return the list of file/directories in that folder.
      each element is mapped into an array ref[ ] containing the original string and the last modified date -M
      the list is sorted by the modification date which is the second element of the array ref, hence $a->[1]
      then the first element of the array (which is the original folder name) is retrieved $_->[0] with another map

      So in short, you get the list of folders, you build an array of arrays containing the folder name and the modification date, you sort by the modification date, and return only the folder names to use.

      Update: :)
      He who asks will be a fool for five minutes, but he who doesn't ask will remain a fool for life.

      Chady | http://chady.net/