xref: /illumos-gate/usr/src/cmd/nl/nl.c (revision 23c352973f956f97f817e65150aad7e1cebeb228)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 1995 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 
31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
32 
33 #include <locale.h>
34 #include <regexpr.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <wchar.h>
40 #include <wctype.h>
41 #include <limits.h>
42 
43 #define	EXPSIZ		512
44 
45 #ifdef XPG4
46 #define	USAGE "usage: nl [-p] [-b type] [-d delim] [ -f type] " \
47 	"[-h type] [-i incr] [-l num] [-n format]\n" \
48 	"[-s sep] [-v startnum] [-w width] [file]\n"
49 #else
50 #define	USAGE "usage: nl [-p] [-btype] [-ddelim] [ -ftype] " \
51 	"[-htype] [-iincr] [-lnum] [-nformat] [-ssep] " \
52 	"[-vstartnum] [-wwidth] [file]\n"
53 #endif
54 
55 #ifdef u370
56 	int nbra, sed;	/* u370 - not used in nl.c, but extern in regexp.h */
57 #endif
58 static int width = 6;	/* Declare default width of number */
59 static char nbuf[100];	/* Declare bufsize used in convert/pad/cnt routines */
60 static char *bexpbuf;	/* Declare the regexp buf */
61 static char *hexpbuf;	/* Declare the regexp buf */
62 static char *fexpbuf;	/* Declare the regexp buf */
63 static char delim1 = '\\';
64 static char delim2 = ':';	/* Default delimiters. */
65 static char pad = ' ';	/* Declare the default pad for numbers */
66 static char *s;	/* Declare the temp array for args */
67 static char s1[EXPSIZ];	/* Declare the conversion array */
68 static char format = 'n'; /* Declare the format of numbers to be rt just */
69 static int q = 2;	/* Initialize arg pointer to drop 1st 2 chars */
70 static int k;	/* Declare var for return of convert */
71 static int r;	/* Declare the arg array ptr for string args */
72 
73 #ifdef XPG4
74 static int convert(int, char *);
75 #else
76 static int convert(char *);
77 #endif
78 static void num(int, int);
79 static void npad(int, char *);
80 #ifdef XPG4
81 static void optmsg(int, char *);
82 #else
83 static void optmsg(char *);
84 #endif
85 static void pnum(int, char *);
86 static void regerr(int);
87 static void usage();
88 
89 extern char *optarg;	/* getopt support */
90 extern int optind;
91 
92 int
93 main(argc, argv)
94 int argc;
95 char *argv[];
96 {
97 	register int j;
98 	register int i = 0;
99 	register char *p;
100 	register char header = 'n';
101 	register char body = 't';
102 	register char footer = 'n';
103 	char line[LINE_MAX];
104 	char tempchr;	/* Temporary holding variable. */
105 	char swtch = 'n';
106 	char cntck = 'n';
107 	char type;
108 	int cnt;	/* line counter */
109 	int pass1 = 1;	/* First pass flag. 1=pass1, 0=additional passes. */
110 	char sep[EXPSIZ];
111 	char pat[EXPSIZ];
112 	int startcnt = 1;
113 	int increment = 1;
114 	int blank = 1;
115 	int blankctr = 0;
116 	int c;
117 	int lnt;
118 	char last;
119 	FILE *iptr = stdin;
120 	FILE *optr = stdout;
121 #ifndef XPG4
122 	int option_end = 0;
123 #endif
124 
125 	sep[0] = '\t';
126 	sep[1] = '\0';
127 
128 	(void) setlocale(LC_ALL, "");
129 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
130 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
131 #endif
132 	(void) textdomain(TEXT_DOMAIN);
133 
134 #ifdef XPG4
135 	/*
136 	 * XPG4:  Allow either a space or no space between the
137 	 *	  options and their required arguments.
138 	 */
139 
140 	while (argc > 0) {
141 		while ((c = getopt(argc, argv,
142 			"pb:d:f:h:i:l:n:s:v:w:")) != EOF) {
143 
144 			switch (c) {
145 			case 'h':
146 				switch (*optarg) {
147 				case 'n':
148 					header = 'n';
149 					break;
150 				case 't':
151 					header = 't';
152 					break;
153 				case 'a':
154 					header = 'a';
155 					break;
156 				case 'p':
157 					(void) strcpy(pat, optarg+1);
158 					header = 'h';
159 					hexpbuf =
160 					    compile(pat, (char *)0, (char *)0);
161 					if (regerrno)
162 						regerr(regerrno);
163 					break;
164 				case '\0':
165 					header = 'n';
166 					break;
167 				default:
168 					optmsg(c, optarg);
169 				}
170 				break;
171 			case 'b':
172 				switch (*optarg) {
173 				case 't':
174 					body = 't';
175 					break;
176 				case 'a':
177 					body = 'a';
178 					break;
179 				case 'n':
180 					body = 'n';
181 					break;
182 				case 'p':
183 					(void) strcpy(pat, optarg+1);
184 					body = 'b';
185 					bexpbuf =
186 					compile(pat, (char *)0, (char *)0);
187 					if (regerrno)
188 						regerr(regerrno);
189 					break;
190 				case '\0':
191 					body = 't';
192 					break;
193 				default:
194 					optmsg(c, optarg);
195 				}
196 				break;
197 			case 'f':
198 				switch (*optarg) {
199 				case 'n':
200 					footer = 'n';
201 					break;
202 				case 't':
203 					footer = 't';
204 					break;
205 				case 'a':
206 					footer = 'a';
207 					break;
208 				case 'p':
209 					(void) strcpy(pat, optarg+1);
210 					footer = 'f';
211 					fexpbuf =
212 					    compile(pat, (char *)0, (char *)0);
213 					if (regerrno)
214 						regerr(regerrno);
215 					break;
216 				case '\0':
217 					footer = 'n';
218 					break;
219 				default:
220 					optmsg(c, optarg);
221 				}
222 				break;
223 			case 'p':
224 				if (optarg == (char *)NULL)
225 					cntck = 'y';
226 				else
227 					optmsg(c, optarg);
228 				break;
229 			case 'v':
230 				if (*optarg == '\0')
231 					startcnt = 1;
232 				else
233 					startcnt = convert(c, optarg);
234 				break;
235 			case 'i':
236 				if (*optarg == '\0')
237 					increment = 1;
238 				else
239 					increment = convert(c, optarg);
240 				break;
241 			case 'w':
242 				if (*optarg == '\0')
243 					width = 6;
244 				else
245 					width = convert(c, optarg);
246 				break;
247 			case 'l':
248 				if (*optarg == '\0')
249 					blank = 1;
250 				else
251 					blank = convert(c, optarg);
252 				break;
253 			case 'n':
254 				switch (*optarg) {
255 				case 'l':
256 					if (*(optarg+1) == 'n')
257 						format = 'l';
258 					else
259 						optmsg(c, optarg);
260 					break;
261 				case 'r':
262 					if ((*(optarg+1) == 'n') ||
263 					    (*(optarg+1) == 'z'))
264 						format = *(optarg+1);
265 					else
266 						optmsg(c, optarg);
267 					break;
268 				case '\0':
269 					format = 'n';
270 					break;
271 				default:
272 					optmsg(c, optarg);
273 					break;
274 				}
275 				break;
276 			case 's':
277 				(void) strcpy(sep, optarg);
278 				break;
279 			case 'd':
280 				delim1 = *optarg;
281 
282 				if (*(optarg+1) == '\0')
283 					break;
284 				delim2 = *(optarg+1);
285 				if (*(optarg+2) != '\0')
286 					optmsg(c, optarg);
287 				break;
288 			default:
289 				optmsg(c, optarg);
290 			} /* end switch char returned from getopt() */
291 		} /* end while getopt */
292 
293 		argv += optind;
294 		argc -= optind;
295 		optind = 0;
296 
297 		if (argc > 0) {
298 			if ((iptr = fopen(argv[0], "r")) == NULL)  {
299 				(void) fprintf(stderr, "nl: %s: ", argv[0]);
300 				perror("");
301 				return (1);
302 			}
303 			++argv;
304 			--argc;
305 		}
306 	} /* end while argc > 0 */
307 /* end XPG4 version of argument parsing */
308 #else
309 /*
310  * Solaris:  For backward compatibility, do not allow a space between the
311  *	     options and their arguments.  Option arguments are optional,
312  *	     not required as in the XPG4 version of nl.
313  */
314 for (j = 1; j < argc; j++) {
315 	if (argv[j][i] == '-' && (c = argv[j][i + 1])) {
316 		if (!option_end) {
317 			switch (c) {
318 			case 'h':
319 				switch (argv[j][i + 2]) {
320 					case 'n':
321 						header = 'n';
322 						break;
323 					case 't':
324 						header = 't';
325 						break;
326 					case 'a':
327 						header = 'a';
328 						break;
329 					case 'p':
330 						s = argv[j];
331 						q = 3;
332 						r = 0;
333 						while (s[q] != '\0') {
334 							pat[r] = s[q];
335 							r++;
336 							q++;
337 						}
338 						pat[r] = '\0';
339 						header = 'h';
340 					hexpbuf =
341 					    compile(pat, (char *)0, (char *)0);
342 					if (regerrno)
343 						regerr(regerrno);
344 						break;
345 					case '\0':
346 						header = 'n';
347 						break;
348 					default:
349 						optmsg(argv[j]);
350 				}
351 				break;
352 			case 'b':
353 				switch (argv[j][i + 2]) {
354 					case 't':
355 						body = 't';
356 						break;
357 					case 'a':
358 						body = 'a';
359 						break;
360 					case 'n':
361 						body = 'n';
362 						break;
363 					case 'p':
364 						s = argv[j];
365 						q = 3;
366 						r = 0;
367 						while (s[q] != '\0') {
368 							pat[r] = s[q];
369 							r++;
370 							q++;
371 						}
372 						pat[r] = '\0';
373 						body = 'b';
374 					bexpbuf =
375 					    compile(pat, (char *)0, (char *)0);
376 					if (regerrno)
377 						regerr(regerrno);
378 						break;
379 					case '\0':
380 						body = 't';
381 						break;
382 					default:
383 						optmsg(argv[j]);
384 				}
385 				break;
386 			case 'f':
387 				switch (argv[j][i + 2]) {
388 					case 'n':
389 						footer = 'n';
390 						break;
391 					case 't':
392 						footer = 't';
393 						break;
394 					case 'a':
395 						footer = 'a';
396 						break;
397 					case 'p':
398 						s = argv[j];
399 						q = 3;
400 						r = 0;
401 						while (s[q] != '\0') {
402 							pat[r] = s[q];
403 							r++;
404 							q++;
405 						}
406 						pat[r] = '\0';
407 						footer = 'f';
408 					fexpbuf =
409 					    compile(pat, (char *)0, (char *)0);
410 					if (regerrno)
411 						regerr(regerrno);
412 						break;
413 					case '\0':
414 						footer = 'n';
415 						break;
416 					default:
417 						optmsg(argv[j]);
418 				}
419 				break;
420 			case 'p':
421 				if (argv[j][i+2] == '\0')
422 				cntck = 'y';
423 				else
424 				{
425 				optmsg(argv[j]);
426 				}
427 				break;
428 			case 'v':
429 				if (argv[j][i+2] == '\0')
430 				startcnt = 1;
431 				else
432 				startcnt = convert(argv[j]);
433 				break;
434 			case 'i':
435 				if (argv[j][i+2] == '\0')
436 				increment = 1;
437 				else
438 				increment = convert(argv[j]);
439 				break;
440 			case 'w':
441 				if (argv[j][i+2] == '\0')
442 				width = 6;
443 				else
444 				width = convert(argv[j]);
445 				break;
446 			case 'l':
447 				if (argv[j][i+2] == '\0')
448 				blank = 1;
449 				else
450 				blank = convert(argv[j]);
451 				break;
452 			case 'n':
453 				switch (argv[j][i+2]) {
454 					case 'l':
455 						if (argv[j][i+3] == 'n')
456 						format = 'l';
457 						else
458 				{
459 				optmsg(argv[j]);
460 				}
461 						break;
462 					case 'r':
463 						if ((argv[j][i+3] == 'n') ||
464 						    (argv[j][i+3] == 'z'))
465 						format = argv[j][i+3];
466 						else
467 				{
468 				optmsg(argv[j]);
469 				}
470 						break;
471 					case '\0':
472 						format = 'n';
473 						break;
474 					default:
475 				optmsg(argv[j]);
476 					break;
477 				}
478 				break;
479 			case 's':
480 				if (argv[j][i + 2] != '\0') {
481 					s = argv[j];
482 					q = 2;
483 					r = 0;
484 					while (s[q] != '\0') {
485 						sep[r] = s[q];
486 						r++;
487 						q++;
488 					}
489 					sep[r] = '\0';
490 				}
491 				/* else default sep is tab (set above) */
492 				break;
493 			case 'd':
494 				tempchr = argv[j][i+2];
495 				if (tempchr == '\0')break;
496 				delim1 = tempchr;
497 
498 				tempchr = argv[j][i+3];
499 				if (tempchr == '\0')break;
500 				delim2 = tempchr;
501 				if (argv[j][i+4] != '\0')optmsg(argv[j]);
502 				break;
503 			case '-':
504 				if (argv[j][i + 2] == '\0') {
505 					option_end = 1;
506 					break;
507 				}
508 			default:
509 				optmsg(argv[j]);
510 			}
511 		} else if ((iptr = fopen(argv[j], "r")) == NULL)  {
512 			/* end of options, filename starting with '-' */
513 			(void) fprintf(stderr, "nl: %s: ", argv[j]);
514 			perror("");
515 			return (1);
516 		}
517 	} else if ((iptr = fopen(argv[j], "r")) == NULL)  {
518 		/* filename starting with char other than '-' */
519 		(void) fprintf(stderr, "nl: %s: ", argv[j]);
520 		perror("");
521 		return (1);
522 	}
523 } /* closing brace of for loop */
524 /* end Solaris version of argument parsing */
525 #endif
526 
527 	/* ON FIRST PASS ONLY, SET LINE COUNTER (cnt) = startcnt & */
528 	/* SET DEFAULT BODY TYPE TO NUMBER ALL LINES.	*/
529 	if (pass1) {
530 		cnt = startcnt;
531 		type = body;
532 		last = 'b';
533 		pass1 = 0;
534 	}
535 
536 /*
537  *		DO WHILE THERE IS INPUT
538  *		CHECK TO SEE IF LINE IS NUMBERED,
539  *		IF SO, CALCULATE NUM, PRINT NUM,
540  *		THEN OUTPUT SEPERATOR CHAR AND LINE
541  */
542 
543 	while ((p = fgets(line, sizeof (line), iptr)) != NULL) {
544 	if (p[0] == delim1 && p[1] == delim2) {
545 		if (p[2] == delim1 &&
546 		    p[3] == delim2 &&
547 		    p[4] == delim1 &&
548 		    p[5] == delim2 &&
549 		    p[6] == '\n') {
550 			if (cntck != 'y')
551 				cnt = startcnt;
552 			type = header;
553 			last = 'h';
554 			swtch = 'y';
555 		} else {
556 			if (p[2] == delim1 && p[3] == delim2 && p[4] == '\n') {
557 				if (cntck != 'y' && last != 'h')
558 					cnt = startcnt;
559 				type = body;
560 				last = 'b';
561 				swtch = 'y';
562 			} else {
563 				if (p[0] == delim1 && p[1] == delim2 &&
564 							p[2] == '\n') {
565 					if (cntck != 'y' && last == 'f')
566 						cnt = startcnt;
567 					type = footer;
568 					last = 'f';
569 					swtch = 'y';
570 				}
571 			}
572 		}
573 	}
574 	if (p[0] != '\n') {
575 		lnt = strlen(p);
576 		if (p[lnt-1] == '\n')
577 			p[lnt-1] = NULL;
578 	}
579 
580 	if (swtch == 'y') {
581 		swtch = 'n';
582 		(void) fprintf(optr, "\n");
583 	} else {
584 		switch (type) {
585 		case 'n':
586 			npad(width, sep);
587 			break;
588 		case 't':
589 			/*
590 			 * XPG4: The wording of Spec 1170 is misleading;
591 			 * the official interpretation is to number all
592 			 * non-empty lines, ie: the Solaris code has not
593 			 * been changed.
594 			 */
595 			if (p[0] != '\n') {
596 				pnum(cnt, sep);
597 				cnt += increment;
598 			} else {
599 				npad(width, sep);
600 			}
601 			break;
602 		case 'a':
603 			if (p[0] == '\n') {
604 				blankctr++;
605 				if (blank == blankctr) {
606 					blankctr = 0;
607 					pnum(cnt, sep);
608 					cnt += increment;
609 				} else
610 					npad(width, sep);
611 			} else {
612 				blankctr = 0;
613 				pnum(cnt, sep);
614 				cnt += increment;
615 			}
616 			break;
617 		case 'b':
618 			if (step(p, bexpbuf)) {
619 				pnum(cnt, sep);
620 				cnt += increment;
621 			} else {
622 				npad(width, sep);
623 			}
624 			break;
625 		case 'h':
626 			if (step(p, hexpbuf)) {
627 				pnum(cnt, sep);
628 				cnt += increment;
629 			} else {
630 				npad(width, sep);
631 			}
632 			break;
633 		case 'f':
634 			if (step(p, fexpbuf)) {
635 				pnum(cnt, sep);
636 				cnt += increment;
637 			} else {
638 				npad(width, sep);
639 			}
640 			break;
641 		}
642 		if (p[0] != '\n')
643 			p[lnt-1] = '\n';
644 		(void) fprintf(optr, "%s", line);
645 
646 	}	/* Closing brace of "else" */
647 	}	/* Closing brace of "while". */
648 	(void) fclose(iptr);
649 
650 	return (0);
651 }
652 
653 /*		REGEXP ERR ROUTINE		*/
654 
655 static void
656 regerr(c)
657 int c;
658 {
659 	(void) fprintf(stderr, gettext(
660 		"nl: invalid regular expression: error code %d\n"), c);
661 	exit(1);
662 }
663 
664 /*		CALCULATE NUMBER ROUTINE	*/
665 
666 static void
667 pnum(n, sep)
668 int	n;
669 char *	sep;
670 {
671 	register int	i;
672 
673 	if (format == 'z') {
674 		pad = '0';
675 	}
676 	for (i = 0; i < width; i++)
677 		nbuf[i] = pad;
678 	num(n, width - 1);
679 	if (format == 'l') {
680 		while (nbuf[0] == ' ') {
681 			for (i = 0; i < width; i++)
682 				nbuf[i] = nbuf[i+1];
683 			nbuf[width-1] = ' ';
684 		}
685 	}
686 	(void) printf("%s%s", nbuf, sep);
687 }
688 
689 /*		IF NUM > 10, THEN USE THIS CALCULATE ROUTINE		*/
690 
691 static void
692 num(v, p)
693 int v, p;
694 {
695 	if (v < 10)
696 		nbuf[p] = v + '0';
697 	else {
698 		nbuf[p] = (v % 10) + '0';
699 		if (p > 0)
700 			num(v / 10, p - 1);
701 	}
702 }
703 
704 /*		CONVERT ARG STRINGS TO STRING ARRAYS	*/
705 
706 #ifdef XPG4
707 static int
708 convert(c, option_arg)
709 int c;
710 char *option_arg;
711 {
712 	s = option_arg;
713 	q = r = 0;
714 	while (s[q] != '\0') {
715 		if (s[q] >= '0' && s[q] <= '9') {
716 			s1[r] = s[q];
717 			r++;
718 			q++;
719 		} else
720 			optmsg(c, option_arg);
721 	}
722 	s1[r] = '\0';
723 	k = atoi(s1);
724 	return (k);
725 }
726 #else
727 /* Solaris version */
728 static int
729 convert(argv)
730 char *argv;
731 {
732 	s = (char *)argv;
733 	q = 2;
734 	r = 0;
735 	while (s[q] != '\0') {
736 		if (s[q] >= '0' && s[q] <= '9')
737 		{
738 		s1[r] = s[q];
739 		r++;
740 		q++;
741 		}
742 		else
743 				{
744 				optmsg(argv);
745 				}
746 	}
747 	s1[r] = '\0';
748 	k = atoi(s1);
749 	return (k);
750 }
751 #endif
752 
753 /*		CALCULATE NUM/TEXT SEPRATOR		*/
754 
755 static void
756 npad(width, sep)
757 	int	width;
758 	char *	sep;
759 {
760 	register int i;
761 
762 	pad = ' ';
763 	for (i = 0; i < width; i++)
764 		nbuf[i] = pad;
765 	(void) printf("%s", nbuf);
766 
767 	for (i = 0; i < (int) strlen(sep); i++)
768 		(void) printf(" ");
769 }
770 
771 #ifdef XPG4
772 static void
773 optmsg(option, option_arg)
774 int option;
775 char *option_arg;
776 {
777 	if (option_arg != (char *)NULL) {
778 		(void) fprintf(stderr, gettext(
779 			"nl: invalid option (-%c %s)\n"), option, option_arg);
780 	}
781 	/* else getopt() will print illegal option message */
782 	usage();
783 }
784 #else
785 /* Solaris version */
786 static void
787 optmsg(option)
788 char *option;
789 {
790 	(void) fprintf(stderr, gettext(
791 		"nl: invalid option (%s)\n"), option);
792 	usage();
793 }
794 #endif
795 
796 void
797 usage()
798 {
799 	(void) fprintf(stderr, gettext(USAGE));
800 	exit(1);
801 }
802