2 * Copyright (c) 1990 The Regents of the University of California.
5 * This code is derived from software contributed to Berkeley by
6 * Michael Rendell of the Memorial University of Newfoundland.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by the University of
19 * California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * Wed Jun 22 22:15:41 1994, faith@cs.unc.edu: Added internationalization
37 * patches from Andries.Brouwer@cwi.nl
38 * Wed Sep 14 22:31:17 1994: patches from Carl Christofferson
39 * (cchris@connected.com)
40 * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL>
41 * added Native Language Support
42 * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
43 * modified to work correctly in multi-byte locales
48 * This command is deprecated. The utility is in maintenance mode,
49 * meaning we keep them in source tree for backward compatibility
50 * only. Do not waste time making this command better, unless the
51 * fix is about security or other very critical issue.
53 * See Documentation/deprecated.txt for more information.
68 #include "closestream.h"
70 #define BS '\b' /* backspace */
71 #define TAB '\t' /* tab */
72 #define SPACE ' ' /* space */
73 #define NL '\n' /* newline */
74 #define CR '\r' /* carriage return */
75 #define ESC '\033' /* escape */
76 #define SI '\017' /* shift in to normal character set */
77 #define SO '\016' /* shift out to alternate character set */
78 #define VT '\013' /* vertical tab (aka reverse line feed) */
79 #define RLF '\007' /* ESC-07 reverse line feed */
80 #define RHLF '\010' /* ESC-010 reverse half-line feed */
81 #define FHLF '\011' /* ESC-011 forward half-line feed */
83 /* build up at least this many lines before flushing them out */
84 #define BUFFER_MARGIN 32
88 typedef struct char_str
{
90 #define CS_ALTERNATE 2
91 int c_column
; /* column character is in */
92 CSET c_set
; /* character set (currently only 2) */
93 wchar_t c_char
; /* character in question */
94 int c_width
; /* character width */
97 typedef struct line_str LINE
;
99 CHAR
*l_line
; /* characters on the line */
100 LINE
*l_prev
; /* previous line */
101 LINE
*l_next
; /* next line */
102 int l_lsize
; /* allocated sizeof l_line */
103 int l_line_len
; /* strlen(l_line) */
104 int l_needs_sort
; /* set if chars went in out of order */
105 int l_max_col
; /* max column in the line */
108 void free_line(LINE
*l
);
109 void flush_line(LINE
*l
);
110 void flush_lines(int);
111 void flush_blanks(void);
112 LINE
*alloc_line(void);
114 static CSET last_set
; /* char_set of last char printed */
116 static int compress_spaces
; /* if doing space -> tab conversion */
117 static int fine
; /* if `fine' resolution (half lines) */
118 static unsigned max_bufd_lines
; /* max # lines to keep in memory */
119 static int nblank_lines
; /* # blanks after last flushed line */
120 static int no_backspaces
; /* if not to output any backspaces */
121 static int pass_unknown_seqs
; /* whether to pass unknown control sequences */
124 if (putwchar(ch) == WEOF) \
127 static void __attribute__((__noreturn__
)) usage(void)
132 " %s [options]\n"), program_invocation_short_name
);
134 fputs(USAGE_SEPARATOR
, out
);
135 fputs(_("Filter out reverse line feeds.\n"), out
);
139 " -b, --no-backspaces do not output backspaces\n"
140 " -f, --fine permit forward half line feeds\n"
141 " -p, --pass pass unknown control sequences\n"
142 " -h, --tabs convert spaces to tabs\n"
143 " -x, --spaces convert tabs to spaces\n"
144 " -l, --lines NUM buffer at least NUM lines\n"
146 printf( " -H, --help %s\n", USAGE_OPTSTR_HELP
);
147 printf( " -V, --version %s\n", USAGE_OPTSTR_VERSION
);
149 fputs(USAGE_SEPARATOR
, out
);
151 "%s reads from standard input and writes to standard output\n\n"),
152 program_invocation_short_name
);
154 printf(USAGE_MAN_TAIL("col(1)"));
158 static void __attribute__((__noreturn__
)) wrerr(void)
160 errx(EXIT_FAILURE
, _("write error"));
163 int main(int argc
, char **argv
)
167 CSET cur_set
; /* current character set */
168 LINE
*l
; /* current line */
169 int extra_lines
; /* # of lines above first line */
170 int cur_col
; /* current column */
171 int cur_line
; /* line number of current position */
172 int max_line
; /* max value of cur_line */
173 int this_line
; /* line l points to */
174 int nflushd_lines
; /* number of lines that were flushed */
175 int adjust
, opt
, warned
;
176 int ret
= EXIT_SUCCESS
;
178 static const struct option longopts
[] = {
179 { "no-backspaces", no_argument
, NULL
, 'b' },
180 { "fine", no_argument
, NULL
, 'f' },
181 { "pass", no_argument
, NULL
, 'p' },
182 { "tabs", no_argument
, NULL
, 'h' },
183 { "spaces", no_argument
, NULL
, 'x' },
184 { "lines", required_argument
, NULL
, 'l' },
185 { "version", no_argument
, NULL
, 'V' },
186 { "help", no_argument
, NULL
, 'H' },
190 setlocale(LC_ALL
, "");
191 bindtextdomain(PACKAGE
, LOCALEDIR
);
193 close_stdout_atexit();
195 max_bufd_lines
= 128 * 2;
196 compress_spaces
= 1; /* compress spaces into tabs */
197 pass_unknown_seqs
= 0; /* remove unknown escape sequences */
199 while ((opt
= getopt_long(argc
, argv
, "bfhl:pxVH", longopts
, NULL
)) != -1)
201 case 'b': /* do not output backspaces */
204 case 'f': /* allow half forward line feeds */
207 case 'h': /* compress spaces into tabs */
212 * Buffered line count, which is a value in half
213 * lines e.g. twice the amount specified.
215 max_bufd_lines
= strtou32_or_err(optarg
, _("bad -l argument")) * 2;
218 pass_unknown_seqs
= 1;
220 case 'x': /* do not compress spaces into tabs */
225 print_version(EXIT_SUCCESS
);
229 errtryhelp(EXIT_FAILURE
);
232 if (optind
!= argc
) {
233 warnx(_("bad usage"));
234 errtryhelp(EXIT_FAILURE
);
237 adjust
= cur_col
= extra_lines
= warned
= 0;
238 cur_line
= max_line
= nflushd_lines
= this_line
= 0;
239 cur_set
= last_set
= CS_NORMAL
;
240 lines
= l
= alloc_line();
242 while (feof(stdin
) == 0) {
244 if ((ch
= getwchar()) == WEOF
) {
245 if (errno
== EILSEQ
) {
246 warn(_("failed on line %d"), max_line
+ 1);
253 case BS
: /* can't go back further */
257 cur_col
-= c
->c_width
;
264 case ESC
: /* just ignore EOF */
274 if (cur_line
> max_line
)
280 if (cur_line
> max_line
)
291 cur_set
= CS_ALTERNATE
;
293 case TAB
: /* adjust column */
303 cur_col
+= wcwidth(ch
);
306 if (!pass_unknown_seqs
)
310 /* Must stuff ch in a line - are we at the right one? */
311 if (cur_line
!= this_line
- adjust
) {
316 nmove
= cur_line
- this_line
;
318 /* round up to next line */
325 for (; nmove
< 0 && l
->l_prev
; nmove
++)
328 if (nflushd_lines
== 0) {
330 * Allow backup past first
331 * line if nothing has been
334 for (; nmove
< 0; nmove
++) {
344 _("warning: can't back up %s."), cur_line
< 0 ?
345 _("past first line") : _("-- line already flushed"));
350 /* may need to allocate here */
351 for (; nmove
> 0 && l
->l_next
; nmove
--)
353 for (; nmove
> 0; nmove
--) {
360 this_line
= cur_line
+ adjust
;
361 nmove
= this_line
- nflushd_lines
;
363 && (unsigned) nmove
>= max_bufd_lines
+ BUFFER_MARGIN
) {
364 nflushd_lines
+= nmove
- max_bufd_lines
;
365 flush_lines(nmove
- max_bufd_lines
);
368 /* grow line's buffer? */
369 if (l
->l_line_len
+ 1 >= l
->l_lsize
) {
372 need
= l
->l_lsize
? l
->l_lsize
* 2 : 90;
373 l
->l_line
= xrealloc((void *) l
->l_line
,
374 (unsigned) need
* sizeof(CHAR
));
377 c
= &l
->l_line
[l
->l_line_len
++];
381 c
->c_column
= cur_col
;
384 c
->c_width
= wcwidth(ch
);
386 * If things are put in out of order, they will need sorting
387 * when it is flushed.
389 if (cur_col
< l
->l_max_col
)
392 l
->l_max_col
= cur_col
;
394 cur_col
+= c
->c_width
;
396 /* goto the last line that had a character on it */
397 for (; l
->l_next
; l
= l
->l_next
)
400 return EXIT_SUCCESS
; /* no lines, so just exit */
401 flush_lines(this_line
- nflushd_lines
+ extra_lines
+ 1);
403 /* make sure we leave things in a sane state */
404 if (last_set
!= CS_NORMAL
)
407 /* flush out the last few blank lines */
408 nblank_lines
= max_line
- this_line
;
411 else if (!nblank_lines
)
412 /* missing a \n on the last line? */
418 void flush_lines(int nflush
)
422 while (--nflush
>= 0) {
430 free((void *)l
->l_line
);
434 lines
->l_prev
= NULL
;
438 * Print a number of newline/half newlines. If fine flag is set, nblank_lines
439 * is the number of half line feeds, otherwise it is the number of whole line
442 void flush_blanks(void)
455 for (i
= nb
; --i
>= 0;)
467 * Write a line to stdout taking care of space to tab conversion (-h flag)
468 * and character set shifts.
470 void flush_line(LINE
*l
)
473 int nchars
, last_col
, this_col
;
476 nchars
= l
->l_line_len
;
478 if (l
->l_needs_sort
) {
479 static CHAR
*sorted
= NULL
;
480 static int count_size
= 0, *count
= NULL
, sorted_size
= 0;
484 * Do an O(n) sort on l->l_line by column being careful to
485 * preserve the order of characters in the same column.
487 if (l
->l_lsize
> sorted_size
) {
488 sorted_size
= l
->l_lsize
;
489 sorted
= xrealloc((void *)sorted
,
490 (unsigned)sizeof(CHAR
) * sorted_size
);
492 if (l
->l_max_col
>= count_size
) {
493 count_size
= l
->l_max_col
+ 1;
494 count
= (int *)xrealloc((void *)count
,
495 (unsigned)sizeof(int) * count_size
);
497 memset(count
, 0, sizeof(int) * l
->l_max_col
+ 1);
498 for (i
= nchars
, c
= l
->l_line
; c
&& --i
>= 0; c
++)
499 count
[c
->c_column
]++;
502 * calculate running total (shifted down by 1) to use as
503 * indices into new line.
505 for (tot
= 0, i
= 0; i
<= l
->l_max_col
; i
++) {
511 for (i
= nchars
, c
= l
->l_line
; --i
>= 0; c
++)
512 sorted
[count
[c
->c_column
]++] = *c
;
517 this_col
= c
->c_column
;
521 } while (--nchars
> 0 && this_col
== endc
->c_column
);
523 /* if -b only print last character */
527 this_col
+ c
->c_width
> endc
->c_column
)
531 if (this_col
> last_col
) {
532 int nspace
= this_col
- last_col
;
534 if (compress_spaces
&& nspace
> 1) {
537 ntabs
= this_col
/ 8 - last_col
/ 8;
539 nspace
= this_col
& 7;
544 while (--nspace
>= 0)
550 if (c
->c_set
!= last_set
) {
561 if ((c
+ 1) < endc
) {
563 for (i
=0; i
< c
->c_width
; i
++)
569 last_col
+= (c
- 1)->c_width
;
575 static LINE
*line_freelist
;
583 if (!line_freelist
) {
584 l
= xmalloc(sizeof(LINE
) * NALLOC
);
586 for (i
= 1; i
< NALLOC
; i
++, l
++)
591 line_freelist
= l
->l_next
;
593 memset(l
, 0, sizeof(LINE
));
597 void free_line(LINE
*l
)
599 l
->l_next
= line_freelist
;