]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
commit bash-20130720 snapshot
authorChet Ramey <chet@caleb.ins.cwru.edu>
Mon, 5 Aug 2013 13:55:44 +0000 (09:55 -0400)
committerChet Ramey <chet@caleb.ins.cwru.edu>
Mon, 5 Aug 2013 13:55:44 +0000 (09:55 -0400)
30 files changed:
CHANGES
CHANGES-4.3
CWRU/CWRU.chlog
CWRU/CWRU.chlog~
MANIFEST
MANIFEST~ [new file with mode: 0644]
NEWS-4.3
command.h
command.h~ [new file with mode: 0644]
ddd1 [new file with mode: 0644]
execute_cmd.c
execute_cmd.c~ [new file with mode: 0644]
lib/readline/histfile.c
lib/readline/isearch.c
lib/readline/isearch.c~ [new file with mode: 0644]
lib/readline/readline.c
lib/readline/readline.c~ [new file with mode: 0644]
subst.c
subst.c~ [new file with mode: 0644]
tests/RUN-ONE-TEST
tests/RUN-ONE-TEST~ [new file with mode: 0755]
tests/array.right
tests/array.tests
tests/array.tests~ [new file with mode: 0644]
tests/array15.sub [new file with mode: 0644]
tests/array15.sub~ [new file with mode: 0644]
tests/trap.right
tests/trap.tests
tests/trap.tests~ [new file with mode: 0644]
tests/trap5.sub

diff --git a/CHANGES b/CHANGES
index f081896e738cb7766f30d36bd0c64a517462c445..3aaa81770c71633342323d9d6eee3a4237e42a7d 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -37,6 +37,21 @@ i.  Fixed the nameref circular reference checking to be less strict and only
 j.  Fixed a bug that caused trap handlers to be executed recursively,
     corrupting internal data structures.
 
+k.  Fixed a bug that could result in bash not compiling if certain options were
+    not enabled.
+
+l.  Fixed a bug that caused the arithmetic expansion code to attempt variable
+    assignments when operator precedence prohibited them.
+
+m.  Word expansions like ${foo##bar} now understand indirect variable references.
+
+n.  Fixed a bug that caused `declare -fp name' to not display a function 
+    definition.
+
+o.  Fixed a bug that caused asynchronous child processes to modify the stdin
+    file pointer when bash was using it to read a script, which modified the
+    parent's value as well.
+
 2.  Changes to Readline
 
 a.  Fixed a bug in vi mode that caused the arrow keys to set the saved last
@@ -51,10 +66,25 @@ c.  Fixed a bug with vi mode that prevented `.' from repeating a command
 d.  Fixed a bug that could cause completion to core dump if it was interrupted
     by a signal.
 
+e.  Readline now sends the meta-key enable string to the terminal if the
+    terminal has been successfully initialized.
+
+f.  Readline now calls the signal hook after resizing the terminal when it
+    receives a SIGWINCH.
+
+g.  Fixed a bug that could cause the history list code to perform an out-of-
+    bounds array reference if the history list is empty.
+
 3.  New Features in Bash
 
 a.  Shells started to run process substitutions now run any trap set on EXIT.
 
+b.  There is now a configure-time option to enable the globasciiranges option
+    by default.
+
+c.  The read builtin now checks its first variable argument for validity before
+    trying to read any input.
+
 ------------------------------------------------------------------------------
 This document details the changes between this version, bash-4.3-alpha,
 and the previous version, bash-4.2-release.
index 45d819c39d6b1b84b8851c4286daf73436019596..94627720a37d304149e9889f1ee8a623d5f96e35 100644 (file)
@@ -37,6 +37,21 @@ i.  Fixed the nameref circular reference checking to be less strict and only
 j.  Fixed a bug that caused trap handlers to be executed recursively,
     corrupting internal data structures.
 
+k.  Fixed a bug that could result in bash not compiling if certain options were
+    not enabled.
+
+l.  Fixed a bug that caused the arithmetic expansion code to attempt variable
+    assignments when operator precedence prohibited them.
+
+m.  Word expansions like ${foo##bar} now understand indirect variable references.
+
+n.  Fixed a bug that caused `declare -fp name' to not display a function 
+    definition.
+
+o.  Fixed a bug that caused asynchronous child processes to modify the stdin
+    file pointer when bash was using it to read a script, which modified the
+    parent's value as well.
+
 2.  Changes to Readline
 
 a.  Fixed a bug in vi mode that caused the arrow keys to set the saved last
@@ -51,564 +66,21 @@ c.  Fixed a bug with vi mode that prevented `.' from repeating a command
 d.  Fixed a bug that could cause completion to core dump if it was interrupted
     by a signal.
 
-3.  New Features in Bash
-
-a.  Shells started to run process substitutions now run any trap set on EXIT.
-
-------------------------------------------------------------------------------
-This document details the changes between this version, bash-4.3-alpha,
-and the previous version, bash-4.2-release.
-
-1.  Changes to Bash
-
-a.  Fixed several bugs concerning incomplete bracket expressions in filename
-    generation (globbing) patterns.
-
-b.  Fixed a bug with single quotes and WORD in ${param op WORD} when running
-    in Posix mode.
-
-c.  Fixed a bug that caused the pattern removal and pattern substitution word
-    expansions and case statement word expansion to not match the empty string.
-
-d.  Fixed a bug that caused the tzset() function to not work after changing
-    the TZ enviroment variable.
-
-e.  Fixed a bug that caused the RHS of an assignment statement to undergo
-    word splitting when it contained an unquoted $@.
-
-f.  Fixed bugs that caused the shell to not react to a SIGINT sent while
-    waiting for a child process to exit.
-
-g.  Bash doesn't try to run things in a signal handler context when it gets a
-    signal (SIGINT/SIGHUP/etc) while reading input using readline but still
-    be responsive to terminating signals.
-
-h.  Fixed a bug that caused bash to go into an infinite loop if a filename
-    to be matched contained an invalid multibyte character.
-
-i.  Fixed a bug that caused PS4 to end up being truncated if it is longer
-    than 128 bytes.
-
-j.  Fixed a bug that caused brace expansion to not skip over double-quoted
-    command substitution.
-
-k.  System-specific updates for: DJGPP, HP/UX, Mac OS X
-
-l.  Fixed a bug in displaying commands that caused redirections to be associated
-    with the wrong part of the command.
-
-m.  Fixed the coproc cleanup to unset the appropriate shell variables when a
-    coproc terminates.
-
-n.  Fixed a bug that caused `fc' to dump core due to incorrect calculation of
-    the last history entry.
-
-o.  Added workarounds for FreeBSD's implementation of faccessat/eaccess and
-    `test -x'.
-
-p.  Fixed a bug that caused the shell to not match patterns containing
-    control-A.
-
-q.  Fixed a bug that could result in doubled error messages when the `printf'
-    builtin got a write error.
-
-r.  Fixed a bug that caused the shell to not correctly expand words containing
-    multiple consecutive quoted empty strings (""""""aa).
-
-s.  Fixed a bug that caused the shell to not correctly parse multi-line
-    process substitutions containing comments and quoted strings.
-
-t.  Fixed a problem with the bash malloc's internal idea of the top of the
-    memory heap that resulted in incorrect decisions to try to reduce the
-    break and give memory back to the kernel.
-
-u.  There are changes to the expansions peformed on compound array assignments,
-    in an effort to make foo=( [ind1]=bar [ind2]=baz ) identical to
-    foo[ind1]=bar foo[ind2]=baz.
-
-v.  Bash now reports an error if `read -a name' is used when `name' is an
-    existing associative array.
-
-w.  Fixed a bug that allowed an attempted assignment to a readonly variable
-    in an arithmetic expression to not return failure.
-
-x.  Fixed several bugs that caused completion functions to be invoked even when
-    the cursor was before the first word in the command.
-
-y.  Fixed a bug that caused parsing a command substitution to overwrite the
-    parsing state associated with the complete input line.
-
-z.  Fixed several bugs with the built-in snprintf replacement and field widths
-    and floating point.
-
-aa. Fixed a bug that caused incorrect offset calculations and input buffer
-    corruption when reading files longer than 2^31 bytes.
-
-bb. Fixed several bugs where bash performed arithmetic evaluation in contexts
-    where evaluation is suppressed.
-
-cc. Fixed a bug that caused bash to close FIFOs used for process substitution
-    too early when a shell function was executing, but protect against using
-    all file descriptors when the shell functions are invoked inside loops.
-
-dd. Added checks for printable (and non-printable) multibyte characters for
-    use in error messages.
-
-ee. Fixed a bug that caused ^O (operate-and-get-next) to not work correctly
-    at the end of the history list.
-
-ff. Fixed a bug that caused command-oriented history to incorrectly combine
-    here documents into one line.
-
-gg. Fixed a bug that caused importing SHELLOPTS from the environment into a
-    Posix-mode shell to print an error message and refuse to parse it.
-
-hh. Fixed a bug that caused the shell to delete an extra history entry when
-    using `history -s'.
-
-ii. Fixed a bug that caused floating-point exceptions and overflow errors
-    for the / and % arithmetic operators when using INTMAX_MIN and -1.
-
-jj. Fixed a bug that caused parsing errors when reading an arithmetic for
-    loop inside a command substitution.
-
-kk. Fixed a bug that caused a readonly function to be unset when unset was
-    called without the -f or -v option.
-
-ll. Fixed several bugs in the code that quotes characters special to regular
-    expressions when used in a quoted string on the RHS of the =~ operator
-    to the [[ command.
-
-mm. Fixed a bug that caused redirections to fail because the file descriptor
-    limit was set to a value less than 10.
-
-nn. Fixed a bug that caused the `read' builtin to execute code in a signal
-    handler context if read timed out.
-
-oo. Fixed a bug that caused extended globbing patterns to not match files
-    beginning with `.' correctly when a `.' was explicitly supplied in the
-    pattern.
-
-pp. Fixed a bug that caused key sequences longer than two characters to not
-    work when used with `bind -x'.
-
-qq. Fixed a bug that resulted in redefined functions having the wrong source
-    file names in BASH_SOURCE.
-
-rr. Fixed a bug that caused the read builtin to assign null strings to variables
-    when using `read -N', which caused core dumps when referenced
-
-ss. Fixed a bug that caused `bash -m script' to not enable job control while
-    running the script.
-
-tt. Fixed a bug that caused `printf -v var' to dump core when used with the
-    %b format code.
-
-uu. Fixed a bug that caused the shell to exit with the wrong status if -e was
-    active and the shell exited on a substitution error.
-
-vv. Fixed a bug that caused the shell to seg fault if an array variable with
-    the same name as an existing associative array was implicitly created by
-    an assignment (declare a[n]=b).
-
-ww. Fixed a bug that caused a redirection to misbehave if the number specified
-    for a file descriptor overflows an intmax_t.
-
-xx. Fixed several bugs with the handling of valid and invalid unicode character
-    values when used with the \u and \U escape sequences to printf and $'...'.
-
-yy. Fixed a bug that caused tildes to not be escaped in expanded filenames,
-    making them subject to later expansion.
-
-zz. When using the pattern substitution word expansion, bash now runs the
-    replacement string through quote removal, since it allows quotes in that
-    string to act as escape characters.  This is not backwards compatible, so
-    it can be disabled by setting the bash compatibility mode to 4.2.
-
-aaa. Fixed the rest of the cases where the shell runs non-allowed code in a
-     signal handler context.
-
-bbb. Fixed a bug that caused spurious DEL characters (\177) to appear in
-     double-quoted expansion where the RHS is evaluated to the empty string.
-
-ccc. Fixed a bug that caused the use of the shell's internal random number
-     generator for temporary file names to perturb the random number
-     sequence.
-
-ddd. Fixed several bugs that caused `declare -g' to not set the right global
-     variables or to misbehave when declaring global indexed arrays.
-
-eee. Fixed a logic bug that caused extended globbing in a multibyte locale to
-     cause failures when using the pattern substititution word expansions.
-
-fff. Fixed a bug that caused the `lastpipe' option to corrupt the file
-     descriptor used to read the script.
-
-ggg. Fixed a bug that causes the shell to delete DEL characters in the
-     expanded value of variables used in the same quoted string as variables
-     that expand to nothing.
-
-hhh. Fixed a bug that caused the shell to assign the wrong value from an
-     assignment like (( x=7 )) when `x' was an existing array variable.
-
-iii. Fixed a bug that caused the shell to misbehave when generating sequences
-     and the boundary values overflow an intmax_t.
-
-jjj. Fixed a bug caused expansion errors if an expansion of "$@" appeared
-     next to another expansion (e.g.. "${@}${x}").
-
-kkk. Fixed a potential buffer overflow bug when performing /dev/fd expansion.
-
-lll. Fixed a bug that resulted in an extra semicolon being added to compound
-     assignments when they were added to the history list.
-
-mmm. Fixed a bug that caused mapfile to read one extra line from the input.
-
-nnn. Fixed a bug that caused the mail checking code to use uninitialized
-     values.
-
-ooo. Fixed a bug that prevented history timestamps from being saved if the
-     history comment character is unset.
-
-ppp. Fixed a bug that caused the case-modifying expansions to not work with
-     multibyte characters.
-
-qqq. Fixed a bug that caused the edit-and-execute bindable readline command
-     to see the wrong data if invoked in the middle of a multi-line quoted
-     string.
-
-rrr. Fixed a bug that resulted in the shell returning the wrong exit status
-     for a background command on systems that recycle PIDs very quickly.
-
-sss. Fixed a bug that caused asynchronous group commands to not run any EXIT
-     trap defined in the body of the command.
-
-ttt. Fixed a bug that caused `eval "... ; return"' to not clean up properly.
-
-uuu. Fixed a bug that caused the shell to dump core if `read' reads an escaped
-     IFS whitespace character.
-
-vvv. Fixed a bug that caused BASH_COMMAND to be set to an incorrect value when
-     executing a (...) subshell.
-
-www. Fixed a couple of pointer aliasing bugs with the token string in arithmetic
-     evaluation.
-
-xxx. Fixed a bug with parsing multi-line command substitutions when reading
-     the `do' keyword followed by whitespace.
-
-yyy. Fixed a bug that caused the shell to seg fault if the time given to the
-     printf %(...)T format overflowed the value accepted by localtime(3).
-
-zzz. Fixed a problem with displaying help topics in two columns when the
-     translated text contained multibyte characters.
-
-aaaa. Fixed a bug with the extended globbing pattern matcher where a `*' was
-      followed by a negated extended glob pattern.
-
-bbbb. Fixed a race condition with short-lived coproc creation and reaping that
-      caused the child process to be reaped before the various coproc shell
-      variables were initialized.
+e.  Readline now sends the meta-key enable string to the terminal if the
+    terminal has been successfully initialized.
 
-cccc. Fixed a bug where turning off `errexit' in command substitution subshells
-      was not reflected in $SHELLOPTS.
+f.  Readline now calls the signal hook after resizing the terminal when it
+    receives a SIGWINCH.
 
-dddd. Partially fixed an inconsistency in how the shell treated shell
-      functions run from an EXIT trap.
-
-eeee. Fixed a bug in how the shell invalidated FIFOs used for process
-      substitution when executing a pipeline (once rather than in every child).
-
-ffff. Fixed a bug that occurred when expanding a special variable ($@, $*)
-      within double quotes and the expansion resulted in an empty string.
-
-gggg. Fixed bugs with executing a SIGCHLD trap handler to make sure that it's
-      executed once per exited child.
-
-hhhh. Fixed a bug that caused `declare' and `test' to find variables that
-      had been given attributes but not assigned values.  Such variables are
-      not set.
-
-iiii. Fixed a bug that caused commands in process substitutions to not look in
-      the local temporary environment when performing word expansions.
-
-jjjj. Fixed several problems with globstar expansions (**/**) returning null
-      filenames and multiple instances of the same pathname.
-
-kkkk. Fixed an oversight that did not allow the exit status of `coproc' to
-      be inverted using `!'.
-
-llll. Fixed a bug that caused the -e option to be re-enabled using `set -e'
-      even when executing in a context where -e is ignored.
-
-mmmm. Fixed a (mostly theoretical) bug with input lines longer than SIZE_MAX.
-
-nnnn. Fixed a bug that could result in double evaluation of command
-      substitutions when they appear in failed redirections.
-
-oooo. Fixed a bug that could cause seg faults during `mapfile' callbacks if
-      the callback unsets the array variable mapfile is using.
-
-pppp. Fixed several problems with variable assignments using ${var:=value}
-      when the variable assignment is supposed to have side effects.
-
-qqqq. Fixed a bug that caused a failure of an assignment statement preceding a
-      builtin caused the next invocation of a special builtin to exit the shell.
-
-rrrr. Fixed several problems with IFS when it appears in the temporary environment
-      and is used in redirections.
-
-ssss. Fixed a problem that caused IFS changes using ${IFS:=value} to modify
-      how preceding expansions were split.
-
-tttt. Fixed a problem that caused subshells to not run an EXIT trap they set.
-
-uuuu. Fixed a problem that caused shells started in posix mode to attempt to
-      import shell functions with invalid names from the environment.  We now
-      print a warning.
-
-vvvv. Worked around a kernel problem that caused SIGCHLD to interrupt open(2)
-      on a FIFO used for process substitution, even if the SIGCHLD handler was
-      installed with the SA_RESTART flag.
-
-wwww. Fixed a problem that resulted in inconsistent expansion of $* and ${a[*]}.
-
-xxxx. Fixed a problem that caused `read -t' to crash when interrupted by
-      SIGINT.
-
-yyyy. Fixed a problem that caused pattern removal to fail randomly because the
-      pattern matcher read beyond the end of a string.
-
-zzzz. Fixed a bug that caused core dumps when shell functions tried to create
-      local shadow copies of special variables like GROUPS.
-
-aaaaa. Fixed a bug that caused SIGTERM to be occasionally lost by children of
-       interactive shells when it arrived before the child process reset the
-       handler from SIG_DFL.
-
-bbbbb. Fixed a bug that caused redirections like <&n- to leave file descriptor
-       n closed if executed with a builtin command.
-
-ccccc. Fixed a bug that caused incorrect completion quoting when completing a
-       word containing a globbing character with `show-all-if-ambiguous' set.
-
-ddddd. Fixed a bug that caused printf's %q format specifier not to quote a
-       tilde even if it appeared in a location where it would be subject to
-       tilde expansion.
-
-2.  Changes to Readline
-
-a.  Fixed a bug that did not allow the `dd', `cc', or `yy' vi editing mode
-    commands to work on the entire line.
-
-b.  Fixed a bug that caused redisplay problems with prompts longer than 128
-    characters and history searches.
-
-c.  Fixed a bug that caused readline to try and run code to modify its idea
-    of the screen size in a signal handler context upon receiving a SIGWINCH.
-
-d.  Fixed a bug that caused the `meta' key to be enabled beyond the duration
-    of an individual call top readline().
-    
-e.  Added a workaround for a wcwidth bug in Mac OS X that caused readline's
-    redisplay to mishandle zero-width combining characters.
-
-f.  Fixed a bug that caused readline to `forget' part of a key sequence when
-    a multiple-key sequence caused it to break out of an incremental search.
-
-g.  Fixed bugs that caused readline to execute code in a signal handler
-    context if interrupted while reading from the file system during completion.
-
-h.  Fixed a bug that caused readline to `forget' part of a key sequence when
-    reading an unbound multi-character key sequence.
-
-i.  Fixed a bug that caused Readline's signal handlers to be installed beyond
-    the bounds of a single call to readline().
-
-j.  Fixed a bug that caused the `.' command to not redo the most recent `R'
-    command in vi mode.
-
-k.  Fixed a bug that caused ignoring case in completion matches to result in
-    readline using the wrong match.
-
-l.  Paren matching now works in vi insert mode.
-
-m.  Fix menu-completion to make show-all-if-ambiguous and menu-complete-display-prefix
-    work together.
-
-n.  Fixed a bug that didn't allow the `cc', `dd', or `yy' commands to be redone
-    in vi editing mode.
-
-o.  Fixed a bug that caused the filename comparison code to not compare
-    multibyte characters correctly when using case-sensitive or case-mapping
-    comparisons.
-
-p.  Fixed the input reading loop to call the input hook function only when there
-    is no terminal input available.
-
-q.  Fixed a bug that caused binding a macro to a multi-character key sequence
-    where the sequence and macro value share a common prefix to not perform
-    the macro replacement.
-
-r.  Fixed several redisplay errors with multibyte characters and prompts
-    containing invisible characters when using horizontal scrolling.
-
-s.  Fixed a bug that caused redisplay errors when trying to overwrite
-    existing characters using multibyte characters.
+g.  Fixed a bug that could cause the history list code to perform an out-of-
+    bounds array reference if the history list is empty.
 
 3.  New Features in Bash
 
-a.  The `helptopic' completion action now maps to all the help topics, not just
-    the shell builtins.
-
-b.  The `help' builtin no longer does prefix substring matching, so `help read'
-    does not match `readonly'.
-
-c.  The shell can be compiled to not display a message about processes that
-    terminate due to SIGTERM.
-
-d.  Non-interactive shells now react to the setting of checkwinsize and set
-    LINES and COLUMNS after a foreground job exits.
-
-e.  There is a new shell option, `globasciiranges', which, when set to on,
-    forces globbing range comparisons to use character ordering as if they
-    were run in the C locale.
-
-f.  There is a new shell option, `direxpand', which makes filename completion
-    expand variables in directory names in the way bash-4.1 did.
-
-g.  In Posix mode, the `command' builtin does not change whether or not a
-    builtin it shadows is treated as an assignment builtin.
-
-h.  The `return' and `exit' builtins accept negative exit status arguments.
-
-i.  The word completion code checks whether or not a filename containing a
-    shell variable expands to a directory name and appends `/' to the word
-    as appropriate.  The same code expands shell variables in command names
-    when performing command completion.
-
-j.  In Posix mode, it is now an error to attempt to define a shell function
-    with the same name as a Posix special builtin.
+a.  Shells started to run process substitutions now run any trap set on EXIT.
 
-k.  When compiled for strict Posix conformance, history expansion is disabled
+b.  There is now a configure-time option to enable the globasciiranges option
     by default.
 
-l.  The history expansion character (!) does not cause history expansion when
-    followed by the closing quote in a double-quoted string.
-
-m.  `complete' and its siblings compgen/compopt now takes a new `-o noquote'
-    option to inhibit quoting of the completions.
-
-n.  Setting HISTSIZE to a value less than zero causes the history list to be
-    unlimited (setting it 0 zero disables the history list).
-
-o.  Setting HISTFILESIZE to a value less than zero causes the history file size
-    to be unlimited (setting it to 0 causes the history file to be truncated
-    to zero size).
-
-p.  The `read' builtin now skips NUL bytes in the input.
-
-q.  There is a new `bind -X' option to print all key sequences bound to Unix
-    commands.
-
-r.  When in Posix mode, `read' is interruptible by a trapped signal.  After
-    running the trap handler, read returns 128+signal and throws away any
-    partially-read input.
-
-s.  The command completion code skips whitespace and assignment statements
-    before looking for the command name word to be completed.
-
-t.  The build process has a new mechanism for constructing separate help files
-    that better reflects the current set of compilation options.
-
-u.  The -nt and -ot options to test now work with files with nanosecond
-    timestamp resolution.
-
-v.  The shell saves the command history in any shell for which history is
-    enabled and HISTFILE is set, not just interactive shells.
-
-w.  The shell has `nameref' variables and new -n(/+n) options to declare and
-    unset to use them, and a `test -R' option to test for them.
-
-x.  The shell now allows assigning, referencing, and unsetting elements of
-    indexed arrays using negative subscripts (a[-1]=2, echo ${a[-1]}) which
-    count back from the last element of the array.
-
-y.  The {x}<word redirection feature now allows words like {array[ind]} and
-    can use variables with special meanings to the shell (e.g., BASH_XTRACEFD).
-
-z.  There is a new CHILD_MAX special shell variable; its value controls the
-    number of exited child statues the shell remembers.
-
-aa. There is a new configuration option (--enable-direxpand-default) that
-    causes the `direxpand' shell option to be enabled by default.
-
-bb. Bash does not do anything special to ensure that the file descriptor
-    assigned to X in {x}<foo remains open after the block containing it
-    completes.
-
-cc. The `wait' builtin has a new `-n' option to wait for the next child to
-    change status.
-
-dd. The `printf' %(...)T format specifier now uses the current time if no
-    argument is supplied.
-
-ee. There is a new variable, BASH_COMPAT, that controls the current shell
-    compatibility level.
-
-ff. The `popd' builtin now treats additional arguments as errors.
-
-gg. The brace expansion code now treats a failed sequence expansion as a
-    simple string and will continue to expand brace terms in the remainder
-    of the word.
-
-4.  New Features in Readline
-
-a.  Readline is now more responsive to SIGHUP and other fatal signals when
-    reading input from the terminal or performing word completion but no
-    longer attempts to run any not-allowable functions from a signal handler
-    context.
-
-b.  There are new bindable commands to search the history for the string of
-    characters between the beginning of the line and the point
-    (history-substring-search-forward, history-substring-search-backward)
-
-c.  Readline allows quoted strings as the values of variables when setting
-    them with `set'.  As a side effect, trailing spaces and tabs are ignored
-    when setting a string variable's value.
-
-d.  The history library creates a backup of the history file when writing it
-    and restores the backup on a write error.
-
-e.  New application-settable variable: rl_filename_stat_hook: a function called
-    with a filename before using it in a call to stat(2).  Bash uses it to
-    expand shell variables so things like $HOME/Downloads have a slash
-    appended.
-
-f.  New bindable function `print-last-kbd-macro', prints the most-recently-
-    defined keyboard macro in a reusable format.
-
-g.  New user-settable variable `colored-stats', enables use of colored text
-    to denote file types when displaying possible completions (colored analog
-    of visible-stats).
-
-h.  New user-settable variable `keyseq-timout', acts as an inter-character
-    timeout when reading input or incremental search strings.
-
-i.  New application-callable function: rl_clear_history. Clears the history list
-    and frees all readline-associated private data.
-
-j.  New user-settable variable, show-mode-in-prompt, adds a characters to the
-    beginning of the prompt indicating the current editing mode.
-
-k.  New application-settable variable: rl_input_available_hook; function to be
-    called when readline detects there is data available on its input file
-    descriptor.
-
-l.  Readline calls an application-set event hook (rl_signal_event_hook) after
-    it gets a signal while reading input (read returns -1/EINTR but readline
-    does not handle the signal immediately) to allow the application to handle
-    or otherwise note it.  Not currently called for SIGHUP or SIGTERM.
-
-m.  If the user-settable variable `history-size' is set to a value less than
-    0, the history list size is unlimited.
+c.  The read builtin now checks its first variable argument for validity before
+    trying to read any input.
index 7bf623c35a6d7b8200eac22aaf10b81a848a72fe..2e033eb1ccadc57bb2a1c7fa65f5207994f87577 100644 (file)
@@ -5079,3 +5079,36 @@ redir.c
 doc/{bash.1,bashref.texi}
        - slight change to add a description of `shopt -o' suggested by Bruce
          Korb <bruce.korb@gmail.com>
+
+                                  7/19
+                                  ----
+lib/readline/histfile.c
+       - history_do_write: if close returns < 0, make sure we restore the
+         backup history file and return a non-zero value
+       - history_truncate_file: if write or close return < 0, make sure we
+         return a non-zero value
+
+[bash-4.3-beta frozen]
+
+                                  7/21
+                                  ----
+lib/readline/isearch.c
+       - rl_display_search: now takes an entire search context flags word as
+         the second argument, instead of just reverse flag; changed callers
+       - rl_display_search: if the search has failed, add `failed ' to the
+         beginning of the search prompt
+       - _rl_isearch_dispatch: if the search has failed, display the entire
+         search string with an indication that the search failed but with the
+         last matching line.  Suggested by jidanni@jidanni.org
+
+command.h
+       - W_ASSIGNINT: new word flag; used internally for make_internal_declare
+         and set by fix_assignment_words
+
+execute_cmd.c
+       - fix_assignment_words: set W_ASSIGNINT if compound assignment and -i
+         given as option.  We don't do anything with the value yet
+
+subst.c
+       - shell_expand_word_list: rework the way the option list that is
+         passed to make_internal_declare is created
index f96ea833782961a7ed301d611e7a4ddbe14f3e1f..81de8b492d365e725f939cab5ac9a1ddfe1d7c31 100644 (file)
@@ -5073,3 +5073,42 @@ redir.c
          the parent process as well, since parents and children share the
          file pointer. Fixes problem originally reported in March 2013 by
          Martin Jackson <mjackson220.list@gmail.com>
+
+                                  7/13
+                                  ----
+doc/{bash.1,bashref.texi}
+       - slight change to add a description of `shopt -o' suggested by Bruce
+         Korb <bruce.korb@gmail.com>
+
+                                  7/19
+                                  ----
+lib/readline/histfile.c
+       - history_do_write: if close returns < 0, make sure we restore the
+         backup history file and return a non-zero value
+       - history_truncate_file: if write or close return < 0, make sure we
+         return a non-zero value
+
+[bash-4.3-beta frozen]
+
+                                  7/21
+                                  ----
+lib/readline/isearch.c
+       - rl_display_search: now takes an entire search context flags word as
+         the second argument, instead of just reverse flag; changed callers
+       - rl_display_search: if the search has failed, add `failed ' to the
+         beginning of the search prompt
+       - _rl_isearch_dispatch: if the search has failed, display the entire
+         search string with an indication that the search failed but with the
+         last matching line.  Suggested by jidanni@jidanni.org
+
+command.h
+       - W_ASSIGNINT: new word flag; used internally for make_internal_declare
+         and set by fix_assignment_words
+
+execute_cmd.c
+       - fix_assignment_words: set W_ASSIGNINT if compound assignment and -i
+         given as option
+
+subst.c
+       - shell_expand_word_list: rework the way the option list that is
+         passed to make_internal_declare is created
index 4eb4d48aec11b0944907534fcd8b5217bbb03363..39409ef0f51c6bdd718bf486d7bf9a29c18088f7 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -801,6 +801,7 @@ tests/array11.sub   f
 tests/array12.sub      f
 tests/array13.sub      f
 tests/array14.sub      f
+tests/array15.sub      f
 tests/array-at-star    f
 tests/array2.right     f
 tests/assoc.tests      f
diff --git a/MANIFEST~ b/MANIFEST~
new file mode 100644 (file)
index 0000000..4eb4d48
--- /dev/null
+++ b/MANIFEST~
@@ -0,0 +1,1250 @@
+#
+# Master distribution manifest for bash
+#
+#
+# Filename     type
+#
+CWRU           d
+CWRU/misc      d
+builtins       d
+cross-build    d
+doc    d
+examples       d
+#examples/obashdb      d
+examples/complete      d
+examples/functions d
+examples/scripts d
+#examples/scripts.v2   d
+#examples/scripts.noah d
+examples/startup-files d
+#examples/startup-files/apple  d
+examples/misc  d
+examples/loadables d
+examples/loadables/perl        d
+include                d
+lib            d
+lib/glob       d
+lib/glob/doc   d
+lib/intl       d
+lib/malloc     d
+lib/readline   d
+lib/readline/doc d
+lib/readline/examples d
+lib/sh         d
+lib/termcap    d
+lib/tilde      d
+m4             d
+po             d
+support                d
+tests          d
+tests/misc     d
+ABOUT-NLS      f
+ChangeLog      s       CWRU/changelog
+CHANGES                f
+COMPAT         f
+COPYING                f
+INSTALL                f
+MANIFEST       f
+NEWS           f
+NOTES          f
+POSIX          f
+README         f
+RBASH          f
+AUTHORS                f
+Y2K            f
+configure.ac   f
+configure      f       755
+Makefile.in    f
+config-top.h   f
+config-bot.h   f
+config.h.in    f
+aclocal.m4     f
+array.c                f
+arrayfunc.c    f
+assoc.c                f
+eval.c         f
+print_cmd.c    f
+general.c      f
+list.c         f
+locale.c       f
+stringlib.c    f
+variables.c    f
+make_cmd.c     f
+copy_cmd.c     f
+unwind_prot.c  f
+dispose_cmd.c  f
+bashhist.c     f
+hashcmd.c      f
+hashlib.c      f
+parse.y                f
+pathexp.c      f
+subst.c                f
+shell.c                f
+trap.c         f
+sig.c          f
+siglist.c      f
+version.c      f
+flags.c                f
+jobs.c         f
+input.c                f
+mailcheck.c    f
+test.c         f
+expr.c         f
+alias.c                f
+execute_cmd.c  f
+findcmd.c      f
+redir.c                f
+bashline.c     f
+braces.c       f
+bracecomp.c    f
+nojobs.c       f
+error.c                f
+xmalloc.c      f
+pcomplete.c    f
+pcomplib.c     f
+mksyntax.c     f
+alias.h                f
+builtins.h     f
+bashhist.h     f
+bashline.h     f
+conftypes.h    f
+patchlevel.h   f
+variables.h    f
+array.h                f
+arrayfunc.h    f
+assoc.h                f
+jobs.h         f
+findcmd.h      f
+hashlib.h      f
+quit.h         f
+flags.h                f
+shell.h                f
+syntax.h       f
+pathexp.h      f
+parser.h       f
+pcomplete.h    f
+sig.h          f
+test.h         f
+trap.h         f
+general.h      f
+unwind_prot.h  f
+input.h                f
+error.h                f
+command.h      f
+externs.h      f
+siglist.h      f
+subst.h                f
+dispose_cmd.h  f
+hashcmd.h      f
+bashansi.h     f
+bashjmp.h      f
+bashintl.h     f
+make_cmd.h     f
+execute_cmd.h  f
+redir.h                f
+bashtypes.h    f
+mailcheck.h    f
+xmalloc.h      f
+y.tab.c                f
+y.tab.h                f
+parser-built   f
+pathnames.h.in f
+builtins/Makefile.in   f
+builtins/alias.def     f
+builtins/bind.def      f
+builtins/break.def     f
+builtins/builtin.def   f
+builtins/caller.def    f
+builtins/cd.def                f
+builtins/colon.def     f
+builtins/command.def   f
+builtins/complete.def  f
+builtins/common.c      f
+builtins/declare.def   f
+builtins/echo.def      f
+builtins/enable.def    f
+builtins/eval.def      f
+builtins/evalfile.c    f
+builtins/evalstring.c  f
+builtins/exec.def      f
+builtins/exit.def      f
+builtins/fc.def                f
+builtins/fg_bg.def     f
+builtins/gen-helpfiles.c       f
+builtins/getopt.c      f
+builtins/getopt.h      f
+builtins/getopts.def   f
+builtins/hash.def      f
+builtins/help.def      f
+builtins/let.def       f
+builtins/history.def   f
+builtins/jobs.def      f
+builtins/kill.def      f
+builtins/mapfile.def   f
+builtins/mkbuiltins.c  f
+builtins/printf.def    f
+builtins/pushd.def     f
+builtins/read.def      f
+builtins/reserved.def  f
+builtins/return.def    f
+builtins/set.def       f
+builtins/setattr.def   f
+builtins/shift.def     f
+builtins/shopt.def     f
+builtins/source.def    f
+builtins/suspend.def   f
+builtins/test.def      f
+builtins/times.def     f
+builtins/trap.def      f
+builtins/type.def      f
+builtins/ulimit.def    f
+builtins/umask.def     f
+builtins/wait.def      f
+builtins/psize.c       f
+builtins/psize.sh      f
+builtins/inlib.def     f
+builtins/bashgetopt.c  f
+builtins/common.h      f
+builtins/bashgetopt.h  f
+cross-build/cygwin32.cache     f
+cross-build/x86-beos.cache     f
+cross-build/opennt.cache       f
+include/ansi_stdlib.h  f
+include/chartypes.h    f
+include/filecntl.h     f
+include/gettext.h      f
+include/maxpath.h      f
+include/memalloc.h     f
+include/ocache.h       f
+include/posixdir.h     f
+include/posixjmp.h     f
+include/posixselect.h  f
+include/posixstat.h    f
+include/posixtime.h    f
+include/posixwait.h    f
+include/shmbchar.h     f
+include/shmbutil.h     f
+include/shtty.h                f
+include/stat-time.h    f
+include/stdc.h         f
+include/systimes.h     f
+include/typemax.h      f
+include/unionwait.h    f
+lib/glob/Makefile.in   f
+lib/glob/sm_loop.c     f
+lib/glob/smatch.c      f
+lib/glob/strmatch.c    f
+lib/glob/strmatch.h    f
+lib/glob/glob.c                f
+lib/glob/glob.h                f
+lib/glob/glob_loop.c   f
+lib/glob/gmisc.c       f
+lib/glob/xmbsrtowcs.c  f
+lib/glob/collsyms.h    f
+lib/glob/doc/Makefile  f
+lib/glob/doc/glob.texi f
+lib/glob/ndir.h                f
+lib/intl/ChangeLog     f
+lib/intl/Makefile.in   f
+lib/intl/VERSION       f
+lib/intl/bindtextdom.c f
+lib/intl/config.charset        f
+lib/intl/dcgettext.c   f
+lib/intl/dcigettext.c  f
+lib/intl/dcngettext.c  f
+lib/intl/dgettext.c    f
+lib/intl/dngettext.c   f
+lib/intl/eval-plural.h f
+lib/intl/explodename.c f
+lib/intl/finddomain.c  f
+lib/intl/gettext.c     f
+lib/intl/gettextP.h    f
+lib/intl/gmo.h         f
+lib/intl/hash-string.h f
+lib/intl/intl-compat.c f
+lib/intl/l10nflist.c   f
+lib/intl/libgnuintl.h.in       f
+lib/intl/loadinfo.h    f
+lib/intl/loadmsgcat.c  f
+lib/intl/localcharset.c        f
+lib/intl/localcharset.h        f
+lib/intl/locale.alias  f
+lib/intl/localealias.c f
+lib/intl/localename.c  f
+lib/intl/log.c         f
+lib/intl/ngettext.c    f
+lib/intl/os2compat.c   f
+lib/intl/os2compat.h   f
+lib/intl/osdep.c       f
+lib/intl/plural-exp.c  f
+lib/intl/plural-exp.h  f
+lib/intl/plural.c      f
+lib/intl/plural.y      f
+lib/intl/ref-add.sin   f
+lib/intl/ref-del.sin   f
+lib/intl/relocatable.c f
+lib/intl/relocatable.h f
+lib/intl/textdomain.c  f
+lib/malloc/Makefile.in f
+lib/malloc/getpagesize.h       f
+lib/malloc/imalloc.h   f
+lib/malloc/mstats.h    f
+lib/malloc/shmalloc.h  f
+lib/malloc/table.h     f
+lib/malloc/watch.h     f
+lib/malloc/alloca.c    f
+lib/malloc/malloc.c    f
+lib/malloc/stats.c     f
+lib/malloc/table.c     f
+lib/malloc/trace.c     f
+lib/malloc/watch.c     f
+lib/malloc/xmalloc.c   f
+lib/malloc/xleaktrace  f       755
+lib/malloc/stub.c      f
+lib/malloc/i386-alloca.s       f
+lib/malloc/x386-alloca.s       f
+lib/readline/COPYING   f
+lib/readline/Makefile.in       f
+lib/readline/ChangeLog f
+lib/readline/README    f
+lib/readline/STANDALONE f
+lib/readline/readline.c        f
+lib/readline/vi_mode.c f
+lib/readline/emacs_keymap.c    f
+lib/readline/vi_keymap.c       f
+lib/readline/history.c f
+lib/readline/histexpand.c      f
+lib/readline/histsearch.c      f
+lib/readline/histfile.c        f
+lib/readline/funmap.c  f
+lib/readline/keymaps.c f
+lib/readline/util.c    f
+lib/readline/terminal.c        f
+lib/readline/xfree.c   f
+lib/readline/xmalloc.c f
+lib/readline/search.c  f
+lib/readline/isearch.c f
+lib/readline/parens.c  f
+lib/readline/rltty.c   f
+lib/readline/compat.c  f
+lib/readline/complete.c        f
+lib/readline/bind.c    f
+lib/readline/display.c f
+lib/readline/signals.c f
+lib/readline/kill.c    f
+lib/readline/text.c    f
+lib/readline/undo.c    f
+lib/readline/macro.c   f
+lib/readline/input.c   f
+lib/readline/callback.c        f
+lib/readline/mbutil.c  f
+lib/readline/misc.c    f
+lib/readline/nls.c     f
+lib/readline/shell.c   f
+lib/readline/colors.c  f
+lib/readline/parse-colors.c    f
+lib/readline/savestring.c      f
+lib/readline/tilde.c   f
+lib/readline/tilde.h   f
+lib/readline/rldefs.h  f
+lib/readline/rlconf.h  f
+lib/readline/rlmbutil.h        f
+lib/readline/rlshell.h f
+lib/readline/rltty.h   f
+lib/readline/rltypedefs.h      f
+lib/readline/rlwinsize.h       f
+lib/readline/readline.h        f
+lib/readline/tcap.h    f
+lib/readline/keymaps.h f
+lib/readline/history.h f
+lib/readline/histlib.h f
+lib/readline/chardefs.h        f
+lib/readline/posixdir.h        f
+lib/readline/posixjmp.h        f
+lib/readline/posixselect.h     f
+lib/readline/posixstat.h       f
+lib/readline/ansi_stdlib.h     f
+lib/readline/rlstdc.h  f
+lib/readline/rlprivate.h       f
+lib/readline/colors.h  f
+lib/readline/parse-colors.h    f
+lib/readline/xmalloc.h f
+lib/readline/doc/Makefile      f
+lib/readline/doc/version.texi  f
+lib/readline/doc/rlman.texi    f
+lib/readline/doc/rltech.texi   f
+lib/readline/doc/rluser.texi   f
+lib/readline/doc/rluserman.texi        f
+lib/readline/doc/history.texi  f
+lib/readline/doc/hstech.texi   f
+lib/readline/doc/hsuser.texi   f
+lib/readline/doc/fdl.texi      f
+lib/readline/examples/Makefile f
+lib/readline/examples/excallback.c     f
+lib/readline/examples/fileman.c        f
+lib/readline/examples/manexamp.c       f
+lib/readline/examples/histexamp.c      f
+lib/readline/examples/rltest.c f
+lib/readline/examples/rl-callbacktest.c        f
+lib/readline/examples/rl.c     f
+lib/readline/examples/rlcat.c  f
+lib/readline/examples/Inputrc  f
+lib/sh/Makefile.in     f
+lib/sh/casemod.c       f
+lib/sh/clktck.c                f
+lib/sh/clock.c         f
+lib/sh/dprintf.c       f
+lib/sh/eaccess.c       f
+lib/sh/fmtullong.c     f
+lib/sh/fmtulong.c      f
+lib/sh/fmtumax.c       f
+lib/sh/fnxform.c       f
+lib/sh/fpurge.c                f
+lib/sh/getcwd.c                f
+lib/sh/getenv.c                f
+lib/sh/inet_aton.c     f
+lib/sh/input_avail.c   f
+lib/sh/itos.c          f
+lib/sh/mailstat.c      f
+lib/sh/makepath.c      f
+lib/sh/mbscasecmp.c    f
+lib/sh/mbschr.c                f
+lib/sh/mbscmp.c                f
+lib/sh/memset.c                f
+lib/sh/mktime.c                f
+lib/sh/netconn.c       f
+lib/sh/netopen.c       f
+lib/sh/oslib.c         f
+lib/sh/pathcanon.c     f
+lib/sh/pathphys.c      f
+lib/sh/rename.c                f
+lib/sh/setlinebuf.c    f
+lib/sh/shmatch.c       f
+lib/sh/shmbchar.c      f
+lib/sh/shquote.c       f
+lib/sh/shtty.c         f
+lib/sh/snprintf.c      f
+lib/sh/spell.c         f
+lib/sh/strcasecmp.c    f
+lib/sh/strcasestr.c    f
+lib/sh/strchrnul.c     f
+lib/sh/strerror.c      f
+lib/sh/strftime.c      f
+lib/sh/stringlist.c    f
+lib/sh/stringvec.c     f
+lib/sh/strnlen.c       f
+lib/sh/strpbrk.c       f
+lib/sh/strstr.c                f
+lib/sh/strtod.c                f
+lib/sh/strtoimax.c     f
+lib/sh/strtol.c                f
+lib/sh/strtoll.c       f
+lib/sh/strtoul.c       f
+lib/sh/strtoull.c      f
+lib/sh/strtoumax.c     f
+lib/sh/strtrans.c      f
+lib/sh/times.c         f
+lib/sh/timeval.c       f
+lib/sh/tmpfile.c       f
+lib/sh/uconvert.c      f
+lib/sh/ufuncs.c                f
+lib/sh/unicode.c       f
+lib/sh/vprint.c                f
+lib/sh/wcsdup.c                f
+lib/sh/wcsnwidth.c     f
+lib/sh/wcswidth.c      f
+lib/sh/winsize.c       f
+lib/sh/zcatfd.c                f
+lib/sh/zgetline.c      f
+lib/sh/zmapfd.c                f
+lib/sh/zread.c         f
+lib/sh/zwrite.c                f
+lib/termcap/Makefile.in        f
+lib/termcap/ltcap.h    f
+lib/termcap/termcap.c  f
+lib/termcap/termcap.h  f
+lib/termcap/tparam.c   f
+lib/termcap/version.c  f
+lib/tilde/README       f
+lib/tilde/Makefile.in  f
+lib/tilde/tilde.c      f
+lib/tilde/tilde.h      f
+lib/tilde/shell.c      f
+m4/stat-time.m4                f
+m4/timespec.m4         f
+po/LINGUAS             f
+po/Makefile.in.in      f
+po/Makevars            f
+po/POTFILES.in         f
+po/README              f
+po/Rules-builtins      f
+po/Rules-quot          f
+po/bash.pot            f
+po/boldquot.sed                f
+po/en@boldquot.gmo     f
+po/en@boldquot.header  f
+po/en@boldquot.po      f
+po/en@quot.gmo         f
+po/en@quot.header      f
+po/en@quot.po          f
+po/af.gmo              f
+po/af.po               f
+po/bg.gmo              f
+po/bg.po               f
+po/ca.gmo              f
+po/ca.po               f
+po/cs.gmo              f
+po/cs.po               f
+po/da.gmo              f
+po/da.po               f
+po/de.gmo              f
+po/de.po               f
+po/eo.gmo              f
+po/eo.po               f
+po/es.gmo              f
+po/es.po               f
+po/et.gmo              f
+po/et.po               f
+po/fi.gmo              f
+po/fi.po               f
+po/fr.gmo              f
+po/fr.po               f
+po/ga.gmo              f
+po/ga.po               f
+po/gl.gmo              f
+po/gl.po               f
+po/hr.gmo              f
+po/hr.po               f
+po/hu.gmo              f
+po/hu.po               f
+po/id.gmo              f
+po/id.po               f
+po/it.gmo              f
+po/it.po               f
+po/ja.gmo              f
+po/ja.po               f
+po/lt.gmo              f
+po/lt.po               f
+po/nl.gmo              f
+po/nl.po               f
+po/pl.gmo              f
+po/pl.po               f
+po/pt_BR.gmo           f
+po/pt_BR.po            f
+po/ro.gmo              f
+po/ro.po               f
+po/ru.gmo              f
+po/ru.po               f
+po/sk.gmo              f
+po/sk.po               f
+po/sl.gmo              f
+po/sl.po               f
+po/sv.gmo              f
+po/sv.po               f
+po/tr.gmo              f
+po/tr.po               f
+po/uk.gmo              f
+po/uk.po               f
+po/vi.gmo              f
+po/vi.po               f
+po/zh_CN.gmo           f
+po/zh_CN.po            f
+po/zh_TW.gmo           f
+po/zh_TW.po            f
+po/insert-header.sin   f
+po/quot.sed            f
+po/remove-potcdate.sin f
+CWRU/misc/open-files.c f
+CWRU/misc/sigs.c       f
+CWRU/misc/sigstat.c    f
+CWRU/misc/bison                f
+CWRU/misc/errlist.c    f
+CWRU/misc/hpux10-dlfcn.h       f
+CWRU/PLATFORMS         f
+CWRU/README            f
+CWRU/changelog         f
+CWRU/sh-redir-hack     f
+doc/FAQ                f
+doc/Makefile.in        f
+doc/bash.1     f
+doc/bashbug.1  f
+doc/builtins.1 f
+doc/rbash.1    f
+doc/README     f
+doc/INTRO      f
+doc/texinfo.tex        f
+doc/bashref.texi       f
+doc/version.texi       f
+doc/bashref.info       f
+doc/article.ms f
+doc/htmlpost.sh        f       755
+doc/infopost.sh        f       755
+doc/fdl.texi   f
+doc/fdl.txt    f
+support/Makefile.in    f
+support/bashversion.c  f
+support/checkbashisms  f       755
+support/config.guess   f
+support/config.rpath   f       755
+support/config.sub     f
+support/printenv.sh    f       755
+support/printenv.c     f
+support/bash.xbm       f
+support/missing                f       755
+support/mkclone                f       755
+support/mkconffiles    f       755
+support/mkdirs         f       755
+support/mkinstalldirs  f       755
+support/mkversion.sh   f       755
+support/mksignames.c   f
+support/signames.c     f
+support/bashbug.sh     f
+support/man2html.c     f
+support/recho.c                f
+support/zecho.c                f
+support/xcase.c                f
+support/SYMLINKS       f
+support/fixlinks       f       755
+support/install.sh     f       755
+support/texi2dvi       f       755
+support/texi2html      f       755
+support/xenix-link.sh  f       755
+support/shobj-conf     f       755
+support/rlvers.sh      f       755
+examples/INDEX.txt     f
+examples/INDEX.html    f
+#examples/obashdb/PERMISSION   f
+#examples/obashdb/README       f
+#examples/obashdb/bashdb               f
+#examples/obashdb/bashdb.el    f
+examples/complete/bash_completion      f
+examples/complete/cdfunc       f
+examples/complete/complete-examples    f
+#examples/complete/complete.ianmac     f
+#examples/complete/complete2.ianmac    f
+#examples/complete/complete.freebsd    f
+#examples/complete/complete.gnu-longopt        f
+examples/complete/bashcc-1.0.1.tar.gz  f
+examples/loadables/README      f
+examples/loadables/template.c  f
+examples/loadables/Makefile.in f
+examples/loadables/necho.c     f
+examples/loadables/hello.c     f
+examples/loadables/print.c     f
+examples/loadables/realpath.c  f
+examples/loadables/sleep.c     f
+examples/loadables/strftime.c  f
+examples/loadables/truefalse.c f
+#examples/loadables/getconf.h  f
+#examples/loadables/getconf.c  f
+examples/loadables/finfo.c     f
+examples/loadables/cat.c       f
+#examples/loadables/cut.c      f
+examples/loadables/logname.c   f
+examples/loadables/basename.c  f
+examples/loadables/dirname.c   f
+examples/loadables/tty.c       f
+examples/loadables/pathchk.c   f
+examples/loadables/tee.c       f
+examples/loadables/rmdir.c     f
+examples/loadables/head.c      f
+examples/loadables/printenv.c  f
+examples/loadables/push.c      f
+examples/loadables/id.c                f
+examples/loadables/whoami.c    f
+examples/loadables/uname.c     f
+examples/loadables/sync.c      f
+examples/loadables/mkdir.c     f
+examples/loadables/ln.c                f
+examples/loadables/mypid.c     f
+examples/loadables/unlink.c    f
+examples/loadables/perl/Makefile.in    f
+examples/loadables/perl/README f
+examples/loadables/perl/bperl.c        f
+examples/loadables/perl/iperl.c        f
+#examples/loadables/sprintf.c  f
+#examples/loadables/xtitle.c   f
+examples/functions/array-stuff f
+examples/functions/array-to-string     f
+examples/functions/autoload    f
+examples/functions/autoload.v2 f
+examples/functions/autoload.v3 f
+examples/functions/basename    f
+#examples/functions/basename2  f
+#examples/functions/coproc.bash        f
+#examples/functions/coshell.README     f
+#examples/functions/coshell.bash       f
+examples/functions/csh-compat  f
+#examples/functions/dirfuncs   f
+examples/functions/dirname     f
+#examples/functions/emptydir   f
+examples/functions/exitstat    f
+examples/functions/external    f
+examples/functions/fact                f
+examples/functions/fstty       f
+examples/functions/func                f
+#examples/functions/gethtml    f
+#examples/functions/getoptx.bash       f
+examples/functions/inetaddr    f
+examples/functions/inpath      f
+#examples/functions/isnum.bash f
+examples/functions/isnum2      f
+examples/functions/isvalidip   f
+#examples/functions/jdate.bash f
+#examples/functions/jj.bash    f
+#examples/functions/keep               f
+examples/functions/ksh-cd      f
+examples/functions/ksh-compat-test     f
+examples/functions/kshenv      f
+examples/functions/login       f
+#examples/functions/lowercase  f
+#examples/functions/manpage    f
+#examples/functions/mhfold     f
+#examples/functions/newdirstack.bsh    f
+examples/functions/notify.bash f
+#examples/functions/pathfuncs  f
+#examples/functions/recurse    f
+#examples/functions/repeat2    f
+#examples/functions/repeat3    f
+examples/functions/seq         f
+examples/functions/seq2                f
+examples/functions/shcat       f
+examples/functions/shcat2      f
+examples/functions/sort-pos-params     f
+#examples/functions/sqroot     f
+examples/functions/substr      f
+examples/functions/substr2     f
+#examples/functions/term               f
+examples/functions/whatis      f
+examples/functions/whence      f
+examples/functions/which       f
+#examples/functions/xalias.bash        f
+#examples/functions/xfind.bash f
+#examples/scripts/adventure.sh f
+#examples/scripts/bash-hexdump.sh      f
+#examples/scripts/bcsh.sh      f
+examples/scripts/cat.sh                f
+examples/scripts/center                f
+#examples/scripts/dd-ex.sh     f
+#examples/scripts/fixfiles.bash        f
+#examples/scripts/hanoi.bash   f
+examples/scripts/inpath                f
+#examples/scripts/krand.bash   f
+#examples/scripts/line-input.bash      f
+#examples/scripts/nohup.bash   f
+#examples/scripts/precedence   f
+#examples/scripts/randomcard.bash      f
+#examples/scripts/scrollbar    f
+#examples/scripts/scrollbar2   f
+#examples/scripts/self-repro   f
+#examples/scripts/showperm.bash        f
+examples/scripts/shprompt      f
+examples/scripts/spin.bash     f
+#examples/scripts/timeout      f
+#examples/scripts/timeout2     f
+#examples/scripts/timeout3     f
+#examples/scripts/vtree2               f
+#examples/scripts/vtree3               f
+#examples/scripts/vtree3a      f
+#examples/scripts/websrv.sh    f
+examples/scripts/xterm_title   f
+examples/scripts/zprintf       f
+examples/startup-files/README  f
+examples/startup-files/Bashrc.bfox     f
+examples/startup-files/Bash_aliases    f
+examples/startup-files/Bash_profile    f
+examples/startup-files/bash-profile    f
+examples/startup-files/bashrc  f
+#examples/startup-files/apple/README   f
+#examples/startup-files/apple/aliases  f
+#examples/startup-files/apple/bash.defaults    f
+#examples/startup-files/apple/environment      f
+#examples/startup-files/apple/login    f
+#examples/startup-files/apple/logout   f
+#examples/startup-files/apple/rc               f
+#examples/misc/suncmd.termcap  f
+examples/misc/aliasconv.sh     f
+examples/misc/aliasconv.bash   f
+examples/misc/cshtobash        f
+tests/README           f
+tests/COPYRIGHT                f
+tests/alias.tests      f
+tests/alias1.sub       f
+tests/alias.right      f
+tests/appendop.tests   f
+tests/appendop1.sub    f
+tests/appendop2.sub    f
+tests/appendop.right   f
+tests/arith-for.tests  f
+tests/arith-for.right  f
+tests/arith.tests      f
+tests/arith.right      f
+tests/arith1.sub       f
+tests/arith2.sub       f
+tests/arith3.sub       f
+tests/arith4.sub       f
+tests/arith5.sub       f
+tests/arith6.sub       f
+tests/array.tests      f
+tests/array.right      f
+tests/array1.sub       f
+tests/array2.sub       f
+tests/array3.sub       f
+tests/array4.sub       f
+tests/array5.sub       f
+tests/array6.sub       f
+tests/array7.sub       f
+tests/array8.sub       f
+tests/array9.sub       f
+tests/array10.sub      f
+tests/array11.sub      f
+tests/array12.sub      f
+tests/array13.sub      f
+tests/array14.sub      f
+tests/array-at-star    f
+tests/array2.right     f
+tests/assoc.tests      f
+tests/assoc.right      f
+tests/assoc1.sub       f
+tests/assoc2.sub       f
+tests/assoc3.sub       f
+tests/assoc4.sub       f
+tests/assoc5.sub       f
+tests/assoc6.sub       f
+tests/assoc7.sub       f
+tests/braces.tests     f
+tests/braces.right     f
+tests/builtins.tests   f
+tests/builtins.right   f
+tests/builtins1.sub    f
+tests/builtins2.sub    f
+tests/builtins3.sub    f
+tests/builtins4.sub    f
+tests/source1.sub      f
+tests/source2.sub      f
+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/case1.sub                f
+tests/casemod.tests    f
+tests/casemod.right    f
+tests/comsub.tests     f
+tests/comsub.right     f
+tests/comsub1.sub      f
+tests/comsub-eof.tests f
+tests/comsub-eof0.sub  f
+tests/comsub-eof1.sub  f
+tests/comsub-eof2.sub  f
+tests/comsub-eof3.sub  f
+tests/comsub-eof4.sub  f
+tests/comsub-eof5.sub  f
+tests/comsub-eof.right f
+tests/comsub-posix.tests       f
+tests/comsub-posix.right       f
+tests/comsub-posix1.sub        f
+tests/comsub-posix2.sub        f
+tests/comsub-posix3.sub        f
+tests/cond.tests       f
+tests/cond.right       f
+tests/cond-regexp1.sub f
+tests/cond-regexp2.sub f
+tests/coproc.tests     f
+tests/coproc.right     f
+tests/cprint.tests     f
+tests/cprint.right     f
+tests/dbg-support.right        f
+tests/dbg-support.sub  f
+tests/dbg-support.tests        f
+tests/dbg-support2.right       f
+tests/dbg-support2.tests       f
+tests/dbg-support3.sub f
+tests/dollar-at-star   f
+tests/dollar-at-star1.sub      f
+tests/dollar-at1.sub   f
+tests/dollar-at2.sub   f
+tests/dollar-at3.sub   f
+tests/dollar-at4.sub   f
+tests/dollar-at5.sub   f
+tests/dollar-star1.sub f
+tests/dollar-star2.sub f
+tests/dollar-star3.sub f
+tests/dollar-star4.sub f
+tests/dollar-star5.sub f
+tests/dollar-star6.sub f
+tests/dollar-star7.sub f
+tests/dollar.right     f
+tests/dstack.tests     f
+tests/dstack.right     f
+tests/dstack2.tests    f
+tests/dstack2.right    f
+tests/errors.tests     f
+tests/errors.right     f
+tests/errors1.sub      f
+tests/errors2.sub      f
+tests/execscript       f
+tests/exec.right       f
+tests/exec1.sub                f       755
+tests/exec2.sub                f
+tests/exec3.sub                f
+tests/exec4.sub                f
+tests/exec5.sub                f
+tests/exec6.sub                f
+tests/exec7.sub                f
+tests/exec8.sub                f
+tests/exec9.sub                f
+tests/exp.tests                f
+tests/exp.right                f
+tests/exp1.sub         f
+tests/exp2.sub         f
+tests/exp3.sub         f
+tests/exp4.sub         f
+tests/exp5.sub         f
+tests/exp6.sub         f
+tests/extglob.tests    f
+tests/extglob.right    f
+tests/extglob1.sub     f
+tests/extglob1a.sub    f
+tests/extglob2.tests   f
+tests/extglob2.right   f
+tests/extglob3.tests   f
+tests/extglob3.right   f
+tests/func.tests       f
+tests/func.right       f
+tests/func1.sub                f
+tests/func2.sub                f
+tests/func3.sub                f
+tests/func4.sub                f
+tests/getopts.tests    f
+tests/getopts.right    f
+tests/getopts1.sub     f
+tests/getopts2.sub     f
+tests/getopts3.sub     f
+tests/getopts4.sub     f
+tests/getopts5.sub     f
+tests/getopts6.sub     f
+tests/getopts7.sub     f
+tests/glob.tests       f
+tests/glob1.sub                f
+tests/glob.right       f
+tests/globstar.tests   f
+tests/globstar.right   f
+tests/globstar1.sub    f
+tests/globstar2.sub    f
+tests/heredoc.tests    f
+tests/heredoc.right    f
+tests/heredoc1.sub     f
+tests/heredoc2.sub     f
+tests/heredoc3.sub     f
+tests/herestr.tests    f
+tests/herestr.right    f
+tests/histexp.tests    f
+tests/histexp.right    f
+tests/history.tests    f
+tests/history.right    f
+tests/history.list     f       444
+tests/history1.sub     f
+tests/history2.sub     f
+tests/ifs.tests                f
+tests/ifs.right                f
+tests/ifs-posix.tests  f
+tests/ifs-posix.right  f
+tests/input-line.sh    f
+tests/input-line.sub   f
+tests/input.right      f
+tests/intl.tests       f
+tests/intl1.sub                f
+tests/intl2.sub                f
+tests/intl.right       f
+tests/iquote.tests     f
+tests/iquote.right     f
+tests/iquote1.sub      f
+tests/invert.tests     f
+tests/invert.right     f
+tests/jobs.tests       f
+tests/jobs1.sub                f
+tests/jobs2.sub                f
+tests/jobs3.sub                f
+tests/jobs4.sub                f
+tests/jobs5.sub                f
+tests/jobs.right       f
+tests/lastpipe.right   f
+tests/lastpipe.tests   f
+tests/lastpipe1.sub    f
+tests/mapfile.data     f
+tests/mapfile.right    f
+tests/mapfile.tests    f
+tests/mapfile1.sub     f
+tests/more-exp.tests   f
+tests/more-exp.right   f
+tests/nameref.tests    f
+tests/nameref1.sub     f
+tests/nameref2.sub     f
+tests/nameref3.sub     f
+tests/nameref4.sub     f
+tests/nameref5.sub     f
+tests/nameref6.sub     f
+tests/nameref.right    f
+tests/new-exp.tests    f
+tests/new-exp1.sub     f
+tests/new-exp2.sub     f
+tests/new-exp3.sub     f
+tests/new-exp4.sub     f
+tests/new-exp5.sub     f
+tests/new-exp6.sub     f
+tests/new-exp7.sub     f
+tests/new-exp8.sub     f
+tests/new-exp9.sub     f
+tests/new-exp.right    f
+tests/nquote.tests     f
+tests/nquote.right     f
+tests/nquote1.sub      f
+tests/nquote2.sub      f
+tests/nquote1.tests    f
+tests/nquote1.right    f
+tests/nquote2.tests    f
+tests/nquote2.right    f
+tests/nquote3.tests    f
+tests/nquote3.right    f
+tests/nquote4.tests    f
+tests/nquote4.right    f
+tests/nquote5.tests    f
+tests/nquote5.right    f
+tests/posix2.tests     f
+tests/posix2.right     f
+tests/posixexp.tests   f
+tests/posixexp.right   f
+tests/posixexp1.sub    f
+tests/posixexp2.sub    f
+tests/posixexp2.tests  f
+tests/posixexp2.right  f
+tests/posixpat.tests   f
+tests/posixpat.right   f
+tests/posixpipe.tests  f
+tests/posixpipe.right  f
+tests/prec.right       f
+tests/precedence       f
+tests/printf.tests     f
+tests/printf.right     f
+tests/printf1.sub      f
+tests/printf2.sub      f
+tests/printf3.sub      f
+tests/printf4.sub      f
+tests/quote.tests      f
+tests/quote.right      f
+tests/quote1.sub       f
+tests/read.tests       f
+tests/read.right       f
+tests/read1.sub                f
+tests/read2.sub                f
+tests/read3.sub                f
+tests/read4.sub                f
+tests/read5.sub                f
+tests/read6.sub                f
+tests/redir.tests      f
+tests/redir.right      f
+tests/redir1.sub       f
+tests/redir2.sub       f
+tests/redir3.sub       f
+tests/redir3.in1       f
+tests/redir3.in2       f
+tests/redir4.sub       f
+tests/redir4.in1       f
+tests/redir5.sub       f
+tests/redir6.sub       f
+tests/redir7.sub       f
+tests/redir8.sub       f
+tests/redir9.sub       f
+tests/redir10.sub      f
+tests/rhs-exp.tests    f
+tests/rhs-exp.right    f
+tests/rhs-exp1.sub     f
+tests/rsh.tests                f
+tests/rsh.right                f
+tests/run-all          f
+tests/run-minimal      f
+tests/run-alias                f
+tests/run-appendop     f
+tests/run-arith-for    f
+tests/run-arith                f
+tests/run-array                f
+tests/run-array2       f
+tests/run-assoc                f
+tests/run-braces       f
+tests/run-builtins     f
+tests/run-case         f
+tests/run-casemod      f
+tests/run-comsub       f
+tests/run-comsub-eof   f
+tests/run-comsub-posix f
+tests/run-cond         f
+tests/run-coproc       f
+tests/run-cprint       f
+tests/run-dbg-support  f
+tests/run-dbg-support2 f
+tests/run-dirstack     f
+tests/run-dollars      f
+tests/run-errors       f
+tests/run-execscript   f
+tests/run-exp-tests    f
+tests/run-extglob      f
+tests/run-extglob2     f
+tests/run-extglob3     f
+tests/run-func         f
+tests/run-getopts      f
+tests/run-glob-test    f
+tests/run-globstar     f
+tests/run-heredoc      f
+tests/run-herestr      f
+tests/run-histexpand   f
+tests/run-history      f
+tests/run-ifs          f
+tests/run-ifs-posix    f
+tests/run-input-test   f
+tests/run-intl         f
+tests/run-iquote       f
+tests/run-invert       f
+tests/run-jobs         f
+tests/run-lastpipe     f
+tests/run-mapfile      f
+tests/run-more-exp     f
+tests/run-nameref      f
+tests/run-new-exp      f
+tests/run-nquote       f
+tests/run-nquote1      f
+tests/run-nquote2      f
+tests/run-nquote3      f
+tests/run-nquote4      f
+tests/run-nquote5      f
+tests/run-posix2       f
+tests/run-posixexp     f
+tests/run-posixexp2    f
+tests/run-posixpat     f
+tests/run-posixpipe    f
+tests/run-precedence   f
+tests/run-printf       f
+tests/run-quote                f
+tests/run-read         f
+tests/run-redir                f
+tests/run-rhs-exp      f
+tests/run-rsh          f
+tests/run-set-e                f
+tests/run-set-x                f
+tests/run-shopt                f
+tests/run-strip                f
+tests/run-test         f
+tests/run-tilde                f
+tests/run-tilde2       f
+tests/run-trap         f
+tests/run-type         f
+tests/run-varenv       f
+tests/run-vredir       f
+tests/set-e.tests      f
+tests/set-e1.sub       f
+tests/set-e2.sub       f
+tests/set-e3.sub       f
+tests/set-e3a.sub      f
+tests/set-e.right      f
+tests/set-x.tests      f
+tests/set-x1.sub       f
+tests/set-x.right      f
+tests/shopt.tests      f       
+tests/shopt.right      f
+tests/strip.tests      f
+tests/strip.right      f
+tests/test.tests       f
+tests/test.right       f
+tests/tilde.tests      f
+tests/tilde.right      f
+tests/tilde2.tests     f
+tests/tilde2.right     f
+tests/trap.tests       f
+tests/trap.right       f
+tests/trap1.sub                f       755
+tests/trap2.sub                f       755
+tests/trap2a.sub       f       755
+tests/trap3.sub                f
+tests/trap4.sub                f
+tests/trap5.sub                f
+tests/type.tests       f
+tests/type.right       f
+tests/type1.sub                f
+tests/type2.sub                f
+tests/type3.sub                f
+tests/type4.sub                f
+tests/unicode1.sub     f
+tests/unicode2.sub     f
+tests/varenv.right     f
+tests/varenv.sh                f
+tests/varenv1.sub      f
+tests/varenv2.sub      f
+tests/varenv3.sub      f
+tests/varenv4.sub      f
+tests/varenv5.sub      f
+tests/version          f
+tests/version.mini     f
+tests/vredir.tests     f
+tests/vredir.right     f
+tests/vredir1.sub      f
+tests/vredir2.sub      f
+tests/vredir3.sub      f
+tests/vredir4.sub      f
+tests/vredir5.sub      f
+tests/vredir6.sub      f
+tests/misc/dev-tcp.tests       f
+tests/misc/perf-script f
+tests/misc/perftest    f
+tests/misc/read-nchars.tests   f
+tests/misc/redir-t2.sh f
+tests/misc/run-r2.sh   f
+tests/misc/sigint-1.sh         f
+tests/misc/sigint-2.sh         f
+tests/misc/sigint-3.sh         f
+tests/misc/sigint-4.sh         f
+tests/misc/test-minus-e.1      f
+tests/misc/test-minus-e.2      f
+tests/misc/wait-bg.tests       f
+#examples/scripts.v2/PERMISSION        f
+#examples/scripts.v2/README    f
+#examples/scripts.v2/arc2tarz  f
+#examples/scripts.v2/bashrand  f
+#examples/scripts.v2/cal2day.bash      f
+#examples/scripts.v2/cdhist.bash       f
+#examples/scripts.v2/corename  f
+#examples/scripts.v2/fman      f
+#examples/scripts.v2/frcp      f
+#examples/scripts.v2/lowercase f
+#examples/scripts.v2/ncp       f
+#examples/scripts.v2/newext    f
+#examples/scripts.v2/nmv       f
+#examples/scripts.v2/pages     f
+#examples/scripts.v2/pf        f
+#examples/scripts.v2/ren               f
+#examples/scripts.v2/rename    f
+#examples/scripts.v2/repeat    f
+#examples/scripts.v2/untar     f
+#examples/scripts.v2/uudec     f
+#examples/scripts.v2/uuenc     f
+#examples/scripts.v2/vtree     f
+#examples/scripts.v2/where     f
+#examples/scripts.v2/pmtop     f
+#examples/scripts.v2/shprof    f
+#examples/scripts.noah/PERMISSION      f
+#examples/scripts.noah/README  f
+#examples/scripts.noah/aref.bash       f
+#examples/scripts.noah/bash.sub.bash   f
+#examples/scripts.noah/bash_version.bash       f
+#examples/scripts.noah/meta.bash       f
+#examples/scripts.noah/mktmp.bash      f
+#examples/scripts.noah/number.bash     f
+#examples/scripts.noah/prompt.bash     f
+#examples/scripts.noah/remap_keys.bash f
+#examples/scripts.noah/require.bash    f
+#examples/scripts.noah/send_mail.bash  f
+#examples/scripts.noah/shcat.bash      f
+#examples/scripts.noah/source.bash     f
+#examples/scripts.noah/string.bash     f
+#examples/scripts.noah/stty.bash       f
+#examples/scripts.noah/y_or_n_p.bash   f
index 99897a6311e7e674399b9b741caaab7f8ea77253..a152fe3fbc5466ebdaddc2fa794d75851b1b3347 100644 (file)
--- a/NEWS-4.3
+++ b/NEWS-4.3
@@ -110,6 +110,12 @@ gg. The brace expansion code now treats a failed sequence expansion as a
 
 hh. Shells started to run process substitutions now run any trap set on EXIT.
 
+ii. There is now a configure-time option to enable the globasciiranges option
+    by default.
+
+jj. The read builtin now checks its first variable argument for validity before
+    trying to read any input.
+
 2.  New Features in Readline
 
 a.  Readline is now more responsive to SIGHUP and other fatal signals when
@@ -164,4 +170,3 @@ m.  If the user-settable variable `history-size' is set to a value less than
 n.  New application-settable variable: rl_signal_event_hook; function that is
     called when readline is reading terminal input and read(2) is interrupted
     by a signal.  Currently not called for SIGHUP or SIGTERM.
-
index ef677a5e833dc55fadefaf9eb8e1e5a1066f3141..735b8bfa6be804e9a7e9c4191de0709484ef23f3 100644 (file)
--- a/command.h
+++ b/command.h
@@ -100,6 +100,7 @@ enum command_type { cm_for, cm_case, cm_while, cm_if, cm_simple, cm_select,
 #define W_ARRAYIND     0x1000000       /* word is an array index being expanded */
 #define W_ASSNGLOBAL   0x2000000       /* word is a global assignment to declare (declare/typeset -g) */
 #define W_NOBRACE      0x4000000       /* Don't perform brace expansion */
+#define W_ASSIGNINT    0x8000000       /* word is an integer assignment to declare */
 
 /* Possible values for subshell_environment */
 #define SUBSHELL_ASYNC 0x01    /* subshell caused by `command &' */
diff --git a/command.h~ b/command.h~
new file mode 100644 (file)
index 0000000..ef677a5
--- /dev/null
@@ -0,0 +1,391 @@
+/* command.h -- The structures used internally to represent commands, and
+   the extern declarations of the functions used to create them. */
+
+/* Copyright (C) 1993-2010 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 3 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#if !defined (_COMMAND_H_)
+#define _COMMAND_H_
+
+#include "stdc.h"
+
+/* Instructions describing what kind of thing to do for a redirection. */
+enum r_instruction {
+  r_output_direction, r_input_direction, r_inputa_direction,
+  r_appending_to, r_reading_until, r_reading_string,
+  r_duplicating_input, r_duplicating_output, r_deblank_reading_until,
+  r_close_this, r_err_and_out, r_input_output, r_output_force,
+  r_duplicating_input_word, r_duplicating_output_word,
+  r_move_input, r_move_output, r_move_input_word, r_move_output_word,
+  r_append_err_and_out
+};
+
+/* Redirection flags; values for rflags */
+#define REDIR_VARASSIGN                0x01
+
+/* Redirection errors. */
+#define AMBIGUOUS_REDIRECT  -1
+#define NOCLOBBER_REDIRECT  -2
+#define RESTRICTED_REDIRECT -3 /* can only happen in restricted shells. */
+#define HEREDOC_REDIRECT    -4  /* here-doc temp file can't be created */
+#define BADVAR_REDIRECT            -5  /* something wrong with {varname}redir */
+
+#define CLOBBERING_REDIRECT(ri) \
+  (ri == r_output_direction || ri == r_err_and_out)
+
+#define OUTPUT_REDIRECT(ri) \
+  (ri == r_output_direction || ri == r_input_output || ri == r_err_and_out || ri == r_append_err_and_out)
+
+#define INPUT_REDIRECT(ri) \
+  (ri == r_input_direction || ri == r_inputa_direction || ri == r_input_output)
+
+#define WRITE_REDIRECT(ri) \
+  (ri == r_output_direction || \
+       ri == r_input_output || \
+       ri == r_err_and_out || \
+       ri == r_appending_to || \
+       ri == r_append_err_and_out || \
+       ri == r_output_force)
+
+/* redirection needs translation */
+#define TRANSLATE_REDIRECT(ri) \
+  (ri == r_duplicating_input_word || ri == r_duplicating_output_word || \
+   ri == r_move_input_word || ri == r_move_output_word)
+
+/* Command Types: */
+enum command_type { cm_for, cm_case, cm_while, cm_if, cm_simple, cm_select,
+                   cm_connection, cm_function_def, cm_until, cm_group,
+                   cm_arith, cm_cond, cm_arith_for, cm_subshell, cm_coproc };
+
+/* Possible values for the `flags' field of a WORD_DESC. */
+#define W_HASDOLLAR    0x000001        /* Dollar sign present. */
+#define W_QUOTED       0x000002        /* Some form of quote character is present. */
+#define W_ASSIGNMENT   0x000004        /* This word is a variable assignment. */
+#define W_SPLITSPACE   0x000008        /* Split this word on " " regardless of IFS */
+#define W_NOSPLIT      0x000010        /* Do not perform word splitting on this word because ifs is empty string. */
+#define W_NOGLOB       0x000020        /* Do not perform globbing on this word. */
+#define W_NOSPLIT2     0x000040        /* Don't split word except for $@ expansion (using spaces) because context does not allow it. */
+#define W_TILDEEXP     0x000080        /* Tilde expand this assignment word */
+#define W_DOLLARAT     0x000100        /* $@ and its special handling */
+#define W_DOLLARSTAR   0x000200        /* $* and its special handling */
+#define W_NOCOMSUB     0x000400        /* Don't perform command substitution on this word */
+#define W_ASSIGNRHS    0x000800        /* Word is rhs of an assignment statement */
+#define W_NOTILDE      0x001000        /* Don't perform tilde expansion on this word */
+#define W_ITILDE       0x002000        /* Internal flag for word expansion */
+#define W_NOEXPAND     0x004000        /* Don't expand at all -- do quote removal */
+#define W_COMPASSIGN   0x008000        /* Compound assignment */
+#define W_ASSNBLTIN    0x010000        /* word is a builtin command that takes assignments */
+#define W_ASSIGNARG    0x020000        /* word is assignment argument to command */
+#define W_HASQUOTEDNULL        0x040000        /* word contains a quoted null character */
+#define W_DQUOTE       0x080000        /* word should be treated as if double-quoted */
+#define W_NOPROCSUB    0x100000        /* don't perform process substitution */
+#define W_HASCTLESC    0x200000        /* word contains literal CTLESC characters */
+#define W_ASSIGNASSOC  0x400000        /* word looks like associative array assignment */
+#define W_ASSIGNARRAY  0x800000        /* word looks like a compound indexed array assignment */
+#define W_ARRAYIND     0x1000000       /* word is an array index being expanded */
+#define W_ASSNGLOBAL   0x2000000       /* word is a global assignment to declare (declare/typeset -g) */
+#define W_NOBRACE      0x4000000       /* Don't perform brace expansion */
+
+/* Possible values for subshell_environment */
+#define SUBSHELL_ASYNC 0x01    /* subshell caused by `command &' */
+#define SUBSHELL_PAREN 0x02    /* subshell caused by ( ... ) */
+#define SUBSHELL_COMSUB        0x04    /* subshell caused by `command` or $(command) */
+#define SUBSHELL_FORK  0x08    /* subshell caused by executing a disk command */
+#define SUBSHELL_PIPE  0x10    /* subshell from a pipeline element */
+#define SUBSHELL_PROCSUB 0x20  /* subshell caused by <(command) or >(command) */
+#define SUBSHELL_COPROC        0x40    /* subshell from a coproc pipeline */
+#define SUBSHELL_RESETTRAP 0x80        /* subshell needs to reset trap strings on first call to trap */
+
+/* A structure which represents a word. */
+typedef struct word_desc {
+  char *word;          /* Zero terminated string. */
+  int flags;           /* Flags associated with this word. */
+} WORD_DESC;
+
+/* A linked list of words. */
+typedef struct word_list {
+  struct word_list *next;
+  WORD_DESC *word;
+} WORD_LIST;
+
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Shell Command Structs                       */
+/*                                                                 */
+/* **************************************************************** */
+
+/* What a redirection descriptor looks like.  If the redirection instruction
+   is ri_duplicating_input or ri_duplicating_output, use DEST, otherwise
+   use the file in FILENAME.  Out-of-range descriptors are identified by a
+   negative DEST. */
+
+typedef union {
+  int dest;                    /* Place to redirect REDIRECTOR to, or ... */
+  WORD_DESC *filename;         /* filename to redirect to. */
+} REDIRECTEE;
+
+/* Structure describing a redirection.  If REDIRECTOR is negative, the parser
+   (or translator in redir.c) encountered an out-of-range file descriptor. */
+typedef struct redirect {
+  struct redirect *next;       /* Next element, or NULL. */
+  REDIRECTEE redirector;       /* Descriptor or varname to be redirected. */
+  int rflags;                  /* Private flags for this redirection */
+  int flags;                   /* Flag value for `open'. */
+  enum r_instruction  instruction; /* What to do with the information. */
+  REDIRECTEE redirectee;       /* File descriptor or filename */
+  char *here_doc_eof;          /* The word that appeared in <<foo. */
+} REDIRECT;
+
+/* An element used in parsing.  A single word or a single redirection.
+   This is an ephemeral construct. */
+typedef struct element {
+  WORD_DESC *word;
+  REDIRECT *redirect;
+} ELEMENT;
+
+/* Possible values for command->flags. */
+#define CMD_WANT_SUBSHELL  0x01        /* User wants a subshell: ( command ) */
+#define CMD_FORCE_SUBSHELL 0x02        /* Shell needs to force a subshell. */
+#define CMD_INVERT_RETURN  0x04        /* Invert the exit value. */
+#define CMD_IGNORE_RETURN  0x08        /* Ignore the exit value.  For set -e. */
+#define CMD_NO_FUNCTIONS   0x10 /* Ignore functions during command lookup. */
+#define CMD_INHIBIT_EXPANSION 0x20 /* Do not expand the command words. */
+#define CMD_NO_FORK       0x40 /* Don't fork; just call execve */
+#define CMD_TIME_PIPELINE  0x80 /* Time a pipeline */
+#define CMD_TIME_POSIX    0x100 /* time -p; use POSIX.2 time output spec. */
+#define CMD_AMPERSAND     0x200 /* command & */
+#define CMD_STDIN_REDIR           0x400 /* async command needs implicit </dev/null */
+#define CMD_COMMAND_BUILTIN 0x0800 /* command executed by `command' builtin */
+#define CMD_COPROC_SUBSHELL 0x1000
+#define CMD_LASTPIPE       0x2000
+
+/* What a command looks like. */
+typedef struct command {
+  enum command_type type;      /* FOR CASE WHILE IF CONNECTION or SIMPLE. */
+  int flags;                   /* Flags controlling execution environment. */
+  int line;                    /* line number the command starts on */
+  REDIRECT *redirects;         /* Special redirects for FOR CASE, etc. */
+  union {
+    struct for_com *For;
+    struct case_com *Case;
+    struct while_com *While;
+    struct if_com *If;
+    struct connection *Connection;
+    struct simple_com *Simple;
+    struct function_def *Function_def;
+    struct group_com *Group;
+#if defined (SELECT_COMMAND)
+    struct select_com *Select;
+#endif
+#if defined (DPAREN_ARITHMETIC)
+    struct arith_com *Arith;
+#endif
+#if defined (COND_COMMAND)
+    struct cond_com *Cond;
+#endif
+#if defined (ARITH_FOR_COMMAND)
+    struct arith_for_com *ArithFor;
+#endif
+    struct subshell_com *Subshell;
+    struct coproc_com *Coproc;
+  } value;
+} COMMAND;
+
+/* Structure used to represent the CONNECTION type. */
+typedef struct connection {
+  int ignore;                  /* Unused; simplifies make_command (). */
+  COMMAND *first;              /* Pointer to the first command. */
+  COMMAND *second;             /* Pointer to the second command. */
+  int connector;               /* What separates this command from others. */
+} CONNECTION;
+
+/* Structures used to represent the CASE command. */
+
+/* Values for FLAGS word in a PATTERN_LIST */
+#define CASEPAT_FALLTHROUGH    0x01
+#define CASEPAT_TESTNEXT       0x02
+
+/* Pattern/action structure for CASE_COM. */
+typedef struct pattern_list {
+  struct pattern_list *next;   /* Clause to try in case this one failed. */
+  WORD_LIST *patterns;         /* Linked list of patterns to test. */
+  COMMAND *action;             /* Thing to execute if a pattern matches. */
+  int flags;
+} PATTERN_LIST;
+
+/* The CASE command. */
+typedef struct case_com {
+  int flags;                   /* See description of CMD flags. */
+  int line;                    /* line number the `case' keyword appears on */
+  WORD_DESC *word;             /* The thing to test. */
+  PATTERN_LIST *clauses;       /* The clauses to test against, or NULL. */
+} CASE_COM;
+
+/* FOR command. */
+typedef struct for_com {
+  int flags;           /* See description of CMD flags. */
+  int line;            /* line number the `for' keyword appears on */
+  WORD_DESC *name;     /* The variable name to get mapped over. */
+  WORD_LIST *map_list; /* The things to map over.  This is never NULL. */
+  COMMAND *action;     /* The action to execute.
+                          During execution, NAME is bound to successive
+                          members of MAP_LIST. */
+} FOR_COM;
+
+#if defined (ARITH_FOR_COMMAND)
+typedef struct arith_for_com {
+  int flags;
+  int line;    /* generally used for error messages */
+  WORD_LIST *init;
+  WORD_LIST *test;
+  WORD_LIST *step;
+  COMMAND *action;
+} ARITH_FOR_COM;
+#endif
+
+#if defined (SELECT_COMMAND)
+/* KSH SELECT command. */
+typedef struct select_com {
+  int flags;           /* See description of CMD flags. */
+  int line;            /* line number the `select' keyword appears on */
+  WORD_DESC *name;     /* The variable name to get mapped over. */
+  WORD_LIST *map_list; /* The things to map over.  This is never NULL. */
+  COMMAND *action;     /* The action to execute.
+                          During execution, NAME is bound to the member of
+                          MAP_LIST chosen by the user. */
+} SELECT_COM;
+#endif /* SELECT_COMMAND */
+
+/* IF command. */
+typedef struct if_com {
+  int flags;                   /* See description of CMD flags. */
+  COMMAND *test;               /* Thing to test. */
+  COMMAND *true_case;          /* What to do if the test returned non-zero. */
+  COMMAND *false_case;         /* What to do if the test returned zero. */
+} IF_COM;
+
+/* WHILE command. */
+typedef struct while_com {
+  int flags;                   /* See description of CMD flags. */
+  COMMAND *test;               /* Thing to test. */
+  COMMAND *action;             /* Thing to do while test is non-zero. */
+} WHILE_COM;
+
+#if defined (DPAREN_ARITHMETIC)
+/* The arithmetic evaluation command, ((...)).  Just a set of flags and
+   a WORD_LIST, of which the first element is the only one used, for the
+   time being. */
+typedef struct arith_com {
+  int flags;
+  int line;
+  WORD_LIST *exp;
+} ARITH_COM;
+#endif /* DPAREN_ARITHMETIC */
+
+/* The conditional command, [[...]].  This is a binary tree -- we slippped
+   a recursive-descent parser into the YACC grammar to parse it. */
+#define COND_AND       1
+#define COND_OR                2
+#define COND_UNARY     3
+#define COND_BINARY    4
+#define COND_TERM      5
+#define COND_EXPR      6
+
+typedef struct cond_com {
+  int flags;
+  int line;
+  int type;
+  WORD_DESC *op;
+  struct cond_com *left, *right;
+} COND_COM;
+
+/* The "simple" command.  Just a collection of words and redirects. */
+typedef struct simple_com {
+  int flags;                   /* See description of CMD flags. */
+  int line;                    /* line number the command starts on */
+  WORD_LIST *words;            /* The program name, the arguments,
+                                  variable assignments, etc. */
+  REDIRECT *redirects;         /* Redirections to perform. */
+} SIMPLE_COM;
+
+/* The "function definition" command. */
+typedef struct function_def {
+  int flags;                   /* See description of CMD flags. */
+  int line;                    /* Line number the function def starts on. */
+  WORD_DESC *name;             /* The name of the function. */
+  COMMAND *command;            /* The parsed execution tree. */
+  char *source_file;           /* file in which function was defined, if any */
+} FUNCTION_DEF;
+
+/* A command that is `grouped' allows pipes and redirections to affect all
+   commands in the group. */
+typedef struct group_com {
+  int ignore;                  /* See description of CMD flags. */
+  COMMAND *command;
+} GROUP_COM;
+
+typedef struct subshell_com {
+  int flags;
+  COMMAND *command;
+} SUBSHELL_COM;
+
+#define COPROC_RUNNING 0x01
+#define COPROC_DEAD    0x02
+
+typedef struct coproc {
+  char *c_name;
+  pid_t c_pid;
+  int c_rfd;
+  int c_wfd;
+  int c_rsave;
+  int c_wsave;
+  int c_flags;
+  int c_status;
+  int c_lock;
+} Coproc;
+
+typedef struct coproc_com {
+  int flags;
+  char *name;
+  COMMAND *command;
+} COPROC_COM;
+
+extern COMMAND *global_command;
+extern Coproc sh_coproc;
+
+/* Possible command errors */
+#define CMDERR_DEFAULT 0
+#define CMDERR_BADTYPE 1
+#define CMDERR_BADCONN 2
+#define CMDERR_BADJUMP 3
+
+#define CMDERR_LAST    3
+
+/* Forward declarations of functions declared in copy_cmd.c. */
+
+extern FUNCTION_DEF *copy_function_def_contents __P((FUNCTION_DEF *, FUNCTION_DEF *));
+extern FUNCTION_DEF *copy_function_def __P((FUNCTION_DEF *));
+
+extern WORD_DESC *copy_word __P((WORD_DESC *));
+extern WORD_LIST *copy_word_list __P((WORD_LIST *));
+extern REDIRECT *copy_redirect __P((REDIRECT *));
+extern REDIRECT *copy_redirects __P((REDIRECT *));
+extern COMMAND *copy_command __P((COMMAND *));
+
+#endif /* _COMMAND_H_ */
diff --git a/ddd1 b/ddd1
new file mode 100644 (file)
index 0000000..69fb4bd
--- /dev/null
+++ b/ddd1
@@ -0,0 +1,385 @@
+*** ../bash-4.2-patched/lib/readline/readline.c        2010-07-25 17:07:40.000000000 -0400
+--- lib/readline/readline.c    2013-07-21 15:52:16.000000000 -0400
+***************
+*** 2,6 ****
+     with emacs style editing and completion. */
+  
+! /* Copyright (C) 1987-2009 Free Software Foundation, Inc.
+  
+     This file is part of the GNU Readline Library (Readline), a library
+--- 2,6 ----
+     with emacs style editing and completion. */
+  
+! /* Copyright (C) 1987-2013 Free Software Foundation, Inc.
+  
+     This file is part of the GNU Readline Library (Readline), a library
+***************
+*** 113,117 ****
+  Keymap _rl_keymap = emacs_standard_keymap;
+  
+- 
+  /* The current style of editing. */
+  int rl_editing_mode = emacs_mode;
+--- 113,116 ----
+***************
+*** 246,249 ****
+--- 245,267 ----
+  _rl_keyseq_cxt *_rl_kscxt = 0;
+  
++ int rl_executing_key;
++ char *rl_executing_keyseq = 0;
++ int _rl_executing_keyseq_size = 0;
++ 
++ /* Timeout (specified in milliseconds) when reading characters making up an
++    ambiguous multiple-key sequence */
++ int _rl_keyseq_timeout = 500;
++ 
++ #define RESIZE_KEYSEQ_BUFFER() \
++   do \
++     { \
++       if (rl_key_sequence_length + 2 >= _rl_executing_keyseq_size) \
++      { \
++        _rl_executing_keyseq_size += 16; \
++        rl_executing_keyseq = xrealloc (rl_executing_keyseq, _rl_executing_keyseq_size); \
++      } \
++     } \
++   while (0);
++         
+  /* Forward declarations used by the display, termcap, and history code. */
+  
+***************
+*** 280,283 ****
+--- 298,305 ----
+  int _rl_echo_control_chars = 1;
+  
++ /* Non-zero means to prefix the displayed prompt with a character indicating
++    the editing mode: @ for emacs, : for vi-command, + for vi-insert. */
++ int _rl_show_mode_in_prompt = 0;
++ 
+  /* **************************************************************** */
+  /*                                                               */
+***************
+*** 353,356 ****
+--- 375,383 ----
+  #endif
+  
++ #if HAVE_DECL_AUDIT_TTY && defined (ENABLE_TTY_AUDIT_SUPPORT)
++   if (value)
++     _rl_audit_tty (value);
++ #endif
++ 
+    return (value);
+  }
+***************
+*** 370,376 ****
+--- 397,413 ----
+    _rl_out_stream = rl_outstream;
+  
++   /* Enable the meta key only for the duration of readline(), if this
++      terminal has one and the terminal has been initialized */
++   if (_rl_enable_meta & RL_ISSTATE (RL_STATE_TERMPREPPED))
++     _rl_enable_meta_key ();
++ 
+    if (rl_startup_hook)
+      (*rl_startup_hook) ();
+  
++ #if defined (VI_MODE)
++   if (rl_editing_mode == vi_mode)
++     rl_vi_insertion_mode (1, 'i');   /* don't want to reset last */
++ #endif /* VI_MODE */
++ 
+    /* If we're not echoing, we still want to at least print a prompt, because
+       rl_redisplay will not do it for us.  If the calling application has a
+***************
+*** 395,403 ****
+      }
+  
+- #if defined (VI_MODE)
+-   if (rl_editing_mode == vi_mode)
+-     rl_vi_insert_mode (1, 'i');
+- #endif /* VI_MODE */
+- 
+    if (rl_pre_input_hook)
+      (*rl_pre_input_hook) ();
+--- 432,435 ----
+***************
+*** 438,441 ****
+--- 470,478 ----
+      rl_free_undo_list ();
+  
++   /* Disable the meta key, if this terminal has one and we were told to use it.
++      The check whether or not we sent the enable string is in
++      _rl_disable_meta_key(); the flag is set in _rl_enable_meta_key */
++   _rl_disable_meta_key ();
++ 
+    /* Restore normal cursor, if available. */
+    _rl_set_insert_mode (RL_IM_INSERT, 0);
+***************
+*** 493,497 ****
+--- 530,538 ----
+        lk = _rl_last_command_was_kill;
+  
++ #if defined (HAVE_POSIX_SIGSETJMP)
++       code = sigsetjmp (_rl_top_level, 0);
++ #else
+        code = setjmp (_rl_top_level);
++ #endif
+  
+        if (code)
+***************
+*** 512,515 ****
+--- 553,557 ----
+         _rl_reset_argument ();
+         rl_key_sequence_length = 0;
++        rl_executing_keyseq[0] = 0;
+       }
+  
+***************
+*** 520,524 ****
+        /* look at input.c:rl_getc() for the circumstances under which this will
+        be returned; punt immediately on read error without converting it to
+!       a newline. */
+        if (c == READERR)
+       {
+--- 562,567 ----
+        /* look at input.c:rl_getc() for the circumstances under which this will
+        be returned; punt immediately on read error without converting it to
+!       a newline; assume that rl_read_key has already called the signal
+!       handler. */
+        if (c == READERR)
+       {
+***************
+*** 532,536 ****
+       }
+  
+!       /* EOF typed to a non-blank line is a <NL>. */
+        if (c == EOF && rl_end)
+       c = NEWLINE;
+--- 575,581 ----
+       }
+  
+!       /* EOF typed to a non-blank line is a <NL>.  If we want to change this,
+!       to force any existing line to be ignored when read(2) reads EOF,
+!       for example, this is the place to change. */
+        if (c == EOF && rl_end)
+       c = NEWLINE;
+***************
+*** 744,750 ****
+         if (RL_ISSTATE (RL_STATE_MACRODEF))
+           _rl_add_macro_char (ESC);
+         map = FUNCTION_TO_KEYMAP (map, ESC);
+         key = UNMETA (key);
+-        rl_key_sequence_length += 2;
+         return (_rl_dispatch (key, map));
+       }
+--- 789,796 ----
+         if (RL_ISSTATE (RL_STATE_MACRODEF))
+           _rl_add_macro_char (ESC);
++        RESIZE_KEYSEQ_BUFFER ();
++        rl_executing_keyseq[rl_key_sequence_length++] = ESC;
+         map = FUNCTION_TO_KEYMAP (map, ESC);
+         key = UNMETA (key);
+         return (_rl_dispatch (key, map));
+       }
+***************
+*** 766,776 ****
+         /* Special case rl_do_lowercase_version (). */
+         if (func == rl_do_lowercase_version)
+           return (_rl_dispatch (_rl_to_lower (key), map));
+  
+         rl_executing_keymap = map;
+  
+         rl_dispatching = 1;
+         RL_SETSTATE(RL_STATE_DISPATCHING);
+!        (*map[key].function)(rl_numeric_arg * rl_arg_sign, key);
+         RL_UNSETSTATE(RL_STATE_DISPATCHING);
+         rl_dispatching = 0;
+--- 812,828 ----
+         /* Special case rl_do_lowercase_version (). */
+         if (func == rl_do_lowercase_version)
++          /* Should we do anything special if key == ANYOTHERKEY? */
+           return (_rl_dispatch (_rl_to_lower (key), map));
+  
+         rl_executing_keymap = map;
++        rl_executing_key = key;
++ 
++        RESIZE_KEYSEQ_BUFFER();
++        rl_executing_keyseq[rl_key_sequence_length++] = key;
++        rl_executing_keyseq[rl_key_sequence_length] = '\0';
+  
+         rl_dispatching = 1;
+         RL_SETSTATE(RL_STATE_DISPATCHING);
+!        r = (*func) (rl_numeric_arg * rl_arg_sign, key);
+         RL_UNSETSTATE(RL_STATE_DISPATCHING);
+         rl_dispatching = 0;
+***************
+*** 789,793 ****
+            shadow function that was overridden when the current keymap
+            was created.  Return -2 to note  that. */
+!        _rl_unget_char  (key);
+         return -2;
+       }
+--- 841,848 ----
+            shadow function that was overridden when the current keymap
+            was created.  Return -2 to note  that. */
+!        if (RL_ISSTATE (RL_STATE_MACROINPUT))
+!          _rl_prev_macro_key ();
+!        else
+!          _rl_unget_char  (key);
+         return -2;
+       }
+***************
+*** 798,802 ****
+            we need to back up the recursion chain and find the last
+            subsequence that is bound to a function. */
+!        _rl_unget_char (key);
+         return -1;
+       }
+--- 853,860 ----
+            we need to back up the recursion chain and find the last
+            subsequence that is bound to a function. */
+!        if (RL_ISSTATE (RL_STATE_MACROINPUT))
+!          _rl_prev_macro_key ();
+!        else
+!          _rl_unget_char (key);
+         return -1;
+       }
+***************
+*** 821,831 ****
+            will be if an arrow key has been pressed, and, if there's not,
+            just dispatch to (what we assume is) rl_vi_movement_mode right
+!           away.  This is essentially an input test with a zero timeout. */
+         if (rl_editing_mode == vi_mode && key == ESC && map == vi_insertion_keymap
+!            && _rl_input_queued (0) == 0)
+           return (_rl_dispatch (ANYOTHERKEY, FUNCTION_TO_KEYMAP (map, key)));
+  #endif
+  
+!        rl_key_sequence_length++;
+         _rl_dispatching_keymap = FUNCTION_TO_KEYMAP (map, key);
+  
+--- 879,893 ----
+            will be if an arrow key has been pressed, and, if there's not,
+            just dispatch to (what we assume is) rl_vi_movement_mode right
+!           away.  This is essentially an input test with a zero timeout (by
+!           default) or a timeout determined by the value of `keyseq-timeout' */
+!        /* _rl_keyseq_timeout specified in milliseconds; _rl_input_queued
+!           takes microseconds, so multiply by 1000 */
+         if (rl_editing_mode == vi_mode && key == ESC && map == vi_insertion_keymap
+!            && _rl_input_queued ((_rl_keyseq_timeout > 0) ? _rl_keyseq_timeout*1000 : 0) == 0)
+           return (_rl_dispatch (ANYOTHERKEY, FUNCTION_TO_KEYMAP (map, key)));
+  #endif
+  
+!        RESIZE_KEYSEQ_BUFFER ();
+!        rl_executing_keyseq[rl_key_sequence_length++] = key;
+         _rl_dispatching_keymap = FUNCTION_TO_KEYMAP (map, key);
+  
+***************
+*** 856,859 ****
+--- 918,933 ----
+  #endif
+  
++        /* Tentative inter-character timeout for potential multi-key
++           sequences?  If no input within timeout, abort sequence and
++           act as if we got non-matching input. */
++        /* _rl_keyseq_timeout specified in milliseconds; _rl_input_queued
++           takes microseconds, so multiply by 1000 */
++        if (_rl_keyseq_timeout > 0 &&
++              (RL_ISSTATE (RL_STATE_INPUTPENDING|RL_STATE_MACROINPUT) == 0) &&
++              _rl_pushed_input_available () == 0 &&
++              _rl_dispatching_keymap[ANYOTHERKEY].function &&
++              _rl_input_queued (_rl_keyseq_timeout*1000) == 0)
++          return (_rl_subseq_result (-2, map, key, got_subseq));
++ 
+         newkey = _rl_subseq_getchar (key);
+         if (newkey < 0)
+***************
+*** 876,879 ****
+--- 950,954 ----
+        if (map[key].function != 0)
+       {
++        rl_executing_keyseq[rl_key_sequence_length] = '\0';
+         macro = savestring ((char *)map[key].function);
+         _rl_with_macro_input (macro);
+***************
+*** 885,888 ****
+--- 960,964 ----
+    if (rl_editing_mode == vi_mode && _rl_keymap == vi_movement_keymap &&
+        key != ANYOTHERKEY &&
++       rl_key_sequence_length == 1 && /* XXX */
+        _rl_vi_textmod_command (key))
+      _rl_vi_set_last (key, rl_numeric_arg, rl_arg_sign);
+***************
+*** 936,940 ****
+        tell the caller that it should try ANYOTHERKEY for an
+        overridden function. */
+!       _rl_unget_char (key);
+        _rl_dispatching_keymap = map;
+        return -2;
+--- 1012,1019 ----
+        tell the caller that it should try ANYOTHERKEY for an
+        overridden function. */
+!       if (RL_ISSTATE (RL_STATE_MACROINPUT))
+!      _rl_prev_macro_key ();
+!       else
+!      _rl_unget_char (key);
+        _rl_dispatching_keymap = map;
+        return -2;
+***************
+*** 943,947 ****
+      {
+        /* OK, back up the chain. */
+!       _rl_unget_char (key);
+        _rl_dispatching_keymap = map;
+        return -1;
+--- 1022,1029 ----
+      {
+        /* OK, back up the chain. */
+!       if (RL_ISSTATE (RL_STATE_MACROINPUT))
+!      _rl_prev_macro_key ();
+!       else
+!      _rl_unget_char (key);
+        _rl_dispatching_keymap = map;
+        return -1;
+***************
+*** 1092,1103 ****
+    bind_arrow_keys ();
+  
+-   /* Enable the meta key, if this terminal has one. */
+-   if (_rl_enable_meta)
+-     _rl_enable_meta_key ();
+- 
+    /* If the completion parser's default word break characters haven't
+       been set yet, then do so now. */
+    if (rl_completer_word_break_characters == (char *)NULL)
+      rl_completer_word_break_characters = (char *)rl_basic_word_break_characters;
+  }
+  
+--- 1174,1190 ----
+    bind_arrow_keys ();
+  
+    /* If the completion parser's default word break characters haven't
+       been set yet, then do so now. */
+    if (rl_completer_word_break_characters == (char *)NULL)
+      rl_completer_word_break_characters = (char *)rl_basic_word_break_characters;
++ 
++ #if defined (COLOR_SUPPORT)
++   if (_rl_colored_stats)
++     _rl_parse_colors ();
++ #endif
++ 
++   rl_executing_keyseq = malloc (_rl_executing_keyseq_size = 16);
++   if (rl_executing_keyseq)
++     rl_executing_keyseq[0] = '\0';
+  }
+  
+***************
+*** 1160,1163 ****
+--- 1247,1254 ----
+    rl_bind_keyseq_if_unbound ("\340M", rl_forward_char);
+    rl_bind_keyseq_if_unbound ("\340K", rl_backward_char);
++   rl_bind_keyseq_if_unbound ("\340G", rl_beg_of_line);
++   rl_bind_keyseq_if_unbound ("\340O", rl_end_of_line);
++   rl_bind_keyseq_if_unbound ("\340S", rl_delete);
++   rl_bind_keyseq_if_unbound ("\340R", rl_overwrite_mode);  
+  #endif
+  
index 4a71150fa5a8e7a542128fcc18930e76983cf521..5f38ab860154b5b9e89cae0b037459a401caca16 100644 (file)
@@ -607,7 +607,7 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out,
             want to note this before execute_in_subshell modifies the
             COMMAND struct.  Need to keep in mind that execute_in_subshell
             runs the exit trap for () subshells itself. */
-         /* This handles { command; } &
+         /* This handles { command; } & */
          s = user_subshell == 0 && command->type == cm_group && pipe_in == NO_PIPE && pipe_out == NO_PIPE && asynchronous;
          /* run exit trap for : | { ...; } and { ...; } | : */
          /* run exit trap for : | ( ...; ) and ( ...; ) | : */
@@ -3748,13 +3748,13 @@ fix_assignment_words (words)
 {
   WORD_LIST *w, *wcmd;
   struct builtin *b;
-  int assoc, global, array;
+  int assoc, global, array, integer;
 
   if (words == 0)
     return;
 
   b = 0;
-  assoc = global = array = 0;
+  assoc = global = array = integer = 0;
 
   /* Skip over assignment statements preceding a command name */
   wcmd = words;
@@ -3789,6 +3789,8 @@ fix_assignment_words (words)
 #endif
        if (global)
          w->word->flags |= W_ASSNGLOBAL;
+       if (integer)
+         w->word->flags |= W_ASSIGNINT;
       }
 #if defined (ARRAY_VARS)
     /* Note that we saw an associative array option to a builtin that takes
@@ -3814,6 +3816,8 @@ fix_assignment_words (words)
          array = 1;
        if ((wcmd->word->flags & W_ASSNBLTIN) && strchr (w->word->word+1, 'g'))
          global = 1;
+       if ((wcmd->word->flags & W_ASSNBLTIN) && strchr (w->word->word+1, 'i'))
+         integer = 1;
       }
 }
 
diff --git a/execute_cmd.c~ b/execute_cmd.c~
new file mode 100644 (file)
index 0000000..f0ef2c6
--- /dev/null
@@ -0,0 +1,5408 @@
+/* execute_cmd.c -- Execute a COMMAND structure. */
+
+/* Copyright (C) 1987-2013 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 3 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX)
+  #pragma alloca
+#endif /* _AIX && RISC6000 && !__GNUC__ */
+
+#include <stdio.h>
+#include "chartypes.h"
+#include "bashtypes.h"
+#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H)
+#  include <sys/file.h>
+#endif
+#include "filecntl.h"
+#include "posixstat.h"
+#include <signal.h>
+#if defined (HAVE_SYS_PARAM_H)
+#  include <sys/param.h>
+#endif
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include "posixtime.h"
+
+#if defined (HAVE_SYS_RESOURCE_H) && !defined (RLIMTYPE)
+#  include <sys/resource.h>
+#endif
+
+#if defined (HAVE_SYS_TIMES_H) && defined (HAVE_TIMES)
+#  include <sys/times.h>
+#endif
+
+#include <errno.h>
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+#define NEED_FPURGE_DECL
+
+#include "bashansi.h"
+#include "bashintl.h"
+
+#include "memalloc.h"
+#include "shell.h"
+#include <y.tab.h>     /* use <...> so we pick it up from the build directory */
+#include "flags.h"
+#include "builtins.h"
+#include "hashlib.h"
+#include "jobs.h"
+#include "execute_cmd.h"
+#include "findcmd.h"
+#include "redir.h"
+#include "trap.h"
+#include "pathexp.h"
+#include "hashcmd.h"
+
+#if defined (COND_COMMAND)
+#  include "test.h"
+#endif
+
+#include "builtins/common.h"
+#include "builtins/builtext.h" /* list of builtins */
+
+#include <glob/strmatch.h>
+#include <tilde/tilde.h>
+
+#if defined (BUFFERED_INPUT)
+#  include "input.h"
+#endif
+
+#if defined (ALIAS)
+#  include "alias.h"
+#endif
+
+#if defined (HISTORY)
+#  include "bashhist.h"
+#endif
+
+extern int dollar_dollar_pid;
+extern int posixly_correct;
+extern int expand_aliases;
+extern int autocd;
+extern int breaking, continuing, loop_level;
+extern int parse_and_execute_level, running_trap, sourcelevel;
+extern int command_string_index, line_number;
+extern int dot_found_in_search;
+extern int already_making_children;
+extern int tempenv_assign_error;
+extern char *the_printed_command, *shell_name;
+extern pid_t last_command_subst_pid;
+extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin;
+extern char **subshell_argv, **subshell_envp;
+extern int subshell_argc;
+extern time_t shell_start_time;
+#if 0
+extern char *glob_argv_flags;
+#endif
+
+extern int job_control;        /* XXX */
+
+extern int close __P((int));
+
+/* Static functions defined and used in this file. */
+static void close_pipes __P((int, int));
+static void do_piping __P((int, int));
+static void bind_lastarg __P((char *));
+static int shell_control_structure __P((enum command_type));
+static void cleanup_redirects __P((REDIRECT *));
+
+#if defined (JOB_CONTROL)
+static int restore_signal_mask __P((sigset_t *));
+#endif
+
+static void async_redirect_stdin __P((void));
+
+static int builtin_status __P((int));
+
+static int execute_for_command __P((FOR_COM *));
+#if defined (SELECT_COMMAND)
+static int displen __P((const char *));
+static int print_index_and_element __P((int, int, WORD_LIST *));
+static void indent __P((int, int));
+static void print_select_list __P((WORD_LIST *, int, int, int));
+static char *select_query __P((WORD_LIST *, int, char *, int));
+static int execute_select_command __P((SELECT_COM *));
+#endif
+#if defined (DPAREN_ARITHMETIC)
+static int execute_arith_command __P((ARITH_COM *));
+#endif
+#if defined (COND_COMMAND)
+static int execute_cond_node __P((COND_COM *));
+static int execute_cond_command __P((COND_COM *));
+#endif
+#if defined (COMMAND_TIMING)
+static int mkfmt __P((char *, int, int, time_t, int));
+static void print_formatted_time __P((FILE *, char *,
+                                     time_t, int, time_t, int,
+                                     time_t, int, int));
+static int time_command __P((COMMAND *, int, int, int, struct fd_bitmap *));
+#endif
+#if defined (ARITH_FOR_COMMAND)
+static intmax_t eval_arith_for_expr __P((WORD_LIST *, int *));
+static int execute_arith_for_command __P((ARITH_FOR_COM *));
+#endif
+static int execute_case_command __P((CASE_COM *));
+static int execute_while_command __P((WHILE_COM *));
+static int execute_until_command __P((WHILE_COM *));
+static int execute_while_or_until __P((WHILE_COM *, int));
+static int execute_if_command __P((IF_COM *));
+static int execute_null_command __P((REDIRECT *, int, int, int));
+static void fix_assignment_words __P((WORD_LIST *));
+static int execute_simple_command __P((SIMPLE_COM *, int, int, int, struct fd_bitmap *));
+static int execute_builtin __P((sh_builtin_func_t *, WORD_LIST *, int, int));
+static int execute_function __P((SHELL_VAR *, WORD_LIST *, int, struct fd_bitmap *, int, int));
+static int execute_builtin_or_function __P((WORD_LIST *, sh_builtin_func_t *,
+                                           SHELL_VAR *,
+                                           REDIRECT *, struct fd_bitmap *, int));
+static void execute_subshell_builtin_or_function __P((WORD_LIST *, REDIRECT *,
+                                                     sh_builtin_func_t *,
+                                                     SHELL_VAR *,
+                                                     int, int, int,
+                                                     struct fd_bitmap *,
+                                                     int));
+static int execute_disk_command __P((WORD_LIST *, REDIRECT *, char *,
+                                     int, int, int, struct fd_bitmap *, int));
+
+static char *getinterp __P((char *, int, int *));
+static void initialize_subshell __P((void));
+static int execute_in_subshell __P((COMMAND *, int, int, int, struct fd_bitmap *));
+#if defined (COPROCESS_SUPPORT)
+static int execute_coproc __P((COMMAND *, int, int, struct fd_bitmap *));
+#endif
+
+static int execute_pipeline __P((COMMAND *, int, int, int, struct fd_bitmap *));
+
+static int execute_connection __P((COMMAND *, int, int, int, struct fd_bitmap *));
+
+static int execute_intern_function __P((WORD_DESC *, FUNCTION_DEF *));
+
+/* Set to 1 if fd 0 was the subject of redirection to a subshell.  Global
+   so that reader_loop can set it to zero before executing a command. */
+int stdin_redir;
+
+/* The name of the command that is currently being executed.
+   `test' needs this, for example. */
+char *this_command_name;
+
+/* The printed representation of the currently-executing command (same as
+   the_printed_command), except when a trap is being executed.  Useful for
+   a debugger to know where exactly the program is currently executing. */
+char *the_printed_command_except_trap;
+
+/* For catching RETURN in a function. */
+int return_catch_flag;
+int return_catch_value;
+procenv_t return_catch;
+
+/* The value returned by the last synchronous command. */
+int last_command_exit_value;
+
+/* Whether or not the last command (corresponding to last_command_exit_value)
+   was terminated by a signal, and, if so, which one. */
+int last_command_exit_signal;
+
+/* Are we currently ignoring the -e option for the duration of a builtin's
+   execution? */
+int builtin_ignoring_errexit = 0;
+
+/* The list of redirections to perform which will undo the redirections
+   that I made in the shell. */
+REDIRECT *redirection_undo_list = (REDIRECT *)NULL;
+
+/* The list of redirections to perform which will undo the internal
+   redirections performed by the `exec' builtin.  These are redirections
+   that must be undone even when exec discards redirection_undo_list. */
+REDIRECT *exec_redirection_undo_list = (REDIRECT *)NULL;
+
+/* When greater than zero, value is the `level' of builtins we are
+   currently executing (e.g. `eval echo a' would have it set to 2). */
+int executing_builtin = 0;
+
+/* Non-zero if we are executing a command list (a;b;c, etc.) */
+int executing_list = 0;
+
+/* Non-zero if failing commands in a command substitution should not exit the
+   shell even if -e is set.  Used to pass the CMD_IGNORE_RETURN flag down to
+   commands run in command substitutions by parse_and_execute. */
+int comsub_ignore_return = 0;
+
+/* Non-zero if we have just forked and are currently running in a subshell
+   environment. */
+int subshell_environment;
+
+/* Count of nested subshells, like SHLVL.  Available via $BASH_SUBSHELL */
+int subshell_level = 0;
+
+/* Currently-executing shell function. */
+SHELL_VAR *this_shell_function;
+
+/* If non-zero, matches in case and [[ ... ]] are case-insensitive */
+int match_ignore_case = 0;
+
+int executing_command_builtin = 0;
+
+struct stat SB;                /* used for debugging */
+
+static int special_builtin_failed;
+
+static COMMAND *currently_executing_command;
+
+/* The line number that the currently executing function starts on. */
+static int function_line_number;
+
+/* XXX - set to 1 if we're running the DEBUG trap and we want to show the line
+   number containing the function name.  Used by executing_line_number to
+   report the correct line number.  Kind of a hack. */
+static int showing_function_line;
+
+/* $LINENO ($BASH_LINENO) for use by an ERR trap.  Global so parse_and_execute
+   can save and restore it. */
+int line_number_for_err_trap;
+
+/* A sort of function nesting level counter */
+int funcnest = 0;
+int funcnest_max = 0;          /* XXX - bash-4.2 */
+
+int lastpipe_opt = 0;
+
+struct fd_bitmap *current_fds_to_close = (struct fd_bitmap *)NULL;
+
+#define FD_BITMAP_DEFAULT_SIZE 32
+
+/* Functions to allocate and deallocate the structures used to pass
+   information from the shell to its children about file descriptors
+   to close. */
+struct fd_bitmap *
+new_fd_bitmap (size)
+     int size;
+{
+  struct fd_bitmap *ret;
+
+  ret = (struct fd_bitmap *)xmalloc (sizeof (struct fd_bitmap));
+
+  ret->size = size;
+
+  if (size)
+    {
+      ret->bitmap = (char *)xmalloc (size);
+      memset (ret->bitmap, '\0', size);
+    }
+  else
+    ret->bitmap = (char *)NULL;
+  return (ret);
+}
+
+void
+dispose_fd_bitmap (fdbp)
+     struct fd_bitmap *fdbp;
+{
+  FREE (fdbp->bitmap);
+  free (fdbp);
+}
+
+void
+close_fd_bitmap (fdbp)
+     struct fd_bitmap *fdbp;
+{
+  register int i;
+
+  if (fdbp)
+    {
+      for (i = 0; i < fdbp->size; i++)
+       if (fdbp->bitmap[i])
+         {
+           close (i);
+           fdbp->bitmap[i] = 0;
+         }
+    }
+}
+
+/* Return the line number of the currently executing command. */
+int
+executing_line_number ()
+{
+  if (executing && showing_function_line == 0 &&
+      (variable_context == 0 || interactive_shell == 0) &&
+      currently_executing_command)
+    {
+#if defined (COND_COMMAND)
+      if (currently_executing_command->type == cm_cond)
+       return currently_executing_command->value.Cond->line;
+#endif
+#if defined (DPAREN_ARITHMETIC)
+      if (currently_executing_command->type == cm_arith)
+       return currently_executing_command->value.Arith->line;
+#endif
+#if defined (ARITH_FOR_COMMAND)
+      if (currently_executing_command->type == cm_arith_for)
+       return currently_executing_command->value.ArithFor->line;
+#endif
+
+       return line_number;
+    }
+  else
+    return line_number;
+}
+
+/* Execute the command passed in COMMAND.  COMMAND is exactly what
+   read_command () places into GLOBAL_COMMAND.  See "command.h" for the
+   details of the command structure.
+
+   EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible
+   return values.  Executing a command with nothing in it returns
+   EXECUTION_SUCCESS. */
+int
+execute_command (command)
+     COMMAND *command;
+{
+  struct fd_bitmap *bitmap;
+  int result;
+
+  current_fds_to_close = (struct fd_bitmap *)NULL;
+  bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE);
+  begin_unwind_frame ("execute-command");
+  add_unwind_protect (dispose_fd_bitmap, (char *)bitmap);
+
+  /* Just do the command, but not asynchronously. */
+  result = execute_command_internal (command, 0, NO_PIPE, NO_PIPE, bitmap);
+
+  dispose_fd_bitmap (bitmap);
+  discard_unwind_frame ("execute-command");
+
+#if defined (PROCESS_SUBSTITUTION)
+  /* don't unlink fifos if we're in a shell function; wait until the function
+     returns. */
+  if (variable_context == 0)
+    unlink_fifo_list ();
+#endif /* PROCESS_SUBSTITUTION */
+
+  QUIT;
+  return (result);
+}
+
+/* Return 1 if TYPE is a shell control structure type. */
+static int
+shell_control_structure (type)
+     enum command_type type;
+{
+  switch (type)
+    {
+#if defined (ARITH_FOR_COMMAND)
+    case cm_arith_for:
+#endif
+#if defined (SELECT_COMMAND)
+    case cm_select:
+#endif
+#if defined (DPAREN_ARITHMETIC)
+    case cm_arith:
+#endif
+#if defined (COND_COMMAND)
+    case cm_cond:
+#endif
+    case cm_case:
+    case cm_while:
+    case cm_until:
+    case cm_if:
+    case cm_for:
+    case cm_group:
+    case cm_function_def:
+      return (1);
+
+    default:
+      return (0);
+    }
+}
+
+/* A function to use to unwind_protect the redirection undo list
+   for loops. */
+static void
+cleanup_redirects (list)
+     REDIRECT *list;
+{
+  do_redirections (list, RX_ACTIVE);
+  dispose_redirects (list);
+}
+
+#if 0
+/* Function to unwind_protect the redirections for functions and builtins. */
+static void
+cleanup_func_redirects (list)
+     REDIRECT *list;
+{
+  do_redirections (list, RX_ACTIVE);
+}
+#endif
+
+void
+dispose_exec_redirects ()
+{
+  if (exec_redirection_undo_list)
+    {
+      dispose_redirects (exec_redirection_undo_list);
+      exec_redirection_undo_list = (REDIRECT *)NULL;
+    }
+}
+
+#if defined (JOB_CONTROL)
+/* A function to restore the signal mask to its proper value when the shell
+   is interrupted or errors occur while creating a pipeline. */
+static int
+restore_signal_mask (set)
+     sigset_t *set;
+{
+  return (sigprocmask (SIG_SETMASK, set, (sigset_t *)NULL));
+}
+#endif /* JOB_CONTROL */
+
+#ifdef DEBUG
+/* A debugging function that can be called from gdb, for instance. */
+void
+open_files ()
+{
+  register int i;
+  int f, fd_table_size;
+
+  fd_table_size = getdtablesize ();
+
+  fprintf (stderr, "pid %ld open files:", (long)getpid ());
+  for (i = 3; i < fd_table_size; i++)
+    {
+      if ((f = fcntl (i, F_GETFD, 0)) != -1)
+       fprintf (stderr, " %d (%s)", i, f ? "close" : "open");
+    }
+  fprintf (stderr, "\n");
+}
+#endif
+
+static void
+async_redirect_stdin ()
+{
+  int fd;
+
+  fd = open ("/dev/null", O_RDONLY);
+  if (fd > 0)
+    {
+      dup2 (fd, 0);
+      close (fd);
+    }
+  else if (fd < 0)
+    internal_error (_("cannot redirect standard input from /dev/null: %s"), strerror (errno));
+}
+
+#define DESCRIBE_PID(pid) do { if (interactive) describe_pid (pid); } while (0)
+
+/* Execute the command passed in COMMAND, perhaps doing it asynchrounously.
+   COMMAND is exactly what read_command () places into GLOBAL_COMMAND.
+   ASYNCHROUNOUS, if non-zero, says to do this command in the background.
+   PIPE_IN and PIPE_OUT are file descriptors saying where input comes
+   from and where it goes.  They can have the value of NO_PIPE, which means
+   I/O is stdin/stdout.
+   FDS_TO_CLOSE is a list of file descriptors to close once the child has
+   been forked.  This list often contains the unusable sides of pipes, etc.
+
+   EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible
+   return values.  Executing a command with nothing in it returns
+   EXECUTION_SUCCESS. */
+int
+execute_command_internal (command, asynchronous, pipe_in, pipe_out,
+                         fds_to_close)
+     COMMAND *command;
+     int asynchronous;
+     int pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  int exec_result, user_subshell, invert, ignore_return, was_error_trap;
+  REDIRECT *my_undo_list, *exec_undo_list;
+  char *tcmd;
+  volatile int last_pid;
+  volatile int save_line_number;
+#if defined (PROCESS_SUBSTITUTION)
+  volatile int ofifo, nfifo, osize, saved_fifo;
+  volatile char *ofifo_list;
+#endif
+
+  if (breaking || continuing)
+    return (last_command_exit_value);
+  if (command == 0 || read_but_dont_execute)
+    return (EXECUTION_SUCCESS);
+
+  QUIT;
+  run_pending_traps ();
+
+#if 0
+  if (running_trap == 0)
+#endif
+    currently_executing_command = command;
+
+  invert = (command->flags & CMD_INVERT_RETURN) != 0;
+
+  /* If we're inverting the return value and `set -e' has been executed,
+     we don't want a failing command to inadvertently cause the shell
+     to exit. */
+  if (exit_immediately_on_error && invert)     /* XXX */
+    command->flags |= CMD_IGNORE_RETURN;       /* XXX */
+
+  exec_result = EXECUTION_SUCCESS;
+
+  /* If a command was being explicitly run in a subshell, or if it is
+     a shell control-structure, and it has a pipe, then we do the command
+     in a subshell. */
+  if (command->type == cm_subshell && (command->flags & CMD_NO_FORK))
+    return (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close));
+
+#if defined (COPROCESS_SUPPORT)
+  if (command->type == cm_coproc)
+    return (execute_coproc (command, pipe_in, pipe_out, fds_to_close));
+#endif
+
+  user_subshell = command->type == cm_subshell || ((command->flags & CMD_WANT_SUBSHELL) != 0);
+
+  if (command->type == cm_subshell ||
+      (command->flags & (CMD_WANT_SUBSHELL|CMD_FORCE_SUBSHELL)) ||
+      (shell_control_structure (command->type) &&
+       (pipe_out != NO_PIPE || pipe_in != NO_PIPE || asynchronous)))
+    {
+      pid_t paren_pid;
+      int s;
+
+      /* Fork a subshell, turn off the subshell bit, turn off job
+        control and call execute_command () on the command again. */
+      line_number_for_err_trap = line_number;
+      tcmd = make_command_string (command);
+      paren_pid = make_child (savestring (tcmd), asynchronous);
+
+      if (user_subshell && signal_is_trapped (ERROR_TRAP) && 
+         signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
+       {
+         FREE (the_printed_command_except_trap);
+         the_printed_command_except_trap = savestring (the_printed_command);
+       }
+
+      if (paren_pid == 0)
+        {
+         /* We want to run the exit trap for forced {} subshells, and we
+            want to note this before execute_in_subshell modifies the
+            COMMAND struct.  Need to keep in mind that execute_in_subshell
+            runs the exit trap for () subshells itself. */
+         /* This handles { command; } & */
+         s = user_subshell == 0 && command->type == cm_group && pipe_in == NO_PIPE && pipe_out == NO_PIPE && asynchronous;
+         /* run exit trap for : | { ...; } and { ...; } | : */
+         /* run exit trap for : | ( ...; ) and ( ...; ) | : */
+         s += user_subshell == 0 && command->type == cm_group && (pipe_in != NO_PIPE || pipe_out != NO_PIPE) && asynchronous == 0;
+
+         last_command_exit_value = execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close);
+         if (s)
+           subshell_exit (last_command_exit_value);
+         else
+           exit (last_command_exit_value);
+         /* NOTREACHED */
+        }
+      else
+       {
+         close_pipes (pipe_in, pipe_out);
+
+#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
+         if (variable_context == 0)    /* wait until shell function completes */
+           unlink_fifo_list ();
+#endif
+         /* If we are part of a pipeline, and not the end of the pipeline,
+            then we should simply return and let the last command in the
+            pipe be waited for.  If we are not in a pipeline, or are the
+            last command in the pipeline, then we wait for the subshell
+            and return its exit status as usual. */
+         if (pipe_out != NO_PIPE)
+           return (EXECUTION_SUCCESS);
+
+         stop_pipeline (asynchronous, (COMMAND *)NULL);
+
+         if (asynchronous == 0)
+           {
+             was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+             invert = (command->flags & CMD_INVERT_RETURN) != 0;
+             ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
+
+             exec_result = wait_for (paren_pid);
+
+             /* If we have to, invert the return value. */
+             if (invert)
+               exec_result = ((exec_result == EXECUTION_SUCCESS)
+                               ? EXECUTION_FAILURE
+                               : EXECUTION_SUCCESS);
+
+             last_command_exit_value = exec_result;
+             if (user_subshell && was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS)
+               {
+                 save_line_number = line_number;
+                 line_number = line_number_for_err_trap;
+                 run_error_trap ();
+                 line_number = save_line_number;
+               }
+
+             if (user_subshell && ignore_return == 0 && invert == 0 && exit_immediately_on_error && exec_result != EXECUTION_SUCCESS)
+               {
+                 run_pending_traps ();
+                 jump_to_top_level (ERREXIT);
+               }
+
+             return (last_command_exit_value);
+           }
+         else
+           {
+             DESCRIBE_PID (paren_pid);
+
+             run_pending_traps ();
+
+             return (EXECUTION_SUCCESS);
+           }
+       }
+    }
+
+#if defined (COMMAND_TIMING)
+  if (command->flags & CMD_TIME_PIPELINE)
+    {
+      if (asynchronous)
+       {
+         command->flags |= CMD_FORCE_SUBSHELL;
+         exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close);
+       }
+      else
+       {
+         exec_result = time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close);
+#if 0
+         if (running_trap == 0)
+#endif
+           currently_executing_command = (COMMAND *)NULL;
+       }
+      return (exec_result);
+    }
+#endif /* COMMAND_TIMING */
+
+  if (shell_control_structure (command->type) && command->redirects)
+    stdin_redir = stdin_redirects (command->redirects);
+
+#if defined (PROCESS_SUBSTITUTION)
+  if (variable_context != 0)
+    {
+      ofifo = num_fifos ();
+      ofifo_list = copy_fifo_list ((int *)&osize);
+      saved_fifo = 1;
+    }
+  else
+    saved_fifo = 0;
+#endif
+
+  /* Handle WHILE FOR CASE etc. with redirections.  (Also '&' input
+     redirection.)  */
+  if (do_redirections (command->redirects, RX_ACTIVE|RX_UNDOABLE) != 0)
+    {
+      cleanup_redirects (redirection_undo_list);
+      redirection_undo_list = (REDIRECT *)NULL;
+      dispose_exec_redirects ();
+#if defined (PROCESS_SUBSTITUTION)
+      if (saved_fifo)
+       free ((void *)ofifo_list);
+#endif
+      return (last_command_exit_value = EXECUTION_FAILURE);
+    }
+
+  if (redirection_undo_list)
+    {
+      /* XXX - why copy here? */
+      my_undo_list = (REDIRECT *)copy_redirects (redirection_undo_list);
+      dispose_redirects (redirection_undo_list);
+      redirection_undo_list = (REDIRECT *)NULL;
+    }
+  else
+    my_undo_list = (REDIRECT *)NULL;
+
+  if (exec_redirection_undo_list)
+    {
+      /* XXX - why copy here? */
+      exec_undo_list = (REDIRECT *)copy_redirects (exec_redirection_undo_list);
+      dispose_redirects (exec_redirection_undo_list);
+      exec_redirection_undo_list = (REDIRECT *)NULL;
+    }
+  else
+    exec_undo_list = (REDIRECT *)NULL;
+
+  if (my_undo_list || exec_undo_list)
+    begin_unwind_frame ("loop_redirections");
+
+  if (my_undo_list)
+    add_unwind_protect ((Function *)cleanup_redirects, my_undo_list);
+
+  if (exec_undo_list)
+    add_unwind_protect ((Function *)dispose_redirects, exec_undo_list);
+
+  ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
+
+  QUIT;
+
+  switch (command->type)
+    {
+    case cm_simple:
+      {
+       save_line_number = line_number;
+       /* We can't rely on variables retaining their values across a
+          call to execute_simple_command if a longjmp occurs as the
+          result of a `return' builtin.  This is true for sure with gcc. */
+#if defined (RECYCLES_PIDS)
+       last_made_pid = NO_PID;
+#endif
+       last_pid = last_made_pid;
+       was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+
+       if (ignore_return && command->value.Simple)
+         command->value.Simple->flags |= CMD_IGNORE_RETURN;
+       if (command->flags & CMD_STDIN_REDIR)
+         command->value.Simple->flags |= CMD_STDIN_REDIR;
+
+       line_number_for_err_trap = line_number = command->value.Simple->line;
+       exec_result =
+         execute_simple_command (command->value.Simple, pipe_in, pipe_out,
+                                 asynchronous, fds_to_close);
+       line_number = save_line_number;
+
+       /* The temporary environment should be used for only the simple
+          command immediately following its definition. */
+       dispose_used_env_vars ();
+
+#if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA)
+       /* Reclaim memory allocated with alloca () on machines which
+          may be using the alloca emulation code. */
+       (void) alloca (0);
+#endif /* (ultrix && mips) || C_ALLOCA */
+
+       /* If we forked to do the command, then we must wait_for ()
+          the child. */
+
+       /* XXX - this is something to watch out for if there are problems
+          when the shell is compiled without job control.  Don't worry about
+          whether or not last_made_pid == last_pid; already_making_children
+          tells us whether or not there are unwaited-for children to wait
+          for and reap. */
+       if (already_making_children && pipe_out == NO_PIPE)
+         {
+           stop_pipeline (asynchronous, (COMMAND *)NULL);
+
+           if (asynchronous)
+             {
+               DESCRIBE_PID (last_made_pid);
+             }
+           else
+#if !defined (JOB_CONTROL)
+             /* Do not wait for asynchronous processes started from
+                startup files. */
+           if (last_made_pid != last_asynchronous_pid)
+#endif
+           /* When executing a shell function that executes other
+              commands, this causes the last simple command in
+              the function to be waited for twice.  This also causes
+              subshells forked to execute builtin commands (e.g., in
+              pipelines) to be waited for twice. */
+             exec_result = wait_for (last_made_pid);
+         }
+      }
+
+      /* 2009/02/13 -- pipeline failure is processed elsewhere.  This handles
+        only the failure of a simple command. */
+      if (was_error_trap && ignore_return == 0 && invert == 0 && pipe_in == NO_PIPE && pipe_out == NO_PIPE && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         line_number = line_number_for_err_trap;
+         run_error_trap ();
+         line_number = save_line_number;
+       }
+
+      if (ignore_return == 0 && invert == 0 &&
+         ((posixly_correct && interactive == 0 && special_builtin_failed) ||
+          (exit_immediately_on_error && pipe_in == NO_PIPE && pipe_out == NO_PIPE && exec_result != EXECUTION_SUCCESS)))
+       {
+         last_command_exit_value = exec_result;
+         run_pending_traps ();
+         jump_to_top_level (ERREXIT);
+       }
+
+      break;
+
+    case cm_for:
+      if (ignore_return)
+       command->value.For->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_for_command (command->value.For);
+      break;
+
+#if defined (ARITH_FOR_COMMAND)
+    case cm_arith_for:
+      if (ignore_return)
+       command->value.ArithFor->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_arith_for_command (command->value.ArithFor);
+      break;
+#endif
+
+#if defined (SELECT_COMMAND)
+    case cm_select:
+      if (ignore_return)
+       command->value.Select->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_select_command (command->value.Select);
+      break;
+#endif
+
+    case cm_case:
+      if (ignore_return)
+       command->value.Case->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_case_command (command->value.Case);
+      break;
+
+    case cm_while:
+      if (ignore_return)
+       command->value.While->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_while_command (command->value.While);
+      break;
+
+    case cm_until:
+      if (ignore_return)
+       command->value.While->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_until_command (command->value.While);
+      break;
+
+    case cm_if:
+      if (ignore_return)
+       command->value.If->flags |= CMD_IGNORE_RETURN;
+      exec_result = execute_if_command (command->value.If);
+      break;
+
+    case cm_group:
+
+      /* This code can be executed from either of two paths: an explicit
+        '{}' command, or via a function call.  If we are executed via a
+        function call, we have already taken care of the function being
+        executed in the background (down there in execute_simple_command ()),
+        and this command should *not* be marked as asynchronous.  If we
+        are executing a regular '{}' group command, and asynchronous == 1,
+        we must want to execute the whole command in the background, so we
+        need a subshell, and we want the stuff executed in that subshell
+        (this group command) to be executed in the foreground of that
+        subshell (i.e. there will not be *another* subshell forked).
+
+        What we do is to force a subshell if asynchronous, and then call
+        execute_command_internal again with asynchronous still set to 1,
+        but with the original group command, so the printed command will
+        look right.
+
+        The code above that handles forking off subshells will note that
+        both subshell and async are on, and turn off async in the child
+        after forking the subshell (but leave async set in the parent, so
+        the normal call to describe_pid is made).  This turning off
+        async is *crucial*; if it is not done, this will fall into an
+        infinite loop of executions through this spot in subshell after
+        subshell until the process limit is exhausted. */
+
+      if (asynchronous)
+       {
+         command->flags |= CMD_FORCE_SUBSHELL;
+         exec_result =
+           execute_command_internal (command, 1, pipe_in, pipe_out,
+                                     fds_to_close);
+       }
+      else
+       {
+         if (ignore_return && command->value.Group->command)
+           command->value.Group->command->flags |= CMD_IGNORE_RETURN;
+         exec_result =
+           execute_command_internal (command->value.Group->command,
+                                     asynchronous, pipe_in, pipe_out,
+                                     fds_to_close);
+       }
+      break;
+
+    case cm_connection:
+      exec_result = execute_connection (command, asynchronous,
+                                       pipe_in, pipe_out, fds_to_close);
+      break;
+
+#if defined (DPAREN_ARITHMETIC)
+    case cm_arith:
+      was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+      if (ignore_return)
+       command->value.Arith->flags |= CMD_IGNORE_RETURN;
+      line_number_for_err_trap = save_line_number = line_number;
+      exec_result = execute_arith_command (command->value.Arith);
+      line_number = save_line_number;
+
+      if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         save_line_number = line_number;
+         line_number = line_number_for_err_trap;
+         run_error_trap ();
+         line_number = save_line_number;
+       }
+
+      if (ignore_return == 0 && invert == 0 && exit_immediately_on_error && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         run_pending_traps ();
+         jump_to_top_level (ERREXIT);
+       }
+
+      break;
+#endif
+
+#if defined (COND_COMMAND)
+    case cm_cond:
+      was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+      if (ignore_return)
+       command->value.Cond->flags |= CMD_IGNORE_RETURN;
+
+      line_number_for_err_trap = save_line_number = line_number;
+      exec_result = execute_cond_command (command->value.Cond);
+      line_number = save_line_number;
+
+      if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         save_line_number = line_number;
+         line_number = line_number_for_err_trap;
+         run_error_trap ();
+         line_number = save_line_number;
+       }
+
+      if (ignore_return == 0 && invert == 0 && exit_immediately_on_error && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         run_pending_traps ();
+         jump_to_top_level (ERREXIT);
+       }
+
+      break;
+#endif
+    
+    case cm_function_def:
+      exec_result = execute_intern_function (command->value.Function_def->name,
+                                            command->value.Function_def);
+      break;
+
+    default:
+      command_error ("execute_command", CMDERR_BADTYPE, command->type, 0);
+    }
+
+  if (my_undo_list)
+    {
+      do_redirections (my_undo_list, RX_ACTIVE);
+      dispose_redirects (my_undo_list);
+    }
+
+  if (exec_undo_list)
+    dispose_redirects (exec_undo_list);
+
+  if (my_undo_list || exec_undo_list)
+    discard_unwind_frame ("loop_redirections");
+
+#if defined (PROCESS_SUBSTITUTION)
+  if (saved_fifo)
+    {
+      nfifo = num_fifos ();
+      if (nfifo > ofifo)
+       close_new_fifos ((char *)ofifo_list, osize);
+      free ((void *)ofifo_list);
+    }
+#endif
+
+  /* Invert the return value if we have to */
+  if (invert)
+    exec_result = (exec_result == EXECUTION_SUCCESS)
+                   ? EXECUTION_FAILURE
+                   : EXECUTION_SUCCESS;
+
+#if defined (DPAREN_ARITHMETIC) || defined (COND_COMMAND)
+  /* This is where we set PIPESTATUS from the exit status of the appropriate
+     compound commands (the ones that look enough like simple commands to
+     cause confusion).  We might be able to optimize by not doing this if
+     subshell_environment != 0. */
+  switch (command->type)
+    {
+#  if defined (DPAREN_ARITHMETIC)
+    case cm_arith:
+#  endif
+#  if defined (COND_COMMAND)
+    case cm_cond:
+#  endif
+      set_pipestatus_from_exit (exec_result);
+      break;
+    }
+#endif
+
+  last_command_exit_value = exec_result;
+  run_pending_traps ();
+#if 0
+  if (running_trap == 0)
+#endif
+    currently_executing_command = (COMMAND *)NULL;
+
+  return (last_command_exit_value);
+}
+
+#if defined (COMMAND_TIMING)
+
+#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY)
+extern struct timeval *difftimeval __P((struct timeval *, struct timeval *, struct timeval *));
+extern struct timeval *addtimeval __P((struct timeval *, struct timeval *, struct timeval *));
+extern int timeval_to_cpu __P((struct timeval *, struct timeval *, struct timeval *));
+#endif
+
+#define POSIX_TIMEFORMAT "real %2R\nuser %2U\nsys %2S"
+#define BASH_TIMEFORMAT  "\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS"
+
+static const int precs[] = { 0, 100, 10, 1 };
+
+/* Expand one `%'-prefixed escape sequence from a time format string. */
+static int
+mkfmt (buf, prec, lng, sec, sec_fraction)
+     char *buf;
+     int prec, lng;
+     time_t sec;
+     int sec_fraction;
+{
+  time_t min;
+  char abuf[INT_STRLEN_BOUND(time_t) + 1];
+  int ind, aind;
+
+  ind = 0;
+  abuf[sizeof(abuf) - 1] = '\0';
+
+  /* If LNG is non-zero, we want to decompose SEC into minutes and seconds. */
+  if (lng)
+    {
+      min = sec / 60;
+      sec %= 60;
+      aind = sizeof(abuf) - 2;
+      do
+       abuf[aind--] = (min % 10) + '0';
+      while (min /= 10);
+      aind++;
+      while (abuf[aind])
+       buf[ind++] = abuf[aind++];
+      buf[ind++] = 'm';
+    }
+
+  /* Now add the seconds. */
+  aind = sizeof (abuf) - 2;
+  do
+    abuf[aind--] = (sec % 10) + '0';
+  while (sec /= 10);
+  aind++;
+  while (abuf[aind])
+    buf[ind++] = abuf[aind++];
+
+  /* We want to add a decimal point and PREC places after it if PREC is
+     nonzero.  PREC is not greater than 3.  SEC_FRACTION is between 0
+     and 999. */
+  if (prec != 0)
+    {
+      buf[ind++] = '.';
+      for (aind = 1; aind <= prec; aind++)
+       {
+         buf[ind++] = (sec_fraction / precs[aind]) + '0';
+         sec_fraction %= precs[aind];
+       }
+    }
+
+  if (lng)
+    buf[ind++] = 's';
+  buf[ind] = '\0';
+
+  return (ind);
+}
+
+/* Interpret the format string FORMAT, interpolating the following escape
+   sequences:
+               %[prec][l][RUS]
+
+   where the optional `prec' is a precision, meaning the number of
+   characters after the decimal point, the optional `l' means to format
+   using minutes and seconds (MMmNN[.FF]s), like the `times' builtin',
+   and the last character is one of
+   
+               R       number of seconds of `real' time
+               U       number of seconds of `user' time
+               S       number of seconds of `system' time
+
+   An occurrence of `%%' in the format string is translated to a `%'.  The
+   result is printed to FP, a pointer to a FILE.  The other variables are
+   the seconds and thousandths of a second of real, user, and system time,
+   resectively. */
+static void
+print_formatted_time (fp, format, rs, rsf, us, usf, ss, ssf, cpu)
+     FILE *fp;
+     char *format;
+     time_t rs;
+     int rsf;
+     time_t us;
+     int usf;
+     time_t ss;
+     int ssf, cpu;
+{
+  int prec, lng, len;
+  char *str, *s, ts[INT_STRLEN_BOUND (time_t) + sizeof ("mSS.FFFF")];
+  time_t sum;
+  int sum_frac;
+  int sindex, ssize;
+
+  len = strlen (format);
+  ssize = (len + 64) - (len % 64);
+  str = (char *)xmalloc (ssize);
+  sindex = 0;
+
+  for (s = format; *s; s++)
+    {
+      if (*s != '%' || s[1] == '\0')
+       {
+         RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64);
+         str[sindex++] = *s;
+       }
+      else if (s[1] == '%')
+       {
+         s++;
+         RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64);
+         str[sindex++] = *s;
+       }
+      else if (s[1] == 'P')
+       {
+         s++;
+#if 0
+         /* clamp CPU usage at 100% */
+         if (cpu > 10000)
+           cpu = 10000;
+#endif
+         sum = cpu / 100;
+         sum_frac = (cpu % 100) * 10;
+         len = mkfmt (ts, 2, 0, sum, sum_frac);
+         RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64);
+         strcpy (str + sindex, ts);
+         sindex += len;
+       }
+      else
+       {
+         prec = 3;     /* default is three places past the decimal point. */
+         lng = 0;      /* default is to not use minutes or append `s' */
+         s++;
+         if (DIGIT (*s))               /* `precision' */
+           {
+             prec = *s++ - '0';
+             if (prec > 3) prec = 3;
+           }
+         if (*s == 'l')                /* `length extender' */
+           {
+             lng = 1;
+             s++;
+           }
+         if (*s == 'R' || *s == 'E')
+           len = mkfmt (ts, prec, lng, rs, rsf);
+         else if (*s == 'U')
+           len = mkfmt (ts, prec, lng, us, usf);
+         else if (*s == 'S')
+           len = mkfmt (ts, prec, lng, ss, ssf);
+         else
+           {
+             internal_error (_("TIMEFORMAT: `%c': invalid format character"), *s);
+             free (str);
+             return;
+           }
+         RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64);
+         strcpy (str + sindex, ts);
+         sindex += len;
+       }
+    }
+
+  str[sindex] = '\0';
+  fprintf (fp, "%s\n", str);
+  fflush (fp);
+
+  free (str);
+}
+
+static int
+time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close)
+     COMMAND *command;
+     int asynchronous, pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  int rv, posix_time, old_flags, nullcmd;
+  time_t rs, us, ss;
+  int rsf, usf, ssf;
+  int cpu;
+  char *time_format;
+
+#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY)
+  struct timeval real, user, sys;
+  struct timeval before, after;
+#  if defined (HAVE_STRUCT_TIMEZONE)
+  struct timezone dtz;                         /* posix doesn't define this */
+#  endif
+  struct rusage selfb, selfa, kidsb, kidsa;    /* a = after, b = before */
+#else
+#  if defined (HAVE_TIMES)
+  clock_t tbefore, tafter, real, user, sys;
+  struct tms before, after;
+#  endif
+#endif
+
+#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY)
+#  if defined (HAVE_STRUCT_TIMEZONE)
+  gettimeofday (&before, &dtz);
+#  else
+  gettimeofday (&before, (void *)NULL);
+#  endif /* !HAVE_STRUCT_TIMEZONE */
+  getrusage (RUSAGE_SELF, &selfb);
+  getrusage (RUSAGE_CHILDREN, &kidsb);
+#else
+#  if defined (HAVE_TIMES)
+  tbefore = times (&before);
+#  endif
+#endif
+
+  posix_time = command && (command->flags & CMD_TIME_POSIX);
+
+  nullcmd = (command == 0) || (command->type == cm_simple && command->value.Simple->words == 0 && command->value.Simple->redirects == 0);
+  if (posixly_correct && nullcmd)
+    {
+#if defined (HAVE_GETRUSAGE)
+      selfb.ru_utime.tv_sec = kidsb.ru_utime.tv_sec = selfb.ru_stime.tv_sec = kidsb.ru_stime.tv_sec = 0;
+      selfb.ru_utime.tv_usec = kidsb.ru_utime.tv_usec = selfb.ru_stime.tv_usec = kidsb.ru_stime.tv_usec = 0;
+      before.tv_sec = shell_start_time;
+      before.tv_usec = 0;
+#else
+      before.tms_utime = before.tms_stime = before.tms_cutime = before.tms_cstime = 0;
+      tbefore = shell_start_time;
+#endif
+    }
+
+  old_flags = command->flags;
+  command->flags &= ~(CMD_TIME_PIPELINE|CMD_TIME_POSIX);
+  rv = execute_command_internal (command, asynchronous, pipe_in, pipe_out, fds_to_close);
+  command->flags = old_flags;
+
+  rs = us = ss = 0;
+  rsf = usf = ssf = cpu = 0;
+
+#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY)
+#  if defined (HAVE_STRUCT_TIMEZONE)
+  gettimeofday (&after, &dtz);
+#  else
+  gettimeofday (&after, (void *)NULL);
+#  endif /* !HAVE_STRUCT_TIMEZONE */
+  getrusage (RUSAGE_SELF, &selfa);
+  getrusage (RUSAGE_CHILDREN, &kidsa);
+
+  difftimeval (&real, &before, &after);
+  timeval_to_secs (&real, &rs, &rsf);
+
+  addtimeval (&user, difftimeval(&after, &selfb.ru_utime, &selfa.ru_utime),
+                    difftimeval(&before, &kidsb.ru_utime, &kidsa.ru_utime));
+  timeval_to_secs (&user, &us, &usf);
+
+  addtimeval (&sys, difftimeval(&after, &selfb.ru_stime, &selfa.ru_stime),
+                   difftimeval(&before, &kidsb.ru_stime, &kidsa.ru_stime));
+  timeval_to_secs (&sys, &ss, &ssf);
+
+  cpu = timeval_to_cpu (&real, &user, &sys);
+#else
+#  if defined (HAVE_TIMES)
+  tafter = times (&after);
+
+  real = tafter - tbefore;
+  clock_t_to_secs (real, &rs, &rsf);
+
+  user = (after.tms_utime - before.tms_utime) + (after.tms_cutime - before.tms_cutime);
+  clock_t_to_secs (user, &us, &usf);
+
+  sys = (after.tms_stime - before.tms_stime) + (after.tms_cstime - before.tms_cstime);
+  clock_t_to_secs (sys, &ss, &ssf);
+
+  cpu = (real == 0) ? 0 : ((user + sys) * 10000) / real;
+
+#  else
+  rs = us = ss = 0;
+  rsf = usf = ssf = cpu = 0;
+#  endif
+#endif
+
+  if (posix_time)
+    time_format = POSIX_TIMEFORMAT;
+  else if ((time_format = get_string_value ("TIMEFORMAT")) == 0)
+    {
+      if (posixly_correct && nullcmd)
+       time_format = "user\t%2lU\nsys\t%2lS";
+      else
+       time_format = BASH_TIMEFORMAT;
+    }
+  if (time_format && *time_format)
+    print_formatted_time (stderr, time_format, rs, rsf, us, usf, ss, ssf, cpu);
+
+  return rv;
+}
+#endif /* COMMAND_TIMING */
+
+/* Execute a command that's supposed to be in a subshell.  This must be
+   called after make_child and we must be running in the child process.
+   The caller will return or exit() immediately with the value this returns. */
+static int
+execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)
+     COMMAND *command;
+     int asynchronous;
+     int pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  int user_subshell, return_code, function_value, should_redir_stdin, invert;
+  int ois, user_coproc;
+  int result;
+  volatile COMMAND *tcom;
+
+  USE_VAR(user_subshell);
+  USE_VAR(user_coproc);
+  USE_VAR(invert);
+  USE_VAR(tcom);
+  USE_VAR(asynchronous);
+
+  subshell_level++;
+  should_redir_stdin = (asynchronous && (command->flags & CMD_STDIN_REDIR) &&
+                         pipe_in == NO_PIPE &&
+                         stdin_redirects (command->redirects) == 0);
+
+  invert = (command->flags & CMD_INVERT_RETURN) != 0;
+  user_subshell = command->type == cm_subshell || ((command->flags & CMD_WANT_SUBSHELL) != 0);
+  user_coproc = command->type == cm_coproc;
+
+  command->flags &= ~(CMD_FORCE_SUBSHELL | CMD_WANT_SUBSHELL | CMD_INVERT_RETURN);
+
+  /* If a command is asynchronous in a subshell (like ( foo ) & or
+     the special case of an asynchronous GROUP command where the
+     the subshell bit is turned on down in case cm_group: below),
+     turn off `asynchronous', so that two subshells aren't spawned.
+     XXX - asynchronous used to be set to 0 in this block, but that
+     means that setup_async_signals was never run.  Now it's set to
+     0 after subshell_environment is set appropriately and setup_async_signals
+     is run.
+
+     This seems semantically correct to me.  For example,
+     ( foo ) & seems to say ``do the command `foo' in a subshell
+     environment, but don't wait for that subshell to finish'',
+     and "{ foo ; bar ; } &" seems to me to be like functions or
+     builtins in the background, which executed in a subshell
+     environment.  I just don't see the need to fork two subshells. */
+
+  /* Don't fork again, we are already in a subshell.  A `doubly
+     async' shell is not interactive, however. */
+  if (asynchronous)
+    {
+#if defined (JOB_CONTROL)
+      /* If a construct like ( exec xxx yyy ) & is given while job
+        control is active, we want to prevent exec from putting the
+        subshell back into the original process group, carefully
+        undoing all the work we just did in make_child. */
+      original_pgrp = -1;
+#endif /* JOB_CONTROL */
+      ois = interactive_shell;
+      interactive_shell = 0;
+      /* This test is to prevent alias expansion by interactive shells that
+        run `(command) &' but to allow scripts that have enabled alias
+        expansion with `shopt -s expand_alias' to continue to expand
+        aliases. */
+      if (ois != interactive_shell)
+       expand_aliases = 0;
+    }
+
+  /* Subshells are neither login nor interactive. */
+  login_shell = interactive = 0;
+
+  if (user_subshell)
+    subshell_environment = SUBSHELL_PAREN;
+  else
+    {
+      subshell_environment = 0;                        /* XXX */
+      if (asynchronous)
+       subshell_environment |= SUBSHELL_ASYNC;
+      if (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
+       subshell_environment |= SUBSHELL_PIPE;
+      if (user_coproc)
+       subshell_environment |= SUBSHELL_COPROC;
+    }
+
+  reset_terminating_signals ();                /* in sig.c */
+  /* Cancel traps, in trap.c. */
+  /* Reset the signal handlers in the child, but don't free the
+     trap strings.  Set a flag noting that we have to free the
+     trap strings if we run trap to change a signal disposition. */
+  reset_signal_handlers ();
+  subshell_environment |= SUBSHELL_RESETTRAP;
+
+  /* Make sure restore_original_signals doesn't undo the work done by
+     make_child to ensure that asynchronous children are immune to SIGINT
+     and SIGQUIT.  Turn off asynchronous to make sure more subshells are
+     not spawned. */
+  if (asynchronous)
+    {
+      setup_async_signals ();
+      asynchronous = 0;
+    }
+
+#if defined (JOB_CONTROL)
+  set_sigchld_handler ();
+#endif /* JOB_CONTROL */
+
+  set_sigint_handler ();
+
+#if defined (JOB_CONTROL)
+  /* Delete all traces that there were any jobs running.  This is
+     only for subshells. */
+  without_job_control ();
+#endif /* JOB_CONTROL */
+
+  if (fds_to_close)
+    close_fd_bitmap (fds_to_close);
+
+  do_piping (pipe_in, pipe_out);
+
+#if defined (COPROCESS_SUPPORT)
+  coproc_closeall ();
+#endif
+
+  /* If this is a user subshell, set a flag if stdin was redirected.
+     This is used later to decide whether to redirect fd 0 to
+     /dev/null for async commands in the subshell.  This adds more
+     sh compatibility, but I'm not sure it's the right thing to do. */
+  if (user_subshell)
+    {
+      stdin_redir = stdin_redirects (command->redirects);
+      restore_default_signal (EXIT_TRAP);
+    }
+
+  /* If this is an asynchronous command (command &), we want to
+     redirect the standard input from /dev/null in the absence of
+     any specific redirection involving stdin. */
+  if (should_redir_stdin && stdin_redir == 0)
+    async_redirect_stdin ();
+
+  /* Do redirections, then dispose of them before recursive call. */
+  if (command->redirects)
+    {
+      if (do_redirections (command->redirects, RX_ACTIVE) != 0)
+       exit (invert ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+
+      dispose_redirects (command->redirects);
+      command->redirects = (REDIRECT *)NULL;
+    }
+
+  if (command->type == cm_subshell)
+    tcom = command->value.Subshell->command;
+  else if (user_coproc)
+    tcom = command->value.Coproc->command;
+  else
+    tcom = command;
+
+  if (command->flags & CMD_TIME_PIPELINE)
+    tcom->flags |= CMD_TIME_PIPELINE;
+  if (command->flags & CMD_TIME_POSIX)
+    tcom->flags |= CMD_TIME_POSIX;
+  
+  /* Make sure the subshell inherits any CMD_IGNORE_RETURN flag. */
+  if ((command->flags & CMD_IGNORE_RETURN) && tcom != command)
+    tcom->flags |= CMD_IGNORE_RETURN;
+
+  /* If this is a simple command, tell execute_disk_command that it
+     might be able to get away without forking and simply exec.
+     This means things like ( sleep 10 ) will only cause one fork.
+     If we're timing the command or inverting its return value, however,
+     we cannot do this optimization. */
+  if ((user_subshell || user_coproc) && (tcom->type == cm_simple || tcom->type == cm_subshell) &&
+      ((tcom->flags & CMD_TIME_PIPELINE) == 0) &&
+      ((tcom->flags & CMD_INVERT_RETURN) == 0))
+    {
+      tcom->flags |= CMD_NO_FORK;
+      if (tcom->type == cm_simple)
+       tcom->value.Simple->flags |= CMD_NO_FORK;
+    }
+
+  invert = (tcom->flags & CMD_INVERT_RETURN) != 0;
+  tcom->flags &= ~CMD_INVERT_RETURN;
+
+  result = setjmp_nosigs (top_level);
+
+  /* If we're inside a function while executing this subshell, we
+     need to handle a possible `return'. */
+  function_value = 0;
+  if (return_catch_flag)
+    function_value = setjmp_nosigs (return_catch);
+
+  /* If we're going to exit the shell, we don't want to invert the return
+     status. */
+  if (result == EXITPROG)
+    invert = 0, return_code = last_command_exit_value;
+  else if (result)
+    return_code = EXECUTION_FAILURE;
+  else if (function_value)
+    return_code = return_catch_value;
+  else
+    return_code = execute_command_internal ((COMMAND *)tcom, asynchronous, NO_PIPE, NO_PIPE, fds_to_close);
+
+  /* If we are asked to, invert the return value. */
+  if (invert)
+    return_code = (return_code == EXECUTION_SUCCESS) ? EXECUTION_FAILURE
+                                                    : EXECUTION_SUCCESS;
+
+  /* If we were explicitly placed in a subshell with (), we need
+     to do the `shell cleanup' things, such as running traps[0]. */
+  if (user_subshell && signal_is_trapped (0))
+    {
+      last_command_exit_value = return_code;
+      return_code = run_exit_trap ();
+    }
+
+  subshell_level--;
+  return (return_code);
+  /* NOTREACHED */
+}
+
+#if defined (COPROCESS_SUPPORT)
+#define COPROC_MAX     16
+
+typedef struct cpelement
+  {
+    struct cpelement *next;
+    struct coproc *coproc;
+  }
+cpelement_t;
+    
+typedef struct cplist
+  {
+    struct cpelement *head;
+    struct cpelement *tail;
+    int ncoproc;
+    int lock;
+  }
+cplist_t;
+
+static struct cpelement *cpe_alloc __P((struct coproc *));
+static void cpe_dispose __P((struct cpelement *));
+static struct cpelement *cpl_add __P((struct coproc *));
+static struct cpelement *cpl_delete __P((pid_t));
+static void cpl_reap __P((void));
+static void cpl_flush __P((void));
+static void cpl_closeall __P((void));
+static struct cpelement *cpl_search __P((pid_t));
+static struct cpelement *cpl_searchbyname __P((const char *));
+static void cpl_prune __P((void));
+
+static void coproc_free __P((struct coproc *));
+
+/* Will go away when there is fully-implemented support for multiple coprocs. */
+Coproc sh_coproc = { 0, NO_PID, -1, -1, 0, 0, 0, 0, 0 };
+
+cplist_t coproc_list = {0, 0, 0};
+
+/* Functions to manage the list of coprocs */
+
+static struct cpelement *
+cpe_alloc (cp)
+     Coproc *cp;
+{
+  struct cpelement *cpe;
+
+  cpe = (struct cpelement *)xmalloc (sizeof (struct cpelement));
+  cpe->coproc = cp;
+  cpe->next = (struct cpelement *)0;
+  return cpe;
+}
+
+static void
+cpe_dispose (cpe)
+      struct cpelement *cpe;
+{
+  free (cpe);
+}
+
+static struct cpelement *
+cpl_add (cp)
+     Coproc *cp;
+{
+  struct cpelement *cpe;
+
+  cpe = cpe_alloc (cp);
+
+  if (coproc_list.head == 0)
+    {
+      coproc_list.head = coproc_list.tail = cpe;
+      coproc_list.ncoproc = 0;                 /* just to make sure */
+    }
+  else
+    {
+      coproc_list.tail->next = cpe;
+      coproc_list.tail = cpe;
+    }
+  coproc_list.ncoproc++;
+
+  return cpe;
+}
+
+static struct cpelement *
+cpl_delete (pid)
+     pid_t pid;
+{
+  struct cpelement *prev, *p;
+
+  for (prev = p = coproc_list.head; p; prev = p, p = p->next)
+    if (p->coproc->c_pid == pid)
+      {
+        prev->next = p->next;  /* remove from list */
+        break;
+      }
+
+  if (p == 0)
+    return 0;          /* not found */
+
+#if defined (DEBUG)
+  itrace("cpl_delete: deleting %d", pid);
+#endif
+
+  /* Housekeeping in the border cases. */
+  if (p == coproc_list.head)
+    coproc_list.head = coproc_list.head->next;
+  else if (p == coproc_list.tail)
+    coproc_list.tail = prev;
+
+  coproc_list.ncoproc--;
+  if (coproc_list.ncoproc == 0)
+    coproc_list.head = coproc_list.tail = 0;
+  else if (coproc_list.ncoproc == 1)
+    coproc_list.tail = coproc_list.head;               /* just to make sure */
+
+  return (p);
+}
+
+static void
+cpl_reap ()
+{
+  struct cpelement *p, *next, *nh, *nt;
+
+  /* Build a new list by removing dead coprocs and fix up the coproc_list
+     pointers when done. */
+  nh = nt = next = (struct cpelement *)0;
+  for (p = coproc_list.head; p; p = next)
+    {
+      next = p->next;
+      if (p->coproc->c_flags & COPROC_DEAD)
+       {
+         coproc_list.ncoproc--;        /* keep running count, fix up pointers later */
+
+#if defined (DEBUG)
+         itrace("cpl_reap: deleting %d", p->coproc->c_pid);
+#endif
+
+         coproc_dispose (p->coproc);
+         cpe_dispose (p);
+       }
+      else if (nh == 0)
+       nh = nt = p;
+      else
+       {
+         nt->next = p;
+         nt = nt->next;
+       }
+    }
+
+  if (coproc_list.ncoproc == 0)
+    coproc_list.head = coproc_list.tail = 0;
+  else
+    {
+      if (nt)
+        nt->next = 0;
+      coproc_list.head = nh;
+      coproc_list.tail = nt;
+      if (coproc_list.ncoproc == 1)
+       coproc_list.tail = coproc_list.head;            /* just to make sure */  
+    }
+}
+
+/* Clear out the list of saved statuses */
+static void
+cpl_flush ()
+{
+  struct cpelement *cpe, *p;
+
+  for (cpe = coproc_list.head; cpe; )
+    {
+      p = cpe;
+      cpe = cpe->next;
+
+      coproc_dispose (p->coproc);
+      cpe_dispose (p);
+    }
+
+  coproc_list.head = coproc_list.tail = 0;
+  coproc_list.ncoproc = 0;
+}
+
+static void
+cpl_closeall ()
+{
+  struct cpelement *cpe;
+
+  for (cpe = coproc_list.head; cpe; cpe = cpe->next)
+    coproc_close (cpe->coproc);
+}
+
+static void
+cpl_fdchk (fd)
+     int fd;
+{
+  struct cpelement *cpe;
+
+  for (cpe = coproc_list.head; cpe; cpe = cpe->next)
+    coproc_checkfd (cpe->coproc, fd);
+}
+
+/* Search for PID in the list of coprocs; return the cpelement struct if
+   found.  If not found, return NULL. */
+static struct cpelement *
+cpl_search (pid)
+     pid_t pid;
+{
+  struct cpelement *cpe;
+
+  for (cpe = coproc_list.head ; cpe; cpe = cpe->next)
+    if (cpe->coproc->c_pid == pid)
+      return cpe;
+  return (struct cpelement *)NULL;
+}
+
+/* Search for the coproc named NAME in the list of coprocs; return the
+   cpelement struct if found.  If not found, return NULL. */
+static struct cpelement *
+cpl_searchbyname (name)
+     const char *name;
+{
+  struct cpelement *cp;
+
+  for (cp = coproc_list.head ; cp; cp = cp->next)
+    if (STREQ (cp->coproc->c_name, name))
+      return cp;
+  return (struct cpelement *)NULL;
+}
+
+#if 0
+static void
+cpl_prune ()
+{
+  struct cpelement *cp;
+
+  while (coproc_list.head && coproc_list.ncoproc > COPROC_MAX)
+    {
+      cp = coproc_list.head;
+      coproc_list.head = coproc_list.head->next;
+      coproc_dispose (cp->coproc);
+      cpe_dispose (cp);
+      coproc_list.ncoproc--;
+    }
+}
+#endif
+
+/* These currently use a single global "shell coproc" but are written in a
+   way to not preclude additional coprocs later (using the list management
+   package above). */
+
+struct coproc *
+getcoprocbypid (pid)
+     pid_t pid;
+{
+#if MULTIPLE_COPROCS
+  struct cpelement *p;
+
+  p = cpl_search (pid);
+  return (p ? p->coproc : 0);
+#else
+  return (pid == sh_coproc.c_pid ? &sh_coproc : 0);
+#endif
+}
+
+struct coproc *
+getcoprocbyname (name)
+     const char *name;
+{
+#if MULTIPLE_COPROCS
+  struct cpelement *p;
+
+  p = cpl_searchbyname (name);
+  return (p ? p->coproc : 0);
+#else
+  return ((sh_coproc.c_name && STREQ (sh_coproc.c_name, name)) ? &sh_coproc : 0);
+#endif
+}
+
+void
+coproc_init (cp)
+     struct coproc *cp;
+{
+  cp->c_name = 0;
+  cp->c_pid = NO_PID;
+  cp->c_rfd = cp->c_wfd = -1;
+  cp->c_rsave = cp->c_wsave = -1;
+  cp->c_flags = cp->c_status = cp->c_lock = 0;
+}
+
+struct coproc *
+coproc_alloc (name, pid)
+     char *name;
+     pid_t pid;
+{
+  struct coproc *cp;
+
+#if MULTIPLE_COPROCS
+  cp = (struct coproc *)xmalloc (sizeof (struct coproc));
+#else
+  cp = &sh_coproc;
+#endif
+  coproc_init (cp);
+  cp->c_lock = 2;
+
+  cp->c_pid = pid;
+  cp->c_name = savestring (name);
+#if MULTIPLE_COPROCS
+  cpl_add (cp);
+#endif
+  cp->c_lock = 0;
+  return (cp);
+}
+
+static void
+coproc_free (cp)
+     struct coproc *cp;
+{
+  free (cp);
+}
+
+void
+coproc_dispose (cp)
+     struct coproc *cp;
+{
+  sigset_t set, oset;
+
+  if (cp == 0)
+    return;
+
+  BLOCK_SIGNAL (SIGCHLD, set, oset);
+  cp->c_lock = 3;
+  coproc_unsetvars (cp);
+  FREE (cp->c_name);
+  coproc_close (cp);
+#if MULTIPLE_COPROCS
+  coproc_free (cp);
+#else
+  coproc_init (cp);
+  cp->c_lock = 0;
+#endif
+  UNBLOCK_SIGNAL (oset);
+}
+
+/* Placeholder for now.  Will require changes for multiple coprocs */
+void
+coproc_flush ()
+{
+#if MULTIPLE_COPROCS
+  cpl_flush ();
+#else
+  coproc_dispose (&sh_coproc);
+#endif
+}
+
+void
+coproc_close (cp)
+     struct coproc *cp;
+{
+  if (cp->c_rfd >= 0)
+    {
+      close (cp->c_rfd);
+      cp->c_rfd = -1;
+    }
+  if (cp->c_wfd >= 0)
+    {
+      close (cp->c_wfd);
+      cp->c_wfd = -1;
+    }
+  cp->c_rsave = cp->c_wsave = -1;
+}
+
+void
+coproc_closeall ()
+{
+#if MULTIPLE_COPROCS
+  cpl_closeall ();
+#else
+  coproc_close (&sh_coproc);   /* XXX - will require changes for multiple coprocs */
+#endif
+}
+
+void
+coproc_reap ()
+{
+#if MULTIPLE_COPROCS
+  cpl_reap ();
+#else
+  struct coproc *cp;
+
+  cp = &sh_coproc;             /* XXX - will require changes for multiple coprocs */
+  if (cp && (cp->c_flags & COPROC_DEAD))
+    coproc_dispose (cp);
+#endif
+}
+
+void
+coproc_rclose (cp, fd)
+     struct coproc *cp;
+     int fd;
+{
+  if (cp->c_rfd >= 0 && cp->c_rfd == fd)
+    {
+      close (cp->c_rfd);
+      cp->c_rfd = -1;
+    }
+}
+
+void
+coproc_wclose (cp, fd)
+     struct coproc *cp;
+     int fd;
+{
+  if (cp->c_wfd >= 0 && cp->c_wfd == fd)
+    {
+      close (cp->c_wfd);
+      cp->c_wfd = -1;
+    }
+}
+
+void
+coproc_checkfd (cp, fd)
+     struct coproc *cp;
+     int fd;
+{
+  int update;
+
+  update = 0;
+  if (cp->c_rfd >= 0 && cp->c_rfd == fd)
+    update = cp->c_rfd = -1;
+  if (cp->c_wfd >= 0 && cp->c_wfd == fd)
+    update = cp->c_wfd = -1;
+  if (update)
+    coproc_setvars (cp);
+}
+
+void
+coproc_fdchk (fd)
+     int fd;
+{
+#if MULTIPLE_COPROCS
+  cpl_fdchk (fd);
+#else
+  coproc_checkfd (&sh_coproc, fd);
+#endif
+}
+
+void
+coproc_fdclose (cp, fd)
+     struct coproc *cp;
+     int fd;
+{
+  coproc_rclose (cp, fd);
+  coproc_wclose (cp, fd);
+  coproc_setvars (cp);
+}
+
+void
+coproc_fdsave (cp)
+     struct coproc *cp;
+{
+  cp->c_rsave = cp->c_rfd;
+  cp->c_wsave = cp->c_wfd;
+}
+
+void
+coproc_fdrestore (cp)
+     struct coproc *cp;
+{
+  cp->c_rfd = cp->c_rsave;
+  cp->c_wfd = cp->c_wsave;
+}
+
+void
+coproc_pidchk (pid, status)
+     pid_t pid;
+{
+  struct coproc *cp;
+
+#if MULTIPLE_COPROCS
+  struct cpelement *cpe;
+
+  cpe = cpl_delete (pid);
+  cp = cpe ? cpe->coproc : 0;
+#else
+  cp = getcoprocbypid (pid);
+#endif
+  if (cp)
+    {
+      cp->c_lock = 4;
+      cp->c_status = status;
+      cp->c_flags |= COPROC_DEAD;
+      cp->c_flags &= ~COPROC_RUNNING;
+      /* Don't dispose the coproc or unset the COPROC_XXX variables because
+        this is executed in a signal handler context.  Wait until coproc_reap
+        takes care of it. */
+      cp->c_lock = 0;
+    }
+}
+
+void
+coproc_setvars (cp)
+     struct coproc *cp;
+{
+  SHELL_VAR *v;
+  char *namevar, *t;
+  int l;
+#if defined (ARRAY_VARS)
+  arrayind_t ind;
+#endif
+
+  if (cp->c_name == 0)
+    return;
+
+  l = strlen (cp->c_name);
+  namevar = xmalloc (l + 16);
+
+#if defined (ARRAY_VARS)
+  v = find_variable (cp->c_name);
+  if (v == 0)
+    v = make_new_array_variable (cp->c_name);
+  if (array_p (v) == 0)
+    v = convert_var_to_array (v);
+
+  t = itos (cp->c_rfd);
+  ind = 0;
+  v = bind_array_variable (cp->c_name, ind, t, 0);
+  free (t);
+
+  t = itos (cp->c_wfd);
+  ind = 1;
+  bind_array_variable (cp->c_name, ind, t, 0);
+  free (t);
+#else
+  sprintf (namevar, "%s_READ", cp->c_name);
+  t = itos (cp->c_rfd);
+  bind_variable (namevar, t, 0);
+  free (t);
+  sprintf (namevar, "%s_WRITE", cp->c_name);
+  t = itos (cp->c_wfd);
+  bind_variable (namevar, t, 0);
+  free (t);
+#endif
+
+  sprintf (namevar, "%s_PID", cp->c_name);
+  t = itos (cp->c_pid);
+  bind_variable (namevar, t, 0);
+  free (t);
+
+  free (namevar);
+}
+
+void
+coproc_unsetvars (cp)
+     struct coproc *cp;
+{
+  int l;
+  char *namevar;
+
+  if (cp->c_name == 0)
+    return;
+
+  l = strlen (cp->c_name);
+  namevar = xmalloc (l + 16);
+
+  sprintf (namevar, "%s_PID", cp->c_name);
+  unbind_variable (namevar);  
+
+#if defined (ARRAY_VARS)
+  unbind_variable (cp->c_name);
+#else
+  sprintf (namevar, "%s_READ", cp->c_name);
+  unbind_variable (namevar);
+  sprintf (namevar, "%s_WRITE", cp->c_name);
+  unbind_variable (namevar);
+#endif  
+
+  free (namevar);
+}
+
+static int
+execute_coproc (command, pipe_in, pipe_out, fds_to_close)
+     COMMAND *command;
+     int pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  int rpipe[2], wpipe[2], estat, invert;
+  pid_t coproc_pid;
+  Coproc *cp;
+  char *tcmd;
+  sigset_t set, oset;
+
+  /* XXX -- can be removed after changes to handle multiple coprocs */
+#if !MULTIPLE_COPROCS
+  if (sh_coproc.c_pid != NO_PID)
+    internal_warning ("execute_coproc: coproc [%d:%s] still exists", sh_coproc.c_pid, sh_coproc.c_name);
+  coproc_init (&sh_coproc);
+#endif
+
+  invert = (command->flags & CMD_INVERT_RETURN) != 0;
+  command_string_index = 0;
+  tcmd = make_command_string (command);
+
+  sh_openpipe ((int *)&rpipe); /* 0 = parent read, 1 = child write */
+  sh_openpipe ((int *)&wpipe); /* 0 = child read, 1 = parent write */
+
+  BLOCK_SIGNAL (SIGCHLD, set, oset);
+
+  coproc_pid = make_child (savestring (tcmd), 1);
+
+  if (coproc_pid == 0)
+    {
+      close (rpipe[0]);
+      close (wpipe[1]);
+
+      UNBLOCK_SIGNAL (oset);
+      estat = execute_in_subshell (command, 1, wpipe[0], rpipe[1], fds_to_close);
+
+      fflush (stdout);
+      fflush (stderr);
+
+      exit (estat);
+    }
+
+  close (rpipe[1]);
+  close (wpipe[0]);
+
+  /* XXX - possibly run Coproc->name through word expansion? */
+  cp = coproc_alloc (command->value.Coproc->name, coproc_pid);
+  cp->c_rfd = rpipe[0];
+  cp->c_wfd = wpipe[1];
+
+  SET_CLOSE_ON_EXEC (cp->c_rfd);
+  SET_CLOSE_ON_EXEC (cp->c_wfd);
+
+  coproc_setvars (cp);
+
+  UNBLOCK_SIGNAL (oset);
+
+#if 0
+  itrace ("execute_coproc: [%d] %s", coproc_pid, the_printed_command);
+#endif
+
+  close_pipes (pipe_in, pipe_out);
+#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
+  unlink_fifo_list ();
+#endif
+  stop_pipeline (1, (COMMAND *)NULL);
+  DESCRIBE_PID (coproc_pid);
+  run_pending_traps ();
+
+  return (invert ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
+}
+#endif
+
+static void
+restore_stdin (s)
+     int s;
+{
+  dup2 (s, 0);
+  close (s);
+}
+
+/* Catch-all cleanup function for lastpipe code for unwind-protects */
+static void
+lastpipe_cleanup (s)
+     int s;
+{
+  unfreeze_jobs_list ();
+}
+
+static int
+execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close)
+     COMMAND *command;
+     int asynchronous, pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  int prev, fildes[2], new_bitmap_size, dummyfd, ignore_return, exec_result;
+  int lstdin, lastpipe_flag, lastpipe_jid;
+  COMMAND *cmd;
+  struct fd_bitmap *fd_bitmap;
+  pid_t lastpid;
+
+#if defined (JOB_CONTROL)
+  sigset_t set, oset;
+  BLOCK_CHILD (set, oset);
+#endif /* JOB_CONTROL */
+
+  ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
+
+  prev = pipe_in;
+  cmd = command;
+
+  while (cmd && cmd->type == cm_connection &&
+        cmd->value.Connection && cmd->value.Connection->connector == '|')
+    {
+      /* Make a pipeline between the two commands. */
+      if (pipe (fildes) < 0)
+       {
+         sys_error (_("pipe error"));
+#if defined (JOB_CONTROL)
+         terminate_current_pipeline ();
+         kill_current_pipeline ();
+         UNBLOCK_CHILD (oset);
+#endif /* JOB_CONTROL */
+         last_command_exit_value = EXECUTION_FAILURE;
+         /* The unwind-protects installed below will take care
+            of closing all of the open file descriptors. */
+         throw_to_top_level ();
+         return (EXECUTION_FAILURE);   /* XXX */
+       }
+
+      /* Here is a problem: with the new file close-on-exec
+        code, the read end of the pipe (fildes[0]) stays open
+        in the first process, so that process will never get a
+        SIGPIPE.  There is no way to signal the first process
+        that it should close fildes[0] after forking, so it
+        remains open.  No SIGPIPE is ever sent because there
+        is still a file descriptor open for reading connected
+        to the pipe.  We take care of that here.  This passes
+        around a bitmap of file descriptors that must be
+        closed after making a child process in execute_simple_command. */
+
+      /* We need fd_bitmap to be at least as big as fildes[0].
+        If fildes[0] is less than fds_to_close->size, then
+        use fds_to_close->size. */
+      new_bitmap_size = (fildes[0] < fds_to_close->size)
+                               ? fds_to_close->size
+                               : fildes[0] + 8;
+
+      fd_bitmap = new_fd_bitmap (new_bitmap_size);
+
+      /* Now copy the old information into the new bitmap. */
+      xbcopy ((char *)fds_to_close->bitmap, (char *)fd_bitmap->bitmap, fds_to_close->size);
+
+      /* And mark the pipe file descriptors to be closed. */
+      fd_bitmap->bitmap[fildes[0]] = 1;
+
+      /* In case there are pipe or out-of-processes errors, we
+        want all these file descriptors to be closed when
+        unwind-protects are run, and the storage used for the
+        bitmaps freed up. */
+      begin_unwind_frame ("pipe-file-descriptors");
+      add_unwind_protect (dispose_fd_bitmap, fd_bitmap);
+      add_unwind_protect (close_fd_bitmap, fd_bitmap);
+      if (prev >= 0)
+       add_unwind_protect (close, prev);
+      dummyfd = fildes[1];
+      add_unwind_protect (close, dummyfd);
+
+#if defined (JOB_CONTROL)
+      add_unwind_protect (restore_signal_mask, &oset);
+#endif /* JOB_CONTROL */
+
+      if (ignore_return && cmd->value.Connection->first)
+       cmd->value.Connection->first->flags |= CMD_IGNORE_RETURN;
+      execute_command_internal (cmd->value.Connection->first, asynchronous,
+                               prev, fildes[1], fd_bitmap);
+
+      if (prev >= 0)
+       close (prev);
+
+      prev = fildes[0];
+      close (fildes[1]);
+
+      dispose_fd_bitmap (fd_bitmap);
+      discard_unwind_frame ("pipe-file-descriptors");
+
+      cmd = cmd->value.Connection->second;
+    }
+
+  lastpid = last_made_pid;
+
+  /* Now execute the rightmost command in the pipeline.  */
+  if (ignore_return && cmd)
+    cmd->flags |= CMD_IGNORE_RETURN;
+
+  lastpipe_flag = 0;
+
+  begin_unwind_frame ("lastpipe-exec");
+  lstdin = -1;
+  /* If the `lastpipe' option is set with shopt, and job control is not
+     enabled, execute the last element of non-async pipelines in the
+     current shell environment. */
+  if (lastpipe_opt && job_control == 0 && asynchronous == 0 && pipe_out == NO_PIPE && prev > 0)
+    {
+      lstdin = move_to_high_fd (0, 1, -1);
+      if (lstdin > 0)
+       {
+         do_piping (prev, pipe_out);
+         prev = NO_PIPE;
+         add_unwind_protect (restore_stdin, lstdin);
+         lastpipe_flag = 1;
+         freeze_jobs_list ();
+         lastpipe_jid = stop_pipeline (0, (COMMAND *)NULL);    /* XXX */
+         add_unwind_protect (lastpipe_cleanup, lastpipe_jid);
+       }
+      if (cmd)
+       cmd->flags |= CMD_LASTPIPE;
+    }    
+  if (prev >= 0)
+    add_unwind_protect (close, prev);
+
+  exec_result = execute_command_internal (cmd, asynchronous, prev, pipe_out, fds_to_close);
+
+  if (lstdin > 0)
+    restore_stdin (lstdin);
+
+  if (prev >= 0)
+    close (prev);
+
+#if defined (JOB_CONTROL)
+  UNBLOCK_CHILD (oset);
+#endif
+
+  QUIT;
+
+  if (lastpipe_flag)
+    {
+#if defined (JOB_CONTROL)
+      append_process (savestring (the_printed_command), dollar_dollar_pid, exec_result, lastpipe_jid);
+#endif
+      lstdin = wait_for (lastpid);
+#if defined (JOB_CONTROL)
+      exec_result = job_exit_status (lastpipe_jid);
+#endif
+      unfreeze_jobs_list ();
+    }
+
+  discard_unwind_frame ("lastpipe-exec");
+
+  return (exec_result);
+}
+
+static int
+execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close)
+     COMMAND *command;
+     int asynchronous, pipe_in, pipe_out;
+     struct fd_bitmap *fds_to_close;
+{
+  COMMAND *tc, *second;
+  int ignore_return, exec_result, was_error_trap, invert;
+  volatile int save_line_number;
+
+  ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
+
+  switch (command->value.Connection->connector)
+    {
+    /* Do the first command asynchronously. */
+    case '&':
+      tc = command->value.Connection->first;
+      if (tc == 0)
+       return (EXECUTION_SUCCESS);
+
+      if (ignore_return)
+       tc->flags |= CMD_IGNORE_RETURN;
+      tc->flags |= CMD_AMPERSAND;
+
+      /* If this shell was compiled without job control support,
+        if we are currently in a subshell via `( xxx )', or if job
+        control is not active then the standard input for an
+        asynchronous command is forced to /dev/null. */
+#if defined (JOB_CONTROL)
+      if ((subshell_environment || !job_control) && !stdin_redir)
+#else
+      if (!stdin_redir)
+#endif /* JOB_CONTROL */
+       tc->flags |= CMD_STDIN_REDIR;
+
+      exec_result = execute_command_internal (tc, 1, pipe_in, pipe_out, fds_to_close);
+      QUIT;
+
+      if (tc->flags & CMD_STDIN_REDIR)
+       tc->flags &= ~CMD_STDIN_REDIR;
+
+      second = command->value.Connection->second;
+      if (second)
+       {
+         if (ignore_return)
+           second->flags |= CMD_IGNORE_RETURN;
+
+         exec_result = execute_command_internal (second, asynchronous, pipe_in, pipe_out, fds_to_close);
+       }
+
+      break;
+
+    /* Just call execute command on both sides. */
+    case ';':
+      if (ignore_return)
+       {
+         if (command->value.Connection->first)
+           command->value.Connection->first->flags |= CMD_IGNORE_RETURN;
+         if (command->value.Connection->second)
+           command->value.Connection->second->flags |= CMD_IGNORE_RETURN;
+       }
+      executing_list++;
+      QUIT;
+      execute_command (command->value.Connection->first);
+      QUIT;
+      exec_result = execute_command_internal (command->value.Connection->second,
+                                     asynchronous, pipe_in, pipe_out,
+                                     fds_to_close);
+      executing_list--;
+      break;
+
+    case '|':
+      was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0;
+      invert = (command->flags & CMD_INVERT_RETURN) != 0;
+      ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0;
+
+      line_number_for_err_trap = line_number;
+      exec_result = execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close);
+
+      if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         save_line_number = line_number;
+         line_number = line_number_for_err_trap;
+         run_error_trap ();
+         line_number = save_line_number;
+       }
+
+      if (ignore_return == 0 && invert == 0 && exit_immediately_on_error && exec_result != EXECUTION_SUCCESS)
+       {
+         last_command_exit_value = exec_result;
+         run_pending_traps ();
+         jump_to_top_level (ERREXIT);
+       }
+
+      break;
+
+    case AND_AND:
+    case OR_OR:
+      if (asynchronous)
+       {
+         /* If we have something like `a && b &' or `a || b &', run the
+            && or || stuff in a subshell.  Force a subshell and just call
+            execute_command_internal again.  Leave asynchronous on
+            so that we get a report from the parent shell about the
+            background job. */
+         command->flags |= CMD_FORCE_SUBSHELL;
+         exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close);
+         break;
+       }
+
+      /* Execute the first command.  If the result of that is successful
+        and the connector is AND_AND, or the result is not successful
+        and the connector is OR_OR, then execute the second command,
+        otherwise return. */
+
+      executing_list++;
+      if (command->value.Connection->first)
+       command->value.Connection->first->flags |= CMD_IGNORE_RETURN;
+
+      exec_result = execute_command (command->value.Connection->first);
+      QUIT;
+      if (((command->value.Connection->connector == AND_AND) &&
+          (exec_result == EXECUTION_SUCCESS)) ||
+         ((command->value.Connection->connector == OR_OR) &&
+          (exec_result != EXECUTION_SUCCESS)))
+       {
+         if (ignore_return && command->value.Connection->second)
+           command->value.Connection->second->flags |= CMD_IGNORE_RETURN;
+
+         exec_result = execute_command (command->value.Connection->second);
+       }
+      executing_list--;
+      break;
+
+    default:
+      command_error ("execute_connection", CMDERR_BADCONN, command->value.Connection->connector, 0);
+      jump_to_top_level (DISCARD);
+      exec_result = EXECUTION_FAILURE;
+    }
+
+  return exec_result;
+}
+
+#define REAP() \
+  do \
+    { \
+      if (!interactive_shell) \
+       reap_dead_jobs (); \
+    } \
+  while (0)
+
+/* Execute a FOR command.  The syntax is: FOR word_desc IN word_list;
+   DO command; DONE */
+static int
+execute_for_command (for_command)
+     FOR_COM *for_command;
+{
+  register WORD_LIST *releaser, *list;
+  SHELL_VAR *v;
+  char *identifier;
+  int retval, save_line_number;
+#if 0
+  SHELL_VAR *old_value = (SHELL_VAR *)NULL; /* Remember the old value of x. */
+#endif
+
+  save_line_number = line_number;
+  if (check_identifier (for_command->name, 1) == 0)
+    {
+      if (posixly_correct && interactive_shell == 0)
+       {
+         last_command_exit_value = EX_BADUSAGE;
+         jump_to_top_level (ERREXIT);
+       }
+      return (EXECUTION_FAILURE);
+    }
+
+  loop_level++;
+  identifier = for_command->name->word;
+
+  list = releaser = expand_words_no_vars (for_command->map_list);
+
+  begin_unwind_frame ("for");
+  add_unwind_protect (dispose_words, releaser);
+
+#if 0
+  if (lexical_scoping)
+    {
+      old_value = copy_variable (find_variable (identifier));
+      if (old_value)
+       add_unwind_protect (dispose_variable, old_value);
+    }
+#endif
+
+  if (for_command->flags & CMD_IGNORE_RETURN)
+    for_command->action->flags |= CMD_IGNORE_RETURN;
+
+  for (retval = EXECUTION_SUCCESS; list; list = list->next)
+    {
+      QUIT;
+
+      line_number = for_command->line;
+
+      /* Remember what this command looks like, for debugger. */
+      command_string_index = 0;
+      print_for_command_head (for_command);
+
+      if (echo_command_at_execute)
+       xtrace_print_for_command_head (for_command);
+
+      /* Save this command unless it's a trap command and we're not running
+        a debug trap. */
+      if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
+       {
+         FREE (the_printed_command_except_trap);
+         the_printed_command_except_trap = savestring (the_printed_command);
+       }
+
+      retval = run_debug_trap ();
+#if defined (DEBUGGER)
+      /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+        skip the command. */
+      if (debugging_mode && retval != EXECUTION_SUCCESS)
+        continue;
+#endif
+
+      this_command_name = (char *)NULL;
+      /* XXX - special ksh93 for command index variable handling */
+      v = find_variable_last_nameref (identifier);
+      if (v && nameref_p (v))
+        {
+          v = bind_variable_value (v, list->word->word, 0);
+        }
+      else
+        v = bind_variable (identifier, list->word->word, 0);
+      if (readonly_p (v) || noassign_p (v))
+       {
+         line_number = save_line_number;
+         if (readonly_p (v) && interactive_shell == 0 && posixly_correct)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             jump_to_top_level (FORCE_EOF);
+           }
+         else
+           {
+             dispose_words (releaser);
+             discard_unwind_frame ("for");
+             loop_level--;
+             return (EXECUTION_FAILURE);
+           }
+       }
+      retval = execute_command (for_command->action);
+      REAP ();
+      QUIT;
+
+      if (breaking)
+       {
+         breaking--;
+         break;
+       }
+
+      if (continuing)
+       {
+         continuing--;
+         if (continuing)
+           break;
+       }
+    }
+
+  loop_level--;
+  line_number = save_line_number;
+
+#if 0
+  if (lexical_scoping)
+    {
+      if (!old_value)
+        unbind_variable (identifier);
+      else
+       {
+         SHELL_VAR *new_value;
+
+         new_value = bind_variable (identifier, value_cell(old_value), 0);
+         new_value->attributes = old_value->attributes;
+         dispose_variable (old_value);
+       }
+    }
+#endif
+
+  dispose_words (releaser);
+  discard_unwind_frame ("for");
+  return (retval);
+}
+
+#if defined (ARITH_FOR_COMMAND)
+/* Execute an arithmetic for command.  The syntax is
+
+       for (( init ; step ; test ))
+       do
+               body
+       done
+
+   The execution should be exactly equivalent to
+
+       eval \(\( init \)\)
+       while eval \(\( test \)\) ; do
+               body;
+               eval \(\( step \)\)
+       done
+*/
+static intmax_t
+eval_arith_for_expr (l, okp)
+     WORD_LIST *l;
+     int *okp;
+{
+  WORD_LIST *new;
+  intmax_t expresult;
+  int r;
+
+  new = expand_words_no_vars (l);
+  if (new)
+    {
+      if (echo_command_at_execute)
+       xtrace_print_arith_cmd (new);
+      this_command_name = "((";                /* )) for expression error messages */
+
+      command_string_index = 0;
+      print_arith_command (new);
+      if (signal_in_progress (DEBUG_TRAP) == 0)
+       {
+         FREE (the_printed_command_except_trap);
+         the_printed_command_except_trap = savestring (the_printed_command);
+       }
+
+      r = run_debug_trap ();
+      /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+        skip the command. */
+#if defined (DEBUGGER)
+      if (debugging_mode == 0 || r == EXECUTION_SUCCESS)
+       expresult = evalexp (new->word->word, okp);
+      else
+       {
+         expresult = 0;
+         if (okp)
+           *okp = 1;
+       }
+#else
+      expresult = evalexp (new->word->word, okp);
+#endif
+      dispose_words (new);
+    }
+  else
+    {
+      expresult = 0;
+      if (okp)
+       *okp = 1;
+    }
+  return (expresult);
+}
+
+static int
+execute_arith_for_command (arith_for_command)
+     ARITH_FOR_COM *arith_for_command;
+{
+  intmax_t expresult;
+  int expok, body_status, arith_lineno, save_lineno;
+
+  body_status = EXECUTION_SUCCESS;
+  loop_level++;
+  save_lineno = line_number;
+
+  if (arith_for_command->flags & CMD_IGNORE_RETURN)
+    arith_for_command->action->flags |= CMD_IGNORE_RETURN;
+
+  this_command_name = "((";    /* )) for expression error messages */
+
+  /* save the starting line number of the command so we can reset
+     line_number before executing each expression -- for $LINENO
+     and the DEBUG trap. */
+  line_number = arith_lineno = arith_for_command->line;
+  if (variable_context && interactive_shell)
+    line_number -= function_line_number;
+
+  /* Evaluate the initialization expression. */
+  expresult = eval_arith_for_expr (arith_for_command->init, &expok);
+  if (expok == 0)
+    {
+      line_number = save_lineno;
+      return (EXECUTION_FAILURE);
+    }
+
+  while (1)
+    {
+      /* Evaluate the test expression. */
+      line_number = arith_lineno;
+      expresult = eval_arith_for_expr (arith_for_command->test, &expok);
+      line_number = save_lineno;
+
+      if (expok == 0)
+       {
+         body_status = EXECUTION_FAILURE;
+         break;
+       }
+      REAP ();
+      if (expresult == 0)
+       break;
+
+      /* Execute the body of the arithmetic for command. */
+      QUIT;
+      body_status = execute_command (arith_for_command->action);
+      QUIT;
+
+      /* Handle any `break' or `continue' commands executed by the body. */
+      if (breaking)
+       {
+         breaking--;
+         break;
+       }
+
+      if (continuing)
+       {
+         continuing--;
+         if (continuing)
+           break;
+       }
+
+      /* Evaluate the step expression. */
+      line_number = arith_lineno;
+      expresult = eval_arith_for_expr (arith_for_command->step, &expok);
+      line_number = save_lineno;
+
+      if (expok == 0)
+       {
+         body_status = EXECUTION_FAILURE;
+         break;
+       }
+    }
+
+  loop_level--;
+  line_number = save_lineno;
+
+  return (body_status);
+}
+#endif
+
+#if defined (SELECT_COMMAND)
+static int LINES, COLS, tabsize;
+
+#define RP_SPACE ") "
+#define RP_SPACE_LEN 2
+
+/* XXX - does not handle numbers > 1000000 at all. */
+#define NUMBER_LEN(s) \
+((s < 10) ? 1 \
+         : ((s < 100) ? 2 \
+                     : ((s < 1000) ? 3 \
+                                  : ((s < 10000) ? 4 \
+                                                : ((s < 100000) ? 5 \
+                                                               : 6)))))
+
+static int
+displen (s)
+     const char *s;
+{
+#if defined (HANDLE_MULTIBYTE)
+  wchar_t *wcstr;
+  size_t slen;
+  int wclen;
+
+  wcstr = 0;
+  slen = mbstowcs (wcstr, s, 0);
+  if (slen == -1)
+    slen = 0;
+  wcstr = (wchar_t *)xmalloc (sizeof (wchar_t) * (slen + 1));
+  mbstowcs (wcstr, s, slen + 1);
+  wclen = wcswidth (wcstr, slen);
+  free (wcstr);
+  return (wclen < 0 ? STRLEN(s) : wclen);
+#else
+  return (STRLEN (s));
+#endif
+}
+
+static int
+print_index_and_element (len, ind, list)
+      int len, ind;
+      WORD_LIST *list;
+{
+  register WORD_LIST *l;
+  register int i;
+
+  if (list == 0)
+    return (0);
+  for (i = ind, l = list; l && --i; l = l->next)
+    ;
+  if (l == 0)          /* don't think this can happen */
+    return (0);
+  fprintf (stderr, "%*d%s%s", len, ind, RP_SPACE, l->word->word);
+  return (displen (l->word->word));
+}
+
+static void
+indent (from, to)
+     int from, to;
+{
+  while (from < to)
+    {
+      if ((to / tabsize) > (from / tabsize))
+       {
+         putc ('\t', stderr);
+         from += tabsize - from % tabsize;
+       }
+      else
+       {
+         putc (' ', stderr);
+         from++;
+       }
+    }
+}
+
+static void
+print_select_list (list, list_len, max_elem_len, indices_len)
+     WORD_LIST *list;
+     int list_len, max_elem_len, indices_len;
+{
+  int ind, row, elem_len, pos, cols, rows;
+  int first_column_indices_len, other_indices_len;
+
+  if (list == 0)
+    {
+      putc ('\n', stderr);
+      return;
+    }
+
+  cols = max_elem_len ? COLS / max_elem_len : 1;
+  if (cols == 0)
+    cols = 1;
+  rows = list_len ? list_len / cols + (list_len % cols != 0) : 1;
+  cols = list_len ? list_len / rows + (list_len % rows != 0) : 1;
+
+  if (rows == 1)
+    {
+      rows = cols;
+      cols = 1;
+    }
+
+  first_column_indices_len = NUMBER_LEN (rows);
+  other_indices_len = indices_len;
+
+  for (row = 0; row < rows; row++)
+    {
+      ind = row;
+      pos = 0;
+      while (1)
+       {
+         indices_len = (pos == 0) ? first_column_indices_len : other_indices_len;
+         elem_len = print_index_and_element (indices_len, ind + 1, list);
+         elem_len += indices_len + RP_SPACE_LEN;
+         ind += rows;
+         if (ind >= list_len)
+           break;
+         indent (pos + elem_len, pos + max_elem_len);
+         pos += max_elem_len;
+       }
+      putc ('\n', stderr);
+    }
+}
+
+/* Print the elements of LIST, one per line, preceded by an index from 1 to
+   LIST_LEN.  Then display PROMPT and wait for the user to enter a number.
+   If the number is between 1 and LIST_LEN, return that selection.  If EOF
+   is read, return a null string.  If a blank line is entered, or an invalid
+   number is entered, the loop is executed again. */
+static char *
+select_query (list, list_len, prompt, print_menu)
+     WORD_LIST *list;
+     int list_len;
+     char *prompt;
+     int print_menu;
+{
+  int max_elem_len, indices_len, len;
+  intmax_t reply;
+  WORD_LIST *l;
+  char *repl_string, *t;
+
+#if 0
+  t = get_string_value ("LINES");
+  LINES = (t && *t) ? atoi (t) : 24;
+#endif
+  t = get_string_value ("COLUMNS");
+  COLS =  (t && *t) ? atoi (t) : 80;
+
+#if 0
+  t = get_string_value ("TABSIZE");
+  tabsize = (t && *t) ? atoi (t) : 8;
+  if (tabsize <= 0)
+    tabsize = 8;
+#else
+  tabsize = 8;
+#endif
+
+  max_elem_len = 0;
+  for (l = list; l; l = l->next)
+    {
+      len = displen (l->word->word);
+      if (len > max_elem_len)
+       max_elem_len = len;
+    }
+  indices_len = NUMBER_LEN (list_len);
+  max_elem_len += indices_len + RP_SPACE_LEN + 2;
+
+  while (1)
+    {
+      if (print_menu)
+       print_select_list (list, list_len, max_elem_len, indices_len);
+      fprintf (stderr, "%s", prompt);
+      fflush (stderr);
+      QUIT;
+
+      if (read_builtin ((WORD_LIST *)NULL) != EXECUTION_SUCCESS)
+       {
+         putchar ('\n');
+         return ((char *)NULL);
+       }
+      repl_string = get_string_value ("REPLY");
+      if (*repl_string == 0)
+       {
+         print_menu = 1;
+         continue;
+       }
+      if (legal_number (repl_string, &reply) == 0)
+       return "";
+      if (reply < 1 || reply > list_len)
+       return "";
+
+      for (l = list; l && --reply; l = l->next)
+       ;
+      return (l->word->word);          /* XXX - can't be null? */
+    }
+}
+
+/* Execute a SELECT command.  The syntax is:
+   SELECT word IN list DO command_list DONE
+   Only `break' or `return' in command_list will terminate
+   the command. */
+static int
+execute_select_command (select_command)
+     SELECT_COM *select_command;
+{
+  WORD_LIST *releaser, *list;
+  SHELL_VAR *v;
+  char *identifier, *ps3_prompt, *selection;
+  int retval, list_len, show_menu, save_line_number;
+
+  if (check_identifier (select_command->name, 1) == 0)
+    return (EXECUTION_FAILURE);
+
+  save_line_number = line_number;
+  line_number = select_command->line;
+
+  command_string_index = 0;
+  print_select_command_head (select_command);
+
+  if (echo_command_at_execute)
+    xtrace_print_select_command_head (select_command);
+
+#if 0
+  if (signal_in_progress (DEBUG_TRAP) == 0 && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)))
+#else
+  if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
+#endif
+    {
+      FREE (the_printed_command_except_trap);
+      the_printed_command_except_trap = savestring (the_printed_command);
+    }
+
+  retval = run_debug_trap ();
+#if defined (DEBUGGER)
+  /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+     skip the command. */
+  if (debugging_mode && retval != EXECUTION_SUCCESS)
+    return (EXECUTION_SUCCESS);
+#endif
+
+  loop_level++;
+  identifier = select_command->name->word;
+
+  /* command and arithmetic substitution, parameter and variable expansion,
+     word splitting, pathname expansion, and quote removal. */
+  list = releaser = expand_words_no_vars (select_command->map_list);
+  list_len = list_length (list);
+  if (list == 0 || list_len == 0)
+    {
+      if (list)
+       dispose_words (list);
+      line_number = save_line_number;
+      return (EXECUTION_SUCCESS);
+    }
+
+  begin_unwind_frame ("select");
+  add_unwind_protect (dispose_words, releaser);
+
+  if (select_command->flags & CMD_IGNORE_RETURN)
+    select_command->action->flags |= CMD_IGNORE_RETURN;
+
+  retval = EXECUTION_SUCCESS;
+  show_menu = 1;
+
+  while (1)
+    {
+      line_number = select_command->line;
+      ps3_prompt = get_string_value ("PS3");
+      if (ps3_prompt == 0)
+       ps3_prompt = "#? ";
+
+      QUIT;
+      selection = select_query (list, list_len, ps3_prompt, show_menu);
+      QUIT;
+      if (selection == 0)
+       {
+         /* select_query returns EXECUTION_FAILURE if the read builtin
+            fails, so we want to return failure in this case. */
+         retval = EXECUTION_FAILURE;
+         break;
+       }
+
+      v = bind_variable (identifier, selection, 0);
+      if (readonly_p (v) || noassign_p (v))
+       {
+         if (readonly_p (v) && interactive_shell == 0 && posixly_correct)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             jump_to_top_level (FORCE_EOF);
+           }
+         else
+           {
+             dispose_words (releaser);
+             discard_unwind_frame ("select");
+             loop_level--;
+             line_number = save_line_number;
+             return (EXECUTION_FAILURE);
+           }
+       }
+
+      retval = execute_command (select_command->action);
+
+      REAP ();
+      QUIT;
+
+      if (breaking)
+       {
+         breaking--;
+         break;
+       }
+
+      if (continuing)
+       {
+         continuing--;
+         if (continuing)
+           break;
+       }
+
+#if defined (KSH_COMPATIBLE_SELECT)
+      show_menu = 0;
+      selection = get_string_value ("REPLY");
+      if (selection && *selection == '\0')
+        show_menu = 1;
+#endif
+    }
+
+  loop_level--;
+  line_number = save_line_number;
+
+  dispose_words (releaser);
+  discard_unwind_frame ("select");
+  return (retval);
+}
+#endif /* SELECT_COMMAND */
+
+/* Execute a CASE command.  The syntax is: CASE word_desc IN pattern_list ESAC.
+   The pattern_list is a linked list of pattern clauses; each clause contains
+   some patterns to compare word_desc against, and an associated command to
+   execute. */
+static int
+execute_case_command (case_command)
+     CASE_COM *case_command;
+{
+  register WORD_LIST *list;
+  WORD_LIST *wlist, *es;
+  PATTERN_LIST *clauses;
+  char *word, *pattern;
+  int retval, match, ignore_return, save_line_number;
+
+  save_line_number = line_number;
+  line_number = case_command->line;
+
+  command_string_index = 0;
+  print_case_command_head (case_command);
+
+  if (echo_command_at_execute)
+    xtrace_print_case_command_head (case_command);
+
+#if 0
+  if (signal_in_progress (DEBUG_TRAP) == 0 && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)))
+#else
+  if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
+#endif
+    {
+      FREE (the_printed_command_except_trap);
+      the_printed_command_except_trap = savestring (the_printed_command);
+    }
+
+  retval = run_debug_trap();
+#if defined (DEBUGGER)
+  /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+     skip the command. */
+  if (debugging_mode && retval != EXECUTION_SUCCESS)
+    {
+      line_number = save_line_number;
+      return (EXECUTION_SUCCESS);
+    }
+#endif
+
+  wlist = expand_word_unsplit (case_command->word, 0);
+  word = wlist ? string_list (wlist) : savestring ("");
+  dispose_words (wlist);
+
+  retval = EXECUTION_SUCCESS;
+  ignore_return = case_command->flags & CMD_IGNORE_RETURN;
+
+  begin_unwind_frame ("case");
+  add_unwind_protect ((Function *)xfree, word);
+
+#define EXIT_CASE()  goto exit_case_command
+
+  for (clauses = case_command->clauses; clauses; clauses = clauses->next)
+    {
+      QUIT;
+      for (list = clauses->patterns; list; list = list->next)
+       {
+         es = expand_word_leave_quoted (list->word, 0);
+
+         if (es && es->word && es->word->word && *(es->word->word))
+           pattern = quote_string_for_globbing (es->word->word, QGLOB_CVTNULL);
+         else
+           {
+             pattern = (char *)xmalloc (1);
+             pattern[0] = '\0';
+           }
+
+         /* Since the pattern does not undergo quote removal (as per
+            Posix.2, section 3.9.4.3), the strmatch () call must be able
+            to recognize backslashes as escape characters. */
+         match = strmatch (pattern, word, FNMATCH_EXTFLAG|FNMATCH_IGNCASE) != FNM_NOMATCH;
+         free (pattern);
+
+         dispose_words (es);
+
+         if (match)
+           {
+             do
+               {
+                 if (clauses->action && ignore_return)
+                   clauses->action->flags |= CMD_IGNORE_RETURN;
+                 retval = execute_command (clauses->action);
+               }
+             while ((clauses->flags & CASEPAT_FALLTHROUGH) && (clauses = clauses->next));
+             if (clauses == 0 || (clauses->flags & CASEPAT_TESTNEXT) == 0)
+               EXIT_CASE ();
+             else
+               break;
+           }
+
+         QUIT;
+       }
+    }
+
+exit_case_command:
+  free (word);
+  discard_unwind_frame ("case");
+  line_number = save_line_number;
+  return (retval);
+}
+
+#define CMD_WHILE 0
+#define CMD_UNTIL 1
+
+/* The WHILE command.  Syntax: WHILE test DO action; DONE.
+   Repeatedly execute action while executing test produces
+   EXECUTION_SUCCESS. */
+static int
+execute_while_command (while_command)
+     WHILE_COM *while_command;
+{
+  return (execute_while_or_until (while_command, CMD_WHILE));
+}
+
+/* UNTIL is just like WHILE except that the test result is negated. */
+static int
+execute_until_command (while_command)
+     WHILE_COM *while_command;
+{
+  return (execute_while_or_until (while_command, CMD_UNTIL));
+}
+
+/* The body for both while and until.  The only difference between the
+   two is that the test value is treated differently.  TYPE is
+   CMD_WHILE or CMD_UNTIL.  The return value for both commands should
+   be EXECUTION_SUCCESS if no commands in the body are executed, and
+   the status of the last command executed in the body otherwise. */
+static int
+execute_while_or_until (while_command, type)
+     WHILE_COM *while_command;
+     int type;
+{
+  int return_value, body_status;
+
+  body_status = EXECUTION_SUCCESS;
+  loop_level++;
+
+  while_command->test->flags |= CMD_IGNORE_RETURN;
+  if (while_command->flags & CMD_IGNORE_RETURN)
+    while_command->action->flags |= CMD_IGNORE_RETURN;
+
+  while (1)
+    {
+      return_value = execute_command (while_command->test);
+      REAP ();
+
+      /* Need to handle `break' in the test when we would break out of the
+         loop.  The job control code will set `breaking' to loop_level
+         when a job in a loop is stopped with SIGTSTP.  If the stopped job
+         is in the loop test, `breaking' will not be reset unless we do
+         this, and the shell will cease to execute commands. */
+      if (type == CMD_WHILE && return_value != EXECUTION_SUCCESS)
+       {
+         if (breaking)
+           breaking--;
+         break;
+       }
+      if (type == CMD_UNTIL && return_value == EXECUTION_SUCCESS)
+       {
+         if (breaking)
+           breaking--;
+         break;
+       }
+
+      QUIT;
+      body_status = execute_command (while_command->action);
+      QUIT;
+
+      if (breaking)
+       {
+         breaking--;
+         break;
+       }
+
+      if (continuing)
+       {
+         continuing--;
+         if (continuing)
+           break;
+       }
+    }
+  loop_level--;
+
+  return (body_status);
+}
+
+/* IF test THEN command [ELSE command].
+   IF also allows ELIF in the place of ELSE IF, but
+   the parser makes *that* stupidity transparent. */
+static int
+execute_if_command (if_command)
+     IF_COM *if_command;
+{
+  int return_value, save_line_number;
+
+  save_line_number = line_number;
+  if_command->test->flags |= CMD_IGNORE_RETURN;
+  return_value = execute_command (if_command->test);
+  line_number = save_line_number;
+
+  if (return_value == EXECUTION_SUCCESS)
+    {
+      QUIT;
+
+      if (if_command->true_case && (if_command->flags & CMD_IGNORE_RETURN))
+       if_command->true_case->flags |= CMD_IGNORE_RETURN;
+
+      return (execute_command (if_command->true_case));
+    }
+  else
+    {
+      QUIT;
+
+      if (if_command->false_case && (if_command->flags & CMD_IGNORE_RETURN))
+       if_command->false_case->flags |= CMD_IGNORE_RETURN;
+
+      return (execute_command (if_command->false_case));
+    }
+}
+
+#if defined (DPAREN_ARITHMETIC)
+static int
+execute_arith_command (arith_command)
+     ARITH_COM *arith_command;
+{
+  int expok, save_line_number, retval;
+  intmax_t expresult;
+  WORD_LIST *new;
+  char *exp;
+
+  expresult = 0;
+
+  save_line_number = line_number;
+  this_command_name = "((";    /* )) */
+  line_number = arith_command->line;
+  /* If we're in a function, update the line number information. */
+  if (variable_context && interactive_shell)
+    line_number -= function_line_number;
+
+  command_string_index = 0;
+  print_arith_command (arith_command->exp);
+
+  if (signal_in_progress (DEBUG_TRAP) == 0)
+    {
+      FREE (the_printed_command_except_trap);
+      the_printed_command_except_trap = savestring (the_printed_command);
+    }
+
+  /* Run the debug trap before each arithmetic command, but do it after we
+     update the line number information and before we expand the various
+     words in the expression. */
+  retval = run_debug_trap ();
+#if defined (DEBUGGER)
+  /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+     skip the command. */
+  if (debugging_mode && retval != EXECUTION_SUCCESS)
+    {
+      line_number = save_line_number;
+      return (EXECUTION_SUCCESS);
+    }
+#endif
+
+  new = expand_words_no_vars (arith_command->exp);
+
+  /* If we're tracing, make a new word list with `((' at the front and `))'
+     at the back and print it. */
+  if (echo_command_at_execute)
+    xtrace_print_arith_cmd (new);
+
+  if (new)
+    {
+      exp = new->next ? string_list (new) : new->word->word;
+      expresult = evalexp (exp, &expok);
+      line_number = save_line_number;
+      if (exp != new->word->word)
+       free (exp);
+      dispose_words (new);
+    }
+  else
+    {
+      expresult = 0;
+      expok = 1;
+    }
+
+  if (expok == 0)
+    return (EXECUTION_FAILURE);
+
+  return (expresult == 0 ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
+}
+#endif /* DPAREN_ARITHMETIC */
+
+#if defined (COND_COMMAND)
+
+static char * const nullstr = "";
+
+/* XXX - can COND ever be NULL when this is called? */
+static int
+execute_cond_node (cond)
+     COND_COM *cond;
+{
+  int result, invert, patmatch, rmatch, mflags, ignore;
+  char *arg1, *arg2;
+
+  invert = (cond->flags & CMD_INVERT_RETURN);
+  ignore = (cond->flags & CMD_IGNORE_RETURN);
+  if (ignore)
+    {
+      if (cond->left)
+       cond->left->flags |= CMD_IGNORE_RETURN;
+      if (cond->right)
+       cond->right->flags |= CMD_IGNORE_RETURN;
+    }
+      
+  if (cond->type == COND_EXPR)
+    result = execute_cond_node (cond->left);
+  else if (cond->type == COND_OR)
+    {
+      result = execute_cond_node (cond->left);
+      if (result != EXECUTION_SUCCESS)
+       result = execute_cond_node (cond->right);
+    }
+  else if (cond->type == COND_AND)
+    {
+      result = execute_cond_node (cond->left);
+      if (result == EXECUTION_SUCCESS)
+       result = execute_cond_node (cond->right);
+    }
+  else if (cond->type == COND_UNARY)
+    {
+      if (ignore)
+       comsub_ignore_return++;
+      arg1 = cond_expand_word (cond->left->op, 0);
+      if (ignore)
+       comsub_ignore_return--;
+      if (arg1 == 0)
+       arg1 = nullstr;
+      if (echo_command_at_execute)
+       xtrace_print_cond_term (cond->type, invert, cond->op, arg1, (char *)NULL);
+      result = unary_test (cond->op->word, arg1) ? EXECUTION_SUCCESS : EXECUTION_FAILURE;
+      if (arg1 != nullstr)
+       free (arg1);
+    }
+  else if (cond->type == COND_BINARY)
+    {
+      rmatch = 0;
+      patmatch = (((cond->op->word[1] == '=') && (cond->op->word[2] == '\0') &&
+                  (cond->op->word[0] == '!' || cond->op->word[0] == '=')) ||
+                 (cond->op->word[0] == '=' && cond->op->word[1] == '\0'));
+#if defined (COND_REGEXP)
+      rmatch = (cond->op->word[0] == '=' && cond->op->word[1] == '~' &&
+               cond->op->word[2] == '\0');
+#endif
+
+      if (ignore)
+       comsub_ignore_return++;
+      arg1 = cond_expand_word (cond->left->op, 0);
+      if (ignore)
+       comsub_ignore_return--;
+      if (arg1 == 0)
+       arg1 = nullstr;
+      if (ignore)
+       comsub_ignore_return++;
+      arg2 = cond_expand_word (cond->right->op,
+                              (rmatch && shell_compatibility_level > 31) ? 2 : (patmatch ? 1 : 0));
+      if (ignore)
+       comsub_ignore_return--;
+      if (arg2 == 0)
+       arg2 = nullstr;
+
+      if (echo_command_at_execute)
+       xtrace_print_cond_term (cond->type, invert, cond->op, arg1, arg2);
+
+#if defined (COND_REGEXP)
+      if (rmatch)
+       {
+         mflags = SHMAT_PWARN;
+#if defined (ARRAY_VARS)
+         mflags |= SHMAT_SUBEXP;
+#endif
+
+         result = sh_regmatch (arg1, arg2, mflags);
+       }
+      else
+#endif /* COND_REGEXP */
+       {
+         int oe;
+         oe = extended_glob;
+         extended_glob = 1;
+         result = binary_test (cond->op->word, arg1, arg2, TEST_PATMATCH|TEST_ARITHEXP|TEST_LOCALE)
+                                 ? EXECUTION_SUCCESS
+                                 : EXECUTION_FAILURE;
+         extended_glob = oe;
+       }
+      if (arg1 != nullstr)
+       free (arg1);
+      if (arg2 != nullstr)
+       free (arg2);
+    }
+  else
+    {
+      command_error ("execute_cond_node", CMDERR_BADTYPE, cond->type, 0);
+      jump_to_top_level (DISCARD);
+      result = EXECUTION_FAILURE;
+    }
+
+  if (invert)
+    result = (result == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS;
+
+  return result;
+}
+
+static int
+execute_cond_command (cond_command)
+     COND_COM *cond_command;
+{
+  int retval, save_line_number;
+
+  retval = EXECUTION_SUCCESS;
+  save_line_number = line_number;
+
+  this_command_name = "[[";
+  line_number = cond_command->line;
+  /* If we're in a function, update the line number information. */
+  if (variable_context && interactive_shell)
+    line_number -= function_line_number;
+  command_string_index = 0;
+  print_cond_command (cond_command);
+
+  if (signal_in_progress (DEBUG_TRAP) == 0)
+    {
+      FREE (the_printed_command_except_trap);
+      the_printed_command_except_trap = savestring (the_printed_command);
+    }
+
+  /* Run the debug trap before each conditional command, but do it after we
+     update the line number information. */
+  retval = run_debug_trap ();
+#if defined (DEBUGGER)
+  /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+     skip the command. */
+  if (debugging_mode && retval != EXECUTION_SUCCESS)
+    {
+      line_number = save_line_number;
+      return (EXECUTION_SUCCESS);
+    }
+#endif
+
+#if 0
+  debug_print_cond_command (cond_command);
+#endif
+
+  last_command_exit_value = retval = execute_cond_node (cond_command);
+  line_number = save_line_number;
+  return (retval);
+}
+#endif /* COND_COMMAND */
+
+static void
+bind_lastarg (arg)
+     char *arg;
+{
+  SHELL_VAR *var;
+
+  if (arg == 0)
+    arg = "";
+  var = bind_variable ("_", arg, 0);
+  VUNSETATTR (var, att_exported);
+}
+
+/* Execute a null command.  Fork a subshell if the command uses pipes or is
+   to be run asynchronously.  This handles all the side effects that are
+   supposed to take place. */
+static int
+execute_null_command (redirects, pipe_in, pipe_out, async)
+     REDIRECT *redirects;
+     int pipe_in, pipe_out, async;
+{
+  int r;
+  int forcefork;
+  REDIRECT *rd;
+
+  for (forcefork = 0, rd = redirects; rd; rd = rd->next)
+    forcefork += rd->rflags & REDIR_VARASSIGN;
+
+  if (forcefork || pipe_in != NO_PIPE || pipe_out != NO_PIPE || async)
+    {
+      /* We have a null command, but we really want a subshell to take
+        care of it.  Just fork, do piping and redirections, and exit. */
+      if (make_child ((char *)NULL, async) == 0)
+       {
+         /* Cancel traps, in trap.c. */
+         restore_original_signals ();          /* XXX */
+
+         do_piping (pipe_in, pipe_out);
+
+#if defined (COPROCESS_SUPPORT)
+         coproc_closeall ();
+#endif
+
+         subshell_environment = 0;
+         if (async)
+           subshell_environment |= SUBSHELL_ASYNC;
+         if (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
+           subshell_environment |= SUBSHELL_PIPE;
+
+         if (do_redirections (redirects, RX_ACTIVE) == 0)
+           exit (EXECUTION_SUCCESS);
+         else
+           exit (EXECUTION_FAILURE);
+       }
+      else
+       {
+         close_pipes (pipe_in, pipe_out);
+#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
+         if (pipe_out == NO_PIPE)
+           unlink_fifo_list ();
+#endif
+         return (EXECUTION_SUCCESS);
+       }
+    }
+  else
+    {
+      /* Even if there aren't any command names, pretend to do the
+        redirections that are specified.  The user expects the side
+        effects to take place.  If the redirections fail, then return
+        failure.  Otherwise, if a command substitution took place while
+        expanding the command or a redirection, return the value of that
+        substitution.  Otherwise, return EXECUTION_SUCCESS. */
+
+      r = do_redirections (redirects, RX_ACTIVE|RX_UNDOABLE);
+      cleanup_redirects (redirection_undo_list);
+      redirection_undo_list = (REDIRECT *)NULL;
+
+      if (r != 0)
+       return (EXECUTION_FAILURE);
+      else if (last_command_subst_pid != NO_PID)
+       return (last_command_exit_value);
+      else
+       return (EXECUTION_SUCCESS);
+    }
+}
+
+/* This is a hack to suppress word splitting for assignment statements
+   given as arguments to builtins with the ASSIGNMENT_BUILTIN flag set. */
+static void
+fix_assignment_words (words)
+     WORD_LIST *words;
+{
+  WORD_LIST *w, *wcmd;
+  struct builtin *b;
+  int assoc, global, array;
+
+  if (words == 0)
+    return;
+
+  b = 0;
+  assoc = global = array = 0;
+
+  /* Skip over assignment statements preceding a command name */
+  wcmd = words;
+  for (wcmd = words; wcmd; wcmd = wcmd->next)
+    if ((wcmd->word->flags & W_ASSIGNMENT) == 0)
+      break;
+
+  for (w = wcmd; w; w = w->next)
+    if (w->word->flags & W_ASSIGNMENT)
+      {
+       if (b == 0)
+         {
+           /* Posix (post-2008) says that `command' doesn't change whether
+              or not the builtin it shadows is a `declaration command', even
+              though it removes other special builtin properties.  In Posix
+              mode, we skip over one or more instances of `command' and
+              deal with the next word as the assignment builtin. */
+           while (posixly_correct && wcmd && wcmd->word && wcmd->word->word && STREQ (wcmd->word->word, "command"))
+             wcmd = wcmd->next;
+           b = builtin_address_internal (wcmd->word->word, 0);
+           if (b == 0 || (b->flags & ASSIGNMENT_BUILTIN) == 0)
+             return;
+           else if (b && (b->flags & ASSIGNMENT_BUILTIN))
+             wcmd->word->flags |= W_ASSNBLTIN;
+         }
+       w->word->flags |= (W_NOSPLIT|W_NOGLOB|W_TILDEEXP|W_ASSIGNARG);
+#if defined (ARRAY_VARS)
+       if (assoc)
+         w->word->flags |= W_ASSIGNASSOC;
+       if (array)
+         w->word->flags |= W_ASSIGNARRAY;
+#endif
+       if (global)
+         w->word->flags |= W_ASSNGLOBAL;
+      }
+#if defined (ARRAY_VARS)
+    /* Note that we saw an associative array option to a builtin that takes
+       assignment statements.  This is a bit of a kludge. */
+    else if (w->word->word[0] == '-' && (strchr (w->word->word+1, 'A') || strchr (w->word->word+1, 'a') || strchr (w->word->word+1, 'g')))
+#else
+    else if (w->word->word[0] == '-' && strchr (w->word->word+1, 'g'))
+#endif
+      {
+       if (b == 0)
+         {
+           while (posixly_correct && wcmd && wcmd->word && wcmd->word->word && STREQ (wcmd->word->word, "command"))
+             wcmd = wcmd->next;
+           b = builtin_address_internal (wcmd->word->word, 0);
+           if (b == 0 || (b->flags & ASSIGNMENT_BUILTIN) == 0)
+             return;
+           else if (b && (b->flags & ASSIGNMENT_BUILTIN))
+             wcmd->word->flags |= W_ASSNBLTIN;
+         }
+       if ((wcmd->word->flags & W_ASSNBLTIN) && strchr (w->word->word+1, 'A'))
+         assoc = 1;
+       else if ((wcmd->word->flags & W_ASSNBLTIN) && strchr (w->word->word+1, 'a'))
+         array = 1;
+       if ((wcmd->word->flags & W_ASSNBLTIN) && strchr (w->word->word+1, 'g'))
+         global = 1;
+      }
+}
+
+/* Return 1 if the file found by searching $PATH for PATHNAME, defaulting
+   to PATHNAME, is a directory.  Used by the autocd code below. */
+static int
+is_dirname (pathname)
+     char *pathname;
+{
+  char *temp;
+  int ret;
+
+  temp = search_for_command (pathname, 0);
+  ret = (temp ? file_isdir (temp) : file_isdir (pathname));
+  free (temp);
+  return ret;
+}
+
+/* The meaty part of all the executions.  We have to start hacking the
+   real execution of commands here.  Fork a process, set things up,
+   execute the command. */
+static int
+execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close)
+     SIMPLE_COM *simple_command;
+     int pipe_in, pipe_out, async;
+     struct fd_bitmap *fds_to_close;
+{
+  WORD_LIST *words, *lastword;
+  char *command_line, *lastarg, *temp;
+  int first_word_quoted, result, builtin_is_special, already_forked, dofork;
+  pid_t old_last_async_pid;
+  sh_builtin_func_t *builtin;
+  SHELL_VAR *func;
+  volatile int old_builtin, old_command_builtin;
+
+  result = EXECUTION_SUCCESS;
+  special_builtin_failed = builtin_is_special = 0;
+  command_line = (char *)0;
+
+  QUIT;
+
+  /* If we're in a function, update the line number information. */
+  if (variable_context && interactive_shell && sourcelevel == 0)
+    line_number -= function_line_number;
+
+  /* Remember what this command line looks like at invocation. */
+  command_string_index = 0;
+  print_simple_command (simple_command);
+
+#if 0
+  if (signal_in_progress (DEBUG_TRAP) == 0 && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)))
+#else
+  if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
+#endif
+    {
+      FREE (the_printed_command_except_trap);
+      the_printed_command_except_trap = the_printed_command ? savestring (the_printed_command) : (char *)0;
+    }
+
+  /* Run the debug trap before each simple command, but do it after we
+     update the line number information. */
+  result = run_debug_trap ();
+#if defined (DEBUGGER)
+  /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+     skip the command. */
+  if (debugging_mode && result != EXECUTION_SUCCESS)
+    return (EXECUTION_SUCCESS);
+#endif
+
+  first_word_quoted =
+    simple_command->words ? (simple_command->words->word->flags & W_QUOTED) : 0;
+
+  last_command_subst_pid = NO_PID;
+  old_last_async_pid = last_asynchronous_pid;
+
+  already_forked = dofork = 0;
+
+  /* If we're in a pipeline or run in the background, set DOFORK so we
+     make the child early, before word expansion.  This keeps assignment
+     statements from affecting the parent shell's environment when they
+     should not. */
+  dofork = pipe_in != NO_PIPE || pipe_out != NO_PIPE || async;
+
+  /* Something like `%2 &' should restart job 2 in the background, not cause
+     the shell to fork here. */
+  if (dofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE &&
+       simple_command->words && simple_command->words->word &&
+       simple_command->words->word->word &&
+       (simple_command->words->word->word[0] == '%'))
+    dofork = 0;
+
+  if (dofork)
+    {
+      /* Do this now, because execute_disk_command will do it anyway in the
+        vast majority of cases. */
+      maybe_make_export_env ();
+
+      /* Don't let a DEBUG trap overwrite the command string to be saved with
+        the process/job associated with this child. */
+      if (make_child (savestring (the_printed_command_except_trap), async) == 0)
+       {
+         already_forked = 1;
+         simple_command->flags |= CMD_NO_FORK;
+
+         subshell_environment = SUBSHELL_FORK;
+         if (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
+           subshell_environment |= SUBSHELL_PIPE;
+         if (async)
+           subshell_environment |= SUBSHELL_ASYNC;
+
+         /* We need to do this before piping to handle some really
+            pathological cases where one of the pipe file descriptors
+            is < 2. */
+         if (fds_to_close)
+           close_fd_bitmap (fds_to_close);
+
+         do_piping (pipe_in, pipe_out);
+         pipe_in = pipe_out = NO_PIPE;
+#if defined (COPROCESS_SUPPORT)
+         coproc_closeall ();
+#endif
+
+         last_asynchronous_pid = old_last_async_pid;
+
+         CHECK_SIGTERM;
+       }
+      else
+       {
+         /* Don't let simple commands that aren't the last command in a
+            pipeline change $? for the rest of the pipeline (or at all). */
+         if (pipe_out != NO_PIPE)
+           result = last_command_exit_value;
+         close_pipes (pipe_in, pipe_out);
+#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
+         /* Close /dev/fd file descriptors in the parent after forking the
+            last child in a (possibly one-element) pipeline. */
+         if (pipe_out == NO_PIPE)              /* XXX */
+           unlink_fifo_list ();                /* XXX */
+#endif
+         command_line = (char *)NULL;      /* don't free this. */
+         bind_lastarg ((char *)NULL);
+         return (result);
+       }
+    }
+
+  /* If we are re-running this as the result of executing the `command'
+     builtin, do not expand the command words a second time. */
+  if ((simple_command->flags & CMD_INHIBIT_EXPANSION) == 0)
+    {
+      current_fds_to_close = fds_to_close;
+      fix_assignment_words (simple_command->words);
+      /* Pass the ignore return flag down to command substitutions */
+      if (simple_command->flags & CMD_IGNORE_RETURN)   /* XXX */
+       comsub_ignore_return++;
+      words = expand_words (simple_command->words);
+      if (simple_command->flags & CMD_IGNORE_RETURN)
+       comsub_ignore_return--;
+      current_fds_to_close = (struct fd_bitmap *)NULL;
+    }
+  else
+    words = copy_word_list (simple_command->words);
+
+  /* It is possible for WORDS not to have anything left in it.
+     Perhaps all the words consisted of `$foo', and there was
+     no variable `$foo'. */
+  if (words == 0)
+    {
+      this_command_name = 0;
+      result = execute_null_command (simple_command->redirects,
+                                    pipe_in, pipe_out,
+                                    already_forked ? 0 : async);
+      if (already_forked)
+       exit (result);
+      else
+       {
+         bind_lastarg ((char *)NULL);
+         set_pipestatus_from_exit (result);
+         return (result);
+       }
+    }
+
+  lastarg = (char *)NULL;
+
+  begin_unwind_frame ("simple-command");
+
+  if (echo_command_at_execute)
+    xtrace_print_word_list (words, 1);
+
+  builtin = (sh_builtin_func_t *)NULL;
+  func = (SHELL_VAR *)NULL;
+  if ((simple_command->flags & CMD_NO_FUNCTIONS) == 0)
+    {
+      /* Posix.2 says special builtins are found before functions.  We
+        don't set builtin_is_special anywhere other than here, because
+        this path is followed only when the `command' builtin is *not*
+        being used, and we don't want to exit the shell if a special
+        builtin executed with `command builtin' fails.  `command' is not
+        a special builtin. */
+      if (posixly_correct)
+       {
+         builtin = find_special_builtin (words->word->word);
+         if (builtin)
+           builtin_is_special = 1;
+       }
+      if (builtin == 0)
+       func = find_function (words->word->word);
+    }
+
+  /* In POSIX mode, assignment errors in the temporary environment cause a
+     non-interactive shell to exit. */
+  if (builtin_is_special && interactive_shell == 0 && tempenv_assign_error)
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      jump_to_top_level (ERREXIT);
+    }
+  tempenv_assign_error = 0;    /* don't care about this any more */
+
+  add_unwind_protect (dispose_words, words);
+  QUIT;
+
+  /* Bind the last word in this command to "$_" after execution. */
+  for (lastword = words; lastword->next; lastword = lastword->next)
+    ;
+  lastarg = lastword->word->word;
+
+#if defined (JOB_CONTROL)
+  /* Is this command a job control related thing? */
+  if (words->word->word[0] == '%' && already_forked == 0)
+    {
+      this_command_name = async ? "bg" : "fg";
+      last_shell_builtin = this_shell_builtin;
+      this_shell_builtin = builtin_address (this_command_name);
+      result = (*this_shell_builtin) (words);
+      goto return_result;
+    }
+
+  /* One other possiblilty.  The user may want to resume an existing job.
+     If they do, find out whether this word is a candidate for a running
+     job. */
+  if (job_control && already_forked == 0 && async == 0 &&
+       !first_word_quoted &&
+       !words->next &&
+       words->word->word[0] &&
+       !simple_command->redirects &&
+       pipe_in == NO_PIPE &&
+       pipe_out == NO_PIPE &&
+       (temp = get_string_value ("auto_resume")))
+    {
+      int job, jflags, started_status;
+
+      jflags = JM_STOPPED|JM_FIRSTMATCH;
+      if (STREQ (temp, "exact"))
+       jflags |= JM_EXACT;
+      else if (STREQ (temp, "substring"))
+       jflags |= JM_SUBSTRING;
+      else
+       jflags |= JM_PREFIX;
+      job = get_job_by_name (words->word->word, jflags);
+      if (job != NO_JOB)
+       {
+         run_unwind_frame ("simple-command");
+         this_command_name = "fg";
+         last_shell_builtin = this_shell_builtin;
+         this_shell_builtin = builtin_address ("fg");
+
+         started_status = start_job (job, 1);
+         return ((started_status < 0) ? EXECUTION_FAILURE : started_status);
+       }
+    }
+#endif /* JOB_CONTROL */
+
+run_builtin:
+  /* Remember the name of this command globally. */
+  this_command_name = words->word->word;
+
+  QUIT;
+
+  /* This command could be a shell builtin or a user-defined function.
+     We have already found special builtins by this time, so we do not
+     set builtin_is_special.  If this is a function or builtin, and we
+     have pipes, then fork a subshell in here.  Otherwise, just execute
+     the command directly. */
+  if (func == 0 && builtin == 0)
+    builtin = find_shell_builtin (this_command_name);
+
+  last_shell_builtin = this_shell_builtin;
+  this_shell_builtin = builtin;
+
+  if (builtin || func)
+    {
+      if (builtin)
+        {
+         old_builtin = executing_builtin;
+         old_command_builtin = executing_command_builtin;
+         unwind_protect_int (executing_builtin);       /* modified in execute_builtin */
+         unwind_protect_int (executing_command_builtin);       /* ditto */
+        }
+      if (already_forked)
+       {
+         /* reset_terminating_signals (); */   /* XXX */
+         /* Reset the signal handlers in the child, but don't free the
+            trap strings.  Set a flag noting that we have to free the
+            trap strings if we run trap to change a signal disposition. */
+         reset_signal_handlers ();
+         subshell_environment |= SUBSHELL_RESETTRAP;
+
+         if (async)
+           {
+             if ((simple_command->flags & CMD_STDIN_REDIR) &&
+                   pipe_in == NO_PIPE &&
+                   (stdin_redirects (simple_command->redirects) == 0))
+               async_redirect_stdin ();
+             setup_async_signals ();
+           }
+
+         subshell_level++;
+         execute_subshell_builtin_or_function
+           (words, simple_command->redirects, builtin, func,
+            pipe_in, pipe_out, async, fds_to_close,
+            simple_command->flags);
+         subshell_level--;
+       }
+      else
+       {
+         result = execute_builtin_or_function
+           (words, builtin, func, simple_command->redirects, fds_to_close,
+            simple_command->flags);
+         if (builtin)
+           {
+             if (result > EX_SHERRBASE)
+               {
+                 result = builtin_status (result);
+                 if (builtin_is_special)
+                   special_builtin_failed = 1;
+               }
+             /* In POSIX mode, if there are assignment statements preceding
+                a special builtin, they persist after the builtin
+                completes. */
+             if (posixly_correct && builtin_is_special && temporary_env)
+               merge_temporary_env ();
+           }
+         else          /* function */
+           {
+             if (result == EX_USAGE)
+               result = EX_BADUSAGE;
+             else if (result > EX_SHERRBASE)
+               result = EXECUTION_FAILURE;
+           }
+
+         set_pipestatus_from_exit (result);
+
+         goto return_result;
+       }
+    }
+
+  if (autocd && interactive && words->word && is_dirname (words->word->word))
+    {
+      words = make_word_list (make_word ("cd"), words);
+      xtrace_print_word_list (words, 0);
+      goto run_builtin;
+    }
+
+  if (command_line == 0)
+    command_line = savestring (the_printed_command_except_trap ? the_printed_command_except_trap : "");
+
+#if defined (PROCESS_SUBSTITUTION)
+  if ((subshell_environment & SUBSHELL_COMSUB) && (simple_command->flags & CMD_NO_FORK) && fifos_pending() > 0)
+    simple_command->flags &= ~CMD_NO_FORK;
+#endif
+
+  result = execute_disk_command (words, simple_command->redirects, command_line,
+                       pipe_in, pipe_out, async, fds_to_close,
+                       simple_command->flags);
+
+ return_result:
+  bind_lastarg (lastarg);
+  FREE (command_line);
+  dispose_words (words);
+  if (builtin)
+    {
+      executing_builtin = old_builtin;
+      executing_command_builtin = old_command_builtin;
+    }
+  discard_unwind_frame ("simple-command");
+  this_command_name = (char *)NULL;    /* points to freed memory now */
+  return (result);
+}
+
+/* Translate the special builtin exit statuses.  We don't really need a
+   function for this; it's a placeholder for future work. */
+static int
+builtin_status (result)
+     int result;
+{
+  int r;
+
+  switch (result)
+    {
+    case EX_USAGE:
+      r = EX_BADUSAGE;
+      break;
+    case EX_REDIRFAIL:
+    case EX_BADSYNTAX:
+    case EX_BADASSIGN:
+    case EX_EXPFAIL:
+      r = EXECUTION_FAILURE;
+      break;
+    default:
+      r = EXECUTION_SUCCESS;
+      break;
+    }
+  return (r);
+}
+
+static int
+execute_builtin (builtin, words, flags, subshell)
+     sh_builtin_func_t *builtin;
+     WORD_LIST *words;
+     int flags, subshell;
+{
+  int old_e_flag, result, eval_unwind;
+  int isbltinenv;
+  char *error_trap;
+
+  error_trap = 0;
+  old_e_flag = exit_immediately_on_error;
+  
+  /* The eval builtin calls parse_and_execute, which does not know about
+     the setting of flags, and always calls the execution functions with
+     flags that will exit the shell on an error if -e is set.  If the
+     eval builtin is being called, and we're supposed to ignore the exit
+     value of the command, we turn the -e flag off ourselves and disable
+     the ERR trap, then restore them when the command completes.  This is
+     also a problem (as below) for the command and source/. builtins. */
+  if (subshell == 0 && (flags & CMD_IGNORE_RETURN) &&
+       (builtin == eval_builtin || builtin == command_builtin || builtin == source_builtin))
+    {
+      begin_unwind_frame ("eval_builtin");
+      unwind_protect_int (exit_immediately_on_error);
+      unwind_protect_int (builtin_ignoring_errexit);
+      error_trap = TRAP_STRING (ERROR_TRAP);
+      if (error_trap)
+       {
+         error_trap = savestring (error_trap);
+         add_unwind_protect (xfree, error_trap);
+         add_unwind_protect (set_error_trap, error_trap);
+         restore_default_signal (ERROR_TRAP);
+       }
+      exit_immediately_on_error = 0;
+      builtin_ignoring_errexit = 1;
+      eval_unwind = 1;
+    }
+  else
+    eval_unwind = 0;
+
+  /* The temporary environment for a builtin is supposed to apply to
+     all commands executed by that builtin.  Currently, this is a
+     problem only with the `unset', `source' and `eval' builtins.
+     `mapfile' is a special case because it uses evalstring (same as
+     eval or source) to run its callbacks. */
+  isbltinenv = (builtin == source_builtin || builtin == eval_builtin || builtin == unset_builtin || builtin == mapfile_builtin);
+
+  if (isbltinenv)
+    {
+      if (subshell == 0)
+       begin_unwind_frame ("builtin_env");
+
+      if (temporary_env)
+       {
+         push_scope (VC_BLTNENV, temporary_env);
+         if (subshell == 0)
+           add_unwind_protect (pop_scope, (flags & CMD_COMMAND_BUILTIN) ? 0 : "1");
+          temporary_env = (HASH_TABLE *)NULL;    
+       }
+    }
+
+  /* `return' does a longjmp() back to a saved environment in execute_function.
+     If a variable assignment list preceded the command, and the shell is
+     running in POSIX mode, we need to merge that into the shell_variables
+     table, since `return' is a POSIX special builtin. */
+  if (posixly_correct && subshell == 0 && builtin == return_builtin && temporary_env)
+    {
+      begin_unwind_frame ("return_temp_env");
+      add_unwind_protect (merge_temporary_env, (char *)NULL);
+    }
+
+  executing_builtin++;
+  executing_command_builtin |= builtin == command_builtin;
+  result = ((*builtin) (words->next));
+
+  /* This shouldn't happen, but in case `return' comes back instead of
+     longjmp'ing, we need to unwind. */
+  if (posixly_correct && subshell == 0 && builtin == return_builtin && temporary_env)
+    discard_unwind_frame ("return_temp_env");
+
+  if (subshell == 0 && isbltinenv)
+    run_unwind_frame ("builtin_env");
+
+  if (eval_unwind)
+    {
+      exit_immediately_on_error = errexit_flag;
+      builtin_ignoring_errexit = 0;
+      if (error_trap)
+       {
+         set_error_trap (error_trap);
+         xfree (error_trap);
+       }
+      discard_unwind_frame ("eval_builtin");
+    }
+
+  return (result);
+}
+
+static int
+execute_function (var, words, flags, fds_to_close, async, subshell)
+     SHELL_VAR *var;
+     WORD_LIST *words;
+     int flags;
+     struct fd_bitmap *fds_to_close;
+     int async, subshell;
+{
+  int return_val, result;
+  COMMAND *tc, *fc, *save_current;
+  char *debug_trap, *error_trap, *return_trap;
+#if defined (ARRAY_VARS)
+  SHELL_VAR *funcname_v, *nfv, *bash_source_v, *bash_lineno_v;
+  ARRAY *funcname_a;
+  volatile ARRAY *bash_source_a;
+  volatile ARRAY *bash_lineno_a;
+#endif
+  FUNCTION_DEF *shell_fn;
+  char *sfile, *t;
+
+  USE_VAR(fc);
+
+  if (funcnest_max > 0 && funcnest >= funcnest_max)
+    {
+      internal_error (_("%s: maximum function nesting level exceeded (%d)"), var->name, funcnest);
+      funcnest = 0;    /* XXX - should we reset it somewhere else? */
+      jump_to_top_level (DISCARD);
+    }
+
+#if defined (ARRAY_VARS)
+  GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a);
+  GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a);
+  GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a);
+#endif
+
+  tc = (COMMAND *)copy_command (function_cell (var));
+  if (tc && (flags & CMD_IGNORE_RETURN))
+    tc->flags |= CMD_IGNORE_RETURN;
+
+  if (subshell == 0)
+    {
+      begin_unwind_frame ("function_calling");
+      push_context (var->name, subshell, temporary_env);
+      add_unwind_protect (pop_context, (char *)NULL);
+      unwind_protect_int (line_number);
+      unwind_protect_int (return_catch_flag);
+      unwind_protect_jmp_buf (return_catch);
+      add_unwind_protect (dispose_command, (char *)tc);
+      unwind_protect_pointer (this_shell_function);
+      unwind_protect_int (loop_level);
+      unwind_protect_int (funcnest);
+    }
+  else
+    push_context (var->name, subshell, temporary_env); /* don't unwind-protect for subshells */
+
+  temporary_env = (HASH_TABLE *)NULL;
+
+  this_shell_function = var;
+  make_funcname_visible (1);
+
+  debug_trap = TRAP_STRING(DEBUG_TRAP);
+  error_trap = TRAP_STRING(ERROR_TRAP);
+  return_trap = TRAP_STRING(RETURN_TRAP);
+  
+  /* The order of the unwind protects for debug_trap, error_trap and
+     return_trap is important here!  unwind-protect commands are run
+     in reverse order of registration.  If this causes problems, take
+     out the xfree unwind-protect calls and live with the small memory leak. */
+
+  /* function_trace_mode != 0 means that all functions inherit the DEBUG trap.
+     if the function has the trace attribute set, it inherits the DEBUG trap */
+  if (debug_trap && ((trace_p (var) == 0) && function_trace_mode == 0))
+    {
+      if (subshell == 0)
+       {
+         debug_trap = savestring (debug_trap);
+         add_unwind_protect (xfree, debug_trap);
+         add_unwind_protect (set_debug_trap, debug_trap);
+       }
+      restore_default_signal (DEBUG_TRAP);
+    }
+
+  /* error_trace_mode != 0 means that functions inherit the ERR trap. */
+  if (error_trap && error_trace_mode == 0)
+    {
+      if (subshell == 0)
+       {
+         error_trap = savestring (error_trap);
+         add_unwind_protect (xfree, error_trap);
+         add_unwind_protect (set_error_trap, error_trap);
+       }
+      restore_default_signal (ERROR_TRAP);
+    }
+
+  /* Shell functions inherit the RETURN trap if function tracing is on
+     globally or on individually for this function. */
+#if 0
+  if (return_trap && ((trace_p (var) == 0) && function_trace_mode == 0))
+#else
+  if (return_trap && (signal_in_progress (DEBUG_TRAP) || ((trace_p (var) == 0) && function_trace_mode == 0)))
+#endif
+    {
+      if (subshell == 0)
+       {
+         return_trap = savestring (return_trap);
+         add_unwind_protect (xfree, return_trap);
+         add_unwind_protect (set_return_trap, return_trap);
+       }
+      restore_default_signal (RETURN_TRAP);
+    }
+  
+  funcnest++;
+#if defined (ARRAY_VARS)
+  /* This is quite similar to the code in shell.c and elsewhere. */
+  shell_fn = find_function_def (this_shell_function->name);
+  sfile = shell_fn ? shell_fn->source_file : "";
+  array_push ((ARRAY *)funcname_a, this_shell_function->name);
+
+  array_push ((ARRAY *)bash_source_a, sfile);
+  t = itos (executing_line_number ());
+  array_push ((ARRAY *)bash_lineno_a, t);
+  free (t);
+#endif
+
+  /* The temporary environment for a function is supposed to apply to
+     all commands executed within the function body. */
+
+  remember_args (words->next, 1);
+
+  /* Update BASH_ARGV and BASH_ARGC */
+  if (debugging_mode)
+    push_args (words->next);
+
+  /* Number of the line on which the function body starts. */
+  line_number = function_line_number = tc->line;
+
+#if defined (JOB_CONTROL)
+  if (subshell)
+    stop_pipeline (async, (COMMAND *)NULL);
+#endif
+
+  fc = tc;
+
+  return_catch_flag++;
+  return_val = setjmp_nosigs (return_catch);
+
+  if (return_val)
+    {
+      result = return_catch_value;
+      /* Run the RETURN trap in the function's context. */
+      save_current = currently_executing_command;
+      run_return_trap ();
+      currently_executing_command = save_current;
+    }
+  else
+    {
+      /* Run the debug trap here so we can trap at the start of a function's
+        execution rather than the execution of the body's first command. */
+      showing_function_line = 1;
+      save_current = currently_executing_command;
+      result = run_debug_trap ();
+#if defined (DEBUGGER)
+      /* In debugging mode, if the DEBUG trap returns a non-zero status, we
+        skip the command. */
+      if (debugging_mode == 0 || result == EXECUTION_SUCCESS)
+       {
+         showing_function_line = 0;
+         currently_executing_command = save_current;
+         result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close);
+
+         /* Run the RETURN trap in the function's context */
+         save_current = currently_executing_command;
+         run_return_trap ();
+         currently_executing_command = save_current;
+       }
+#else
+      result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close);
+
+      save_current = currently_executing_command;
+      run_return_trap ();
+      currently_executing_command = save_current;
+#endif
+      showing_function_line = 0;
+    }
+
+  /* Restore BASH_ARGC and BASH_ARGV */
+  if (debugging_mode)
+    pop_args ();
+
+  if (subshell == 0)
+    run_unwind_frame ("function_calling");
+
+#if defined (ARRAY_VARS)
+  /* These two variables cannot be unset, and cannot be affected by the
+     function. */
+  array_pop ((ARRAY *)bash_source_a);
+  array_pop ((ARRAY *)bash_lineno_a);
+
+  /* FUNCNAME can be unset, and so can potentially be changed by the
+     function. */
+  GET_ARRAY_FROM_VAR ("FUNCNAME", nfv, funcname_a);
+  if (nfv == funcname_v)
+    array_pop (funcname_a);
+#endif
+  
+  if (variable_context == 0 || this_shell_function == 0)
+    {
+      make_funcname_visible (0);
+#if defined (PROCESS_SUBSTITUTION)
+      unlink_fifo_list ();
+#endif
+    }
+  
+  return (result);
+}
+
+/* A convenience routine for use by other parts of the shell to execute
+   a particular shell function. */
+int
+execute_shell_function (var, words)
+     SHELL_VAR *var;
+     WORD_LIST *words;
+{
+  int ret;
+  struct fd_bitmap *bitmap;
+
+  bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE);
+  begin_unwind_frame ("execute-shell-function");
+  add_unwind_protect (dispose_fd_bitmap, (char *)bitmap);
+      
+  ret = execute_function (var, words, 0, bitmap, 0, 0);
+
+  dispose_fd_bitmap (bitmap);
+  discard_unwind_frame ("execute-shell-function");
+
+  return ret;
+}
+
+/* Execute a shell builtin or function in a subshell environment.  This
+   routine does not return; it only calls exit().  If BUILTIN is non-null,
+   it points to a function to call to execute a shell builtin; otherwise
+   VAR points at the body of a function to execute.  WORDS is the arguments
+   to the command, REDIRECTS specifies redirections to perform before the
+   command is executed. */
+static void
+execute_subshell_builtin_or_function (words, redirects, builtin, var,
+                                     pipe_in, pipe_out, async, fds_to_close,
+                                     flags)
+     WORD_LIST *words;
+     REDIRECT *redirects;
+     sh_builtin_func_t *builtin;
+     SHELL_VAR *var;
+     int pipe_in, pipe_out, async;
+     struct fd_bitmap *fds_to_close;
+     int flags;
+{
+  int result, r, funcvalue;
+#if defined (JOB_CONTROL)
+  int jobs_hack;
+
+  jobs_hack = (builtin == jobs_builtin) &&
+               ((subshell_environment & SUBSHELL_ASYNC) == 0 || pipe_out != NO_PIPE);
+#endif
+
+  /* A subshell is neither a login shell nor interactive. */
+  login_shell = interactive = 0;
+
+  if (async)
+    subshell_environment |= SUBSHELL_ASYNC;
+  if (pipe_in != NO_PIPE || pipe_out != NO_PIPE)
+    subshell_environment |= SUBSHELL_PIPE;
+
+  maybe_make_export_env ();    /* XXX - is this needed? */
+
+#if defined (JOB_CONTROL)
+  /* Eradicate all traces of job control after we fork the subshell, so
+     all jobs begun by this subshell are in the same process group as
+     the shell itself. */
+
+  /* Allow the output of `jobs' to be piped. */
+  if (jobs_hack)
+    kill_current_pipeline ();
+  else
+    without_job_control ();
+
+  set_sigchld_handler ();
+#endif /* JOB_CONTROL */
+
+  set_sigint_handler ();
+
+  if (fds_to_close)
+    close_fd_bitmap (fds_to_close);
+
+  do_piping (pipe_in, pipe_out);
+
+  if (do_redirections (redirects, RX_ACTIVE) != 0)
+    exit (EXECUTION_FAILURE);
+
+  if (builtin)
+    {
+      /* Give builtins a place to jump back to on failure,
+        so we don't go back up to main(). */
+      result = setjmp_nosigs (top_level);
+
+      /* Give the return builtin a place to jump to when executed in a subshell
+         or pipeline */
+      funcvalue = 0;
+      if (return_catch_flag && builtin == return_builtin)
+        funcvalue = setjmp_nosigs (return_catch);
+
+      if (result == EXITPROG)
+       exit (last_command_exit_value);
+      else if (result)
+       exit (EXECUTION_FAILURE);
+      else if (funcvalue)
+       exit (return_catch_value);
+      else
+       {
+         r = execute_builtin (builtin, words, flags, 1);
+         fflush (stdout);
+         if (r == EX_USAGE)
+           r = EX_BADUSAGE;
+         exit (r);
+       }
+    }
+  else
+    {
+      r = execute_function (var, words, flags, fds_to_close, async, 1);
+      fflush (stdout);
+      exit (r);
+    }
+}
+
+/* Execute a builtin or function in the current shell context.  If BUILTIN
+   is non-null, it is the builtin command to execute, otherwise VAR points
+   to the body of a function.  WORDS are the command's arguments, REDIRECTS
+   are the redirections to perform.  FDS_TO_CLOSE is the usual bitmap of
+   file descriptors to close.
+
+   If BUILTIN is exec_builtin, the redirections specified in REDIRECTS are
+   not undone before this function returns. */
+static int
+execute_builtin_or_function (words, builtin, var, redirects,
+                            fds_to_close, flags)
+     WORD_LIST *words;
+     sh_builtin_func_t *builtin;
+     SHELL_VAR *var;
+     REDIRECT *redirects;
+     struct fd_bitmap *fds_to_close;
+     int flags;
+{
+  int result;
+  REDIRECT *saved_undo_list;
+#if defined (PROCESS_SUBSTITUTION)
+  int ofifo, nfifo, osize;
+  char *ofifo_list;
+#endif
+
+
+#if defined (PROCESS_SUBSTITUTION)  
+  ofifo = num_fifos ();
+  ofifo_list = copy_fifo_list (&osize);
+#endif
+
+  if (do_redirections (redirects, RX_ACTIVE|RX_UNDOABLE) != 0)
+    {
+      cleanup_redirects (redirection_undo_list);
+      redirection_undo_list = (REDIRECT *)NULL;
+      dispose_exec_redirects ();
+#if defined (PROCESS_SUBSTITUTION)
+      free (ofifo_list);
+#endif
+      return (EX_REDIRFAIL);   /* was EXECUTION_FAILURE */
+    }
+
+  saved_undo_list = redirection_undo_list;
+
+  /* Calling the "exec" builtin changes redirections forever. */
+  if (builtin == exec_builtin)
+    {
+      dispose_redirects (saved_undo_list);
+      saved_undo_list = exec_redirection_undo_list;
+      exec_redirection_undo_list = (REDIRECT *)NULL;
+    }
+  else
+    dispose_exec_redirects ();
+
+  if (saved_undo_list)
+    {
+      begin_unwind_frame ("saved redirects");
+      add_unwind_protect (cleanup_redirects, (char *)saved_undo_list);
+    }
+
+  redirection_undo_list = (REDIRECT *)NULL;
+
+  if (builtin)
+    result = execute_builtin (builtin, words, flags, 0);
+  else
+    result = execute_function (var, words, flags, fds_to_close, 0, 0);
+
+  /* We do this before undoing the effects of any redirections. */
+  fflush (stdout);
+  fpurge (stdout);
+  if (ferror (stdout))
+    clearerr (stdout);  
+
+  /* If we are executing the `command' builtin, but this_shell_builtin is
+     set to `exec_builtin', we know that we have something like
+     `command exec [redirection]', since otherwise `exec' would have
+     overwritten the shell and we wouldn't get here.  In this case, we
+     want to behave as if the `command' builtin had not been specified
+     and preserve the redirections. */
+  if (builtin == command_builtin && this_shell_builtin == exec_builtin)
+    {
+      int discard;
+
+      discard = 0;
+      if (saved_undo_list)
+       {
+         dispose_redirects (saved_undo_list);
+         discard = 1;
+       }
+      redirection_undo_list = exec_redirection_undo_list;
+      saved_undo_list = exec_redirection_undo_list = (REDIRECT *)NULL;      
+      if (discard)
+       discard_unwind_frame ("saved redirects");
+    }
+
+  if (saved_undo_list)
+    {
+      redirection_undo_list = saved_undo_list;
+      discard_unwind_frame ("saved redirects");
+    }
+
+  if (redirection_undo_list)
+    {
+      cleanup_redirects (redirection_undo_list);
+      redirection_undo_list = (REDIRECT *)NULL;
+    }
+
+#if defined (PROCESS_SUBSTITUTION)
+  /* Close any FIFOs created by this builtin or function. */
+  nfifo = num_fifos ();
+  if (nfifo > ofifo)
+    close_new_fifos (ofifo_list, osize);
+  free (ofifo_list);
+#endif
+
+  return (result);
+}
+
+void
+setup_async_signals ()
+{
+#if defined (__BEOS__)
+  set_signal_handler (SIGHUP, SIG_IGN);        /* they want csh-like behavior */
+#endif
+
+#if defined (JOB_CONTROL)
+  if (job_control == 0)
+#endif
+    {
+      set_signal_handler (SIGINT, SIG_IGN);
+      set_signal_ignored (SIGINT);
+      set_signal_handler (SIGQUIT, SIG_IGN);
+      set_signal_ignored (SIGQUIT);
+    }
+}
+
+/* Execute a simple command that is hopefully defined in a disk file
+   somewhere.
+
+   1) fork ()
+   2) connect pipes
+   3) look up the command
+   4) do redirections
+   5) execve ()
+   6) If the execve failed, see if the file has executable mode set.
+   If so, and it isn't a directory, then execute its contents as
+   a shell script.
+
+   Note that the filename hashing stuff has to take place up here,
+   in the parent.  This is probably why the Bourne style shells
+   don't handle it, since that would require them to go through
+   this gnarly hair, for no good reason.
+
+   NOTE: callers expect this to fork or exit(). */
+
+/* Name of a shell function to call when a command name is not found. */
+#ifndef NOTFOUND_HOOK
+#  define NOTFOUND_HOOK "command_not_found_handle"
+#endif
+
+static int
+execute_disk_command (words, redirects, command_line, pipe_in, pipe_out,
+                     async, fds_to_close, cmdflags)
+     WORD_LIST *words;
+     REDIRECT *redirects;
+     char *command_line;
+     int pipe_in, pipe_out, async;
+     struct fd_bitmap *fds_to_close;
+     int cmdflags;
+{
+  char *pathname, *command, **args;
+  int nofork, result;
+  pid_t pid;
+  SHELL_VAR *hookf;
+  WORD_LIST *wl;
+
+  nofork = (cmdflags & CMD_NO_FORK);  /* Don't fork, just exec, if no pipes */
+  pathname = words->word->word;
+
+  result = EXECUTION_SUCCESS;
+#if defined (RESTRICTED_SHELL)
+  command = (char *)NULL;
+  if (restricted && mbschr (pathname, '/'))
+    {
+      internal_error (_("%s: restricted: cannot specify `/' in command names"),
+                   pathname);
+      result = last_command_exit_value = EXECUTION_FAILURE;
+
+      /* If we're not going to fork below, we must already be in a child
+         process or a context in which it's safe to call exit(2).  */
+      if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE)
+       exit (last_command_exit_value);
+      else
+       goto parent_return;
+    }
+#endif /* RESTRICTED_SHELL */
+
+  command = search_for_command (pathname, 1);
+
+  if (command)
+    {
+      maybe_make_export_env ();
+      put_command_name_into_env (command);
+    }
+
+  /* We have to make the child before we check for the non-existence
+     of COMMAND, since we want the error messages to be redirected. */
+  /* If we can get away without forking and there are no pipes to deal with,
+     don't bother to fork, just directly exec the command. */
+  if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE)
+    pid = 0;
+  else
+    pid = make_child (savestring (command_line), async);
+
+  if (pid == 0)
+    {
+      int old_interactive;
+
+      reset_terminating_signals ();    /* XXX */
+      /* Cancel traps, in trap.c. */
+      restore_original_signals ();
+
+      CHECK_SIGTERM;
+
+      /* restore_original_signals may have undone the work done
+        by make_child to ensure that SIGINT and SIGQUIT are ignored
+        in asynchronous children. */
+      if (async)
+       {
+         if ((cmdflags & CMD_STDIN_REDIR) &&
+               pipe_in == NO_PIPE &&
+               (stdin_redirects (redirects) == 0))
+           async_redirect_stdin ();
+         setup_async_signals ();
+       }
+
+      /* This functionality is now provided by close-on-exec of the
+        file descriptors manipulated by redirection and piping.
+        Some file descriptors still need to be closed in all children
+        because of the way bash does pipes; fds_to_close is a
+        bitmap of all such file descriptors. */
+      if (fds_to_close)
+       close_fd_bitmap (fds_to_close);
+
+      do_piping (pipe_in, pipe_out);
+
+      old_interactive = interactive;
+      if (async)
+       interactive = 0;
+
+      subshell_environment = SUBSHELL_FORK;
+
+      if (redirects && (do_redirections (redirects, RX_ACTIVE) != 0))
+       {
+#if defined (PROCESS_SUBSTITUTION)
+         /* Try to remove named pipes that may have been created as the
+            result of redirections. */
+         unlink_fifo_list ();
+#endif /* PROCESS_SUBSTITUTION */
+         exit (EXECUTION_FAILURE);
+       }
+
+      if (async)
+       interactive = old_interactive;
+
+      if (command == 0)
+       {
+         hookf = find_function (NOTFOUND_HOOK);
+         if (hookf == 0)
+           {
+             /* Make sure filenames are displayed using printable characters */
+             if (ansic_shouldquote (pathname))
+               pathname = ansic_quote (pathname, 0, NULL);
+             internal_error (_("%s: command not found"), pathname);
+             exit (EX_NOTFOUND);       /* Posix.2 says the exit status is 127 */
+           }
+
+#if defined (JOB_CONTROL)
+         /* May need to reinitialize more of the job control state here. */
+         kill_current_pipeline ();
+#endif
+
+         wl = make_word_list (make_word (NOTFOUND_HOOK), words);
+         exit (execute_shell_function (hookf, wl));
+       }
+
+      CHECK_SIGTERM;
+
+      /* Execve expects the command name to be in args[0].  So we
+        leave it there, in the same format that the user used to
+        type it in. */
+      args = strvec_from_word_list (words, 0, 0, (int *)NULL);
+      exit (shell_execve (command, args, export_env));
+    }
+  else
+    {
+parent_return:
+      QUIT;
+
+      /* Make sure that the pipes are closed in the parent. */
+      close_pipes (pipe_in, pipe_out);
+#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD)
+      if (variable_context == 0)
+        unlink_fifo_list ();
+#endif
+      FREE (command);
+      return (result);
+    }
+}
+
+/* CPP defines to decide whether a particular index into the #! line
+   corresponds to a valid interpreter name or argument character, or
+   whitespace.  The MSDOS define is to allow \r to be treated the same
+   as \n. */
+
+#if !defined (MSDOS)
+#  define STRINGCHAR(ind) \
+    (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n')
+#  define WHITECHAR(ind) \
+    (ind < sample_len && whitespace (sample[ind]))
+#else  /* MSDOS */
+#  define STRINGCHAR(ind) \
+    (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n' && sample[ind] != '\r')
+#  define WHITECHAR(ind) \
+    (ind < sample_len && whitespace (sample[ind]))
+#endif /* MSDOS */
+
+static char *
+getinterp (sample, sample_len, endp)
+     char *sample;
+     int sample_len, *endp;
+{
+  register int i;
+  char *execname;
+  int start;
+
+  /* Find the name of the interpreter to exec. */
+  for (i = 2; i < sample_len && whitespace (sample[i]); i++)
+    ;
+
+  for (start = i; STRINGCHAR(i); i++)
+    ;
+
+  execname = substring (sample, start, i);
+
+  if (endp)
+    *endp = i;
+  return execname;
+}
+
+#if !defined (HAVE_HASH_BANG_EXEC)
+/* If the operating system on which we're running does not handle
+   the #! executable format, then help out.  SAMPLE is the text read
+   from the file, SAMPLE_LEN characters.  COMMAND is the name of
+   the script; it and ARGS, the arguments given by the user, will
+   become arguments to the specified interpreter.  ENV is the environment
+   to pass to the interpreter.
+
+   The word immediately following the #! is the interpreter to execute.
+   A single argument to the interpreter is allowed. */
+
+static int
+execute_shell_script (sample, sample_len, command, args, env)
+     char *sample;
+     int sample_len;
+     char *command;
+     char **args, **env;
+{
+  char *execname, *firstarg;
+  int i, start, size_increment, larry;
+
+  /* Find the name of the interpreter to exec. */
+  execname = getinterp (sample, sample_len, &i);
+  size_increment = 1;
+
+  /* Now the argument, if any. */
+  for (firstarg = (char *)NULL, start = i; WHITECHAR(i); i++)
+    ;
+
+  /* If there is more text on the line, then it is an argument for the
+     interpreter. */
+
+  if (STRINGCHAR(i))  
+    {
+      for (start = i; STRINGCHAR(i); i++)
+       ;
+      firstarg = substring ((char *)sample, start, i);
+      size_increment = 2;
+    }
+
+  larry = strvec_len (args) + size_increment;
+  args = strvec_resize (args, larry + 1);
+
+  for (i = larry - 1; i; i--)
+    args[i] = args[i - size_increment];
+
+  args[0] = execname;
+  if (firstarg)
+    {
+      args[1] = firstarg;
+      args[2] = command;
+    }
+  else
+    args[1] = command;
+
+  args[larry] = (char *)NULL;
+
+  return (shell_execve (execname, args, env));
+}
+#undef STRINGCHAR
+#undef WHITECHAR
+
+#endif /* !HAVE_HASH_BANG_EXEC */
+
+static void
+initialize_subshell ()
+{
+#if defined (ALIAS)
+  /* Forget about any aliases that we knew of.  We are in a subshell. */
+  delete_all_aliases ();
+#endif /* ALIAS */
+
+#if defined (HISTORY)
+  /* Forget about the history lines we have read.  This is a non-interactive
+     subshell. */
+  history_lines_this_session = 0;
+#endif
+
+#if defined (JOB_CONTROL)
+  /* Forget about the way job control was working. We are in a subshell. */
+  without_job_control ();
+  set_sigchld_handler ();
+  init_job_stats ();
+#endif /* JOB_CONTROL */
+
+  /* Reset the values of the shell flags and options. */
+  reset_shell_flags ();
+  reset_shell_options ();
+  reset_shopt_options ();
+
+  /* Zero out builtin_env, since this could be a shell script run from a
+     sourced file with a temporary environment supplied to the `source/.'
+     builtin.  Such variables are not supposed to be exported (empirical
+     testing with sh and ksh).  Just throw it away; don't worry about a
+     memory leak. */
+  if (vc_isbltnenv (shell_variables))
+    shell_variables = shell_variables->down;
+
+  clear_unwind_protect_list (0);
+  /* XXX -- are there other things we should be resetting here? */
+  parse_and_execute_level = 0;         /* nothing left to restore it */
+
+  /* We're no longer inside a shell function. */
+  variable_context = return_catch_flag = funcnest = 0;
+
+  executing_list = 0;          /* XXX */
+
+  /* If we're not interactive, close the file descriptor from which we're
+     reading the current shell script. */
+  if (interactive_shell == 0)
+    unset_bash_input (0);
+}
+
+#if defined (HAVE_SETOSTYPE) && defined (_POSIX_SOURCE)
+#  define SETOSTYPE(x) __setostype(x)
+#else
+#  define SETOSTYPE(x)
+#endif
+
+#define READ_SAMPLE_BUF(file, buf, len) \
+  do \
+    { \
+      fd = open(file, O_RDONLY); \
+      if (fd >= 0) \
+       { \
+         len = read (fd, buf, 80); \
+         close (fd); \
+       } \
+      else \
+       len = -1; \
+    } \
+  while (0)
+      
+/* Call execve (), handling interpreting shell scripts, and handling
+   exec failures. */
+int
+shell_execve (command, args, env)
+     char *command;
+     char **args, **env;
+{
+  int larray, i, fd;
+  char sample[80];
+  int sample_len;
+
+  SETOSTYPE (0);               /* Some systems use for USG/POSIX semantics */
+  execve (command, args, env);
+  i = errno;                   /* error from execve() */
+  CHECK_TERMSIG;
+  SETOSTYPE (1);
+
+  /* If we get to this point, then start checking out the file.
+     Maybe it is something we can hack ourselves. */
+  if (i != ENOEXEC)
+    {
+      if (file_isdir (command))
+#if defined (EISDIR)
+       internal_error (_("%s: %s"), command, strerror (EISDIR));
+#else
+       internal_error (_("%s: is a directory"), command);
+#endif
+      else if (executable_file (command) == 0)
+       {
+         errno = i;
+         file_error (command);
+       }
+      /* errors not involving the path argument to execve. */
+      else if (i == E2BIG || i == ENOMEM)
+       {
+         errno = i;
+         file_error (command);
+       }
+      else
+       {
+         /* The file has the execute bits set, but the kernel refuses to
+            run it for some reason.  See why. */
+#if defined (HAVE_HASH_BANG_EXEC)
+         READ_SAMPLE_BUF (command, sample, sample_len);
+         sample[sample_len - 1] = '\0';
+         if (sample_len > 2 && sample[0] == '#' && sample[1] == '!')
+           {
+             char *interp;
+             int ilen;
+
+             interp = getinterp (sample, sample_len, (int *)NULL);
+             ilen = strlen (interp);
+             errno = i;
+             if (interp[ilen - 1] == '\r')
+               {
+                 interp = xrealloc (interp, ilen + 2);
+                 interp[ilen - 1] = '^';
+                 interp[ilen] = 'M';
+                 interp[ilen + 1] = '\0';
+               }
+             sys_error (_("%s: %s: bad interpreter"), command, interp ? interp : "");
+             FREE (interp);
+             return (EX_NOEXEC);
+           }
+#endif
+         errno = i;
+         file_error (command);
+       }
+      return ((i == ENOENT) ? EX_NOTFOUND : EX_NOEXEC);        /* XXX Posix.2 says that exit status is 126 */
+    }
+
+  /* This file is executable.
+     If it begins with #!, then help out people with losing operating
+     systems.  Otherwise, check to see if it is a binary file by seeing
+     if the contents of the first line (or up to 80 characters) are in the
+     ASCII set.  If it's a text file, execute the contents as shell commands,
+     otherwise return 126 (EX_BINARY_FILE). */
+  READ_SAMPLE_BUF (command, sample, sample_len);
+
+  if (sample_len == 0)
+    return (EXECUTION_SUCCESS);
+
+  /* Is this supposed to be an executable script?
+     If so, the format of the line is "#! interpreter [argument]".
+     A single argument is allowed.  The BSD kernel restricts
+     the length of the entire line to 32 characters (32 bytes
+     being the size of the BSD exec header), but we allow 80
+     characters. */
+  if (sample_len > 0)
+    {
+#if !defined (HAVE_HASH_BANG_EXEC)
+      if (sample_len > 2 && sample[0] == '#' && sample[1] == '!')
+       return (execute_shell_script (sample, sample_len, command, args, env));
+      else
+#endif
+      if (check_binary_file (sample, sample_len))
+       {
+         internal_error (_("%s: cannot execute binary file: %s"), command, strerror (i));
+         return (EX_BINARY_FILE);
+       }
+    }
+
+  /* We have committed to attempting to execute the contents of this file
+     as shell commands. */
+
+  initialize_subshell ();
+
+  set_sigint_handler ();
+
+  /* Insert the name of this shell into the argument list. */
+  larray = strvec_len (args) + 1;
+  args = strvec_resize (args, larray + 1);
+
+  for (i = larray - 1; i; i--)
+    args[i] = args[i - 1];
+
+  args[0] = shell_name;
+  args[1] = command;
+  args[larray] = (char *)NULL;
+
+  if (args[0][0] == '-')
+    args[0]++;
+
+#if defined (RESTRICTED_SHELL)
+  if (restricted)
+    change_flag ('r', FLAG_OFF);
+#endif
+
+  if (subshell_argv)
+    {
+      /* Can't free subshell_argv[0]; that is shell_name. */
+      for (i = 1; i < subshell_argc; i++)
+       free (subshell_argv[i]);
+      free (subshell_argv);
+    }
+
+  dispose_command (currently_executing_command);       /* XXX */
+  currently_executing_command = (COMMAND *)NULL;
+
+  subshell_argc = larray;
+  subshell_argv = args;
+  subshell_envp = env;
+
+  unbind_args ();      /* remove the positional parameters */
+
+  longjmp (subshell_top_level, 1);
+  /*NOTREACHED*/
+}
+
+static int
+execute_intern_function (name, funcdef)
+     WORD_DESC *name;
+     FUNCTION_DEF *funcdef;
+{
+  SHELL_VAR *var;
+
+  if (check_identifier (name, posixly_correct) == 0)
+    {
+      if (posixly_correct && interactive_shell == 0)
+       {
+         last_command_exit_value = EX_BADUSAGE;
+         jump_to_top_level (ERREXIT);
+       }
+      return (EXECUTION_FAILURE);
+    }
+
+  /* Posix interpretation 383 */
+  if (posixly_correct && find_special_builtin (name->word))
+    {
+      internal_error (_("`%s': is a special builtin"), name->word);
+      last_command_exit_value = EX_BADUSAGE;
+      jump_to_top_level (ERREXIT);
+    }
+
+  var = find_function (name->word);
+  if (var && (readonly_p (var) || noassign_p (var)))
+    {
+      if (readonly_p (var))
+       internal_error (_("%s: readonly function"), var->name);
+      return (EXECUTION_FAILURE);
+    }
+
+#if defined (DEBUGGER)
+  bind_function_def (name->word, funcdef);
+#endif
+
+  bind_function (name->word, funcdef->command);
+  return (EXECUTION_SUCCESS);
+}
+
+#if defined (INCLUDE_UNUSED)
+#if defined (PROCESS_SUBSTITUTION)
+void
+close_all_files ()
+{
+  register int i, fd_table_size;
+
+  fd_table_size = getdtablesize ();
+  if (fd_table_size > 256)     /* clamp to a reasonable value */
+    fd_table_size = 256;
+
+  for (i = 3; i < fd_table_size; i++)
+    close (i);
+}
+#endif /* PROCESS_SUBSTITUTION */
+#endif
+
+static void
+close_pipes (in, out)
+     int in, out;
+{
+  if (in >= 0)
+    close (in);
+  if (out >= 0)
+    close (out);
+}
+
+static void
+dup_error (oldd, newd)
+     int oldd, newd;
+{
+  sys_error (_("cannot duplicate fd %d to fd %d"), oldd, newd);
+}
+
+/* Redirect input and output to be from and to the specified pipes.
+   NO_PIPE and REDIRECT_BOTH are handled correctly. */
+static void
+do_piping (pipe_in, pipe_out)
+     int pipe_in, pipe_out;
+{
+  if (pipe_in != NO_PIPE)
+    {
+      if (dup2 (pipe_in, 0) < 0)
+       dup_error (pipe_in, 0);
+      if (pipe_in > 0)
+       close (pipe_in);
+#ifdef __CYGWIN__
+      /* Let stdio know the fd may have changed from text to binary mode. */
+      freopen (NULL, "r", stdin);
+#endif /* __CYGWIN__ */
+    }
+  if (pipe_out != NO_PIPE)
+    {
+      if (pipe_out != REDIRECT_BOTH)
+       {
+         if (dup2 (pipe_out, 1) < 0)
+           dup_error (pipe_out, 1);
+         if (pipe_out == 0 || pipe_out > 1)
+           close (pipe_out);
+       }
+      else
+       {
+         if (dup2 (1, 2) < 0)
+           dup_error (1, 2);
+       }
+#ifdef __CYGWIN__
+      /* Let stdio know the fd may have changed from text to binary mode, and
+        make sure to preserve stdout line buffering. */
+      freopen (NULL, "w", stdout);
+      sh_setlinebuf (stdout);
+#endif /* __CYGWIN__ */
+    }
+}
index 6ab3a1cea61e61bed63e7e558a43ea9284a76eb1..bb7431567ec6950e12d47914973b10919ec0e67a 100644 (file)
@@ -411,14 +411,16 @@ history_truncate_file (fname, lines)
      truncate to. */
   if (bp > buffer && ((file = open (filename, O_WRONLY|O_TRUNC|O_BINARY, 0600)) != -1))
     {
-      write (file, bp, chars_read - (bp - buffer));
+      if (write (file, bp, chars_read - (bp - buffer)) < 0)
+       rv = errno;
 
 #if defined (__BEOS__)
       /* BeOS ignores O_TRUNC. */
       ftruncate (file, chars_read - (bp - buffer));
 #endif
 
-      close (file);
+      if (close (file) < 0 && rv == 0)
+       rv = errno;
     }
 
  truncate_exit:
@@ -547,7 +549,8 @@ mmap_error:
 #endif
   }
 
-  close (file);
+  if (close (file) < 0 && rv == 0)
+    rv = errno;
 
   if (rv != 0 && output && bakname)
     rename (bakname, output);
index dfd8ff8c9d9f13bba7a10ad9aa42d8771620c8d0..fc56537c94ed1802f396d2f8ba6d02c53140be3c 100644 (file)
@@ -156,16 +156,16 @@ rl_forward_search_history (sign, key)
    WHERE is the history list number of the current line.  If it is
    -1, then this line is the starting one. */
 static void
-rl_display_search (search_string, reverse_p, where)
+rl_display_search (search_string, flags, where)
      char *search_string;
-     int reverse_p, where;
+     int flags, where;
 {
   char *message;
   int msglen, searchlen;
 
   searchlen = (search_string && *search_string) ? strlen (search_string) : 0;
 
-  message = (char *)xmalloc (searchlen + 33);
+  message = (char *)xmalloc (searchlen + 64);
   msglen = 0;
 
 #if defined (NOTDEF)
@@ -178,7 +178,13 @@ rl_display_search (search_string, reverse_p, where)
 
   message[msglen++] = '(';
 
-  if (reverse_p)
+  if (flags & SF_FAILED)
+    {
+      strcpy (message + msglen, "failed ");
+      msglen += 7;
+    }
+
+  if (flags & SF_REVERSE)
     {
       strcpy (message + msglen, "reverse-");
       msglen += 8;
@@ -517,7 +523,7 @@ add_character:
              cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size);
              strcpy (cxt->search_string, last_isearch_string);
              cxt->search_string_index = last_isearch_string_len;
-             rl_display_search (cxt->search_string, (cxt->sflags & SF_REVERSE), -1);
+             rl_display_search (cxt->search_string, cxt->sflags, -1);
              break;
            }
          return (1);
@@ -691,6 +697,7 @@ add_character:
       /* We cannot find the search string.  Ding the bell. */
       rl_ding ();
       cxt->history_pos = cxt->last_found_line;
+      rl_display_search (cxt->search_string, cxt->sflags, (cxt->history_pos == cxt->save_line) ? -1 : cxt->history_pos);
       return 1;
     }
 
@@ -703,7 +710,7 @@ add_character:
       rl_replace_line (cxt->lines[cxt->history_pos], 0);
       rl_point = cxt->sline_index;
       cxt->last_found_line = cxt->history_pos;
-      rl_display_search (cxt->search_string, (cxt->sflags & SF_REVERSE), (cxt->history_pos == cxt->save_line) ? -1 : cxt->history_pos);
+      rl_display_search (cxt->search_string, cxt->sflags, (cxt->history_pos == cxt->save_line) ? -1 : cxt->history_pos);
     }
 
   return 1;
@@ -738,7 +745,7 @@ rl_search_history (direction, invoking_key)
   RL_SETSTATE(RL_STATE_ISEARCH);
   cxt = _rl_isearch_init (direction);
 
-  rl_display_search (cxt->search_string, (cxt->sflags & SF_REVERSE), -1);
+  rl_display_search (cxt->search_string, cxt->sflags, -1);
 
   /* If we are using the callback interface, all we do is set up here and
       return.  The key is that we leave RL_STATE_ISEARCH set. */
diff --git a/lib/readline/isearch.c~ b/lib/readline/isearch.c~
new file mode 100644 (file)
index 0000000..6ba03ca
--- /dev/null
@@ -0,0 +1,789 @@
+/* isearch.c - incremental searching */
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     I-Search and Searching                      */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Copyright (C) 1987-2012 Free Software Foundation, Inc.
+
+   This file is part of the GNU Readline Library (Readline), a library
+   for reading lines of text with interactive input and history editing.      
+
+   Readline 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 3 of the License, or
+   (at your option) any later version.
+
+   Readline 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 Readline.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define READLINE_LIBRARY
+
+#if defined (HAVE_CONFIG_H)
+#  include <config.h>
+#endif
+
+#include <sys/types.h>
+
+#include <stdio.h>
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#if defined (HAVE_STDLIB_H)
+#  include <stdlib.h>
+#else
+#  include "ansi_stdlib.h"
+#endif
+
+#include "rldefs.h"
+#include "rlmbutil.h"
+
+#include "readline.h"
+#include "history.h"
+
+#include "rlprivate.h"
+#include "xmalloc.h"
+
+/* Variables exported to other files in the readline library. */
+char *_rl_isearch_terminators = (char *)NULL;
+
+_rl_search_cxt *_rl_iscxt = 0;
+
+/* Variables imported from other files in the readline library. */
+extern HIST_ENTRY *_rl_saved_line_for_history;
+
+static int rl_search_history PARAMS((int, int));
+
+static _rl_search_cxt *_rl_isearch_init PARAMS((int));
+static void _rl_isearch_fini PARAMS((_rl_search_cxt *));
+static int _rl_isearch_cleanup PARAMS((_rl_search_cxt *, int));
+
+/* Last line found by the current incremental search, so we don't `find'
+   identical lines many times in a row.  Now part of isearch context. */
+/* static char *prev_line_found; */
+
+/* Last search string and its length. */
+static char *last_isearch_string;
+static int last_isearch_string_len;
+
+static char * const default_isearch_terminators = "\033\012";
+
+_rl_search_cxt *
+_rl_scxt_alloc (type, flags)
+     int type, flags;
+{
+  _rl_search_cxt *cxt;
+
+  cxt = (_rl_search_cxt *)xmalloc (sizeof (_rl_search_cxt));
+
+  cxt->type = type;
+  cxt->sflags = flags;
+
+  cxt->search_string = 0;
+  cxt->search_string_size = cxt->search_string_index = 0;
+
+  cxt->lines = 0;
+  cxt->allocated_line = 0;
+  cxt->hlen = cxt->hindex = 0;
+
+  cxt->save_point = rl_point;
+  cxt->save_mark = rl_mark;
+  cxt->save_line = where_history ();
+  cxt->last_found_line = cxt->save_line;
+  cxt->prev_line_found = 0;
+
+  cxt->save_undo_list = 0;
+
+  cxt->keymap = _rl_keymap;
+  cxt->okeymap = _rl_keymap;
+
+  cxt->history_pos = 0;
+  cxt->direction = 0;
+
+  cxt->prevc = cxt->lastc = 0;
+
+  cxt->sline = 0;
+  cxt->sline_len = cxt->sline_index = 0;
+
+  cxt->search_terminators = 0;
+
+  return cxt;
+}
+
+void
+_rl_scxt_dispose (cxt, flags)
+     _rl_search_cxt *cxt;
+     int flags;
+{
+  FREE (cxt->search_string);
+  FREE (cxt->allocated_line);
+  FREE (cxt->lines);
+
+  xfree (cxt);
+}
+
+/* Search backwards through the history looking for a string which is typed
+   interactively.  Start with the current line. */
+int
+rl_reverse_search_history (sign, key)
+     int sign, key;
+{
+  return (rl_search_history (-sign, key));
+}
+
+/* Search forwards through the history looking for a string which is typed
+   interactively.  Start with the current line. */
+int
+rl_forward_search_history (sign, key)
+     int sign, key;
+{
+  return (rl_search_history (sign, key));
+}
+
+/* Display the current state of the search in the echo-area.
+   SEARCH_STRING contains the string that is being searched for,
+   DIRECTION is zero for forward, or non-zero for reverse,
+   WHERE is the history list number of the current line.  If it is
+   -1, then this line is the starting one. */
+static void
+rl_display_search (search_string, flags, where)
+     char *search_string;
+     int flags, where;
+{
+  char *message;
+  int msglen, searchlen;
+
+  searchlen = (search_string && *search_string) ? strlen (search_string) : 0;
+
+  message = (char *)xmalloc (searchlen + 64);
+  msglen = 0;
+
+#if defined (NOTDEF)
+  if (where != -1)
+    {
+      sprintf (message, "[%d]", where + history_base);
+      msglen = strlen (message);
+    }
+#endif /* NOTDEF */
+
+  message[msglen++] = '(';
+
+  if (flags & SF_FAILED)
+    {
+      strcpy (message + msglen, "failed ");
+      msglen += 7;
+    }
+
+  if (flags & SF_REVERSE)
+    {
+      strcpy (message + msglen, "reverse-");
+      msglen += 8;
+    }
+
+  strcpy (message + msglen, "i-search)`");
+  msglen += 10;
+
+  if (search_string)
+    {
+      strcpy (message + msglen, search_string);
+      msglen += searchlen;
+    }
+
+  strcpy (message + msglen, "': ");
+
+  rl_message ("%s", message);
+  xfree (message);
+  (*rl_redisplay_function) ();
+}
+
+static _rl_search_cxt *
+_rl_isearch_init (direction)
+     int direction;
+{
+  _rl_search_cxt *cxt;
+  register int i;
+  HIST_ENTRY **hlist;
+
+  cxt = _rl_scxt_alloc (RL_SEARCH_ISEARCH, 0);
+  if (direction < 0)
+    cxt->sflags |= SF_REVERSE;
+
+  cxt->search_terminators = _rl_isearch_terminators ? _rl_isearch_terminators
+                                               : default_isearch_terminators;
+
+  /* Create an arrary of pointers to the lines that we want to search. */
+  hlist = history_list ();
+  rl_maybe_replace_line ();
+  i = 0;
+  if (hlist)
+    for (i = 0; hlist[i]; i++);
+
+  /* Allocate space for this many lines, +1 for the current input line,
+     and remember those lines. */
+  cxt->lines = (char **)xmalloc ((1 + (cxt->hlen = i)) * sizeof (char *));
+  for (i = 0; i < cxt->hlen; i++)
+    cxt->lines[i] = hlist[i]->line;
+
+  if (_rl_saved_line_for_history)
+    cxt->lines[i] = _rl_saved_line_for_history->line;
+  else
+    {
+      /* Keep track of this so we can free it. */
+      cxt->allocated_line = (char *)xmalloc (1 + strlen (rl_line_buffer));
+      strcpy (cxt->allocated_line, &rl_line_buffer[0]);
+      cxt->lines[i] = cxt->allocated_line;
+    }
+
+  cxt->hlen++;
+
+  /* The line where we start the search. */
+  cxt->history_pos = cxt->save_line;
+
+  rl_save_prompt ();
+
+  /* Initialize search parameters. */
+  cxt->search_string = (char *)xmalloc (cxt->search_string_size = 128);
+  cxt->search_string[cxt->search_string_index = 0] = '\0';
+
+  /* Normalize DIRECTION into 1 or -1. */
+  cxt->direction = (direction >= 0) ? 1 : -1;
+
+  cxt->sline = rl_line_buffer;
+  cxt->sline_len = strlen (cxt->sline);
+  cxt->sline_index = rl_point;
+
+  _rl_iscxt = cxt;             /* save globally */
+
+  return cxt;
+}
+
+static void
+_rl_isearch_fini (cxt)
+     _rl_search_cxt *cxt;
+{
+  /* First put back the original state. */
+  strcpy (rl_line_buffer, cxt->lines[cxt->save_line]);
+
+  rl_restore_prompt ();
+
+  /* Save the search string for possible later use. */
+  FREE (last_isearch_string);
+  last_isearch_string = cxt->search_string;
+  last_isearch_string_len = cxt->search_string_index;
+  cxt->search_string = 0;
+
+  if (cxt->last_found_line < cxt->save_line)
+    rl_get_previous_history (cxt->save_line - cxt->last_found_line, 0);
+  else
+    rl_get_next_history (cxt->last_found_line - cxt->save_line, 0);
+
+  /* If the string was not found, put point at the end of the last matching
+     line.  If last_found_line == orig_line, we didn't find any matching
+     history lines at all, so put point back in its original position. */
+  if (cxt->sline_index < 0)
+    {
+      if (cxt->last_found_line == cxt->save_line)
+       cxt->sline_index = cxt->save_point;
+      else
+       cxt->sline_index = strlen (rl_line_buffer);
+      rl_mark = cxt->save_mark;
+    }
+
+  rl_point = cxt->sline_index;
+  /* Don't worry about where to put the mark here; rl_get_previous_history
+     and rl_get_next_history take care of it. */
+
+  rl_clear_message ();
+}
+
+int
+_rl_search_getchar (cxt)
+     _rl_search_cxt *cxt;
+{
+  int c;
+
+  /* Read a key and decide how to proceed. */
+  RL_SETSTATE(RL_STATE_MOREINPUT);
+  c = cxt->lastc = rl_read_key ();
+  RL_UNSETSTATE(RL_STATE_MOREINPUT);
+
+#if defined (HANDLE_MULTIBYTE)
+  /* This ends up with C (and LASTC) being set to the last byte of the
+     multibyte character.  In most cases c == lastc == mb[0] */
+  if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
+    c = cxt->lastc = _rl_read_mbstring (cxt->lastc, cxt->mb, MB_LEN_MAX);
+#endif
+
+  RL_CHECK_SIGNALS ();
+  return c;
+}
+
+#define ENDSRCH_CHAR(c) \
+  ((CTRL_CHAR (c) || META_CHAR (c) || (c) == RUBOUT) && ((c) != CTRL ('G')))
+
+/* Process just-read character C according to isearch context CXT.  Return
+   -1 if the caller should just free the context and return, 0 if we should
+   break out of the loop, and 1 if we should continue to read characters. */
+int
+_rl_isearch_dispatch (cxt, c)
+     _rl_search_cxt *cxt;
+     int c;
+{
+  int n, wstart, wlen, limit, cval;
+  rl_command_func_t *f;
+
+  f = (rl_command_func_t *)NULL;
+
+  if (c < 0)
+    {
+      cxt->sflags |= SF_FAILED;
+      cxt->history_pos = cxt->last_found_line;
+      return -1;
+    }
+
+  /* If we are moving into a new keymap, modify cxt->keymap and go on.
+     This can be a problem if c == ESC and we want to terminate the
+     incremental search, so we check */
+  if (c >= 0 && cxt->keymap[c].type == ISKMAP && strchr (cxt->search_terminators, cxt->lastc) == 0)
+    {
+      /* _rl_keyseq_timeout specified in milliseconds; _rl_input_queued
+        takes microseconds, so multiply by 1000.  If we don't get any
+        additional input and we this keymap shadows another function, process
+        that key as if it was all we read. */
+      if (_rl_keyseq_timeout > 0 &&
+           RL_ISSTATE (RL_STATE_CALLBACK) == 0 &&
+           RL_ISSTATE (RL_STATE_INPUTPENDING) == 0 &&
+           _rl_pushed_input_available () == 0 &&
+           ((Keymap)(cxt->keymap[c].function))[ANYOTHERKEY].function &&
+           _rl_input_queued (_rl_keyseq_timeout*1000) == 0)
+       goto add_character;
+
+      cxt->okeymap = cxt->keymap;
+      cxt->keymap = FUNCTION_TO_KEYMAP (cxt->keymap, c);
+      cxt->sflags |= SF_CHGKMAP;
+      /* XXX - we should probably save this sequence, so we can do
+        something useful if this doesn't end up mapping to a command we
+        interpret here.  Right now we just save the most recent character
+        that caused the index into a new keymap. */
+      cxt->prevc = c;
+#if defined (HANDLE_MULTIBYTE)
+      if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
+       {
+         if (cxt->mb[1] == 0)
+           {
+             cxt->pmb[0] = c;          /* XXX should be == cxt->mb[0] */
+             cxt->pmb[1] = '\0';
+           }
+         else
+           memcpy (cxt->pmb, cxt->mb, sizeof (cxt->pmb));
+       }
+#endif
+      return 1;
+    }
+
+add_character:
+
+  /* Translate the keys we do something with to opcodes. */
+  if (c >= 0 && cxt->keymap[c].type == ISFUNC)
+    {
+      f = cxt->keymap[c].function;
+
+      if (f == rl_reverse_search_history)
+       cxt->lastc = (cxt->sflags & SF_REVERSE) ? -1 : -2;
+      else if (f == rl_forward_search_history)
+       cxt->lastc = (cxt->sflags & SF_REVERSE) ? -2 : -1;
+      else if (f == rl_rubout)
+       cxt->lastc = -3;
+      else if (c == CTRL ('G') || f == rl_abort)
+       cxt->lastc = -4;
+      else if (c == CTRL ('W') || f == rl_unix_word_rubout)    /* XXX */
+       cxt->lastc = -5;
+      else if (c == CTRL ('Y') || f == rl_yank)        /* XXX */
+       cxt->lastc = -6;
+    }
+
+  /* If we changed the keymap earlier while translating a key sequence into
+     a command, restore it now that we've succeeded. */
+  if (cxt->sflags & SF_CHGKMAP)
+    {
+      cxt->keymap = cxt->okeymap;
+      cxt->sflags &= ~SF_CHGKMAP;
+      /* If we indexed into a new keymap, but didn't map to a command that
+        affects the search (lastc > 0), and the character that mapped to a
+        new keymap would have ended the search (ENDSRCH_CHAR(cxt->prevc)),
+        handle that now as if the previous char would have ended the search
+        and we would have read the current character. */
+      /* XXX - should we check cxt->mb? */
+      if (cxt->lastc > 0 && ENDSRCH_CHAR (cxt->prevc))
+       {
+         rl_stuff_char (cxt->lastc);
+         rl_execute_next (cxt->prevc);
+         /* XXX - do we insert everything in cxt->pmb? */
+         return (0);
+       }
+      /* Otherwise, if the current character is mapped to self-insert or
+        nothing (i.e., not an editing command), and the previous character
+        was a keymap index, then we need to insert both the previous
+        character and the current character into the search string. */
+      else if (cxt->lastc > 0 && cxt->prevc > 0 &&
+              cxt->keymap[cxt->prevc].type == ISKMAP &&
+              (f == 0 || f == rl_insert))
+       {
+         /* Make lastc be the next character read */
+         /* XXX - do we insert everything in cxt->mb? */
+         rl_execute_next (cxt->lastc);
+         /* Dispatch on the previous character (insert into search string) */
+         cxt->lastc = cxt->prevc;
+#if defined (HANDLE_MULTIBYTE)
+         /* Have to overwrite cxt->mb here because dispatch uses it below */
+         if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
+           {  
+             if (cxt->pmb[1] == 0)       
+               {
+                 cxt->mb[0] = cxt->lastc;      /* == cxt->prevc */
+                 cxt->mb[1] = '\0';
+               }
+             else
+               memcpy (cxt->mb, cxt->pmb, sizeof (cxt->mb));
+           }
+#endif
+         cxt->prevc = 0;         
+       }
+      else if (cxt->lastc > 0 && cxt->prevc > 0 && f && f != rl_insert)
+       {
+         rl_stuff_char (cxt->lastc);
+         rl_execute_next (cxt->prevc);
+         /* XXX - do we insert everything in cxt->pmb? */
+         return (0);
+       }
+    }
+
+  /* The characters in isearch_terminators (set from the user-settable
+     variable isearch-terminators) are used to terminate the search but
+     not subsequently execute the character as a command.  The default
+     value is "\033\012" (ESC and C-J). */
+  if (cxt->lastc > 0 && strchr (cxt->search_terminators, cxt->lastc))
+    {
+      /* ESC still terminates the search, but if there is pending
+        input or if input arrives within 0.1 seconds (on systems
+        with select(2)) it is used as a prefix character
+        with rl_execute_next.  WATCH OUT FOR THIS!  This is intended
+        to allow the arrow keys to be used like ^F and ^B are used
+        to terminate the search and execute the movement command.
+        XXX - since _rl_input_available depends on the application-
+        settable keyboard timeout value, this could alternatively
+        use _rl_input_queued(100000) */
+      if (cxt->lastc == ESC && _rl_input_available ())
+       rl_execute_next (ESC);
+      return (0);
+    }
+
+#if defined (HANDLE_MULTIBYTE)
+  if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
+    {
+      if (cxt->lastc >= 0 && (cxt->mb[0] && cxt->mb[1] == '\0') && ENDSRCH_CHAR (cxt->lastc))
+       {
+         /* This sets rl_pending_input to LASTC; it will be picked up the next
+            time rl_read_key is called. */
+         rl_execute_next (cxt->lastc);
+         return (0);
+       }
+    }
+  else
+#endif
+    if (cxt->lastc >= 0 && ENDSRCH_CHAR (cxt->lastc))
+      {
+       /* This sets rl_pending_input to LASTC; it will be picked up the next
+          time rl_read_key is called. */
+       rl_execute_next (cxt->lastc);
+       return (0);
+      }
+
+  /* Now dispatch on the character.  `Opcodes' affect the search string or
+     state.  Other characters are added to the string.  */
+  switch (cxt->lastc)
+    {
+    /* search again */
+    case -1:
+      if (cxt->search_string_index == 0)
+       {
+         if (last_isearch_string)
+           {
+             cxt->search_string_size = 64 + last_isearch_string_len;
+             cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size);
+             strcpy (cxt->search_string, last_isearch_string);
+             cxt->search_string_index = last_isearch_string_len;
+             rl_display_search (cxt->search_string, cxt->sflags, -1);
+             break;
+           }
+         return (1);
+       }
+      else if (cxt->sflags & SF_REVERSE)
+       cxt->sline_index--;
+      else if (cxt->sline_index != cxt->sline_len)
+       cxt->sline_index++;
+      else
+       rl_ding ();
+      break;
+
+    /* switch directions */
+    case -2:
+      cxt->direction = -cxt->direction;
+      if (cxt->direction < 0)
+       cxt->sflags |= SF_REVERSE;
+      else
+       cxt->sflags &= ~SF_REVERSE;
+      break;
+
+    /* delete character from search string. */
+    case -3:   /* C-H, DEL */
+      /* This is tricky.  To do this right, we need to keep a
+        stack of search positions for the current search, with
+        sentinels marking the beginning and end.  But this will
+        do until we have a real isearch-undo. */
+      if (cxt->search_string_index == 0)
+       rl_ding ();
+      else
+       cxt->search_string[--cxt->search_string_index] = '\0';
+      break;
+
+    case -4:   /* C-G, abort */
+      rl_replace_line (cxt->lines[cxt->save_line], 0);
+      rl_point = cxt->save_point;
+      rl_mark = cxt->save_mark;
+      rl_restore_prompt();
+      rl_clear_message ();
+
+      return -1;
+
+    case -5:   /* C-W */
+      /* skip over portion of line we already matched and yank word */
+      wstart = rl_point + cxt->search_string_index;
+      if (wstart >= rl_end)
+       {
+         rl_ding ();
+         break;
+       }
+
+      /* if not in a word, move to one. */
+      cval = _rl_char_value (rl_line_buffer, wstart);
+      if (_rl_walphabetic (cval) == 0)
+       {
+         rl_ding ();
+         break;
+       }
+      n = MB_NEXTCHAR (rl_line_buffer, wstart, 1, MB_FIND_NONZERO);;
+      while (n < rl_end)
+       {
+         cval = _rl_char_value (rl_line_buffer, n);
+         if (_rl_walphabetic (cval) == 0)
+           break;
+         n = MB_NEXTCHAR (rl_line_buffer, n, 1, MB_FIND_NONZERO);;
+       }
+      wlen = n - wstart + 1;
+      if (cxt->search_string_index + wlen + 1 >= cxt->search_string_size)
+       {
+         cxt->search_string_size += wlen + 1;
+         cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size);
+       }
+      for (; wstart < n; wstart++)
+       cxt->search_string[cxt->search_string_index++] = rl_line_buffer[wstart];
+      cxt->search_string[cxt->search_string_index] = '\0';
+      break;
+
+    case -6:   /* C-Y */
+      /* skip over portion of line we already matched and yank rest */
+      wstart = rl_point + cxt->search_string_index;
+      if (wstart >= rl_end)
+       {
+         rl_ding ();
+         break;
+       }
+      n = rl_end - wstart + 1;
+      if (cxt->search_string_index + n + 1 >= cxt->search_string_size)
+       {
+         cxt->search_string_size += n + 1;
+         cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size);
+       }
+      for (n = wstart; n < rl_end; n++)
+       cxt->search_string[cxt->search_string_index++] = rl_line_buffer[n];
+      cxt->search_string[cxt->search_string_index] = '\0';
+      break;
+
+    /* Add character to search string and continue search. */
+    default:
+      if (cxt->search_string_index + 2 >= cxt->search_string_size)
+       {
+         cxt->search_string_size += 128;
+         cxt->search_string = (char *)xrealloc (cxt->search_string, cxt->search_string_size);
+       }
+#if defined (HANDLE_MULTIBYTE)
+      if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
+       {
+         int j, l;
+
+         if (cxt->mb[0] == 0 || cxt->mb[1] == 0)
+           cxt->search_string[cxt->search_string_index++] = cxt->mb[0];
+         else
+           for (j = 0, l = RL_STRLEN (cxt->mb); j < l; )
+             cxt->search_string[cxt->search_string_index++] = cxt->mb[j++];
+       }
+      else
+#endif
+       cxt->search_string[cxt->search_string_index++] = cxt->lastc;    /* XXX - was c instead of lastc */
+      cxt->search_string[cxt->search_string_index] = '\0';
+      break;
+    }
+
+  for (cxt->sflags &= ~(SF_FOUND|SF_FAILED);; )
+    {
+      limit = cxt->sline_len - cxt->search_string_index + 1;
+
+      /* Search the current line. */
+      while ((cxt->sflags & SF_REVERSE) ? (cxt->sline_index >= 0) : (cxt->sline_index < limit))
+       {
+         if (STREQN (cxt->search_string, cxt->sline + cxt->sline_index, cxt->search_string_index))
+           {
+             cxt->sflags |= SF_FOUND;
+             break;
+           }
+         else
+           cxt->sline_index += cxt->direction;
+       }
+      if (cxt->sflags & SF_FOUND)
+       break;
+
+      /* Move to the next line, but skip new copies of the line
+        we just found and lines shorter than the string we're
+        searching for. */
+      do
+       {
+         /* Move to the next line. */
+         cxt->history_pos += cxt->direction;
+
+         /* At limit for direction? */
+         if ((cxt->sflags & SF_REVERSE) ? (cxt->history_pos < 0) : (cxt->history_pos == cxt->hlen))
+           {
+             cxt->sflags |= SF_FAILED;
+             break;
+           }
+
+         /* We will need these later. */
+         cxt->sline = cxt->lines[cxt->history_pos];
+         cxt->sline_len = strlen (cxt->sline);
+       }
+      while ((cxt->prev_line_found && STREQ (cxt->prev_line_found, cxt->lines[cxt->history_pos])) ||
+            (cxt->search_string_index > cxt->sline_len));
+
+      if (cxt->sflags & SF_FAILED)
+       break;
+
+      /* Now set up the line for searching... */
+      cxt->sline_index = (cxt->sflags & SF_REVERSE) ? cxt->sline_len - cxt->search_string_index : 0;
+    }
+
+  if (cxt->sflags & SF_FAILED)
+    {
+      /* We cannot find the search string.  Ding the bell. */
+      rl_ding ();
+      cxt->history_pos = cxt->last_found_line;
+rl_display_search (cxt->search_string, cxt->sflags, (cxt->history_pos == cxt->save_line) ? -1 : cxt->history_pos);
+      return 1;
+    }
+
+  /* We have found the search string.  Just display it.  But don't
+     actually move there in the history list until the user accepts
+     the location. */
+  if (cxt->sflags & SF_FOUND)
+    {
+      cxt->prev_line_found = cxt->lines[cxt->history_pos];
+      rl_replace_line (cxt->lines[cxt->history_pos], 0);
+      rl_point = cxt->sline_index;
+      cxt->last_found_line = cxt->history_pos;
+      rl_display_search (cxt->search_string, cxt->sflags, (cxt->history_pos == cxt->save_line) ? -1 : cxt->history_pos);
+    }
+
+  return 1;
+}
+
+static int
+_rl_isearch_cleanup (cxt, r)
+     _rl_search_cxt *cxt;
+     int r;
+{
+  if (r >= 0)
+    _rl_isearch_fini (cxt);
+  _rl_scxt_dispose (cxt, 0);
+  _rl_iscxt = 0;
+
+  RL_UNSETSTATE(RL_STATE_ISEARCH);
+
+  return (r != 0);
+}
+
+/* Search through the history looking for an interactively typed string.
+   This is analogous to i-search.  We start the search in the current line.
+   DIRECTION is which direction to search; >= 0 means forward, < 0 means
+   backwards. */
+static int
+rl_search_history (direction, invoking_key)
+     int direction, invoking_key;
+{
+  _rl_search_cxt *cxt;         /* local for now, but saved globally */
+  int c, r;
+
+  RL_SETSTATE(RL_STATE_ISEARCH);
+  cxt = _rl_isearch_init (direction);
+
+  rl_display_search (cxt->search_string, cxt->sflags, -1);
+
+  /* If we are using the callback interface, all we do is set up here and
+      return.  The key is that we leave RL_STATE_ISEARCH set. */
+  if (RL_ISSTATE (RL_STATE_CALLBACK))
+    return (0);
+
+  r = -1;
+  for (;;)
+    {
+      c = _rl_search_getchar (cxt);
+      /* We might want to handle EOF here (c == 0) */
+      r = _rl_isearch_dispatch (cxt, cxt->lastc);
+      if (r <= 0)
+        break;
+    }
+
+  /* The searching is over.  The user may have found the string that she
+     was looking for, or else she may have exited a failing search.  If
+     LINE_INDEX is -1, then that shows that the string searched for was
+     not found.  We use this to determine where to place rl_point. */
+  return (_rl_isearch_cleanup (cxt, r));
+}
+
+#if defined (READLINE_CALLBACKS)
+/* Called from the callback functions when we are ready to read a key.  The
+   callback functions know to call this because RL_ISSTATE(RL_STATE_ISEARCH).
+   If _rl_isearch_dispatch finishes searching, this function is responsible
+   for turning off RL_STATE_ISEARCH, which it does using _rl_isearch_cleanup. */
+int
+_rl_isearch_callback (cxt)
+     _rl_search_cxt *cxt;
+{
+  int c, r;
+
+  c = _rl_search_getchar (cxt);
+  /* We might want to handle EOF here */
+  r = _rl_isearch_dispatch (cxt, cxt->lastc);
+
+  return (r <= 0) ? _rl_isearch_cleanup (cxt, r) : 0;
+}
+#endif
index 45e5ada58d15f8acc53b79c83c3342882c3d37cb..524d43a4253e843059c4d0d57e11d6607e3482bb 100644 (file)
@@ -1,7 +1,7 @@
 /* readline.c -- a general facility for reading lines of input
    with emacs style editing and completion. */
 
-/* Copyright (C) 1987-2012 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2013 Free Software Foundation, Inc.
 
    This file is part of the GNU Readline Library (Readline), a library
    for reading lines of text with interactive input and history editing.      
diff --git a/lib/readline/readline.c~ b/lib/readline/readline.c~
new file mode 100644 (file)
index 0000000..45e5ada
--- /dev/null
@@ -0,0 +1,1348 @@
+/* readline.c -- a general facility for reading lines of input
+   with emacs style editing and completion. */
+
+/* Copyright (C) 1987-2012 Free Software Foundation, Inc.
+
+   This file is part of the GNU Readline Library (Readline), a library
+   for reading lines of text with interactive input and history editing.      
+
+   Readline 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 3 of the License, or
+   (at your option) any later version.
+
+   Readline 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 Readline.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define READLINE_LIBRARY
+
+#if defined (HAVE_CONFIG_H)
+#  include <config.h>
+#endif
+
+#include <sys/types.h>
+#include "posixstat.h"
+#include <fcntl.h>
+#if defined (HAVE_SYS_FILE_H)
+#  include <sys/file.h>
+#endif /* HAVE_SYS_FILE_H */
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+
+#if defined (HAVE_STDLIB_H)
+#  include <stdlib.h>
+#else
+#  include "ansi_stdlib.h"
+#endif /* HAVE_STDLIB_H */
+
+#if defined (HAVE_LOCALE_H)
+#  include <locale.h>
+#endif
+
+#include <stdio.h>
+#include "posixjmp.h"
+#include <errno.h>
+
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+/* System-specific feature definitions and include files. */
+#include "rldefs.h"
+#include "rlmbutil.h"
+
+#if defined (__EMX__)
+#  define INCL_DOSPROCESS
+#  include <os2.h>
+#endif /* __EMX__ */
+
+/* Some standard library routines. */
+#include "readline.h"
+#include "history.h"
+
+#include "rlprivate.h"
+#include "rlshell.h"
+#include "xmalloc.h"
+
+#ifndef RL_LIBRARY_VERSION
+#  define RL_LIBRARY_VERSION "5.1"
+#endif
+
+#ifndef RL_READLINE_VERSION
+#  define RL_READLINE_VERSION  0x0501
+#endif
+
+extern void _rl_free_history_entry PARAMS((HIST_ENTRY *));
+
+/* Forward declarations used in this file. */
+static char *readline_internal PARAMS((void));
+static void readline_initialize_everything PARAMS((void));
+
+static void bind_arrow_keys_internal PARAMS((Keymap));
+static void bind_arrow_keys PARAMS((void));
+
+static void readline_default_bindings PARAMS((void));
+static void reset_default_bindings PARAMS((void));
+
+static int _rl_subseq_result PARAMS((int, Keymap, int, int));
+static int _rl_subseq_getchar PARAMS((int));
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Line editing input utility                  */
+/*                                                                 */
+/* **************************************************************** */
+
+const char *rl_library_version = RL_LIBRARY_VERSION;
+
+int rl_readline_version = RL_READLINE_VERSION;
+
+/* True if this is `real' readline as opposed to some stub substitute. */
+int rl_gnu_readline_p = 1;
+
+/* A pointer to the keymap that is currently in use.
+   By default, it is the standard emacs keymap. */
+Keymap _rl_keymap = emacs_standard_keymap;
+
+/* The current style of editing. */
+int rl_editing_mode = emacs_mode;
+
+/* The current insert mode:  input (the default) or overwrite */
+int rl_insert_mode = RL_IM_DEFAULT;
+
+/* Non-zero if we called this function from _rl_dispatch().  It's present
+   so functions can find out whether they were called from a key binding
+   or directly from an application. */
+int rl_dispatching;
+
+/* Non-zero if the previous command was a kill command. */
+int _rl_last_command_was_kill = 0;
+
+/* The current value of the numeric argument specified by the user. */
+int rl_numeric_arg = 1;
+
+/* Non-zero if an argument was typed. */
+int rl_explicit_arg = 0;
+
+/* Temporary value used while generating the argument. */
+int rl_arg_sign = 1;
+
+/* Non-zero means we have been called at least once before. */
+static int rl_initialized;
+
+#if 0
+/* If non-zero, this program is running in an EMACS buffer. */
+static int running_in_emacs;
+#endif
+
+/* Flags word encapsulating the current readline state. */
+int rl_readline_state = RL_STATE_NONE;
+
+/* The current offset in the current input line. */
+int rl_point;
+
+/* Mark in the current input line. */
+int rl_mark;
+
+/* Length of the current input line. */
+int rl_end;
+
+/* Make this non-zero to return the current input_line. */
+int rl_done;
+
+/* The last function executed by readline. */
+rl_command_func_t *rl_last_func = (rl_command_func_t *)NULL;
+
+/* Top level environment for readline_internal (). */
+procenv_t _rl_top_level;
+
+/* The streams we interact with. */
+FILE *_rl_in_stream, *_rl_out_stream;
+
+/* The names of the streams that we do input and output to. */
+FILE *rl_instream = (FILE *)NULL;
+FILE *rl_outstream = (FILE *)NULL;
+
+/* Non-zero means echo characters as they are read.  Defaults to no echo;
+   set to 1 if there is a controlling terminal, we can get its attributes,
+   and the attributes include `echo'.  Look at rltty.c:prepare_terminal_settings
+   for the code that sets it. */
+int _rl_echoing_p = 0;
+
+/* Current prompt. */
+char *rl_prompt = (char *)NULL;
+int rl_visible_prompt_length = 0;
+
+/* Set to non-zero by calling application if it has already printed rl_prompt
+   and does not want readline to do it the first time. */
+int rl_already_prompted = 0;
+
+/* The number of characters read in order to type this complete command. */
+int rl_key_sequence_length = 0;
+
+/* If non-zero, then this is the address of a function to call just
+   before readline_internal_setup () prints the first prompt. */
+rl_hook_func_t *rl_startup_hook = (rl_hook_func_t *)NULL;
+
+/* If non-zero, this is the address of a function to call just before
+   readline_internal_setup () returns and readline_internal starts
+   reading input characters. */
+rl_hook_func_t *rl_pre_input_hook = (rl_hook_func_t *)NULL;
+
+/* What we use internally.  You should always refer to RL_LINE_BUFFER. */
+static char *the_line;
+
+/* The character that can generate an EOF.  Really read from
+   the terminal driver... just defaulted here. */
+int _rl_eof_char = CTRL ('D');
+
+/* Non-zero makes this the next keystroke to read. */
+int rl_pending_input = 0;
+
+/* Pointer to a useful terminal name. */
+const char *rl_terminal_name = (const char *)NULL;
+
+/* Non-zero means to always use horizontal scrolling in line display. */
+int _rl_horizontal_scroll_mode = 0;
+
+/* Non-zero means to display an asterisk at the starts of history lines
+   which have been modified. */
+int _rl_mark_modified_lines = 0;  
+
+/* The style of `bell' notification preferred.  This can be set to NO_BELL,
+   AUDIBLE_BELL, or VISIBLE_BELL. */
+int _rl_bell_preference = AUDIBLE_BELL;
+     
+/* String inserted into the line by rl_insert_comment (). */
+char *_rl_comment_begin;
+
+/* Keymap holding the function currently being executed. */
+Keymap rl_executing_keymap;
+
+/* Keymap we're currently using to dispatch. */
+Keymap _rl_dispatching_keymap;
+
+/* Non-zero means to erase entire line, including prompt, on empty input lines. */
+int rl_erase_empty_line = 0;
+
+/* Non-zero means to read only this many characters rather than up to a
+   character bound to accept-line. */
+int rl_num_chars_to_read;
+
+/* Line buffer and maintenence. */
+char *rl_line_buffer = (char *)NULL;
+int rl_line_buffer_len = 0;
+
+/* Key sequence `contexts' */
+_rl_keyseq_cxt *_rl_kscxt = 0;
+
+int rl_executing_key;
+char *rl_executing_keyseq = 0;
+int _rl_executing_keyseq_size = 0;
+
+/* Timeout (specified in milliseconds) when reading characters making up an
+   ambiguous multiple-key sequence */
+int _rl_keyseq_timeout = 500;
+
+#define RESIZE_KEYSEQ_BUFFER() \
+  do \
+    { \
+      if (rl_key_sequence_length + 2 >= _rl_executing_keyseq_size) \
+       { \
+         _rl_executing_keyseq_size += 16; \
+         rl_executing_keyseq = xrealloc (rl_executing_keyseq, _rl_executing_keyseq_size); \
+       } \
+    } \
+  while (0);
+        
+/* Forward declarations used by the display, termcap, and history code. */
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     `Forward' declarations                      */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Non-zero means do not parse any lines other than comments and
+   parser directives. */
+unsigned char _rl_parsing_conditionalized_out = 0;
+
+/* Non-zero means to convert characters with the meta bit set to
+   escape-prefixed characters so we can indirect through
+   emacs_meta_keymap or vi_escape_keymap. */
+int _rl_convert_meta_chars_to_ascii = 1;
+
+/* Non-zero means to output characters with the meta bit set directly
+   rather than as a meta-prefixed escape sequence. */
+int _rl_output_meta_chars = 0;
+
+/* Non-zero means to look at the termios special characters and bind
+   them to equivalent readline functions at startup. */
+int _rl_bind_stty_chars = 1;
+
+/* Non-zero means to go through the history list at every newline (or
+   whenever rl_done is set and readline returns) and revert each line to
+   its initial state. */
+int _rl_revert_all_at_newline = 0;
+
+/* Non-zero means to honor the termios ECHOCTL bit and echo control
+   characters corresponding to keyboard-generated signals. */
+int _rl_echo_control_chars = 1;
+
+/* Non-zero means to prefix the displayed prompt with a character indicating
+   the editing mode: @ for emacs, : for vi-command, + for vi-insert. */
+int _rl_show_mode_in_prompt = 0;
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Top Level Functions                         */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Non-zero means treat 0200 bit in terminal input as Meta bit. */
+int _rl_meta_flag = 0; /* Forward declaration */
+
+/* Set up the prompt and expand it.  Called from readline() and
+   rl_callback_handler_install (). */
+int
+rl_set_prompt (prompt)
+     const char *prompt;
+{
+  FREE (rl_prompt);
+  rl_prompt = prompt ? savestring (prompt) : (char *)NULL;
+  rl_display_prompt = rl_prompt ? rl_prompt : "";
+
+  rl_visible_prompt_length = rl_expand_prompt (rl_prompt);
+  return 0;
+}
+  
+/* Read a line of input.  Prompt with PROMPT.  An empty PROMPT means
+   none.  A return value of NULL means that EOF was encountered. */
+char *
+readline (prompt)
+     const char *prompt;
+{
+  char *value;
+#if 0
+  int in_callback;
+#endif
+
+  /* If we are at EOF return a NULL string. */
+  if (rl_pending_input == EOF)
+    {
+      rl_clear_pending_input ();
+      return ((char *)NULL);
+    }
+
+#if 0
+  /* If readline() is called after installing a callback handler, temporarily
+     turn off the callback state to avoid ensuing messiness.  Patch supplied
+     by the gdb folks.  XXX -- disabled.  This can be fooled and readline
+     left in a strange state by a poorly-timed longjmp. */
+  if (in_callback = RL_ISSTATE (RL_STATE_CALLBACK))
+    RL_UNSETSTATE (RL_STATE_CALLBACK);
+#endif
+
+  rl_set_prompt (prompt);
+
+  rl_initialize ();
+  if (rl_prep_term_function)
+    (*rl_prep_term_function) (_rl_meta_flag);
+
+#if defined (HANDLE_SIGNALS)
+  rl_set_signals ();
+#endif
+
+  value = readline_internal ();
+  if (rl_deprep_term_function)
+    (*rl_deprep_term_function) ();
+
+#if defined (HANDLE_SIGNALS)
+  rl_clear_signals ();
+#endif
+
+#if 0
+  if (in_callback)
+    RL_SETSTATE (RL_STATE_CALLBACK);
+#endif
+
+#if HAVE_DECL_AUDIT_TTY && defined (ENABLE_TTY_AUDIT_SUPPORT)
+  if (value)
+    _rl_audit_tty (value);
+#endif
+
+  return (value);
+}
+
+#if defined (READLINE_CALLBACKS)
+#  define STATIC_CALLBACK
+#else
+#  define STATIC_CALLBACK static
+#endif
+
+STATIC_CALLBACK void
+readline_internal_setup ()
+{
+  char *nprompt;
+
+  _rl_in_stream = rl_instream;
+  _rl_out_stream = rl_outstream;
+
+  /* Enable the meta key only for the duration of readline(), if this
+     terminal has one and the terminal has been initialized */
+  if (_rl_enable_meta & RL_ISSTATE (RL_STATE_TERMPREPPED))
+    _rl_enable_meta_key ();
+
+  if (rl_startup_hook)
+    (*rl_startup_hook) ();
+
+#if defined (VI_MODE)
+  if (rl_editing_mode == vi_mode)
+    rl_vi_insertion_mode (1, 'i');     /* don't want to reset last */
+#endif /* VI_MODE */
+
+  /* If we're not echoing, we still want to at least print a prompt, because
+     rl_redisplay will not do it for us.  If the calling application has a
+     custom redisplay function, though, let that function handle it. */
+  if (_rl_echoing_p == 0 && rl_redisplay_function == rl_redisplay)
+    {
+      if (rl_prompt && rl_already_prompted == 0)
+       {
+         nprompt = _rl_strip_prompt (rl_prompt);
+         fprintf (_rl_out_stream, "%s", nprompt);
+         fflush (_rl_out_stream);
+         xfree (nprompt);
+       }
+    }
+  else
+    {
+      if (rl_prompt && rl_already_prompted)
+       rl_on_new_line_with_prompt ();
+      else
+       rl_on_new_line ();
+      (*rl_redisplay_function) ();
+    }
+
+  if (rl_pre_input_hook)
+    (*rl_pre_input_hook) ();
+
+  RL_CHECK_SIGNALS ();
+}
+
+STATIC_CALLBACK char *
+readline_internal_teardown (eof)
+     int eof;
+{
+  char *temp;
+  HIST_ENTRY *entry;
+
+  RL_CHECK_SIGNALS ();
+
+  /* Restore the original of this history line, iff the line that we
+     are editing was originally in the history, AND the line has changed. */
+  entry = current_history ();
+
+  if (entry && rl_undo_list)
+    {
+      temp = savestring (the_line);
+      rl_revert_line (1, 0);
+      entry = replace_history_entry (where_history (), the_line, (histdata_t)NULL);
+      _rl_free_history_entry (entry);
+
+      strcpy (the_line, temp);
+      xfree (temp);
+    }
+
+  if (_rl_revert_all_at_newline)
+    _rl_revert_all_lines ();
+
+  /* At any rate, it is highly likely that this line has an undo list.  Get
+     rid of it now. */
+  if (rl_undo_list)
+    rl_free_undo_list ();
+
+  /* Disable the meta key, if this terminal has one and we were told to use it.
+     The check whether or not we sent the enable string is in
+     _rl_disable_meta_key(); the flag is set in _rl_enable_meta_key */
+  _rl_disable_meta_key ();
+
+  /* Restore normal cursor, if available. */
+  _rl_set_insert_mode (RL_IM_INSERT, 0);
+
+  return (eof ? (char *)NULL : savestring (the_line));
+}
+
+void
+_rl_internal_char_cleanup ()
+{
+#if defined (VI_MODE)
+  /* In vi mode, when you exit insert mode, the cursor moves back
+     over the previous character.  We explicitly check for that here. */
+  if (rl_editing_mode == vi_mode && _rl_keymap == vi_movement_keymap)
+    rl_vi_check ();
+#endif /* VI_MODE */
+
+  if (rl_num_chars_to_read && rl_end >= rl_num_chars_to_read)
+    {
+      (*rl_redisplay_function) ();
+      _rl_want_redisplay = 0;
+      rl_newline (1, '\n');
+    }
+
+  if (rl_done == 0)
+    {
+      (*rl_redisplay_function) ();
+      _rl_want_redisplay = 0;
+    }
+
+  /* If the application writer has told us to erase the entire line if
+     the only character typed was something bound to rl_newline, do so. */
+  if (rl_erase_empty_line && rl_done && rl_last_func == rl_newline &&
+      rl_point == 0 && rl_end == 0)
+    _rl_erase_entire_line ();
+}
+
+STATIC_CALLBACK int
+#if defined (READLINE_CALLBACKS)
+readline_internal_char ()
+#else
+readline_internal_charloop ()
+#endif
+{
+  static int lastc, eof_found;
+  int c, code, lk;
+
+  lastc = -1;
+  eof_found = 0;
+
+#if !defined (READLINE_CALLBACKS)
+  while (rl_done == 0)
+    {
+#endif
+      lk = _rl_last_command_was_kill;
+
+#if defined (HAVE_POSIX_SIGSETJMP)
+      code = sigsetjmp (_rl_top_level, 0);
+#else
+      code = setjmp (_rl_top_level);
+#endif
+
+      if (code)
+       {
+         (*rl_redisplay_function) ();
+         _rl_want_redisplay = 0;
+         /* If we get here, we're not being called from something dispatched
+            from _rl_callback_read_char(), which sets up its own value of
+            _rl_top_level (saving and restoring the old, of course), so
+            we can just return here. */
+         if (RL_ISSTATE (RL_STATE_CALLBACK))
+           return (0);
+       }
+
+      if (rl_pending_input == 0)
+       {
+         /* Then initialize the argument and number of keys read. */
+         _rl_reset_argument ();
+         rl_key_sequence_length = 0;
+         rl_executing_keyseq[0] = 0;
+       }
+
+      RL_SETSTATE(RL_STATE_READCMD);
+      c = rl_read_key ();
+      RL_UNSETSTATE(RL_STATE_READCMD);
+
+      /* look at input.c:rl_getc() for the circumstances under which this will
+        be returned; punt immediately on read error without converting it to
+        a newline; assume that rl_read_key has already called the signal
+        handler. */
+      if (c == READERR)
+       {
+#if defined (READLINE_CALLBACKS)
+         RL_SETSTATE(RL_STATE_DONE);
+         return (rl_done = 1);
+#else
+         eof_found = 1;
+         break;
+#endif
+       }
+
+      /* EOF typed to a non-blank line is a <NL>.  If we want to change this,
+        to force any existing line to be ignored when read(2) reads EOF,
+        for example, this is the place to change. */
+      if (c == EOF && rl_end)
+       c = NEWLINE;
+
+      /* The character _rl_eof_char typed to blank line, and not as the
+        previous character is interpreted as EOF. */
+      if (((c == _rl_eof_char && lastc != c) || c == EOF) && !rl_end)
+       {
+#if defined (READLINE_CALLBACKS)
+         RL_SETSTATE(RL_STATE_DONE);
+         return (rl_done = 1);
+#else
+         eof_found = 1;
+         break;
+#endif
+       }
+
+      lastc = c;
+      _rl_dispatch ((unsigned char)c, _rl_keymap);
+      RL_CHECK_SIGNALS ();
+
+      /* If there was no change in _rl_last_command_was_kill, then no kill
+        has taken place.  Note that if input is pending we are reading
+        a prefix command, so nothing has changed yet. */
+      if (rl_pending_input == 0 && lk == _rl_last_command_was_kill)
+       _rl_last_command_was_kill = 0;
+
+      _rl_internal_char_cleanup ();
+
+#if defined (READLINE_CALLBACKS)
+      return 0;
+#else
+    }
+
+  return (eof_found);
+#endif
+}
+
+#if defined (READLINE_CALLBACKS)
+static int
+readline_internal_charloop ()
+{
+  int eof = 1;
+
+  while (rl_done == 0)
+    eof = readline_internal_char ();
+  return (eof);
+}
+#endif /* READLINE_CALLBACKS */
+
+/* Read a line of input from the global rl_instream, doing output on
+   the global rl_outstream.
+   If rl_prompt is non-null, then that is our prompt. */
+static char *
+readline_internal ()
+{
+  int eof;
+
+  readline_internal_setup ();
+  eof = readline_internal_charloop ();
+  return (readline_internal_teardown (eof));
+}
+
+void
+_rl_init_line_state ()
+{
+  rl_point = rl_end = rl_mark = 0;
+  the_line = rl_line_buffer;
+  the_line[0] = 0;
+}
+
+void
+_rl_set_the_line ()
+{
+  the_line = rl_line_buffer;
+}
+
+#if defined (READLINE_CALLBACKS)
+_rl_keyseq_cxt *
+_rl_keyseq_cxt_alloc ()
+{
+  _rl_keyseq_cxt *cxt;
+
+  cxt = (_rl_keyseq_cxt *)xmalloc (sizeof (_rl_keyseq_cxt));
+
+  cxt->flags = cxt->subseq_arg = cxt->subseq_retval = 0;
+
+  cxt->okey = 0;
+  cxt->ocxt = _rl_kscxt;
+  cxt->childval = 42;          /* sentinel value */
+
+  return cxt;
+}
+
+void
+_rl_keyseq_cxt_dispose (cxt)
+    _rl_keyseq_cxt *cxt;
+{
+  xfree (cxt);
+}
+
+void
+_rl_keyseq_chain_dispose ()
+{
+  _rl_keyseq_cxt *cxt;
+
+  while (_rl_kscxt)
+    {
+      cxt = _rl_kscxt;
+      _rl_kscxt = _rl_kscxt->ocxt;
+      _rl_keyseq_cxt_dispose (cxt);
+    }
+}
+#endif
+
+static int
+_rl_subseq_getchar (key)
+     int key;
+{
+  int k;
+
+  if (key == ESC)
+    RL_SETSTATE(RL_STATE_METANEXT);
+  RL_SETSTATE(RL_STATE_MOREINPUT);
+  k = rl_read_key ();
+  RL_UNSETSTATE(RL_STATE_MOREINPUT);
+  if (key == ESC)
+    RL_UNSETSTATE(RL_STATE_METANEXT);
+
+  return k;
+}
+
+#if defined (READLINE_CALLBACKS)
+int
+_rl_dispatch_callback (cxt)
+     _rl_keyseq_cxt *cxt;
+{
+  int nkey, r;
+
+  /* For now */
+  /* The first time this context is used, we want to read input and dispatch
+     on it.  When traversing the chain of contexts back `up', we want to use
+     the value from the next context down.  We're simulating recursion using
+     a chain of contexts. */
+  if ((cxt->flags & KSEQ_DISPATCHED) == 0)
+    {
+      nkey = _rl_subseq_getchar (cxt->okey);
+      if (nkey < 0)
+       {
+         _rl_abort_internal ();
+         return -1;
+       }
+      r = _rl_dispatch_subseq (nkey, cxt->dmap, cxt->subseq_arg);
+      cxt->flags |= KSEQ_DISPATCHED;
+    }
+  else
+    r = cxt->childval;
+
+  /* For now */
+  if (r != -3) /* don't do this if we indicate there will be other matches */
+    r = _rl_subseq_result (r, cxt->oldmap, cxt->okey, (cxt->flags & KSEQ_SUBSEQ));
+
+  RL_CHECK_SIGNALS ();
+  if (r == 0)                  /* success! */
+    {
+      _rl_keyseq_chain_dispose ();
+      RL_UNSETSTATE (RL_STATE_MULTIKEY);
+      return r;
+    }
+
+  if (r != -3)                 /* magic value that says we added to the chain */
+    _rl_kscxt = cxt->ocxt;
+  if (_rl_kscxt)
+    _rl_kscxt->childval = r;
+  if (r != -3)
+    _rl_keyseq_cxt_dispose (cxt);
+
+  return r;
+}
+#endif /* READLINE_CALLBACKS */
+  
+/* Do the command associated with KEY in MAP.
+   If the associated command is really a keymap, then read
+   another key, and dispatch into that map. */
+int
+_rl_dispatch (key, map)
+     register int key;
+     Keymap map;
+{
+  _rl_dispatching_keymap = map;
+  return _rl_dispatch_subseq (key, map, 0);
+}
+
+int
+_rl_dispatch_subseq (key, map, got_subseq)
+     register int key;
+     Keymap map;
+     int got_subseq;
+{
+  int r, newkey;
+  char *macro;
+  rl_command_func_t *func;
+#if defined (READLINE_CALLBACKS)
+  _rl_keyseq_cxt *cxt;
+#endif
+
+  if (META_CHAR (key) && _rl_convert_meta_chars_to_ascii)
+    {
+      if (map[ESC].type == ISKMAP)
+       {
+         if (RL_ISSTATE (RL_STATE_MACRODEF))
+           _rl_add_macro_char (ESC);
+         RESIZE_KEYSEQ_BUFFER ();
+         rl_executing_keyseq[rl_key_sequence_length++] = ESC;
+         map = FUNCTION_TO_KEYMAP (map, ESC);
+         key = UNMETA (key);
+         return (_rl_dispatch (key, map));
+       }
+      else
+       rl_ding ();
+      return 0;
+    }
+
+  if (RL_ISSTATE (RL_STATE_MACRODEF))
+    _rl_add_macro_char (key);
+
+  r = 0;
+  switch (map[key].type)
+    {
+    case ISFUNC:
+      func = map[key].function;
+      if (func)
+       {
+         /* Special case rl_do_lowercase_version (). */
+         if (func == rl_do_lowercase_version)
+           /* Should we do anything special if key == ANYOTHERKEY? */
+           return (_rl_dispatch (_rl_to_lower (key), map));
+
+         rl_executing_keymap = map;
+         rl_executing_key = key;
+
+         RESIZE_KEYSEQ_BUFFER();
+         rl_executing_keyseq[rl_key_sequence_length++] = key;
+         rl_executing_keyseq[rl_key_sequence_length] = '\0';
+
+         rl_dispatching = 1;
+         RL_SETSTATE(RL_STATE_DISPATCHING);
+         r = (*func) (rl_numeric_arg * rl_arg_sign, key);
+         RL_UNSETSTATE(RL_STATE_DISPATCHING);
+         rl_dispatching = 0;
+
+         /* If we have input pending, then the last command was a prefix
+            command.  Don't change the state of rl_last_func.  Otherwise,
+            remember the last command executed in this variable. */
+         if (rl_pending_input == 0 && map[key].function != rl_digit_argument)
+           rl_last_func = map[key].function;
+
+         RL_CHECK_SIGNALS ();
+       }
+      else if (map[ANYOTHERKEY].function)
+       {
+         /* OK, there's no function bound in this map, but there is a
+            shadow function that was overridden when the current keymap
+            was created.  Return -2 to note  that. */
+         if (RL_ISSTATE (RL_STATE_MACROINPUT))
+           _rl_prev_macro_key ();
+         else
+           _rl_unget_char  (key);
+         return -2;
+       }
+      else if (got_subseq)
+       {
+         /* Return -1 to note that we're in a subsequence, but  we don't
+            have a matching key, nor was one overridden.  This means
+            we need to back up the recursion chain and find the last
+            subsequence that is bound to a function. */
+         if (RL_ISSTATE (RL_STATE_MACROINPUT))
+           _rl_prev_macro_key ();
+         else
+           _rl_unget_char (key);
+         return -1;
+       }
+      else
+       {
+#if defined (READLINE_CALLBACKS)
+         RL_UNSETSTATE (RL_STATE_MULTIKEY);
+         _rl_keyseq_chain_dispose ();
+#endif
+         _rl_abort_internal ();
+         return -1;
+       }
+      break;
+
+    case ISKMAP:
+      if (map[key].function != 0)
+       {
+#if defined (VI_MODE)
+         /* The only way this test will be true is if a subsequence has been
+            bound starting with ESC, generally the arrow keys.  What we do is
+            check whether there's input in the queue, which there generally
+            will be if an arrow key has been pressed, and, if there's not,
+            just dispatch to (what we assume is) rl_vi_movement_mode right
+            away.  This is essentially an input test with a zero timeout (by
+            default) or a timeout determined by the value of `keyseq-timeout' */
+         /* _rl_keyseq_timeout specified in milliseconds; _rl_input_queued
+            takes microseconds, so multiply by 1000 */
+         if (rl_editing_mode == vi_mode && key == ESC && map == vi_insertion_keymap
+             && _rl_input_queued ((_rl_keyseq_timeout > 0) ? _rl_keyseq_timeout*1000 : 0) == 0)
+           return (_rl_dispatch (ANYOTHERKEY, FUNCTION_TO_KEYMAP (map, key)));
+#endif
+
+         RESIZE_KEYSEQ_BUFFER ();
+         rl_executing_keyseq[rl_key_sequence_length++] = key;
+         _rl_dispatching_keymap = FUNCTION_TO_KEYMAP (map, key);
+
+         /* Allocate new context here.  Use linked contexts (linked through
+            cxt->ocxt) to simulate recursion */
+#if defined (READLINE_CALLBACKS)
+         if (RL_ISSTATE (RL_STATE_CALLBACK))
+           {
+             /* Return 0 only the first time, to indicate success to
+                _rl_callback_read_char.  The rest of the time, we're called
+                from _rl_dispatch_callback, so we return -3 to indicate
+                special handling is necessary. */
+             r = RL_ISSTATE (RL_STATE_MULTIKEY) ? -3 : 0;
+             cxt = _rl_keyseq_cxt_alloc ();
+
+             if (got_subseq)
+               cxt->flags |= KSEQ_SUBSEQ;
+             cxt->okey = key;
+             cxt->oldmap = map;
+             cxt->dmap = _rl_dispatching_keymap;
+             cxt->subseq_arg = got_subseq || cxt->dmap[ANYOTHERKEY].function;
+
+             RL_SETSTATE (RL_STATE_MULTIKEY);
+             _rl_kscxt = cxt;
+
+             return r;         /* don't indicate immediate success */
+           }
+#endif
+
+         /* Tentative inter-character timeout for potential multi-key
+            sequences?  If no input within timeout, abort sequence and
+            act as if we got non-matching input. */
+         /* _rl_keyseq_timeout specified in milliseconds; _rl_input_queued
+            takes microseconds, so multiply by 1000 */
+         if (_rl_keyseq_timeout > 0 &&
+               (RL_ISSTATE (RL_STATE_INPUTPENDING|RL_STATE_MACROINPUT) == 0) &&
+               _rl_pushed_input_available () == 0 &&
+               _rl_dispatching_keymap[ANYOTHERKEY].function &&
+               _rl_input_queued (_rl_keyseq_timeout*1000) == 0)
+           return (_rl_subseq_result (-2, map, key, got_subseq));
+
+         newkey = _rl_subseq_getchar (key);
+         if (newkey < 0)
+           {
+             _rl_abort_internal ();
+             return -1;
+           }
+
+         r = _rl_dispatch_subseq (newkey, _rl_dispatching_keymap, got_subseq || map[ANYOTHERKEY].function);
+         return _rl_subseq_result (r, map, key, got_subseq);
+       }
+      else
+       {
+         _rl_abort_internal ();
+         return -1;
+       }
+      break;
+
+    case ISMACR:
+      if (map[key].function != 0)
+       {
+         rl_executing_keyseq[rl_key_sequence_length] = '\0';
+         macro = savestring ((char *)map[key].function);
+         _rl_with_macro_input (macro);
+         return 0;
+       }
+      break;
+    }
+#if defined (VI_MODE)
+  if (rl_editing_mode == vi_mode && _rl_keymap == vi_movement_keymap &&
+      key != ANYOTHERKEY &&
+      rl_key_sequence_length == 1 &&   /* XXX */
+      _rl_vi_textmod_command (key))
+    _rl_vi_set_last (key, rl_numeric_arg, rl_arg_sign);
+#endif
+
+  return (r);
+}
+
+static int
+_rl_subseq_result (r, map, key, got_subseq)
+     int r;
+     Keymap map;
+     int key, got_subseq;
+{
+  Keymap m;
+  int type, nt;
+  rl_command_func_t *func, *nf;
+
+  if (r == -2)
+    /* We didn't match anything, and the keymap we're indexed into
+       shadowed a function previously bound to that prefix.  Call
+       the function.  The recursive call to _rl_dispatch_subseq has
+       already taken care of pushing any necessary input back onto
+       the input queue with _rl_unget_char. */
+    {
+      m = _rl_dispatching_keymap;
+      type = m[ANYOTHERKEY].type;
+      func = m[ANYOTHERKEY].function;
+      if (type == ISFUNC && func == rl_do_lowercase_version)
+       r = _rl_dispatch (_rl_to_lower (key), map);
+      else if (type == ISFUNC && func == rl_insert)
+       {
+         /* If the function that was shadowed was self-insert, we
+            somehow need a keymap with map[key].func == self-insert.
+            Let's use this one. */
+         nt = m[key].type;
+         nf = m[key].function;
+
+         m[key].type = type;
+         m[key].function = func;
+         r = _rl_dispatch (key, m);
+         m[key].type = nt;
+         m[key].function = nf;
+       }
+      else
+       r = _rl_dispatch (ANYOTHERKEY, m);
+    }
+  else if (r && map[ANYOTHERKEY].function)
+    {
+      /* We didn't match (r is probably -1), so return something to
+        tell the caller that it should try ANYOTHERKEY for an
+        overridden function. */
+      if (RL_ISSTATE (RL_STATE_MACROINPUT))
+       _rl_prev_macro_key ();
+      else
+       _rl_unget_char (key);
+      _rl_dispatching_keymap = map;
+      return -2;
+    }
+  else if (r && got_subseq)
+    {
+      /* OK, back up the chain. */
+      if (RL_ISSTATE (RL_STATE_MACROINPUT))
+       _rl_prev_macro_key ();
+      else
+       _rl_unget_char (key);
+      _rl_dispatching_keymap = map;
+      return -1;
+    }
+
+  return r;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Initializations                             */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Initialize readline (and terminal if not already). */
+int
+rl_initialize ()
+{
+  /* If we have never been called before, initialize the
+     terminal and data structures. */
+  if (!rl_initialized)
+    {
+      RL_SETSTATE(RL_STATE_INITIALIZING);
+      readline_initialize_everything ();
+      RL_UNSETSTATE(RL_STATE_INITIALIZING);
+      rl_initialized++;
+      RL_SETSTATE(RL_STATE_INITIALIZED);
+    }
+
+  /* Initalize the current line information. */
+  _rl_init_line_state ();
+
+  /* We aren't done yet.  We haven't even gotten started yet! */
+  rl_done = 0;
+  RL_UNSETSTATE(RL_STATE_DONE);
+
+  /* Tell the history routines what is going on. */
+  _rl_start_using_history ();
+
+  /* Make the display buffer match the state of the line. */
+  rl_reset_line_state ();
+
+  /* No such function typed yet. */
+  rl_last_func = (rl_command_func_t *)NULL;
+
+  /* Parsing of key-bindings begins in an enabled state. */
+  _rl_parsing_conditionalized_out = 0;
+
+#if defined (VI_MODE)
+  if (rl_editing_mode == vi_mode)
+    _rl_vi_initialize_line ();
+#endif
+
+  /* Each line starts in insert mode (the default). */
+  _rl_set_insert_mode (RL_IM_DEFAULT, 1);
+
+  return 0;
+}
+
+#if 0
+#if defined (__EMX__)
+static void
+_emx_build_environ ()
+{
+  TIB *tibp;
+  PIB *pibp;
+  char *t, **tp;
+  int c;
+
+  DosGetInfoBlocks (&tibp, &pibp);
+  t = pibp->pib_pchenv;
+  for (c = 1; *t; c++)
+    t += strlen (t) + 1;
+  tp = environ = (char **)xmalloc ((c + 1) * sizeof (char *));
+  t = pibp->pib_pchenv;
+  while (*t)
+    {
+      *tp++ = t;
+      t += strlen (t) + 1;
+    }
+  *tp = 0;
+}
+#endif /* __EMX__ */
+#endif
+
+/* Initialize the entire state of the world. */
+static void
+readline_initialize_everything ()
+{
+#if 0
+#if defined (__EMX__)
+  if (environ == 0)
+    _emx_build_environ ();
+#endif
+#endif
+
+#if 0
+  /* Find out if we are running in Emacs -- UNUSED. */
+  running_in_emacs = sh_get_env_value ("EMACS") != (char *)0;
+#endif
+
+  /* Set up input and output if they are not already set up. */
+  if (!rl_instream)
+    rl_instream = stdin;
+
+  if (!rl_outstream)
+    rl_outstream = stdout;
+
+  /* Bind _rl_in_stream and _rl_out_stream immediately.  These values
+     may change, but they may also be used before readline_internal ()
+     is called. */
+  _rl_in_stream = rl_instream;
+  _rl_out_stream = rl_outstream;
+
+  /* Allocate data structures. */
+  if (rl_line_buffer == 0)
+    rl_line_buffer = (char *)xmalloc (rl_line_buffer_len = DEFAULT_BUFFER_SIZE);
+
+  /* Initialize the terminal interface. */
+  if (rl_terminal_name == 0)
+    rl_terminal_name = sh_get_env_value ("TERM");
+  _rl_init_terminal_io (rl_terminal_name);
+
+  /* Bind tty characters to readline functions. */
+  readline_default_bindings ();
+
+  /* Initialize the function names. */
+  rl_initialize_funmap ();
+
+  /* Decide whether we should automatically go into eight-bit mode. */
+  _rl_init_eightbit ();
+      
+  /* Read in the init file. */
+  rl_read_init_file ((char *)NULL);
+
+  /* XXX */
+  if (_rl_horizontal_scroll_mode && _rl_term_autowrap)
+    {
+      _rl_screenwidth--;
+      _rl_screenchars -= _rl_screenheight;
+    }
+
+  /* Override the effect of any `set keymap' assignments in the
+     inputrc file. */
+  rl_set_keymap_from_edit_mode ();
+
+  /* Try to bind a common arrow key prefix, if not already bound. */
+  bind_arrow_keys ();
+
+  /* If the completion parser's default word break characters haven't
+     been set yet, then do so now. */
+  if (rl_completer_word_break_characters == (char *)NULL)
+    rl_completer_word_break_characters = (char *)rl_basic_word_break_characters;
+
+#if defined (COLOR_SUPPORT)
+  if (_rl_colored_stats)
+    _rl_parse_colors ();
+#endif
+
+  rl_executing_keyseq = malloc (_rl_executing_keyseq_size = 16);
+  if (rl_executing_keyseq)
+    rl_executing_keyseq[0] = '\0';
+}
+
+/* If this system allows us to look at the values of the regular
+   input editing characters, then bind them to their readline
+   equivalents, iff the characters are not bound to keymaps. */
+static void
+readline_default_bindings ()
+{
+  if (_rl_bind_stty_chars)
+    rl_tty_set_default_bindings (_rl_keymap);
+}
+
+/* Reset the default bindings for the terminal special characters we're
+   interested in back to rl_insert and read the new ones. */
+static void
+reset_default_bindings ()
+{
+  if (_rl_bind_stty_chars)
+    {
+      rl_tty_unset_default_bindings (_rl_keymap);
+      rl_tty_set_default_bindings (_rl_keymap);
+    }
+}
+
+/* Bind some common arrow key sequences in MAP. */
+static void
+bind_arrow_keys_internal (map)
+     Keymap map;
+{
+  Keymap xkeymap;
+
+  xkeymap = _rl_keymap;
+  _rl_keymap = map;
+
+#if defined (__MSDOS__)
+  rl_bind_keyseq_if_unbound ("\033[0A", rl_get_previous_history);
+  rl_bind_keyseq_if_unbound ("\033[0B", rl_backward_char);
+  rl_bind_keyseq_if_unbound ("\033[0C", rl_forward_char);
+  rl_bind_keyseq_if_unbound ("\033[0D", rl_get_next_history);
+#endif
+
+  rl_bind_keyseq_if_unbound ("\033[A", rl_get_previous_history);
+  rl_bind_keyseq_if_unbound ("\033[B", rl_get_next_history);
+  rl_bind_keyseq_if_unbound ("\033[C", rl_forward_char);
+  rl_bind_keyseq_if_unbound ("\033[D", rl_backward_char);
+  rl_bind_keyseq_if_unbound ("\033[H", rl_beg_of_line);
+  rl_bind_keyseq_if_unbound ("\033[F", rl_end_of_line);
+
+  rl_bind_keyseq_if_unbound ("\033OA", rl_get_previous_history);
+  rl_bind_keyseq_if_unbound ("\033OB", rl_get_next_history);
+  rl_bind_keyseq_if_unbound ("\033OC", rl_forward_char);
+  rl_bind_keyseq_if_unbound ("\033OD", rl_backward_char);
+  rl_bind_keyseq_if_unbound ("\033OH", rl_beg_of_line);
+  rl_bind_keyseq_if_unbound ("\033OF", rl_end_of_line);
+
+#if defined (__MINGW32__)
+  rl_bind_keyseq_if_unbound ("\340H", rl_get_previous_history);
+  rl_bind_keyseq_if_unbound ("\340P", rl_get_next_history);
+  rl_bind_keyseq_if_unbound ("\340M", rl_forward_char);
+  rl_bind_keyseq_if_unbound ("\340K", rl_backward_char);
+  rl_bind_keyseq_if_unbound ("\340G", rl_beg_of_line);
+  rl_bind_keyseq_if_unbound ("\340O", rl_end_of_line);
+  rl_bind_keyseq_if_unbound ("\340S", rl_delete);
+  rl_bind_keyseq_if_unbound ("\340R", rl_overwrite_mode);  
+#endif
+
+  _rl_keymap = xkeymap;
+}
+
+/* Try and bind the common arrow key prefixes after giving termcap and
+   the inputrc file a chance to bind them and create `real' keymaps
+   for the arrow key prefix. */
+static void
+bind_arrow_keys ()
+{
+  bind_arrow_keys_internal (emacs_standard_keymap);
+
+#if defined (VI_MODE)
+  bind_arrow_keys_internal (vi_movement_keymap);
+  /* Unbind vi_movement_keymap[ESC] to allow users to repeatedly hit ESC
+     in vi command mode while still allowing the arrow keys to work. */
+  if (vi_movement_keymap[ESC].type == ISKMAP)
+    rl_bind_keyseq_in_map ("\033", (rl_command_func_t *)NULL, vi_movement_keymap);
+  bind_arrow_keys_internal (vi_insertion_keymap);
+#endif
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*             Saving and Restoring Readline's state               */
+/*                                                                 */
+/* **************************************************************** */
+
+int
+rl_save_state (sp)
+     struct readline_state *sp;
+{
+  if (sp == 0)
+    return -1;
+
+  sp->point = rl_point;
+  sp->end = rl_end;
+  sp->mark = rl_mark;
+  sp->buffer = rl_line_buffer;
+  sp->buflen = rl_line_buffer_len;
+  sp->ul = rl_undo_list;
+  sp->prompt = rl_prompt;
+
+  sp->rlstate = rl_readline_state;
+  sp->done = rl_done;
+  sp->kmap = _rl_keymap;
+
+  sp->lastfunc = rl_last_func;
+  sp->insmode = rl_insert_mode;
+  sp->edmode = rl_editing_mode;
+  sp->kseqlen = rl_key_sequence_length;
+  sp->inf = rl_instream;
+  sp->outf = rl_outstream;
+  sp->pendingin = rl_pending_input;
+  sp->macro = rl_executing_macro;
+
+  sp->catchsigs = rl_catch_signals;
+  sp->catchsigwinch = rl_catch_sigwinch;
+
+  return (0);
+}
+
+int
+rl_restore_state (sp)
+     struct readline_state *sp;
+{
+  if (sp == 0)
+    return -1;
+
+  rl_point = sp->point;
+  rl_end = sp->end;
+  rl_mark = sp->mark;
+  the_line = rl_line_buffer = sp->buffer;
+  rl_line_buffer_len = sp->buflen;
+  rl_undo_list = sp->ul;
+  rl_prompt = sp->prompt;
+
+  rl_readline_state = sp->rlstate;
+  rl_done = sp->done;
+  _rl_keymap = sp->kmap;
+
+  rl_last_func = sp->lastfunc;
+  rl_insert_mode = sp->insmode;
+  rl_editing_mode = sp->edmode;
+  rl_key_sequence_length = sp->kseqlen;
+  rl_instream = sp->inf;
+  rl_outstream = sp->outf;
+  rl_pending_input = sp->pendingin;
+  rl_executing_macro = sp->macro;
+
+  rl_catch_signals = sp->catchsigs;
+  rl_catch_sigwinch = sp->catchsigwinch;
+
+  return (0);
+}
diff --git a/subst.c b/subst.c
index 9894792215eb717921836741cc5cfdbb3604d166..250c1eb1b90cada912bcd9a43a960da27dcd7097 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -380,6 +380,11 @@ dump_word_flags (flags)
       f &= ~W_ASSNGLOBAL;
       fprintf (stderr, "W_ASSNGLOBAL%s", f ? "|" : "");
     }
+  if (f & W_ASSIGNINT)
+    {
+      f &= ~W_ASSIGNINT;
+      fprintf (stderr, "W_ASSIGNINT%s", f ? "|" : "");
+    }
   if (f & W_COMPASSIGN)
     {
       f &= ~W_COMPASSIGN;
@@ -9430,17 +9435,38 @@ shell_expand_word_list (tlist, eflags)
       if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
        {
          int t;
+         char opts[8], opti;
+
+         opti = 0;
+         if (tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL|W_ASSIGNARRAY))
+           opts[opti++] = '-';
 
          if ((tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL)) == (W_ASSIGNASSOC|W_ASSNGLOBAL))
-           make_internal_declare (tlist->word->word, "-gA");
+           {
+             opts[opti++] = 'g';
+             opts[opti++] = 'A';
+           }
          else if (tlist->word->flags & W_ASSIGNASSOC)
-           make_internal_declare (tlist->word->word, "-A");
+           opts[opti++] = 'A';
          else if ((tlist->word->flags & (W_ASSIGNARRAY|W_ASSNGLOBAL)) == (W_ASSIGNARRAY|W_ASSNGLOBAL))
-           make_internal_declare (tlist->word->word, "-ga");
+           {
+             opts[opti++] = 'g';
+             opts[opti++] = 'a';
+           }
          else if (tlist->word->flags & W_ASSIGNARRAY)
-           make_internal_declare (tlist->word->word, "-a");
+           opts[opti++] = 'a';
          else if (tlist->word->flags & W_ASSNGLOBAL)
-           make_internal_declare (tlist->word->word, "-g");
+           opts[opti++] = 'g';
+
+#if 0
+         /* If we have special handling note the integer attribute */
+         if (opti > 0 && (tlist->word->flags & W_ASSIGNINT))
+           opts[opti++] = 'i';
+#endif
+
+         opts[opti] = '\0';
+         if (opti > 0)
+           make_internal_declare (tlist->word->word, opts);
 
          t = do_word_assignment (tlist->word, 0);
          if (t == 0)
diff --git a/subst.c~ b/subst.c~
new file mode 100644 (file)
index 0000000..d03931b
--- /dev/null
+++ b/subst.c~
@@ -0,0 +1,9662 @@
+/* subst.c -- The part of the shell that does parameter, command, arithmetic,
+   and globbing substitutions. */
+
+/* ``Have a little faith, there's magic in the night.  You ain't a
+     beauty, but, hey, you're alright.'' */
+
+/* Copyright (C) 1987-2013 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 3 of the License, 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.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include "bashtypes.h"
+#include <stdio.h>
+#include "chartypes.h"
+#if defined (HAVE_PWD_H)
+#  include <pwd.h>
+#endif
+#include <signal.h>
+#include <errno.h>
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include "bashansi.h"
+#include "posixstat.h"
+#include "bashintl.h"
+
+#include "shell.h"
+#include "parser.h"
+#include "flags.h"
+#include "jobs.h"
+#include "execute_cmd.h"
+#include "filecntl.h"
+#include "trap.h"
+#include "pathexp.h"
+#include "mailcheck.h"
+
+#include "shmbutil.h"
+#include "typemax.h"
+
+#include "builtins/getopt.h"
+#include "builtins/common.h"
+
+#include "builtins/builtext.h"
+
+#include <tilde/tilde.h>
+#include <glob/strmatch.h>
+
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+/* The size that strings change by. */
+#define DEFAULT_INITIAL_ARRAY_SIZE 112
+#define DEFAULT_ARRAY_SIZE 128
+
+/* Variable types. */
+#define VT_VARIABLE    0
+#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 */
+
+/* Flags for quoted_strchr */
+#define ST_BACKSL      0x01
+#define ST_CTLESC      0x02
+#define ST_SQUOTE      0x04    /* unused yet */
+#define ST_DQUOTE      0x08    /* unused yet */
+
+/* Flags for the `pflags' argument to param_expand() */
+#define PF_NOCOMSUB    0x01    /* Do not perform command substitution */
+#define PF_IGNUNBOUND  0x02    /* ignore unbound vars even if -u set */
+#define PF_NOSPLIT2    0x04    /* same as W_NOSPLIT2 */
+#define PF_ASSIGNRHS   0x08    /* same as W_ASSIGNRHS */
+
+/* These defs make it easier to use the editor. */
+#define LBRACE         '{'
+#define RBRACE         '}'
+#define LPAREN         '('
+#define RPAREN         ')'
+
+#if defined (HANDLE_MULTIBYTE)
+#define WLPAREN                L'('
+#define WRPAREN                L')'
+#endif
+
+/* Evaluates to 1 if C is one of the shell's special parameters whose length
+   can be taken, but is also one of the special expansion characters. */
+#define VALID_SPECIAL_LENGTH_PARAM(c) \
+  ((c) == '-' || (c) == '?' || (c) == '#')
+
+/* Evaluates to 1 if C is one of the shell's special parameters for which an
+   indirect variable reference may be made. */
+#define VALID_INDIR_PARAM(c) \
+  ((posixly_correct == 0 && (c) == '#') || (posixly_correct == 0 && (c) == '?') || (c) == '@' || (c) == '*')
+
+/* Evaluates to 1 if C is one of the OP characters that follows the parameter
+   in ${parameter[:]OPword}. */
+#define VALID_PARAM_EXPAND_CHAR(c) (sh_syntaxtab[(unsigned char)c] & CSUBSTOP)
+
+/* Evaluates to 1 if this is one of the shell's special variables. */
+#define SPECIAL_VAR(name, wi) \
+ ((DIGIT (*name) && all_digits (name)) || \
+      (name[1] == '\0' && (sh_syntaxtab[(unsigned char)*name] & CSPECVAR)) || \
+      (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1])))
+
+/* An expansion function that takes a string and a quoted flag and returns
+   a WORD_LIST *.  Used as the type of the third argument to
+   expand_string_if_necessary(). */
+typedef WORD_LIST *EXPFUNC __P((char *, int));
+
+/* Process ID of the last command executed within command substitution. */
+pid_t last_command_subst_pid = NO_PID;
+pid_t current_command_subst_pid = NO_PID;
+
+/* Variables used to keep track of the characters in IFS. */
+SHELL_VAR *ifs_var;
+char *ifs_value;
+unsigned char ifs_cmap[UCHAR_MAX + 1];
+int ifs_is_set, ifs_is_null;
+
+#if defined (HANDLE_MULTIBYTE)
+unsigned char ifs_firstc[MB_LEN_MAX];
+size_t ifs_firstc_len;
+#else
+unsigned char ifs_firstc;
+#endif
+
+/* Sentinel to tell when we are performing variable assignments preceding a
+   command name and putting them into the environment.  Used to make sure
+   we use the temporary environment when looking up variable values. */
+int assigning_in_environment;
+
+/* Used to hold a list of variable assignments preceding a command.  Global
+   so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a
+   SIGCHLD trap and so it can be saved and restored by the trap handlers. */
+WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL;
+
+/* Extern functions and variables from different files. */
+extern int last_command_exit_value, last_command_exit_signal;
+extern int subshell_environment, line_number;
+extern int subshell_level, parse_and_execute_level, sourcelevel;
+extern int eof_encountered;
+extern int return_catch_flag, return_catch_value;
+extern pid_t dollar_dollar_pid;
+extern int posixly_correct;
+extern char *this_command_name;
+extern struct fd_bitmap *current_fds_to_close;
+extern int wordexp_only;
+extern int expanding_redir;
+extern int tempenv_assign_error;
+extern int builtin_ignoring_errexit;
+
+#if !defined (HAVE_WCSDUP) && defined (HANDLE_MULTIBYTE)
+extern wchar_t *wcsdup __P((const wchar_t *));
+#endif
+
+/* Non-zero means to allow unmatched globbed filenames to expand to
+   a null file. */
+int allow_null_glob_expansion;
+
+/* Non-zero means to throw an error when globbing fails to match anything. */
+int fail_glob_expansion;
+
+#if 0
+/* Variables to keep track of which words in an expanded word list (the
+   output of expand_word_list_internal) are the result of globbing
+   expansions.  GLOB_ARGV_FLAGS is used by execute_cmd.c.
+   (CURRENTLY UNUSED). */
+char *glob_argv_flags;
+static int glob_argv_flags_size;
+#endif
+
+static WORD_LIST expand_word_error, expand_word_fatal;
+static WORD_DESC expand_wdesc_error, expand_wdesc_fatal;
+static char expand_param_error, expand_param_fatal;
+static char extract_string_error, extract_string_fatal;
+
+/* Tell the expansion functions to not longjmp back to top_level on fatal
+   errors.  Enabled when doing completion and prompt string expansion. */
+static int no_longjmp_on_fatal_error = 0;
+
+/* Set by expand_word_unsplit; used to inhibit splitting and re-joining
+   $* on $IFS, primarily when doing assignment statements. */
+static int expand_no_split_dollar_star = 0;
+
+/* A WORD_LIST of words to be expanded by expand_word_list_internal,
+   without any leading variable assignments. */
+static WORD_LIST *garglist = (WORD_LIST *)NULL;
+
+static char *quoted_substring __P((char *, int, int));
+static int quoted_strlen __P((char *));
+static char *quoted_strchr __P((char *, int, int));
+
+static char *expand_string_if_necessary __P((char *, int, EXPFUNC *));
+static inline char *expand_string_to_string_internal __P((char *, int, EXPFUNC *));
+static WORD_LIST *call_expand_word_internal __P((WORD_DESC *, int, int, int *, int *));
+static WORD_LIST *expand_string_internal __P((char *, int));
+static WORD_LIST *expand_string_leave_quoted __P((char *, int));
+static WORD_LIST *expand_string_for_rhs __P((char *, int, int *, int *));
+
+static WORD_LIST *list_quote_escapes __P((WORD_LIST *));
+static char *make_quoted_char __P((int));
+static WORD_LIST *quote_list __P((WORD_LIST *));
+
+static int unquoted_substring __P((char *, char *));
+static int unquoted_member __P((int, char *));
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *do_compound_assignment __P((char *, char *, int));
+#endif
+static int do_assignment_internal __P((const WORD_DESC *, int));
+
+static char *string_extract_verbatim __P((char *, size_t, int *, char *, int));
+static char *string_extract __P((char *, int *, char *, int));
+static char *string_extract_double_quoted __P((char *, int *, int));
+static inline char *string_extract_single_quoted __P((char *, int *));
+static inline int skip_single_quoted __P((const char *, size_t, int));
+static int skip_double_quoted __P((char *, size_t, int));
+static char *extract_delimited_string __P((char *, int *, char *, char *, char *, int));
+static char *extract_dollar_brace_string __P((char *, int *, int, int));
+static int skip_matched_pair __P((const char *, int, int, int, int));
+
+static char *pos_params __P((char *, int, int, int));
+
+static unsigned char *mb_getcharlens __P((char *, int));
+
+static char *remove_upattern __P((char *, char *, int));
+#if defined (HANDLE_MULTIBYTE) 
+static wchar_t *remove_wpattern __P((wchar_t *, size_t, wchar_t *, int));
+#endif
+static char *remove_pattern __P((char *, char *, int));
+
+static int match_upattern __P((char *, char *, int, char **, char **));
+#if defined (HANDLE_MULTIBYTE)
+static int match_wpattern __P((wchar_t *, char **, size_t, wchar_t *, int, char **, char **));
+#endif
+static int match_pattern __P((char *, char *, int, char **, char **));
+static int getpatspec __P((int, char *));
+static char *getpattern __P((char *, int, int));
+static char *variable_remove_pattern __P((char *, char *, int, int));
+static char *list_remove_pattern __P((WORD_LIST *, char *, int, int, int));
+static char *parameter_list_remove_pattern __P((int, char *, int, int));
+#ifdef ARRAY_VARS
+static char *array_remove_pattern __P((SHELL_VAR *, char *, int, char *, int));
+#endif
+static char *parameter_brace_remove_pattern __P((char *, char *, int, char *, int, int, int));
+
+static char *process_substitute __P((char *, int));
+
+static char *read_comsub __P((int, int, int *));
+
+#ifdef ARRAY_VARS
+static arrayind_t array_length_reference __P((char *));
+#endif
+
+static int valid_brace_expansion_word __P((char *, int));
+static int chk_atstar __P((char *, int, int *, int *));
+static int chk_arithsub __P((const char *, int));
+
+static WORD_DESC *parameter_brace_expand_word __P((char *, int, int, int, arrayind_t *));
+static char *parameter_brace_find_indir __P((char *, int, int, int));
+static WORD_DESC *parameter_brace_expand_indir __P((char *, int, int, int *, int *));
+static WORD_DESC *parameter_brace_expand_rhs __P((char *, char *, int, int, int *, int *));
+static void parameter_brace_expand_error __P((char *, char *));
+
+static int valid_length_expression __P((char *));
+static intmax_t parameter_brace_expand_length __P((char *));
+
+static char *skiparith __P((char *, int));
+static int verify_substring_values __P((SHELL_VAR *, char *, char *, int, intmax_t *, intmax_t *));
+static int get_var_and_type __P((char *, char *, arrayind_t, int, int, SHELL_VAR **, char **));
+static char *mb_substring __P((char *, int, int));
+static char *parameter_brace_substring __P((char *, char *, int, char *, int, int));
+
+static int shouldexp_replacement __P((char *));
+
+static char *pos_params_pat_subst __P((char *, char *, char *, int));
+
+static char *parameter_brace_patsub __P((char *, char *, int, char *, int, int));
+
+static char *pos_params_casemod __P((char *, char *, int, int));
+static char *parameter_brace_casemod __P((char *, char *, int, int, char *, int, int));
+
+static WORD_DESC *parameter_brace_expand __P((char *, int *, int, int, int *, int *));
+static WORD_DESC *param_expand __P((char *, int *, int, int *, int *, int *, int *, int));
+
+static WORD_LIST *expand_word_internal __P((WORD_DESC *, int, int, int *, int *));
+
+static WORD_LIST *word_list_split __P((WORD_LIST *));
+
+static void exp_jump_to_top_level __P((int));
+
+static WORD_LIST *separate_out_assignments __P((WORD_LIST *));
+static WORD_LIST *glob_expand_word_list __P((WORD_LIST *, int));
+#ifdef BRACE_EXPANSION
+static WORD_LIST *brace_expand_word_list __P((WORD_LIST *, int));
+#endif
+#if defined (ARRAY_VARS)
+static int make_internal_declare __P((char *, char *));
+#endif
+static WORD_LIST *shell_expand_word_list __P((WORD_LIST *, int));
+static WORD_LIST *expand_word_list_internal __P((WORD_LIST *, int));
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Utility Functions                           */
+/*                                                                 */
+/* **************************************************************** */
+
+#if defined (DEBUG)
+void
+dump_word_flags (flags)
+     int flags;
+{
+  int f;
+
+  f = flags;
+  fprintf (stderr, "%d -> ", f);
+  if (f & W_ASSIGNASSOC)
+    {
+      f &= ~W_ASSIGNASSOC;
+      fprintf (stderr, "W_ASSIGNASSOC%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNARRAY)
+    {
+      f &= ~W_ASSIGNARRAY;
+      fprintf (stderr, "W_ASSIGNARRAY%s", f ? "|" : "");
+    }
+  if (f & W_HASCTLESC)
+    {
+      f &= ~W_HASCTLESC;
+      fprintf (stderr, "W_HASCTLESC%s", f ? "|" : "");
+    }
+  if (f & W_NOPROCSUB)
+    {
+      f &= ~W_NOPROCSUB;
+      fprintf (stderr, "W_NOPROCSUB%s", f ? "|" : "");
+    }
+  if (f & W_DQUOTE)
+    {
+      f &= ~W_DQUOTE;
+      fprintf (stderr, "W_DQUOTE%s", f ? "|" : "");
+    }
+  if (f & W_HASQUOTEDNULL)
+    {
+      f &= ~W_HASQUOTEDNULL;
+      fprintf (stderr, "W_HASQUOTEDNULL%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNARG)
+    {
+      f &= ~W_ASSIGNARG;
+      fprintf (stderr, "W_ASSIGNARG%s", f ? "|" : "");
+    }
+  if (f & W_ASSNBLTIN)
+    {
+      f &= ~W_ASSNBLTIN;
+      fprintf (stderr, "W_ASSNBLTIN%s", f ? "|" : "");
+    }
+  if (f & W_ASSNGLOBAL)
+    {
+      f &= ~W_ASSNGLOBAL;
+      fprintf (stderr, "W_ASSNGLOBAL%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNINT)
+    {
+      f &= ~W_ASSIGNINT;
+      fprintf (stderr, "W_ASSIGNINT%s", f ? "|" : "");
+    }
+  if (f & W_COMPASSIGN)
+    {
+      f &= ~W_COMPASSIGN;
+      fprintf (stderr, "W_COMPASSIGN%s", f ? "|" : "");
+    }
+  if (f & W_NOEXPAND)
+    {
+      f &= ~W_NOEXPAND;
+      fprintf (stderr, "W_NOEXPAND%s", f ? "|" : "");
+    }
+  if (f & W_ITILDE)
+    {
+      f &= ~W_ITILDE;
+      fprintf (stderr, "W_ITILDE%s", f ? "|" : "");
+    }
+  if (f & W_NOTILDE)
+    {
+      f &= ~W_NOTILDE;
+      fprintf (stderr, "W_NOTILDE%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNRHS)
+    {
+      f &= ~W_ASSIGNRHS;
+      fprintf (stderr, "W_ASSIGNRHS%s", f ? "|" : "");
+    }
+  if (f & W_NOCOMSUB)
+    {
+      f &= ~W_NOCOMSUB;
+      fprintf (stderr, "W_NOCOMSUB%s", f ? "|" : "");
+    }
+  if (f & W_DOLLARSTAR)
+    {
+      f &= ~W_DOLLARSTAR;
+      fprintf (stderr, "W_DOLLARSTAR%s", f ? "|" : "");
+    }
+  if (f & W_DOLLARAT)
+    {
+      f &= ~W_DOLLARAT;
+      fprintf (stderr, "W_DOLLARAT%s", f ? "|" : "");
+    }
+  if (f & W_TILDEEXP)
+    {
+      f &= ~W_TILDEEXP;
+      fprintf (stderr, "W_TILDEEXP%s", f ? "|" : "");
+    }
+  if (f & W_NOSPLIT2)
+    {
+      f &= ~W_NOSPLIT2;
+      fprintf (stderr, "W_NOSPLIT2%s", f ? "|" : "");
+    }
+  if (f & W_NOSPLIT)
+    {
+      f &= ~W_NOSPLIT;
+      fprintf (stderr, "W_NOSPLIT%s", f ? "|" : "");
+    }
+  if (f & W_NOBRACE)
+    {
+      f &= ~W_NOBRACE;
+      fprintf (stderr, "W_NOBRACE%s", f ? "|" : "");
+    }
+  if (f & W_NOGLOB)
+    {
+      f &= ~W_NOGLOB;
+      fprintf (stderr, "W_NOGLOB%s", f ? "|" : "");
+    }
+  if (f & W_SPLITSPACE)
+    {
+      f &= ~W_SPLITSPACE;
+      fprintf (stderr, "W_SPLITSPACE%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNMENT)
+    {
+      f &= ~W_ASSIGNMENT;
+      fprintf (stderr, "W_ASSIGNMENT%s", f ? "|" : "");
+    }
+  if (f & W_QUOTED)
+    {
+      f &= ~W_QUOTED;
+      fprintf (stderr, "W_QUOTED%s", f ? "|" : "");
+    }
+  if (f & W_HASDOLLAR)
+    {
+      f &= ~W_HASDOLLAR;
+      fprintf (stderr, "W_HASDOLLAR%s", f ? "|" : "");
+    }
+  fprintf (stderr, "\n");
+  fflush (stderr);
+}
+#endif
+
+#ifdef INCLUDE_UNUSED
+static char *
+quoted_substring (string, start, end)
+     char *string;
+     int start, end;
+{
+  register int len, l;
+  register char *result, *s, *r;
+
+  len = end - start;
+
+  /* Move to string[start], skipping quoted characters. */
+  for (s = string, l = 0; *s && l < start; )
+    {
+      if (*s == CTLESC)
+       {
+         s++;
+         continue;
+       }
+      l++;
+      if (*s == 0)
+       break;
+    }
+
+  r = result = (char *)xmalloc (2*len + 1);      /* save room for quotes */
+
+  /* Copy LEN characters, including quote characters. */
+  s = string + l;
+  for (l = 0; l < len; s++)
+    {
+      if (*s == CTLESC)
+       *r++ = *s++;
+      *r++ = *s;
+      l++;
+      if (*s == 0)
+       break;
+    }
+  *r = '\0';
+  return result;
+}
+#endif
+
+#ifdef INCLUDE_UNUSED
+/* Return the length of S, skipping over quoted characters */
+static int
+quoted_strlen (s)
+     char *s;
+{
+  register char *p;
+  int i;
+
+  i = 0;
+  for (p = s; *p; p++)
+    {
+      if (*p == CTLESC)
+       {
+         p++;
+         if (*p == 0)
+           return (i + 1);
+       }
+      i++;
+    }
+
+  return i;
+}
+#endif
+
+/* Find the first occurrence of character C in string S, obeying shell
+   quoting rules.  If (FLAGS & ST_BACKSL) is non-zero, backslash-escaped
+   characters are skipped.  If (FLAGS & ST_CTLESC) is non-zero, characters
+   escaped with CTLESC are skipped. */
+static char *
+quoted_strchr (s, c, flags)
+     char *s;
+     int c, flags;
+{
+  register char *p;
+
+  for (p = s; *p; p++)
+    {
+      if (((flags & ST_BACKSL) && *p == '\\')
+           || ((flags & ST_CTLESC) && *p == CTLESC))
+       {
+         p++;
+         if (*p == '\0')
+           return ((char *)NULL);
+         continue;
+       }
+      else if (*p == c)
+       return p;
+    }
+  return ((char *)NULL);
+}
+
+/* Return 1 if CHARACTER appears in an unquoted portion of
+   STRING.  Return 0 otherwise.  CHARACTER must be a single-byte character. */
+static int
+unquoted_member (character, string)
+     int character;
+     char *string;
+{
+  size_t slen;
+  int sindex, c;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+  sindex = 0;
+  while (c = string[sindex])
+    {
+      if (c == character)
+       return (1);
+
+      switch (c)
+       {
+       default:
+         ADVANCE_CHAR (string, slen, sindex);
+         break;
+
+       case '\\':
+         sindex++;
+         if (string[sindex])
+           ADVANCE_CHAR (string, slen, sindex);
+         break;
+
+       case '\'':
+         sindex = skip_single_quoted (string, slen, ++sindex);
+         break;
+
+       case '"':
+         sindex = skip_double_quoted (string, slen, ++sindex);
+         break;
+       }
+    }
+  return (0);
+}
+
+/* Return 1 if SUBSTR appears in an unquoted portion of STRING. */
+static int
+unquoted_substring (substr, string)
+     char *substr, *string;
+{
+  size_t slen;
+  int sindex, c, sublen;
+  DECLARE_MBSTATE;
+
+  if (substr == 0 || *substr == '\0')
+    return (0);
+
+  slen = strlen (string);
+  sublen = strlen (substr);
+  for (sindex = 0; c = string[sindex]; )
+    {
+      if (STREQN (string + sindex, substr, sublen))
+       return (1);
+
+      switch (c)
+       {
+       case '\\':
+         sindex++;
+         if (string[sindex])
+           ADVANCE_CHAR (string, slen, sindex);
+         break;
+
+       case '\'':
+         sindex = skip_single_quoted (string, slen, ++sindex);
+         break;
+
+       case '"':
+         sindex = skip_double_quoted (string, slen, ++sindex);
+         break;
+
+       default:
+         ADVANCE_CHAR (string, slen, sindex);
+         break;
+       }
+    }
+  return (0);
+}
+
+/* Most of the substitutions must be done in parallel.  In order
+   to avoid using tons of unclear goto's, I have some functions
+   for manipulating malloc'ed strings.  They all take INDX, a
+   pointer to an integer which is the offset into the string
+   where manipulation is taking place.  They also take SIZE, a
+   pointer to an integer which is the current length of the
+   character array for this string. */
+
+/* Append SOURCE to TARGET at INDEX.  SIZE is the current amount
+   of space allocated to TARGET.  SOURCE can be NULL, in which
+   case nothing happens.  Gets rid of SOURCE by freeing it.
+   Returns TARGET in case the location has changed. */
+INLINE char *
+sub_append_string (source, target, indx, size)
+     char *source, *target;
+     int *indx, *size;
+{
+  if (source)
+    {
+      int srclen, n;
+
+      srclen = STRLEN (source);
+      if (srclen >= (int)(*size - *indx))
+       {
+         n = srclen + *indx;
+         n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE);
+         target = (char *)xrealloc (target, (*size = n));
+       }
+
+      FASTCOPY (source, target + *indx, srclen);
+      *indx += srclen;
+      target[*indx] = '\0';
+
+      free (source);
+    }
+  return (target);
+}
+
+#if 0
+/* UNUSED */
+/* Append the textual representation of NUMBER to TARGET.
+   INDX and SIZE are as in SUB_APPEND_STRING. */
+char *
+sub_append_number (number, target, indx, size)
+     intmax_t number;
+     int *indx, *size;
+     char *target;
+{
+  char *temp;
+
+  temp = itos (number);
+  return (sub_append_string (temp, target, indx, size));
+}
+#endif
+
+/* Extract a substring from STRING, starting at SINDEX and ending with
+   one of the characters in CHARLIST.  Don't make the ending character
+   part of the string.  Leave SINDEX pointing at the ending character.
+   Understand about backslashes in the string.  If (flags & SX_VARNAME)
+   is non-zero, and array variables have been compiled into the shell,
+   everything between a `[' and a corresponding `]' is skipped over.
+   If (flags & SX_NOALLOC) is non-zero, don't return the substring, just
+   update SINDEX.  If (flags & SX_REQMATCH) is non-zero, the string must
+   contain a closing character from CHARLIST. */
+static char *
+string_extract (string, sindex, charlist, flags)
+     char *string;
+     int *sindex;
+     char *charlist;
+     int flags;
+{
+  register int c, i;
+  int found;
+  size_t slen;
+  char *temp;
+  DECLARE_MBSTATE;
+
+  slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0;
+  i = *sindex;
+  found = 0;
+  while (c = string[i])
+    {
+      if (c == '\\')
+       {
+         if (string[i + 1])
+           i++;
+         else
+           break;
+       }
+#if defined (ARRAY_VARS)
+      else if ((flags & SX_VARNAME) && c == '[')
+       {
+         int ni;
+         /* If this is an array subscript, skip over it and continue. */
+         ni = skipsubscript (string, i, 0);
+         if (string[ni] == ']')
+           i = ni;
+       }
+#endif
+      else if (MEMBER (c, charlist))
+       {
+         found = 1;
+         break;
+       }
+
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+  /* If we had to have a matching delimiter and didn't find one, return an
+     error and let the caller deal with it. */
+  if ((flags & SX_REQMATCH) && found == 0)
+    {
+      *sindex = i;
+      return (&extract_string_error);
+    }
+  
+  temp = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i);
+  *sindex = i;
+  
+  return (temp);
+}
+
+/* Extract the contents of STRING as if it is enclosed in double quotes.
+   SINDEX, when passed in, is the offset of the character immediately
+   following the opening double quote; on exit, SINDEX is left pointing after
+   the closing double quote.  If STRIPDQ is non-zero, unquoted double
+   quotes are stripped and the string is terminated by a null byte.
+   Backslashes between the embedded double quotes are processed.  If STRIPDQ
+   is zero, an unquoted `"' terminates the string. */
+static char *
+string_extract_double_quoted (string, sindex, stripdq)
+     char *string;
+     int *sindex, stripdq;
+{
+  size_t slen;
+  char *send;
+  int j, i, t;
+  unsigned char c;
+  char *temp, *ret;            /* The new string we return. */
+  int pass_next, backquote, si;        /* State variables for the machine. */
+  int dquote;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string + *sindex) + *sindex;
+  send = string + slen;
+
+  pass_next = backquote = dquote = 0;
+  temp = (char *)xmalloc (1 + slen - *sindex);
+
+  j = 0;
+  i = *sindex;
+  while (c = string[i])
+    {
+      /* Process a character that was quoted by a backslash. */
+      if (pass_next)
+       {
+         /* XXX - take another look at this in light of Interp 221 */
+         /* Posix.2 sez:
+
+            ``The backslash shall retain its special meaning as an escape
+            character only when followed by one of the characters:
+               $       `       "       \       <newline>''.
+
+            If STRIPDQ is zero, we handle the double quotes here and let
+            expand_word_internal handle the rest.  If STRIPDQ is non-zero,
+            we have already been through one round of backslash stripping,
+            and want to strip these backslashes only if DQUOTE is non-zero,
+            indicating that we are inside an embedded double-quoted string. */
+
+            /* If we are in an embedded quoted string, then don't strip
+               backslashes before characters for which the backslash
+               retains its special meaning, but remove backslashes in
+               front of other characters.  If we are not in an
+               embedded quoted string, don't strip backslashes at all.
+               This mess is necessary because the string was already
+               surrounded by double quotes (and sh has some really weird
+               quoting rules).
+               The returned string will be run through expansion as if
+               it were double-quoted. */
+         if ((stripdq == 0 && c != '"') ||
+             (stripdq && ((dquote && (sh_syntaxtab[c] & CBSDQUOTE)) || dquote == 0)))
+           temp[j++] = '\\';
+         pass_next = 0;
+
+add_one_character:
+         COPY_CHAR_I (temp, j, string, send, i);
+         continue;
+       }
+
+      /* A backslash protects the next character.  The code just above
+        handles preserving the backslash in front of any character but
+        a double quote. */
+      if (c == '\\')
+       {
+         pass_next++;
+         i++;
+         continue;
+       }
+
+      /* Inside backquotes, ``the portion of the quoted string from the
+        initial backquote and the characters up to the next backquote
+        that is not preceded by a backslash, having escape characters
+        removed, defines that command''. */
+      if (backquote)
+       {
+         if (c == '`')
+           backquote = 0;
+         temp[j++] = c;
+         i++;
+         continue;
+       }
+
+      if (c == '`')
+       {
+         temp[j++] = c;
+         backquote++;
+         i++;
+         continue;
+       }
+
+      /* Pass everything between `$(' and the matching `)' or a quoted
+        ${ ... } pair through according to the Posix.2 specification. */
+      if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE)))
+       {
+         int free_ret = 1;
+
+         si = i + 2;
+         if (string[i + 1] == LPAREN)
+           ret = extract_command_subst (string, &si, 0);
+         else
+           ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, 0);
+
+         temp[j++] = '$';
+         temp[j++] = string[i + 1];
+
+         /* Just paranoia; ret will not be 0 unless no_longjmp_on_fatal_error
+            is set. */
+         if (ret == 0 && no_longjmp_on_fatal_error)
+           {
+             free_ret = 0;
+             ret = string + i + 2;
+           }
+
+         for (t = 0; ret[t]; t++, j++)
+           temp[j] = ret[t];
+         temp[j] = string[si];
+
+         if (string[si])
+           {
+             j++;
+             i = si + 1;
+           }
+         else
+           i = si;
+
+         if (free_ret)
+           free (ret);
+         continue;
+       }
+
+      /* Add any character but a double quote to the quoted string we're
+        accumulating. */
+      if (c != '"')
+       goto add_one_character;
+
+      /* c == '"' */
+      if (stripdq)
+       {
+         dquote ^= 1;
+         i++;
+         continue;
+       }
+
+      break;
+    }
+  temp[j] = '\0';
+
+  /* Point to after the closing quote. */
+  if (c)
+    i++;
+  *sindex = i;
+
+  return (temp);
+}
+
+/* This should really be another option to string_extract_double_quoted. */
+static int
+skip_double_quoted (string, slen, sind)
+     char *string;
+     size_t slen;
+     int sind;
+{
+  int c, i;
+  char *ret;
+  int pass_next, backquote, si;
+  DECLARE_MBSTATE;
+
+  pass_next = backquote = 0;
+  i = sind;
+  while (c = string[i])
+    {
+      if (pass_next)
+       {
+         pass_next = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '\\')
+       {
+         pass_next++;
+         i++;
+         continue;
+       }
+      else if (backquote)
+       {
+         if (c == '`')
+           backquote = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '`')
+       {
+         backquote++;
+         i++;
+         continue;
+       }
+      else if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE)))
+       {
+         si = i + 2;
+         if (string[i + 1] == LPAREN)
+           ret = extract_command_subst (string, &si, SX_NOALLOC);
+         else
+           ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, SX_NOALLOC);
+
+         i = si + 1;
+         continue;
+       }
+      else if (c != '"')
+       {
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else
+       break;
+    }
+
+  if (c)
+    i++;
+
+  return (i);
+}
+
+/* Extract the contents of STRING as if it is enclosed in single quotes.
+   SINDEX, when passed in, is the offset of the character immediately
+   following the opening single quote; on exit, SINDEX is left pointing after
+   the closing single quote. */
+static inline char *
+string_extract_single_quoted (string, sindex)
+     char *string;
+     int *sindex;
+{
+  register int i;
+  size_t slen;
+  char *t;
+  DECLARE_MBSTATE;
+
+  /* Don't need slen for ADVANCE_CHAR unless multibyte chars possible. */
+  slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0;
+  i = *sindex;
+  while (string[i] && string[i] != '\'')
+    ADVANCE_CHAR (string, slen, i);
+
+  t = substring (string, *sindex, i);
+
+  if (string[i])
+    i++;
+  *sindex = i;
+
+  return (t);
+}
+
+static inline int
+skip_single_quoted (string, slen, sind)
+     const char *string;
+     size_t slen;
+     int sind;
+{
+  register int c;
+  DECLARE_MBSTATE;
+
+  c = sind;
+  while (string[c] && string[c] != '\'')
+    ADVANCE_CHAR (string, slen, c);
+
+  if (string[c])
+    c++;
+  return c;
+}
+
+/* Just like string_extract, but doesn't hack backslashes or any of
+   that other stuff.  Obeys CTLESC quoting.  Used to do splitting on $IFS. */
+static char *
+string_extract_verbatim (string, slen, sindex, charlist, flags)
+     char *string;
+     size_t slen;
+     int *sindex;
+     char *charlist;
+     int flags;
+{
+  register int i;
+#if defined (HANDLE_MULTIBYTE)
+  size_t clen;
+  wchar_t *wcharlist;
+#endif
+  int c;
+  char *temp;
+  DECLARE_MBSTATE;
+
+  if (charlist[0] == '\'' && charlist[1] == '\0')
+    {
+      temp = string_extract_single_quoted (string, sindex);
+      --*sindex;       /* leave *sindex at separator character */
+      return temp;
+    }
+
+  i = *sindex;
+#if 0
+  /* See how the MBLEN and ADVANCE_CHAR macros work to understand why we need
+     this only if MB_CUR_MAX > 1. */
+  slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 1;
+#endif
+#if defined (HANDLE_MULTIBYTE)
+  clen = strlen (charlist);
+  wcharlist = 0;
+#endif
+  while (c = string[i])
+    {
+#if defined (HANDLE_MULTIBYTE)
+      size_t mblength;
+#endif
+      if ((flags & SX_NOCTLESC) == 0 && c == CTLESC)
+       {
+         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);
+      if (mblength > 1)
+       {
+         wchar_t wc;
+         mblength = mbtowc (&wc, string + i, slen - i);
+         if (MB_INVALIDCH (mblength))
+           {
+             if (MEMBER (c, charlist))
+               break;
+           }
+         else
+           {
+             if (wcharlist == 0)
+               {
+                 size_t len;
+                 len = mbstowcs (wcharlist, charlist, 0);
+                 if (len == -1)
+                   len = 0;
+                 wcharlist = (wchar_t *)xmalloc (sizeof (wchar_t) * (len + 1));
+                 mbstowcs (wcharlist, charlist, len + 1);
+               }
+
+             if (wcschr (wcharlist, wc))
+               break;
+           }
+       }
+      else             
+#endif
+      if (MEMBER (c, charlist))
+       break;
+
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+#if defined (HANDLE_MULTIBYTE)
+  FREE (wcharlist);
+#endif
+
+  temp = substring (string, *sindex, i);
+  *sindex = i;
+
+  return (temp);
+}
+
+/* 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 ")". )
+   XFLAGS is additional flags to pass to other extraction functions. */
+char *
+extract_command_subst (string, sindex, xflags)
+     char *string;
+     int *sindex;
+     int xflags;
+{
+  if (string[*sindex] == LPAREN)
+    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. (])
+   Start extracting at (SINDEX) as if we had just seen "$[".
+   Make (SINDEX) get the position of the matching "]". */
+char *
+extract_arithmetic_subst (string, sindex)
+     char *string;
+     int *sindex;
+{
+  return (extract_delimited_string (string, sindex, "$[", "[", "]", 0)); /*]*/
+}
+
+#if defined (PROCESS_SUBSTITUTION)
+/* Extract the <( or >( construct in STRING, and return a new string.
+   Start extracting at (SINDEX) as if we had just seen "<(".
+   Make (SINDEX) get the position of the matching ")". */ /*))*/
+char *
+extract_process_subst (string, starter, sindex)
+     char *string;
+     char *starter;
+     int *sindex;
+{
+  return (extract_delimited_string (string, sindex, starter, "(", ")", SX_COMMAND));
+}
+#endif /* PROCESS_SUBSTITUTION */
+
+#if defined (ARRAY_VARS)
+/* This can be fooled by unquoted right parens in the passed string. If
+   each caller verifies that the last character in STRING is a right paren,
+   we don't even need to call extract_delimited_string. */
+char *
+extract_array_assignment_list (string, sindex)
+     char *string;
+     int *sindex;
+{
+  int slen;
+  char *ret;
+
+  slen = strlen (string);      /* ( */
+  if (string[slen - 1] == ')')
+   {
+      ret = substring (string, *sindex, slen - 1);
+      *sindex = slen - 1;
+      return ret;
+    }
+  return 0;  
+}
+#endif
+
+/* Extract and create a new string from the contents of STRING, a
+   character string delimited with OPENER and CLOSER.  SINDEX is
+   the address of an int describing the current offset in STRING;
+   it should point to just after the first OPENER found.  On exit,
+   SINDEX gets the position of the last character of the matching CLOSER.
+   If OPENER is more than a single character, ALT_OPENER, if non-null,
+   contains a character string that can also match CLOSER and thus
+   needs to be skipped. */
+static char *
+extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
+     char *string;
+     int *sindex;
+     char *opener, *alt_opener, *closer;
+     int flags;
+{
+  int i, c, si;
+  size_t slen;
+  char *t, *result;
+  int pass_character, nesting_level, in_comment;
+  int len_closer, len_opener, len_alt_opener;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string + *sindex) + *sindex;
+  len_opener = STRLEN (opener);
+  len_alt_opener = STRLEN (alt_opener);
+  len_closer = STRLEN (closer);
+
+  pass_character = in_comment = 0;
+
+  nesting_level = 1;
+  i = *sindex;
+
+  while (nesting_level)
+    {
+      c = string[i];
+
+      if (c == 0)
+       break;
+
+      if (in_comment)
+       {
+         if (c == '\n')
+           in_comment = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+
+      if (pass_character)      /* previous char was backslash */
+       {
+         pass_character = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+
+      /* Not exactly right yet; should handle shell metacharacters and
+        multibyte characters, too.  See COMMENT_BEGIN define in parse.y */
+      if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
+       {
+          in_comment = 1;
+          ADVANCE_CHAR (string, slen, i);
+          continue;
+       }
+        
+      if (c == CTLESC || c == '\\')
+       {
+         pass_character++;
+         i++;
+         continue;
+       }
+
+      /* Process a nested command substitution, but only if we're parsing an
+        arithmetic substitution. */
+      if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LPAREN)
+        {
+          si = i + 2;
+          t = extract_command_subst (string, &si, flags|SX_NOALLOC);
+          i = si + 1;
+          continue;
+        }
+
+      /* Process a nested OPENER. */
+      if (STREQN (string + i, opener, len_opener))
+       {
+         si = i + len_opener;
+         t = extract_delimited_string (string, &si, opener, alt_opener, closer, flags|SX_NOALLOC);
+         i = si + 1;
+         continue;
+       }
+
+      /* Process a nested ALT_OPENER */
+      if (len_alt_opener && STREQN (string + i, alt_opener, len_alt_opener))
+       {
+         si = i + len_alt_opener;
+         t = extract_delimited_string (string, &si, alt_opener, alt_opener, closer, flags|SX_NOALLOC);
+         i = si + 1;
+         continue;
+       }
+
+      /* If the current substring terminates the delimited string, decrement
+        the nesting level. */
+      if (STREQN (string + i, closer, len_closer))
+       {
+         i += len_closer - 1;  /* move to last byte of the closer */
+         nesting_level--;
+         if (nesting_level == 0)
+           break;
+       }
+
+      /* Pass old-style command substitution through verbatim. */
+      if (c == '`')
+       {
+         si = i + 1;
+         t = string_extract (string, &si, "`", flags|SX_NOALLOC);
+         i = si + 1;
+         continue;
+       }
+
+      /* Pass single-quoted and double-quoted strings through verbatim. */
+      if (c == '\'' || c == '"')
+       {
+         si = i + 1;
+         i = (c == '\'') ? skip_single_quoted (string, slen, si)
+                         : skip_double_quoted (string, slen, si);
+         continue;
+       }
+
+      /* move past this character, which was not special. */
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+  if (c == 0 && nesting_level)
+    {
+      if (no_longjmp_on_fatal_error == 0)
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("bad substitution: no closing `%s' in %s"), closer, string);
+         exp_jump_to_top_level (DISCARD);
+       }
+      else
+       {
+         *sindex = i;
+         return (char *)NULL;
+       }
+    }
+
+  si = i - *sindex - len_closer + 1;
+  if (flags & SX_NOALLOC)
+    result = (char *)NULL;
+  else    
+    {
+      result = (char *)xmalloc (1 + si);
+      strncpy (result, string + *sindex, si);
+      result[si] = '\0';
+    }
+  *sindex = i;
+
+  return (result);
+}
+
+/* Extract a parameter expansion expression within ${ and } from STRING.
+   Obey the Posix.2 rules for finding the ending `}': count braces while
+   skipping over enclosed quoted strings and command substitutions.
+   SINDEX is the address of an int describing the current offset in STRING;
+   it should point to just after the first `{' found.  On exit, SINDEX
+   gets the position of the matching `}'.  QUOTED is non-zero if this
+   occurs inside double quotes. */
+/* XXX -- this is very similar to extract_delimited_string -- XXX */
+static char *
+extract_dollar_brace_string (string, sindex, quoted, flags)
+     char *string;
+     int *sindex, quoted, flags;
+{
+  register int i, c;
+  size_t slen;
+  int pass_character, nesting_level, si, dolbrace_state;
+  char *result, *t;
+  DECLARE_MBSTATE;
+
+  pass_character = 0;
+  nesting_level = 1;
+  slen = strlen (string + *sindex) + *sindex;
+
+  /* The handling of dolbrace_state needs to agree with the code in parse.y:
+     parse_matched_pair().  The different initial value is to handle the
+     case where this function is called to parse the word in
+     ${param op word} (SX_WORD). */
+  dolbrace_state = (flags & SX_WORD) ? DOLBRACE_WORD : DOLBRACE_PARAM;
+  if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && (flags & SX_POSIXEXP))
+    dolbrace_state = DOLBRACE_QUOTE;
+
+  i = *sindex;
+  while (c = string[i])
+    {
+      if (pass_character)
+       {
+         pass_character = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+
+      /* CTLESCs and backslashes quote the next character. */
+      if (c == CTLESC || c == '\\')
+       {
+         pass_character++;
+         i++;
+         continue;
+       }
+
+      if (string[i] == '$' && string[i+1] == LBRACE)
+       {
+         nesting_level++;
+         i += 2;
+         continue;
+       }
+
+      if (c == RBRACE)
+       {
+         nesting_level--;
+         if (nesting_level == 0)
+           break;
+         i++;
+         continue;
+       }
+
+      /* Pass the contents of old-style command substitutions through
+        verbatim. */
+      if (c == '`')
+       {
+         si = i + 1;
+         t = string_extract (string, &si, "`", flags|SX_NOALLOC);
+         i = si + 1;
+         continue;
+       }
+
+      /* Pass the contents of new-style command substitutions and
+        arithmetic substitutions through verbatim. */
+      if (string[i] == '$' && string[i+1] == LPAREN)
+       {
+         si = i + 2;
+         t = extract_command_subst (string, &si, flags|SX_NOALLOC);
+         i = si + 1;
+         continue;
+       }
+
+      /* Pass the contents of double-quoted strings through verbatim. */
+      if (c == '"')
+       {
+         si = i + 1;
+         i = skip_double_quoted (string, slen, si);
+         /* skip_XXX_quoted leaves index one past close quote */
+         continue;
+       }
+
+      if (c == '\'')
+       {
+/*itrace("extract_dollar_brace_string: c == single quote flags = %d quoted = %d dolbrace_state = %d", flags, quoted, dolbrace_state);*/
+         if (posixly_correct && shell_compatibility_level > 42 && dolbrace_state != DOLBRACE_QUOTE && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+           ADVANCE_CHAR (string, slen, i);
+         else
+           {
+             si = i + 1;
+             i = skip_single_quoted (string, slen, si);
+           }
+
+          continue;
+       }
+
+      /* move past this character, which was not special. */
+      ADVANCE_CHAR (string, slen, i);
+
+      /* This logic must agree with parse.y:parse_matched_pair, since they
+        share the same defines. */
+      if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 1)
+       dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '#' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '/' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE2;      /* XXX */
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '^' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == ',' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", c) != 0)
+       dolbrace_state = DOLBRACE_OP;
+      else if (dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", c) == 0)
+       dolbrace_state = DOLBRACE_WORD;
+    }
+
+  if (c == 0 && nesting_level)
+    {
+      if (no_longjmp_on_fatal_error == 0)
+       {                       /* { */
+         last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("bad substitution: no closing `%s' in %s"), "}", string);
+         exp_jump_to_top_level (DISCARD);
+       }
+      else
+       {
+         *sindex = i;
+         return ((char *)NULL);
+       }
+    }
+
+  result = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i);
+  *sindex = i;
+
+  return (result);
+}
+
+/* Remove backslashes which are quoting backquotes from STRING.  Modifies
+   STRING, and returns a pointer to it. */
+char *
+de_backslash (string)
+     char *string;
+{
+  register size_t slen;
+  register int i, j, prev_i;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+  i = j = 0;
+
+  /* Loop copying string[i] to string[j], i >= j. */
+  while (i < slen)
+    {
+      if (string[i] == '\\' && (string[i + 1] == '`' || string[i + 1] == '\\' ||
+                             string[i + 1] == '$'))
+       i++;
+      prev_i = i;
+      ADVANCE_CHAR (string, slen, i);
+      if (j < prev_i)
+       do string[j++] = string[prev_i++]; while (prev_i < i);
+      else
+       j = i;
+    }
+  string[j] = '\0';
+
+  return (string);
+}
+
+#if 0
+/*UNUSED*/
+/* Replace instances of \! in a string with !. */
+void
+unquote_bang (string)
+     char *string;
+{
+  register int i, j;
+  register char *temp;
+
+  temp = (char *)xmalloc (1 + strlen (string));
+
+  for (i = 0, j = 0; (temp[j] = string[i]); i++, j++)
+    {
+      if (string[i] == '\\' && string[i + 1] == '!')
+       {
+         temp[j] = '!';
+         i++;
+       }
+    }
+  strcpy (string, temp);
+  free (temp);
+}
+#endif
+
+#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
+
+/* This function assumes s[i] == open; returns with s[ret] == close; used to
+   parse array subscripts.  FLAGS & 1 means to not attempt to skip over
+   matched pairs of quotes or backquotes, or skip word expansions; it is
+   intended to be used after expansion has been performed and during final
+   assignment parsing (see arrayfunc.c:assign_compound_array_list()). */
+static int
+skip_matched_pair (string, start, open, close, flags)
+     const char *string;
+     int start, open, close, flags;
+{
+  int i, pass_next, backq, si, c, count;
+  size_t slen;
+  char *temp, *ss;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string + start) + start;
+  no_longjmp_on_fatal_error = 1;
+
+  i = start + 1;               /* skip over leading bracket */
+  count = 1;
+  pass_next = backq = 0;
+  ss = (char *)string;
+  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 ((flags & 1) == 0 && c == '`')
+       {
+         backq = 1;
+         i++;
+         continue;
+       }
+      else if ((flags & 1) == 0 && c == open)
+       {
+         count++;
+         i++;
+         continue;
+       }
+      else if (c == close)
+       {
+         count--;
+         if (count == 0)
+           break;
+         i++;
+         continue;
+       }
+      else if ((flags & 1) == 0 && (c == '\'' || c == '"'))
+       {
+         i = (c == '\'') ? skip_single_quoted (ss, slen, ++i)
+                         : skip_double_quoted (ss, slen, ++i);
+         /* no increment, the skip functions increment past the closing quote. */
+       }
+      else if ((flags&1) == 0 && 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 (ss, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+         else
+           temp = extract_dollar_brace_string (ss, &si, 0, SX_NOALLOC);
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
+      else
+       ADVANCE_CHAR (string, slen, i);
+    }
+
+  CQ_RETURN(i);
+}
+
+#if defined (ARRAY_VARS)
+int
+skipsubscript (string, start, flags)
+     const char *string;
+     int start, flags;
+{
+  return (skip_matched_pair (string, start, '[', ']', flags));
+}
+#endif
+
+/* 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, invert, skipquote, skipcmd;
+  size_t slen;
+  char *temp, open[3];
+  DECLARE_MBSTATE;
+
+  slen = strlen (string + start) + start;
+  if (flags & SD_NOJMP)
+    no_longjmp_on_fatal_error = 1;
+  invert = (flags & SD_INVERT);
+  skipcmd = (flags & SD_NOSKIPCMD) == 0;
+
+  i = start;
+  pass_next = backq = 0;
+  while (c = string[i])
+    {
+      /* If this is non-zero, we should not let quote characters be delimiters
+        and the current character is a single or double quote.  We should not
+        test whether or not it's a delimiter until after we skip single- or
+        double-quoted strings. */
+      skipquote = ((flags & SD_NOQUOTEDELIM) && (c == '\'' || c =='"'));
+      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 (skipquote == 0 && invert == 0 && member (c, delims))
+       break;
+      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 == '$' && ((skipcmd && 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;
+       }
+#if defined (PROCESS_SUBSTITUTION)
+      else if (skipcmd && (c == '<' || c == '>') && string[i+1] == LPAREN)
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+         temp = extract_process_subst (string, (c == '<') ? "<(" : ">(", &si);
+         free (temp);          /* no SX_ALLOC here */
+         i = si;
+         if (string[i] == '\0')
+           break;
+         i++;
+         continue;
+       }
+#endif /* PROCESS_SUBSTITUTION */
+#if defined (EXTENDED_GLOB)
+      else if ((flags & SD_EXTGLOB) && extended_glob && string[i+1] == LPAREN && member (c, "?*+!@"))
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         open[0] = c;
+         open[1] = LPAREN;
+         open[2] = '\0';
+         temp = extract_delimited_string (string, &si, open, "(", ")", SX_NOALLOC); /* ) */
+
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
+#endif
+      else if ((skipquote || invert) && (member (c, delims) == 0))
+       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
+   by a backslash. NO_LONGJMP_ON_FATAL_ERROR is used to flag that the various
+   single and double-quoted string parsing functions should not return an
+   error if there are unclosed quotes or braces.  The characters that this
+   recognizes need to be the same as the contents of
+   rl_completer_quote_characters. */
+
+int
+char_is_quoted (string, eindex)
+     char *string;
+     int eindex;
+{
+  int i, pass_next, c;
+  size_t slen;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+  no_longjmp_on_fatal_error = 1;
+  i = pass_next = 0;
+  while (i <= eindex)
+    {
+      c = string[i];
+
+      if (pass_next)
+       {
+         pass_next = 0;
+         if (i >= eindex)      /* XXX was if (i >= eindex - 1) */
+           CQ_RETURN(1);
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '\\')
+       {
+         pass_next = 1;
+         i++;
+         continue;
+       }
+      else if (c == '\'' || c == '"')
+       {
+         i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
+                         : skip_double_quoted (string, slen, ++i);
+         if (i > eindex)
+           CQ_RETURN(1);
+         /* no increment, the skip_xxx functions go one past end */
+       }
+      else
+       ADVANCE_CHAR (string, slen, i);
+    }
+
+  CQ_RETURN(0);
+}
+
+int
+unclosed_pair (string, eindex, openstr)
+     char *string;
+     int eindex;
+     char *openstr;
+{
+  int i, pass_next, openc, olen;
+  size_t slen;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+  olen = strlen (openstr);
+  i = pass_next = openc = 0;
+  while (i <= eindex)
+    {
+      if (pass_next)
+       {
+         pass_next = 0;
+         if (i >= eindex)      /* XXX was if (i >= eindex - 1) */
+           return 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (string[i] == '\\')
+       {
+         pass_next = 1;
+         i++;
+         continue;
+       }
+      else if (STREQN (string + i, openstr, olen))
+       {
+         openc = 1 - openc;
+         i += olen;
+       }
+      else if (string[i] == '\'' || string[i] == '"')
+       {
+         i = (string[i] == '\'') ? skip_single_quoted (string, slen, i)
+                                 : skip_double_quoted (string, slen, i);
+         if (i > eindex)
+           return 0;
+       }
+      else
+       ADVANCE_CHAR (string, slen, i);
+    }
+  return (openc);
+}
+
+/* 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
+   rules.  SENTINEL is an index to look for.  NWP, if non-NULL,
+   gets the number of words in the returned list.  CWP, if non-NULL, gets
+   the index of the word containing SENTINEL.  Non-whitespace chars in
+   DELIMS delimit separate fields. */
+WORD_LIST *
+split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp)
+     char *string;
+     int slen;
+     char *delims;
+     int sentinel, flags;
+     int *nwp, *cwp;
+{
+  int ts, te, i, nw, cw, ifs_split, dflags;
+  char *token, *d, *d2;
+  WORD_LIST *ret, *tl;
+
+  if (string == 0 || *string == '\0')
+    {
+      if (nwp)
+       *nwp = 0;
+      if (cwp)
+       *cwp = 0;       
+      return ((WORD_LIST *)NULL);
+    }
+
+  d = (delims == 0) ? ifs_value : delims;
+  ifs_split = delims == 0;
+
+  /* Make d2 the non-whitespace characters in delims */
+  d2 = 0;
+  if (delims)
+    {
+      size_t slength;
+#if defined (HANDLE_MULTIBYTE)
+      size_t mblength = 1;
+#endif
+      DECLARE_MBSTATE;
+
+      slength = strlen (delims);
+      d2 = (char *)xmalloc (slength + 1);
+      i = ts = 0;
+      while (delims[i])
+       {
+#if defined (HANDLE_MULTIBYTE)
+         mbstate_t state_bak;
+         state_bak = state;
+         mblength = MBRLEN (delims + i, slength, &state);
+         if (MB_INVALIDCH (mblength))
+           state = state_bak;
+         else if (mblength > 1)
+           {
+             memcpy (d2 + ts, delims + i, mblength);
+             ts += mblength;
+             i += mblength;
+             slength -= mblength;
+             continue;
+           }
+#endif
+         if (whitespace (delims[i]) == 0)
+           d2[ts++] = delims[i];
+
+         i++;
+         slength--;
+       }
+      d2[ts] = '\0';
+    }
+
+  ret = (WORD_LIST *)NULL;
+
+  /* Remove sequences of whitespace characters at the start of the string, as
+     long as those characters are delimiters. */
+  for (i = 0; member (string[i], d) && spctabnl (string[i]); i++)
+    ;
+  if (string[i] == '\0')
+    return (ret);
+
+  ts = i;
+  nw = 0;
+  cw = -1;
+  dflags = flags|SD_NOJMP;
+  while (1)
+    {
+      te = skip_to_delim (string, ts, d, dflags);
+
+      /* If we have a non-whitespace delimiter character, use it to make a
+        separate field.  This is just about what $IFS splitting does and
+        is closer to the behavior of the shell parser. */
+      if (ts == te && d2 && member (string[ts], d2))
+       {
+         te = ts + 1;
+         /* If we're using IFS splitting, the non-whitespace delimiter char
+            and any additional IFS whitespace delimits a field. */
+         if (ifs_split)
+           while (member (string[te], d) && spctabnl (string[te]))
+             te++;
+         else
+           while (member (string[te], d2))
+             te++;
+       }
+
+      token = substring (string, ts, te);
+
+      ret = add_string_to_list (token, ret);
+      free (token);
+      nw++;
+
+      if (sentinel >= ts && sentinel <= te)
+       cw = nw;
+
+      /* If the cursor is at whitespace just before word start, set the
+        sentinel word to the current word. */
+      if (cwp && cw == -1 && sentinel == ts-1)
+       cw = nw;
+
+      /* If the cursor is at whitespace between two words, make a new, empty
+        word, add it before (well, after, since the list is in reverse order)
+        the word we just added, and set the current word to that one. */
+      if (cwp && cw == -1 && sentinel < ts)
+       {
+         tl = make_word_list (make_word (""), ret->next);
+         ret->next = tl;
+         cw = nw;
+         nw++;
+       }
+
+      if (string[te] == 0)
+       break;
+
+      i = te;
+      while (member (string[i], d) && (ifs_split || spctabnl(string[i])))
+       i++;
+
+      if (string[i])
+       ts = i;
+      else
+       break;
+    }
+
+  /* Special case for SENTINEL at the end of STRING.  If we haven't found
+     the word containing SENTINEL yet, and the index we're looking for is at
+     the end of STRING (or past the end of the previously-found token,
+     possible if the end of the line is composed solely of IFS whitespace)
+     add an additional null argument and set the current word pointer to that. */
+  if (cwp && cw == -1 && (sentinel >= slen || sentinel >= te))
+    {
+      if (whitespace (string[sentinel - 1]))
+       {
+         token = "";
+         ret = add_string_to_list (token, ret);
+         nw++;
+       }
+      cw = nw;
+    }
+
+  if (nwp)
+    *nwp = nw;
+  if (cwp)
+    *cwp = cw;
+
+  FREE (d2);
+
+  return (REVERSE_LIST (ret, WORD_LIST *));
+}
+#endif /* READLINE */
+
+#if 0
+/* UNUSED */
+/* Extract the name of the variable to bind to from the assignment string. */
+char *
+assignment_name (string)
+     char *string;
+{
+  int offset;
+  char *temp;
+
+  offset = assignment (string, 0);
+  if (offset == 0)
+    return (char *)NULL;
+  temp = substring (string, 0, offset);
+  return (temp);
+}
+#endif
+
+/* **************************************************************** */
+/*                                                                 */
+/*     Functions to convert strings to WORD_LISTs and vice versa    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Return a single string of all the words in LIST.  SEP is the separator
+   to put between individual elements of LIST in the output string. */
+char *
+string_list_internal (list, sep)
+     WORD_LIST *list;
+     char *sep;
+{
+  register WORD_LIST *t;
+  char *result, *r;
+  int word_len, sep_len, result_size;
+
+  if (list == 0)
+    return ((char *)NULL);
+
+  /* Short-circuit quickly if we don't need to separate anything. */
+  if (list->next == 0)
+    return (savestring (list->word->word));
+
+  /* This is nearly always called with either sep[0] == 0 or sep[1] == 0. */
+  sep_len = STRLEN (sep);
+  result_size = 0;
+
+  for (t = list; t; t = t->next)
+    {
+      if (t != list)
+       result_size += sep_len;
+      result_size += strlen (t->word->word);
+    }
+
+  r = result = (char *)xmalloc (result_size + 1);
+
+  for (t = list; t; t = t->next)
+    {
+      if (t != list && sep_len)
+       {
+         if (sep_len > 1)
+           {
+             FASTCOPY (sep, r, sep_len);
+             r += sep_len;
+           }
+         else
+           *r++ = sep[0];
+       }
+
+      word_len = strlen (t->word->word);
+      FASTCOPY (t->word->word, r, word_len);
+      r += word_len;
+    }
+
+  *r = '\0';
+  return (result);
+}
+
+/* Return a single string of all the words present in LIST, separating
+   each word with a space. */
+char *
+string_list (list)
+     WORD_LIST *list;
+{
+  return (string_list_internal (list, " "));
+}
+
+/* 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
+   to a single field with the value of each parameter separated by the
+   first character of the IFS variable, or by a <space> if IFS is unset." */
+char *
+string_list_dollar_star (list)
+     WORD_LIST *list;
+{
+  char *ret;
+#if defined (HANDLE_MULTIBYTE)
+#  if defined (__GNUC__)
+  char sep[MB_CUR_MAX + 1];
+#  else
+  char *sep = 0;
+#  endif
+#else
+  char sep[2];
+#endif
+
+#if defined (HANDLE_MULTIBYTE)
+#  if !defined (__GNUC__)
+  sep = (char *)xmalloc (MB_CUR_MAX + 1);
+#  endif /* !__GNUC__ */
+  if (ifs_firstc_len == 1)
+    {
+      sep[0] = ifs_firstc[0];
+      sep[1] = '\0';
+    }
+  else
+    {
+      memcpy (sep, ifs_firstc, ifs_firstc_len);
+      sep[ifs_firstc_len] = '\0';
+    }
+#else
+  sep[0] = ifs_firstc;
+  sep[1] = '\0';
+#endif
+
+  ret = string_list_internal (list, sep);
+#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__)
+  free (sep);
+#endif
+  return ret;
+}
+
+/* Turn $@ into a string.  If (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+   is non-zero, the $@ appears within double quotes, and we should quote
+   the list before converting it into a string.  If IFS is unset, and the
+   word is not quoted, we just need to quote CTLESC and CTLNUL characters
+   in the words in the list, because the default value of $IFS is
+   <space><tab><newline>, IFS characters in the words in the list should
+   also be split.  If IFS is null, and the word is not quoted, we need
+   to quote the words in the list to preserve the positional parameters
+   exactly. */
+char *
+string_list_dollar_at (list, quoted)
+     WORD_LIST *list;
+     int quoted;
+{
+  char *ifs, *ret;
+#if defined (HANDLE_MULTIBYTE)
+#  if defined (__GNUC__)
+  char sep[MB_CUR_MAX + 1];
+#  else
+  char *sep = 0;
+#  endif /* !__GNUC__ */
+#else
+  char sep[2];
+#endif
+  WORD_LIST *tlist;
+
+  /* XXX this could just be ifs = ifs_value; */
+  ifs = ifs_var ? value_cell (ifs_var) : (char *)0;
+
+#if defined (HANDLE_MULTIBYTE)
+#  if !defined (__GNUC__)
+  sep = (char *)xmalloc (MB_CUR_MAX + 1);
+#  endif /* !__GNUC__ */
+  if (ifs && *ifs)
+    {
+      if (ifs_firstc_len == 1)
+       {
+         sep[0] = ifs_firstc[0];
+         sep[1] = '\0';
+       }
+      else
+       {
+         memcpy (sep, ifs_firstc, ifs_firstc_len);
+         sep[ifs_firstc_len] = '\0';
+       }
+    }
+  else
+    {
+      sep[0] = ' ';
+      sep[1] = '\0';
+    }
+#else
+  sep[0] = (ifs == 0 || *ifs == 0) ? ' ' : *ifs;
+  sep[1] = '\0';
+#endif
+
+  /* XXX -- why call quote_list if ifs == 0?  we can get away without doing
+     it now that quote_escapes quotes spaces */
+  tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
+               ? quote_list (list)
+               : list_quote_escapes (list);
+
+  ret = string_list_internal (tlist, sep);
+#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__)
+  free (sep);
+#endif
+  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
+   the quoted flag is left as make_word () deemed fit.
+
+   This obeys the P1003.2 word splitting semantics.  If `separators' is
+   exactly <space><tab><newline>, then the splitting algorithm is that of
+   the Bourne shell, which treats any sequence of characters from `separators'
+   as a delimiter.  If IFS is unset, which results in `separators' being set
+   to "", no splitting occurs.  If separators has some other value, the
+   following rules are applied (`IFS white space' means zero or more
+   occurrences of <space>, <tab>, or <newline>, as long as those characters
+   are in `separators'):
+
+       1) IFS white space is ignored at the start and the end of the
+          string.
+       2) Each occurrence of a character in `separators' that is not
+          IFS white space, along with any adjacent occurrences of
+          IFS white space delimits a field.
+       3) Any nonzero-length sequence of IFS white space delimits a field.
+   */
+
+/* BEWARE!  list_string strips null arguments.  Don't call it twice and
+   expect to have "" preserved! */
+
+/* This performs word splitting and quoted null character removal on
+   STRING. */
+#define issep(c) \
+       (((separators)[0]) ? ((separators)[1] ? isifs(c) \
+                                             : (c) == (separators)[0]) \
+                          : 0)
+
+WORD_LIST *
+list_string (string, separators, quoted)
+     register char *string, *separators;
+     int quoted;
+{
+  WORD_LIST *result;
+  WORD_DESC *t;
+  char *current_word, *s;
+  int sindex, sh_style_split, whitesep, xflags;
+  size_t slen;
+
+  if (!string || !*string)
+    return ((WORD_LIST *)NULL);
+
+  sh_style_split = separators && separators[0] == ' ' &&
+                                separators[1] == '\t' &&
+                                separators[2] == '\n' &&
+                                separators[3] == '\0';
+  for (xflags = 0, s = ifs_value; s && *s; s++)
+    {
+      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
+     long as those characters appear in IFS.  Do not do this if
+     STRING is quoted or if there are no separator characters. */
+  if (!quoted || !separators || !*separators)
+    {
+      for (s = string; *s && spctabnl (*s) && issep (*s); s++);
+
+      if (!*s)
+       return ((WORD_LIST *)NULL);
+
+      string = s;
+    }
+
+  /* OK, now STRING points to a word that does not begin with white space.
+     The splitting algorithm is:
+       extract a word, stopping at a separator
+       skip sequences of spc, tab, or nl as long as they are separators
+     This obeys the field splitting rules in Posix.2. */
+  slen = (MB_CUR_MAX > 1) ? strlen (string) : 1;
+  for (result = (WORD_LIST *)NULL, sindex = 0; string[sindex]; )
+    {
+      /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim
+        unless multibyte chars are possible. */
+      current_word = string_extract_verbatim (string, slen, &sindex, separators, xflags);
+      if (current_word == 0)
+       break;
+
+      /* If we have a quoted empty string, add a quoted null argument.  We
+        want to preserve the quoted null character iff this is a quoted
+        empty string; otherwise the quoted null characters are removed
+        below. */
+      if (QUOTED_NULL (current_word))
+       {
+         t = alloc_word_desc ();
+         t->word = make_quoted_char ('\0');
+         t->flags |= W_QUOTED|W_HASQUOTEDNULL;
+         result = make_word_list (t, result);
+       }
+      else if (current_word[0] != '\0')
+       {
+         /* If we have something, then add it regardless.  However,
+            perform quoted null character removal on the current word. */
+         remove_quoted_nulls (current_word);
+         result = add_string_to_list (current_word, result);
+         result->word->flags &= ~W_HASQUOTEDNULL;      /* just to be sure */
+         if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+           result->word->flags |= W_QUOTED;
+       }
+
+      /* If we're not doing sequences of separators in the traditional
+        Bourne shell style, then add a quoted null argument. */
+      else if (!sh_style_split && !spctabnl (string[sindex]))
+       {
+         t = alloc_word_desc ();
+         t->word = make_quoted_char ('\0');
+         t->flags |= W_QUOTED|W_HASQUOTEDNULL;
+         result = make_word_list (t, result);
+       }
+
+      free (current_word);
+
+      /* Note whether or not the separator is IFS whitespace, used later. */
+      whitesep = string[sindex] && spctabnl (string[sindex]);
+
+      /* Move past the current separator character. */
+      if (string[sindex])
+       {
+         DECLARE_MBSTATE;
+         ADVANCE_CHAR (string, slen, sindex);
+       }
+
+      /* Now skip sequences of space, tab, or newline characters if they are
+        in the list of separators. */
+      while (string[sindex] && spctabnl (string[sindex]) && issep (string[sindex]))
+       sindex++;
+
+      /* If the first separator was IFS whitespace and the current character
+        is a non-whitespace IFS character, it should be part of the current
+        field delimiter, not a separate delimiter that would result in an
+        empty field.  Look at POSIX.2, 3.6.5, (3)(b). */
+      if (string[sindex] && whitesep && issep (string[sindex]) && !spctabnl (string[sindex]))
+       {
+         sindex++;
+         /* An IFS character that is not IFS white space, along with any
+            adjacent IFS white space, shall delimit a field. (SUSv3) */
+         while (string[sindex] && spctabnl (string[sindex]) && isifs (string[sindex]))
+           sindex++;
+       }
+    }
+  return (REVERSE_LIST (result, WORD_LIST *));
+}
+
+/* Parse a single word from STRING, using SEPARATORS to separate fields.
+   ENDPTR is set to the first character after the word.  This is used by
+   the `read' builtin.  This is never called with SEPARATORS != $IFS;
+   it should be simplified.
+
+   XXX - this function is very similar to list_string; they should be
+        combined - XXX */
+char *
+get_word_from_string (stringp, separators, endptr)
+     char **stringp, *separators, **endptr;
+{
+  register char *s;
+  char *current_word;
+  int sindex, sh_style_split, whitesep, xflags;
+  size_t slen;
+
+  if (!stringp || !*stringp || !**stringp)
+    return ((char *)NULL);
+
+  sh_style_split = separators && separators[0] == ' ' &&
+                                separators[1] == '\t' &&
+                                separators[2] == '\n' &&
+                                separators[3] == '\0';
+  for (xflags = 0, s = ifs_value; s && *s; s++)
+    {
+      if (*s == CTLESC) xflags |= SX_NOCTLESC;
+      if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+    }
+
+  s = *stringp;
+  slen = 0;
+
+  /* Remove sequences of whitespace at the beginning of STRING, as
+     long as those characters appear in IFS. */
+  if (sh_style_split || !separators || !*separators)
+    {
+      for (; *s && spctabnl (*s) && isifs (*s); s++);
+
+      /* If the string is nothing but whitespace, update it and return. */
+      if (!*s)
+       {
+         *stringp = s;
+         if (endptr)
+           *endptr = s;
+         return ((char *)NULL);
+       }
+    }
+
+  /* OK, S points to a word that does not begin with white space.
+     Now extract a word, stopping at a separator, save a pointer to
+     the first character after the word, then skip sequences of spc,
+     tab, or nl as long as they are separators.
+
+     This obeys the field splitting rules in Posix.2. */
+  sindex = 0;
+  /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim
+     unless multibyte chars are possible. */
+  slen = (MB_CUR_MAX > 1) ? strlen (s) : 1;
+  current_word = string_extract_verbatim (s, slen, &sindex, separators, xflags);
+
+  /* Set ENDPTR to the first character after the end of the word. */
+  if (endptr)
+    *endptr = s + sindex;
+
+  /* Note whether or not the separator is IFS whitespace, used later. */
+  whitesep = s[sindex] && spctabnl (s[sindex]);
+
+  /* Move past the current separator character. */
+  if (s[sindex])
+    {
+      DECLARE_MBSTATE;
+      ADVANCE_CHAR (s, slen, sindex);
+    }
+
+  /* Now skip sequences of space, tab, or newline characters if they are
+     in the list of separators. */
+  while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex]))
+    sindex++;
+
+  /* If the first separator was IFS whitespace and the current character is
+     a non-whitespace IFS character, it should be part of the current field
+     delimiter, not a separate delimiter that would result in an empty field.
+     Look at POSIX.2, 3.6.5, (3)(b). */
+  if (s[sindex] && whitesep && isifs (s[sindex]) && !spctabnl (s[sindex]))
+    {
+      sindex++;
+      /* An IFS character that is not IFS white space, along with any adjacent
+        IFS white space, shall delimit a field. */
+      while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex]))
+       sindex++;
+    }
+
+  /* Update STRING to point to the next field. */
+  *stringp = s + sindex;
+  return (current_word);
+}
+
+/* Remove IFS white space at the end of STRING.  Start at the end
+   of the string and walk backwards until the beginning of the string
+   or we find a character that's not IFS white space and not CTLESC.
+   Only let CTLESC escape a white space character if SAW_ESCAPE is
+   non-zero.  */
+char *
+strip_trailing_ifs_whitespace (string, separators, saw_escape)
+     char *string, *separators;
+     int saw_escape;
+{
+  char *s;
+
+  s = string + STRLEN (string) - 1;
+  while (s > string && ((spctabnl (*s) && isifs (*s)) ||
+                       (saw_escape && *s == CTLESC && spctabnl (s[1]))))
+    s--;
+  *++s = '\0';
+  return string;
+}
+
+#if 0
+/* UNUSED */
+/* Split STRING into words at whitespace.  Obeys shell-style quoting with
+   backslashes, single and double quotes. */
+WORD_LIST *
+list_string_with_quotes (string)
+     char *string;
+{
+  WORD_LIST *list;
+  char *token, *s;
+  size_t s_len;
+  int c, i, tokstart, len;
+
+  for (s = string; s && *s && spctabnl (*s); s++)
+    ;
+  if (s == 0 || *s == 0)
+    return ((WORD_LIST *)NULL);
+
+  s_len = strlen (s);
+  tokstart = i = 0;
+  list = (WORD_LIST *)NULL;
+  while (1)
+    {
+      c = s[i];
+      if (c == '\\')
+       {
+         i++;
+         if (s[i])
+           i++;
+       }
+      else if (c == '\'')
+       i = skip_single_quoted (s, s_len, ++i);
+      else if (c == '"')
+       i = skip_double_quoted (s, s_len, ++i);
+      else if (c == 0 || spctabnl (c))
+       {
+         /* We have found the end of a token.  Make a word out of it and
+            add it to the word list. */
+         token = substring (s, tokstart, i);
+         list = add_string_to_list (token, list);
+         free (token);
+         while (spctabnl (s[i]))
+           i++;
+         if (s[i])
+           tokstart = i;
+         else
+           break;
+       }
+      else
+       i++;    /* normal character */
+    }
+  return (REVERSE_LIST (list, WORD_LIST *));
+}
+#endif
+
+/********************************************************/
+/*                                                     */
+/*     Functions to perform assignment statements      */
+/*                                                     */
+/********************************************************/
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *
+do_compound_assignment (name, value, flags)
+     char *name, *value;
+     int flags;
+{
+  SHELL_VAR *v;
+  int mklocal, mkassoc, mkglobal;
+  WORD_LIST *list;
+
+  mklocal = flags & ASS_MKLOCAL;
+  mkassoc = flags & ASS_MKASSOC;
+  mkglobal = flags & ASS_MKGLOBAL;
+
+  if (mklocal && variable_context)
+    {
+      v = find_variable (name);
+      list = expand_compound_array_assignment (v, value, flags);
+      if (mkassoc)
+       v = make_local_assoc_variable (name);
+      else if (v == 0 || (array_p (v) == 0 && assoc_p (v) == 0) || v->context != variable_context)
+        v = make_local_array_variable (name, 0);
+      if (v)
+       assign_compound_array_list (v, list, flags);
+    }
+  /* In a function but forcing assignment in global context */
+  else if (mkglobal && variable_context)
+    {
+      v = find_global_variable (name);
+      list = expand_compound_array_assignment (v, value, flags);
+      if (v == 0 && mkassoc)
+       v = make_new_assoc_variable (name);
+      else if (v && mkassoc && assoc_p (v) == 0)
+       v = convert_var_to_assoc (v);
+      else if (v == 0)
+       v = make_new_array_variable (name);
+      else if (v && mkassoc == 0 && array_p (v) == 0)
+       v = convert_var_to_array (v);
+      if (v)
+       assign_compound_array_list (v, list, flags);
+    }
+  else
+    v = assign_array_from_string (name, value, flags);
+
+  return (v);
+}
+#endif
+
+/* Given STRING, an assignment string, get the value of the right side
+   of the `=', and bind it to the left side.  If EXPAND is true, then
+   perform parameter expansion, command substitution, and arithmetic
+   expansion on the right-hand side.  Perform tilde expansion in any
+   case.  Do not perform word splitting on the result of expansion. */
+static int
+do_assignment_internal (word, expand)
+     const WORD_DESC *word;
+     int expand;
+{
+  int offset, appendop, assign_list, aflags, retval;
+  char *name, *value, *temp;
+  SHELL_VAR *entry;
+#if defined (ARRAY_VARS)
+  char *t;
+  int ni;
+#endif
+  const char *string;
+
+  if (word == 0 || word->word == 0)
+    return 0;
+
+  appendop = assign_list = aflags = 0;
+  string = word->word;
+  offset = assignment (string, 0);
+  name = savestring (string);
+  value = (char *)NULL;
+
+  if (name[offset] == '=')
+    {
+      if (name[offset - 1] == '+')
+       {
+         appendop = 1;
+         name[offset - 1] = '\0';
+       }
+
+      name[offset] = 0;                /* might need this set later */
+      temp = name + offset + 1;
+
+#if defined (ARRAY_VARS)
+      if (expand && (word->flags & W_COMPASSIGN))
+       {
+         assign_list = ni = 1;
+         value = extract_array_assignment_list (temp, &ni);
+       }
+      else
+#endif
+      if (expand && temp[0])
+       value = expand_string_if_necessary (temp, 0, expand_string_assignment);
+      else
+       value = savestring (temp);
+    }
+
+  if (value == 0)
+    {
+      value = (char *)xmalloc (1);
+      value[0] = '\0';
+    }
+
+  if (echo_command_at_execute)
+    {
+      if (appendop)
+       name[offset - 1] = '+';
+      xtrace_print_assignment (name, value, assign_list, 1);
+      if (appendop)
+       name[offset - 1] = '\0';
+    }
+
+#define ASSIGN_RETURN(r)       do { FREE (value); free (name); return (r); } while (0)
+
+  if (appendop)
+    aflags |= ASS_APPEND;
+
+#if defined (ARRAY_VARS)
+  if (t = mbschr (name, '['))  /*]*/
+    {
+      if (assign_list)
+       {
+         report_error (_("%s: cannot assign list to array member"), name);
+         ASSIGN_RETURN (0);
+       }
+      entry = assign_array_element (name, value, aflags);
+      if (entry == 0)
+       ASSIGN_RETURN (0);
+    }
+  else if (assign_list)
+    {
+      if ((word->flags & W_ASSIGNARG) && (word->flags & W_ASSNGLOBAL) == 0)
+       aflags |= ASS_MKLOCAL;
+      if ((word->flags & W_ASSIGNARG) && (word->flags & W_ASSNGLOBAL))
+       aflags |= ASS_MKGLOBAL;
+      if (word->flags & W_ASSIGNASSOC)
+       aflags |= ASS_MKASSOC;
+      entry = do_compound_assignment (name, value, aflags);
+    }
+  else
+#endif /* ARRAY_VARS */
+  entry = bind_variable (name, value, aflags);
+
+  stupidly_hack_special_variables (name);
+
+  /* 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;
+
+  if (entry && retval != 0 && noassign_p (entry) == 0)
+    VUNSETATTR (entry, att_invisible);
+
+  ASSIGN_RETURN (retval);
+}
+
+/* Perform the assignment statement in STRING, and expand the
+   right side by doing tilde, command and parameter expansion. */
+int
+do_assignment (string)
+     char *string;
+{
+  WORD_DESC td;
+
+  td.flags = W_ASSIGNMENT;
+  td.word = string;
+
+  return do_assignment_internal (&td, 1);
+}
+
+int
+do_word_assignment (word, flags)
+     WORD_DESC *word;
+     int flags;
+{
+  return do_assignment_internal (word, 1);
+}
+
+/* Given STRING, an assignment string, get the value of the right side
+   of the `=', and bind it to the left side.  Do not perform any word
+   expansions on the right hand side. */
+int
+do_assignment_no_expand (string)
+     char *string;
+{
+  WORD_DESC td;
+
+  td.flags = W_ASSIGNMENT;
+  td.word = string;
+
+  return (do_assignment_internal (&td, 0));
+}
+
+/***************************************************
+ *                                                *
+ *  Functions to manage the positional parameters  *
+ *                                                *
+ ***************************************************/
+
+/* Return the word list that corresponds to `$*'. */
+WORD_LIST *
+list_rest_of_args ()
+{
+  register WORD_LIST *list, *args;
+  int i;
+
+  /* Break out of the loop as soon as one of the dollar variables is null. */
+  for (i = 1, list = (WORD_LIST *)NULL; i < 10 && dollar_vars[i]; i++)
+    list = make_word_list (make_bare_word (dollar_vars[i]), list);
+
+  for (args = rest_of_args; args; args = args->next)
+    list = make_word_list (make_bare_word (args->word->word), list);
+
+  return (REVERSE_LIST (list, WORD_LIST *));
+}
+
+int
+number_of_args ()
+{
+  register WORD_LIST *list;
+  int n;
+
+  for (n = 0; n < 9 && dollar_vars[n+1]; n++)
+    ;
+  for (list = rest_of_args; list; list = list->next)
+    n++;
+  return n;
+}
+
+/* Return the value of a positional parameter.  This handles values > 10. */
+char *
+get_dollar_var_value (ind)
+     intmax_t ind;
+{
+  char *temp;
+  WORD_LIST *p;
+
+  if (ind < 10)
+    temp = dollar_vars[ind] ? savestring (dollar_vars[ind]) : (char *)NULL;
+  else /* We want something like ${11} */
+    {
+      ind -= 10;
+      for (p = rest_of_args; p && ind--; p = p->next)
+       ;
+      temp = p ? savestring (p->word->word) : (char *)NULL;
+    }
+  return (temp);
+}
+
+/* Make a single large string out of the dollar digit variables,
+   and the rest_of_args.  If DOLLAR_STAR is 1, then obey the special
+   case of "$*" with respect to IFS. */
+char *
+string_rest_of_args (dollar_star)
+     int dollar_star;
+{
+  register WORD_LIST *list;
+  char *string;
+
+  list = list_rest_of_args ();
+  string = dollar_star ? string_list_dollar_star (list) : string_list (list);
+  dispose_words (list);
+  return (string);
+}
+
+/* Return a string containing the positional parameters from START to
+   END, inclusive.  If STRING[0] == '*', we obey the rules for $*,
+   which only makes a difference if QUOTED is non-zero.  If QUOTED includes
+   Q_HERE_DOCUMENT or Q_DOUBLE_QUOTES, this returns a quoted list, otherwise
+   no quoting chars are added. */
+static char *
+pos_params (string, start, end, quoted)
+     char *string;
+     int start, end, quoted;
+{
+  WORD_LIST *save, *params, *h, *t;
+  char *ret;
+  int i;
+
+  /* see if we can short-circuit.  if start == end, we want 0 parameters. */
+  if (start == end)
+    return ((char *)NULL);
+
+  save = params = list_rest_of_args ();
+  if (save == 0)
+    return ((char *)NULL);
+
+  if (start == 0)              /* handle ${@:0[:x]} specially */
+    {
+      t = make_word_list (make_word (dollar_vars[0]), params);
+      save = params = t;
+    }
+
+  for (i = start ? 1 : 0; params && i < start; i++)
+    params = params->next;
+  if (params == 0)
+    return ((char *)NULL);
+  for (h = t = params; params && i < end; i++)
+    {
+      t = params;
+      params = params->next;
+    }
+
+  t->next = (WORD_LIST *)NULL;
+
+  ret = string_list_pos_params (string[0], h, quoted);
+
+  if (t != params)
+    t->next = params;
+
+  dispose_words (save);
+  return (ret);
+}
+
+/******************************************************************/
+/*                                                               */
+/*     Functions to expand strings to strings or WORD_LISTs      */
+/*                                                               */
+/******************************************************************/
+
+#if defined (PROCESS_SUBSTITUTION)
+#define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC || s == '~')
+#else
+#define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC || s == '~')
+#endif
+
+/* If there are any characters in STRING that require full expansion,
+   then call FUNC to expand STRING; otherwise just perform quote
+   removal if necessary.  This returns a new string. */
+static char *
+expand_string_if_necessary (string, quoted, func)
+     char *string;
+     int quoted;
+     EXPFUNC *func;
+{
+  WORD_LIST *list;
+  size_t slen;
+  int i, saw_quote;
+  char *ret;
+  DECLARE_MBSTATE;
+
+  /* Don't need string length for ADVANCE_CHAR unless multibyte chars possible. */
+  slen = (MB_CUR_MAX > 1) ? strlen (string) : 0;
+  i = saw_quote = 0;
+  while (string[i])
+    {
+      if (EXP_CHAR (string[i]))
+       break;
+      else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
+       saw_quote = 1;
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+  if (string[i])
+    {
+      list = (*func) (string, quoted);
+      if (list)
+       {
+         ret = string_list (list);
+         dispose_words (list);
+       }
+      else
+       ret = (char *)NULL;
+    }
+  else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+    ret = string_quote_removal (string, quoted);
+  else
+    ret = savestring (string);
+
+  return ret;
+}
+
+static inline char *
+expand_string_to_string_internal (string, quoted, func)
+     char *string;
+     int quoted;
+     EXPFUNC *func;
+{
+  WORD_LIST *list;
+  char *ret;
+
+  if (string == 0 || *string == '\0')
+    return ((char *)NULL);
+
+  list = (*func) (string, quoted);
+  if (list)
+    {
+      ret = string_list (list);
+      dispose_words (list);
+    }
+  else
+    ret = (char *)NULL;
+
+  return (ret);
+}
+
+char *
+expand_string_to_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  return (expand_string_to_string_internal (string, quoted, expand_string));
+}
+
+char *
+expand_string_unsplit_to_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  return (expand_string_to_string_internal (string, quoted, expand_string_unsplit));
+}
+
+char *
+expand_assignment_string_to_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  return (expand_string_to_string_internal (string, quoted, expand_string_assignment));
+}
+
+char *
+expand_arith_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_DESC td;
+  WORD_LIST *list, *tlist;
+  size_t slen;
+  int i, saw_quote;
+  char *ret;
+  DECLARE_MBSTATE;
+
+  /* Don't need string length for ADVANCE_CHAR unless multibyte chars possible. */
+  slen = (MB_CUR_MAX > 1) ? strlen (string) : 0;
+  i = saw_quote = 0;
+  while (string[i])
+    {
+      if (EXP_CHAR (string[i]))
+       break;
+      else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
+       saw_quote = 1;
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+  if (string[i])
+    {
+      /* This is expanded version of expand_string_internal as it's called by
+        expand_string_leave_quoted  */
+      td.flags = W_NOPROCSUB;  /* don't want process substitution */
+      td.word = savestring (string);
+      list = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+      /* This takes care of the calls from expand_string_leave_quoted and
+        expand_string */
+      if (list)
+       {
+         tlist = word_list_split (list);
+         dispose_words (list);
+         list = tlist;
+         if (list)
+           dequote_list (list);
+       }
+      /* This comes from expand_string_if_necessary */
+      if (list)
+       {
+         ret = string_list (list);
+         dispose_words (list);
+       }
+      else
+       ret = (char *)NULL;
+      FREE (td.word);
+    }
+  else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+    ret = string_quote_removal (string, quoted);
+  else
+    ret = savestring (string);
+
+  return ret;
+}
+
+#if defined (COND_COMMAND)
+/* Just remove backslashes in STRING.  Returns a new string. */
+char *
+remove_backslashes (string)
+     char *string;
+{
+  char *r, *ret, *s;
+
+  r = ret = (char *)xmalloc (strlen (string) + 1);
+  for (s = string; s && *s; )
+    {
+      if (*s == '\\')
+       s++;
+      if (*s == 0)
+       break;
+      *r++ = *s++;
+    }
+  *r = '\0';
+  return ret;
+}
+
+/* This needs better error handling. */
+/* Expand W for use as an argument to a unary or binary operator in a
+   [[...]] expression.  If SPECIAL is 1, this is the rhs argument
+   to the != or == operator, and should be treated as a pattern.  In
+   this case, we quote the string specially for the globbing code.  If
+   SPECIAL is 2, this is an rhs argument for the =~ operator, and should
+   be quoted appropriately for regcomp/regexec.  The caller is responsible
+   for removing the backslashes if the unquoted word is needed later. */   
+char *
+cond_expand_word (w, special)
+     WORD_DESC *w;
+     int special;
+{
+  char *r, *p;
+  WORD_LIST *l;
+  int qflags;
+
+  if (w->word == 0 || w->word[0] == '\0')
+    return ((char *)NULL);
+
+  w->flags |= W_NOSPLIT2;
+  l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0);
+  if (l)
+    {
+      if (special == 0)
+       {
+         dequote_list (l);
+         r = string_list (l);
+       }
+      else
+       {
+         qflags = QGLOB_CVTNULL;
+         if (special == 2)
+           qflags |= QGLOB_REGEXP;
+         p = string_list (l);
+         r = quote_string_for_globbing (p, qflags);
+         free (p);
+       }
+      dispose_words (l);
+    }
+  else
+    r = (char *)NULL;
+
+  return r;
+}
+#endif
+
+/* Call expand_word_internal to expand W and handle error returns.
+   A convenience function for functions that don't want to handle
+   any errors or free any memory before aborting. */
+static WORD_LIST *
+call_expand_word_internal (w, q, i, c, e)
+     WORD_DESC *w;
+     int q, i, *c, *e;
+{
+  WORD_LIST *result;
+
+  result = expand_word_internal (w, q, i, c, e);
+  if (result == &expand_word_error || result == &expand_word_fatal)
+    {
+      /* By convention, each time this error is returned, w->word has
+        already been freed (it sometimes may not be in the fatal case,
+        but that doesn't result in a memory leak because we're going
+        to exit in most cases). */
+      w->word = (char *)NULL;
+      last_command_exit_value = EXECUTION_FAILURE;
+      exp_jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF);
+      /* NOTREACHED */
+      return (NULL);
+    }
+  else
+    return (result);
+}
+
+/* Perform parameter expansion, command substitution, and arithmetic
+   expansion on STRING, as if it were a word.  Leave the result quoted.
+   Since this does not perform word splitting, it leaves quoted nulls
+   in the result.  */
+static WORD_LIST *
+expand_string_internal (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_DESC td;
+  WORD_LIST *tresult;
+
+  if (string == 0 || *string == 0)
+    return ((WORD_LIST *)NULL);
+
+  td.flags = 0;
+  td.word = savestring (string);
+
+  tresult = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+
+  FREE (td.word);
+  return (tresult);
+}
+
+/* Expand STRING by performing parameter expansion, command substitution,
+   and arithmetic expansion.  Dequote the resulting WORD_LIST before
+   returning it, but do not perform word splitting.  The call to
+   remove_quoted_nulls () is in here because word splitting normally
+   takes care of quote removal. */
+WORD_LIST *
+expand_string_unsplit (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_LIST *value;
+
+  if (string == 0 || *string == '\0')
+    return ((WORD_LIST *)NULL);
+
+  expand_no_split_dollar_star = 1;
+  value = expand_string_internal (string, quoted);
+  expand_no_split_dollar_star = 0;
+
+  if (value)
+    {
+      if (value->word)
+       {
+         remove_quoted_nulls (value->word->word);
+         value->word->flags &= ~W_HASQUOTEDNULL;
+       }
+      dequote_list (value);
+    }
+  return (value);
+}
+
+/* Expand the rhs of an assignment statement */
+WORD_LIST *
+expand_string_assignment (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_DESC td;
+  WORD_LIST *value;
+
+  if (string == 0 || *string == '\0')
+    return ((WORD_LIST *)NULL);
+
+  expand_no_split_dollar_star = 1;
+
+  td.flags = W_ASSIGNRHS;
+  td.word = savestring (string);
+  value = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+  FREE (td.word);
+
+  expand_no_split_dollar_star = 0;
+
+  if (value)
+    {
+      if (value->word)
+       {
+         remove_quoted_nulls (value->word->word);
+         value->word->flags &= ~W_HASQUOTEDNULL;
+       }
+      dequote_list (value);
+    }
+  return (value);
+}
+
+
+/* Expand one of the PS? prompt strings. This is a sort of combination of
+   expand_string_unsplit and expand_string_internal, but returns the
+   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, wflags)
+     char *string;
+     int quoted;
+     int wflags;
+{
+  WORD_LIST *value;
+  WORD_DESC td;
+
+  if (string == 0 || *string == 0)
+    return ((WORD_LIST *)NULL);
+
+  td.flags = wflags;
+  td.word = savestring (string);
+
+  no_longjmp_on_fatal_error = 1;
+  value = expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+  no_longjmp_on_fatal_error = 0;
+
+  if (value == &expand_word_error || value == &expand_word_fatal)
+    {
+      value = make_word_list (make_bare_word (string), (WORD_LIST *)NULL);
+      return value;
+    }
+  FREE (td.word);
+  if (value)
+    {
+      if (value->word)
+       {
+         remove_quoted_nulls (value->word->word);
+         value->word->flags &= ~W_HASQUOTEDNULL;
+       }
+      dequote_list (value);
+    }
+  return (value);
+}
+
+/* Expand STRING just as if you were expanding a word, but do not dequote
+   the resultant WORD_LIST.  This is called only from within this file,
+   and is used to correctly preserve quoted characters when expanding
+   things like ${1+"$@"}.  This does parameter expansion, command
+   substitution, arithmetic expansion, and word splitting. */
+static WORD_LIST *
+expand_string_leave_quoted (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_LIST *tlist;
+  WORD_LIST *tresult;
+
+  if (string == 0 || *string == '\0')
+    return ((WORD_LIST *)NULL);
+
+  tlist = expand_string_internal (string, quoted);
+
+  if (tlist)
+    {
+      tresult = word_list_split (tlist);
+      dispose_words (tlist);
+      return (tresult);
+    }
+  return ((WORD_LIST *)NULL);
+}
+
+/* This does not perform word splitting or dequote the WORD_LIST
+   it returns. */
+static WORD_LIST *
+expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at)
+     char *string;
+     int quoted, *dollar_at_p, *has_dollar_at;
+{
+  WORD_DESC td;
+  WORD_LIST *tresult;
+
+  if (string == 0 || *string == '\0')
+    return (WORD_LIST *)NULL;
+
+  td.flags = W_NOSPLIT2;               /* no splitting, remove "" and '' */
+  td.word = string;
+  tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at);
+  return (tresult);
+}
+
+/* Expand STRING just as if you were expanding a word.  This also returns
+   a list of words.  Note that filename globbing is *NOT* done for word
+   or string expansion, just when the shell is expanding a command.  This
+   does parameter expansion, command substitution, arithmetic expansion,
+   and word splitting.  Dequote the resultant WORD_LIST before returning. */
+WORD_LIST *
+expand_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_LIST *result;
+
+  if (string == 0 || *string == '\0')
+    return ((WORD_LIST *)NULL);
+
+  result = expand_string_leave_quoted (string, quoted);
+  return (result ? dequote_list (result) : result);
+}
+
+/***************************************************
+ *                                                *
+ *     Functions to handle quoting chars          *
+ *                                                *
+ ***************************************************/
+
+/* Conventions:
+
+     A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string.
+     The parser passes CTLNUL as CTLESC CTLNUL. */
+
+/* Quote escape characters in string s, but no other characters.  This is
+   used to protect CTLESC and CTLNUL in variable values from the rest of
+   the word expansion process after the variable is expanded (word splitting
+   and filename generation).  If IFS is null, we quote spaces as well, just
+   in case we split on spaces later (in the case of unquoted $@, we will
+   eventually attempt to split the entire word on spaces).  Corresponding
+   code exists in dequote_escapes.  Even if we don't end up splitting on
+   spaces, quoting spaces is not a problem.  This should never be called on
+   a string that is quoted with single or double quotes or part of a here
+   document (effectively double-quoted). */
+char *
+quote_escapes (string)
+     char *string;
+{
+  register char *s, *t;
+  size_t slen;
+  char *result, *send;
+  int quote_spaces, skip_ctlesc, skip_ctlnul;
+  DECLARE_MBSTATE; 
+
+  slen = strlen (string);
+  send = string + slen;
+
+  quote_spaces = (ifs_value && *ifs_value == 0);
+
+  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) || (skip_ctlnul == 0 && *s == CTLNUL) || (quote_spaces && *s == ' '))
+       *t++ = CTLESC;
+      COPY_CHAR_P (t, s, send);
+    }
+  *t = '\0';
+  return (result);
+}
+
+static WORD_LIST *
+list_quote_escapes (list)
+     WORD_LIST *list;
+{
+  register WORD_LIST *w;
+  char *t;
+
+  for (w = list; w; w = w->next)
+    {
+      t = w->word->word;
+      w->word->word = quote_escapes (t);
+      free (t);
+    }
+  return list;
+}
+
+/* Inverse of quote_escapes; remove CTLESC protecting CTLESC or CTLNUL.
+
+   The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL.
+   This is necessary to make unquoted CTLESC and CTLNUL characters in the
+   data stream pass through properly.
+
+   We need to remove doubled CTLESC characters inside quoted strings before
+   quoting the entire string, so we do not double the number of CTLESC
+   characters.
+
+   Also used by parts of the pattern substitution code. */
+char *
+dequote_escapes (string)
+     char *string;
+{
+  register char *s, *t, *s1;
+  size_t slen;
+  char *result, *send;
+  int quote_spaces;
+  DECLARE_MBSTATE;
+
+  if (string == 0)
+    return string;
+
+  slen = strlen (string);
+  send = string + slen;
+
+  t = result = (char *)xmalloc (slen + 1);
+
+  if (strchr (string, CTLESC) == 0)
+    return (strcpy (result, string));
+
+  quote_spaces = (ifs_value && *ifs_value == 0);
+
+  s = string;
+  while (*s)
+    {
+      if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL || (quote_spaces && s[1] == ' ')))
+       {
+         s++;
+         if (*s == '\0')
+           break;
+       }
+      COPY_CHAR_P (t, s, send);
+    }
+  *t = '\0';
+  return result;
+}
+
+/* Return a new string with the quoted representation of character C.
+   This turns "" into QUOTED_NULL, so the W_HASQUOTEDNULL flag needs to be
+   set in any resultant WORD_DESC where this value is the word. */
+static char *
+make_quoted_char (c)
+     int c;
+{
+  char *temp;
+
+  temp = (char *)xmalloc (3);
+  if (c == 0)
+    {
+      temp[0] = CTLNUL;
+      temp[1] = '\0';
+    }
+  else
+    {
+      temp[0] = CTLESC;
+      temp[1] = c;
+      temp[2] = '\0';
+    }
+  return (temp);
+}
+
+/* Quote STRING, returning a new string.  This turns "" into QUOTED_NULL, so
+   the W_HASQUOTEDNULL flag needs to be set in any resultant WORD_DESC where
+   this value is the word. */
+char *
+quote_string (string)
+     char *string;
+{
+  register char *t;
+  size_t slen;
+  char *result, *send;
+
+  if (*string == 0)
+    {
+      result = (char *)xmalloc (2);
+      result[0] = CTLNUL;
+      result[1] = '\0';
+    }
+  else
+    {
+      DECLARE_MBSTATE;
+
+      slen = strlen (string);
+      send = string + slen;
+
+      result = (char *)xmalloc ((slen * 2) + 1);
+
+      for (t = result; string < send; )
+       {
+         *t++ = CTLESC;
+         COPY_CHAR_P (t, string, send);
+       }
+      *t = '\0';
+    }
+  return (result);
+}
+
+/* De-quote quoted characters in STRING. */
+char *
+dequote_string (string)
+     char *string;
+{
+  register char *s, *t;
+  size_t slen;
+  char *result, *send;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+
+  t = result = (char *)xmalloc (slen + 1);
+
+  if (QUOTED_NULL (string))
+    {
+      result[0] = '\0';
+      return (result);
+    }
+
+  /* If no character in the string can be quoted, don't bother examining
+     each character.  Just return a copy of the string passed to us. */
+  if (strchr (string, CTLESC) == NULL)
+    return (strcpy (result, string));
+
+  send = string + slen;
+  s = string;
+  while (*s)
+    {
+      if (*s == CTLESC)
+       {
+         s++;
+         if (*s == '\0')
+           break;
+       }
+      COPY_CHAR_P (t, s, send);
+    }
+
+  *t = '\0';
+  return (result);
+}
+
+/* Quote the entire WORD_LIST list. */
+static WORD_LIST *
+quote_list (list)
+     WORD_LIST *list;
+{
+  register WORD_LIST *w;
+  char *t;
+
+  for (w = list; w; w = w->next)
+    {
+      t = w->word->word;
+      w->word->word = quote_string (t);
+      if (*t == 0)
+       w->word->flags |= W_HASQUOTEDNULL;      /* XXX - turn on W_HASQUOTEDNULL here? */
+      w->word->flags |= W_QUOTED;
+      free (t);
+    }
+  return list;
+}
+
+/* De-quote quoted characters in each word in LIST. */
+WORD_LIST *
+dequote_list (list)
+     WORD_LIST *list;
+{
+  register char *s;
+  register WORD_LIST *tlist;
+
+  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;
+    }
+  return list;
+}
+
+/* Remove CTLESC protecting a CTLESC or CTLNUL in place.  Return the passed
+   string. */
+char *
+remove_quoted_escapes (string)
+     char *string;
+{
+  char *t;
+
+  if (string)
+    {
+      t = dequote_escapes (string);
+      strcpy (string, t);
+      free (t);
+    }
+
+  return (string);
+}
+
+/* Perform quoted null character removal on STRING.  We don't allow any
+   quoted null characters in the middle or at the ends of strings because
+   of how expand_word_internal works.  remove_quoted_nulls () turns
+   STRING into an empty string iff it only consists of a quoted null,
+   and removes all unquoted CTLNUL characters. */
+char *
+remove_quoted_nulls (string)
+     char *string;
+{
+  register size_t slen;
+  register int i, j, prev_i;
+  DECLARE_MBSTATE;
+
+  if (strchr (string, CTLNUL) == 0)            /* XXX */
+    return string;                             /* XXX */
+
+  slen = strlen (string);
+  i = j = 0;
+
+  while (i < slen)
+    {
+      if (string[i] == CTLESC)
+       {
+         /* Old code had j++, but we cannot assume that i == j at this
+            point -- what if a CTLNUL has already been removed from the
+            string?  We don't want to drop the CTLESC or recopy characters
+            that we've already copied down. */
+         i++; string[j++] = CTLESC;
+         if (i == slen)
+           break;
+       }
+      else if (string[i] == CTLNUL)
+       {
+         i++;
+         continue;
+       }
+
+      prev_i = i;
+      ADVANCE_CHAR (string, slen, i);
+      if (j < prev_i)
+       {
+         do string[j++] = string[prev_i++]; while (prev_i < i);
+       }
+      else
+       j = i;
+    }
+  string[j] = '\0';
+
+  return (string);
+}
+
+/* Perform quoted null character removal on each element of LIST.
+   This modifies LIST. */
+void
+word_list_remove_quoted_nulls (list)
+     WORD_LIST *list;
+{
+  register WORD_LIST *t;
+
+  for (t = list; t; t = t->next)
+    {
+      remove_quoted_nulls (t->word->word);
+      t->word->flags &= ~W_HASQUOTEDNULL;
+    }
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*        Functions for Matching and Removing Patterns             */
+/*                                                                 */
+/* **************************************************************** */
+
+#if defined (HANDLE_MULTIBYTE)
+#if 0 /* Currently unused */
+static unsigned char *
+mb_getcharlens (string, len)
+     char *string;
+     int len;
+{
+  int i, offset, last;
+  unsigned char *ret;
+  char *p;
+  DECLARE_MBSTATE;
+
+  i = offset = 0;
+  last = 0;
+  ret = (unsigned char *)xmalloc (len);
+  memset (ret, 0, len);
+  while (string[last])
+    {
+      ADVANCE_CHAR (string, len, offset);
+      ret[last] = offset - last;
+      last = offset;
+    }
+  return ret;
+}
+#endif
+#endif
+
+/* Remove the portion of PARAM matched by PATTERN according to OP, where OP
+   can have one of 4 values:
+       RP_LONG_LEFT    remove longest matching portion at start of PARAM
+       RP_SHORT_LEFT   remove shortest matching portion at start of PARAM
+       RP_LONG_RIGHT   remove longest matching portion at end of PARAM
+       RP_SHORT_RIGHT  remove shortest matching portion at end of PARAM
+*/
+
+#define RP_LONG_LEFT   1
+#define RP_SHORT_LEFT  2
+#define RP_LONG_RIGHT  3
+#define RP_SHORT_RIGHT 4
+
+/* Returns its first argument if nothing matched; new memory otherwise */
+static char *
+remove_upattern (param, pattern, op)
+     char *param, *pattern;
+     int op;
+{
+  register int len;
+  register char *end;
+  register char *p, *ret, c;
+
+  len = STRLEN (param);
+  end = param + len;
+
+  switch (op)
+    {
+      case RP_LONG_LEFT:       /* remove longest match at start */
+       for (p = end; p >= param; p--)
+         {
+           c = *p; *p = '\0';
+           if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               *p = c;
+               return (savestring (p));
+             }
+           *p = c;
+
+         }
+       break;
+
+      case RP_SHORT_LEFT:      /* remove shortest match at start */
+       for (p = param; p <= end; p++)
+         {
+           c = *p; *p = '\0';
+           if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               *p = c;
+               return (savestring (p));
+             }
+           *p = c;
+         }
+       break;
+
+      case RP_LONG_RIGHT:      /* remove longest match at end */
+       for (p = param; p <= end; p++)
+         {
+           if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               c = *p; *p = '\0';
+               ret = savestring (param);
+               *p = c;
+               return (ret);
+             }
+         }
+       break;
+
+      case RP_SHORT_RIGHT:     /* remove shortest match at end */
+       for (p = end; p >= param; p--)
+         {
+           if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               c = *p; *p = '\0';
+               ret = savestring (param);
+               *p = c;
+               return (ret);
+             }
+         }
+       break;
+    }
+
+  return (param);      /* no match, return original string */
+}
+
+#if defined (HANDLE_MULTIBYTE)
+/* Returns its first argument if nothing matched; new memory otherwise */
+static wchar_t *
+remove_wpattern (wparam, wstrlen, wpattern, op)
+     wchar_t *wparam;
+     size_t wstrlen;
+     wchar_t *wpattern;
+     int op;
+{
+  wchar_t wc, *ret;
+  int n;
+
+  switch (op)
+    {
+      case RP_LONG_LEFT:       /* remove longest match at start */
+        for (n = wstrlen; n >= 0; n--)
+         {
+           wc = wparam[n]; wparam[n] = L'\0';
+           if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               wparam[n] = wc;
+               return (wcsdup (wparam + n));
+             }
+           wparam[n] = wc;
+         }
+       break;
+
+      case RP_SHORT_LEFT:      /* remove shortest match at start */
+       for (n = 0; n <= wstrlen; n++)
+         {
+           wc = wparam[n]; wparam[n] = L'\0';
+           if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               wparam[n] = wc;
+               return (wcsdup (wparam + n));
+             }
+           wparam[n] = wc;
+         }
+       break;
+
+      case RP_LONG_RIGHT:      /* remove longest match at end */
+        for (n = 0; n <= wstrlen; n++)
+         {
+           if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               wc = wparam[n]; wparam[n] = L'\0';
+               ret = wcsdup (wparam);
+               wparam[n] = wc;
+               return (ret);
+             }
+         }
+       break;
+
+      case RP_SHORT_RIGHT:     /* remove shortest match at end */
+       for (n = wstrlen; n >= 0; n--)
+         {
+           if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               wc = wparam[n]; wparam[n] = L'\0';
+               ret = wcsdup (wparam);
+               wparam[n] = wc;
+               return (ret);
+             }
+         }
+       break;
+    }
+
+  return (wparam);     /* no match, return original string */
+}
+#endif /* HANDLE_MULTIBYTE */
+
+static char *
+remove_pattern (param, pattern, op)
+     char *param, *pattern;
+     int op;
+{
+  char *xret;
+
+  if (param == NULL)
+    return (param);
+  if (*param == '\0' || pattern == NULL || *pattern == '\0')   /* minor optimization */
+    return (savestring (param));
+
+#if defined (HANDLE_MULTIBYTE)
+  if (MB_CUR_MAX > 1)
+    {
+      wchar_t *ret, *oret;
+      size_t n;
+      wchar_t *wparam, *wpattern;
+      mbstate_t ps;
+
+      n = xdupmbstowcs (&wpattern, NULL, pattern);
+      if (n == (size_t)-1)
+       {
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
+       }
+      n = xdupmbstowcs (&wparam, NULL, param);
+
+      if (n == (size_t)-1)
+       {
+         free (wpattern);
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
+       }
+      oret = ret = remove_wpattern (wparam, n, wpattern, op);
+      /* Don't bother to convert wparam back to multibyte string if nothing
+        matched; just return copy of original string */
+      if (ret == wparam)
+        {
+          free (wparam);
+          free (wpattern);
+          return (savestring (param));
+        }
+
+      free (wparam);
+      free (wpattern);
+
+      n = strlen (param);
+      xret = (char *)xmalloc (n + 1);
+      memset (&ps, '\0', sizeof (mbstate_t));
+      n = wcsrtombs (xret, (const wchar_t **)&ret, n, &ps);
+      xret[n] = '\0';          /* just to make sure */
+      free (oret);
+      return xret;      
+    }
+  else
+#endif
+    {
+      xret = remove_upattern (param, pattern, op);
+      return ((xret == param) ? savestring (param) : xret);
+    }
+}
+
+/* Match PAT anywhere in STRING and return the match boundaries.
+   This returns 1 in case of a successful match, 0 otherwise.  SP
+   and EP are pointers into the string where the match begins and
+   ends, respectively.  MTYPE controls what kind of match is attempted.
+   MATCH_BEG and MATCH_END anchor the match at the beginning and end
+   of the string, respectively.  The longest match is returned. */
+static int
+match_upattern (string, pat, mtype, sp, ep)
+     char *string, *pat;
+     int mtype;
+     char **sp, **ep;
+{
+  int c, len, mlen;
+  register char *p, *p1, *npat;
+  char *end;
+  int n1;
+
+  /* If the pattern doesn't match anywhere in the string, go ahead and
+     short-circuit right away.  A minor optimization, saves a bunch of
+     unnecessary calls to strmatch (up to N calls for a string of N
+     characters) if the match is unsuccessful.  To preserve the semantics
+     of the substring matches below, we make sure that the pattern has
+     `*' as first and last character, making a new pattern if necessary. */
+  /* XXX - check this later if I ever implement `**' with special meaning,
+     since this will potentially result in `**' at the beginning or end */
+  len = STRLEN (pat);
+  if (pat[0] != '*' || (pat[0] == '*' && pat[1] == LPAREN && extended_glob) || pat[len - 1] != '*')
+    {
+      p = npat = (char *)xmalloc (len + 3);
+      p1 = pat;
+      if (*p1 != '*' || (*p1 == '*' && p1[1] == LPAREN && extended_glob))
+       *p++ = '*';
+      while (*p1)
+       *p++ = *p1++;
+      if (p1[-1] != '*' || p[-2] == '\\')
+       *p++ = '*';
+      *p = '\0';
+    }
+  else
+    npat = pat;
+  c = strmatch (npat, string, FNMATCH_EXTFLAG);
+  if (npat != pat)
+    free (npat);
+  if (c == FNM_NOMATCH)
+    return (0);
+
+  len = STRLEN (string);
+  end = string + len;
+
+  mlen = umatchlen (pat, len);
+
+  switch (mtype)
+    {
+    case MATCH_ANY:
+      for (p = string; p <= end; p++)
+       {
+         if (match_pattern_char (pat, p))
+           {
+             p1 = (mlen == -1) ? end : p + mlen;
+             /* p1 - p = length of portion of string to be considered
+                p = current position in string
+                mlen = number of characters consumed by match (-1 for entire string)
+                end = end of string
+                we want to break immediately if the potential match len
+                is greater than the number of characters remaining in the
+                string
+             */
+             if (p1 > end)
+               break;
+             for ( ; p1 >= p; p1--)
+               {
+                 c = *p1; *p1 = '\0';
+                 if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
+                   {
+                     *p1 = c;
+                     *sp = p;
+                     *ep = p1;
+                     return 1;
+                   }
+                 *p1 = c;
+#if 1
+                 /* If MLEN != -1, we have a fixed length pattern. */
+                 if (mlen != -1)
+                   break;
+#endif
+               }
+           }
+       }
+
+      return (0);
+
+    case MATCH_BEG:
+      if (match_pattern_char (pat, string) == 0)
+       return (0);
+
+      for (p = (mlen == -1) ? end : string + mlen; p >= string; p--)
+       {
+         c = *p; *p = '\0';
+         if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0)
+           {
+             *p = c;
+             *sp = string;
+             *ep = p;
+             return 1;
+           }
+         *p = c;
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+       }
+
+      return (0);
+
+    case MATCH_END:
+      for (p = end - ((mlen == -1) ? len : mlen); p <= end; p++)
+       {
+         if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
+           {
+             *sp = p;
+             *ep = end;
+             return 1;
+           }
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+       }
+
+      return (0);
+    }
+
+  return (0);
+}
+
+#if defined (HANDLE_MULTIBYTE)
+/* Match WPAT anywhere in WSTRING and return the match boundaries.
+   This returns 1 in case of a successful match, 0 otherwise.  Wide
+   character version. */
+static int
+match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
+     wchar_t *wstring;
+     char **indices;
+     size_t wstrlen;
+     wchar_t *wpat;
+     int mtype;
+     char **sp, **ep;
+{
+  wchar_t wc, *wp, *nwpat, *wp1;
+  size_t len;
+  int mlen;
+  int n, n1, n2, simple;
+
+  simple = (wpat[0] != L'\\' && wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'[');
+#if defined (EXTENDED_GLOB)
+  if (extended_glob)
+    simple &= (wpat[1] != L'(' || (wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'+' && wpat[0] != L'!' && wpat[0] != L'@')); /*)*/
+#endif
+
+  /* If the pattern doesn't match anywhere in the string, go ahead and
+     short-circuit right away.  A minor optimization, saves a bunch of
+     unnecessary calls to strmatch (up to N calls for a string of N
+     characters) if the match is unsuccessful.  To preserve the semantics
+     of the substring matches below, we make sure that the pattern has
+     `*' as first and last character, making a new pattern if necessary. */
+  len = wcslen (wpat);
+  if (wpat[0] != L'*' || (wpat[0] == L'*' && wpat[1] == WLPAREN && extended_glob) || wpat[len - 1] != L'*')
+    {
+      wp = nwpat = (wchar_t *)xmalloc ((len + 3) * sizeof (wchar_t));
+      wp1 = wpat;
+      if (*wp1 != L'*' || (*wp1 == '*' && wp1[1] == WLPAREN && extended_glob))
+       *wp++ = L'*';
+      while (*wp1 != L'\0')
+       *wp++ = *wp1++;
+      if (wp1[-1] != L'*' || wp1[-2] == L'\\')
+        *wp++ = L'*';
+      *wp = '\0';
+    }
+  else
+    nwpat = wpat;
+  len = wcsmatch (nwpat, wstring, FNMATCH_EXTFLAG);
+  if (nwpat != wpat)
+    free (nwpat);
+  if (len == FNM_NOMATCH)
+    return (0);
+
+  mlen = wmatchlen (wpat, wstrlen);
+
+/* itrace("wmatchlen (%ls) -> %d", wpat, mlen); */
+  switch (mtype)
+    {
+    case MATCH_ANY:
+      for (n = 0; n <= wstrlen; n++)
+       {
+         n2 = simple ? (*wpat == wstring[n]) : match_pattern_wchar (wpat, wstring + n);
+         if (n2)
+           {
+             n1 = (mlen == -1) ? wstrlen : n + mlen;
+             if (n1 > wstrlen)
+               break;
+
+             for ( ; n1 >= n; n1--)
+               {
+                 wc = wstring[n1]; wstring[n1] = L'\0';
+                 if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
+                   {
+                     wstring[n1] = wc;
+                     *sp = indices[n];
+                     *ep = indices[n1];
+                     return 1;
+                   }
+                 wstring[n1] = wc;
+                 /* If MLEN != -1, we have a fixed length pattern. */
+                 if (mlen != -1)
+                   break;
+               }
+           }
+       }
+
+      return (0);
+
+    case MATCH_BEG:
+      if (match_pattern_wchar (wpat, wstring) == 0)
+       return (0);
+
+      for (n = (mlen == -1) ? wstrlen : mlen; n >= 0; n--)
+       {
+         wc = wstring[n]; wstring[n] = L'\0';
+         if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG) == 0)
+           {
+             wstring[n] = wc;
+             *sp = indices[0];
+             *ep = indices[n];
+             return 1;
+           }
+         wstring[n] = wc;
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+       }
+
+      return (0);
+
+    case MATCH_END:
+      for (n = wstrlen - ((mlen == -1) ? wstrlen : mlen); n <= wstrlen; n++)
+       {
+         if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
+           {
+             *sp = indices[n];
+             *ep = indices[wstrlen];
+             return 1;
+           }
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+       }
+
+      return (0);
+    }
+
+  return (0);
+}
+#endif /* HANDLE_MULTIBYTE */
+
+static int
+match_pattern (string, pat, mtype, sp, ep)
+     char *string, *pat;
+     int mtype;
+     char **sp, **ep;
+{
+#if defined (HANDLE_MULTIBYTE)
+  int ret;
+  size_t n;
+  wchar_t *wstring, *wpat;
+  char **indices;
+  size_t slen, plen, mslen, mplen;
+#endif
+
+  if (string == 0 || *string == 0 || pat == 0 || *pat == 0)
+    return (0);
+
+#if defined (HANDLE_MULTIBYTE)
+  if (MB_CUR_MAX > 1)
+    {
+      if (mbsmbchar (string) == 0 && mbsmbchar (pat) == 0)
+        return (match_upattern (string, pat, mtype, sp, ep));
+
+      n = xdupmbstowcs (&wpat, NULL, pat);
+      if (n == (size_t)-1)
+       return (match_upattern (string, pat, mtype, sp, ep));
+      n = xdupmbstowcs (&wstring, &indices, string);
+      if (n == (size_t)-1)
+       {
+         free (wpat);
+         return (match_upattern (string, pat, mtype, sp, ep));
+       }
+      ret = match_wpattern (wstring, indices, n, wpat, mtype, sp, ep);
+
+      free (wpat);
+      free (wstring);
+      free (indices);
+
+      return (ret);
+    }
+  else
+#endif
+    return (match_upattern (string, pat, mtype, sp, ep));
+}
+
+static int
+getpatspec (c, value)
+     int c;
+     char *value;
+{
+  if (c == '#')
+    return ((*value == '#') ? RP_LONG_LEFT : RP_SHORT_LEFT);
+  else /* c == '%' */
+    return ((*value == '%') ? RP_LONG_RIGHT : RP_SHORT_RIGHT);
+}
+
+/* Posix.2 says that the WORD should be run through tilde expansion,
+   parameter expansion, command substitution and arithmetic expansion.
+   This leaves the result quoted, so quote_string_for_globbing () has
+   to be called to fix it up for strmatch ().  If QUOTED is non-zero,
+   it means that the entire expression was enclosed in double quotes.
+   This means that quoting characters in the pattern do not make any
+   special pattern characters quoted.  For example, the `*' in the
+   following retains its special meaning: "${foo#'*'}". */
+static char *
+getpattern (value, quoted, expandpat)
+     char *value;
+     int quoted, expandpat;
+{
+  char *pat, *tword;
+  WORD_LIST *l;
+#if 0
+  int i;
+#endif
+  /* There is a problem here:  how to handle single or double quotes in the
+     pattern string when the whole expression is between double quotes?
+     POSIX.2 says that enclosing double quotes do not cause the pattern to
+     be quoted, but does that leave us a problem with @ and array[@] and their
+     expansions inside a pattern? */
+#if 0
+  if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword)
+    {
+      i = 0;
+      pat = string_extract_double_quoted (tword, &i, 1);
+      free (tword);
+      tword = pat;
+    }
+#endif
+
+  /* expand_string_for_rhs () leaves WORD quoted and does not perform
+     word splitting. */
+  l = *value ? expand_string_for_rhs (value,
+                                     (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_PATQUOTE : quoted,
+                                     (int *)NULL, (int *)NULL)
+            : (WORD_LIST *)0;
+  pat = string_list (l);
+  dispose_words (l);
+  if (pat)
+    {
+      tword = quote_string_for_globbing (pat, QGLOB_CVTNULL);
+      free (pat);
+      pat = tword;
+    }
+  return (pat);
+}
+
+#if 0
+/* Handle removing a pattern from a string as a result of ${name%[%]value}
+   or ${name#[#]value}. */
+static char *
+variable_remove_pattern (value, pattern, patspec, quoted)
+     char *value, *pattern;
+     int patspec, quoted;
+{
+  char *tword;
+
+  tword = remove_pattern (value, pattern, patspec);
+
+  return (tword);
+}
+#endif
+
+static char *
+list_remove_pattern (list, pattern, patspec, itype, quoted)
+     WORD_LIST *list;
+     char *pattern;
+     int patspec, itype, quoted;
+{
+  WORD_LIST *new, *l;
+  WORD_DESC *w;
+  char *tword;
+
+  for (new = (WORD_LIST *)NULL, l = list; l; l = l->next)
+    {
+      tword = remove_pattern (l->word->word, pattern, patspec);
+      w = alloc_word_desc ();
+      w->word = tword ? tword : savestring ("");
+      new = make_word_list (w, new);
+    }
+
+  l = REVERSE_LIST (new, WORD_LIST *);
+  tword = string_list_pos_params (itype, l, quoted);
+  dispose_words (l);
+
+  return (tword);
+}
+
+static char *
+parameter_list_remove_pattern (itype, pattern, patspec, quoted)
+     int itype;
+     char *pattern;
+     int patspec, quoted;
+{
+  char *ret;
+  WORD_LIST *list;
+
+  list = list_rest_of_args ();
+  if (list == 0)
+    return ((char *)NULL);
+  ret = list_remove_pattern (list, pattern, patspec, itype, quoted);
+  dispose_words (list);
+  return (ret);
+}
+
+#if defined (ARRAY_VARS)
+static char *
+array_remove_pattern (var, pattern, patspec, varname, quoted)
+     SHELL_VAR *var;
+     char *pattern;
+     int patspec;
+     char *varname;    /* so we can figure out how it's indexed */
+     int quoted;
+{
+  ARRAY *a;
+  HASH_TABLE *h;
+  int itype;
+  char *ret;
+  WORD_LIST *list;
+  SHELL_VAR *v;
+
+  /* compute itype from varname here */
+  v = array_variable_part (varname, &ret, 0);
+
+  /* XXX */
+  if (v && invisible_p (var))
+    return ((char *)NULL);
+
+  itype = ret[0];
+
+  a = (v && array_p (v)) ? array_cell (v) : 0;
+  h = (v && assoc_p (v)) ? assoc_cell (v) : 0;
+  
+  list = a ? array_to_word_list (a) : (h ? assoc_to_word_list (h) : 0);
+  if (list == 0)
+   return ((char *)NULL);
+  ret = list_remove_pattern (list, pattern, patspec, itype, quoted);
+  dispose_words (list);
+
+  return ret;
+}
+#endif /* ARRAY_VARS */
+
+static char *
+parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *patstr;
+     int rtype, quoted, flags;
+{
+  int vtype, patspec, starsub;
+  char *temp1, *val, *pattern;
+  SHELL_VAR *v;
+
+  if (value == 0)
+    return ((char *)NULL);
+
+  this_command_name = varname;
+
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  if (vtype == -1)
+    return ((char *)NULL);
+
+  starsub = vtype & VT_STARSUB;
+  vtype &= ~VT_STARSUB;
+
+  patspec = getpatspec (rtype, patstr);
+  if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
+    patstr++;
+
+  /* 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)
+    {
+    case VT_VARIABLE:
+    case VT_ARRAYMEMBER:
+      temp1 = remove_pattern (val, pattern, patspec);
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      if (temp1)
+       {
+         val = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+                       ? quote_string (temp1)
+                       : quote_escapes (temp1);
+         free (temp1);
+         temp1 = val;
+       }
+      break;
+#if defined (ARRAY_VARS)
+    case VT_ARRAYVAR:
+      temp1 = array_remove_pattern (v, pattern, patspec, varname, quoted);
+      if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+       {
+         val = quote_escapes (temp1);
+         free (temp1);
+         temp1 = val;
+       }
+      break;
+#endif
+    case VT_POSPARMS:
+      temp1 = parameter_list_remove_pattern (varname[0], pattern, patspec, quoted);
+      if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+       {
+         val = quote_escapes (temp1);
+         free (temp1);
+         temp1 = val;
+       }
+      break;
+    }
+
+  FREE (pattern);
+  return temp1;
+}    
+
+/*******************************************
+ *                                        *
+ *     Functions to expand WORD_DESCs     *
+ *                                        *
+ *******************************************/
+
+/* Expand WORD, performing word splitting on the result.  This does
+   parameter expansion, command substitution, arithmetic expansion,
+   word splitting, and quote removal. */
+
+WORD_LIST *
+expand_word (word, quoted)
+     WORD_DESC *word;
+     int quoted;
+{
+  WORD_LIST *result, *tresult;
+
+  tresult = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
+  result = word_list_split (tresult);
+  dispose_words (tresult);
+  return (result ? dequote_list (result) : result);
+}
+
+/* Expand WORD, but do not perform word splitting on the result.  This
+   does parameter expansion, command substitution, arithmetic expansion,
+   and quote removal. */
+WORD_LIST *
+expand_word_unsplit (word, quoted)
+     WORD_DESC *word;
+     int quoted;
+{
+  WORD_LIST *result;
+
+  expand_no_split_dollar_star = 1;
+#if defined (HANDLE_MULTIBYTE)
+  if (ifs_firstc[0] == 0)
+#else
+  if (ifs_firstc == 0)
+#endif
+    word->flags |= W_NOSPLIT;
+  word->flags |= W_NOSPLIT2;
+  result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
+  expand_no_split_dollar_star = 0;
+
+  return (result ? dequote_list (result) : result);
+}
+
+/* Perform shell expansions on WORD, but do not perform word splitting or
+   quote removal on the result.  Virtually identical to expand_word_unsplit;
+   could be combined if implementations don't diverge. */
+WORD_LIST *
+expand_word_leave_quoted (word, quoted)
+     WORD_DESC *word;
+     int quoted;
+{
+  WORD_LIST *result;
+
+  expand_no_split_dollar_star = 1;
+#if defined (HANDLE_MULTIBYTE)
+  if (ifs_firstc[0] == 0)
+#else
+  if (ifs_firstc == 0)
+#endif
+    word->flags |= W_NOSPLIT;
+  word->flags |= W_NOSPLIT2;
+  result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
+  expand_no_split_dollar_star = 0;
+
+  return result;
+}
+
+#if defined (PROCESS_SUBSTITUTION)
+
+/*****************************************************************/
+/*                                                              */
+/*                 Hacking Process Substitution                 */
+/*                                                              */
+/*****************************************************************/
+
+#if !defined (HAVE_DEV_FD)
+/* Named pipes must be removed explicitly with `unlink'.  This keeps a list
+   of FIFOs the shell has open.  unlink_fifo_list will walk the list and
+   unlink all of them. add_fifo_list adds the name of an open FIFO to the
+   list.  NFIFO is a count of the number of FIFOs in the list. */
+#define FIFO_INCR 20
+
+struct temp_fifo {
+  char *file;
+  pid_t proc;
+};
+
+static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL;
+static int nfifo;
+static int fifo_list_size;
+
+char *
+copy_fifo_list (sizep)
+     int *sizep;
+{
+  if (sizep)
+    *sizep = 0;
+  return (char *)NULL;
+}
+
+static void
+add_fifo_list (pathname)
+     char *pathname;
+{
+  if (nfifo >= fifo_list_size - 1)
+    {
+      fifo_list_size += FIFO_INCR;
+      fifo_list = (struct temp_fifo *)xrealloc (fifo_list,
+                               fifo_list_size * sizeof (struct temp_fifo));
+    }
+
+  fifo_list[nfifo].file = savestring (pathname);
+  nfifo++;
+}
+
+void
+unlink_fifo (i)
+     int i;
+{
+  if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1))
+    {
+      unlink (fifo_list[i].file);
+      free (fifo_list[i].file);
+      fifo_list[i].file = (char *)NULL;
+      fifo_list[i].proc = -1;
+    }
+}
+
+void
+unlink_fifo_list ()
+{
+  int saved, i, j;
+
+  if (nfifo == 0)
+    return;
+
+  for (i = saved = 0; i < nfifo; i++)
+    {
+      if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1))
+       {
+         unlink (fifo_list[i].file);
+         free (fifo_list[i].file);
+         fifo_list[i].file = (char *)NULL;
+         fifo_list[i].proc = -1;
+       }
+      else
+       saved++;
+    }
+
+  /* If we didn't remove some of the FIFOs, compact the list. */
+  if (saved)
+    {
+      for (i = j = 0; i < nfifo; i++)
+       if (fifo_list[i].file)
+         {
+           fifo_list[j].file = fifo_list[i].file;
+           fifo_list[j].proc = fifo_list[i].proc;
+           j++;
+         }
+      nfifo = j;
+    }
+  else
+    nfifo = 0;
+}
+
+/* Take LIST, which is a bitmap denoting active FIFOs in fifo_list
+   from some point in the past, and close all open FIFOs in fifo_list
+   that are not marked as active in LIST.  If LIST is NULL, close
+   everything in fifo_list. LSIZE is the number of elements in LIST, in
+   case it's larger than fifo_list_size (size of fifo_list). */
+void
+close_new_fifos (list, lsize)
+     char *list;
+     int lsize;
+{
+  int i;
+
+  if (list == 0)
+    {
+      unlink_fifo_list ();
+      return;
+    }
+
+  for (i = 0; i < lsize; i++)
+    if (list[i] == 0 && i < fifo_list_size && fifo_list[i].proc != -1)
+      unlink_fifo (i);
+
+  for (i = lsize; i < fifo_list_size; i++)
+    unlink_fifo (i);  
+}
+
+int
+fifos_pending ()
+{
+  return nfifo;
+}
+
+int
+num_fifos ()
+{
+  return nfifo;
+}
+
+static char *
+make_named_pipe ()
+{
+  char *tname;
+
+  tname = sh_mktmpname ("sh-np", MT_USERANDOM|MT_USETMPDIR);
+  if (mkfifo (tname, 0600) < 0)
+    {
+      free (tname);
+      return ((char *)NULL);
+    }
+
+  add_fifo_list (tname);
+  return (tname);
+}
+
+#else /* HAVE_DEV_FD */
+
+/* DEV_FD_LIST is a bitmap of file descriptors attached to pipes the shell
+   has open to children.  NFDS is a count of the number of bits currently
+   set in DEV_FD_LIST.  TOTFDS is a count of the highest possible number
+   of open files. */
+static char *dev_fd_list = (char *)NULL;
+static int nfds;
+static int totfds;     /* The highest possible number of open files. */
+
+char *
+copy_fifo_list (sizep)
+     int *sizep;
+{
+  char *ret;
+
+  if (nfds == 0 || totfds == 0)
+    {
+      if (sizep)
+       *sizep = 0;
+      return (char *)NULL;
+    }
+
+  if (sizep)
+    *sizep = totfds;
+  ret = (char *)xmalloc (totfds);
+  return (memcpy (ret, dev_fd_list, totfds));
+}
+
+static void
+add_fifo_list (fd)
+     int fd;
+{
+  if (dev_fd_list == 0 || fd >= totfds)
+    {
+      int ofds;
+
+      ofds = totfds;
+      totfds = getdtablesize ();
+      if (totfds < 0 || totfds > 256)
+       totfds = 256;
+      if (fd >= totfds)
+       totfds = fd + 2;
+
+      dev_fd_list = (char *)xrealloc (dev_fd_list, totfds);
+      memset (dev_fd_list + ofds, '\0', totfds - ofds);
+    }
+
+  dev_fd_list[fd] = 1;
+  nfds++;
+}
+
+int
+fifos_pending ()
+{
+  return 0;    /* used for cleanup; not needed with /dev/fd */
+}
+
+int
+num_fifos ()
+{
+  return nfds;
+}
+
+void
+unlink_fifo (fd)
+     int fd;
+{
+  if (dev_fd_list[fd])
+    {
+      close (fd);
+      dev_fd_list[fd] = 0;
+      nfds--;
+    }
+}
+
+void
+unlink_fifo_list ()
+{
+  register int i;
+
+  if (nfds == 0)
+    return;
+
+  for (i = 0; nfds && i < totfds; i++)
+    unlink_fifo (i);
+
+  nfds = 0;
+}
+
+/* Take LIST, which is a snapshot copy of dev_fd_list from some point in
+   the past, and close all open fds in dev_fd_list that are not marked
+   as open in LIST.  If LIST is NULL, close everything in dev_fd_list.
+   LSIZE is the number of elements in LIST, in case it's larger than
+   totfds (size of dev_fd_list). */
+void
+close_new_fifos (list, lsize)
+     char *list;
+     int lsize;
+{
+  int i;
+
+  if (list == 0)
+    {
+      unlink_fifo_list ();
+      return;
+    }
+
+  for (i = 0; i < lsize; i++)
+    if (list[i] == 0 && i < totfds && dev_fd_list[i])
+      unlink_fifo (i);
+
+  for (i = lsize; i < totfds; i++)
+    unlink_fifo (i);  
+}
+
+#if defined (NOTDEF)
+print_dev_fd_list ()
+{
+  register int i;
+
+  fprintf (stderr, "pid %ld: dev_fd_list:", (long)getpid ());
+  fflush (stderr);
+
+  for (i = 0; i < totfds; i++)
+    {
+      if (dev_fd_list[i])
+       fprintf (stderr, " %d", i);
+    }
+  fprintf (stderr, "\n");
+}
+#endif /* NOTDEF */
+
+static char *
+make_dev_fd_filename (fd)
+     int fd;
+{
+  char *ret, intbuf[INT_STRLEN_BOUND (int) + 1], *p;
+
+  ret = (char *)xmalloc (sizeof (DEV_FD_PREFIX) + 8);
+
+  strcpy (ret, DEV_FD_PREFIX);
+  p = inttostr (fd, intbuf, sizeof (intbuf));
+  strcpy (ret + sizeof (DEV_FD_PREFIX) - 1, p);
+
+  add_fifo_list (fd);
+  return (ret);
+}
+
+#endif /* HAVE_DEV_FD */
+
+/* Return a filename that will open a connection to the process defined by
+   executing STRING.  HAVE_DEV_FD, if defined, means open a pipe and return
+   a filename in /dev/fd corresponding to a descriptor that is one of the
+   ends of the pipe.  If not defined, we use named pipes on systems that have
+   them.  Systems without /dev/fd and named pipes are out of luck.
+
+   OPEN_FOR_READ_IN_CHILD, if 1, means open the named pipe for reading or
+   use the read end of the pipe and dup that file descriptor to fd 0 in
+   the child.  If OPEN_FOR_READ_IN_CHILD is 0, we open the named pipe for
+   writing or use the write end of the pipe in the child, and dup that
+   file descriptor to fd 1 in the child.  The parent does the opposite. */
+
+static char *
+process_substitute (string, open_for_read_in_child)
+     char *string;
+     int open_for_read_in_child;
+{
+  char *pathname;
+  int fd, result;
+  pid_t old_pid, pid;
+#if defined (HAVE_DEV_FD)
+  int parent_pipe_fd, child_pipe_fd;
+  int fildes[2];
+#endif /* HAVE_DEV_FD */
+#if defined (JOB_CONTROL)
+  pid_t old_pipeline_pgrp;
+#endif
+
+  if (!string || !*string || wordexp_only)
+    return ((char *)NULL);
+
+#if !defined (HAVE_DEV_FD)
+  pathname = make_named_pipe ();
+#else /* HAVE_DEV_FD */
+  if (pipe (fildes) < 0)
+    {
+      sys_error (_("cannot make pipe for process substitution"));
+      return ((char *)NULL);
+    }
+  /* If OPEN_FOR_READ_IN_CHILD == 1, we want to use the write end of
+     the pipe in the parent, otherwise the read end. */
+  parent_pipe_fd = fildes[open_for_read_in_child];
+  child_pipe_fd = fildes[1 - open_for_read_in_child];
+  /* Move the parent end of the pipe to some high file descriptor, to
+     avoid clashes with FDs used by the script. */
+  parent_pipe_fd = move_to_high_fd (parent_pipe_fd, 1, 64);
+
+  pathname = make_dev_fd_filename (parent_pipe_fd);
+#endif /* HAVE_DEV_FD */
+
+  if (pathname == 0)
+    {
+      sys_error (_("cannot make pipe for process substitution"));
+      return ((char *)NULL);
+    }
+
+  old_pid = last_made_pid;
+
+#if defined (JOB_CONTROL)
+  old_pipeline_pgrp = pipeline_pgrp;
+  pipeline_pgrp = shell_pgrp;
+  save_pipeline (1);
+#endif /* JOB_CONTROL */
+
+  pid = make_child ((char *)NULL, 1);
+  if (pid == 0)
+    {
+      reset_terminating_signals ();    /* XXX */
+      free_pushed_string_input ();
+      /* Cancel traps, in trap.c. */
+      restore_original_signals ();     /* XXX - what about special builtins? bash-4.2 */
+      setup_async_signals ();
+      subshell_environment |= SUBSHELL_COMSUB|SUBSHELL_PROCSUB;
+    }
+
+#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 */
+
+  if (pid < 0)
+    {
+      sys_error (_("cannot make child for process substitution"));
+      free (pathname);
+#if defined (HAVE_DEV_FD)
+      close (parent_pipe_fd);
+      close (child_pipe_fd);
+#endif /* HAVE_DEV_FD */
+      return ((char *)NULL);
+    }
+
+  if (pid > 0)
+    {
+#if defined (JOB_CONTROL)
+      restore_pipeline (1);
+#endif
+
+#if !defined (HAVE_DEV_FD)
+      fifo_list[nfifo-1].proc = pid;
+#endif
+
+      last_made_pid = old_pid;
+
+#if defined (JOB_CONTROL) && defined (PGRP_PIPE)
+      close_pgrp_pipe ();
+#endif /* JOB_CONTROL && PGRP_PIPE */
+
+#if defined (HAVE_DEV_FD)
+      close (child_pipe_fd);
+#endif /* HAVE_DEV_FD */
+
+      return (pathname);
+    }
+
+  set_sigint_handler ();
+
+#if defined (JOB_CONTROL)
+  set_job_control (0);
+#endif /* JOB_CONTROL */
+
+#if !defined (HAVE_DEV_FD)
+  /* Open the named pipe in the child. */
+  fd = open (pathname, open_for_read_in_child ? O_RDONLY|O_NONBLOCK : O_WRONLY);
+  if (fd < 0)
+    {
+      /* Two separate strings for ease of translation. */
+      if (open_for_read_in_child)
+       sys_error (_("cannot open named pipe %s for reading"), pathname);
+      else
+       sys_error (_("cannot open named pipe %s for writing"), pathname);
+
+      exit (127);
+    }
+  if (open_for_read_in_child)
+    {
+      if (sh_unset_nodelay_mode (fd) < 0)
+       {
+         sys_error (_("cannot reset nodelay mode for fd %d"), fd);
+         exit (127);
+       }
+    }
+#else /* HAVE_DEV_FD */
+  fd = child_pipe_fd;
+#endif /* HAVE_DEV_FD */
+
+  if (dup2 (fd, open_for_read_in_child ? 0 : 1) < 0)
+    {
+      sys_error (_("cannot duplicate named pipe %s as fd %d"), pathname,
+       open_for_read_in_child ? 0 : 1);
+      exit (127);
+    }
+
+  if (fd != (open_for_read_in_child ? 0 : 1))
+    close (fd);
+
+  /* Need to close any files that this process has open to pipes inherited
+     from its parent. */
+  if (current_fds_to_close)
+    {
+      close_fd_bitmap (current_fds_to_close);
+      current_fds_to_close = (struct fd_bitmap *)NULL;
+    }
+
+#if defined (HAVE_DEV_FD)
+  /* Make sure we close the parent's end of the pipe and clear the slot
+     in the fd list so it is not closed later, if reallocated by, for
+     instance, pipe(2). */
+  close (parent_pipe_fd);
+  dev_fd_list[parent_pipe_fd] = 0;
+#endif /* HAVE_DEV_FD */
+
+  /* subshells shouldn't have this flag, which controls using the temporary
+     environment for variable lookups. */
+  expanding_redir = 0;
+
+  result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST));
+
+#if !defined (HAVE_DEV_FD)
+  /* Make sure we close the named pipe in the child before we exit. */
+  close (open_for_read_in_child ? 0 : 1);
+#endif /* !HAVE_DEV_FD */
+
+  last_command_exit_value = result;
+  result = run_exit_trap ();
+  exit (result);
+  /*NOTREACHED*/
+}
+#endif /* PROCESS_SUBSTITUTION */
+
+/***********************************/
+/*                                */
+/*     Command Substitution       */
+/*                                */
+/***********************************/
+
+static char *
+read_comsub (fd, quoted, rflag)
+     int fd, quoted;
+     int *rflag;
+{
+  char *istring, buf[128], *bufp, *s;
+  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 = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+    skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
+
+  /* Read the output of the command through the pipe.  This may need to be
+     changed to understand multibyte characters in the future. */
+  while (1)
+    {
+      if (fd < 0)
+       break;
+      if (--bufn <= 0)
+       {
+         bufn = zread (fd, buf, sizeof (buf));
+         if (bufn <= 0) 
+           break;
+         bufp = buf;
+       }
+      c = *bufp++;
+
+      if (c == 0)
+       {
+#if 0
+         internal_warning ("read_comsub: ignored null byte in input");
+#endif
+         continue;
+       }
+
+      /* Add the character to ISTRING, possibly after resizing it. */
+      RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE);
+
+      /* This is essentially quote_string inline */
+      if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) /* || c == CTLESC || c == CTLNUL */)
+       istring[istring_index++] = CTLESC;
+      /* Escape CTLESC and CTLNUL in the output to protect those characters
+        from the rest of the word expansions (word splitting and globbing.)
+        This is essentially quote_escapes inline. */
+      else if (skip_ctlesc == 0 && c == CTLESC)
+       {
+         tflag |= W_HASCTLESC;
+         istring[istring_index++] = CTLESC;
+       }
+      else if ((skip_ctlnul == 0 && c == CTLNUL) || (c == ' ' && (ifs_value && *ifs_value == 0)))
+       istring[istring_index++] = CTLESC;
+
+      istring[istring_index++] = c;
+
+#if 0
+#if defined (__CYGWIN__)
+      if (c == '\n' && istring_index > 1 && istring[istring_index - 2] == '\r')
+       {
+         istring_index--;
+         istring[istring_index - 1] = '\n';
+       }
+#endif
+#endif
+    }
+
+  if (istring)
+    istring[istring_index] = '\0';
+
+  /* If we read no output, just return now and save ourselves some
+     trouble. */
+  if (istring_index == 0)
+    {
+      FREE (istring);
+      if (rflag)
+       *rflag = tflag;
+      return (char *)NULL;
+    }
+
+  /* Strip trailing newlines from the output of the command. */
+  if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+    {
+      while (istring_index > 0)
+       {
+         if (istring[istring_index - 1] == '\n')
+           {
+             --istring_index;
+
+             /* If the newline was quoted, remove the quoting char. */
+             if (istring[istring_index - 1] == CTLESC)
+               --istring_index;
+           }
+         else
+           break;
+       }
+      istring[istring_index] = '\0';
+    }
+  else
+    strip_trailing (istring, istring_index - 1, 1);
+
+  if (rflag)
+    *rflag = tflag;
+  return istring;
+}
+
+/* Perform command substitution on STRING.  This returns a WORD_DESC * with the
+   contained string possibly quoted. */
+WORD_DESC *
+command_substitute (string, quoted)
+     char *string;
+     int quoted;
+{
+  pid_t pid, old_pid, old_pipeline_pgrp, old_async_pid;
+  char *istring;
+  int result, fildes[2], function_value, pflags, rc, tflag;
+  WORD_DESC *ret;
+
+  istring = (char *)NULL;
+
+  /* Don't fork () if there is no need to.  In the case of no command to
+     run, just return NULL. */
+  if (!string || !*string || (string[0] == '\n' && !string[1]))
+    return ((WORD_DESC *)NULL);
+
+  if (wordexp_only && read_but_dont_execute)
+    {
+      last_command_exit_value = EX_WEXPCOMSUB;
+      jump_to_top_level (EXITPROG);
+    }
+
+  /* We're making the assumption here that the command substitution will
+     eventually run a command from the file system.  Since we'll run
+     maybe_make_export_env in this subshell before executing that command,
+     the parent shell and any other shells it starts will have to remake
+     the environment.  If we make it before we fork, other shells won't
+     have to.  Don't bother if we have any temporary variable assignments,
+     though, because the export environment will be remade after this
+     command completes anyway, but do it if all the words to be expanded
+     are variable assignments. */
+  if (subst_assign_varlist == 0 || garglist == 0)
+    maybe_make_export_env ();  /* XXX */
+
+  /* Flags to pass to parse_and_execute() */
+  pflags = (interactive && sourcelevel == 0) ? SEVAL_RESETLINE : 0;
+
+  /* Pipe the output of executing STRING into the current shell. */
+  if (pipe (fildes) < 0)
+    {
+      sys_error (_("cannot make pipe for command substitution"));
+      goto error_exit;
+    }
+
+  old_pid = last_made_pid;
+#if defined (JOB_CONTROL)
+  old_pipeline_pgrp = pipeline_pgrp;
+  /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline. */
+  if ((subshell_environment & SUBSHELL_PIPE) == 0)
+    pipeline_pgrp = shell_pgrp;
+  cleanup_the_pipeline ();
+#endif /* JOB_CONTROL */
+
+  old_async_pid = last_asynchronous_pid;
+  pid = make_child ((char *)NULL, subshell_environment&SUBSHELL_ASYNC);
+  last_asynchronous_pid = old_async_pid;
+
+  if (pid == 0)
+    {
+      /* Reset the signal handlers in the child, but don't free the
+        trap strings.  Set a flag noting that we have to free the
+        trap strings if we run trap to change a signal disposition. */
+      reset_signal_handlers ();
+      subshell_environment |= SUBSHELL_RESETTRAP;
+    }
+
+#if defined (JOB_CONTROL)
+  /* XXX DO THIS ONLY IN PARENT ? XXX */
+  set_sigchld_handler ();
+  stop_making_children ();
+  if (pid != 0)
+    pipeline_pgrp = old_pipeline_pgrp;
+#else
+  stop_making_children ();
+#endif /* JOB_CONTROL */
+
+  if (pid < 0)
+    {
+      sys_error (_("cannot make child for command substitution"));
+    error_exit:
+
+      last_made_pid = old_pid;
+
+      FREE (istring);
+      close (fildes[0]);
+      close (fildes[1]);
+      return ((WORD_DESC *)NULL);
+    }
+
+  if (pid == 0)
+    {
+      set_sigint_handler ();   /* XXX */
+
+      free_pushed_string_input ();
+
+      if (dup2 (fildes[1], 1) < 0)
+       {
+         sys_error (_("command_substitute: cannot duplicate pipe as fd 1"));
+         exit (EXECUTION_FAILURE);
+       }
+
+      /* If standard output is closed in the parent shell
+        (such as after `exec >&-'), file descriptor 1 will be
+        the lowest available file descriptor, and end up in
+        fildes[0].  This can happen for stdin and stderr as well,
+        but stdout is more important -- it will cause no output
+        to be generated from this command. */
+      if ((fildes[1] != fileno (stdin)) &&
+         (fildes[1] != fileno (stdout)) &&
+         (fildes[1] != fileno (stderr)))
+       close (fildes[1]);
+
+      if ((fildes[0] != fileno (stdin)) &&
+         (fildes[0] != fileno (stdout)) &&
+         (fildes[0] != fileno (stderr)))
+       close (fildes[0]);
+
+#ifdef __CYGWIN__
+      /* Let stdio know the fd may have changed from text to binary mode, and
+        make sure to preserve stdout line buffering. */
+      freopen (NULL, "w", stdout);
+      sh_setlinebuf (stdout);
+#endif /* __CYGWIN__ */
+
+      /* The currently executing shell is not interactive. */
+      interactive = 0;
+
+      /* This is a subshell environment. */
+      subshell_environment |= SUBSHELL_COMSUB;
+
+      /* When not in POSIX mode, command substitution does not inherit
+        the -e flag. */
+      if (posixly_correct == 0)
+        {
+          builtin_ignoring_errexit = 0;
+         change_flag ('e', FLAG_OFF);
+         set_shellopts ();
+        }
+
+      remove_quoted_escapes (string);
+
+      startup_state = 2;       /* see if we can avoid a fork */
+      /* Give command substitution a place to jump back to on failure,
+        so we don't go back up to main (). */
+      result = setjmp_nosigs (top_level);
+
+      /* If we're running a command substitution inside a shell function,
+        trap `return' so we don't return from the function in the subshell
+        and go off to never-never land. */
+      if (result == 0 && return_catch_flag)
+       function_value = setjmp_nosigs (return_catch);
+      else
+       function_value = 0;
+
+      if (result == ERREXIT)
+       rc = last_command_exit_value;
+      else if (result == EXITPROG)
+       rc = last_command_exit_value;
+      else if (result)
+       rc = EXECUTION_FAILURE;
+      else if (function_value)
+       rc = return_catch_value;
+      else
+       {
+         subshell_level++;
+         rc = parse_and_execute (string, "command substitution", pflags|SEVAL_NOHIST);
+         subshell_level--;
+       }
+
+      last_command_exit_value = rc;
+      rc = run_exit_trap ();
+#if defined (PROCESS_SUBSTITUTION)
+      unlink_fifo_list ();
+#endif
+      exit (rc);
+    }
+  else
+    {
+#if defined (JOB_CONTROL) && defined (PGRP_PIPE)
+      close_pgrp_pipe ();
+#endif /* JOB_CONTROL && PGRP_PIPE */
+
+      close (fildes[1]);
+
+      tflag = 0;
+      istring = read_comsub (fildes[0], quoted, &tflag);
+
+      close (fildes[0]);
+
+      current_command_subst_pid = pid;
+      last_command_exit_value = wait_for (pid);
+      last_command_subst_pid = pid;
+      last_made_pid = old_pid;
+
+#if defined (JOB_CONTROL)
+      /* If last_command_exit_value > 128, then the substituted command
+        was terminated by a signal.  If that signal was SIGINT, then send
+        SIGINT to ourselves.  This will break out of loops, for instance. */
+      if (last_command_exit_value == (128 + SIGINT) && last_command_exit_signal == SIGINT)
+       kill (getpid (), SIGINT);
+
+      /* wait_for gives the terminal back to shell_pgrp.  If some other
+        process group should have it, give it away to that group here.
+        pipeline_pgrp is non-zero only while we are constructing a
+        pipline, so what we are concerned about is whether or not that
+        pipeline was started in the background.  A pipeline started in
+        the background should never get the tty back here. */
+      if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0)
+       give_terminal_to (pipeline_pgrp, 0);
+#endif /* JOB_CONTROL */
+
+      ret = alloc_word_desc ();
+      ret->word = istring;
+      ret->flags = tflag;
+
+      return ret;
+    }
+}
+
+/********************************************************
+ *                                                     *
+ *     Utility functions for parameter expansion       *
+ *                                                     *
+ ********************************************************/
+
+#if defined (ARRAY_VARS)
+
+static arrayind_t
+array_length_reference (s)
+     char *s;
+{
+  int len;
+  arrayind_t ind;
+  char *akey;
+  char *t, c;
+  ARRAY *array;
+  HASH_TABLE *h;
+  SHELL_VAR *var;
+
+  var = array_variable_part (s, &t, &len);
+
+  /* If unbound variables should generate an error, report one and return
+     failure. */
+  if ((var == 0 || invisible_p (var) || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error)
+    {
+      c = *--t;
+      *t = '\0';
+      last_command_exit_value = EXECUTION_FAILURE;
+      err_unboundvar (s);
+      *t = c;
+      return (-1);
+    }
+  else if (var == 0 || invisible_p (var))
+    return 0;
+
+  /* We support a couple of expansions for variables that are not arrays.
+     We'll return the length of the value for v[0], and 1 for v[@] or
+     v[*].  Return 0 for everything else. */
+
+  array = array_p (var) ? array_cell (var) : (ARRAY *)NULL;
+  h = assoc_p (var) ? assoc_cell (var) : (HASH_TABLE *)NULL;
+
+  if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
+    {
+      if (assoc_p (var))
+       return (h ? assoc_num_elements (h) : 0);
+      else if (array_p (var))
+       return (array ? array_num_elements (array) : 0);
+      else
+       return (var_isset (var) ? 1 : 0);
+    }
+
+  if (assoc_p (var))
+    {
+      t[len - 1] = '\0';
+      akey = expand_assignment_string_to_string (t, 0);        /* [ */
+      t[len - 1] = ']';
+      if (akey == 0 || *akey == 0)
+       {
+         err_badarraysub (t);
+         FREE (akey);
+         return (-1);
+       }
+      t = assoc_reference (assoc_cell (var), akey);
+      free (akey);
+    }
+  else
+    {
+      ind = array_expand_index (var, t, len);
+      /* negative subscripts to indexed arrays count back from end */
+      if (var && array_p (var) && ind < 0)
+       ind = array_max_index (array_cell (var)) + 1 + ind;
+      if (ind < 0)
+       {
+         err_badarraysub (t);
+         return (-1);
+       }
+      if (array_p (var))
+       t = array_reference (array, ind);
+      else
+       t = (ind == 0) ? value_cell (var) : (char *)NULL;
+    }
+
+  len = MB_STRLEN (t);
+  return (len);
+}
+#endif /* ARRAY_VARS */
+
+static int
+valid_brace_expansion_word (name, var_is_special)
+     char *name;
+     int var_is_special;
+{
+  if (DIGIT (*name) && all_digits (name))
+    return 1;
+  else if (var_is_special)
+    return 1;
+#if defined (ARRAY_VARS)
+  else if (valid_array_reference (name))
+    return 1;
+#endif /* ARRAY_VARS */
+  else if (legal_identifier (name))
+    return 1;
+  else
+    return 0;
+}
+
+static int
+chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at)
+     char *name;
+     int quoted;
+     int *quoted_dollar_atp, *contains_dollar_at;
+{
+  char *temp1;
+
+  if (name == 0)
+    {
+      if (quoted_dollar_atp)
+       *quoted_dollar_atp = 0;
+      if (contains_dollar_at)
+       *contains_dollar_at = 0;
+      return 0;
+    }
+
+  /* check for $@ and $* */
+  if (name[0] == '@' && name[1] == 0)
+    {
+      if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+       *quoted_dollar_atp = 1;
+      if (contains_dollar_at)
+       *contains_dollar_at = 1;
+      return 1;
+    }
+  else if (name[0] == '*' && name[1] == '\0' && quoted == 0)
+    {
+      if (contains_dollar_at)
+       *contains_dollar_at = 1;
+      return 1;
+    }
+
+  /* Now check for ${array[@]} and ${array[*]} */
+#if defined (ARRAY_VARS)
+  else if (valid_array_reference (name))
+    {
+      temp1 = mbschr (name, '[');
+      if (temp1 && temp1[1] == '@' && temp1[2] == ']')
+       {
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+           *quoted_dollar_atp = 1;
+         if (contains_dollar_at)
+           *contains_dollar_at = 1;
+         return 1;
+       }       /* [ */
+      /* ${array[*]}, when unquoted, should be treated like ${array[@]},
+        which should result in separate words even when IFS is unset. */
+      if (temp1 && temp1[1] == '*' && temp1[2] == ']' && quoted == 0)
+       {
+         if (contains_dollar_at)
+           *contains_dollar_at = 1;
+         return 1;
+       }
+    }
+#endif
+  return 0;
+}
+
+/* Parameter expand NAME, and return a new string which is the expansion,
+   or NULL if there was no expansion.
+   VAR_IS_SPECIAL is non-zero if NAME is one of the special variables in
+   the shell, e.g., "@", "$", "*", etc.  QUOTED, if non-zero, means that
+   NAME was found inside of a double-quoted expression. */
+static WORD_DESC *
+parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp)
+     char *name;
+     int var_is_special, quoted, pflags;
+     arrayind_t *indp;
+{
+  WORD_DESC *ret;
+  char *temp, *tt;
+  intmax_t arg_index;
+  SHELL_VAR *var;
+  int atype, rflags;
+  arrayind_t ind;
+
+  ret = 0;
+  temp = 0;
+  rflags = 0;
+
+  if (indp)
+    *indp = INTMAX_MIN;
+
+  /* Handle multiple digit arguments, as in ${11}. */  
+  if (legal_number (name, &arg_index))
+    {
+      tt = get_dollar_var_value (arg_index);
+      if (tt)
+       temp = (*tt && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+                 ? quote_string (tt)
+                 : quote_escapes (tt);
+      else
+        temp = (char *)NULL;
+      FREE (tt);
+    }
+  else if (var_is_special)      /* ${@} */
+    {
+      int sindex;
+      tt = (char *)xmalloc (2 + strlen (name));
+      tt[sindex = 0] = '$';
+      strcpy (tt + 1, name);
+
+      ret = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL,
+                         (int *)NULL, (int *)NULL, pflags);
+      free (tt);
+    }
+#if defined (ARRAY_VARS)
+  else if (valid_array_reference (name))
+    {
+expand_arrayref:
+      /* XXX - does this leak if name[@] or name[*]? */
+      if (pflags & PF_ASSIGNRHS)
+        {
+          temp = array_variable_name (name, &tt, (int *)0);
+          if (ALL_ELEMENT_SUB (tt[0]) && tt[1] == ']')
+           temp = array_value (name, quoted|Q_DOUBLE_QUOTES, 0, &atype, &ind);
+         else
+           temp = array_value (name, quoted, 0, &atype, &ind);
+        }
+      else
+       temp = array_value (name, quoted, 0, &atype, &ind);
+      if (atype == 0 && temp)
+       {
+         temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+                   ? quote_string (temp)
+                   : quote_escapes (temp);
+         rflags |= W_ARRAYIND;
+         if (indp)
+           *indp = ind;
+       }                 
+      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))
+    {
+      if (var_isset (var) && invisible_p (var) == 0)
+       {
+#if defined (ARRAY_VARS)
+         if (assoc_p (var))
+           temp = assoc_reference (assoc_cell (var), "0");
+         else if (array_p (var))
+           temp = array_reference (array_cell (var), 0);
+         else
+           temp = value_cell (var);
+#else
+         temp = value_cell (var);
+#endif
+
+         if (temp)
+           temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+                     ? quote_string (temp)
+                     : quote_escapes (temp);
+       }
+      else
+       temp = (char *)NULL;
+    }
+  else if (var = find_variable_last_nameref (name))
+    {
+      temp = nameref_cell (var);
+#if defined (ARRAY_VARS)
+      /* Handle expanding nameref whose value is x[n] */
+      if (temp && *temp && valid_array_reference (temp))
+       {
+         name = temp;
+         goto expand_arrayref;
+       }
+      else
+#endif
+      /* y=2 ; typeset -n x=y; echo ${x} is not the same as echo ${2} in ksh */
+      if (temp && *temp && legal_identifier (temp) == 0)
+        {
+         last_command_exit_value = EXECUTION_FAILURE;
+         report_error (_("%s: invalid variable name for name reference"), temp);
+         temp = &expand_param_error;
+        }
+      else
+       temp = (char *)NULL;
+    }
+  else
+    temp = (char *)NULL;
+
+  if (ret == 0)
+    {
+      ret = alloc_word_desc ();
+      ret->word = temp;
+      ret->flags |= rflags;
+    }
+  return ret;
+}
+
+static char *
+parameter_brace_find_indir (name, var_is_special, quoted, find_nameref)
+     char *name;
+     int var_is_special, quoted, find_nameref;
+{
+  char *temp, *t;
+  WORD_DESC *w;
+  SHELL_VAR *v;
+
+  if (find_nameref && var_is_special == 0 && (v = find_variable_last_nameref (name)) &&
+      nameref_p (v) && (t = nameref_cell (v)) && *t)
+    return (savestring (t));
+
+  /* If var_is_special == 0, and name is not an array reference, this does
+     more expansion than necessary.  It should really look up the variable's
+     value and not try to expand it. */
+  w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND, 0);
+  t = w->word;
+  /* Have to dequote here if necessary */
+  if (t)
+    {
+      temp = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+               ? dequote_string (t)
+               : dequote_escapes (t);
+      free (t);
+      t = temp;
+    }
+  dispose_word_desc (w);
+
+  return t;
+}
+  
+/* Expand an indirect reference to a variable: ${!NAME} expands to the
+   value of the variable whose name is the value of NAME. */
+static WORD_DESC *
+parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at)
+     char *name;
+     int var_is_special, quoted;
+     int *quoted_dollar_atp, *contains_dollar_at;
+{
+  char *temp, *t;
+  WORD_DESC *w;
+  SHELL_VAR *v;
+
+  /* See if it's a nameref first, behave in ksh93-compatible fashion.
+     There is at least one incompatibility: given ${!foo[0]} where foo=bar,
+     bash performs an indirect lookup on foo[0] and expands the result;
+     ksh93 expands bar[0].  We could do that here -- there are enough usable
+     primitives to do that -- but do not at this point. */
+  if (var_is_special == 0 && (v = find_variable_last_nameref (name)))
+    {
+      if (nameref_p (v) && (t = nameref_cell (v)) && *t)
+       {
+         w = alloc_word_desc ();
+         w->word = savestring (t);
+         w->flags = 0;
+         return w;
+       }
+    }
+
+  t = parameter_brace_find_indir (name, var_is_special, quoted, 0);
+
+  chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at);
+  if (t == 0)
+    return (WORD_DESC *)NULL;
+
+  w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, 0, 0);
+  free (t);
+
+  return w;
+}
+
+/* Expand the right side of a parameter expansion of the form ${NAMEcVALUE},
+   depending on the value of C, the separating character.  C can be one of
+   "-", "+", or "=".  QUOTED is true if the entire brace expression occurs
+   between double quotes. */
+static WORD_DESC *
+parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat)
+     char *name, *value;
+     int c, quoted, *qdollaratp, *hasdollarat;
+{
+  WORD_DESC *w;
+  WORD_LIST *l;
+  char *t, *t1, *temp;
+  int hasdol;
+
+  /* If the entire expression is between double quotes, we want to treat
+     the value as a double-quoted string, with the exception that we strip
+     embedded unescaped double quotes (for sh backwards compatibility). */
+  if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *value)
+    {
+      hasdol = 0;
+      temp = string_extract_double_quoted (value, &hasdol, 1);
+    }
+  else
+    temp = value;
+
+  w = alloc_word_desc ();
+  hasdol = 0;
+  /* XXX was 0 not quoted */
+  l = *temp ? expand_string_for_rhs (temp, quoted, &hasdol, (int *)NULL)
+           : (WORD_LIST *)0;
+  if (hasdollarat)
+    *hasdollarat = hasdol || (l && l->next);
+  if (temp != value)
+    free (temp);
+  if (l)
+    {
+      /* The expansion of TEMP returned something.  We need to treat things
+         slightly differently if HASDOL is non-zero.  If we have "$@", the
+         individual words have already been quoted.  We need to turn them
+         into a string with the words separated by the first character of
+         $IFS without any additional quoting, so string_list_dollar_at won't
+         do the right thing.  We use string_list_dollar_star instead. */
+      temp = (hasdol || l->next) ? string_list_dollar_star (l) : string_list (l);
+
+      /* If l->next is not null, we know that TEMP contained "$@", since that
+        is the only expansion that creates more than one word. */
+      if (qdollaratp && ((hasdol && quoted) || l->next))
+       *qdollaratp = 1;
+      /* If we have a quoted null result (QUOTED_NULL(temp)) and the word is
+        a quoted null (l->next == 0 && QUOTED_NULL(l->word->word)), the
+        flags indicate it (l->word->flags & W_HASQUOTEDNULL), and the
+        expansion is quoted (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+        (which is more paranoia than anything else), we need to return the
+        quoted null string and set the flags to indicate it. */
+      if (l->next == 0 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp) && QUOTED_NULL (l->word->word) && (l->word->flags & W_HASQUOTEDNULL))
+       {
+         w->flags |= W_HASQUOTEDNULL;
+       }
+      dispose_words (l);
+    }
+  else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && hasdol)
+    {
+      /* The brace expansion occurred between double quotes and there was
+        a $@ in TEMP.  It does not matter if the $@ is quoted, as long as
+        it does not expand to anything.  In this case, we want to return
+        a quoted empty string. */
+      temp = make_quoted_char ('\0');
+      w->flags |= W_HASQUOTEDNULL;
+    }
+  else
+    temp = (char *)NULL;
+
+  if (c == '-' || c == '+')
+    {
+      w->word = temp;
+      return w;
+    }
+
+  /* c == '=' */
+  t = temp ? savestring (temp) : savestring ("");
+  t1 = dequote_string (t);
+  free (t);
+#if defined (ARRAY_VARS)
+  if (valid_array_reference (name))
+    assign_array_element (name, t1, 0);
+  else
+#endif /* ARRAY_VARS */
+  bind_variable (name, t1, 0);
+#if 0
+  if (STREQ (name, "IFS") == 0)
+#endif
+    stupidly_hack_special_variables (name);
+
+  /* From Posix group discussion Feb-March 2010.  Issue 7 0000221 */
+  free (temp);
+
+  w->word = t1;
+  return w;
+}
+
+/* Deal with the right hand side of a ${name:?value} expansion in the case
+   that NAME is null or not set.  If VALUE is non-null it is expanded and
+   used as the error message to print, otherwise a standard message is
+   printed. */
+static void
+parameter_brace_expand_error (name, value)
+     char *name, *value;
+{
+  WORD_LIST *l;
+  char *temp;
+
+  last_command_exit_value = EXECUTION_FAILURE; /* ensure it's non-zero */
+  if (value && *value)
+    {
+      l = expand_string (value, 0);
+      temp =  string_list (l);
+      report_error ("%s: %s", name, temp ? temp : ""); /* XXX was value not "" */
+      FREE (temp);
+      dispose_words (l);
+    }
+  else
+    report_error (_("%s: parameter null or not set"), name);
+
+  /* Free the data we have allocated during this expansion, since we
+     are about to longjmp out. */
+  free (name);
+  FREE (value);
+}
+
+/* Return 1 if NAME is something for which parameter_brace_expand_length is
+   OK to do. */
+static int
+valid_length_expression (name)
+     char *name;
+{
+  return (name[1] == '\0' ||                                   /* ${#} */
+         ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') ||  /* special param */
+         (DIGIT (name[1]) && all_digits (name + 1)) || /* ${#11} */
+#if defined (ARRAY_VARS)
+         valid_array_reference (name + 1) ||                   /* ${#a[7]} */
+#endif
+         legal_identifier (name + 1));                         /* ${#PS1} */
+}
+
+/* Handle the parameter brace expansion that requires us to return the
+   length of a parameter. */
+static intmax_t
+parameter_brace_expand_length (name)
+     char *name;
+{
+  char *t, *newname;
+  intmax_t number, arg_index;
+  WORD_LIST *list;
+#if defined (ARRAY_VARS)
+  SHELL_VAR *var;
+#endif
+
+  if (name[1] == '\0')                 /* ${#} */
+    number = number_of_args ();
+  else if ((name[1] == '@' || name[1] == '*') && name[2] == '\0')      /* ${#@}, ${#*} */
+    number = number_of_args ();
+  else if ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0')
+    {
+      /* Take the lengths of some of the shell's special parameters. */
+      switch (name[1])
+       {
+       case '-':
+         t = which_set_flags ();
+         break;
+       case '?':
+         t = itos (last_command_exit_value);
+         break;
+       case '$':
+         t = itos (dollar_dollar_pid);
+         break;
+       case '!':
+         if (last_asynchronous_pid == NO_PID)
+           t = (char *)NULL;   /* XXX - error if set -u set? */
+         else
+           t = itos (last_asynchronous_pid);
+         break;
+       case '#':
+         t = itos (number_of_args ());
+         break;
+       }
+      number = STRLEN (t);
+      FREE (t);
+    }
+#if defined (ARRAY_VARS)
+  else if (valid_array_reference (name + 1))
+    number = array_length_reference (name + 1);
+#endif /* ARRAY_VARS */
+  else
+    {
+      number = 0;
+
+      if (legal_number (name + 1, &arg_index))         /* ${#1} */
+       {
+         t = get_dollar_var_value (arg_index);
+         if (t == 0 && unbound_vars_is_error)
+           return INTMAX_MIN;
+         number = MB_STRLEN (t);
+         FREE (t);
+       }
+#if defined (ARRAY_VARS)
+      else if ((var = find_variable (name + 1)) && (invisible_p (var) == 0) && (array_p (var) || assoc_p (var)))
+       {
+         if (assoc_p (var))
+           t = assoc_reference (assoc_cell (var), "0");
+         else
+           t = array_reference (array_cell (var), 0);
+         if (t == 0 && unbound_vars_is_error)
+           return INTMAX_MIN;
+         number = MB_STRLEN (t);
+       }
+#endif
+      else                             /* ${#PS1} */
+       {
+         newname = savestring (name);
+         newname[0] = '$';
+         list = expand_string (newname, Q_DOUBLE_QUOTES);
+         t = list ? string_list (list) : (char *)NULL;
+         free (newname);
+         if (list)
+           dispose_words (list);
+
+         number = t ? MB_STRLEN (t) : 0;
+         FREE (t);
+       }
+    }
+
+  return (number);
+}
+
+/* Skip characters in SUBSTR until DELIM.  SUBSTR is an arithmetic expression,
+   so we do some ad-hoc parsing of an arithmetic expression to find
+   the first DELIM, instead of using strchr(3).  Two rules:
+       1.  If the substring contains a `(', read until closing `)'.
+       2.  If the substring contains a `?', read past one `:' for each `?'.
+*/
+
+static char *
+skiparith (substr, delim)
+     char *substr;
+     int delim;
+{
+  size_t sublen;
+  int skipcol, pcount, i;
+  DECLARE_MBSTATE;
+
+  sublen = strlen (substr);
+  i = skipcol = pcount = 0;
+  while (substr[i])
+    {
+      /* Balance parens */
+      if (substr[i] == LPAREN)
+       {
+         pcount++;
+         i++;
+         continue;
+       }
+      if (substr[i] == RPAREN && pcount)
+       {
+         pcount--;
+         i++;
+         continue;
+       }
+      if (pcount)
+       {
+         ADVANCE_CHAR (substr, sublen, i);
+         continue;
+       }
+
+      /* Skip one `:' for each `?' */
+      if (substr[i] == ':' && skipcol)
+       {
+         skipcol--;
+         i++;
+         continue;
+       }
+      if (substr[i] == delim)
+       break;
+      if (substr[i] == '?')
+       {
+         skipcol++;
+         i++;
+         continue;
+       }
+      ADVANCE_CHAR (substr, sublen, i);
+    }
+
+  return (substr + i);
+}
+
+/* Verify and limit the start and end of the desired substring.  If
+   VTYPE == 0, a regular shell variable is being used; if it is 1,
+   then the positional parameters are being used; if it is 2, then
+   VALUE is really a pointer to an array variable that should be used.
+   Return value is 1 if both values were OK, 0 if there was a problem
+   with an invalid expression, or -1 if the values were out of range. */
+static int
+verify_substring_values (v, value, substr, vtype, e1p, e2p)
+     SHELL_VAR *v;
+     char *value, *substr;
+     int vtype;
+     intmax_t *e1p, *e2p;
+{
+  char *t, *temp1, *temp2;
+  arrayind_t len;
+  int expok;
+#if defined (ARRAY_VARS)
+ ARRAY *a;
+ HASH_TABLE *h;
+#endif
+
+  /* duplicate behavior of strchr(3) */
+  t = skiparith (substr, ':');
+  if (*t && *t == ':')
+    *t = '\0';
+  else
+    t = (char *)0;
+
+  temp1 = expand_arith_string (substr, Q_DOUBLE_QUOTES);
+  *e1p = evalexp (temp1, &expok);
+  free (temp1);
+  if (expok == 0)
+    return (0);
+
+  len = -1;    /* paranoia */
+  switch (vtype)
+    {
+    case VT_VARIABLE:
+    case VT_ARRAYMEMBER:
+      len = MB_STRLEN (value);
+      break;
+    case VT_POSPARMS:
+      len = number_of_args () + 1;
+      if (*e1p == 0)
+       len++;          /* add one arg if counting from $0 */
+      break;
+#if defined (ARRAY_VARS)
+    case VT_ARRAYVAR:
+      /* For arrays, the first value deals with array indices.  Negative
+        offsets count from one past the array's maximum index.  Associative
+        arrays treat the number of elements as the maximum index. */
+      if (assoc_p (v))
+       {
+         h = assoc_cell (v);
+         len = assoc_num_elements (h) + (*e1p < 0);
+       }
+      else
+       {
+         a = (ARRAY *)value;
+         len = array_max_index (a) + (*e1p < 0);       /* arrays index from 0 to n - 1 */
+       }
+      break;
+#endif
+    }
+
+  if (len == -1)       /* paranoia */
+    return -1;
+
+  if (*e1p < 0)                /* negative offsets count from end */
+    *e1p += len;
+
+  if (*e1p > len || *e1p < 0)
+    return (-1);
+
+#if defined (ARRAY_VARS)
+  /* For arrays, the second offset deals with the number of elements. */
+  if (vtype == VT_ARRAYVAR)
+    len = assoc_p (v) ? assoc_num_elements (h) : array_num_elements (a);
+#endif
+
+  if (t)
+    {
+      t++;
+      temp2 = savestring (t);
+      temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES);
+      free (temp2);
+      t[-1] = ':';
+      *e2p = evalexp (temp1, &expok);
+      free (temp1);
+      if (expok == 0)
+       return (0);
+#if 1
+      if ((vtype == VT_ARRAYVAR || vtype == VT_POSPARMS) && *e2p < 0)
+#else
+      /* bash-4.3: allow positional parameter length < 0 to count backwards
+        from end of positional parameters */
+      if (vtype == VT_ARRAYVAR && *e2p < 0)
+#endif
+       {
+         internal_error (_("%s: substring expression < 0"), t);
+         return (0);
+       }
+#if defined (ARRAY_VARS)
+      /* In order to deal with sparse arrays, push the intelligence about how
+        to deal with the number of elements desired down to the array-
+        specific functions.  */
+      if (vtype != VT_ARRAYVAR)
+#endif
+       {
+         if (*e2p < 0)
+           {
+             *e2p += len;
+             if (*e2p < 0 || *e2p < *e1p)
+               {
+                 internal_error (_("%s: substring expression < 0"), t);
+                 return (0);
+               }
+           }
+         else
+           *e2p += *e1p;               /* want E2 chars starting at E1 */
+         if (*e2p > len)
+           *e2p = len;
+       }
+    }
+  else
+    *e2p = len;
+
+  return (1);
+}
+
+/* Return the type of variable specified by VARNAME (simple variable,
+   positional param, or array variable).  Also return the value specified
+   by VARNAME (value of a variable or a reference to an array element).
+   QUOTED is the standard description of quoting state, using Q_* defines.
+   FLAGS is currently a set of flags to pass to array_value.  If IND is
+   non-null and not INTMAX_MIN, and FLAGS includes AV_USEIND, IND is
+   passed to array_value so the array index is not computed again.
+   If this returns VT_VARIABLE, the caller assumes that CTLESC and CTLNUL
+   characters in the value are quoted with CTLESC and takes appropriate
+   steps.  For convenience, *VALP is set to the dequoted VALUE. */
+static int
+get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
+     char *varname, *value;
+     arrayind_t ind;
+     int quoted, flags;
+     SHELL_VAR **varp;
+     char **valp;
+{
+  int vtype, want_indir;
+  char *temp, *vname;
+  WORD_DESC *wd;
+#if defined (ARRAY_VARS)
+  SHELL_VAR *v;
+#endif
+  arrayind_t lind;
+
+  want_indir = *varname == '!' &&
+    (legal_variable_starter ((unsigned char)varname[1]) || DIGIT (varname[1])
+                                       || VALID_INDIR_PARAM (varname[1]));
+  if (want_indir)
+    vname = parameter_brace_find_indir (varname+1, SPECIAL_VAR (varname, 1), quoted, 1);
+  else
+    vname = varname;
+    
+  /* This sets vtype to VT_VARIABLE or VT_POSPARMS */
+  vtype = (vname[0] == '@' || vname[0] == '*') && vname[1] == '\0';
+  if (vtype == VT_POSPARMS && vname[0] == '*')
+    vtype |= VT_STARSUB;
+  *varp = (SHELL_VAR *)NULL;
+
+#if defined (ARRAY_VARS)
+  if (valid_array_reference (vname))
+    {
+      v = array_variable_part (vname, &temp, (int *)0);
+      /* If we want to signal array_value to use an already-computed index,
+        set LIND to that index */
+      lind = (ind != INTMAX_MIN && (flags & AV_USEIND)) ? ind : 0;
+      if (v && invisible_p (v))
+       {
+         vtype = VT_ARRAYMEMBER;
+         *varp = (SHELL_VAR *)NULL;
+         *valp = (char *)NULL;
+       }
+      if (v && (array_p (v) || assoc_p (v)))
+       { /* [ */
+         if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']')
+           {
+             /* Callers have to differentiate betwen indexed and associative */
+             vtype = VT_ARRAYVAR;
+             if (temp[0] == '*')
+               vtype |= VT_STARSUB;
+             *valp = array_p (v) ? (char *)array_cell (v) : (char *)assoc_cell (v);
+           }
+         else
+           {
+             vtype = VT_ARRAYMEMBER;
+             *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
+           }
+         *varp = v;
+       }
+      else if (v && (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']'))
+       {
+         vtype = VT_VARIABLE;
+         *varp = v;
+         if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+           *valp = dequote_string (value);
+         else
+           *valp = dequote_escapes (value);
+       }
+      else
+       {
+         vtype = VT_ARRAYMEMBER;
+         *varp = v;
+         *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
+       }
+    }
+  else if ((v = find_variable (vname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
+    {
+      vtype = VT_ARRAYMEMBER;
+      *varp = v;
+      *valp = assoc_p (v) ? assoc_reference (assoc_cell (v), "0") : array_reference (array_cell (v), 0);
+    }
+  else
+#endif
+    {
+      if (value && vtype == VT_VARIABLE)
+       {
+         if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+           *valp = dequote_string (value);
+         else
+           *valp = dequote_escapes (value);
+       }
+      else
+       *valp = value;
+    }
+
+  if (want_indir)
+    free (vname);
+
+  return vtype;
+}
+
+/******************************************************/
+/*                                                   */
+/* Functions to extract substrings of variable values */
+/*                                                   */
+/******************************************************/
+
+#if defined (HANDLE_MULTIBYTE)
+/* Character-oriented rather than strictly byte-oriented substrings.  S and
+   E, rather being strict indices into STRING, indicate character (possibly
+   multibyte character) positions that require calculation.
+   Used by the ${param:offset[:length]} expansion. */
+static char *
+mb_substring (string, s, e)
+     char *string;
+     int s, e;
+{
+  char *tt;
+  int start, stop, i, slen;
+  DECLARE_MBSTATE;
+
+  start = 0;
+  /* Don't need string length in ADVANCE_CHAR unless multibyte chars possible. */
+  slen = (MB_CUR_MAX > 1) ? STRLEN (string) : 0;
+
+  i = s;
+  while (string[start] && i--)
+    ADVANCE_CHAR (string, slen, start);
+  stop = start;
+  i = e - s;
+  while (string[stop] && i--)
+    ADVANCE_CHAR (string, slen, stop);
+  tt = substring (string, start, stop);
+  return tt;
+}
+#endif
+  
+/* Process a variable substring expansion: ${name:e1[:e2]}.  If VARNAME
+   is `@', use the positional parameters; otherwise, use the value of
+   VARNAME.  If VARNAME is an array variable, use the array elements. */
+
+static char *
+parameter_brace_substring (varname, value, ind, substr, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *substr;
+     int quoted, flags;
+{
+  intmax_t e1, e2;
+  int vtype, r, starsub;
+  char *temp, *val, *tt, *oname;
+  SHELL_VAR *v;
+
+  if (value == 0)
+    return ((char *)NULL);
+
+  oname = this_command_name;
+  this_command_name = varname;
+
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  if (vtype == -1)
+    {
+      this_command_name = oname;
+      return ((char *)NULL);
+    }
+
+  starsub = vtype & VT_STARSUB;
+  vtype &= ~VT_STARSUB;
+
+  r = verify_substring_values (v, val, substr, vtype, &e1, &e2);
+  this_command_name = oname;
+  if (r <= 0)
+    {
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      return ((r == 0) ? &expand_param_error : (char *)NULL);
+    }
+
+  switch (vtype)
+    {
+    case VT_VARIABLE:
+    case VT_ARRAYMEMBER:
+#if defined (HANDLE_MULTIBYTE)
+      if (MB_CUR_MAX > 1)
+       tt = mb_substring (val, e1, e2);
+      else
+#endif
+      tt = substring (val, e1, e2);
+
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+       temp = quote_string (tt);
+      else
+       temp = tt ? quote_escapes (tt) : (char *)NULL;
+      FREE (tt);
+      break;
+    case VT_POSPARMS:
+      tt = pos_params (varname, e1, e2, quoted);
+      if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
+       {
+         temp = tt ? quote_escapes (tt) : (char *)NULL;
+         FREE (tt);
+       }
+      else
+       temp = tt;
+      break;
+#if defined (ARRAY_VARS)
+    case VT_ARRAYVAR:
+      if (assoc_p (v))
+       /* we convert to list and take first e2 elements starting at e1th
+          element -- officially undefined for now */   
+       temp = assoc_subrange (assoc_cell (v), e1, e2, starsub, quoted);
+      else
+      /* We want E2 to be the number of elements desired (arrays can be sparse,
+        so verify_substring_values just returns the numbers specified and we
+        rely on array_subrange to understand how to deal with them). */
+       temp = array_subrange (array_cell (v), e1, e2, starsub, quoted);
+      /* array_subrange now calls array_quote_escapes as appropriate, so the
+        caller no longer needs to. */
+      break;
+#endif
+    default:
+      temp = (char *)NULL;
+    }
+
+  return temp;
+}
+
+/****************************************************************/
+/*                                                             */
+/* Functions to perform pattern substitution on variable values */
+/*                                                             */
+/****************************************************************/
+
+static int
+shouldexp_replacement (s)
+     char *s;
+{
+  register char *p;
+
+  for (p = s; p && *p; p++)
+    {
+      if (*p == '\\')
+       p++;
+      else if (*p == '&')
+       return 1;
+    }
+  return 0;
+}
+
+char *
+pat_subst (string, pat, rep, mflags)
+     char *string, *pat, *rep;
+     int mflags;
+{
+  char *ret, *s, *e, *str, *rstr, *mstr;
+  int rsize, rptr, l, replen, mtype, rxpand, rslen, mlen;
+
+  if (string  == 0)
+    return (savestring (""));
+
+  mtype = mflags & MATCH_TYPEMASK;
+
+#if 0  /* bash-4.2 ? */
+  rxpand = (rep && *rep) ? shouldexp_replacement (rep) : 0;
+#else
+  rxpand = 0;
+#endif
+
+  /* Special cases:
+   *   1.  A null pattern with mtype == MATCH_BEG means to prefix STRING
+   *       with REP and return the result.
+   *   2.  A null pattern with mtype == MATCH_END means to append REP to
+   *       STRING and return the result.
+   * These don't understand or process `&' in the replacement string.
+   */
+  if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END))
+    {
+      replen = STRLEN (rep);
+      l = STRLEN (string);
+      ret = (char *)xmalloc (replen + l + 2);
+      if (replen == 0)
+       strcpy (ret, string);
+      else if (mtype == MATCH_BEG)
+       {
+         strcpy (ret, rep);
+         strcpy (ret + replen, string);
+       }
+      else
+       {
+         strcpy (ret, string);
+         strcpy (ret + l, rep);
+       }
+      return (ret);
+    }
+
+  ret = (char *)xmalloc (rsize = 64);
+  ret[0] = '\0';
+
+  for (replen = STRLEN (rep), rptr = 0, str = string;;)
+    {
+      if (match_pattern (str, pat, mtype, &s, &e) == 0)
+       break;
+      l = s - str;
+
+      if (rxpand)
+        {
+          int x;
+          mlen = e - s;
+          mstr = xmalloc (mlen + 1);
+         for (x = 0; x < mlen; x++)
+           mstr[x] = s[x];
+          mstr[mlen] = '\0';
+          rstr = strcreplace (rep, '&', mstr, 0);
+          rslen = strlen (rstr);
+        }
+      else
+        {
+          rstr = rep;
+          rslen = replen;
+        }
+        
+      RESIZE_MALLOCED_BUFFER (ret, rptr, (l + rslen), rsize, 64);
+
+      /* OK, now copy the leading unmatched portion of the string (from
+        str to s) to ret starting at rptr (the current offset).  Then copy
+        the replacement string at ret + rptr + (s - str).  Increment
+        rptr (if necessary) and str and go on. */
+      if (l)
+       {
+         strncpy (ret + rptr, str, l);
+         rptr += l;
+       }
+      if (replen)
+       {
+         strncpy (ret + rptr, rstr, rslen);
+         rptr += rslen;
+       }
+      str = e;         /* e == end of match */
+
+      if (rstr != rep)
+       free (rstr);
+
+      if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY)
+       break;
+
+      if (s == e)
+       {
+         /* On a zero-length match, make sure we copy one character, since
+            we increment one character to avoid infinite recursion. */
+         RESIZE_MALLOCED_BUFFER (ret, rptr, 1, rsize, 64);
+         ret[rptr++] = *str++;
+         e++;          /* avoid infinite recursion on zero-length match */
+       }
+    }
+
+  /* Now copy the unmatched portion of the input string */
+  if (str && *str)
+    {
+      RESIZE_MALLOCED_BUFFER (ret, rptr, STRLEN(str) + 1, rsize, 64);
+      strcpy (ret + rptr, str);
+    }
+  else
+    ret[rptr] = '\0';
+
+  return ret;
+}
+
+/* Do pattern match and replacement on the positional parameters. */
+static char *
+pos_params_pat_subst (string, pat, rep, mflags)
+     char *string, *pat, *rep;
+     int mflags;
+{
+  WORD_LIST *save, *params;
+  WORD_DESC *w;
+  char *ret;
+  int pchar, qflags;
+
+  save = params = list_rest_of_args ();
+  if (save == 0)
+    return ((char *)NULL);
+
+  for ( ; params; params = params->next)
+    {
+      ret = pat_subst (params->word->word, pat, rep, mflags);
+      w = alloc_word_desc ();
+      w->word = ret ? ret : savestring ("");
+      dispose_word (params->word);
+      params->word = w;
+    }
+
+  pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
+  qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+
+  ret = string_list_pos_params (pchar, save, qflags);
+
+  dispose_words (save);
+
+  return (ret);
+}
+
+/* Perform pattern substitution on VALUE, which is the expansion of
+   VARNAME.  PATSUB is an expression supplying the pattern to match
+   and the string to substitute.  QUOTED is a flags word containing
+   the type of quoting currently in effect. */
+static char *
+parameter_brace_patsub (varname, value, ind, patsub, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *patsub;
+     int quoted, flags;
+{
+  int vtype, mflags, starsub, delim;
+  char *val, *temp, *pat, *rep, *p, *lpatsub, *tt;
+  SHELL_VAR *v;
+
+  if (value == 0)
+    return ((char *)NULL);
+
+  this_command_name = varname;
+
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  if (vtype == -1)
+    return ((char *)NULL);
+
+  starsub = vtype & VT_STARSUB;
+  vtype &= ~VT_STARSUB;
+
+  mflags = 0;
+  /* PATSUB is never NULL when this is called. */
+  if (*patsub == '/')
+    {
+      mflags |= MATCH_GLOBREP;
+      patsub++;
+    }
+
+  /* Malloc this because expand_string_if_necessary or one of the expansion
+     functions in its call chain may free it on a substitution error. */
+  lpatsub = savestring (patsub);
+
+  if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+    mflags |= MATCH_QUOTED;
+
+  if (starsub)
+    mflags |= MATCH_STARSUB;
+
+  /* If the pattern starts with a `/', make sure we skip over it when looking
+     for the replacement delimiter. */
+  delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0);
+  if (lpatsub[delim] == '/')
+    {
+      lpatsub[delim] = 0;
+      rep = lpatsub + delim + 1;
+    }
+  else
+    rep = (char *)NULL;
+
+  if (rep && *rep == '\0')
+    rep = (char *)NULL;
+
+  /* Perform the same expansions on the pattern as performed by the
+     pattern removal expansions. */
+  pat = getpattern (lpatsub, quoted, 1);
+
+  if (rep)
+    {
+      /* We want to perform quote removal on the expanded replacement even if
+        the entire expansion is double-quoted because the parser and string
+        extraction functions treated quotes in the replacement string as
+        special.  THIS IS NOT BACKWARDS COMPATIBLE WITH BASH-4.2. */
+      if (shell_compatibility_level > 42)
+       rep = expand_string_if_necessary (rep, quoted & ~(Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT), expand_string_unsplit);
+      /* This is the bash-4.2 code. */      
+      else if ((mflags & MATCH_QUOTED) == 0)
+       rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit);
+      else
+       rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit);
+    }
+
+  /* ksh93 doesn't allow the match specifier to be a part of the expanded
+     pattern.  This is an extension.  Make sure we don't anchor the pattern
+     at the beginning or end of the string if we're doing global replacement,
+     though. */
+  p = pat;
+  if (mflags & MATCH_GLOBREP)
+    mflags |= MATCH_ANY;
+  else if (pat && pat[0] == '#')
+    {
+      mflags |= MATCH_BEG;
+      p++;
+    }
+  else if (pat && pat[0] == '%')
+    {
+      mflags |= MATCH_END;
+      p++;
+    }
+  else
+    mflags |= MATCH_ANY;
+
+  /* OK, we now want to substitute REP for PAT in VAL.  If
+     flags & MATCH_GLOBREP is non-zero, the substitution is done
+     everywhere, otherwise only the first occurrence of PAT is
+     replaced.  The pattern matching code doesn't understand
+     CTLESC quoting CTLESC and CTLNUL so we use the dequoted variable
+     values passed in (VT_VARIABLE) so the pattern substitution
+     code works right.  We need to requote special chars after
+     we're done for VT_VARIABLE and VT_ARRAYMEMBER, and for the
+     other cases if QUOTED == 0, since the posparams and arrays
+     indexed by * or @ do special things when QUOTED != 0. */
+
+  switch (vtype)
+    {
+    case VT_VARIABLE:
+    case VT_ARRAYMEMBER:
+      temp = pat_subst (val, p, rep, mflags);
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      if (temp)
+       {
+         tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp);
+         free (temp);
+         temp = tt;
+       }
+      break;
+    case VT_POSPARMS:
+      temp = pos_params_pat_subst (val, p, rep, mflags);
+      if (temp && (mflags & MATCH_QUOTED) == 0)
+       {
+         tt = quote_escapes (temp);
+         free (temp);
+         temp = tt;
+       }
+      break;
+#if defined (ARRAY_VARS)
+    case VT_ARRAYVAR:
+      temp = assoc_p (v) ? assoc_patsub (assoc_cell (v), p, rep, mflags)
+                        : array_patsub (array_cell (v), p, rep, mflags);
+      /* Don't call quote_escapes anymore; array_patsub calls
+        array_quote_escapes as appropriate before adding the
+        space separators; ditto for assoc_patsub. */
+      break;
+#endif
+    }
+
+  FREE (pat);
+  FREE (rep);
+  free (lpatsub);
+
+  return temp;
+}
+
+/****************************************************************/
+/*                                                             */
+/*   Functions to perform case modification on variable values  */
+/*                                                             */
+/****************************************************************/
+
+/* Do case modification on the positional parameters. */
+
+static char *
+pos_params_modcase (string, pat, modop, mflags)
+     char *string, *pat;
+     int modop;
+     int mflags;
+{
+  WORD_LIST *save, *params;
+  WORD_DESC *w;
+  char *ret;
+  int pchar, qflags;
+
+  save = params = list_rest_of_args ();
+  if (save == 0)
+    return ((char *)NULL);
+
+  for ( ; params; params = params->next)
+    {
+      ret = sh_modcase (params->word->word, pat, modop);
+      w = alloc_word_desc ();
+      w->word = ret ? ret : savestring ("");
+      dispose_word (params->word);
+      params->word = w;
+    }
+
+  pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
+  qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+
+  ret = string_list_pos_params (pchar, save, qflags);
+  dispose_words (save);
+
+  return (ret);
+}
+
+/* Perform case modification on VALUE, which is the expansion of
+   VARNAME.  MODSPEC is an expression supplying the type of modification
+   to perform.  QUOTED is a flags word containing the type of quoting
+   currently in effect. */
+static char *
+parameter_brace_casemod (varname, value, ind, modspec, patspec, quoted, flags)
+     char *varname, *value;
+     int ind, modspec;
+     char *patspec;
+     int quoted, flags;
+{
+  int vtype, starsub, modop, mflags, x;
+  char *val, *temp, *pat, *p, *lpat, *tt;
+  SHELL_VAR *v;
+
+  if (value == 0)
+    return ((char *)NULL);
+
+  this_command_name = varname;
+
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  if (vtype == -1)
+    return ((char *)NULL);
+
+  starsub = vtype & VT_STARSUB;
+  vtype &= ~VT_STARSUB;
+
+  modop = 0;
+  mflags = 0;
+  if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+    mflags |= MATCH_QUOTED;
+  if (starsub)
+    mflags |= MATCH_STARSUB;
+  
+  p = patspec;
+  if (modspec == '^')
+    {
+      x = p && p[0] == modspec;
+      modop = x ? CASE_UPPER : CASE_UPFIRST;
+      p += x;
+    }
+  else if (modspec == ',')
+    {
+      x = p && p[0] == modspec;
+      modop = x ? CASE_LOWER : CASE_LOWFIRST;
+      p += x;
+    }
+  else if (modspec == '~')
+    {
+      x = p && p[0] == modspec;
+      modop = x ? CASE_TOGGLEALL : CASE_TOGGLE;
+      p += x;
+    }
+    
+  lpat = p ? savestring (p) : 0;
+  /* Perform the same expansions on the pattern as performed by the
+     pattern removal expansions.  FOR LATER */
+  pat = lpat ? getpattern (lpat, quoted, 1) : 0;
+
+  /* OK, now we do the case modification. */
+  switch (vtype)
+    {
+    case VT_VARIABLE:
+    case VT_ARRAYMEMBER:
+      temp = sh_modcase (val, pat, modop);
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      if (temp)
+       {
+         tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp);
+         free (temp);
+         temp = tt;
+       }
+      break;
+
+    case VT_POSPARMS:
+      temp = pos_params_modcase (val, pat, modop, mflags);
+      if (temp && (mflags & MATCH_QUOTED)  == 0)
+       {
+         tt = quote_escapes (temp);
+         free (temp);
+         temp = tt;
+       }
+      break;
+
+#if defined (ARRAY_VARS)
+    case VT_ARRAYVAR:
+      temp = assoc_p (v) ? assoc_modcase (assoc_cell (v), pat, modop, mflags)
+                        : array_modcase (array_cell (v), pat, modop, mflags);
+      /* Don't call quote_escapes; array_modcase calls array_quote_escapes
+        as appropriate before adding the space separators; ditto for
+        assoc_modcase. */
+      break;
+#endif
+    }
+
+  FREE (pat);
+  free (lpat);
+
+  return temp;
+}
+
+/* Check for unbalanced parens in S, which is the contents of $(( ... )).  If
+   any occur, this must be a nested command substitution, so return 0.
+   Otherwise, return 1.  A valid arithmetic expression must always have a
+   ( before a matching ), so any cases where there are more right parens
+   means that this must not be an arithmetic expression, though the parser
+   will not accept it without a balanced total number of parens. */
+static int
+chk_arithsub (s, len)
+     const char *s;
+     int len;
+{
+  int i, count;
+  DECLARE_MBSTATE;
+
+  i = count = 0;
+  while (i < len)
+    {
+      if (s[i] == LPAREN)
+       count++;
+      else if (s[i] == RPAREN)
+       {
+         count--;
+         if (count < 0)
+           return 0;
+       }
+
+      switch (s[i])
+       {
+       default:
+         ADVANCE_CHAR (s, len, i);
+         break;
+
+       case '\\':
+         i++;
+         if (s[i])
+           ADVANCE_CHAR (s, len, i);
+         break;
+
+       case '\'':
+         i = skip_single_quoted (s, len, ++i);
+         break;
+
+       case '"':
+         i = skip_double_quoted ((char *)s, len, ++i);
+         break;
+       }
+    }
+
+  return (count == 0);
+}
+
+/****************************************************************/
+/*                                                             */
+/*     Functions to perform parameter expansion on a string    */
+/*                                                             */
+/****************************************************************/
+
+/* ${[#][!]name[[:][^[^]][,[,]]#[#]%[%]-=?+[word][:e1[:e2]]]} */
+static WORD_DESC *
+parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, contains_dollar_at)
+     char *string;
+     int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at, pflags;
+{
+  int check_nullness, var_is_set, var_is_null, var_is_special;
+  int want_substring, want_indir, want_patsub, want_casemod;
+  char *name, *value, *temp, *temp1;
+  WORD_DESC *tdesc, *ret;
+  int t_index, sindex, c, tflag, modspec;
+  intmax_t number;
+  arrayind_t ind;
+
+  temp = temp1 = value = (char *)NULL;
+  var_is_set = var_is_null = var_is_special = check_nullness = 0;
+  want_substring = want_indir = want_patsub = want_casemod = 0;
+
+  sindex = *indexp;
+  t_index = ++sindex;
+  /* ${#var} doesn't have any of the other parameter expansions on it. */
+  if (string[t_index] == '#' && legal_variable_starter (string[t_index+1]))            /* {{ */
+    name = string_extract (string, &t_index, "}", SX_VARNAME);
+  else
+#if defined (CASEMOD_EXPANSIONS)
+    /* To enable case-toggling expansions using the `~' operator character
+       change the 1 to 0. */
+#  if defined (CASEMOD_CAPCASE)
+    name = string_extract (string, &t_index, "#%^,~:-=?+/}", SX_VARNAME);
+#  else
+    name = string_extract (string, &t_index, "#%^,:-=?+/}", SX_VARNAME);
+#  endif /* CASEMOD_CAPCASE */
+#else
+    name = string_extract (string, &t_index, "#%:-=?+/}", SX_VARNAME);
+#endif /* CASEMOD_EXPANSIONS */
+
+  ret = 0;
+  tflag = 0;
+
+  ind = INTMAX_MIN;
+
+  /* If the name really consists of a special variable, then make sure
+     that we have the entire name.  We don't allow indirect references
+     to special variables except `#', `?', `@' and `*'. */
+  if ((sindex == t_index && VALID_SPECIAL_LENGTH_PARAM (string[t_index])) ||
+      (sindex == t_index - 1 && string[sindex] == '!' && VALID_INDIR_PARAM (string[t_index])))
+    {
+      t_index++;
+      temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0);
+      name = (char *)xrealloc (name, 3 + (strlen (temp1)));
+      *name = string[sindex];
+      if (string[sindex] == '!')
+       {
+         /* indirect reference of $#, $?, $@, or $* */
+         name[1] = string[sindex + 1];
+         strcpy (name + 2, temp1);
+       }
+      else     
+       strcpy (name + 1, temp1);
+      free (temp1);
+    }
+  sindex = t_index;
+
+  /* Find out what character ended the variable name.  Then
+     do the appropriate thing. */
+  if (c = string[sindex])
+    sindex++;
+
+  /* If c is followed by one of the valid parameter expansion
+     characters, move past it as normal.  If not, assume that
+     a substring specification is being given, and do not move
+     past it. */
+  if (c == ':' && VALID_PARAM_EXPAND_CHAR (string[sindex]))
+    {
+      check_nullness++;
+      if (c = string[sindex])
+       sindex++;
+    }
+  else if (c == ':' && string[sindex] != RBRACE)
+    want_substring = 1;
+  else if (c == '/' /* && string[sindex] != RBRACE */) /* XXX */
+    want_patsub = 1;
+#if defined (CASEMOD_EXPANSIONS)
+  else if (c == '^' || c == ',' || c == '~')
+    {
+      modspec = c;
+      want_casemod = 1;
+    }
+#endif
+
+  /* Catch the valid and invalid brace expressions that made it through the
+     tests above. */
+  /* ${#-} is a valid expansion and means to take the length of $-.
+     Similarly for ${#?} and ${##}... */
+  if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 &&
+       VALID_SPECIAL_LENGTH_PARAM (c) && string[sindex] == RBRACE)
+    {
+      name = (char *)xrealloc (name, 3);
+      name[1] = c;
+      name[2] = '\0';
+      c = string[sindex++];
+    }
+
+  /* ...but ${#%}, ${#:}, ${#=}, ${#+}, and ${#/} are errors. */
+  if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 &&
+       member (c, "%:=+/") && string[sindex] == RBRACE)
+    {
+      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. */
+  want_indir = *name == '!' &&
+    (legal_variable_starter ((unsigned char)name[1]) || DIGIT (name[1])
+                                       || VALID_INDIR_PARAM (name[1]));
+
+  /* Determine the value of this variable. */
+
+  /* Check for special variables, directly referenced. */
+  if (SPECIAL_VAR (name, want_indir))
+    var_is_special++;
+
+  /* Check for special expansion things, like the length of a parameter */
+  if (*name == '#' && name[1])
+    {
+      /* If we are not pointing at the character just after the
+        closing brace, then we haven't gotten all of the name.
+        Since it begins with a special character, this is a bad
+        substitution.  Also check NAME for validity before trying
+        to go on. */
+      if (string[sindex - 1] != RBRACE || (valid_length_expression (name) == 0))
+       {
+         temp = (char *)NULL;
+         goto bad_substitution;
+       }
+
+      number = parameter_brace_expand_length (name);
+      if (number == INTMAX_MIN && unbound_vars_is_error)
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (name+1);
+         free (name);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+      free (name);
+
+      *indexp = sindex;
+      if (number < 0)
+        return (&expand_wdesc_error);
+      else
+       {
+         ret = alloc_word_desc ();
+         ret->word = itos (number);
+         return ret;
+       }
+    }
+
+  /* ${@} is identical to $@. */
+  if (name[0] == '@' && name[1] == '\0')
+    {
+      if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+       *quoted_dollar_atp = 1;
+
+      if (contains_dollar_at)
+       *contains_dollar_at = 1;
+
+      tflag |= W_DOLLARAT;
+    }
+
+  /* Process ${!PREFIX*} expansion. */
+  if (want_indir && string[sindex - 1] == RBRACE &&
+      (string[sindex - 2] == '*' || string[sindex - 2] == '@') &&
+      legal_variable_starter ((unsigned char) name[1]))
+    {
+      char **x;
+      WORD_LIST *xlist;
+
+      temp1 = savestring (name + 1);
+      number = strlen (temp1);
+      temp1[number - 1] = '\0';
+      x = all_variables_matching_prefix (temp1);
+      xlist = strvec_to_word_list (x, 0, 0);
+      if (string[sindex - 2] == '*')
+       temp = string_list_dollar_star (xlist);
+      else
+       {
+         temp = string_list_dollar_at (xlist, quoted);
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+           *quoted_dollar_atp = 1;
+         if (contains_dollar_at)
+           *contains_dollar_at = 1;
+
+         tflag |= W_DOLLARAT;
+       }
+      free (x);
+      dispose_words (xlist);
+      free (temp1);
+      *indexp = sindex;
+
+      free (name);
+
+      ret = alloc_word_desc ();
+      ret->word = temp;
+      ret->flags = tflag;      /* XXX */
+      return ret;
+    }
+
+#if defined (ARRAY_VARS)      
+  /* Process ${!ARRAY[@]} and ${!ARRAY[*]} expansion. */ /* [ */
+  if (want_indir && string[sindex - 1] == RBRACE &&
+      string[sindex - 2] == ']' && valid_array_reference (name+1))
+    {
+      char *x, *x1;
+
+      temp1 = savestring (name + 1);
+      x = array_variable_name (temp1, &x1, (int *)0);  /* [ */
+      FREE (x);
+      if (ALL_ELEMENT_SUB (x1[0]) && x1[1] == ']')
+       {
+         temp = array_keys (temp1, quoted);    /* handles assoc vars too */
+         if (x1[0] == '@')
+           {
+             if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+               *quoted_dollar_atp = 1;
+             if (contains_dollar_at)
+               *contains_dollar_at = 1;
+
+             tflag |= W_DOLLARAT;
+           }       
+
+         free (temp1);
+         *indexp = sindex;
+
+         ret = alloc_word_desc ();
+         ret->word = temp;
+         ret->flags = tflag;   /* XXX */
+         return ret;
+       }
+
+      free (temp1);
+    }
+#endif /* ARRAY_VARS */
+      
+  /* Make sure that NAME is valid before trying to go on. */
+  if (valid_brace_expansion_word (want_indir ? name + 1 : name,
+                                       var_is_special) == 0)
+    {
+      temp = (char *)NULL;
+      goto bad_substitution;
+    }
+
+  if (want_indir)
+    tdesc = parameter_brace_expand_indir (name + 1, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at);
+  else
+    tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND|(pflags&(PF_NOSPLIT2|PF_ASSIGNRHS)), &ind);
+
+  if (tdesc)
+    {
+      temp = tdesc->word;
+      tflag = tdesc->flags;
+      dispose_word_desc (tdesc);
+    }
+  else
+    temp = (char  *)0;
+
+  if (temp == &expand_param_error || temp == &expand_param_fatal)
+    {
+      FREE (name);
+      FREE (value);
+      return (temp == &expand_param_error ? &expand_wdesc_error : &expand_wdesc_fatal);
+    }
+
+#if defined (ARRAY_VARS)
+  if (valid_array_reference (name))
+    chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at);
+#endif
+
+  var_is_set = temp != (char *)0;
+  var_is_null = check_nullness && (var_is_set == 0 || *temp == 0);
+  /* XXX - this may not need to be restricted to special variables */
+  if (check_nullness)
+    var_is_null |= var_is_set && var_is_special && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp);
+
+  /* Get the rest of the stuff inside the braces. */
+  if (c && c != RBRACE)
+    {
+      /* Extract the contents of the ${ ... } expansion
+        according to the Posix.2 rules. */
+      value = extract_dollar_brace_string (string, &sindex, quoted, (c == '%' || c == '#' || c =='/' || c == '^' || c == ',' || c ==':') ? SX_POSIXEXP|SX_WORD : SX_WORD);
+      if (string[sindex] == RBRACE)
+       sindex++;
+      else
+       goto bad_substitution;
+    }
+  else
+    value = (char *)NULL;
+
+  *indexp = sindex;
+
+  /* All the cases where an expansion can possibly generate an unbound
+     variable error. */
+  if (want_substring || want_patsub || want_casemod || c == '#' || c == '%' || c == RBRACE)
+    {
+      if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1]))
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (name);
+         FREE (value);
+         FREE (temp);
+         free (name);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+    }
+    
+  /* If this is a substring spec, process it and add the result. */
+  if (want_substring)
+    {
+      temp1 = parameter_brace_substring (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      FREE (name);
+      FREE (value);
+      FREE (temp);
+
+      if (temp1 == &expand_param_error)
+       return (&expand_wdesc_error);
+      else if (temp1 == &expand_param_fatal)
+       return (&expand_wdesc_fatal);
+
+      ret = alloc_word_desc ();
+      ret->word = temp1;
+      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+      return ret;
+    }
+  else if (want_patsub)
+    {
+      temp1 = parameter_brace_patsub (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      FREE (name);
+      FREE (value);
+      FREE (temp);
+
+      if (temp1 == &expand_param_error)
+       return (&expand_wdesc_error);
+      else if (temp1 == &expand_param_fatal)
+       return (&expand_wdesc_fatal);
+
+      ret = alloc_word_desc ();
+      ret->word = temp1;
+      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+      return ret;
+    }
+#if defined (CASEMOD_EXPANSIONS)
+  else if (want_casemod)
+    {
+      temp1 = parameter_brace_casemod (name, temp, ind, modspec, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      FREE (name);
+      FREE (value);
+      FREE (temp);
+
+      if (temp1 == &expand_param_error)
+       return (&expand_wdesc_error);
+      else if (temp1 == &expand_param_fatal)
+       return (&expand_wdesc_fatal);
+
+      ret = alloc_word_desc ();
+      ret->word = temp1;
+      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+      return ret;
+    }
+#endif
+
+  /* Do the right thing based on which character ended the variable name. */
+  switch (c)
+    {
+    default:
+    case '\0':
+    bad_substitution:
+      last_command_exit_value = EXECUTION_FAILURE;
+      report_error (_("%s: bad substitution"), string ? string : "??");
+      FREE (value);
+      FREE (temp);
+      free (name);
+      return &expand_wdesc_error;
+
+    case RBRACE:
+      break;
+
+    case '#':  /* ${param#[#]pattern} */
+    case '%':  /* ${param%[%]pattern} */
+      if (value == 0 || *value == '\0' || temp == 0 || *temp == '\0')
+       {
+         FREE (value);
+         break;
+       }
+      temp1 = parameter_brace_remove_pattern (name, temp, ind, value, c, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      free (temp);
+      free (value);
+      free (name);
+
+      ret = alloc_word_desc ();
+      ret->word = temp1;
+      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+      return ret;
+
+    case '-':
+    case '=':
+    case '?':
+    case '+':
+      if (var_is_set && var_is_null == 0)
+       {
+         /* If the operator is `+', we don't want the value of the named
+            variable for anything, just the value of the right hand side. */
+         if (c == '+')
+           {
+             /* XXX -- if we're double-quoted and the named variable is "$@",
+                       we want to turn off any special handling of "$@" --
+                       we're not using it, so whatever is on the rhs applies. */
+             if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+               *quoted_dollar_atp = 0;
+             if (contains_dollar_at)
+               *contains_dollar_at = 0;
+
+             FREE (temp);
+             if (value)
+               {
+                 /* From Posix discussion on austin-group list.  Issue 221
+                    requires that backslashes escaping `}' inside
+                    double-quoted ${...} be removed. */
+                 if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+                   quoted |= Q_DOLBRACE;
+                 ret = parameter_brace_expand_rhs (name, value, c,
+                                                   quoted,
+                                                   quoted_dollar_atp,
+                                                   contains_dollar_at);
+                 /* XXX - fix up later, esp. noting presence of
+                          W_HASQUOTEDNULL in ret->flags */
+                 free (value);
+               }
+             else
+               temp = (char *)NULL;
+           }
+         else
+           {
+             FREE (value);
+           }
+         /* Otherwise do nothing; just use the value in TEMP. */
+       }
+      else     /* VAR not set or VAR is NULL. */
+       {
+         FREE (temp);
+         temp = (char *)NULL;
+         if (c == '=' && var_is_special)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             report_error (_("$%s: cannot assign in this way"), name);
+             free (name);
+             free (value);
+             return &expand_wdesc_error;
+           }
+         else if (c == '?')
+           {
+             parameter_brace_expand_error (name, value);
+             return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+           }
+         else if (c != '+')
+           {
+             /* XXX -- if we're double-quoted and the named variable is "$@",
+                       we want to turn off any special handling of "$@" --
+                       we're not using it, so whatever is on the rhs applies. */
+             if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+               *quoted_dollar_atp = 0;
+             if (contains_dollar_at)
+               *contains_dollar_at = 0;
+
+             /* From Posix discussion on austin-group list.  Issue 221 requires
+                that backslashes escaping `}' inside double-quoted ${...} be
+                removed. */
+             if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+               quoted |= Q_DOLBRACE;
+             ret = parameter_brace_expand_rhs (name, value, c, quoted,
+                                               quoted_dollar_atp,
+                                               contains_dollar_at);
+             /* XXX - fix up later, esp. noting presence of
+                      W_HASQUOTEDNULL in tdesc->flags */
+           }
+         free (value);
+       }
+
+      break;
+    }
+  free (name);
+
+  if (ret == 0)
+    {
+      ret = alloc_word_desc ();
+      ret->flags = tflag;
+      ret->word = temp;
+    }
+  return (ret);
+}
+
+/* Expand a single ${xxx} expansion.  The braces are optional.  When
+   the braces are used, parameter_brace_expand() does the work,
+   possibly calling param_expand recursively. */
+static WORD_DESC *
+param_expand (string, sindex, quoted, expanded_something,
+             contains_dollar_at, quoted_dollar_at_p, had_quoted_null_p,
+             pflags)
+     char *string;
+     int *sindex, quoted, *expanded_something, *contains_dollar_at;
+     int *quoted_dollar_at_p, *had_quoted_null_p, pflags;
+{
+  char *temp, *temp1, uerror[3];
+  int zindex, t_index, expok;
+  unsigned char c;
+  intmax_t number;
+  SHELL_VAR *var;
+  WORD_LIST *list;
+  WORD_DESC *tdesc, *ret;
+  int tflag;
+
+  zindex = *sindex;
+  c = string[++zindex];
+
+  temp = (char *)NULL;
+  ret = tdesc = (WORD_DESC *)NULL;
+  tflag = 0;
+
+  /* Do simple cases first. Switch on what follows '$'. */
+  switch (c)
+    {
+    /* $0 .. $9? */
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+      temp1 = dollar_vars[TODIGIT (c)];
+      if (unbound_vars_is_error && temp1 == (char *)NULL)
+       {
+         uerror[0] = '$';
+         uerror[1] = c;
+         uerror[2] = '\0';
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (uerror);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+      if (temp1)
+       temp = (*temp1 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+                 ? quote_string (temp1)
+                 : quote_escapes (temp1);
+      else
+       temp = (char *)NULL;
+
+      break;
+
+    /* $$ -- pid of the invoking shell. */
+    case '$':
+      temp = itos (dollar_dollar_pid);
+      break;
+
+    /* $# -- number of positional parameters. */
+    case '#':
+      temp = itos (number_of_args ());
+      break;
+
+    /* $? -- return value of the last synchronous command. */
+    case '?':
+      temp = itos (last_command_exit_value);
+      break;
+
+    /* $- -- flags supplied to the shell on invocation or by `set'. */
+    case '-':
+      temp = which_set_flags ();
+      break;
+
+      /* $! -- Pid of the last asynchronous command. */
+    case '!':
+      /* If no asynchronous pids have been created, expand to nothing.
+        If `set -u' has been executed, and no async processes have
+        been created, this is an expansion error. */
+      if (last_asynchronous_pid == NO_PID)
+       {
+         if (expanded_something)
+           *expanded_something = 0;
+         temp = (char *)NULL;
+         if (unbound_vars_is_error)
+           {
+             uerror[0] = '$';
+             uerror[1] = c;
+             uerror[2] = '\0';
+             last_command_exit_value = EXECUTION_FAILURE;
+             err_unboundvar (uerror);
+             return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+           }
+       }
+      else
+       temp = itos (last_asynchronous_pid);
+      break;
+
+    /* The only difference between this and $@ is when the arg is quoted. */
+    case '*':          /* `$*' */
+      list = list_rest_of_args ();
+
+#if 0
+      /* According to austin-group posix proposal by Geoff Clare in
+        <20090505091501.GA10097@squonk.masqnet> of 5 May 2009:
+
+       "The shell shall write a message to standard error and
+        immediately exit when it tries to expand an unset parameter
+        other than the '@' and '*' special parameters."
+      */
+
+      if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0)
+       {
+         uerror[0] = '$';
+         uerror[1] = '*';
+         uerror[2] = '\0';
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (uerror);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+#endif
+
+      /* If there are no command-line arguments, this should just
+        disappear if there are other characters in the expansion,
+        even if it's quoted. */
+      if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && list == 0)
+       temp = (char *)NULL;
+      else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
+       {
+         /* If we have "$*" we want to make a string of the positional
+            parameters, separated by the first character of $IFS, and
+            quote the whole string, including the separators.  If IFS
+            is unset, the parameters are separated by ' '; if $IFS is
+            null, the parameters are concatenated. */
+         temp = (quoted & (Q_DOUBLE_QUOTES|Q_PATQUOTE)) ? string_list_dollar_star (list) : string_list (list);
+         if (temp)
+           {
+             temp1 = quote_string (temp);
+             if (*temp == 0)
+               tflag |= W_HASQUOTEDNULL;
+             free (temp);
+             temp = temp1;
+           }
+       }
+      else
+       {
+         /* We check whether or not we're eventually going to split $* here,
+            for example when IFS is empty and we are processing the rhs of
+            an assignment statement.  In that case, we don't separate the
+            arguments at all.  Otherwise, if the $* is not quoted it is
+            identical to $@ */
+#  if defined (HANDLE_MULTIBYTE)
+         if (expand_no_split_dollar_star && ifs_firstc[0] == 0)
+#  else
+         if (expand_no_split_dollar_star && ifs_firstc == 0)
+#  endif
+           temp = string_list_dollar_star (list);
+         else
+           {
+             temp = string_list_dollar_at (list, quoted);
+             if (quoted == 0 && (ifs_is_set == 0 || ifs_is_null))
+               tflag |= W_SPLITSPACE;
+           }
+
+         if (expand_no_split_dollar_star == 0 && contains_dollar_at)
+           *contains_dollar_at = 1;
+       }
+
+      dispose_words (list);
+      break;
+
+    /* When we have "$@" what we want is "$1" "$2" "$3" ... This
+       means that we have to turn quoting off after we split into
+       the individually quoted arguments so that the final split
+       on the first character of $IFS is still done.  */
+    case '@':          /* `$@' */
+      list = list_rest_of_args ();
+
+#if 0
+      /* According to austin-group posix proposal by Geoff Clare in
+        <20090505091501.GA10097@squonk.masqnet> of 5 May 2009:
+
+       "The shell shall write a message to standard error and
+        immediately exit when it tries to expand an unset parameter
+        other than the '@' and '*' special parameters."
+      */
+
+      if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0)
+       {
+         uerror[0] = '$';
+         uerror[1] = '@';
+         uerror[2] = '\0';
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (uerror);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+#endif
+
+      /* We want to flag the fact that we saw this.  We can't turn
+        off quoting entirely, because other characters in the
+        string might need it (consider "\"$@\""), but we need some
+        way to signal that the final split on the first character
+        of $IFS should be done, even though QUOTED is 1. */
+      /* XXX - should this test include Q_PATQUOTE? */
+      if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       *quoted_dollar_at_p = 1;
+      if (contains_dollar_at)
+       *contains_dollar_at = 1;
+
+      /* We want to separate the positional parameters with the first
+        character of $IFS in case $IFS is something other than a space.
+        We also want to make sure that splitting is done no matter what --
+        according to POSIX.2, this expands to a list of the positional
+        parameters no matter what IFS is set to. */
+      temp = string_list_dollar_at (list, (pflags & PF_ASSIGNRHS) ? (quoted|Q_DOUBLE_QUOTES) : quoted);
+
+      tflag |= W_DOLLARAT;
+      dispose_words (list);
+      break;
+
+    case LBRACE:
+      tdesc = parameter_brace_expand (string, &zindex, quoted, pflags,
+                                     quoted_dollar_at_p,
+                                     contains_dollar_at);
+
+      if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
+       return (tdesc);
+      temp = tdesc ? tdesc->word : (char *)0;
+
+      /* XXX */
+      /* Quoted nulls should be removed if there is anything else
+        in the string. */
+      /* Note that we saw the quoted null so we can add one back at
+        the end of this function if there are no other characters
+        in the string, discard TEMP, and go on.  The exception to
+        this is when we have "${@}" and $1 is '', since $@ needs
+        special handling. */
+      if (tdesc && tdesc->word && (tdesc->flags & W_HASQUOTEDNULL) && QUOTED_NULL (temp))
+       {
+         if (had_quoted_null_p)
+           *had_quoted_null_p = 1;
+         if (*quoted_dollar_at_p == 0)
+           {
+             free (temp);
+             tdesc->word = temp = (char *)NULL;
+           }
+           
+       }
+
+      ret = tdesc;
+      goto return0;
+
+    /* Do command or arithmetic substitution. */
+    case LPAREN:
+      /* We have to extract the contents of this paren substitution. */
+      t_index = zindex + 1;
+      temp = extract_command_subst (string, &t_index, 0);
+      zindex = t_index;
+
+      /* For Posix.2-style `$(( ))' arithmetic substitution,
+        extract the expression and pass it to the evaluator. */
+      if (temp && *temp == LPAREN)
+       {
+         char *temp2;
+         temp1 = temp + 1;
+         temp2 = savestring (temp1);
+         t_index = strlen (temp2) - 1;
+
+         if (temp2[t_index] != RPAREN)
+           {
+             free (temp2);
+             goto comsub;
+           }
+
+         /* Cut off ending `)' */
+         temp2[t_index] = '\0';
+
+         if (chk_arithsub (temp2, t_index) == 0)
+           {
+             free (temp2);
+#if 0
+             internal_warning (_("future versions of the shell will force evaluation as an arithmetic substitution"));
+#endif
+             goto comsub;
+           }
+
+         /* Expand variables found inside the expression. */
+         temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES);
+         free (temp2);
+
+arithsub:
+         /* No error messages. */
+         this_command_name = (char *)NULL;
+         number = evalexp (temp1, &expok);
+         free (temp);
+         free (temp1);
+         if (expok == 0)
+           {
+             if (interactive_shell == 0 && posixly_correct)
+               {
+                 last_command_exit_value = EXECUTION_FAILURE;
+                 return (&expand_wdesc_fatal);
+               }
+             else
+               return (&expand_wdesc_error);
+           }
+         temp = itos (number);
+         break;
+       }
+
+comsub:
+      if (pflags & PF_NOCOMSUB)
+       /* we need zindex+1 because string[zindex] == RPAREN */
+       temp1 = substring (string, *sindex, zindex+1);
+      else
+       {
+         tdesc = command_substitute (temp, quoted);
+         temp1 = tdesc ? tdesc->word : (char *)NULL;
+         if (tdesc)
+           dispose_word_desc (tdesc);
+       }
+      FREE (temp);
+      temp = temp1;
+      break;
+
+    /* Do POSIX.2d9-style arithmetic substitution.  This will probably go
+       away in a future bash release. */
+    case '[':
+      /* Extract the contents of this arithmetic substitution. */
+      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);
+
+      goto arithsub;
+
+    default:
+      /* Find the variable in VARIABLE_LIST. */
+      temp = (char *)NULL;
+
+      for (t_index = zindex; (c = string[zindex]) && legal_variable_char (c); zindex++)
+       ;
+      temp1 = (zindex > t_index) ? substring (string, t_index, zindex) : (char *)NULL;
+
+      /* If this isn't a variable name, then just output the `$'. */
+      if (temp1 == 0 || *temp1 == '\0')
+       {
+         FREE (temp1);
+         temp = (char *)xmalloc (2);
+         temp[0] = '$';
+         temp[1] = '\0';
+         if (expanded_something)
+           *expanded_something = 0;
+         goto return0;
+       }
+
+      /* If the variable exists, return its value cell. */
+      var = find_variable (temp1);
+
+      if (var && invisible_p (var) == 0 && var_isset (var))
+       {
+#if defined (ARRAY_VARS)
+         if (assoc_p (var) || array_p (var))
+           {
+             temp = array_p (var) ? array_reference (array_cell (var), 0)
+                                  : assoc_reference (assoc_cell (var), "0");
+             if (temp)
+               temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+                         ? quote_string (temp)
+                         : quote_escapes (temp);
+             else if (unbound_vars_is_error)
+               goto unbound_variable;
+           }
+         else
+#endif
+           {
+             temp = value_cell (var);
+
+             temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+                       ? quote_string (temp)
+                       : quote_escapes (temp);
+           }
+
+         free (temp1);
+
+         goto return0;
+       }
+      else if (var = find_variable_last_nameref (temp1))
+       {
+         temp = nameref_cell (var);
+#if defined (ARRAY_VARS)
+         if (temp && *temp && valid_array_reference (temp))
+           {
+             tdesc = parameter_brace_expand_word (temp, SPECIAL_VAR (temp, 0), quoted, pflags, (arrayind_t *)NULL);
+             if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
+               return (tdesc);
+             ret = tdesc;
+             goto return0;
+           }
+         else
+#endif
+         /* y=2 ; typeset -n x=y; echo $x is not the same as echo $2 in ksh */
+         if (temp && *temp && legal_identifier (temp) == 0)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             report_error (_("%s: invalid variable name for name reference"), temp);
+             return (&expand_wdesc_error);     /* XXX */
+           }
+         else
+           temp = (char *)NULL;
+       }
+
+      temp = (char *)NULL;
+
+unbound_variable:
+      if (unbound_vars_is_error)
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (temp1);
+       }
+      else
+       {
+         free (temp1);
+         goto return0;
+       }
+
+      free (temp1);
+      last_command_exit_value = EXECUTION_FAILURE;
+      return ((unbound_vars_is_error && interactive_shell == 0)
+               ? &expand_wdesc_fatal
+               : &expand_wdesc_error);
+    }
+
+  if (string[zindex])
+    zindex++;
+
+return0:
+  *sindex = zindex;
+
+  if (ret == 0)
+    {
+      ret = alloc_word_desc ();
+      ret->flags = tflag;      /* XXX */
+      ret->word = temp;
+    }
+  return ret;
+}
+
+/* Make a word list which is the result of parameter and variable
+   expansion, command substitution, arithmetic substitution, and
+   quote removal of WORD.  Return a pointer to a WORD_LIST which is
+   the result of the expansion.  If WORD contains a null word, the
+   word list returned is also null.
+
+   QUOTED contains flag values defined in shell.h.
+
+   ISEXP is used to tell expand_word_internal that the word should be
+   treated as the result of an expansion.  This has implications for
+   how IFS characters in the word are treated.
+
+   CONTAINS_DOLLAR_AT and EXPANDED_SOMETHING are return values; when non-null
+   they point to an integer value which receives information about expansion.
+   CONTAINS_DOLLAR_AT gets non-zero if WORD contained "$@", else zero.
+   EXPANDED_SOMETHING get non-zero if WORD contained any parameter expansions,
+   else zero.
+
+   This only does word splitting in the case of $@ expansion.  In that
+   case, we split on ' '. */
+
+/* Values for the local variable quoted_state. */
+#define UNQUOTED        0
+#define PARTIALLY_QUOTED 1
+#define WHOLLY_QUOTED    2
+
+static WORD_LIST *
+expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_something)
+     WORD_DESC *word;
+     int quoted, isexp;
+     int *contains_dollar_at;
+     int *expanded_something;
+{
+  WORD_LIST *list;
+  WORD_DESC *tword;
+
+  /* The intermediate string that we build while expanding. */
+  char *istring;
+
+  /* The current size of the above object. */
+  int istring_size;
+
+  /* Index into ISTRING. */
+  int istring_index;
+
+  /* Temporary string storage. */
+  char *temp, *temp1;
+
+  /* The text of WORD. */
+  register char *string;
+
+  /* The size of STRING. */
+  size_t string_size;
+
+  /* The index into STRING. */
+  int sindex;
+
+  /* This gets 1 if we see a $@ while quoted. */
+  int quoted_dollar_at;
+
+  /* One of UNQUOTED, PARTIALLY_QUOTED, or WHOLLY_QUOTED, depending on
+     whether WORD contains no quoting characters, a partially quoted
+     string (e.g., "xx"ab), or is fully quoted (e.g., "xxab"). */
+  int quoted_state;
+
+  /* State flags */
+  int had_quoted_null;
+  int has_dollar_at, temp_has_dollar_at;
+  int split_on_spaces;
+  int tflag;
+  int pflags;                  /* flags passed to param_expand */
+
+  int assignoff;               /* If assignment, offset of `=' */
+
+  register unsigned char c;    /* Current character. */
+  int t_index;                 /* For calls to string_extract_xxx. */
+
+  char twochars[2];
+
+  DECLARE_MBSTATE;
+
+  istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE);
+  istring[istring_index = 0] = '\0';
+  quoted_dollar_at = had_quoted_null = has_dollar_at = 0;
+  split_on_spaces = 0;
+  quoted_state = UNQUOTED;
+
+  string = word->word;
+  if (string == 0)
+    goto finished_with_string;
+  /* Don't need the string length for the SADD... and COPY_ macros unless
+     multibyte characters are possible. */
+  string_size = (MB_CUR_MAX > 1) ? strlen (string) : 1;
+
+  if (contains_dollar_at)
+    *contains_dollar_at = 0;
+
+  assignoff = -1;
+
+  /* Begin the expansion. */
+
+  for (sindex = 0; ;)
+    {
+      c = string[sindex];
+
+      /* Case on toplevel character. */
+      switch (c)
+       {
+       case '\0':
+         goto finished_with_string;
+
+       case CTLESC:
+         sindex++;
+#if HANDLE_MULTIBYTE
+         if (MB_CUR_MAX > 1 && string[sindex])
+           {
+             SADD_MBQCHAR_BODY(temp, string, sindex, string_size);
+           }
+         else
+#endif
+           {
+             temp = (char *)xmalloc (3);
+             temp[0] = CTLESC;
+             temp[1] = c = string[sindex];
+             temp[2] = '\0';
+           }
+
+dollar_add_string:
+         if (string[sindex])
+           sindex++;
+
+add_string:
+         if (temp)
+           {
+             istring = sub_append_string (temp, istring, &istring_index, &istring_size);
+             temp = (char *)0;
+           }
+
+         break;
+
+#if defined (PROCESS_SUBSTITUTION)
+         /* Process substitution. */
+       case '<':
+       case '>':
+         {
+           if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (word->flags & (W_DQUOTE|W_NOPROCSUB)) || posixly_correct)
+             {
+               sindex--;       /* add_character: label increments sindex */
+               goto add_character;
+             }
+           else
+             t_index = sindex + 1; /* skip past both '<' and LPAREN */
+
+           temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index); /*))*/
+           sindex = t_index;
+
+           /* If the process substitution specification is `<()', we want to
+              open the pipe for writing in the child and produce output; if
+              it is `>()', we want to open the pipe for reading in the child
+              and consume input. */
+           temp = temp1 ? process_substitute (temp1, (c == '>')) : (char *)0;
+
+           FREE (temp1);
+
+           goto dollar_add_string;
+         }
+#endif /* PROCESS_SUBSTITUTION */
+
+       case '=':
+         /* Posix.2 section 3.6.1 says that tildes following `=' in words
+            which are not assignment statements are not expanded.  If the
+            shell isn't in posix mode, though, we perform tilde expansion
+            on `likely candidate' unquoted assignment statements (flags
+            include W_ASSIGNMENT but not W_QUOTED).  A likely candidate
+            contains an unquoted :~ or =~.  Something to think about: we
+            now have a flag that says  to perform tilde expansion on arguments
+            to `assignment builtins' like declare and export that look like
+            assignment statements.  We now do tilde expansion on such words
+            even in POSIX mode. */     
+         if (word->flags & (W_ASSIGNRHS|W_NOTILDE))
+           {
+             if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+               goto add_ifs_character;
+             else
+               goto add_character;
+           }
+         /* If we're not in posix mode or forcing assignment-statement tilde
+            expansion, note where the `=' appears in the word and prepare to
+            do tilde expansion following the first `='. */
+         if ((word->flags & W_ASSIGNMENT) &&
+             (posixly_correct == 0 || (word->flags & W_TILDEEXP)) &&
+             assignoff == -1 && sindex > 0)
+           assignoff = sindex;
+         if (sindex == assignoff && string[sindex+1] == '~')   /* XXX */
+           word->flags |= W_ITILDE;
+#if 0
+         else if ((word->flags & W_ASSIGNMENT) &&
+                  (posixly_correct == 0 || (word->flags & W_TILDEEXP)) &&
+                  string[sindex+1] == '~')
+           word->flags |= W_ITILDE;
+#endif
+         if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+           goto add_ifs_character;
+         else
+           goto add_character;
+
+       case ':':
+         if (word->flags & W_NOTILDE)
+           {
+             if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+               goto add_ifs_character;
+             else
+               goto add_character;
+           }
+
+         if ((word->flags & (W_ASSIGNMENT|W_ASSIGNRHS|W_TILDEEXP)) &&
+             string[sindex+1] == '~')
+           word->flags |= W_ITILDE;
+
+         if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+           goto add_ifs_character;
+         else
+           goto add_character;
+
+       case '~':
+         /* If the word isn't supposed to be tilde expanded, or we're not
+            at the start of a word or after an unquoted : or = in an
+            assignment statement, we don't do tilde expansion. */
+         if ((word->flags & (W_NOTILDE|W_DQUOTE)) ||
+             (sindex > 0 && ((word->flags & W_ITILDE) == 0)) ||
+             (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+           {
+             word->flags &= ~W_ITILDE;
+             if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
+               goto add_ifs_character;
+             else
+               goto add_character;
+           }
+
+         if (word->flags & W_ASSIGNRHS)
+           tflag = 2;
+         else if (word->flags & (W_ASSIGNMENT|W_TILDEEXP))
+           tflag = 1;
+         else
+           tflag = 0;
+
+         temp = bash_tilde_find_word (string + sindex, tflag, &t_index);
+           
+         word->flags &= ~W_ITILDE;
+
+         if (temp && *temp && t_index > 0)
+           {
+             temp1 = bash_tilde_expand (temp, tflag);
+             if  (temp1 && *temp1 == '~' && STREQ (temp, temp1))
+               {
+                 FREE (temp);
+                 FREE (temp1);
+                 goto add_character;           /* tilde expansion failed */
+               }
+             free (temp);
+             temp = temp1;
+             sindex += t_index;
+             goto add_quoted_string;           /* XXX was add_string */
+           }
+         else
+           {
+             FREE (temp);
+             goto add_character;
+           }
+       
+       case '$':
+         if (expanded_something)
+           *expanded_something = 1;
+
+         temp_has_dollar_at = 0;
+         pflags = (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0;
+         if (word->flags & W_NOSPLIT2)
+           pflags |= PF_NOSPLIT2;
+         if (word->flags & W_ASSIGNRHS)
+           pflags |= PF_ASSIGNRHS;
+         tword = param_expand (string, &sindex, quoted, expanded_something,
+                              &temp_has_dollar_at, &quoted_dollar_at,
+                              &had_quoted_null, pflags);
+         has_dollar_at += temp_has_dollar_at;
+         split_on_spaces += (tword->flags & W_SPLITSPACE);
+
+         if (tword == &expand_wdesc_error || tword == &expand_wdesc_fatal)
+           {
+             free (string);
+             free (istring);
+             return ((tword == &expand_wdesc_error) ? &expand_word_error
+                                                    : &expand_word_fatal);
+           }
+         if (contains_dollar_at && has_dollar_at)
+           *contains_dollar_at = 1;
+
+         if (tword && (tword->flags & W_HASQUOTEDNULL))
+           had_quoted_null = 1;
+
+         temp = tword ? tword->word : (char *)NULL;
+         dispose_word_desc (tword);
+
+         /* Kill quoted nulls; we will add them back at the end of
+            expand_word_internal if nothing else in the string */
+         if (had_quoted_null && temp && QUOTED_NULL (temp))
+           {
+             FREE (temp);
+             temp = (char *)NULL;
+           }
+
+         goto add_string;
+         break;
+
+       case '`':               /* Backquoted command substitution. */
+         {
+           t_index = sindex++;
+
+           temp = string_extract (string, &sindex, "`", SX_REQMATCH);
+           /* The test of sindex against t_index is to allow bare instances of
+              ` to pass through, for backwards compatibility. */
+           if (temp == &extract_string_error || temp == &extract_string_fatal)
+             {
+               if (sindex - 1 == t_index)
+                 {
+                   sindex = t_index;
+                   goto add_character;
+                 }
+               last_command_exit_value = EXECUTION_FAILURE;
+               report_error (_("bad substitution: no closing \"`\" in %s") , string+t_index);
+               free (string);
+               free (istring);
+               return ((temp == &extract_string_error) ? &expand_word_error
+                                                       : &expand_word_fatal);
+             }
+               
+           if (expanded_something)
+             *expanded_something = 1;
+
+           if (word->flags & W_NOCOMSUB)
+             /* sindex + 1 because string[sindex] == '`' */
+             temp1 = substring (string, t_index, sindex + 1);
+           else
+             {
+               de_backslash (temp);
+               tword = command_substitute (temp, quoted);
+               temp1 = tword ? tword->word : (char *)NULL;
+               if (tword)
+                 dispose_word_desc (tword);
+             }
+           FREE (temp);
+           temp = temp1;
+           goto dollar_add_string;
+         }
+
+       case '\\':
+         if (string[sindex + 1] == '\n')
+           {
+             sindex += 2;
+             continue;
+           }
+
+         c = string[++sindex];
+
+         if (quoted & Q_HERE_DOCUMENT)
+           tflag = CBSHDOC;
+         else if (quoted & Q_DOUBLE_QUOTES)
+           tflag = CBSDQUOTE;
+         else
+           tflag = 0;
+
+         /* From Posix discussion on austin-group list:  Backslash escaping
+            a } in ${...} is removed.  Issue 0000221 */
+         if ((quoted & Q_DOLBRACE) && c == RBRACE)
+           {
+             SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
+           }
+         /* This is the fix for " $@\ " */
+         else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0) & isexp == 0 && isifs (c))
+           {
+             RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size,
+                                     DEFAULT_ARRAY_SIZE);
+             istring[istring_index++] = CTLESC;
+             istring[istring_index++] = '\\';
+             istring[istring_index] = '\0';
+
+             SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
+           }
+         else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0))
+           {
+             SCOPY_CHAR_I (twochars, '\\', c, string, sindex, string_size);
+           }
+         else if (c == 0)
+           {
+             c = CTLNUL;
+             sindex--;         /* add_character: label increments sindex */
+             goto add_character;
+           }
+         else
+           {
+             SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
+           }
+
+         sindex++;
+add_twochars:
+         /* BEFORE jumping here, we need to increment sindex if appropriate */
+         RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size,
+                                 DEFAULT_ARRAY_SIZE);
+         istring[istring_index++] = twochars[0];
+         istring[istring_index++] = twochars[1];
+         istring[istring_index] = '\0';
+
+         break;
+
+       case '"':
+         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+           goto add_character;
+
+         t_index = ++sindex;
+         temp = string_extract_double_quoted (string, &sindex, 0);
+
+         /* If the quotes surrounded the entire string, then the
+            whole word was quoted. */
+         quoted_state = (t_index == 1 && string[sindex] == '\0')
+                           ? WHOLLY_QUOTED
+                           : PARTIALLY_QUOTED;
+
+         if (temp && *temp)
+           {
+             tword = alloc_word_desc ();
+             tword->word = temp;
+
+             temp = (char *)NULL;
+
+             temp_has_dollar_at = 0;   /* XXX */
+             /* Need to get W_HASQUOTEDNULL flag through this function. */
+             list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &temp_has_dollar_at, (int *)NULL);
+             has_dollar_at += temp_has_dollar_at;
+
+             if (list == &expand_word_error || list == &expand_word_fatal)
+               {
+                 free (istring);
+                 free (string);
+                 /* expand_word_internal has already freed temp_word->word
+                    for us because of the way it prints error messages. */
+                 tword->word = (char *)NULL;
+                 dispose_word (tword);
+                 return list;
+               }
+
+             dispose_word (tword);
+
+             /* "$@" (a double-quoted dollar-at) expands into nothing,
+                not even a NULL word, when there are no positional
+                parameters. */
+             if (list == 0 && has_dollar_at)
+               {
+                 quoted_dollar_at++;
+                 break;
+               }
+
+             /* If we get "$@", we know we have expanded something, so we
+                need to remember it for the final split on $IFS.  This is
+                a special case; it's the only case where a quoted string
+                can expand into more than one word.  It's going to come back
+                from the above call to expand_word_internal as a list with
+                a single word, in which all characters are quoted and
+                separated by blanks.  What we want to do is to turn it back
+                into a list for the next piece of code. */
+             if (list)
+               dequote_list (list);
+
+             if (list && list->word && (list->word->flags & W_HASQUOTEDNULL))
+               had_quoted_null = 1;            /* XXX */
+
+             if (has_dollar_at)
+               {
+                 quoted_dollar_at++;
+                 if (contains_dollar_at)
+                   *contains_dollar_at = 1;
+                 if (expanded_something)
+                   *expanded_something = 1;
+               }
+           }
+         else
+           {
+             /* What we have is "".  This is a minor optimization. */
+             FREE (temp);
+             list = (WORD_LIST *)NULL;
+           }
+
+         /* The code above *might* return a list (consider the case of "$@",
+            where it returns "$1", "$2", etc.).  We can't throw away the
+            rest of the list, and we have to make sure each word gets added
+            as quoted.  We test on tresult->next:  if it is non-NULL, we
+            quote the whole list, save it to a string with string_list, and
+            add that string. We don't need to quote the results of this
+            (and it would be wrong, since that would quote the separators
+            as well), so we go directly to add_string. */
+         if (list)
+           {
+             if (list->next)
+               {
+                 /* Testing quoted_dollar_at makes sure that "$@" is
+                    split correctly when $IFS does not contain a space. */
+                 temp = quoted_dollar_at
+                               ? string_list_dollar_at (list, Q_DOUBLE_QUOTES)
+                               : string_list (quote_list (list));
+                 dispose_words (list);
+                 goto add_string;
+               }
+             else
+               {
+                 temp = savestring (list->word->word);
+                 tflag = list->word->flags;
+                 dispose_words (list);
+
+                 /* If the string is not a quoted null string, we want
+                    to remove any embedded unquoted CTLNUL characters.
+                    We do not want to turn quoted null strings back into
+                    the empty string, though.  We do this because we
+                    want to remove any quoted nulls from expansions that
+                    contain other characters.  For example, if we have
+                    x"$*"y or "x$*y" and there are no positional parameters,
+                    the $* should expand into nothing. */
+                 /* We use the W_HASQUOTEDNULL flag to differentiate the
+                    cases:  a quoted null character as above and when
+                    CTLNUL is contained in the (non-null) expansion
+                    of some variable.  We use the had_quoted_null flag to
+                    pass the value through this function to its caller. */
+                 if ((tflag & W_HASQUOTEDNULL) && QUOTED_NULL (temp) == 0)
+                   remove_quoted_nulls (temp); /* XXX */
+               }
+           }
+         else
+           temp = (char *)NULL;
+
+         /* We do not want to add quoted nulls to strings that are only
+            partially quoted; we can throw them away.  The exception to
+            this is when we are going to be performing word splitting,
+            since we have to preserve a null argument if the next character
+            will cause word splitting. */
+         if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
+           continue;
+
+       add_quoted_string:
+
+         if (temp)
+           {
+             temp1 = temp;
+             temp = quote_string (temp);
+             free (temp1);
+             goto add_string;
+           }
+         else
+           {
+             /* Add NULL arg. */
+             c = CTLNUL;
+             sindex--;         /* add_character: label increments sindex */
+             goto add_character;
+           }
+
+         /* break; */
+
+       case '\'':
+         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+           goto add_character;
+
+         t_index = ++sindex;
+         temp = string_extract_single_quoted (string, &sindex);
+
+         /* If the entire STRING was surrounded by single quotes,
+            then the string is wholly quoted. */
+         quoted_state = (t_index == 1 && string[sindex] == '\0')
+                           ? WHOLLY_QUOTED
+                           : PARTIALLY_QUOTED;
+
+         /* If all we had was '', it is a null expansion. */
+         if (*temp == '\0')
+           {
+             free (temp);
+             temp = (char *)NULL;
+           }
+         else
+           remove_quoted_escapes (temp);       /* ??? */
+
+         /* We do not want to add quoted nulls to strings that are only
+            partially quoted; such nulls are discarded. */
+         if (temp == 0 && (quoted_state == PARTIALLY_QUOTED))
+           continue;
+
+         /* If we have a quoted null expansion, add a quoted NULL to istring. */
+         if (temp == 0)
+           {
+             c = CTLNUL;
+             sindex--;         /* add_character: label increments sindex */
+             goto add_character;
+           }
+         else
+           goto add_quoted_string;
+
+         /* break; */
+
+       default:
+         /* This is the fix for " $@ " */
+       add_ifs_character:
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c)))
+           {
+             if (string[sindex])       /* from old goto dollar_add_string */
+               sindex++;
+             if (c == 0)
+               {
+                 c = CTLNUL;
+                 goto add_character;
+               }
+             else
+               {
+#if HANDLE_MULTIBYTE
+                 if (MB_CUR_MAX > 1)
+                   sindex--;
+
+                 if (MB_CUR_MAX > 1)
+                   {
+                     SADD_MBQCHAR_BODY(temp, string, sindex, string_size);
+                   }
+                 else
+#endif
+                   {
+                     twochars[0] = CTLESC;
+                     twochars[1] = c;
+                     goto add_twochars;
+                   }
+               }
+           }
+
+         SADD_MBCHAR (temp, string, sindex, string_size);
+
+       add_character:
+         RESIZE_MALLOCED_BUFFER (istring, istring_index, 1, istring_size,
+                                 DEFAULT_ARRAY_SIZE);
+         istring[istring_index++] = c;
+         istring[istring_index] = '\0';
+
+         /* Next character. */
+         sindex++;
+       }
+    }
+
+finished_with_string:
+  /* OK, we're ready to return.  If we have a quoted string, and
+     quoted_dollar_at is not set, we do no splitting at all; otherwise
+     we split on ' '.  The routines that call this will handle what to
+     do if nothing has been expanded. */
+
+  /* Partially and wholly quoted strings which expand to the empty
+     string are retained as an empty arguments.  Unquoted strings
+     which expand to the empty string are discarded.  The single
+     exception is the case of expanding "$@" when there are no
+     positional parameters.  In that case, we discard the expansion. */
+
+  /* Because of how the code that handles "" and '' in partially
+     quoted strings works, we need to make ISTRING into a QUOTED_NULL
+     if we saw quoting characters, but the expansion was empty.
+     "" and '' are tossed away before we get to this point when
+     processing partially quoted strings.  This makes "" and $xxx""
+     equivalent when xxx is unset.  We also look to see whether we
+     saw a quoted null from a ${} expansion and add one back if we
+     need to. */
+
+  /* If we expand to nothing and there were no single or double quotes
+     in the word, we throw it away.  Otherwise, we return a NULL word.
+     The single exception is for $@ surrounded by double quotes when
+     there are no positional parameters.  In that case, we also throw
+     the word away. */
+
+  if (*istring == '\0')
+    {
+      if (quoted_dollar_at == 0 && (had_quoted_null || quoted_state == PARTIALLY_QUOTED))
+       {
+         istring[0] = CTLNUL;
+         istring[1] = '\0';
+         tword = make_bare_word (istring);
+         tword->flags |= W_HASQUOTEDNULL;              /* XXX */
+         list = make_word_list (tword, (WORD_LIST *)NULL);
+         if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+           tword->flags |= W_QUOTED;
+       }
+      /* According to sh, ksh, and Posix.2, if a word expands into nothing
+        and a double-quoted "$@" appears anywhere in it, then the entire
+        word is removed. */
+      else  if (quoted_state == UNQUOTED || quoted_dollar_at)
+       list = (WORD_LIST *)NULL;
+#if 0
+      else
+       {
+         tword = make_bare_word (istring);
+         if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+           tword->flags |= W_QUOTED;
+         list = make_word_list (tword, (WORD_LIST *)NULL);
+       }
+#else
+      else
+       list = (WORD_LIST *)NULL;
+#endif
+    }
+  else if (word->flags & W_NOSPLIT)
+    {
+      tword = make_bare_word (istring);
+      if (word->flags & W_ASSIGNMENT)
+       tword->flags |= W_ASSIGNMENT;   /* XXX */
+      if (word->flags & W_COMPASSIGN)
+       tword->flags |= W_COMPASSIGN;   /* XXX */
+      if (word->flags & W_NOGLOB)
+       tword->flags |= W_NOGLOB;       /* XXX */
+      if (word->flags & W_NOBRACE)
+       tword->flags |= W_NOBRACE;      /* XXX */
+      if (word->flags & W_NOEXPAND)
+       tword->flags |= W_NOEXPAND;     /* XXX */
+      if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+       tword->flags |= W_QUOTED;
+      if (had_quoted_null && QUOTED_NULL (istring))
+       tword->flags |= W_HASQUOTEDNULL;
+      list = make_word_list (tword, (WORD_LIST *)NULL);
+    }
+  else
+    {
+      char *ifs_chars;
+
+      ifs_chars = (quoted_dollar_at || has_dollar_at) ? ifs_value : (char *)NULL;
+
+      /* If we have $@, we need to split the results no matter what.  If
+        IFS is unset or NULL, string_list_dollar_at has separated the
+        positional parameters with a space, so we split on space (we have
+        set ifs_chars to " \t\n" above if ifs is unset).  If IFS is set,
+        string_list_dollar_at has separated the positional parameters
+        with the first character of $IFS, so we split on $IFS.  If
+        SPLIT_ON_SPACES is set, we expanded $* (unquoted) with IFS either
+        unset or null, and we want to make sure that we split on spaces
+        regardless of what else has happened to IFS since the expansion. */
+      if (split_on_spaces)
+       list = list_string (istring, " ", 1);   /* XXX quoted == 1? */
+      else if (has_dollar_at && ifs_chars)
+       list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1);
+      else
+       {
+         tword = make_bare_word (istring);
+         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED))
+           tword->flags |= W_QUOTED;
+         if (word->flags & W_ASSIGNMENT)
+           tword->flags |= W_ASSIGNMENT;
+         if (word->flags & W_COMPASSIGN)
+           tword->flags |= W_COMPASSIGN;
+         if (word->flags & W_NOGLOB)
+           tword->flags |= W_NOGLOB;
+         if (word->flags & W_NOBRACE)
+           tword->flags |= W_NOBRACE;
+         if (word->flags & W_NOEXPAND)
+           tword->flags |= W_NOEXPAND;
+         if (had_quoted_null && QUOTED_NULL (istring))
+           tword->flags |= W_HASQUOTEDNULL;    /* XXX */
+         list = make_word_list (tword, (WORD_LIST *)NULL);
+       }
+    }
+
+  free (istring);
+  return (list);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                Functions for Quote Removal                      */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Perform quote removal on STRING.  If QUOTED > 0, assume we are obeying the
+   backslash quoting rules for within double quotes or a here document. */
+char *
+string_quote_removal (string, quoted)
+     char *string;
+     int quoted;
+{
+  size_t slen;
+  char *r, *result_string, *temp, *send;
+  int sindex, tindex, dquote;
+  unsigned char c;
+  DECLARE_MBSTATE;
+
+  /* The result can be no longer than the original string. */
+  slen = strlen (string);
+  send = string + slen;
+
+  r = result_string = (char *)xmalloc (slen + 1);
+
+  for (dquote = sindex = 0; c = string[sindex];)
+    {
+      switch (c)
+       {
+       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 */
+
+       default:
+         SCOPY_CHAR_M (r, string, send, sindex);
+         break;
+
+       case '\'':
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote)
+           {
+             *r++ = c;
+             sindex++;
+             break;
+           }
+         tindex = sindex + 1;
+         temp = string_extract_single_quoted (string, &tindex);
+         if (temp)
+           {
+             strcpy (r, temp);
+             r += strlen (r);
+             free (temp);
+           }
+         sindex = tindex;
+         break;
+
+       case '"':
+         dquote = 1 - dquote;
+         sindex++;
+         break;
+       }
+    }
+    *r = '\0';
+    return (result_string);
+}
+
+#if 0
+/* UNUSED */
+/* Perform quote removal on word WORD.  This allocates and returns a new
+   WORD_DESC *. */
+WORD_DESC *
+word_quote_removal (word, quoted)
+     WORD_DESC *word;
+     int quoted;
+{
+  WORD_DESC *w;
+  char *t;
+
+  t = string_quote_removal (word->word, quoted);
+  w = alloc_word_desc ();
+  w->word = t ? t : savestring ("");
+  return (w);
+}
+
+/* Perform quote removal on all words in LIST.  If QUOTED is non-zero,
+   the members of the list are treated as if they are surrounded by
+   double quotes.  Return a new list, or NULL if LIST is NULL. */
+WORD_LIST *
+word_list_quote_removal (list, quoted)
+     WORD_LIST *list;
+     int quoted;
+{
+  WORD_LIST *result, *t, *tresult, *e;
+
+  for (t = list, result = (WORD_LIST *)NULL; t; t = t->next)
+    {
+      tresult = make_word_list (word_quote_removal (t->word, quoted), (WORD_LIST *)NULL);
+#if 0
+      result = (WORD_LIST *) list_append (result, tresult);
+#else
+      if (result == 0)
+       result = e = tresult;
+      else
+       {
+         e->next = tresult;
+         while (e->next)
+           e = e->next;
+       }
+#endif
+    }
+  return (result);
+}
+#endif
+
+/*******************************************
+ *                                        *
+ *    Functions to perform word splitting  *
+ *                                        *
+ *******************************************/
+
+void
+setifs (v)
+     SHELL_VAR *v;
+{
+  char *t;
+  unsigned char uc;
+
+  ifs_var = v;
+  ifs_value = (v && value_cell (v)) ? value_cell (v) : " \t\n";
+
+  ifs_is_set = ifs_var != 0;
+  ifs_is_null = ifs_is_set && (*ifs_value == 0);
+
+  /* Should really merge ifs_cmap with sh_syntaxtab.  XXX - doesn't yet
+     handle multibyte chars in IFS */
+  memset (ifs_cmap, '\0', sizeof (ifs_cmap));
+  for (t = ifs_value ; t && *t; t++)
+    {
+      uc = *t;
+      ifs_cmap[uc] = 1;
+    }
+
+#if defined (HANDLE_MULTIBYTE)
+  if (ifs_value == 0)
+    {
+      ifs_firstc[0] = '\0';
+      ifs_firstc_len = 1;
+    }
+  else
+    {
+      size_t ifs_len;
+      ifs_len = strnlen (ifs_value, MB_CUR_MAX);
+      ifs_firstc_len = MBLEN (ifs_value, ifs_len);
+      if (ifs_firstc_len == 1 || ifs_firstc_len == 0 || MB_INVALIDCH (ifs_firstc_len))
+       {
+         ifs_firstc[0] = ifs_value[0];
+         ifs_firstc[1] = '\0';
+         ifs_firstc_len = 1;
+       }
+      else
+       memcpy (ifs_firstc, ifs_value, ifs_firstc_len);
+    }
+#else
+  ifs_firstc = ifs_value ? *ifs_value : 0;
+#endif
+}
+
+char *
+getifs ()
+{
+  return ifs_value;
+}
+
+/* This splits a single word into a WORD LIST on $IFS, but only if the word
+   is not quoted.  list_string () performs quote removal for us, even if we
+   don't do any splitting. */
+WORD_LIST *
+word_split (w, ifs_chars)
+     WORD_DESC *w;
+     char *ifs_chars;
+{
+  WORD_LIST *result;
+
+  if (w)
+    {
+      char *xifs;
+
+      xifs = ((w->flags & W_QUOTED) || ifs_chars == 0) ? "" : ifs_chars;
+      result = list_string (w->word, xifs, w->flags & W_QUOTED);
+    }
+  else
+    result = (WORD_LIST *)NULL;
+
+  return (result);
+}
+
+/* Perform word splitting on LIST and return the RESULT.  It is possible
+   to return (WORD_LIST *)NULL. */
+static WORD_LIST *
+word_list_split (list)
+     WORD_LIST *list;
+{
+  WORD_LIST *result, *t, *tresult, *e;
+
+  for (t = list, result = (WORD_LIST *)NULL; t; t = t->next)
+    {
+      tresult = word_split (t->word, ifs_value);
+      if (result == 0)
+        result = e = tresult;
+      else
+       {
+         e->next = tresult;
+         while (e->next)
+           e = e->next;
+       }
+    }
+  return (result);
+}
+
+/**************************************************
+ *                                               *
+ *    Functions to expand an entire WORD_LIST    *
+ *                                               *
+ **************************************************/
+
+/* Do any word-expansion-specific cleanup and jump to top_level */
+static void
+exp_jump_to_top_level (v)
+     int v;
+{
+  set_pipestatus_from_exit (last_command_exit_value);
+
+  /* Cleanup code goes here. */
+  expand_no_split_dollar_star = 0;     /* XXX */
+  expanding_redir = 0;
+  assigning_in_environment = 0;
+
+  if (parse_and_execute_level == 0)
+    top_level_cleanup ();                      /* from sig.c */
+
+  jump_to_top_level (v);
+}
+
+/* Put NLIST (which is a WORD_LIST * of only one element) at the front of
+   ELIST, and set ELIST to the new list. */
+#define PREPEND_LIST(nlist, elist) \
+       do { nlist->next = elist; elist = nlist; } while (0)
+
+/* Separate out any initial variable assignments from TLIST.  If set -k has
+   been executed, remove all assignment statements from TLIST.  Initial
+   variable assignments and other environment assignments are placed
+   on SUBST_ASSIGN_VARLIST. */
+static WORD_LIST *
+separate_out_assignments (tlist)
+     WORD_LIST *tlist;
+{
+  register WORD_LIST *vp, *lp;
+
+  if (tlist == 0)
+    return ((WORD_LIST *)NULL);
+
+  if (subst_assign_varlist)
+    dispose_words (subst_assign_varlist);      /* Clean up after previous error */
+
+  subst_assign_varlist = (WORD_LIST *)NULL;
+  vp = lp = tlist;
+
+  /* Separate out variable assignments at the start of the command.
+     Loop invariant: vp->next == lp
+     Loop postcondition:
+       lp = list of words left after assignment statements skipped
+       tlist = original list of words
+  */
+  while (lp && (lp->word->flags & W_ASSIGNMENT))
+    {
+      vp = lp;
+      lp = lp->next;
+    }
+
+  /* If lp != tlist, we have some initial assignment statements.
+     We make SUBST_ASSIGN_VARLIST point to the list of assignment
+     words and TLIST point to the remaining words.  */
+  if (lp != tlist)
+    {
+      subst_assign_varlist = tlist;
+      /* ASSERT(vp->next == lp); */
+      vp->next = (WORD_LIST *)NULL;    /* terminate variable list */
+      tlist = lp;                      /* remainder of word list */
+    }
+
+  /* vp == end of variable list */
+  /* tlist == remainder of original word list without variable assignments */
+  if (!tlist)
+    /* All the words in tlist were assignment statements */
+    return ((WORD_LIST *)NULL);
+
+  /* ASSERT(tlist != NULL); */
+  /* ASSERT((tlist->word->flags & W_ASSIGNMENT) == 0); */
+
+  /* If the -k option is in effect, we need to go through the remaining
+     words, separate out the assignment words, and place them on
+     SUBST_ASSIGN_VARLIST. */
+  if (place_keywords_in_env)
+    {
+      WORD_LIST *tp;   /* tp == running pointer into tlist */
+
+      tp = tlist;
+      lp = tlist->next;
+
+      /* Loop Invariant: tp->next == lp */
+      /* Loop postcondition: tlist == word list without assignment statements */
+      while (lp)
+       {
+         if (lp->word->flags & W_ASSIGNMENT)
+           {
+             /* Found an assignment statement, add this word to end of
+                subst_assign_varlist (vp). */
+             if (!subst_assign_varlist)
+               subst_assign_varlist = vp = lp;
+             else
+               {
+                 vp->next = lp;
+                 vp = lp;
+               }
+
+             /* Remove the word pointed to by LP from TLIST. */
+             tp->next = lp->next;
+             /* ASSERT(vp == lp); */
+             lp->next = (WORD_LIST *)NULL;
+             lp = tp->next;
+           }
+         else
+           {
+             tp = lp;
+             lp = lp->next;
+           }
+       }
+    }
+  return (tlist);
+}
+
+#define WEXP_VARASSIGN 0x001
+#define WEXP_BRACEEXP  0x002
+#define WEXP_TILDEEXP  0x004
+#define WEXP_PARAMEXP  0x008
+#define WEXP_PATHEXP   0x010
+
+/* All of the expansions, including variable assignments at the start of
+   the list. */
+#define WEXP_ALL       (WEXP_VARASSIGN|WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP)
+
+/* All of the expansions except variable assignments at the start of
+   the list. */
+#define WEXP_NOVARS    (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP)
+
+/* All of the `shell expansions': brace expansion, tilde expansion, parameter
+   expansion, command substitution, arithmetic expansion, word splitting, and
+   quote removal. */
+#define WEXP_SHELLEXP  (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP)
+
+/* Take the list of words in LIST and do the various substitutions.  Return
+   a new list of words which is the expanded list, and without things like
+   variable assignments. */
+
+WORD_LIST *
+expand_words (list)
+     WORD_LIST *list;
+{
+  return (expand_word_list_internal (list, WEXP_ALL));
+}
+
+/* Same as expand_words (), but doesn't hack variable or environment
+   variables. */
+WORD_LIST *
+expand_words_no_vars (list)
+     WORD_LIST *list;
+{
+  return (expand_word_list_internal (list, WEXP_NOVARS));
+}
+
+WORD_LIST *
+expand_words_shellexp (list)
+     WORD_LIST *list;
+{
+  return (expand_word_list_internal (list, WEXP_SHELLEXP));
+}
+
+static WORD_LIST *
+glob_expand_word_list (tlist, eflags)
+     WORD_LIST *tlist;
+     int eflags;
+{
+  char **glob_array, *temp_string;
+  register int glob_index;
+  WORD_LIST *glob_list, *output_list, *disposables, *next;
+  WORD_DESC *tword;
+
+  output_list = disposables = (WORD_LIST *)NULL;
+  glob_array = (char **)NULL;
+  while (tlist)
+    {
+      /* For each word, either globbing is attempted or the word is
+        added to orig_list.  If globbing succeeds, the results are
+        added to orig_list and the word (tlist) is added to the list
+        of disposable words.  If globbing fails and failed glob
+        expansions are left unchanged (the shell default), the
+        original word is added to orig_list.  If globbing fails and
+        failed glob expansions are removed, the original word is
+        added to the list of disposable words.  orig_list ends up
+        in reverse order and requires a call to REVERSE_LIST to
+        be set right.  After all words are examined, the disposable
+        words are freed. */
+      next = tlist->next;
+
+      /* If the word isn't an assignment and contains an unquoted
+        pattern matching character, then glob it. */
+      if ((tlist->word->flags & W_NOGLOB) == 0 &&
+         unquoted_glob_pattern_p (tlist->word->word))
+       {
+         glob_array = shell_glob_filename (tlist->word->word);
+
+         /* Handle error cases.
+            I don't think we should report errors like "No such file
+            or directory".  However, I would like to report errors
+            like "Read failed". */
+
+         if (glob_array == 0 || GLOB_FAILED (glob_array))
+           {
+             glob_array = (char **)xmalloc (sizeof (char *));
+             glob_array[0] = (char *)NULL;
+           }
+
+         /* Dequote the current word in case we have to use it. */
+         if (glob_array[0] == NULL)
+           {
+             temp_string = dequote_string (tlist->word->word);
+             free (tlist->word->word);
+             tlist->word->word = temp_string;
+           }
+
+         /* Make the array into a word list. */
+         glob_list = (WORD_LIST *)NULL;
+         for (glob_index = 0; glob_array[glob_index]; glob_index++)
+           {
+             tword = make_bare_word (glob_array[glob_index]);
+             glob_list = make_word_list (tword, glob_list);
+           }
+
+         if (glob_list)
+           {
+             output_list = (WORD_LIST *)list_append (glob_list, output_list);
+             PREPEND_LIST (tlist, disposables);
+           }
+         else if (fail_glob_expansion != 0)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             report_error (_("no match: %s"), tlist->word->word);
+             exp_jump_to_top_level (DISCARD);
+           }
+         else if (allow_null_glob_expansion == 0)
+           {
+             /* Failed glob expressions are left unchanged. */
+             PREPEND_LIST (tlist, output_list);
+           }
+         else
+           {
+             /* Failed glob expressions are removed. */
+             PREPEND_LIST (tlist, disposables);
+           }
+       }
+      else
+       {
+         /* Dequote the string. */
+         temp_string = dequote_string (tlist->word->word);
+         free (tlist->word->word);
+         tlist->word->word = temp_string;
+         PREPEND_LIST (tlist, output_list);
+       }
+
+      strvec_dispose (glob_array);
+      glob_array = (char **)NULL;
+
+      tlist = next;
+    }
+
+  if (disposables)
+    dispose_words (disposables);
+
+  if (output_list)
+    output_list = REVERSE_LIST (output_list, WORD_LIST *);
+
+  return (output_list);
+}
+
+#if defined (BRACE_EXPANSION)
+static WORD_LIST *
+brace_expand_word_list (tlist, eflags)
+     WORD_LIST *tlist;
+     int eflags;
+{
+  register char **expansions;
+  char *temp_string;
+  WORD_LIST *disposables, *output_list, *next;
+  WORD_DESC *w;
+  int eindex;
+
+  for (disposables = output_list = (WORD_LIST *)NULL; tlist; tlist = next)
+    {
+      next = tlist->next;
+
+      if (tlist->word->flags & W_NOBRACE)
+        {
+/*itrace("brace_expand_word_list: %s: W_NOBRACE", tlist->word->word);*/
+         PREPEND_LIST (tlist, output_list);
+         continue;
+        }
+
+      if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
+        {
+/*itrace("brace_expand_word_list: %s: W_COMPASSIGN|W_ASSIGNARG", tlist->word->word);*/
+         PREPEND_LIST (tlist, output_list);
+         continue;
+        }
+
+      /* Only do brace expansion if the word has a brace character.  If
+        not, just add the word list element to BRACES and continue.  In
+        the common case, at least when running shell scripts, this will
+        degenerate to a bunch of calls to `mbschr', and then what is
+        basically a reversal of TLIST into BRACES, which is corrected
+        by a call to REVERSE_LIST () on BRACES when the end of TLIST
+        is reached. */
+      if (mbschr (tlist->word->word, LBRACE))
+       {
+         expansions = brace_expand (tlist->word->word);
+
+         for (eindex = 0; temp_string = expansions[eindex]; eindex++)
+           {
+             w = alloc_word_desc ();
+             w->word = temp_string;
+
+             /* If brace expansion didn't change the word, preserve
+                the flags.  We may want to preserve the flags
+                unconditionally someday -- XXX */
+             if (STREQ (temp_string, tlist->word->word))
+               w->flags = tlist->word->flags;
+             else
+               w = make_word_flags (w, temp_string);
+
+             output_list = make_word_list (w, output_list);
+           }
+         free (expansions);
+
+         /* Add TLIST to the list of words to be freed after brace
+            expansion has been performed. */
+         PREPEND_LIST (tlist, disposables);
+       }
+      else
+       PREPEND_LIST (tlist, output_list);
+    }
+
+  if (disposables)
+    dispose_words (disposables);
+
+  if (output_list)
+    output_list = REVERSE_LIST (output_list, WORD_LIST *);
+
+  return (output_list);
+}
+#endif
+
+#if defined (ARRAY_VARS)
+/* Take WORD, a compound associative array assignment, and internally run
+   'declare -A w', where W is the variable name portion of WORD. */
+static int
+make_internal_declare (word, option)
+     char *word;
+     char *option;
+{
+  int t;
+  WORD_LIST *wl;
+  WORD_DESC *w;
+
+  w = make_word (word);
+
+  t = assignment (w->word, 0);
+  w->word[t] = '\0';
+
+  wl = make_word_list (w, (WORD_LIST *)NULL);
+  wl = make_word_list (make_word (option), wl);
+
+  return (declare_builtin (wl));  
+}  
+#endif
+
+static WORD_LIST *
+shell_expand_word_list (tlist, eflags)
+     WORD_LIST *tlist;
+     int eflags;
+{
+  WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list;
+  int expanded_something, has_dollar_at;
+  char *temp_string;
+
+  /* We do tilde expansion all the time.  This is what 1003.2 says. */
+  new_list = (WORD_LIST *)NULL;
+  for (orig_list = tlist; tlist; tlist = next)
+    {
+      temp_string = tlist->word->word;
+
+      next = tlist->next;
+
+#if defined (ARRAY_VARS)
+      /* If this is a compound array assignment to a builtin that accepts
+         such assignments (e.g., `declare'), take the assignment and perform
+         it separately, handling the semantics of declarations inside shell
+         functions.  This avoids the double-evaluation of such arguments,
+         because `declare' does some evaluation of compound assignments on
+         its own. */
+      if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
+       {
+         int t;
+         char opts[8], opti;
+
+         opti = 0;
+         if (tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL|W_ASSIGNARRAY))
+           opts[opti++] = '-';
+
+         if ((tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL)) == (W_ASSIGNASSOC|W_ASSNGLOBAL))
+           {
+             opts[opti++] = 'g';
+             opts[opti++] = 'A';
+           }
+         else if (tlist->word->flags & W_ASSIGNASSOC)
+           opts[opti++] = 'A';
+         else if ((tlist->word->flags & (W_ASSIGNARRAY|W_ASSNGLOBAL)) == (W_ASSIGNARRAY|W_ASSNGLOBAL))
+           {
+             opts[opti++] = 'g';
+             opts[opti++] = 'a';
+           }
+         else if (tlist->word->flags & W_ASSIGNARRAY)
+           opts[opti++] = 'a';
+         else if (tlist->word->flags & W_ASSNGLOBAL)
+           opts[opti++] = 'g';
+
+         /* If we have special handling note the integer attribute */
+         if (opti > 0 && (tlist->word->flags & W_ASSIGNINT))
+           opts[opti++] = 'i';
+
+         opts[opti] = '\0';
+         if (opti > 0)
+           make_internal_declare (tlist->word->word, opts);
+
+         t = do_word_assignment (tlist->word, 0);
+         if (t == 0)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             exp_jump_to_top_level (DISCARD);
+           }
+
+         /* Now transform the word as ksh93 appears to do and go on */
+         t = assignment (tlist->word->word, 0);
+         tlist->word->word[t] = '\0';
+         tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC|W_ASSIGNARRAY);
+       }
+#endif
+
+      expanded_something = 0;
+      expanded = expand_word_internal
+       (tlist->word, 0, 0, &has_dollar_at, &expanded_something);
+
+      if (expanded == &expand_word_error || expanded == &expand_word_fatal)
+       {
+         /* By convention, each time this error is returned,
+            tlist->word->word has already been freed. */
+         tlist->word->word = (char *)NULL;
+
+         /* Dispose our copy of the original list. */
+         dispose_words (orig_list);
+         /* Dispose the new list we're building. */
+         dispose_words (new_list);
+
+         last_command_exit_value = EXECUTION_FAILURE;
+         if (expanded == &expand_word_error)
+           exp_jump_to_top_level (DISCARD);
+         else
+           exp_jump_to_top_level (FORCE_EOF);
+       }
+
+      /* Don't split words marked W_NOSPLIT. */
+      if (expanded_something && (tlist->word->flags & W_NOSPLIT) == 0)
+       {
+         temp_list = word_list_split (expanded);
+         dispose_words (expanded);
+       }
+      else
+       {
+         /* If no parameter expansion, command substitution, process
+            substitution, or arithmetic substitution took place, then
+            do not do word splitting.  We still have to remove quoted
+            null characters from the result. */
+         word_list_remove_quoted_nulls (expanded);
+         temp_list = expanded;
+       }
+
+      expanded = REVERSE_LIST (temp_list, WORD_LIST *);
+      new_list = (WORD_LIST *)list_append (expanded, new_list);
+    }
+
+  if (orig_list)  
+    dispose_words (orig_list);
+
+  if (new_list)
+    new_list = REVERSE_LIST (new_list, WORD_LIST *);
+
+  return (new_list);
+}
+
+/* The workhorse for expand_words () and expand_words_no_vars ().
+   First arg is LIST, a WORD_LIST of words.
+   Second arg EFLAGS is a flags word controlling which expansions are
+   performed.
+
+   This does all of the substitutions: brace expansion, tilde expansion,
+   parameter expansion, command substitution, arithmetic expansion,
+   process substitution, word splitting, and pathname expansion, according
+   to the bits set in EFLAGS.  Words with the W_QUOTED or W_NOSPLIT bits
+   set, or for which no expansion is done, do not undergo word splitting.
+   Words with the W_NOGLOB bit set do not undergo pathname expansion; words
+   with W_NOBRACE set do not undergo brace expansion (see
+   brace_expand_word_list above). */
+static WORD_LIST *
+expand_word_list_internal (list, eflags)
+     WORD_LIST *list;
+     int eflags;
+{
+  WORD_LIST *new_list, *temp_list;
+  int tint;
+
+  tempenv_assign_error = 0;
+  if (list == 0)
+    return ((WORD_LIST *)NULL);
+
+  garglist = new_list = copy_word_list (list);
+  if (eflags & WEXP_VARASSIGN)
+    {
+      garglist = new_list = separate_out_assignments (new_list);
+      if (new_list == 0)
+       {
+         if (subst_assign_varlist)
+           {
+             /* All the words were variable assignments, so they are placed
+                into the shell's environment. */
+             for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
+               {
+                 this_command_name = (char *)NULL;     /* no arithmetic errors */
+                 tint = do_word_assignment (temp_list->word, 0);
+                 /* Variable assignment errors in non-interactive shells
+                    running in Posix.2 mode cause the shell to exit. */
+                 if (tint == 0)
+                   {
+                     last_command_exit_value = EXECUTION_FAILURE;
+                     if (interactive_shell == 0 && posixly_correct)
+                       exp_jump_to_top_level (FORCE_EOF);
+                     else
+                       exp_jump_to_top_level (DISCARD);
+                   }
+               }
+             dispose_words (subst_assign_varlist);
+             subst_assign_varlist = (WORD_LIST *)NULL;
+           }
+         return ((WORD_LIST *)NULL);
+       }
+    }
+
+  /* Begin expanding the words that remain.  The expansions take place on
+     things that aren't really variable assignments. */
+
+#if defined (BRACE_EXPANSION)
+  /* Do brace expansion on this word if there are any brace characters
+     in the string. */
+  if ((eflags & WEXP_BRACEEXP) && brace_expansion && new_list)
+    new_list = brace_expand_word_list (new_list, eflags);
+#endif /* BRACE_EXPANSION */
+
+  /* Perform the `normal' shell expansions: tilde expansion, parameter and
+     variable substitution, command substitution, arithmetic expansion,
+     and word splitting. */
+  new_list = shell_expand_word_list (new_list, eflags);
+
+  /* Okay, we're almost done.  Now let's just do some filename
+     globbing. */
+  if (new_list)
+    {
+      if ((eflags & WEXP_PATHEXP) && disallow_filename_globbing == 0)
+       /* Glob expand the word list unless globbing has been disabled. */
+       new_list = glob_expand_word_list (new_list, eflags);
+      else
+       /* Dequote the words, because we're not performing globbing. */
+       new_list = dequote_list (new_list);
+    }
+
+  if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist)
+    {
+      sh_wassign_func_t *assign_func;
+      int is_special_builtin, is_builtin_or_func;
+
+      /* If the remainder of the words expand to nothing, Posix.2 requires
+        that the variable and environment assignments affect the shell's
+        environment. */
+      assign_func = new_list ? assign_in_env : do_word_assignment;
+      tempenv_assign_error = 0;
+
+      is_builtin_or_func = (new_list && new_list->word && (find_shell_builtin (new_list->word->word) || find_function (new_list->word->word)));
+      /* Posix says that special builtins exit if a variable assignment error
+        occurs in an assignment preceding it. */
+      is_special_builtin = (posixly_correct && new_list && new_list->word && find_special_builtin (new_list->word->word));
+      
+      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, is_builtin_or_func);
+         assigning_in_environment = 0;
+         /* Variable assignment errors in non-interactive shells running
+            in Posix.2 mode cause the shell to exit. */
+         if (tint == 0)
+           {
+             if (assign_func == do_word_assignment)
+               {
+                 last_command_exit_value = EXECUTION_FAILURE;
+                 if (interactive_shell == 0 && posixly_correct && is_special_builtin)
+                   exp_jump_to_top_level (FORCE_EOF);
+                 else
+                   exp_jump_to_top_level (DISCARD);
+               }
+             else
+               tempenv_assign_error++;
+           }
+       }
+
+      dispose_words (subst_assign_varlist);
+      subst_assign_varlist = (WORD_LIST *)NULL;
+    }
+
+  return (new_list);
+}
index 3efcf32d68e9722024b6ca9d67f9e81b2aa5ac04..72ec06a2c1fd8dde92acea5e8ac773e35f1d061b 100755 (executable)
@@ -1,4 +1,4 @@
-BUILD_DIR=/usr/local/build/chet/bash/bash-current
+BUILD_DIR=/usr/local/build/bash/bash-current
 THIS_SH=$BUILD_DIR/bash
 PATH=$PATH:$BUILD_DIR
 
diff --git a/tests/RUN-ONE-TEST~ b/tests/RUN-ONE-TEST~
new file mode 100755 (executable)
index 0000000..3efcf32
--- /dev/null
@@ -0,0 +1,9 @@
+BUILD_DIR=/usr/local/build/chet/bash/bash-current
+THIS_SH=$BUILD_DIR/bash
+PATH=$PATH:$BUILD_DIR
+
+export THIS_SH PATH
+
+rm -f /tmp/xx
+
+/bin/sh "$@"
index 27edb50753114bd29db44f00cc43be22e2a2e426..4bcd29af0585933755c3587d05bb066b3801ce0d 100644 (file)
@@ -375,3 +375,6 @@ declare -a x='([0]="0" [1]="1" [2]="2" [3]="3" [4]="4" [5]="5foo")'
 declare -a x='([0]="0" [1]="1" [2]="2" [3]="3" [4]="4" [5]="5")'
 declare -a x='([0]="0" [1]="1" [2]="2" [3]="3" [4]="4four" [5]="5")'
 strlen(4four) = 5
+1 2 0 3
+1 2 0 3
+1 2 0 3
index c9f25854cf4ba4f56bab18f936b014c05188d152..53c7d8c0481885cd3dd3823aa983cc49ae134c86 100644 (file)
@@ -396,3 +396,5 @@ ${THIS_SH} ./array12.sub
 ${THIS_SH} ./array13.sub
 
 ${THIS_SH} ./array14.sub
+
+${THIS_SH} ./array15.sub
diff --git a/tests/array.tests~ b/tests/array.tests~
new file mode 100644 (file)
index 0000000..c9f2585
--- /dev/null
@@ -0,0 +1,398 @@
+# this is needed so that the bad assignments (b[]=bcde, for example) do not
+# cause fatal shell errors when in posix mode
+set +o posix
+
+set +a
+# The calls to egrep -v are to filter out builtin array variables that are
+# automatically set and possibly contain values that vary.
+
+# first make sure we handle the basics
+x=()
+echo ${x[@]}
+unset x
+
+# this should be an error
+test=(first & second)
+echo $?
+unset test
+
+# make sure declare -a converts an existing variable to an array
+unset a
+a=abcde
+declare -a a
+echo ${a[0]}
+
+unset a
+a=abcde
+a[2]=bdef
+
+unset b
+declare -a b[256]
+
+unset c[2]
+unset c[*]
+
+a[1]=
+
+_ENV=/bin/true
+x=${_ENV[(_$-=0)+(_=1)-_${-%%*i*}]}
+
+declare -r c[100]
+
+echo ${a[0]} ${a[4]}
+echo ${a[@]}
+
+echo ${a[*]}
+
+# this should print out values, too
+declare -a | egrep -v '(BASH_VERSINFO|PIPESTATUS|GROUPS)'
+
+unset a[7]
+echo ${a[*]}
+
+unset a[4]
+echo ${a[*]}
+
+echo ${a}
+echo "${a}"
+echo $a
+
+unset a[0]
+echo ${a}
+
+echo ${a[@]}
+
+a[5]="hello world"
+echo ${a[5]}
+echo ${#a[5]}
+
+echo ${#a[@]}
+
+a[4+5/2]="test expression"
+declare a["7 + 8"]="test 2"
+a[7 + 8]="test 2"
+echo ${a[@]}
+
+readonly a[5]
+readonly a
+# these two lines should output `declare' commands
+readonly -a | egrep -v '(BASH_VERSINFO|PIPESTATUS|GROUPS)'
+declare -ar | egrep -v '(BASH_VERSINFO|PIPESTATUS|GROUPS)'
+# this line should output `readonly' commands, even for arrays
+set -o posix
+readonly -a | egrep -v '(BASH_VERSINFO|PIPESTATUS|GROUPS)'
+set +o posix
+
+declare -a d='([1]="" [2]="bdef" [5]="hello world" "test")'
+d[9]="ninth element"
+
+declare -a e[10]=test  # this works in post-bash-2.05 versions
+declare -a e[10]='(test)'
+
+pass=/etc/passwd
+declare -a f='("${d[@]}")'
+b=([0]=this [1]=is [2]=a [3]=test [4]="$PS1" [5]=$pass)
+
+echo ${b[@]:2:3}
+
+declare -pa | egrep -v '(BASH_VERSINFO|PIPESTATUS|GROUPS)'
+
+a[3]="this is a test"
+
+b[]=bcde
+b[*]=aaa
+echo ${b[   ]}
+
+c[-2]=4
+echo ${c[-4]}
+
+d[7]=(abdedfegeee)
+
+d=([]=abcde [1]="test test" [*]=last [-65]=negative )
+
+unset d[12]
+unset e[*]
+
+declare -a | egrep -v '(BASH_VERSINFO|PIPESTATUS|GROUPS)'
+
+ps1='hello'
+unset ps1[2]
+unset ${ps1[2]}
+
+declare +a ps1
+declare +a c
+
+# the prompt should not print when using a here doc
+read -p "array test: " -a rv <<!
+this is a test of read using arrays
+!
+
+echo ${rv[0]} ${rv[4]}
+echo ${rv[@]}
+
+# the variable should be converted to an array when `read -a' is done
+vv=1
+read -a vv <<!
+this is a test of arrays
+!
+echo ${vv[0]} ${vv[3]}
+echo ${vv[@]}
+unset vv
+
+declare -a | egrep -v '(BASH_VERSINFO|PIPESTATUS|GROUPS)'
+
+export rv
+#set
+
+x[4]=bbb
+x=abde
+echo $x
+echo ${x[0]}
+echo ${x[4]}
+echo efgh | ( read x[1] ; echo ${x[1]} )
+echo wxyz | ( declare -a x ; read x ; echo $x ; echo ${x[0]} )
+
+# Make sure that arrays can be used to save the positional paramters verbatim
+set -- a 'b c' d 'e f g' h
+
+ARGV=( [0]=$0 "$@" )
+
+for z in "${ARGV[@]}"
+do
+       echo "$z"
+done
+
+echo "$0"
+for z in "$@"
+do
+       echo "$z"
+done
+
+# do various pattern removal and length tests
+XPATH=/bin:/usr/bin:/usr/ucb:/usr/local/bin:.:/sbin:/usr/sbin
+
+xpath=( $( IFS=: ; echo $XPATH ) )
+
+echo ${xpath[@]}
+echo ${xpath[@]##*/}
+echo ${xpath[0]##*/}
+echo ${xpath[@]%%[!/]*}
+echo ${xpath[0]%%[!/]*}
+recho ${xpath##*/}
+recho ${xpath%%[!/]*}
+recho ${xpath[5]##*/}
+recho ${xpath[5]%%[!/]*}
+
+# let's try to make it a DOS-style path
+
+zecho "${xpath[@]/\//\\}"
+zecho "${xpath[@]//\//\\}"
+zecho "${xpath[@]//[\/]/\\}"
+
+# length of the first element of the array, since array without subscript
+# is equivalent to referencing first element
+echo ${#xpath} -- ${#xpath[0]}
+
+# number of elements in the array
+nelem=${#xpath[@]}
+echo ${#xpath[@]} -- $nelem
+
+# total length of all elements in the array, including space separators
+xx="${xpath[*]}"
+echo ${#xx}
+
+# total length of all elements in the array
+xx=$( IFS='' ; echo "${xpath[*]}" )
+echo ${#xx}
+
+unset xpath[nelem-1]
+
+nelem=${#xpath[@]}
+echo ${#xpath[@]} -- $nelem
+
+# arrays and things that look like index assignments
+array=(42 [1]=14 [2]=44)
+
+array2=(grep [ 123 ] \*)
+
+echo ${array[@]}
+echo "${array2[@]}"
+
+# arrays and implicit arithmetic evaluation
+declare -i -a iarray
+
+iarray=( 2+4 1+6 7+2 )
+echo ${iarray[@]}
+
+iarray[4]=4+1
+echo ${iarray[@]}
+
+# make sure assignment using the compound assignment syntax removes all
+# of the old elements from the array value
+barray=(old1 old2 old3 old4 old5)
+barray=(new1 new2 new3)
+echo "length = ${#barray[@]}"
+echo "value = ${barray[*]}"
+
+# make sure the array code behaves correctly with respect to unset variables
+set -u
+( echo ${#narray[4]} )
+
+${THIS_SH} ./array1.sub
+${THIS_SH} ./array2.sub
+
+# some old bugs and ksh93 compatibility tests
+${THIS_SH} ./array3.sub
+
+# some compound assingment parsing problems that showed up in bash-3.1-release
+${THIS_SH} ./array4.sub
+
+set +u
+cd /tmp
+
+touch 1=bar
+foo=([10]="bar")
+echo ${foo[0]}
+rm 1=bar
+
+cd $OLDPWD
+
+foo=(a b c d e f g)
+echo ${foo[@]}
+
+# quoted reserved words are ok
+foo=(\for \case \if \then \else)
+echo ${foo[@]}
+
+# quoted metacharacters are ok
+foo=( [1]='<>' [2]='<' [3]='>' [4]='!' )
+echo ${foo[@]}
+
+# numbers are just words when not in a redirection context
+foo=( 12 14 16 18 20 )
+echo ${foo[@]}
+
+foo=( 4414758999202 )
+echo ${foo[@]}
+
+# this was a bug in all versions of bash 2.x up to and including bash-2.04
+declare -a ddd=(aaa
+bbb)
+echo ${ddd[@]}
+
+# errors until post-bash-2.05a; now reserved words are OK
+foo=(a b c for case if then else)
+
+foo=(for case if then else)
+
+# errors
+metas=( <> < > ! )
+metas=( [1]=<> [2]=< [3]=> [4]=! )
+
+# various expansions that didn't really work right until post-bash-2.04
+foo='abc'
+echo ${foo[0]} ${#foo[0]}
+echo ${foo[1]} ${#foo[1]}
+echo ${foo[@]} ${#foo[@]}
+echo ${foo[*]} ${#foo[*]}
+
+foo=''
+echo ${foo[0]} ${#foo[0]}
+echo ${foo[1]} ${#foo[1]}
+echo ${foo[@]} ${#foo[@]}
+echo ${foo[*]} ${#foo[*]}
+
+# new expansions added after bash-2.05b
+x[0]=zero
+x[1]=one
+x[4]=four
+x[10]=ten
+
+recho ${!x[@]}
+recho "${!x[@]}"
+recho ${!x[*]}
+recho "${!x[*]}"
+
+# sparse array tests for code fixed in bash-3.0
+unset av
+av[1]='one'
+av[2]=''
+
+av[3]=three
+av[5]=five
+av[7]=seven
+
+echo include null element -- expect one
+echo ${av[@]:1:2}      # what happens when we include a null element?
+echo include unset element -- expect three five
+echo ${av[@]:3:2}      # what happens when we include an unset element?
+echo start at unset element -- expect five seven
+echo ${av[@]:4:2}      # what happens when we start at an unset element?
+
+echo too many elements -- expect three five seven
+echo ${av[@]:3:5}      # how about too many elements?
+
+echo positive offset - expect five seven
+echo ${av[@]:5:2}
+echo negative offset to unset element - expect seven
+echo ${av[@]: -2:2}
+
+echo positive offset 2 - expect seven
+echo ${av[@]: 6:2}
+echo negative offset 2 - expect seven
+echo ${av[@]: -1:2}
+
+echo out-of-range offset
+echo ${av[@]:12}
+
+# parsing problems and other inconsistencies not fixed until post bash-3.0
+unset x
+declare -a x=(')' $$)
+[ ${x[1]} -eq $$ ] || echo bad
+
+unset x
+declare -a x=(a b c d e)
+echo ${x[4]}
+
+z=([1]=one [4]=four [7]=seven [10]=ten)
+
+echo ${#z[@]}
+
+echo ${!z[@]}
+
+unset x
+declare -a x=(a \'b  c\')
+
+echo "${x[1]}"
+
+unset x
+declare -a x=(a 'b  c')
+
+echo "${x[1]}"
+
+unset x
+declare -a x=($0)
+[ "${x[@]}" = $0 ] || echo double expansion of \$0
+declare -a x=(\$0)
+echo "${x[@]}"
+
+# tests for bash-3.1 problems
+${THIS_SH} ./array5.sub
+
+# tests for post-bash-3.2 problems, most fixed in bash-3.2 patches
+${THIS_SH} ./array6.sub
+${THIS_SH} ./array7.sub
+
+${THIS_SH} ./array8.sub
+
+${THIS_SH} ./array9.sub
+
+${THIS_SH} ./array10.sub
+
+${THIS_SH} ./array11.sub
+
+${THIS_SH} ./array12.sub
+
+${THIS_SH} ./array13.sub
+
+${THIS_SH} ./array14.sub
diff --git a/tests/array15.sub b/tests/array15.sub
new file mode 100644 (file)
index 0000000..b056ccb
--- /dev/null
@@ -0,0 +1,24 @@
+# fixes for make_internal_declare not handling integer attribute for arrays
+declare -ai -g foo=(1 2 xx 3)
+echo "${foo[@]}"
+
+unset foo
+declare -ai -g foo='(1 2 xx 3)'
+echo "${foo[@]}"
+
+unset foo
+declare -ia -g foo=(1 2 xx 3)
+echo "${foo[@]}" 
+
+unset foo
+declare -ia -g foo='(1 2 xx 3)'
+echo "${foo[@]}" 
+
+unset foo
+func()
+{
+       declare -ai -g foo=(1 2 xx 3)
+}
+
+func
+echo "${foo[@]}" 
diff --git a/tests/array15.sub~ b/tests/array15.sub~
new file mode 100644 (file)
index 0000000..0f04d62
--- /dev/null
@@ -0,0 +1,22 @@
+# fixes for make_internal_declare not handling integer attribute for arrays
+declare -ai -g foo=(1 2 xx 3)
+echo "${foo[@]}"
+
+declare -ai -g foo='(1 2 xx 3)'
+echo "${foo[@]}"
+
+unset foo
+declare -ia -g foo=(1 2 xx 3)
+echo "${foo[@]}" 
+
+declare -ia -g foo='(1 2 xx 3)'
+echo "${foo[@]}" 
+
+unset foo
+func()
+{
+       declare -ai -g foo=(1 2 xx 3)
+}
+
+func
+echo "${foo[@]}" 
index 1da72a52ff06dc979b0d78754e2bd05328ba963d..b36156462d50876a105483ba3609cab86f264f49 100644 (file)
@@ -93,16 +93,6 @@ sleep 2
 wait $!
 exit
 in trap EXIT
-works
-bar
-bar
-foo
-trap -- '' SIGINT
-trap -- '' SIGUSR2
-foo
-bar
-foo
-bar
 caught a child death
 caught a child death
 caught a child death
index 0af03ad7c0f9726ddfa8788694a74a0277992dfe..5d8b06c0a28b88b61b0253bd4c30db9c0502debf 100644 (file)
@@ -70,7 +70,8 @@ ${THIS_SH} ./trap3.sub
 
 ${THIS_SH} ./trap4.sub
 
-${THIS_SH} ./trap5.sub
+# This doesn't work right on all Unix versions
+#${THIS_SH} ./trap5.sub
 
 #
 # show that setting a trap on SIGCHLD is not disastrous.
diff --git a/tests/trap.tests~ b/tests/trap.tests~
new file mode 100644 (file)
index 0000000..0af03ad
--- /dev/null
@@ -0,0 +1,96 @@
+# test the trap code
+
+trap 'echo exiting' 0
+trap 'echo aborting' 1 2 3 6 15
+
+# make sure a user-specified subshell runs the exit trap, but does not
+# inherit the exit trap from a parent shell
+( trap 'echo subshell exit' 0; exit 0 )
+( exit 0 )
+
+trap
+
+func()
+{
+       trap 'echo ${FUNCNAME:-$0}[$LINENO] funcdebug' DEBUG
+       echo funcdebug line
+}
+
+trap 'echo [$LINENO] debug' DEBUG
+echo debug line
+
+trap
+
+func
+
+trap
+
+trap 'echo ${FUNCNAME:-$0}[$LINENO] debug' DEBUG
+func2()
+{
+       echo func2debug line
+}
+declare -ft func2
+func2
+
+unset -f func2
+
+trap '' DEBUG
+
+trap
+
+trap - debug
+
+trap
+
+trap - HUP
+trap hup
+trap '' INT
+trap '' int
+
+trap
+
+# exit 0 in exit trap should set exit status
+(
+set -e
+trap 'exit 0' EXIT
+false   
+echo bad
+)
+echo $?
+
+# hmmm...should this set the handling to SIG_IGN for children, too?
+trap '' USR2
+./trap1.sub
+
+# test ERR trap
+./trap2.sub
+
+${THIS_SH} ./trap3.sub
+
+${THIS_SH} ./trap4.sub
+
+${THIS_SH} ./trap5.sub
+
+#
+# show that setting a trap on SIGCHLD is not disastrous.
+#
+set -o monitor
+
+trap 'echo caught a child death' SIGCHLD
+
+sleep 7 & sleep 6 & sleep 5 &
+
+# this will only catch the first, since there's a trap on SIGCHLD
+wait
+
+trap -p SIGCHLD
+
+# Now reset some of the signals the shell handles specially back to
+# their default values (with or without the SIG prefix)
+trap - SIGINT QUIT TERM
+
+trap
+
+trap - SIGCHLD
+wait
index 91947d54ef358eedb8d34d1d5502f362c9239b81..7f3380b41184a3f87d7b46376110e2d974be2a02 100644 (file)
@@ -1,5 +1,5 @@
 # make sure process substitution runs the exit trap
-[[ -n $(< <(trap "tee /dev/fd/3" EXIT)) ]] 3>&1 <<<works || echo "fail :("
+[[ -n $(< <(trap "cat /dev/fd/0" EXIT)) ]] <<<works && echo works || echo "fail :("
 
 read foo < <(trap "echo bar" EXIT)
 echo $foo