From: simon Date: Sat, 20 Nov 2004 10:41:26 +0000 (+0000) Subject: `multi' is also of releasable quality, given a man page. Move it X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/utils/commitdiff_plain/29efe1b927173560be46e59e0986ef6228ff6330 `multi' is also of releasable quality, given a man page. Move it into `utils' and provide one. git-svn-id: svn://svn.tartarus.org/sgt/utils@4842 cda61777-01e9-0310-a592-d414129be87e --- diff --git a/Makefile b/Makefile index a4a46c4..cbb0f20 100644 --- 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 index 0000000..51b171d --- /dev/null +++ b/multi/Makefile @@ -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 index 0000000..7cdbe6c --- /dev/null +++ b/multi/multi @@ -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 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 \n" . + " e.g. multi mv 'tr/A-Z/a-z/' *\n" if $#ARGV < 2; + " also: multi - - \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 index 0000000..20180f4 --- /dev/null +++ b/multi/multi.but @@ -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.