/* * SYNOPSIS * hc [-s] [+tab | -col | -l str | filename] ... * * DESCRIPTION * * hc concatenates files horizontally. It concatenates correspon- * ding lines from files. The effects of hc are best visualized by * imagining the files to be printed on long strips of paper with * Velcro strips on the left and right margins; hc sticks these * files together to make one wide file on the standard output. * * Besides file names, the command line can contain tab positions, * column numbers, and literal strings. A maximum of thirteen files * can be specified. * * a TAB POSITION is denoted by a positive decimal number. It * specifies that the next item should be placed at a particular * column. The first column number is one. For example, a tab * position of five would cause the next item to be placed at column * 5 in the output. If the last argument is a tab position, then * each line of output will contain enough trailing spaces to bring * it to that column. * * a START POSITION is denoted by a negative decimal. It specifies * the left margin of the next file to be concatenated. The first * column is number one, and normally the left margin number is one. * For a start column number greater than one, the first (col-1) * characters of each line of the next file are discarded. * * a LITERAL STRING is preceded by a '-l' flag. It may be surrounded * by double quotes (under DOS, at least). It specifies characters * to be inserted in every output line at its relative position in * the argument list. In an extension to the Knowlogy HC, this * version interprets the standard C backslashed escape codes. The * following command interleaves lines from the two files, separa- * ting them by a '\n' or newline: * hc file1 -l "\n" file2 * * sjw - for sun use form -l "nnnnnn" * sjw example: hc A +7 -l " & " -16 B +15 * take the first 6 columns of file A (start next item in * column +7), attach a & with a space on either side (columns * 1 - 9 are now full), start at column 16 in file B and add * trailing blanks beginning in column 15 in the hc output file * (i.e. columns 10 - 14 are taken from columns 16 - 20 in file B) * * * Of necessity, all tabs are expanded to spaces upon input, so that * columns can be determined. On output, spaces are compressed back * to tabs, and, unless a TAB POSITION argument follows the last * file name, trailing spaces are removed from each line. * * All files but the longest are conceptually padded at the bottom * with zero-length lines to bring all files to bring to the same * length. That is, concatenation continues until the end of every * file is encountered. However, if there are one or more literal * strings in the argument list, at least one line of output will be * written even if all files are empty or no files are specified. * * The -s flag prevents the output from including any tabs; that is, * the normal tab compression of the output line is suppressed. * * EXAMPLES * * Assume the files A, B, and C, whose contents are as follows: * * 00000000011111111112 * 12345678901234567890 * -- this is file A -- * Little Miss Muffet * Sat on her tuffet, * Eating her curds * and whey. * Along came a spider * Who sat down * beside her, * And frightened * Miss Muffet away. * * 000000000111111111122222222223 * 123456789012345678901234567890 * -- this is file B -- * Four score and seven years * ago, our fathers brought forth * upon this continent a new * nation, conceived in liberty, * and dedicated to the * proposition that all men are * created equal. * * 0000000001 * 1234567890 * - file C - * gimme a U * gimme an N * gimme an I * gimme a C * gimme an A * whazzat * spell? * whazzat * spell? * arright! * * Example: hc A B C simply concatenate the files, making * no attempt to justify any margins. * * 000000000111111111120000000001111111111222222222230000000001 * 123456789012345678901234567890123456789012345678901234567890 * -- this is file A -- -- this is file B --- file C - * Little Miss MuffetFour score and seven yearsgimme a U * Sat on her tuffet,ago, our fathers brought forthgimme an N * Eating her curdsupon this continent a newgimme an I * and whey.nation, conceived in liberty,gimme a C * Along came a spiderand dedicated to thegimme an A * Who sat downproposition that all men arewhazzat * beside her,created equal. spell? * And frightenedwhazzat * Miss Muffet away. spell? * arright! * * Example: hc A +21 B +51 C This concatenates the three files, * with file A starting at column 1, * file B starting at column 21, and * file C starting at column 51. * * 000000000111111111120000000001111111111222222222230000000001 * 123456789012345678901234567890123456789012345678901234567890 * -- this is file A -- -- this is file B -- - file C - * Little Miss Muffet Four score and seven years gimme a U * Sat on her tuffet, ago, our fathers brought forthgimme an N * Eating her curds upon this continent a new gimme an I * and whey. nation, conceived in liberty, gimme a C * Along came a spider and dedicated to the gimme an A * Who sat down proposition that all men are whazzat * beside her, created equal. spell? * And frightened whazzat * Miss Muffet away. spell? * arright! * * Example: hc B +40 A Concatenate files B, starting at column 1, * and A, starting at column 40. The two files * are displayed side by side for comparison * viewing. * * 000000000111111111122222222223 00000000011111111112 * 123456789012345678901234567890 12345678901234567890 * -- this is file B -- -- this is file A -- * Four score and seven years Little Miss Muffet * ago, our fathers brought forth Sat on her tuffet, * upon this continent a new Eating her curds * nation, conceived in liberty, and whey. * and dedicated to the Along came a spider * proposition that all men are Who sat down * created equal. beside her, * And frightened * Miss Muffet away. * * * Example: hc -5 B +17 Chop the first 4 and last 5 columns off * of file B. * 0000011111111112 * 5678901234567890 * -- this is file * score and seven * our fathers bro * this continent * on, conceived in * dedicated to the * osition that all * ted equal. * * * Example: hc -s -5 A +10 +15 -5 C +50 Concatenate file A (starting with * its fifth column), and cut it off * 000001111 000001 when it reaches column 9 (just be- * 567890123 567890 fore column 10); then add file B * his is fi le C - (startint with its fifth column) * le Miss M e a U beginning at column 15; then pad * on her tu e an N out with spaces through column 49 * ng her cu e an I (just before column 50). Don't * d whey. e a C compress spaces to tabs. * g came a e an A * sat down zat * side her, ell? * frightene zat * ss Muffet ell? * ght! * * * Example: hc A "-----" B Concatenate files A and B, with * five dashes between them. * * 00000000011111111112-----000000000111111111122222222223 * 12345678901234567890-----123456789012345678901234567890 * -- this is file A ------- -- this is file B -- * Little Miss Muffet-----Four score and seven years * Sat on her tuffet,-----ago, our fathers brought forth * Eating her curds-----upon this continent a new * and whey.-----nation, conceived in liberty, * Along came a spider-----and dedicated to the * Who sat down-----proposition that all men are * beside her,-----created equal. * And frightened----- * Miss Muffet away.----- * * * Example: hc "| " A +24 "|| " C +38 "|" * Concatenate files A and C, * with vertical walls sur- * rounding them. * * | 00000000011111111112 || 0000000001 | * | 12345678901234567890 || 1234567890 | * | -- this is file A -- || - file C - | * | Little Miss Muffet || gimme a U | * | Sat on her tuffet, || gimme an N | * | Eating her curds || gimme an I | * | and whey. || gimme a C | * | Along came a spider || gimme an A | * | Who sat down || whazzat | * | beside her, || spell? | * | And frightened || whazzat | * | Miss Muffet away. || spell? | * | || arright! | * * * Example: ls ? | hc -s "sp " - " | pr" > tmp * * sp A | pr This example builds a shell * sp B | pr script to be run. The script * sp C | pr will run the spelling checker * sp D | pr on each file and print its * output. * * Example: hc -s input > tmp Expand tabs to spaces while * copying input to tmp. * * Example: ... | hc - > tmp Compact spaces to tabs while * copying stdin to tmp. * * From the Unica hc utility published by Knowlogy, Inc. in 1982, for * use under CP/M. As far as I know, Knowlogy no longer exists. The * above documentation is quoted nearly verbatim from the Unica manual. * My thanks to Knowlogy's people for a most useful design. * * Comments and bugs to: * Bill Cox (714)631-4452 (voice) */ #include #include #include /* #include /* for atoi */ #define TRUE 1 #define FALSE 0 #define LINELEN 256 #define LITLEN 80 #define NARGS 13 #define TABINT 8 /* tab interval, in columns */ #define FIL 0 /* remember what type arg */ #define LIT 1 /* stored in parmtype */ #define TAB 2 #define STR 3 int ch, sflag = 0, /* TRUE when "-s" flag specified */ filesact = 0, /* count of active files */ filecnt = 0, /* count of specified file names */ actarg = 0, /* actual argument-table index */ saweof[NARGS], /* TRUE when this file reaches EOF */ parmtype[NARGS], /* type of current argument */ tabposn[NARGS], /* TAB POSITION arguments here */ startcol[NARGS]; /* START COLUMN arguments here */ char literals[NARGS][LITLEN]; /* LITERAL arguments here */ FILE *infiles[NARGS]; /* FILE argument handles here */ extern void exit(); void usage(); /* forward declaration */ main(argc, argv) int argc; char *argv[]; { int argno, i, j, k; if (argc == 1) /* informational prompt */ usage(); /* * collect input line arguments, initialize */ tabposn[0] = startcol[0] = 0; for (argno = 1; argno < argc; argno++) { saweof[argno] = TRUE; startcol[argno] = 0; tabposn[argno] = 0; switch (argv[argno][0]) { /* * tab position - the next literal or input line will * be placed at this column in the output line. */ case '+': tabposn[actarg] = atoi(&argv[argno][1])-1; parmtype[actarg++] = TAB; break; case '-': switch (argv[argno][1]) { case 's': case 'S': sflag = TRUE; break; case 'l': case 'L': argno++; /* NEXT arg is literal */ if (argno >= argc) { (void)fprintf(stderr, "hc: missing literal argument\n"); exit(4); } if (strlen(argv[argno]) > LITLEN-1) { (void)fprintf(stderr, "hc: literal %d too long\n", argno-1); exit(3); } /* * copy string literal, interpreting backslash notation */ for (i = j = 0; (ch = argv[argno][j++]) != '\0';) if (ch != '\\') literals[actarg][i++] = ch; else switch (argv[argno][j++]) { case 'n': /* newline */ literals[actarg][i++] = '\n'; break; case 't': /* horizontal tab */ literals[actarg][i++] = '\t'; break; case 'v': /* vertical tab */ literals[actarg][i++] = '\v'; break; case 'b': /* backspace */ literals[actarg][i++] = '\b'; break; case 'r': /* carriage return */ literals[actarg][i++] = '\r'; break; case 'f': /* form feed */ literals[actarg][i++] = '\f'; break; case '\\': /* actual backslash */ literals[actarg][i++] = '\\'; break; case '\'': /* single quote */ literals[actarg][i++] = '\''; break; case '\"': /* double quote */ literals[actarg][i++] = '\"'; break; case '\0': /* '\' at end */ literals[actarg][i++] = '\\'; --j; /* let FOR see null */ break; default: /* not recognized */ literals[actarg][i++] = '\\'; literals[actarg][i++] = ch; break; } /* switch */ literals[actarg][i] = '\0'; parmtype[actarg++] = LIT; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* col number */ startcol[actarg] = atoi(&argv[argno][1])-1; parmtype[actarg++] = STR; break; case '\0': /* lone '-' is ref to stdin */ filesact++; filecnt++; infiles[actarg] = stdin; saweof[actarg] = FALSE; parmtype[actarg++] = FIL; break; default: (void)fprintf(stderr, "hc: unrecognized option=%s\n", argv[argno]); usage(); } /* inner switch */ break; default: /* open the file */ if (argno > NARGS) { (void)fprintf(stderr, "hc: too many file names\n"); continue; } if ((infiles[actarg] = fopen(argv[argno], "r")) != NULL) { saweof[actarg] = FALSE; filesact++; filecnt++; } else { (void)fprintf(stderr, "hc: can't access %s\n", argv[actarg]); exit(2); } actarg++; break; } /* outer switch */ } /* for */ /* * process input lines, generate output */ do { char bufin[NARGS][LINELEN], /* input file line buffers here */ intmp[LINELEN], /* buffer for tab expansion */ bufout[LINELEN]; /* output buffer */ bufout[0] = '\0'; /* ready for output */ for (argno = 0; argno < actarg; argno++) { switch (parmtype[argno]) { case TAB: /* pad/truncate bufout */ if ((j = tabposn[argno]-strlen(bufout)) < 0) bufout[tabposn[argno]] = '\0'; else for (; j > 0; j--) strcat(bufout, " "); break; case LIT: /* output the literal */ for (j = strlen(bufout), k = startcol[argno-1]; (bufout[j++] = literals[argno][k++]) != 0;); bufout[j] = '\0'; break; case FIL: /* get next line into intmp */ if (saweof[argno] == TRUE) intmp[0] = '\0'; else if (fgets(intmp, LINELEN, infiles[argno]) != NULL) if (intmp[strlen(intmp)-1] == '\n') intmp[strlen(intmp)-1] = '\0'; else { (void)fprintf(stderr, "hc: input line length > "); (void)fprintf(stderr, "%d chars, truncated.\n", LINELEN); exit(2); } else { intmp[0] = '\0'; filesact--; saweof[argno] = TRUE; } /* * expand TABs from intmp into spaces in bufin */ for (i = 0, j = 0; (ch = intmp[j++]) != '\0';) if (ch == '\t') for (k = TABINT - (i % TABINT); k-- > 0;) bufin[argno][i++] = ' '; else bufin[argno][i++] = ch; bufin[argno][i] = '\0'; /* * move bufin into bufout, if it's not null string */ if (bufin[argno][0] != '\0') { for (j = strlen(bufout), k = startcol[argno-1]; (bufout[j++] = bufin[argno][k++]) != 0;); bufout[j] = '\0'; } break; } /* switch */ } /* for */ /* * compress tabs in bufout */ if (!sflag) { int sp; strcpy(intmp, bufout); for (i = 0, j = 0, sp = 0; (ch = intmp[j++]) != '\0';) { if (ch == ' ') { sp++; if ((sp > 1) && ((j % TABINT) == 0)) { bufout[i++] = '\t'; sp = 0; } } else { while (sp > 0) { bufout[i++] = ' '; sp--; } bufout[i++] = ch; } } bufout[i] = '\0'; } /* * remove trailing blanks */ if (tabposn[actarg-1] == 0) { j = strlen(bufout); while (bufout[--j] == ' ') ; bufout[j+1] = '\0'; } /* * if there were input files specified, and all files have reached EOF, * don't bother to output this line. */ if ((filecnt != 0) && (filesact != 0)) { fputs(bufout, stdout); fputc('\n', stdout); } } while (filesact > 0); exit(0); /* successful return */ } /* hc routine */ void usage() { (void)fprintf(stderr, "usage:"); (void)fprintf(stderr, " hc [-s] [+tab | -col | -l \"str\" | filename] ...\n"); exit(2); }