[comp.sources.unix] v21i087: Safely rename wildcarded files, Part01/02

rsalz@uunet.uu.net (Rich Salz) (04/10/90)

Submitted-by: Vladimir Lanin <lanin@csd4.cs.nyu.edu>
Posting-number: Volume 21, Issue 87
Archive-name: mmv/part01

[  I use ren, the predecessor to mmv, all the time.  This is even
   better!  --r$  ]

This is mmv, a program to move/copy/append/link multiple files according
to a set of wildcard patterns.  This multiple action is performed safely,
i.e., without any unexpected deletion of files due to collisions of target
names with existing filenames or with other target names.  Furthermore,
before doing anything, mmv attempts to detect any errors that would result
from the entire set of actions specified and gives the user the choice of
either aborting before beginning, or proceeding by avoiding the offending
parts.

Improvements over mmv's predecessor, ren:
  Support for BSD, System 5, and MS-DOS
  Source and target files may (usually) reside in different directories
  Paths may contain wildcards
  Supports all csh wildcards: '*', '?', '['...']', and '~'
  The ';' wildcard finds files at any level in the tree
  Can copy, append, or link instead of moving/renaming
  Reads multiple patterns from standard input (or one from command line)
  No-execute option (whose output can be fed back in on standard input)

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 1 (of 2)."
# Contents:  MANIFEST Makefile PACKNOTES announce mmv.1 mmv.c.2
# Wrapped by rsalz@litchi.bbn.com on Mon Apr  9 17:05:22 1990
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'MANIFEST' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'MANIFEST'\"
else
echo shar: Extracting \"'MANIFEST'\" \(359 characters\)
sed "s/^X//" >'MANIFEST' <<'END_OF_FILE'
X   File Name		Archive #	Description
X-----------------------------------------------------------
X MANIFEST                   1	
X Makefile                   1	
X PACKNOTES                  1	Warnings about long lines, etc
X announce                   1	
X mmv.1                      1	
X mmv.c.1                    2	(part 1)
X mmv.c.2                    1	(part 2)
END_OF_FILE
if test 359 -ne `wc -c <'MANIFEST'`; then
    echo shar: \"'MANIFEST'\" unpacked with wrong size!
fi
# end of 'MANIFEST'
fi
if test -f 'Makefile' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'Makefile'\"
else
echo shar: Extracting \"'Makefile'\" \(281 characters\)
sed "s/^X//" >'Makefile' <<'END_OF_FILE'
Xall:	mmv mmv.1
X
Xmmv:	mmv.c
X	$(CC) -o mmv $(CFLAGS) mmv.c
Xinstall:	all
X	@echo "Install mmv according to local convention,"
X	@echo "then make links named mcp, mad, and mln to mmv."
X	@echo "Under System V, edit mmv.1 to uncomment the .nr O 1 line."
Xclean:
X	rm -f core a.out mmv mmv.o
END_OF_FILE
if test 281 -ne `wc -c <'Makefile'`; then
    echo shar: \"'Makefile'\" unpacked with wrong size!
fi
# end of 'Makefile'
fi
if test -f 'PACKNOTES' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'PACKNOTES'\"
else
echo shar: Extracting \"'PACKNOTES'\" \(82 characters\)
sed "s/^X//" >'PACKNOTES' <<'END_OF_FILE'
X
XFile "mmv.c" was split because of its size; to create it, do
X	cat mmv.c.? >mmv.c
END_OF_FILE
if test 82 -ne `wc -c <'PACKNOTES'`; then
    echo shar: \"'PACKNOTES'\" unpacked with wrong size!
fi
# end of 'PACKNOTES'
fi
if test -f 'announce' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'announce'\"
else
echo shar: Extracting \"'announce'\" \(1588 characters\)
sed "s/^X//" >'announce' <<'END_OF_FILE'
XCopyright (c) 1989 Vladimir Lanin
X
XThis is mmv, a program to move/copy/append/link multiple files
Xaccording to a set of wildcard patterns. This multiple action is
Xperformed safely, i.e. without any unexpected deletion of files due to
Xcollisions of target names with existing filenames or with other
Xtarget names. Furthermore, before doing anything, mmv attempts to
Xdetect any errors that would result from the entire set of actions
Xspecified and gives the user the choice of either aborting before
Xbeginning, or proceeding by avoiding the offending parts.
X
XImprovements over mmv's predecessor, ren:
X
X. support for BSD, System 5, and MS-DOS
X
X. source and target files may (usually) reside in different directories
X
X. paths may contain wildcards
X
X. supports all csh wildcards: '*', '?', '['...']', and '~'
X
X. the ';' wildcard finds files at any level in the tree
X
X. can copy, append, or link instead of moving/renaming
X
X. reads multiple patterns from standard input (or one from command line)
X
X. no-execute option (whose output can be fed back in on standard input)
X
XNote to users familiar with ren: the -a and -k options have been renamed
Xto -t and -g, respectively, and their semantics have somewhat changed.
X
X
XMmv is freeware. That means that the entire package of software and
Xdocumentation is copyrighted, and may not be distributed with any
Xmodifications or for any charge (without the author's explicit written
Xpermission). Other than that, it may be used and distributed freely.
X
X
XVladimir Lanin
X330 Wadsworth Ave, Apt 6F
XNew York, NY 10040
X
Xlanin@csd2.nyu.edu
X...!cmcl2!csd2!lanin
END_OF_FILE
if test 1588 -ne `wc -c <'announce'`; then
    echo shar: \"'announce'\" unpacked with wrong size!
fi
# end of 'announce'
fi
if test -f 'mmv.1' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'mmv.1'\"
else
echo shar: Extracting \"'mmv.1'\" \(17091 characters\)
sed "s/^X//" >'mmv.1' <<'END_OF_FILE'
X.\" Under BSD, just give to nroff or troff (with -man).
X.\" To print the MS-DOS version, use option -rO2.
X.\" Under System V, take out the '.\"  ' from the next line.
X.\" .nr O 1
X.TH MMV 1 "November 20, 1989 (v1.0)"
X.ie !'\nO'2' \{\
X.SH NAME
Xmmv \- move/copy/append/link multiple files by wildcard patterns
X\}
X.el \{
X.SH NAME
Xmmv \- move/copy/append multiple files by wildcard patterns
X\}
X.ie '\nO'2' \{\
X.ds SL \\\\
X.ds ES '
X\}
X.el \{\
X.ds SL /
X.ds ES \\\\
X\}
X.SH SYNOPSIS
X.B mmv
X.if '\nO'2' [\fB-m\fP|\fBx\fP|\fBr\fP|\fBc\fP|\fBo\fP|\fBa\fP|\fBz\fP]
X.if '\nO'0' [\fB-m\fP|\fBx\fP|\fBr\fP|\fBc\fP|\fBo\fP|\fBa\fP|\fBl\fP|\fBs\fP]
X.if '\nO'1' [\fB-m\fP|\fBx\fP|\fBr\fP|\fBc\fP|\fBo\fP|\fBa\fP|\fBl\fP]
X[\fB-h\fP]
X[\fB-d\fP|\fBp\fP]
X[\fB-g\fP|\fBt\fP]
X[\fB-v\fP|\fBn\fP]
X[\fBfrom to\fP]
X.if '\nO'2' \{\
X.br
X.B mmvpatch
X[\fBexecutable\fP]
X\}
X.SH "DESCRIPTION"
X.I Mmv
Xmoves (or copies,
X.ie '\nO'2' or appends,
X.el appends, or links,
Xas specified)
Xeach source file matching a
X.I from
Xpattern to the target name specified by the
X.I to
Xpattern.
XThis multiple action is performed safely,
Xi.e. without any unexpected deletion of files
Xdue to collisions of target names with existing filenames
Xor with other target names.
XFurthermore, before doing anything,
X.I mmv
Xattempts to detect any errors that would result
Xfrom the entire set of actions specified
Xand gives the user the choice of either
Xproceeding by avoiding the offending parts
Xor aborting.
X
X.ce
XThe Task Options
X.PP
XWhether
X.I mmv
Xmoves, copies,
X.ie '\nO'2' or appends
X.el appends, or links
Xis governed by the first set of options given
Xabove.
XIf none of these are specified,
X.ie '\nO'2' \{\
Xa default (patchable by
X.IR mmvpatch ,
Xand initially -x)
Xdetermines the task.
X\}
X.el \{\
Xthe task is given by the command name under which
X.I mmv
Xwas invoked (argv[0]):
X
X	command name	default task
X
X	mmv			-x
X.br
X	mcp			-c
X.br
X	mad			-a
X.br
X	mln			-l
X\}
X.PP
XThe task option choices are:
X.TP
X-m :
Xmove source file to target name.
XBoth must be on the same device.
XWill not move directories.
X.if '\nO'0' \{\
XIf the source file is a symbolic link,
Xmoves the link without checking if the link's target from the new
Xdirectory is different than the old.
X\}
X.TP
X-x :
Xsame as -m, except cross-device moves are done
Xby copying, then deleting source.
XWhen copying, sets the
X.ie !'\nO'2' permission bits
X.el attributes
Xand file modification time
Xof the target file to that of the source file.
X.TP
X-r :
Xrename source file or directory to target name.
XThe target name must not include a path:
Xthe file remains in the same directory in all cases.
XThis option is the only way of renaming directories under
X.IR mmv .
X.if '\nO'2' It is only available under DOS version 3.0 or higher.
X.TP
X-c :
Xcopy source file to target name.
XSets the file modification time and
X.ie !'\nO'2' permission bits
X.el attributes
Xof the target file to that of the source file,
Xregardless of whether the target file already exists.
XChains and cycles (to be explained below) are not allowed.
X.TP
X-o :
Xoverwrite target name with source file.
X.ie '\nO'2' \{\
XIf target file exists, its attributes are left unchanged.
XIf not, it is created with ordinary attributes
Xunrelated to the source file's attributes.
XIn either case, the file modification time is set to the current time.
X\}
X.el \{\
XIf target file exists, it is overwritten,
Xkeeping its original owner and permission bits.
XIf it does not exist, it is created, with read-write permission bits
Xset according to
X.IR umask (1),
Xand the execute permission bits copied from the source file.
XIn either case, the file modification time is set to the current time.
X\}
X.TP
X-a :
Xappend contents of source file to target name.
XTarget file modification time is set to the current time.
XIf target file does not exist,
Xit is created with
X.ie '\nO'2' attributes
X.el permission bits
Xset as under -o.
XUnlike all other options, -a allows multiple source files to have the
Xsame target name, e.g. "mmv -a
X.ie '\nO'2' *.c
X.el \\*.c
Xbig" will append all ".c" files to "big".
XChains and cycles are also allowed, so "mmv -a f f" will double up "f".
X.ie '\nO'2' \{\
X.TP
X-z :
Xsame as -a, but if the target file exists, and its last character is a ^Z,
Xand the source file is not empty,
Xthis ^Z is truncated before doing the append.
X\}
X.el \{\
X.TP
X-l :
Xlink target name to source file.
XBoth must be on the same device,
Xand the source must not be a directory.
XChains and cycles are not allowed.
X.if '\nO'0' \{\
X.TP
X-s :
Xsame as -l, but use symbolic links instead of hard links.
XFor the resulting link to aim back at the source,
Xeither the source name must begin with a '/',
Xor the target must reside in either the current or the source directory.
XIf none of these conditions are met, the link is refused.
XHowever, source and target can reside on different devices,
Xand the source can be a directory.
X\}
X\}
X.PP
XOnly one of these option may be given,
Xand it applies to all matching files.
XRemaining options need not be given separately,
Xi.e. "mmv -mk" is allowed.
X
X.ce
XMultiple Pattern Pairs
X.PP
XMultiple
X.I from
X--
X.I to
Xpattern pairs may be specified by omitting
Xthe pattern pair on the command line,
Xand entering them on the standard input,
Xone pair per line.
X(If a pattern pair is given on the command line,
Xthe standard input is not read.)
XThus,
X
X.in +3
Xmmv
X.br
Xa b
X.br
Xc d
X.in -3
X
Xwould rename "a" to "b" and "c" to "d".
XIf a file can be matched to several of the given
X.I from
Xpatterns,
Xthe
X.I to
Xpattern of the first matching pair is used.
XThus,
X
X.in +3
Xmmv
X.br
Xa b
X.br
Xa c
X.in -3
X
Xwould give the error message "a -> c : no match" because file "a"
X(even if it exists)
Xwas already matched by the first pattern pair.
X
X.ce
XThe \fIFrom\fP Pattern
X.PP
XThe
X.I from
Xpattern is a filename
Xwith embedded wildcards: '*', '?', '['...']',
X.if '\nO'2' \{\
X\'!',
X\}
Xand ';'.
XThe first three have their usual
X.IR sh (1)
Xmeanings of, respectively,
Xmatching any string of characters,
Xmatching any single character,
Xand matching any one of a set of characters.
X.PP
XBetween the '[' and ']', a range from character 'a' through character 'z'
Xis specified with "a-z".
XThe set of matching characters can be negated by inserting
Xa '^' after the '['.
XThus, "[^b-e2-5_]"
Xwill match any character but 'b' through 'e', '2' through '5', and '_'.
X.if '\nO'2' \{\
X.PP
XUnlike DOS wildcards,
Xall mmv wildcards (except for cases listed below)
Xcan occur anywhere in the pattern,
Xwhether preceding or following explicit characters or other wildcards.
XFor example, the pattern "*z\\foo.bar" will search
Xfor files named "foo.bar" in all subdirectories whose names end in 'z'.
XHowever, no wildcards can occur in the drive letter.
X.PP
XThe character '.' is not matched by any of '*', '?', or '['...']'.
XThus, the pattern "*" will only match files with a null extension.
XTo save yourself some typing, use the '!' wildcard instead,
Xwhich matches the same as "*.*",
Xexcept it is assigned only one wildcard index (see below).
XThus, both "f!" and "f*.*"
Xwill match all of "f", "f.ext", "foo", and "foo.ext",
Xwhile "f*" will match only the first and the third.
X\}
X.PP
XNote that paths are allowed in the patterns,
Xand wildcards may be intermingled with slashes arbitrarily.
XThe ';' wildcard
Xis useful for matching files at any depth in the directory tree.
XIt matches the same as "*\*(SL" repeated any number of times, including zero,
Xand can only occur either at the beginning of the pattern
Xor following a '\*(SL'.
XThus ";*.c" will match all ".c" files in or below the current directory,
Xwhile "\*(SL;*.c" will match them anywhere on the file system.
X.if !'\nO'2' \{\
X.PP
XIn addition, if the
X.I from
Xpattern
X(or the
X.I to
Xpattern)
Xbegins with "~/", the '~' is replaced with the home directory name.
X(Note that the "~user" feature of
X.IR csh (1)
Xis not implemented.)
XHowever, the '~' is not treated as a wildcard,
Xin the sense that it is not assigned a wildcard index (see below).
X\}
X.PP
XSince matching a directory under a task option other than -r or -s
Xwould result in an error,
Xtasks other than -r and -s
Xmatch directories only against completely explicit
X.I from
Xpatterns (i.e. not containing wildcards).
XUnder -r and -s, this applies only to "." and "..".
X.PP
X.ie '\nO'2' \{\
XHidden and system files are also only matched
Xagainst completely explicit
X.I from
Xpatterns.
X\}
X.el \{\
XFiles beginning with '.' are only matched against
X.I from
Xpatterns that begin with an explicit '.'.
X\}
XHowever, if -h is specified, they are matched normally.
X.if !'\nO'2' \{\
X.PP
XWarning: since the shell normally expands wildcards
Xbefore passing the command-line arguments to
X.IR mmv ,
Xit is usually necessary to enclose the command-line
X.I from
Xpattern
Xin quotes.
X\}
X
X.ce
XThe \fITo\fP Pattern
X.PP
XThe
X.I to
Xpattern is a filename
Xwith embedded
X.I wildcard
X.IR indexes ,
Xwhere an index consists of the character '='
Xfollowed by a string of digits.
XWhen a source file matches a
X.I from
Xpattern,
Xa target name for the file is constructed out of the
X.I to
Xpattern by
Xreplacing the wildcard indexes by the
Xactual characters that matched the referenced wildcards
Xin the source name.
XThus, if the
X.I from
Xpattern is "abc*.*" and the
X.I to
Xpattern is "xyz=2.=1",
Xthen "abc.txt" is targeted to "xyztxt.".
X(The first '*' matched "", and the second matched "txt".)
XSimilarly, for the pattern pair ";*.[clp]" -> "=1=3\*(SL=2",
X"foo1\*(SLfoo2\*(SLprog.c" is targeted to "foo1\*(SLfoo2\*(SLc\*(SLprog".
XNote that there is no '\*(SL' following the "=1" in the
X.I to
Xpattern,
Xsince the string matched by any ';' is always either empty
Xor ends in a '\*(SL'.
XIn this case, it matches "foo1\*(SLfoo2\*(SL".
X.if !'\nO'2' \{\
X.PP
XTo convert the string matched by a wildcard
Xto either lowercase or uppercase before embedding it in the target name,
Xinsert 'l' or 'u', respectively,
Xbetween the '=' and the string of digits.
X.PP
XThe
X.I to
Xpattern,
Xlike the
X.I from
Xpattern,
Xcan begin with a "~/" (see above).
XThis does not necessitate enclosing the
X.I to
Xpattern in quotes on the command line
Xsince
X.IR csh (1)
Xexpands the '~' in the exact same manner as
X.I mmv
X(or, in the case of
X.IR sh (1),
Xdoes not expand it at all).
X\}
X.PP
XFor all task options other than -r, if the target name is a directory,
Xthe real target name is formed by appending
Xa '\*(SL' followed by the last component
Xof the source file name.
XFor example, "mmv dir1\*(SLa dir2" will,
Xif "dir2" is indeed a directory, actually move "dir1\*(SLa" to "dir2\*(SLa".
XHowever, if "dir2\*(SLa" already exists and is itself a directory,
Xthis is considered an error.
X.PP
XTo strip any character (e.g. '*', '?', or '=')
Xof its special meaning to
X.IR mmv ,
Xas when the actual replacement name must contain the character '=',
Xprecede the special character with a
X.ie '\nO'2' \{\
Xsingle quote (').
X\}
X.el \{\
X\'\\'
X(and enclose the argument in quotes because of the shell).
X\}
XThis also works to terminate a wildcard index
Xwhen it has to be followed by a digit in the filename, e.g. "a=1\*(ES1".
X
X.ce
XChains and Cycles
X.PP
XA chain is a sequence of specified actions where the target name of
Xone action refers to the source file of another action.
XFor example,
X
Xmmv
X.br
Xa b
X.br
Xb c
X
Xspecifies the chain "a" -> "b" -> "c".
XA cycle is a chain where the last target name
Xrefers back to the first source file,
Xe.g. "mmv a a".
X.I Mmv
Xdetects chains and cycles regardless of the order in which
Xtheir constituent actions are actually given.
XWhere allowed, i.e. in moving, renaming, and appending files,
Xchains and cycles are handled gracefully, by performing them in the proper
Xorder.
XCycles are broken by first renaming one of the files to a temporary name
X(or just remembering its original size when doing appends).
X
X.ce
XCollisions and Deletions
X.PP
XWhen any two or more matching files
Xwould have to be
X.ie '\nO'2' moved or copied
X.el moved, copied, or linked
Xto the same target filename,
X.I mmv
Xdetects the condition as an error before performing any actions.
XFurthermore,
X.I mmv
Xchecks if any of its actions will result
Xin the destruction of existing files.
XIf the -d (delete) option is specified,
Xall file deletions or overwrites are done silently.
XUnder -p (protect), all deletions or overwrites
X(except those specified with "(*)" on the standard input, see below)
Xare treated as errors.
XAnd if neither option is specified,
Xthe user is queried about each deletion or overwrite separately.
X(A new stream to
X.ie '\nO'2' "\\dev\\con"
X.el "/dev/tty"
Xis used for all interactive queries,
Xnot the standard input.)
X
X.ce
XError Handling
X.PP
XWhenever any error in the user's action specifications is detected,
Xan error message is given on the standard output,
Xand
X.I mmv
Xproceeds to check the rest of the specified actions.
XOnce all errors are detected,
X.I mmv
Xqueries the user whether he wishes
Xto continue by avoiding the erroneous actions or to abort altogether.
XThis and all other queries may be avoided by specifying either the
X-g (go) or -t (terminate) option.
XThe former will resolve all difficulties by avoiding the erroneous actions;
Xthe latter will abort
X.I mmv
Xif any errors are detected.
XSpecifying either of them defaults
X.I mmv
Xto -p, unless -d is specified
X(see above).
XThus, -g and -t are most useful when running
X.I mmv
Xin the background or in
Xa shell script,
Xwhen interactive queries are undesirable.
X
X.ce
XReports
X.PP
XOnce the actions to be performed are determined,
X.I mmv
Xperforms them silently,
Xunless either the -v (verbose) or -n (no-execute) option is specified.
XThe former causes
X.I mmv
Xto report each performed action
Xon the standard output as
X
Xa -> b : done.
X
XHere, "a" and "b" would be replaced by the source and target names,
Xrespectively.
XIf the action deletes the old target,
Xa "(*)" is inserted after the the target name.
XAlso, the "->" symbol is modified when a cycle has to be broken:
Xthe '>' is changed to a '^' on the action prior to which the old target
Xis renamed to a temporary,
Xand the '-' is changed to a '=' on the action where the temporary is used.
X.PP
XUnder -n, none of the actions are performed,
Xbut messages like the above are printed on the standard output
Xwith the ": done." omitted.
X.PP
XThe output generated by -n can (after editing, if desired)
Xbe fed back to
X.I mmv
Xon the standard input
X(by omitting the
X.I from
X--
X.I to
Xpair on the
X.I mmv
Xcommand line).
XTo facilitate this,
X.I mmv
Xignores lines on the standard input that look
Xlike its own error and "done" messages,
Xas well as all lines beginning with white space,
Xand will accept pattern pairs with or without the intervening "->"
X(or "-^", "=>", or "=^").
XLines with "(*)" after the target pattern have the effect of enabling -d
Xfor the files matching this pattern only,
Xso that such deletions are done silently.
XWhen feeding
X.I mmv
Xits own output,
Xone must remember to specify again the task option (if any)
Xoriginally used to generate it.
X.PP
XAlthough
X.I mmv
Xattempts to predict all mishaps prior to performing any specified actions,
Xaccidents may happen.
XFor example,
X.I mmv
Xdoes not check for adequate free space when copying.
XThus, despite all efforts,
Xit is still possible for an action to fail
Xafter some others have already been done.
XTo make recovery as easy as possible,
X.I mmv
Xreports which actions have already been done and
Xwhich are still to be performed
Xafter such a failure occurs.
XIt then aborts, not attempting to do anything else.
XOnce the user has cleared up the problem,
Xhe can feed this report back to
X.I mmv
Xon the standard input
Xto have it complete the task.
X(The user is queried for a file name to dump this report
Xif the standard output has not been redirected.)
X.if '\nO'2' \{\
X
X.ce
X\fIMmvpatch\fP
X.PP
XYou can customize a copy of
X.I mmv
Xvia the
X.I mmvpatch
Xutility.
XIf you wish to change the default task option,
Xrun
X.I mmvpatch
Xon a copy of
X.I mmv
Xnamed as follows:
X
X	-x, -m, -r		mmv.exe
X.br
X	-c, -o			mcp.exe
X.br
X	-a, -z			mad.exe
X.PP
X.I Mmvpatch
Xalso determines the best way to uniquely identify directories.
XAs distributed,
X.I mmv
Xis set to use a method that is guaranteed to work the same way
Xfor all versions of DOS,
Xbut is both slow
Xand unable to correctly handle drives
Xaffected by the
X.I join
Xand
X.I subst
XDOS commands.
XAlternatively,
Xthere is a method that is fast and correct,
Xbut uses an undocumented DOS feature
Xthat may not work properly under all versions of DOS.
X(However, 2.0 and 3.3 are known to work.)
X.I Mmv
Xdoes
X.I not
Xdetermine the best method to use on your system
Xat run-time since this is too slow.
XThe choice is left to
X.I mmvpatch,
Xwhich determines if the fast method works,
Xbut also allows you to return to the slow method.
X\}
X.SH "EXIT STATUS"
X.I Mmv
Xexits with status 1 if it aborts before doing anything,
Xwith status 2 if it aborts due to failure after completing some of the
Xactions,
Xand with status 0 otherwise.
X.if !'\nO'2' \{\
X.SH "SEE ALSO"
Xmv(1), cp(1), ln(1), umask(1)
X\}
X.SH "AUTHOR"
XVladimir Lanin
X.br
Xlanin@csd2.nyu.edu
X.SH "BUGS"
X.if !'\nO'2' \{\
XIf the search pattern is not quoted,
Xthe shell expands the wildcards.
X.I Mmv
Xthen (usually) gives some error message,
Xbut can not determine that the lack of quotes is the cause.
X.PP
X\}\
XTo avoid difficulties in semantics and error checking,
X.I mmv
Xrefuses to move or create directories.
END_OF_FILE
if test 17091 -ne `wc -c <'mmv.1'`; then
    echo shar: \"'mmv.1'\" unpacked with wrong size!
fi
# end of 'mmv.1'
fi
if test -f 'mmv.c.2' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'mmv.c.2'\"
else
echo shar: Extracting \"'mmv.c.2'\" \(7214 characters\)
sed "s/^X//" >'mmv.c.2' <<'END_OF_FILE'
Xstatic int movealias(first, p, pprintaliased)
X	REP *first, *p;
X	int *pprintaliased;
X{
X	char *fstart;
X	int ret;
X
X	strcpy(pathbuf, p->r_hto->h_name);
X	fstart = pathbuf + strlen(pathbuf);
X	strcpy(fstart, TEMP);
X	for (
X		ret = 0;
X		sprintf(fstart + STRLEN(TEMP), "%03d", ret),
X		fsearch(fstart, p->r_hto->h_di) != NULL;
X		ret++
X	)
X		;
X	if (rename(fullrep, pathbuf)) {
X		fprintf(stderr,
X			"%s -> %s has failed.\n", fullrep, pathbuf);
X		*pprintaliased = snap(first, p);
X	}
X	return(ret);
X}
X
X
Xstatic int snap(first, p)
X	REP *first, *p;
X{
X	char fname[80];
X	int redirected = 0;
X
X	if (noex)
X		exit(1);
X
X	failed = 1;
X#ifdef MSDOS
X	ctrlbrk((int (*)())breakstat);
X#else
X	signal(SIGINT, breakstat);
X#endif
X	if (
X		badstyle == ASKBAD &&
X		isatty(fileno(stdout)) &&
X		getreply("Redirect standard output to file? ", 0)
X	) {
X		redirected = 1;
X#ifndef MSDOS
X		umask(oldumask);
X#endif
X		while (
X			fprintf(stderr, "File name> "),
X			(outfile = fopen(gets(fname), "w")) == NULL
X		)
X			fprintf(stderr, "Can't open %s.\n", fname);
X	}
X	if (redirected || !verbose)
X		showdone(p);
X	fprintf(outfile, "The following left undone:\n");
X	noex = 1;
X	return(first != p);
X}
X
X
Xstatic void showdone(fin)
X	REP *fin;
X{
X	REP *first, *p;
X
X	for (first = hrep.r_next; ; first = first->r_next)
X		for (p = first; p != NULL; p = p->r_thendo) {
X			if (p == fin)
X				return;
X			fprintf(outfile, "%s%s %c%c %s%s : done%s\n",
X				p->r_hfrom->h_name, p->r_ffrom->fi_name,
X				p->r_flags & R_ISALIASED ? '=' : '-',
X				p->r_flags & R_ISCYCLE ? '^' : '>',
X				p->r_hto->h_name, p->r_nto,
X				(p->r_fdel != NULL && !(op & APPEND)) ? " (*)" : "");
X		}
X}
X
X
Xstatic void breakout()
X{
X	fflush(stdout);
X	fprintf(stderr, "Aborting, nothing done.\n");
X	exit(1);
X}
X
X
Xstatic int breakrep()
X{
X	gotsig = 1;
X	return(1);
X}
X
X
Xstatic void breakstat()
X{
X	exit(1);
X}
X
X
Xstatic void quit()
X{
X	fprintf(stderr, "Aborting, nothing done.\n");
X	exit(1);
X}
X
X
Xstatic int copymove(p)
X	REP *p;
X{
X#ifndef MSDOS
X#ifndef SYSV
X	{
X		int llen;
X		char linkbuf[MAXPATH];
X
X		if ((llen = readlink(pathbuf, linkbuf, MAXPATH - 1)) != 1) {
X			linkbuf[llen] = '\0';
X			return(symlink(linkbuf, fullrep) || myunlink(pathbuf, p->r_ffrom));
X		}
X	}
X#endif
X#endif
X	return(copy(p->r_ffrom, -1) || myunlink(pathbuf, p->r_ffrom));
X}
X
X
X
X#define IRWMASK (S_IREAD | S_IWRITE)
X#define RWMASK (IRWMASK | (IRWMASK >> 3) | (IRWMASK >> 6))
X
Xstatic int copy(ff, len)
X	FILEINFO *ff;
X	long len;
X{
X	char buf[BUFSIZE], c;
X	int f, t, k, mode, perm;
X#ifdef MSDOS
X	struct ftime tim;
X#else
X#ifdef SYSV
X	struct utimbuf tim;
X#else
X	struct timeval tim[2];
X#endif
X	struct stat fstat;
X#endif
X
X	if ((f = open(pathbuf, O_RDONLY | O_BINARY, 0)) < 0)
X		return(-1);
X	perm =
X#ifdef MSDOS
X		IRWMASK		/* will _chmod it later (to get all the attributes) */
X#else
X		(op & (APPEND | OVERWRITE)) ?
X			(~oldumask & RWMASK) | (ff->fi_mode & ~RWMASK) :
X			ff->fi_mode
X#endif
X		;
X	mode = O_CREAT |
X#ifdef MSDOS
X		O_BINARY | (op & ZAPPEND ? O_RDWR : O_WRONLY)
X#else
X		O_WRONLY
X#endif
X		;
X	if (!(op & APPEND))
X		mode |= O_TRUNC;
X	if ((t = open(fullrep, mode, perm)) < 0) {
X		close(f);
X		return(-1);
X	}
X	if (op & APPEND)
X		lseek(t, 0, 2);
X#ifdef MSDOS
X	if (op & ZAPPEND && filelength(t) != 0) {
X		if (lseek(t, -1, 1) == -1L || read(t, &c, 1) != 1) {
X			close(f);
X			close(t);
X			return(-1);
X		}
X		if (c == 26)
X			lseek(t, -1, 1);
X	}
X#endif
X	if ((op & APPEND) && len != -1L) {
X		while (
X			len != 0 &&
X			(k = read(f, buf, len > BUFSIZE ? BUFSIZE : (unsigned)len)) > 0 &&
X			write(t, buf, k) == k
X		)
X			len -= k;
X		if (len == 0)
X			k = 0;
X	}
X	else 
X		while ((k = read(f, buf, BUFSIZE)) > 0 && write(t, buf, k) == k)
X			;
X	if (!(op & (APPEND | OVERWRITE)))
X		if (
X#ifdef MSDOS
X			getftime(f, &tim) ||
X			setftime(t, &tim) ||
X			_chmod(fullrep, 1, ff->fi_attrib) == -1
X#else
X			stat(pathbuf, &fstat) ||
X			(
X#ifdef SYSV
X				tim.actime = fstat.st_atime,
X				tim.modtime = fstat.st_mtime,
X#else
X				tim[0].tv_sec = fstat.st_atime,
X				tim[1].tv_sec = fstat.st_mtime,
X#endif
X				utimes(fullrep, tim)
X			)
X#endif
X		)
X			fprintf(stderr, "Strange, couldn't transfer time from %s to %s.\n",
X				pathbuf, fullrep);
X
X	close(f);
X	close(t);
X	if (k != 0) {
X		if (!(op & APPEND))
X			unlink(fullrep);
X		return(-1);
X	}
X	return(0);
X}
X
X
X#ifndef RENAME
Xstatic int rename(from, to)
X	char *from, *to;
X{
X	if (link(from, to))
X		return(-1);
X	if (unlink(from)) {
X		unlink(to);
X		return(-1);
X	}
X	return(0);
X}
X#endif
X
X
Xstatic int myunlink(n, f)
X	char *n;
X	FILEINFO *f;
X{
X#ifdef MSDOS
X	int a;
X
X	if (((a = f->fi_attrib) & FA_RDONLY) && _chmod(n, 1, a & ~FA_RDONLY) < 0) {
X		fprintf(stderr, "Strange, can not _chmod (or unlink) %s.\n", f);
X		return(-1);
X	}
X#endif
X	if (unlink(n)) {
X		fprintf(stderr, "Strange, can not unlink %s.\n", n);
X		return(-1);
X	}
X	return(0);
X}
X
X
Xstatic int getreply(m, failact)
X	char *m;
X	int failact;
X{
X	static FILE *tty = NULL;
X	int c, r;
X
X	fprintf(stderr, m);
X	if (tty == NULL && (tty = fopen(TTY, "r")) == NULL) {
X		fprintf(stderr, "Can not open %s to get reply.\n", TTY);
X		if (failact == -1)
X			quit();
X		else
X			return(failact);
X	}
X	for (;;) {
X		r = fgetc(tty);
X		if (r == EOF) {
X			fprintf(stderr, "Can not get reply.\n");
X			if (failact == -1)
X				quit();
X			else
X				return(failact);
X		}
X		if (r != '\n')
X			while ((c = fgetc(tty)) != '\n' && c != EOF)
X				;
X		r = mylower(r);
X		if (r == 'y' || r == 'n')
X			return(r == 'y');
X		fprintf(stderr, "Yes or No? ");
X	}
X}
X
X
Xstatic void *myalloc(k)
X	unsigned k;
X{
X	void *ret;
X
X	if (k == 0)
X		return(NULL);
X	if ((ret = (void *)malloc(k)) == NULL) {
X		fprintf(stderr, "Insufficient memory.\n");
X		quit();
X	}
X	return(ret);
X}
X
X
Xstatic void *challoc(k, which)
X	int which;
X	int k;
X{
X	void *ret;
X	CHUNK *p, *q;
X	SLICER *sl = &(slicer[which]);
X
X	if (k > sl->sl_len) {
X		for (
X			q = NULL, p = freechunks;
X			p != NULL && (sl->sl_len = p->ch_len) < k;
X			q = p, p = p->ch_next
X		)
X			;
X		if (p == NULL) {
X			sl->sl_len = CHUNKSIZE - sizeof(CHUNK *);
X			p = (CHUNK *)myalloc(CHUNKSIZE);
X		}
X		else if (q == NULL)
X			freechunks = p->ch_next;
X		else
X			q->ch_next = p->ch_next;
X		p->ch_next = sl->sl_first;
X		sl->sl_first = p;
X		sl->sl_unused = (char *)&(p->ch_len);
X	}
X	sl->sl_len -= k;
X	ret = (void *)sl->sl_unused;
X	sl->sl_unused += k;
X	return(ret);
X}
X
X
Xstatic void chgive(p, k)
X	void *p;
X	unsigned k;
X{
X	((CHUNK *)p)->ch_len = k - sizeof(CHUNK *);
X	((CHUNK *)p)->ch_next = freechunks;
X	freechunks = (CHUNK *)p;
X}
X
X
X#ifndef MSDOS
Xstatic void memmove(to, from, k)
X	char *to, *from;
X	unsigned k;
X{
X	if (from > to)
X		while (k-- != 0)
X			*(to++) = *(from++);
X	else {
X		from += k;
X		to += k;
X		while (k-- != 0)
X			*(--to) = *(--from);
X	}
X}
X#endif
X
X
Xstatic int mygetc()
X{
X	static int lastc = 0;
X
X	if (lastc == EOF)
X		return(EOF);
X	return(lastc = getchar());
X}
X
X
X#ifdef MSDOS
Xstatic int leave()
X{
X	return(0);
X}
X
Xstatic void cleanup()
X{
X	int i;
X
X	if (patch.ph_safeid) {
X		for (i = 0; i < nhandles; i++) {
X			if (!(handles[i]->h_di->di_flags & DI_CLEANED)) {
X				sprintf(pathbuf, "%s%s%03d",
X					handles[i]->h_name, IDF, handles[i]->h_di->di_did);
X				if (unlink(pathbuf))
X					fprintf(stderr, "Strange, couldn't unlink %s.\n", pathbuf);
X				handles[i]->h_di->di_flags |= DI_CLEANED;
X			}
X		}
X	}
X/*
X	Write device availability: undocumented internal MS-D*S function.
X	Restore previous value.
X*/
X	bdos(0x37, olddevflag, 3);
X}
X
X#endif
END_OF_FILE
if test 7214 -ne `wc -c <'mmv.c.2'`; then
    echo shar: \"'mmv.c.2'\" unpacked with wrong size!
fi
# end of 'mmv.c.2'
fi
echo shar: End of archive 1 \(of 2\).
cp /dev/null ark1isdone
MISSING=""
for I in 1 2 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked both archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0
exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.