aboutsummaryrefslogtreecommitdiffstats
path: root/subst.c
diff options
context:
space:
mode:
authorJari Aalto <jari.aalto@cante.net>1996-08-26 18:22:31 +0000
committerJari Aalto <jari.aalto@cante.net>2009-09-12 16:46:49 +0000
commit726f63884db0132f01745f1fb4465e6621088ccf (patch)
tree6c2f7765a890a97e0e513cb539df43283a8f7c4d /subst.c
downloadandroid_external_bash-726f63884db0132f01745f1fb4465e6621088ccf.tar.gz
android_external_bash-726f63884db0132f01745f1fb4465e6621088ccf.tar.bz2
android_external_bash-726f63884db0132f01745f1fb4465e6621088ccf.zip
Imported from ../bash-1.14.7.tar.gz.
Diffstat (limited to 'subst.c')
-rw-r--r--subst.c4867
1 files changed, 4867 insertions, 0 deletions
diff --git a/subst.c b/subst.c
new file mode 100644
index 0000000..9dd00a8
--- /dev/null
+++ b/subst.c
@@ -0,0 +1,4867 @@
+/* subst.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 2, 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 "bashtypes.h"
+#include <stdio.h>
+#include <pwd.h>
+#include <signal.h>
+#include <errno.h>
+/* Not all systems declare ERRNO in errno.h... and some systems #define it! */
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+#include "bashansi.h"
+#include "posixstat.h"
+
+#include "shell.h"
+#include "flags.h"
+#include "jobs.h"
+#include "execute_cmd.h"
+#include "filecntl.h"
+
+#if defined (READLINE)
+# include <readline/readline.h>
+#else
+# include <tilde/tilde.h>
+#endif
+
+#if defined (HISTORY)
+# include "bashhist.h"
+# include <readline/history.h>
+#endif
+
+#include <glob/fnmatch.h>
+#include "builtins/getopt.h"
+
+/* The size that strings change by. */
+#define DEFAULT_ARRAY_SIZE 512
+
+/* How to quote and determine the quoted state of the character C. */
+static char *make_quoted_char ();
+#define QUOTED_CHAR(c) ((c) == CTLESC)
+
+/* Process ID of the last command executed within command substitution. */
+pid_t last_command_subst_pid = NO_PID;
+
+/* Extern functions and variables from different files. */
+extern int last_command_exit_value, interactive, interactive_shell;
+extern int subshell_environment;
+extern int dollar_dollar_pid, no_brace_expansion;
+extern int posixly_correct;
+extern int eof_encountered, eof_encountered_limit, ignoreeof;
+extern char *this_command_name;
+extern jmp_buf top_level;
+#if defined (READLINE)
+extern int no_line_editing;
+extern int hostname_list_initialized;
+#endif
+
+#if !defined (USE_POSIX_GLOB_LIBRARY)
+extern int glob_dot_filenames, noglob_dot_filenames;
+extern char *glob_error_return;
+#endif
+
+static WORD_LIST expand_word_error, expand_word_fatal;
+static char expand_param_error, expand_param_fatal;
+
+static WORD_LIST *expand_string_internal ();
+static WORD_LIST *expand_word_internal (), *expand_words_internal ();
+static WORD_LIST *expand_string_leave_quoted ();
+static WORD_LIST *word_list_split ();
+static char *quote_string ();
+static int unquoted_substring (), unquoted_member ();
+static int unquoted_glob_pattern_p ();
+static void quote_list (), dequote_list ();
+static int do_assignment_internal ();
+static char *string_extract_verbatim (), *string_extract ();
+static char *string_extract_double_quoted (), *string_extract_single_quoted ();
+static char *extract_delimited_string ();
+static char *extract_dollar_brace_string ();
+
+/* **************************************************************** */
+/* */
+/* 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 = xmalloc (len + 1);
+
+ strncpy (result, string + start, len);
+ result[len] = '\0';
+ return (result);
+}
+
+/* Conventions:
+
+ A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string.
+ The parser passes CTLNUL as CTLESC CTLNUL. */
+
+/* The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL.
+ This is necessary to make unquoted CTLESC and CTLNUL characters in the
+ data stream pass through properly.
+ Here we remove doubled CTLESC characters inside quoted strings before
+ quoting the entire string, so we do not double the number of CTLESC
+ characters. */
+static char *
+remove_quoted_escapes (string)
+ char *string;
+{
+ register char *s;
+
+ for (s = string; s && *s; s++)
+ {
+ if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL))
+ strcpy (s, s + 1); /* XXX - should be memmove */
+ }
+ return (string);
+}
+
+/* Quote escape characters in string s, but no other characters. This is
+ used to protect CTLESC and CTLNUL in variable values from the rest of
+ the word expansion process after the variable is expanded. */
+static char *
+quote_escapes (string)
+ char *string;
+{
+ register char *s, *t;
+ char *result;
+
+ result = xmalloc ((strlen (string) * 2) + 1);
+ for (s = string, t = result; s && *s; )
+ {
+ if (*s == CTLESC || *s == CTLNUL)
+ *t++ = CTLESC;
+ *t++ = *s++;
+ }
+ *t = '\0';
+ return (result);
+}
+
+/* Just like string_extract, but doesn't hack backslashes or any of
+ that other stuff. Obeys quoting. Used to do splitting on $IFS. */
+static char *
+string_extract_verbatim (string, sindex, charlist)
+ char *string, *charlist;
+ int *sindex;
+{
+ register int i = *sindex;
+ int c;
+ char *temp;
+
+ if (charlist[0] == '\'' && !charlist[1])
+ {
+ temp = string_extract_single_quoted (string, sindex);
+ i = *sindex - 1;
+ *sindex = i;
+ return (temp);
+ }
+
+ for (i = *sindex; (c = string[i]); i++)
+ {
+ if (c == CTLESC)
+ {
+ i++;
+ continue;
+ }
+
+ if (MEMBER (c, charlist))
+ break;
+ }
+
+ temp = 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 SINDEX and ending with
+ one of the characters in CHARLIST. Don't make the ending character
+ part of the string. Leave SINDEX pointing at the ending character.
+ Understand about backslashes in the string. */
+static 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 = 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] == '`' || string[i + 1] == '\\' ||
+ string[i + 1] == '$'))
+ strcpy (string + i, string + i + 1); /* XXX - should be memmove */
+ return (string);
+}
+
+#if 0
+/* Replace instances of \! in a string with !. */
+void
+unquote_bang (string)
+ char *string;
+{
+ register int i, j;
+ register char *temp;
+
+ temp = xmalloc (1 + strlen (string));
+
+ for (i = 0, j = 0; (temp[j] = string[i]); i++, j++)
+ {
+ if (string[i] == '\\' && string[i + 1] == '!')
+ {
+ temp[j] = '!';
+ i++;
+ }
+ }
+ strcpy (string, temp);
+ free (temp);
+}
+#endif
+
+/* 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;
+{
+ return (extract_delimited_string (string, sindex, "$(", "(", ")"));
+}
+
+/* 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_arithmetic_subst (string, sindex)
+ char *string;
+ int *sindex;
+{
+ return (extract_delimited_string (string, sindex, "$[", "[", "]"));
+}
+
+#if defined (PROCESS_SUBSTITUTION)
+/* Extract the <( or >( 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_process_subst (string, starter, sindex)
+ char *string;
+ char *starter;
+ int *sindex;
+{
+ return (extract_delimited_string (string, sindex, starter, "(", ")"));
+}
+#endif /* PROCESS_SUBSTITUTION */
+
+/* Extract and create a new string from the contents of STRING, a
+ character string delimited with OPENER and CLOSER. SINDEX is
+ the address of an int describing the current offset in STRING;
+ it should point to just after the first OPENER found. On exit,
+ SINDEX gets the position just after the matching CLOSER. If
+ OPENER is more than a single character, ALT_OPENER, if non-null,
+ contains a character string that can also match CLOSER and thus
+ needs to be skipped. */
+static char *
+extract_delimited_string (string, sindex, opener, alt_opener, closer)
+ char *string;
+ int *sindex;
+ char *opener, *alt_opener, *closer;
+{
+ register int i, c, l;
+ int pass_character, nesting_level;
+ int delimiter, delimited_nesting_level;
+ int len_closer, len_opener, len_alt_opener;
+ char *result;
+
+ len_opener = STRLEN (opener);
+ len_alt_opener = STRLEN (alt_opener);
+ len_closer = STRLEN (closer);
+
+ pass_character = delimiter = delimited_nesting_level = 0;
+
+ nesting_level = 1;
+
+ for (i = *sindex; c = string[i]; i++)
+ {
+ if (pass_character)
+ {
+ pass_character = 0;
+ continue;
+ }
+
+ if (c == CTLESC)
+ {
+ pass_character++;
+ continue;
+ }
+
+ if (c == '\\')
+ {
+ if ((delimiter == '"') &&
+ (member (string[i + 1], slashify_in_quotes)))
+ {
+ pass_character++;
+ continue;
+ }
+ }
+
+ if (!delimiter || delimiter == '"')
+ {
+ if (STREQN (string + i, opener, len_opener))
+ {
+ if (!delimiter)
+ nesting_level++;
+ else
+ delimited_nesting_level++;
+
+ i += len_opener - 1;
+ continue;
+ }
+
+ if (len_alt_opener && STREQN (string + i, alt_opener, len_alt_opener))
+ {
+ if (!delimiter)
+ nesting_level++;
+ else
+ delimited_nesting_level++;
+
+ i += len_alt_opener - 1;
+ continue;
+ }
+
+ if (STREQN (string + i, closer, len_closer))
+ {
+ i += len_closer - 1;
+
+ if (delimiter && delimited_nesting_level)
+ delimited_nesting_level--;
+
+ if (!delimiter)
+ {
+ nesting_level--;
+ if (nesting_level == 0)
+ break;
+ }
+ }
+ }
+
+ if (delimiter)
+ {
+ if (c == delimiter || delimiter == '\\')
+ delimiter = 0;
+ continue;
+ }
+ else
+ {
+ if (c == '"' || c == '\'' || c == '\\')
+ delimiter = c;
+ }
+ }
+
+ l = i - *sindex;
+ result = xmalloc (1 + l);
+ strncpy (result, string + *sindex, l);
+ result[l] = '\0';
+ *sindex = i;
+
+ if (!c && (delimiter || nesting_level))
+ {
+ report_error ("bad substitution: no `%s' in %s", closer, string);
+ free (result);
+ longjmp (top_level, DISCARD);
+ }
+ return (result);
+}
+
+/* Extract a parameter expansion expression within ${ and } from STRING.
+ Obey the Posix.2 rules for finding the ending `}': count braces while
+ skipping over enclosed quoted strings and command substitutions.
+ SINDEX is the address of an int describing the current offset in STRING;
+ it should point to just after the first `{' found. On exit, SINDEX
+ gets the position just after the matching `}'. */
+/* XXX -- this is very similar to extract_delimited_string -- XXX */
+static char *
+extract_dollar_brace_string (string, sindex)
+ char *string;
+ int *sindex;
+{
+ register int i, c, l;
+ int pass_character, nesting_level;
+ int delimiter, delimited_nesting_level;
+ char *result;
+
+ pass_character = delimiter = delimited_nesting_level = 0;
+
+ nesting_level = 1;
+
+ for (i = *sindex; c = string[i]; i++)
+ {
+ if (pass_character)
+ {
+ pass_character = 0;
+ continue;
+ }
+
+ if (c == CTLESC)
+ {
+ pass_character++;
+ continue;
+ }
+
+ /* Backslashes quote the next character. */
+ if (c == '\\')
+ {
+ if ((delimiter == '"') &&
+ (member (string[i + 1], slashify_in_quotes)))
+ {
+ pass_character++;
+ continue;
+ }
+ }
+
+ if (!delimiter || delimiter == '"')
+ {
+ if (string[i] == '$' && string[i+1] == '{')
+ {
+ if (!delimiter)
+ nesting_level++;
+ else
+ delimited_nesting_level++;
+
+ i++;
+ continue;
+ }
+
+ /* Pass the contents of old-style command substitutions through
+ verbatim. */
+ if (string[i] == '`')
+ {
+ int si;
+ char *t;
+
+ si = i + 1;
+ t = string_extract (string, &si, "`");
+ i = si;
+ free (t);
+ continue;
+ }
+
+ /* Pass the contents of new-style command substitutions through
+ verbatim. */
+ if (string[i] == '$' && string[i+1] == '(')
+ {
+ int si;
+ char *t;
+
+ si = i + 2;
+ t = extract_delimited_string (string, &si, "$(", "(", ")");
+ i = si;
+ free (t);
+ continue;
+ }
+
+ if (string[i] == '{')
+ {
+ if (!delimiter)
+ nesting_level++;
+ else
+ delimited_nesting_level++;
+
+ continue;
+ }
+
+ if (string[i] == '}')
+ {
+ if (delimiter && delimited_nesting_level)
+ delimited_nesting_level--;
+
+ if (!delimiter)
+ {
+ nesting_level--;
+ if (nesting_level == 0)
+ break;
+ }
+ }
+ }
+
+ if (delimiter)
+ {
+ if (c == delimiter || delimiter == '\\')
+ delimiter = 0;
+ continue;
+ }
+ else
+ {
+ if (c == '"' || c == '\'' || c == '\\')
+ delimiter = c;
+ }
+ }
+
+ l = i - *sindex;
+ result = xmalloc (1 + l);
+ strncpy (result, string + *sindex, l);
+ result[l] = '\0';
+ *sindex = i;
+
+ if (!c && (delimiter || nesting_level))
+ {
+ report_error ("bad substitution: no ending `}' in %s", string);
+ free (result);
+ longjmp (top_level, DISCARD);
+ }
+ return (result);
+}
+
+/* Extract the contents of STRING as if it is enclosed in double quotes.
+ SINDEX, when passed in, is the offset of the character immediately
+ following the opening double quote; on exit, SINDEX is left pointing after
+ the closing double quote. */
+static char *
+string_extract_double_quoted (string, sindex)
+ char *string;
+ int *sindex;
+{
+ register int c, j, i;
+ char *temp; /* The new string we return. */
+ int pass_next, backquote; /* State variables for the machine. */
+
+ pass_next = backquote = 0;
+ temp = xmalloc (1 + strlen (string) - *sindex);
+
+ for (j = 0, i = *sindex; c = string[i]; i++)
+ {
+ /* Process a character that was quoted by a backslash. */
+ if (pass_next)
+ {
+ /* Posix.2 sez:
+
+ ``The backslash shall retain its special meaning as an escape
+ character only when followed by one of the characters:
+ $ ` " \ <newline>''.
+
+ We handle the double quotes here. expand_word_internal handles
+ the rest. */
+ if (c != '"')
+ temp[j++] = '\\';
+ temp[j++] = c;
+ pass_next = 0;
+ continue;
+ }
+
+ /* A backslash protects the next character. The code just above
+ handles preserving the backslash in front of any character but
+ a double quote. */
+ if (c == '\\')
+ {
+ pass_next++;
+ continue;
+ }
+
+ /* Inside backquotes, ``the portion of the quoted string from the
+ initial backquote and the characters up to the next backquote
+ that is not preceded by a backslash, having escape characters
+ removed, defines that command''. */
+ if (backquote)
+ {
+ if (c == '`')
+ backquote = 0;
+ temp[j++] = c;
+ continue;
+ }
+
+ if (c == '`')
+ {
+ temp[j++] = c;
+ backquote++;
+ continue;
+ }
+
+ /* Pass everything between `$(' and the matching `)' or a quoted
+ ${ ... } pair through according to the Posix.2 specification. */
+ if (c == '$' && ((string[i + 1] == '(') || (string[i + 1] == '{')))
+ {
+ register int t;
+ int si;
+ char *ret;
+
+ si = i + 2;
+ if (string[i + 1] == '(')
+ ret = extract_delimited_string (string, &si, "$(", "(", ")");
+ else
+ ret = extract_dollar_brace_string (string, &si);
+
+ temp[j++] = '$';
+ temp[j++] = string[i + 1];
+
+ for (t = 0; ret[t]; t++)
+ temp[j++] = ret[t];
+
+ i = si;
+ temp[j++] = string[i];
+ free (ret);
+ continue;
+ }
+
+ /* An unescaped double quote serves to terminate the string. */
+ if (c == '"')
+ break;
+
+ /* Add the character to the quoted string we're accumulating. */
+ temp[j++] = c;
+ }
+ temp[j] = '\0';
+
+ /* Point to after the closing quote. */
+ if (c)
+ i++;
+ *sindex = i;
+
+ return (temp);
+}
+
+/* Extract the contents of STRING as if it is enclosed in single quotes.
+ SINDEX, when passed in, is the offset of the character immediately
+ following the opening single quote; on exit, SINDEX is left pointing after
+ the closing single quote. */
+static char *
+string_extract_single_quoted (string, sindex)
+ char *string;
+ int *sindex;
+{
+ register int i = *sindex;
+ char *temp;
+
+ while (string[i] && string[i] != '\'')
+ i++;
+
+ temp = xmalloc (1 + i - *sindex);
+ strncpy (temp, string + *sindex, i - *sindex);
+ temp[i - *sindex] = '\0';
+
+ if (string[i])
+ i++;
+ *sindex = i;
+
+ return (temp);
+}
+
+/* Return 1 if the portion of STRING ending at EINDEX is quoted (there is
+ an unclosed quoted string), or if the character at EINDEX is quoted
+ by a backslash. */
+int
+char_is_quoted (string, eindex)
+ char *string;
+ int eindex;
+{
+ int i, pass_next, quoted;
+ char *temp;
+
+ for (i = pass_next = quoted = 0; i <= eindex; i++)
+ {
+ if (pass_next)
+ {
+ pass_next = 0;
+ if (i >= eindex) /* XXX was if (i >= eindex - 1) */
+ return 1;
+ continue;
+ }
+ else if (string[i] == '\'')
+ {
+ i++;
+ temp = string_extract_single_quoted (string, &i);
+ free (temp);
+ if (i > eindex)
+ return 1;
+ i--;
+ }
+ else if (string[i] == '"')
+ {
+ i++;
+ temp = string_extract_double_quoted (string, &i);
+ free (temp);
+ if (i > eindex)
+ return 1;
+ i--;
+ }
+ else if (string[i] == '\\')
+ {
+ pass_next = 1;
+ continue;
+ }
+ }
+ return (0);
+}
+
+#if defined (READLINE)
+int
+unclosed_pair (string, eindex, openstr)
+ char *string;
+ int eindex;
+ char *openstr;
+{
+ int i, pass_next, openc, c, olen;
+ char *temp, *s;
+
+ olen = strlen (openstr);
+ for (i = pass_next = openc = 0; i <= eindex; i++)
+ {
+ if (pass_next)
+ {
+ pass_next = 0;
+ if (i >= eindex) /* XXX was if (i >= eindex - 1) */
+ return 0;
+ continue;
+ }
+ else if (STREQN (string + i, openstr, olen))
+ {
+ openc = 1 - openc;
+ i += olen - 1;
+ }
+ else if (string[i] == '\'')
+ {
+ i++;
+ temp = string_extract_single_quoted (string, &i);
+ free (temp);
+ if (i > eindex)
+ return 0;
+ }
+ else if (string[i] == '"')
+ {
+ i++;
+ temp = string_extract_double_quoted (string, &i);
+ free (temp);
+ if (i > eindex)
+ return 0;
+ }
+ else if (string[i] == '\\')
+ {
+ pass_next = 1;
+ continue;
+ }
+ }
+ return (openc);
+}
+#endif /* READLINE */
+
+/* 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 = xmalloc (offset + 1);
+ strncpy (temp, string, offset);
+ temp[offset] = '\0';
+ return (temp);
+}
+
+/* Return a single string of all the words in LIST. SEP is the separator
+ to put between individual elements of LIST in the output string. */
+static char *
+string_list_internal (list, sep)
+ WORD_LIST *list;
+ char *sep;
+{
+ register WORD_LIST *t;
+ char *result, *r;
+ int word_len, sep_len, result_size;
+
+ if (!list)
+ return ((char *)NULL);
+
+ /* This is nearly always called with either sep[0] == 0 or sep[1] == 0. */
+ sep_len = STRLEN (sep);
+ result_size = 0;
+
+ for (t = list; t; t = t->next)
+ {
+ if (t != list)
+ result_size += sep_len;
+ result_size += strlen (t->word->word);
+ }
+
+ r = result = xmalloc (result_size + 1);
+
+ for (t = list; t; t = t->next)
+ {
+ if (t != list && sep_len)
+ {
+ FASTCOPY (sep, r, sep_len);
+ r += sep_len;
+ }
+
+ word_len = strlen (t->word->word);
+ FASTCOPY (t->word->word, r, word_len);
+ r += word_len;
+ }
+
+ *r = '\0';
+ return (result);
+}
+
+/* Return a single string of all the words present in LIST, separating
+ each word with a space. */
+char *
+string_list (list)
+ WORD_LIST *list;
+{
+ return (string_list_internal (list, " "));
+}
+
+/* Return a single string of all the words present in LIST, obeying the
+ quoting rules for "$*", to wit: (P1003.2, draft 11, 3.5.2) "If the
+ expansion [of $*] appears within a double quoted string, it expands
+ to a single field with the value of each parameter separated by the
+ first character of the IFS variable, or by a <space> if IFS is unset." */
+char *
+string_list_dollar_star (list)
+ WORD_LIST *list;
+{
+ char *ifs = get_string_value ("IFS");
+ char sep[2];
+
+ if (!ifs)
+ sep[0] = ' ';
+ else if (!*ifs)
+ sep[0] = '\0';
+ else
+ sep[0] = *ifs;
+
+ sep[1] = '\0';
+
+ return (string_list_internal (list, sep));
+}
+
+/* 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.
+
+ This obeys the P1003.2 word splitting semantics. If `separators' is
+ exactly <space><tab><newline>, then the splitting algorithm is that of
+ the Bourne shell, which treats any sequence of characters from `separators'
+ as a delimiter. If IFS is unset, which results in `separators' being set
+ to "", no splitting occurs. If separators has some other value, the
+ following rules are applied (`IFS white space' means zero or more
+ occurrences of <space>, <tab>, or <newline>, as long as those characters
+ are in `separators'):
+
+ 1) IFS white space is ignored at the start and the end of the
+ string.
+ 2) Each occurrence of a character in `separators' that is not
+ IFS white space, along with any adjacent occurrences of
+ IFS white space delimits a field.
+ 3) Any nonzero-length sequence of IFS white space delimits a field.
+ */
+
+/* BEWARE! list_string strips null arguments. Don't call it twice and
+ expect to have "" preserved! */
+
+/* Is the first character of STRING a quoted NULL character? */
+#define QUOTED_NULL(string) ((string)[0] == CTLNUL && (string)[1] == '\0')
+
+/* Perform quoted null character removal on STRING. We don't allow any
+ quoted null characters in the middle or at the ends of strings because
+ of how expand_word_internal works. remove_quoted_nulls () simply
+ turns STRING into an empty string iff it only consists of a quoted null. */
+/*
+#define remove_quoted_nulls(string) \
+ do { if (QUOTED_NULL (string)) string[0] ='\0'; } while (0)
+*/
+static void
+remove_quoted_nulls (string)
+ char *string;
+{
+ char *nstr, *s, *p;
+
+ nstr = savestring (string);
+ nstr[0] = '\0';
+ for (p = nstr, s = string; *s; s++)
+ {
+ if (*s == CTLESC)
+ {
+ *p++ = *s++; /* CTLESC */
+ if (*s == 0)
+ break;
+ *p++ = *s; /* quoted char */
+ continue;
+ }
+ if (*s == CTLNUL)
+ continue;
+ *p++ = *s;
+ }
+ *p = '\0';
+ strcpy (string, nstr);
+ free (nstr);
+}
+
+/* Perform quoted null character removal on each element of LIST.
+ This modifies LIST. */
+void
+word_list_remove_quoted_nulls (list)
+ WORD_LIST *list;
+{
+ register WORD_LIST *t;
+
+ t = list;
+
+ while (t)
+ {
+ remove_quoted_nulls (t->word->word);
+ t = t->next;
+ }
+}
+
+/* This performs word splitting and quoted null character removal on
+ STRING. */
+
+#define issep(c) (member ((c), separators))
+
+WORD_LIST *
+list_string (string, separators, quoted)
+ register char *string, *separators;
+ int quoted;
+{
+ WORD_LIST *result = (WORD_LIST *)NULL;
+ char *current_word = (char *)NULL, *s;
+ int sindex = 0;
+ int sh_style_split;
+
+ if (!string || !*string)
+ return ((WORD_LIST *)NULL);
+
+ sh_style_split =
+ separators && *separators && (STREQ (separators, " \t\n"));
+
+ /* Remove sequences of whitespace at the beginning of STRING, as
+ long as those characters appear in IFS. Do not do this if
+ STRING is quoted or if there are no separator characters. */
+ if (!quoted || !separators || !*separators)
+ {
+ for (s = string; *s && spctabnl (*s) && issep (*s); s++);
+
+ if (!*s)
+ return ((WORD_LIST *)NULL);
+
+ string = s;
+ }
+
+ /* OK, now STRING points to a word that does not begin with white space.
+ The splitting algorithm is:
+ extract a word, stopping at a separator
+ skip sequences of spc, tab, or nl as long as they are separators
+ This obeys the field splitting rules in Posix.2. */
+
+ while (string[sindex])
+ {
+ current_word = string_extract_verbatim (string, &sindex, separators);
+ if (!current_word)
+ break;
+
+ /* If we have a quoted empty string, add a quoted null argument. We
+ want to preserve the quoted null character iff this is a quoted
+ empty string; otherwise the quoted null characters are removed
+ below. */
+ if (QUOTED_NULL (current_word))
+ {
+ WORD_DESC *t = make_word (" ");
+ t->quoted++;
+ free (t->word);
+ t->word = make_quoted_char ('\0');
+ result = make_word_list (t, result);
+ }
+ else if (strlen (current_word))
+ {
+ /* If we have something, then add it regardless. However,
+ perform quoted null character removal on the current word. */
+ remove_quoted_nulls (current_word);
+ result = make_word_list (make_word (current_word), result);
+ if (quoted)
+ result->word->quoted = 1;
+ }
+
+ /* If we're not doing sequences of separators in the traditional
+ Bourne shell style, then add a quoted null argument. */
+
+ else if (!sh_style_split && !spctabnl (string[sindex]))
+ {
+ result = make_word_list (make_word (""), result);
+ result->word->quoted = 1;
+ }
+
+ free (current_word);
+
+ /* Move past the current separator character. */
+ if (string[sindex])
+ sindex++;
+
+ /* Now skip sequences of space, tab, or newline characters if they are
+ in the list of separators. */
+ while (string[sindex] && spctabnl (string[sindex]) && issep (string[sindex]))
+ sindex++;
+
+ }
+ return (REVERSE_LIST (result, WORD_LIST *));
+}
+
+/* Parse a single word from STRING, using SEPARATORS to separate fields.
+ ENDPTR is set to the first character after the word. This is used by
+ the `read' builtin.
+ XXX - this function is very similar to list_string; they should be
+ combined - XXX */
+char *
+get_word_from_string (stringp, separators, endptr)
+ char **stringp, *separators, **endptr;
+{
+ register char *s;
+ char *current_word;
+ int sindex, sh_style_split;
+
+ if (!stringp || !*stringp || !**stringp)
+ return ((char *)NULL);
+
+ s = *stringp;
+
+ sh_style_split =
+ separators && *separators && (STREQ (separators, " \t\n"));
+
+ /* Remove sequences of whitespace at the beginning of STRING, as
+ long as those characters appear in IFS. */
+ if (sh_style_split || !separators || !*separators)
+ {
+ for (; *s && spctabnl (*s) && issep (*s); s++);
+
+ /* If the string is nothing but whitespace, update it and return. */
+ if (!*s)
+ {
+ *stringp = s;
+ if (endptr)
+ *endptr = s;
+ return ((char *)NULL);
+ }
+ }
+
+ /* OK, S points to a word that does not begin with white space.
+ Now extract a word, stopping at a separator, save a pointer to
+ the first character after the word, then skip sequences of spc,
+ tab, or nl as long as they are separators.
+
+ This obeys the field splitting rules in Posix.2. */
+ sindex = 0;
+ current_word = string_extract_verbatim (s, &sindex, separators);
+
+ /* Set ENDPTR to the first character after the end of the word. */
+ if (endptr)
+ *endptr = s + sindex;
+
+ /* Move past the current separator character. */
+ if (s[sindex])
+ sindex++;
+
+ /* Now skip sequences of space, tab, or newline characters if they are
+ in the list of separators. */
+ while (s[sindex] && spctabnl (s[sindex]) && issep (s[sindex]))
+ sindex++;
+
+ /* Update STRING to point to the next field. */
+ *stringp = s + sindex;
+ return (current_word);
+}
+
+/* Remove IFS white space at the end of STRING. Start at the end
+ of the string and walk backwards until the beginning of the string
+ or we find a character that's not IFS white space and not CTLESC.
+ Only let CTLESC escape a white space character if SAW_ESCAPE is
+ non-zero. */
+char *
+strip_trailing_ifs_whitespace (string, separators, saw_escape)
+ char *string, *separators;
+ int saw_escape;
+{
+ char *s;
+
+ s = string + STRLEN (string) - 1;
+ while (s > string && ((spctabnl (*s) && issep (*s)) ||
+ (saw_escape && *s == CTLESC && spctabnl (s[1]))))
+ s--;
+ *++s = '\0';
+ return string;
+}
+
+#if defined (PROCESS_SUBSTITUTION)
+#define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC)
+#else
+#define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC)
+#endif
+
+/* If there are any characters in STRING that require full expansion,
+ then call FUNC to expand STRING; otherwise just perform quote
+ removal if necessary. This returns a new string. */
+static char *
+maybe_expand_string (string, quoted, func)
+ char *string;
+ int quoted;
+ WORD_LIST *(*func)();
+{
+ WORD_LIST *list;
+ int i, saw_quote;
+ char *ret;
+
+ for (i = saw_quote = 0; string[i]; i++)
+ {
+ if (EXP_CHAR (string[i]))
+ break;
+ else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
+ saw_quote = 1;
+ }
+
+ if (string[i])
+ {
+ list = (*func) (string, quoted);
+ if (list)
+ {
+ ret = string_list (list);
+ dispose_words (list);
+ }
+ else
+ ret = (char *)NULL;
+ }
+ else if (saw_quote && !quoted)
+ ret = string_quote_removal (string, quoted);
+ else
+ ret = savestring (string);
+ return ret;
+}
+
+/* Given STRING, an assignment string, get the value of the right side
+ of the `=', and bind it to the left side. If EXPAND is true, then
+ perform parameter expansion, command substitution, and arithmetic
+ expansion on the right-hand side. Perform tilde expansion in any
+ case. Do not perform word splitting on the result of expansion. */
+static int
+do_assignment_internal (string, expand)
+ char *string;
+ int expand;
+{
+ int offset = assignment (string);
+ char *name = savestring (string);
+ char *value = (char *)NULL;
+ SHELL_VAR *entry = (SHELL_VAR *)NULL;
+
+ if (name[offset] == '=')
+ {
+ char *temp;
+
+ name[offset] = 0;
+ temp = name + offset + 1;
+
+ if (expand && temp[0])
+ {
+ if (strchr (temp, '~') && unquoted_member ('~', temp))
+ temp = tilde_expand (temp);
+ else
+ temp = savestring (temp);
+
+ value = maybe_expand_string (temp, 0, expand_string_unsplit);
+ free (temp);
+ }
+ else
+ value = savestring (temp);
+ }
+
+ if (value == 0)
+ value = savestring ("");
+
+ entry = bind_variable (name, value);
+
+ if (echo_command_at_execute)
+ fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value);
+
+ stupidly_hack_special_variables (name);
+
+ if (entry)
+ entry->attributes &= ~att_invisible;
+
+ FREE (value);
+ free (name);
+
+ /* Return 1 if the assignment seems to have been performed correctly. */
+ return (entry ? ((entry->attributes & att_readonly) == 0) : 0);
+}
+
+/* Perform the assignment statement in STRING, and expand the
+ right side by doing command and parameter expansion. */
+do_assignment (string)
+ char *string;
+{
+ return do_assignment_internal (string, 1);
+}
+
+/* Given STRING, an assignment string, get the value of the right side
+ of the `=', and bind it to the left side. Do not do command and
+ parameter substitution on the right hand side. */
+do_assignment_no_expand (string)
+ char *string;
+{
+ return do_assignment_internal (string, 0);
+}
+
+/* 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 INDX, 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. */
+inline char *
+sub_append_string (source, target, indx, size)
+ char *source, *target;
+ int *indx, *size;
+{
+ if (source)
+ {
+ int srclen, n;
+
+ srclen = strlen (source);
+ if (srclen >= (int)(*size - *indx))
+ {
+ n = srclen + *indx;
+ n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE);
+ target = xrealloc (target, (*size = n));
+ }
+
+ FASTCOPY (source, target + *indx, srclen);
+ *indx += srclen;
+ target[*indx] = '\0';
+
+ free (source);
+ }
+ return (target);
+}
+
+/* Append the textual representation of NUMBER to TARGET.
+ INDX and SIZE are as in SUB_APPEND_STRING. */
+char *
+sub_append_number (number, target, indx, size)
+ int number, *indx, *size;
+ char *target;
+{
+ char *temp;
+
+ temp = itos (number);
+ return (sub_append_string (temp, target, indx, 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;
+
+ /* Break out of the loop as soon as one of the dollar variables is null. */
+ for (i = 1; i < 10 && dollar_vars[i]; 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 (REVERSE_LIST (list, WORD_LIST *));
+}
+
+/* Make a single large string out of the dollar digit variables,
+ and the rest_of_args. If DOLLAR_STAR is 1, then obey the special
+ case of "$*" with respect to IFS. */
+char *
+string_rest_of_args (dollar_star)
+ int dollar_star;
+{
+ register WORD_LIST *list = list_rest_of_args ();
+ char *string;
+
+ string = dollar_star ? string_list_dollar_star (list) : string_list (list);
+ dispose_words (list);
+ return (string);
+}
+
+/***************************************************
+ * *
+ * Functions to Expand a String *
+ * *
+ ***************************************************/
+/* Call expand_word_internal to expand W and handle error returns.
+ A convenience function for functions that don't want to handle
+ any errors or free any memory before aborting. */
+static WORD_LIST *
+call_expand_word_internal (w, q, c, e)
+ WORD_DESC *w;
+ int q, *c, *e;
+{
+ WORD_LIST *result;
+
+ result = expand_word_internal (w, q, c, e);
+ if (result == &expand_word_error)
+ longjmp (top_level, DISCARD);
+ else if (result == &expand_word_fatal)
+ longjmp (top_level, FORCE_EOF);
+ else
+ return (result);
+}
+
+/* Perform parameter expansion, command substitution, and arithmetic
+ expansion on STRING, as if it were a word. Leave the result quoted. */
+static WORD_LIST *
+expand_string_internal (string, quoted)
+ char *string;
+ int quoted;
+{
+ WORD_DESC td;
+ WORD_LIST *tresult;
+
+ if (!string || !*string)
+ return ((WORD_LIST *)NULL);
+
+ bzero (&td, sizeof (td));
+ td.word = string;
+ tresult = call_expand_word_internal (&td, quoted, (int *)NULL, (int *)NULL);
+ return (tresult);
+}
+
+/* Expand STRING by performing parameter expansion, command substitution,
+ and arithmetic expansion. Dequote the resulting WORD_LIST before
+ returning it, but do not perform word splitting. The call to
+ remove_quoted_nulls () is in here because word splitting normally
+ takes care of quote removal. */
+WORD_LIST *
+expand_string_unsplit (string, quoted)
+ char *string;
+ int quoted;
+{
+ WORD_LIST *value;
+
+ if (!string || !*string)
+ return ((WORD_LIST *)NULL);
+
+ value = expand_string_internal (string, quoted);
+ if (value)
+ {
+ if (value->word)
+ remove_quoted_nulls (value->word->word);
+ dequote_list (value);
+ }
+ return (value);
+}
+
+/* This does not perform word splitting or dequote the WORD_LIST
+ it returns. */
+static WORD_LIST *
+expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at)
+ char *string;
+ int quoted, *dollar_at_p, *has_dollar_at;
+{
+ WORD_DESC td;
+ WORD_LIST *tresult;
+
+ if (string == 0 || *string == '\0')
+ return (WORD_LIST *)NULL;
+
+ bzero (&td, sizeof (td));
+ td.word = string;
+ tresult = call_expand_word_internal (&td, quoted, dollar_at_p, has_dollar_at);
+ return (tresult);
+}
+
+/* Expand STRING just as if you were expanding a word, but do not dequote
+ the resultant WORD_LIST. This is called only from within this file,
+ and is used to correctly preserve quoted characters when expanding
+ things like ${1+"$@"}. This does parameter expansion, command
+ subsitution, arithmetic expansion, and word splitting. */
+static WORD_LIST *
+expand_string_leave_quoted (string, quoted)
+ char *string;
+ int quoted;
+{
+ WORD_LIST *tlist;
+ WORD_LIST *tresult;
+
+ if (!string || !*string)
+ return ((WORD_LIST *)NULL);
+
+ tlist = expand_string_internal (string, quoted);
+
+ if (tlist)
+ {
+ tresult = word_list_split (tlist);
+ dispose_words (tlist);
+ return (tresult);
+ }
+ return ((WORD_LIST *)NULL);
+}
+
+/* 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. This
+ does parameter expansion, command substitution, arithmetic expansion,
+ and word splitting. Dequote the resultant WORD_LIST before returning. */
+WORD_LIST *
+expand_string (string, quoted)
+ char *string;
+ int quoted;
+{
+ WORD_LIST *result;
+
+ if (!string || !*string)
+ return ((WORD_LIST *)NULL);
+
+ result = expand_string_leave_quoted (string, quoted);
+
+ if (result)
+ dequote_list (result);
+ return (result);
+}
+
+/***************************************************
+ * *
+ * Functions to handle quoting chars *
+ * *
+ ***************************************************/
+
+/* I'm going to have to rewrite expansion because filename globbing is
+ beginning to make the entire arrangement ugly. I'll do this soon. */
+static void
+dequote_list (list)
+ register WORD_LIST *list;
+{
+ register char *s;
+
+ while (list)
+ {
+ s = dequote_string (list->word->word);
+ free (list->word->word);
+ list->word->word = s;
+ list = list->next;
+ }
+}
+
+static char *
+make_quoted_char (c)
+ int c;
+{
+ char *temp;
+
+ temp = xmalloc (3);
+ if (c == 0)
+ {
+ temp[0] = CTLNUL;
+ temp[1] = '\0';
+ }
+ else
+ {
+ temp[0] = CTLESC;
+ temp[1] = c;
+ temp[2] = '\0';
+ }
+ return (temp);
+}
+
+/* Quote STRING. Return a new string. */
+static char *
+quote_string (string)
+ char *string;
+{
+ char *result;
+
+ if (!*string)
+ {
+ result = xmalloc (2);
+ result[0] = CTLNUL;
+ result[1] = '\0';
+ }
+ else
+ {
+ register char *t;
+
+ result = xmalloc ((strlen (string) * 2) + 1);
+
+ for (t = result; string && *string; )
+ {
+ *t++ = CTLESC;
+ *t++ = *string++;
+ }
+ *t = '\0';
+ }
+ return (result);
+}
+
+/* De-quoted quoted characters in STRING. */
+char *
+dequote_string (string)
+ char *string;
+{
+ register char *t;
+ char *result;
+
+ result = xmalloc (strlen (string) + 1);
+
+ if (QUOTED_NULL (string))
+ {
+ result[0] = '\0';
+ return (result);
+ }
+
+ /* If no character in the string can be quoted, don't bother examining
+ each character. Just return a copy of the string passed to us. */
+ if (strchr (string, CTLESC) == NULL) /* XXX */
+ { /* XXX */
+ strcpy (result, string); /* XXX */
+ return (result); /* XXX */
+ }
+
+ for (t = result; string && *string; string++)
+ {
+ if (*string == CTLESC)
+ {
+ string++;
+
+ if (!*string)
+ break;
+ }
+
+ *t++ = *string;
+ }
+
+ *t = '\0';
+ return (result);
+}
+
+/* Quote the entire WORD_LIST list. */
+static void
+quote_list (list)
+ WORD_LIST *list;
+{
+ register WORD_LIST *w;
+
+ for (w = list; w; w = w->next)
+ {
+ char *t = w->word->word;
+ w->word->word = quote_string (t);
+ free (t);
+ w->word->quoted = 1;
+ }
+}
+
+/* **************************************************************** */
+/* */
+/* Functions for Removing Patterns */
+/* */
+/* **************************************************************** */
+
+/* Remove the portion of PARAM matched by PATTERN according to OP, where OP
+ can have one of 4 values:
+ RP_LONG_LEFT remove longest matching portion at start of PARAM
+ RP_SHORT_LEFT remove shortest matching portion at start of PARAM
+ RP_LONG_RIGHT remove longest matching portion at end of PARAM
+ RP_SHORT_RIGHT remove shortest matching portion at end of PARAM
+*/
+
+#define RP_LONG_LEFT 1
+#define RP_SHORT_LEFT 2
+#define RP_LONG_RIGHT 3
+#define RP_SHORT_RIGHT 4
+
+static char *
+remove_pattern (param, pattern, op)
+ char *param, *pattern;
+ int op;
+{
+ register int len = param ? strlen (param) : 0;
+ register char *end = param + len;
+ register char *p, *ret, c;
+
+ if (pattern == NULL || *pattern == '\0') /* minor optimization */
+ return (savestring (param));
+
+ if (param == NULL || *param == '\0')
+ return (param);
+
+ switch (op)
+ {
+ case RP_LONG_LEFT: /* remove longest match at start */
+ for (p = end; p >= param; p--)
+ {
+ c = *p; *p = '\0';
+ if (fnmatch (pattern, param, 0) != FNM_NOMATCH)
+ {
+ *p = c;
+ return (savestring (p));
+ }
+ *p = c;
+ }
+ break;
+
+ case RP_SHORT_LEFT: /* remove shortest match at start */
+ for (p = param; p <= end; p++)
+ {
+ c = *p; *p = '\0';
+ if (fnmatch (pattern, param, 0) != FNM_NOMATCH)
+ {
+ *p = c;
+ return (savestring (p));
+ }
+ *p = c;
+ }
+ break;
+
+ case RP_LONG_RIGHT: /* remove longest match at end */
+ for (p = param; p <= end; p++)
+ {
+ if (fnmatch (pattern, p, 0) != FNM_NOMATCH)
+ {
+ c = *p;
+ *p = '\0';
+ ret = savestring (param);
+ *p = c;
+ return (ret);
+ }
+ }
+ break;
+
+ case RP_SHORT_RIGHT: /* remove shortest match at end */
+ for (p = end; p >= param; p--)
+ {
+ if (fnmatch (pattern, p, 0) != FNM_NOMATCH)
+ {
+ c = *p;
+ *p = '\0';
+ ret = savestring (param);
+ *p = c;
+ return (ret);
+ }
+ }
+ break;
+ }
+ return (savestring (param)); /* no match, return original string */
+}
+
+/*******************************************
+ * *
+ * Functions to expand WORD_DESCs *
+ * *
+ *******************************************/
+
+/* Expand WORD, performing word splitting on the result. This does
+ parameter expansion, command substitution, arithmetic expansion,
+ word splitting, and quote removal. */
+
+WORD_LIST *
+expand_word (word, quoted)
+ WORD_DESC *word;
+ int quoted;
+{
+ WORD_LIST *result, *tresult;
+
+ tresult = call_expand_word_internal (word, quoted, (int *)NULL, (int *)NULL);
+ result = word_list_split (tresult);
+ dispose_words (tresult);
+ if (result)
+ dequote_list (result);
+ return (result);
+}
+
+/* Expand WORD, but do not perform word splitting on the result. This
+ does parameter expansion, command substitution, arithmetic expansion,
+ and quote removal. */
+WORD_LIST *
+expand_word_no_split (word, quoted)
+ WORD_DESC *word;
+ int quoted;
+{
+ WORD_LIST *result;
+
+ result = call_expand_word_internal (word, quoted, (int *)NULL, (int *)NULL);
+ if (result)
+ dequote_list (result);
+ return (result);
+}
+
+/* Perform shell expansions on WORD, but do not perform word splitting or
+ quote removal on the result. */
+WORD_LIST *
+expand_word_leave_quoted (word, quoted)
+ WORD_DESC *word;
+ int quoted;
+{
+ WORD_LIST *result;
+
+ result = call_expand_word_internal (word, quoted, (int *)NULL, (int *)NULL);
+ return (result);
+}
+
+/* Return the value of a positional parameter. This handles values > 10. */
+char *
+get_dollar_var_value (ind)
+ int ind;
+{
+ char *temp;
+
+ if (ind < 10)
+ {
+ if (dollar_vars[ind])
+ temp = savestring (dollar_vars[ind]);
+ else
+ temp = (char *)NULL;
+ }
+ else /* We want something like ${11} */
+ {
+ WORD_LIST *p = rest_of_args;
+
+ ind -= 10;
+ while (p && ind--)
+ p = p->next;
+ if (p)
+ temp = savestring (p->word->word);
+ else
+ temp = (char *)NULL;
+ }
+ return (temp);
+}
+
+#if defined (PROCESS_SUBSTITUTION)
+
+/* **************************************************************** */
+/* */
+/* Hacking Process Substitution */
+/* */
+/* **************************************************************** */
+
+extern struct fd_bitmap *current_fds_to_close;
+extern char *mktemp ();
+
+#if !defined (HAVE_DEV_FD)
+/* Named pipes must be removed explicitly with `unlink'. This keeps a list
+ of FIFOs the shell has open. unlink_fifo_list will walk the list and
+ unlink all of them. add_fifo_list adds the name of an open FIFO to the
+ list. NFIFO is a count of the number of FIFOs in the list. */
+#define FIFO_INCR 20
+
+static char **fifo_list = (char **)NULL;
+static int nfifo = 0;
+static int fifo_list_size = 0;
+
+static void
+add_fifo_list (pathname)
+ char *pathname;
+{
+ if (nfifo >= fifo_list_size - 1)
+ {
+ fifo_list_size += FIFO_INCR;
+ fifo_list = (char **)xrealloc (fifo_list,
+ fifo_list_size * sizeof (char *));
+ }
+
+ fifo_list[nfifo++] = savestring (pathname);
+}
+
+void
+unlink_fifo_list ()
+{
+ if (!nfifo)
+ return;
+
+ while (nfifo--)
+ {
+ unlink (fifo_list[nfifo]);
+ free (fifo_list[nfifo]);
+ fifo_list[nfifo] = (char *)NULL;
+ }
+ nfifo = 0;
+}
+
+static char *
+make_named_pipe ()
+{
+ char *tname;
+
+ tname = mktemp (savestring ("/tmp/sh-np-XXXXXX"));
+ if (mkfifo (tname, 0600) < 0)
+ {
+ free (tname);
+ return ((char *)NULL);
+ }
+
+ add_fifo_list (tname);
+ return (tname);
+}
+
+#if !defined (_POSIX_VERSION)
+int
+mkfifo (path, mode)
+ char *path;
+ int mode;
+{
+#if defined (S_IFIFO)
+ return (mknod (path, (mode | S_IFIFO), 0));
+#else /* !S_IFIFO */
+ return (-1);
+#endif /* !S_IFIFO */
+}
+#endif /* !_POSIX_VERSION */
+
+#else /* HAVE_DEV_FD */
+
+/* DEV_FD_LIST is a bitmap of file descriptors attached to pipes the shell
+ has open to children. NFDS is a count of the number of bits currently
+ set in DEV_FD_LIST. TOTFDS is a count of the highest possible number
+ of open files. */
+static char *dev_fd_list = (char *)NULL;
+static int nfds = 0;
+static int totfds; /* The highest possible number of open files. */
+
+static void
+add_fifo_list (fd)
+ int fd;
+{
+ if (!dev_fd_list || fd >= totfds)
+ {
+ int ofds;
+
+ ofds = totfds;
+ totfds = getdtablesize ();
+ if (totfds < 0 || totfds > 256)
+ totfds = 256;
+ if (fd > totfds)
+ totfds = fd + 2;
+
+ dev_fd_list = xrealloc (dev_fd_list, totfds);
+ bzero (dev_fd_list + ofds, totfds - ofds);
+ }
+
+ dev_fd_list[fd] = 1;
+ nfds++;
+}
+
+void
+unlink_fifo_list ()
+{
+ register int i;
+
+ if (!nfds)
+ return;
+
+ for (i = 0; nfds && i < totfds; i++)
+ if (dev_fd_list[i])
+ {
+ close (i);
+ dev_fd_list[i] = 0;
+ nfds--;
+ }
+
+ nfds = 0;
+}
+
+#if defined (NOTDEF)
+print_dev_fd_list ()
+{
+ register int i;
+
+ fprintf (stderr, "pid %d: dev_fd_list:", getpid ());
+ fflush (stderr);
+
+ for (i = 0; i < totfds; i++)
+ {
+ if (dev_fd_list[i])
+ fprintf (stderr, " %d", i);
+ }
+ fprintf (stderr, "\n");
+}
+#endif /* NOTDEF */
+
+static char *
+make_dev_fd_filename (fd)
+ int fd;
+{
+ char *ret;
+
+ ret = xmalloc (16 * sizeof (char));
+ sprintf (ret, "/dev/fd/%d", fd);
+ add_fifo_list (fd);
+ return (ret);
+}
+
+#endif /* HAVE_DEV_FD */
+
+/* Return a filename that will open a connection to the process defined by
+ executing STRING. HAVE_DEV_FD, if defined, means open a pipe and return
+ a filename in /dev/fd corresponding to a descriptor that is one of the
+ ends of the pipe. If not defined, we use named pipes on systems that have
+ them. Systems without /dev/fd and named pipes are out of luck.
+
+ OPEN_FOR_READ_IN_CHILD, if 1, means open the named pipe for reading or
+ use the read end of the pipe and dup that file descriptor to fd 0 in
+ the child. If OPEN_FOR_READ_IN_CHILD is 0, we open the named pipe for
+ writing or use the write end of the pipe in the child, and dup that
+ file descriptor to fd 1 in the child. The parent does the opposite. */
+
+static char *
+process_substitute (string, open_for_read_in_child)
+ char *string;
+ int open_for_read_in_child;
+{
+ char *pathname;
+ int fd, result;
+ pid_t old_pid, pid;
+#if defined (HAVE_DEV_FD)
+ int parent_pipe_fd, child_pipe_fd;
+ int fildes[2];
+#endif /* HAVE_DEV_FD */
+#if defined (JOB_CONTROL)
+ pid_t old_pipeline_pgrp;
+#endif
+
+ if (!string || !*string)
+ return ((char *)NULL);
+
+#if !defined (HAVE_DEV_FD)
+ pathname = make_named_pipe ();
+#else /* HAVE_DEV_FD */
+ if (pipe (fildes) < 0)
+ {
+ internal_error ("can't make pipes for process substitution: %s",
+ strerror (errno));
+ return ((char *)NULL);
+ }
+ /* If OPEN_FOR_READ_IN_CHILD == 1, we want to use the write end of
+ the pipe in the parent, otherwise the read end. */
+ parent_pipe_fd = fildes[open_for_read_in_child];
+ child_pipe_fd = fildes[1 - open_for_read_in_child];
+ pathname = make_dev_fd_filename (parent_pipe_fd);
+#endif /* HAVE_DEV_FD */
+
+ if (!pathname)
+ {
+ internal_error ("cannot make pipe for process subsitution: %s",
+ strerror (errno));
+ return ((char *)NULL);
+ }
+
+ old_pid = last_made_pid;
+
+#if defined (JOB_CONTROL)
+ old_pipeline_pgrp = pipeline_pgrp;
+ pipeline_pgrp = shell_pgrp;
+ cleanup_the_pipeline ();
+ pid = make_child ((char *)NULL, 1);
+ if (pid == 0)
+ {
+ /* Cancel traps, in trap.c. */
+ restore_original_signals ();
+ setup_async_signals ();
+ subshell_environment++;
+ }
+ set_sigchld_handler ();
+ stop_making_children ();
+ pipeline_pgrp = old_pipeline_pgrp;
+#else /* !JOB_CONTROL */
+ pid = make_child ((char *)NULL, 1);
+ if (pid == 0)
+ {
+ /* Cancel traps, in trap.c. */
+ restore_original_signals ();
+ setup_async_signals ();
+ subshell_environment++;
+ }
+#endif /* !JOB_CONTROL */
+
+ if (pid < 0)
+ {
+ internal_error ("cannot make a child for process substitution: %s",
+ strerror (errno));
+ free (pathname);
+#if defined (HAVE_DEV_FD)
+ close (parent_pipe_fd);
+ close (child_pipe_fd);
+#endif /* HAVE_DEV_FD */
+ return ((char *)NULL);
+ }
+
+ if (pid > 0)
+ {
+ last_made_pid = old_pid;
+
+#if defined (JOB_CONTROL) && defined (PGRP_PIPE)
+ close_pgrp_pipe ();
+#endif /* JOB_CONTROL && PGRP_PIPE */
+
+#if defined (HAVE_DEV_FD)
+ close (child_pipe_fd);
+#endif /* HAVE_DEV_FD */
+
+ return (pathname);
+ }
+
+ set_sigint_handler ();
+
+#if defined (JOB_CONTROL)
+ set_job_control (0);
+#endif /* JOB_CONTROL */
+
+#if !defined (HAVE_DEV_FD)
+ /* Open the named pipe in the child. */
+ fd = open (pathname, open_for_read_in_child ? O_RDONLY : O_WRONLY);
+ if (fd < 0)
+ {
+ internal_error ("cannot open named pipe %s for %s: %s", pathname,
+ open_for_read_in_child ? "reading" : "writing", strerror (errno));
+ exit (127);
+ }
+#else /* HAVE_DEV_FD */
+ fd = child_pipe_fd;
+#endif /* HAVE_DEV_FD */
+
+ if (dup2 (fd, open_for_read_in_child ? 0 : 1) < 0)
+ {
+ internal_error ("cannot duplicate named pipe %s as fd %d: %s",
+ pathname, open_for_read_in_child ? 0 : 1, strerror (errno));
+ exit (127);
+ }
+
+ close (fd);
+
+ /* Need to close any files that this process has open to pipes inherited
+ from its parent. */
+ if (current_fds_to_close)
+ {
+ close_fd_bitmap (current_fds_to_close);
+ current_fds_to_close = (struct fd_bitmap *)NULL;
+ }
+
+#if defined (HAVE_DEV_FD)
+ /* Make sure we close the parent's end of the pipe and clear the slot
+ in the fd list so it is not closed later, if reallocated by, for
+ instance, pipe(2). */
+ close (parent_pipe_fd);
+ dev_fd_list[parent_pipe_fd] = 0;
+#endif /* HAVE_DEV_FD */
+
+ result = parse_and_execute (string, "process substitution", 0);
+
+#if !defined (HAVE_DEV_FD)
+ /* Make sure we close the named pipe in the child before we exit. */
+ close (open_for_read_in_child ? 0 : 1);
+#endif /* !HAVE_DEV_FD */
+
+ exit (result);
+ /*NOTREACHED*/
+}
+#endif /* PROCESS_SUBSTITUTION */
+
+/* Perform command substitution on STRING. This returns a string,
+ possibly quoted. */
+static char *
+command_substitute (string, quoted)
+ char *string;
+ int quoted;
+{
+ pid_t pid, old_pid;
+ int fildes[2];
+ char *istring = (char *)NULL;
+ int istring_index, istring_size, c = 1;
+ int result;
+
+ istring_index = istring_size = 0;
+
+ /* Don't fork () if there is no need to. In the case of no command to
+ run, just return NULL. */
+ if (!string || !*string || (string[0] == '\n' && !string[1]))
+ return ((char *)NULL);
+
+ /* Pipe the output of executing STRING into the current shell. */
+ if (pipe (fildes) < 0)
+ {
+ internal_error ("Can't make pipes for command substitution!");
+ goto error_exit;
+ }
+
+ old_pid = last_made_pid;
+#if defined (JOB_CONTROL)
+ {
+ pid_t old_pipeline_pgrp = pipeline_pgrp;
+
+ pipeline_pgrp = shell_pgrp;
+ cleanup_the_pipeline ();
+ pid = make_child ((char *)NULL, 0);
+ if (pid == 0)
+ /* Reset the signal handlers in the child, but don't free the
+ trap strings. */
+ reset_signal_handlers ();
+ set_sigchld_handler ();
+ stop_making_children ();
+ pipeline_pgrp = old_pipeline_pgrp;
+ }
+#else /* !JOB_CONTROL */
+ pid = make_child ((char *)NULL, 0);
+
+ if (pid == 0)
+ /* Reset the signal handlers in the child, but don't free the
+ trap strings. */
+ reset_signal_handlers ();
+#endif /* !JOB_CONTROL */
+
+ if (pid < 0)
+ {
+ internal_error ("Can't make a child for command substitution: %s",
+ strerror (errno));
+ error_exit:
+
+ FREE (istring);
+ close (fildes[0]);
+ close (fildes[1]);
+ return ((char *)NULL);
+ }
+
+ if (pid == 0)
+ {
+ set_sigint_handler (); /* XXX */
+#if defined (JOB_CONTROL)
+ set_job_control (0);
+#endif
+ if (dup2 (fildes[1], 1) < 0)
+ {
+ internal_error
+ ("command_substitute: cannot duplicate pipe as fd 1: %s",
+ strerror (errno));
+ exit (EXECUTION_FAILURE);
+ }
+
+ /* If standard output is closed in the parent shell
+ (such as after `exec >&-'), file descriptor 1 will be
+ the lowest available file descriptor, and end up in
+ fildes[0]. This can happen for stdin and stderr as well,
+ but stdout is more important -- it will cause no output
+ to be generated from this command. */
+ if ((fildes[1] != fileno (stdin)) &&
+ (fildes[1] != fileno (stdout)) &&
+ (fildes[1] != fileno (stderr)))
+ close (fildes[1]);
+
+ if ((fildes[0] != fileno (stdin)) &&
+ (fildes[0] != fileno (stdout)) &&
+ (fildes[0] != fileno (stderr)))
+ close (fildes[0]);
+
+ /* The currently executing shell is not interactive. */
+ interactive = 0;
+
+ /* Command substitution does not inherit the -e flag. */
+ exit_immediately_on_error = 0;
+
+ remove_quoted_escapes (string);
+
+ /* Give command substitution a place to jump back to on failure,
+ so we don't go back up to main (). */
+ result = setjmp (top_level);
+
+ if (result == EXITPROG)
+ exit (last_command_exit_value);
+ else if (result)
+ exit (EXECUTION_FAILURE);
+ else
+ exit (parse_and_execute (string, "command substitution", -1));
+ }
+ else
+ {
+ FILE *istream;
+
+ istream = fdopen (fildes[0], "r");
+
+#if defined (JOB_CONTROL) && defined (PGRP_PIPE)
+ close_pgrp_pipe ();
+#endif /* JOB_CONTROL && PGRP_PIPE */
+
+ close (fildes[1]);
+
+ if (!istream)
+ {
+ internal_error ("Can't reopen pipe to command substitution (fd %d): %s",
+ fildes[0], strerror (errno));
+ goto error_exit;
+ }
+
+ /* Read the output of the command through the pipe. */
+ while (1)
+ {
+#if defined (NO_READ_RESTART_ON_SIGNAL)
+ c = getc_with_restart (istream);
+#else
+ c = getc (istream);
+#endif /* !NO_READ_RESTART_ON_SIGNAL */
+
+ if (c == EOF)
+ break;
+
+ /* Add the character to ISTRING. */
+ if (istring_index + 2 >= istring_size)
+ {
+ while (istring_index + 2 >= istring_size)
+ istring_size += DEFAULT_ARRAY_SIZE;
+ istring = xrealloc (istring, istring_size);
+ }
+
+ if (quoted || c == CTLESC || c == CTLNUL)
+ istring[istring_index++] = CTLESC;
+
+ istring[istring_index++] = c;
+ istring[istring_index] = '\0';
+ }
+
+ fclose (istream);
+ close (fildes[0]);
+
+ last_command_exit_value = wait_for (pid);
+ last_command_subst_pid = pid;
+ last_made_pid = old_pid;
+
+#if defined (JOB_CONTROL)
+ /* If last_command_exit_value > 128, then the substituted command
+ was terminated by a signal. If that signal was SIGINT, then send
+ SIGINT to ourselves. This will break out of loops, for instance. */
+ if (last_command_exit_value == (128 + SIGINT))
+ kill (getpid (), SIGINT);
+
+ /* wait_for gives the terminal back to shell_pgrp. If some other
+ process group should have it, give it away to that group here. */
+ if (interactive && pipeline_pgrp != (pid_t)0)
+ give_terminal_to (pipeline_pgrp);
+#endif /* JOB_CONTROL */
+
+ /* If we read no output, just return now and save ourselves some
+ trouble. */
+ if (istring_index == 0)
+ goto error_exit;
+
+ /* Strip trailing newlines from the output of the command. */
+ if (quoted)
+ {
+ while (istring_index > 0)
+ {
+ if (istring[istring_index - 1] == '\n')
+ {
+ --istring_index;
+
+ /* If the newline was quoted, remove the quoting char. */
+ if (istring[istring_index - 1] == CTLESC)
+ --istring_index;
+ }
+ else
+ break;
+ }
+ istring[istring_index] = '\0';
+ }
+ else
+ strip_trailing (istring, 1);
+
+ return (istring);
+ }
+}
+
+/********************************************************
+ * *
+ * Utility functions for parameter expansion *
+ * *
+ ********************************************************/
+
+/* Handle removing a pattern from a string as a result of ${name%[%]value}
+ or ${name#[#]value}. */
+static char *
+parameter_brace_remove_pattern (value, temp, c)
+ char *value, *temp;
+ int c;
+{
+ int pattern_specifier;
+ WORD_LIST *l;
+ char *pattern, *t, *tword;
+
+ if (c == '#')
+ {
+ if (*value == '#')
+ {
+ value++;
+ pattern_specifier = RP_LONG_LEFT;
+ }
+ else
+ pattern_specifier = RP_SHORT_LEFT;
+ }
+ else /* c == '%' */
+ {
+ if (*value == '%')
+ {
+ value++;
+ pattern_specifier = RP_LONG_RIGHT;
+ }
+ else
+ pattern_specifier = RP_SHORT_RIGHT;
+ }
+
+ /* Posix.2 says that the WORD should be run through tilde expansion,
+ parameter expansion, command substitution and arithmetic expansion.
+ This leaves the result quoted, so quote_string_for_globbing () has
+ to be called to fix it up for fnmatch (). */
+ if (strchr (value, '~'))
+ tword = tilde_expand (value);
+ else
+ tword = savestring (value);
+
+ /* expand_string_internal () leaves WORD quoted and does not perform
+ word splitting. */
+ l = expand_string_internal (tword, 0);
+ free (tword);
+ pattern = string_list (l);
+ dispose_words (l);
+
+ if (pattern)
+ {
+ tword = quote_string_for_globbing (pattern, 1);
+ free (pattern);
+ pattern = tword;
+ }
+
+ t = remove_pattern (temp, pattern, pattern_specifier);
+
+ FREE (pattern);
+ return (t);
+}
+
+static int
+valid_brace_expansion_word (name, var_is_special)
+ char *name;
+ int var_is_special;
+{
+ if (digit (*name) && all_digits (name))
+ return 1;
+ else if (var_is_special)
+ return 1;
+ else if (legal_identifier (name))
+ return 1;
+ else
+ return 0;
+}
+/* Parameter expand NAME, and return a new string which is the expansion,
+ or NULL if there was no expansion.
+ VAR_IS_SPECIAL is non-zero if NAME is one of the special variables in
+ the shell, e.g., "@", "$", "*", etc. QUOTED, if non-zero, means that
+ NAME was found inside of a double-quoted expression. */
+static char *
+parameter_brace_expand_word (name, var_is_special, quoted)
+ char *name;
+ int var_is_special, quoted;
+{
+ char *temp = (char *)NULL;
+
+ /* Handle multiple digit arguments, as in ${11}. */
+ if (digit (*name))
+ {
+ int arg_index = atoi (name);
+
+ temp = get_dollar_var_value (arg_index);
+ }
+ else if (var_is_special) /* ${@} */
+ {
+ char *tt;
+ WORD_LIST *l;
+
+ tt = xmalloc (2 + strlen (name));
+ tt[0] = '$'; tt[1] = '\0';
+ strcpy (tt + 1, name);
+ l = expand_string_leave_quoted (tt, quoted);
+ free (tt);
+ temp = string_list (l);
+ dispose_words (l);
+ }
+ else
+ {
+ SHELL_VAR *var = find_variable (name);
+
+ if (var && !invisible_p (var) && (temp = value_cell (var)))
+ temp = quoted && temp && *temp ? quote_string (temp)
+ : quote_escapes (temp);
+ }
+ return (temp);
+}
+
+/* Expand the right side of a parameter expansion of the form ${NAMEcVALUE},
+ depending on the value of C, the separating character. C can be one of
+ "-", "+", or "=". */
+static char *
+parameter_brace_expand_rhs (name, value, c, quoted)
+ char *name, *value;
+ int c, quoted;
+{
+ WORD_LIST *l;
+ char *t, *t1, *temp;
+ int i, lquote, hasdol;
+
+ if (value[0] == '~' ||
+ (strchr (value, '~') && unquoted_substring ("=~", value)))
+ temp = tilde_expand (value);
+ else
+ temp = savestring (value);
+
+ /* This is a hack. A better fix is coming later. */
+ lquote = 0;
+ if (*temp == '"' && temp[strlen (temp) - 1] == '"')
+ {
+ i = 1;
+ t = string_extract_double_quoted (temp, &i); /* XXX */
+ free (temp);
+ temp = t;
+ lquote = 1; /* XXX */
+ }
+ hasdol = 0;
+ /* XXX was quoted not lquote */
+ l = *temp ? expand_string_for_rhs (temp, quoted||lquote, &hasdol, (int *)NULL)
+ : (WORD_LIST *)NULL;
+ free (temp);
+ /* expand_string_for_rhs does not dequote the word list it returns, but
+ there are a few cases in which we need to add quotes. */
+ if (lquote && quoted == 0 && hasdol == 0 && l && l->word->quoted == 0)
+ quote_list (l);
+
+ if (l)
+ {
+ temp = string_list (l);
+ dispose_words (l);
+ }
+ else if (lquote)
+ {
+ temp = xmalloc (2);
+ temp[0] = CTLNUL;
+ temp[1] = '\0';
+ }
+ else
+ temp = (char *)NULL;
+
+ if (c == '-' || c == '+')
+ return (temp);
+
+ /* c == '=' */
+ if (temp)
+ t = savestring (temp);
+ else
+ t = savestring ("");
+ t1 = dequote_string (t);
+ free (t);
+ bind_variable (name, t1);
+ free (t1);
+ return (temp);
+}
+
+/* Deal with the right hand side of a ${name:?value} expansion in the case
+ that NAME is null or not set. If VALUE is non-null it is expanded and
+ used as the error message to print, otherwise a standard message is
+ printed. */
+static void
+parameter_brace_expand_error (name, value)
+ char *name, *value;
+{
+ if (value && *value)
+ {
+ WORD_LIST *l = expand_string (value, 0);
+ char *temp1 = string_list (l);
+ report_error ("%s: %s", name, temp1 ? temp1 : value);
+ FREE (temp1);
+ dispose_words (l);
+ }
+ else
+ report_error ("%s: parameter null or not set", name);
+
+ /* Free the data we have allocated during this expansion, since we
+ are about to longjmp out. */
+ free (name);
+ FREE (value);
+}
+
+/* Return 1 if NAME is something for which parameter_brace_expand_length is
+ OK to do. */
+static int
+valid_length_expression (name)
+ char *name;
+{
+ return (!name[1] || /* ${#} */
+ ((name[1] == '@' || name[1] == '*') && !name[2]) || /* ${#@}, ${#*} */
+ (digit (name[1]) && all_digits (name + 1)) || /* ${#11} */
+ legal_identifier (name + 1)); /* ${#PS1} */
+}
+
+/* Handle the parameter brace expansion that requires us to return the
+ length of a parameter. */
+static int
+parameter_brace_expand_length (name)
+ char *name;
+{
+ char *t;
+ int number = 0;
+
+ if (name[1] == '\0') /* ${#} */
+ {
+ WORD_LIST *l = list_rest_of_args ();
+ number = list_length (l);
+ dispose_words (l);
+ }
+ else if (name[1] != '*' && name[1] != '@')
+ {
+ number = 0;
+
+ if (digit (name[1])) /* ${#1} */
+ {
+ if (t = get_dollar_var_value (atoi (name + 1)))
+ {
+ number = strlen (t);
+ free (t);
+ }
+ }
+ else /* ${#PS1} */
+ {
+ WORD_LIST *list;
+ char *newname;
+
+ newname = savestring (name);
+ newname[0] = '$';
+ list = expand_string (newname, 0);
+ t = string_list (list);
+ free (newname);
+ dispose_words (list);
+
+ if (t)
+ number = strlen (t);
+
+ FREE (t);
+ }
+ }
+ else /* ${#@} and ${#*} */
+ {
+#if !defined (KSH_INCOMPATIBLE)
+ WORD_LIST *l = list_rest_of_args ();
+ number = l ? list_length (l) : 0;
+ dispose_words (l);
+#else
+ if (t = string_rest_of_args (1))
+ {
+ number = strlen (t);
+ free (t);
+ }
+#endif /* KSH_INCOMPATIBLE */
+ }
+ return (number);
+}
+
+/* Make a word list which is the parameter and variable expansion,
+ command substitution, arithmetic substitution, and quote removed
+ expansion of WORD. Return a pointer to a WORD_LIST which is the
+ result of the expansion. If WORD contains a null word, the word
+ list returned is also null.
+
+ QUOTED, when non-zero specifies that the text of WORD is treated
+ as if it were surrounded by double quotes.
+ CONTAINS_DOLLAR_AT and EXPANDED_SOMETHING are return values; when non-null
+ they point to an integer value which receives information about expansion.
+ CONTAINS_DOLLAR_AT gets non-zero if WORD contained "$@", else zero.
+ EXPANDED_SOMETHING get non-zero if WORD contained any parameter expansions,
+ else zero.
+
+ This only does word splitting in the case of $@ expansion. In that
+ case, we split on ' '. */
+
+/* Values for the local variable quoted_state. */
+#define UNQUOTED 0
+#define PARTIALLY_QUOTED 1
+#define WHOLLY_QUOTED 2
+
+static WORD_LIST *
+expand_word_internal (word, quoted, contains_dollar_at, expanded_something)
+ WORD_DESC *word;
+ int quoted;
+ int *contains_dollar_at;
+ int *expanded_something;
+{
+ /* The thing that we finally output. */
+ WORD_LIST *result = (WORD_LIST *)NULL;
+
+ /* The intermediate string that we build while expanding. */
+ char *istring = 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. */
+ int sindex = 0;
+
+ /* This gets 1 if we see a $@ while quoted. */
+ int quoted_dollar_at = 0;
+
+ /* One of UNQUOTED, PARTIALLY_QUOTED, or WHOLLY_QUOTED, depending on
+ whether WORD contains no quoting characters, a partially quoted
+ string (e.g., "xx"ab), or is fully quoted (e.g., "xxab"). */
+ int quoted_state = UNQUOTED;
+
+ register int c; /* Current character. */
+ int number; /* Temporary number value. */
+ int t_index; /* For calls to string_extract_xxx. */
+ char *command_subst_result; /* For calls to command_substitute (). */
+
+ istring[0] = '\0';
+
+ if (!string) goto final_exit;
+
+ if (contains_dollar_at)
+ *contains_dollar_at = 0;
+
+ /* Begin the expansion. */
+
+ for (;;)
+ {
+ c = string[sindex];
+
+ /* Case on toplevel character. */
+ switch (c)
+ {
+ case '\0':
+ goto finished_with_string;
+
+ case CTLESC:
+ temp = xmalloc (3);
+ temp[0] = CTLESC;
+ temp[1] = c = string[++sindex];
+ temp[2] = '\0';
+
+ if (string[sindex])
+ sindex++;
+
+ goto add_string;
+
+#if defined (PROCESS_SUBSTITUTION)
+ /* Process substitution. */
+ case '<':
+ case '>':
+ {
+ char *temp1;
+ int old_index;
+
+ if (string[++sindex] != '(' || quoted || posixly_correct)
+ {
+ sindex--;
+ goto add_character;
+ }
+ else
+ old_index = ++sindex; /* skip past both '<' and '(' */
+
+ temp1 = extract_process_subst
+ (string, (c == '<') ? "<(" : ">(", &old_index);
+ sindex = old_index;
+
+ /* If the process substitution specification is `<()', we want to
+ open the pipe for writing in the child and produce output; if
+ it is `>()', we want to open the pipe for reading in the child
+ and consume input. */
+ temp = process_substitute (temp1, (c == '>'));
+
+ FREE (temp1);
+
+ goto dollar_add_string;
+ }
+#endif /* PROCESS_SUBSTITUTION */
+
+ /* See about breaking this into a separate function:
+ char *
+ param_expand (string, sindex, quoted, expanded_something,
+ contains_dollar_at, quoted_dollar_at)
+ char *string;
+ int *sindex, quoted, *expanded_something, *contains_dollar_at;
+ int *quoted_dollar_at;
+ */
+ case '$':
+
+ if (expanded_something)
+ *expanded_something = 1;
+
+ c = string[++sindex];
+
+ /* Do simple cases first. Switch on what follows '$'. */
+ switch (c)
+ {
+ /* $0 .. $9? */
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ temp = dollar_vars[digit_value (c)];
+ if (unbound_vars_is_error && temp == (char *)NULL)
+ {
+ report_error ("$%c: unbound variable", c);
+ free (string);
+ free (istring);
+ last_command_exit_value = 1;
+ return (&expand_word_error);
+ }
+ if (temp)
+ temp = savestring (temp);
+ goto dollar_add_string;
+
+ /* $$ -- pid of the invoking shell. */
+ case '$':
+ number = dollar_dollar_pid;
+
+ add_number:
+ temp = itos (number);
+ dollar_add_string:
+ if (string[sindex]) sindex++;
+
+ /* Add TEMP to ISTRING. */
+ add_string:
+ istring = sub_append_string
+ (temp, istring, &istring_index, &istring_size);
+ temp = (char *)NULL;
+ break;
+
+ /* $# -- number of positional parameters. */
+ case '#':
+ {
+ WORD_LIST *list = list_rest_of_args ();
+ number = list_length (list);
+ dispose_words (list);
+ goto add_number;
+ }
+
+ /* $? -- return value of the last synchronous command. */
+ case '?':
+ number = last_command_exit_value;
+ goto add_number;
+
+ /* $- -- flags supplied to the shell on invocation or
+ by `set'. */
+ case '-':
+ temp = which_set_flags ();
+ goto dollar_add_string;
+
+ /* $! -- Pid of the last asynchronous command. */
+ case '!':
+ number = (int)last_asynchronous_pid;
+
+ /* If no asynchronous pids have been created, echo nothing. */
+ if (number == (int)NO_PID)
+ {
+ if (string[sindex])
+ sindex++;
+ if (expanded_something)
+ *expanded_something = 0;
+ break;
+ }
+ goto add_number;
+
+ /* The only difference between this and $@ is when the
+ arg is quoted. */
+ case '*': /* `$*' */
+ temp = string_rest_of_args (quoted);
+
+ if (quoted && temp && *temp == '\0' /* && istring_index > 0 */)
+ {
+ free (temp);
+ temp = (char *)NULL;
+ }
+
+ /* In the case of a quoted string, quote the entire arg-list.
+ "$1 $2 $3". */
+ if (quoted && temp)
+ {
+ char *james_brown = temp;
+ temp = quote_string (temp);
+ free (james_brown);
+ }
+ goto dollar_add_string;
+
+ /* When we have "$@" what we want is "$1" "$2" "$3" ... This
+ means that we have to turn quoting off after we split into
+ the individually quoted arguments so that the final split
+ on the first character of $IFS is still done. */
+ case '@': /* `$@' */
+ {
+ WORD_LIST *tlist = list_rest_of_args ();
+ if (quoted && tlist)
+ quote_list (tlist);
+
+ /* We want to flag the fact that we saw this. We can't turn
+ off quoting entirely, because other characters in the
+ string might need it (consider "\"$@\""), but we need some
+ way to signal that the final split on the first character
+ of $IFS should be done, even though QUOTED is 1. */
+ if (quoted)
+ quoted_dollar_at = 1;
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+ temp = string_list (tlist);
+ dispose_words (tlist);
+ goto dollar_add_string;
+ }
+
+ /* ${[#]name[[:]#[#]%[%]-=?+[word]]} */
+ case '{':
+ {
+ int check_nullness = 0;
+ int var_is_set = 0;
+ int var_is_null = 0;
+ int var_is_special = 0;
+ char *name, *value;
+
+ t_index = ++sindex;
+ name = string_extract (string, &t_index, "#%:-=?+}");
+ value = (char *)NULL;
+
+ /* 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] == '?' ||
+ string[sindex] == '#'))
+ {
+ char *tt;
+ t_index++;
+ free (name);
+ tt = string_extract (string, &t_index, "#%:-=?+}");
+ name = 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 ((digit (*name) && all_digits (name)) ||
+ (strlen (name) == 1 && member (*name, "#-?$!@*")))
+ var_is_special++;
+
+ /* Check for special expansion things. */
+ if (*name == '#')
+ {
+ /* Handle ${#-} and ${#?}. They return the lengths of
+ $- and $?, respectively. */
+ if (string[sindex] == '}' &&
+ !name[1] &&
+ !check_nullness &&
+ (c == '-' || c == '?'))
+ {
+ char *s;
+
+ free (name);
+
+ if (c == '-')
+ s = which_set_flags ();
+ else
+ s = itos (last_command_exit_value);
+
+ number = STRLEN (s);
+ FREE (s);
+ goto add_number;
+ }
+
+ /* Don't allow things like ${#:-foo} to go by; they are
+ errors. If we are not pointing at the character just
+ after the closing brace, then we haven't gotten all of
+ the name. Since it begins with a special character,
+ this is a bad substitution. Explicitly check for ${#:},
+ which the rules do not catch. */
+ if (string[sindex - 1] != '}' || member (c, "?-=+") ||
+ (string[sindex - 1] == '}' && !name[1] && c == '}' &&
+ check_nullness))
+ {
+ free (name);
+ name = string;
+ goto bad_substitution;
+ }
+
+ /* Check NAME for validity before trying to go on. */
+ if (!valid_length_expression (name))
+ {
+ free (name);
+ name = string;
+ goto bad_substitution;
+ }
+
+ number = parameter_brace_expand_length (name);
+ free (name);
+ /* We are pointing one character after the brace which
+ closes this expression. Since the code at add_number
+ increments SINDEX, we back up a single character. */
+ sindex--;
+ goto add_number;
+ }
+
+ /* ${@} is identical to $@. */
+ if (name[0] == '@' && name[1] == '\0')
+ {
+ if (quoted)
+ quoted_dollar_at = 1;
+
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+ }
+
+ /* Make sure that NAME is valid before trying to go on. */
+ if (!valid_brace_expansion_word (name, var_is_special))
+ {
+ free (name);
+ name = string;
+ goto bad_substitution;
+ }
+
+ temp =
+ parameter_brace_expand_word (name, var_is_special, quoted);
+
+ if (temp)
+ var_is_set++;
+
+ if (!var_is_set || !temp || !*temp)
+ var_is_null++;
+
+ if (!check_nullness)
+ var_is_null = 0;
+
+ /* Get the rest of the stuff inside the braces. */
+ if (c && c != '}')
+ {
+ /* Extract the contents of the ${ ... } expansion
+ according to the Posix.2 rules. It's much less of
+ a hack that the former extract_delimited_string ()
+ scheme. */
+ value = extract_dollar_brace_string (string, &sindex);
+
+ if (string[sindex] == '}')
+ sindex++;
+ else
+ {
+ free (name);
+ name = string;
+ goto bad_substitution;
+ }
+ }
+ else
+ value = (char *)NULL;
+
+ /* Do the right thing based on which character ended the
+ variable name. */
+ switch (c)
+ {
+ default:
+ free (name);
+ name = string;
+ /* FALL THROUGH */
+
+ case '\0':
+ bad_substitution:
+ report_error ("%s: bad substitution", name ? name : "??");
+ FREE (value);
+ free (temp);
+ free (name);
+ free (istring);
+ return &expand_word_error;
+
+ case '}':
+ if (!var_is_set && unbound_vars_is_error)
+ {
+ report_error ("%s: unbound variable", name);
+ FREE (value);
+ free (temp);
+ free (name);
+ free (string);
+ last_command_exit_value = 1;
+ free (istring);
+ return &expand_word_error;
+ }
+ break;
+
+ case '#': /* ${param#[#]pattern} */
+ case '%': /* ${param%[%]pattern} */
+ {
+ char *t;
+ if (!value || !*value || !temp || !*temp)
+ break;
+ if (quoted)
+ {
+ t = dequote_string (temp);
+ free (temp);
+ temp = t;
+ }
+ t = parameter_brace_remove_pattern (value, temp, c);
+ free (temp);
+ free (value);
+ temp = t;
+ }
+ break;
+
+ case '-':
+ case '=':
+ case '?':
+ case '+':
+ if (var_is_set && !var_is_null)
+ {
+ /* We don't want the value of the named variable for
+ anything, just the value of the right hand side. */
+ if (c == '+')
+ {
+ FREE (temp);
+ if (value)
+ {
+ temp = parameter_brace_expand_rhs
+ (name, value, c, quoted);
+ /* XXX - this is a hack. A better fix is
+ coming later. */
+ if ((value[0] == '$' && value[1] == '@') ||
+ (value[0] == '"' && value[1] == '$' && value[2] == '@'))
+ {
+ if (quoted)
+ quoted_dollar_at++;
+ if (contains_dollar_at)
+ *contains_dollar_at = 1;
+ }
+ free (value);
+ }
+ else
+ temp = (char *)NULL;
+ }
+ else
+ {
+ FREE (value);
+ }
+ /* Otherwise do nothing; just use the value in TEMP. */
+ }
+ else /* VAR not set or VAR is NULL. */
+ {
+ FREE (temp);
+ temp = (char *)NULL;
+ if (c == '=' && var_is_special)
+ {
+ report_error
+ ("$%s: cannot assign in this way", name);
+ free (name);
+ free (value);
+ free (string);
+ free (istring);
+ return &expand_word_error;
+ }
+ else if (c == '?')
+ {
+ free (string);
+ free (istring);
+ parameter_brace_expand_error (name, value);
+ if (!interactive)
+ return &expand_word_fatal;
+ else
+ return &expand_word_error;
+ }
+ else if (c != '+')
+ temp = parameter_brace_expand_rhs
+ (name, value, c, quoted);
+ free (value);
+ }
+ break;
+ } /* end case on closing character. */
+ free (name);
+ goto add_string;
+ } /* end case '{' */
+ /* break; */
+
+ /* Do command or arithmetic substitution. */
+ case '(':
+ /* We have to extract the contents of this paren substitution. */
+ {
+ int old_index = ++sindex;
+
+ temp = extract_command_subst (string, &old_index);
+ sindex = old_index;
+
+ /* For Posix.2-style `$(( ))' arithmetic substitution,
+ extract the expression and pass it to the evaluator. */
+ if (temp && *temp == '(')
+ {
+ char *t = temp + 1;
+ int last = strlen (t) - 1;
+
+ if (t[last] != ')')
+ {
+ report_error ("%s: bad arithmetic substitution", temp);
+ free (temp);
+ free (string);
+ free (istring);
+ return &expand_word_error;
+ }
+
+ /* Cut off ending `)' */
+ t[last] = '\0';
+
+ /* Expand variables found inside the expression. */
+ {
+ WORD_LIST *l;
+
+ l = expand_string (t, 1);
+ t = string_list (l);
+ dispose_words (l);
+ }
+
+ /* No error messages. */
+ this_command_name = (char *)NULL;
+
+ number = evalexp (t);
+ free (temp);
+ free (t);
+
+ goto add_number;
+ }
+
+ goto handle_command_substitution;
+ }
+
+ /* Do straight arithmetic substitution. */
+ case '[':
+ /* We have to extract the contents of this
+ arithmetic substitution. */
+ {
+ char *t;
+ int old_index = ++sindex;
+ WORD_LIST *l;
+
+ temp = extract_arithmetic_subst (string, &old_index);
+ sindex = old_index;
+
+ /* Do initial variable expansion. */
+ l = expand_string (temp, 1);
+ t = string_list (l);
+ dispose_words (l);
+
+ /* No error messages. */
+ this_command_name = (char *)NULL;
+ number = evalexp (t);
+ free (t);
+ free (temp);
+
+ goto add_number;
+ }
+
+ default:
+ {
+ /* Find the variable in VARIABLE_LIST. */
+ int old_index;
+ char *name;
+ SHELL_VAR *var;
+
+ temp = (char *)NULL;
+
+ for (old_index = sindex;
+ (c = string[sindex]) &&
+ (isletter (c) || digit (c) || c == '_');
+ sindex++);
+ name = substring (string, old_index, sindex);
+
+ /* If this isn't a variable name, then just output the `$'. */
+ if (!name || !*name)
+ {
+ FREE (name);
+ temp = savestring ("$");
+ if (expanded_something)
+ *expanded_something = 0;
+ goto add_string;
+ }
+
+ /* If the variable exists, return its value cell. */
+ var = find_variable (name);
+
+ if (var && !invisible_p (var) && value_cell (var))
+ {
+ temp = value_cell (var);
+ temp = quoted && temp && *temp ? quote_string (temp)
+ : quote_escapes (temp);
+ free (name);
+ goto add_string;
+ }
+ else
+ temp = (char *)NULL;
+
+ if (unbound_vars_is_error)
+ report_error ("%s: unbound variable", name);
+ else
+ {
+ free (name);
+ goto add_string;
+ }
+
+ free (name);
+ free (string);
+ last_command_exit_value = 1;
+ free (istring);
+ return &expand_word_error;
+ }
+ }
+ break; /* End case '$': */
+
+ case '`': /* Backquoted command substitution. */
+ {
+ sindex++;
+
+ if (expanded_something)
+ *expanded_something = 1;
+
+ temp = string_extract (string, &sindex, "`");
+ de_backslash (temp);
+
+ handle_command_substitution:
+ command_subst_result = command_substitute (temp, quoted);
+
+ FREE (temp);
+
+ temp = command_subst_result;
+
+ if (string[sindex])
+ sindex++;
+
+ goto add_string;
+ }
+
+ case '\\':
+ if (string[sindex + 1] == '\n')
+ {
+ sindex += 2;
+ continue;
+ }
+ else
+ {
+ char *slashify_chars = "";
+
+ c = string[++sindex];
+
+ if (quoted == Q_HERE_DOCUMENT)
+ slashify_chars = slashify_in_here_document;
+ else if (quoted == Q_DOUBLE_QUOTES)
+ slashify_chars = slashify_in_quotes;
+
+ if (quoted && !member (c, slashify_chars))
+ {
+ temp = xmalloc (3);
+ temp[0] = '\\'; temp[1] = c; temp[2] = '\0';
+ if (c)
+ sindex++;
+ goto add_string;
+ }
+ else
+ {
+ /* This character is quoted, so add it in quoted mode. */
+ temp = make_quoted_char (c);
+ if (c)
+ sindex++;
+ goto add_string;
+ }
+ }
+
+ case '"':
+ if (quoted)
+ goto add_character;
+ sindex++;
+ {
+ WORD_LIST *tresult = (WORD_LIST *)NULL;
+
+ t_index = sindex;
+ temp = string_extract_double_quoted (string, &sindex);
+
+ /* If the quotes surrounded the entire string, then the
+ whole word was quoted. */
+ if (t_index == 1 && !string[sindex])
+ quoted_state = WHOLLY_QUOTED;
+ else
+ quoted_state = PARTIALLY_QUOTED;
+
+ if (temp && *temp)
+ {
+ int dollar_at_flag;
+ int quoting_flags = Q_DOUBLE_QUOTES;
+ WORD_DESC *temp_word = make_word (temp);
+
+ free (temp);
+
+ tresult = expand_word_internal
+ (temp_word, quoting_flags, &dollar_at_flag, (int *)NULL);
+
+ if (tresult == &expand_word_error || tresult == &expand_word_fatal)
+ {
+ free (istring);
+ free (string);
+ /* expand_word_internal has already freed temp_word->word
+ for us because of the way it prints error messages. */
+ temp_word->word = (char *)NULL;
+ dispose_word (temp_word);
+ return tresult;
+ }
+
+ dispose_word (temp_word);
+
+ /* "$@" (a double-quoted dollar-at) expands into nothing,
+ not even a NULL word, when there are no positional
+ parameters. */
+ if (!tresult && dollar_at_flag)
+ {
+ quoted_dollar_at++;
+ break;
+ }
+
+ /* If we get "$@", we know we have expanded something, so we
+ need to remember it for the final split on $IFS. This is
+ a special case; it's the only case where a quoted string
+ can expand into more than one word. It's going to come back
+ from the above call to expand_word_internal as a list with
+ a single word, in which all characters are quoted and
+ separated by blanks. What we want to do is to turn it back
+ into a list for the next piece of code. */
+ dequote_list (tresult);
+
+ if (dollar_at_flag)
+ {
+ quoted_dollar_at++;
+ if (expanded_something)
+ *expanded_something = 1;
+ }
+ }
+ else
+ {
+ /* What we have is "". This is a minor optimization. */
+ free (temp);
+ tresult = (WORD_LIST *)NULL;
+ }
+
+ /* The code above *might* return a list (consider the case of "$@",
+ where it returns "$1", "$2", etc.). We can't throw away the
+ rest of the list, and we have to make sure each word gets added
+ as quoted. We test on tresult->next: if it is non-NULL, we
+ quote the whole list, save it to a string with string_list, and
+ add that string. We don't need to quote the results of this
+ (and it would be wrong, since that would quote the separators
+ as well), so we go directly to add_string. */
+ if (tresult)
+ {
+ if (tresult->next)
+ {
+ quote_list (tresult);
+ temp = string_list (tresult);
+ dispose_words (tresult);
+ goto add_string;
+ }
+ else
+ {
+ temp = savestring (tresult->word->word);
+ dispose_words (tresult);
+ }
+ }
+ else
+ temp = (char *)NULL;
+
+ /* We do not want to add quoted nulls to strings that are only
+ partially quoted; we can throw them away. */
+ if (!temp && (quoted_state == PARTIALLY_QUOTED))
+ continue;
+
+ add_quoted_string:
+
+ if (temp)
+ {
+ char *t = temp;
+ temp = quote_string (temp);
+ free (t);
+ }
+ else
+ {
+ /* Add NULL arg. */
+ temp = xmalloc (2);
+ temp[0] = CTLNUL;
+ temp[1] = '\0';
+ }
+ goto add_string;
+ }
+ /* break; */
+
+ case '\'':
+ {
+ if (!quoted)
+ {
+ sindex++;
+
+ t_index = sindex;
+ temp = string_extract_single_quoted (string, &sindex);
+
+ /* If the entire STRING was surrounded by single quotes,
+ then the string is wholly quoted. */
+ if (t_index == 1 && !string[sindex])
+ quoted_state = WHOLLY_QUOTED;
+ else
+ quoted_state = PARTIALLY_QUOTED;
+
+ /* If all we had was '', it is a null expansion. */
+ if (!*temp)
+ {
+ free (temp);
+ temp = (char *)NULL;
+ }
+ else
+ remove_quoted_escapes (temp);
+
+ /* We do not want to add quoted nulls to strings that are only
+ partially quoted; such nulls are discarded. */
+ if (!temp && (quoted_state == PARTIALLY_QUOTED))
+ continue;
+
+ goto add_quoted_string;
+ }
+ else
+ goto add_character;
+
+ break;
+ }
+
+ default:
+
+ /* This is the fix for " $@ " */
+ if (quoted)
+ {
+ temp = make_quoted_char (c);
+ if (string[sindex])
+ sindex++;
+ goto add_string;
+ }
+
+ add_character:
+ if (istring_index + 1 >= istring_size)
+ {
+ while (istring_index + 1 >= istring_size)
+ istring_size += DEFAULT_ARRAY_SIZE;
+ istring = xrealloc (istring, istring_size);
+ }
+ istring[istring_index++] = c;
+ istring[istring_index] = '\0';
+
+ /* Next character. */
+ sindex++;
+ }
+ }
+
+finished_with_string:
+final_exit:
+ /* OK, we're ready to return. If we have a quoted string, and
+ quoted_dollar_at is not set, we do no splitting at all; otherwise
+ we split on ' '. The routines that call this will handle what to
+ do if nothing has been expanded. */
+ if (istring)
+ {
+ WORD_LIST *temp_list;
+
+ /* Partially and wholly quoted strings which expand to the empty
+ string are retained as an empty arguments. Unquoted strings
+ which expand to the empty string are discarded. The single
+ exception is the case of expanding "$@" when there are no
+ positional parameters. In that case, we discard the expansion. */
+
+ /* Because of how the code that handles "" and '' in partially
+ quoted strings works, we need to make ISTRING into a QUOTED_NULL
+ if we saw quoting characters, but the expansion was empty.
+ "" and '' are tossed away before we get to this point when
+ processing partially quoted strings. This makes "" and $xxx""
+ equivalent when xxx is unset. */
+ if (!*istring && quoted_state == PARTIALLY_QUOTED)
+ {
+ if (istring_size < 2)
+ istring = xrealloc (istring, istring_size += 2);
+ istring[0] = CTLNUL;
+ istring[1] = '\0';
+ }
+
+ /* If we expand to nothing and there were no single or double quotes
+ in the word, we throw it away. Otherwise, we return a NULL word.
+ The single exception is for $@ surrounded by double quotes when
+ there are no positional parameters. In that case, we also throw
+ the word away. */
+ if (!*istring)
+ {
+ if (quoted_state == UNQUOTED ||
+ (quoted_dollar_at && quoted_state == WHOLLY_QUOTED))
+ temp_list = (WORD_LIST *)NULL;
+ else
+ {
+ temp_list = make_word_list
+ (make_word (istring), (WORD_LIST *)NULL);
+ temp_list->word->quoted = quoted;
+ }
+ }
+ else if (word->assignment)
+ {
+ temp_list = make_word_list (make_word (istring), (WORD_LIST *)NULL);
+ temp_list->word->quoted = quoted;
+ temp_list->word->assignment = assignment (temp_list->word->word);
+ }
+ else
+ {
+ char *ifs_chars = (char *)NULL;
+
+ if (quoted_dollar_at)
+ {
+ SHELL_VAR *ifs = find_variable ("IFS");
+ if (ifs)
+ ifs_chars = value_cell (ifs);
+ else
+ ifs_chars = " \t\n";
+ }
+
+ /* According to Posix.2, "$@" expands to a single word if
+ IFS="" and the positional parameters are not empty. */
+ if (quoted_dollar_at && ifs_chars && *ifs_chars)
+ {
+ temp_list = list_string (istring, " ", 1);
+#if 0
+ /* This turns quoted null strings back into CTLNULs */
+ dequote_list (temp_list);
+ quote_list (temp_list);
+#endif
+ }
+ else
+ {
+ WORD_DESC *tword;
+ tword = make_word (istring);
+ temp_list = make_word_list (tword, (WORD_LIST *)NULL);
+ tword->quoted = quoted || (quoted_state == WHOLLY_QUOTED);
+ tword->assignment = word->assignment;
+ }
+ }
+
+ free (istring);
+ result = (WORD_LIST *)
+ list_append (REVERSE_LIST (result, WORD_LIST *), temp_list);
+ }
+ else
+ result = (WORD_LIST *)NULL;
+
+ return (result);
+}
+
+/* **************************************************************** */
+/* */
+/* Functions for Quote Removal */
+/* */
+/* **************************************************************** */
+
+/* Perform quote removal on STRING. If QUOTED > 0, assume we are obeying the
+ backslash quoting rules for within double quotes. */
+char *
+string_quote_removal (string, quoted)
+ char *string;
+ int quoted;
+{
+ char *r, *result_string, *temp, *temp1;
+ int sindex, tindex, c, dquote;
+
+ /* The result can be no longer than the original string. */
+ r = result_string = xmalloc (strlen (string) + 1);
+
+ for (sindex = dquote = 0; c = string[sindex];)
+ {
+ switch (c)
+ {
+ case '\\':
+ c = string[++sindex];
+ if ((quoted || dquote) && !member (c, slashify_in_quotes))
+ *r++ = '\\';
+
+ default:
+ *r++ = c;
+ sindex++;
+ break;
+
+ case '\'':
+ if (quoted || dquote)
+ {
+ *r++ = c;
+ sindex++;
+ }
+ else
+ {
+ tindex = ++sindex;
+ temp = string_extract_single_quoted (string, &tindex);
+ sindex = tindex;
+
+ if (temp)
+ {
+ strcpy (r, temp);
+ r += strlen (r);
+ free (temp);
+ }
+ }
+ break;
+
+ case '"':
+ dquote = 1 - dquote;
+ sindex++;
+ break;
+ }
+ }
+ *r = '\0';
+ return (result_string);
+}
+
+/* Perform quote removal on word WORD. This allocates and returns a new
+ WORD_DESC *. */
+WORD_DESC *
+word_quote_removal (word, quoted)
+ WORD_DESC *word;
+ int quoted;
+{
+ WORD_DESC *w;
+ char *t;
+
+ t = string_quote_removal (word->word, quoted);
+ w = make_word (t);
+ return (w);
+}
+
+/* Perform quote removal on all words in LIST. If QUOTED is non-zero,
+ the members of the list are treated as if they are surrounded by
+ double quotes. Return a new list, or NULL if LIST is NULL. */
+WORD_LIST *
+word_list_quote_removal (list, quoted)
+ WORD_LIST *list;
+ int quoted;
+{
+ WORD_LIST *result = (WORD_LIST *)NULL, *t, *tresult;
+
+ t = list;
+ while (t)
+ {
+ tresult = (WORD_LIST *)xmalloc (sizeof (WORD_LIST));
+ tresult->word = word_quote_removal (t->word, quoted);
+ tresult->next = (WORD_LIST *)NULL;
+ result = (WORD_LIST *) list_append (result, tresult);
+ t = t->next;
+ }
+ return (result);
+}
+
+/* Return 1 if CHARACTER appears in an unquoted portion of
+ STRING. Return 0 otherwise. */
+static int
+unquoted_member (character, string)
+ int character;
+ char *string;
+{
+ int sindex, tindex, c;
+ char *temp;
+
+ sindex = 0;
+
+ while (c = string[sindex])
+ {
+ if (c == character)
+ return (1);
+
+ switch (c)
+ {
+ case '\\':
+ sindex++;
+ if (string[sindex])
+ sindex++;
+ break;
+
+ case '"':
+ case '\'':
+
+ tindex = ++sindex;
+ if (c == '"')
+ temp = string_extract_double_quoted (string, &tindex);
+ else
+ temp = string_extract_single_quoted (string, &tindex);
+ sindex = tindex;
+
+ FREE (temp);
+ break;
+
+ default:
+ sindex++;
+ break;
+ }
+ }
+ return (0);
+}
+
+/* Return 1 if SUBSTR appears in an unquoted portion of STRING. */
+static int
+unquoted_substring (substr, string)
+ char *substr, *string;
+{
+ int sindex, tindex, c, sublen;
+ char *temp;
+
+ if (!substr || !*substr)
+ return (0);
+
+ sublen = strlen (substr);
+ sindex = 0;
+
+ while (c = string[sindex])
+ {
+ if (STREQN (string + sindex, substr, sublen))
+ return (1);
+
+ switch (c)
+ {
+ case '\\':
+ sindex++;
+
+ if (string[sindex])
+ sindex++;
+ break;
+
+ case '"':
+ case '\'':
+
+ tindex = ++sindex;
+
+ if (c == '"')
+ temp = string_extract_double_quoted (string, &tindex);
+ else
+ temp = string_extract_single_quoted (string, &tindex);
+ sindex = tindex;
+
+ FREE (temp);
+
+ break;
+
+ default:
+ sindex++;
+ break;
+ }
+ }
+ return (0);
+}
+
+/*******************************************
+ * *
+ * Functions to perform word splitting *
+ * *
+ *******************************************/
+
+/* This splits a single word into a WORD LIST on $IFS, but only if the word
+ is not quoted. list_string () performs quote removal for us, even if we
+ don't do any splitting. */
+WORD_LIST *
+word_split (w)
+ WORD_DESC *w;
+{
+ WORD_LIST *result;
+
+ if (w)
+ {
+ SHELL_VAR *ifs = find_variable ("IFS");
+ char *ifs_chars;
+
+ /* If IFS is unset, it defaults to " \t\n". */
+ if (ifs)
+ ifs_chars = value_cell (ifs);
+ else
+ ifs_chars = " \t\n";
+
+ if (w->quoted || !ifs_chars)
+ ifs_chars = "";
+
+#ifdef NOT_YET_MAYBE_LATER
+ if (!*ifs)
+ {
+ /* No splitting done if word quoted or ifs set to "". */
+ WORD_DESC *wtemp;
+ wtemp = make_word (w->word);
+ wtemp->quoted = w->quoted;
+ result = make_word_list (wtemp);
+ }
+ else
+#endif
+ result = list_string (w->word, ifs_chars, w->quoted);
+ }
+ else
+ result = (WORD_LIST *)NULL;
+ return (result);
+}
+
+/* Perform word splitting on LIST and return the RESULT. It is possible
+ to return (WORD_LIST *)NULL. */
+static WORD_LIST *
+word_list_split (list)
+ WORD_LIST *list;
+{
+ WORD_LIST *result = (WORD_LIST *)NULL, *t, *tresult;
+
+ t = list;
+ while (t)
+ {
+ tresult = word_split (t->word);
+ result = (WORD_LIST *) list_append (result, tresult);
+ t = t->next;
+ }
+ return (result);
+}
+
+/**************************************************
+ * *
+ * Functions to expand an entire WORD_LIST *
+ * *
+ **************************************************/
+
+static WORD_LIST *varlist = (WORD_LIST *)NULL;
+
+/* Separate out any initial variable assignments from TLIST. If set -k has
+ been executed, remove all assignment statements from TLIST. Initial
+ variable assignments and other environment assignments are placed
+ on VARLIST. */
+static WORD_LIST *
+separate_out_assignments (tlist)
+ WORD_LIST *tlist;
+{
+ register WORD_LIST *vp, *lp;
+
+ if (!tlist)
+ return ((WORD_LIST *)NULL);
+
+ varlist = (WORD_LIST *)NULL;
+ vp = lp = tlist;
+
+ /* Separate out variable assignments at the start of the command.
+ Loop invariant: vp->next == lp
+ Loop postcondition:
+ lp = list of words left after assignment statements skipped
+ tlist = original list of words
+ */
+ while (lp && lp->word->assignment)
+ {
+ vp = lp;
+ lp = lp->next;
+ }
+
+ /* If lp != tlist, we have some initial assignment statements. */
+ /* We make VARLIST point to the list of assignment words and
+ TLIST point to the remaining words. */
+ if (lp != tlist)
+ {
+ varlist = tlist;
+ /* ASSERT(vp->next == lp); */
+ vp->next = (WORD_LIST *)NULL; /* terminate variable list */
+ tlist = lp; /* remainder of word list */
+ }
+
+ /* vp == end of variable list */
+ /* tlist == remainder of original word list without variable assignments */
+ if (!tlist)
+ /* All the words in tlist were assignment statements */
+ return ((WORD_LIST *)NULL);
+
+ /* ASSERT(tlist != NULL); */
+ /* ASSERT(tlist->word->assignment == 0); */
+
+ /* If the -k option is in effect, we need to go through the remaining
+ words, separate out the assignment words, and place them on VARLIST. */
+ if (place_keywords_in_env)
+ {
+ WORD_LIST *tp; /* tp == running pointer into tlist */
+
+ tp = tlist;
+ lp = tlist->next;
+
+ /* Loop Invariant: tp->next == lp */
+ /* Loop postcondition: tlist == word list without assignment statements */
+ while (lp)
+ {
+ if (lp->word->assignment)
+ {
+ /* Found an assignment statement, add this word to end of
+ varlist (vp). */
+ if (!varlist)
+ varlist = vp = lp;
+ else
+ {
+ vp->next = lp;
+ vp = lp;
+ }
+
+ /* Remove the word pointed to by LP from TLIST. */
+ tp->next = lp->next;
+ /* ASSERT(vp == lp); */
+ lp->next = (WORD_LIST *)NULL;
+ lp = tp->next;
+ }
+ else
+ {
+ tp = lp;
+ lp = lp->next;
+ }
+ }
+ }
+ return (tlist);
+}
+
+/* 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;
+{
+ return (expand_words_internal (list, 1));
+}
+
+/* Same as expand_words (), but doesn't hack variable or environment
+ variables. */
+WORD_LIST *
+expand_words_no_vars (list)
+ WORD_LIST *list;
+{
+ return (expand_words_internal (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.
+
+ This does all of the substitutions: brace expansion, tilde expansion,
+ parameter expansion, command substitution, arithmetic expansion,
+ process substitution, word splitting, and pathname expansion.
+ Words with the `quoted' or `assignment' bits set, or for which no
+ expansion is done, do not undergo word splitting. Words with the
+ `assignment' but set do not undergo pathname expansion. */
+static WORD_LIST *
+expand_words_internal (list, do_vars)
+ WORD_LIST *list;
+ int do_vars;
+{
+ register WORD_LIST *tlist, *new_list = (WORD_LIST *)NULL;
+ WORD_LIST *orig_list;
+
+ if (!list)
+ return ((WORD_LIST *)NULL);
+
+ tlist = copy_word_list (list);
+
+ if (do_vars)
+ {
+ tlist = separate_out_assignments (tlist);
+ if (!tlist)
+ {
+ if (varlist)
+ {
+ /* All the words were variable assignments, so they are placed
+ into the shell's environment. */
+ register WORD_LIST *lp;
+ for (lp = varlist; lp; lp = lp->next)
+ do_assignment (lp->word->word);
+ dispose_words (varlist);
+ varlist = (WORD_LIST *)NULL;
+ }
+ return ((WORD_LIST *)NULL);
+ }
+ }
+
+ /* Begin expanding the words that remain. The expansions take place on
+ things that aren't really variable assignments. */
+
+#if defined (BRACE_EXPANSION)
+ /* Do brace expansion on this word if there are any brace characters
+ in the string. */
+ if (!no_brace_expansion)
+ {
+ register char **expansions;
+ WORD_LIST *braces = (WORD_LIST *)NULL, *disposables = (WORD_LIST *)NULL;
+ int eindex;
+
+ while (tlist)
+ {
+ WORD_LIST *next;
+
+ next = tlist->next;
+
+ /* Only do brace expansion if the word has a brace character. If
+ not, just add the word list element to BRACES and continue. In
+ the common case, at least when running shell scripts, this will
+ degenerate to a bunch of calls to `strchr', and then what is
+ basically a reversal of TLIST into BRACES, which is corrected
+ by a call to reverse_list () on BRACES when the end of TLIST
+ is reached. */
+ if (strchr (tlist->word->word, '{'))
+ {
+ expansions = brace_expand (tlist->word->word);
+
+ for (eindex = 0; expansions[eindex]; eindex++)
+ {
+ braces = make_word_list (make_word (expansions[eindex]),
+ braces);
+ free (expansions[eindex]);
+ }
+ free (expansions);
+
+ /* Add TLIST to the list of words to be freed after brace
+ expansion has been performed. */
+ tlist->next = disposables;
+ disposables = tlist;
+ }
+ else
+ {
+ tlist->next = braces;
+ braces = tlist;
+ }
+
+ tlist = next;
+ }
+
+ dispose_words (disposables);
+ tlist = REVERSE_LIST (braces, WORD_LIST *);
+ }
+#endif /* BRACE_EXPANSION */
+
+ orig_list = tlist;
+
+ /* We do tilde expansion all the time. This is what 1003.2 says. */
+ while (tlist)
+ {
+ register char *current_word;
+ WORD_LIST *expanded, *t, *reversed, *next;
+ int expanded_something = 0;
+
+ current_word = tlist->word->word;
+
+ next = tlist->next;
+
+ /* Posix.2 section 3.6.1 says that tildes following `=' in words
+ which are not assignment statements are not expanded. We do
+ this only if POSIXLY_CORRECT is enabled. */
+ if (current_word[0] == '~' ||
+ (!posixly_correct && strchr (current_word, '~') &&
+ unquoted_substring ("=~", current_word)))
+ {
+ char *tt;
+
+ tt = tlist->word->word;
+ tlist->word->word = tilde_expand (tt);
+ free (tt);
+ }
+
+ expanded = expand_word_internal
+ (tlist->word, 0, (int *)NULL, &expanded_something);
+
+ if (expanded == &expand_word_error || expanded == &expand_word_fatal)
+ {
+ /* By convention, each time this error is returned,
+ tlist->word->word has already been freed. */
+ tlist->word->word = (char *)NULL;
+
+ /* Dispose our copy of the original list. */
+ dispose_words (orig_list);
+ /* Dispose the new list we're building. */
+ dispose_words (new_list);
+
+ if (expanded == &expand_word_error)
+ longjmp (top_level, DISCARD);
+ else
+ longjmp (top_level, FORCE_EOF);
+ }
+
+ /* Don't split assignment words, even when they do not precede a
+ command name. */
+ if (expanded_something && tlist->word->assignment == 0)
+ {
+ t = word_list_split (expanded);
+ dispose_words (expanded);
+ }
+ else
+ {
+ /* If no parameter expansion, command substitution, process
+ substitution, or arithmetic substitution took place, then
+ do not do word splitting. We still have to remove quoted
+ null characters from the result. */
+ word_list_remove_quoted_nulls (expanded);
+ t = expanded;
+ }
+
+ /* In the most common cases, t will be a list containing only one
+ element, so the call to reverse_list would be wasted. */
+ reversed = REVERSE_LIST (t, WORD_LIST *);
+ new_list = (WORD_LIST *)list_append (reversed, new_list);
+
+ tlist = next;
+ }
+
+ new_list = REVERSE_LIST (new_list, WORD_LIST *);
+
+ dispose_words (orig_list);
+
+#if defined (USE_POSIX_GLOB_LIBRARY)
+# define GLOB_FAILED(glist) !(glist)
+#else /* !USE_POSIX_GLOB_LIBRARY */
+# define GLOB_FAILED(glist) (glist) == (char **)&glob_error_return
+#endif /* !USE_POSIX_GLOB_LIBRARY */
+
+ /* Okay, we're almost done. Now let's just do some filename
+ globbing. */
+ if (new_list)
+ {
+ char **temp_list = (char **)NULL;
+ register int list_index;
+ WORD_LIST *glob_list, *disposables;
+
+ orig_list = disposables = (WORD_LIST *)NULL;
+ tlist = new_list;
+
+ /* orig_list == output list, despite the name. */
+ if (!disallow_filename_globbing)
+ {
+ while (tlist)
+ {
+ /* For each word, either globbing is attempted or the word is
+ added to orig_list. If globbing succeeds, the results are
+ added to orig_list and the word (tlist) is added to the list
+ of disposable words. If globbing fails and failed glob
+ expansions are left unchanged (the shell default), the
+ original word is added to orig_list. If globbing fails and
+ failed glob expansions are removed, the original word is
+ added to the list of disposable words. orig_list ends up
+ in reverse order and requires a call to reverse_list to
+ be set right. After all words are examined, the disposable
+ words are freed. */
+ WORD_LIST *next;
+
+ next = tlist->next;
+
+ /* If the word isn't quoted and there is an unquoted pattern
+ matching character in the word, then glob it. */
+ if (!tlist->word->quoted && !tlist->word->assignment &&
+ unquoted_glob_pattern_p (tlist->word->word))
+ {
+ temp_list = shell_glob_filename (tlist->word->word);
+
+ /* 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 (GLOB_FAILED (temp_list))
+ {
+ temp_list = (char **) xmalloc (sizeof (char *));
+ temp_list[0] = (char *)NULL;
+ }
+
+ /* Dequote the current word in case we have to use it. */
+ if (!temp_list[0])
+ {
+ register char *t = dequote_string (tlist->word->word);
+ free (tlist->word->word);
+ tlist->word->word = t;
+ }
+
+ /* 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);
+ tlist->next = disposables;
+ disposables = tlist;
+ }
+ else
+ if (!allow_null_glob_expansion)
+ {
+ /* Failed glob expressions are left unchanged. */
+ tlist->next = orig_list;
+ orig_list = tlist;
+ }
+ else
+ {
+ /* Failed glob expressions are removed. */
+ tlist->next = disposables;
+ disposables = tlist;
+ }
+ }
+ else
+ {
+ /* Dequote the string. */
+ register char *t = dequote_string (tlist->word->word);
+ free (tlist->word->word);
+ tlist->word->word = t;
+ tlist->next = orig_list;
+ orig_list = tlist;
+ }
+
+ free_array (temp_list);
+ temp_list = (char **)NULL;
+
+ tlist = next;
+ }
+
+ if (disposables)
+ dispose_words (disposables);
+
+ new_list = REVERSE_LIST (orig_list, WORD_LIST *);
+ }
+ else
+ {
+ /* Dequote the words, because we're not performing globbing. */
+ register WORD_LIST *wl = new_list;
+ register char *wp;
+ while (wl)
+ {
+ wp = dequote_string (wl->word->word);
+ free (wl->word->word);
+ wl->word->word = wp;
+ wl = wl->next;
+ }
+ }
+ }
+
+ if (do_vars)
+ {
+ register WORD_LIST *lp;
+ Function *assign_func;
+
+ /* If the remainder of the words expand to nothing, Posix.2 requires
+ that the variable and environment assignments affect the shell's
+ environment. */
+ assign_func = new_list ? assign_in_env : do_assignment;
+
+ for (lp = varlist; lp; lp = lp->next)
+ (*assign_func) (lp->word->word);
+
+ dispose_words (varlist);
+ varlist = (WORD_LIST *)NULL;
+ }
+
+ return (new_list);
+}
+
+/* Return nonzero if S has any unquoted special globbing chars in it. */
+static int
+unquoted_glob_pattern_p (string)
+ register char *string;
+{
+ register int c;
+ int open = 0;
+
+ while (c = *string++)
+ {
+ switch (c)
+ {
+ case '?':
+ case '*':
+ return (1);
+
+ case '[':
+ open++;
+ continue;
+
+ case ']':
+ if (open)
+ return (1);
+ continue;
+
+ case CTLESC:
+ case '\\':
+ if (*string++ == '\0')
+ return (0);
+ }
+ }
+ return (0);
+}
+
+/* PATHNAME can contain characters prefixed by CTLESC; this indicates
+ that the character is to be quoted. We quote it here in the style
+ that the glob library recognizes. If CONVERT_QUOTED_NULLS is non-zero,
+ we change quoted null strings (pathname[0] == CTLNUL) into empty
+ strings (pathname[0] == 0). If this is called after quote removal
+ is performed, CONVERT_QUOTED_NULLS should be 0; if called when quote
+ removal has not been done (for example, before attempting to match a
+ pattern while executing a case statement), CONVERT_QUOTED_NULLS should
+ be 1. */
+char *
+quote_string_for_globbing (pathname, convert_quoted_nulls)
+ char *pathname;
+ int convert_quoted_nulls;
+{
+ char *temp;
+ register int i;
+
+ temp = savestring (pathname);
+
+ if (convert_quoted_nulls && QUOTED_NULL (pathname))
+ {
+ temp[0] = '\0';
+ return temp;
+ }
+
+ for (i = 0; temp[i]; i++)
+ {
+ if (temp[i] == CTLESC)
+ temp[i++] = '\\';
+ }
+
+ return (temp);
+}
+
+/* Call the glob library to do globbing on PATHNAME. */
+char **
+shell_glob_filename (pathname)
+ char *pathname;
+{
+#if defined (USE_POSIX_GLOB_LIBRARY)
+ extern int glob_dot_filenames;
+ register int i;
+ char *temp, **return_value;
+ glob_t filenames;
+ int glob_flags;
+
+ temp = quote_string_for_globbing (pathname, 0);
+
+ filenames.gl_offs = 0;
+
+ glob_flags = glob_dot_filenames ? GLOB_PERIOD : 0;
+ glob_flags |= (GLOB_ERR | GLOB_DOOFFS);
+
+ i = glob (temp, glob_flags, (Function *)NULL, &filenames);
+
+ free (temp);
+
+ if (i == GLOB_NOSPACE || i == GLOB_ABEND)
+ return ((char **)NULL);
+
+ if (i == GLOB_NOMATCH)
+ filenames.gl_pathv[0] = (char *)NULL;
+
+ return (filenames.gl_pathv);
+
+#else /* !USE_POSIX_GLOB_LIBRARY */
+
+ char *temp, **results;
+
+ noglob_dot_filenames = !glob_dot_filenames;
+
+ temp = quote_string_for_globbing (pathname, 0);
+
+ results = glob_filename (temp);
+ free (temp);
+
+ if (results && !(GLOB_FAILED(results)))
+ sort_char_array (results);
+
+ return (results);
+#endif /* !USE_POSIX_GLOB_LIBRARY */
+}
+
+/*************************************************
+ * *
+ * Functions to manage special variables *
+ * *
+ *************************************************/
+
+/* 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. */
+void
+ sv_path (), sv_mail (), sv_uids (), sv_ignoreeof (),
+ sv_glob_dot_filenames (), sv_nolinks (),
+ sv_noclobber (), sv_allow_null_glob_expansion (), sv_strict_posix ();
+
+#if defined (READLINE)
+void sv_terminal (), sv_hostname_completion_file ();
+#endif
+
+#if defined (HISTORY)
+void sv_histsize (), sv_histfilesize (),
+ sv_history_control (), sv_command_oriented_history ();
+# if defined (BANG_HISTORY)
+void sv_histchars ();
+# endif
+#endif /* HISTORY */
+
+#if defined (GETOPTS_BUILTIN)
+void sv_optind (), sv_opterr ();
+#endif /* GETOPTS_BUILTIN */
+
+#if defined (JOB_CONTROL)
+void sv_notify ();
+#endif
+
+#define SET_INT_VAR(name, intvar) intvar = find_variable (name) != 0
+
+struct name_and_function {
+ char *name;
+ VFunction *function;
+} special_vars[] = {
+ { "PATH", sv_path },
+ { "MAIL", sv_mail },
+ { "MAILPATH", sv_mail },
+ { "MAILCHECK", sv_mail },
+
+ { "POSIXLY_CORRECT", sv_strict_posix },
+ { "POSIX_PEDANTIC", sv_strict_posix },
+ /* Variables which only do something special when READLINE is defined. */
+#if defined (READLINE)
+ { "TERM", sv_terminal },
+ { "TERMCAP", sv_terminal },
+ { "TERMINFO", sv_terminal },
+ { "hostname_completion_file", sv_hostname_completion_file },
+ { "HOSTFILE", sv_hostname_completion_file },
+#endif /* READLINE */
+
+ /* Variables which only do something special when HISTORY is defined. */
+#if defined (HISTORY)
+ { "HISTSIZE", sv_histsize },
+ { "HISTFILESIZE", sv_histfilesize },
+ { "command_oriented_history", sv_command_oriented_history },
+# if defined (BANG_HISTORY)
+ { "histchars", sv_histchars },
+# endif
+ { "history_control", sv_history_control },
+ { "HISTCONTROL", sv_history_control },
+#endif /* HISTORY */
+
+ { "EUID", sv_uids},
+ { "UID", sv_uids},
+ { "IGNOREEOF", sv_ignoreeof },
+ { "ignoreeof", sv_ignoreeof },
+
+#if defined (GETOPTS_BUILTIN)
+ { "OPTIND", sv_optind },
+ { "OPTERR", sv_opterr },
+#endif /* GETOPTS_BUILTIN */
+
+#if defined (JOB_CONTROL)
+ { "notify", sv_notify },
+#endif /* JOB_CONTROL */
+
+ { "glob_dot_filenames", sv_glob_dot_filenames },
+ { "allow_null_glob_expansion", sv_allow_null_glob_expansion },
+ { "noclobber", sv_noclobber },
+ { "nolinks", sv_nolinks },
+ { (char *)0x00, (VFunction *)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. */
+void
+stupidly_hack_special_variables (name)
+ char *name;
+{
+ int i = 0;
+
+ while (special_vars[i].name)
+ {
+ if (STREQ (special_vars[i].name, name))
+ {
+ (*(special_vars[i].function)) (name);
+ return;
+ }
+ i++;
+ }
+}
+
+/* Set/unset noclobber. */
+void
+sv_noclobber (name)
+ char *name;
+{
+ SET_INT_VAR (name, noclobber);
+}
+
+/* What to do just after the PATH variable has changed. */
+void
+sv_path (name)
+ char *name;
+{
+ /* 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. This is called with NAME set to one of
+ MAIL, MAILCHECK, or MAILPATH. */
+void
+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 (name[4] == 'C') /* if (strcmp (name, "MAILCHECK") == 0) */
+ reset_mail_timer ();
+ else
+ {
+ free_mail_files ();
+ remember_mail_dates ();
+ }
+}
+
+#if defined (READLINE)
+/* 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. */
+void
+sv_terminal (name)
+ char *name;
+{
+ if (interactive_shell && !no_line_editing)
+ rl_reset_terminal (get_string_value ("TERM"));
+}
+
+void
+sv_hostname_completion_file (name)
+ char *name;
+{
+ hostname_list_initialized = 0;
+}
+#endif /* READLINE */
+
+#if defined (HISTORY)
+/* 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. */
+void
+sv_histsize (name)
+ char *name;
+{
+ char *temp = get_string_value (name);
+
+ if (temp && *temp)
+ {
+ int num;
+ if (sscanf (temp, "%d", &num) == 1)
+ {
+ stifle_history (num);
+ if (history_lines_this_session > where_history ())
+ history_lines_this_session = where_history ();
+ }
+ }
+ else
+ unstifle_history ();
+}
+
+/* What to do if the HISTFILESIZE variable changes. */
+void
+sv_histfilesize (name)
+ char *name;
+{
+ char *temp = get_string_value (name);
+
+ if (temp && *temp)
+ {
+ int num;
+ if (sscanf (temp, "%d", &num) == 1)
+ {
+ history_truncate_file (get_string_value ("HISTFILE"), num);
+ if (num <= history_lines_in_file)
+ history_lines_in_file = num;
+ }
+ }
+}
+
+/* What to do after the HISTORY_CONTROL variable changes. */
+void
+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;
+ else if (strcmp (temp, "ignoreboth") == 0)
+ history_control = 3;
+ }
+}
+
+/* What to do after the COMMAND_ORIENTED_HISTORY variable changes. */
+void
+sv_command_oriented_history (name)
+ char *name;
+{
+ SET_INT_VAR (name, command_oriented_history);
+}
+
+# if defined (BANG_HISTORY)
+/* Setting/unsetting of the history expansion character. */
+
+void
+sv_histchars (name)
+ char *name;
+{
+ char *temp = get_string_value (name);
+
+ if (temp)
+ {
+ history_expansion_char = *temp;
+ if (temp[0] && temp[1])
+ {
+ history_subst_char = temp[1];
+ if (temp[2])
+ history_comment_char = temp[2];
+ }
+ }
+ else
+ {
+ history_expansion_char = '!';
+ history_subst_char = '^';
+ history_comment_char = '#';
+ }
+}
+# endif /* BANG_HISTORY */
+#endif /* HISTORY */
+
+void
+sv_allow_null_glob_expansion (name)
+ char *name;
+{
+ SET_INT_VAR (name, allow_null_glob_expansion);
+}
+
+/* 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). */
+void
+sv_ignoreeof (name)
+ char *name;
+{
+ SHELL_VAR *tmp_var;
+ char *temp;
+ int new_limit;
+
+ eof_encountered = 0;
+
+ tmp_var = find_variable (name);
+ ignoreeof = tmp_var != 0;
+ temp = tmp_var ? value_cell (tmp_var) : (char *)NULL;
+ if (temp)
+ {
+ if (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. */
+int glob_dot_filenames = 0;
+
+void
+sv_glob_dot_filenames (name)
+ char *name;
+{
+ SET_INT_VAR (name, glob_dot_filenames);
+}
+
+#if defined (JOB_CONTROL)
+/* Job notification feature desired? */
+void
+sv_notify (name)
+ char *name;
+{
+ SET_INT_VAR (name, asynchronous_notification);
+}
+#endif /* JOB_CONTROL */
+
+/* If the variable `nolinks' exists, it specifies that symbolic links are
+ not to be followed in `cd' commands. */
+void
+sv_nolinks (name)
+ char *name;
+{
+ SET_INT_VAR (name, no_symbolic_links);
+}
+
+/* Don't let users hack the user id variables. */
+void
+sv_uids (name)
+ char *name;
+{
+ char *buff;
+ register SHELL_VAR *v;
+
+ buff = itos (current_user.uid);
+ v = find_variable ("UID");
+ if (v)
+ v->attributes &= ~att_readonly;
+
+ v = bind_variable ("UID", buff);
+ v->attributes |= (att_readonly | att_integer);
+ free (buff);
+
+ buff = itos (current_user.euid);
+ v = find_variable ("EUID");
+ if (v)
+ v->attributes &= ~att_readonly;
+
+ v = bind_variable ("EUID", buff);
+ v->attributes |= (att_readonly | att_integer);
+ free (buff);
+}
+
+#if defined (GETOPTS_BUILTIN)
+void
+sv_optind (name)
+ char *name;
+{
+ char *tt = get_string_value ("OPTIND");
+ int s = 0;
+
+ if (tt && *tt)
+ {
+ s = atoi (tt);
+
+ /* According to POSIX, setting OPTIND=1 resets the internal state
+ of getopt (). */
+ if (s < 0 || s == 1)
+ s = 0;
+ }
+ getopts_reset (s);
+}
+
+void
+sv_opterr (name)
+ char *name;
+{
+ char *tt = get_string_value ("OPTERR");
+ int s = 1;
+
+ if (tt && *tt)
+ s = atoi (tt);
+ sh_opterr = s;
+}
+#endif /* GETOPTS_BUILTIN */
+
+void
+sv_strict_posix (name)
+ char *name;
+{
+ SET_INT_VAR (name, posixly_correct);
+ if (posixly_correct)
+ interactive_comments = 1;
+#if defined (READLINE)
+ posix_readline_initialize (posixly_correct);
+#endif /* READLINE */
+}