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