dan@rna.UUCP (Dan Ts'o) (07/07/84)
Hi, Here are the sources for a program to generate a shell command file from a global filename expression. A classic usage is when you want to rename files which end in, say, ".c", to ".c.old": mkcmd "mv *.c #.c.old" | sh -x This program was originally written at Harvard back in the V6 UNIX days and was called "expand". I have modified it to work with the 4.2BSD directory routines as well as the original directory format and have hacked it to use malloc() rather than the original stack grabbing memory allocation. I also renamed it to "mkcmd" since Berkeley UNIX already has an "expand" command. If there is a more elegant way to do similar shell commands, I'd appreciate some feedback. I know you can piece together something with a "for" loop and either basename or sed. Cheers, Dan Ts'o ...cmcl2!rna!dan echo mkcmd.doc sed 's/^X//' > mkcmd.doc << 'All work and no play makes Jack a dull boy' XNAME X mkcmd - expand complicated global commands X XSYNOPSIS X mkcmd commandstring > shellfile X mkcmd commandstring | sh X XDESCRIPTION X X There is now a command to do those impossible wild card Xrequests like rename everything which ends with ".ftn" to end Xwith ".f4p" . This is done by: X X % mkcmd "mv *.ftn #.f4p" > file X % sh file X Xmkcmd generates a command file on its std output which can be Xexamined on your terminal, redirected into a command file, or Xpiped directly into the shell. X X '#' is the new special character introduced with mkcmd. It Xrepresents the string matched by '*' . If there are more than Xone wild card specifications in the file name, they may be refer- Xenced by '#1', '#2', ..., '#9'. e.g.: X X % mkcmd "mv *.[cs] #1.#2.old" >file X Xwill generate a command file which will add the extension ".old" Xto all files which end either ".c" or ".s" . X '#1' represents the string matched by the '*' and '#2' Xrepresents the character matched by the '[cs]' . X '?' is also allowed and matches any single character. X '#0' represents the logname of the owner of the file matched. Xe.g.: X X % mkcmd "/tmp/* is owned by #0" X Xwill give a listing of all files in /tmp and their owners. X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X All work and no play makes Jack a dull boy echo mkcmd.c sed 's/^X//' > mkcmd.c << 'All work and no play makes Jack a dull boy' X/* X * mkcmd - create shell command stream on stdout using global filename X * expressions that might not match existing files. X * Originally from Harvard X * Modified by Dan Ts'o (rna!dan) for 4.2BSD X */ X#include <sys/types.h> X#include <sys/stat.h> X#define boolean int X#define true 1 X#define false 0 X X#define space 0377 X#define chars 0376 X#define any 0375 X#define one 0374 X#define range 0373 X#define arg 0372 X#define chend 0371 X X/* dyt - mods for 4.2BSD */ X#define NDIR 1 /* Undefine for non-NDIR directories */ X#ifdef NDIR X#include <sys/dir.h> X#else X#define NAMSIZ 14 /* Directory entry size */ X#endif X X#include <stdio.h> X#define MAXARG 10 Xchar *getlogn(), *getpath(), *ealloc(); Xint getunum(); X#define unsign(chr) (0377&chr) Xstruct entry { X struct entry *e_link; X int e_namlen; X int e_level[2*MAXARG]; X#ifdef NDIR X char e_name[1]; X#else X char e_name[NAMSIZ+1]; X#endif X} *ehead = 0; X Xchar *lim,*beg,cmnd[1500],output[1000],*p,*glob,*lastbl,*o,*nomore; Xboolean instring,argb; Xint globcn,oldi; X Xput (c) Xchar c; X{ X if (instring) *o++ = c; X else { X *o++ = chars; X *o++ = c; X instring = true; X } X} X Xchar get () X{ X if (*p == 0) X error ("Unexpected end of command string"); X return (*p++); X} X Xout (c) Xchar c; X{ X if (instring) { X *o++ = chend; X instring = false; X } X *o++ = c; X} X Xseen (c) Xchar c; X{ X if (nomore) X error ("Only one global allowed"); X glob = lastbl; X ++globcn; X if (argb) X error ("Arguments can't mix with globals"); X out (c); X} X Xboolean globr(c1,c2,lev) Xregister char *c1,*c2; Xregister int *lev; X{ X register char *b1; X X while (true) { X b1 = c1; X switch (unsign(*c2++)) { X X case any: X while (c1<lim && !globr(c1,c2,lev+2)) ++c1; X *lev++ = b1-beg; X *lev++ = c1-b1; X return (c1<lim); X case range: X do { X if (*c2 == ']') X return(false); X if (*c1 == *c2++) X break; X } while (*c2 != '-' || *c1 < (++c2)[-2] || *c1 > *c2++); X while (*c2++ != ']'); X case one: X *lev++ = b1-beg; X *lev++ = 1; X ++c1; X continue; X case chars: X while (unsign(*c2) != chend && *c1++ == *c2) ++c2; X if (unsign(*c2++) != chend) X return (false); X continue; X case space: X while (*c1==0 && c1<lim) ++c1; X return (c1 == lim); X X } X } X} X Xboolean globbr (ep, c2) Xregister struct entry *ep; Xchar *c2; X{ X beg = ep->e_name; X lim = beg + ep->e_namlen + 1; /* Include ending null */ X return (globr(beg, c2, ep->e_level)); X} X Xmain (n,car) Xchar **car; Xint n; X{ X register char ch,*x,*y; X int i; X struct stat sbuf; X char *gp, *gep; X#ifdef NDIR X DIR *dhan; X struct direct *dp; X#else X int han; X#endif X struct entry *ep, **ea, **et; X int entcmp(); X X if (n != 2) X error ("Exactly one argument required"); X p = car[1]; X oldi = 0; X globcn = 0; X nomore = 0; X argb = instring = false; X o = cmnd; X out (space); X lastbl = o; X X while (*p) switch (ch = get()) { X case '#': X i = (*p>='0' && *p<='9') ? *p++-'0' : 1; X out (arg); X out (i); X argb = true; X if (i>oldi) oldi = i; X if (glob==lastbl) X error("Globals can't mix with arguments"); X continue; X case '~': X *--p = '/'; X case '*': X if (unsign(o[-1]) == space && *p=='/') { X x = ++p; X while (get()!='/'); X *--p = 0; X x = getpath(getunum(x)); X *p = '/'; X if (*x==0) X error("User does not exist"); X while (*x) X put (*x++); X } X else seen (any); X continue; X case '?': X seen (one); X continue; X case '[': X seen (range); X do out(ch = get()); X while (ch != ']'); X continue; X case '\t': X case ' ': X if (unsign(o[-1]) != space) X out (space); X nomore = glob; X lastbl = o; X argb = false; X continue; X case '"': X while ((ch = get()) != '"') X put (ch); X continue; X case '\'': X while ((ch = get()) != '\'') X put (ch); X continue; X default: X put (ch); X continue; X X } X X out (space); X out (0); X X if (oldi>globcn) X error ("Unmatched argument"); X y = 0; X if (glob && unsign(*glob) == chars) X for (x = glob; unsign(*x) != chend;) X if (*x++ == '/') X y = x; X if (y) { X ch = *y; X *y = 0; X#ifdef NDIR X if ((dhan = opendir(glob+1)) == NULL) X#else X if ((han = open(glob+1, 0)) < 0) X#endif X error("Non-existent directory"); X *y = ch; X *--y = chars; X } X else { X#ifdef NDIR X dhan = opendir("."); X if (dhan ==NULL) X#else X han = open(".", 0); X if (han < 0) X#endif X error("Can't open directory"); X y = glob; X } X X if (glob == 0) X error("No globals"); X X i = 0; X ep = 0; X#ifdef NDIR X while (dp = readdir(dhan)) { X if (*dp->d_name == '.' && y[1] != '.') X continue; X if (ep) X free(ep); X ep = (struct entry *)ealloc(dp->d_namlen+sizeof (struct entry)); X ep->e_namlen = dp->d_namlen; X strcpy(ep->e_name, dp->d_name); X#else X while (read(han, &oldi, 2) == 2) { X if (ep == 0) X ep = (struct entry *)ealloc(sizeof (struct entry)); X if (read(han, ep->e_name, NAMSIZ) != NAMSIZ) X break; X if (oldi == 0) X continue; X if (*ep->e_name == '.' && y[1] != '.') X continue; X ep->e_name[NAMSIZ] = 0; X ep->e_namlen = NAMSIZ; X#endif X if (globbr(ep,y)) { X ++i; X ep->e_link = ehead; X ehead = ep; X ep = 0; X } X } X#ifdef NDIR X closedir(dhan); X#else X close(han); X#endif X if (ep) X free(ep); X if (i==0) X error ("No matches"); X X /* X * Sort the entries - user friendly ? X */ X et = ea = (struct entry **) ealloc(i * sizeof (struct entry *)); X for (ep = ehead; ep; ep = ep->e_link) X *ea++ = ep; X qsort(et, i, sizeof (struct entry *), entcmp); X X ea = et; X do { X ep = *ea++; X gp = 0; X p = cmnd; X o = output; X while (ch = *p++) switch (unsign(ch)) { X X case space: X if (o!=output) *o++ = ' '; X if (glob==p) { X gp = o; /* save ptr for stat */ X if (glob!=y) { X for (x = glob+1; unsign(*x) != chars;) X *o++ = *x++; X *o++ = '/'; X } X x = ep->e_name; X while (*x) X *o++ = *x++; X while (*--o==' '); X ++o; X *o++ = ' '; X gep = o-1; /* save ptr for stat */ X while (unsign(*p++) != space); X } X continue; X case chars: X while (unsign((ch = *p++)) != chend) X *o++ = ch; X continue; X case arg: X ch = *p++ - 1; X if (ch < 0) { X /* #0 requests name of owner */ X if (gp == 0) X error("#0 before global"); X *gep = 0; X stat(gp,&sbuf); X *gep = ' '; X for (x = getlogn(sbuf.st_uid);*o++ = *x++;); X --o; X continue; X } X ch *= 2; X oldi = ep->e_level[ch+1]; X for (x = ep->e_name+ep->e_level[ch]; --oldi>=0;) X *o++ = *x++; X continue; X X } X *o++ = '\n'; X *o = 0; X printf("%s", output); X } X while (--i); X} X Xentcmp(e1, e2) Xregister struct entry **e1, **e2; X{ X return strcmp((*e1)->e_name, (*e2)->e_name); X} X X#include <pwd.h> X Xchar *getpath(uid) Xint uid; X{ X register struct passwd *p; X X p = getpwuid(uid); X if (p == NULL) X return ""; X return p->pw_dir; X} X Xint getunum(name) Xchar *name; X{ X register struct passwd *p; X X p = getpwnam(name); X if (p == NULL) X return -1; X return p->pw_uid; X} X Xchar *getlogn(uid) Xint uid; X{ X register struct passwd *p; X X p = getpwuid(uid); X if (p == NULL) X return ""; X return p->pw_name; X} X Xchar *ealloc(n) Xint n; X{ X register char *p; X X p = (char *)malloc(n); X if (p == NULL) X error("Can't get memory"); X return p; X} X Xerror(a, b, c) X{ X fprintf(stderr, "mkcmd: "); X fprintf(stderr, a, b, c); X fprintf(stderr, "\n"); X exit(-1); X} All work and no play makes Jack a dull boy