summaryrefslogtreecommitdiffstats
path: root/src/echo.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/echo.c')
-rw-r--r--src/echo.c277
1 files changed, 277 insertions, 0 deletions
diff --git a/src/echo.c b/src/echo.c
new file mode 100644
index 0000000..1f2cbc7
--- /dev/null
+++ b/src/echo.c
@@ -0,0 +1,277 @@
+/* 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 <https://www.gnu.org/licenses/>. */
+
+#include <config.h>
+#include <stdio.h>
+#include <assert.h>
+#include <sys/types.h>
+#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;
+}