1 /* Substitution of environment variables in shell format strings.
2 Copyright (C) 2003-2007 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2003.
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.
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.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
33 #include "relocatable.h"
36 #include "propername.h"
39 #define _(str) gettext (str)
41 /* If true, substitution shall be performed on all variables. */
42 static bool all_variables;
45 static const struct option long_options[] =
47 { "help", no_argument, NULL, 'h' },
48 { "variables", no_argument, NULL, 'v' },
49 { "version", no_argument, NULL, 'V' },
53 /* Forward declaration of local functions. */
54 static void usage (int status)
55 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
56 __attribute__ ((noreturn))
59 static void print_variables (const char *string);
60 static void note_variables (const char *string);
61 static void subst_from_stdin (void);
64 main (int argc, char *argv[])
66 /* Default values for command line options. */
67 bool show_variables = false;
69 bool do_version = false;
73 /* Set program name for message texts. */
74 set_program_name (argv[0]);
77 /* Set locale via LC_ALL. */
78 setlocale (LC_ALL, "");
81 /* Set the text message domain. */
82 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
85 /* Ensure that write errors on stdout are detected. */
86 atexit (close_stdout);
88 /* Parse command line options. */
89 while ((opt = getopt_long (argc, argv, "hvV", long_options, NULL)) != EOF)
92 case '\0': /* Long option. */
98 show_variables = true;
104 usage (EXIT_FAILURE);
107 /* Version information is requested. */
110 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
111 /* xgettext: no-wrap */
112 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
113 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
114 This is free software: you are free to change and redistribute it.\n\
115 There is NO WARRANTY, to the extent permitted by law.\n\
118 printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
122 /* Help is requested. */
124 usage (EXIT_SUCCESS);
126 if (argc - optind > 1)
127 error (EXIT_FAILURE, 0, _("too many arguments"));
129 /* Distinguish the two main operation modes. */
132 /* Output only the variables. */
133 switch (argc - optind)
138 error (EXIT_FAILURE, 0, _("missing arguments"));
142 print_variables (argv[optind++]);
146 /* Actually perform the substitutions. */
147 switch (argc - optind)
150 all_variables = false;
151 note_variables (argv[optind++]);
154 all_variables = true;
166 /* Display usage information and exit. */
170 if (status != EXIT_SUCCESS)
171 fprintf (stderr, _("Try `%s --help' for more information.\n"),
175 /* xgettext: no-wrap */
177 Usage: %s [OPTION] [SHELL-FORMAT]\n\
180 /* xgettext: no-wrap */
182 Substitutes the values of environment variables.\n"));
184 /* xgettext: no-wrap */
186 Operation mode:\n"));
187 /* xgettext: no-wrap */
189 -v, --variables output the variables occurring in SHELL-FORMAT\n"));
191 /* xgettext: no-wrap */
193 Informative output:\n"));
194 /* xgettext: no-wrap */
196 -h, --help display this help and exit\n"));
197 /* xgettext: no-wrap */
199 -V, --version output version information and exit\n"));
201 /* xgettext: no-wrap */
203 In normal operation mode, standard input is copied to standard output,\n\
204 with references to environment variables of the form $VARIABLE or ${VARIABLE}\n\
205 being replaced with the corresponding values. If a SHELL-FORMAT is given,\n\
206 only those environment variables that are referenced in SHELL-FORMAT are\n\
207 substituted; otherwise all environment variables references occurring in\n\
208 standard input are substituted.\n"));
210 /* xgettext: no-wrap */
212 When --variables is used, standard input is ignored, and the output consists\n\
213 of the environment variables that are referenced in SHELL-FORMAT, one per line.\n"));
215 /* TRANSLATORS: The placeholder indicates the bug-reporting address
216 for this package. Please add _another line_ saying
217 "Report translation bugs to <...>\n" with the address for translation
218 bugs (typically your translation team's web or email address). */
219 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout);
226 /* Parse the string and invoke the callback each time a $VARIABLE or
227 ${VARIABLE} construct is seen, where VARIABLE is a nonempty sequence
228 of ASCII alphanumeric/underscore characters, starting with an ASCII
229 alphabetic/underscore character.
230 We allow only ASCII characters, to avoid dependencies w.r.t. the current
231 encoding: While "${\xe0}" looks like a variable access in ISO-8859-1
232 encoding, it doesn't look like one in the BIG5, BIG5-HKSCS, GBK, GB18030,
233 SHIFT_JIS, JOHAB encodings, because \xe0\x7d is a single character in these
236 find_variables (const char *string,
237 void (*callback) (const char *var_ptr, size_t var_len))
239 for (; *string != '\0';)
240 if (*string++ == '$')
242 const char *variable_start;
243 const char *variable_end;
250 variable_start = string;
252 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
256 while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
257 || (c >= '0' && c <= '9') || c == '_');
258 variable_end = string;
260 if (variable_start[-1] == '{')
274 callback (variable_start, variable_end - variable_start);
280 /* Print a variable to stdout, followed by a newline. */
282 print_variable (const char *var_ptr, size_t var_len)
284 fwrite (var_ptr, var_len, 1, stdout);
288 /* Print the variables contained in STRING to stdout, each one followed by a
291 print_variables (const char *string)
293 find_variables (string, &print_variable);
297 /* Type describing list of immutable strings,
298 implemented using a dynamic array. */
299 typedef struct string_list_ty string_list_ty;
300 struct string_list_ty
307 /* Initialize an empty list of strings. */
309 string_list_init (string_list_ty *slp)
316 /* Append a single string to the end of a list of strings. */
318 string_list_append (string_list_ty *slp, const char *s)
321 if (slp->nitems >= slp->nitems_max)
325 slp->nitems_max = slp->nitems_max * 2 + 4;
326 nbytes = slp->nitems_max * sizeof (slp->item[0]);
327 slp->item = (const char **) xrealloc (slp->item, nbytes);
330 /* Add the string to the end of the list. */
331 slp->item[slp->nitems++] = s;
334 /* Compare two strings given by reference. */
336 cmp_string (const void *pstr1, const void *pstr2)
338 const char *str1 = *(const char **)pstr1;
339 const char *str2 = *(const char **)pstr2;
341 return strcmp (str1, str2);
344 /* Sort a list of strings. */
346 string_list_sort (string_list_ty *slp)
349 qsort (slp->item, slp->nitems, sizeof (slp->item[0]), cmp_string);
352 /* Test whether a string list contains a given string. */
354 string_list_member (const string_list_ty *slp, const char *s)
358 for (j = 0; j < slp->nitems; ++j)
359 if (strcmp (slp->item[j], s) == 0)
364 /* Test whether a sorted string list contains a given string. */
366 sorted_string_list_member (const string_list_ty *slp, const char *s)
377 /* Here we know that if s is in the list, it is at an index j
378 with j1 <= j < j2. */
379 size_t j = (j1 + j2) >> 1;
380 int result = strcmp (slp->item[j], s);
384 else if (result == 0)
390 if (strcmp (slp->item[j1], s) == 0)
396 /* Destroy a list of strings. */
398 string_list_destroy (string_list_ty *slp)
402 for (j = 0; j < slp->nitems; ++j)
403 free ((char *) slp->item[j]);
404 if (slp->item != NULL)
409 /* Set of variables on which to perform substitution.
410 Used only if !all_variables. */
411 static string_list_ty variables_set;
413 /* Adds a variable to variables_set. */
415 note_variable (const char *var_ptr, size_t var_len)
417 char *string = XNMALLOC (var_len + 1, char);
418 memcpy (string, var_ptr, var_len);
419 string[var_len] = '\0';
421 string_list_append (&variables_set, string);
424 /* Stores the variables occurring in the string in variables_set. */
426 note_variables (const char *string)
428 string_list_init (&variables_set);
429 find_variables (string, ¬e_variable);
430 string_list_sort (&variables_set);
437 int c = getc (stdin);
442 error (EXIT_FAILURE, errno, _("\
443 error while reading \"%s\""), _("standard input"));
456 /* Copies stdin to stdout, performing substitutions. */
461 static size_t bufmax;
462 static size_t buflen;
470 /* Look for $VARIABLE or ${VARIABLE}. */
473 bool opening_brace = false;
474 bool closing_brace = false;
479 opening_brace = true;
482 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
486 /* Accumulate the VARIABLE in buffer. */
490 if (buflen >= bufmax)
492 bufmax = 2 * bufmax + 10;
493 buffer = xrealloc (buffer, bufmax);
495 buffer[buflen++] = c;
499 while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
500 || (c >= '0' && c <= '9') || c == '_');
506 closing_brace = true;
523 /* Terminate the variable in the buffer. */
524 if (buflen >= bufmax)
526 bufmax = 2 * bufmax + 10;
527 buffer = xrealloc (buffer, bufmax);
529 buffer[buflen] = '\0';
531 /* Test whether the variable shall be substituted. */
533 && !sorted_string_list_member (&variables_set, buffer))
539 /* Substitute the variable's value from the environment. */
540 const char *env_value = getenv (buffer);
542 if (env_value != NULL)
543 fputs (env_value, stdout);
547 /* Perform no substitution at all. Since the buffered input
548 contains no other '$' than at the start, we can just
549 output all the buffered contents. */
553 fwrite (buffer, buflen, 1, stdout);