--- /dev/null
+#!/usr/bin/env perl
+
+# Perform multiple moves/copies.
+
+# Usage: multi mv 's/$/.old/' *.c
+# multi cp 's:/tmp/(.*):$1:' *
+# multi - svn mv - 's/foo/bar/' *foo*
+#
+# Options: -n don't actually do anything, just print what it would do
+# -r reverse the arguments to each command
+# -q don't print the commands as they are executed
+
+# Note that all variables in this script begin with a double
+# underscore. This is because the <action> parameter is a piece of
+# user-supplied Perl, which might perfectly legitimately want to
+# define and use its own variables in the course of doing a complex
+# transformation on file names. Thus, I arrange that all _my_
+# variables stay as far out of its likely namespace as they can.
+
+$__quiet = $__donothing = $__reverse = 0;
+
+while ($ARGV[0] =~ /^-(.+)$/) {
+ shift @ARGV;
+ $__quiet = 1, next if $1 eq "q";
+ $__quiet = 0, $__donothing = 1, next if $1 eq "n";
+ $__reverse = 1, next if $1 eq "r";
+}
+
+die "usage: multi <cmd> <action> <files>\n" .
+ " e.g. multi mv 'tr/A-Z/a-z/' *\n" if $#ARGV < 2;
+ " also: multi - <multiple-word-cmd> - <action> <files>\n" .
+ " or multi - svn mv - 'tr/A-Z/a-z/' *\n" if $#ARGV < 2;
+
+@__cmd = ();
+if ($ARGV[0] eq "-") {
+ shift @ARGV;
+ while (defined $ARGV[0] and $ARGV[0] ne "-") {
+ push @__cmd, shift @ARGV;
+ }
+ if (!defined $ARGV[0]) {
+ die "multi: no terminating - in multiple-word command mode\n";
+ }
+ shift @ARGV; # eat trailing -
+} else {
+ $__cmd[0] = shift @ARGV;
+}
+$__action = shift @ARGV;
+
+while (@ARGV) {
+ $_ = $__origfile = shift @ARGV;
+ eval $__action;
+ $__newfile = $_;
+ ($__origfile, $__newfile) = ($__newfile, $__origfile) if $__reverse;
+ &pcmd(@__cmd, $__origfile, $__newfile) if !$__quiet;
+ system @__cmd, $__origfile, $__newfile if !$__donothing;
+}
+
+sub pcmd {
+ my (@words) = @_;
+ printf "%s\n", join " ", map { &fmt($_) } @words;
+}
+
+sub fmt {
+ local ($_) = @_;
+ if (/[ !"#$&'()*;<>?\\`|~]/) {
+ s/'/'\\''/g;
+ "'$_'";
+ } else {
+ $_;
+ }
+}