diff options
author | Chet Ramey <chet.ramey@case.edu> | 2011-12-07 09:24:46 -0500 |
---|---|---|
committer | Chet Ramey <chet.ramey@case.edu> | 2011-12-07 09:24:46 -0500 |
commit | e33f22038cc3dc3146a8cc58e2c5b603e8578972 (patch) | |
tree | 8df8a3e07f6edb3568a674e5134c3d1c9a1bfa06 | |
parent | ed35cb4a6394a5121724fc8a2c0ace6eb5be9de1 (diff) | |
download | bash-e33f22038cc3dc3146a8cc58e2c5b603e8578972.tar.gz |
commit bash-20080605 snapshot
-rw-r--r-- | CWRU/CWRU.chlog | 79 | ||||
-rw-r--r-- | CWRU/CWRU.chlog~ | 82 | ||||
-rw-r--r-- | MANIFEST | 4 | ||||
-rw-r--r-- | MANIFEST~ | 6 | ||||
-rw-r--r-- | braces.c | 2 | ||||
-rw-r--r-- | builtins/break.def | 4 | ||||
-rw-r--r-- | builtins/common.c | 31 | ||||
-rw-r--r-- | builtins/common.h | 2 | ||||
-rw-r--r-- | builtins/history.def | 14 | ||||
-rw-r--r-- | builtins/read.def | 5 | ||||
-rw-r--r-- | builtins/read.def~ | 2 | ||||
-rw-r--r-- | builtins/shift.def | 3 | ||||
-rw-r--r-- | builtins/ulimit.def | 16 | ||||
-rw-r--r-- | builtins/ulimit.def~ | 19 | ||||
-rw-r--r-- | doc/bash.1 | 8 | ||||
-rw-r--r-- | doc/bash.1~ | 11 | ||||
-rw-r--r-- | doc/bashref.texi | 18 | ||||
-rw-r--r-- | doc/bashref.texi~ | 20 | ||||
-rw-r--r-- | parse.y | 51 | ||||
-rw-r--r-- | parse.y.save2 | 5538 | ||||
-rw-r--r-- | parse.y~ | 57 | ||||
-rw-r--r-- | subst.c | 136 | ||||
-rw-r--r-- | subst.c.save1 | 422 | ||||
-rw-r--r-- | subst.c.save2 | 425 | ||||
-rw-r--r-- | subst.c~ | 157 | ||||
-rw-r--r-- | subst.h | 20 | ||||
-rw-r--r-- | subst.h~ | 16 | ||||
-rw-r--r-- | tests/comsub-posix.right | 57 | ||||
-rw-r--r-- | tests/comsub-posix.tests | 199 | ||||
-rw-r--r-- | tests/comsub-posix.tests~ | 197 | ||||
-rw-r--r-- | tests/comsub-posix1.sub | 3 | ||||
-rw-r--r-- | tests/errors.right | 7 | ||||
-rw-r--r-- | tests/redir8.sub | 1 | ||||
-rw-r--r-- | tests/run-comsub-posix | 2 | ||||
-rw-r--r-- | variables.h | 20 | ||||
-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> @@ -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 @@ -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 @@ -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, ×) == 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) @@ -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 @@ -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 = ¤t_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 */ @@ -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) @@ -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) @@ -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 */ @@ -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 *)); @@ -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 |