[comp.sources.unix] v18i022: Rename multiple files

rsalz@uunet.uu.net (Rich Salz) (03/14/89)

Submitted-by: Vladimir Lanin <lanin@csd2.nyu.edu>
Posting-number: Volume 18, Issue 22
Archive-name: rename-files

This must have been done already by someone somewhere, but I could not
find it, so here goes.

Whereas mv can rename (as opposed to move) only one file at a time, the
following program (ren) can rename many files according to search and
replacement patterns, ala VMS (but better).  ren checks for replacement
name collisions and handles rename chains gracefully.

To compile, just unshar the following and cc ren.c.  ren.1 contains the
man style doc.

I have not tried this under anything but bsd 4.2 and 4.3, but I don't see
why there should be a problem.

Vladimir Lanin
lanin@csd2.nyu.edu

Any comments appreciated.

--cut here--
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	Makefile (spliced in by hand)
#	ren.1
#	ren.c
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: 'extracting Makefile'
if test -f Makefile ; then
	echo shar: will not clobber existing Makefile
else
sed 's/^X//' <<\SHAR_EOF >Makefile
Xall:	ren ren.1
X
Xren:	ren.c
X	$(CC) -o ren $(CFLAGS) ren.c
Xinstall:	all
X	@echo "install according to local convention"
Xclean:
X	rm -f core a.out ren ren.o
SHAR_EOF
fi
echo shar: "extracting 'ren.1'" '(3843 characters)'
if test -f 'ren.1'
then
	echo shar: "will not over-write existing file 'ren.1'"
else
sed 's/^X//' << \SHAR_EOF > 'ren.1'
X.TH REN 1 "May 20, 1988"
X.UC 4
X.SH NAME
Xren \- rename multiple files
X.SH SYNOPSIS
X.B ren
X[
X.B \-d
X|
X.B \-k
X|
X.B \-a
X] [
X.B \-v
X] [path/]search replacement
X.SH DESCRIPTION
X.I Ren
Xrenames each file in the current directory
X(or in the
X.I path
Xdirectory, if specified)
Xthat matches the
X.I search
Xpattern;
Xeach matching file's new name is given by the
X.I replacement
Xpattern.
XThe multiple rename is performed safely,
Xi.e. without any unexpected deletion of files
Xdue to collisions of replacement names with existing names,
Xor with other replacement names.
XAlso, all error checking is done prior to doing any renames,
Xso either all renames are done, or none.
X.PP
XThe search pattern is a filename
Xwith embedded wildcards, i.e. * and ?,
Xwhich have their usual meanings of, respectively,
Xmatching any string of characters,
Xand matching any single character.
XThe replacement pattern is another filename
Xwith embedded
X.I wildcard
X.IR indexes ,
Xeach of which consists of the character # followed by a digit
Xfrom 1 to 9.
XIn the new name of a matching file,
Xthe wildcard indexes are replaced by the
Xactual characters that matched the referenced wildcards
Xin the original filename.
XThus, if the search pattern is "abc*.*.*"
Xand the replacement pattern is "xyz#1.#3",
Xthen "abc.txt.doc" is renamed to "xyz.doc"
X(since the first * matched "", the second matched "txt",
Xand the third matched "doc").
X.PP
XNote that the shell normally expands the wildcards * and ?,
Xwhich in the case of
X.I ren
Xis undesirable.
XThus, in most cases it is necessary to enclose the search pattern
Xin quotes, e.g.
Xren "*.a" #1.b.
XTo strip any of the characters *, ?, and # of their special meaning to
X.I ren,
Xas when the actual replacement name must contain the character #,
Xprecede the special character with \\
X(and enclose the argument in qoutes because of the shell).
X.PP
XNote that a path is not allowed in the replacement pattern.
X.I Ren
Xdoes not allow moving files between directories,
Xwhich facilitates the safety checks next described.
X.PP
XWhen any two matching files
Xwould have to be renamed to the same new filename,
X.I ren
Xdetects the condition prior to doing any renames
Xand aborts with an error message.
X.pp
X.I Ren
Xalso checks if any file deletes would result from the rename,
Xi.e. if some file1 would have to be renamed to file2,
Xand file2 already exists and is not itself being renamed.
X(Here and below, "delete" really means "unlink".)
XIn such a case,
X.I ren
Xasks you (by reading a line from standard input)
Xif you really wish file2 to be deleted.
XIf your answer is negative, file1 is not renamed.
X.PP
X.I Ren
Xsafely performs chain renames,
Xi.e. when file1 is renamed to file2,
Xfile2 to file3, file3 to file4, etc,
Xby doing the renames in the proper order.
XIn the case that the chain is a cycle, i.e. filen is renamed back to file1,
X.I ren
Xbreaks the cycle by using a temporary name.
X.PP
XFiles beginning with . are not matched against the search pattern
X(and thus not renamed)
Xunless the search pattern explicitly begins with '.'.
XIn any case, "." and ".." are never matched.
X.PP
XOptions:
X.TP
X.I \-v
X(verbose):
Xafter each rename,
Xthe message "file1 -> file2 [(*)]" appears on the standard output.
XThe (*) appears in the case of a deleting rename,
Xi.e. when the old file2 is deleted.
X.TP
X.IR \-d ,
X.IR \-k ,
X.IR \-a :
Xsuppress interrogation with regard to deleting renames, and:
X.TP
X.I \-d
X(delete): perform all deleting renames silently.
X.TP
X.I \-k
X(keep): perform no deleting renames.
X.TP
X.I \-a
X(abort): if any deleting renames are detected,
Xabort prior to doing any renames.
X.SH "SEE ALSO"
Xmv(1)
X.SH "AUTHOR"
XVladimir Lanin
X.br
Xlanin@csd2.nyu.edu
X.SH "BUGS"
XIf the search pattern is not quoted,
Xthe shell expands the wildcards.
X.I Ren
Xthen complains that there are too many arguments
X(if indeed there are),
Xbut can not determine that the lack of quotes is the cause.
SHAR_EOF
if test 3843 -ne "`wc -c < 'ren.1'`"
then
	echo shar: "error transmitting 'ren.1'" '(should have been 3843 characters)'
fi
fi
echo shar: "extracting 'ren.c'" '(11365 characters)'
if test -f 'ren.c'
then
	echo shar: "will not over-write existing file 'ren.c'"
else
sed 's/^X//' << \SHAR_EOF > 'ren.c'
X#include <stdio.h>
X#include <sys/file.h>
X#include <sys/types.h>
X#include <sys/dir.h>
X
X#define MAXWILD 20
X#define MAXREP 40
X
Xtypedef struct rep{
X	int ftorep;
X	char *repname;
X	struct rep *dofirst;
X	int mustdel;
X	int status;
X	struct rep *nextrep;
X} REP;
X
X#define DO 0
X#define FORGET 1
X#define DONE 2
X
X#define ASKDEL 0
X#define QUIETDEL 1
X#define NODEL 2
X#define ABORTDEL 3
X
Xextern int alphasort();
Xextern int scandir();
Xextern qsort();
X
Xstatic procargs();
Xstatic int procdir();
Xstatic int checkpats();
Xstatic int getreps();
Xstatic int scan();
Xstatic char *makerep();
Xstatic checkcollisons();
Xstatic int mycmp();
Xstatic checkdeletes();
Xstatic int bsearch();
Xstatic char** breakcycles();
Xstatic dorenames();
X
Xstatic char tempprefix[] = "renTMP";
X
Xmain(argc, argv)
X	int argc;
X	char *(argv[]);
X{
X	char *from_pat, *to_pat, *path;
X	int verbose, delstyle;
X	int nfils, nreps;
X	struct direct **dot;
X	REP *reps, **filrep;
X	char **tempnames;
X
X	procargs(argc, argv, &verbose, &delstyle, &from_pat, &to_pat, &path);
X	checkpats(from_pat, to_pat);
X	nfils = procdir(path, &dot, &filrep);
X	nreps = getreps(dot, nfils, from_pat, to_pat, filrep, &reps);
X	checkcollisons(reps, nreps);
X	checkdeletes(reps, dot, filrep, nfils, delstyle);
X	tempnames = breakcycles(reps, nreps, dot);
X	dorenames(reps, dot, tempnames, verbose);
X	return(0);
X}
X
X
Xstatic procargs(argc, argv, pverbose, pdelstyle, pfrom_pat, pto_pat, ppath)
X	int argc;
X	char *(argv[]);
X	int *pverbose, *pdelstyle;
X	char **pfrom_pat, **pto_pat, **ppath;
X{
X	char *p;
X	int arg1;
X
X	*pverbose = 0;
X	*pdelstyle = ASKDEL;
X	for (arg1 = 1; arg1 < argc && *(argv[arg1]) == '-'; arg1++)
X		for (p = argv[arg1]+1; *p != '\0'; p++)
X			if (*p == 'v')
X				*pverbose = 1;
X			else if (*p == 'd')
X				*pdelstyle = QUIETDEL;
X			else if (*p == 'k')
X				*pdelstyle = NODEL;
X			else if (*p == 'a')
X				*pdelstyle = ABORTDEL;
X			else {
X				fputs("Illegal option -", stderr);
X				fputc(*p, stderr);
X				fputc('\n', stderr);
X				exit(1);
X			}
X
X	if (argc - arg1 != 2) {
X		fputs(
X			"Usage: ren [-d|-k|-a] [-v] [path/]search_pattern replacement_pattern\n",
X			stderr);
X		fputs("\nSearch patterns containing wildcard(s) should be quoted.\n",
X			stderr);
X		fputs("Put #n into the replacement pattern to insert the string\n",
X			stderr);
X		fputs("matched by the n'th wildcard in the search pattern.\n", stderr);
X		exit(1);
X	}
X
X	*ppath = argv[arg1];
X	*pto_pat = argv[arg1 + 1];
X	for (
X		*pfrom_pat = *ppath + strlen(*ppath);
X		**pfrom_pat != '/' && *pfrom_pat != *ppath;
X		--(*pfrom_pat)
X	) {}
X	if (**pfrom_pat == '/') {
X		**pfrom_pat = '\0';
X		if (*pfrom_pat == *ppath)
X			*ppath = "/";
X		(*pfrom_pat)++;
X	}
X	else
X		*ppath = ".";
X}
X
X
Xstatic checkpats(from_pat, to_pat)
X	char *from_pat, *to_pat;
X{
X	char *p;
X	int nwilds;
X
X	for (nwilds = 0, p = from_pat; *p != '\0'; p++) {
X		if (*p == '\\') {
X			p++;
X			if (*p == '\0')
X				break;
X		}
X		else if (*p == '*' || *p == '?')
X			nwilds++;
X	}
X
X	for (p = to_pat; *p != '\0'; p++) {
X		if (*p == '/') {
X			fputs("The replacement pattern must not contain a path.\n",
X				stderr);
X			exit(1);
X		}
X		else if (*p == '*' || *p == '?') {
X			fputs("No wildcards allowed in replacement pattern.\n", stderr);
X			fputs("Use #n to insert the substring matched\n", stderr);
X			fputs("by the n'th wildcard in the search pattern.\n", stderr);
X			exit(1);
X		}
X		else if (*p == '#') {
X			if (*(p+1) < '1' || *(p+1) > '9' || *(p+1) - '0' > nwilds) {
X				fputc('#', stderr);
X				fputc(*(p+1), stderr);
X				fputs(": bad substring numer.\n", stderr);
X				fputs("(Use '\\#' to get '#' in replacement string.)\n", stderr);
X				exit(1);
X			}
X			p++;
X		}
X		else if (*p == '\\') {
X			p++;
X			if (*p == '\0')
X				break;
X		}
X	}
X}
X
X
Xstatic int procdir(path, pdot, pfilrep)
X	char *path;
X	struct direct ***pdot;
X	REP ***pfilrep;
X{
X	int nfils;
X
X	if (access(path, R_OK | X_OK | W_OK) < 0) {
X		fputs("Read/write access denied to ", stderr);
X		fputs(path, stderr);
X		fputc('\n', stderr);
X		exit(1);
X	}
X	if (chdir(path) < 0) {
X		fputs("Strange, can not change directory to ", stderr);
X		fputs(path, stderr);
X		fputc('\n', stderr);
X		exit(1);
X	}
X	if ((nfils = scandir(".", pdot, NULL, alphasort)) < 0) {
X		fputs("Strange, can not scan ", stderr);
X		fputs(path, stderr);
X		fputc('\n');
X		exit(1);
X	}
X	*pfilrep = (REP **)malloc(nfils * sizeof(REP *));
X	return(nfils);
X}
X
X
X
Xstatic int getreps(dot, nfils, from_pat, to_pat, filrep, preps)
X	struct direct *(dot[]);
X	int nfils;
X	char *from_pat, *to_pat;
X	REP *(filrep[]);
X	REP **preps;
X{
X	char *(start[MAXWILD]);
X	int len[MAXWILD];
X	int nreps, i;
X	REP *cur;
X
X	for (*preps = NULL, nreps = 0, i = 0; i < nfils; i++) {
X		if (
X			(*(dot[i]->d_name) != '.' || *from_pat == '.') &&
X			strcmp(dot[i]->d_name, ".") != 0 &&
X			strcmp(dot[i]->d_name, "..") != 0 &&
X			scan(from_pat, dot[i]->d_name, start, len)
X		) {
X			cur = (REP *)malloc(sizeof(REP));
X			filrep[i] = cur;
X			cur->repname = makerep(to_pat, start, len);
X			cur->ftorep = i;
X			cur->mustdel = -1;
X			cur->nextrep = *preps;
X			cur->status = DO;
X			*preps = cur;
X			nreps++;
X			if (*(cur->repname) == '\0') {
X				fputc('\'', stderr);
X				fputs(dot[i]->d_name, stderr);
X				fputs("' would have to be renamed to empty string.\n",
X					stderr);
X				fputs("Aborting, no renames done.\n");
X				exit(1);
X			}
X		}
X		else
X			filrep[i] = NULL;
X	}
X	if (nreps == 0) {
X		fputs("No match.\n", stderr);
X		exit(1);
X	}
X	return(nreps);
X}
X
X
Xstatic int scan(pat, s, start1, len1)
X	char *pat, *s, **start1;
X	int *len1;
X{
X	*start1 = 0;
X	while (1) {
X		if (*pat == '*') {
X			pat++;
X			*start1 = s;
X			if (*pat == '\0') {
X				*len1 = strlen(s);
X				return(1);
X			}
X			else {
X				for (*len1=0; !scan(pat, s, start1+1, len1+1); (*len1)++, s++)
X					if (*s == '\0')
X						return(0);
X				return(1);
X			}
X		}
X		else if (*pat == '\0')
X			return(*s == '\0');
X		else if (*pat == '?') {
X			if (*s == '\0')
X				return(0);
X			*(start1++) = s;
X			*(len1++) = 1;
X			pat++;
X			s++;
X		}
X		else {
X			if (*pat == '\\') {
X				pat++;
X				if (*pat == '\0')
X					return(*s == '\0');
X			}
X			if (*pat == *s) {
X 				pat++;
X				s++;
X			}
X			else
X				return(0);
X		}
X	}
X}
X
X
Xstatic char *makerep(pat, start, len)
X	char *pat, **start;
X	int *len;
X{
X	int i, k;
X	char *q, *p, *res;
X	char b[MAXNAMLEN];
X
X	p = b;
X	for ( ; *pat != '\0'; pat++) {
X		if (*pat == '#') {
X			pat++;
X			k = *pat - '1';
X			if (p - b + len[k] > MAXNAMLEN) {
X				fputs("Replacement name too long.\n", stderr);
X				exit(1);
X			}
X			for (i=0, q = start[k]; i < len[k]; i++)
X				*(p++)= *(q++);
X		}
X		else {
X			if (*pat == '\\') {
X				pat++;
X				if (*pat == '\0')
X					break;
X			}
X			if (p - b + 1 > MAXNAMLEN) {
X				fputs("Replacement name too long.\n", stderr);
X				exit(1);
X			}
X			*(p++)= *pat;
X		}
X	}
X	*(p++) = '\0';
X	res = (char *)malloc((p - b) * sizeof(char));
X	strcpy(res, b);
X	return(res);
X}
X
X
Xstatic checkcollisons(reps, nreps)
X	REP *reps;
X	int nreps;
X{
X	char **repdict;
X	REP *cur;
X	int i;
X
X	repdict = (char **)malloc(nreps * sizeof(char *));
X	for (i = 0, cur = reps; cur != NULL; cur = cur->nextrep)
X		repdict[i++] = cur->repname;
X	qsort(repdict, nreps, sizeof(char *), mycmp);
X	for (i = 0; i < nreps-1; i++)
X		if (strcmp(repdict[i], repdict[i+1]) == 0) {
X			fputs("Two or more files would have to be renamed to '",
X				stderr);
X			fputs(repdict[i], stderr);
X			fputs("'.\n", stderr);
X			fputs("Aborting, no renames done.\n", stderr);
X			exit(1);
X		}
X}
X
X
Xstatic int mycmp(pps1, pps2)
X	char **pps1, **pps2;
X{
X	return(strcmp(*pps1, *pps2));
X}
X
X
Xstatic checkdeletes(reps, dot, filrep, nfils, delstyle)
X	REP *reps;
X	struct direct *(dot[]);
X	REP *(filrep[]);
X	int nfils, delstyle;
X{
X	int recheck, i;
X	REP *cur;
X	char doit, reply[MAXREP];
X
X	do {
X		recheck = 0;
X		for (cur = reps; cur != NULL; cur = cur->nextrep) {
X			if (cur->status == FORGET)
X				continue;
X			if ((i = bsearch(cur->repname, dot, nfils)) >= 0) {
X				if (filrep[i] == NULL && cur->mustdel < 0) {
X					cur->dofirst = NULL;
X					if (delstyle == QUIETDEL)
X						cur->mustdel = i;
X					else if (delstyle == NODEL) {
X						cur->status = FORGET;
X						filrep[cur->ftorep] = NULL;
X						recheck = 1;
X					}
X					else if (delstyle == ABORTDEL) {
X						fputs(dot[i]->d_name, stderr);
X						fputs(" would have to be removed.\n", stderr);
X						fputs("Aborting, no renames done.\n", stderr);
X						exit(1);
X					}
X					else {
X						fputs(dot[cur->ftorep]->d_name, stderr);
X						fputs(" -> ", stderr);
X						fputs(cur->repname, stderr);
X						fputs(" ; remove old ", stderr);
X						fputs(dot[i]->d_name, stderr);
X						fputs("? ", stderr);
X						for (;;) {
X							doit = *fgets(reply, MAXREP, stdin);
X							if (doit ==  'Y' || doit == 'y') {
X								cur->mustdel = i;
X								break;
X							}
X							else if (doit == 'N' || doit == 'n') {
X								cur->status = FORGET;
X								filrep[cur->ftorep] = NULL;
X								recheck = 1;
X								break;
X							}
X							else
X								fputs("Yes or No? ", stderr);
X						}
X					}
X				}
X				else {
X					cur->dofirst = filrep[i];
X					cur->mustdel = -1;
X				}
X			}
X			else {
X				cur->dofirst = NULL;
X				cur->mustdel = -1;
X			}
X		}
X	} while (recheck);
X}
X
X
Xstatic int bsearch(s, dlist, n)
X	char *s;
X	struct direct *(dlist[]);
X	int n;
X{
X	int first, k, last, res;
X
X	for(first = 0, last = n - 1;;) {
X		if (last < first)
X			return(-1);
X		k = (first + last) >> 1;
X		if ((res = strcmp(s, dlist[k]->d_name)) == 0)
X			return(k);
X		if (res < 0)
X			last = k - 1;
X		else
X			first = k + 1;
X	}
X}
X
X
Xstatic char** breakcycles(reps, nreps, dot)
X	REP *reps;
X	int nreps;
X	struct direct *(dot[]);
X{
X	char **tempnames;
X	int tempno;
X	REP *cur, *pred;
X
X	tempnames = (char **)malloc(nreps * sizeof(char *) + 1);
X	tempno = 0;
X	for (cur = reps; cur != NULL; cur = cur->nextrep) {
X		if (cur->status == FORGET)
X			continue;
X		for (pred = cur->dofirst;
X			 pred != NULL && pred != cur;
X			 pred = pred->dofirst)
X		{
X			if (pred->status != DO) {
X				fputs("Strange error in cycle checking.\n", stderr);
X				exit(1);
X			}
X		}
X		if (pred == cur)
X			if (cur->dofirst == cur)
X				cur->status = FORGET;
X			else {
X				pred = (REP *)malloc(sizeof(REP));
X				tempnames[++tempno] =
X					(char *)malloc(sizeof(tempprefix) + strlen(cur->repname));
X				strcpy(tempnames[tempno], tempprefix);
X				strcat(tempnames[tempno], cur->repname);
X				pred->repname = cur->repname;
X				pred->ftorep = -tempno;
X				pred->dofirst = cur->dofirst;
X				pred->mustdel = -1;
X				pred->status = DO;
X				pred->nextrep = cur->nextrep;
X				cur->repname = tempnames[tempno];
X				cur->dofirst = NULL;
X				cur->nextrep = pred;
X			}
X	}
X	return(tempnames);
X}
X
X
Xstatic dorenames(reps, dot, tempnames, verbose)
X	REP *reps;
X	struct direct *(dot[]);
X	char *(tempnames[]);
X	int verbose;
X{
X	REP *cur;
X	int skipped;
X	char *fromname;
X
X	do {
X		skipped = 0;
X		for (cur = reps; cur != NULL; cur = cur->nextrep) {
X			if (cur->status == DO) {
X				if (cur->dofirst != NULL && cur->dofirst->status != DONE)
X					skipped = 1;
X				else {
X					fromname = (cur->ftorep < 0 ?
X						tempnames[-(cur->ftorep)] :
X						dot[cur->ftorep]->d_name);
X					cur->status = DONE;
X					if (rename(fromname, cur->repname)) {
X						fputs("Strange. Can not rename '", stderr);
X						fputs(fromname, stderr);
X						fputs("' to '", stderr);
X						fputs(cur->repname, stderr);
X						fputs("'\n", stderr);
X					}
X					else if (verbose) {
X						fputs(fromname, stdout);
X						fputs(" -> ", stdout);
X						fputs(cur->repname, stdout);
X						if (cur->mustdel >= 0)
X							fputs(" (*)", stderr);
X						fputc('\n', stdout);
X					}
X				}
X			}
X		}
X	} while (skipped);
X}
SHAR_EOF
if test 11365 -ne "`wc -c < 'ren.c'`"
then
	echo shar: "error transmitting 'ren.c'" '(should have been 11365 characters)'
fi
fi
#	End of shell archive
exit 0

-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.