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