home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_300 / 329_01 / paste.c < prev    next >
C/C++ Source or Header  |  1988-12-16  |  14KB  |  578 lines

  1. /*-
  2.  * paste - a recreation of the Unix(Tm) paste(1) command.
  3.  *
  4.  * syntax:    paste file1 file2 ...
  5.  *        paste -dLIST file1 file2 ...
  6.  *        paste -s [-dLIST] file1 file2 ...
  7.  *
  8.  *    Copyright (C) 1984 by David M. Ihnat
  9.  *
  10.  * This program is a total rewrite of the Bell Laboratories Unix(Tm)
  11.  * command of the same name, as of System V.  It contains no proprietary
  12.  * code, and therefore may be used without violation of any proprietary
  13.  * agreements whatsoever.  However, you will notice that the program is
  14.  * copyrighted by me.  This is to assure the program does *not* fall
  15.  * into the public domain.  Thus, I may specify just what I am now:
  16.  * This program may be freely copied and distributed, provided this notice
  17.  * remains; it may not be sold for profit without express written consent of
  18.  * the author.
  19.  * Please note that I recreated the behavior of the Unix(Tm) 'paste' command
  20.  * as faithfully as possible, with minor exceptions (noted below); however,
  21.  * I haven't run a full set of regression * tests.  Thus, the user of
  22.  * this program accepts full responsibility for any effects or loss;
  23.  * in particular, the author is not responsible for any losses,
  24.  * explicit or incidental, that may be incurred through use of this program.
  25.  *
  26.  * The changes to the program, with one exception, are transparent to
  27.  * a user familiar with the Unix command of the same name.  These changes
  28.  * are:
  29.  *
  30.  * 1) The '-s' option had a bug in the Unix version when used with multiple
  31.  *    files.  (It would repeat each file in a list, i.e., for
  32.  *    'paste -s file1 file2 file3', it would list
  33.  *    <file1\n><file1\n><file2\n><file1\n><file2\n><file3\n>
  34.  *    I fixed this, and reported the bug to the providers of the command in
  35.  *    Unix.
  36.  *
  37.  * 2) The list of valid escape sequences has been expanded to include
  38.  *    \b,\f,\r, and \v.  (Just because *I* can't imagine why you'd want
  39.  *    to use them doesn't mean I should keep them from you.)
  40.  *
  41.  * 3) There is no longer any restriction on line length.
  42.  *
  43.  * I ask that any bugs (and, if possible, fixes) be reported to me when
  44.  * possible.  -David Ihnat (312) 784-4544 ihuxx!ignatz
  45. **
  46. **    Roberto Artigas Jr
  47. **    P.O. Box 281415
  48. **    Memphis, TN 38168-1415
  49. **    work: 901-762-6092
  50. **    home: 901-373-4738
  51. **
  52. **    1988.12.08 - Changes to get this to work under OS/2
  53. **        Used C/2 version 1.10 under OS/2 E 1.1
  54. **        Commands where:
  55. **        cl -c -AL paste.c
  56. **        link /st:4000 paste,/noi,paste,llibce+os2, ;
  57. **
  58. **    Worked out includes
  59. **    Fixed command line processing problem
  60. **    Fixed delimiter string definition problem
  61. **    Added delimiter string size checking
  62. **
  63. **+
  64. */
  65. #define    CPM    0
  66. #define    DOS    0
  67. #define    OS2    1
  68.  
  69. #include <stdio.h>
  70.  
  71. #if    CPM
  72. extern int errno;
  73. #endif
  74.  
  75. #if    CPM
  76. #define    _MAXSZ        512
  77. #else
  78. #define    _MAXSZ        (BUFSIZ*4)
  79. #endif
  80.  
  81. #if    DOS || OS2
  82. #include    <ctype.h>
  83. #include    <stdlib.h>
  84. #include    <string.h>
  85. #endif
  86.  
  87. /* I'd love to use enums, but not everyone has them.  Portability, y'know. */
  88. #define NODELIM        1
  89. #define USAGE        2
  90. #define BADFILE        3
  91. #define TOOMANY        4
  92. #define    DELIMTOOLONG    5
  93.  
  94. #define    TAB        '\t'
  95. #define    NL        '\n'
  96. #define    BS        '\b'
  97. #define    FF        '\f'
  98. #define    CR        '\r'
  99. #define    VT        '\v'
  100. #define    DEL        '\177'
  101.  
  102. #define _MAXFILES    12
  103. #define    CLOSED        ((FILE *)-1)
  104. #define ENDLIST        ((FILE *)-2)
  105. #define COMMAND        "paste"
  106. #define    SZDELIM        31
  107.  
  108. char *cmdnam;
  109.  
  110. short int dflag,
  111.  sflag;
  112. char delims[SZDELIM] = {TAB, '\0'};    /* Delimiter STRING!!!! */
  113.  
  114. /*
  115.  * Function prototypes 
  116.  */
  117. void prerr();
  118. void delimbuild();
  119. void docol();
  120. void doserial();
  121.  
  122.  
  123. /*-
  124. **    name:        main
  125. **    purpose:    Begin program here
  126. **+
  127. */
  128. int 
  129. main(argc, argv)
  130. int argc;
  131. char *argv[];
  132. {
  133.     char *strcpy();
  134.  
  135.     dflag = sflag = 0;
  136.  
  137. #if    CPM
  138.     cmdnam = COMMAND;
  139. #else
  140.     cmdnam = *argv;
  141. #endif
  142.  
  143.     /* Skip invocation name */
  144.     argv++;
  145.     argc--;
  146.  
  147.     if (!argc)
  148.     prerr(USAGE, "");
  149.  
  150.     /* First, parse input options */
  151.  
  152.     while (argv[0] && argv[0][0] == '-' && argv[0][1] != '\0')
  153.     {
  154.     switch (argv[0][1])
  155.     {
  156.     case 'd':
  157.     case 'D':
  158.         /* Delimiter character(s) */
  159.         if (strlen(&argv[0][2]) > SZDELIM)
  160.         prerr(DELIMTOOLONG,
  161.               (char *) &argv[0][2]);
  162.         if (*delims == '\0')
  163.         prerr(NODELIM, "");
  164.         else
  165.         {
  166.         strcpy(delims, &argv[0][2]);
  167.         delimbuild(delims);
  168.         }
  169.  
  170.         break;
  171.  
  172.     case 's':
  173.     case 'S':
  174.         sflag++;
  175.         break;
  176.  
  177.     default:
  178.         prerr(USAGE, "");
  179.     }
  180.     argv++;
  181.     argc--;
  182.     }
  183.  
  184.     /*
  185.      * If no files specified, simply exit.  Otherwise, if not the old '-s'
  186.      * option, process all files. If '-s', then process files one-at-a-time. 
  187.      */
  188.     if (!sflag)
  189.     docol(argc, argv);           /* Column paste */
  190.     else
  191.     doserial(argc, argv);           /* Serial paste */
  192.  
  193.     return (0);
  194. }
  195.  
  196. void 
  197. docol(nfiles, fnamptr)
  198. int nfiles;
  199. char **fnamptr;
  200. {
  201.     char iobuff[_MAXSZ];           /* i/o buffer for the fgets */
  202.     short int somedone;               /* flag for blank field handling */
  203.  
  204.     /*
  205.      * There is a strange case where all files are just ready to be closed,
  206.      * or will on this round.  In that case, the string of delimiters must
  207.      * be preserved.  delbuf[1] ->delbuf[MAXFILES+1] provides intermediate
  208.      * storage for closed files, if needed; delbuf[0] is the current index. 
  209.      */
  210.     char delbuf[_MAXFILES + 2];
  211.  
  212.     FILE *fileptr[_MAXFILES + 1];
  213.     FILE *fopen();
  214.  
  215.     char *fgets();
  216.  
  217.     int filecnt;               /* Set to number of files to process */
  218.     register char *delimptr;           /* Cycling delimiter pointer */
  219.     int index;                   /* Working variable */
  220.     int strend;                   /* End of string in buffer */
  221.  
  222.     /*
  223.      * Perform column paste.  First, attempt to open all files. (This could
  224.      * be expanded to an infinite number of files, but at the (considerable)
  225.      * expense of remembering the file and its current offset, then
  226.      * opening/reading/closing.  The commands' utility doesn't warrant the
  227.      * effort; at least, to me...) 
  228.      */
  229.  
  230.     for (filecnt = 0; (nfiles > 0) && (filecnt < _MAXFILES); filecnt++, nfiles--, fnamptr++)
  231.     {
  232.     if (fnamptr[0][0] == '-')
  233.         fileptr[filecnt] = stdin;
  234.     else
  235.     {
  236. #if    DOS || OS2
  237.         fileptr[filecnt] = fopen(*fnamptr, "rb");
  238. #else
  239.         fileptr[filecnt] = fopen(*fnamptr, "r");
  240. #endif
  241.         if (fileptr[filecnt] == NULL)
  242.         prerr(BADFILE, *fnamptr);
  243.     }
  244.     }
  245.  
  246.     fileptr[filecnt] = ENDLIST;           /* End of list. */
  247.  
  248.     if (nfiles)
  249.     prerr(TOOMANY, "");
  250.  
  251.     /*
  252.      * Have all files.  Now, read a line from each file, and output to
  253.      * stdout.  Notice that the old 511 character limitation on the line
  254.      * length no longer applies, since this program doesn't do the
  255.      * buffering.  Do this until you go through the loop and don't
  256.      * successfully read from any of the files. 
  257.      */
  258.     for (; filecnt;)
  259.     {
  260.     somedone = 0;               /* Blank field handling flag */
  261.     delimptr = delims;           /* Start at beginning of delim list */
  262.     delbuf[0] = 0;               /* No squirreled delims */
  263.  
  264.     for (index = 0; (fileptr[index] != ENDLIST) && filecnt; index++)
  265.     {
  266.         /*
  267.          * Read a line and immediately output. If it's too big for the
  268.          * buffer, then dump what was read and go back for more. 
  269.          *
  270.          * Otherwise, if it is from the last file, then leave the carriage
  271.          * return in place; if not, replace with a delimiter (if any) 
  272.          */
  273.  
  274.         strend = 0;               /* Set so can easily detect EOF */
  275.  
  276.         if (fileptr[index] != CLOSED)
  277.  
  278.         while (fgets(iobuff, (_MAXSZ - 1), fileptr[index]) != (char *) NULL)
  279.         {
  280.             strend = strlen(iobuff);    /* Did the buffer fill? */
  281.  
  282.             if (strend == (_MAXSZ - 1))
  283.             {
  284.             /* Gosh, what a long line. */
  285.             fputs(iobuff, stdout);
  286.             strend = 0;
  287.             continue;
  288.             }
  289.             /* Ok got whole line in buffer. */
  290.             break;           /* Out of loop for this file */
  291.         }
  292.  
  293.         /*
  294.          * Ended either on an EOF (well, actually NULL return-- it
  295.          * *could* be some sort of file error, but but if the file was
  296.          * opened successfully, this is unlikely. Besides, error
  297.          * checking on streams doesn't allow us to decide exactly what
  298.          * went wrong, so I'm going to be very Unix-like and ignore
  299.          * it!), or a closed file, or a received line. If an EOF, close
  300.          * the file and mark it in the list.  In any case, output the
  301.          * delimiter of choice. 
  302.          */
  303.  
  304.         if (!strend)
  305.         {
  306.         if (fileptr[index] != CLOSED)
  307.         {
  308.             fclose(fileptr[index]);
  309.             fileptr[index] = CLOSED;
  310.             filecnt--;
  311.         }
  312.         /* Is this the end of the whole thing? */
  313.         if ((fileptr[index + 1] == ENDLIST) && !somedone)
  314.             continue;           /* EXITS */
  315.  
  316.         /* Ok, some files not closed this line. Last file? */
  317.         if (fileptr[index + 1] == ENDLIST)
  318.         {
  319.             if (delbuf[0])
  320.             {
  321.             fputs(&delbuf[1], stdout);
  322.             delbuf[0] = 0;
  323.             }
  324.             putc((int) NL, stdout);
  325.             continue;           /* Next read of files */
  326.         } else
  327.         {
  328.             /* Closed file; setup delim */
  329.             if (*delimptr != DEL)
  330.             {
  331.             delbuf[0]++;
  332.             delbuf[delbuf[0]] = *delimptr++;
  333.             delbuf[delbuf[0] + 1] = '\0';
  334.             } else
  335.             delimptr++;
  336.         }
  337.  
  338.         /* Reset end of delimiter string if necessary */
  339.         if (*delimptr == '\0')
  340.             delimptr = delims;
  341.         } else
  342.         {
  343.         /* Some data read. */
  344.         somedone++;
  345.  
  346.         /* Any saved delims? */
  347.         if (delbuf[0])
  348.         {
  349.             fputs(&delbuf[1], stdout);
  350.             delbuf[0] = 0;
  351.         }
  352.         /* If last file, last char will be NL. */
  353.         if (fileptr[index + 1] != ENDLIST)
  354.         {
  355.             if (*delimptr == DEL)
  356.             {
  357.             delimptr++;
  358.             iobuff[strend - 1] = '\0';    /* No delim */
  359.             } else
  360.             iobuff[strend - 1] = *delimptr++;
  361.         }
  362.         if (*delimptr == '\0')
  363.             delimptr = delims;
  364.  
  365.         /* Now dump the buffer */
  366.         fputs(iobuff, stdout);
  367.         }
  368.     }
  369.     }
  370. }
  371.  
  372. void 
  373. doserial(nfiles, fnamptr)
  374. int nfiles;
  375. char **fnamptr;
  376. {
  377.     /*
  378.      * Do serial paste.  Simply scarf characters, performing one-character
  379.      * buffering to facilitate delim processing. 
  380.      */
  381.  
  382.     register int charnew,
  383.      charold;
  384.     register char *delimptr;
  385.  
  386.     register FILE *fileptr;
  387.     FILE *fopen();
  388.  
  389.     for (; nfiles; nfiles--, fnamptr++)
  390.     {
  391.     if (fnamptr[0][0] == '-')
  392.         fileptr = stdin;
  393.     else
  394.     {
  395. #if    DOS || OS2
  396.         fileptr = fopen(*fnamptr, "rb");
  397. #else
  398.         fileptr = fopen(*fnamptr, "r");
  399. #endif
  400.  
  401.         if (fileptr == NULL)
  402.         prerr(BADFILE, *fnamptr);
  403.     }
  404.  
  405.     /*
  406.      * The file is open; just keep taking characters, stashing them in
  407.      * charnew; output charold, converting to the appropriate delimiter
  408.      * character if needful.  After the EOF, simply output 'charold' if
  409.      * it's a newline; otherwise, output it and then a newline. 
  410.      */
  411.  
  412.     delimptr = delims;           /* Set up for delimiter string */
  413.  
  414.     if ((charold = getc(fileptr)) == EOF)
  415.     {
  416.         /* Empty file! */
  417.         putc(NL, stdout);
  418.         continue;               /* Go on to the next file */
  419.     }
  420.     /* Ok, 'charold' is set up.  Hit it! */
  421.  
  422.     while ((charnew = getc(fileptr)) != EOF)
  423.     {
  424.         /* Ok, process the old character */
  425.         if (charold == NL)
  426.         {
  427.         if (*delimptr != DEL)
  428.             putc(*delimptr++, stdout);
  429.  
  430.         /* Reset pointer at end of delimiter string */
  431.         if (*delimptr == '\0')
  432.             delimptr = delims;
  433.         } else
  434.         putc((char) charold, stdout);
  435.  
  436.         charold = charnew;
  437.     }
  438.  
  439.     /* Ok, hit EOF.  Process that last character */
  440.  
  441.     putc((char) charold, stdout);
  442.  
  443.     if ((char) charold != NL)
  444.         putc(NL, stdout);
  445.     }
  446. }
  447. /*-
  448. **    name:        delimbuild
  449. **    purpose:    Build a delimiter string
  450. **+
  451. */
  452. void 
  453. delimbuild(strptr)
  454. char *strptr;
  455. {
  456.     /*
  457.      * Process the delimiter string into something that can be used by the
  458.      * routines.  This involves, primarily, collapsing the backslash
  459.      * representations of special characters into their actual values, and
  460.      * terminating the string in a manner that the routines can recognize. 
  461.      * The set of possible backslash characters has been expanded beyond
  462.      * that recognized by the vanilla Unix(Tm) version. 
  463.      */
  464.     register char *strout;
  465.     register int delimleft = SZDELIM;  /* Checking delimiter size */
  466.  
  467.     strout = strptr;               /* Start at the same place, anyway */
  468.  
  469.     while (*strptr && (--delimleft))
  470.     {
  471.     if (*strptr != '\\')           /* Is it an escape character? */
  472.         *strout++ = *strptr++;     /* No, just transfer it */
  473.     else
  474.     {
  475.         strptr++;               /* Get past escape character */
  476.         switch (*strptr)
  477.         {
  478.         case '0':
  479.         *strout++ = DEL;
  480.         break;
  481.  
  482. #if    CPM || DOS || OS2
  483.         case 'T':
  484.         case 't':
  485. #else
  486.         case 't':
  487. #endif
  488.         *strout++ = TAB;
  489.         break;
  490.  
  491. #if    CPM || DOS || OS2
  492.         case 'N':
  493.         case 'n':
  494. #else
  495.         case 'n':
  496. #endif
  497.         *strout++ = NL;
  498.         break;
  499.  
  500. #if    CPM || DOS || OS2
  501.         case 'B':
  502.         case 'b':
  503. #else
  504.         case 'b':
  505. #endif
  506.         *strout++ = BS;
  507.         break;
  508.  
  509. #if    CPM || DOS || OS2
  510.         case 'F':
  511.         case 'f':
  512. #else
  513.         case 'f':
  514. #endif
  515.         *strout++ = FF;
  516.         break;
  517.  
  518. #if    CPM || DOS || OS2
  519.         case 'R':
  520.         case 'r':
  521. #else
  522.         case 'r':
  523. #endif
  524.         *strout++ = CR;
  525.         break;
  526.  
  527. #if    CPM || DOS || OS2
  528.         case 'V':
  529.         case 'v':
  530. #else
  531.         case 'v':
  532. #endif
  533.         *strout++ = VT;
  534.         break;
  535.  
  536.         default:
  537.         *strout++ = *strptr;
  538.         }
  539.  
  540.         strptr++;
  541.     }
  542.  
  543.     }
  544.     *strout = '\0';               /* Heaven forfend that we forget
  545.                         * this! */
  546. }
  547.  
  548. void 
  549. prerr(etype, estring)
  550. int etype;
  551. char *estring;
  552. {
  553.     switch (etype)
  554.     {
  555.     case USAGE:
  556.     fprintf(stderr, "%s: Usage: %s [-s] [-d<delimiterstring>] file1 file2 ...\n", cmdnam, cmdnam);
  557.     break;
  558.  
  559.     case DELIMTOOLONG:
  560.     fprintf(stderr, "%s: Delimiter (%s) too long max is %d\n",
  561.         cmdnam, estring, SZDELIM - 1);
  562.     break;
  563.  
  564.     case NODELIM:
  565.     fprintf(stderr, "%s: no delimiters\n", cmdnam);
  566.     break;
  567.  
  568.     case BADFILE:
  569.     fprintf(stderr, "%s: %s : cannot open\n", cmdnam, estring);
  570.     break;
  571.  
  572.     case TOOMANY:
  573.     fprintf(stderr, "%s: too many files\n", cmdnam);
  574.     break;
  575.     }
  576.     exit(1);
  577. }
  578.