DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T s

⟦903219cec⟧ TextFile

    Length: 41826 (0xa362)
    Types: TextFile
    Names: »subst.c«

Derivation

└─⟦a05ed705a⟧ Bits:30007078 DKUUG GNU 2/12/89
    └─⟦ca1f037a2⟧ »./bash-1.04.tar.Z« 
        └─⟦46465a4db⟧ 
            └─⟦this⟧ »bash-1.04/subst.c« 

TextFile

/* substitutions.c -- The part of the shell that does parameter,
   command, and globbing substitutions. */

/* Copyright (C) 1987,1989 Free Software Foundation, Inc.

This file is part of GNU Bash, the Bourne Again SHell.

Bash is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 1, or (at your option) any later
version.

Bash is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License along
with Bash; see the file COPYING.  If not, write to the Free Software
Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */

#include <stdio.h>
#include <pwd.h>
#include <fcntl.h>
#include "shell.h"
#include "flags.h"
#include "alias.h"
#include <readline/history.h>

/* The size that strings change by. */
#define DEFAULT_ARRAY_SIZE 512

/* Some forward declarations. */

extern WORD_LIST *expand_string (), *expand_word (), *list_string ();
extern char *string_list ();
extern WORD_DESC *make_word ();


/* **************************************************************** */
/*								    */
/*			Utility Functions			    */
/*								    */
/* **************************************************************** */


/* Cons a new string from STRING starting at START and ending at END,
   not including END. */
char *
substring (string, start, end)
     char *string;
     int start, end;
{
  register int len = end - start;
  register char *result = (char *)xmalloc (len + 1);




























  strncpy (result, string + start, len);
  result[len] = '\0';
  return (result);
}

/* Just like string_extract, but doesn't hack backslashes or any of
   that other stuff. */
char *
string_extract_verbatim (string, sindex, charlist)
     char *string, *charlist;
     int *sindex;
{
  register int i = *sindex;
  int c;
  char *temp;

  while ((c = string[i]) && (!member (c, charlist))) i++;
  temp = (char *)xmalloc (1 + (i - *sindex));
  strncpy (temp, string + (*sindex), i - (*sindex));
  temp[i - (*sindex)] = '\0';
  *sindex = i;
  return (temp);
}

/* Extract a substring from STRING, starting at INDEX and ending with
   one of the characters in CHARLIST.  Don't make the ending character
   part of the string.  Leave INDEX pointing at the ending character.
   Understand about backslashes in the string. */
char *
string_extract (string, sindex, charlist)
     char *string, *charlist;
     int *sindex;
{
  register int c, i = *sindex;
  char *temp;

  while (c = string[i]) {
    if (c == '\\')
      if (string[i + 1])
	i++;
      else
	break;
    else
      if (member (c, charlist))
	break;
    i++;
  }
  temp = (char *)xmalloc (1 + (i - *sindex));
  strncpy (temp, string + (*sindex), i - (*sindex));
  temp[i - (*sindex)] = '\0';
  *sindex = i;
  return (temp);
}

/* Remove backslashes which are quoting backquotes from STRING.  Modifies
   STRING, and returns a pointer to it. */
char *
de_backslash (string)
     char *string;
{
  register int i, l = strlen (string);

  for (i = 0; i < l; i++)
    if (string[i] == '\\' && string[i + 1] == '`')
      strcpy (&string[i], &string[i + 1]);
  return (string);
}

/* Extract the $( construct in STRING, and return a new string.
   Start extracting at (SINDEX) as if we had just seen "$(".
   Make (SINDEX) get the position just after the matching ")". */
char *
extract_command_subst (string, sindex)
     char *string;
     int *sindex;
{
  register int i, c, l;
  int pass_character, paren_level;
  int delimiter, delimited_paren_level;
  char *result;

  pass_character = delimiter = delimited_paren_level = 0;
  paren_level = 1;

  for (i = *sindex; c = string[i]; i++)
    {
      if (pass_character)
	{
	  pass_character = 0;
	  continue;
	}

      if (c == '\\')
	{
	  if ((delimiter == '"') &&
	      (member (string[i + 1], slashify_in_quotes)))
	    {
	    pass_next_character:
	      pass_character++;
	      continue;
	    }
	}

      if (!delimiter || delimiter == '"')
	{
	  if (c == '$' && (string[i + 1] == '('))
	    {
	      if (!delimiter)
		paren_level++;
	      else
		delimited_paren_level++;
	      goto pass_next_character;
	    }

	  if (c == ')')
	    {
	      if (delimiter && delimited_paren_level)
		delimited_paren_level--;

	      if (!delimiter)
		{
		  paren_level--;
		  if (paren_level == 0)
		    break;
		}
	    }
	}

      if (delimiter)
	{
	  if (c == delimiter)
	    delimiter = 0;
	  continue;
	}
      else
	{
	  if (c == '"' || c == '\'' || c == '\\')
	    delimiter = c;
	}
    }

  l = i - *sindex;
  result = (char *)xmalloc (1 + l);
  strncpy (result, &string[*sindex], l);
  result[l] = '\0';
  *sindex = i;

  if (!c && (delimiter || paren_level))
    {
      report_error ("Bad command substitution: `$(%s'", result);
      free (result);
      longjmp (top_level, DISCARD);
    }

  return (result);
}

/* An artifact for extracting the contents of a quoted string.  Since the
   string is about to be evaluated, we pass everything through, and only
   ignore the \" combination. */
char *
string_extract_double_quoted (string, sindex)
     char *string;
     int *sindex;
{
  register int c, j, i;
  char *temp = (char *)xmalloc (1 + strlen (string + (*sindex)));

  for (j = 0, i = *sindex; ((c = string[i]) && (c != '"')); i++)
    {
      if (c == '\\')
	{
	  c = string[++i];
	  temp[j++] = '\\';
	}
      temp[j++] = c;
    }
  temp[j] = '\0';
  *sindex = i;
  return (temp);
}

/* Extract the name of the variable to bind to from the assignment string. */
char *
assignment_name (string)
     char *string;
{
  int offset = assignment (string);
  char *temp;
  if (!offset) return (char *)NULL;
  temp = (char *)xmalloc (offset + 1);
  strncpy (temp, string, offset);
  temp[offset] = '\0';
  return (temp);
}

/* Return a single string of all the words in LIST. */
char *
string_list (list)
     WORD_LIST *list;
{
  char *result = (char *)NULL;

  while (list)
    {
      if (!result)
	result = savestring ("");

      result =
	(char *)xrealloc (result, 3 + strlen (result) + strlen (list->word->word));
      strcat (result, list->word->word);
      if (list->next) strcat (result, " ");
      list = list->next;
    }
  return (result);
}

/* Return the list of words present in STRING.  Separate the string into
   words at any of the characters found in SEPARATORS.  If QUOTED is
   non-zero then word in the list will have its quoted flag set, otherwise
   the quoted flag is left as make_word () deemed fit. */
WORD_LIST *
list_string (string, separators, quoted)
     register char *string, *separators;
     int quoted;
{
  WORD_LIST *result = (WORD_LIST *)NULL;
  char *current_word = (char *)NULL;
  int sindex =0;

  while (string[sindex] &&
	 (current_word =
	  string_extract_verbatim (string, &sindex, separators)))
    {
      if (strlen (current_word))
	{
	  register char *temp_string;

 	  for (temp_string = current_word; *temp_string; temp_string++)
	    if ((unsigned char)(*temp_string) == (unsigned char)0x80)
	      {
		strcpy (temp_string, temp_string + 1);
		temp_string--;
	      }

	  result = make_word_list (make_word (current_word), result);
	  if (quoted)
	    result->word->quoted++;
	}
      free (current_word);
      if (string[sindex]) sindex++;
    }
  return (WORD_LIST *)reverse_list (result);
}
  
/* Given STRING, an assignment string, get the value of the right side
   of the `=', and bind it to the left side. */
do_assignment (string)
     char *string;
{
  int offset = assignment (string);
  char *name = savestring (string);
  char *value = (char *)NULL;
  SHELL_VAR *entry = (SHELL_VAR *)NULL;

  if (name[offset] == '=')
    {
      char *temp, *tilde_expand (), *string_list ();
      WORD_LIST *list, *expand_string ();
      extern int disallow_filename_globbing;

      name[offset] = 0;
      temp = name + offset + 1;
      if (!disallow_filename_globbing)
	temp = tilde_expand (temp);
      else
	temp = savestring (temp);

      list = expand_string (temp, 0);
      if (list)
	{
	  value = string_list (list);
	  dispose_words (list);
	}
      free (temp);
    }

  if (!value) value = savestring ("");
  entry = bind_variable (name, value);

  /* Yes, here is where the special shell variables get tested for.
     Don't ask me, I just work here.  This is really stupid.  I would
     swear, but I've decided that that is an impolite thing to do in
     source that is to be distributed around the net, even if this code
     is totally brain-damaged. */

  /* if (strcmp (name, "PATH") == 0) Yeeecchhh!!!*/
  stupidly_hack_special_variables (name);

  if (entry)
    entry->attributes &= ~att_invisible;
  if (value) free (value);
  free (name);
}

/* Most of the substitutions must be done in parallel.  In order
   to avoid using tons of unclear goto's, I have some functions
   for manipulating malloc'ed strings.  They all take INDEX, a
   pointer to an integer which is the offset into the string
   where manipulation is taking place.  They also take SIZE, a
   pointer to an integer which is the current length of the
   character array for this string. */

/* Append SOURCE to TARGET at INDEX.  SIZE is the current amount
   of space allocated to TARGET.  SOURCE can be NULL, in which
   case nothing happens.  Gets rid of SOURCE by free ()ing it.
   Returns TARGET in case the location has changed. */
char *
sub_append_string (source, target, index, size)
     char *source, *target;
     int *index, *size;
{
  if (source)
    {
      if (!target)
	target = (char *)xmalloc (*size = DEFAULT_ARRAY_SIZE);

      while (strlen (source) >= (*size - *index))
	target = (char *)xrealloc (target, *size += DEFAULT_ARRAY_SIZE);

      strcat (target, source);
      *index += strlen (source);

      free (source);
    }
  return (target);
}

/* Append the textual representation of NUMBER to TARGET.
   INDEX and SIZE are as in SUB_APPEND_STRING. */
char *
sub_append_number (number, target, index, size)
     int number, *index, *size;
     char *target;
{
  char *temp = (char *)xmalloc (10);
  sprintf (temp, "%d", number);
  return (sub_append_string (temp, target, index, size));
}

/* Return the word list that corresponds to `$*'. */
WORD_LIST *
list_rest_of_args ()
{
  register WORD_LIST *list = (WORD_LIST *)NULL;
  register WORD_LIST *args = rest_of_args;
  int i;

  for (i = 1; i < 10; i++)
    if (dollar_vars[i])
      list = make_word_list (make_word (dollar_vars[i]), list);
  while (args)
    {
      list = make_word_list (make_word (args->word->word), list);
      args = args->next;
    }
  return ((WORD_LIST *)reverse_list (list));
}

/* Make a single large string out of the dollar digit variables,
   and the rest_of_args. */
char *
string_rest_of_args ()
{
  register WORD_LIST *list = list_rest_of_args ();
  char *string = string_list (list);

  dispose_words (list);
  return (string);
}

/* Expand STRING just as if you were expanding a word.  This also returns
   a list of words.  Note that filename globbing is *NOT* done for word
   or string expansion, just when the shell is expanding a command. */
WORD_LIST *
expand_string (string, quoted)
     char *string;
     int quoted;
{
  WORD_DESC *make_word (), *temp = make_word (string);
  WORD_LIST *expand_word ();
  WORD_LIST *value = expand_word (temp, quoted);

  dispose_word (temp);
  dequote (value);
  return (value);
}

/* I'm going to have to rewrite expansion because filename globbing is
   beginning to make the entire arrangement ugly.  I'll do this soon. */
dequote (list)
     register WORD_LIST *list;
{
  register char *s;

  while (list)
    {
      s = list->word->word;
      while (*s)
	{
	  *s &= 0x7f;
	  s++;
	}
      list = list->next;
    }
}

WORD_LIST *
expand_word (word, quoted)
     WORD_DESC *word;
     int quoted;
{
  WORD_LIST *expand_word_internal ();
  WORD_LIST *result = expand_word_internal (word, quoted);
  if (result)
    dequote (result);
  return (result);
}

/* Make a word list which is the expansion of the word passed in WORD.
   This returns a WORD_LIST * if the expansion expands to something,
   else it returns NULL.  If QUOTED is non-zero, then the text of WORD is
   treated as if it was surrounded by double-quotes.  */
WORD_LIST *
expand_word_internal (word, quoted)
     WORD_DESC *word;
     int quoted;
{
  /* The thing that we finally output. */
  WORD_LIST *result = (WORD_LIST *)NULL;

  /* The intermediate string that we build while expanding. */
  char *istring = (char *)xmalloc (DEFAULT_ARRAY_SIZE);

  /* The current size of the above object. */
  int istring_size = DEFAULT_ARRAY_SIZE;

  /* Index into ISTRING. */
  int istring_index = 0;

  /* Temporary string storage. */
  char *temp = (char *)NULL;

  /* The text of WORD. */
  register char *string = word->word;

  /* The index into STRING. */
  register int sindex = 0;

  register int c;		/* Current character. */
  int number;			/* Temporary number value. */
  int t_index;			/* For calls to string_extract_xxx. */

  istring[0] = '\0';

  if (!string) goto final_exit;

  /* Begin the expansion. */

  for (;;) {

    c = string[sindex];

    switch (c) {		/* Case on toplevel character. */

    case '\0':
      goto finished_with_string;

    case '$':

      c = string[++sindex];

      /* Do simple cases first... */

      switch (c) {		/* Case on what follows '$'. */

      case '0':			/* $0 .. $9? */
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
#define SINGLE_DIGIT_COMPATIBILITY /* I'm beginning to hate Bourne. */
#ifdef SINGLE_DIGIT_COMPATIBILITY
	if (dollar_vars[digit_value (c)])
	  temp = savestring (dollar_vars[digit_value (c)]);
	else
	  temp = (char *)NULL;
#else /* We read the whole number, not just the first digit. */
#endif
	goto dollar_add_string;

      case '$':			/* $$ -- pid of the invoking shell. */
	{
	  extern int dollar_dollar_pid;
	  number = dollar_dollar_pid;
	}
add_number:
	temp = (char *)xmalloc (10);
	sprintf (temp, "%d", number);
dollar_add_string:
	if (string[sindex]) sindex++;

	/* Add TEMP to ISTRING. */
add_string:
	istring = sub_append_string (temp, istring,
				     &istring_index, &istring_size);
	break;
	
      case '#':			/* $# -- number of positional parameters. */
	{
	  WORD_LIST *list = list_rest_of_args ();
	  number = list_length (list);
	  dispose_words (list);
	  goto add_number;
	}
	break;
      
      case '?':			/* $? -- return value of the last synchronous command. */
	{
	  extern int last_command_exit_value;
	  number = last_command_exit_value;
	  goto add_number;
	}
	break;
      
      case '-':			/* $- -- flags supplied to the shell on
				   invocation or by the `set' builtin. */
	temp = (char *)which_set_flags ();
	goto dollar_add_string;
	break;
      
      case '!':			/* $! -- Pid of the last asynchronous command. */
	{
	  extern int last_asynchronous_pid;
	  number = last_asynchronous_pid;
	  goto add_number;
	}
	break;
      
	/* The only difference between this and $@ is when the arg is
	   quoted. */
      case '*':			/* `$*' */
	temp = string_rest_of_args ();

	/* In the case of a quoted string, quote the entire arg-list. */
	if (quoted && temp)
	  {
	    register unsigned char *t = (unsigned char *)temp;

	    for (; *t; t++)
	      if (*t == ' ')
		*t |= 0x80;
	  }
	goto dollar_add_string;
	break;
	
      case '@':			/* `$@' */
	temp = string_rest_of_args ();
	goto dollar_add_string;
	break;

      case '{':			/* ${[#]name[[:]-=?+[word]]} */
	{
	  int check_nullness = 0;
	  int var_is_set = 0;
	  int var_is_null = 0;
	  int var_is_special = 0;
	  char *name, *value, *string_extract ();

	  sindex++;
	  t_index = sindex;
	  name = string_extract (string, &t_index, ":-=?+}");

	  /* If the name really consists of a special variable, then
	     make sure that we have the entire name. */
	  if (sindex == t_index &&
	      (string[sindex] == '-' || string[sindex] == '?'))
	    {
	      char *tt;
	      t_index++;
	      free (name);
	      tt = (string_extract (string, &t_index, ":-=?+}"));
	      name = (char *)xmalloc (2 + (strlen (tt)));
	      *name = string[sindex];
	      strcpy (name + 1, tt);
	      free (tt);
	    }
	  sindex = t_index;

	  /* Find out what character ended the variable name.  Then
	     do the appropriate thing. */
	  
	  if (c = string[sindex])
	    sindex++;

	  if (c == ':')
	    {
	      check_nullness++;
	      if (c = string[sindex])
		sindex++;
	    }
	  
	  /* Determine the value of this variable. */
	  if (strlen (name) == 1 && (digit(*name) || member(*name, "#-?$!@*")))
	    var_is_special++;
	    
	  /* Check for special expansion things. */
	  if (*name == '#')
	    {
	      if (name[1] != '*' && name[1] != '@')
		{
		  char *tt = get_string_value (&name[1]);
		  if (tt)
		    number = strlen (tt);
		  else
		    number = 0;
		  goto add_number;
		}
	    }
	  
	  if (var_is_special)
	    {
	      char *tt = (char *)alloca (2 + strlen (name));
	      WORD_LIST *l;
	      tt[0] = '$'; tt[1] = '\0';
	      strcat (tt, name);
	      l = expand_string (tt, 0);
	      temp = string_list (l);
	      dispose_words (l);
	    }
	  else
	    {
	      temp = get_string_value (name);
	      if (temp)
		temp = savestring (temp);
	    }

	  {
	    SHELL_VAR *f = find_variable (name);

	    if (f && !invisible_p (f) && temp)
	      var_is_set++;
	  }

	  if (var_is_set)
	    {
	      if (!*temp)
		var_is_null++;
	    }
	  else
	    var_is_null++;
	  
	  if (!check_nullness)
	    var_is_null = 0;

	  /* Get the rest of the stuff inside the braces. */
	  if (c && c != '}')
	    {
	      /* Scan forward searching for last `{'.  This is a hack,
		 it will always be a hack, and it always has been a hack. */
	      {
		int braces_found = 0;

		for (t_index = sindex; string[t_index]; t_index++)
		  {
		    if (string[t_index] == '{')
		      braces_found++;
		    else
		      if (string[t_index] == '}')
			if (!braces_found)
			  break;
			else
			  braces_found--;
		  }

		value = (char *)xmalloc (1 + (t_index - sindex));
		strncpy (value, &string[sindex], t_index - sindex);
		value[t_index - sindex] = '\0';
		sindex = t_index;
	      }

	      if (string[sindex] == '}') sindex++;
	      else goto bad_substitution;
	    
	    }
	  else
	    {
	      value = (char *)NULL;
	    }
	  
	  /* Do the right thing based on which character ended the variable
	     name. */
	  switch (c)
	    {
	    case '\0':
	    bad_substitution:
	      report_error ("%s: bad substitution", name ? name : "??");
	      longjmp (top_level, DISCARD);

	    case '}':
	      break;
	    
	    case '-':
	      if (var_is_set && !var_is_null)
		{
		  /* Do nothing.  Just use the value in temp. */
		}
	      else
		{
		  WORD_LIST *l = expand_string (value, 0);
		  free (temp);
		  temp = (char *)string_list (l);
		  dispose_words (l);
		  free (value);
		}
	      break;
	    
	    case '=':
	      if (var_is_set && !var_is_null)
		{
		  /* Do nothing.  The value of temp is desired. */
		}
	      else
		{
		  if (var_is_special)
		    {
		      report_error ("$%s: cannot assign in this way", name);
		      free (temp);
		      temp = (char *)NULL;
		    }
		  else
		    {
		      WORD_LIST *l = expand_string (value, 0);
		      free (temp);
		      temp = (char *)string_list (l);
		      dispose_words (l);
		      bind_variable (name, temp);
		    }
		}
	      break;
	    
	    case '?':
	      if (var_is_set && !var_is_null)
		{
		  /* Do nothing.  The value in temp is desired. */
		}
	      else
		{
		  /* The spec says to "print `word' [the right hand
		     side], and exit from the shell", but I think that
		     is brain-damaged beyond all belief.  I also haven't
		     found another shell that does that yet.  I don't
		     think that I ever will find one.  The spec goes on
		     to then say "If `word' is omitted, the message
		     ``parameter null or not set'' is printed", but that
		     is so stupid it is clearly to be ignored.  I just
		     print whatever was found on the right-hand of the
		     statement.  If nothing is found on the right hand
		     side, I am told that I must print a default error
		     message and exit from the shell.  Remember, this
		     isn't my idea.  */

		  free (temp);
		  temp = (char *)NULL;
		  if (value)
		    {
		      WORD_LIST *l = expand_string (value, 0);
		      temp =  string_list (l);
		      fprintf (stderr, "%s\n", temp ? temp : "");
		      free (temp);
		      dispose_words (l);
		      temp = (char *)NULL;
		    }
		  else
		    {
		      if (var_is_special)
			report_error ("%s: Too few arguments", dollar_vars[0]);
		      else
			report_error ("%s: parameter not set", name);
		    }
		  longjmp (top_level, FORCE_EOF);
		}
	      break;
	    
	    case '+':
	      /* We don't want the value of the named variable for anything. */
	      free (temp);
	      temp = (char *)NULL;
	    
	      if (var_is_set && !var_is_null)
		{
		  /* Use the right-hand side.  Pretty weird. */
		  if (value)
		    {
		      WORD_LIST *l = expand_string (value, 0);
		      temp = (char *)string_list (l);
		      dispose_words (l);
		    }
		}
	      break;
	    }			/* end case on closing character. */
	  free (name);
	  goto add_string;
	}			/* end case '{' */
      break;
    
      case '(':			/* Do command substitution. */
	/* We have to extract the contents of this paren substitution. */
	{
	  char *extract_command_subst ();
	  int old_index = ++sindex;

	  temp = extract_command_subst (string, &old_index);
	  sindex = old_index;

	  goto command_substitution;
	}

      default:
	{
	  /* Find the variable in VARIABLE_LIST. */
	  int old_index = sindex;
	  char *name;
	  SHELL_VAR *var;
	  
	  temp = (char *)NULL;

	  for (;
	       (c = string[sindex]) && (isletter (c) || digit (c) || c == '_');
	       sindex++);
	  name = (char *)substring (string, old_index, sindex);

	  /* If this isn't a variable name, then just output the `$'. */
	  if (!name || !*name) {
	    free (name);
	    temp = savestring ("$");
	    goto add_string;
	  }

	  /* If the variable exists and isn't a function, return its value. */
	  var = find_variable (name);
	  if (var && !function_p (var)) {
	    temp = savestring (var->value);
	    free (name);
	    goto add_string;
	  } else temp = (char *)NULL;

	  if (var) {
	    report_error ("%s: cannot substitute function", name);
	  } else {
	    if (unbound_vars_is_error) {
	      report_error ("%s: unbound variable", name);
	    } else goto add_string;
	  }
	  free (name);
	  longjmp (top_level, DISCARD);
	}
      }
      break;			/* End case '$': */

    case '`':
      {
	sindex++;

	t_index = sindex;
	temp = string_extract (string, &t_index, "`");
	sindex = t_index;
	de_backslash (temp);
	
      command_substitution:
	/* Pipe the output of executing TEMP into the current shell.
	   Make any and all whitespace (including newlines) be just
	   one space character. */
	{
	  extern int last_made_pid;
	  int pid, old_pid, fildes[2];
	  
	  if (pipe (fildes) < 0) {
	    report_error ("Can't make pipes for backquote substitution!");
	    goto error_exit;
	  }
	  
	  old_pid = last_made_pid;
#ifdef JOB_CONTROL
	  {
	    extern int pipeline_pgrp, shell_pgrp;
	    pipeline_pgrp = shell_pgrp;
	    pid = make_child (savestring ("backquote"), 0);

	    stop_making_children ();
	    pipeline_pgrp = 0;
	  }
#else   /* JOB_CONTROL */
	  pid = make_child (savestring ("backquote"), 0);
#endif  /* JOB_CONTROL */

	  if (pid < 0) {
	    report_error ("Can't make a child for backquote substitution!");
	  error_exit:
	    if (istring)
	      free (istring);
	    dispose_words (result);
	    return ((WORD_LIST *)NULL);
	  }

	  if (pid == 0)
	    {
#ifdef JOB_CONTROL
	      set_job_control (0);
#endif
	      dup2 (fildes[1], 1);
	      close (fildes[1]);
	      close (fildes[0]);

	      exit (parse_and_execute (temp, "backquote"));
	    }
	  else
	    {
	      FILE *istream = fdopen (fildes[0], "r");
	      int start_index = istring_index;
	      int lastc;
	    
	      close (fildes[1]);
	    
	      if (!istream)
		exit (1);

	      while ((lastc = c) &&
#ifdef SYSV
		     ((c = sysv_getc (istream)) != EOF)
#else
		     ((c = getc (istream)) != EOF)
#endif
		     )
		{

		  /* Duplicated from `add_character:' */
		  while (istring_index + 1 >= istring_size)
		    istring =
		      (char *)xrealloc (istring,
					istring_size += DEFAULT_ARRAY_SIZE);

		  if (whitespace  (c) || c == '\n')
		    {
		      if (!whitespace (lastc) && lastc != '\n')
			istring[istring_index++] = ' ';
		    }
		  else
		    {
		      istring[istring_index++] = c;
		    }
		  istring[istring_index] = '\0';
		}
	      fclose (istream);
	      close (fildes[0]);

	      wait_for (pid);
	      last_made_pid = old_pid;

	      if (temp)
		free (temp);

	      strip_leading (istring + start_index);
	      strip_trailing (istring + start_index);
	      istring_index = strlen (istring);
	      goto next_character;
	    }
	}
      }
      
    case '\\':
      if (string[sindex + 1] == '\n') {
	sindex += 2;
	continue;
      } else {
	c = (string[++sindex]);
	if (quoted && !member (c, slashify_in_quotes)) {
	  temp = (char *)xmalloc (3);
	  temp[0] = '\\'; temp[1] = c; temp[2] = '\0';
	  sindex++;
	  goto add_string;
	} else {
	  /* This character is quoted, so add it it in quoted mode. */
	  c = c | 0x80;
	  goto add_character;
	}
      }
      
    case '"':
      if (quoted)
	goto add_character;
      sindex++;
      {
	WORD_LIST *tresult = (WORD_LIST *)NULL;

	t_index = sindex;
	temp = string_extract_double_quoted (string, &t_index);
	sindex = t_index;

	if (string[sindex])
	  sindex++;

	tresult = expand_string (temp, 1);
	free (temp);

	if (tresult)
	  {
	    temp = savestring (tresult->word->word);
	    dispose_words (tresult);
	  }
	else
	  temp = (char *)NULL;

      add_quoted_string:

	if (temp)
	  {
	    register char *tt;
	    for (tt = temp; *tt && (*tt |= 0x80); tt++);
	  }
	else
	  {
	  add_null_arg:
	    temp = savestring (" ");
	    temp[0] = (unsigned char)0x80;
	  }
	goto add_string;
      }
      break;
      
    case '\'':
      if (!quoted)
	{
	  sindex++;

	  t_index = sindex;
	  temp = string_extract_verbatim (string, &t_index, "'");
	  sindex = t_index;

	  if (string[sindex]) sindex++;
	  goto add_quoted_string;
	}
      else
	{
	  goto add_character;
	}
      break;

    default:

add_character:
      while (istring_index + 1 >= istring_size)
	istring = (char *)xrealloc (istring,
				    istring_size += DEFAULT_ARRAY_SIZE);
      istring[istring_index++] = c;
      istring[istring_index] = '\0';

next_character:
      sindex++;
    }
  }

finished_with_string:
final_exit:
  if (istring)
    {
      WORD_LIST *temp_list;
      char *ifs_chars = get_string_value ("IFS");

      if (quoted || !ifs_chars) ifs_chars = "";

      temp_list = list_string (istring, ifs_chars, quoted);
      free (istring);
      result = (WORD_LIST *)list_append (reverse_list (result), temp_list);
    }
  else
    result = (WORD_LIST *)NULL;
  return (result);
}

/* Do all of the assignments in LIST upto a word which isn't an
   assignment. */
WORD_LIST *
get_rid_of_variable_assignments (list)
     WORD_LIST *list;
{
  WORD_LIST *copy_word_list ();
  WORD_LIST *orig = list;

  while (list)
    if (!list->word->assignment)
      {
	WORD_LIST *new_list = copy_word_list (list);
	dispose_words (orig);
	return (new_list);
      }
    else
      {
	do_assignment (list->word->word);
	list = list->next;
      }
  dispose_words (orig);
  return ((WORD_LIST *)NULL);
}

/* Check and handle the case where there are some variable assignments
   in LIST which go into the environment for this command. */
WORD_LIST *
get_rid_of_environment_assignments (list)
     WORD_LIST *list;
{
  register WORD_LIST *tlist = list;
  register WORD_LIST *new_list;
  WORD_LIST *copy_word_list (), *copy_word ();

  while (tlist)
    {
      if (!tlist->word->assignment) goto make_assignments;
      tlist = tlist->next;
    }
  /* Since all of the assignments are variable assignments. */
  return (list);

make_assignments:
  tlist = list;
  while (tlist)
    {
      if (tlist->word->assignment)
	assign_in_env (tlist->word->word);
      else
	{
	  if (!place_keywords_in_env)
	    {
	      new_list = copy_word_list (tlist);
	      dispose_words (list);
	      return (new_list);
	    }
	}
      tlist = tlist->next;
    }

  /* We got all of the keywords assigned.  Now return the remainder
     of the words. */
  {
    register WORD_LIST *new_list = (WORD_LIST *)NULL;

    tlist = list;

    /* Skip the ones at the start. */
    while (tlist && tlist->word->assignment)
      tlist = tlist->next;

    /* If we placed all the keywords in the list into the environment,
       then remove them from the output list. */
    if (place_keywords_in_env)
      {
	while (tlist)
	  {
	    if (!tlist->word->assignment)
	      new_list = make_word_list (copy_word (tlist->word), new_list);
	    tlist = tlist->next;
	  }
	new_list = (WORD_LIST *)reverse_list (new_list);
      }
    else
      {
	/* Just copy the list. */
	new_list = copy_word_list (tlist);
      }
    dispose_words (list);
    return (new_list);
  }
}

/* Take the list of words in LIST and do the various substitutions.  Return
   a new list of words which is the expanded list, and without things like
   variable assignments. */
WORD_LIST *
expand_words (list)
     WORD_LIST *list;
{
  WORD_LIST *expand_words_1 ();
  return (expand_words_1 (list, 1));
}

/* Same as expand_words (), but doesn't hack variable or environment
   variables. */
WORD_LIST *
expand_words_no_vars (list)
     WORD_LIST *list;
{
  WORD_LIST *expand_words_1 ();
  return (expand_words_1 (list, 0));
}

/* Non-zero means to allow unmatched globbed filenames to expand to
   a null file. */
static int allow_null_glob_expansion = 0;

/* The workhorse for expand_words () and expand_words_no_var ().
   First arg is LIST, a WORD_LIST of words.
   Second arg DO_VARS is non-zero if you want to do environment and
   variable assignments, else zero. */
WORD_LIST *
expand_words_1 (list, do_vars)
     WORD_LIST *list;
     int do_vars;
{
  register WORD_LIST *tlist, *new_list = (WORD_LIST *)NULL;
  WORD_LIST *orig_list;
  extern int no_brace_expansion;

  tlist = (WORD_LIST *)copy_word_list (list);

  if (do_vars)
    {
      /* Handle the case where the arguments are assignments for
	 the environment of this command. */
      tlist = get_rid_of_environment_assignments (tlist);

      /* Handle the case where the arguments are all variable assignments. */
      tlist = get_rid_of_variable_assignments (tlist);
    }

  /* Begin expanding the words that remain.  The expansions take place on
     things that aren't really variable assignments. */

  if (!tlist)
    return ((WORD_LIST *)NULL);

  /* Do brace expansion on this word if there are any brace characters
     in the string. */
  if (!no_brace_expansion)
    {
      extern char **brace_expand ();
      register char **expansions;
      WORD_LIST *braces = (WORD_LIST *)NULL;
      int index;
    
      orig_list = tlist;

      while (tlist)
	{
	  expansions = brace_expand (tlist->word->word);

	  for (index = 0; expansions[index]; index++)
	    {
	      braces = make_word_list (make_word (expansions[index]), braces);
	      free (expansions[index]);
	    }
	  free (expansions);

	  tlist = tlist->next;
	}
      dispose_words (orig_list);
      tlist = (WORD_LIST *)reverse_list (braces);
    }

  orig_list = tlist;

  /* We do tilde expansion, but only if globbing is enabled.
     I guess this is the right thing. */
  while (tlist)
    {
      if (!disallow_filename_globbing && !tlist->word->quoted &&
	  (*(tlist->word->word) == '~'))
	{
	  char *tilde_expand (), *tt = tlist->word->word;
	  tlist->word->word = tilde_expand (tlist->word->word);
	  free (tt);
	}

      new_list =
	(WORD_LIST *)list_append
	  (reverse_list (expand_word_internal (tlist->word, 0)), new_list);

      tlist = tlist->next;
    }

  new_list = (WORD_LIST *)reverse_list (new_list);

  dispose_words (orig_list);

  /* Okay, we're almost done.  Now let's just do some filename
     globbing. */
  {
    char **shell_glob_filename (), **temp_list = (char **)NULL;
    register int list_index;
    WORD_LIST *glob_list;

    orig_list = (WORD_LIST *)NULL;
    tlist = new_list;

    if (!disallow_filename_globbing)
      {
	while (tlist)
	  {
	    /* If the word isn't quoted, then glob it. */
	    if (!tlist->word->quoted && glob_pattern_p (tlist->word->word))
	      {
		temp_list = shell_glob_filename (tlist->word->word);

		/* Fix the hi-bits. (This is how we quoted
		   special characters.) */
		{
		  register char *t = tlist->word->word;
		  while (*t) *t++ &= 0x7f;
		}

		/* Handle error cases.
		   I don't think we should report errors like "No such file
		   or directory".  However, I would like to report errors
		   like "Read failed". */
		 
		if (temp_list == (char **)-1)
		  {
		    /* file_error (tlist->word->word); */
		    return (new_list);
		  }

		if (!temp_list) abort ();

		/* Sort the returned names.  Maybe this should be done in
		   glob_filename (). */
		{
		  int qsort_string_compare ();
		  qsort (temp_list, array_len (temp_list),
			 sizeof (char *), qsort_string_compare);
		}

		/* Make the array into a word list. */
		glob_list = (WORD_LIST *)NULL;
		for (list_index = 0; temp_list[list_index]; list_index++)
		  glob_list =
		    make_word_list (make_word (temp_list[list_index]), glob_list);
	  
		if (glob_list)
		  orig_list = (WORD_LIST *)list_append (glob_list, orig_list);
		else
		  if (!allow_null_glob_expansion)
		    orig_list =
		      make_word_list (copy_word (tlist->word), orig_list);
	      }
	    else
	      {
		/* Fix the hi-bits. (This is how we quoted special
		   characters.) */
		register char *t = tlist->word->word;
		while (*t) *t++ &= 0x7f;
		orig_list = make_word_list (copy_word (tlist->word), orig_list);
	      }

	    free_array (temp_list);
	    temp_list = (char **)NULL;

	    tlist = tlist->next;
	  }
	dispose_words (new_list);
	new_list = orig_list;
      }
    else
      {
	/* Fix the hi-bits. (This is how we quoted special characters.) */
	register WORD_LIST *wl = new_list;
	register char *wp;
	while (wl) {
	  wp = wl->word->word;
	  while (*wp) *wp++ &= 0x7f;
	  wl = wl->next;
	}
	return (new_list);
      }
  }
  return (WORD_LIST *)(reverse_list (new_list));
}

/* Call the glob library to do globbing on PATHNAME.
   PATHNAME can contain characters with the hi bit set; this indicates
   that the character is to be quoted.  We quote it here. */
char **
shell_glob_filename (pathname)
     char *pathname;
{
  extern char **glob_filename ();
  register int i, j;
  char *temp = (char *)alloca (2 * strlen (pathname) + 1);

  for (i = j = 0; pathname[i]; i++, j++)
    {
      if ((unsigned char)pathname[i] > 0x7f)
	temp[j++] = '\\';
  
      temp[j] = (unsigned char)pathname[i] & 0x7f;
    }
  temp[j] = '\0';

  return (glob_filename (temp));
}

/* An alist of name.function for each special variable.  Most of the functions
   don't do much, and in fact, this would be faster with a switch statement,
   but by the end of this file, I am sick of switch statements. */

/* The functions that get called. */
int sv_path (), sv_mail (), sv_terminal (), sv_histsize (), sv_uids (),
  sv_ignoreeof (), sv_glob_dot_filenames (), sv_histchars (), sv_nolinks (),
  sv_hostname_completion_file (), sv_allow_null_glob_expansion (),
  sv_history_control ();

#ifdef JOB_CONTROL
extern int sv_notify ();
#endif


struct name_and_function {
  char *name;
  Function *function;
} special_vars[] = {
  { "PATH", sv_path },
  { "MAIL", sv_mail },
  { "MAILPATH", sv_mail },
  { "MAILCHECK", sv_mail },
  { "TERMCAP", sv_terminal },
  { "TERM", sv_terminal },
  { "HISTSIZE", sv_histsize },
  { "EUID", sv_uids},
  { "UID", sv_uids},
  { "ignoreeof", sv_ignoreeof },
#ifdef JOB_CONTROL
  { "notify", sv_notify },
#endif  /* JOB_CONTROL */
  { "glob_dot_filenames", sv_glob_dot_filenames },
  { "allow_null_glob_expansion", sv_allow_null_glob_expansion },
  { "histchars", sv_histchars },
  { "nolinks", sv_nolinks },
  { "hostname_completion_file", sv_hostname_completion_file },
  { "history_control", sv_history_control },
  { (char *)0x00, (Function *)0x00 }
};

/* The variable in NAME has just had its state changed.  Check to see if it
   is one of the special ones where something special happens. */
stupidly_hack_special_variables (name)
     char *name;
{
  int i = 0;

  while (special_vars[i].name)
    {
      if (strcmp (special_vars[i].name, name) == 0)
	{
	  (*(special_vars[i].function)) (name);
	  return;
	}
      i++;
    }
}

/* What to do just after the PATH variable has changed. */
sv_path ()
{
  /* hash -r */
  WORD_LIST *args;

  args = make_word_list (make_word ("-r"), NULL);
  hash_builtin (args);
  dispose_words (args);
}

/* What to do just after one of the MAILxxxx variables has changed.  NAME
   is the name of the variable.  */
sv_mail (name)
     char *name;
{
  /* If the time interval for checking the files has changed, then
     reset the mail timer.  Otherwise, one of the pathname vars
     to the users mailbox has changed, so rebuild the array of
     filenames. */
  if (strcmp (name, "MAILCHECK") == 0)
    reset_mail_timer ();
  else
    {
      if ((strcmp (name, "MAIL") == 0) || (strcmp (name, "MAILPATH") == 0))
	{
	  free_mail_files ();
	  remember_mail_dates ();
	}
    }
}

/* What to do just after one of the TERMxxx variables has changed.
   If we are an interactive shell, then try to reset the terminal
   information in readline. */
sv_terminal (name)
     char *name;
{
  extern int interactive;

  if (interactive)
    rl_reset_terminal (get_string_value ("TERM"));
}

/* What to do after the HISTSIZE variable changes.
   If there is a value for this variable (and it is numeric), then stifle
   the history.  Otherwise, if there is NO value for this variable,
   unstifle the history. */
sv_histsize (name)
     char *name;
{
  char *temp = get_string_value (name);
  
  if (temp)
    {
      int num;
      if (sscanf (temp, "%d", &num) == 1)
	stifle_history (num);
    }
  else
    unstifle_history ();
}

/* A nit for picking at history saving.
   Value of 0 means save all lines parsed by the shell on the history.
   Value of 1 means save all lines that do not start with a space.
   Value of 2 means save all lines that do not match the last line saved. */
int history_control = 0;

/* What to do after the HISTORY_CONTROL variable changes. */
sv_history_control (name)
     char *name;
{
  char *temp = get_string_value (name);

  history_control = 0;

  if (temp && *temp)
    {
      if (strcmp (temp, "ignorespace") == 0)
	history_control = 1;
      else if (strcmp (temp, "ignoredups") == 0)
	history_control = 2;
    }
}

/* If the variable exists, then the value of it can be the number
   of times we actually ignore the EOF.  The default is small,
   (smaller than csh, anyway). */
sv_ignoreeof (name)
     char *name;
{
  extern int eof_encountered, eof_encountered_limit;
  char *temp = get_string_value (name);
  int new_limit;

  eof_encountered = 0;

  if (temp && (sscanf (temp, "%d", &new_limit) == 1))
    eof_encountered_limit = new_limit;
  else
    eof_encountered_limit = 10; /* csh uses 26. */
}

/* Control whether * matches .files in globbing.  Yechh. */

sv_glob_dot_filenames (name)
     char *name;
{
  extern int noglob_dot_filenames;

  noglob_dot_filenames = !(find_variable (name));
}

/* Setting/unsetting of the history expansion character. */
char old_history_expansion_char = '!';
char old_history_comment_char = '#';
char old_history_subst_char = '^';

sv_histchars (name)
     char *name;
{
  extern int history_expansion_char;
  extern int history_comment_char;
  extern int history_subst_char;
  char *temp = get_string_value (name);

  if (temp)
    {
      old_history_expansion_char = history_expansion_char;
      history_expansion_char = *temp;

      if (temp[1])
	{
	  old_history_subst_char = history_subst_char;
	  history_subst_char = temp[1];

	  if (temp[2])
	    {
	      old_history_comment_char = history_comment_char;
	      history_comment_char = temp[2];
	    }
	}
    }
  else
    {
      history_expansion_char = '!';
      history_subst_char = '^';
      history_comment_char = '#';
    }
}

#ifdef JOB_CONTROL
/* Job notification feature desired? */
sv_notify (name)
     char *name;
{
  extern int asynchronous_notification;

  if (get_string_value (name))
    asynchronous_notification = 1;
  else
    asynchronous_notification = 0;
}
#endif  /* JOB_CONTROL */

/* If the variable `nolinks' exists, it specifies that symbolic links are
   not to be followed in `cd' commands. */
sv_nolinks (name)
     char *name;
{
  extern int follow_symbolic_links;

  follow_symbolic_links = !find_variable (name);
}

/* Don't let users hack the user id variables. */
sv_uids ()
{
  int uid = getuid ();
  int euid = geteuid ();
  char buff[10];
  register SHELL_VAR *v;

  sprintf (buff, "%d", uid);
  v = find_variable ("UID");
  if (v)
    v->attributes &= ~att_readonly;

  v = bind_variable ("UID", buff);
  v->attributes |= att_readonly;

  sprintf (buff, "%d", euid);
  v = find_variable ("EUID");
  if (v)
    v->attributes &= ~att_readonly;

  v = bind_variable ("EUID", buff);
  v->attributes |= att_readonly;
}

sv_hostname_completion_file ()
{
  extern int hostname_list_initialized;

  hostname_list_initialized = 0;
}

sv_allow_null_glob_expansion (name)
{
  allow_null_glob_expansion = (int)find_variable (name);
}