/* echo.c, derived from code echo.c in Bash.
Copyright (C) 1987-2022 Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#include
#include
#include
#include
#include "system.h"
/* The official name of this program (e.g., no 'g' prefix). */
#define PROGRAM_NAME "echo"
#define AUTHORS \
proper_name ("Brian Fox"), \
proper_name ("Chet Ramey")
/* If true, interpret backslash escapes by default. */
#ifndef DEFAULT_ECHO_TO_XPG
enum { DEFAULT_ECHO_TO_XPG = false };
#endif
void
usage (int status)
{
/* STATUS should always be EXIT_SUCCESS (unlike in most other
utilities which would call emit_try_help otherwise). */
assert (status == EXIT_SUCCESS);
printf (_("\
Usage: %s [SHORT-OPTION]... [STRING]...\n\
or: %s LONG-OPTION\n\
"), program_name, program_name);
fputs (_("\
Echo the STRING(s) to standard output.\n\
\n\
-n do not output the trailing newline\n\
"), stdout);
fputs (_(DEFAULT_ECHO_TO_XPG
? N_("\
-e enable interpretation of backslash escapes (default)\n\
-E disable interpretation of backslash escapes\n")
: N_("\
-e enable interpretation of backslash escapes\n\
-E disable interpretation of backslash escapes (default)\n")),
stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
fputs (_("\
\n\
If -e is in effect, the following sequences are recognized:\n\
\n\
"), stdout);
fputs (_("\
\\\\ backslash\n\
\\a alert (BEL)\n\
\\b backspace\n\
\\c produce no further output\n\
\\e escape\n\
\\f form feed\n\
\\n new line\n\
\\r carriage return\n\
\\t horizontal tab\n\
\\v vertical tab\n\
"), stdout);
fputs (_("\
\\0NNN byte with octal value NNN (1 to 3 digits)\n\
\\xHH byte with hexadecimal value HH (1 to 2 digits)\n\
"), stdout);
printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
fputs (_("\n\
NOTE: printf(1) is a preferred alternative,\n\
which does not have issues outputting option-like strings.\n\
"), stdout);
emit_ancillary_info (PROGRAM_NAME);
exit (status);
}
/* Convert C from hexadecimal character to integer. */
static int
hextobin (unsigned char c)
{
switch (c)
{
default: return c - '0';
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
}
}
/* Print the words in LIST to standard output. If the first word is
'-n', then don't print a trailing newline. We also support the
echo syntax from Version 9 unix systems. */
int
main (int argc, char **argv)
{
bool display_return = true;
bool posixly_correct = !!getenv ("POSIXLY_CORRECT");
bool allow_options =
(! posixly_correct
|| (! DEFAULT_ECHO_TO_XPG && 1 < argc && STREQ (argv[1], "-n")));
/* System V machines already have a /bin/sh with a v9 behavior.
Use the identical behavior for these machines so that the
existing system shell scripts won't barf. */
bool do_v9 = DEFAULT_ECHO_TO_XPG;
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
atexit (close_stdout);
/* We directly parse options, rather than use parse_long_options, in
order to avoid accepting abbreviations. */
if (allow_options && argc == 2)
{
if (STREQ (argv[1], "--help"))
usage (EXIT_SUCCESS);
if (STREQ (argv[1], "--version"))
{
version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
(char *) NULL);
return EXIT_SUCCESS;
}
}
--argc;
++argv;
if (allow_options)
while (argc > 0 && *argv[0] == '-')
{
char const *temp = argv[0] + 1;
size_t i;
/* If it appears that we are handling options, then make sure that
all of the options specified are actually valid. Otherwise, the
string should just be echoed. */
for (i = 0; temp[i]; i++)
switch (temp[i])
{
case 'e': case 'E': case 'n':
break;
default:
goto just_echo;
}
if (i == 0)
goto just_echo;
/* All of the options in TEMP are valid options to ECHO.
Handle them. */
while (*temp)
switch (*temp++)
{
case 'e':
do_v9 = true;
break;
case 'E':
do_v9 = false;
break;
case 'n':
display_return = false;
break;
}
argc--;
argv++;
}
just_echo:
if (do_v9 || posixly_correct)
{
while (argc > 0)
{
char const *s = argv[0];
unsigned char c;
while ((c = *s++))
{
if (c == '\\' && *s)
{
switch (c = *s++)
{
case 'a': c = '\a'; break;
case 'b': c = '\b'; break;
case 'c': return EXIT_SUCCESS;
case 'e': c = '\x1B'; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'v': c = '\v'; break;
case 'x':
{
unsigned char ch = *s;
if (! isxdigit (ch))
goto not_an_escape;
s++;
c = hextobin (ch);
ch = *s;
if (isxdigit (ch))
{
s++;
c = c * 16 + hextobin (ch);
}
}
break;
case '0':
c = 0;
if (! ('0' <= *s && *s <= '7'))
break;
c = *s++;
FALLTHROUGH;
case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
c -= '0';
if ('0' <= *s && *s <= '7')
c = c * 8 + (*s++ - '0');
if ('0' <= *s && *s <= '7')
c = c * 8 + (*s++ - '0');
break;
case '\\': break;
not_an_escape:
default: putchar ('\\'); break;
}
}
putchar (c);
}
argc--;
argv++;
if (argc > 0)
putchar (' ');
}
}
else
{
while (argc > 0)
{
fputs (argv[0], stdout);
argc--;
argv++;
if (argc > 0)
putchar (' ');
}
}
if (display_return)
putchar ('\n');
return EXIT_SUCCESS;
}