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