#!/usr/bin/perl # © Gilles Debunne - 1999-2003 - Gilles.Debunne@imag.fr # Apply a given shell command to a set of files. # Try Apply -help for details sub help { print << 'END'; Usage : Apply [-h|-v|-q|-p|-r] to Applies a command to a set of files, with possible joker replacements. -h displays this help (use -hh for an help on extended functions). -v (verify) displays commands but they are not executed. -q (quiet) removes all messages. Commands are executed. -p (protects) quote arguments to protect special characters. -r (recursive) recursively calls Apply on sub-directories. Exemples follow. Try them on temporary files using the -v (verify) option. ~ > Apply unzip to *.zip Will execute an unzip on all the .zip files of the current dir. ~ > Apply cp @ SAVE/@.old to *.C *h The '@' in the command represents each of the files' names. Here, .C and .h files will be copied in the 'SAVE' dir, with an added .old extension. ~ > Apply mv @ image#.jpg to img#.JPEG The '#' acts has a '*' (using a '*' would result in its expansion by the shell). Each '#' in will be replaced by the matching '#' expression found in . Here, all the img*.JPEG files will be moved in their equivalent image*.jpg file. img001.JPEG will for instance be replaced by image001.jpg, and so on. Apply mv img#.JPEG image#.jpg to img#.JPEG would do the same, but @ is more convenient. ~ > Apply convert #.jpg #.eps All the .jpg files will be converted in their associated .eps file. When keyword 'to' is missing, the first '#' word is used as (*.jpg here). ~ > Apply -r chmod 755 to directories 'directories' is a special target file. The -r(ecursive) option is useful here. ~ > Apply 'echo "File @ : `grep HREF @ | wc -l` html pointers." >> listRef' to *.html Creates a file (listRef) that lists the number of HTML links in every html file. As a shell command is interpreted by the shell before being parsed by Apply, all the shell special characters ( |,>,<,`,",#,@,& ) have to be '\' protected individually or enclosed in '...' as is done in the last example. If your file names include spaces (Windows names), consider using the -p option. Use '##' instead of '#' if you really want a '#' in your command. Use Apply -hh for an help on extended functions. Version 1.9 © Gilles Debunne 1999-2003 END exit; #` } sub extendedhelp { print << 'END'; Usage : Apply [-h|-v|-q|-p|-r] to in except Extended functions use the 'in' and 'except' keywords. There can be multiple clauses, in any order. After keyword 'in', you can specify a list of file/regexp the have to match. 'except' is the opposite : only that do not match 'except' names/regexp will be used. regexp should be enclosed by '"'. They use the perl syntax (".*"=anything "\w"=word ...). Exemples : ~ > Apply convert @ img#.png to img#.jpg in img[0-9].jpg Only img[0-9].jpg files will be converted. img with a number larger than 9 will be skipped. ~ > Apply ls -l to `find . -mtime -1` in ".*/SRC/\w" except ".*\.bak" ".*~" Recursively look for file that were touched during the last 24 hours. Only consider those matching the "/SRC/" regexp (i.e. located inside a SRC directory). Among those, skip all the .bak and ~ files. Version 1.9 © Gilles Debunne 1999-2003 END exit; } sub expandPound { # protect the '+' from reg exp treatment $w =~ s/\+/\\+/g; # save beginning and end of joker expression, replace # by * $w =~ s/\#/*/; $beg=$`; # PREMATCH $end=$'; # POSTMATCH' # find joker files @list = glob($w); foreach $f (@list) { push names,$f; $repl = $f; $repl =~ s/^$beg(.*)$end$/$1/; push pound,$repl; } } sub message { if (!$quiet) { print(@_); } } if ($#ARGV == -1) { print("Usage : Apply [-h|-v|-q|-p] to in \n"); print("Type Apply -h for help.\n"); } else { $mode = 0; # 0:option 1:cmd 2:files 3:in-files 4:except-files $cmd = ""; $arg = ""; WORD:foreach $w (@ARGV) { # print("parsing $w, mode=$mode\n"); $arg = $arg."$w "; # Mode switching. Must be done here and in reverse order if (($mode>=2) && ($w eq "except")) { $mode = 4; next WORD; } # Switch to except mode if (($mode>=2) && ($w eq "in")) { $mode = 3; } # Switch to in mode if (($mode==1) && ($w eq "to")) { $mode = 2; next WORD; } # Switch to file mode if (($mode==0) && ($w !~ /^\-/)) { $mode = 1; } # Keep option mode as long as words start with '-' if (($mode>2) && (!$initNameOKDone)) { foreach $n (@names) { $nameOK{$n} = 1; } $initNameOKDone = 1; } if (($mode == 3) && ($w eq "in")) { foreach $n (@names) { $nameOK{$n} = $nameOK{$n} - 1; } next WORD; } if ($mode==0) # args { if ($w =~ /f/) { $directory=$'; next WORD; } #' if ($w =~ /hh/) { extendedhelp; } if ($w =~ /h/) { help; } if ($w =~ /v/) { $verify = 1; } if ($w =~ /q/) { $quiet = 1; } if ($w =~ /p/) { $protects = 1; } if ($w =~ /r/) { $recursive = 1; } next WORD; } if ($mode==1) # command { $cmd = $cmd."$w "; # If there's only ONE # ('##' means protect the '#') if ($w =~ /([^\#]|^)\#([^\#]|$)/) { $requirePound = 1; } next WORD; } if ($mode==2) # file list { # No double # are allowed in this version if ($w =~ /\#.*\#/) { print("Double # not (yet) supported in file list ($w)\n"); exit; } if ($w =~ /\#/) { expandPound; } elsif ($requirePound) { print("A '#' in the command requires '#' in the file list (\"$w\" has not)\n"); print("Use '\@' to replace the entire argument if you don't use '#' in the file list\n"); exit; } elsif ($w =~ /^directories$/i) { @list = glob("* .??*"); foreach $f (@list) { if (-d $f && ! -l $f) { push names,$f; } } } else { push names,$w; } next WORD; } # more precise file matching if ($mode==3) { foreach $n (@names) { if (($nameOK{$n} == 0) && ($n =~ /^$w$/)) { $nameOK{$n} = 1; } } } if ($mode==4) { foreach $n (@names) { if (($nameOK{$n} == 1) && ($n =~ /^$w$/)) { $nameOK{$n} = 0; } } } } if ($verify && !$directory) { message(" Verification mode - No command execution\n"); } # Specific cases if ($cmd eq "") { print("No command given - Aborting\nTry Apply -help\n"); exit; } if ($mode==1) # no 'to' was found. Args are infered from the first '#' in cmd { @words= split/\s+/,$cmd; PARSE:foreach $w (@words) { if ($w =~ /\#/) { expandPound; message(" Keyword 'to' not found. Using $w as arg list\n"); last PARSE; } } } if ($mode>=3) { if ($nameOK < 0) { print("Please give specific in/except files"); exit; } while ($#names >= 0) { $n = pop(names); $p = pop(pound); if ($nameOK{$n} == 1) { push newNames,$n; push newPound,$p; } } @names = @newNames; @pound = @newPound; } else { @names = reverse @names; @pound = reverse @pound; } if ($#names < 0 && !$recursive) { print("No file match file list - Aborting\n"); exit; } if ($cmd !~ /[\#,\@]/) { $cmd = $cmd."@"; } # No file given. Assume command is applied on all files # Apply the command while ($#names >= 0) { $n = pop(names); $p = pop(pound); # protects spaces and special chars if ($protects) { $n = "\"$n\""; $p = "\"$p\""; } $command = $cmd; # protects '##', temporarily replacing by '÷×÷' $command =~ s/\#\#/÷×÷/g; $command =~ s/\#/$p/g; # restore protected #'s $command =~ s/÷×÷/\#/g; $command =~ s/\@/$n/g; message("$command\n"); if (!$verify) { $res = system($command); } if ($res) { message("Error\n"); } } if ($recursive) { $arg =~ s/\#/\\\#/g; if ($directory) { $directory = "$directory/"; } @list = glob("* .*"); DIR:foreach $f (@list) { if ($f eq "." || $f eq "..") { next DIR; } if (-d $f && ! -l $f && -r $f) { message("-- Directory $directory$f\n"); system("cd $f ; $0 -f$directory$f $arg ; cd -"); } } } }