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. */ 44static bool all_variables; 45 46/* Long options. */ 47static 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. */ 56static void usage (int status) 57#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) 58 __attribute__ ((noreturn)) 59#endif 60; 61static void print_variables (const char *string); 62static void note_variables (const char *string); 63static void subst_from_stdin (void); 64 65int 66main (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\ 115This is free software; see the source for copying conditions. There is NO\n\ 116warranty; 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. */ 168static void 169usage (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 (_("\ 178Usage: %s [OPTION] [SHELL-FORMAT]\n\ 179"), program_name); 180 printf ("\n"); 181 /* xgettext: no-wrap */ 182 printf (_("\ 183Substitutes the values of environment variables.\n")); 184 printf ("\n"); 185 /* xgettext: no-wrap */ 186 printf (_("\ 187Operation 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 (_("\ 194Informative 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 (_("\ 204In normal operation mode, standard input is copied to standard output,\n\ 205with references to environment variables of the form $VARIABLE or ${VARIABLE}\n\ 206being replaced with the corresponding values. If a SHELL-FORMAT is given,\n\ 207only those environment variables that are referenced in SHELL-FORMAT are\n\ 208substituted; otherwise all environment variables references occurring in\n\ 209standard input are substituted.\n")); 210 printf ("\n"); 211 /* xgettext: no-wrap */ 212 printf (_("\ 213When --variables is used, standard input is ignored, and the output consists\n\ 214of 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. */ 232static void 233find_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. */ 278static void 279print_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. */ 287static void 288print_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. */ 296typedef struct string_list_ty string_list_ty; 297struct 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. */ 305static inline void 306string_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. */ 314static inline void 315string_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. */ 332static int 333cmp_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. */ 342static inline void 343string_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. */ 350static inline int 351string_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. */ 362static int 363sorted_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. */ 394static inline void 395string_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. */ 408static string_list_ty variables_set; 409 410/* Adds a variable to variables_set. */ 411static void 412note_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. */ 422static void 423note_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 431static int 432do_getc () 433{ 434 int c = getc (stdin); 435 436 if (c == EOF) 437 { 438 if (ferror (stdin)) 439 error (EXIT_FAILURE, errno, _("\ 440error while reading \"%s\""), _("standard input")); 441 } 442 443 return c; 444} 445 446static inline void 447do_ungetc (int c) 448{ 449 if (c != EOF) 450 ungetc (c, stdin); 451} 452 453/* Copies stdin to stdout, performing substitutions. */ 454static void 455subst_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