8833 libcmd: variable 'ep' set but not used
[unleashed.git] / usr / src / lib / libcmd / common / cut.c
blob6b6cabe60e92ab34b70c690b9440edc7dfadce60
1 /***********************************************************************
2 * *
3 * This software is part of the ast package *
4 * Copyright (c) 1992-2010 AT&T Intellectual Property *
5 * and is licensed under the *
6 * Common Public License, Version 1.0 *
7 * by AT&T Intellectual Property *
8 * *
9 * A copy of the License is available at *
10 * http://www.opensource.org/licenses/cpl1.0.txt *
11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
12 * *
13 * Information and Software Systems Research *
14 * AT&T Research *
15 * Florham Park NJ *
16 * *
17 * Glenn Fowler <gsf@research.att.com> *
18 * David Korn <dgk@research.att.com> *
19 * *
20 ***********************************************************************/
21 #pragma prototyped
23 * David Korn
24 * AT&T Bell Laboratories
26 * cut fields or columns from fields from a file
29 static const char usage[] =
30 "[-?\n@(#)$Id: cut (AT&T Research) 2009-12-04 $\n]"
31 USAGE_LICENSE
32 "[+NAME?cut - cut out selected columns or fields of each line of a file]"
33 "[+DESCRIPTION?\bcut\b bytes, characters, or character-delimited fields "
34 "from one or more files, contatenating them on standard output.]"
35 "[+?The option argument \alist\a is a comma-separated or blank-separated "
36 "list of positive numbers and ranges. Ranges can be of three "
37 "forms. The first is two positive integers separated by a hyphen "
38 "(\alow\a\b-\b\ahigh\a), which represents all fields from \alow\a to "
39 "\ahigh\a. The second is a positive number preceded by a hyphen "
40 "(\b-\b\ahigh\a), which represents all fields from field \b1\b to "
41 "\ahigh\a. The last is a positive number followed by a hyphen "
42 "(\alow\a\b-\b), which represents all fields from \alow\a to the "
43 "last field, inclusive. Elements in the \alist\a can be repeated, "
44 "can overlap, and can appear in any order. The order of the "
45 "output is that of the input.]"
46 "[+?One and only one of \b-b\b, \b-c\b, or \b-f\b must be specified.]"
47 "[+?If no \afile\a is given, or if the \afile\a is \b-\b, \bcut\b "
48 "cuts from standard input. The start of the file is defined "
49 "as the current offset.]"
50 "[b:bytes]:[list?\bcut\b based on a list of byte counts.]"
51 "[c:characters]:[list?\bcut\b based on a list of character counts.]"
52 "[d:delimiter]:[delim?The field character for the \b-f\b option is set "
53 "to \adelim\a. The default is the \btab\b character.]"
54 "[f:fields]:[list?\bcut\b based on fields separated by the delimiter "
55 "character specified with the \b-d\b optiion.]"
56 "[n!:split?Split multibyte characters selected by the \b-b\b option.]"
57 "[R|r:reclen]#[reclen?If \areclen\a > 0, the input will be read as fixed length "
58 "records of length \areclen\a when used with the \b-b\b or \b-c\b "
59 "option.]"
60 "[s:suppress|only-delimited?Suppress lines with no delimiter characters, "
61 "when used with the \b-f\b option. By default, lines with no "
62 "delimiters will be passsed in untouched.]"
63 "[D:line-delimeter|output-delimiter]:[ldelim?The line delimiter character for "
64 "the \b-f\b option is set to \aldelim\a. The default is the "
65 "\bnewline\b character.]"
66 "[N!:newline?Output new-lines at end of each record when used "
67 "with the \b-b\b or \b-c\b option.]"
68 "\n"
69 "\n[file ...]\n"
70 "\n"
71 "[+EXIT STATUS?]{"
72 "[+0?All files processed successfully.]"
73 "[+>0?One or more files failed to open or could not be read.]"
74 "}"
75 "[+SEE ALSO?\bpaste\b(1), \bgrep\b(1)]"
78 #include <cmd.h>
79 #include <ctype.h>
81 typedef struct Delim_s
83 char* str;
84 int len;
85 int chr;
86 } Delim_t;
88 typedef struct Cut_s
90 int mb;
91 int eob;
92 int cflag;
93 int nosplit;
94 int sflag;
95 int nlflag;
96 int reclen;
97 Delim_t wdelim;
98 Delim_t ldelim;
99 unsigned char space[UCHAR_MAX+1];
100 int list[2]; /* NOTE: must be last member */
101 } Cut_t;
103 #define HUGE INT_MAX
104 #define BLOCK 8*1024
105 #define C_BYTES 1
106 #define C_CHARS 2
107 #define C_FIELDS 4
108 #define C_SUPRESS 8
109 #define C_NOSPLIT 16
110 #define C_NONEWLINE 32
112 #define SP_LINE 1
113 #define SP_WORD 2
114 #define SP_WIDE 3
116 #define mb2wc(w,p,n) (*ast.mb_towc)(&w,(char*)p,n)
119 * compare the first of an array of integers
122 static int
123 mycomp(register const void* a, register const void* b)
125 if (*((int*)a) < *((int*)b))
126 return -1;
127 if (*((int*)a) > *((int*)b))
128 return 1;
129 return 0;
132 static Cut_t*
133 cutinit(int mode, char* str, Delim_t* wdelim, Delim_t* ldelim, size_t reclen)
135 register int* lp;
136 register int c;
137 register int n = 0;
138 register int range = 0;
139 register char* cp = str;
140 Cut_t* cut;
142 if (!(cut = (Cut_t*)stakalloc(sizeof(Cut_t) + strlen(cp) * sizeof(int))))
143 error(ERROR_exit(1), "out of space");
144 if (cut->mb = mbwide())
146 memset(cut->space, 0, sizeof(cut->space) / 2);
147 memset(cut->space + sizeof(cut->space) / 2, SP_WIDE, sizeof(cut->space) / 2);
149 else
150 memset(cut->space, 0, sizeof(cut->space));
151 cut->wdelim = *wdelim;
152 if (wdelim->len == 1)
153 cut->space[wdelim->chr] = SP_WORD;
154 cut->ldelim = *ldelim;
155 cut->eob = (ldelim->len == 1) ? ldelim->chr : 0;
156 cut->space[cut->eob] = SP_LINE;
157 cut->cflag = (mode&C_CHARS) && cut->mb;
158 cut->nosplit = (mode&(C_BYTES|C_NOSPLIT)) == (C_BYTES|C_NOSPLIT) && cut->mb;
159 cut->sflag = (mode&C_SUPRESS) != 0;
160 cut->nlflag = (mode&C_NONEWLINE) != 0;
161 cut->reclen = reclen;
162 lp = cut->list;
163 for (;;)
164 switch(c = *cp++)
166 case ' ':
167 case '\t':
168 while(*cp==' ' || *cp=='\t')
169 cp++;
170 /*FALLTHROUGH*/
171 case 0:
172 case ',':
173 if(range)
175 --range;
176 if((n = (n ? (n-range) : (HUGE-1))) < 0)
177 error(ERROR_exit(1),"invalid range for c/f option");
178 *lp++ = range;
179 *lp++ = n;
181 else
183 *lp++ = --n;
184 *lp++ = 1;
186 if(c==0)
188 register int *dp;
189 *lp = HUGE;
190 n = 1 + (lp-cut->list)/2;
191 qsort(lp=cut->list,n,2*sizeof(*lp),mycomp);
192 /* eliminate overlapping regions */
193 for(n=0,range= -2,dp=lp; *lp!=HUGE; lp+=2)
195 if(lp[0] <= range)
197 if(lp[1]==HUGE)
199 dp[-1] = HUGE;
200 break;
202 if((c = lp[0]+lp[1]-range)>0)
204 range += c;
205 dp[-1] += c;
208 else
210 range = *dp++ = lp[0];
211 if(lp[1]==HUGE)
213 *dp++ = HUGE;
214 break;
216 range += (*dp++ = lp[1]);
219 *dp = HUGE;
220 lp = cut->list;
221 /* convert ranges into gaps */
222 for(n=0; *lp!=HUGE; lp+=2)
224 c = *lp;
225 *lp -= n;
226 n = c+lp[1];
228 return cut;
230 n = range = 0;
231 break;
233 case '-':
234 if(range)
235 error(ERROR_exit(1),"bad list for c/f option");
236 range = n?n:1;
237 n = 0;
238 break;
240 default:
241 if(!isdigit(c))
242 error(ERROR_exit(1),"bad list for c/f option");
243 n = 10*n + (c-'0');
244 break;
246 /* NOTREACHED */
250 * cut each line of file <fdin> and put results to <fdout> using list <list>
253 static void
254 cutcols(Cut_t* cut, Sfio_t* fdin, Sfio_t* fdout)
256 register int c;
257 register int len;
258 register int ncol = 0;
259 register const int* lp = cut->list;
260 register char* bp;
261 register int skip; /* non-zero for don't copy */
262 int must;
263 const char* xx;
265 for (;;)
267 if (len = cut->reclen)
268 bp = sfreserve(fdin, len, -1);
269 else
270 bp = sfgetr(fdin, '\n', 0);
271 if (!bp && !(bp = sfgetr(fdin, 0, SF_LASTR)))
272 break;
273 len = sfvalue(fdin);
274 xx = 0;
275 if (!(ncol = skip = *(lp = cut->list)))
276 ncol = *++lp;
277 must = 1;
280 if (cut->nosplit)
282 register const char* s = bp;
283 register int w = len < ncol ? len : ncol;
284 register int z;
286 while (w > 0)
288 if (!(*s & 0x80))
289 z = 1;
290 else if ((z = mblen(s, w)) <= 0)
292 if (s == bp && xx)
294 w += s - xx;
295 bp = (char*)(s = xx);
296 xx = 0;
297 continue;
299 xx = s;
300 if (skip)
301 s += w;
302 w = 0;
303 break;
305 s += z;
306 w -= z;
308 c = s - bp;
309 ncol = !w && ncol >= len;
311 else if (cut->cflag)
313 register const char* s = bp;
314 register int w = len;
315 register int z;
317 while (w > 0 && ncol > 0)
319 ncol--;
320 if (!(*s & 0x80) || (z = mblen(s, w)) <= 0)
321 z = 1;
322 s += z;
323 w -= z;
326 c = s - bp;
327 ncol = !w && (ncol || !skip);
329 else
331 if ((c = ncol) > len)
332 c = len;
333 else if (c == len && !skip)
334 ncol++;
335 ncol -= c;
337 if (!skip && c)
339 if (sfwrite(fdout, (char*)bp, c) < 0)
340 return;
341 must = 0;
343 bp += c;
344 if (ncol)
345 break;
346 len -= c;
347 ncol = *++lp;
348 skip = !skip;
349 } while (ncol != HUGE);
350 if (!cut->nlflag && (skip || must || cut->reclen))
352 if (cut->ldelim.len > 1)
353 sfwrite(fdout, cut->ldelim.str, cut->ldelim.len);
354 else
355 sfputc(fdout, cut->ldelim.chr);
361 * cut each line of file <fdin> and put results to <fdout> using list <list>
362 * stream <fdin> must be line buffered
365 static void
366 cutfields(Cut_t* cut, Sfio_t* fdin, Sfio_t* fdout)
368 register unsigned char *sp = cut->space;
369 register unsigned char *cp;
370 register unsigned char *wp;
371 register int c, nfields;
372 register const int *lp = cut->list;
373 register unsigned char *copy;
374 register int nodelim, empty, inword=0;
375 register unsigned char *ep;
376 unsigned char *bp, *first;
377 int lastchar;
378 wchar_t w;
379 Sfio_t *fdtmp = 0;
380 long offset = 0;
381 unsigned char mb[8];
382 /* process each buffer */
383 while ((bp = (unsigned char*)sfreserve(fdin, SF_UNBOUND, -1)) && (c = sfvalue(fdin)) > 0)
385 cp = bp;
386 ep = cp + --c;
387 if((lastchar = cp[c]) != cut->eob)
388 *ep = cut->eob;
389 /* process each line in the buffer */
390 while (cp <= ep)
392 first = cp;
393 if (!inword)
395 nodelim = empty = 1;
396 copy = cp;
397 if (nfields = *(lp = cut->list))
398 copy = 0;
399 else
400 nfields = *++lp;
402 else if (copy)
403 copy = cp;
404 inword = 0;
407 /* skip over non-delimiter characters */
408 if (cut->mb)
409 for (;;)
411 switch (c = sp[*(unsigned char*)cp++])
413 case 0:
414 continue;
415 case SP_WIDE:
416 wp = --cp;
417 while ((c = mb2wc(w, cp, ep - cp)) <= 0)
419 /* mb char possibly spanning buffer boundary -- fun stuff */
420 if ((ep - cp) < mbmax())
422 int i;
423 int j;
424 int k;
426 if (lastchar != cut->eob)
428 *ep = lastchar;
429 if ((c = mb2wc(w, cp, ep - cp)) > 0)
430 break;
432 if (copy)
434 empty = 0;
435 if ((c = cp - copy) > 0 && sfwrite(fdout, (char*)copy, c) < 0)
436 goto failed;
438 for (i = 0; i <= (ep - cp); i++)
439 mb[i] = cp[i];
440 if (!(bp = (unsigned char*)sfreserve(fdin, SF_UNBOUND, -1)) || (c = sfvalue(fdin)) <= 0)
441 goto failed;
442 cp = bp;
443 ep = cp + --c;
444 if ((lastchar = cp[c]) != cut->eob)
445 *ep = cut->eob;
446 j = i;
447 k = 0;
448 while (j < mbmax())
449 mb[j++] = cp[k++];
450 if ((c = mb2wc(w, (char*)mb, j)) <= 0)
452 c = i;
453 w = 0;
455 first = bp = cp += c - i;
456 if (copy)
458 copy = bp;
459 if (w == cut->ldelim.chr)
460 lastchar = cut->ldelim.chr;
461 else if (w != cut->wdelim.chr)
463 empty = 0;
464 if (sfwrite(fdout, (char*)mb, c) < 0)
465 goto failed;
468 c = 0;
470 else
472 w = *cp;
473 c = 1;
475 break;
477 cp += c;
478 c = w;
479 if (c == cut->wdelim.chr)
481 c = SP_WORD;
482 break;
484 if (c == cut->ldelim.chr)
486 c = SP_LINE;
487 break;
489 continue;
490 default:
491 wp = cp - 1;
492 break;
494 break;
496 else
498 while (!(c = sp[*cp++]));
499 wp = cp - 1;
501 /* check for end-of-line */
502 if (c == SP_LINE)
504 if (cp <= ep)
505 break;
506 if (lastchar == cut->ldelim.chr)
507 break;
508 /* restore cut->last character */
509 if (lastchar != cut->eob)
510 *ep = lastchar;
511 inword++;
512 if (!sp[lastchar])
513 break;
515 nodelim = 0;
516 if (--nfields > 0)
517 continue;
518 nfields = *++lp;
519 if (copy)
521 empty = 0;
522 if ((c = wp - copy) > 0 && sfwrite(fdout, (char*)copy, c) < 0)
523 goto failed;
524 copy = 0;
526 else
527 /* set to delimiter unless the first field */
528 copy = empty ? cp : wp;
529 } while (!inword);
530 if (!inword)
532 if (!copy)
534 if (nodelim)
536 if (!cut->sflag)
538 if (offset)
540 sfseek(fdtmp,(Sfoff_t)0,SEEK_SET);
541 sfmove(fdtmp,fdout,offset,-1);
543 copy = first;
546 else
547 sfputc(fdout,'\n');
549 if (offset)
550 sfseek(fdtmp,offset=0,SEEK_SET);
552 if (copy && (c=cp-copy)>0 && (!nodelim || !cut->sflag) && sfwrite(fdout,(char*)copy,c)< 0)
553 goto failed;
555 /* see whether to save in tmp file */
556 if(inword && nodelim && !cut->sflag && (c=cp-first)>0)
558 /* copy line to tmpfile in case no fields */
559 if(!fdtmp)
560 fdtmp = sftmp(BLOCK);
561 sfwrite(fdtmp,(char*)first,c);
562 offset +=c;
565 failed:
566 if(fdtmp)
567 sfclose(fdtmp);
571 b_cut(int argc, char** argv, void* context)
573 register char* cp = 0;
574 register Sfio_t* fp;
575 char* s;
576 int n;
577 Cut_t* cut;
578 int mode = 0;
579 Delim_t wdelim;
580 Delim_t ldelim;
581 size_t reclen = 0;
583 cmdinit(argc, argv, context, ERROR_CATALOG, 0);
584 wdelim.chr = '\t';
585 ldelim.chr = '\n';
586 wdelim.len = ldelim.len = 1;
587 for (;;)
589 switch (n = optget(argv, usage))
591 case 0:
592 break;
593 case 'b':
594 case 'c':
595 if(mode&C_FIELDS)
597 error(2, "f option already specified");
598 continue;
600 cp = opt_info.arg;
601 if(n=='b')
602 mode |= C_BYTES;
603 else
604 mode |= C_CHARS;
605 continue;
606 case 'D':
607 ldelim.str = opt_info.arg;
608 if (mbwide())
610 s = opt_info.arg;
611 ldelim.chr = mbchar(s);
612 if ((n = s - opt_info.arg) > 1)
614 ldelim.len = n;
615 continue;
618 ldelim.chr = *(unsigned char*)opt_info.arg;
619 ldelim.len = 1;
620 continue;
621 case 'd':
622 wdelim.str = opt_info.arg;
623 if (mbwide())
625 s = opt_info.arg;
626 wdelim.chr = mbchar(s);
627 if ((n = s - opt_info.arg) > 1)
629 wdelim.len = n;
630 continue;
633 wdelim.chr = *(unsigned char*)opt_info.arg;
634 wdelim.len = 1;
635 continue;
636 case 'f':
637 if(mode&(C_CHARS|C_BYTES))
639 error(2, "c option already specified");
640 continue;
642 cp = opt_info.arg;
643 mode |= C_FIELDS;
644 continue;
645 case 'n':
646 mode |= C_NOSPLIT;
647 continue;
648 case 'N':
649 mode |= C_NONEWLINE;
650 continue;
651 case 'R':
652 case 'r':
653 if(opt_info.num>0)
654 reclen = opt_info.num;
655 continue;
656 case 's':
657 mode |= C_SUPRESS;
658 continue;
659 case ':':
660 error(2, "%s", opt_info.arg);
661 break;
662 case '?':
663 error(ERROR_usage(2), "%s", opt_info.arg);
664 break;
666 break;
668 argv += opt_info.index;
669 if (error_info.errors)
670 error(ERROR_usage(2), "%s",optusage(NiL));
671 if(!cp)
673 error(2, "b, c or f option must be specified");
674 error(ERROR_usage(2), "%s", optusage(NiL));
676 if(!*cp)
677 error(3, "non-empty b, c or f option must be specified");
678 if((mode & (C_FIELDS|C_SUPRESS)) == C_SUPRESS)
679 error(3, "s option requires f option");
680 cut = cutinit(mode, cp, &wdelim, &ldelim, reclen);
681 if(cp = *argv)
682 argv++;
685 if(!cp || streq(cp,"-"))
686 fp = sfstdin;
687 else if(!(fp = sfopen(NiL,cp,"r")))
689 error(ERROR_system(0),"%s: cannot open",cp);
690 continue;
692 if(mode&C_FIELDS)
693 cutfields(cut,fp,sfstdout);
694 else
695 cutcols(cut,fp,sfstdout);
696 if(fp!=sfstdin)
697 sfclose(fp);
698 } while(cp = *argv++);
699 if (sfsync(sfstdout))
700 error(ERROR_system(0), "write error");
701 return error_info.errors != 0;