xref: /illumos-gate/usr/src/cmd/nl/nl.c (revision 6a7605d4240442195b2a8f79d02a64359f74257e)
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
main(int argc,char * argv[])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 				/* FALLTHROUGH */
505 			default:
506 				optmsg(argv[j]);
507 			}
508 		} else if ((iptr = fopen(argv[j], "r")) == NULL)  {
509 			/* end of options, filename starting with '-' */
510 			(void) fprintf(stderr, "nl: %s: ", argv[j]);
511 			perror("");
512 			return (1);
513 		}
514 	} else if ((iptr = fopen(argv[j], "r")) == NULL)  {
515 		/* filename starting with char other than '-' */
516 		(void) fprintf(stderr, "nl: %s: ", argv[j]);
517 		perror("");
518 		return (1);
519 	}
520 } /* closing brace of for loop */
521 /* end Solaris version of argument parsing */
522 #endif
523 
524 	/* ON FIRST PASS ONLY, SET LINE COUNTER (cnt) = startcnt & */
525 	/* SET DEFAULT BODY TYPE TO NUMBER ALL LINES.	*/
526 	if (pass1) {
527 		cnt = startcnt;
528 		type = body;
529 		last = 'b';
530 		pass1 = 0;
531 	}
532 
533 /*
534  *		DO WHILE THERE IS INPUT
535  *		CHECK TO SEE IF LINE IS NUMBERED,
536  *		IF SO, CALCULATE NUM, PRINT NUM,
537  *		THEN OUTPUT SEPERATOR CHAR AND LINE
538  */
539 
540 	while ((p = fgets(line, sizeof (line), iptr)) != NULL) {
541 	if (p[0] == delim1 && p[1] == delim2) {
542 		if (p[2] == delim1 &&
543 		    p[3] == delim2 &&
544 		    p[4] == delim1 &&
545 		    p[5] == delim2 &&
546 		    p[6] == '\n') {
547 			if (cntck != 'y')
548 				cnt = startcnt;
549 			type = header;
550 			last = 'h';
551 			swtch = 'y';
552 		} else {
553 			if (p[2] == delim1 && p[3] == delim2 && p[4] == '\n') {
554 				if (cntck != 'y' && last != 'h')
555 					cnt = startcnt;
556 				type = body;
557 				last = 'b';
558 				swtch = 'y';
559 			} else {
560 				if (p[0] == delim1 && p[1] == delim2 &&
561 							p[2] == '\n') {
562 					if (cntck != 'y' && last == 'f')
563 						cnt = startcnt;
564 					type = footer;
565 					last = 'f';
566 					swtch = 'y';
567 				}
568 			}
569 		}
570 	}
571 	if (p[0] != '\n') {
572 		lnt = strlen(p);
573 		if (p[lnt-1] == '\n')
574 			p[lnt-1] = '\0';
575 	}
576 
577 	if (swtch == 'y') {
578 		swtch = 'n';
579 		(void) fprintf(optr, "\n");
580 	} else {
581 		switch (type) {
582 		case 'n':
583 			npad(width, sep);
584 			break;
585 		case 't':
586 			/*
587 			 * XPG4: The wording of Spec 1170 is misleading;
588 			 * the official interpretation is to number all
589 			 * non-empty lines, ie: the Solaris code has not
590 			 * been changed.
591 			 */
592 			if (p[0] != '\n') {
593 				pnum(cnt, sep);
594 				cnt += increment;
595 			} else {
596 				npad(width, sep);
597 			}
598 			break;
599 		case 'a':
600 			if (p[0] == '\n') {
601 				blankctr++;
602 				if (blank == blankctr) {
603 					blankctr = 0;
604 					pnum(cnt, sep);
605 					cnt += increment;
606 				} else
607 					npad(width, sep);
608 			} else {
609 				blankctr = 0;
610 				pnum(cnt, sep);
611 				cnt += increment;
612 			}
613 			break;
614 		case 'b':
615 			if (step(p, bexpbuf)) {
616 				pnum(cnt, sep);
617 				cnt += increment;
618 			} else {
619 				npad(width, sep);
620 			}
621 			break;
622 		case 'h':
623 			if (step(p, hexpbuf)) {
624 				pnum(cnt, sep);
625 				cnt += increment;
626 			} else {
627 				npad(width, sep);
628 			}
629 			break;
630 		case 'f':
631 			if (step(p, fexpbuf)) {
632 				pnum(cnt, sep);
633 				cnt += increment;
634 			} else {
635 				npad(width, sep);
636 			}
637 			break;
638 		}
639 		if (p[0] != '\n')
640 			p[lnt-1] = '\n';
641 		(void) fprintf(optr, "%s", line);
642 
643 	}	/* Closing brace of "else" */
644 	}	/* Closing brace of "while". */
645 	(void) fclose(iptr);
646 
647 	return (0);
648 }
649 
650 /*		REGEXP ERR ROUTINE		*/
651 
652 static void
regerr(int c)653 regerr(int c)
654 {
655 	(void) fprintf(stderr, gettext(
656 	    "nl: invalid regular expression: error code %d\n"), c);
657 	exit(1);
658 }
659 
660 /*		CALCULATE NUMBER ROUTINE	*/
661 
662 static void
pnum(int n,char * sep)663 pnum(int n, char *sep)
664 {
665 	register int	i;
666 
667 	if (format == 'z') {
668 		pad = '0';
669 	}
670 	for (i = 0; i < width; i++)
671 		nbuf[i] = pad;
672 	num(n, width - 1);
673 	if (format == 'l') {
674 		while (nbuf[0] == ' ') {
675 			for (i = 0; i < width; i++)
676 				nbuf[i] = nbuf[i+1];
677 			nbuf[width-1] = ' ';
678 		}
679 	}
680 	(void) printf("%s%s", nbuf, sep);
681 }
682 
683 /*		IF NUM > 10, THEN USE THIS CALCULATE ROUTINE		*/
684 
685 static void
num(int v,int p)686 num(int v, int p)
687 {
688 	if (v < 10)
689 		nbuf[p] = v + '0';
690 	else {
691 		nbuf[p] = (v % 10) + '0';
692 		if (p > 0)
693 			num(v / 10, p - 1);
694 	}
695 }
696 
697 /*		CONVERT ARG STRINGS TO STRING ARRAYS	*/
698 
699 #ifdef XPG4
700 static int
convert(int c,char * option_arg)701 convert(int c, char *option_arg)
702 {
703 	s = option_arg;
704 	q = r = 0;
705 	while (s[q] != '\0') {
706 		if (s[q] >= '0' && s[q] <= '9') {
707 			s1[r] = s[q];
708 			r++;
709 			q++;
710 		} else
711 			optmsg(c, option_arg);
712 	}
713 	s1[r] = '\0';
714 	k = atoi(s1);
715 	return (k);
716 }
717 #else
718 /* Solaris version */
719 static int
convert(char * argv)720 convert(char *argv)
721 {
722 	s = (char *)argv;
723 	q = 2;
724 	r = 0;
725 	while (s[q] != '\0') {
726 		if (s[q] >= '0' && s[q] <= '9') {
727 			s1[r] = s[q];
728 			r++;
729 			q++;
730 		} else {
731 			optmsg(argv);
732 		}
733 	}
734 	s1[r] = '\0';
735 	k = atoi(s1);
736 	return (k);
737 }
738 #endif
739 
740 /*		CALCULATE NUM/TEXT SEPRATOR		*/
741 
742 static void
npad(int width,char * sep)743 npad(int width, char *sep)
744 {
745 	register int i;
746 
747 	pad = ' ';
748 	for (i = 0; i < width; i++)
749 		nbuf[i] = pad;
750 	(void) printf("%s", nbuf);
751 
752 	for (i = 0; i < (int)strlen(sep); i++)
753 		(void) printf(" ");
754 }
755 
756 #ifdef XPG4
757 static void
optmsg(int option,char * option_arg)758 optmsg(int option, char *option_arg)
759 {
760 	if (option_arg != (char *)NULL) {
761 		(void) fprintf(stderr, gettext(
762 		    "nl: invalid option (-%c %s)\n"), option, option_arg);
763 	}
764 	/* else getopt() will print illegal option message */
765 	usage();
766 }
767 #else
768 /* Solaris version */
769 static void
optmsg(char * option)770 optmsg(char *option)
771 {
772 	(void) fprintf(stderr, gettext("nl: invalid option (%s)\n"), option);
773 	usage();
774 }
775 #endif
776 
777 void
usage()778 usage()
779 {
780 	(void) fprintf(stderr, gettext(USAGE));
781 	exit(1);
782 }
783