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.