FSearch 
A CLI command for searching and listing files 

Download source 

This is the source for FSearch which is a small pure CLI command for searching and listing files. 
The command is pretty fast (about 10 times faster than c:search), can be made resident, 
uses very little memory and has a lot of options which greatly reduce the scope of the search.
It is compiled with SAS C 6.51 with the NOSTARTUP option. 

Todo: 

- FSearch uses a very simple search algorithm. 
  I tried the Boyer-Moore algorithm  but it was too much of a bother to add it in and the speed 
  increase was something like half a percent which is not worth it. 
- I would also like to add asynchronous I/O to speed up the reading of files. I tried the 
  asynclib routines available in aminet but couldn't get them to work properly for some reason. 
  Maybe I didn't try hard enough.. 

Anyway, if someone adds these things successfuly, please send the result to me. 
 

Template: 

The command has the following template: 

ROOT/A/M        Where to start searching from. 
                May be multiple root sources - with wild-cards 
                ex : FSearch ram: dh0:mydir#? all 
                List all files in all dirs in ram: and all 
                files in all dirs matching dh0:mydir#? 

                This option must be given.  
                All others are optional.  

PAT/K           (string) File pattern to match 
                Only files meeting this pattern will be searched 
                ex : FSearch ram: pat=#?.info all 
                List all info files in ram: or it's sub-dirs 

TXT/K           (string) Text to search for. 
                If this option is not specified FSearch will act 
                like "list" and will not search the files. 
                ex : FSearch ram: txt="my name" all 
                Note: By default the search is case-insensitive. 

FROM/K          (date) Only search files FROM this date and up. 
                Dates must be written as dd-mm-yy. 
                ex : FSearch ram: from=07-08-96 

TO/K            (date) Only search files UP to this date 

MIN/N/K         (number) Only search files more than MIN Kb. 
                ex : FSearch ram: min=100 (look for files >= 100k) 

MAX/N/K         (number) Only search files less than MAX Kb. 

HEADER/K        (string) Only search files which have this header. 
                The Header specification may contain wild cards. 
                The first 100 bytes of the file will be loaded 
                and checked against this Header. 
                ex : FSearch ram: header=FORM????ILBM#? all 
                will list all iff pictures in ram: 

ALL/S           Recursively scan sub-directories 

CS=CASESENSITIVE/S  
                Make the search case-sensitive 

V=VERBOSE/S     Print Line number & text of lines found. 
                The default is to just print the file name on the 
                first occurance of the TXT you're looking for. 
                With this option all the matches and their  
                context will be displayed. If the line length is  
                over 80 chars it will be clipped. 

NOBIN/S         Do not check binary files. Up to 100 bytes of the 
                file will be loaded and file will be skipped if 
                this header contains any non-ascii characters. 

HL=HIGHLIGHT/S  Highlight the search results 

INFO/S          Print file size/date/time next to it's name 
                No file paths are printed if this option is on. 

NOPATH/S        Print only the file name - not the full path. 
 

The Source: 

/*---------------------------------------------------------------------------*/ 
/* Command : fsearch.c                                                       */ 
/*           fast, pure CLI command for listing/searching files.             */ 
/*                                                                           */ 
/* Author  : Dimitri Keletsekis                                              */ 
/*           Parts of the recursive dir scanning and argument parsing        */ 
/*           used in here are based on the cat example in the SAS C pack,    */ 
/*           but have been altered as needed.                                */ 
/*           The program is just one function with no global variables       */ 
/*           so its pure and can be made resident. Also, its compiled with   */ 
/*           the NOSTARTUP code option making the binary very small.         */ 
/*                                                                           */ 
/*           To compile (with SAS C v6.50+), place this file and the         */  
/*           SCOPTIONS file in a dir, open a shell, CD to that dir and       */ 
/*           write "sc fsearch link"                                         */ 
/*                                                                           */ 
/* Date    : Athens, 7 September 97                                          */ 
/*                                                                           */ 
/*---------------------------------------------------------------------------*/ 

#define __USE_SYSBASE 
#include <exec/types.h> 
#include <exec/execbase.h> 
#include <exec/memory.h> 
#include <dos/dosextens.h> 
#include <dos/rdargs.h> 
#include <string.h> 
#include <proto/dos.h> 
#include <proto/exec.h> 

#define THISPROC   ((struct Process *)(SysBase->ThisTask)) 
#define Result2(x) THISPROC->pr_Result2 = x 

static const char VERSION[] = "\0$VER: FSearch 1.1"; 

// The program's options 

#define TEMPLATE  "ROOT/A/M,PAT/K,TXT/K,FROM/K,TO/K,MIN/N/K,MAX/N/K,HEADER/K,ALL/S,CS=CASESENSITIVE/S,V=VERBOSE/S,NOBIN/S,HL=HIGHLIGHT/S,NOPATH/S,INFO/S" 
#define OPT_ROOT   0    //A/M,  multiple, wildcards - dir(s) to search 
#define OPT_PAT    1    //K,    file pattern to search - ex - PAT=#?.h 
#define OPT_TXT    2    //K,    text to search for - ex - TXT="some string" 
#define OPT_FROM   3    //K,    search files over this date - FROM=25/3/96 
#define OPT_TO     4    //K,    search files up to this date 
#define OPT_MIN    5    //N/K,  search files of this minimum size (in Kb) 
#define OPT_MAX    6    //N/K,    search files of maximum this size 
#define OPT_HEADER 7    //K,    search files starting with this header 
#define OPT_ALL    8    //S,    recurse sub-directories 
#define OPT_CS     9    //=CASESENSITIVE/S,     search case sensitive 
#define OPT_V      10   //=VERBOSE/S,           print text found 
#define OPT_NOBIN  11   //S,    do not search binary files 
#define OPT_HL     12   //S,    Highlight filename & linenumbers 
#define OPT_NOPATH 13   //S,    do not print the file's full path 
#define OPT_INFO   14   //S,    print size/date etc next to file name 

#define OPT_COUNT  15   // the number of options 

// ------- escape sequences for changing color of text output 

#define SET_WHITE  Write (outpt,"›32m", 4) 
#define SET_BLUE   Write (outpt,"›33m", 4) 
#define SET_BLACK  Write (outpt,"›31m", 4) 

// ------------------------ Structure definition --------------------- 

// Altered AnchorPath structure with enlarged ap_Buf for storing 
// the full path & file name - set ap_Strlen to indicate it. 

struct MyAnchorPath { 
        struct AChain   *ap_Base;       /* pointer to first anchor */ 
#define ap_First ap_Base 
        struct AChain   *ap_Last;       /* pointer to last anchor */ 
#define ap_Current ap_Last 
        LONG    ap_BreakBits;   /* Bits we want to break on */ 
        LONG    ap_FoundBreak;  /* Bits we broke on. Also returns ERROR_BREAK */ 
        BYTE    ap_Flags;       /* New use for extra word. */ 
        BYTE    ap_Reserved; 
        WORD    ap_Strlen;      /* This is what ap_Length used to be */ 
#define ap_Length ap_Flags      /* Old compatability for LONGWORD ap_Length */ 
        struct  FileInfoBlock ap_Info; 
        UBYTE   ap_Buf[150];    /* Buffer for path name, allocated by user */ 
}; 

// ------------------------- Program start ---------------------------- 

int cmd_fsearch(void) 
{ 
struct ExecBase *SysBase = (*((struct ExecBase **) 4)); 
struct DosLibrary *DOSBase; 
long temprc, rc, hdlength; 
long buffsize, actual, line; 
long opts[OPT_COUNT]; 
struct RDArgs *rdargs; 
struct MyAnchorPath __aligned ua; 
struct FileInfoBlock *fib; 
BPTR oldlock, curlock, lk, fh=NULL, outpt; 
char *fname; 
char root[150], *rtpt;  // buffer for root 
char head[120];         // buffer to store header 
char patbuff[512];      // buffer for pattern matching 
char hdbuff[512];       // buffer for header matching 
char *str, upstr[256];  // buffer for other case string 
char linebuff[30];      // buffer for line number convertion 
char info[100];         // buffer for file/dir info 
char outbuff[100];      // buffer for possible split line output 
char *p, *d, *u, *sl, *ssl; 
char *el, *endbuff, *t; 
char *curarg, **argptr, *buff=NULL; 
char *pat, *hd; 
struct DateTime __aligned dt;  // for FORM/TO 
struct DateTime __aligned dd;  // for info 
int c, strline, linelength; 
BOOL flag, printfile; 

// the default return code 
rc = RETURN_FAIL; 

// clear the locks we'll use so we know if we used them 
oldlock = NULL; 
curlock = NULL; 

// setup the DateTime structure flag for dd-mm-yy 
memset(&dt, 0, sizeof(struct DateTime)); 
dt.dat_Format = FORMAT_CDN; 

// open dos lib or die.. 
if (!(DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 36L))) 
{  Result2(ERROR_INVALID_RESIDENT_LIBRARY); 
   return (20); 
} 

// get our output (pointer to the console or redirected file) 
outpt = Output(); 

// ------------ clear options array & parse command line 

memset((char *)opts, 0, sizeof(opts)); 
rdargs = ReadArgs(TEMPLATE, opts, NULL); 

// if there was a parser error - print slogan etc and skip to end 
if (rdargs == NULL) 
{   Write (outpt, "›32mby D.Keletsekis\n›33mdck@hol.gr›31m\n", 39); 
    PrintFault(IoErr(), NULL); 
    goto endprog; 
} 

// ------------ ok, now set up the CLI arguments 

// point to first (of possibly multiple) source args, 
// which are set up as an array of pointers to chars 
argptr = (char **)opts[OPT_ROOT]; 

// setup ParsePattern for PAT and HEADER 
if (pat = (char *)opts[OPT_PAT]) 
{   if ((ParsePatternNoCase (pat, patbuff, 500)) < 1) 
    {   Write (outpt, "Error parsing file pattern\n", 27); 
        goto endprog; 
    } 
} 
if (hd = (char *)opts[OPT_HEADER]) 
{   if ((ParsePatternNoCase (hd, hdbuff, 500)) < 1) 
    {   Write (outpt, "Error parsing header\n", 27); 
        goto endprog; 
    } 
} 

// allow / an . as date separators 
if (opts[OPT_FROM]) 
{   for (p = (char *)opts[OPT_FROM]; *p; ++p) 
    {   if ((*p=='/')||(*p=='.'))  
             *p = '-';  
}   } 
if (opts[OPT_TO]) 
{   for (p = (char *)opts[OPT_TO]; *p; ++p) 
    {   if ((*p=='/')||(*p=='.'))  
             *p = '-';  
}   } 

// setup comparisson & read buffer for searching 
if (opts[OPT_TXT]) 
{ 
    // get copy buffer - try 32k.. 8k 
    for (c = 32, flag = 0; c > 2 && !flag;) 
    {   buffsize = c * 1024; 
        if (buff = (char *)AllocVec(buffsize+16, MEMF_ANY))  
              ++flag; 
        else  c-=5; 
    } 
    if (!buff) 
    {   Write (outpt, "No memory!\n", 11); 
        goto endprog; 
    } 

    str = (char *)opts[OPT_TXT]; 
    strncpy (upstr, str, 250); 
    upstr[250] = 0; 

    // count new lines in search string (if any) 
    for (p = str, strline = 0; *p; ++p) 
         if (*p == 10) ++strline; 

    // if not case sensitive make upstr oposite case to str 
    // we can not use isupper() for some reason - because no startup code? 
    if (opts[OPT_CS] == NULL) 
    {   u = upstr; 
        while (*u) 
        {  if ((*u >= 'A') && (*u < 'a'))  *u = ((*u) + ('a' - 'A')); 
           else if ((*u >= 'a') && (*u <= 'z')) *u = ((*u) - ('a' - 'A')); 
           ++u; 
        } 
    } 
} 

// -------------- LOOP FOR ALL "ROOT" ARGUMENTS ---------- 

while (curarg = *argptr++) 
{ 
   // clear the anchorpath structure to 0's 
   memset(&ua, 0, sizeof(struct MyAnchorPath)); 
   // indicate - put full path in ap_Buf which is 150 bytes 
   ua.ap_Strlen = 150; 
   // set up what to print in case of error 
   strcpy(ua.ap_Buf, "FSearch failed :(\n"); 

   // append #? to the root if all is not declared 
   if (opts[OPT_ALL]) 
       rtpt = curarg; 
   else 
   {   strcpy (root, curarg); 
       p = &root[strlen(root)-1]; 
       if ((*p=='/') || (*p==':')) 
           strcat (root, "#?"); 
       else if (*p!='?') 
           strcat (root, "/#?"); 
       rtpt = root; 
   } 

   // call the matcher.. (0=success) 
   MatchFirst(rtpt, (struct AnchorPath *)&ua); 
   temprc = IoErr(); 

   // ---------------- LOOP FOR ALL MATCHES -------------------- 

   while (temprc == 0) 
   { 
      // check for control-C this way since we use nostartup 
      if (SetSignal(0, 0) & SIGBREAKF_CTRL_C) 
      {   temprc = ERROR_BREAK; 
          goto enderror; 
      } 

      // ------ if it's a directory and the ALL option is on.. 
      if (ua.ap_Info.fib_DirEntryType > 0 && opts[OPT_ALL]) 
      { 
         // if the backing-out-of-dir flag is not set... 
         if (!(ua.ap_Flags & APF_DIDDIR)) 
         { 
            // tell matcher to enter this sub-dir. 
            ua.ap_Flags |= APF_DODIR; 
         } 
         // and clear the flag 
         ua.ap_Flags &= ~APF_DIDDIR; 
      } 

      // ------ if dir changed - cd to it 
      if (ua.ap_Flags & APF_DirChanged) 
      { 
          if (curlock) 
              UnLock (curlock); 
          curlock = DupLock(ua.ap_Current->an_Lock); 
          lk = CurrentDir(curlock); 
          // if this is the first dir change, store the lock 
          if (!oldlock) 
              oldlock = lk; 
      } 

      // -------------- IF IT'S A FILE ------- do your stuff... 

      if (ua.ap_Info.fib_DirEntryType < 0) 
      { 
          // point to our FileInfoBlock & clear flags 
          fib = &ua.ap_Info; 
          flag = 0; 
          printfile = 0; 
          // point to file name 
          if (opts[OPT_NOPATH]) 
              fname = fib->fib_FileName; 
          else 
              fname = ua.ap_Buf; 

          // --------- check file pattern 
          if (opts[OPT_PAT]) 
          {  if(!(MatchPatternNoCase(patbuff, fib->fib_FileName))) 
                  goto skipfile; 
          } 

          // ---------- check file size (MIN/MAX) 
          if (opts[OPT_MIN]) 
          {   if ((fib->fib_Size / 1024) < (*((LONG *)opts[OPT_MIN]))) 
                  goto skipfile; 
          } 
          if (opts[OPT_MAX]) 
          {   if ((fib->fib_Size / 1024) > (*((LONG *)opts[OPT_MAX]))) 
                  goto skipfile; 
          } 

          // --------- check dates 
          if (opts[OPT_FROM]) 
          {   dt.dat_StrDate = (char *)opts[OPT_FROM]; 
              if (StrToDate(&dt)) 
              {   if ((CompareDates(&dt.dat_Stamp, &fib->fib_Date)) <= 0) 
                      goto skipfile; 
              } 
              // error in date format.. 
              else 
              {   Write (outpt, "FROM error\n", 11); 
                  goto enderror; 
              } 
          } 
          if (opts[OPT_TO]) 
          {   dt.dat_StrDate = (char *)opts[OPT_TO]; 
              if (StrToDate(&dt)) 
              {   if ((CompareDates(&dt.dat_Stamp, &fib->fib_Date)) >= 0) 
                      goto skipfile; 
              } 
              else 
              {   Write (outpt, "TO error\n", 9);  
                  goto enderror; 
              } 
          } 

          // ------------ HEADER or NOBIN options 
          if (opts[OPT_HEADER] || opts[OPT_NOBIN]) 
          {   hdlength = 0; 
              // open & read file 
              if (fh = Open(fib->fib_FileName, MODE_OLDFILE)) 
              {   hdlength = Read(fh, head, 100); 
                  Close (fh); 
              } 
              if (hdlength == -1) 
              {   temprc = IoErr(); 
                  goto enderror; 
              } 
              else if (!hdlength) 
                  goto skipfile; 
  
              // check the options 
              head[hdlength] = 0; 
              if (opts[OPT_HEADER]) 
              {   // replace null bytes with full stops 
                  for (c=0; c < hdlength; ++c) 
                       if (!head[c]) head[c] = '.'; 
                  if(!(MatchPatternNoCase(hdbuff, head))) 
                       goto skipfile; 
              } 
              if (opts[OPT_NOBIN]) 
              {   p = head; 
                  c = 0; 
                  while (c < hdlength) 
                  {   if ( ((*p < 32) || (*p > 127)) && 
                           (*p != '\n') && (*p != '\r') && (*p != '\t') ) 
                           goto skipfile; 
                      ++p; ++c; 
                  } 
              } 
          } 

          // ------------- prepare file info buffer - uses outbuff&linebuff 
          if (opts[OPT_INFO]) 
          {     memset (info, ' ', 80); 
                c = stcl_d (outbuff, fib->fib_Size); 
                strcpy (&info[12-c], outbuff); 
                strcat (info, " "); 
                dd.dat_Stamp   = fib->fib_Date; 
                dd.dat_StrDate = outbuff; 
                dd.dat_StrTime = linebuff; 
                DateToStr (&dd); 
                strcat (info, outbuff); 
                strcat (info, " "); 
                strcat (info, linebuff); 
          } 

          // ------------- Search the file  
          if (opts[OPT_TXT]) 
          { 
               // open the file 
               if ((fh = Open(fib->fib_FileName, MODE_OLDFILE)) == NULL) 
               {  temprc = IoErr(); 
                  goto enderror; 
               } 
               // reset the line number counter & split line stuff  
               line = 1; 
               linelength = 80; 
               outbuff[0] = 0; 

               // start reading - flag will indicate if we're finished 
               flag = 0; 
               while (!flag) 
               {  
                   actual = Read (fh, buff, buffsize); 
                   // read error 
                   if (actual == -1) 
                   {   temprc = IoErr(); 
                       Close (fh); 
                       goto enderror; 
                   } 
                   // file is empty 
                   else if (!actual) 
                   {   Close (fh); 
                       goto skipfile; 
                   } 
                   // file end 
                   else if (actual < buffsize) 
                       ++flag; 

                   p  = buff; // our main working pointer 
                   sl = p;  // start of line pointer 
                   endbuff = &buff[actual]; // ptr to end of buffer 

                   // ----- start comparisson 
                   while (p < endbuff) 
                   { 
                       // match first letter 
                       if ((*p == *str) || (*p == *upstr)) 
                       {   t = p; 
                           d = str; 
                           u = upstr; 

                           // match rest of txt string - read buffer if string is 
                           // between buffers 
                           while ((*d) && (*t) && ((*d == *t) || (*u == *t)))  
                           {    ++t; ++d; ++u; 
                                // read in more text if needed 
                                if ((t >= endbuff) && !flag) 
                                {   // copy part of previous buff 
                                    c = endbuff - sl; 
                                    if (c > 40) 
                                    {   sl = endbuff - 40; 
                                        c = 40; 
                                    } 
                                    strncpy (outbuff, sl, c); 
                                    outbuff[c] = 0; 
                                    // read in more stuff 
                                    if ((actual = Read (fh, buff, buffsize)) == -1) 
                                    {   temprc = IoErr(); 
                                        Close (fh); 
                                        goto enderror; 
                                    } 
                                    // file end 
                                    else if (actual < buffsize) 
                                        ++flag; 
                                    t = buff; 
                                    p = buff; 
                                    sl = buff; 
                                    endbuff = &buff[actual]; 
                                } 
                            } 

                           // ------ found match ! 
                           if (*d == 0)  
                           {   --t; 
                               p = t; 
                               // if not already printed - print file name 
                               if (!printfile) 
                               { 
                                   if (opts[OPT_HL]) SET_WHITE; 
                                   // if info - print name & info in head buffer 
                                   if (opts[OPT_INFO]) 
                                   {   memset(head, ' ', 110); 
                                       strcpy (head, fib->fib_FileName); 
                                       head[strlen(head)] = ' '; 
                                       strcpy (&head[35], info); 
                                       Write (outpt, head, strlen(head)); 
                                   } 
                                   else 
                                       Write(outpt, fname, strlen(fname)); 
                                   if (opts[OPT_HL]) SET_BLACK; 
                                   Write(outpt, "\n", 1); 
                                   printfile = 1; 
                                } 

                                // if verbose, print the line context 
                                if (opts[OPT_V]) 
                                { 
                                    linebuff[0] = ' '; 
                                    stcl_d (&linebuff[1], line); 
                                    strcat (linebuff, ": "); 
                                    if (opts[OPT_HL]) SET_BLUE; 
                                    Write (outpt, linebuff, strlen(linebuff)); 
                                    if (opts[OPT_HL]) SET_BLACK; 

                                    // go to line start 
                                    ssl = sl; ++ssl; 
                                    for (el=ssl, c=0; (*el) && (*el!=10) && (el < endbuff); 
                                         ++el, ++c); 
                                    // adjust start of printing 
                                    if ((sl == buff) && (*sl != '\n'))  
                                        --ssl; 

                                    // if we had split line... 
                                    if (outbuff[0]) 
                                    {  
                                        Write (outpt, outbuff, strlen(outbuff)); 
                                        linelength -= (strlen(outbuff)); 
                                    } 
                                    // or if line is over 80 chars 
                                    else if ((p - ssl) > 80) 
                                        ssl = p - 79; 
                                    if (c < linelength) 
                                        Write (outpt, ssl, c); 
                                    else 
                                        Write (outpt, ssl, linelength); 
                                    Write (outpt, "\n", 1); 

                                    line += strline; // add any lines the string had in it 
                                    p = --el;        // go to end of shown line 
                                } 
                                // if no verbose we're finished with this file 
                                else 
                                {    Close (fh); 
                                     goto skipfile; 
                                } 
                                linelength = 80; 
                                outbuff[0] = 0; 
                           } 
                           else 
                               if ((!*p) || (*p == 10)) { ++line; sl=p; } 
                       } 
                       else 
                           if ((!*p) || (*p == 10)) { ++line; sl=p; } 

                       ++p; 
                   }   // end of compare loop 

               }   // end of read loop 

               // close the file 
               Close (fh); 
          } // end of dealing with file for opts[OPT_TXT] 

          // ------- if no txt option was declared, we list the file 
          else  
          {   if (opts[OPT_INFO]) 
              {   memset(head, ' ', 110); 
                  strcpy (head, fib->fib_FileName); 
                  head[strlen(head)] = ' '; 
                  strcpy (&head[35], info); 
                  Write (outpt, head, strlen(head)); 
              } 
              else 
              {   Write(outpt, fname, strlen(fname)); 
              } 
              Write(outpt, "\n", 1); 
          } 

      } // end of IF IT'S A FILE 

      // point at which we goto if file does not meet our requirement 
      skipfile: 

      // match the next file 
      MatchNext((struct AnchorPath *)&ua); 
      temprc = IoErr(); 
   } 

   // ------------------- END OF MATCH LOOP --------------------- 

   // tell the matcher we've finished 
   MatchEnd((struct AnchorPath *)&ua); 
} 

// ------------- END OF LOOP FOR ALL "ROOT" ARGS ------------- 

// check if we had any errors 
enderror: 

if (temprc) 
{ 
    // if we ran out of files, we set error code to OK - i.e. 0 
    if (temprc == ERROR_NO_MORE_ENTRIES) 
        rc = RETURN_OK; 
    else 
    {  
       // if the user hit control-C, set WARN return code (5) 
       if (temprc == ERROR_BREAK) 
          rc = RETURN_WARN; 
       else 
       {  Write (outpt, "FSearch Error", 13); 
          rc = RETURN_FAIL; 
       } 
       // there was a reportable error - so print it 
       PrintFault(temprc, fname); 
    } 
} 

// ------------------------- end - clean up -------------------- 

endprog: 

// change back to our original dir.. 
if (oldlock) 
    CurrentDir(oldlock); 
// and unlock the current lock we have (if any) 
if (curlock) 
    UnLock(curlock); 

if (rdargs) FreeArgs(rdargs); 
if (buff)   FreeVec(buff); 
CloseLibrary((struct Library *)DOSBase); 
return(rc); 
}