`multi' is also of releasable quality, given a man page. Move it
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sat, 20 Nov 2004 10:41:26 +0000 (10:41 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sat, 20 Nov 2004 10:41:26 +0000 (10:41 +0000)
into `utils' and provide one.

git-svn-id: svn://svn.tartarus.org/sgt/utils@4842 cda61777-01e9-0310-a592-d414129be87e

Makefile
multi/Makefile [new file with mode: 0644]
multi/multi [new file with mode: 0755]
multi/multi.but [new file with mode: 0644]

index a4a46c4..cbb0f20 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-SUBDIRS = base64 cvt-utf8 xcopy
+SUBDIRS = base64 cvt-utf8 multi xcopy
 
 all:
        for i in $(SUBDIRS); do make -C $$i; done
diff --git a/multi/Makefile b/multi/Makefile
new file mode 100644 (file)
index 0000000..51b171d
--- /dev/null
@@ -0,0 +1,7 @@
+all: multi.1
+
+%.1: %.but
+       halibut --man=$@ $<
+
+clean:
+       rm -f *.1
diff --git a/multi/multi b/multi/multi
new file mode 100755 (executable)
index 0000000..7cdbe6c
--- /dev/null
@@ -0,0 +1,71 @@
+#!/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 {
+    $_;
+  }
+}
diff --git a/multi/multi.but b/multi/multi.but
new file mode 100644 (file)
index 0000000..20180f4
--- /dev/null
@@ -0,0 +1,205 @@
+\cfg{man-identity}{multi}{1}{2004-11-20}{Simon Tatham}{Simon Tatham}
+\cfg{man-mindepth}{1}
+
+\C{multi-manpage} Man page for \cw{multi}
+
+\H{multi-manpage-name} NAME
+
+\cw{multi} - bulk file rename/copy utility using Perl regexps
+
+\H{multi-manpage-synopsis} SYNOPSIS
+
+\c multi [ -n | -q ] [ -r ] cmd perlfragment file [file...]
+\e bbbbb   bb   bb     bb   iii iiiiiiiiiiii iiii  iiii
+\c multi [ -n | -q ] [ -r ] - cmd cmd - perlfragment file [file...]
+\e bbbbb   bb   bb     bb     iii iii   iiiiiiiiiiii iiii  iiii
+
+\H{multi-manpage-description} DESCRIPTION
+
+\cw{multi} is a utility which allows you to invoke a command
+(typically, but not always, \cw{mv} or \cw{cp}) on a lot of files in
+a complicated way.
+
+The command-line arguments to \cw{multi} include a command, a set of
+filenames, and a fragment of Perl. for each of the filenames,
+\cw{multi} will use the fragment of Perl to transform the filename
+into a new filename, and will then invoke the given command, passing
+the old and new filenames as arguments.
+
+\cw{multi} is most often useful as a bulk rename or copy utility, by
+passing \cw{mv} or \cw{cp} as the command. However, it can have more
+complex uses as well; see the examples below.
+
+\H{multi-manpage-args} ARGUMENTS
+
+\dt \e{cmd}
+
+\dd Provides the command to which pairs of filenames will eventually
+be passed. If this is just one word (typically \c{cp} or \c{mv}),
+you can simply supply that word on the command line.
+
+\lcont{
+
+A multiple-word command (such as \c{ln -s} or \c{svn mv}) can be
+used if you place it between two arguments containing only minus
+signs.
+
+}
+
+\dt \e{perlfragment}
+
+\dd This fragment of Perl will be run for each file name you supply.
+The file name will be passed in in the special Perl variable
+\cw{$_}, and the altered file name should be passed out in \cw{$_}
+as well. (Therefore, the simplest kind of Perl fragment you could
+use is a single \cw{s///} substitution command.)
+
+\lcont{
+
+All the Perl variable names used internally by \cw{multi} itself
+begin with two underscore characters. Therefore, your Perl fragment
+can safely define its own variables (provided they do not begin with
+two underscores), without worrying about upsetting the functioning
+of \cw{multi}.
+
+}
+
+\dt \e{files}
+
+\dd After the Perl fragment, \cw{multi} expects a list of file names
+to be transformed. Typically these will be generated by typing one
+or more wildcard expressions on the shell command line.
+
+\H{multi-manpage-options} OPTIONS
+
+By default, \cw{multi} will print every command it executes on
+standard output, so that you can see what it has just done (in case
+it turns out to be wrong!).
+
+Bourne-shell-style quoting is provided, so that copying the output
+of \cw{multi} and pasting it into a shell script or on to a shell
+command line should work correctly.
+
+\dt \cw{-n}
+
+\dd Do not actually execute the commands. Instead, \e{only} print
+them on standard output as they would be executed. (Useful for a dry
+run to make sure your Perl does what you meant it to do. When you've
+got it right, take off the \cw{-n} option and let it run for real.)
+
+\dt \cw{-q}
+
+\dd \e{Only} execute the commands, without printing them. (Useful
+for running within a larger script.)
+
+By default, the two arguments passed to each invocation of the
+subcommand are the original filename and the transformed filename,
+in that order.
+
+\dt \cw{-r}
+
+\dd Reverse the order of arguments to the subcommand, so that it
+receives the transformed file name \e{before} the original one.
+
+\H{multi-manpage-examples} EXAMPLES
+
+The simplest use of \cw{multi} is to rename a large number of files.
+Suppose, for example, you have a lot of text files with \cw{.txt}
+extensions, and you prefer to use \cw{.text} extensions:
+
+\c $ multi mv 's/.txt$/.text/' *.txt
+\e   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+\c mv bar.txt bar.text
+\c mv baz.txt baz.text
+\c mv foo.txt foo.text
+
+If you wanted to copy the files rather than moving them, the command
+becomes
+
+\c $ multi cp 's/.txt$/.text/' *.txt
+\e   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+\c cp bar.txt bar.text
+\c cp baz.txt baz.text
+\c cp foo.txt foo.text
+
+If you wanted to create symbolic links, you now need the command
+\cw{ln -s}, which is composed of two words. So you need to tell
+\cw{multi} where the command words stop and the Perl begins, using
+two single-dash arguments:
+
+\c $ multi - ln -s - 's/.txt$/.text/' *.txt
+\e   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+\c ln -s bar.txt bar.text
+\c ln -s baz.txt baz.text
+\c ln -s foo.txt foo.text
+
+Note that simply quoting the two-word command would not have worked,
+because \cw{multi} would have assumed you genuinely meant a one-word
+command which had a space in the middle...
+
+\c $ multi "ln -s" 's/.txt$/.text/' *.txt
+\e   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+\c 'ln -s' bar.txt bar.text
+\c 'ln -s' baz.txt baz.text
+\c 'ln -s' foo.txt foo.text
+
+... which was almost certainly not what you wanted!
+
+The version control utility \e{Subversion} has a subcommand for
+moving files around within your working directory. However, it does
+not support wildcards, because \cw{svn mv} expects to see
+\e{precisely} two arguments. So if you want to move a whole load of
+files into a subdirectory, a command such as \cw{svn mv win*.c
+windows} will not work. \cw{multi} comes to the rescue:
+
+\c $ multi - svn mv - 's:^:windows/:' win*.c
+\e   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+\c svn mv winmain.c windows/winmain.c
+\c svn mv winprint.c windows/winprint.c
+\c svn mv winutils.c windows/winutils.c
+
+Of course, your Perl fragment can be more complex than just a
+\cw{s///} command. Here's a means of tidying up after extracting a
+MS-DOS zip file containing all filenames in upper case:
+
+\c $ multi mv 'y/A-Z/a-z/' *[A-Z]*
+\e   bbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+\c mv HEADER.H header.h
+\c mv MAIN.C main.c
+\c mv STUFF.C stuff.c
+
+Here's an example using \cw{-r}. Suppose you have lots of small C
+programs and you want to quickly compile them all into binaries:
+
+\c $ multi -r - cc -o - 's/.c$//' *.c
+\c cc -o bar bar.c
+\c cc -o baz baz.c
+\c cc -o foo foo.c
+
+Finally, here's a general technique for going beyond the limits of
+\cw{multi}, in the case where you need to do something more
+ambitious with your two file names. Suppose you want to use one file
+name as the target of a shell redirection operator, for example.
+
+\c $ multi - sh -c 'grep foo $0 > $1' - 's/.txt$/.grepped/' *.txt
+\e   bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+\c sh -c 'grep foo $0 > $1' bar.txt bar.grepped
+\c sh -c 'grep foo $0 > $1' baz.txt baz.grepped
+\c sh -c 'grep foo $0 > $1' foo.txt foo.grepped
+
+As each of these commands will be executed, the (explicitly invoked)
+shell will substitute the two filename arguments in place of \cw{$0}
+and \cw{$1}, so that the \e{effect} will be that of running a set of
+commands like
+
+\c grep foo bar.txt > bar.grepped
+\c grep foo baz.txt > baz.grepped
+\c grep foo foo.txt > foo.grepped
+
+\H{multi-manpage-ack} ACKNOWLEDGEMENTS
+
+The O'Reilly book \q{Programming Perl} includes a simple example
+script which contains the core idea of this program. It takes a
+single Perl argument followed by filenames, and invokes Perl's
+internal \cw{rename} function. \cw{multi} is a complete rewrite of
+this basic idea, supplying more options and configurability.