Listing 15 An external sort that uses an exit handler

/* vsort.c: An simple, external sort/merge
 *
 * Divides large files into sorted subfiles,
 * if necessary, then merges them.
 *
 * OPTIONS:
 *     -i      ignore case
 *     -nW     sort numbers of width W
 *     -r      reverse (descending order)
 *     -v      verbose (display progress)
 *     -N      sort field starts in column N
 *                 (1-based)
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <float.h>

#define MAXLINES 1000
   /* Watch your stack size! */
#define MAXFILES 15
#define MAXNUM DBL_DIG+9

/* Compare function for qsort */
int comp(const void *, const void *);

/* Merge file info */
struct mfile
{
   FILE *f;
   char name[L_tmpnam];
   char line[BUFSIZ];
} files[MAXFILES];

/* Option control flags */
int
   width = MAXNUM,
   fold = 0,
   numeric = 0,
   reverse = 0,
   verbose = 0,
   start_col = 0,
   nfiles = 0,

main(int argc, char **argv)
{
   int i;
   void sort(void);

   /* Process command-line options */
   for (i = 1; i <argc && *argv[i] == '-'; ++i)
   {
      char *p;

      for (p = argv[i]+1; *p; ++p)
         switch(tolower(*p))
         {
            case 'i':
               fold = 1;
               break;
            case 'n':
               numeric = 1;
               width = atoi(p+1);
               if (width <= 0)
                  width = MAXNUM;
               goto next_arg;
            case 'r':
               reverse = 1;
               break;
            case 'v':
               verbose = 1;
               break;
            default:
              if (isdigit(*p))
                 start_col =
                       max(atoi(p)-1,0);
              else
                  fprintf(stderr, "Bad option: %c\n",*p);
              goto next_arg;
         }
next_arg:
      /* Break inner loop to cycle on outer */
       ;
   }

   if (verbose)
   {
      /* Display options */
      fprintf(stderr,"Start column = %d.\n",
             start_col+1);

      if (numeric)
         fprintf(stderr,
                "Sorting numerically (width ="
                "%d).\n",width);
      else if (fold)
         fputs("Case ignored in"
              "comparisons.\n",stderr);
      if (reverse)
         fputs("Sorting in descending order.\n",stderr);
   }

   /* Redirect standard input */
   if (i < argc && freopen(argv[i],"r",stdin) == NULL)
   {
      fprintf(stderr,"Can't open %s.\n",argv[1]);
      exit(EXIT_FAILURE);
   }

   sort();
   return EXIT_SUCCESS;
}

void sort(void)
{
   static char buf[BUFSIZ], *lines[MAXLINES];
   int i, nlines, min_idx;
   int merge_flag = 0;
   void make_mfile(char **, int, int);
   void remove_temps(void);

   if (verbose)
      fputs("Reading file...\n",stderr);

   /* Register exit handler */
   atexit(remove_temps);

   /* Read input lines */
   for (nlines = 0; fgets(buf,BUFSIZ,stdin); ++nlines)
   {
      if (nlines == MAXLINES ||
         (lines[nlines] = malloc(strlen(buf)+1)) == NULL)
      {
         /* Store in temporary merge file */
         merge_flag = 1;
         make_mfile(lines,nlines,nfiles++);

         lines[nlines = 0] = malloc(strlen(buf)+1);
      }
      strcpy(lines[nlines],buf);
   }

   if (merge_flag)
   {
      /* Form last merge file from remaining lines */
      make_mfile(lines,nlines,nfiles++);

      if (verbose)
         fprintf(stderr,"Merge phase (%d runs)\n", nfiles);

      /* Open temporary files */
      for (i = 0; i < nfiles; ++i)
      {
         files[i].f = fopen(files[i].name,"r");
         if (files[i].f == NULL)
         {
            fputs("Temporary file error.\n",stderr);
            exit(EXIT_FAILURE);
         }

         /* Read first line */
         fgets(files[i].line,BUFSIZ,files[i].f);
      }

      /* Do the merge */
      while (nfiles)
      {
         /* Find next output line */
         for (min_idx = 0, i = 1; i < nfiles; ++i)
         {
            char *s1 = files[i].line;
            char *s2 = files[min_idx].line;
            if (comp(&s1,&s2) < 0)
               min_idx = i;
         }

         /* Print it */
         fputs(files[min_idx].line,stdout);

         /* Get the next line from this file */
         if (fgets(files[min_idx].line, BUFSIZ, files[min_idx].f)
            == NULL)
         {
            /* This file is finished */
            fclose(files[min_idx].f);
            if (verbose)
               fputs("Deleting temporary file\n", stderr);
            remove(files[min_idx].name);
            files[min_idx] = files[--nfiles];
         }
      }
   }
   else
   {
      /* Sort singleton file and print it*/
      if (verbose)
         fputs("Sorting...\n",stderr);
      qsort(lines,nlines,sizeof lines[0],comp);
      for (i = 0; i < nlines; ++i)
      {
         fputs(lines[i],stdout);
         free(lines[i]);
      }
   }
}

int comp(const void *xp, const void *yp)
{
   char *x = *(char **)xp, *y = *(char **)yp;
   int result;

   if (numeric)
   {
      double diff;
      char xtemp[MAXNUM+1], ytemp[MAXNUM+1];

      /* Compare numbers */
      sprintf(xtemp,"%-.*s",width,x+start_col);
      sprintf(ytemp,"%-.*s",width,y+start_col);
      diff = atof(xtemp) - atof(ytemp);
      result = (diff < 0.0) ? -1 : (diff > 0.0) ? 1 : 0;
   }
   else if (fold)
      result = stricmp(x+start_col,y+start_col);
   else
      result = strcmp(x+start_col,y+start_col);

   return reverse ? -result : result;

}

void make_mfile(char **lin, int nl, int nf)
{
   FILE *f;
   int i;

   if (nl == 0)
      return;

   /* Sort lines and store in a temporary file */
   tmpnam(files[nf].name);
   if ((f = fopen(files[nf].name,"w")) == NULL)
   {
      fputs("Can't open temporary file.\n",stderr);
      exit(EXIT_FAILURE);
   }
   qsort(lin,nl,sizeof lin[0],comp);

   if (verbose)
      fprintf(stderr, "Writing temporary file %d...\n", nf);
   for (i = 0; i < nl; ++i)
   {
      fputs(lin[i],f):
      free(lin[i]);
   }
   fclose(f);
   files[nf].f = NULL;
}

void remove_temps(void)
{
   int i;

   /* Delete stragglers at exit */
   for (i = 0; i < nfiles; ++i)
   {
      if (files[i].f) fclose(files[i].f);
      if (verbose)
         fputs("Deleting temporary file.\n", stderr);
      remove(files[i].name);
   }
}
/* End of File *