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