koreth@panarthea.ebay.sun.com (Steven Grimm) (10/11/89)
Submitted-by: uunet!unido!infbs!tubsibr!hafer (Udo Hafermann) Posting-number: Volume 2, Issue 88 Archive-name: yaff Here is yet another version of an Atari ST file finder. This one is a no-frills version meant to be used from a shell (gulam, msh etc.); it will run from the desktop though. As the basic machinery was there, I went a little option-crazy and included stuff like filesize counting etc. #!/bin/sh # shar: Shell Archiver (v1.22) # # Run the following text with /bin/sh to create: # FF.C # FF.MAN # MAKEFILE # sed 's/^X//' << 'SHAR_EOF' > FF.C && X X X/* X * Searches recursively for all directory entries matching a given X * specification beginning at the directory specified. X * See ff.man for details. X * X * For ST-TOS, MWC 3.05. X * History: 17-SEP-87 X * 06-JUL-89 regexp as filespec; -x option X * 24-JUL-89 file sizes X */ X X#include <stdio.h> X#include <osbind.h> X#include <bios.h> X#include <ctype.h> X X/* requirements: */ X X#define PATHLEN 256 /* max. length of paths we can manage */ X /* (more than GEMDOS can, hopefully) */ X#define BUFLEN 1000 /* string buffer size */ X#define CLUSTERSIZE 1024 /* cluster size assumed for -k */ X#define STACKSIZE 10000 /* recursive search might need it */ X X/* file types: */ X X#define FWPROT (0x01) /* write protected */ X#define FHIDE (0x02) /* hidden */ X#define FSYS (0x04) /* system file */ X#define FVOL (0x08) /* volume label */ X#define FSUB (0x10) /* subdirectory */ X#define FARCH (0x20) /* archive bit */ X X/* return codes: */ X X#define E_OK (0L) /* no error */ X#define E_FILNF (-33L) /* file not found */ X#define E_PTHNF (-34L) /* path not found */ X#define E_NMFIL (-49L) /* no more files */ X X/* check macro for "." and ".." file entries: */ X#define NOTDOT(fname) (fname[0]!='.' || (fname[1]!='\0' && fname[1]!='.')) X X/* stack request: */ Xlong _stksize = STACKSIZE; X X/* variables: */ X Xstatic char *whoami = "ff"; /* if non-empty, will point to argv[0] */ Xstatic char *olddta; /* dta backup (do we really need that?) */ X Xstatic char filespec[PATHLEN]; X /* wildcard-spec of filenames */ X /* to be searched for */ Xstatic char path[PATHLEN], /* path of directory */ X /* currently being searched */ X drivespec[BUFSIZ], X /* drive specification part of filespec */ X buf1[BUFSIZ], X *xflag; /* pointer to "-x cmd", if not null */ X Xstatic int cflag, /* if true, -c flag */ X qflag, /* if true, -q flag */ X vflag, /* ... and so on ... */ X hflag, X sflag, X dflag, X tflag, X kflag, X count, /* # matches so far */ X ftypes; /* attribute bits */ X Xstatic long total; /* accumulated size */ X Xunsigned char fattset, /* if non-zero, these atts must be set */ X fattunset = FSUB; X /* these atts must be unset */ X Xstatic terminate(status) Xint status; X{ X /* terminate program returning status */ X Fsetdta(olddta); /* is this necessary? */ X if (hflag) { X fprintf(stderr, "Type <Return>: "); X getchar(); X } X exit(status); X} X Xstatic info(full) Xint full; X{ X /* usage info, short or full */ X fprintf((full ? stdout : stderr), X "Usage: %s [-?] [-hcqvsktd] [-aatts] [-iatts] [-x command] 'filespec'\n", X whoami); X if (full) { X printf("\tPrints all filenames matching 'filespec', recursively searching\n"); X printf("\tsubdirectories from the one specified (default: current).\n"); X printf("\tWildcards: * ? []\n"); X printf("Flags:\n"); X printf("\t-h\tWait before terminating\n"); X printf("\t-c\tPrint match count\n"); X printf("\t-q\tQuiet: print no filenames\n"); X printf("\t-v\tVerbose: list directories searched (on stderr)\n"); X printf("\t-s\tList file sizes\n"); X printf("\t-d\tList subtotals for subdirectories\n"); X printf("\t-t\tSuppress total size\n"); X printf("\t-k\tList file sizes, rounded to multiples of %d\n", X CLUSTERSIZE); X printf("\t-aatts\tOnly list files with these attributes\n"); X printf("\t-iatts\tIgnore files with these attributes (default: d)\n"); X printf("\t-x cmd\tExecute command, where %%%% is replaced by filename\n"); X printf("Exit status: The match count\n"); X } X terminate (-1); X} X Xstatic unsigned char parseatts (cp) Xchar *cp; X{ X /* cp points to string of letters r,h,s,d, or m. X Returns bitmap of attributes. */ X unsigned char atts = 0; X char c; X X while (c = *cp++) { X if (c=='r') X atts |= FWPROT; X else if (c=='h') X atts |= FHIDE; X else if (c=='s') X atts |= FSYS; X else if (c=='v') X atts |= FVOL; X else if (c=='d') X atts |= FSUB; X else if (c=='m') X atts |= FARCH; X } X return atts; X} X Xstatic void str_tolower(s) Xchar *s; X{ X /* convert s to lower case */ X while (*s) { X (*s) = tolower(*s); X s++; X } X} X Xstatic void str_repl (target, source, s1, s2) Xchar *target, *source, *s1, *s2; X{ X /* copies source into target, replacing each occurrence X of s1 with s2 */ X unsigned int s1len = strlen(s1), s2len = strlen(s2); X X while (*source) { X if (0 == strncmp(source, s1, s1len)) { X /* substitute s2 for s1: */ X strcpy (target, s2); X target += s2len; X source += s1len; X } else { X *target++ = *source++; X } X } X *target = '\0'; X} X Xmain(argc, argv) Xint argc; Xchar *argv[]; X{ X olddta = (char *)Fgetdta(); /* restored by terminate() */ X X /* First, save program name for messages: */ X if (argv[0][0]) X whoami = argv[0]; X X /* Process options: */ X while (--argc) { X char c, X *cp; X X cp = *++argv; X if (cp[0] != '-') { X /* not an option: must be first filespec */ X break; X } X if (strlen(cp) < 2) { X /* invalid option */ X info(0); X } X /* process these options: */ X str_tolower(cp); X while (c = *++cp) { X if (c == 'c') X cflag = -1; X else if (c=='h') X hflag = -1; X else if (c=='q') X qflag = -1; X else if (c=='v') X vflag = -1; X else if (c=='s') X sflag = -1; X else if (c=='t') X tflag = -1; X else if (c=='k') X kflag = sflag = -1; X else if (c=='d') X dflag = -1; X else if (c=='x') { X xflag = *++argv; X argc--; X } else if (c=='a') { X /* set attributes */ X fattset = parseatts (++cp); X break; X } else if (c=='i') { X /* unset atts */ X fattunset = 0; X fattunset = parseatts (++cp); X break; X } else { X /* unknown option (possibly '?') */ X info(c=='?'); X } X } X } X X /* set up file types for fsnext: */ X ftypes = FSUB | FHIDE | FWPROT | FSYS | FARCH; X X /* apply principle of least surprise (we ignore subdirs by X default, remember): */ X if (fattset & FSUB) X fattunset &= ~FSUB; X X /* now process the filespecs: */ X if (!argc) { X info(0); /* we must have at least one... */ X } X X while (argc--) { X char *cp, *dp, *ep; /* quite a lot, eh? */ X /* but wait and see... */ X int have_drive; X X /* parse the argument into: X filespec - argument for pnmatch X drivespec - drive portion (possibly empty) X path - absolute part of path (first subdir to search) X */ X X drivespec[0] = 0; X cp = *argv; /* argument pointer */ X dp = path; /* path construction pointer */ X ep = path; /* current end of absolute path */ X have_drive = 0; /* true when drive spec is passed */ X do { X *dp++ = tolower(*cp); X if (*cp == '\\') { X /* drivespec is complete: */ X have_drive = 1; X /* mark position: */ X ep = dp; X } else if (*cp == ':' && !have_drive) { X /* end of real drive spec! */ X /* copy it: */ X *(dp-1) = 0; X strcpy (drivespec, path); X have_drive = 1; X /* intialize path appropriately: */ X dp = path; X *dp++ = 'x'; *dp++ = ':'; X ep = dp; X } X } while (*cp++); X /* copy end of path to filespec: */ X strcpy (filespec, ep); X /* and terminate path after last backslash: */ X *ep = 0; X X if (drivespec[0]) { X /* drive specified; we must try all: */ X char drive; X long drivemap; X char *hugo = "x"; X X drivemap = Drvmap(); X for (drive='a'; drive<='p'; drive++) { X if (drivemap & 0x1) { X path[0] = hugo[0] = drive; X if (pnmatch(hugo, drivespec, 0)) X recursive(*argv); X } X drivemap >>= 1; X } X } else { X recursive(*argv); X } X argv++; X } X X /* Report results: */ X if (cflag) { X if (vflag) X printf ("Entries: "); X printf ("%d\n", count); X } else { X if (vflag && !count) X fprintf (stderr, "%s: no match.\n", whoami); X } X if (sflag && !(tflag || dflag)) X if (vflag) X printf ("Total size: %ld\n", total); X else X printf ("%10ld\n", total); X X terminate(count); X} X Xstatic recursive (arg) Xchar *arg; /* the original search argument (for verbose mode) */ X{ X /* search for files matching filespec in current path and subdirs */ X struct Buff { X unsigned char reserved[21]; X unsigned char type; X unsigned short time; X unsigned short date; X unsigned long size; X unsigned char name[14]; X } buffer; /* dta buffer for this path */ X int pp, /* path marker for backtracking */ X status; /* GEMDOS return code */ X long oldtotal = total; X /* so that we can work out subtotal */ X X /* first, search current dir: */ X if (vflag) X fprintf (stderr, X "%s: Searching for '%s' in '%s'\n", whoami, arg, path); X Fsetdta(&buffer); X pp = strlen(path); X strcat (path, "*.*"); X status = Fsfirst(path, ftypes); X while (status == E_OK) { X /* does this entry match? */ X str_tolower(buffer.name); X if (pnmatch(buffer.name, filespec, 0) X && (!fattset || fattset == (fattset & buffer.type)) X && (! (fattunset & buffer.type)) X && NOTDOT (buffer.name)) { X path[pp] = '\0'; X strcat(path, buffer.name); X if (sflag || dflag) { X long s = buffer.size; X if (kflag) X s = CLUSTERSIZE * X ((s+CLUSTERSIZE-1)/CLUSTERSIZE); X if (!qflag) X printf("%10ld ", s); X total += s; X } X if (!qflag) { X printf("%s\n", path); X } X count++; X if (xflag) { X str_repl (buf1, xflag, "%%", path); X system (buf1); X } X path[pp] = '\0'; X strcat(path, "*.*"); X } X status = Fsnext(); X } X path[pp] = '\0'; X /* check return code for unusual stuff: */ X if (status != E_FILNF && status != E_NMFIL) { X /* unexpected return code! */ X fprintf (stderr, X "%s '%s': Oops - unexpected error code: %d\n", X whoami, path, status); X terminate(status); X } X /* Now search all subdirs: */ X strcat(path, "*.*"); X status = Fsfirst(path, FSUB|FHIDE|FSYS|FWPROT|FARCH); X path[pp] = '\0'; X while (status==E_OK) { X if (buffer.type&FSUB && NOTDOT(buffer.name)) { X str_tolower(buffer.name); X strcat (path, buffer.name); X strcat (path, "\\"); X recursive(arg); X path[pp] = '\0'; X Fsetdta(&buffer); X } X status = Fsnext(); X } X if (dflag) X printf ("%10ld %s\n", total-oldtotal, path); X} SHAR_EOF chmod 0600 FF.C || echo "restore of FF.C fails" sed 's/^X//' << 'SHAR_EOF' > FF.MAN && X XFF (1) ATARI ST UTILITIES FF (1) X X XNAME X ff - _f_i_l_e_f_i_n_d: recursive search for files X XSYNOPSIS X X ff [-cqvsktd] [-aatts] [-iatts] [-x command] 'filespec' X XDESCRIPTION X X ff takes everything up to the last backslash as the directory to X start searching (default is the current directory). The rest of X filespec is the pattern. ff will list all files (with their complete X paths) whose last names match this pattern, recursively scanning all X subdirectories. filespec may begin with a drive specification X (ending with a colon; a backslash should follow), which is matched X against all drives present. X X ff uses the MWC routine pnmatch() for the pattern match; thus, X permissible wildcards are * (matches any sequence of characters), X ? (matches any one character), and character lists enclosed in X brackets ([]). X X Flags: X -c _C_o_u_n_t: print number of files found. X -q _Q_u_i_e_t: print no filenames or extra messages. X -v _V_e_r_b_o_s_e: list directories searched. X -aatts Include only files with these _a_t_t_r_i_b_u_t_e_s X (r,h,s,d,m for readonly, hidden, system, subdir, X modified flag, respectively). X -iatts _I_g_n_o_r_e files with these attributes (default: d). X -s _S_i_z_e: list file sizes and total. X -t Suppress _t_o_t_a_l. X -k _k_B_y_t_e: round sizes to multiples of 1024 (implies -s). X On systems with a cluster size of 1 kByte, this should X report the space actually used by the files. X -d List subtotal for each _s_u_b_d_i_r_e_c_t_o_r_y (implies -s). X -x cmd For each match, _e_x_e_c_u_t_e command cmd X using msh, where %% is replaced with the filename. X X Exit status: The number of matching filenames found, or a negative X error number. X XEXAMPLES X X ff 'hugo' find files named hugo, beginning at the current X directory. X ff -c '*' find all files, beginning at the current directory, X reporting the number of files found. X ff '?:\*.c' find all C source files on all existing drives. X ff -am '[cd]:\hugo\*' find all files, beginning at the X subdirectories "c:\hugo" and "d:\hugo", which have X the "modified" attribute set. X XRESTRICTIONS X X - For the -x option, msh.prg must be in the search path. X - The files are listed in the order in which they appear in the X directory (i.e., not sorted in any way). X - As the backslash is the directory seperator, there is no escape X character available for the wildcard characters. X XNOTES X X - The filespec must be enclosed in quotes if the shell used X would otherwise expand the wildcards. Contemplate the X difference between "ff *.c" and "ff '*.c'" under gulam (or msh). X - Arguments are forced to lower case (for use from the desktop under X old TOS versions). X - "ff -qkd 'dir\*'" is roughly equivalent to "du dir". X - Very convenient under msh using backquotes: X rm `ff '*'` is equivalent to UNIX rm -r, with the X exception that subdirectories are not deleted, and that the msh X cannot process an unlimited number of arguments that way. X XWARNINGS X X As ff may traverse quite a few directories, you might run into X the _4_0_-_f_o_l_d_e_r_-_b_u_g (discussed at length elsewhere). X XTODO X - There should be support for matching against the file timestamps. X - ff should, instead of just calling system(), use the gulam shell X interface if present. X - There should be a replacement for the pnmatch() escape character. X XFILES X ff.ttp X XAUTHOR X X hafer@tubsibr july 1989 X SHAR_EOF chmod 0600 FF.MAN || echo "restore of FF.MAN fails" sed 's/^X//' << 'SHAR_EOF' > MAKEFILE && X Xff.ttp: ff.c X cc -s -o ff.ttp ff.c X X SHAR_EOF chmod 0600 MAKEFILE || echo "restore of MAKEFILE fails" exit 0