tail: avoid infloop with -c on /dev/zero
[coreutils.git] / src / expand.c
bloba6176a9743179e3d00f4034766574bce67d48cf4
1 /* expand - convert tabs to spaces
2 Copyright (C) 1989-2024 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* By default, convert all tabs to spaces.
18 Preserves backspace characters in the output; they decrement the
19 column count for tab calculations.
20 The default action is equivalent to -8.
22 Options:
23 --tabs=tab1[,tab2[,...]]
24 -t tab1[,tab2[,...]]
25 -tab1[,tab2[,...]] If only one tab stop is given, set the tabs tab1
26 columns apart instead of the default 8. Otherwise,
27 set the tabs at columns tab1, tab2, etc. (numbered from
28 0); replace any tabs beyond the tab stops given with
29 single spaces.
30 --initial
31 -i Only convert initial tabs on each line to spaces.
33 David MacKenzie <djm@gnu.ai.mit.edu> */
35 #include <config.h>
37 #include <ctype.h>
38 #include <stdio.h>
39 #include <getopt.h>
40 #include <sys/types.h>
41 #include "system.h"
42 #include "expand-common.h"
44 /* The official name of this program (e.g., no 'g' prefix). */
45 #define PROGRAM_NAME "expand"
47 #define AUTHORS proper_name ("David MacKenzie")
49 static char const shortopts[] = "it:0::1::2::3::4::5::6::7::8::9::";
51 static struct option const longopts[] =
53 {"tabs", required_argument, nullptr, 't'},
54 {"initial", no_argument, nullptr, 'i'},
55 {GETOPT_HELP_OPTION_DECL},
56 {GETOPT_VERSION_OPTION_DECL},
57 {nullptr, 0, nullptr, 0}
60 void
61 usage (int status)
63 if (status != EXIT_SUCCESS)
64 emit_try_help ();
65 else
67 printf (_("\
68 Usage: %s [OPTION]... [FILE]...\n\
69 "),
70 program_name);
71 fputs (_("\
72 Convert tabs in each FILE to spaces, writing to standard output.\n\
73 "), stdout);
75 emit_stdin_note ();
76 emit_mandatory_arg_note ();
78 fputs (_("\
79 -i, --initial do not convert tabs after non blanks\n\
80 -t, --tabs=N have tabs N characters apart, not 8\n\
81 "), stdout);
82 emit_tab_list_info ();
83 fputs (HELP_OPTION_DESCRIPTION, stdout);
84 fputs (VERSION_OPTION_DESCRIPTION, stdout);
85 emit_ancillary_info (PROGRAM_NAME);
87 exit (status);
91 /* Change tabs to spaces, writing to stdout.
92 Read each file in 'file_list', in order. */
94 static void
95 expand (void)
97 /* Input stream. */
98 FILE *fp = next_file (nullptr);
100 if (!fp)
101 return;
103 while (true)
105 /* Input character, or EOF. */
106 int c;
108 /* If true, perform translations. */
109 bool convert = true;
112 /* The following variables have valid values only when CONVERT
113 is true: */
115 /* Column of next input character. */
116 uintmax_t column = 0;
118 /* Index in TAB_LIST of next tab stop to examine. */
119 size_t tab_index = 0;
122 /* Convert a line of text. */
126 while ((c = getc (fp)) < 0 && (fp = next_file (fp)))
127 continue;
129 if (convert)
131 if (c == '\t')
133 /* Column the next input tab stop is on. */
134 uintmax_t next_tab_column;
135 bool last_tab;
137 next_tab_column = get_next_tab_column (column, &tab_index,
138 &last_tab);
140 if (last_tab)
141 next_tab_column = column + 1;
143 if (next_tab_column < column)
144 error (EXIT_FAILURE, 0, _("input line is too long"));
146 while (++column < next_tab_column)
147 if (putchar (' ') < 0)
148 write_error ();
150 c = ' ';
152 else if (c == '\b')
154 /* Go back one column, and force recalculation of the
155 next tab stop. */
156 column -= !!column;
157 tab_index -= !!tab_index;
159 else
161 column++;
162 if (!column)
163 error (EXIT_FAILURE, 0, _("input line is too long"));
166 convert &= convert_entire_line || !! isblank (c);
169 if (c < 0)
170 return;
172 if (putchar (c) < 0)
173 write_error ();
175 while (c != '\n');
180 main (int argc, char **argv)
182 int c;
184 initialize_main (&argc, &argv);
185 set_program_name (argv[0]);
186 setlocale (LC_ALL, "");
187 bindtextdomain (PACKAGE, LOCALEDIR);
188 textdomain (PACKAGE);
190 atexit (close_stdout);
191 convert_entire_line = true;
193 while ((c = getopt_long (argc, argv, shortopts, longopts, nullptr)) != -1)
195 switch (c)
197 case 'i':
198 convert_entire_line = false;
199 break;
201 case 't':
202 parse_tab_stops (optarg);
203 break;
205 case '0': case '1': case '2': case '3': case '4':
206 case '5': case '6': case '7': case '8': case '9':
207 if (optarg)
208 parse_tab_stops (optarg - 1);
209 else
211 char tab_stop[2];
212 tab_stop[0] = c;
213 tab_stop[1] = '\0';
214 parse_tab_stops (tab_stop);
216 break;
218 case_GETOPT_HELP_CHAR;
220 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
222 default:
223 usage (EXIT_FAILURE);
227 finalize_tab_stops ();
229 set_file_list (optind < argc ? &argv[optind] : nullptr);
231 expand ();
233 cleanup_file_list_stdin ();
235 return exit_status;