doc: remove 'proposed' in regard to $'' descriptions
[coreutils.git] / src / echo.c
blob11290f6f1db01569fef7911fc467e3e41f6484e8
1 /* echo.c, derived from code echo.c in Bash.
2 Copyright (C) 1987-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 #include <config.h>
18 #include <stdio.h>
19 #include <sys/types.h>
20 #include "system.h"
21 #include "assure.h"
22 #include "c-ctype.h"
24 /* The official name of this program (e.g., no 'g' prefix). */
25 #define PROGRAM_NAME "echo"
27 #define AUTHORS \
28 proper_name ("Brian Fox"), \
29 proper_name ("Chet Ramey")
31 /* If true, interpret backslash escapes by default. */
32 #ifndef DEFAULT_ECHO_TO_XPG
33 enum { DEFAULT_ECHO_TO_XPG = false };
34 #endif
36 void
37 usage (int status)
39 /* STATUS should always be EXIT_SUCCESS (unlike in most other
40 utilities which would call emit_try_help otherwise). */
41 affirm (status == EXIT_SUCCESS);
43 printf (_("\
44 Usage: %s [SHORT-OPTION]... [STRING]...\n\
45 or: %s LONG-OPTION\n\
46 "), program_name, program_name);
47 fputs (_("\
48 Echo the STRING(s) to standard output.\n\
49 \n\
50 -n do not output the trailing newline\n\
51 "), stdout);
52 fputs (_(DEFAULT_ECHO_TO_XPG
53 ? N_("\
54 -e enable interpretation of backslash escapes (default)\n\
55 -E disable interpretation of backslash escapes\n")
56 : N_("\
57 -e enable interpretation of backslash escapes\n\
58 -E disable interpretation of backslash escapes (default)\n")),
59 stdout);
60 fputs (HELP_OPTION_DESCRIPTION, stdout);
61 fputs (VERSION_OPTION_DESCRIPTION, stdout);
62 fputs (_("\
63 \n\
64 If -e is in effect, the following sequences are recognized:\n\
65 \n\
66 "), stdout);
67 fputs (_("\
68 \\\\ backslash\n\
69 \\a alert (BEL)\n\
70 \\b backspace\n\
71 \\c produce no further output\n\
72 \\e escape\n\
73 \\f form feed\n\
74 \\n new line\n\
75 \\r carriage return\n\
76 \\t horizontal tab\n\
77 \\v vertical tab\n\
78 "), stdout);
79 fputs (_("\
80 \\0NNN byte with octal value NNN (1 to 3 digits)\n\
81 \\xHH byte with hexadecimal value HH (1 to 2 digits)\n\
82 "), stdout);
83 printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
84 fputs (_("\n\
85 Consider using the 'printf' command instead,\n\
86 as it avoids problems when outputting option-like strings.\n\
87 "), stdout);
88 emit_ancillary_info (PROGRAM_NAME);
89 exit (status);
92 /* Convert C from hexadecimal character to integer. */
93 static int
94 hextobin (unsigned char c)
96 switch (c)
98 default: return c - '0';
99 case 'a': case 'A': return 10;
100 case 'b': case 'B': return 11;
101 case 'c': case 'C': return 12;
102 case 'd': case 'D': return 13;
103 case 'e': case 'E': return 14;
104 case 'f': case 'F': return 15;
108 /* Print the words in LIST to standard output. If the first word is
109 '-n', then don't print a trailing newline. We also support the
110 echo syntax from Version 9 unix systems. */
113 main (int argc, char **argv)
115 bool display_return = true;
116 bool posixly_correct = !!getenv ("POSIXLY_CORRECT");
117 bool allow_options =
118 (! posixly_correct
119 || (! DEFAULT_ECHO_TO_XPG && 1 < argc && STREQ (argv[1], "-n")));
121 /* System V machines already have a /bin/sh with a v9 behavior.
122 Use the identical behavior for these machines so that the
123 existing system shell scripts won't barf. */
124 bool do_v9 = DEFAULT_ECHO_TO_XPG;
126 initialize_main (&argc, &argv);
127 set_program_name (argv[0]);
128 setlocale (LC_ALL, "");
129 bindtextdomain (PACKAGE, LOCALEDIR);
130 textdomain (PACKAGE);
132 atexit (close_stdout);
134 /* We directly parse options, rather than use parse_long_options, in
135 order to avoid accepting abbreviations. */
136 if (allow_options && argc == 2)
138 if (STREQ (argv[1], "--help"))
139 usage (EXIT_SUCCESS);
141 if (STREQ (argv[1], "--version"))
143 version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
144 (char *) nullptr);
145 return EXIT_SUCCESS;
149 --argc;
150 ++argv;
152 if (allow_options)
153 while (argc > 0 && *argv[0] == '-')
155 char const *temp = argv[0] + 1;
156 size_t i;
158 /* If it appears that we are handling options, then make sure that
159 all of the options specified are actually valid. Otherwise, the
160 string should just be echoed. */
162 for (i = 0; temp[i]; i++)
163 switch (temp[i])
165 case 'e': case 'E': case 'n':
166 break;
167 default:
168 goto just_echo;
171 if (i == 0)
172 goto just_echo;
174 /* All of the options in TEMP are valid options to ECHO.
175 Handle them. */
176 while (*temp)
177 switch (*temp++)
179 case 'e':
180 do_v9 = true;
181 break;
183 case 'E':
184 do_v9 = false;
185 break;
187 case 'n':
188 display_return = false;
189 break;
192 argc--;
193 argv++;
196 just_echo:
198 if (do_v9 || posixly_correct)
200 while (argc > 0)
202 char const *s = argv[0];
203 unsigned char c;
205 while ((c = *s++))
207 if (c == '\\' && *s)
209 switch (c = *s++)
211 case 'a': c = '\a'; break;
212 case 'b': c = '\b'; break;
213 case 'c': return EXIT_SUCCESS;
214 case 'e': c = '\x1B'; break;
215 case 'f': c = '\f'; break;
216 case 'n': c = '\n'; break;
217 case 'r': c = '\r'; break;
218 case 't': c = '\t'; break;
219 case 'v': c = '\v'; break;
220 case 'x':
222 unsigned char ch = *s;
223 if (! c_isxdigit (ch))
224 goto not_an_escape;
225 s++;
226 c = hextobin (ch);
227 ch = *s;
228 if (c_isxdigit (ch))
230 s++;
231 c = c * 16 + hextobin (ch);
234 break;
235 case '0':
236 c = 0;
237 if (! ('0' <= *s && *s <= '7'))
238 break;
239 c = *s++;
240 FALLTHROUGH;
241 case '1': case '2': case '3':
242 case '4': case '5': case '6': case '7':
243 c -= '0';
244 if ('0' <= *s && *s <= '7')
245 c = c * 8 + (*s++ - '0');
246 if ('0' <= *s && *s <= '7')
247 c = c * 8 + (*s++ - '0');
248 break;
249 case '\\': break;
251 not_an_escape:
252 default: putchar ('\\'); break;
255 putchar (c);
257 argc--;
258 argv++;
259 if (argc > 0)
260 putchar (' ');
263 else
265 while (argc > 0)
267 fputs (argv[0], stdout);
268 argc--;
269 argv++;
270 if (argc > 0)
271 putchar (' ');
275 if (display_return)
276 putchar ('\n');
277 return EXIT_SUCCESS;