#!/usr/bin/perl # Filename: cpx/mvx # Author: David Ljung # Description: Does a copy or move using regular expressions use strict; ################################################## # Setup the variables ################################################## my $PROGNAME = $0; $PROGNAME =~ s|.*/||; ################################################## # Usage ################################################## sub usage { my $msg; foreach $msg (@_) { print STDERR "\nERROR: $msg\n"; } print STDERR "\n"; print STDERR "Usage:\t$PROGNAME \n"; print STDERR " \t$PROGNAME \n"; print STDERR "\n"; print STDERR "\tCopies/Moves files using Perl regular expressions or /search/replace/\n"; print STDERR "\n"; print STDERR "Options:\n"; print STDERR "\t-f\tForce overwrite of existing destinations\n"; print STDERR "\t-i\tInteractive - ask before overwrite\n"; print STDERR "\t-v\tVerbose mode - show actions\n"; print STDERR "\t-d\tDebug mode - just show actions\n"; print STDERR "\n"; print STDERR "Examples:\n"; print STDERR "\n"; print STDERR "% mvx '(.*)-(.*)' '\$2--\$1'\n"; print STDERR " Would move files like 'bob-joe' to 'joe--bob'\n"; print STDERR "% mvx '104.(.*)' '00104.\$1'\n"; print STDERR " Moves files from 104.* to 00104.*\n"; print STDERR "% mvx /^104/00104/\n"; print STDERR " Same as above in /search/replace/ format\n"; print STDERR "% cpx |^one/foo|two/bar|\n"; print STDERR " Copy all files from one/foo* to two/bar*\n"; print STDERR "% mvx '/([-_])+/\$1/'\n"; print STDERR " Reduce multiple dash/underscores\n"; print STDERR "\n"; exit -1; } sub parse_args { my ($pat,$to,%opt); # Action $opt{'action'} = $PROGNAME =~ /mv/ ? "mv" : "cp"; # Args my $arg; while ($#ARGV>=0) { $arg=shift(@ARGV); if ($arg =~ /^-h$/) { usage(); } if ($arg =~ /^-f$/) { $opt{'force'}=1; next; } if ($arg =~ /^-i$/) { $opt{'interactive'}=1; next; } if ($arg =~ /^-v$/) { $opt{'verbose'}=1; next; } if ($arg =~ /^-d$/) { $opt{'debug'}=1; next; } if ($arg =~ /^-/) { usage("Unknown option: $arg"); } (defined $pat) ? $to = $arg : $pat = $arg; } usage("No patterns specified") unless $pat; unless ($to) { # Must be /search/replace/ form my $ch=substr($pat,0,1); $ch="\\$ch"; usage("Search/replace patterns must be of the form: /search/replace/\n". " (where '/' can be any character)\n") if ($pat !~ /^$ch(.*)$ch(.*)$ch(g)?$/ || $1 =~ /$ch/); ($pat,$to)=($1,$2); $opt{'global'} = $3 eq "g" ? 1 : 0; # if ($pat =~ /\(/) { # ; # } elsif ($pat =~ m|^/|) { # $pat="$pat(.*)"; $to="$to\$1"; # } else { # $pat="(.*)$pat(.*)"; $to="\$1$to\$2"; # } } ($pat,$to,\%opt); } ################################################## # Find files that match patterns ################################################## sub find_files { my ($pat) = @_; # Allow them to use "pat" instead of "^.*pat.*$" $pat = ".*$pat" unless ($pat =~ m|^/| || $pat =~ m|^\^|); $pat = "$pat.*" unless ($pat =~ m|\$$|); my @files; # '/' is not allowed as part of the regular expression my @els = split(/\//,$pat); if ($els[0] eq "") { $files[0]="/"; shift(@els); } else { $files[0]="."; } my (@new_files,$el); # Look at each element of the pattern foreach $el ( @els ) { # Compared to all the current paths so far my $path; foreach $path ( @files ) { # Read the current directory - match the current element if (opendir(DIR,$path)) { push(@new_files,map(($path eq "/" ? "/$_" : $path eq "." ? $_ : "$path/$_"), grep(/^$el$/,readdir(DIR)))); closedir(DIR); } } @files=@new_files; undef @new_files; } @files; } ################################################## # Act on the files (either move or copy) ################################################## sub act_on { my ($from,$to,$pat,$opt_H) = @_; my ($old,$new) = ($from); while ($old =~ /$pat/) { my ($pre,$pat_matched,$post) = ($`,$&,$'); my $match_to = $to; # Replace '$1' in $to with the first match, etc.. my @matches = ($pat_matched =~ /$pat/g); my $num = 1; foreach my $paren_match ( @matches ) { $match_to =~ s/\$$num/$paren_match/g; $num++; } $new .= $pre.$match_to; $old = $post; last unless ($opt_H->{'global'}); } $new .= $old; print "$opt_H->{'action'} $from $new\n" if ($opt_H->{'verbose'} || $opt_H->{'debug'}); return if ($opt_H->{'debug'}); # Avoid simple overwrite possibilities: 'mvx "many_files.*" file' return print STDERR "[$PROGNAME] ERROR: Multiple use of target $new (must be a directory)\n" if (grep($new eq $_,@{$opt_H->{'destinations'}}) && (! -d $new)); push(@{$opt_H->{'destinations'}},$new); # Didn't change? (i.e.: 'mvx /_+/_/' on 'Kodi__Bear' and 'Dave_Madison') return print STDERR "[$PROGNAME] $from not changed\n" if ($from eq $new); # Is it an overwrite? (-f overrides -i) if (-f $new && !$opt_H->{'force'}) { return print STDERR "[$PROGNAME] $from -> $new failed, file exists\n" if (!$opt_H->{'interactive'}); print STDERR "[$PROGNAME] $from: overwrite $new (yes/no)? "; return if (<> !~ /^y/i); } system("$opt_H->{'action'} \Q$from\E \Q$new\E"); } ################################################## # Main code ################################################## sub main { my ($pat, $to, $opt_H) = parse_args(); map(act_on($_,$to,$pat,$opt_H), find_files($pat)); } main();