summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChet Ramey <chet.ramey@case.edu>2011-12-07 09:24:46 -0500
committerChet Ramey <chet.ramey@case.edu>2011-12-07 09:24:46 -0500
commite33f22038cc3dc3146a8cc58e2c5b603e8578972 (patch)
tree8df8a3e07f6edb3568a674e5134c3d1c9a1bfa06
parented35cb4a6394a5121724fc8a2c0ace6eb5be9de1 (diff)
downloadbash-e33f22038cc3dc3146a8cc58e2c5b603e8578972.tar.gz
commit bash-20080605 snapshot
-rw-r--r--CWRU/CWRU.chlog79
-rw-r--r--CWRU/CWRU.chlog~82
-rw-r--r--MANIFEST4
-rw-r--r--MANIFEST~6
-rw-r--r--braces.c2
-rw-r--r--builtins/break.def4
-rw-r--r--builtins/common.c31
-rw-r--r--builtins/common.h2
-rw-r--r--builtins/history.def14
-rw-r--r--builtins/read.def5
-rw-r--r--builtins/read.def~2
-rw-r--r--builtins/shift.def3
-rw-r--r--builtins/ulimit.def16
-rw-r--r--builtins/ulimit.def~19
-rw-r--r--doc/bash.18
-rw-r--r--doc/bash.1~11
-rw-r--r--doc/bashref.texi18
-rw-r--r--doc/bashref.texi~20
-rw-r--r--parse.y51
-rw-r--r--parse.y.save25538
-rw-r--r--parse.y~57
-rw-r--r--subst.c136
-rw-r--r--subst.c.save1422
-rw-r--r--subst.c.save2425
-rw-r--r--subst.c~157
-rw-r--r--subst.h20
-rw-r--r--subst.h~16
-rw-r--r--tests/comsub-posix.right57
-rw-r--r--tests/comsub-posix.tests199
-rw-r--r--tests/comsub-posix.tests~197
-rw-r--r--tests/comsub-posix1.sub3
-rw-r--r--tests/errors.right7
-rw-r--r--tests/redir8.sub1
-rw-r--r--tests/run-comsub-posix2
-rw-r--r--variables.h20
-rw-r--r--variables.h~24
36 files changed, 7216 insertions, 442 deletions
diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog
index 8682715e..dcf0a138 100644
--- a/CWRU/CWRU.chlog
+++ b/CWRU/CWRU.chlog
@@ -15989,3 +15989,82 @@ parse.y
doc/{bash.1,bashref.texi}
- document the effect of setting PROMPT_DIRTRIM
+
+ 6/3
+ ---
+builtins/ulimit.def
+ - make the multiplier (block size) for -c and -f 512 bytes only if in
+ Posix mode and 1024 bytes otherwise (as in previous versions). Uses
+ POSIXBLK and BLOCK_SIZE defines to parameterize size based on value
+ of posixly_correct
+
+doc/bashref.texi
+ - document this addition to posix mode
+
+builtins/common.c
+ - change get_numeric_arg to have a calling sequence and return value
+ more closely mimicking general.c:legal_number(), with the addition
+ of a flags word
+ - add extra value for `fatal' argument to get_numeric_arg to force it
+ to return failure to the caller rather than longjmping
+
+builtins/common.h
+ - change prototype declaration for get_numeric_arg
+
+builtins/{break,shift}.def
+ - change calls to get_numeric_arg to deal with new semantics and calling
+ sequence
+
+builtins/history.def
+ - display_history now returns an int
+ - change calling sequence for get_numeric_arg in display_history
+ - display_history now returns failure to the caller if get_numeric_arg
+ detects an invalid number, rather than jumping back to the top level
+ - use value returned by display_history as return status of history
+ builtin, filtered through sh_chkwrite
+ - history no longer aborts compound commands on invalid arguments.
+ fixes problem reported by Chu Li <chul@cn.fujitsu.com>
+
+{braces,subst}.c
+ - extract_command_subst now takes a third flags argument; passed flags
+ are ORd into flags passed to other functions; changed callers
+
+subst.h
+ - move SX_* defines here from subst.c so parse.y:xparse_dolparen can
+ see them and behave appropriately
+ - extract_command_subst now takes a third flags argument; change
+ prototype
+
+subst.c
+ - change extract_command_subst to call xparse_dolparen when extracting
+ a $() construct
+ - change calls to extract_delimited_string to extract_command_subst
+ as appropriate
+ - if command_substitute returns a NULL word desc, don't call
+ dispose_word_desc on it
+
+parse.y
+ - change xparse_dolparen to use the SX_* flags now in subst.h
+
+ 6/16
+ ----
+subst.c
+ - in quote_list, set W_HASQUOTEDNULL flag in the word if quote_string
+ turns "" into CTLNUL
+ - in dequote_list, turn off W_HASQUOTEDNULL flag in the word if
+ dequote_string turns CTLNUL into ""
+ - new function, string_list_pos_params, encapsulates everything
+ needed to turn the positional parameters or an array indexed with
+ '@' or '*' into a string, including taking care of quoting and
+ using the first char of $IFS, when used in another expansion like
+ pattern removal or pattern substitution
+ - change list_remove_pattern, pos_params, pos_params_pat_subst to
+ call string_list_pos_params. Fixes problems reported by
+ Stephane Chazelas <stephane_chazelas@yahoo.fr>
+
+ 6/22
+ ----
+variables.h
+ - include assoc.h for associative arrays
+ - defines for case-modifying expansions and associative array variables
+ - sh_var_assign_func_t functions now take an extra char * parameter
diff --git a/CWRU/CWRU.chlog~ b/CWRU/CWRU.chlog~
index 1a438c5a..d6a8a2b3 100644
--- a/CWRU/CWRU.chlog~
+++ b/CWRU/CWRU.chlog~
@@ -15979,3 +15979,85 @@ parse.y
substitution. From bash-4.0-devel branch
- changes to parse_matched_pair to combine most of the flag variables
(was_dollar, in_comment, and so on) into a local flags word
+
+ 6/2
+ ---
+parse.y
+ - call trim_pathname, which retains only the last $PROMPT_DIRTRIM
+ directories and replaces the intervening characters with `...',
+ when expanding \w and \W
+
+doc/{bash.1,bashref.texi}
+ - document the effect of setting PROMPT_DIRTRIM
+
+ 6/3
+ ---
+builtins/ulimit.def
+ - make the multiplier (block size) for -c and -f 512 bytes only if in
+ Posix mode and 1024 bytes otherwise (as in previous versions). Uses
+ POSIXBLK and BLOCK_SIZE defines to parameterize size based on value
+ of posixly_correct
+
+doc/bashref.texi
+ - document this addition to posix mode
+
+builtins/common.c
+ - change get_numeric_arg to have a calling sequence and return value
+ more closely mimicking general.c:legal_number(), with the addition
+ of a flags word
+ - add extra value for `fatal' argument to get_numeric_arg to force it
+ to return failure to the caller rather than longjmping
+
+builtins/common.h
+ - change prototype declaration for get_numeric_arg
+
+builtins/{break,shift}.def
+ - change calls to get_numeric_arg to deal with new semantics and calling
+ sequence
+
+builtins/history.def
+ - display_history now returns an int
+ - change calling sequence for get_numeric_arg in display_history
+ - display_history now returns failure to the caller if get_numeric_arg
+ detects an invalid number, rather than jumping back to the top level
+ - use value returned by display_history as return status of history
+ builtin, filtered through sh_chkwrite
+ - history no longer aborts compound commands on invalid arguments.
+ fixes problem reported by Chu Li <chul@cn.fujitsu.com>
+
+{braces,subst}.c
+ - extract_command_subst now takes a third flags argument; passed flags
+ are ORd into flags passed to other functions; changed callers
+
+subst.h
+ - move SX_* defines here from subst.c so parse.y:xparse_dolparen can
+ see them and behave appropriately
+ - extract_command_subst now takes a third flags argument; change
+ prototype
+
+subst.c
+ - change extract_command_subst to call xparse_dolparen when extracting
+ a $() construct
+ - change calls to extract_delimited_string to extract_command_subst
+ as appropriate
+ - if command_substitute returns a NULL word desc, don't call
+ dispose_word_desc on it
+
+parse.y
+ - change xparse_dolparen to use the SX_* flags now in subst.h
+
+ 6/16
+ ----
+subst.c
+ - in quote_list, set W_HASQUOTEDNULL flag in the word if quote_string
+ turns "" into CTLNUL
+ - in dequote_list, turn off W_HASQUOTEDNULL flag in the word if
+ dequote_string turns CTLNUL into ""
+ - new function, string_list_pos_params, encapsulates everything
+ needed to turn the positional parameters or an array indexed with
+ '@' or '*' into a string, including taking care of quoting and
+ using the first char of $IFS, when used in another expansion like
+ pattern removal or pattern substitution
+ - change list_remove_pattern, pos_params, pos_params_pat_subst to
+ call string_list_pos_params. Fixes problems reported by
+ Stephane Chazelas <stephane_chazelas@yahoo.fr>
diff --git a/MANIFEST b/MANIFEST
index 6eb4ec72..37d2b926 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -763,6 +763,9 @@ tests/case.tests f
tests/case.right f
tests/comsub.tests f
tests/comsub.right f
+tests/comsub-posix.tests f
+tests/comsub-posix.right f
+tests/comsub-posix1.sub f
tests/cond.tests f
tests/cond.right f
tests/cond-regexp.sub f
@@ -925,6 +928,7 @@ tests/run-braces f
tests/run-builtins f
tests/run-case f
tests/run-comsub f
+tests/run-comsub-posix f
tests/run-cond f
tests/run-cprint f
tests/run-dbg-support f
diff --git a/MANIFEST~ b/MANIFEST~
index 2e09b62b..fa6eafc7 100644
--- a/MANIFEST~
+++ b/MANIFEST~
@@ -759,8 +759,12 @@ tests/source3.sub f
tests/source4.sub f
tests/source5.sub f
tests/source6.sub f
+tests/case.tests f
+tests/case.right f
tests/comsub.tests f
tests/comsub.right f
+tests/comsub-posix.tests f
+tests/comsub-posix.right f
tests/cond.tests f
tests/cond.right f
tests/cond-regexp.sub f
@@ -921,7 +925,9 @@ tests/run-array f
tests/run-array2 f
tests/run-braces f
tests/run-builtins f
+tests/run-case f
tests/run-comsub f
+tests/run-comsub-posix f
tests/run-cond f
tests/run-cprint f
tests/run-dbg-support f
diff --git a/braces.c b/braces.c
index 5810d0f5..86d1413a 100644
--- a/braces.c
+++ b/braces.c
@@ -538,7 +538,7 @@ brace_gobbler (text, tlen, indx, satisfy)
if ((c == '$' || c == '<' || c == '>') && text[i+1] == '(') /* ) */
{
si = i + 2;
- t = extract_command_subst (text, &si);
+ t = extract_command_subst (text, &si, 0);
i = si;
free (t);
i++;
diff --git a/builtins/break.def b/builtins/break.def
index 76326f0a..fbfe40eb 100644
--- a/builtins/break.def
+++ b/builtins/break.def
@@ -71,7 +71,7 @@ break_builtin (list)
if (check_loop_level () == 0)
return (EXECUTION_SUCCESS);
- newbreak = get_numeric_arg (list, 1);
+ (void)get_numeric_arg (list, 1, &newbreak);
if (newbreak <= 0)
{
@@ -111,7 +111,7 @@ continue_builtin (list)
if (check_loop_level () == 0)
return (EXECUTION_SUCCESS);
- newcont = get_numeric_arg (list, 1);
+ (void)get_numeric_arg (list, 1, &newcont);
if (newcont <= 0)
{
diff --git a/builtins/common.c b/builtins/common.c
index 8f047ce5..1a802c3c 100644
--- a/builtins/common.c
+++ b/builtins/common.c
@@ -399,30 +399,35 @@ set_dollar_vars_changed ()
/* Read a numeric arg for this_command_name, the name of the shell builtin
that wants it. LIST is the word list that the arg is to come from.
Accept only the numeric argument; report an error if other arguments
- follow. If FATAL is true, call throw_to_top_level, which exits the
- shell; if not, call jump_to_top_level (DISCARD), which aborts the
- current command. */
-intmax_t
-get_numeric_arg (list, fatal)
+ follow. If FATAL is 1, call throw_to_top_level, which exits the
+ shell; if it's 2, call jump_to_top_level (DISCARD), which aborts the
+ current command; if FATAL is 0, return an indication of an invalid
+ number by setting *NUMOK == 0 and return -1. */
+int
+get_numeric_arg (list, fatal, count)
WORD_LIST *list;
int fatal;
+ intmax_t *count;
{
- intmax_t count = 1;
+ char *arg;
+
+ if (count)
+ *count = 1;
if (list && list->word && ISOPTION (list->word->word, '-'))
list = list->next;
if (list)
{
- register char *arg;
-
arg = list->word->word;
- if (arg == 0 || (legal_number (arg, &count) == 0))
+ if (arg == 0 || (legal_number (arg, count) == 0))
{
- sh_neednumarg (list->word->word);
- if (fatal)
+ sh_neednumarg (list->word->word ? list->word->word : "`'");
+ if (fatal == 0)
+ return 0;
+ else if (fatal == 1) /* fatal == 1; abort */
throw_to_top_level ();
- else
+ else /* fatal == 2; discard current command */
{
top_level_cleanup ();
jump_to_top_level (DISCARD);
@@ -431,7 +436,7 @@ get_numeric_arg (list, fatal)
no_args (list->next);
}
- return (count);
+ return (1);
}
/* Get an eight-bit status value from LIST */
diff --git a/builtins/common.h b/builtins/common.h
index ec1200b5..190ae41e 100644
--- a/builtins/common.h
+++ b/builtins/common.h
@@ -89,7 +89,7 @@ extern int dollar_vars_changed __P((void));
extern void set_dollar_vars_unchanged __P((void));
extern void set_dollar_vars_changed __P((void));
-extern intmax_t get_numeric_arg __P((WORD_LIST *, int));
+extern int get_numeric_arg __P((WORD_LIST *, int, intmax_t *));
extern int get_exitstat __P((WORD_LIST *));
extern int read_octal __P((char *));
diff --git a/builtins/history.def b/builtins/history.def
index f21e7c9e..19c2b81c 100644
--- a/builtins/history.def
+++ b/builtins/history.def
@@ -88,7 +88,7 @@ extern int current_command_line_count;
extern int force_append_history; /* shopt -s histappend */
static char *histtime __P((HIST_ENTRY *, const char *));
-static void display_history __P((WORD_LIST *));
+static int display_history __P((WORD_LIST *));
static void push_history __P((WORD_LIST *));
static int expand_and_print_history __P((WORD_LIST *));
@@ -197,8 +197,8 @@ history_builtin (list)
}
else if ((flags & (AFLAG|RFLAG|NFLAG|WFLAG|CFLAG)) == 0)
{
- display_history (list);
- return (sh_chkwrite (EXECUTION_SUCCESS));
+ result = display_history (list);
+ return (sh_chkwrite (result));
}
filename = list ? list->word->word : get_string_value ("HISTFILE");
@@ -260,7 +260,7 @@ histtime (hlist, histtimefmt)
return timestr;
}
-static void
+static int
display_history (list)
WORD_LIST *list;
{
@@ -271,7 +271,9 @@ display_history (list)
if (list)
{
- limit = get_numeric_arg (list, 0);
+ if (get_numeric_arg (list, 0, &limit) == 0)
+ return (EXECUTION_FAILURE);
+
if (limit < 0)
limit = -limit;
}
@@ -305,6 +307,8 @@ display_history (list)
i++;
}
}
+
+ return (EXECUTION_SUCCESS);
}
/* Remove the last entry in the history list and add each argument in
diff --git a/builtins/read.def b/builtins/read.def
index d7a43202..4945538d 100644
--- a/builtins/read.def
+++ b/builtins/read.def
@@ -51,7 +51,8 @@ Options:
-t timeout time out and return failure if a complete line of input is
not read withint TIMEOUT seconds. The value of the TMOUT
variable is the default timeout. TIMEOUT may be a
- fractional number.
+ fractional number. The exit status is greater than 128 if
+ the timeout is exceeded
-u fd read from file descriptor FD instead of the standard input
Exit Status:
@@ -374,7 +375,7 @@ read_builtin (list)
return (EXECUTION_FAILURE);
#else
input_string[i] = '\0'; /* make sure it's terminated */
- retval = EXECUTION_FAILURE;
+ retval = 128+SIGALRM;;
goto assign_vars;
#endif
}
diff --git a/builtins/read.def~ b/builtins/read.def~
index bf1e0300..d7a43202 100644
--- a/builtins/read.def~
+++ b/builtins/read.def~
@@ -573,7 +573,7 @@ add_char:
if (tmsec > 0 || tmusec > 0)
reset_alarm ();
-< if (nchars > 0 || delim != '\n')
+ if (nchars > 0 || delim != '\n')
{
#if defined (READLINE)
if (edit)
diff --git a/builtins/shift.def b/builtins/shift.def
index 37c12529..fb2cc992 100644
--- a/builtins/shift.def
+++ b/builtins/shift.def
@@ -62,7 +62,8 @@ shift_builtin (list)
register int count;
WORD_LIST *temp;
- times = get_numeric_arg (list, 0);
+ if (get_numeric_arg (list, 0, &times) == 0)
+ return (EXECUTION_FAILURE);
if (times == 0)
return (EXECUTION_SUCCESS);
diff --git a/builtins/ulimit.def b/builtins/ulimit.def
index b47dd823..47904394 100644
--- a/builtins/ulimit.def
+++ b/builtins/ulimit.def
@@ -179,6 +179,14 @@ extern int errno;
#define LIMIT_HARD 0x01
#define LIMIT_SOFT 0x02
+/* "Blocks" are defined as 512 bytes when in Posix mode and 1024 bytes
+ otherwise. */
+#define POSIXBLK -2
+
+#define BLOCKSIZE(x) (((x) == POSIXBLK && posixly_correct) ? 512 : 1024)
+
+extern int posixly_correct;
+
static int _findlim __P((int));
static int ulimit_internal __P((int, char *, int, int));
@@ -212,7 +220,7 @@ static RESOURCE_LIMITS limits[] = {
{ 'b', RLIMIT_SBSIZE, 1, "socket buffer size", "bytes" },
#endif
#ifdef RLIMIT_CORE
- { 'c', RLIMIT_CORE, 512, "core file size", "blocks" },
+ { 'c', RLIMIT_CORE, POSIXBLK, "core file size", "blocks" },
#endif
#ifdef RLIMIT_DATA
{ 'd', RLIMIT_DATA, 1024, "data seg size", "kbytes" },
@@ -220,7 +228,7 @@ static RESOURCE_LIMITS limits[] = {
#ifdef RLIMIT_NICE
{ 'e', RLIMIT_NICE, 1, "scheduling priority", (char *)NULL },
#endif
- { 'f', RLIMIT_FILESIZE, 512, "file size", "blocks" },
+ { 'f', RLIMIT_FILESIZE, POSIXBLK, "file size", "blocks" },
#ifdef RLIMIT_SIGPENDING
{ 'i', RLIMIT_SIGPENDING, 1, "pending signals", (char *)NULL },
#endif
@@ -438,7 +446,7 @@ ulimit_internal (cmd, cmdarg, mode, multiple)
else if (all_digits (cmdarg))
{
limit = string_to_rlimtype (cmdarg);
- block_factor = limits[limind].block_factor;
+ block_factor = BLOCKSIZE(limits[limind].block_factor);
real_limit = limit * block_factor;
if ((real_limit / block_factor) != limit)
@@ -702,7 +710,7 @@ printone (limind, curlim, pdesc)
char unitstr[64];
int factor;
- factor = limits[limind].block_factor;
+ factor = BLOCKSIZE(limits[limind].block_factor);
if (pdesc)
{
if (limits[limind].units)
diff --git a/builtins/ulimit.def~ b/builtins/ulimit.def~
index 8f8fb1aa..f3d11ac0 100644
--- a/builtins/ulimit.def~
+++ b/builtins/ulimit.def~
@@ -61,6 +61,9 @@ no option is given, then -f is assumed.
Values are in 1024-byte increments, except for -t, which is in seconds,
-p, which is in increments of 512 bytes, and -u, which is an unscaled
number of processes.
+
+Exit Status:
+Returns success unless an invalid option is supplied or an error occurs.
$END
#if !defined (_MINIX)
@@ -176,6 +179,14 @@ extern int errno;
#define LIMIT_HARD 0x01
#define LIMIT_SOFT 0x02
+/* "Blocks" are defined as 512 bytes when in Posix mode and 1024 bytes
+ otherwise. */
+#define POSIXBLK -2
+
+#define BLOCKSIZE(x) (((x) == POSIXBLK && posixly_correct) ? 512 : (x))
+
+extern int posixly_correct;
+
static int _findlim __P((int));
static int ulimit_internal __P((int, char *, int, int));
@@ -209,7 +220,7 @@ static RESOURCE_LIMITS limits[] = {
{ 'b', RLIMIT_SBSIZE, 1, "socket buffer size", "bytes" },
#endif
#ifdef RLIMIT_CORE
- { 'c', RLIMIT_CORE, 512, "core file size", "blocks" },
+ { 'c', RLIMIT_CORE, POSIXBLK, "core file size", "blocks" },
#endif
#ifdef RLIMIT_DATA
{ 'd', RLIMIT_DATA, 1024, "data seg size", "kbytes" },
@@ -217,7 +228,7 @@ static RESOURCE_LIMITS limits[] = {
#ifdef RLIMIT_NICE
{ 'e', RLIMIT_NICE, 1, "scheduling priority", (char *)NULL },
#endif
- { 'f', RLIMIT_FILESIZE, 512, "file size", "blocks" },
+ { 'f', RLIMIT_FILESIZE, POSIXBLK, "file size", "blocks" },
#ifdef RLIMIT_SIGPENDING
{ 'i', RLIMIT_SIGPENDING, 1, "pending signals", (char *)NULL },
#endif
@@ -435,7 +446,7 @@ ulimit_internal (cmd, cmdarg, mode, multiple)
else if (all_digits (cmdarg))
{
limit = string_to_rlimtype (cmdarg);
- block_factor = limits[limind].block_factor;
+ block_factor = BLOCKSIZE(limits[limind].block_factor);
real_limit = limit * block_factor;
if ((real_limit / block_factor) != limit)
@@ -699,7 +710,7 @@ printone (limind, curlim, pdesc)
char unitstr[64];
int factor;
- factor = limits[limind].block_factor;
+ factor = BLOCKSIZE(limits[limind].block_factor);
if (pdesc)
{
if (limits[limind].units)
diff --git a/doc/bash.1 b/doc/bash.1
index 224bdb37..32ce9bd3 100644
--- a/doc/bash.1
+++ b/doc/bash.1
@@ -4242,6 +4242,8 @@ The
.I "previous job"
may be referenced using
.BR %\- .
+If there is only a single job, \fB%+\fP and \fB%\-\fP can both be used
+to refer to that job.
In output pertaining to jobs (e.g., the output of the
.B jobs
command), the current job is always flagged with a
@@ -7749,8 +7751,10 @@ Cause \fBread\fP to time out and return failure if a complete line of
input is not read within \fItimeout\fP seconds.
\fItimeout\fP may be a decimal number with a fractional portion following
the decimal point.
-This option has no effect if \fBread\fP is not reading input from the
-terminal or a pipe.
+This option is only effective if \fBread\fP is reading input from a
+terminal, pipe, or other special file; it has no effect when reading
+from regular files.
+The exit status is greater than 128 if the timeout is exceeded.
.TP
.B \-u \fIfd\fP
Read input from file descriptor \fIfd\fP.
diff --git a/doc/bash.1~ b/doc/bash.1~
index 30d90470..abda317e 100644
--- a/doc/bash.1~
+++ b/doc/bash.1~
@@ -5,12 +5,12 @@
.\" Case Western Reserve University
.\" chet@po.cwru.edu
.\"
-.\" Last Change: Sun May 25 10:41:29 EDT 2008
+.\" Last Change: Mon Jun 2 20:32:53 EDT 2008
.\"
.\" bash_builtins, strip all but Built-Ins section
.if \n(zZ=1 .ig zZ
.if \n(zY=1 .ig zY
-.TH BASH 1 "2008 May 25" "GNU Bash-4.0"
+.TH BASH 1 "2008 June 2" "GNU Bash-4.0"
.\"
.\" There's some problem with having a `@'
.\" in a tagged paragraph with the BSD man macros.
@@ -4242,6 +4242,8 @@ The
.I "previous job"
may be referenced using
.BR %\- .
+If there is only a single job, \fB%+\fP and \fB%\-\fP can both be used
+to refer to that job.
In output pertaining to jobs (e.g., the output of the
.B jobs
command), the current job is always flagged with a
@@ -7749,8 +7751,9 @@ Cause \fBread\fP to time out and return failure if a complete line of
input is not read within \fItimeout\fP seconds.
\fItimeout\fP may be a decimal number with a fractional portion following
the decimal point.
-This option has no effect if \fBread\fP is not reading input from the
-terminal or a pipe.
+This option is only effective if \fBread\fP is reading input from a
+terminal, pipe, or other special file; it has no effect when reading
+from regular files.
.TP
.B \-u \fIfd\fP
Read input from file descriptor \fIfd\fP.
diff --git a/doc/bashref.texi b/doc/bashref.texi
index 38fec7fc..98f852be 100644
--- a/doc/bashref.texi
+++ b/doc/bashref.texi
@@ -3661,8 +3661,10 @@ Cause @code{read} to time out and return failure if a complete line of
input is not read within @var{timeout} seconds.
@var{timeout} may be a decimal number with a fractional portion following
the decimal point.
-This option has no effect if @code{read} is not reading input from the
-terminal or a pipe.
+This option is only effective if @code{read} is reading input from a
+terminal, pipe, or other special file; it has no effect when reading
+from regular files.
+The exit status is greater than 128 if the timeout is exceeded.
@item -u @var{fd}
Read input from file descriptor @var{fd}.
@@ -6351,6 +6353,10 @@ When the @code{xpg_echo} option is enabled, Bash does not attempt to interpret
any arguments to @code{echo} as options. Each argument is displayed, after
escape characters are converted.
+@item
+The @code{ulimit} builtin uses a block size of 512 bytes for the @option{-c}
+and @option{-f} options.
+
@end enumerate
There is other @sc{posix} behavior that Bash does not implement by
@@ -6454,9 +6460,11 @@ current job, which is the last job stopped while it was in the foreground
or started in the background.
A single @samp{%} (with no accompanying job specification) also refers
to the current job.
-The previous job may be referenced using @samp{%-}. In output
-pertaining to jobs (e.g., the output of the @code{jobs} command),
-the current job is always flagged with a @samp{+}, and the
+The previous job may be referenced using @samp{%-}.
+If there is only a single job, @samp{%+} and @samp{%-} can both be used
+to refer to that job.
+In output pertaining to jobs (e.g., the output of the @code{jobs}
+command), the current job is always flagged with a @samp{+}, and the
previous job with a @samp{-}.
A job may also be referred to
diff --git a/doc/bashref.texi~ b/doc/bashref.texi~
index 0ccfa392..64961cd0 100644
--- a/doc/bashref.texi~
+++ b/doc/bashref.texi~
@@ -3661,8 +3661,9 @@ Cause @code{read} to time out and return failure if a complete line of
input is not read within @var{timeout} seconds.
@var{timeout} may be a decimal number with a fractional portion following
the decimal point.
-This option has no effect if @code{read} is not reading input from the
-terminal or a pipe.
+This option is only effective if @code{read} is reading input from a
+terminal, pipe, or other special file; it has no effect when reading
+from regular files.
@item -u @var{fd}
Read input from file descriptor @var{fd}.
@@ -6056,7 +6057,8 @@ The version of Bash (e.g., 2.00)
@item \V
The release of Bash, version + patchlevel (e.g., 2.00.0)
@item \w
-The current working directory, with @env{$HOME} abbreviated with a tilde.
+The current working directory, with @env{$HOME} abbreviated with a tilde
+(uses the @env{$PROMPT_DIRTRIM} variable).
@item \W
The basename of @env{$PWD}, with @env{$HOME} abbreviated with a tilde.
@item \!
@@ -6350,6 +6352,10 @@ When the @code{xpg_echo} option is enabled, Bash does not attempt to interpret
any arguments to @code{echo} as options. Each argument is displayed, after
escape characters are converted.
+@item
+The @code{ulimit} builtin uses a block size of 512 bytes for the @option{-c}
+and @option{-f} options.
+
@end enumerate
There is other @sc{posix} behavior that Bash does not implement by
@@ -6453,9 +6459,11 @@ current job, which is the last job stopped while it was in the foreground
or started in the background.
A single @samp{%} (with no accompanying job specification) also refers
to the current job.
-The previous job may be referenced using @samp{%-}. In output
-pertaining to jobs (e.g., the output of the @code{jobs} command),
-the current job is always flagged with a @samp{+}, and the
+The previous job may be referenced using @samp{%-}.
+If there is only a single job, @samp{%+} and @samp{%-} can both be used
+to refer to that job.
+In output pertaining to jobs (e.g., the output of the @code{jobs}
+command), the current job is always flagged with a @samp{+}, and the
previous job with a @samp{-}.
A job may also be referred to
diff --git a/parse.y b/parse.y
index 638ad52c..68098628 100644
--- a/parse.y
+++ b/parse.y
@@ -372,6 +372,8 @@ inputunit: simple_list simple_list_terminator
global_command = $1;
eof_encountered = 0;
/* discard_parser_constructs (0); */
+ if (parser_state & PST_CMDSUBST)
+ parser_state |= PST_EOFTOKEN;
YYACCEPT;
}
| '\n'
@@ -379,6 +381,8 @@ inputunit: simple_list simple_list_terminator
/* Case of regular command, but not a very
interesting one. Return a NULL command. */
global_command = (COMMAND *)NULL;
+ if (parser_state & PST_CMDSUBST)
+ parser_state |= PST_EOFTOKEN;
YYACCEPT;
}
| error '\n'
@@ -770,7 +774,6 @@ function_def: WORD '(' ')' newline_list function_body
{ $$ = make_function_def ($2, $4, function_dstart, function_bstart); }
;
-
function_body: shell_command
{ $$ = $1; }
| shell_command redirection_list
@@ -953,6 +956,13 @@ simple_list: simple_list1
$$ = $1;
if (need_here_doc)
gather_here_documents ();
+ if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+ {
+ global_command = $1;
+ eof_encountered = 0;
+ rewind_input_string ();
+ YYACCEPT;
+ }
}
| simple_list1 '&'
{
@@ -962,12 +972,26 @@ simple_list: simple_list1
$$ = command_connect ($1, (COMMAND *)NULL, '&');
if (need_here_doc)
gather_here_documents ();
+ if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+ {
+ global_command = $1;
+ eof_encountered = 0;
+ rewind_input_string ();
+ YYACCEPT;
+ }
}
| simple_list1 ';'
{
$$ = $1;
if (need_here_doc)
gather_here_documents ();
+ if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+ {
+ global_command = $1;
+ eof_encountered = 0;
+ rewind_input_string ();
+ YYACCEPT;
+ }
}
;
@@ -2258,6 +2282,15 @@ yylex ()
token_before_that = last_read_token;
last_read_token = current_token;
current_token = read_token (READ);
+
+ if ((parser_state & PST_EOFTOKEN) && current_token == shell_eof_token)
+ {
+ current_token = yacc_EOF;
+ if (bash_input.type == st_string)
+ rewind_input_string ();
+ }
+ parser_state &= ~PST_EOFTOKEN;
+
return (current_token);
}
@@ -2686,6 +2719,7 @@ read_token (command)
#if defined (ALIAS)
parser_state &= ~PST_ALEXPNEXT;
#endif /* ALIAS */
+
peek_char = shell_getc (1);
if MBTEST(peek_char == '&')
return (SEMI_SEMI_AND);
@@ -2895,7 +2929,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
/* Not exactly right yet, should handle shell metacharacters, too. If
any changes are made to this test, make analogous changes to subst.c:
extract_delimited_string(). */
- else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || whitespace (ret[retind - 1])))
+ else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || shellblank (ret[retind - 1])))
tflags |= LEX_INCOMMENT;
if (tflags & LEX_PASSNEXT) /* last char was backslash */
@@ -3015,7 +3049,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
if (open == ch) /* undo previous increment */
count--;
if (ch == '(') /* ) */
- nestret = parse_matched_pair (0, '(', ')', &nestlen, rflags & ~P_DQUOTE);
+ nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
else if (ch == '{') /* } */
nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags);
else if (ch == '[') /* ] */
@@ -3414,7 +3448,7 @@ xparse_dolparen (base, string, indp, flags)
ostring = string;
sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE;
- if (flags & 0x010) /* EX_NOLONGJMP */
+ if (flags & SX_NOLONGJMP)
sflags |= SEVAL_NOLONGJMP;
save_parser_state (&ps);
@@ -3436,7 +3470,8 @@ xparse_dolparen (base, string, indp, flags)
if (ep[-1] != ')')
{
#if DEBUG
- itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep);
+ if (ep[-1] != '\n')
+ itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep);
#endif
while (ep > ostring && ep[-1] == '\n') ep--;
}
@@ -3450,7 +3485,7 @@ xparse_dolparen (base, string, indp, flags)
itrace("xparse_dolparen:%d: base[%d] != RPAREN (%d), base = `%s'", line_number, *indp, base[*indp], base);
#endif
- if (flags & 1)
+ if (flags & SX_NOALLOC)
return (char *)NULL;
if (nc == 0)
@@ -4017,7 +4052,7 @@ read_token_word (character)
history literally rather than causing a possibly-
incorrect `;' to be added. ) */
push_delimiter (dstack, peek_char);
- ttok = parse_matched_pair (cd, '(', ')', &ttoklen, P_COMMAND);
+ ttok = parse_comsub (cd, '(', ')', &ttoklen, P_COMMAND);
pop_delimiter (dstack);
}
else
@@ -4424,7 +4459,7 @@ history_delimiting_chars ()
{
/* Tricky. `for i\nin ...' should not have a semicolon, but
`for i\ndo ...' should. We do what we can. */
- for (i = shell_input_line_index; whitespace(shell_input_line[i]); i++)
+ for (i = shell_input_line_index; whitespace (shell_input_line[i]); i++)
;
if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n')
return " ";
diff --git a/parse.y.save2 b/parse.y.save2
new file mode 100644
index 00000000..2b116374
--- /dev/null
+++ b/parse.y.save2
@@ -0,0 +1,5538 @@
+/* Yacc grammar for bash. */
+
+/* Copyright (C) 1989-2008 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 LICENSE. If not, write to the Free Software
+ Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+%{
+#include "config.h"
+
+#include "bashtypes.h"
+#include "bashansi.h"
+
+#include "filecntl.h"
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#if defined (HAVE_LOCALE_H)
+# include <locale.h>
+#endif
+
+#include <stdio.h>
+#include "chartypes.h"
+#include <signal.h>
+
+#include "memalloc.h"
+
+#include "bashintl.h"
+
+#define NEED_STRFTIME_DECL /* used in externs.h */
+
+#include "shell.h"
+#include "trap.h"
+#include "flags.h"
+#include "parser.h"
+#include "mailcheck.h"
+#include "test.h"
+#include "builtins.h"
+#include "builtins/common.h"
+#include "builtins/builtext.h"
+
+#include "shmbutil.h"
+
+#if defined (READLINE)
+# include "bashline.h"
+# include <readline/readline.h>
+#endif /* READLINE */
+
+#if defined (HISTORY)
+# include "bashhist.h"
+# include <readline/history.h>
+#endif /* HISTORY */
+
+#if defined (JOB_CONTROL)
+# include "jobs.h"
+#endif /* JOB_CONTROL */
+
+#if defined (ALIAS)
+# include "alias.h"
+#else
+typedef void *alias_t;
+#endif /* ALIAS */
+
+#if defined (PROMPT_STRING_DECODE)
+# ifndef _MINIX
+# include <sys/param.h>
+# endif
+# include <time.h>
+# if defined (TM_IN_SYS_TIME)
+# include <sys/types.h>
+# include <sys/time.h>
+# endif /* TM_IN_SYS_TIME */
+# include "maxpath.h"
+#endif /* PROMPT_STRING_DECODE */
+
+#define RE_READ_TOKEN -99
+#define NO_EXPANSION -100
+
+#ifdef DEBUG
+# define YYDEBUG 1
+#else
+# define YYDEBUG 0
+#endif
+
+#if defined (HANDLE_MULTIBYTE)
+# define last_shell_getc_is_singlebyte \
+ ((shell_input_line_index > 1) \
+ ? shell_input_line_property[shell_input_line_index - 1] \
+ : 1)
+# define MBTEST(x) ((x) && last_shell_getc_is_singlebyte)
+#else
+# define last_shell_getc_is_singlebyte 1
+# define MBTEST(x) ((x))
+#endif
+
+#if defined (EXTENDED_GLOB)
+extern int extended_glob;
+#endif
+
+extern int eof_encountered;
+extern int no_line_editing, running_under_emacs;
+extern int current_command_number;
+extern int sourcelevel;
+extern int posixly_correct;
+extern int last_command_exit_value;
+extern char *shell_name, *current_host_name;
+extern char *dist_version;
+extern int patch_level;
+extern int dump_translatable_strings, dump_po_strings;
+extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin;
+#if defined (BUFFERED_INPUT)
+extern int bash_input_fd_changed;
+#endif
+
+extern int errno;
+/* **************************************************************** */
+/* */
+/* "Forward" declarations */
+/* */
+/* **************************************************************** */
+
+#ifdef DEBUG
+static void debug_parser __P((int));
+#endif
+
+static int yy_getc __P((void));
+static int yy_ungetc __P((int));
+
+#if defined (READLINE)
+static int yy_readline_get __P((void));
+static int yy_readline_unget __P((int));
+#endif
+
+static int yy_string_get __P((void));
+static int yy_string_unget __P((int));
+static void rewind_input_string __P((void));
+static int yy_stream_get __P((void));
+static int yy_stream_unget __P((int));
+
+static int shell_getc __P((int));
+static void shell_ungetc __P((int));
+static void discard_until __P((int));
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+static void push_string __P((char *, int, alias_t *));
+static void pop_string __P((void));
+static void free_string_list __P((void));
+#endif
+
+static char *read_a_line __P((int));
+
+static int reserved_word_acceptable __P((int));
+static int yylex __P((void));
+static int alias_expand_token __P((char *));
+static int time_command_acceptable __P((void));
+static int special_case_tokens __P((char *));
+static int read_token __P((int));
+static char *parse_matched_pair __P((int, int, int, int *, int));
+static char *parse_comsub __P((int, int, int, int *, int));
+#if defined (ARRAY_VARS)
+static char *parse_compound_assignment __P((int *));
+#endif
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+static int parse_dparen __P((int));
+static int parse_arith_cmd __P((char **, int));
+#endif
+#if defined (COND_COMMAND)
+static void cond_error __P((void));
+static COND_COM *cond_expr __P((void));
+static COND_COM *cond_or __P((void));
+static COND_COM *cond_and __P((void));
+static COND_COM *cond_term __P((void));
+static int cond_skip_newlines __P((void));
+static COMMAND *parse_cond_command __P((void));
+#endif
+#if defined (ARRAY_VARS)
+static int token_is_assignment __P((char *, int));
+static int token_is_ident __P((char *, int));
+#endif
+static int read_token_word __P((int));
+static void discard_parser_constructs __P((int));
+
+static char *error_token_from_token __P((int));
+static char *error_token_from_text __P((void));
+static void print_offending_line __P((void));
+static void report_syntax_error __P((char *));
+
+static void handle_eof_input_unit __P((void));
+static void prompt_again __P((void));
+#if 0
+static void reset_readline_prompt __P((void));
+#endif
+static void print_prompt __P((void));
+
+#if defined (HANDLE_MULTIBYTE)
+static void set_line_mbstate __P((void));
+static char *shell_input_line_property = NULL;
+#else
+# define set_line_mbstate()
+#endif
+
+extern int yyerror __P((const char *));
+
+#ifdef DEBUG
+extern int yydebug;
+#endif
+
+/* Default prompt strings */
+char *primary_prompt = PPROMPT;
+char *secondary_prompt = SPROMPT;
+
+/* PROMPT_STRING_POINTER points to one of these, never to an actual string. */
+char *ps1_prompt, *ps2_prompt;
+
+/* Handle on the current prompt string. Indirectly points through
+ ps1_ or ps2_prompt. */
+char **prompt_string_pointer = (char **)NULL;
+char *current_prompt_string;
+
+/* Non-zero means we expand aliases in commands. */
+int expand_aliases = 0;
+
+/* If non-zero, the decoded prompt string undergoes parameter and
+ variable substitution, command substitution, arithmetic substitution,
+ string expansion, process substitution, and quote removal in
+ decode_prompt_string. */
+int promptvars = 1;
+
+/* If non-zero, $'...' and $"..." are expanded when they appear within
+ a ${...} expansion, even when the expansion appears within double
+ quotes. */
+int extended_quote = 1;
+
+/* The decoded prompt string. Used if READLINE is not defined or if
+ editing is turned off. Analogous to current_readline_prompt. */
+static char *current_decoded_prompt;
+
+/* The number of lines read from input while creating the current command. */
+int current_command_line_count;
+
+/* The token that currently denotes the end of parse. */
+int shell_eof_token;
+
+/* The token currently being read. */
+int current_token;
+
+/* Variables to manage the task of reading here documents, because we need to
+ defer the reading until after a complete command has been collected. */
+static REDIRECT *redir_stack[10];
+int need_here_doc;
+
+/* Where shell input comes from. History expansion is performed on each
+ line when the shell is interactive. */
+static char *shell_input_line = (char *)NULL;
+static int shell_input_line_index;
+static int shell_input_line_size; /* Amount allocated for shell_input_line. */
+static int shell_input_line_len; /* strlen (shell_input_line) */
+
+/* Either zero or EOF. */
+static int shell_input_line_terminator;
+
+/* The line number in a script on which a function definition starts. */
+static int function_dstart;
+
+/* The line number in a script on which a function body starts. */
+static int function_bstart;
+
+/* The line number in a script at which an arithmetic for command starts. */
+static int arith_for_lineno;
+
+/* The current parser state. */
+static int parser_state;
+
+/* The last read token, or NULL. read_token () uses this for context
+ checking. */
+static int last_read_token;
+
+/* The token read prior to last_read_token. */
+static int token_before_that;
+
+/* The token read prior to token_before_that. */
+static int two_tokens_ago;
+
+/* The line number in a script where the word in a `case WORD', `select WORD'
+ or `for WORD' begins. This is a nested command maximum, since the array
+ index is decremented after a case, select, or for command is parsed. */
+#define MAX_CASE_NEST 128
+static int word_lineno[MAX_CASE_NEST];
+static int word_top = -1;
+
+/* If non-zero, it is the token that we want read_token to return
+ regardless of what text is (or isn't) present to be read. This
+ is reset by read_token. If token_to_read == WORD or
+ ASSIGNMENT_WORD, yylval.word should be set to word_desc_to_read. */
+static int token_to_read;
+static WORD_DESC *word_desc_to_read;
+
+static REDIRECTEE redir;
+%}
+
+%union {
+ WORD_DESC *word; /* the word that we read. */
+ int number; /* the number that we read. */
+ WORD_LIST *word_list;
+ COMMAND *command;
+ REDIRECT *redirect;
+ ELEMENT element;
+ PATTERN_LIST *pattern;
+}
+
+/* Reserved words. Members of the first group are only recognized
+ in the case that they are preceded by a list_terminator. Members
+ of the second group are for [[...]] commands. Members of the
+ third group are recognized only under special circumstances. */
+%token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION
+%token COND_START COND_END COND_ERROR
+%token IN BANG TIME TIMEOPT
+
+/* More general tokens. yylex () knows how to make these. */
+%token <word> WORD ASSIGNMENT_WORD
+%token <number> NUMBER
+%token <word_list> ARITH_CMD ARITH_FOR_EXPRS
+%token <command> COND_CMD
+%token AND_AND OR_OR GREATER_GREATER LESS_LESS LESS_AND LESS_LESS_LESS
+%token GREATER_AND SEMI_SEMI SEMI_AND SEMI_SEMI_AND
+%token LESS_LESS_MINUS AND_GREATER AND_GREATER_GREATER LESS_GREATER
+%token GREATER_BAR BAR_AND
+
+/* The types that the various syntactical units return. */
+
+%type <command> inputunit command pipeline pipeline_command
+%type <command> list list0 list1 compound_list simple_list simple_list1
+%type <command> simple_command shell_command
+%type <command> for_command select_command case_command group_command
+%type <command> arith_command
+%type <command> cond_command
+%type <command> arith_for_command
+%type <command> function_def function_body if_command elif_clause subshell
+%type <redirect> redirection redirection_list
+%type <element> simple_command_element
+%type <word_list> word_list pattern
+%type <pattern> pattern_list case_clause_sequence case_clause
+%type <number> timespec
+%type <number> list_terminator
+
+%start inputunit
+
+%left '&' ';' '\n' yacc_EOF
+%left AND_AND OR_OR
+%right '|' BAR_AND
+%%
+
+inputunit: simple_list simple_list_terminator
+ {
+ /* Case of regular command. Discard the error
+ safety net,and return the command just parsed. */
+ global_command = $1;
+ eof_encountered = 0;
+ /* discard_parser_constructs (0); */
+ if (parser_state & PST_CMDSUBST)
+ parser_state |= PST_EOFTOKEN;
+ YYACCEPT;
+ }
+ | '\n'
+ {
+ /* Case of regular command, but not a very
+ interesting one. Return a NULL command. */
+ global_command = (COMMAND *)NULL;
+ if (parser_state & PST_CMDSUBST)
+ parser_state |= PST_EOFTOKEN;
+ YYACCEPT;
+ }
+ | error '\n'
+ {
+ /* Error during parsing. Return NULL command. */
+ global_command = (COMMAND *)NULL;
+ eof_encountered = 0;
+ /* discard_parser_constructs (1); */
+ if (interactive)
+ {
+ YYACCEPT;
+ }
+ else
+ {
+ YYABORT;
+ }
+ }
+ | yacc_EOF
+ {
+ /* Case of EOF seen by itself. Do ignoreeof or
+ not. */
+ global_command = (COMMAND *)NULL;
+ handle_eof_input_unit ();
+ YYACCEPT;
+ }
+ ;
+
+word_list: WORD
+ { $$ = make_word_list ($1, (WORD_LIST *)NULL); }
+ | word_list WORD
+ { $$ = make_word_list ($2, $1); }
+ ;
+
+redirection: '>' WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection (1, r_output_direction, redir);
+ }
+ | '<' WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection (0, r_input_direction, redir);
+ }
+ | NUMBER '>' WORD
+ {
+ redir.filename = $3;
+ $$ = make_redirection ($1, r_output_direction, redir);
+ }
+ | NUMBER '<' WORD
+ {
+ redir.filename = $3;
+ $$ = make_redirection ($1, r_input_direction, redir);
+ }
+ | GREATER_GREATER WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection (1, r_appending_to, redir);
+ }
+ | NUMBER GREATER_GREATER WORD
+ {
+ redir.filename = $3;
+ $$ = make_redirection ($1, r_appending_to, redir);
+ }
+ | LESS_LESS WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection (0, r_reading_until, redir);
+ redir_stack[need_here_doc++] = $$;
+ }
+ | NUMBER LESS_LESS WORD
+ {
+ redir.filename = $3;
+ $$ = make_redirection ($1, r_reading_until, redir);
+ redir_stack[need_here_doc++] = $$;
+ }
+ | LESS_LESS_LESS WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection (0, r_reading_string, redir);
+ }
+ | NUMBER LESS_LESS_LESS WORD
+ {
+ redir.filename = $3;
+ $$ = make_redirection ($1, r_reading_string, redir);
+ }
+ | LESS_AND NUMBER
+ {
+ redir.dest = $2;
+ $$ = make_redirection (0, r_duplicating_input, redir);
+ }
+ | NUMBER LESS_AND NUMBER
+ {
+ redir.dest = $3;
+ $$ = make_redirection ($1, r_duplicating_input, redir);
+ }
+ | GREATER_AND NUMBER
+ {
+ redir.dest = $2;
+ $$ = make_redirection (1, r_duplicating_output, redir);
+ }
+ | NUMBER GREATER_AND NUMBER
+ {
+ redir.dest = $3;
+ $$ = make_redirection ($1, r_duplicating_output, redir);
+ }
+ | LESS_AND WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection (0, r_duplicating_input_word, redir);
+ }
+ | NUMBER LESS_AND WORD
+ {
+ redir.filename = $3;
+ $$ = make_redirection ($1, r_duplicating_input_word, redir);
+ }
+ | GREATER_AND WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection (1, r_duplicating_output_word, redir);
+ }
+ | NUMBER GREATER_AND WORD
+ {
+ redir.filename = $3;
+ $$ = make_redirection ($1, r_duplicating_output_word, redir);
+ }
+ | LESS_LESS_MINUS WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection
+ (0, r_deblank_reading_until, redir);
+ redir_stack[need_here_doc++] = $$;
+ }
+ | NUMBER LESS_LESS_MINUS WORD
+ {
+ redir.filename = $3;
+ $$ = make_redirection
+ ($1, r_deblank_reading_until, redir);
+ redir_stack[need_here_doc++] = $$;
+ }
+ | GREATER_AND '-'
+ {
+ redir.dest = 0;
+ $$ = make_redirection (1, r_close_this, redir);
+ }
+ | NUMBER GREATER_AND '-'
+ {
+ redir.dest = 0;
+ $$ = make_redirection ($1, r_close_this, redir);
+ }
+ | LESS_AND '-'
+ {
+ redir.dest = 0;
+ $$ = make_redirection (0, r_close_this, redir);
+ }
+ | NUMBER LESS_AND '-'
+ {
+ redir.dest = 0;
+ $$ = make_redirection ($1, r_close_this, redir);
+ }
+ | AND_GREATER WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection (1, r_err_and_out, redir);
+ }
+ | AND_GREATER_GREATER WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection (1, r_append_err_and_out, redir);
+ }
+ | NUMBER LESS_GREATER WORD
+ {
+ redir.filename = $3;
+ $$ = make_redirection ($1, r_input_output, redir);
+ }
+ | LESS_GREATER WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection (0, r_input_output, redir);
+ }
+ | GREATER_BAR WORD
+ {
+ redir.filename = $2;
+ $$ = make_redirection (1, r_output_force, redir);
+ }
+ | NUMBER GREATER_BAR WORD
+ {
+ redir.filename = $3;
+ $$ = make_redirection ($1, r_output_force, redir);
+ }
+ ;
+
+simple_command_element: WORD
+ { $$.word = $1; $$.redirect = 0; }
+ | ASSIGNMENT_WORD
+ { $$.word = $1; $$.redirect = 0; }
+ | redirection
+ { $$.redirect = $1; $$.word = 0; }
+ ;
+
+redirection_list: redirection
+ {
+ $$ = $1;
+ }
+ | redirection_list redirection
+ {
+ register REDIRECT *t;
+
+ for (t = $1; t->next; t = t->next)
+ ;
+ t->next = $2;
+ $$ = $1;
+ }
+ ;
+
+simple_command: simple_command_element
+ { $$ = make_simple_command ($1, (COMMAND *)NULL); }
+ | simple_command simple_command_element
+ { $$ = make_simple_command ($2, $1); }
+ ;
+
+command: simple_command
+ { $$ = clean_simple_command ($1); }
+ | shell_command
+ { $$ = $1; }
+ | shell_command redirection_list
+ {
+ COMMAND *tc;
+
+ tc = $1;
+ if (tc->redirects)
+ {
+ register REDIRECT *t;
+ for (t = tc->redirects; t->next; t = t->next)
+ ;
+ t->next = $2;
+ }
+ else
+ tc->redirects = $2;
+ $$ = $1;
+ }
+ | function_def
+ { $$ = $1; }
+ ;
+
+shell_command: for_command
+ { $$ = $1; }
+ | case_command
+ { $$ = $1; }
+ | WHILE compound_list DO compound_list DONE
+ { $$ = make_while_command ($2, $4); }
+ | UNTIL compound_list DO compound_list DONE
+ { $$ = make_until_command ($2, $4); }
+ | select_command
+ { $$ = $1; }
+ | if_command
+ { $$ = $1; }
+ | subshell
+ { $$ = $1; }
+ | group_command
+ { $$ = $1; }
+ | arith_command
+ { $$ = $1; }
+ | cond_command
+ { $$ = $1; }
+ | arith_for_command
+ { $$ = $1; }
+ ;
+
+for_command: FOR WORD newline_list DO compound_list DONE
+ {
+ $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | FOR WORD newline_list '{' compound_list '}'
+ {
+ $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | FOR WORD ';' newline_list DO compound_list DONE
+ {
+ $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | FOR WORD ';' newline_list '{' compound_list '}'
+ {
+ $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | FOR WORD newline_list IN word_list list_terminator newline_list DO compound_list DONE
+ {
+ $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | FOR WORD newline_list IN word_list list_terminator newline_list '{' compound_list '}'
+ {
+ $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | FOR WORD newline_list IN list_terminator newline_list DO compound_list DONE
+ {
+ $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | FOR WORD newline_list IN list_terminator newline_list '{' compound_list '}'
+ {
+ $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ ;
+
+arith_for_command: FOR ARITH_FOR_EXPRS list_terminator newline_list DO compound_list DONE
+ {
+ $$ = make_arith_for_command ($2, $6, arith_for_lineno);
+ if (word_top > 0) word_top--;
+ }
+ | FOR ARITH_FOR_EXPRS list_terminator newline_list '{' compound_list '}'
+ {
+ $$ = make_arith_for_command ($2, $6, arith_for_lineno);
+ if (word_top > 0) word_top--;
+ }
+ | FOR ARITH_FOR_EXPRS DO compound_list DONE
+ {
+ $$ = make_arith_for_command ($2, $4, arith_for_lineno);
+ if (word_top > 0) word_top--;
+ }
+ | FOR ARITH_FOR_EXPRS '{' compound_list '}'
+ {
+ $$ = make_arith_for_command ($2, $4, arith_for_lineno);
+ if (word_top > 0) word_top--;
+ }
+ ;
+
+select_command: SELECT WORD newline_list DO list DONE
+ {
+ $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | SELECT WORD newline_list '{' list '}'
+ {
+ $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | SELECT WORD ';' newline_list DO list DONE
+ {
+ $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | SELECT WORD ';' newline_list '{' list '}'
+ {
+ $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | SELECT WORD newline_list IN word_list list_terminator newline_list DO list DONE
+ {
+ $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | SELECT WORD newline_list IN word_list list_terminator newline_list '{' list '}'
+ {
+ $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ ;
+
+case_command: CASE WORD newline_list IN newline_list ESAC
+ {
+ $$ = make_case_command ($2, (PATTERN_LIST *)NULL, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | CASE WORD newline_list IN case_clause_sequence newline_list ESAC
+ {
+ $$ = make_case_command ($2, $5, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ | CASE WORD newline_list IN case_clause ESAC
+ {
+ $$ = make_case_command ($2, $5, word_lineno[word_top]);
+ if (word_top > 0) word_top--;
+ }
+ ;
+
+function_def: WORD '(' ')' newline_list function_body
+ { $$ = make_function_def ($1, $5, function_dstart, function_bstart); }
+
+ | FUNCTION WORD '(' ')' newline_list function_body
+ { $$ = make_function_def ($2, $6, function_dstart, function_bstart); }
+
+ | FUNCTION WORD newline_list function_body
+ { $$ = make_function_def ($2, $4, function_dstart, function_bstart); }
+ ;
+
+
+function_body: shell_command
+ { $$ = $1; }
+ | shell_command redirection_list
+ {
+ COMMAND *tc;
+
+ tc = $1;
+ /* According to Posix.2 3.9.5, redirections
+ specified after the body of a function should
+ be attached to the function and performed when
+ the function is executed, not as part of the
+ function definition command. */
+ /* XXX - I don't think it matters, but we might
+ want to change this in the future to avoid
+ problems differentiating between a function
+ definition with a redirection and a function
+ definition containing a single command with a
+ redirection. The two are semantically equivalent,
+ though -- the only difference is in how the
+ command printing code displays the redirections. */
+ if (tc->redirects)
+ {
+ register REDIRECT *t;
+ for (t = tc->redirects; t->next; t = t->next)
+ ;
+ t->next = $2;
+ }
+ else
+ tc->redirects = $2;
+ $$ = $1;
+ }
+ ;
+
+subshell: '(' compound_list ')'
+ {
+ $$ = make_subshell_command ($2);
+ $$->flags |= CMD_WANT_SUBSHELL;
+ }
+ ;
+
+if_command: IF compound_list THEN compound_list FI
+ { $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
+ | IF compound_list THEN compound_list ELSE compound_list FI
+ { $$ = make_if_command ($2, $4, $6); }
+ | IF compound_list THEN compound_list elif_clause FI
+ { $$ = make_if_command ($2, $4, $5); }
+ ;
+
+
+group_command: '{' compound_list '}'
+ { $$ = make_group_command ($2); }
+ ;
+
+arith_command: ARITH_CMD
+ { $$ = make_arith_command ($1); }
+ ;
+
+cond_command: COND_START COND_CMD COND_END
+ { $$ = $2; }
+ ;
+
+elif_clause: ELIF compound_list THEN compound_list
+ { $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
+ | ELIF compound_list THEN compound_list ELSE compound_list
+ { $$ = make_if_command ($2, $4, $6); }
+ | ELIF compound_list THEN compound_list elif_clause
+ { $$ = make_if_command ($2, $4, $5); }
+ ;
+
+case_clause: pattern_list
+ | case_clause_sequence pattern_list
+ { $2->next = $1; $$ = $2; }
+ ;
+
+pattern_list: newline_list pattern ')' compound_list
+ { $$ = make_pattern_list ($2, $4); }
+ | newline_list pattern ')' newline_list
+ { $$ = make_pattern_list ($2, (COMMAND *)NULL); }
+ | newline_list '(' pattern ')' compound_list
+ { $$ = make_pattern_list ($3, $5); }
+ | newline_list '(' pattern ')' newline_list
+ { $$ = make_pattern_list ($3, (COMMAND *)NULL); }
+ ;
+
+case_clause_sequence: pattern_list SEMI_SEMI
+ { $$ = $1; }
+ | case_clause_sequence pattern_list SEMI_SEMI
+ { $2->next = $1; $$ = $2; }
+ | pattern_list SEMI_AND
+ { $1->flags |= CASEPAT_FALLTHROUGH; $$ = $1; }
+ | case_clause_sequence pattern_list SEMI_AND
+ { $2->flags |= CASEPAT_FALLTHROUGH; $2->next = $1; $$ = $2; }
+ | pattern_list SEMI_SEMI_AND
+ { $1->flags |= CASEPAT_TESTNEXT; $$ = $1; }
+ | case_clause_sequence pattern_list SEMI_SEMI_AND
+ { $2->flags |= CASEPAT_TESTNEXT; $2->next = $1; $$ = $2; }
+ ;
+
+pattern: WORD
+ { $$ = make_word_list ($1, (WORD_LIST *)NULL); }
+ | pattern '|' WORD
+ { $$ = make_word_list ($3, $1); }
+ ;
+
+/* A list allows leading or trailing newlines and
+ newlines as operators (equivalent to semicolons).
+ It must end with a newline or semicolon.
+ Lists are used within commands such as if, for, while. */
+
+list: newline_list list0
+ {
+ $$ = $2;
+ if (need_here_doc)
+ gather_here_documents ();
+ }
+ ;
+
+compound_list: list
+ | newline_list list1
+ {
+ $$ = $2;
+ }
+ ;
+
+list0: list1 '\n' newline_list
+ | list1 '&' newline_list
+ {
+ if ($1->type == cm_connection)
+ $$ = connect_async_list ($1, (COMMAND *)NULL, '&');
+ else
+ $$ = command_connect ($1, (COMMAND *)NULL, '&');
+ }
+ | list1 ';' newline_list
+
+ ;
+
+list1: list1 AND_AND newline_list list1
+ { $$ = command_connect ($1, $4, AND_AND); }
+ | list1 OR_OR newline_list list1
+ { $$ = command_connect ($1, $4, OR_OR); }
+ | list1 '&' newline_list list1
+ {
+ if ($1->type == cm_connection)
+ $$ = connect_async_list ($1, $4, '&');
+ else
+ $$ = command_connect ($1, $4, '&');
+ }
+ | list1 ';' newline_list list1
+ { $$ = command_connect ($1, $4, ';'); }
+ | list1 '\n' newline_list list1
+ { $$ = command_connect ($1, $4, ';'); }
+ | pipeline_command
+ { $$ = $1; }
+ ;
+
+simple_list_terminator: '\n'
+ | yacc_EOF
+ ;
+
+list_terminator:'\n'
+ { $$ = '\n'; }
+ | ';'
+ { $$ = ';'; }
+ | yacc_EOF
+ { $$ = yacc_EOF; }
+ ;
+
+newline_list:
+ | newline_list '\n'
+ ;
+
+/* A simple_list is a list that contains no significant newlines
+ and no leading or trailing newlines. Newlines are allowed
+ only following operators, where they are not significant.
+
+ This is what an inputunit consists of. */
+
+simple_list: simple_list1
+ {
+ $$ = $1;
+ if (need_here_doc)
+ gather_here_documents ();
+ if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+ {
+ global_command = $1;
+ eof_encountered = 0;
+ rewind_input_string ();
+ YYACCEPT;
+ }
+ }
+ | simple_list1 '&'
+ {
+ if ($1->type == cm_connection)
+ $$ = connect_async_list ($1, (COMMAND *)NULL, '&');
+ else
+ $$ = command_connect ($1, (COMMAND *)NULL, '&');
+ if (need_here_doc)
+ gather_here_documents ();
+ if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+ {
+ global_command = $1;
+ eof_encountered = 0;
+ rewind_input_string ();
+ YYACCEPT;
+ }
+ }
+ | simple_list1 ';'
+ {
+ $$ = $1;
+ if (need_here_doc)
+ gather_here_documents ();
+ if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+ {
+ global_command = $1;
+ eof_encountered = 0;
+ rewind_input_string ();
+ YYACCEPT;
+ }
+ }
+ ;
+
+simple_list1: simple_list1 AND_AND newline_list simple_list1
+ { $$ = command_connect ($1, $4, AND_AND); }
+ | simple_list1 OR_OR newline_list simple_list1
+ { $$ = command_connect ($1, $4, OR_OR); }
+ | simple_list1 '&' simple_list1
+ {
+ if ($1->type == cm_connection)
+ $$ = connect_async_list ($1, $3, '&');
+ else
+ $$ = command_connect ($1, $3, '&');
+ }
+ | simple_list1 ';' simple_list1
+ { $$ = command_connect ($1, $3, ';'); }
+
+ | pipeline_command
+ { $$ = $1; }
+ ;
+
+pipeline_command: pipeline
+ { $$ = $1; }
+ | BANG pipeline
+ {
+ if ($2)
+ $2->flags |= CMD_INVERT_RETURN;
+ $$ = $2;
+ }
+ | timespec pipeline
+ {
+ if ($2)
+ $2->flags |= $1;
+ $$ = $2;
+ }
+ | timespec BANG pipeline
+ {
+ if ($3)
+ $3->flags |= $1|CMD_INVERT_RETURN;
+ $$ = $3;
+ }
+ | BANG timespec pipeline
+ {
+ if ($3)
+ $3->flags |= $2|CMD_INVERT_RETURN;
+ $$ = $3;
+ }
+ | timespec list_terminator
+ {
+ ELEMENT x;
+
+ /* Boy, this is unclean. `time' by itself can
+ time a null command. We cheat and push a
+ newline back if the list_terminator was a newline
+ to avoid the double-newline problem (one to
+ terminate this, one to terminate the command) */
+ x.word = 0;
+ x.redirect = 0;
+ $$ = make_simple_command (x, (COMMAND *)NULL);
+ $$->flags |= $1;
+ /* XXX - let's cheat and push a newline back */
+ if ($2 == '\n')
+ token_to_read = '\n';
+ }
+
+ ;
+
+pipeline: pipeline '|' newline_list pipeline
+ { $$ = command_connect ($1, $4, '|'); }
+ | pipeline BAR_AND newline_list pipeline
+ {
+ /* Make cmd1 |& cmd2 equivalent to cmd1 2>&1 | cmd2 */
+ COMMAND *tc;
+ REDIRECTEE rd;
+ REDIRECT *r;
+
+ tc = $1;
+ rd.dest = 1;
+ r = make_redirection (2, r_duplicating_output, rd);
+ if (tc->redirects)
+ {
+ register REDIRECT *t;
+ for (t = tc->redirects; t->next; t = t->next)
+ ;
+ t->next = r;
+ }
+ else
+ tc->redirects = r;
+
+ $$ = command_connect ($1, $4, '|');
+ }
+ | command
+ { $$ = $1; }
+ ;
+
+timespec: TIME
+ { $$ = CMD_TIME_PIPELINE; }
+ | TIME TIMEOPT
+ { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
+ ;
+%%
+
+/* Initial size to allocate for tokens, and the
+ amount to grow them by. */
+#define TOKEN_DEFAULT_INITIAL_SIZE 496
+#define TOKEN_DEFAULT_GROW_SIZE 512
+
+/* Should we call prompt_again? */
+#define SHOULD_PROMPT() \
+ (interactive && (bash_input.type == st_stdin || bash_input.type == st_stream))
+
+#if defined (ALIAS)
+# define expanding_alias() (pushed_string_list && pushed_string_list->expander)
+#else
+# define expanding_alias() 0
+#endif
+
+/* Global var is non-zero when end of file has been reached. */
+int EOF_Reached = 0;
+
+#ifdef DEBUG
+static void
+debug_parser (i)
+ int i;
+{
+#if YYDEBUG != 0
+ yydebug = i;
+#endif
+}
+#endif
+
+/* yy_getc () returns the next available character from input or EOF.
+ yy_ungetc (c) makes `c' the next character to read.
+ init_yy_io (get, unget, type, location) makes the function GET the
+ installed function for getting the next character, makes UNGET the
+ installed function for un-getting a character, sets the type of stream
+ (either string or file) from TYPE, and makes LOCATION point to where
+ the input is coming from. */
+
+/* Unconditionally returns end-of-file. */
+int
+return_EOF ()
+{
+ return (EOF);
+}
+
+/* Variable containing the current get and unget functions.
+ See ./input.h for a clearer description. */
+BASH_INPUT bash_input;
+
+/* Set all of the fields in BASH_INPUT to NULL. Free bash_input.name if it
+ is non-null, avoiding a memory leak. */
+void
+initialize_bash_input ()
+{
+ bash_input.type = st_none;
+ FREE (bash_input.name);
+ bash_input.name = (char *)NULL;
+ bash_input.location.file = (FILE *)NULL;
+ bash_input.location.string = (char *)NULL;
+ bash_input.getter = (sh_cget_func_t *)NULL;
+ bash_input.ungetter = (sh_cunget_func_t *)NULL;
+}
+
+/* Set the contents of the current bash input stream from
+ GET, UNGET, TYPE, NAME, and LOCATION. */
+void
+init_yy_io (get, unget, type, name, location)
+ sh_cget_func_t *get;
+ sh_cunget_func_t *unget;
+ enum stream_type type;
+ const char *name;
+ INPUT_STREAM location;
+{
+ bash_input.type = type;
+ FREE (bash_input.name);
+ bash_input.name = name ? savestring (name) : (char *)NULL;
+
+ /* XXX */
+#if defined (CRAY)
+ memcpy((char *)&bash_input.location.string, (char *)&location.string, sizeof(location));
+#else
+ bash_input.location = location;
+#endif
+ bash_input.getter = get;
+ bash_input.ungetter = unget;
+}
+
+char *
+yy_input_name ()
+{
+ return (bash_input.name ? bash_input.name : "stdin");
+}
+
+/* Call this to get the next character of input. */
+static int
+yy_getc ()
+{
+ return (*(bash_input.getter)) ();
+}
+
+/* Call this to unget C. That is, to make C the next character
+ to be read. */
+static int
+yy_ungetc (c)
+ int c;
+{
+ return (*(bash_input.ungetter)) (c);
+}
+
+#if defined (BUFFERED_INPUT)
+#ifdef INCLUDE_UNUSED
+int
+input_file_descriptor ()
+{
+ switch (bash_input.type)
+ {
+ case st_stream:
+ return (fileno (bash_input.location.file));
+ case st_bstream:
+ return (bash_input.location.buffered_fd);
+ case st_stdin:
+ default:
+ return (fileno (stdin));
+ }
+}
+#endif
+#endif /* BUFFERED_INPUT */
+
+/* **************************************************************** */
+/* */
+/* Let input be read from readline (). */
+/* */
+/* **************************************************************** */
+
+#if defined (READLINE)
+char *current_readline_prompt = (char *)NULL;
+char *current_readline_line = (char *)NULL;
+int current_readline_line_index = 0;
+
+static int
+yy_readline_get ()
+{
+ SigHandler *old_sigint;
+ int line_len;
+ unsigned char c;
+
+ if (!current_readline_line)
+ {
+ if (!bash_readline_initialized)
+ initialize_readline ();
+
+#if defined (JOB_CONTROL)
+ if (job_control)
+ give_terminal_to (shell_pgrp, 0);
+#endif /* JOB_CONTROL */
+
+ old_sigint = (SigHandler *)NULL;
+ if (signal_is_ignored (SIGINT) == 0)
+ {
+ old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler);
+ interrupt_immediately++;
+ }
+ terminate_immediately = 1;
+
+ current_readline_line = readline (current_readline_prompt ?
+ current_readline_prompt : "");
+
+ terminate_immediately = 0;
+ if (signal_is_ignored (SIGINT) == 0 && old_sigint)
+ {
+ interrupt_immediately--;
+ set_signal_handler (SIGINT, old_sigint);
+ }
+
+#if 0
+ /* Reset the prompt to the decoded value of prompt_string_pointer. */
+ reset_readline_prompt ();
+#endif
+
+ if (current_readline_line == 0)
+ return (EOF);
+
+ current_readline_line_index = 0;
+ line_len = strlen (current_readline_line);
+
+ current_readline_line = (char *)xrealloc (current_readline_line, 2 + line_len);
+ current_readline_line[line_len++] = '\n';
+ current_readline_line[line_len] = '\0';
+ }
+
+ if (current_readline_line[current_readline_line_index] == 0)
+ {
+ free (current_readline_line);
+ current_readline_line = (char *)NULL;
+ return (yy_readline_get ());
+ }
+ else
+ {
+ c = current_readline_line[current_readline_line_index++];
+ return (c);
+ }
+}
+
+static int
+yy_readline_unget (c)
+ int c;
+{
+ if (current_readline_line_index && current_readline_line)
+ current_readline_line[--current_readline_line_index] = c;
+ return (c);
+}
+
+void
+with_input_from_stdin ()
+{
+ INPUT_STREAM location;
+
+ if (bash_input.type != st_stdin && stream_on_stack (st_stdin) == 0)
+ {
+ location.string = current_readline_line;
+ init_yy_io (yy_readline_get, yy_readline_unget,
+ st_stdin, "readline stdin", location);
+ }
+}
+
+#else /* !READLINE */
+
+void
+with_input_from_stdin ()
+{
+ with_input_from_stream (stdin, "stdin");
+}
+#endif /* !READLINE */
+
+/* **************************************************************** */
+/* */
+/* Let input come from STRING. STRING is zero terminated. */
+/* */
+/* **************************************************************** */
+
+static int
+yy_string_get ()
+{
+ register char *string;
+ register unsigned char c;
+
+ string = bash_input.location.string;
+
+ /* If the string doesn't exist, or is empty, EOF found. */
+ if (string && *string)
+ {
+ c = *string++;
+ bash_input.location.string = string;
+ return (c);
+ }
+ else
+ return (EOF);
+}
+
+static int
+yy_string_unget (c)
+ int c;
+{
+ *(--bash_input.location.string) = c;
+ return (c);
+}
+
+void
+with_input_from_string (string, name)
+ char *string;
+ const char *name;
+{
+ INPUT_STREAM location;
+
+ location.string = string;
+ init_yy_io (yy_string_get, yy_string_unget, st_string, name, location);
+}
+
+/* Count the number of characters we've consumed from bash_input.location.string
+ and read into shell_input_line, but have not returned from shell_getc.
+ That is the true input location. Rewind bash_input.location.string by
+ that number of characters, so it points to the last character actually
+ consumed by the parser. */
+void
+rewind_input_string ()
+{
+ int xchars;
+
+ /* number of unconsumed characters in the input -- XXX need to take newlines
+ into account, e.g., $(...\n) */
+ xchars = shell_input_line_len - shell_input_line_index;
+
+ /* XXX - how to reflect bash_input.location.string back to string passed to
+ parse_and_execute or xparse_dolparen? xparse_dolparen needs to know how
+ far into the string we parsed. parse_and_execute knows where bash_input.
+ location.string is, and how far from orig_string that is -- that's the
+ number of characters the command consumed. */
+
+ /* bash_input.location.string - xchars should be where we parsed to */
+ /* need to do more validation on xchars value for sanity -- test cases. */
+ bash_input.location.string -= xchars;
+}
+
+/* **************************************************************** */
+/* */
+/* Let input come from STREAM. */
+/* */
+/* **************************************************************** */
+
+/* These two functions used to test the value of the HAVE_RESTARTABLE_SYSCALLS
+ define, and just use getc/ungetc if it was defined, but since bash
+ installs its signal handlers without the SA_RESTART flag, some signals
+ (like SIGCHLD, SIGWINCH, etc.) received during a read(2) will not cause
+ the read to be restarted. We need to restart it ourselves. */
+
+static int
+yy_stream_get ()
+{
+ int result;
+
+ result = EOF;
+ if (bash_input.location.file)
+ {
+ if (interactive)
+ {
+ interrupt_immediately++;
+ terminate_immediately++;
+ }
+ result = getc_with_restart (bash_input.location.file);
+ if (interactive)
+ {
+ interrupt_immediately--;
+ terminate_immediately--;
+ }
+ }
+ return (result);
+}
+
+static int
+yy_stream_unget (c)
+ int c;
+{
+ return (ungetc_with_restart (c, bash_input.location.file));
+}
+
+void
+with_input_from_stream (stream, name)
+ FILE *stream;
+ const char *name;
+{
+ INPUT_STREAM location;
+
+ location.file = stream;
+ init_yy_io (yy_stream_get, yy_stream_unget, st_stream, name, location);
+}
+
+typedef struct stream_saver {
+ struct stream_saver *next;
+ BASH_INPUT bash_input;
+ int line;
+#if defined (BUFFERED_INPUT)
+ BUFFERED_STREAM *bstream;
+#endif /* BUFFERED_INPUT */
+} STREAM_SAVER;
+
+/* The globally known line number. */
+int line_number = 0;
+
+#if defined (COND_COMMAND)
+static int cond_lineno;
+static int cond_token;
+#endif
+
+STREAM_SAVER *stream_list = (STREAM_SAVER *)NULL;
+
+void
+push_stream (reset_lineno)
+ int reset_lineno;
+{
+ STREAM_SAVER *saver = (STREAM_SAVER *)xmalloc (sizeof (STREAM_SAVER));
+
+ xbcopy ((char *)&bash_input, (char *)&(saver->bash_input), sizeof (BASH_INPUT));
+
+#if defined (BUFFERED_INPUT)
+ saver->bstream = (BUFFERED_STREAM *)NULL;
+ /* If we have a buffered stream, clear out buffers[fd]. */
+ if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0)
+ saver->bstream = set_buffered_stream (bash_input.location.buffered_fd,
+ (BUFFERED_STREAM *)NULL);
+#endif /* BUFFERED_INPUT */
+
+ saver->line = line_number;
+ bash_input.name = (char *)NULL;
+ saver->next = stream_list;
+ stream_list = saver;
+ EOF_Reached = 0;
+ if (reset_lineno)
+ line_number = 0;
+}
+
+void
+pop_stream ()
+{
+ if (!stream_list)
+ EOF_Reached = 1;
+ else
+ {
+ STREAM_SAVER *saver = stream_list;
+
+ EOF_Reached = 0;
+ stream_list = stream_list->next;
+
+ init_yy_io (saver->bash_input.getter,
+ saver->bash_input.ungetter,
+ saver->bash_input.type,
+ saver->bash_input.name,
+ saver->bash_input.location);
+
+#if defined (BUFFERED_INPUT)
+ /* If we have a buffered stream, restore buffers[fd]. */
+ /* If the input file descriptor was changed while this was on the
+ save stack, update the buffered fd to the new file descriptor and
+ re-establish the buffer <-> bash_input fd correspondence. */
+ if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0)
+ {
+ if (bash_input_fd_changed)
+ {
+ bash_input_fd_changed = 0;
+ if (default_buffered_input >= 0)
+ {
+ bash_input.location.buffered_fd = default_buffered_input;
+ saver->bstream->b_fd = default_buffered_input;
+ SET_CLOSE_ON_EXEC (default_buffered_input);
+ }
+ }
+ /* XXX could free buffered stream returned as result here. */
+ set_buffered_stream (bash_input.location.buffered_fd, saver->bstream);
+ }
+#endif /* BUFFERED_INPUT */
+
+ line_number = saver->line;
+
+ FREE (saver->bash_input.name);
+ free (saver);
+ }
+}
+
+/* Return 1 if a stream of type TYPE is saved on the stack. */
+int
+stream_on_stack (type)
+ enum stream_type type;
+{
+ register STREAM_SAVER *s;
+
+ for (s = stream_list; s; s = s->next)
+ if (s->bash_input.type == type)
+ return 1;
+ return 0;
+}
+
+/* Save the current token state and return it in a malloced array. */
+int *
+save_token_state ()
+{
+ int *ret;
+
+ ret = (int *)xmalloc (3 * sizeof (int));
+ ret[0] = last_read_token;
+ ret[1] = token_before_that;
+ ret[2] = two_tokens_ago;
+ return ret;
+}
+
+void
+restore_token_state (ts)
+ int *ts;
+{
+ if (ts == 0)
+ return;
+ last_read_token = ts[0];
+ token_before_that = ts[1];
+ two_tokens_ago = ts[2];
+}
+
+/*
+ * This is used to inhibit alias expansion and reserved word recognition
+ * inside case statement pattern lists. A `case statement pattern list' is:
+ *
+ * everything between the `in' in a `case word in' and the next ')'
+ * or `esac'
+ * everything between a `;;' and the next `)' or `esac'
+ */
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+
+#define END_OF_ALIAS 0
+
+/*
+ * Pseudo-global variables used in implementing token-wise alias expansion.
+ */
+
+/*
+ * Pushing and popping strings. This works together with shell_getc to
+ * implement alias expansion on a per-token basis.
+ */
+
+typedef struct string_saver {
+ struct string_saver *next;
+ int expand_alias; /* Value to set expand_alias to when string is popped. */
+ char *saved_line;
+#if defined (ALIAS)
+ alias_t *expander; /* alias that caused this line to be pushed. */
+#endif
+ int saved_line_size, saved_line_index, saved_line_terminator;
+} STRING_SAVER;
+
+STRING_SAVER *pushed_string_list = (STRING_SAVER *)NULL;
+
+/*
+ * Push the current shell_input_line onto a stack of such lines and make S
+ * the current input. Used when expanding aliases. EXPAND is used to set
+ * the value of expand_next_token when the string is popped, so that the
+ * word after the alias in the original line is handled correctly when the
+ * alias expands to multiple words. TOKEN is the token that was expanded
+ * into S; it is saved and used to prevent infinite recursive expansion.
+ */
+static void
+push_string (s, expand, ap)
+ char *s;
+ int expand;
+ alias_t *ap;
+{
+ STRING_SAVER *temp = (STRING_SAVER *)xmalloc (sizeof (STRING_SAVER));
+
+ temp->expand_alias = expand;
+ temp->saved_line = shell_input_line;
+ temp->saved_line_size = shell_input_line_size;
+ temp->saved_line_index = shell_input_line_index;
+ temp->saved_line_terminator = shell_input_line_terminator;
+#if defined (ALIAS)
+ temp->expander = ap;
+#endif
+ temp->next = pushed_string_list;
+ pushed_string_list = temp;
+
+#if defined (ALIAS)
+ if (ap)
+ ap->flags |= AL_BEINGEXPANDED;
+#endif
+
+ shell_input_line = s;
+ shell_input_line_size = strlen (s);
+ shell_input_line_index = 0;
+ shell_input_line_terminator = '\0';
+#if 0
+ parser_state &= ~PST_ALEXPNEXT; /* XXX */
+#endif
+
+ set_line_mbstate ();
+}
+
+/*
+ * Make the top of the pushed_string stack be the current shell input.
+ * Only called when there is something on the stack. Called from shell_getc
+ * when it thinks it has consumed the string generated by an alias expansion
+ * and needs to return to the original input line.
+ */
+static void
+pop_string ()
+{
+ STRING_SAVER *t;
+
+ FREE (shell_input_line);
+ shell_input_line = pushed_string_list->saved_line;
+ shell_input_line_index = pushed_string_list->saved_line_index;
+ shell_input_line_size = pushed_string_list->saved_line_size;
+ shell_input_line_terminator = pushed_string_list->saved_line_terminator;
+
+ if (pushed_string_list->expand_alias)
+ parser_state |= PST_ALEXPNEXT;
+ else
+ parser_state &= ~PST_ALEXPNEXT;
+
+ t = pushed_string_list;
+ pushed_string_list = pushed_string_list->next;
+
+#if defined (ALIAS)
+ if (t->expander)
+ t->expander->flags &= ~AL_BEINGEXPANDED;
+#endif
+
+ free ((char *)t);
+
+ set_line_mbstate ();
+}
+
+static void
+free_string_list ()
+{
+ register STRING_SAVER *t, *t1;
+
+ for (t = pushed_string_list; t; )
+ {
+ t1 = t->next;
+ FREE (t->saved_line);
+#if defined (ALIAS)
+ if (t->expander)
+ t->expander->flags &= ~AL_BEINGEXPANDED;
+#endif
+ free ((char *)t);
+ t = t1;
+ }
+ pushed_string_list = (STRING_SAVER *)NULL;
+}
+
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+void
+free_pushed_string_input ()
+{
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+ free_string_list ();
+#endif
+}
+
+/* Return a line of text, taken from wherever yylex () reads input.
+ If there is no more input, then we return NULL. If REMOVE_QUOTED_NEWLINE
+ is non-zero, we remove unquoted \<newline> pairs. This is used by
+ read_secondary_line to read here documents. */
+static char *
+read_a_line (remove_quoted_newline)
+ int remove_quoted_newline;
+{
+ static char *line_buffer = (char *)NULL;
+ static int buffer_size = 0;
+ int indx = 0, c, peekc, pass_next;
+
+#if defined (READLINE)
+ if (no_line_editing && SHOULD_PROMPT ())
+#else
+ if (SHOULD_PROMPT ())
+#endif
+ print_prompt ();
+
+ pass_next = 0;
+ while (1)
+ {
+ /* Allow immediate exit if interrupted during input. */
+ QUIT;
+
+ c = yy_getc ();
+
+ /* Ignore null bytes in input. */
+ if (c == 0)
+ {
+#if 0
+ internal_warning ("read_a_line: ignored null byte in input");
+#endif
+ continue;
+ }
+
+ /* If there is no more input, then we return NULL. */
+ if (c == EOF)
+ {
+ if (interactive && bash_input.type == st_stream)
+ clearerr (stdin);
+ if (indx == 0)
+ return ((char *)NULL);
+ c = '\n';
+ }
+
+ /* `+2' in case the final character in the buffer is a newline. */
+ RESIZE_MALLOCED_BUFFER (line_buffer, indx, 2, buffer_size, 128);
+
+ /* IF REMOVE_QUOTED_NEWLINES is non-zero, we are reading a
+ here document with an unquoted delimiter. In this case,
+ the line will be expanded as if it were in double quotes.
+ We allow a backslash to escape the next character, but we
+ need to treat the backslash specially only if a backslash
+ quoting a backslash-newline pair appears in the line. */
+ if (pass_next)
+ {
+ line_buffer[indx++] = c;
+ pass_next = 0;
+ }
+ else if (c == '\\' && remove_quoted_newline)
+ {
+ peekc = yy_getc ();
+ if (peekc == '\n')
+ {
+ line_number++;
+ continue; /* Make the unquoted \<newline> pair disappear. */
+ }
+ else
+ {
+ yy_ungetc (peekc);
+ pass_next = 1;
+ line_buffer[indx++] = c; /* Preserve the backslash. */
+ }
+ }
+ else
+ line_buffer[indx++] = c;
+
+ if (c == '\n')
+ {
+ line_buffer[indx] = '\0';
+ return (line_buffer);
+ }
+ }
+}
+
+/* Return a line as in read_a_line (), but insure that the prompt is
+ the secondary prompt. This is used to read the lines of a here
+ document. REMOVE_QUOTED_NEWLINE is non-zero if we should remove
+ newlines quoted with backslashes while reading the line. It is
+ non-zero unless the delimiter of the here document was quoted. */
+char *
+read_secondary_line (remove_quoted_newline)
+ int remove_quoted_newline;
+{
+ prompt_string_pointer = &ps2_prompt;
+ if (SHOULD_PROMPT())
+ prompt_again ();
+ return (read_a_line (remove_quoted_newline));
+}
+
+/* **************************************************************** */
+/* */
+/* YYLEX () */
+/* */
+/* **************************************************************** */
+
+/* Reserved words. These are only recognized as the first word of a
+ command. */
+STRING_INT_ALIST word_token_alist[] = {
+ { "if", IF },
+ { "then", THEN },
+ { "else", ELSE },
+ { "elif", ELIF },
+ { "fi", FI },
+ { "case", CASE },
+ { "esac", ESAC },
+ { "for", FOR },
+#if defined (SELECT_COMMAND)
+ { "select", SELECT },
+#endif
+ { "while", WHILE },
+ { "until", UNTIL },
+ { "do", DO },
+ { "done", DONE },
+ { "in", IN },
+ { "function", FUNCTION },
+#if defined (COMMAND_TIMING)
+ { "time", TIME },
+#endif
+ { "{", '{' },
+ { "}", '}' },
+ { "!", BANG },
+#if defined (COND_COMMAND)
+ { "[[", COND_START },
+ { "]]", COND_END },
+#endif
+ { (char *)NULL, 0}
+};
+
+/* other tokens that can be returned by read_token() */
+STRING_INT_ALIST other_token_alist[] = {
+ /* Multiple-character tokens with special values */
+ { "-p", TIMEOPT },
+ { "&&", AND_AND },
+ { "||", OR_OR },
+ { ">>", GREATER_GREATER },
+ { "<<", LESS_LESS },
+ { "<&", LESS_AND },
+ { ">&", GREATER_AND },
+ { ";;", SEMI_SEMI },
+ { ";&", SEMI_AND },
+ { ";;&", SEMI_SEMI_AND },
+ { "<<-", LESS_LESS_MINUS },
+ { "<<<", LESS_LESS_LESS },
+ { "&>", AND_GREATER },
+ { "&>>", AND_GREATER_GREATER },
+ { "<>", LESS_GREATER },
+ { ">|", GREATER_BAR },
+ { "|&", BAR_AND },
+ { "EOF", yacc_EOF },
+ /* Tokens whose value is the character itself */
+ { ">", '>' },
+ { "<", '<' },
+ { "-", '-' },
+ { "{", '{' },
+ { "}", '}' },
+ { ";", ';' },
+ { "(", '(' },
+ { ")", ')' },
+ { "|", '|' },
+ { "&", '&' },
+ { "newline", '\n' },
+ { (char *)NULL, 0}
+};
+
+/* others not listed here:
+ WORD look at yylval.word
+ ASSIGNMENT_WORD look at yylval.word
+ NUMBER look at yylval.number
+ ARITH_CMD look at yylval.word_list
+ ARITH_FOR_EXPRS look at yylval.word_list
+ COND_CMD look at yylval.command
+*/
+
+/* These are used by read_token_word, but appear up here so that shell_getc
+ can use them to decide when to add otherwise blank lines to the history. */
+
+/* The primary delimiter stack. */
+struct dstack dstack = { (char *)NULL, 0, 0 };
+
+/* A temporary delimiter stack to be used when decoding prompt strings.
+ This is needed because command substitutions in prompt strings (e.g., PS2)
+ can screw up the parser's quoting state. */
+static struct dstack temp_dstack = { (char *)NULL, 0, 0 };
+
+/* Macro for accessing the top delimiter on the stack. Returns the
+ delimiter or zero if none. */
+#define current_delimiter(ds) \
+ (ds.delimiter_depth ? ds.delimiters[ds.delimiter_depth - 1] : 0)
+
+#define push_delimiter(ds, character) \
+ do \
+ { \
+ if (ds.delimiter_depth + 2 > ds.delimiter_space) \
+ ds.delimiters = (char *)xrealloc \
+ (ds.delimiters, (ds.delimiter_space += 10) * sizeof (char)); \
+ ds.delimiters[ds.delimiter_depth] = character; \
+ ds.delimiter_depth++; \
+ } \
+ while (0)
+
+#define pop_delimiter(ds) ds.delimiter_depth--
+
+/* Return the next shell input character. This always reads characters
+ from shell_input_line; when that line is exhausted, it is time to
+ read the next line. This is called by read_token when the shell is
+ processing normal command input. */
+
+/* This implements one-character lookahead/lookbehind across physical input
+ lines, to avoid something being lost because it's pushed back with
+ shell_ungetc when we're at the start of a line. */
+static int eol_ungetc_lookahead = 0;
+
+static int
+shell_getc (remove_quoted_newline)
+ int remove_quoted_newline;
+{
+ register int i;
+ int c;
+ unsigned char uc;
+
+ QUIT;
+
+ if (sigwinch_received)
+ {
+ sigwinch_received = 0;
+ get_new_window_size (0, (int *)0, (int *)0);
+ }
+
+ if (eol_ungetc_lookahead)
+ {
+ c = eol_ungetc_lookahead;
+ eol_ungetc_lookahead = 0;
+ return (c);
+ }
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+ /* If shell_input_line[shell_input_line_index] == 0, but there is
+ something on the pushed list of strings, then we don't want to go
+ off and get another line. We let the code down below handle it. */
+
+ if (!shell_input_line || ((!shell_input_line[shell_input_line_index]) &&
+ (pushed_string_list == (STRING_SAVER *)NULL)))
+#else /* !ALIAS && !DPAREN_ARITHMETIC */
+ if (!shell_input_line || !shell_input_line[shell_input_line_index])
+#endif /* !ALIAS && !DPAREN_ARITHMETIC */
+ {
+ line_number++;
+
+ restart_read:
+
+ /* Allow immediate exit if interrupted during input. */
+ QUIT;
+
+ i = 0;
+ shell_input_line_terminator = 0;
+
+ /* If the shell is interatctive, but not currently printing a prompt
+ (interactive_shell && interactive == 0), we don't want to print
+ notifies or cleanup the jobs -- we want to defer it until we do
+ print the next prompt. */
+ if (interactive_shell == 0 || SHOULD_PROMPT())
+ {
+#if defined (JOB_CONTROL)
+ /* This can cause a problem when reading a command as the result
+ of a trap, when the trap is called from flush_child. This call
+ had better not cause jobs to disappear from the job table in
+ that case, or we will have big trouble. */
+ notify_and_cleanup ();
+#else /* !JOB_CONTROL */
+ cleanup_dead_jobs ();
+#endif /* !JOB_CONTROL */
+ }
+
+#if defined (READLINE)
+ if (no_line_editing && SHOULD_PROMPT())
+#else
+ if (SHOULD_PROMPT())
+#endif
+ print_prompt ();
+
+ if (bash_input.type == st_stream)
+ clearerr (stdin);
+
+ while (1)
+ {
+ c = yy_getc ();
+
+ /* Allow immediate exit if interrupted during input. */
+ QUIT;
+
+ if (c == '\0')
+ {
+#if 0
+ internal_warning ("shell_getc: ignored null byte in input");
+#endif
+ continue;
+ }
+
+ RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256);
+
+ if (c == EOF)
+ {
+ if (bash_input.type == st_stream)
+ clearerr (stdin);
+
+ if (i == 0)
+ shell_input_line_terminator = EOF;
+
+ shell_input_line[i] = '\0';
+ break;
+ }
+
+ shell_input_line[i++] = c;
+
+ if (c == '\n')
+ {
+ shell_input_line[--i] = '\0';
+ current_command_line_count++;
+ break;
+ }
+ }
+
+ shell_input_line_index = 0;
+ shell_input_line_len = i; /* == strlen (shell_input_line) */
+
+ set_line_mbstate ();
+
+#if defined (HISTORY)
+ if (remember_on_history && shell_input_line && shell_input_line[0])
+ {
+ char *expansions;
+# if defined (BANG_HISTORY)
+ int old_hist;
+
+ /* If the current delimiter is a single quote, we should not be
+ performing history expansion, even if we're on a different
+ line from the original single quote. */
+ old_hist = history_expansion_inhibited;
+ if (current_delimiter (dstack) == '\'')
+ history_expansion_inhibited = 1;
+# endif
+ expansions = pre_process_line (shell_input_line, 1, 1);
+# if defined (BANG_HISTORY)
+ history_expansion_inhibited = old_hist;
+# endif
+ if (expansions != shell_input_line)
+ {
+ free (shell_input_line);
+ shell_input_line = expansions;
+ shell_input_line_len = shell_input_line ?
+ strlen (shell_input_line) : 0;
+ if (!shell_input_line_len)
+ current_command_line_count--;
+
+ /* We have to force the xrealloc below because we don't know
+ the true allocated size of shell_input_line anymore. */
+ shell_input_line_size = shell_input_line_len;
+
+ set_line_mbstate ();
+ }
+ }
+ /* Try to do something intelligent with blank lines encountered while
+ entering multi-line commands. XXX - this is grotesque */
+ else if (remember_on_history && shell_input_line &&
+ shell_input_line[0] == '\0' &&
+ current_command_line_count > 1)
+ {
+ if (current_delimiter (dstack))
+ /* We know shell_input_line[0] == 0 and we're reading some sort of
+ quoted string. This means we've got a line consisting of only
+ a newline in a quoted string. We want to make sure this line
+ gets added to the history. */
+ maybe_add_history (shell_input_line);
+ else
+ {
+ char *hdcs;
+ hdcs = history_delimiting_chars ();
+ if (hdcs && hdcs[0] == ';')
+ maybe_add_history (shell_input_line);
+ }
+ }
+
+#endif /* HISTORY */
+
+ if (shell_input_line)
+ {
+ /* Lines that signify the end of the shell's input should not be
+ echoed. */
+ if (echo_input_at_read && (shell_input_line[0] ||
+ shell_input_line_terminator != EOF))
+ fprintf (stderr, "%s\n", shell_input_line);
+ }
+ else
+ {
+ shell_input_line_size = 0;
+ prompt_string_pointer = &current_prompt_string;
+ if (SHOULD_PROMPT ())
+ prompt_again ();
+ goto restart_read;
+ }
+
+ /* Add the newline to the end of this string, iff the string does
+ not already end in an EOF character. */
+ if (shell_input_line_terminator != EOF)
+ {
+ if (shell_input_line_len + 3 > shell_input_line_size)
+ shell_input_line = (char *)xrealloc (shell_input_line,
+ 1 + (shell_input_line_size += 2));
+
+ shell_input_line[shell_input_line_len] = '\n';
+ shell_input_line[shell_input_line_len + 1] = '\0';
+
+ set_line_mbstate ();
+ }
+ }
+
+ uc = shell_input_line[shell_input_line_index];
+
+ if (uc)
+ shell_input_line_index++;
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+ /* If UC is NULL, we have reached the end of the current input string. If
+ pushed_string_list is non-empty, it's time to pop to the previous string
+ because we have fully consumed the result of the last alias expansion.
+ Do it transparently; just return the next character of the string popped
+ to. */
+ if (!uc && (pushed_string_list != (STRING_SAVER *)NULL))
+ {
+ pop_string ();
+ uc = shell_input_line[shell_input_line_index];
+ if (uc)
+ shell_input_line_index++;
+ }
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+ if MBTEST(uc == '\\' && remove_quoted_newline && shell_input_line[shell_input_line_index] == '\n')
+ {
+ if (SHOULD_PROMPT ())
+ prompt_again ();
+ line_number++;
+ goto restart_read;
+ }
+
+ if (!uc && shell_input_line_terminator == EOF)
+ return ((shell_input_line_index != 0) ? '\n' : EOF);
+
+ return (uc);
+}
+
+/* Put C back into the input for the shell. This might need changes for
+ HANDLE_MULTIBYTE around EOLs. Since we (currently) never push back a
+ character different than we read, shell_input_line_property doesn't need
+ to change when manipulating shell_input_line. The define for
+ last_shell_getc_is_singlebyte should take care of it, though. */
+static void
+shell_ungetc (c)
+ int c;
+{
+ if (shell_input_line && shell_input_line_index)
+ shell_input_line[--shell_input_line_index] = c;
+ else
+ eol_ungetc_lookahead = c;
+}
+
+#ifdef INCLUDE_UNUSED
+/* Back the input pointer up by one, effectively `ungetting' a character. */
+static void
+shell_ungetchar ()
+{
+ if (shell_input_line && shell_input_line_index)
+ shell_input_line_index--;
+}
+#endif
+
+/* Discard input until CHARACTER is seen, then push that character back
+ onto the input stream. */
+static void
+discard_until (character)
+ int character;
+{
+ int c;
+
+ while ((c = shell_getc (0)) != EOF && c != character)
+ ;
+
+ if (c != EOF)
+ shell_ungetc (c);
+}
+
+void
+execute_variable_command (command, vname)
+ char *command, *vname;
+{
+ char *last_lastarg;
+ sh_parser_state_t ps;
+
+ save_parser_state (&ps);
+ last_lastarg = get_string_value ("_");
+ if (last_lastarg)
+ last_lastarg = savestring (last_lastarg);
+
+ parse_and_execute (savestring (command), vname, SEVAL_NONINT|SEVAL_NOHIST);
+
+ restore_parser_state (&ps);
+ bind_variable ("_", last_lastarg, 0);
+ FREE (last_lastarg);
+
+ if (token_to_read == '\n') /* reset_parser was called */
+ token_to_read = 0;
+}
+
+/* Place to remember the token. We try to keep the buffer
+ at a reasonable size, but it can grow. */
+static char *token = (char *)NULL;
+
+/* Current size of the token buffer. */
+static int token_buffer_size;
+
+/* Command to read_token () explaining what we want it to do. */
+#define READ 0
+#define RESET 1
+#define prompt_is_ps1 \
+ (!prompt_string_pointer || prompt_string_pointer == &ps1_prompt)
+
+/* Function for yyparse to call. yylex keeps track of
+ the last two tokens read, and calls read_token. */
+static int
+yylex ()
+{
+ if (interactive && (current_token == 0 || current_token == '\n'))
+ {
+ /* Before we print a prompt, we might have to check mailboxes.
+ We do this only if it is time to do so. Notice that only here
+ is the mail alarm reset; nothing takes place in check_mail ()
+ except the checking of mail. Please don't change this. */
+ if (prompt_is_ps1 && time_to_check_mail ())
+ {
+ check_mail ();
+ reset_mail_timer ();
+ }
+
+ /* Avoid printing a prompt if we're not going to read anything, e.g.
+ after resetting the parser with read_token (RESET). */
+ if (token_to_read == 0 && SHOULD_PROMPT ())
+ prompt_again ();
+ }
+
+ two_tokens_ago = token_before_that;
+ token_before_that = last_read_token;
+ last_read_token = current_token;
+ current_token = read_token (READ);
+
+ if ((parser_state & PST_EOFTOKEN) && current_token == shell_eof_token)
+ {
+ current_token = yacc_EOF;
+ if (bash_input.type == st_string)
+ rewind_input_string ();
+ }
+ parser_state &= ~PST_EOFTOKEN;
+
+ return (current_token);
+}
+
+/* When non-zero, we have read the required tokens
+ which allow ESAC to be the next one read. */
+static int esacs_needed_count;
+
+void
+gather_here_documents ()
+{
+ int r = 0;
+ while (need_here_doc)
+ {
+ make_here_document (redir_stack[r++]);
+ need_here_doc--;
+ }
+}
+
+/* When non-zero, an open-brace used to create a group is awaiting a close
+ brace partner. */
+static int open_brace_count;
+
+#define command_token_position(token) \
+ (((token) == ASSIGNMENT_WORD) || \
+ ((token) != SEMI_SEMI && (token) != SEMI_AND && (token) != SEMI_SEMI_AND && reserved_word_acceptable(token)))
+
+#define assignment_acceptable(token) \
+ (command_token_position(token) && ((parser_state & PST_CASEPAT) == 0))
+
+/* Check to see if TOKEN is a reserved word and return the token
+ value if it is. */
+#define CHECK_FOR_RESERVED_WORD(tok) \
+ do { \
+ if (!dollar_present && !quoted && \
+ reserved_word_acceptable (last_read_token)) \
+ { \
+ int i; \
+ for (i = 0; word_token_alist[i].word != (char *)NULL; i++) \
+ if (STREQ (tok, word_token_alist[i].word)) \
+ { \
+ if ((parser_state & PST_CASEPAT) && (word_token_alist[i].token != ESAC)) \
+ break; \
+ if (word_token_alist[i].token == TIME && time_command_acceptable () == 0) \
+ break; \
+ if (word_token_alist[i].token == ESAC) \
+ parser_state &= ~(PST_CASEPAT|PST_CASESTMT); \
+ else if (word_token_alist[i].token == CASE) \
+ parser_state |= PST_CASESTMT; \
+ else if (word_token_alist[i].token == COND_END) \
+ parser_state &= ~(PST_CONDCMD|PST_CONDEXPR); \
+ else if (word_token_alist[i].token == COND_START) \
+ parser_state |= PST_CONDCMD; \
+ else if (word_token_alist[i].token == '{') \
+ open_brace_count++; \
+ else if (word_token_alist[i].token == '}' && open_brace_count) \
+ open_brace_count--; \
+ return (word_token_alist[i].token); \
+ } \
+ } \
+ } while (0)
+
+#if defined (ALIAS)
+
+ /* OK, we have a token. Let's try to alias expand it, if (and only if)
+ it's eligible.
+
+ It is eligible for expansion if EXPAND_ALIASES is set, and
+ the token is unquoted and the last token read was a command
+ separator (or expand_next_token is set), and we are currently
+ processing an alias (pushed_string_list is non-empty) and this
+ token is not the same as the current or any previously
+ processed alias.
+
+ Special cases that disqualify:
+ In a pattern list in a case statement (parser_state & PST_CASEPAT). */
+
+static char *
+mk_alexpansion (s)
+ char *s;
+{
+ int l;
+ char *r;
+
+ l = strlen (s);
+ r = xmalloc (l + 2);
+ strcpy (r, s);
+ if (r[l -1] != ' ')
+ r[l++] = ' ';
+ r[l] = '\0';
+ return r;
+}
+
+static int
+alias_expand_token (tokstr)
+ char *tokstr;
+{
+ char *expanded;
+ alias_t *ap;
+
+ if (((parser_state & PST_ALEXPNEXT) || command_token_position (last_read_token)) &&
+ (parser_state & PST_CASEPAT) == 0)
+ {
+ ap = find_alias (tokstr);
+
+ /* Currently expanding this token. */
+ if (ap && (ap->flags & AL_BEINGEXPANDED))
+ return (NO_EXPANSION);
+
+ /* mk_alexpansion puts an extra space on the end of the alias expansion,
+ so the lookahead by the parser works right. If this gets changed,
+ make sure the code in shell_getc that deals with reaching the end of
+ an expanded alias is changed with it. */
+ expanded = ap ? mk_alexpansion (ap->value) : (char *)NULL;
+
+ if (expanded)
+ {
+ push_string (expanded, ap->flags & AL_EXPANDNEXT, ap);
+ return (RE_READ_TOKEN);
+ }
+ else
+ /* This is an eligible token that does not have an expansion. */
+ return (NO_EXPANSION);
+ }
+ return (NO_EXPANSION);
+}
+#endif /* ALIAS */
+
+static int
+time_command_acceptable ()
+{
+#if defined (COMMAND_TIMING)
+ switch (last_read_token)
+ {
+ case 0:
+ case ';':
+ case '\n':
+ case AND_AND:
+ case OR_OR:
+ case '&':
+ case DO:
+ case THEN:
+ case ELSE:
+ case '{': /* } */
+ case '(': /* ) */
+ return 1;
+ default:
+ return 0;
+ }
+#else
+ return 0;
+#endif /* COMMAND_TIMING */
+}
+
+/* Handle special cases of token recognition:
+ IN is recognized if the last token was WORD and the token
+ before that was FOR or CASE or SELECT.
+
+ DO is recognized if the last token was WORD and the token
+ before that was FOR or SELECT.
+
+ ESAC is recognized if the last token caused `esacs_needed_count'
+ to be set
+
+ `{' is recognized if the last token as WORD and the token
+ before that was FUNCTION, or if we just parsed an arithmetic
+ `for' command.
+
+ `}' is recognized if there is an unclosed `{' present.
+
+ `-p' is returned as TIMEOPT if the last read token was TIME.
+
+ ']]' is returned as COND_END if the parser is currently parsing
+ a conditional expression ((parser_state & PST_CONDEXPR) != 0)
+
+ `time' is returned as TIME if and only if it is immediately
+ preceded by one of `;', `\n', `||', `&&', or `&'.
+*/
+
+static int
+special_case_tokens (tokstr)
+ char *tokstr;
+{
+ if ((last_read_token == WORD) &&
+#if defined (SELECT_COMMAND)
+ ((token_before_that == FOR) || (token_before_that == CASE) || (token_before_that == SELECT)) &&
+#else
+ ((token_before_that == FOR) || (token_before_that == CASE)) &&
+#endif
+ (tokstr[0] == 'i' && tokstr[1] == 'n' && tokstr[2] == 0))
+ {
+ if (token_before_that == CASE)
+ {
+ parser_state |= PST_CASEPAT;
+ esacs_needed_count++;
+ }
+ return (IN);
+ }
+
+ if (last_read_token == WORD &&
+#if defined (SELECT_COMMAND)
+ (token_before_that == FOR || token_before_that == SELECT) &&
+#else
+ (token_before_that == FOR) &&
+#endif
+ (tokstr[0] == 'd' && tokstr[1] == 'o' && tokstr[2] == '\0'))
+ return (DO);
+
+ /* Ditto for ESAC in the CASE case.
+ Specifically, this handles "case word in esac", which is a legal
+ construct, certainly because someone will pass an empty arg to the
+ case construct, and we don't want it to barf. Of course, we should
+ insist that the case construct has at least one pattern in it, but
+ the designers disagree. */
+ if (esacs_needed_count)
+ {
+ esacs_needed_count--;
+ if (STREQ (tokstr, "esac"))
+ {
+ parser_state &= ~PST_CASEPAT;
+ return (ESAC);
+ }
+ }
+
+ /* The start of a shell function definition. */
+ if (parser_state & PST_ALLOWOPNBRC)
+ {
+ parser_state &= ~PST_ALLOWOPNBRC;
+ if (tokstr[0] == '{' && tokstr[1] == '\0') /* } */
+ {
+ open_brace_count++;
+ function_bstart = line_number;
+ return ('{'); /* } */
+ }
+ }
+
+ /* We allow a `do' after a for ((...)) without an intervening
+ list_terminator */
+ if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == 'd' && tokstr[1] == 'o' && !tokstr[2])
+ return (DO);
+ if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == '{' && tokstr[1] == '\0') /* } */
+ {
+ open_brace_count++;
+ return ('{'); /* } */
+ }
+
+ if (open_brace_count && reserved_word_acceptable (last_read_token) && tokstr[0] == '}' && !tokstr[1])
+ {
+ open_brace_count--; /* { */
+ return ('}');
+ }
+
+#if defined (COMMAND_TIMING)
+ /* Handle -p after `time'. */
+ if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2])
+ return (TIMEOPT);
+#endif
+
+#if 0
+#if defined (COMMAND_TIMING)
+ if (STREQ (token, "time") && ((parser_state & PST_CASEPAT) == 0) && time_command_acceptable ())
+ return (TIME);
+#endif /* COMMAND_TIMING */
+#endif
+
+#if defined (COND_COMMAND) /* [[ */
+ if ((parser_state & PST_CONDEXPR) && tokstr[0] == ']' && tokstr[1] == ']' && tokstr[2] == '\0')
+ return (COND_END);
+#endif
+
+ return (-1);
+}
+
+/* Called from shell.c when Control-C is typed at top level. Or
+ by the error rule at top level. */
+void
+reset_parser ()
+{
+ dstack.delimiter_depth = 0; /* No delimiters found so far. */
+ open_brace_count = 0;
+
+ parser_state = 0;
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+ if (pushed_string_list)
+ free_string_list ();
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+ if (shell_input_line)
+ {
+ free (shell_input_line);
+ shell_input_line = (char *)NULL;
+ shell_input_line_size = shell_input_line_index = 0;
+ }
+
+ FREE (word_desc_to_read);
+ word_desc_to_read = (WORD_DESC *)NULL;
+
+ last_read_token = '\n';
+ token_to_read = '\n';
+}
+
+/* Read the next token. Command can be READ (normal operation) or
+ RESET (to normalize state). */
+static int
+read_token (command)
+ int command;
+{
+ int character; /* Current character. */
+ int peek_char; /* Temporary look-ahead character. */
+ int result; /* The thing to return. */
+
+ if (command == RESET)
+ {
+ reset_parser ();
+ return ('\n');
+ }
+
+ if (token_to_read)
+ {
+ result = token_to_read;
+ if (token_to_read == WORD || token_to_read == ASSIGNMENT_WORD)
+ {
+ yylval.word = word_desc_to_read;
+ word_desc_to_read = (WORD_DESC *)NULL;
+ }
+ token_to_read = 0;
+ return (result);
+ }
+
+#if defined (COND_COMMAND)
+ if ((parser_state & (PST_CONDCMD|PST_CONDEXPR)) == PST_CONDCMD)
+ {
+ cond_lineno = line_number;
+ parser_state |= PST_CONDEXPR;
+ yylval.command = parse_cond_command ();
+ if (cond_token != COND_END)
+ {
+ cond_error ();
+ return (-1);
+ }
+ token_to_read = COND_END;
+ parser_state &= ~(PST_CONDEXPR|PST_CONDCMD);
+ return (COND_CMD);
+ }
+#endif
+
+#if defined (ALIAS)
+ /* This is a place to jump back to once we have successfully expanded a
+ token with an alias and pushed the string with push_string () */
+ re_read_token:
+#endif /* ALIAS */
+
+ /* Read a single word from input. Start by skipping blanks. */
+ while ((character = shell_getc (1)) != EOF && shellblank (character))
+ ;
+
+ if (character == EOF)
+ {
+ EOF_Reached = 1;
+ return (yacc_EOF);
+ }
+
+ if MBTEST(character == '#' && (!interactive || interactive_comments))
+ {
+ /* A comment. Discard until EOL or EOF, and then return a newline. */
+ discard_until ('\n');
+ shell_getc (0);
+ character = '\n'; /* this will take the next if statement and return. */
+ }
+
+ if (character == '\n')
+ {
+ /* If we're about to return an unquoted newline, we can go and collect
+ the text of any pending here document. */
+ if (need_here_doc)
+ gather_here_documents ();
+
+#if defined (ALIAS)
+ parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+
+ parser_state &= ~PST_ASSIGNOK;
+
+ return (character);
+ }
+
+ if (parser_state & PST_REGEXP)
+ goto tokword;
+
+ /* Shell meta-characters. */
+ if MBTEST(shellmeta (character) && ((parser_state & PST_DBLPAREN) == 0))
+ {
+#if defined (ALIAS)
+ /* Turn off alias tokenization iff this character sequence would
+ not leave us ready to read a command. */
+ if (character == '<' || character == '>')
+ parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+
+ parser_state &= ~PST_ASSIGNOK;
+
+ peek_char = shell_getc (1);
+ if (character == peek_char)
+ {
+ switch (character)
+ {
+ case '<':
+ /* If '<' then we could be at "<<" or at "<<-". We have to
+ look ahead one more character. */
+ peek_char = shell_getc (1);
+ if MBTEST(peek_char == '-')
+ return (LESS_LESS_MINUS);
+ else if MBTEST(peek_char == '<')
+ return (LESS_LESS_LESS);
+ else
+ {
+ shell_ungetc (peek_char);
+ return (LESS_LESS);
+ }
+
+ case '>':
+ return (GREATER_GREATER);
+
+ case ';':
+ parser_state |= PST_CASEPAT;
+#if defined (ALIAS)
+ parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+ peek_char = shell_getc (1);
+ if MBTEST(peek_char == '&')
+ return (SEMI_SEMI_AND);
+ else
+ {
+ shell_ungetc (peek_char);
+ return (SEMI_SEMI);
+ }
+
+ case '&':
+ return (AND_AND);
+
+ case '|':
+ return (OR_OR);
+
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+ case '(': /* ) */
+ result = parse_dparen (character);
+ if (result == -2)
+ break;
+ else
+ return result;
+#endif
+ }
+ }
+ else if MBTEST(character == '<' && peek_char == '&')
+ return (LESS_AND);
+ else if MBTEST(character == '>' && peek_char == '&')
+ return (GREATER_AND);
+ else if MBTEST(character == '<' && peek_char == '>')
+ return (LESS_GREATER);
+ else if MBTEST(character == '>' && peek_char == '|')
+ return (GREATER_BAR);
+ else if MBTEST(character == '&' && peek_char == '>')
+ {
+ peek_char = shell_getc (1);
+ if MBTEST(peek_char == '>')
+ return (AND_GREATER_GREATER);
+ else
+ {
+ shell_ungetc (peek_char);
+ return (AND_GREATER);
+ }
+ }
+ else if MBTEST(character == '|' && peek_char == '&')
+ return (BAR_AND);
+ else if MBTEST(character == ';' && peek_char == '&')
+ {
+ parser_state |= PST_CASEPAT;
+#if defined (ALIAS)
+ parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+ return (SEMI_AND);
+ }
+
+ shell_ungetc (peek_char);
+
+ /* If we look like we are reading the start of a function
+ definition, then let the reader know about it so that
+ we will do the right thing with `{'. */
+ if MBTEST(character == ')' && last_read_token == '(' && token_before_that == WORD)
+ {
+ parser_state |= PST_ALLOWOPNBRC;
+#if defined (ALIAS)
+ parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+ function_dstart = line_number;
+ }
+
+ /* case pattern lists may be preceded by an optional left paren. If
+ we're not trying to parse a case pattern list, the left paren
+ indicates a subshell. */
+ if MBTEST(character == '(' && (parser_state & PST_CASEPAT) == 0) /* ) */
+ parser_state |= PST_SUBSHELL;
+ /*(*/
+ else if MBTEST((parser_state & PST_CASEPAT) && character == ')')
+ parser_state &= ~PST_CASEPAT;
+ /*(*/
+ else if MBTEST((parser_state & PST_SUBSHELL) && character == ')')
+ parser_state &= ~PST_SUBSHELL;
+
+#if defined (PROCESS_SUBSTITUTION)
+ /* Check for the constructs which introduce process substitution.
+ Shells running in `posix mode' don't do process substitution. */
+ if MBTEST(posixly_correct || ((character != '>' && character != '<') || peek_char != '(')) /*)*/
+#endif /* PROCESS_SUBSTITUTION */
+ return (character);
+ }
+
+ /* Hack <&- (close stdin) case. Also <&N- (dup and close). */
+ if MBTEST(character == '-' && (last_read_token == LESS_AND || last_read_token == GREATER_AND))
+ return (character);
+
+tokword:
+ /* Okay, if we got this far, we have to read a word. Read one,
+ and then check it against the known ones. */
+ result = read_token_word (character);
+#if defined (ALIAS)
+ if (result == RE_READ_TOKEN)
+ goto re_read_token;
+#endif
+ return result;
+}
+
+/*
+ * Match a $(...) or other grouping construct. This has to handle embedded
+ * quoted strings ('', ``, "") and nested constructs. It also must handle
+ * reprompting the user, if necessary, after reading a newline, and returning
+ * correct error values if it reads EOF.
+ */
+#define P_FIRSTCLOSE 0x01
+#define P_ALLOWESC 0x02
+#define P_DQUOTE 0x04
+#define P_COMMAND 0x08 /* parsing a command, so look for comments */
+#define P_BACKQUOTE 0x10 /* parsing a backquoted command substitution */
+
+/* Lexical state while parsing a grouping construct or $(...). */
+#define LEX_WASDOL 0x001
+#define LEX_CKCOMMENT 0x002
+#define LEX_INCOMMENT 0x004
+#define LEX_PASSNEXT 0x008
+#define LEX_RESWDOK 0x010
+#define LEX_CKCASE 0x020
+#define LEX_INCASE 0x040
+#define LEX_INHEREDOC 0x080
+#define LEX_HEREDELIM 0x100 /* reading here-doc delimiter */
+#define LEX_STRIPDOC 0x200 /* <<- strip tabs from here doc delim */
+
+#define COMSUB_META(ch) ((ch) == ';' || (ch) == '&' || (ch) == '|')
+
+#define CHECK_NESTRET_ERROR() \
+ do { \
+ if (nestret == &matched_pair_error) \
+ { \
+ free (ret); \
+ return &matched_pair_error; \
+ } \
+ } while (0)
+
+#define APPEND_NESTRET() \
+ do { \
+ if (nestlen) \
+ { \
+ RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64); \
+ strcpy (ret + retind, nestret); \
+ retind += nestlen; \
+ } \
+ } while (0)
+
+static char matched_pair_error;
+
+static char *
+parse_matched_pair (qc, open, close, lenp, flags)
+ int qc; /* `"' if this construct is within double quotes */
+ int open, close;
+ int *lenp, flags;
+{
+ int count, ch, tflags;
+ int nestlen, ttranslen, start_lineno;
+ char *ret, *nestret, *ttrans;
+ int retind, retsize, rflags;
+
+/* itrace("parse_matched_pair: open = %c close = %c", open, close); */
+ count = 1;
+ tflags = 0;
+
+ if ((flags & P_COMMAND) && qc != '`' && qc != '\'' && qc != '"' && (flags & P_DQUOTE) == 0)
+ tflags |= LEX_CKCOMMENT;
+
+ /* RFLAGS is the set of flags we want to pass to recursive calls. */
+ rflags = (qc == '"') ? P_DQUOTE : (flags & P_DQUOTE);
+
+ ret = (char *)xmalloc (retsize = 64);
+ retind = 0;
+
+ start_lineno = line_number;
+ while (count)
+ {
+ ch = shell_getc (qc != '\'' && (tflags & LEX_PASSNEXT) == 0);
+
+ if (ch == EOF)
+ {
+ free (ret);
+ parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close);
+ EOF_Reached = 1; /* XXX */
+ return (&matched_pair_error);
+ }
+
+ /* Possible reprompting. */
+ if (ch == '\n' && SHOULD_PROMPT ())
+ prompt_again ();
+
+ /* Don't bother counting parens or doing anything else if in a comment
+ or part of a case statement */
+ if (tflags & LEX_INCOMMENT)
+ {
+ /* Add this character. */
+ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+ ret[retind++] = ch;
+
+ if (ch == '\n')
+ tflags &= ~LEX_INCOMMENT;
+
+ continue;
+ }
+
+ /* Not exactly right yet, should handle shell metacharacters, too. If
+ any changes are made to this test, make analogous changes to subst.c:
+ extract_delimited_string(). */
+ else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || shellblank (ret[retind - 1])))
+ tflags |= LEX_INCOMMENT;
+
+ if (tflags & LEX_PASSNEXT) /* last char was backslash */
+ {
+ tflags &= ~LEX_PASSNEXT;
+ if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
+ {
+ if (retind > 0)
+ retind--; /* swallow previously-added backslash */
+ continue;
+ }
+
+ RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+ if MBTEST(ch == CTLESC || ch == CTLNUL)
+ ret[retind++] = CTLESC;
+ ret[retind++] = ch;
+ continue;
+ }
+ else if MBTEST(ch == CTLESC || ch == CTLNUL) /* special shell escapes */
+ {
+ RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+ ret[retind++] = CTLESC;
+ ret[retind++] = ch;
+ continue;
+ }
+ else if MBTEST(ch == close) /* ending delimiter */
+ count--;
+ /* handle nested ${...} specially. */
+ else if MBTEST(open != close && (tflags & LEX_WASDOL) && open == '{' && ch == open) /* } */
+ count++;
+ else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && ch == open) /* nested begin */
+ count++;
+
+ /* Add this character. */
+ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+ ret[retind++] = ch;
+
+ /* If we just read the ending character, don't bother continuing. */
+ if (count == 0)
+ break;
+
+ if (open == '\'') /* '' inside grouping construct */
+ {
+ if MBTEST((flags & P_ALLOWESC) && ch == '\\')
+ tflags |= LEX_PASSNEXT;
+ continue;
+ }
+
+ if MBTEST(ch == '\\') /* backslashes */
+ tflags |= LEX_PASSNEXT;
+
+ /* Could also check open == '`' if we want to parse grouping constructs
+ inside old-style command substitution. */
+ if (open != close) /* a grouping construct */
+ {
+ if MBTEST(shellquote (ch))
+ {
+ /* '', ``, or "" inside $(...) or other grouping construct. */
+ push_delimiter (dstack, ch);
+ if MBTEST((tflags & LEX_WASDOL) && ch == '\'') /* $'...' inside group */
+ nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC|rflags);
+ else
+ nestret = parse_matched_pair (ch, ch, ch, &nestlen, rflags);
+ pop_delimiter (dstack);
+ CHECK_NESTRET_ERROR ();
+
+ if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0))
+ {
+ /* Translate $'...' here. */
+ ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen);
+ xfree (nestret);
+
+ if ((rflags & P_DQUOTE) == 0)
+ {
+ nestret = sh_single_quote (ttrans);
+ free (ttrans);
+ nestlen = strlen (nestret);
+ }
+ else
+ {
+ nestret = ttrans;
+ nestlen = ttranslen;
+ }
+ retind -= 2; /* back up before the $' */
+ }
+ else if MBTEST((tflags & LEX_WASDOL) && ch == '"' && (extended_quote || (rflags & P_DQUOTE) == 0))
+ {
+ /* Locale expand $"..." here. */
+ ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen);
+ xfree (nestret);
+
+ nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+ free (ttrans);
+ nestlen = ttranslen + 2;
+ retind -= 2; /* back up before the $" */
+ }
+
+ APPEND_NESTRET ();
+ FREE (nestret);
+ }
+ }
+ /* Parse an old-style command substitution within double quotes as a
+ single word. */
+ /* XXX - sh and ksh93 don't do this - XXX */
+ else if MBTEST(open == '"' && ch == '`')
+ {
+ nestret = parse_matched_pair (0, '`', '`', &nestlen, rflags);
+
+ CHECK_NESTRET_ERROR ();
+ APPEND_NESTRET ();
+
+ FREE (nestret);
+ }
+ else if MBTEST(open != '`' && (tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '[')) /* ) } ] */
+ /* check for $(), $[], or ${} inside quoted string. */
+ {
+ if (open == ch) /* undo previous increment */
+ count--;
+ if (ch == '(') /* ) */
+ nestret = parse_matched_pair (0, '(', ')', &nestlen, rflags & ~P_DQUOTE);
+ else if (ch == '{') /* } */
+ nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags);
+ else if (ch == '[') /* ] */
+ nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags);
+
+ CHECK_NESTRET_ERROR ();
+ APPEND_NESTRET ();
+
+ FREE (nestret);
+ }
+ if MBTEST(ch == '$')
+ tflags |= LEX_WASDOL;
+ else
+ tflags &= ~LEX_WASDOL;
+ }
+
+ ret[retind] = '\0';
+ if (lenp)
+ *lenp = retind;
+ return ret;
+}
+
+/* Parse a $(...) command substitution. This is messier than I'd like, and
+ reproduces a lot more of the token-reading code than I'd like. */
+static char *
+parse_comsub (qc, open, close, lenp, flags)
+ int qc; /* `"' if this construct is within double quotes */
+ int open, close;
+ int *lenp, flags;
+{
+ int count, ch, peekc, tflags, lex_rwlen, lex_firstind;
+ int nestlen, ttranslen, start_lineno;
+ char *ret, *nestret, *ttrans, *heredelim;
+ int retind, retsize, rflags, hdlen;
+
+/*itrace("parse_comsub: qc = `%c' open = %c close = %c", qc, open, close);*/
+ count = 1;
+ tflags = LEX_RESWDOK;
+
+ if ((flags & P_COMMAND) && qc != '\'' && qc != '"' && (flags & P_DQUOTE) == 0)
+ tflags |= LEX_CKCASE;
+ if ((tflags & LEX_CKCASE) && (interactive == 0 || interactive_comments))
+ tflags |= LEX_CKCOMMENT;
+
+ /* RFLAGS is the set of flags we want to pass to recursive calls. */
+ rflags = (flags & P_DQUOTE);
+
+ ret = (char *)xmalloc (retsize = 64);
+ retind = 0;
+
+ start_lineno = line_number;
+ lex_rwlen = 0;
+
+ heredelim = 0;
+ lex_firstind = -1;
+
+ while (count)
+ {
+comsub_readchar:
+ ch = shell_getc (qc != '\'' && (tflags & LEX_PASSNEXT) == 0);
+
+ if (ch == EOF)
+ {
+eof_error:
+ free (ret);
+ FREE (heredelim);
+ parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close);
+ EOF_Reached = 1; /* XXX */
+ return (&matched_pair_error);
+ }
+
+ /* If we hit the end of a line and are reading the contents of a here
+ document, and it's not the same line that the document starts on,
+ check for this line being the here doc delimiter. Otherwise, if
+ we're in a here document, mark the next character as the beginning
+ of a line. */
+ if (ch == '\n')
+ {
+ if ((tflags & LEX_HEREDELIM) && heredelim)
+ {
+ tflags &= ~LEX_HEREDELIM;
+ tflags |= LEX_INHEREDOC;
+ lex_firstind = retind + 1;
+ }
+ else if (tflags & LEX_INHEREDOC)
+ {
+ int tind;
+ tind = lex_firstind;
+ while ((tflags & LEX_STRIPDOC) && ret[tind] == '\t')
+ tind++;
+ if (STREQN (ret + tind, heredelim, hdlen))
+ {
+ tflags &= ~(LEX_STRIPDOC|LEX_INHEREDOC);
+/*itrace("parse_comsub:%d: found here doc end `%s'", line_number, ret + tind);*/
+ lex_firstind = -1;
+ }
+ else
+ lex_firstind = retind + 1;
+ }
+ }
+
+ /* Possible reprompting. */
+ if (ch == '\n' && SHOULD_PROMPT ())
+ prompt_again ();
+
+ /* Don't bother counting parens or doing anything else if in a comment */
+ if (tflags & (LEX_INCOMMENT|LEX_INHEREDOC))
+ {
+ /* Add this character. */
+ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+ ret[retind++] = ch;
+
+ if ((tflags & LEX_INCOMMENT) && ch == '\n')
+ tflags &= ~LEX_INCOMMENT;
+
+ continue;
+ }
+
+ /* Skip whitespace */
+ if MBTEST(shellblank (ch) && lex_rwlen == 0)
+ {
+ /* Add this character. */
+ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+ ret[retind++] = ch;
+ continue;
+ }
+
+ /* Either we are looking for the start of the here-doc delimiter
+ (lex_firstind == -1) or we are reading one (lex_firstind >= 0).
+ If this character is a shell break character and we are reading
+ the delimiter, save it and note that we are now reading a here
+ document. If we've found the start of the delimiter, note it by
+ setting lex_firstind. Backslashes can quote shell metacharacters
+ in here-doc delimiters. */
+ if (tflags & LEX_HEREDELIM)
+ {
+ if (lex_firstind == -1 && shellbreak (ch) == 0)
+ lex_firstind = retind;
+ else if (lex_firstind >= 0 && (tflags & LEX_PASSNEXT) == 0 && shellbreak (ch))
+ {
+ nestret = substring (ret, lex_firstind, retind);
+ heredelim = string_quote_removal (nestret, 0);
+ free (nestret);
+ hdlen = STRLEN(heredelim);
+/*itrace("parse_comsub:%d: found here doc delimiter `%s' (%d)", line_number, heredelim, hdlen);*/
+ if (ch == '\n')
+ {
+ tflags |= LEX_INHEREDOC;
+ tflags &= ~LEX_HEREDELIM;
+ lex_firstind = retind + 1;
+ }
+ else
+ lex_firstind = -1;
+ }
+ }
+
+ /* Meta-characters that can introduce a reserved word. Not perfect yet. */
+ if MBTEST((tflags & LEX_RESWDOK) == 0 && (tflags & LEX_CKCASE) && (tflags & LEX_INCOMMENT) == 0 && shellmeta(ch))
+ {
+ /* Add this character. */
+ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+ ret[retind++] = ch;
+ peekc = shell_getc (1);
+ if (ch == peekc && (ch == '&' || ch == '|' || ch == ';')) /* two-character tokens */
+ {
+ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+ ret[retind++] = peekc;
+/*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch); */
+ tflags |= LEX_RESWDOK;
+ lex_rwlen = 0;
+ continue;
+ }
+ else if (ch == '\n' || COMSUB_META(ch))
+ {
+ shell_ungetc (peekc);
+ tflags |= LEX_RESWDOK;
+/*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch);*/
+ lex_rwlen = 0;
+ continue;
+ }
+ else if (ch == EOF)
+ goto eof_error;
+ else
+ {
+ /* `unget' the character we just added and fall through */
+ retind--;
+ shell_ungetc (peekc);
+ }
+ }
+
+ /* If we can read a reserved word, try to read one. */
+ if (tflags & LEX_RESWDOK)
+ {
+ if MBTEST(islower (ch))
+ {
+ /* Add this character. */
+ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+ ret[retind++] = ch;
+ lex_rwlen++;
+ continue;
+ }
+ else if MBTEST(lex_rwlen == 4 && shellbreak (ch))
+ {
+ if (STREQN (ret + retind - 4, "case", 4))
+{
+ tflags |= LEX_INCASE;
+/*itrace("parse_comsub:%d: found `case', lex_incase -> 1", line_number);*/
+}
+ else if (STREQN (ret + retind - 4, "esac", 4))
+{
+ tflags &= ~LEX_INCASE;
+/*itrace("parse_comsub:%d: found `esac', lex_incase -> 0", line_number);*/
+}
+ tflags &= ~LEX_RESWDOK;
+ }
+ else if (shellbreak (ch) == 0)
+{
+ tflags &= ~LEX_RESWDOK;
+/*itrace("parse_comsub:%d: found `%c', lex_reswordok -> 0", line_number, ch);*/
+}
+ }
+
+ if MBTEST((tflags & LEX_INCOMMENT) == 0 && (tflags & LEX_CKCASE) && ch == '<')
+ {
+ /* Add this character. */
+ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+ ret[retind++] = ch;
+ peekc = shell_getc (1);
+ if (peekc == EOF)
+ goto eof_error;
+ if (peekc == ch)
+ {
+ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+ ret[retind++] = peekc;
+ peekc = shell_getc (1);
+ if (peekc == EOF)
+ goto eof_error;
+ if (peekc == '-')
+ {
+ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+ ret[retind++] = peekc;
+ tflags |= LEX_STRIPDOC;
+ }
+ else
+ shell_ungetc (peekc);
+ tflags |= LEX_HEREDELIM;
+ lex_firstind = -1;
+ continue;
+ }
+ else
+ ch = peekc; /* fall through and continue XXX - this skips comments if peekc == '#' */
+ }
+ /* Not exactly right yet, should handle shell metacharacters, too. If
+ any changes are made to this test, make analogous changes to subst.c:
+ extract_delimited_string(). */
+ else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || shellblank (ret[retind - 1])))
+ tflags |= LEX_INCOMMENT;
+
+ if (tflags & LEX_PASSNEXT) /* last char was backslash */
+ {
+ tflags &= ~LEX_PASSNEXT;
+ if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
+ {
+ if (retind > 0)
+ retind--; /* swallow previously-added backslash */
+ continue;
+ }
+
+ RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+ if MBTEST(ch == CTLESC || ch == CTLNUL)
+ ret[retind++] = CTLESC;
+ ret[retind++] = ch;
+ continue;
+ }
+ else if MBTEST(ch == CTLESC || ch == CTLNUL) /* special shell escapes */
+ {
+ RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+ ret[retind++] = CTLESC;
+ ret[retind++] = ch;
+ continue;
+ }
+#if 0
+ else if MBTEST((tflags & LEX_INCASE) && ch == close && close == ')')
+ tflags &= ~LEX_INCASE; /* XXX */
+#endif
+ else if MBTEST(ch == close && (tflags & LEX_INCASE) == 0) /* ending delimiter */
+{
+ count--;
+/*itrace("parse_comsub:%d: found close: count = %d", line_number, count);*/
+}
+ else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && (tflags & LEX_INCASE) == 0 && ch == open) /* nested begin */
+ count++;
+
+ /* Add this character. */
+ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+ ret[retind++] = ch;
+
+ /* If we just read the ending character, don't bother continuing. */
+ if (count == 0)
+ break;
+
+ if MBTEST(ch == '\\') /* backslashes */
+ tflags |= LEX_PASSNEXT;
+
+ if MBTEST(shellquote (ch))
+ {
+ /* '', ``, or "" inside $(...). */
+ push_delimiter (dstack, ch);
+ if MBTEST((tflags & LEX_WASDOL) && ch == '\'') /* $'...' inside group */
+ nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC|rflags);
+ else
+ nestret = parse_matched_pair (ch, ch, ch, &nestlen, rflags);
+ pop_delimiter (dstack);
+ CHECK_NESTRET_ERROR ();
+
+ if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0))
+ {
+ /* Translate $'...' here. */
+ ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen);
+ xfree (nestret);
+
+ if ((rflags & P_DQUOTE) == 0)
+ {
+ nestret = sh_single_quote (ttrans);
+ free (ttrans);
+ nestlen = strlen (nestret);
+ }
+ else
+ {
+ nestret = ttrans;
+ nestlen = ttranslen;
+ }
+ retind -= 2; /* back up before the $' */
+ }
+ else if MBTEST((tflags & LEX_WASDOL) && ch == '"' && (extended_quote || (rflags & P_DQUOTE) == 0))
+ {
+ /* Locale expand $"..." here. */
+ ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen);
+ xfree (nestret);
+
+ nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+ free (ttrans);
+ nestlen = ttranslen + 2;
+ retind -= 2; /* back up before the $" */
+ }
+
+ APPEND_NESTRET ();
+ FREE (nestret);
+ }
+ else if MBTEST((tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '[')) /* ) } ] */
+ /* check for $(), $[], or ${} inside command substitution. */
+ {
+ if ((tflags & LEX_INCASE) == 0 && open == ch) /* undo previous increment */
+ count--;
+ if (ch == '(') /* ) */
+ nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
+ else if (ch == '{') /* } */
+ nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags);
+ else if (ch == '[') /* ] */
+ nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags);
+
+ CHECK_NESTRET_ERROR ();
+ APPEND_NESTRET ();
+
+ FREE (nestret);
+ }
+ if MBTEST(ch == '$')
+ tflags |= LEX_WASDOL;
+ else
+ tflags &= ~LEX_WASDOL;
+ }
+
+ FREE (heredelim);
+ ret[retind] = '\0';
+ if (lenp)
+ *lenp = retind;
+/*itrace("parse_comsub:%d: returning `%s'", line_number, ret);*/
+ return ret;
+}
+
+/* XXX - this needs to handle functionality like subst.c:no_longjmp_on_fatal_error;
+ maybe extract_command_subst should handle it. */
+char *
+xparse_dolparen (base, string, indp, flags)
+ char *base;
+ char *string;
+ int *indp;
+ int flags;
+{
+ sh_parser_state_t ps;
+ int orig_ind, nc, sflags;
+ char *ret, *s, *ep, *ostring;
+
+ /*yydebug = 1;*/
+ orig_ind = *indp;
+ ostring = string;
+
+ sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE;
+ if (flags & 0x010) /* EX_NOLONGJMP */
+ sflags |= SEVAL_NOLONGJMP;
+ save_parser_state (&ps);
+
+ /*(*/
+ parser_state |= PST_CMDSUBST|PST_EOFTOKEN; /* allow instant ')' */ /*(*/
+ shell_eof_token = ')';
+ parse_string (string, "command substitution", sflags, &ep);
+
+ restore_parser_state (&ps);
+ reset_parser ();
+ if (interactive)
+ token_to_read = 0;
+
+ /* Need to find how many characters parse_and_execute consumed, update
+ *indp, if flags != 0, copy the portion of the string parsed into RET
+ and return it. If flags & 1 (EX_NOALLOC) we can return NULL. */
+
+ /*(*/
+ if (ep[-1] != ')')
+ {
+#if DEBUG
+ itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep);
+#endif
+ while (ep > ostring && ep[-1] == '\n') ep--;
+ }
+
+ nc = ep - ostring;
+ *indp = ep - base - 1;
+
+ /*(*/
+#if DEBUG
+ if (base[*indp] != ')')
+ itrace("xparse_dolparen:%d: base[%d] != RPAREN (%d), base = `%s'", line_number, *indp, base[*indp], base);
+#endif
+
+ if (flags & 1)
+ return (char *)NULL;
+
+ if (nc == 0)
+ {
+ ret = xmalloc (1);
+ ret[0] = '\0';
+ }
+ else
+ ret = substring (ostring, 0, nc - 1);
+
+ return ret;
+}
+
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+/* Parse a double-paren construct. It can be either an arithmetic
+ command, an arithmetic `for' command, or a nested subshell. Returns
+ the parsed token, -1 on error, or -2 if we didn't do anything and
+ should just go on. */
+static int
+parse_dparen (c)
+ int c;
+{
+ int cmdtyp, sline;
+ char *wval;
+ WORD_DESC *wd;
+
+#if defined (ARITH_FOR_COMMAND)
+ if (last_read_token == FOR)
+ {
+ arith_for_lineno = line_number;
+ cmdtyp = parse_arith_cmd (&wval, 0);
+ if (cmdtyp == 1)
+ {
+ wd = alloc_word_desc ();
+ wd->word = wval;
+ yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL);
+ return (ARITH_FOR_EXPRS);
+ }
+ else
+ return -1; /* ERROR */
+ }
+#endif
+
+#if defined (DPAREN_ARITHMETIC)
+ if (reserved_word_acceptable (last_read_token))
+ {
+ sline = line_number;
+
+ cmdtyp = parse_arith_cmd (&wval, 0);
+ if (cmdtyp == 1) /* arithmetic command */
+ {
+ wd = alloc_word_desc ();
+ wd->word = wval;
+ wd->flags = W_QUOTED|W_NOSPLIT|W_NOGLOB|W_DQUOTE;
+ yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL);
+ return (ARITH_CMD);
+ }
+ else if (cmdtyp == 0) /* nested subshell */
+ {
+ push_string (wval, 0, (alias_t *)NULL);
+ if ((parser_state & PST_CASEPAT) == 0)
+ parser_state |= PST_SUBSHELL;
+ return (c);
+ }
+ else /* ERROR */
+ return -1;
+ }
+#endif
+
+ return -2; /* XXX */
+}
+
+/* We've seen a `(('. Look for the matching `))'. If we get it, return 1.
+ If not, assume it's a nested subshell for backwards compatibility and
+ return 0. In any case, put the characters we've consumed into a locally-
+ allocated buffer and make *ep point to that buffer. Return -1 on an
+ error, for example EOF. */
+static int
+parse_arith_cmd (ep, adddq)
+ char **ep;
+ int adddq;
+{
+ int exp_lineno, rval, c;
+ char *ttok, *tokstr;
+ int ttoklen;
+
+ exp_lineno = line_number;
+ ttok = parse_matched_pair (0, '(', ')', &ttoklen, 0);
+ rval = 1;
+ if (ttok == &matched_pair_error)
+ return -1;
+ /* Check that the next character is the closing right paren. If
+ not, this is a syntax error. ( */
+ c = shell_getc (0);
+ if MBTEST(c != ')')
+ rval = 0;
+
+ tokstr = (char *)xmalloc (ttoklen + 4);
+
+ /* if ADDDQ != 0 then (( ... )) -> "..." */
+ if (rval == 1 && adddq) /* arith cmd, add double quotes */
+ {
+ tokstr[0] = '"';
+ strncpy (tokstr + 1, ttok, ttoklen - 1);
+ tokstr[ttoklen] = '"';
+ tokstr[ttoklen+1] = '\0';
+ }
+ else if (rval == 1) /* arith cmd, don't add double quotes */
+ {
+ strncpy (tokstr, ttok, ttoklen - 1);
+ tokstr[ttoklen-1] = '\0';
+ }
+ else /* nested subshell */
+ {
+ tokstr[0] = '(';
+ strncpy (tokstr + 1, ttok, ttoklen - 1);
+ tokstr[ttoklen] = ')';
+ tokstr[ttoklen+1] = c;
+ tokstr[ttoklen+2] = '\0';
+ }
+
+ *ep = tokstr;
+ FREE (ttok);
+ return rval;
+}
+#endif /* DPAREN_ARITHMETIC || ARITH_FOR_COMMAND */
+
+#if defined (COND_COMMAND)
+static void
+cond_error ()
+{
+ char *etext;
+
+ if (EOF_Reached && cond_token != COND_ERROR) /* [[ */
+ parser_error (cond_lineno, _("unexpected EOF while looking for `]]'"));
+ else if (cond_token != COND_ERROR)
+ {
+ if (etext = error_token_from_token (cond_token))
+ {
+ parser_error (cond_lineno, _("syntax error in conditional expression: unexpected token `%s'"), etext);
+ free (etext);
+ }
+ else
+ parser_error (cond_lineno, _("syntax error in conditional expression"));
+ }
+}
+
+static COND_COM *
+cond_expr ()
+{
+ return (cond_or ());
+}
+
+static COND_COM *
+cond_or ()
+{
+ COND_COM *l, *r;
+
+ l = cond_and ();
+ if (cond_token == OR_OR)
+ {
+ r = cond_or ();
+ l = make_cond_node (COND_OR, (WORD_DESC *)NULL, l, r);
+ }
+ return l;
+}
+
+static COND_COM *
+cond_and ()
+{
+ COND_COM *l, *r;
+
+ l = cond_term ();
+ if (cond_token == AND_AND)
+ {
+ r = cond_and ();
+ l = make_cond_node (COND_AND, (WORD_DESC *)NULL, l, r);
+ }
+ return l;
+}
+
+static int
+cond_skip_newlines ()
+{
+ while ((cond_token = read_token (READ)) == '\n')
+ {
+ if (SHOULD_PROMPT ())
+ prompt_again ();
+ }
+ return (cond_token);
+}
+
+#define COND_RETURN_ERROR() \
+ do { cond_token = COND_ERROR; return ((COND_COM *)NULL); } while (0)
+
+static COND_COM *
+cond_term ()
+{
+ WORD_DESC *op;
+ COND_COM *term, *tleft, *tright;
+ int tok, lineno;
+ char *etext;
+
+ /* Read a token. It can be a left paren, a `!', a unary operator, or a
+ word that should be the first argument of a binary operator. Start by
+ skipping newlines, since this is a compound command. */
+ tok = cond_skip_newlines ();
+ lineno = line_number;
+ if (tok == COND_END)
+ {
+ COND_RETURN_ERROR ();
+ }
+ else if (tok == '(')
+ {
+ term = cond_expr ();
+ if (cond_token != ')')
+ {
+ if (term)
+ dispose_cond_node (term); /* ( */
+ if (etext = error_token_from_token (cond_token))
+ {
+ parser_error (lineno, _("unexpected token `%s', expected `)'"), etext);
+ free (etext);
+ }
+ else
+ parser_error (lineno, _("expected `)'"));
+ COND_RETURN_ERROR ();
+ }
+ term = make_cond_node (COND_EXPR, (WORD_DESC *)NULL, term, (COND_COM *)NULL);
+ (void)cond_skip_newlines ();
+ }
+ else if (tok == BANG || (tok == WORD && (yylval.word->word[0] == '!' && yylval.word->word[1] == '\0')))
+ {
+ if (tok == WORD)
+ dispose_word (yylval.word); /* not needed */
+ term = cond_term ();
+ if (term)
+ term->flags |= CMD_INVERT_RETURN;
+ }
+ else if (tok == WORD && yylval.word->word[0] == '-' && yylval.word->word[2] == 0 && test_unop (yylval.word->word))
+ {
+ op = yylval.word;
+ tok = read_token (READ);
+ if (tok == WORD)
+ {
+ tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+ term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL);
+ }
+ else
+ {
+ dispose_word (op);
+ if (etext = error_token_from_token (tok))
+ {
+ parser_error (line_number, _("unexpected argument `%s' to conditional unary operator"), etext);
+ free (etext);
+ }
+ else
+ parser_error (line_number, _("unexpected argument to conditional unary operator"));
+ COND_RETURN_ERROR ();
+ }
+
+ (void)cond_skip_newlines ();
+ }
+ else if (tok == WORD) /* left argument to binary operator */
+ {
+ /* lhs */
+ tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+
+ /* binop */
+ tok = read_token (READ);
+ if (tok == WORD && test_binop (yylval.word->word))
+ op = yylval.word;
+#if defined (COND_REGEXP)
+ else if (tok == WORD && STREQ (yylval.word->word, "=~"))
+ {
+ op = yylval.word;
+ parser_state |= PST_REGEXP;
+ }
+#endif
+ else if (tok == '<' || tok == '>')
+ op = make_word_from_token (tok); /* ( */
+ /* There should be a check before blindly accepting the `)' that we have
+ seen the opening `('. */
+ else if (tok == COND_END || tok == AND_AND || tok == OR_OR || tok == ')')
+ {
+ /* Special case. [[ x ]] is equivalent to [[ -n x ]], just like
+ the test command. Similarly for [[ x && expr ]] or
+ [[ x || expr ]] or [[ (x) ]]. */
+ op = make_word ("-n");
+ term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL);
+ cond_token = tok;
+ return (term);
+ }
+ else
+ {
+ if (etext = error_token_from_token (tok))
+ {
+ parser_error (line_number, _("unexpected token `%s', conditional binary operator expected"), etext);
+ free (etext);
+ }
+ else
+ parser_error (line_number, _("conditional binary operator expected"));
+ dispose_cond_node (tleft);
+ COND_RETURN_ERROR ();
+ }
+
+ /* rhs */
+ tok = read_token (READ);
+ parser_state &= ~PST_REGEXP;
+ if (tok == WORD)
+ {
+ tright = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+ term = make_cond_node (COND_BINARY, op, tleft, tright);
+ }
+ else
+ {
+ if (etext = error_token_from_token (tok))
+ {
+ parser_error (line_number, _("unexpected argument `%s' to conditional binary operator"), etext);
+ free (etext);
+ }
+ else
+ parser_error (line_number, _("unexpected argument to conditional binary operator"));
+ dispose_cond_node (tleft);
+ dispose_word (op);
+ COND_RETURN_ERROR ();
+ }
+
+ (void)cond_skip_newlines ();
+ }
+ else
+ {
+ if (tok < 256)
+ parser_error (line_number, _("unexpected token `%c' in conditional command"), tok);
+ else if (etext = error_token_from_token (tok))
+ {
+ parser_error (line_number, _("unexpected token `%s' in conditional command"), etext);
+ free (etext);
+ }
+ else
+ parser_error (line_number, _("unexpected token %d in conditional command"), tok);
+ COND_RETURN_ERROR ();
+ }
+ return (term);
+}
+
+/* This is kind of bogus -- we slip a mini recursive-descent parser in
+ here to handle the conditional statement syntax. */
+static COMMAND *
+parse_cond_command ()
+{
+ COND_COM *cexp;
+
+ cexp = cond_expr ();
+ return (make_cond_command (cexp));
+}
+#endif
+
+#if defined (ARRAY_VARS)
+/* When this is called, it's guaranteed that we don't care about anything
+ in t beyond i. We do save and restore the chars, though. */
+static int
+token_is_assignment (t, i)
+ char *t;
+ int i;
+{
+ unsigned char c, c1;
+ int r;
+
+ c = t[i]; c1 = t[i+1];
+ t[i] = '='; t[i+1] = '\0';
+ r = assignment (t, (parser_state & PST_COMPASSIGN) != 0);
+ t[i] = c; t[i+1] = c1;
+ return r;
+}
+
+/* XXX - possible changes here for `+=' */
+static int
+token_is_ident (t, i)
+ char *t;
+ int i;
+{
+ unsigned char c;
+ int r;
+
+ c = t[i];
+ t[i] = '\0';
+ r = legal_identifier (t);
+ t[i] = c;
+ return r;
+}
+#endif
+
+static int
+read_token_word (character)
+ int character;
+{
+ /* The value for YYLVAL when a WORD is read. */
+ WORD_DESC *the_word;
+
+ /* Index into the token that we are building. */
+ int token_index;
+
+ /* ALL_DIGITS becomes zero when we see a non-digit. */
+ int all_digit_token;
+
+ /* DOLLAR_PRESENT becomes non-zero if we see a `$'. */
+ int dollar_present;
+
+ /* COMPOUND_ASSIGNMENT becomes non-zero if we are parsing a compound
+ assignment. */
+ int compound_assignment;
+
+ /* QUOTED becomes non-zero if we see one of ("), ('), (`), or (\). */
+ int quoted;
+
+ /* Non-zero means to ignore the value of the next character, and just
+ to add it no matter what. */
+ int pass_next_character;
+
+ /* The current delimiting character. */
+ int cd;
+ int result, peek_char;
+ char *ttok, *ttrans;
+ int ttoklen, ttranslen;
+ intmax_t lvalue;
+
+ if (token_buffer_size < TOKEN_DEFAULT_INITIAL_SIZE)
+ token = (char *)xrealloc (token, token_buffer_size = TOKEN_DEFAULT_INITIAL_SIZE);
+
+ token_index = 0;
+ all_digit_token = DIGIT (character);
+ dollar_present = quoted = pass_next_character = compound_assignment = 0;
+
+ for (;;)
+ {
+ if (character == EOF)
+ goto got_token;
+
+ if (pass_next_character)
+ {
+ pass_next_character = 0;
+ goto got_escaped_character;
+ }
+
+ cd = current_delimiter (dstack);
+
+ /* Handle backslashes. Quote lots of things when not inside of
+ double-quotes, quote some things inside of double-quotes. */
+ if MBTEST(character == '\\')
+ {
+ peek_char = shell_getc (0);
+
+ /* Backslash-newline is ignored in all cases except
+ when quoted with single quotes. */
+ if (peek_char == '\n')
+ {
+ character = '\n';
+ goto next_character;
+ }
+ else
+ {
+ shell_ungetc (peek_char);
+
+ /* If the next character is to be quoted, note it now. */
+ if (cd == 0 || cd == '`' ||
+ (cd == '"' && peek_char >= 0 && (sh_syntaxtab[peek_char] & CBSDQUOTE)))
+ pass_next_character++;
+
+ quoted = 1;
+ goto got_character;
+ }
+ }
+
+ /* Parse a matched pair of quote characters. */
+ if MBTEST(shellquote (character))
+ {
+ push_delimiter (dstack, character);
+ ttok = parse_matched_pair (character, character, character, &ttoklen, (character == '`') ? P_COMMAND : 0);
+ pop_delimiter (dstack);
+ if (ttok == &matched_pair_error)
+ return -1; /* Bail immediately. */
+ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+ token_buffer_size, TOKEN_DEFAULT_GROW_SIZE);
+ token[token_index++] = character;
+ strcpy (token + token_index, ttok);
+ token_index += ttoklen;
+ all_digit_token = 0;
+ quoted = 1;
+ dollar_present |= (character == '"' && strchr (ttok, '$') != 0);
+ FREE (ttok);
+ goto next_character;
+ }
+
+#ifdef COND_REGEXP
+ /* When parsing a regexp as a single word inside a conditional command,
+ we need to special-case characters special to both the shell and
+ regular expressions. Right now, that is only '(' and '|'. */ /*)*/
+ if MBTEST((parser_state & PST_REGEXP) && (character == '(' || character == '|')) /*)*/
+ {
+ if (character == '|')
+ goto got_character;
+
+ push_delimiter (dstack, character);
+ ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+ pop_delimiter (dstack);
+ if (ttok == &matched_pair_error)
+ return -1; /* Bail immediately. */
+ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+ token_buffer_size, TOKEN_DEFAULT_GROW_SIZE);
+ token[token_index++] = character;
+ strcpy (token + token_index, ttok);
+ token_index += ttoklen;
+ FREE (ttok);
+ dollar_present = all_digit_token = 0;
+ goto next_character;
+ }
+#endif /* COND_REGEXP */
+
+#ifdef EXTENDED_GLOB
+ /* Parse a ksh-style extended pattern matching specification. */
+ if MBTEST(extended_glob && PATTERN_CHAR (character))
+ {
+ peek_char = shell_getc (1);
+ if MBTEST(peek_char == '(') /* ) */
+ {
+ push_delimiter (dstack, peek_char);
+ ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+ pop_delimiter (dstack);
+ if (ttok == &matched_pair_error)
+ return -1; /* Bail immediately. */
+ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+ token_buffer_size,
+ TOKEN_DEFAULT_GROW_SIZE);
+ token[token_index++] = character;
+ token[token_index++] = peek_char;
+ strcpy (token + token_index, ttok);
+ token_index += ttoklen;
+ FREE (ttok);
+ dollar_present = all_digit_token = 0;
+ goto next_character;
+ }
+ else
+ shell_ungetc (peek_char);
+ }
+#endif /* EXTENDED_GLOB */
+
+ /* If the delimiter character is not single quote, parse some of
+ the shell expansions that must be read as a single word. */
+ if (shellexp (character))
+ {
+ peek_char = shell_getc (1);
+ /* $(...), <(...), >(...), $((...)), ${...}, and $[...] constructs */
+ if MBTEST(peek_char == '(' || \
+ ((peek_char == '{' || peek_char == '[') && character == '$')) /* ) ] } */
+ {
+ if (peek_char == '{') /* } */
+ ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE);
+ else if (peek_char == '(') /* ) */
+ {
+ /* XXX - push and pop the `(' as a delimiter for use by
+ the command-oriented-history code. This way newlines
+ appearing in the $(...) string get added to the
+ history literally rather than causing a possibly-
+ incorrect `;' to be added. ) */
+ push_delimiter (dstack, peek_char);
+ ttok = parse_matched_pair (cd, '(', ')', &ttoklen, P_COMMAND);
+ pop_delimiter (dstack);
+ }
+ else
+ ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0);
+ if (ttok == &matched_pair_error)
+ return -1; /* Bail immediately. */
+ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+ token_buffer_size,
+ TOKEN_DEFAULT_GROW_SIZE);
+ token[token_index++] = character;
+ token[token_index++] = peek_char;
+ strcpy (token + token_index, ttok);
+ token_index += ttoklen;
+ FREE (ttok);
+ dollar_present = 1;
+ all_digit_token = 0;
+ goto next_character;
+ }
+ /* This handles $'...' and $"..." new-style quoted strings. */
+ else if MBTEST(character == '$' && (peek_char == '\'' || peek_char == '"'))
+ {
+ int first_line;
+
+ first_line = line_number;
+ push_delimiter (dstack, peek_char);
+ ttok = parse_matched_pair (peek_char, peek_char, peek_char,
+ &ttoklen,
+ (peek_char == '\'') ? P_ALLOWESC : 0);
+ pop_delimiter (dstack);
+ if (ttok == &matched_pair_error)
+ return -1;
+ if (peek_char == '\'')
+ {
+ ttrans = ansiexpand (ttok, 0, ttoklen - 1, &ttranslen);
+ free (ttok);
+
+ /* Insert the single quotes and correctly quote any
+ embedded single quotes (allowed because P_ALLOWESC was
+ passed to parse_matched_pair). */
+ ttok = sh_single_quote (ttrans);
+ free (ttrans);
+ ttranslen = strlen (ttok);
+ ttrans = ttok;
+ }
+ else
+ {
+ /* Try to locale-expand the converted string. */
+ ttrans = localeexpand (ttok, 0, ttoklen - 1, first_line, &ttranslen);
+ free (ttok);
+
+ /* Add the double quotes back */
+ ttok = sh_mkdoublequoted (ttrans, ttranslen, 0);
+ free (ttrans);
+ ttranslen += 2;
+ ttrans = ttok;
+ }
+
+ RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 2,
+ token_buffer_size,
+ TOKEN_DEFAULT_GROW_SIZE);
+ strcpy (token + token_index, ttrans);
+ token_index += ttranslen;
+ FREE (ttrans);
+ quoted = 1;
+ all_digit_token = 0;
+ goto next_character;
+ }
+ /* This could eventually be extended to recognize all of the
+ shell's single-character parameter expansions, and set flags.*/
+ else if MBTEST(character == '$' && peek_char == '$')
+ {
+ ttok = (char *)xmalloc (3);
+ ttok[0] = ttok[1] = '$';
+ ttok[2] = '\0';
+ RESIZE_MALLOCED_BUFFER (token, token_index, 3,
+ token_buffer_size,
+ TOKEN_DEFAULT_GROW_SIZE);
+ strcpy (token + token_index, ttok);
+ token_index += 2;
+ dollar_present = 1;
+ all_digit_token = 0;
+ FREE (ttok);
+ goto next_character;
+ }
+ else
+ shell_ungetc (peek_char);
+ }
+
+#if defined (ARRAY_VARS)
+ /* Identify possible array subscript assignment; match [...] */
+ else if MBTEST(character == '[' && token_index > 0 && assignment_acceptable (last_read_token) && token_is_ident (token, token_index)) /* ] */
+ {
+ ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0);
+ if (ttok == &matched_pair_error)
+ return -1; /* Bail immediately. */
+ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+ token_buffer_size,
+ TOKEN_DEFAULT_GROW_SIZE);
+ token[token_index++] = character;
+ strcpy (token + token_index, ttok);
+ token_index += ttoklen;
+ FREE (ttok);
+ all_digit_token = 0;
+ goto next_character;
+ }
+ /* Identify possible compound array variable assignment. */
+ else if MBTEST(character == '=' && token_index > 0 && (assignment_acceptable (last_read_token) || (parser_state & PST_ASSIGNOK)) && token_is_assignment (token, token_index))
+ {
+ peek_char = shell_getc (1);
+ if MBTEST(peek_char == '(') /* ) */
+ {
+ ttok = parse_compound_assignment (&ttoklen);
+
+ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 4,
+ token_buffer_size,
+ TOKEN_DEFAULT_GROW_SIZE);
+
+ token[token_index++] = '=';
+ token[token_index++] = '(';
+ if (ttok)
+ {
+ strcpy (token + token_index, ttok);
+ token_index += ttoklen;
+ }
+ token[token_index++] = ')';
+ FREE (ttok);
+ all_digit_token = 0;
+ compound_assignment = 1;
+#if 1
+ goto next_character;
+#else
+ goto got_token; /* ksh93 seems to do this */
+#endif
+ }
+ else
+ shell_ungetc (peek_char);
+ }
+#endif
+
+ /* When not parsing a multi-character word construct, shell meta-
+ characters break words. */
+ if MBTEST(shellbreak (character))
+ {
+ shell_ungetc (character);
+ goto got_token;
+ }
+
+ got_character:
+
+ if (character == CTLESC || character == CTLNUL)
+ token[token_index++] = CTLESC;
+
+ got_escaped_character:
+
+ all_digit_token &= DIGIT (character);
+ dollar_present |= character == '$';
+
+ token[token_index++] = character;
+
+ RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size,
+ TOKEN_DEFAULT_GROW_SIZE);
+
+ next_character:
+ if (character == '\n' && SHOULD_PROMPT ())
+ prompt_again ();
+
+ /* We want to remove quoted newlines (that is, a \<newline> pair)
+ unless we are within single quotes or pass_next_character is
+ set (the shell equivalent of literal-next). */
+ cd = current_delimiter (dstack);
+ character = shell_getc (cd != '\'' && pass_next_character == 0);
+ } /* end for (;;) */
+
+got_token:
+
+ token[token_index] = '\0';
+
+ /* Check to see what thing we should return. If the last_read_token
+ is a `<', or a `&', or the character which ended this token is
+ a '>' or '<', then, and ONLY then, is this input token a NUMBER.
+ Otherwise, it is just a word, and should be returned as such. */
+ if MBTEST(all_digit_token && (character == '<' || character == '>' || \
+ last_read_token == LESS_AND || \
+ last_read_token == GREATER_AND))
+ {
+ if (legal_number (token, &lvalue) && (int)lvalue == lvalue)
+ yylval.number = lvalue;
+ else
+ yylval.number = -1;
+ return (NUMBER);
+ }
+
+ /* Check for special case tokens. */
+ result = (last_shell_getc_is_singlebyte) ? special_case_tokens (token) : -1;
+ if (result >= 0)
+ return result;
+
+#if defined (ALIAS)
+ /* Posix.2 does not allow reserved words to be aliased, so check for all
+ of them, including special cases, before expanding the current token
+ as an alias. */
+ if MBTEST(posixly_correct)
+ CHECK_FOR_RESERVED_WORD (token);
+
+ /* Aliases are expanded iff EXPAND_ALIASES is non-zero, and quoting
+ inhibits alias expansion. */
+ if (expand_aliases && quoted == 0)
+ {
+ result = alias_expand_token (token);
+ if (result == RE_READ_TOKEN)
+ return (RE_READ_TOKEN);
+ else if (result == NO_EXPANSION)
+ parser_state &= ~PST_ALEXPNEXT;
+ }
+
+ /* If not in Posix.2 mode, check for reserved words after alias
+ expansion. */
+ if MBTEST(posixly_correct == 0)
+#endif
+ CHECK_FOR_RESERVED_WORD (token);
+
+ the_word = (WORD_DESC *)xmalloc (sizeof (WORD_DESC));
+ the_word->word = (char *)xmalloc (1 + token_index);
+ the_word->flags = 0;
+ strcpy (the_word->word, token);
+ if (dollar_present)
+ the_word->flags |= W_HASDOLLAR;
+ if (quoted)
+ the_word->flags |= W_QUOTED; /*(*/
+ if (compound_assignment && token[token_index-1] == ')')
+ the_word->flags |= W_COMPASSIGN;
+ /* A word is an assignment if it appears at the beginning of a
+ simple command, or after another assignment word. This is
+ context-dependent, so it cannot be handled in the grammar. */
+ if (assignment (token, (parser_state & PST_COMPASSIGN) != 0))
+ {
+ the_word->flags |= W_ASSIGNMENT;
+ /* Don't perform word splitting on assignment statements. */
+ if (assignment_acceptable (last_read_token) || (parser_state & PST_COMPASSIGN) != 0)
+ the_word->flags |= W_NOSPLIT;
+ }
+
+ if (command_token_position (last_read_token))
+ {
+ struct builtin *b;
+ b = builtin_address_internal (token, 0);
+ if (b && (b->flags & ASSIGNMENT_BUILTIN))
+ parser_state |= PST_ASSIGNOK;
+ else if (STREQ (token, "eval") || STREQ (token, "let"))
+ parser_state |= PST_ASSIGNOK;
+ }
+
+ yylval.word = the_word;
+
+ result = ((the_word->flags & (W_ASSIGNMENT|W_NOSPLIT)) == (W_ASSIGNMENT|W_NOSPLIT))
+ ? ASSIGNMENT_WORD : WORD;
+
+ switch (last_read_token)
+ {
+ case FUNCTION:
+ parser_state |= PST_ALLOWOPNBRC;
+ function_dstart = line_number;
+ break;
+ case CASE:
+ case SELECT:
+ case FOR:
+ if (word_top < MAX_CASE_NEST)
+ word_top++;
+ word_lineno[word_top] = line_number;
+ break;
+ }
+
+ return (result);
+}
+
+/* Return 1 if TOKSYM is a token that after being read would allow
+ a reserved word to be seen, else 0. */
+static int
+reserved_word_acceptable (toksym)
+ int toksym;
+{
+ switch (toksym)
+ {
+ case '\n':
+ case ';':
+ case '(':
+ case ')':
+ case '|':
+ case '&':
+ case '{':
+ case '}': /* XXX */
+ case AND_AND:
+ case BANG:
+ case DO:
+ case DONE:
+ case ELIF:
+ case ELSE:
+ case ESAC:
+ case FI:
+ case IF:
+ case OR_OR:
+ case SEMI_SEMI:
+ case SEMI_AND:
+ case SEMI_SEMI_AND:
+ case THEN:
+ case TIME:
+ case TIMEOPT:
+ case UNTIL:
+ case WHILE:
+ case 0:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/* Return the index of TOKEN in the alist of reserved words, or -1 if
+ TOKEN is not a shell reserved word. */
+int
+find_reserved_word (tokstr)
+ char *tokstr;
+{
+ int i;
+ for (i = 0; word_token_alist[i].word; i++)
+ if (STREQ (tokstr, word_token_alist[i].word))
+ return i;
+ return -1;
+}
+
+#if 0
+#if defined (READLINE)
+/* Called after each time readline is called. This insures that whatever
+ the new prompt string is gets propagated to readline's local prompt
+ variable. */
+static void
+reset_readline_prompt ()
+{
+ char *temp_prompt;
+
+ if (prompt_string_pointer)
+ {
+ temp_prompt = (*prompt_string_pointer)
+ ? decode_prompt_string (*prompt_string_pointer)
+ : (char *)NULL;
+
+ if (temp_prompt == 0)
+ {
+ temp_prompt = (char *)xmalloc (1);
+ temp_prompt[0] = '\0';
+ }
+
+ FREE (current_readline_prompt);
+ current_readline_prompt = temp_prompt;
+ }
+}
+#endif /* READLINE */
+#endif /* 0 */
+
+#if defined (HISTORY)
+/* A list of tokens which can be followed by newlines, but not by
+ semi-colons. When concatenating multiple lines of history, the
+ newline separator for such tokens is replaced with a space. */
+static const int no_semi_successors[] = {
+ '\n', '{', '(', ')', ';', '&', '|',
+ CASE, DO, ELSE, IF, SEMI_SEMI, SEMI_AND, SEMI_SEMI_AND, THEN, UNTIL,
+ WHILE, AND_AND, OR_OR, IN,
+ 0
+};
+
+/* If we are not within a delimited expression, try to be smart
+ about which separators can be semi-colons and which must be
+ newlines. Returns the string that should be added into the
+ history entry. */
+char *
+history_delimiting_chars ()
+{
+ register int i;
+
+ if (dstack.delimiter_depth != 0)
+ return ("\n");
+
+ /* First, handle some special cases. */
+ /*(*/
+ /* If we just read `()', assume it's a function definition, and don't
+ add a semicolon. If the token before the `)' was not `(', and we're
+ not in the midst of parsing a case statement, assume it's a
+ parenthesized command and add the semicolon. */
+ /*)(*/
+ if (token_before_that == ')')
+ {
+ if (two_tokens_ago == '(') /*)*/ /* function def */
+ return " ";
+ /* This does not work for subshells inside case statement
+ command lists. It's a suboptimal solution. */
+ else if (parser_state & PST_CASESTMT) /* case statement pattern */
+ return " ";
+ else
+ return "; "; /* (...) subshell */
+ }
+ else if (token_before_that == WORD && two_tokens_ago == FUNCTION)
+ return " "; /* function def using `function name' without `()' */
+
+ else if (token_before_that == WORD && two_tokens_ago == FOR)
+ {
+ /* Tricky. `for i\nin ...' should not have a semicolon, but
+ `for i\ndo ...' should. We do what we can. */
+ for (i = shell_input_line_index; whitespace (shell_input_line[i]); i++)
+ ;
+ if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n')
+ return " ";
+ return ";";
+ }
+ else if (two_tokens_ago == CASE && token_before_that == WORD && (parser_state & PST_CASESTMT))
+ return " ";
+
+ for (i = 0; no_semi_successors[i]; i++)
+ {
+ if (token_before_that == no_semi_successors[i])
+ return (" ");
+ }
+
+ return ("; ");
+}
+#endif /* HISTORY */
+
+/* Issue a prompt, or prepare to issue a prompt when the next character
+ is read. */
+static void
+prompt_again ()
+{
+ char *temp_prompt;
+
+ if (interactive == 0 || expanding_alias()) /* XXX */
+ return;
+
+ ps1_prompt = get_string_value ("PS1");
+ ps2_prompt = get_string_value ("PS2");
+
+ if (!prompt_string_pointer)
+ prompt_string_pointer = &ps1_prompt;
+
+ temp_prompt = *prompt_string_pointer
+ ? decode_prompt_string (*prompt_string_pointer)
+ : (char *)NULL;
+
+ if (temp_prompt == 0)
+ {
+ temp_prompt = (char *)xmalloc (1);
+ temp_prompt[0] = '\0';
+ }
+
+ current_prompt_string = *prompt_string_pointer;
+ prompt_string_pointer = &ps2_prompt;
+
+#if defined (READLINE)
+ if (!no_line_editing)
+ {
+ FREE (current_readline_prompt);
+ current_readline_prompt = temp_prompt;
+ }
+ else
+#endif /* READLINE */
+ {
+ FREE (current_decoded_prompt);
+ current_decoded_prompt = temp_prompt;
+ }
+}
+
+int
+get_current_prompt_level ()
+{
+ return ((current_prompt_string && current_prompt_string == ps2_prompt) ? 2 : 1);
+}
+
+void
+set_current_prompt_level (x)
+ int x;
+{
+ prompt_string_pointer = (x == 2) ? &ps2_prompt : &ps1_prompt;
+ current_prompt_string = *prompt_string_pointer;
+}
+
+static void
+print_prompt ()
+{
+ fprintf (stderr, "%s", current_decoded_prompt);
+ fflush (stderr);
+}
+
+/* Return a string which will be printed as a prompt. The string
+ may contain special characters which are decoded as follows:
+
+ \a bell (ascii 07)
+ \d the date in Day Mon Date format
+ \e escape (ascii 033)
+ \h the hostname up to the first `.'
+ \H the hostname
+ \j the number of active jobs
+ \l the basename of the shell's tty device name
+ \n CRLF
+ \r CR
+ \s the name of the shell
+ \t the time in 24-hour hh:mm:ss format
+ \T the time in 12-hour hh:mm:ss format
+ \@ the time in 12-hour hh:mm am/pm format
+ \A the time in 24-hour hh:mm format
+ \D{fmt} the result of passing FMT to strftime(3)
+ \u your username
+ \v the version of bash (e.g., 2.00)
+ \V the release of bash, version + patchlevel (e.g., 2.00.0)
+ \w the current working directory
+ \W the last element of $PWD
+ \! the history number of this command
+ \# the command number of this command
+ \$ a $ or a # if you are root
+ \nnn character code nnn in octal
+ \\ a backslash
+ \[ begin a sequence of non-printing chars
+ \] end a sequence of non-printing chars
+*/
+#define PROMPT_GROWTH 48
+char *
+decode_prompt_string (string)
+ char *string;
+{
+ WORD_LIST *list;
+ char *result, *t;
+ struct dstack save_dstack;
+ int last_exit_value;
+#if defined (PROMPT_STRING_DECODE)
+ int result_size, result_index;
+ int c, n, i;
+ char *temp, octal_string[4];
+ struct tm *tm;
+ time_t the_time;
+ char timebuf[128];
+ char *timefmt;
+
+ result = (char *)xmalloc (result_size = PROMPT_GROWTH);
+ result[result_index = 0] = 0;
+ temp = (char *)NULL;
+
+ while (c = *string++)
+ {
+ if (posixly_correct && c == '!')
+ {
+ if (*string == '!')
+ {
+ temp = savestring ("!");
+ goto add_string;
+ }
+ else
+ {
+#if !defined (HISTORY)
+ temp = savestring ("1");
+#else /* HISTORY */
+ temp = itos (history_number ());
+#endif /* HISTORY */
+ string--; /* add_string increments string again. */
+ goto add_string;
+ }
+ }
+ if (c == '\\')
+ {
+ c = *string;
+
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ strncpy (octal_string, string, 3);
+ octal_string[3] = '\0';
+
+ n = read_octal (octal_string);
+ temp = (char *)xmalloc (3);
+
+ if (n == CTLESC || n == CTLNUL)
+ {
+ temp[0] = CTLESC;
+ temp[1] = n;
+ temp[2] = '\0';
+ }
+ else if (n == -1)
+ {
+ temp[0] = '\\';
+ temp[1] = '\0';
+ }
+ else
+ {
+ temp[0] = n;
+ temp[1] = '\0';
+ }
+
+ for (c = 0; n != -1 && c < 3 && ISOCTAL (*string); c++)
+ string++;
+
+ c = 0; /* tested at add_string: */
+ goto add_string;
+
+ case 'd':
+ case 't':
+ case 'T':
+ case '@':
+ case 'A':
+ /* Make the current time/date into a string. */
+ (void) time (&the_time);
+ tm = localtime (&the_time);
+
+ if (c == 'd')
+ n = strftime (timebuf, sizeof (timebuf), "%a %b %d", tm);
+ else if (c == 't')
+ n = strftime (timebuf, sizeof (timebuf), "%H:%M:%S", tm);
+ else if (c == 'T')
+ n = strftime (timebuf, sizeof (timebuf), "%I:%M:%S", tm);
+ else if (c == '@')
+ n = strftime (timebuf, sizeof (timebuf), "%I:%M %p", tm);
+ else if (c == 'A')
+ n = strftime (timebuf, sizeof (timebuf), "%H:%M", tm);
+
+ if (n == 0)
+ timebuf[0] = '\0';
+ else
+ timebuf[sizeof(timebuf) - 1] = '\0';
+
+ temp = savestring (timebuf);
+ goto add_string;
+
+ case 'D': /* strftime format */
+ if (string[1] != '{') /* } */
+ goto not_escape;
+
+ (void) time (&the_time);
+ tm = localtime (&the_time);
+ string += 2; /* skip { */
+ timefmt = xmalloc (strlen (string) + 3);
+ for (t = timefmt; *string && *string != '}'; )
+ *t++ = *string++;
+ *t = '\0';
+ c = *string; /* tested at add_string */
+ if (timefmt[0] == '\0')
+ {
+ timefmt[0] = '%';
+ timefmt[1] = 'X'; /* locale-specific current time */
+ timefmt[2] = '\0';
+ }
+ n = strftime (timebuf, sizeof (timebuf), timefmt, tm);
+ free (timefmt);
+
+ if (n == 0)
+ timebuf[0] = '\0';
+ else
+ timebuf[sizeof(timebuf) - 1] = '\0';
+
+ if (promptvars || posixly_correct)
+ /* Make sure that expand_prompt_string is called with a
+ second argument of Q_DOUBLE_QUOTES if we use this
+ function here. */
+ temp = sh_backslash_quote_for_double_quotes (timebuf);
+ else
+ temp = savestring (timebuf);
+ goto add_string;
+
+ case 'n':
+ temp = (char *)xmalloc (3);
+ temp[0] = no_line_editing ? '\n' : '\r';
+ temp[1] = no_line_editing ? '\0' : '\n';
+ temp[2] = '\0';
+ goto add_string;
+
+ case 's':
+ temp = base_pathname (shell_name);
+ temp = savestring (temp);
+ goto add_string;
+
+ case 'v':
+ case 'V':
+ temp = (char *)xmalloc (16);
+ if (c == 'v')
+ strcpy (temp, dist_version);
+ else
+ sprintf (temp, "%s.%d", dist_version, patch_level);
+ goto add_string;
+
+ case 'w':
+ case 'W':
+ {
+ /* Use the value of PWD because it is much more efficient. */
+ char t_string[PATH_MAX];
+ int tlen;
+
+ temp = get_string_value ("PWD");
+
+ if (temp == 0)
+ {
+ if (getcwd (t_string, sizeof(t_string)) == 0)
+ {
+ t_string[0] = '.';
+ tlen = 1;
+ }
+ else
+ tlen = strlen (t_string);
+ }
+ else
+ {
+ tlen = sizeof (t_string) - 1;
+ strncpy (t_string, temp, tlen);
+ }
+ t_string[tlen] = '\0';
+
+#define ROOT_PATH(x) ((x)[0] == '/' && (x)[1] == 0)
+#define DOUBLE_SLASH_ROOT(x) ((x)[0] == '/' && (x)[1] == '/' && (x)[2] == 0)
+ /* Abbreviate \W as ~ if $PWD == $HOME */
+ if (c == 'W' && (((t = get_string_value ("HOME")) == 0) || STREQ (t, t_string) == 0))
+ {
+ if (ROOT_PATH (t_string) == 0 && DOUBLE_SLASH_ROOT (t_string) == 0)
+ {
+ t = strrchr (t_string, '/');
+ if (t)
+ strcpy (t_string, t + 1);
+ }
+ }
+#undef ROOT_PATH
+#undef DOUBLE_SLASH_ROOT
+ else
+ /* polite_directory_format is guaranteed to return a string
+ no longer than PATH_MAX - 1 characters. */
+ strcpy (t_string, polite_directory_format (t_string));
+
+ temp = trim_pathname (t_string, PATH_MAX - 1);
+ /* If we're going to be expanding the prompt string later,
+ quote the directory name. */
+ if (promptvars || posixly_correct)
+ /* Make sure that expand_prompt_string is called with a
+ second argument of Q_DOUBLE_QUOTES if we use this
+ function here. */
+ temp = sh_backslash_quote_for_double_quotes (t_string);
+ else
+ temp = savestring (t_string);
+
+ goto add_string;
+ }
+
+ case 'u':
+ if (current_user.user_name == 0)
+ get_current_user_info ();
+ temp = savestring (current_user.user_name);
+ goto add_string;
+
+ case 'h':
+ case 'H':
+ temp = savestring (current_host_name);
+ if (c == 'h' && (t = (char *)strchr (temp, '.')))
+ *t = '\0';
+ goto add_string;
+
+ case '#':
+ temp = itos (current_command_number);
+ goto add_string;
+
+ case '!':
+#if !defined (HISTORY)
+ temp = savestring ("1");
+#else /* HISTORY */
+ temp = itos (history_number ());
+#endif /* HISTORY */
+ goto add_string;
+
+ case '$':
+ t = temp = (char *)xmalloc (3);
+ if ((promptvars || posixly_correct) && (current_user.euid != 0))
+ *t++ = '\\';
+ *t++ = current_user.euid == 0 ? '#' : '$';
+ *t = '\0';
+ goto add_string;
+
+ case 'j':
+ temp = itos (count_all_jobs ());
+ goto add_string;
+
+ case 'l':
+#if defined (HAVE_TTYNAME)
+ temp = (char *)ttyname (fileno (stdin));
+ t = temp ? base_pathname (temp) : "tty";
+ temp = savestring (t);
+#else
+ temp = savestring ("tty");
+#endif /* !HAVE_TTYNAME */
+ goto add_string;
+
+#if defined (READLINE)
+ case '[':
+ case ']':
+ if (no_line_editing)
+ {
+ string++;
+ break;
+ }
+ temp = (char *)xmalloc (3);
+ n = (c == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
+ i = 0;
+ if (n == CTLESC || n == CTLNUL)
+ temp[i++] = CTLESC;
+ temp[i++] = n;
+ temp[i] = '\0';
+ goto add_string;
+#endif /* READLINE */
+
+ case '\\':
+ case 'a':
+ case 'e':
+ case 'r':
+ temp = (char *)xmalloc (2);
+ if (c == 'a')
+ temp[0] = '\07';
+ else if (c == 'e')
+ temp[0] = '\033';
+ else if (c == 'r')
+ temp[0] = '\r';
+ else /* (c == '\\') */
+ temp[0] = c;
+ temp[1] = '\0';
+ goto add_string;
+
+ default:
+not_escape:
+ temp = (char *)xmalloc (3);
+ temp[0] = '\\';
+ temp[1] = c;
+ temp[2] = '\0';
+
+ add_string:
+ if (c)
+ string++;
+ result =
+ sub_append_string (temp, result, &result_index, &result_size);
+ temp = (char *)NULL; /* Freed in sub_append_string (). */
+ result[result_index] = '\0';
+ break;
+ }
+ }
+ else
+ {
+ RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, PROMPT_GROWTH);
+ result[result_index++] = c;
+ result[result_index] = '\0';
+ }
+ }
+#else /* !PROMPT_STRING_DECODE */
+ result = savestring (string);
+#endif /* !PROMPT_STRING_DECODE */
+
+ /* Save the delimiter stack and point `dstack' to temp space so any
+ command substitutions in the prompt string won't result in screwing
+ up the parser's quoting state. */
+ save_dstack = dstack;
+ dstack = temp_dstack;
+ dstack.delimiter_depth = 0;
+
+ /* Perform variable and parameter expansion and command substitution on
+ the prompt string. */
+ if (promptvars || posixly_correct)
+ {
+ last_exit_value = last_command_exit_value;
+ list = expand_prompt_string (result, Q_DOUBLE_QUOTES, 0);
+ free (result);
+ result = string_list (list);
+ dispose_words (list);
+ last_command_exit_value = last_exit_value;
+ }
+ else
+ {
+ t = dequote_string (result);
+ free (result);
+ result = t;
+ }
+
+ dstack = save_dstack;
+
+ return (result);
+}
+
+/************************************************
+ * *
+ * ERROR HANDLING *
+ * *
+ ************************************************/
+
+/* Report a syntax error, and restart the parser. Call here for fatal
+ errors. */
+int
+yyerror (msg)
+ const char *msg;
+{
+ report_syntax_error ((char *)NULL);
+ reset_parser ();
+ return (0);
+}
+
+static char *
+error_token_from_token (tok)
+ int tok;
+{
+ char *t;
+
+ if (t = find_token_in_alist (tok, word_token_alist, 0))
+ return t;
+
+ if (t = find_token_in_alist (tok, other_token_alist, 0))
+ return t;
+
+ t = (char *)NULL;
+ /* This stuff is dicy and needs closer inspection */
+ switch (current_token)
+ {
+ case WORD:
+ case ASSIGNMENT_WORD:
+ if (yylval.word)
+ t = savestring (yylval.word->word);
+ break;
+ case NUMBER:
+ t = itos (yylval.number);
+ break;
+ case ARITH_CMD:
+ if (yylval.word_list)
+ t = string_list (yylval.word_list);
+ break;
+ case ARITH_FOR_EXPRS:
+ if (yylval.word_list)
+ t = string_list_internal (yylval.word_list, " ; ");
+ break;
+ case COND_CMD:
+ t = (char *)NULL; /* punt */
+ break;
+ }
+
+ return t;
+}
+
+static char *
+error_token_from_text ()
+{
+ char *msg, *t;
+ int token_end, i;
+
+ t = shell_input_line;
+ i = shell_input_line_index;
+ token_end = 0;
+ msg = (char *)NULL;
+
+ if (i && t[i] == '\0')
+ i--;
+
+ while (i && (whitespace (t[i]) || t[i] == '\n'))
+ i--;
+
+ if (i)
+ token_end = i + 1;
+
+ while (i && (member (t[i], " \n\t;|&") == 0))
+ i--;
+
+ while (i != token_end && (whitespace (t[i]) || t[i] == '\n'))
+ i++;
+
+ /* Return our idea of the offending token. */
+ if (token_end || (i == 0 && token_end == 0))
+ {
+ if (token_end)
+ msg = substring (t, i, token_end);
+ else /* one-character token */
+ {
+ msg = (char *)xmalloc (2);
+ msg[0] = t[i];
+ msg[1] = '\0';
+ }
+ }
+
+ return (msg);
+}
+
+static void
+print_offending_line ()
+{
+ char *msg;
+ int token_end;
+
+ msg = savestring (shell_input_line);
+ token_end = strlen (msg);
+ while (token_end && msg[token_end - 1] == '\n')
+ msg[--token_end] = '\0';
+
+ parser_error (line_number, "`%s'", msg);
+ free (msg);
+}
+
+/* Report a syntax error with line numbers, etc.
+ Call here for recoverable errors. If you have a message to print,
+ then place it in MESSAGE, otherwise pass NULL and this will figure
+ out an appropriate message for you. */
+static void
+report_syntax_error (message)
+ char *message;
+{
+ char *msg;
+
+ if (message)
+ {
+ parser_error (line_number, "%s", message);
+ if (interactive && EOF_Reached)
+ EOF_Reached = 0;
+ last_command_exit_value = EX_USAGE;
+ return;
+ }
+
+ /* If the line of input we're reading is not null, try to find the
+ objectionable token. First, try to figure out what token the
+ parser's complaining about by looking at current_token. */
+ if (current_token != 0 && EOF_Reached == 0 && (msg = error_token_from_token (current_token)))
+ {
+ parser_error (line_number, _("syntax error near unexpected token `%s'"), msg);
+ free (msg);
+
+ if (interactive == 0)
+ print_offending_line ();
+
+ last_command_exit_value = EX_USAGE;
+ return;
+ }
+
+ /* If looking at the current token doesn't prove fruitful, try to find the
+ offending token by analyzing the text of the input line near the current
+ input line index and report what we find. */
+ if (shell_input_line && *shell_input_line)
+ {
+ msg = error_token_from_text ();
+ if (msg)
+ {
+ parser_error (line_number, _("syntax error near `%s'"), msg);
+ free (msg);
+ }
+
+ /* If not interactive, print the line containing the error. */
+ if (interactive == 0)
+ print_offending_line ();
+ }
+ else
+ {
+ msg = EOF_Reached ? _("syntax error: unexpected end of file") : _("syntax error");
+ parser_error (line_number, "%s", msg);
+ /* When the shell is interactive, this file uses EOF_Reached
+ only for error reporting. Other mechanisms are used to
+ decide whether or not to exit. */
+ if (interactive && EOF_Reached)
+ EOF_Reached = 0;
+ }
+
+ last_command_exit_value = EX_USAGE;
+}
+
+/* ??? Needed function. ??? We have to be able to discard the constructs
+ created during parsing. In the case of error, we want to return
+ allocated objects to the memory pool. In the case of no error, we want
+ to throw away the information about where the allocated objects live.
+ (dispose_command () will actually free the command.) */
+static void
+discard_parser_constructs (error_p)
+ int error_p;
+{
+}
+
+/************************************************
+ * *
+ * EOF HANDLING *
+ * *
+ ************************************************/
+
+/* Do that silly `type "bye" to exit' stuff. You know, "ignoreeof". */
+
+/* A flag denoting whether or not ignoreeof is set. */
+int ignoreeof = 0;
+
+/* The number of times that we have encountered an EOF character without
+ another character intervening. When this gets above the limit, the
+ shell terminates. */
+int eof_encountered = 0;
+
+/* The limit for eof_encountered. */
+int eof_encountered_limit = 10;
+
+/* If we have EOF as the only input unit, this user wants to leave
+ the shell. If the shell is not interactive, then just leave.
+ Otherwise, if ignoreeof is set, and we haven't done this the
+ required number of times in a row, print a message. */
+static void
+handle_eof_input_unit ()
+{
+ if (interactive)
+ {
+ /* shell.c may use this to decide whether or not to write out the
+ history, among other things. We use it only for error reporting
+ in this file. */
+ if (EOF_Reached)
+ EOF_Reached = 0;
+
+ /* If the user wants to "ignore" eof, then let her do so, kind of. */
+ if (ignoreeof)
+ {
+ if (eof_encountered < eof_encountered_limit)
+ {
+ fprintf (stderr, _("Use \"%s\" to leave the shell.\n"),
+ login_shell ? "logout" : "exit");
+ eof_encountered++;
+ /* Reset the parsing state. */
+ last_read_token = current_token = '\n';
+ /* Reset the prompt string to be $PS1. */
+ prompt_string_pointer = (char **)NULL;
+ prompt_again ();
+ return;
+ }
+ }
+
+ /* In this case EOF should exit the shell. Do it now. */
+ reset_parser ();
+ exit_builtin ((WORD_LIST *)NULL);
+ }
+ else
+ {
+ /* We don't write history files, etc., for non-interactive shells. */
+ EOF_Reached = 1;
+ }
+}
+
+/************************************************
+ * *
+ * STRING PARSING FUNCTIONS *
+ * *
+ ************************************************/
+
+/* It's very important that these two functions treat the characters
+ between ( and ) identically. */
+
+static WORD_LIST parse_string_error;
+
+/* Take a string and run it through the shell parser, returning the
+ resultant word list. Used by compound array assignment. */
+WORD_LIST *
+parse_string_to_word_list (s, flags, whom)
+ char *s;
+ int flags;
+ const char *whom;
+{
+ WORD_LIST *wl;
+ int tok, orig_current_token, orig_line_number, orig_input_terminator;
+ int orig_line_count;
+ int old_echo_input, old_expand_aliases;
+#if defined (HISTORY)
+ int old_remember_on_history, old_history_expansion_inhibited;
+#endif
+
+#if defined (HISTORY)
+ old_remember_on_history = remember_on_history;
+# if defined (BANG_HISTORY)
+ old_history_expansion_inhibited = history_expansion_inhibited;
+# endif
+ bash_history_disable ();
+#endif
+
+ orig_line_number = line_number;
+ orig_line_count = current_command_line_count;
+ orig_input_terminator = shell_input_line_terminator;
+ old_echo_input = echo_input_at_read;
+ old_expand_aliases = expand_aliases;
+
+ push_stream (1);
+ last_read_token = WORD; /* WORD to allow reserved words here */
+ current_command_line_count = 0;
+ echo_input_at_read = expand_aliases = 0;
+
+ with_input_from_string (s, whom);
+ wl = (WORD_LIST *)NULL;
+
+ if (flags & 1)
+ parser_state |= PST_COMPASSIGN;
+
+ while ((tok = read_token (READ)) != yacc_EOF)
+ {
+ if (tok == '\n' && *bash_input.location.string == '\0')
+ break;
+ if (tok == '\n') /* Allow newlines in compound assignments */
+ continue;
+ if (tok != WORD && tok != ASSIGNMENT_WORD)
+ {
+ line_number = orig_line_number + line_number - 1;
+ orig_current_token = current_token;
+ current_token = tok;
+ yyerror (NULL); /* does the right thing */
+ current_token = orig_current_token;
+ if (wl)
+ dispose_words (wl);
+ wl = &parse_string_error;
+ break;
+ }
+ wl = make_word_list (yylval.word, wl);
+ }
+
+ last_read_token = '\n';
+ pop_stream ();
+
+#if defined (HISTORY)
+ remember_on_history = old_remember_on_history;
+# if defined (BANG_HISTORY)
+ history_expansion_inhibited = old_history_expansion_inhibited;
+# endif /* BANG_HISTORY */
+#endif /* HISTORY */
+
+ echo_input_at_read = old_echo_input;
+ expand_aliases = old_expand_aliases;
+
+ current_command_line_count = orig_line_count;
+ shell_input_line_terminator = orig_input_terminator;
+
+ if (flags & 1)
+ parser_state &= ~PST_COMPASSIGN;
+
+ if (wl == &parse_string_error)
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ if (interactive_shell == 0 && posixly_correct)
+ jump_to_top_level (FORCE_EOF);
+ else
+ jump_to_top_level (DISCARD);
+ }
+
+ return (REVERSE_LIST (wl, WORD_LIST *));
+}
+
+static char *
+parse_compound_assignment (retlenp)
+ int *retlenp;
+{
+ WORD_LIST *wl, *rl;
+ int tok, orig_line_number, orig_token_size, orig_last_token, assignok;
+ char *saved_token, *ret;
+
+ saved_token = token;
+ orig_token_size = token_buffer_size;
+ orig_line_number = line_number;
+ orig_last_token = last_read_token;
+
+ last_read_token = WORD; /* WORD to allow reserved words here */
+
+ token = (char *)NULL;
+ token_buffer_size = 0;
+
+ assignok = parser_state&PST_ASSIGNOK; /* XXX */
+
+ wl = (WORD_LIST *)NULL; /* ( */
+ parser_state |= PST_COMPASSIGN;
+
+ while ((tok = read_token (READ)) != ')')
+ {
+ if (tok == '\n') /* Allow newlines in compound assignments */
+ {
+ if (SHOULD_PROMPT ())
+ prompt_again ();
+ continue;
+ }
+ if (tok != WORD && tok != ASSIGNMENT_WORD)
+ {
+ current_token = tok; /* for error reporting */
+ if (tok == yacc_EOF) /* ( */
+ parser_error (orig_line_number, _("unexpected EOF while looking for matching `)'"));
+ else
+ yyerror(NULL); /* does the right thing */
+ if (wl)
+ dispose_words (wl);
+ wl = &parse_string_error;
+ break;
+ }
+ wl = make_word_list (yylval.word, wl);
+ }
+
+ FREE (token);
+ token = saved_token;
+ token_buffer_size = orig_token_size;
+
+ parser_state &= ~PST_COMPASSIGN;
+
+ if (wl == &parse_string_error)
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ last_read_token = '\n'; /* XXX */
+ if (interactive_shell == 0 && posixly_correct)
+ jump_to_top_level (FORCE_EOF);
+ else
+ jump_to_top_level (DISCARD);
+ }
+
+ last_read_token = orig_last_token; /* XXX - was WORD? */
+
+ if (wl)
+ {
+ rl = REVERSE_LIST (wl, WORD_LIST *);
+ ret = string_list (rl);
+ dispose_words (rl);
+ }
+ else
+ ret = (char *)NULL;
+
+ if (retlenp)
+ *retlenp = (ret && *ret) ? strlen (ret) : 0;
+
+ if (assignok)
+ parser_state |= PST_ASSIGNOK;
+
+ return ret;
+}
+
+/************************************************
+ * *
+ * SAVING AND RESTORING PARTIAL PARSE STATE *
+ * *
+ ************************************************/
+
+sh_parser_state_t *
+save_parser_state (ps)
+ sh_parser_state_t *ps;
+{
+#if defined (ARRAY_VARS)
+ SHELL_VAR *v;
+#endif
+
+ if (ps == 0)
+ ps = (sh_parser_state_t *)xmalloc (sizeof (sh_parser_state_t));
+ if (ps == 0)
+ return ((sh_parser_state_t *)NULL);
+
+ ps->parser_state = parser_state;
+ ps->token_state = save_token_state ();
+
+ ps->input_line_terminator = shell_input_line_terminator;
+ ps->eof_encountered = eof_encountered;
+
+ ps->current_command_line_count = current_command_line_count;
+
+#if defined (HISTORY)
+ ps->remember_on_history = remember_on_history;
+# if defined (BANG_HISTORY)
+ ps->history_expansion_inhibited = history_expansion_inhibited;
+# endif
+#endif
+
+ ps->last_command_exit_value = last_command_exit_value;
+#if defined (ARRAY_VARS)
+ v = find_variable ("PIPESTATUS");
+ if (v && array_p (v) && array_cell (v))
+ ps->pipestatus = array_copy (array_cell (v));
+ else
+ ps->pipestatus = (ARRAY *)NULL;
+#endif
+
+ ps->last_shell_builtin = last_shell_builtin;
+ ps->this_shell_builtin = this_shell_builtin;
+
+ ps->expand_aliases = expand_aliases;
+ ps->echo_input_at_read = echo_input_at_read;
+
+ return (ps);
+}
+
+void
+restore_parser_state (ps)
+ sh_parser_state_t *ps;
+{
+#if defined (ARRAY_VARS)
+ SHELL_VAR *v;
+#endif
+
+ if (ps == 0)
+ return;
+
+ parser_state = ps->parser_state;
+ if (ps->token_state)
+ {
+ restore_token_state (ps->token_state);
+ free (ps->token_state);
+ }
+
+ shell_input_line_terminator = ps->input_line_terminator;
+ eof_encountered = ps->eof_encountered;
+
+ current_command_line_count = ps->current_command_line_count;
+
+#if defined (HISTORY)
+ remember_on_history = ps->remember_on_history;
+# if defined (BANG_HISTORY)
+ history_expansion_inhibited = ps->history_expansion_inhibited;
+# endif
+#endif
+
+ last_command_exit_value = ps->last_command_exit_value;
+#if defined (ARRAY_VARS)
+ v = find_variable ("PIPESTATUS");
+ if (v && array_p (v) && array_cell (v))
+ {
+ array_dispose (array_cell (v));
+ var_setarray (v, ps->pipestatus);
+ }
+#endif
+
+ last_shell_builtin = ps->last_shell_builtin;
+ this_shell_builtin = ps->this_shell_builtin;
+
+ expand_aliases = ps->expand_aliases;
+ echo_input_at_read = ps->echo_input_at_read;
+}
+
+/************************************************
+ * *
+ * MULTIBYTE CHARACTER HANDLING *
+ * *
+ ************************************************/
+
+#if defined (HANDLE_MULTIBYTE)
+static void
+set_line_mbstate ()
+{
+ int i, previ, len, c;
+ mbstate_t mbs, prevs;
+ size_t mbclen;
+
+ if (shell_input_line == NULL)
+ return;
+ len = strlen (shell_input_line); /* XXX - shell_input_line_len ? */
+ FREE (shell_input_line_property);
+ shell_input_line_property = (char *)xmalloc (len + 1);
+
+ memset (&prevs, '\0', sizeof (mbstate_t));
+ for (i = previ = 0; i < len; i++)
+ {
+ mbs = prevs;
+
+ c = shell_input_line[i];
+ if (c == EOF)
+ {
+ int j;
+ for (j = i; j < len; j++)
+ shell_input_line_property[j] = 1;
+ break;
+ }
+
+ mbclen = mbrlen (shell_input_line + previ, i - previ + 1, &mbs);
+ if (mbclen == 1 || mbclen == (size_t)-1)
+ {
+ mbclen = 1;
+ previ = i + 1;
+ }
+ else if (mbclen == (size_t)-2)
+ mbclen = 0;
+ else if (mbclen > 1)
+ {
+ mbclen = 0;
+ previ = i + 1;
+ prevs = mbs;
+ }
+ else
+ {
+ /* XXX - what to do if mbrlen returns 0? (null wide character) */
+ int j;
+ for (j = i; j < len; j++)
+ shell_input_line_property[j] = 1;
+ break;
+ }
+
+ shell_input_line_property[i] = mbclen;
+ }
+}
+#endif /* HANDLE_MULTIBYTE */
diff --git a/parse.y~ b/parse.y~
index 0d33de53..a7279f61 100644
--- a/parse.y~
+++ b/parse.y~
@@ -372,6 +372,8 @@ inputunit: simple_list simple_list_terminator
global_command = $1;
eof_encountered = 0;
/* discard_parser_constructs (0); */
+ if (parser_state & PST_CMDSUBST)
+ parser_state |= PST_EOFTOKEN;
YYACCEPT;
}
| '\n'
@@ -379,6 +381,8 @@ inputunit: simple_list simple_list_terminator
/* Case of regular command, but not a very
interesting one. Return a NULL command. */
global_command = (COMMAND *)NULL;
+ if (parser_state & PST_CMDSUBST)
+ parser_state |= PST_EOFTOKEN;
YYACCEPT;
}
| error '\n'
@@ -762,12 +766,10 @@ case_command: CASE WORD newline_list IN newline_list ESAC
function_def: WORD '(' ')' newline_list function_body
{ $$ = make_function_def ($1, $5, function_dstart, function_bstart); }
-
- | FUNCTION WORD '(' ')' newline_list function_body
- { $$ = make_function_def ($2, $6, function_dstart, function_bstart); }
-
| FUNCTION WORD newline_list function_body
{ $$ = make_function_def ($2, $4, function_dstart, function_bstart); }
+ | FUNCTION WORD '(' ')' newline_list function_body
+ { $$ = make_function_def ($2, $6, function_dstart, function_bstart); }
;
@@ -953,6 +955,13 @@ simple_list: simple_list1
$$ = $1;
if (need_here_doc)
gather_here_documents ();
+ if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+ {
+ global_command = $1;
+ eof_encountered = 0;
+ rewind_input_string ();
+ YYACCEPT;
+ }
}
| simple_list1 '&'
{
@@ -962,12 +971,26 @@ simple_list: simple_list1
$$ = command_connect ($1, (COMMAND *)NULL, '&');
if (need_here_doc)
gather_here_documents ();
+ if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+ {
+ global_command = $1;
+ eof_encountered = 0;
+ rewind_input_string ();
+ YYACCEPT;
+ }
}
| simple_list1 ';'
{
$$ = $1;
if (need_here_doc)
gather_here_documents ();
+ if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+ {
+ global_command = $1;
+ eof_encountered = 0;
+ rewind_input_string ();
+ YYACCEPT;
+ }
}
;
@@ -2258,6 +2281,15 @@ yylex ()
token_before_that = last_read_token;
last_read_token = current_token;
current_token = read_token (READ);
+
+ if ((parser_state & PST_EOFTOKEN) && current_token == shell_eof_token)
+ {
+ current_token = yacc_EOF;
+ if (bash_input.type == st_string)
+ rewind_input_string ();
+ }
+ parser_state &= ~PST_EOFTOKEN;
+
return (current_token);
}
@@ -2686,6 +2718,7 @@ read_token (command)
#if defined (ALIAS)
parser_state &= ~PST_ALEXPNEXT;
#endif /* ALIAS */
+
peek_char = shell_getc (1);
if MBTEST(peek_char == '&')
return (SEMI_SEMI_AND);
@@ -2895,7 +2928,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
/* Not exactly right yet, should handle shell metacharacters, too. If
any changes are made to this test, make analogous changes to subst.c:
extract_delimited_string(). */
- else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || whitespace (ret[retind - 1])))
+ else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || shellblank (ret[retind - 1])))
tflags |= LEX_INCOMMENT;
if (tflags & LEX_PASSNEXT) /* last char was backslash */
@@ -3015,7 +3048,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
if (open == ch) /* undo previous increment */
count--;
if (ch == '(') /* ) */
- nestret = parse_matched_pair (0, '(', ')', &nestlen, rflags & ~P_DQUOTE);
+ nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
else if (ch == '{') /* } */
nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags);
else if (ch == '[') /* ] */
@@ -3414,7 +3447,7 @@ xparse_dolparen (base, string, indp, flags)
ostring = string;
sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE;
- if (flags & 0x010) /* EX_NOLONGJMP */
+ if (flags & SX_NOLONGJMP)
sflags |= SEVAL_NOLONGJMP;
save_parser_state (&ps);
@@ -3436,7 +3469,8 @@ xparse_dolparen (base, string, indp, flags)
if (ep[-1] != ')')
{
#if DEBUG
- itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep);
+ if (ep[-1] != '\n')
+ itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep);
#endif
while (ep > ostring && ep[-1] == '\n') ep--;
}
@@ -3450,7 +3484,7 @@ xparse_dolparen (base, string, indp, flags)
itrace("xparse_dolparen:%d: base[%d] != RPAREN (%d), base = `%s'", line_number, *indp, base[*indp], base);
#endif
- if (flags & 1)
+ if (flags & SX_NOALLOC)
return (char *)NULL;
if (nc == 0)
@@ -4017,7 +4051,7 @@ read_token_word (character)
history literally rather than causing a possibly-
incorrect `;' to be added. ) */
push_delimiter (dstack, peek_char);
- ttok = parse_matched_pair (cd, '(', ')', &ttoklen, P_COMMAND);
+ ttok = parse_comsub (cd, '(', ')', &ttoklen, P_COMMAND);
pop_delimiter (dstack);
}
else
@@ -4424,7 +4458,7 @@ history_delimiting_chars ()
{
/* Tricky. `for i\nin ...' should not have a semicolon, but
`for i\ndo ...' should. We do what we can. */
- for (i = shell_input_line_index; whitespace(shell_input_line[i]); i++)
+ for (i = shell_input_line_index; whitespace (shell_input_line[i]); i++)
;
if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n')
return " ";
@@ -4752,6 +4786,7 @@ decode_prompt_string (string)
no longer than PATH_MAX - 1 characters. */
strcpy (t_string, polite_directory_format (t_string));
+ temp = trim_pathname (t_string, PATH_MAX - 1);
/* If we're going to be expanding the prompt string later,
quote the directory name. */
if (promptvars || posixly_correct)
diff --git a/subst.c b/subst.c
index 6cc70a51..e5cc76cc 100644
--- a/subst.c
+++ b/subst.c
@@ -4,7 +4,7 @@
/* ``Have a little faith, there's magic in the night. You ain't a
beauty, but, hey, you're alright.'' */
-/* Copyright (C) 1987-2007 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -69,6 +69,7 @@ extern int errno;
#define VT_POSPARMS 1
#define VT_ARRAYVAR 2
#define VT_ARRAYMEMBER 3
+#define VT_ASSOCVAR 4
#define VT_STARSUB 128 /* $* or ${array[*]} -- used to split */
@@ -78,14 +79,6 @@ extern int errno;
#define ST_SQUOTE 0x04 /* unused yet */
#define ST_DQUOTE 0x08 /* unused yet */
-/* Flags for the string extraction functions. */
-#define SX_NOALLOC 0x01 /* just skip; don't return substring */
-#define SX_VARNAME 0x02 /* variable name; for string_extract () */
-#define SX_REQMATCH 0x04 /* closing/matching delimiter required */
-#define SX_COMMAND 0x08 /* extracting a shell script/command */
-#define SX_NOCTLESC 0x10 /* don't honor CTLESC quoting */
-#define SX_NOESCCTLNUL 0x20 /* don't let CTLESC quote CTLNUL */
-
/* Flags for the `pflags' argument to param_expand() */
#define PF_NOCOMSUB 0x01 /* Do not perform command substitution */
@@ -715,7 +708,7 @@ add_one_character:
si = i + 2;
if (string[i + 1] == LPAREN)
- ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_COMMAND); /*)*/
+ ret = extract_command_subst (string, &si, 0);
else
ret = extract_dollar_brace_string (string, &si, 1, 0);
@@ -817,7 +810,7 @@ skip_double_quoted (string, slen, sind)
{
si = i + 2;
if (string[i + 1] == LPAREN)
- ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+ ret = extract_command_subst (string, &si, SX_NOALLOC);
else
ret = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
@@ -940,6 +933,7 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
i += 2;
continue;
}
+
#if defined (HANDLE_MULTIBYTE)
mblength = MBLEN (string + i, slen - i);
if (mblength > 1)
@@ -987,13 +981,21 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
/* 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 of the matching ")". ) */
+ Make (SINDEX) get the position of the matching ")". )
+ XFLAGS is additional flags to pass to other extraction functions, */
char *
-extract_command_subst (string, sindex)
+extract_command_subst (string, sindex, xflags)
char *string;
int *sindex;
+ int xflags;
{
- return (extract_delimited_string (string, sindex, "$(", "(", ")", SX_COMMAND)); /*)*/
+ if (string[*sindex] == '(') /*)*/
+ return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
+ else
+ {
+ xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+ return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+ }
}
/* Extract the $[ construct in STRING, and return a new string. (])
@@ -1100,7 +1102,7 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
/* Not exactly right yet; should handle shell metacharacters and
multibyte characters, too. */
- if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || whitespace (string[i - 1])))
+ if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
{
in_comment = 1;
ADVANCE_CHAR (string, slen, i);
@@ -1265,7 +1267,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
if (string[i] == '$' && string[i+1] == LPAREN)
{
si = i + 2;
- t = extract_delimited_string (string, &si, "$(", "(", ")", flags|SX_NOALLOC|SX_COMMAND); /*)*/
+ t = extract_command_subst (string, &si, flags|SX_NOALLOC);
i = si + 1;
continue;
}
@@ -1953,6 +1955,55 @@ string_list_dollar_at (list, quoted)
return ret;
}
+/* Turn the positional paramters into a string, understanding quoting and
+ the various subtleties of using the first character of $IFS as the
+ separator. Calls string_list_dollar_at, string_list_dollar_star, and
+ string_list as appropriate. */
+char *
+string_list_pos_params (pchar, list, quoted)
+ int pchar;
+ WORD_LIST *list;
+ int quoted;
+{
+ char *ret;
+ WORD_LIST *tlist;
+
+ if (pchar == '*' && (quoted & Q_DOUBLE_QUOTES))
+ {
+ tlist = quote_list (list);
+ word_list_remove_quoted_nulls (tlist);
+ ret = string_list_dollar_star (tlist);
+ }
+ else if (pchar == '*' && (quoted & Q_HERE_DOCUMENT))
+ {
+ tlist = quote_list (list);
+ word_list_remove_quoted_nulls (tlist);
+ ret = string_list (tlist);
+ }
+ else if (pchar == '*')
+ {
+ /* Even when unquoted, string_list_dollar_star does the right thing
+ making sure that the first character of $IFS is used as the
+ separator. */
+ ret = string_list_dollar_star (list);
+ }
+ else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ /* We use string_list_dollar_at, but only if the string is quoted, since
+ that quotes the escapes if it's not, which we don't want. We could
+ use string_list (the old code did), but that doesn't do the right
+ thing if the first character of $IFS is not a space. We use
+ string_list_dollar_star if the string is unquoted so we make sure that
+ the elements of $@ are separated by the first character of $IFS for
+ later splitting. */
+ ret = string_list_dollar_at (list, quoted);
+ else if (pchar == '@')
+ ret = string_list_dollar_star (list);
+ else
+ ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list);
+
+ return ret;
+}
+
/* 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
@@ -2578,17 +2629,9 @@ pos_params (string, start, end, quoted)
}
t->next = (WORD_LIST *)NULL;
- if (string[0] == '*')
- {
- if (quoted & Q_DOUBLE_QUOTES)
- ret = string_list_dollar_star (quote_list (h));
- else if (quoted & Q_HERE_DOCUMENT)
- ret = string_list (quote_list (h));
- else
- ret = string_list (h);
- }
- else
- ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h);
+
+ ret = string_list_pos_params (string[0], h, quoted);
+
if (t != params)
t->next = params;
@@ -3226,9 +3269,10 @@ quote_list (list)
{
t = w->word->word;
w->word->word = quote_string (t);
- free (t);
+ if (*t == 0)
+ w->word->flags |= W_HASQUOTEDNULL; /* XXX - turn on W_HASQUOTEDNULL here? */
w->word->flags |= W_QUOTED;
- /* XXX - turn on W_HAVEQUOTEDNULL here? */
+ free (t);
}
return list;
}
@@ -3244,9 +3288,10 @@ dequote_list (list)
for (tlist = list; tlist; tlist = tlist->next)
{
s = dequote_string (tlist->word->word);
+ if (QUOTED_NULL (tlist->word->word))
+ tlist->word->flags &= ~W_HASQUOTEDNULL;
free (tlist->word->word);
tlist->word->word = s;
- /* XXX - turn off W_HAVEQUOTEDNULL here? */
}
return list;
}
@@ -3983,12 +4028,9 @@ list_remove_pattern (list, pattern, patspec, itype, quoted)
}
l = REVERSE_LIST (new, WORD_LIST *);
- if (itype == '*')
- tword = (quoted & Q_DOUBLE_QUOTES) ? string_list_dollar_star (l) : string_list (l);
- else
- tword = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l);
-
+ tword = string_list_pos_params (itype, l, quoted);
dispose_words (l);
+
return (tword);
}
@@ -5813,6 +5855,7 @@ pos_params_pat_subst (string, pat, rep, mflags)
WORD_LIST *save, *params;
WORD_DESC *w;
char *ret;
+ int pchar, qflags;
save = params = list_rest_of_args ();
if (save == 0)
@@ -5827,10 +5870,22 @@ pos_params_pat_subst (string, pat, rep, mflags)
params->word = w;
}
+ pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
+ qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+
+#if 0
if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
ret = string_list_dollar_star (quote_list (save));
+ else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB)
+ ret = string_list_dollar_star (save);
+ else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
+ ret = string_list_dollar_at (save, qflags);
else
- ret = string_list ((mflags & MATCH_QUOTED) ? quote_list (save) : save);
+ ret = string_list_dollar_star (save);
+#else
+ ret = string_list_pos_params (pchar, save, qflags);
+#endif
+
dispose_words (save);
return (ret);
@@ -6137,6 +6192,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
temp = (char *)NULL;
goto bad_substitution;
}
+
/* Indirect expansion begins with a `!'. A valid indirect expansion is
either a variable name, one of the positional parameters or a special
variable that expands to one of the positional parameters. */
@@ -6676,7 +6732,7 @@ param_expand (string, sindex, quoted, expanded_something,
case LPAREN:
/* We have to extract the contents of this paren substitution. */
t_index = zindex + 1;
- temp = extract_command_subst (string, &t_index);
+ temp = extract_command_subst (string, &t_index, 0);
zindex = t_index;
/* For Posix.2-style `$(( ))' arithmetic substitution,
@@ -6735,7 +6791,8 @@ comsub:
{
tdesc = command_substitute (temp, quoted);
temp1 = tdesc ? tdesc->word : (char *)NULL;
- dispose_word_desc (tdesc);
+ if (tdesc)
+ dispose_word_desc (tdesc);
}
FREE (temp);
temp = temp1;
@@ -7153,7 +7210,8 @@ add_string:
de_backslash (temp);
tword = command_substitute (temp, quoted);
temp1 = tword ? tword->word : (char *)NULL;
- dispose_word_desc (tword);
+ if (tword)
+ dispose_word_desc (tword);
}
FREE (temp);
temp = temp1;
diff --git a/subst.c.save1 b/subst.c.save1
index 4fcbc60a..75734570 100644
--- a/subst.c.save1
+++ b/subst.c.save1
@@ -4,7 +4,7 @@
/* ``Have a little faith, there's magic in the night. You ain't a
beauty, but, hey, you're alright.'' */
-/* Copyright (C) 1987-2007 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -69,6 +69,7 @@ extern int errno;
#define VT_POSPARMS 1
#define VT_ARRAYVAR 2
#define VT_ARRAYMEMBER 3
+#define VT_ASSOCVAR 4
#define VT_STARSUB 128 /* $* or ${array[*]} -- used to split */
@@ -78,13 +79,6 @@ extern int errno;
#define ST_SQUOTE 0x04 /* unused yet */
#define ST_DQUOTE 0x08 /* unused yet */
-/* Flags for the string extraction functions. */
-#define SX_NOALLOC 0x01 /* just skip; don't return substring */
-#define SX_VARNAME 0x02 /* variable name; for string_extract () */
-#define SX_REQMATCH 0x04 /* closing/matching delimiter required */
-#define SX_COMMAND 0x08 /* extracting a shell script/command */
-#define SX_NOCTLESC 0x10 /* don't honor CTLESC quoting */
-
/* Flags for the `pflags' argument to param_expand() */
#define PF_NOCOMSUB 0x01 /* Do not perform command substitution */
@@ -135,6 +129,8 @@ size_t ifs_firstc_len;
unsigned char ifs_firstc;
#endif
+int assigning_in_environment;
+
/* Extern functions and variables from different files. */
extern int last_command_exit_value, last_command_exit_signal;
extern int subshell_environment;
@@ -206,7 +202,6 @@ static WORD_LIST *list_quote_escapes __P((WORD_LIST *));
static char *dequote_escapes __P((char *));
static char *make_quoted_char __P((int));
static WORD_LIST *quote_list __P((WORD_LIST *));
-static char *remove_quoted_escapes __P((char *));
static char *remove_quoted_nulls __P((char *));
static int unquoted_substring __P((char *, char *));
@@ -713,7 +708,7 @@ add_one_character:
si = i + 2;
if (string[i + 1] == LPAREN)
- ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_COMMAND); /*)*/
+ ret = extract_command_subst (string, &si, 0);
else
ret = extract_dollar_brace_string (string, &si, 1, 0);
@@ -815,7 +810,7 @@ skip_double_quoted (string, slen, sind)
{
si = i + 2;
if (string[i + 1] == LPAREN)
- ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+ ret = extract_command_subst (string, &si, SX_NOALLOC);
else
ret = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
@@ -930,6 +925,14 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
i += 2;
continue;
}
+ /* Even if flags contains SX_NOCTLESC, we let CTLESC quoting CTLNUL
+ through, to protect the CTLNULs from later calls to
+ remove_quoted_nulls. */
+ else if ((flags & SX_NOESCCTLNUL) == 0 && c == CTLESC && string[i+1] == CTLNUL)
+ {
+ i += 2;
+ continue;
+ }
#if defined (HANDLE_MULTIBYTE)
mblength = MBLEN (string + i, slen - i);
@@ -978,13 +981,21 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
/* 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 of the matching ")". ) */
+ Make (SINDEX) get the position of the matching ")". )
+ XFLAGS is additional flags to pass to other extraction functions, */
char *
-extract_command_subst (string, sindex)
+extract_command_subst (string, sindex, xflags)
char *string;
int *sindex;
+ int xflags;
{
- return (extract_delimited_string (string, sindex, "$(", "(", ")", SX_COMMAND)); /*)*/
+ if (string[*sindex] == '(') /*)*/
+ return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
+ else
+ {
+ xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+ return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+ }
}
/* Extract the $[ construct in STRING, and return a new string. (])
@@ -1091,7 +1102,7 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
/* Not exactly right yet; should handle shell metacharacters and
multibyte characters, too. */
- if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || whitespace (string[i - 1])))
+ if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
{
in_comment = 1;
ADVANCE_CHAR (string, slen, i);
@@ -1256,7 +1267,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
if (string[i] == '$' && string[i+1] == LPAREN)
{
si = i + 2;
- t = extract_delimited_string (string, &si, "$(", "(", ")", flags|SX_NOALLOC|SX_COMMAND); /*)*/
+ t = extract_command_subst (string, &si, flags|SX_NOALLOC);
i = si + 1;
continue;
}
@@ -1353,6 +1364,91 @@ unquote_bang (string)
}
#endif
+#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
+
+/* Skip characters in STRING until we find a character in DELIMS, and return
+ the index of that character. START is the index into string at which we
+ begin. This is similar in spirit to strpbrk, but it returns an index into
+ STRING and takes a starting index. This little piece of code knows quite
+ a lot of shell syntax. It's very similar to skip_double_quoted and other
+ functions of that ilk. */
+int
+skip_to_delim (string, start, delims, flags)
+ char *string;
+ int start;
+ char *delims;
+ int flags;
+{
+ int i, pass_next, backq, si, c;
+ size_t slen;
+ char *temp;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string + start) + start;
+ if (flags & SD_NOJMP)
+ no_longjmp_on_fatal_error = 1;
+ i = start;
+ pass_next = backq = 0;
+ while (c = string[i])
+ {
+ if (pass_next)
+ {
+ pass_next = 0;
+ if (c == 0)
+ CQ_RETURN(i);
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if (c == '\\')
+ {
+ pass_next = 1;
+ i++;
+ continue;
+ }
+ else if (backq)
+ {
+ if (c == '`')
+ backq = 0;
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if (c == '`')
+ {
+ backq = 1;
+ i++;
+ continue;
+ }
+ else if (c == '\'' || c == '"')
+ {
+ i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
+ : skip_double_quoted (string, slen, ++i);
+ /* no increment, the skip functions increment past the closing quote. */
+ }
+ else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
+ {
+ si = i + 2;
+ if (string[si] == '\0')
+ CQ_RETURN(si);
+
+ if (string[i+1] == LPAREN)
+ temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+ else
+ temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
+ i = si;
+ if (string[i] == '\0') /* don't increment i past EOS in loop */
+ break;
+ i++;
+ continue;
+ }
+ else if (member (c, delims))
+ break;
+ else
+ ADVANCE_CHAR (string, slen, i);
+ }
+
+ CQ_RETURN(i);
+}
+
#if defined (READLINE)
/* 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
@@ -1362,8 +1458,6 @@ unquote_bang (string)
recognizes need to be the same as the contents of
rl_completer_quote_characters. */
-#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
-
int
char_is_quoted (string, eindex)
char *string;
@@ -1456,87 +1550,6 @@ unclosed_pair (string, eindex, openstr)
return (openc);
}
-/* Skip characters in STRING until we find a character in DELIMS, and return
- the index of that character. START is the index into string at which we
- begin. This is similar in spirit to strpbrk, but it returns an index into
- STRING and takes a starting index. This little piece of code knows quite
- a lot of shell syntax. It's very similar to skip_double_quoted and other
- functions of that ilk. */
-int
-skip_to_delim (string, start, delims)
- char *string;
- int start;
- char *delims;
-{
- int i, pass_next, backq, si, c;
- size_t slen;
- char *temp;
- DECLARE_MBSTATE;
-
- slen = strlen (string + start) + start;
- no_longjmp_on_fatal_error = 1;
- i = start;
- pass_next = backq = 0;
- while (c = string[i])
- {
- if (pass_next)
- {
- pass_next = 0;
- if (c == 0)
- CQ_RETURN(i);
- ADVANCE_CHAR (string, slen, i);
- continue;
- }
- else if (c == '\\')
- {
- pass_next = 1;
- i++;
- continue;
- }
- else if (backq)
- {
- if (c == '`')
- backq = 0;
- ADVANCE_CHAR (string, slen, i);
- continue;
- }
- else if (c == '`')
- {
- backq = 1;
- i++;
- continue;
- }
- else if (c == '\'' || c == '"')
- {
- i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
- : skip_double_quoted (string, slen, ++i);
- /* no increment, the skip functions increment past the closing quote. */
- }
- else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
- {
- si = i + 2;
- if (string[si] == '\0')
- CQ_RETURN(si);
-
- if (string[i+1] == LPAREN)
- temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
- else
- temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
- i = si;
- if (string[i] == '\0') /* don't increment i past EOS in loop */
- break;
- i++;
- continue;
- }
- else if (member (c, delims))
- break;
- else
- ADVANCE_CHAR (string, slen, i);
- }
-
- CQ_RETURN(i);
-}
-
/* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the
individual words. If DELIMS is NULL, the current value of $IFS is used
to split the string, and the function follows the shell field splitting
@@ -1621,7 +1634,7 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
cw = -1;
while (1)
{
- te = skip_to_delim (string, ts, d);
+ te = skip_to_delim (string, ts, d, SD_NOJMP);
/* If we have a non-whitespace delimiter character, use it to make a
separate field. This is just about what $IFS splitting does and
@@ -1787,6 +1800,42 @@ string_list (list)
return (string_list_internal (list, " "));
}
+/* An external interface that can be used by the rest of the shell to
+ obtain a string containing the first character in $IFS. Handles all
+ the multibyte complications. If LENP is non-null, it is set to the
+ length of the returned string. */
+char *
+ifs_firstchar (lenp)
+ int *lenp;
+{
+ char *ret;
+ int len;
+
+ ret = xmalloc (MB_LEN_MAX + 1);
+#if defined (HANDLE_MULTIBYTE)
+ if (ifs_firstc_len == 1)
+ {
+ ret[0] = ifs_firstc[0];
+ ret[1] = '\0';
+ len = ret[0] ? 1 : 0;
+ }
+ else
+ {
+ memcpy (ret, ifs_firstc, ifs_firstc_len);
+ ret[len = ifs_firstc_len] = '\0';
+ }
+#else
+ ret[0] = ifs_firstc;
+ ret[1] = '\0';
+ len = ret[0] ? 0 : 1;
+#endif
+
+ if (lenp)
+ *lenp = len;
+
+ return ret;
+}
+
/* 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
@@ -1906,6 +1955,45 @@ string_list_dollar_at (list, quoted)
return ret;
}
+/* Turn the positional paramters into a string, understanding quoting and
+ the various subtleties of using the first character of $IFS as the
+ separator. Calls string_list_dollar_at, string_list_dollar_star, and
+ string_list as appropriate. */
+char *
+string_list_pos_params (pchar, list, quoted)
+ int pchar;
+ WORD_LIST *list;
+ int quoted;
+{
+ char *ret;
+
+ if (pchar == '*')
+ {
+ if (quoted & Q_DOUBLE_QUOTES)
+ ret = string_list_dollar_star (quote_list (list));
+ else if (quoted & Q_HERE_DOCUMENT)
+ ret = string_list (quote_list (list));
+ else
+ /* Even when unquoted, string_list_dollar_star does the right thing
+ making sure that the first character of $IFS is used as the
+ separator. */
+ ret = string_list_dollar_star (list);
+ }
+ else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+ /* We use string_list_dollar_at, but only if the string is quoted, since
+ that quotes the escapes if it's not, which we don't want. We could
+ use string_list (the old code did), but that doesn't do the right
+ thing if the first character of $IFS is not a space. We use
+ string_list_dollar_star if the string is unquoted so we make sure that
+ the elements of $@ are separated by the first character of $IFS for
+ later splitting. */
+ ret = string_list_dollar_at (list, quoted);
+ else
+ ret = string_list_dollar_star (list);
+
+ return ret;
+}
+
/* 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
@@ -1957,7 +2045,10 @@ list_string (string, separators, quoted)
separators[2] == '\n' &&
separators[3] == '\0';
for (xflags = 0, s = ifs_value; s && *s; s++)
- if (*s == CTLESC) xflags |= SX_NOCTLESC;
+ {
+ if (*s == CTLESC) xflags |= SX_NOCTLESC;
+ else if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+ }
slen = 0;
/* Remove sequences of whitespace at the beginning of STRING, as
@@ -2076,7 +2167,10 @@ get_word_from_string (stringp, separators, endptr)
separators[2] == '\n' &&
separators[3] == '\0';
for (xflags = 0, s = ifs_value; s && *s; s++)
- if (*s == CTLESC) xflags |= SX_NOCTLESC;
+ {
+ if (*s == CTLESC) xflags |= SX_NOCTLESC;
+ if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+ }
s = *stringp;
slen = 0;
@@ -2264,7 +2358,7 @@ do_assignment_internal (word, expand)
const WORD_DESC *word;
int expand;
{
- int offset, tlen, appendop, assign_list, aflags;
+ int offset, tlen, appendop, assign_list, aflags, retval;
char *name, *value;
SHELL_VAR *entry;
#if defined (ARRAY_VARS)
@@ -2355,11 +2449,27 @@ do_assignment_internal (word, expand)
stupidly_hack_special_variables (name);
+#if 1
+ /* Return 1 if the assignment seems to have been performed correctly. */
+ if (entry == 0 || readonly_p (entry))
+ retval = 0; /* assignment failure */
+ else if (noassign_p (entry))
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ retval = 1; /* error status, but not assignment failure */
+ }
+ else
+ retval = 1;
+ ASSIGN_RETURN (retval);
+
+ if (entry && retval != 0)
+ VUNSETATTR (entry, att_invisible);
+#else
if (entry)
VUNSETATTR (entry, att_invisible);
- /* Return 1 if the assignment seems to have been performed correctly. */
ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0);
+#endif
}
/* Perform the assignment statement in STRING, and expand the
@@ -2509,17 +2619,9 @@ pos_params (string, start, end, quoted)
}
t->next = (WORD_LIST *)NULL;
- if (string[0] == '*')
- {
- if (quoted & Q_DOUBLE_QUOTES)
- ret = string_list_dollar_star (quote_list (h));
- else if (quoted & Q_HERE_DOCUMENT)
- ret = string_list (quote_list (h));
- else
- ret = string_list (h);
- }
- else
- ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h);
+
+ ret = string_list_pos_params (string[0], h, quoted);
+
if (t != params)
t->next = params;
@@ -2825,9 +2927,10 @@ expand_string_assignment (string, quoted)
passed string when an error occurs. Might want to trap other calls
to jump_to_top_level here so we don't endlessly loop. */
WORD_LIST *
-expand_prompt_string (string, quoted)
+expand_prompt_string (string, quoted, wflags)
char *string;
int quoted;
+ int wflags;
{
WORD_LIST *value;
WORD_DESC td;
@@ -2835,7 +2938,7 @@ expand_prompt_string (string, quoted)
if (string == 0 || *string == 0)
return ((WORD_LIST *)NULL);
- td.flags = 0;
+ td.flags = wflags;
td.word = savestring (string);
no_longjmp_on_fatal_error = 1;
@@ -2953,7 +3056,7 @@ quote_escapes (string)
register char *s, *t;
size_t slen;
char *result, *send;
- int quote_spaces, skip_ctlesc;
+ int quote_spaces, skip_ctlesc, skip_ctlnul;
DECLARE_MBSTATE;
slen = strlen (string);
@@ -2961,15 +3064,15 @@ quote_escapes (string)
quote_spaces = (ifs_value && *ifs_value == 0);
- for (skip_ctlesc = 0, s = ifs_value; s && *s; s++)
- skip_ctlesc |= *s == CTLESC;
+ for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+ skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
t = result = (char *)xmalloc ((slen * 2) + 1);
s = string;
while (*s)
{
- if ((skip_ctlesc == 0 && *s == CTLESC) || *s == CTLNUL || (quote_spaces && *s == ' '))
+ if ((skip_ctlesc == 0 && *s == CTLESC) || (skip_ctlnul == 0 && *s == CTLNUL) || (quote_spaces && *s == ' '))
*t++ = CTLESC;
COPY_CHAR_P (t, s, send);
}
@@ -3183,7 +3286,7 @@ dequote_list (list)
/* Remove CTLESC protecting a CTLESC or CTLNUL in place. Return the passed
string. */
-static char *
+char *
remove_quoted_escapes (string)
char *string;
{
@@ -3992,7 +4095,11 @@ parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted)
if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
patstr++;
- pattern = getpattern (patstr, quoted, 1);
+ /* Need to pass getpattern newly-allocated memory in case of expansion --
+ the expansion code will free the passed string on an error. */
+ temp1 = savestring (patstr);
+ pattern = getpattern (temp1, quoted, 1);
+ free (temp1);
temp1 = (char *)NULL; /* shut up gcc */
switch (vtype)
@@ -4356,6 +4463,7 @@ process_substitute (string, open_for_read_in_child)
#if defined (JOB_CONTROL)
set_sigchld_handler ();
stop_making_children ();
+ /* XXX - should we only do this in the parent? (as in command subst) */
pipeline_pgrp = old_pipeline_pgrp;
#endif /* JOB_CONTROL */
@@ -4474,14 +4582,14 @@ read_comsub (fd, quoted, rflag)
int *rflag;
{
char *istring, buf[128], *bufp, *s;
- int istring_index, istring_size, c, tflag, skip_ctlesc;
+ int istring_index, istring_size, c, tflag, skip_ctlesc, skip_ctlnul;
ssize_t bufn;
istring = (char *)NULL;
istring_index = istring_size = bufn = tflag = 0;
- for (skip_ctlesc = 0, s = ifs_value; s && *s; s++)
- skip_ctlesc |= *s == CTLESC;
+ for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+ skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
#ifdef __CYGWIN__
setmode (fd, O_TEXT); /* we don't want CR/LF, we want Unix-style */
@@ -4524,7 +4632,7 @@ read_comsub (fd, quoted, rflag)
tflag |= W_HASCTLESC;
istring[istring_index++] = CTLESC;
}
- else if (c == CTLNUL || (c == ' ' && (ifs_value && *ifs_value == 0)))
+ else if ((skip_ctlnul == 0 && c == CTLNUL) || (c == ' ' && (ifs_value && *ifs_value == 0)))
istring[istring_index++] = CTLESC;
istring[istring_index++] = c;
@@ -4645,9 +4753,11 @@ command_substitute (string, quoted)
reset_signal_handlers ();
#if defined (JOB_CONTROL)
+ /* XXX DO THIS ONLY IN PARENT ? XXX */
set_sigchld_handler ();
stop_making_children ();
- pipeline_pgrp = old_pipeline_pgrp;
+ if (pid != 0)
+ pipeline_pgrp = old_pipeline_pgrp;
#else
stop_making_children ();
#endif /* JOB_CONTROL */
@@ -4937,10 +5047,11 @@ parameter_brace_expand_word (name, var_is_special, quoted)
char *temp, *tt;
intmax_t arg_index;
SHELL_VAR *var;
- int atype;
+ int atype, rflags;
ret = 0;
temp = 0;
+ rflags = 0;
/* Handle multiple digit arguments, as in ${11}. */
if (legal_number (name, &arg_index))
@@ -4973,6 +5084,8 @@ parameter_brace_expand_word (name, var_is_special, quoted)
temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
? quote_string (temp)
: quote_escapes (temp);
+ else if (atype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+ rflags |= W_HASQUOTEDNULL;
}
#endif
else if (var = find_variable (name))
@@ -5000,6 +5113,7 @@ parameter_brace_expand_word (name, var_is_special, quoted)
{
ret = alloc_word_desc ();
ret->word = temp;
+ ret->flags |= rflags;
}
return ret;
}
@@ -5749,7 +5863,14 @@ pos_params_pat_subst (string, pat, rep, mflags)
if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
ret = string_list_dollar_star (quote_list (save));
else
+#if 0
ret = string_list ((mflags & MATCH_QUOTED) ? quote_list (save) : save);
+#else
+ if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
+ ret = string_list_dollar_star (quote_list (save));
+ else
+ ret = string_list (save);
+#endif
dispose_words (save);
return (ret);
@@ -5764,7 +5885,7 @@ parameter_brace_patsub (varname, value, patsub, quoted)
char *varname, *value, *patsub;
int quoted;
{
- int vtype, mflags, starsub;
+ int vtype, mflags, starsub, delim;
char *val, *temp, *pat, *rep, *p, *lpatsub, *tt;
SHELL_VAR *v;
@@ -5799,10 +5920,21 @@ parameter_brace_patsub (varname, value, patsub, quoted)
/* If the pattern starts with a `/', make sure we skip over it when looking
for the replacement delimiter. */
+#if 0
if (rep = quoted_strchr ((*patsub == '/') ? lpatsub+1 : lpatsub, '/', ST_BACKSL))
*rep++ = '\0';
else
rep = (char *)NULL;
+#else
+ delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0);
+ if (lpatsub[delim] == '/')
+ {
+ lpatsub[delim] = 0;
+ rep = lpatsub + delim + 1;
+ }
+ else
+ rep = (char *)NULL;
+#endif
if (rep && *rep == '\0')
rep = (char *)NULL;
@@ -5960,7 +6092,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
int t_index, sindex, c, tflag;
intmax_t number;
- value = (char *)NULL;
+ temp = temp1 = value = (char *)NULL;
var_is_set = var_is_null = var_is_special = check_nullness = 0;
want_substring = want_indir = want_patsub = 0;
@@ -6585,7 +6717,7 @@ param_expand (string, sindex, quoted, expanded_something,
case LPAREN:
/* We have to extract the contents of this paren substitution. */
t_index = zindex + 1;
- temp = extract_command_subst (string, &t_index);
+ temp = extract_command_subst (string, &t_index, 0);
zindex = t_index;
/* For Posix.2-style `$(( ))' arithmetic substitution,
@@ -6644,7 +6776,8 @@ comsub:
{
tdesc = command_substitute (temp, quoted);
temp1 = tdesc ? tdesc->word : (char *)NULL;
- dispose_word_desc (tdesc);
+ if (tdesc)
+ dispose_word_desc (tdesc);
}
FREE (temp);
temp = temp1;
@@ -6657,6 +6790,13 @@ comsub:
t_index = zindex + 1;
temp = extract_arithmetic_subst (string, &t_index);
zindex = t_index;
+ if (temp == 0)
+ {
+ temp = savestring (string);
+ if (expanded_something)
+ *expanded_something = 0;
+ goto return0;
+ }
/* Do initial variable expansion. */
temp1 = expand_arith_string (temp, Q_DOUBLE_QUOTES);
@@ -6986,7 +7126,7 @@ add_string:
free (temp);
temp = temp1;
sindex += t_index;
- goto add_string;
+ goto add_quoted_string; /* XXX was add_string */
}
else
{
@@ -7055,7 +7195,8 @@ add_string:
de_backslash (temp);
tword = command_substitute (temp, quoted);
temp1 = tword ? tword->word : (char *)NULL;
- dispose_word_desc (tword);
+ if (tword)
+ dispose_word_desc (tword);
}
FREE (temp);
temp = temp1;
@@ -7483,6 +7624,11 @@ string_quote_removal (string, quoted)
{
case '\\':
c = string[++sindex];
+ if (c == 0)
+ {
+ *r++ = '\\';
+ break;
+ }
if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0)
*r++ = '\\';
/* FALLTHROUGH */
@@ -7683,7 +7829,9 @@ exp_jump_to_top_level (v)
/* Cleanup code goes here. */
expand_no_split_dollar_star = 0; /* XXX */
expanding_redir = 0;
+ assigning_in_environment = 0;
+ top_level_cleanup (); /* from sig.c */
jump_to_top_level (v);
}
@@ -7901,7 +8049,7 @@ glob_expand_word_list (tlist, eflags)
else if (fail_glob_expansion != 0)
{
report_error (_("no match: %s"), tlist->word->word);
- jump_to_top_level (DISCARD);
+ exp_jump_to_top_level (DISCARD);
}
else if (allow_null_glob_expansion == 0)
{
@@ -8183,7 +8331,9 @@ expand_word_list_internal (list, eflags)
for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
{
this_command_name = (char *)NULL;
+ assigning_in_environment = (assign_func == assign_in_env);
tint = (*assign_func) (temp_list->word);
+ assigning_in_environment = 0;
/* Variable assignment errors in non-interactive shells running
in Posix.2 mode cause the shell to exit. */
if (tint == 0)
diff --git a/subst.c.save2 b/subst.c.save2
index 9944f5bc..080da0f8 100644
--- a/subst.c.save2
+++ b/subst.c.save2
@@ -4,7 +4,7 @@
/* ``Have a little faith, there's magic in the night. You ain't a
beauty, but, hey, you're alright.'' */
-/* Copyright (C) 1987-2007 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -69,6 +69,7 @@ extern int errno;
#define VT_POSPARMS 1
#define VT_ARRAYVAR 2
#define VT_ARRAYMEMBER 3
+#define VT_ASSOCVAR 4
#define VT_STARSUB 128 /* $* or ${array[*]} -- used to split */
@@ -78,13 +79,6 @@ extern int errno;
#define ST_SQUOTE 0x04 /* unused yet */
#define ST_DQUOTE 0x08 /* unused yet */
-/* Flags for the string extraction functions. */
-#define SX_NOALLOC 0x01 /* just skip; don't return substring */
-#define SX_VARNAME 0x02 /* variable name; for string_extract () */
-#define SX_REQMATCH 0x04 /* closing/matching delimiter required */
-#define SX_COMMAND 0x08 /* extracting a shell script/command */
-#define SX_NOCTLESC 0x10 /* don't honor CTLESC quoting */
-
/* Flags for the `pflags' argument to param_expand() */
#define PF_NOCOMSUB 0x01 /* Do not perform command substitution */
@@ -135,6 +129,8 @@ size_t ifs_firstc_len;
unsigned char ifs_firstc;
#endif
+int assigning_in_environment;
+
/* Extern functions and variables from different files. */
extern int last_command_exit_value, last_command_exit_signal;
extern int subshell_environment;
@@ -206,7 +202,6 @@ static WORD_LIST *list_quote_escapes __P((WORD_LIST *));
static char *dequote_escapes __P((char *));
static char *make_quoted_char __P((int));
static WORD_LIST *quote_list __P((WORD_LIST *));
-static char *remove_quoted_escapes __P((char *));
static char *remove_quoted_nulls __P((char *));
static int unquoted_substring __P((char *, char *));
@@ -713,7 +708,7 @@ add_one_character:
si = i + 2;
if (string[i + 1] == LPAREN)
- ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_COMMAND); /*)*/
+ ret = extract_command_subst (string, &si, 0);
else
ret = extract_dollar_brace_string (string, &si, 1, 0);
@@ -815,7 +810,7 @@ skip_double_quoted (string, slen, sind)
{
si = i + 2;
if (string[i + 1] == LPAREN)
- ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+ ret = extract_command_subst (string, &si, SX_NOALLOC);
else
ret = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
@@ -933,11 +928,12 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
/* Even if flags contains SX_NOCTLESC, we let CTLESC quoting CTLNUL
through, to protect the CTLNULs from later calls to
remove_quoted_nulls. */
- else if (c == CTLESC && string[i+1] == CTLNUL)
+ else if ((flags & SX_NOESCCTLNUL) == 0 && c == CTLESC && string[i+1] == CTLNUL)
{
i += 2;
continue;
}
+
#if defined (HANDLE_MULTIBYTE)
mblength = MBLEN (string + i, slen - i);
if (mblength > 1)
@@ -985,13 +981,21 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
/* 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 of the matching ")". ) */
+ Make (SINDEX) get the position of the matching ")". )
+ XFLAGS is additional flags to pass to other extraction functions, */
char *
-extract_command_subst (string, sindex)
+extract_command_subst (string, sindex, xflags)
char *string;
int *sindex;
+ int xflags;
{
- return (extract_delimited_string (string, sindex, "$(", "(", ")", SX_COMMAND)); /*)*/
+ if (string[*sindex] == '(') /*)*/
+ return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
+ else
+ {
+ xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+ return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+ }
}
/* Extract the $[ construct in STRING, and return a new string. (])
@@ -1098,7 +1102,7 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
/* Not exactly right yet; should handle shell metacharacters and
multibyte characters, too. */
- if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || whitespace (string[i - 1])))
+ if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
{
in_comment = 1;
ADVANCE_CHAR (string, slen, i);
@@ -1263,7 +1267,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
if (string[i] == '$' && string[i+1] == LPAREN)
{
si = i + 2;
- t = extract_delimited_string (string, &si, "$(", "(", ")", flags|SX_NOALLOC|SX_COMMAND); /*)*/
+ t = extract_command_subst (string, &si, flags|SX_NOALLOC);
i = si + 1;
continue;
}
@@ -1360,6 +1364,91 @@ unquote_bang (string)
}
#endif
+#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
+
+/* Skip characters in STRING until we find a character in DELIMS, and return
+ the index of that character. START is the index into string at which we
+ begin. This is similar in spirit to strpbrk, but it returns an index into
+ STRING and takes a starting index. This little piece of code knows quite
+ a lot of shell syntax. It's very similar to skip_double_quoted and other
+ functions of that ilk. */
+int
+skip_to_delim (string, start, delims, flags)
+ char *string;
+ int start;
+ char *delims;
+ int flags;
+{
+ int i, pass_next, backq, si, c;
+ size_t slen;
+ char *temp;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string + start) + start;
+ if (flags & SD_NOJMP)
+ no_longjmp_on_fatal_error = 1;
+ i = start;
+ pass_next = backq = 0;
+ while (c = string[i])
+ {
+ if (pass_next)
+ {
+ pass_next = 0;
+ if (c == 0)
+ CQ_RETURN(i);
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if (c == '\\')
+ {
+ pass_next = 1;
+ i++;
+ continue;
+ }
+ else if (backq)
+ {
+ if (c == '`')
+ backq = 0;
+ ADVANCE_CHAR (string, slen, i);
+ continue;
+ }
+ else if (c == '`')
+ {
+ backq = 1;
+ i++;
+ continue;
+ }
+ else if (c == '\'' || c == '"')
+ {
+ i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
+ : skip_double_quoted (string, slen, ++i);
+ /* no increment, the skip functions increment past the closing quote. */
+ }
+ else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
+ {
+ si = i + 2;
+ if (string[si] == '\0')
+ CQ_RETURN(si);
+
+ if (string[i+1] == LPAREN)
+ temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+ else
+ temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
+ i = si;
+ if (string[i] == '\0') /* don't increment i past EOS in loop */
+ break;
+ i++;
+ continue;
+ }
+ else if (member (c, delims))
+ break;
+ else
+ ADVANCE_CHAR (string, slen, i);
+ }
+
+ CQ_RETURN(i);
+}
+
#if defined (READLINE)
/* 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
@@ -1369,8 +1458,6 @@ unquote_bang (string)
recognizes need to be the same as the contents of
rl_completer_quote_characters. */
-#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
-
int
char_is_quoted (string, eindex)
char *string;
@@ -1463,87 +1550,6 @@ unclosed_pair (string, eindex, openstr)
return (openc);
}
-/* Skip characters in STRING until we find a character in DELIMS, and return
- the index of that character. START is the index into string at which we
- begin. This is similar in spirit to strpbrk, but it returns an index into
- STRING and takes a starting index. This little piece of code knows quite
- a lot of shell syntax. It's very similar to skip_double_quoted and other
- functions of that ilk. */
-int
-skip_to_delim (string, start, delims)
- char *string;
- int start;
- char *delims;
-{
- int i, pass_next, backq, si, c;
- size_t slen;
- char *temp;
- DECLARE_MBSTATE;
-
- slen = strlen (string + start) + start;
- no_longjmp_on_fatal_error = 1;
- i = start;
- pass_next = backq = 0;
- while (c = string[i])
- {
- if (pass_next)
- {
- pass_next = 0;
- if (c == 0)
- CQ_RETURN(i);
- ADVANCE_CHAR (string, slen, i);
- continue;
- }
- else if (c == '\\')
- {
- pass_next = 1;
- i++;
- continue;
- }
- else if (backq)
- {
- if (c == '`')
- backq = 0;
- ADVANCE_CHAR (string, slen, i);
- continue;
- }
- else if (c == '`')
- {
- backq = 1;
- i++;
- continue;
- }
- else if (c == '\'' || c == '"')
- {
- i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
- : skip_double_quoted (string, slen, ++i);
- /* no increment, the skip functions increment past the closing quote. */
- }
- else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
- {
- si = i + 2;
- if (string[si] == '\0')
- CQ_RETURN(si);
-
- if (string[i+1] == LPAREN)
- temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
- else
- temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
- i = si;
- if (string[i] == '\0') /* don't increment i past EOS in loop */
- break;
- i++;
- continue;
- }
- else if (member (c, delims))
- break;
- else
- ADVANCE_CHAR (string, slen, i);
- }
-
- CQ_RETURN(i);
-}
-
/* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the
individual words. If DELIMS is NULL, the current value of $IFS is used
to split the string, and the function follows the shell field splitting
@@ -1628,7 +1634,7 @@ split_at_delims (string, slen, delims, sentinel, nwp, cwp)
cw = -1;
while (1)
{
- te = skip_to_delim (string, ts, d);
+ te = skip_to_delim (string, ts, d, SD_NOJMP);
/* If we have a non-whitespace delimiter character, use it to make a
separate field. This is just about what $IFS splitting does and
@@ -1794,6 +1800,42 @@ string_list (list)
return (string_list_internal (list, " "));
}
+/* An external interface that can be used by the rest of the shell to
+ obtain a string containing the first character in $IFS. Handles all
+ the multibyte complications. If LENP is non-null, it is set to the
+ length of the returned string. */
+char *
+ifs_firstchar (lenp)
+ int *lenp;
+{
+ char *ret;
+ int len;
+
+ ret = xmalloc (MB_LEN_MAX + 1);
+#if defined (HANDLE_MULTIBYTE)
+ if (ifs_firstc_len == 1)
+ {
+ ret[0] = ifs_firstc[0];
+ ret[1] = '\0';
+ len = ret[0] ? 1 : 0;
+ }
+ else
+ {
+ memcpy (ret, ifs_firstc, ifs_firstc_len);
+ ret[len = ifs_firstc_len] = '\0';
+ }
+#else
+ ret[0] = ifs_firstc;
+ ret[1] = '\0';
+ len = ret[0] ? 0 : 1;
+#endif
+
+ if (lenp)
+ *lenp = len;
+
+ return ret;
+}
+
/* 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
@@ -1913,6 +1955,47 @@ string_list_dollar_at (list, quoted)
return ret;
}
+/* Turn the positional paramters into a string, understanding quoting and
+ the various subtleties of using the first character of $IFS as the
+ separator. Calls string_list_dollar_at, string_list_dollar_star, and
+ string_list as appropriate. */
+char *
+string_list_pos_params (pchar, list, quoted)
+ int pchar;
+ WORD_LIST *list;
+ int quoted;
+{
+ char *ret;
+
+ if (pchar == '*')
+ {
+ if (quoted & Q_DOUBLE_QUOTES)
+ ret = string_list_dollar_star (quote_list (list));
+ else if (quoted & Q_HERE_DOCUMENT)
+ ret = string_list (quote_list (list));
+ else
+ /* Even when unquoted, string_list_dollar_star does the right thing
+ making sure that the first character of $IFS is used as the
+ separator. */
+ ret = string_list_dollar_star (list);
+ }
+ else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ /* We use string_list_dollar_at, but only if the string is quoted, since
+ that quotes the escapes if it's not, which we don't want. We could
+ use string_list (the old code did), but that doesn't do the right
+ thing if the first character of $IFS is not a space. We use
+ string_list_dollar_star if the string is unquoted so we make sure that
+ the elements of $@ are separated by the first character of $IFS for
+ later splitting. */
+ ret = string_list_dollar_at (list, quoted);
+ else if (pchar == '@')
+ ret = string_list_dollar_star (list);
+ else
+ ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list);
+
+ return ret;
+}
+
/* 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
@@ -1964,7 +2047,10 @@ list_string (string, separators, quoted)
separators[2] == '\n' &&
separators[3] == '\0';
for (xflags = 0, s = ifs_value; s && *s; s++)
- if (*s == CTLESC) xflags |= SX_NOCTLESC;
+ {
+ if (*s == CTLESC) xflags |= SX_NOCTLESC;
+ else if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+ }
slen = 0;
/* Remove sequences of whitespace at the beginning of STRING, as
@@ -2083,7 +2169,10 @@ get_word_from_string (stringp, separators, endptr)
separators[2] == '\n' &&
separators[3] == '\0';
for (xflags = 0, s = ifs_value; s && *s; s++)
- if (*s == CTLESC) xflags |= SX_NOCTLESC;
+ {
+ if (*s == CTLESC) xflags |= SX_NOCTLESC;
+ if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+ }
s = *stringp;
slen = 0;
@@ -2271,7 +2360,7 @@ do_assignment_internal (word, expand)
const WORD_DESC *word;
int expand;
{
- int offset, tlen, appendop, assign_list, aflags;
+ int offset, tlen, appendop, assign_list, aflags, retval;
char *name, *value;
SHELL_VAR *entry;
#if defined (ARRAY_VARS)
@@ -2362,11 +2451,27 @@ do_assignment_internal (word, expand)
stupidly_hack_special_variables (name);
+#if 1
+ /* Return 1 if the assignment seems to have been performed correctly. */
+ if (entry == 0 || readonly_p (entry))
+ retval = 0; /* assignment failure */
+ else if (noassign_p (entry))
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ retval = 1; /* error status, but not assignment failure */
+ }
+ else
+ retval = 1;
+ ASSIGN_RETURN (retval);
+
+ if (entry && retval != 0)
+ VUNSETATTR (entry, att_invisible);
+#else
if (entry)
VUNSETATTR (entry, att_invisible);
- /* Return 1 if the assignment seems to have been performed correctly. */
ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0);
+#endif
}
/* Perform the assignment statement in STRING, and expand the
@@ -2516,17 +2621,9 @@ pos_params (string, start, end, quoted)
}
t->next = (WORD_LIST *)NULL;
- if (string[0] == '*')
- {
- if (quoted & Q_DOUBLE_QUOTES)
- ret = string_list_dollar_star (quote_list (h));
- else if (quoted & Q_HERE_DOCUMENT)
- ret = string_list (quote_list (h));
- else
- ret = string_list (h);
- }
- else
- ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h);
+
+ ret = string_list_pos_params (string[0], h, quoted);
+
if (t != params)
t->next = params;
@@ -2832,9 +2929,10 @@ expand_string_assignment (string, quoted)
passed string when an error occurs. Might want to trap other calls
to jump_to_top_level here so we don't endlessly loop. */
WORD_LIST *
-expand_prompt_string (string, quoted)
+expand_prompt_string (string, quoted, wflags)
char *string;
int quoted;
+ int wflags;
{
WORD_LIST *value;
WORD_DESC td;
@@ -2842,7 +2940,7 @@ expand_prompt_string (string, quoted)
if (string == 0 || *string == 0)
return ((WORD_LIST *)NULL);
- td.flags = 0;
+ td.flags = wflags;
td.word = savestring (string);
no_longjmp_on_fatal_error = 1;
@@ -2960,7 +3058,7 @@ quote_escapes (string)
register char *s, *t;
size_t slen;
char *result, *send;
- int quote_spaces, skip_ctlesc;
+ int quote_spaces, skip_ctlesc, skip_ctlnul;
DECLARE_MBSTATE;
slen = strlen (string);
@@ -2968,15 +3066,15 @@ quote_escapes (string)
quote_spaces = (ifs_value && *ifs_value == 0);
- for (skip_ctlesc = 0, s = ifs_value; s && *s; s++)
- skip_ctlesc |= *s == CTLESC;
+ for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+ skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
t = result = (char *)xmalloc ((slen * 2) + 1);
s = string;
while (*s)
{
- if ((skip_ctlesc == 0 && *s == CTLESC) || *s == CTLNUL || (quote_spaces && *s == ' '))
+ if ((skip_ctlesc == 0 && *s == CTLESC) || (skip_ctlnul == 0 && *s == CTLNUL) || (quote_spaces && *s == ' '))
*t++ = CTLESC;
COPY_CHAR_P (t, s, send);
}
@@ -3190,7 +3288,7 @@ dequote_list (list)
/* Remove CTLESC protecting a CTLESC or CTLNUL in place. Return the passed
string. */
-static char *
+char *
remove_quoted_escapes (string)
char *string;
{
@@ -3920,12 +4018,9 @@ list_remove_pattern (list, pattern, patspec, itype, quoted)
}
l = REVERSE_LIST (new, WORD_LIST *);
- if (itype == '*')
- tword = (quoted & Q_DOUBLE_QUOTES) ? string_list_dollar_star (l) : string_list (l);
- else
- tword = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l);
-
+ tword = string_list_pos_params (itype, l, quoted);
dispose_words (l);
+
return (tword);
}
@@ -3999,7 +4094,11 @@ parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted)
if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
patstr++;
- pattern = getpattern (patstr, quoted, 1);
+ /* Need to pass getpattern newly-allocated memory in case of expansion --
+ the expansion code will free the passed string on an error. */
+ temp1 = savestring (patstr);
+ pattern = getpattern (temp1, quoted, 1);
+ free (temp1);
temp1 = (char *)NULL; /* shut up gcc */
switch (vtype)
@@ -4363,6 +4462,7 @@ process_substitute (string, open_for_read_in_child)
#if defined (JOB_CONTROL)
set_sigchld_handler ();
stop_making_children ();
+ /* XXX - should we only do this in the parent? (as in command subst) */
pipeline_pgrp = old_pipeline_pgrp;
#endif /* JOB_CONTROL */
@@ -4481,14 +4581,14 @@ read_comsub (fd, quoted, rflag)
int *rflag;
{
char *istring, buf[128], *bufp, *s;
- int istring_index, istring_size, c, tflag, skip_ctlesc;
+ int istring_index, istring_size, c, tflag, skip_ctlesc, skip_ctlnul;
ssize_t bufn;
istring = (char *)NULL;
istring_index = istring_size = bufn = tflag = 0;
- for (skip_ctlesc = 0, s = ifs_value; s && *s; s++)
- skip_ctlesc |= *s == CTLESC;
+ for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+ skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
#ifdef __CYGWIN__
setmode (fd, O_TEXT); /* we don't want CR/LF, we want Unix-style */
@@ -4531,7 +4631,7 @@ read_comsub (fd, quoted, rflag)
tflag |= W_HASCTLESC;
istring[istring_index++] = CTLESC;
}
- else if (c == CTLNUL || (c == ' ' && (ifs_value && *ifs_value == 0)))
+ else if ((skip_ctlnul == 0 && c == CTLNUL) || (c == ' ' && (ifs_value && *ifs_value == 0)))
istring[istring_index++] = CTLESC;
istring[istring_index++] = c;
@@ -4652,9 +4752,11 @@ command_substitute (string, quoted)
reset_signal_handlers ();
#if defined (JOB_CONTROL)
+ /* XXX DO THIS ONLY IN PARENT ? XXX */
set_sigchld_handler ();
stop_making_children ();
- pipeline_pgrp = old_pipeline_pgrp;
+ if (pid != 0)
+ pipeline_pgrp = old_pipeline_pgrp;
#else
stop_making_children ();
#endif /* JOB_CONTROL */
@@ -4944,10 +5046,11 @@ parameter_brace_expand_word (name, var_is_special, quoted)
char *temp, *tt;
intmax_t arg_index;
SHELL_VAR *var;
- int atype;
+ int atype, rflags;
ret = 0;
temp = 0;
+ rflags = 0;
/* Handle multiple digit arguments, as in ${11}. */
if (legal_number (name, &arg_index))
@@ -4980,6 +5083,8 @@ parameter_brace_expand_word (name, var_is_special, quoted)
temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
? quote_string (temp)
: quote_escapes (temp);
+ else if (atype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+ rflags |= W_HASQUOTEDNULL;
}
#endif
else if (var = find_variable (name))
@@ -5007,6 +5112,7 @@ parameter_brace_expand_word (name, var_is_special, quoted)
{
ret = alloc_word_desc ();
ret->word = temp;
+ ret->flags |= rflags;
}
return ret;
}
@@ -5739,6 +5845,7 @@ pos_params_pat_subst (string, pat, rep, mflags)
WORD_LIST *save, *params;
WORD_DESC *w;
char *ret;
+ int pchar;
save = params = list_rest_of_args ();
if (save == 0)
@@ -5755,8 +5862,11 @@ pos_params_pat_subst (string, pat, rep, mflags)
if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
ret = string_list_dollar_star (quote_list (save));
+ else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
+ ret = string_list_dollar_star (quote_list (save));
else
- ret = string_list ((mflags & MATCH_QUOTED) ? quote_list (save) : save);
+ ret = string_list_dollar_star (save);
+
dispose_words (save);
return (ret);
@@ -5771,7 +5881,7 @@ parameter_brace_patsub (varname, value, patsub, quoted)
char *varname, *value, *patsub;
int quoted;
{
- int vtype, mflags, starsub;
+ int vtype, mflags, starsub, delim;
char *val, *temp, *pat, *rep, *p, *lpatsub, *tt;
SHELL_VAR *v;
@@ -5806,10 +5916,21 @@ parameter_brace_patsub (varname, value, patsub, quoted)
/* If the pattern starts with a `/', make sure we skip over it when looking
for the replacement delimiter. */
+#if 0
if (rep = quoted_strchr ((*patsub == '/') ? lpatsub+1 : lpatsub, '/', ST_BACKSL))
*rep++ = '\0';
else
rep = (char *)NULL;
+#else
+ delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0);
+ if (lpatsub[delim] == '/')
+ {
+ lpatsub[delim] = 0;
+ rep = lpatsub + delim + 1;
+ }
+ else
+ rep = (char *)NULL;
+#endif
if (rep && *rep == '\0')
rep = (char *)NULL;
@@ -5967,7 +6088,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
int t_index, sindex, c, tflag;
intmax_t number;
- value = (char *)NULL;
+ temp = temp1 = value = (char *)NULL;
var_is_set = var_is_null = var_is_special = check_nullness = 0;
want_substring = want_indir = want_patsub = 0;
@@ -6592,7 +6713,7 @@ param_expand (string, sindex, quoted, expanded_something,
case LPAREN:
/* We have to extract the contents of this paren substitution. */
t_index = zindex + 1;
- temp = extract_command_subst (string, &t_index);
+ temp = extract_command_subst (string, &t_index, 0);
zindex = t_index;
/* For Posix.2-style `$(( ))' arithmetic substitution,
@@ -6651,7 +6772,8 @@ comsub:
{
tdesc = command_substitute (temp, quoted);
temp1 = tdesc ? tdesc->word : (char *)NULL;
- dispose_word_desc (tdesc);
+ if (tdesc)
+ dispose_word_desc (tdesc);
}
FREE (temp);
temp = temp1;
@@ -6664,6 +6786,13 @@ comsub:
t_index = zindex + 1;
temp = extract_arithmetic_subst (string, &t_index);
zindex = t_index;
+ if (temp == 0)
+ {
+ temp = savestring (string);
+ if (expanded_something)
+ *expanded_something = 0;
+ goto return0;
+ }
/* Do initial variable expansion. */
temp1 = expand_arith_string (temp, Q_DOUBLE_QUOTES);
@@ -6993,7 +7122,7 @@ add_string:
free (temp);
temp = temp1;
sindex += t_index;
- goto add_string;
+ goto add_quoted_string; /* XXX was add_string */
}
else
{
@@ -7062,7 +7191,8 @@ add_string:
de_backslash (temp);
tword = command_substitute (temp, quoted);
temp1 = tword ? tword->word : (char *)NULL;
- dispose_word_desc (tword);
+ if (tword)
+ dispose_word_desc (tword);
}
FREE (temp);
temp = temp1;
@@ -7490,6 +7620,11 @@ string_quote_removal (string, quoted)
{
case '\\':
c = string[++sindex];
+ if (c == 0)
+ {
+ *r++ = '\\';
+ break;
+ }
if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0)
*r++ = '\\';
/* FALLTHROUGH */
@@ -7690,7 +7825,9 @@ exp_jump_to_top_level (v)
/* Cleanup code goes here. */
expand_no_split_dollar_star = 0; /* XXX */
expanding_redir = 0;
+ assigning_in_environment = 0;
+ top_level_cleanup (); /* from sig.c */
jump_to_top_level (v);
}
@@ -7908,7 +8045,7 @@ glob_expand_word_list (tlist, eflags)
else if (fail_glob_expansion != 0)
{
report_error (_("no match: %s"), tlist->word->word);
- jump_to_top_level (DISCARD);
+ exp_jump_to_top_level (DISCARD);
}
else if (allow_null_glob_expansion == 0)
{
@@ -8190,7 +8327,9 @@ expand_word_list_internal (list, eflags)
for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
{
this_command_name = (char *)NULL;
+ assigning_in_environment = (assign_func == assign_in_env);
tint = (*assign_func) (temp_list->word);
+ assigning_in_environment = 0;
/* Variable assignment errors in non-interactive shells running
in Posix.2 mode cause the shell to exit. */
if (tint == 0)
diff --git a/subst.c~ b/subst.c~
index bd935218..71ddc8fb 100644
--- a/subst.c~
+++ b/subst.c~
@@ -4,7 +4,7 @@
/* ``Have a little faith, there's magic in the night. You ain't a
beauty, but, hey, you're alright.'' */
-/* Copyright (C) 1987-2007 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -69,6 +69,7 @@ extern int errno;
#define VT_POSPARMS 1
#define VT_ARRAYVAR 2
#define VT_ARRAYMEMBER 3
+#define VT_ASSOCVAR 4
#define VT_STARSUB 128 /* $* or ${array[*]} -- used to split */
@@ -78,14 +79,6 @@ extern int errno;
#define ST_SQUOTE 0x04 /* unused yet */
#define ST_DQUOTE 0x08 /* unused yet */
-/* Flags for the string extraction functions. */
-#define SX_NOALLOC 0x01 /* just skip; don't return substring */
-#define SX_VARNAME 0x02 /* variable name; for string_extract () */
-#define SX_REQMATCH 0x04 /* closing/matching delimiter required */
-#define SX_COMMAND 0x08 /* extracting a shell script/command */
-#define SX_NOCTLESC 0x10 /* don't honor CTLESC quoting */
-#define SX_NOESCCTLNUL 0x20 /* don't let CTLESC quote CTLNUL */
-
/* Flags for the `pflags' argument to param_expand() */
#define PF_NOCOMSUB 0x01 /* Do not perform command substitution */
@@ -715,7 +708,7 @@ add_one_character:
si = i + 2;
if (string[i + 1] == LPAREN)
- ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_COMMAND); /*)*/
+ ret = extract_command_subst (string, &si, 0);
else
ret = extract_dollar_brace_string (string, &si, 1, 0);
@@ -817,7 +810,7 @@ skip_double_quoted (string, slen, sind)
{
si = i + 2;
if (string[i + 1] == LPAREN)
- ret = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+ ret = extract_command_subst (string, &si, SX_NOALLOC);
else
ret = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
@@ -940,6 +933,7 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
i += 2;
continue;
}
+
#if defined (HANDLE_MULTIBYTE)
mblength = MBLEN (string + i, slen - i);
if (mblength > 1)
@@ -987,13 +981,21 @@ string_extract_verbatim (string, slen, sindex, charlist, flags)
/* 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 of the matching ")". ) */
+ Make (SINDEX) get the position of the matching ")". )
+ XFLAGS is additional flags to pass to other extraction functions, */
char *
-extract_command_subst (string, sindex)
+extract_command_subst (string, sindex, xflags)
char *string;
int *sindex;
+ int xflags;
{
- return (extract_delimited_string (string, sindex, "$(", "(", ")", SX_COMMAND)); /*)*/
+ if (string[*sindex] == '(') /*)*/
+ return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
+ else
+ {
+ xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+ return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+ }
}
/* Extract the $[ construct in STRING, and return a new string. (])
@@ -1100,7 +1102,7 @@ extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
/* Not exactly right yet; should handle shell metacharacters and
multibyte characters, too. */
- if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || whitespace (string[i - 1])))
+ if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
{
in_comment = 1;
ADVANCE_CHAR (string, slen, i);
@@ -1265,7 +1267,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
if (string[i] == '$' && string[i+1] == LPAREN)
{
si = i + 2;
- t = extract_delimited_string (string, &si, "$(", "(", ")", flags|SX_NOALLOC|SX_COMMAND); /*)*/
+ t = extract_command_subst (string, &si, flags|SX_NOALLOC);
i = si + 1;
continue;
}
@@ -1953,6 +1955,55 @@ string_list_dollar_at (list, quoted)
return ret;
}
+/* Turn the positional paramters into a string, understanding quoting and
+ the various subtleties of using the first character of $IFS as the
+ separator. Calls string_list_dollar_at, string_list_dollar_star, and
+ string_list as appropriate. */
+char *
+string_list_pos_params (pchar, list, quoted)
+ int pchar;
+ WORD_LIST *list;
+ int quoted;
+{
+ char *ret;
+ WORD_LIST *tlist;
+
+ if (pchar == '*' && (quoted & Q_DOUBLE_QUOTES))
+ {
+ tlist = quote_list (list);
+ word_list_remove_quoted_nulls (tlist);
+ ret = string_list_dollar_star (tlist);
+ }
+ else if (pchar == '*' && (quoted & Q_HERE_DOCUMENT))
+ {
+ tlist = quote_list (list);
+ word_list_remove_quoted_nulls (tlist);
+ ret = string_list (tlist);
+ }
+ else if (pchar == '*')
+ {
+ /* Even when unquoted, string_list_dollar_star does the right thing
+ making sure that the first character of $IFS is used as the
+ separator. */
+ ret = string_list_dollar_star (list);
+ }
+ else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ /* We use string_list_dollar_at, but only if the string is quoted, since
+ that quotes the escapes if it's not, which we don't want. We could
+ use string_list (the old code did), but that doesn't do the right
+ thing if the first character of $IFS is not a space. We use
+ string_list_dollar_star if the string is unquoted so we make sure that
+ the elements of $@ are separated by the first character of $IFS for
+ later splitting. */
+ ret = string_list_dollar_at (list, quoted);
+ else if (pchar == '@')
+ ret = string_list_dollar_star (list);
+ else
+ ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list);
+
+ return ret;
+}
+
/* 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
@@ -2578,17 +2629,9 @@ pos_params (string, start, end, quoted)
}
t->next = (WORD_LIST *)NULL;
- if (string[0] == '*')
- {
- if (quoted & Q_DOUBLE_QUOTES)
- ret = string_list_dollar_star (quote_list (h));
- else if (quoted & Q_HERE_DOCUMENT)
- ret = string_list (quote_list (h));
- else
- ret = string_list (h);
- }
- else
- ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h);
+
+ ret = string_list_pos_params (string[0], h, quoted);
+
if (t != params)
t->next = params;
@@ -2894,9 +2937,10 @@ expand_string_assignment (string, quoted)
passed string when an error occurs. Might want to trap other calls
to jump_to_top_level here so we don't endlessly loop. */
WORD_LIST *
-expand_prompt_string (string, quoted)
+expand_prompt_string (string, quoted, wflags)
char *string;
int quoted;
+ int wflags;
{
WORD_LIST *value;
WORD_DESC td;
@@ -2904,7 +2948,7 @@ expand_prompt_string (string, quoted)
if (string == 0 || *string == 0)
return ((WORD_LIST *)NULL);
- td.flags = 0;
+ td.flags = wflags;
td.word = savestring (string);
no_longjmp_on_fatal_error = 1;
@@ -3225,9 +3269,10 @@ quote_list (list)
{
t = w->word->word;
w->word->word = quote_string (t);
- free (t);
+ if (*t == 0)
+ w->word->flags |= W_HASQUOTEDNULL; /* XXX - turn on W_HASQUOTEDNULL here? */
w->word->flags |= W_QUOTED;
- /* XXX - turn on W_HAVEQUOTEDNULL here? */
+ free (t);
}
return list;
}
@@ -3243,9 +3288,10 @@ dequote_list (list)
for (tlist = list; tlist; tlist = tlist->next)
{
s = dequote_string (tlist->word->word);
+ if (QUOTED_NULL (tlist->word->word))
+ tlist->word->flags &= ~W_HASQUOTEDNULL;
free (tlist->word->word);
tlist->word->word = s;
- /* XXX - turn off W_HAVEQUOTEDNULL here? */
}
return list;
}
@@ -3982,12 +4028,22 @@ list_remove_pattern (list, pattern, patspec, itype, quoted)
}
l = REVERSE_LIST (new, WORD_LIST *);
- if (itype == '*')
- tword = (quoted & Q_DOUBLE_QUOTES) ? string_list_dollar_star (l) : string_list (l);
+#if 0
+ if (itype == '*' && (quoted & Q_DOUBLE_QUOTES))
+ tword = string_list_dollar_star (quote_list (l));
+ else if (itype == '*')
+ tword = string_list_dollar_star (l);
+ else if (itype == '@' && (quoted & Q_DOUBLE_QUOTES))
+ tword = string_list_dollar_at (l, quoted);
+ else if (itype == '@')
+ tword = string_list_dollar_star (l);
else
- tword = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l);
-
+ tword = string_list (l);
+#else
+ tword = string_list_pos_params (itype, l, quoted);
+#endif
dispose_words (l);
+
return (tword);
}
@@ -5812,6 +5868,7 @@ pos_params_pat_subst (string, pat, rep, mflags)
WORD_LIST *save, *params;
WORD_DESC *w;
char *ret;
+ int pchar, qflags;
save = params = list_rest_of_args ();
if (save == 0)
@@ -5826,10 +5883,22 @@ pos_params_pat_subst (string, pat, rep, mflags)
params->word = w;
}
+ pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
+ qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+
+#if 0
if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
ret = string_list_dollar_star (quote_list (save));
+ else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB)
+ ret = string_list_dollar_star (save);
+ else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
+ ret = string_list_dollar_at (save, qflags);
else
- ret = string_list ((mflags & MATCH_QUOTED) ? quote_list (save) : save);
+ ret = string_list_dollar_star (save);
+#else
+ ret = string_list_pos_params (pchar, save, qflags);
+#endif
+
dispose_words (save);
return (ret);
@@ -6136,6 +6205,7 @@ parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_doll
temp = (char *)NULL;
goto bad_substitution;
}
+
/* Indirect expansion begins with a `!'. A valid indirect expansion is
either a variable name, one of the positional parameters or a special
variable that expands to one of the positional parameters. */
@@ -6675,7 +6745,7 @@ param_expand (string, sindex, quoted, expanded_something,
case LPAREN:
/* We have to extract the contents of this paren substitution. */
t_index = zindex + 1;
- temp = extract_command_subst (string, &t_index);
+ temp = extract_command_subst (string, &t_index, 0);
zindex = t_index;
/* For Posix.2-style `$(( ))' arithmetic substitution,
@@ -6734,7 +6804,8 @@ comsub:
{
tdesc = command_substitute (temp, quoted);
temp1 = tdesc ? tdesc->word : (char *)NULL;
- dispose_word_desc (tdesc);
+ if (tdesc)
+ dispose_word_desc (tdesc);
}
FREE (temp);
temp = temp1;
@@ -7152,7 +7223,8 @@ add_string:
de_backslash (temp);
tword = command_substitute (temp, quoted);
temp1 = tword ? tword->word : (char *)NULL;
- dispose_word_desc (tword);
+ if (tword)
+ dispose_word_desc (tword);
}
FREE (temp);
temp = temp1;
@@ -7580,6 +7652,11 @@ string_quote_removal (string, quoted)
{
case '\\':
c = string[++sindex];
+ if (c == 0)
+ {
+ *r++ = '\\';
+ break;
+ }
if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0)
*r++ = '\\';
/* FALLTHROUGH */
diff --git a/subst.h b/subst.h
index fc07a823..6f30b43d 100644
--- a/subst.h
+++ b/subst.h
@@ -43,6 +43,15 @@
#define ASS_APPEND 0x01
#define ASS_MKLOCAL 0x02
+/* Flags for the string extraction functions. */
+#define SX_NOALLOC 0x01 /* just skip; don't return substring */
+#define SX_VARNAME 0x02 /* variable name; for string_extract () */
+#define SX_REQMATCH 0x04 /* closing/matching delimiter required */
+#define SX_COMMAND 0x08 /* extracting a shell script/command */
+#define SX_NOCTLESC 0x10 /* don't honor CTLESC quoting */
+#define SX_NOESCCTLNUL 0x20 /* don't let CTLESC quote CTLNUL */
+#define SX_NOLONGJMP 0x40 /* don't longjmp on fatal error */
+
/* Remove backslashes which are quoting backquotes from STRING. Modifies
STRING, and returns a pointer to it. */
extern char * de_backslash __P((char *));
@@ -52,8 +61,9 @@ extern void unquote_bang __P((char *));
/* 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 ")". */
-extern char *extract_command_subst __P((char *, int *));
+ Make (SINDEX) get the position just after the matching ")".
+ XFLAGS is additional flags to pass to other extraction functions, */
+extern char *extract_command_subst __P((char *, int *, int));
/* Extract the $[ construct in STRING, and return a new string.
Start extracting at (SINDEX) as if we had just seen "$[".
@@ -84,6 +94,12 @@ extern char *string_list_dollar_star __P((WORD_LIST *));
/* Expand $@ into a single string, obeying POSIX rules. */
extern char *string_list_dollar_at __P((WORD_LIST *, int));
+/* Turn the positional paramters into a string, understanding quoting and
+ the various subtleties of using the first character of $IFS as the
+ separator. Calls string_list_dollar_at, string_list_dollar_star, and
+ string_list as appropriate. */
+extern char *string_list_pos_params __P((int, WORD_LIST *, int));
+
/* Perform quoted null character removal on each element of LIST.
This modifies LIST. */
extern void word_list_remove_quoted_nulls __P((WORD_LIST *));
diff --git a/subst.h~ b/subst.h~
index f0e417fa..1093ee25 100644
--- a/subst.h~
+++ b/subst.h~
@@ -43,6 +43,15 @@
#define ASS_APPEND 0x01
#define ASS_MKLOCAL 0x02
+/* Flags for the string extraction functions. */
+#define SX_NOALLOC 0x01 /* just skip; don't return substring */
+#define SX_VARNAME 0x02 /* variable name; for string_extract () */
+#define SX_REQMATCH 0x04 /* closing/matching delimiter required */
+#define SX_COMMAND 0x08 /* extracting a shell script/command */
+#define SX_NOCTLESC 0x10 /* don't honor CTLESC quoting */
+#define SX_NOESCCTLNUL 0x20 /* don't let CTLESC quote CTLNUL */
+#define SX_NOLONGJMP 0x40 /* don't longjmp on fatal error */
+
/* Remove backslashes which are quoting backquotes from STRING. Modifies
STRING, and returns a pointer to it. */
extern char * de_backslash __P((char *));
@@ -52,8 +61,9 @@ extern void unquote_bang __P((char *));
/* 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 ")". */
-extern char *extract_command_subst __P((char *, int *));
+ Make (SINDEX) get the position just after the matching ")".
+ XFLAGS is additional flags to pass to other extraction functions, */
+extern char *extract_command_subst __P((char *, int *, int));
/* Extract the $[ construct in STRING, and return a new string.
Start extracting at (SINDEX) as if we had just seen "$[".
@@ -136,7 +146,7 @@ extern WORD_LIST *expand_string_unsplit __P((char *, int));
extern WORD_LIST *expand_string_assignment __P((char *, int));
/* Expand a prompt string. */
-extern WORD_LIST *expand_prompt_string __P((char *, int));
+extern WORD_LIST *expand_prompt_string __P((char *, int, int));
/* 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
diff --git a/tests/comsub-posix.right b/tests/comsub-posix.right
new file mode 100644
index 00000000..a24f25f8
--- /dev/null
+++ b/tests/comsub-posix.right
@@ -0,0 +1,57 @@
+abmnopyz
+abmnopyz
+ab
+
+
+abcd
+sh_352.26ax
+sh_352.26ay
+sh_352.25a sh_352.25b
+sh_352.27 ) ) )
+abc
+here doc with )
+)
+bad' syntax
+a
+sh_352.26a
+sh_352.26a
+sh_352.26
+sh_352.26
+sh_352.26
+1
+sh_352.28 )
+sh_352.28 )
+k
+abcd
+ab
+ab
+abcde
+
+argv[1] = <abcde^J >
+abcdefoo
+argv[1] = <abcde>
+argv[2] = <foo>
+argv[1] = <wxabcdeyz>
+argv[1] = <abcde>
+'
+after 1
+'
+after 2
+'
+after 3
+`
+after 4
+hello
+after 5
+'
+after 6
+x
+x
+quoted )
+comment
+here-doc with )
+here-doc terminated with a parenthesis
+' # or a single back- or doublequote
+./comsub-posix1.sub: command substitution: line 2: syntax error near unexpected token `)'
+./comsub-posix1.sub: command substitution: line 2: ` if x; then echo foo )'
+after
diff --git a/tests/comsub-posix.tests b/tests/comsub-posix.tests
new file mode 100644
index 00000000..abd73969
--- /dev/null
+++ b/tests/comsub-posix.tests
@@ -0,0 +1,199 @@
+
+# works right
+echo ab$(echo mnop)yz
+# works right
+echo ab$(echo mnop
+)yz
+#
+# works right
+echo $(echo ab
+ )
+# works right
+echo $(
+)
+echo $()
+echo ab$()cd
+
+echo $(case a in (a) echo sh_352.26ax; esac )
+echo $(case a in (a) echo sh_352.26ay; esac)
+
+echo $((echo sh_352.25a);(echo sh_352.25b))
+
+echo $(echo sh_352.27 ')' ")" \)
+ # ) comment
+ )
+
+echo $(
+echo abc # a comment with )
+)
+
+echo $(
+cat <<eof
+here doc with )
+eof
+)
+
+echo $(
+echo ')'
+)
+
+unset x
+x=$(cat <<"EOF"
+bad' syntax
+EOF
+)
+echo "$x"
+unset x
+
+echo $(for f in \); do echo a; done )
+echo $(case a in a) echo sh_352.26a; esac )
+echo $(case a in a) echo sh_352.26a; esac)
+
+echo $(case a in
+ (a) echo sh_352.26
+ ;;
+ esac
+ )
+
+echo $(case a in
+ a) echo sh_352.26
+ ;;
+ esac
+ )
+
+
+echo $(case a in
+ a) echo sh_352.26
+ ;;
+
+
+
+
+
+ esac
+
+ )
+
+echo $(( 4<(2+3) ? 1 : 32))
+
+echo $(cat << end
+sh_352.28 )
+end
+)
+
+echo $(cat <<- end
+sh_352.28 )
+ end
+)
+
+k=$(case x in x) echo k;; esac)
+echo $k
+
+x=$(
+ case $(ls) in
+ example) echo foobix;;
+ esac
+)
+
+echo $( echo ab\
+cd)
+
+echo `echo ab
+cd`
+
+echo `echo ab #xyz
+cd`
+
+echo "$(echo abcde)
+"
+
+recho "$(echo abcde)
+ "
+
+echo $(echo abcde)\
+foo
+
+recho $(echo abcde)\
+ foo
+
+recho "wx$(echo abcde)yz"
+recho "$(echo abcde)"
+
+echo $(cat <<eof
+'
+eof
+)
+
+echo after 1
+
+echo $(cat <<\eof
+'
+eof
+)
+
+echo after 2
+
+echo "$(cat <<\eof
+'
+eof
+)"
+
+echo after 3
+
+echo "$(cat <<\eof
+`
+eof
+)"
+
+echo after 4
+
+echo $(
+cat << ')'
+hello
+)
+)
+
+echo after 5
+
+echo $(cat <<'eof'
+'
+eof
+)
+
+echo after 6
+
+echo $(
+ case x in x) echo x;; esac
+)
+
+echo $(
+ case x in (x) echo x;; esac
+)
+
+echo $(
+ echo 'quoted )'
+)
+
+echo $(
+ echo comment # with )
+)
+
+echo $(
+cat <<\eof
+ here-doc with )
+eof
+)
+
+echo $(
+cat <<\)
+ here-doc terminated with a parenthesis
+)
+)
+
+echo $(
+cat <<\eof
+ ' # or a single back- or doublequote
+eof
+)
+
+${THIS_SH} ./comsub-posix1.sub
diff --git a/tests/comsub-posix.tests~ b/tests/comsub-posix.tests~
new file mode 100644
index 00000000..b68668e5
--- /dev/null
+++ b/tests/comsub-posix.tests~
@@ -0,0 +1,197 @@
+
+# works right
+echo ab$(echo mnop)yz
+# works right
+echo ab$(echo mnop
+)yz
+#
+# works right
+echo $(echo ab
+ )
+# works right
+echo $(
+)
+echo $()
+echo ab$()cd
+
+echo $(case a in (a) echo sh_352.26ax; esac )
+echo $(case a in (a) echo sh_352.26ay; esac)
+
+echo $((echo sh_352.25a);(echo sh_352.25b))
+
+echo $(echo sh_352.27 ')' ")" \)
+ # ) comment
+ )
+
+echo $(
+echo abc # a comment with )
+)
+
+echo $(
+cat <<eof
+here doc with )
+eof
+)
+
+echo $(
+echo ')'
+)
+
+unset x
+x=$(cat <<"EOF"
+bad' syntax
+EOF
+)
+echo "$x"
+unset x
+
+echo $(for f in \); do echo a; done )
+echo $(case a in a) echo sh_352.26a; esac )
+echo $(case a in a) echo sh_352.26a; esac)
+
+echo $(case a in
+ (a) echo sh_352.26
+ ;;
+ esac
+ )
+
+echo $(case a in
+ a) echo sh_352.26
+ ;;
+ esac
+ )
+
+
+echo $(case a in
+ a) echo sh_352.26
+ ;;
+
+
+
+
+
+ esac
+
+ )
+
+echo $(( 4<(2+3) ? 1 : 32))
+
+echo $(cat << end
+sh_352.28 )
+end
+)
+
+echo $(cat <<- end
+sh_352.28 )
+ end
+)
+
+k=$(case x in x) echo k;; esac)
+echo $k
+
+x=$(
+ case $(ls) in
+ example) echo foobix;;
+ esac
+)
+
+echo $( echo ab\
+cd)
+
+echo `echo ab
+cd`
+
+echo `echo ab #xyz
+cd`
+
+echo "$(echo abcde)
+"
+
+recho "$(echo abcde)
+ "
+
+echo $(echo abcde)\
+foo
+
+recho $(echo abcde)\
+ foo
+
+recho "wx$(echo abcde)yz"
+recho "$(echo abcde)"
+
+echo $(cat <<eof
+'
+eof
+)
+
+echo after 1
+
+echo $(cat <<\eof
+'
+eof
+)
+
+echo after 2
+
+echo "$(cat <<\eof
+'
+eof
+)"
+
+echo after 3
+
+echo "$(cat <<\eof
+`
+eof
+)"
+
+echo after 4
+
+echo $(
+cat << ')'
+hello
+)
+)
+
+echo after 5
+
+echo $(cat <<'eof'
+'
+eof
+)
+
+echo after 6
+
+echo $(
+ case x in x) echo x;; esac
+)
+
+echo $(
+ case x in (x) echo x;; esac
+)
+
+echo $(
+ echo 'quoted )'
+)
+
+echo $(
+ echo comment # with )
+)
+
+echo $(
+cat <<\eof
+ here-doc with )
+eof
+)
+
+echo $(
+cat <<\)
+ here-doc terminated with a parenthesis
+)
+)
+
+echo $(
+cat <<\eof
+ ' # or a single back- or doublequote
+eof
+)
diff --git a/tests/comsub-posix1.sub b/tests/comsub-posix1.sub
new file mode 100644
index 00000000..bbcc60fb
--- /dev/null
+++ b/tests/comsub-posix1.sub
@@ -0,0 +1,3 @@
+echo $( if x; then echo foo )
+
+echo after
diff --git a/tests/errors.right b/tests/errors.right
index 346d2e97..890e9a53 100644
--- a/tests/errors.right
+++ b/tests/errors.right
@@ -53,9 +53,10 @@ umask: usage: umask [-p] [-S] [mode]
./errors.tests: line 159: declare: VAR: readonly variable
./errors.tests: line 161: declare: unset: not found
./errors.tests: line 164: VAR: readonly variable
-./errors.tests: command substitution: line 168: syntax error: unexpected end of file
-./errors.tests: command substitution: line 168: syntax error near unexpected token `done'
-./errors.tests: command substitution: line 168: ` for z in 1 2 3; done '
+./errors.tests: command substitution: line 168: syntax error near unexpected token `)'
+./errors.tests: command substitution: line 168: ` for z in 1 2 3; do )'
+./errors.tests: command substitution: line 169: syntax error near unexpected token `done'
+./errors.tests: command substitution: line 169: ` for z in 1 2 3; done )'
./errors.tests: line 171: cd: HOME not set
./errors.tests: line 172: cd: /tmp/xyz.bash: No such file or directory
./errors.tests: line 174: cd: OLDPWD not set
diff --git a/tests/redir8.sub b/tests/redir8.sub
index cf8862ae..7990a4af 100644
--- a/tests/redir8.sub
+++ b/tests/redir8.sub
@@ -54,4 +54,5 @@ exec 8<&0; cat <&8
exec 0<&7
exec 7<&-
+rm -f infile
exit 0
diff --git a/tests/run-comsub-posix b/tests/run-comsub-posix
new file mode 100644
index 00000000..38bf8eea
--- /dev/null
+++ b/tests/run-comsub-posix
@@ -0,0 +1,2 @@
+${THIS_SH} ./comsub-posix.tests > /tmp/xx 2>&1
+diff /tmp/xx comsub-posix.right && rm -f /tmp/xx
diff --git a/variables.h b/variables.h
index 9e5ab162..7bcc7f9f 100644
--- a/variables.h
+++ b/variables.h
@@ -1,6 +1,6 @@
/* variables.h -- data structures for shell variables. */
-/* Copyright (C) 1987-2005 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -23,6 +23,7 @@
#include "stdc.h"
#include "array.h"
+#include "assoc.h"
/* Shell variables and functions are stored in hash tables. */
#include "hashlib.h"
@@ -61,7 +62,7 @@ typedef struct var_context {
/* What a shell variable looks like. */
typedef struct variable *sh_var_value_func_t __P((struct variable *));
-typedef struct variable *sh_var_assign_func_t __P((struct variable *, char *, arrayind_t));
+typedef struct variable *sh_var_assign_func_t __P((struct variable *, char *, arrayind_t, char *));
/* For the future */
union _value {
@@ -108,8 +109,11 @@ typedef struct _vlist {
#define att_local 0x0000020 /* variable is local to a function */
#define att_assoc 0x0000040 /* variable is an associative array */
#define att_trace 0x0000080 /* function is traced with DEBUG trap */
+#define att_uppercase 0x0000100 /* word converted to uppercase on assignment */
+#define att_lowercase 0x0000200 /* word converted to lowercase on assignment */
+#define att_capcase 0x0000400 /* word capitalized on assignment */
-#define user_attrs (att_exported|att_readonly|att_integer|att_local|att_trace)
+#define user_attrs (att_exported|att_readonly|att_integer|att_local|att_trace|att_uppercase|att_lowercase|att_capcase)
#define attmask_user 0x0000fff
@@ -119,6 +123,7 @@ typedef struct _vlist {
#define att_noassign 0x0004000 /* assignment not allowed */
#define att_imported 0x0008000 /* came from environment */
#define att_special 0x0010000 /* requires special handling */
+#define att_nofree 0x0020000 /* do not free value on unset */
#define attmask_int 0x00ff000
@@ -136,12 +141,16 @@ typedef struct _vlist {
#define local_p(var) ((((var)->attributes) & (att_local)))
#define assoc_p(var) ((((var)->attributes) & (att_assoc)))
#define trace_p(var) ((((var)->attributes) & (att_trace)))
+#define uppercase_p(var) ((((var)->attributes) & (att_uppercase)))
+#define lowercase_p(var) ((((var)->attributes) & (att_lowercase)))
+#define capcase_p(var) ((((var)->attributes) & (att_capcase)))
#define invisible_p(var) ((((var)->attributes) & (att_invisible)))
#define non_unsettable_p(var) ((((var)->attributes) & (att_nounset)))
#define noassign_p(var) ((((var)->attributes) & (att_noassign)))
#define imported_p(var) ((((var)->attributes) & (att_imported)))
#define specialvar_p(var) ((((var)->attributes) & (att_special)))
+#define nofree_p(var) ((((var)->attributes) & (att_nofree)))
#define tempvar_p(var) ((((var)->attributes) & (att_tempvar)))
@@ -149,6 +158,7 @@ typedef struct _vlist {
#define value_cell(var) ((var)->value)
#define function_cell(var) (COMMAND *)((var)->value)
#define array_cell(var) (ARRAY *)((var)->value)
+#define assoc_cell(var) (HASH_TABLE *)((var)->value)
#define var_isnull(var) ((var)->value == 0)
#define var_isset(var) ((var)->value != 0)
@@ -157,6 +167,7 @@ typedef struct _vlist {
#define var_setvalue(var, str) ((var)->value = (str))
#define var_setfunc(var, func) ((var)->value = (char *)(func))
#define var_setarray(var, arr) ((var)->value = (char *)(arr))
+#define var_setassoc(var, arr) ((var)->value = (char *)(arr))
/* Make VAR be auto-exported. */
#define set_auto_export(var) \
@@ -316,6 +327,9 @@ extern void print_var_function __P((SHELL_VAR *));
extern SHELL_VAR *make_new_array_variable __P((char *));
extern SHELL_VAR *make_local_array_variable __P((char *));
+extern SHELL_VAR *make_new_assoc_variable __P((char *));
+extern SHELL_VAR *make_local_assoc_variable __P((char *));
+
extern void set_pipestatus_array __P((int *, int));
#endif
diff --git a/variables.h~ b/variables.h~
index cc621740..036fe495 100644
--- a/variables.h~
+++ b/variables.h~
@@ -1,6 +1,6 @@
/* variables.h -- data structures for shell variables. */
-/* Copyright (C) 1987-2005 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2008 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
@@ -23,6 +23,7 @@
#include "stdc.h"
#include "array.h"
+#include "assoc.h"
/* Shell variables and functions are stored in hash tables. */
#include "hashlib.h"
@@ -61,7 +62,7 @@ typedef struct var_context {
/* What a shell variable looks like. */
typedef struct variable *sh_var_value_func_t __P((struct variable *));
-typedef struct variable *sh_var_assign_func_t __P((struct variable *, char *, arrayind_t));
+typedef struct variable *sh_var_assign_func_t __P((struct variable *, char *, arrayind_t, char *));
/* For the future */
union _value {
@@ -108,8 +109,11 @@ typedef struct _vlist {
#define att_local 0x0000020 /* variable is local to a function */
#define att_assoc 0x0000040 /* variable is an associative array */
#define att_trace 0x0000080 /* function is traced with DEBUG trap */
+#define att_uppercase 0x0000100 /* word converted to uppercase on assignment */
+#define att_lowercase 0x0000200 /* word converted to lowercase on assignment */
+#define att_capcase 0x0000400 /* word capitalized on assignment */
-#define user_attrs (att_exported|att_readonly|att_integer|att_local|att_trace)
+#define user_attrs (att_exported|att_readonly|att_integer|att_local|att_trace|att_uppercase|att_lowercase|att_capcase)
#define attmask_user 0x0000fff
@@ -119,6 +123,7 @@ typedef struct _vlist {
#define att_noassign 0x0004000 /* assignment not allowed */
#define att_imported 0x0008000 /* came from environment */
#define att_special 0x0010000 /* requires special handling */
+#define att_nofree 0x0020000 /* do not free value on unset */
#define attmask_int 0x00ff000
@@ -136,12 +141,16 @@ typedef struct _vlist {
#define local_p(var) ((((var)->attributes) & (att_local)))
#define assoc_p(var) ((((var)->attributes) & (att_assoc)))
#define trace_p(var) ((((var)->attributes) & (att_trace)))
+#define uppercase_p(var) ((((var)->attributes) & (att_uppercase)))
+#define lowercase_p(var) ((((var)->attributes) & (att_lowercase)))
+#define capcase_p(var) ((((var)->attributes) & (att_capcase)))
#define invisible_p(var) ((((var)->attributes) & (att_invisible)))
#define non_unsettable_p(var) ((((var)->attributes) & (att_nounset)))
#define noassign_p(var) ((((var)->attributes) & (att_noassign)))
#define imported_p(var) ((((var)->attributes) & (att_imported)))
#define specialvar_p(var) ((((var)->attributes) & (att_special)))
+#define nofree_p(var) ((((var)->attributes) & (att_nofree)))
#define tempvar_p(var) ((((var)->attributes) & (att_tempvar)))
@@ -149,6 +158,7 @@ typedef struct _vlist {
#define value_cell(var) ((var)->value)
#define function_cell(var) (COMMAND *)((var)->value)
#define array_cell(var) (ARRAY *)((var)->value)
+#define assoc_cell(var) (HASH_TABLE *)((var)->value)
#define var_isnull(var) ((var)->value == 0)
#define var_isset(var) ((var)->value != 0)
@@ -157,6 +167,7 @@ typedef struct _vlist {
#define var_setvalue(var, str) ((var)->value = (str))
#define var_setfunc(var, func) ((var)->value = (char *)(func))
#define var_setarray(var, arr) ((var)->value = (char *)(arr))
+#define var_setassoc(var, arr) ((var)->value = (char *)(arr))
/* Make VAR be auto-exported. */
#define set_auto_export(var) \
@@ -316,6 +327,9 @@ extern void print_var_function __P((SHELL_VAR *));
extern SHELL_VAR *make_new_array_variable __P((char *));
extern SHELL_VAR *make_local_array_variable __P((char *));
+extern SHELL_VAR *make_new_assoc_variable __P((char *));
+extern SHELL_VAR *make_local_assoc_variable __P((char *));
+
extern void set_pipestatus_array __P((int *, int));
#endif
@@ -325,6 +339,10 @@ extern void set_pipestatus_from_exit __P((int));
is one of the special ones where something special happens. */
extern void stupidly_hack_special_variables __P((char *));
+/* Reinitialize some special variables that have external effects upon unset
+ when the shell reinitializes itself. */
+extern void reinit_special_variables __P((void));
+
extern int get_random_number __P((void));
/* The `special variable' functions that get called when a particular