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