2 * Copyright (c) 1989, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL>
36 * added Native Language Support
37 * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
38 * modified to work correctly in multi-byte locales
41 #include <sys/types.h>
42 #include <sys/ioctl.h>
57 #include "closestream.h"
61 static wchar_t *mbs_to_wcs(const char *);
63 #define mbs_to_wcs(s) xstrdup(s)
64 static char *mtsafe_strtok(char *, const char *, char **);
65 #define wcstok mtsafe_strtok
71 #define MAXLINELEN (LINE_MAX + 1)
73 static int input(FILE *fp
, int *maxlength
, wchar_t ***list
, int *entries
);
74 static void c_columnate(int maxlength
, long termwidth
, wchar_t **list
, int entries
);
75 static void r_columnate(int maxlength
, long termwidth
, wchar_t **list
, int entries
);
76 static wchar_t *local_wcstok(wchar_t *p
, const wchar_t *separator
, int greedy
, wchar_t **wcstok_state
);
77 static void maketbl(wchar_t **list
, int entries
, wchar_t *separator
, int greedy
, wchar_t *colsep
);
78 static void print(wchar_t **list
, int entries
);
87 /* Don't use wcswidth(), we need to ignore non-printable chars. */
88 static int width(const wchar_t *str
)
92 for (; *str
!= '\0'; str
++) {
100 static int width(const char *str
)
104 for (; *str
!= '\0'; str
++) {
112 static void __attribute__((__noreturn__
)) usage(int rc
)
114 FILE *out
= rc
== EXIT_FAILURE
? stderr
: stdout
;
116 fputs(USAGE_HEADER
, out
);
117 fprintf(out
, _(" %s [options] [<file>...]\n"), program_invocation_short_name
);
119 fputs(USAGE_SEPARATOR
, out
);
120 fputs(_("Columnate lists.\n"), out
);
122 fputs(USAGE_OPTIONS
, out
);
123 fputs(_(" -t, --table create a table\n"), out
);
124 fputs(_(" -s, --separator <string> possible table delimiters\n"), out
);
125 fputs(_(" -o, --output-separator <string> columns separator for table output\n"
126 " (default is two spaces)\n"), out
);
127 fputs(_(" -c, --output-width <width> width of output in number of characters\n"), out
);
128 fputs(_(" -x, --fillrows fill rows before columns\n"), out
);
129 fputs(USAGE_SEPARATOR
, out
);
130 fputs(USAGE_HELP
, out
);
131 fputs(USAGE_VERSION
, out
);
132 fprintf(out
, USAGE_MAN_TAIL("column(1)"));
137 int main(int argc
, char **argv
)
139 int ch
, tflag
= 0, xflag
= 0;
142 int entries
= 0; /* number of records */
143 unsigned int eval
= 0; /* exit value */
144 int maxlength
= 0; /* longest record */
145 wchar_t **list
= NULL
; /* array of pointers to records */
147 wchar_t *colsep
; /* table column output separator */
149 /* field separator for table option */
150 wchar_t default_separator
[] = { '\t', ' ', 0 };
151 wchar_t *separator
= default_separator
;
153 static const struct option longopts
[] =
155 { "columns", required_argument
, NULL
, 'c' }, /* deprecated */
156 { "fillrows", no_argument
, NULL
, 'x' },
157 { "help", no_argument
, NULL
, 'h' },
158 { "output-separator", required_argument
, NULL
, 'o' },
159 { "output-width", required_argument
, NULL
, 'c' },
160 { "separator", required_argument
, NULL
, 's' },
161 { "table", no_argument
, NULL
, 't' },
162 { "version", no_argument
, NULL
, 'V' },
163 { NULL
, 0, NULL
, 0 },
166 setlocale(LC_ALL
, "");
167 bindtextdomain(PACKAGE
, LOCALEDIR
);
169 atexit(close_stdout
);
171 termwidth
= get_terminal_width(80);
172 colsep
= mbs_to_wcs(" ");
174 while ((ch
= getopt_long(argc
, argv
, "hVc:s:txo:", longopts
, NULL
)) != -1)
180 printf(UTIL_LINUX_VERSION
);
183 termwidth
= strtou32_or_err(optarg
, _("invalid columns argument"));
186 separator
= mbs_to_wcs(optarg
);
191 colsep
= mbs_to_wcs(optarg
);
200 errtryhelp(EXIT_FAILURE
);
206 eval
+= input(stdin
, &maxlength
, &list
, &entries
);
208 for (; *argv
; ++argv
) {
211 if ((fp
= fopen(*argv
, "r")) != NULL
) {
212 eval
+= input(fp
, &maxlength
, &list
, &entries
);
216 eval
+= EXIT_FAILURE
;
224 maketbl(list
, entries
, separator
, greedy
, colsep
);
225 else if (maxlength
>= termwidth
)
226 print(list
, entries
);
228 c_columnate(maxlength
, termwidth
, list
, entries
);
230 r_columnate(maxlength
, termwidth
, list
, entries
);
232 for (i
= 0; i
< entries
; i
++)
242 static void c_columnate(int maxlength
, long termwidth
, wchar_t **list
, int entries
)
244 int chcnt
, col
, cnt
, endcol
, numcols
;
247 maxlength
= (maxlength
+ TAB
) & ~(TAB
- 1);
248 numcols
= termwidth
/ maxlength
;
250 for (chcnt
= col
= 0, lp
= list
;; ++lp
) {
255 if (++col
== numcols
) {
260 while ((cnt
= ((chcnt
+ TAB
) & ~(TAB
- 1))) <= endcol
) {
271 static void r_columnate(int maxlength
, long termwidth
, wchar_t **list
, int entries
)
273 int base
, chcnt
, cnt
, col
, endcol
, numcols
, numrows
, row
;
275 maxlength
= (maxlength
+ TAB
) & ~(TAB
- 1);
276 numcols
= termwidth
/ maxlength
;
279 numrows
= entries
/ numcols
;
280 if (entries
% numcols
)
283 for (row
= 0; row
< numrows
; ++row
) {
285 for (base
= row
, chcnt
= col
= 0; col
< numcols
; ++col
) {
286 fputws(list
[base
], stdout
);
287 chcnt
+= width(list
[base
]);
288 if ((base
+= numrows
) >= entries
)
290 while ((cnt
= ((chcnt
+ TAB
) & ~(TAB
- 1))) <= endcol
) {
300 static void print(wchar_t **list
, int entries
)
305 for (cnt
= entries
, lp
= list
; cnt
--; ++lp
) {
311 wchar_t *local_wcstok(wchar_t * p
, const wchar_t * separator
, int greedy
,
312 wchar_t ** wcstok_state
)
316 return wcstok(p
, separator
, wcstok_state
);
319 if (*wcstok_state
== NULL
)
325 p
= wcspbrk(result
, separator
);
327 *wcstok_state
= NULL
;
330 *wcstok_state
= p
+ 1;
335 static void maketbl(wchar_t **list
, int entries
, wchar_t *separator
, int greedy
, wchar_t *colsep
)
341 ssize_t maxcols
= DEFCOLS
, coloff
;
344 wchar_t *wcstok_state
= NULL
;
346 t
= tbl
= xcalloc(entries
, sizeof(TBL
));
347 cols
= xcalloc(maxcols
, sizeof(wchar_t *));
348 lens
= xcalloc(maxcols
, sizeof(ssize_t
));
350 for (lp
= list
, cnt
= 0; cnt
< entries
; ++cnt
, ++lp
, ++t
) {
353 while ((cols
[coloff
] = local_wcstok(p
, separator
, greedy
, &wcstok_state
)) != NULL
) {
354 if (++coloff
== maxcols
) {
356 cols
= xrealloc(cols
, maxcols
* sizeof(wchar_t *));
357 lens
= xrealloc(lens
, maxcols
* sizeof(ssize_t
));
358 /* zero fill only new memory */
359 memset(lens
+ (maxcols
- DEFCOLS
), 0,
360 DEFCOLS
* sizeof(*lens
));
364 t
->list
= xcalloc(coloff
, sizeof(wchar_t *));
365 t
->len
= xcalloc(coloff
, sizeof(int));
366 for (t
->cols
= coloff
; --coloff
>= 0;) {
367 t
->list
[coloff
] = cols
[coloff
];
368 t
->len
[coloff
] = width(cols
[coloff
]);
369 if (t
->len
[coloff
] > lens
[coloff
])
370 lens
[coloff
] = t
->len
[coloff
];
374 for (t
= tbl
, cnt
= 0; cnt
< entries
; ++cnt
, ++t
) {
375 for (coloff
= 0; coloff
< t
->cols
- 1; ++coloff
) {
376 fputws(t
->list
[coloff
], stdout
);
378 wprintf(L
"%*s", lens
[coloff
] - t
->len
[coloff
], "");
380 printf("%*s", (int) lens
[coloff
] - t
->len
[coloff
], "");
382 fputws(colsep
, stdout
);
384 if (coloff
< t
->cols
) {
385 fputws(t
->list
[coloff
], stdout
);
390 for (cnt
= 0; cnt
< entries
; ++cnt
) {
391 free((tbl
+cnt
)->list
);
392 free((tbl
+cnt
)->len
);
399 static int input(FILE *fp
, int *maxlength
, wchar_t ***list
, int *entries
)
401 static int maxentry
= DEFNUM
;
402 int len
, lineno
= 1, reportedline
= 0, eval
= 0;
403 wchar_t *p
, buf
[MAXLINELEN
];
404 wchar_t **local_list
= *list
;
405 int local_entries
= *entries
;
408 local_list
= xcalloc(maxentry
, sizeof(wchar_t *));
411 if (fgetws(buf
, MAXLINELEN
, fp
) == NULL
) {
415 err(EXIT_FAILURE
, _("read failed"));
417 for (p
= buf
; *p
&& iswspace(*p
); ++p
)
421 if (!(p
= wcschr(p
, '\n')) && !feof(fp
)) {
422 if (reportedline
< lineno
) {
423 warnx(_("line %d is too long, output will be truncated"),
425 reportedline
= lineno
;
433 len
= width(buf
); /* len = p - buf; */
434 if (*maxlength
< len
)
436 if (local_entries
== maxentry
) {
438 local_list
= xrealloc(local_list
,
439 (u_int
)maxentry
* sizeof(wchar_t *));
441 local_list
[local_entries
++] = wcsdup(buf
);
445 *entries
= local_entries
;
451 static wchar_t *mbs_to_wcs(const char *s
)
456 n
= mbstowcs((wchar_t *)0, s
, 0);
459 wcs
= xmalloc((n
+ 1) * sizeof(wchar_t));
460 n
= mbstowcs(wcs
, s
, n
+ 1);
469 #ifndef HAVE_WIDECHAR
470 static char *mtsafe_strtok(char *str
, const char *delim
, char **ptr
)
477 str
+= strspn(str
, delim
);
482 char *token_end
= strpbrk(str
, delim
);
485 *ptr
= token_end
+ 1;