(root)/
gettext-0.22.4/
gettext-runtime/
src/
envsubst.c
       1  /* Substitution of environment variables in shell format strings.
       2     Copyright (C) 2003-2007, 2012, 2018-2023 Free Software Foundation, Inc.
       3     Written by Bruno Haible <bruno@clisp.org>, 2003.
       4  
       5     This program is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published by
       7     the Free Software Foundation; either version 3 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  #ifdef HAVE_CONFIG_H
      19  # include <config.h>
      20  #endif
      21  
      22  #include <errno.h>
      23  #include <getopt.h>
      24  #include <stdbool.h>
      25  #include <stdio.h>
      26  #include <stdlib.h>
      27  #include <string.h>
      28  #include <unistd.h>
      29  #include <locale.h>
      30  
      31  #include "noreturn.h"
      32  #include "closeout.h"
      33  #include "error.h"
      34  #include "progname.h"
      35  #include "relocatable.h"
      36  #include "basename-lgpl.h"
      37  #include "xalloc.h"
      38  #include "propername.h"
      39  #include "binary-io.h"
      40  #include "gettext.h"
      41  
      42  #define _(str) gettext (str)
      43  
      44  /* If true, substitution shall be performed on all variables.  */
      45  static bool all_variables;
      46  
      47  /* Long options.  */
      48  static const struct option long_options[] =
      49  {
      50    { "help", no_argument, NULL, 'h' },
      51    { "variables", no_argument, NULL, 'v' },
      52    { "version", no_argument, NULL, 'V' },
      53    { NULL, 0, NULL, 0 }
      54  };
      55  
      56  /* Forward declaration of local functions.  */
      57  _GL_NORETURN_FUNC static void usage (int status);
      58  static void print_variables (const char *string);
      59  static void note_variables (const char *string);
      60  static void subst_from_stdin (void);
      61  
      62  int
      63  main (int argc, char *argv[])
      64  {
      65    /* Default values for command line options.  */
      66    bool show_variables = false;
      67    bool do_help = false;
      68    bool do_version = false;
      69  
      70    int opt;
      71  
      72    /* Set program name for message texts.  */
      73    set_program_name (argv[0]);
      74  
      75    /* Set locale via LC_ALL.  */
      76    setlocale (LC_ALL, "");
      77  
      78    /* Set the text message domain.  */
      79    bindtextdomain (PACKAGE, relocate (LOCALEDIR));
      80    textdomain (PACKAGE);
      81  
      82    /* Ensure that write errors on stdout are detected.  */
      83    atexit (close_stdout);
      84  
      85    /* Parse command line options.  */
      86    while ((opt = getopt_long (argc, argv, "hvV", long_options, NULL)) != EOF)
      87      switch (opt)
      88      {
      89      case '\0':          /* Long option.  */
      90        break;
      91      case 'h':
      92        do_help = true;
      93        break;
      94      case 'v':
      95        show_variables = true;
      96        break;
      97      case 'V':
      98        do_version = true;
      99        break;
     100      default:
     101        usage (EXIT_FAILURE);
     102      }
     103  
     104    /* Version information is requested.  */
     105    if (do_version)
     106      {
     107        printf ("%s (GNU %s) %s\n", last_component (program_name),
     108                PACKAGE, VERSION);
     109        /* xgettext: no-wrap */
     110        printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
     111  License GPLv3+: GNU GPL version 3 or later <%s>\n\
     112  This is free software: you are free to change and redistribute it.\n\
     113  There is NO WARRANTY, to the extent permitted by law.\n\
     114  "),
     115                "2003-2023", "https://gnu.org/licenses/gpl.html");
     116        printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
     117        exit (EXIT_SUCCESS);
     118      }
     119  
     120    /* Help is requested.  */
     121    if (do_help)
     122      usage (EXIT_SUCCESS);
     123  
     124    if (argc - optind > 1)
     125      error (EXIT_FAILURE, 0, _("too many arguments"));
     126  
     127    /* Distinguish the two main operation modes.  */
     128    if (show_variables)
     129      {
     130        /* Output only the variables.  */
     131        switch (argc - optind)
     132          {
     133          case 1:
     134            break;
     135          case 0:
     136            error (EXIT_FAILURE, 0, _("missing arguments"));
     137          default:
     138            abort ();
     139          }
     140  
     141        /* The result is most often used in shell `...` expressions.
     142           Therefore, on native Windows, don't produce CR/LF newlines.  */
     143        set_binary_mode (STDOUT_FILENO, O_BINARY);
     144  
     145        print_variables (argv[optind++]);
     146      }
     147    else
     148      {
     149        /* Actually perform the substitutions.  */
     150        switch (argc - optind)
     151          {
     152          case 1:
     153            all_variables = false;
     154            note_variables (argv[optind++]);
     155            break;
     156          case 0:
     157            all_variables = true;
     158            break;
     159          default:
     160            abort ();
     161          }
     162        subst_from_stdin ();
     163      }
     164  
     165    exit (EXIT_SUCCESS);
     166  }
     167  
     168  
     169  /* Display usage information and exit.  */
     170  static void
     171  usage (int status)
     172  {
     173    if (status != EXIT_SUCCESS)
     174      fprintf (stderr, _("Try '%s --help' for more information.\n"),
     175               program_name);
     176    else
     177      {
     178        /* xgettext: no-wrap */
     179        printf (_("\
     180  Usage: %s [OPTION] [SHELL-FORMAT]\n\
     181  "), program_name);
     182        printf ("\n");
     183        /* xgettext: no-wrap */
     184        printf (_("\
     185  Substitutes the values of environment variables.\n"));
     186        printf ("\n");
     187        /* xgettext: no-wrap */
     188        printf (_("\
     189  Operation mode:\n"));
     190        /* xgettext: no-wrap */
     191        printf (_("\
     192    -v, --variables             output the variables occurring in SHELL-FORMAT\n"));
     193        printf ("\n");
     194        /* xgettext: no-wrap */
     195        printf (_("\
     196  Informative output:\n"));
     197        /* xgettext: no-wrap */
     198        printf (_("\
     199    -h, --help                  display this help and exit\n"));
     200        /* xgettext: no-wrap */
     201        printf (_("\
     202    -V, --version               output version information and exit\n"));
     203        printf ("\n");
     204        /* xgettext: no-wrap */
     205        printf (_("\
     206  In normal operation mode, standard input is copied to standard output,\n\
     207  with references to environment variables of the form $VARIABLE or ${VARIABLE}\n\
     208  being replaced with the corresponding values.  If a SHELL-FORMAT is given,\n\
     209  only those environment variables that are referenced in SHELL-FORMAT are\n\
     210  substituted; otherwise all environment variables references occurring in\n\
     211  standard input are substituted.\n"));
     212        printf ("\n");
     213        /* xgettext: no-wrap */
     214        printf (_("\
     215  When --variables is used, standard input is ignored, and the output consists\n\
     216  of the environment variables that are referenced in SHELL-FORMAT, one per line.\n"));
     217        printf ("\n");
     218        /* TRANSLATORS: The first placeholder is the web address of the Savannah
     219           project of this package.  The second placeholder is the bug-reporting
     220           email address for this package.  Please add _another line_ saying
     221           "Report translation bugs to <...>\n" with the address for translation
     222           bugs (typically your translation team's web or email address).  */
     223        printf(_("\
     224  Report bugs in the bug tracker at <%s>\n\
     225  or by email to <%s>.\n"),
     226               "https://savannah.gnu.org/projects/gettext",
     227               "bug-gettext@gnu.org");
     228      }
     229  
     230    exit (status);
     231  }
     232  
     233  
     234  /* Parse the string and invoke the callback each time a $VARIABLE or
     235     ${VARIABLE} construct is seen, where VARIABLE is a nonempty sequence
     236     of ASCII alphanumeric/underscore characters, starting with an ASCII
     237     alphabetic/underscore character.
     238     We allow only ASCII characters, to avoid dependencies w.r.t. the current
     239     encoding: While "${\xe0}" looks like a variable access in ISO-8859-1
     240     encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030,
     241     SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these
     242     encodings.  */
     243  static void
     244  find_variables (const char *string,
     245                  void (*callback) (const char *var_ptr, size_t var_len))
     246  {
     247    for (; *string != '\0';)
     248      if (*string++ == '$')
     249        {
     250          const char *variable_start;
     251          const char *variable_end;
     252          bool valid;
     253          char c;
     254  
     255          if (*string == '{')
     256            string++;
     257  
     258          variable_start = string;
     259          c = *string;
     260          if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
     261            {
     262              do
     263                c = *++string;
     264              while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
     265                     || (c >= '0' && c <= '9') || c == '_');
     266              variable_end = string;
     267  
     268              if (variable_start[-1] == '{')
     269                {
     270                  if (*string == '}')
     271                    {
     272                      string++;
     273                      valid = true;
     274                    }
     275                  else
     276                    valid = false;
     277                }
     278              else
     279                valid = true;
     280  
     281              if (valid)
     282                callback (variable_start, variable_end - variable_start);
     283            }
     284        }
     285  }
     286  
     287  
     288  /* Print a variable to stdout, followed by a newline.  */
     289  static void
     290  print_variable (const char *var_ptr, size_t var_len)
     291  {
     292    fwrite (var_ptr, var_len, 1, stdout);
     293    putchar ('\n');
     294  }
     295  
     296  /* Print the variables contained in STRING to stdout, each one followed by a
     297     newline.  */
     298  static void
     299  print_variables (const char *string)
     300  {
     301    find_variables (string, &print_variable);
     302  }
     303  
     304  
     305  /* Type describing list of immutable strings,
     306     implemented using a dynamic array.  */
     307  typedef struct string_list_ty string_list_ty;
     308  struct string_list_ty
     309  {
     310    const char **item;
     311    size_t nitems;
     312    size_t nitems_max;
     313  };
     314  
     315  /* Initialize an empty list of strings.  */
     316  static inline void
     317  string_list_init (string_list_ty *slp)
     318  {
     319    slp->item = NULL;
     320    slp->nitems = 0;
     321    slp->nitems_max = 0;
     322  }
     323  
     324  /* Append a single string to the end of a list of strings.  */
     325  static inline void
     326  string_list_append (string_list_ty *slp, const char *s)
     327  {
     328    /* Grow the list.  */
     329    if (slp->nitems >= slp->nitems_max)
     330      {
     331        size_t nbytes;
     332  
     333        slp->nitems_max = slp->nitems_max * 2 + 4;
     334        nbytes = slp->nitems_max * sizeof (slp->item[0]);
     335        slp->item = (const char **) xrealloc (slp->item, nbytes);
     336      }
     337  
     338    /* Add the string to the end of the list.  */
     339    slp->item[slp->nitems++] = s;
     340  }
     341  
     342  /* Compare two strings given by reference.  */
     343  static int
     344  cmp_string (const void *pstr1, const void *pstr2)
     345  {
     346    const char *str1 = *(const char * const *)pstr1;
     347    const char *str2 = *(const char * const *)pstr2;
     348  
     349    return strcmp (str1, str2);
     350  }
     351  
     352  /* Sort a list of strings.  */
     353  static inline void
     354  string_list_sort (string_list_ty *slp)
     355  {
     356    if (slp->nitems > 0)
     357      qsort (slp->item, slp->nitems, sizeof (slp->item[0]), cmp_string);
     358  }
     359  
     360  /* Test whether a string list contains a given string.  */
     361  static inline int
     362  string_list_member (const string_list_ty *slp, const char *s)
     363  {
     364    size_t j;
     365  
     366    for (j = 0; j < slp->nitems; ++j)
     367      if (strcmp (slp->item[j], s) == 0)
     368        return 1;
     369    return 0;
     370  }
     371  
     372  /* Test whether a sorted string list contains a given string.  */
     373  static int
     374  sorted_string_list_member (const string_list_ty *slp, const char *s)
     375  {
     376    size_t j1, j2;
     377  
     378    j1 = 0;
     379    j2 = slp->nitems;
     380    if (j2 > 0)
     381      {
     382        /* Binary search.  */
     383        while (j2 - j1 > 1)
     384          {
     385            /* Here we know that if s is in the list, it is at an index j
     386               with j1 <= j < j2.  */
     387            size_t j = (j1 + j2) >> 1;
     388            int result = strcmp (slp->item[j], s);
     389  
     390            if (result > 0)
     391              j2 = j;
     392            else if (result == 0)
     393              return 1;
     394            else
     395              j1 = j + 1;
     396          }
     397        if (j2 > j1)
     398          if (strcmp (slp->item[j1], s) == 0)
     399            return 1;
     400      }
     401    return 0;
     402  }
     403  
     404  /* Destroy a list of strings.  */
     405  static inline void
     406  string_list_destroy (string_list_ty *slp)
     407  {
     408    size_t j;
     409  
     410    for (j = 0; j < slp->nitems; ++j)
     411      free ((char *) slp->item[j]);
     412    if (slp->item != NULL)
     413      free (slp->item);
     414  }
     415  
     416  
     417  /* Set of variables on which to perform substitution.
     418     Used only if !all_variables.  */
     419  static string_list_ty variables_set;
     420  
     421  /* Adds a variable to variables_set.  */
     422  static void
     423  note_variable (const char *var_ptr, size_t var_len)
     424  {
     425    char *string = XNMALLOC (var_len + 1, char);
     426    memcpy (string, var_ptr, var_len);
     427    string[var_len] = '\0';
     428  
     429    string_list_append (&variables_set, string);
     430  }
     431  
     432  /* Stores the variables occurring in the string in variables_set.  */
     433  static void
     434  note_variables (const char *string)
     435  {
     436    string_list_init (&variables_set);
     437    find_variables (string, &note_variable);
     438    string_list_sort (&variables_set);
     439  }
     440  
     441  
     442  static int
     443  do_getc ()
     444  {
     445    int c = getc (stdin);
     446  
     447    if (c == EOF)
     448      {
     449        if (ferror (stdin))
     450          error (EXIT_FAILURE, errno,
     451                 _("error while reading \"%s\""), _("standard input"));
     452      }
     453  
     454    return c;
     455  }
     456  
     457  static inline void
     458  do_ungetc (int c)
     459  {
     460    if (c != EOF)
     461      ungetc (c, stdin);
     462  }
     463  
     464  /* Copies stdin to stdout, performing substitutions.  */
     465  static void
     466  subst_from_stdin ()
     467  {
     468    static char *buffer;
     469    static size_t bufmax;
     470    static size_t buflen;
     471    int c;
     472  
     473    for (;;)
     474      {
     475        c = do_getc ();
     476        if (c == EOF)
     477          break;
     478        /* Look for $VARIABLE or ${VARIABLE}.  */
     479        if (c == '$')
     480          {
     481            bool opening_brace = false;
     482            bool closing_brace = false;
     483  
     484            c = do_getc ();
     485            if (c == '{')
     486              {
     487                opening_brace = true;
     488                c = do_getc ();
     489              }
     490            if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
     491              {
     492                bool valid;
     493  
     494                /* Accumulate the VARIABLE in buffer.  */
     495                buflen = 0;
     496                do
     497                  {
     498                    if (buflen >= bufmax)
     499                      {
     500                        bufmax = 2 * bufmax + 10;
     501                        buffer = xrealloc (buffer, bufmax);
     502                      }
     503                    buffer[buflen++] = c;
     504  
     505                    c = do_getc ();
     506                  }
     507                while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
     508                       || (c >= '0' && c <= '9') || c == '_');
     509  
     510                if (opening_brace)
     511                  {
     512                    if (c == '}')
     513                      {
     514                        closing_brace = true;
     515                        valid = true;
     516                      }
     517                    else
     518                      {
     519                        valid = false;
     520                        do_ungetc (c);
     521                      }
     522                  }
     523                else
     524                  {
     525                    valid = true;
     526                    do_ungetc (c);
     527                  }
     528  
     529                if (valid)
     530                  {
     531                    /* Terminate the variable in the buffer.  */
     532                    if (buflen >= bufmax)
     533                      {
     534                        bufmax = 2 * bufmax + 10;
     535                        buffer = xrealloc (buffer, bufmax);
     536                      }
     537                    buffer[buflen] = '\0';
     538  
     539                    /* Test whether the variable shall be substituted.  */
     540                    if (!all_variables
     541                        && !sorted_string_list_member (&variables_set, buffer))
     542                      valid = false;
     543                  }
     544  
     545                if (valid)
     546                  {
     547                    /* Substitute the variable's value from the environment.  */
     548                    const char *env_value = getenv (buffer);
     549  
     550                    if (env_value != NULL)
     551                      fputs (env_value, stdout);
     552                  }
     553                else
     554                  {
     555                    /* Perform no substitution at all.  Since the buffered input
     556                       contains no other '$' than at the start, we can just
     557                       output all the buffered contents.  */
     558                    putchar ('$');
     559                    if (opening_brace)
     560                      putchar ('{');
     561                    fwrite (buffer, buflen, 1, stdout);
     562                    if (closing_brace)
     563                      putchar ('}');
     564                  }
     565              }
     566            else
     567              {
     568                do_ungetc (c);
     569                putchar ('$');
     570                if (opening_brace)
     571                  putchar ('{');
     572              }
     573          }
     574        else
     575          putchar (c);
     576      }
     577  }