$ random_file.pl /path/to/mp3s -t mp3 -e 'xmms -e' # random song $ random_file.pl /path/to/mp3s -t m3u -e 'xmms -e' # random playlist $ random_file.pl /path/to/mp3s -d -e 'xmms -e' # random directory #### #!/usr/local/bin/perl -w # # $Id: random_file.pl,v 1.17 2004/05/10 17:33:50 zackse Exp $ # # picks a random file or directory and passes it to another program use strict; use File::Find; use Cache::FileCache; use Getopt::Long; use Pod::Usage; my %opts; $opts{dirs}= shift; unless ( $opts{dirs} and -d $opts{dirs} ) { warn "need a directory\n\n"; pod2usage(2); } GetOptions( \%opts, 'type:s@', 'dironly', 'exec:s', 'ignore:s@', 'cache:s', 'help' ); pod2usage(1) if $opts{help}; $opts{exec} ||= '/bin/echo'; # bleh pod2usage(2) unless $opts{exec}; ## my $files= get_candidates( $opts{dirs} ); my $selected= select_random_file( \%opts ); die "No match\n" unless $selected; exec( split( ' ' => $opts{exec} ), $selected ) or die "error running '$opts{exec} $selected': $!\n"; # should never get here exit; ## sub get_candidates { my $path= shift || return []; # stores all files in specied directory tree my $dircache= Cache::FileCache->new({ namespace => $path, default_expires_in => 86400, # 1 day auto_purge_interval => 3600 * 5, # 5 hours }); my $files= $dircache->get( $path ); # regenerate file list unless( $files and @$files ) { find( sub { push @$files => $File::Find::name }, $path ); $dircache->set( $opts{dirs} => $files ); } return $files; } sub select_random_file { my $opts= shift; my( $exts, $ignores )= map generate_patterns( $_ ) => @$opts{qw/type ignore/}; # stores selected file my $cache= Cache::FileCache->new({ namespace => $opts->{cache} || 'random_file', default_expires_in => 86400 * 4, # 4 days auto_purge_interval => 3600 * 5, # 5 hours }); my $file_count; my $selected= ''; foreach( @$files ) { # match directories only? next if $opts->{dironly} and ! -d; # matches ignore list? next if $ignores && $_ =~ $ignores; # matches extensions list? next unless ( ! $exts || /(?:$exts)\z/o ); # already selected recently? next if $cache->get( $_ ); # OK, candidate for selection $selected= $_ if rand( ++$file_count ) < 1; # see perlfaq5 } return unless $selected; $cache->set( $selected => 1 ); return $selected; } sub generate_patterns { return map { my $pats= join '|' => map "\Q$_\E" => @{ $_ || [] }; $pats ? qr/$pats/ : undef } @_; } __END__ =head1 NAME random_file.pl - pick a random file in a directory, pass it as an argument to another program =head1 SYNOPSIS random_file.pl B -exec B [ -type B ] [ -ignore B ] [ -c B ] [ -d ] Options: -c --cache NAMESPACE use NAMESPACE for cache -d --dironly only match against directories -e --exec PROGRAM pass selection as argument to PROGRAM -h --help display this help text -i --ignore PATTERN ignore files matching PATTERN -t --type EXTENSION files must match EXTENSION =head1 EXPLANATION Perhaps you have a large directory of mp3s, but you've grown tired of scanning through the file dialog from xmms. This program can select a random song, or playlist, or directory, and enqueue it for you: $ random_file.pl /path/to/mp3s -t mp3 -e 'xmms -e' # song $ random_file.pl /path/to/mp3s -t m3u -e 'xmms -e' # playlist $ random_file.pl /path/to/mp3s -d -e 'xmms -e' # directory Or maybe you have a nice collection of humorous videos you've downloaded from the Internet, and you'd like to view one at random with mplayer: $ random_file.pl /path/to/downloads -t mpg -t mpeg -e 'mplayer -idx' =head1 OPTIONS =over 4 =item B<-cache> This option specifies the namespace for the cache used to save recently selected items. The default is 'random_file'. =item B<-dironly> This option specifies that only directories should be matched. =item B<-exec> This is an external program to which the result will be passed as a parameter. =item B<-ignore> This is a pattern matching files you wish to be excluded from the generated list of candidates. You may specify this option multiple times. =item B<-type> This is a pattern matching files you want to match, which is anchored to the end of the filenames examined. For example, to match all mp3 files, you would use C<-type mp3>. You may specify this option multiple times. If you do not supply this parameter, all files will be matched. =back =head1 MORE EXAMPLES View a random image from the opera web browser's cache, ignoring any matching 'doubleclick': random_file.pl /home/zackse/.opera/cache4 -t jpe -i doubleclick \ -e /usr/bin/X11/xv =head1 USEFUL SHELL FUNCTIONS I use this program every day to add random entries to my xmms playlist, and I have the following bash shell functions that make this easy: randalbum () { DIR=${1:-/usr/local/audio}; random_file.pl $DIR -t m3u -e 'xmms -e' } randmp3 () { DIR=${1:-/usr/local/audio}; random_file.pl $DIR -t mp3 -e 'xmms -e' } randmp3dir () { DIR=${1:-/usr/local/audio}; random_file.pl $DIR -d -e 'xmms -e' } You can supply an alternate directory as an argument to the function, or just update the default (/usr/local/audio) above. You may also find the following one-liner useful to dump the contents of the current cache of recently selected files (replace the cache name 'random_file' as needed): $ perl -MCache::FileCache -le \ 'my $cache= Cache::FileCache->new({namespace, "random_file"}); print join "\n" => $cache->get_keys()' \ | sort =head1 AUTHOR Evan A. Zacks Copyright (c) 2004 Evan A. Zacks. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO File::Find(3), Cache::Cache(3), perl(1) =cut