From: Bruno Haible Date: Wed, 17 Sep 2003 08:21:32 +0000 (+0000) Subject: Support for internationalized shell scripts: Program to substitute X-Git-Tag: v0.13~255 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a9a59a45cbd352345a5b105f818298ab3c0f57ec;p=thirdparty%2Fgettext.git Support for internationalized shell scripts: Program to substitute environment variable values. --- diff --git a/gettext-runtime/src/envsubst.c b/gettext-runtime/src/envsubst.c new file mode 100644 index 000000000..7164834e8 --- /dev/null +++ b/gettext-runtime/src/envsubst.c @@ -0,0 +1,569 @@ +/* Substitution of environment variables in shell format strings. + Copyright (C) 2003 Free Software Foundation, Inc. + Written by Bruno Haible , 2003. + + 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 2, 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, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include "closeout.h" +#include "error.h" +#include "progname.h" +#include "relocatable.h" +#include "basename.h" +#include "xmalloc.h" +#include "exit.h" +#include "gettext.h" + +#define _(str) gettext (str) + +/* If nonzero, substitution shall be performed on all variables. */ +static int all_variables; + +/* Long options. */ +static const struct option long_options[] = +{ + { "help", no_argument, NULL, 'h' }, + { "variables", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } +}; + +/* Forward declaration of local functions. */ +static void usage (int status) +#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) + __attribute__ ((noreturn)) +#endif +; +static void print_variables (const char *string); +static void note_variables (const char *string); +static void subst_from_stdin (void); + +int +main (int argc, char *argv[]) +{ + /* Default values for command line options. */ + int show_variables = 0; + int do_help = 0; + int do_version = 0; + + int opt; + + /* Set program name for message texts. */ + set_program_name (argv[0]); + +#ifdef HAVE_SETLOCALE + /* Set locale via LC_ALL. */ + setlocale (LC_ALL, ""); +#endif + + /* Set the text message domain. */ + bindtextdomain (PACKAGE, relocate (LOCALEDIR)); + textdomain (PACKAGE); + + /* Ensure that write errors on stdout are detected. */ + atexit (close_stdout); + + /* Parse command line options. */ + while ((opt = getopt_long (argc, argv, "hvV", long_options, NULL)) != EOF) + switch (opt) + { + case '\0': /* Long option. */ + break; + case 'h': + do_help = 1; + break; + case 'v': + show_variables = 1; + break; + case 'V': + do_version = 1; + break; + default: + usage (EXIT_FAILURE); + } + + /* Version information is requested. */ + if (do_version) + { + printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); + /* xgettext: no-wrap */ + printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ +This is free software; see the source for copying conditions. There is NO\n\ +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\ +"), + "2003"); + printf (_("Written by %s.\n"), "Bruno Haible"); + exit (EXIT_SUCCESS); + } + + /* Help is requested. */ + if (do_help) + usage (EXIT_SUCCESS); + + if (argc - optind > 1) + error (EXIT_FAILURE, 0, _("too many arguments")); + + /* Distinguish the two main operation modes. */ + if (show_variables) + { + /* Output only the variables. */ + switch (argc - optind) + { + case 1: + break; + case 0: + error (EXIT_FAILURE, 0, _("missing arguments")); + default: + abort (); + } + print_variables (argv[optind++]); + } + else + { + /* Actually perform the substitutions. */ + switch (argc - optind) + { + case 1: + all_variables = 0; + note_variables (argv[optind++]); + break; + case 0: + all_variables = 1; + break; + default: + abort (); + } + subst_from_stdin (); + } + + /* Make sure nothing went wrong. */ + if (fflush (stdout) || ferror (stdout)) + error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"), + _("standard output")); + + exit (EXIT_SUCCESS); +} + + +/* Display usage information and exit. */ +static void +usage (int status) +{ + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n"), + program_name); + else + { + /* xgettext: no-wrap */ + printf (_("\ +Usage: %s [OPTION] [SHELL-FORMAT]\n\ +"), program_name); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Substitutes the values of environment variables.\n")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Operation mode:\n")); + /* xgettext: no-wrap */ + printf (_("\ + -v, --variables output the variables occurring in SHELL-FORMAT\n")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +Informative output:\n")); + /* xgettext: no-wrap */ + printf (_("\ + -h, --help display this help and exit\n")); + /* xgettext: no-wrap */ + printf (_("\ + -V, --version output version information and exit\n")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +In normal operation mode, standard input is copied to standard output,\n\ +with references to environment variables of the form $VARIABLE or ${VARIABLE}\n\ +being replaced with the corresponding values. If a SHELL-FORMAT is given,\n\ +only those environment variables that are referenced in SHELL-FORMAT are\n\ +substituted; otherwise all environment variables references occurring in\n\ +standard input are substituted.\n")); + printf ("\n"); + /* xgettext: no-wrap */ + printf (_("\ +When --variables is used, standard input is ignored, and the output consists\n\ +of the environment variables that are referenced in SHELL-FORMAT, one per line.\n")); + printf ("\n"); + fputs (_("Report bugs to .\n"), stdout); + } + + exit (status); +} + + +/* Parse the string and invoke the callback each time a $VARIABLE or + ${VARIABLE} construct is seen, where VARIABLE is a nonempty sequence + of ASCII alphanumeric/underscore characters, starting with an ASCII + alphabetic/underscore character. + We allow only ASCII characters, to avoid dependencies w.r.t. the current + encoding: While "${\xe0}" looks like a variable access in ISO-8859-1 + encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030, + SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these + encodings. */ +static void +find_variables (const char *string, + void (*callback) (const char *var_ptr, size_t var_len)) +{ + for (; *string != '\0';) + if (*string++ == '$') + { + const char *variable_start; + const char *variable_end; + int valid; + char c; + + if (*string == '{') + string++; + + variable_start = string; + c = *string; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') + { + do + c = *++string; + while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') || c == '_'); + variable_end = string; + + if (variable_start[-1] == '{') + { + if (*string == '}') + { + string++; + valid = 1; + } + else + valid = 0; + } + else + valid = 1; + + if (valid) + callback (variable_start, variable_end - variable_start); + } + } +} + + +/* Print a variable to stdout, followed by a newline. */ +static void +print_variable (const char *var_ptr, size_t var_len) +{ + fwrite (var_ptr, var_len, 1, stdout); + putchar ('\n'); +} + +/* Print the variables contained in STRING to stdout, each one followed by a + newline. */ +static void +print_variables (const char *string) +{ + find_variables (string, &print_variable); +} + + +/* Type describing list of immutable strings, + implemented using a dynamic array. */ +typedef struct string_list_ty string_list_ty; +struct string_list_ty +{ + const char **item; + size_t nitems; + size_t nitems_max; +}; + +/* Initialize an empty list of strings. */ +static inline void +string_list_init (string_list_ty *slp) +{ + slp->item = NULL; + slp->nitems = 0; + slp->nitems_max = 0; +} + +/* Append a single string to the end of a list of strings. */ +static inline void +string_list_append (string_list_ty *slp, const char *s) +{ + /* Grow the list. */ + if (slp->nitems >= slp->nitems_max) + { + size_t nbytes; + + slp->nitems_max = slp->nitems_max * 2 + 4; + nbytes = slp->nitems_max * sizeof (slp->item[0]); + slp->item = (const char **) xrealloc (slp->item, nbytes); + } + + /* Add the string to the end of the list. */ + slp->item[slp->nitems++] = s; +} + +/* Compare two strings given by reference. */ +static int +cmp_string (const void *pstr1, const void *pstr2) +{ + const char *str1 = *(const char **)pstr1; + const char *str2 = *(const char **)pstr2; + + return strcmp (str1, str2); +} + +/* Sort a list of strings. */ +static inline void +string_list_sort (string_list_ty *slp) +{ + if (slp->nitems > 0) + qsort (slp->item, slp->nitems, sizeof (slp->item[0]), cmp_string); +} + +/* Test whether a string list contains a given string. */ +static inline int +string_list_member (const string_list_ty *slp, const char *s) +{ + size_t j; + + for (j = 0; j < slp->nitems; ++j) + if (strcmp (slp->item[j], s) == 0) + return 1; + return 0; +} + +/* Test whether a sorted string list contains a given string. */ +static int +sorted_string_list_member (const string_list_ty *slp, const char *s) +{ + size_t j1, j2; + + j1 = 0; + j2 = slp->nitems; + if (j2 > 0) + { + /* Binary search. */ + while (j2 - j1 > 1) + { + /* Here we know that if s is in the list, it is at an index j + with j1 <= j < j2. */ + size_t j = (j1 + j2) >> 1; + int result = strcmp (slp->item[j], s); + + if (result > 0) + j2 = j; + else if (result == 0) + return 1; + else + j1 = j + 1; + } + if (j2 > j1) + if (strcmp (slp->item[j1], s) == 0) + return 1; + } + return 0; +} + +/* Destroy a list of strings. */ +static inline void +string_list_destroy (string_list_ty *slp) +{ + size_t j; + + for (j = 0; j < slp->nitems; ++j) + free ((char *) slp->item[j]); + if (slp->item != NULL) + free (slp->item); +} + + +/* Set of variables on which to perform substitution. + Used only if !all_variables. */ +static string_list_ty variables_set; + +/* Adds a variable to variables_set. */ +static void +note_variable (const char *var_ptr, size_t var_len) +{ + char *string = (char *) xmalloc (var_len + 1); + memcpy (string, var_ptr, var_len); + string[var_len] = '\0'; + + string_list_append (&variables_set, string); +} + +/* Stores the variables occurring in the string in variables_set. */ +static void +note_variables (const char *string) +{ + string_list_init (&variables_set); + find_variables (string, ¬e_variable); + string_list_sort (&variables_set); +} + + +static int +do_getc () +{ + int c = getc (stdin); + + if (c == EOF) + { + if (ferror (stdin)) + error (EXIT_FAILURE, errno, _("\ +error while reading \"%s\""), _("standard input")); + } + + return c; +} + +static inline void +do_ungetc (int c) +{ + if (c != EOF) + ungetc (c, stdin); +} + +/* Copies stdin to stdout, performing substitutions. */ +static void +subst_from_stdin () +{ + static char *buffer; + static size_t bufmax; + static size_t buflen; + int c; + + for (;;) + { + c = do_getc (); + if (c == EOF) + break; + /* Look for $VARIABLE or ${VARIABLE}. */ + if (c == '$') + { + int opening_brace = 0; + int closing_brace = 0; + + c = do_getc (); + if (c == '{') + { + opening_brace = 1; + c = do_getc (); + } + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') + { + int valid; + + /* Accumulate the VARIABLE in buffer. */ + buflen = 0; + do + { + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen++] = c; + + c = do_getc (); + } + while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') || c == '_'); + + if (opening_brace) + { + if (c == '}') + { + closing_brace = 1; + valid = 1; + } + else + { + valid = 0; + do_ungetc (c); + } + } + else + { + valid = 1; + do_ungetc (c); + } + + if (valid) + { + /* Terminate the variable in the buffer. */ + if (buflen >= bufmax) + { + bufmax = 2 * bufmax + 10; + buffer = xrealloc (buffer, bufmax); + } + buffer[buflen] = '\0'; + + /* Test whether the variable shall be substituted. */ + if (!all_variables + && !sorted_string_list_member (&variables_set, buffer)) + valid = 0; + } + + if (valid) + { + /* Substitute the variable's value from the environment. */ + const char *env_value = getenv (buffer); + + if (env_value != NULL) + fputs (env_value, stdout); + } + else + { + /* Perform no substitution at all. Since the buffered input + contains no other '$' than at the start, we can just + output all the buffered contents. */ + putchar ('$'); + if (opening_brace) + putchar ('{'); + fwrite (buffer, buflen, 1, stdout); + if (closing_brace) + putchar ('}'); + } + } + else + { + do_ungetc (c); + putchar ('$'); + if (opening_brace) + putchar ('{'); + } + } + else + putchar (c); + } +}