<svdb@stack.nl>, fix from Andreas Schwab <schwab@linux-m68k.org>
- call run_pending_traps after each call to QUIT or test of
interrupt_state, like we do in mainline shell code
+ - glob_vector: don't call QUIT; in `if (lose)' code block; just free
+ memory, return NULL, and let callers deal with interrupt_state or
+ other signals and traps
+
+ 6/25
+ ----
+lib/readline/input.c
+ - rl_read_key: restructure the loop that calls the event hook a little,
+ so that the hook is called only after rl_gather_tyi returns no input,
+ and any pending input is returned first. This results in better
+ efficiency for processing pending input without calling the hook
+ on every input character as bash-4.1 did. From a report from
+ Max Horn <max@quendi.de>
+
+ 6/26
+ ----
+trap.c
+ - signal_is_pending: return TRUE if SIG argument has been received and
+ a trap is waiting to execute
+
+trap.h
+ - signal_is_pending: extern declaration
+
+lib/glob/glob.c
+ - glob_vector: check for pending SIGINT trap each time through the loop,
+ just like we check for interrupt_state or terminating_signal, and
+ set `lose = 1' so we clean up after ourselves and interrupt the
+ operation before running the trap. This may require a change later,
+ maybe call run_pending_traps and do that if run_pending_traps returns?
+
+variables.c
+ - sv_histtimefmt: set history_comment_character to default (`#') if
+ it's 0 when we're turning on history timestamps. The history code
+ uses the history comment character to prefix timestamps, and
+ leaving it at 0 effectively removes them from the history. From a
+ report to help-bash by Dennis Williamson <dennistwilliamson@gmail.com>
+
+ 6/27
+ ----
+lib/readline/signals.c
+ - rl_maybe_restore_sighandler: new function, sets handler for SIG to
+ HANDLER->sa_handler only if it's not SIG_IGN. Needs to be called
+ on same signals set using rl_maybe_set_sighandler, which does not
+ override an existing SIG_IGN handler (SIGALRM is ok since it does
+ the check inline; doesn't mess with SIGWINCH)
+
+ 6/30
+ ----
+variables.h
+ - additional defines for the new `nameref' variable attribute
+ (att_nameref): nameref_p, nameref_cell, var_setref
+
+variables.c
+ - find_variable_nameref: resolve SHELL_VAR V through chain of namerefs
+ - find_variable_last_nameref: resolve variable NAME until last in a
+ chain of possibly more than one nameref starting at shell_variables
+ - find_global_variable_last_nameref: resolve variable NAME until last
+ in a chain of possibly more than one nameref starting at
+ global_variables
+ - find_nameref_at_context: resolve SHELL_VAR V through chain of namerefs
+ in a specific variable context (usually a local variable hash table)
+ - find_variable_nameref_context: resolve SHELL_VAR V through chain of
+ namerefs following a chain of varible contexts
+ - find_variable_last_nameref_context: resolve SHELL_VAR V as in
+ find_variable_last_context, but return the final nameref instead of
+ what the final nameref resolves to
+ - find_variable_tempenv, find_variable_notempenv, find_global_variable,
+ find_shell_variable, find_variable: modified to follow namerefs
+ - find_global_variable_noref: look up a global variable without following
+ any namerefs
+ - find_variable_noref: look up a shell variable without following any
+ namerefs
+ - bind_variable_internal: modify to follow a chain of namerefs in the
+ global variables table; change to handle assignments to a nameref by
+ following nameref chain
+ - bind_variable: modify to follow chain of namerefs when binding to a
+ local variable
+ - unbind_variable: changes to unset nameref variables (unsets both
+ nameref and variable it resolves to)
+
+subst.c
+ - parameter_brace_expand_word: change to handle expanding nameref whose
+ value is x[n]
+ - parameter_brace_expand_indir: change to expand in ksh93-compatible
+ way if variable to be indirected is nameref and a simple (non-array)
+ expansion
+ - param_expand: change to expand $foo where foo is a nameref whose value
+ is x[n]
+
+execute_cmd.c
+ - execute_for_command: changes to implement ksh93 semantics when index
+ variable is a nameref
+
+builtins/setattr.def
+ - show_var_attributes: change to add `n' to flags list if att_nameref
+ is set
+
+builtins/set.def
+ - unset_builtin: changes to error messages to follow nameref variables
+
+builtins/declare.def
+ - document new -n option
+ - declare_internal: new `-n' and `+n' options
+ - declare_internal: handle declare -n var[=value] and
+ declare +n var[=value] for existing and non-existant variables.
+ Enforce restriction that nameref variables cannot be arrays.
+ Implement semi-peculiar ksh93 semantics for typeset +n ref=value
+
- glob_filename: check for interrupts before returning if glob_vector
returns NULL or an error. Bug reported by Serge van den Boom
<svdb@stack.nl>, fix from Andreas Schwab <schwab@linux-m68k.org>
+ - call run_pending_traps after each call to QUIT or test of
+ interrupt_state, like we do in mainline shell code
+ - glob_vector: don't call QUIT; in `if (lose)' code block; just free
+ memory, return NULL, and let callers deal with interrupt_state or
+ other signals and traps
+
+ 6/25
+ ----
+lib/readline/input.c
+ - rl_read_key: restructure the loop that calls the event hook a little,
+ so that the hook is called only after rl_gather_tyi returns no input,
+ and any pending input is returned first. This results in better
+ efficiency for processing pending input without calling the hook
+ on every input character as bash-4.1 did. From a report from
+ Max Horn <max@quendi.de>
+
+ 6/26
+ ----
+trap.c
+ - signal_is_pending: return TRUE if SIG argument has been received and
+ a trap is waiting to execute
+
+trap.h
+ - signal_is_pending: extern declaration
+
+lib/glob/glob.c
+ - glob_vector: check for pending SIGINT trap each time through the loop,
+ just like we check for interrupt_state or terminating_signal, and
+ set `lose = 1' so we clean up after ourselves and interrupt the
+ operation before running the trap. This may require a change later,
+ maybe call run_pending_traps and do that if run_pending_traps returns?
+
+variables.c
+ - sv_histtimefmt: set history_comment_character to default (`#') if
+ it's 0 when we're turning on history timestamps. The history code
+ uses the history comment character to prefix timestamps, and
+ leaving it at 0 effectively removes them from the history. From a
+ report to help-bash by Dennis Williamson <dennistwilliamson@gmail.com>
+
+ 6/27
+ ----
+lib/readline/signals.c
+ - rl_maybe_restore_sighandler: new function, sets handler for SIG to
+ HANDLER->sa_handler only if it's not SIG_IGN. Needs to be called
+ on same signals set using rl_maybe_set_sighandler, which does not
+ override an existing SIG_IGN handler (SIGALRM is ok since it does
+ the check inline; doesn't mess with SIGWINCH)
+
+ 6/30
+ ----
+variables.h
+ - additional defines for the new `nameref' variable attribute
+ (att_nameref): nameref_p, nameref_cell, var_setref
+
+variables.c
+ - find_variable_nameref: resolve SHELL_VAR V through chain of namerefs
+ - find_variable_last_nameref: resolve variable NAME until last in a
+ chain of possibly more than one nameref starting at shell_variables
+ - find_global_variable_last_nameref: resolve variable NAME until last
+ in a chain of possibly more than one nameref starting at
+ global_variables
+ - find_nameref_at_context: resolve SHELL_VAR V through chain of namerefs
+ in a specific variable context (usually a local variable hash table)
+ - find_variable_nameref_context: resolve SHELL_VAR V through chain of
+ namerefs following a chain of varible contexts
+ - find_variable_last_nameref_context: resolve SHELL_VAR V as in
+ find_variable_last_context, but return the final nameref instead of
+ what the final nameref resolves to
+ - find_variable_tempenv, find_variable_notempenv, find_global_variable,
+ find_shell_variable, find_variable: modified to follow namerefs
+ - find_global_variable_noref: look up a global variable without following
+ any namerefs
+ - find_variable_noref: look up a shell variable without following any
+ namerefs
+ - bind_variable_internal: modify to follow a chain of namerefs in the
+ global variables table; change to handle assignments to a nameref by
+ following nameref chain
+ - bind_variable: modify to follow chain of namerefs when binding to a
+ local variable
+ - unbind_variable: changes to unset nameref variables (unsets both
+ nameref and variable it resolves to)
+
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/nameref.right f
tests/new-exp.tests f
tests/new-exp1.sub f
tests/new-exp2.sub 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
--- /dev/null
+#
+# 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.in 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.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/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/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
+CWRU/mh-folder-comp 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/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/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/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/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/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/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/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-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.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/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/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/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/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/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/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-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-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/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/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
SHELL_VAR *var;
var = find_variable (name);
+ if (var == 0)
+ {
+ /* See if we have a nameref pointing to a variable that hasn't been
+ created yet. */
+ var = find_variable_last_nameref (name);
+ if (var && nameref_p (var))
+ var = (flags & 2) ? make_new_assoc_variable (nameref_cell (var)) : make_new_array_variable (nameref_cell (var));
+ }
if (var == 0)
var = (flags & 2) ? make_new_assoc_variable (name) : make_new_array_variable (name);
--- /dev/null
+/* arrayfunc.c -- High-level array functions used by other parts of the shell. */
+
+/* Copyright (C) 2001-2011 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 (ARRAY_VARS)
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+#include <stdio.h>
+
+#include "bashintl.h"
+
+#include "shell.h"
+#include "pathexp.h"
+
+#include "shmbutil.h"
+
+#include "builtins/common.h"
+
+extern char *this_command_name;
+extern int last_command_exit_value;
+extern int array_needs_making;
+
+static SHELL_VAR *bind_array_var_internal __P((SHELL_VAR *, arrayind_t, char *, char *, int));
+static SHELL_VAR *assign_array_element_internal __P((SHELL_VAR *, char *, char *, char *, int, char *, int));
+
+static char *quote_assign __P((const char *));
+static void quote_array_assignment_chars __P((WORD_LIST *));
+static char *array_value_internal __P((char *, int, int, int *, arrayind_t *));
+
+/* Standard error message to use when encountering an invalid array subscript */
+const char * const bash_badsub_errmsg = N_("bad array subscript");
+
+/* **************************************************************** */
+/* */
+/* Functions to manipulate array variables and perform assignments */
+/* */
+/* **************************************************************** */
+
+/* Convert a shell variable to an array variable. The original value is
+ saved as array[0]. */
+SHELL_VAR *
+convert_var_to_array (var)
+ SHELL_VAR *var;
+{
+ char *oldval;
+ ARRAY *array;
+
+ oldval = value_cell (var);
+ array = array_create ();
+ if (oldval)
+ array_insert (array, 0, oldval);
+
+ FREE (value_cell (var));
+ var_setarray (var, array);
+
+ /* these aren't valid anymore */
+ var->dynamic_value = (sh_var_value_func_t *)NULL;
+ var->assign_func = (sh_var_assign_func_t *)NULL;
+
+ INVALIDATE_EXPORTSTR (var);
+ if (exported_p (var))
+ array_needs_making++;
+
+ VSETATTR (var, att_array);
+ VUNSETATTR (var, att_invisible);
+
+ return var;
+}
+
+/* Convert a shell variable to an array variable. The original value is
+ saved as array[0]. */
+SHELL_VAR *
+convert_var_to_assoc (var)
+ SHELL_VAR *var;
+{
+ char *oldval;
+ HASH_TABLE *hash;
+
+ oldval = value_cell (var);
+ hash = assoc_create (0);
+ if (oldval)
+ assoc_insert (hash, savestring ("0"), oldval);
+
+ FREE (value_cell (var));
+ var_setassoc (var, hash);
+
+ /* these aren't valid anymore */
+ var->dynamic_value = (sh_var_value_func_t *)NULL;
+ var->assign_func = (sh_var_assign_func_t *)NULL;
+
+ INVALIDATE_EXPORTSTR (var);
+ if (exported_p (var))
+ array_needs_making++;
+
+ VSETATTR (var, att_assoc);
+ VUNSETATTR (var, att_invisible);
+
+ return var;
+}
+
+char *
+make_array_variable_value (entry, ind, key, value, flags)
+ SHELL_VAR *entry;
+ arrayind_t ind;
+ char *key;
+ char *value;
+ int flags;
+{
+ SHELL_VAR *dentry;
+ char *newval;
+
+ /* If we're appending, we need the old value of the array reference, so
+ fake out make_variable_value with a dummy SHELL_VAR */
+ if (flags & ASS_APPEND)
+ {
+ dentry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+ dentry->name = savestring (entry->name);
+ if (assoc_p (entry))
+ newval = assoc_reference (assoc_cell (entry), key);
+ else
+ newval = array_reference (array_cell (entry), ind);
+ if (newval)
+ dentry->value = savestring (newval);
+ else
+ {
+ dentry->value = (char *)xmalloc (1);
+ dentry->value[0] = '\0';
+ }
+ dentry->exportstr = 0;
+ dentry->attributes = entry->attributes & ~(att_array|att_assoc|att_exported);
+ /* Leave the rest of the members uninitialized; the code doesn't look
+ at them. */
+ newval = make_variable_value (dentry, value, flags);
+ dispose_variable (dentry);
+ }
+ else
+ newval = make_variable_value (entry, value, flags);
+
+ return newval;
+}
+
+static SHELL_VAR *
+bind_array_var_internal (entry, ind, key, value, flags)
+ SHELL_VAR *entry;
+ arrayind_t ind;
+ char *key;
+ char *value;
+ int flags;
+{
+ char *newval;
+
+ newval = make_array_variable_value (entry, ind, key, value, flags);
+
+ if (entry->assign_func)
+ (*entry->assign_func) (entry, newval, ind, key);
+ else if (assoc_p (entry))
+ assoc_insert (assoc_cell (entry), key, newval);
+ else
+ array_insert (array_cell (entry), ind, newval);
+ FREE (newval);
+
+ return (entry);
+}
+
+/* Perform an array assignment name[ind]=value. If NAME already exists and
+ is not an array, and IND is 0, perform name=value instead. If NAME exists
+ and is not an array, and IND is not 0, convert it into an array with the
+ existing value as name[0].
+
+ If NAME does not exist, just create an array variable, no matter what
+ IND's value may be. */
+SHELL_VAR *
+bind_array_variable (name, ind, value, flags)
+ char *name;
+ arrayind_t ind;
+ char *value;
+ int flags;
+{
+ SHELL_VAR *entry;
+
+ entry = find_shell_variable (name);
+
+ if (entry == (SHELL_VAR *) 0)
+ entry = make_new_array_variable (name);
+ else if (readonly_p (entry) || noassign_p (entry))
+ {
+ if (readonly_p (entry))
+ err_readonly (name);
+ return (entry);
+ }
+ else if (array_p (entry) == 0)
+ entry = convert_var_to_array (entry);
+
+ /* ENTRY is an array variable, and ARRAY points to the value. */
+ return (bind_array_var_internal (entry, ind, 0, value, flags));
+}
+
+SHELL_VAR *
+bind_array_element (entry, ind, value, flags)
+ SHELL_VAR *entry;
+ arrayind_t ind;
+ char *value;
+ int flags;
+{
+ return (bind_array_var_internal (entry, ind, 0, value, flags));
+}
+
+SHELL_VAR *
+bind_assoc_variable (entry, name, key, value, flags)
+ SHELL_VAR *entry;
+ char *name;
+ char *key;
+ char *value;
+ int flags;
+{
+ SHELL_VAR *dentry;
+ char *newval;
+
+ if (readonly_p (entry) || noassign_p (entry))
+ {
+ if (readonly_p (entry))
+ err_readonly (name);
+ return (entry);
+ }
+
+ return (bind_array_var_internal (entry, 0, key, value, flags));
+}
+
+/* Parse NAME, a lhs of an assignment statement of the form v[s], and
+ assign VALUE to that array element by calling bind_array_variable(). */
+SHELL_VAR *
+assign_array_element (name, value, flags)
+ char *name, *value;
+ int flags;
+{
+ char *sub, *vname;
+ int sublen;
+ SHELL_VAR *entry;
+
+ vname = array_variable_name (name, &sub, &sublen);
+
+ if (vname == 0)
+ return ((SHELL_VAR *)NULL);
+
+ if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1))
+ {
+ free (vname);
+ err_badarraysub (name);
+ return ((SHELL_VAR *)NULL);
+ }
+
+ entry = find_variable (vname);
+ entry = assign_array_element_internal (entry, name, vname, sub, sublen, value, flags);
+
+ free (vname);
+ return entry;
+}
+
+static SHELL_VAR *
+assign_array_element_internal (entry, name, vname, sub, sublen, value, flags)
+ SHELL_VAR *entry;
+ char *name; /* only used for error messages */
+ char *vname;
+ char *sub;
+ int sublen;
+ char *value;
+ int flags;
+{
+ char *akey;
+ arrayind_t ind;
+
+ if (entry && assoc_p (entry))
+ {
+ sub[sublen-1] = '\0';
+ akey = expand_assignment_string_to_string (sub, 0); /* [ */
+ sub[sublen-1] = ']';
+ if (akey == 0 || *akey == 0)
+ {
+ err_badarraysub (name);
+ FREE (akey);
+ return ((SHELL_VAR *)NULL);
+ }
+ entry = bind_assoc_variable (entry, vname, akey, value, flags);
+ }
+ else
+ {
+ ind = array_expand_index (entry, sub, sublen);
+ if (ind < 0)
+ {
+ err_badarraysub (name);
+ return ((SHELL_VAR *)NULL);
+ }
+ entry = bind_array_variable (vname, ind, value, flags);
+ }
+
+ return (entry);
+}
+
+/* Find the array variable corresponding to NAME. If there is no variable,
+ create a new array variable. If the variable exists but is not an array,
+ convert it to an indexed array. If FLAGS&1 is non-zero, an existing
+ variable is checked for the readonly or noassign attribute in preparation
+ for assignment (e.g., by the `read' builtin). If FLAGS&2 is non-zero, we
+ create an associative array. */
+SHELL_VAR *
+find_or_make_array_variable (name, flags)
+ char *name;
+ int flags;
+{
+ SHELL_VAR *var;
+
+ var = find_variable (name);
+ if (var == 0)
+ {
+ var = find_variable_last_nameref (name);
+ if (var && nameref_p (var))
+ var = (flags & 2) ? make_new_assoc_variable (nameref_cell (var)) : make_new_array_variable (nameref_cell (var));
+ }
+
+ if (var == 0)
+ var = (flags & 2) ? make_new_assoc_variable (name) : make_new_array_variable (name);
+ else if ((flags & 1) && (readonly_p (var) || noassign_p (var)))
+ {
+ if (readonly_p (var))
+ err_readonly (name);
+ return ((SHELL_VAR *)NULL);
+ }
+ else if ((flags & 2) && array_p (var))
+ {
+ last_command_exit_value = 1;
+ report_error (_("%s: cannot convert indexed to associative array"), name);
+ return ((SHELL_VAR *)NULL);
+ }
+ else if (array_p (var) == 0 && assoc_p (var) == 0)
+ var = convert_var_to_array (var);
+
+ return (var);
+}
+
+/* Perform a compound assignment statement for array NAME, where VALUE is
+ the text between the parens: NAME=( VALUE ) */
+SHELL_VAR *
+assign_array_from_string (name, value, flags)
+ char *name, *value;
+ int flags;
+{
+ SHELL_VAR *var;
+ int vflags;
+
+ vflags = 1;
+ if (flags & ASS_MKASSOC)
+ vflags |= 2;
+
+ var = find_or_make_array_variable (name, vflags);
+ if (var == 0)
+ return ((SHELL_VAR *)NULL);
+
+ return (assign_array_var_from_string (var, value, flags));
+}
+
+/* Sequentially assign the indices of indexed array variable VAR from the
+ words in LIST. */
+SHELL_VAR *
+assign_array_var_from_word_list (var, list, flags)
+ SHELL_VAR *var;
+ WORD_LIST *list;
+ int flags;
+{
+ register arrayind_t i;
+ register WORD_LIST *l;
+ ARRAY *a;
+
+ a = array_cell (var);
+ i = (flags & ASS_APPEND) ? array_max_index (a) + 1 : 0;
+
+ for (l = list; l; l = l->next, i++)
+ if (var->assign_func)
+ (*var->assign_func) (var, l->word->word, i, 0);
+ else
+ array_insert (a, i, l->word->word);
+ return var;
+}
+
+WORD_LIST *
+expand_compound_array_assignment (var, value, flags)
+ SHELL_VAR *var;
+ char *value;
+ int flags;
+{
+ WORD_LIST *list, *nlist;
+ WORD_LIST *hd, *tl, *t, *n;
+ char *val;
+ int ni;
+
+ /* This condition is true when invoked from the declare builtin with a
+ command like
+ declare -a d='([1]="" [2]="bdef" [5]="hello world" "test")' */
+ if (*value == '(') /*)*/
+ {
+ ni = 1;
+ val = extract_array_assignment_list (value, &ni);
+ if (val == 0)
+ return (WORD_LIST *)NULL;
+ }
+ else
+ val = value;
+
+ /* Expand the value string into a list of words, performing all the
+ shell expansions including pathname generation and word splitting. */
+ /* First we split the string on whitespace, using the shell parser
+ (ksh93 seems to do this). */
+ list = parse_string_to_word_list (val, 1, "array assign");
+
+ if (var && assoc_p (var))
+ {
+ if (val != value)
+ free (val);
+ return list;
+ }
+
+ /* If we're using [subscript]=value, we need to quote each [ and ] to
+ prevent unwanted filename expansion. This doesn't need to be done
+ for associative array expansion, since that uses a different expansion
+ function (see assign_compound_array_list below). */
+ if (list)
+ quote_array_assignment_chars (list);
+
+ /* Now that we've split it, perform the shell expansions on each
+ word in the list. */
+ nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL;
+
+ dispose_words (list);
+
+ if (val != value)
+ free (val);
+
+ return nlist;
+}
+
+/* Callers ensure that VAR is not NULL */
+void
+assign_compound_array_list (var, nlist, flags)
+ SHELL_VAR *var;
+ WORD_LIST *nlist;
+ int flags;
+{
+ ARRAY *a;
+ HASH_TABLE *h;
+ WORD_LIST *list;
+ char *w, *val, *nval;
+ int len, iflags, free_val;
+ arrayind_t ind, last_ind;
+ char *akey;
+
+ a = (var && array_p (var)) ? array_cell (var) : (ARRAY *)0;
+ h = (var && assoc_p (var)) ? assoc_cell (var) : (HASH_TABLE *)0;
+
+ akey = (char *)0;
+ ind = 0;
+
+ /* Now that we are ready to assign values to the array, kill the existing
+ value. */
+ if ((flags & ASS_APPEND) == 0)
+ {
+ if (a && array_p (var))
+ array_flush (a);
+ else if (h && assoc_p (var))
+ assoc_flush (h);
+ }
+
+ last_ind = (a && (flags & ASS_APPEND)) ? array_max_index (a) + 1 : 0;
+
+ for (list = nlist; list; list = list->next)
+ {
+ iflags = flags;
+ w = list->word->word;
+
+ /* We have a word of the form [ind]=value */
+ if ((list->word->flags & W_ASSIGNMENT) && w[0] == '[')
+ {
+ /* Don't have to handle embedded quotes specially any more, since
+ associative array subscripts have not been expanded yet (see
+ above). */
+ len = skipsubscript (w, 0, 0);
+
+ /* XXX - changes for `+=' */
+ if (w[len] != ']' || (w[len+1] != '=' && (w[len+1] != '+' || w[len+2] != '=')))
+ {
+ if (assoc_p (var))
+ {
+ err_badarraysub (w);
+ continue;
+ }
+ nval = make_variable_value (var, w, flags);
+ if (var->assign_func)
+ (*var->assign_func) (var, nval, last_ind, 0);
+ else
+ array_insert (a, last_ind, nval);
+ FREE (nval);
+ last_ind++;
+ continue;
+ }
+
+ if (len == 1)
+ {
+ err_badarraysub (w);
+ continue;
+ }
+
+ if (ALL_ELEMENT_SUB (w[1]) && len == 2)
+ {
+ last_command_exit_value = 1;
+ if (assoc_p (var))
+ report_error (_("%s: invalid associative array key"), w);
+ else
+ report_error (_("%s: cannot assign to non-numeric index"), w);
+ continue;
+ }
+
+ if (array_p (var))
+ {
+ ind = array_expand_index (var, w + 1, len);
+ if (ind < 0)
+ {
+ err_badarraysub (w);
+ continue;
+ }
+
+ last_ind = ind;
+ }
+ else if (assoc_p (var))
+ {
+ /* This is not performed above, see expand_compound_array_assignment */
+ w[len] = '\0'; /*[*/
+ akey = expand_assignment_string_to_string (w+1, 0);
+ w[len] = ']';
+ /* And we need to expand the value also, see below */
+ if (akey == 0 || *akey == 0)
+ {
+ err_badarraysub (w);
+ FREE (akey);
+ continue;
+ }
+ }
+
+ /* XXX - changes for `+=' -- just accept the syntax. ksh93 doesn't do this */
+ if (w[len + 1] == '+' && w[len + 2] == '=')
+ {
+ iflags |= ASS_APPEND;
+ val = w + len + 3;
+ }
+ else
+ val = w + len + 2;
+ }
+ else if (assoc_p (var))
+ {
+ last_command_exit_value = 1;
+ report_error (_("%s: %s: must use subscript when assigning associative array"), var->name, w);
+ continue;
+ }
+ else /* No [ind]=value, just a stray `=' */
+ {
+ ind = last_ind;
+ val = w;
+ }
+
+ free_val = 0;
+ /* See above; we need to expand the value here */
+ if (assoc_p (var))
+ {
+ val = expand_assignment_string_to_string (val, 0);
+ free_val = 1;
+ }
+
+ if (integer_p (var))
+ this_command_name = (char *)NULL; /* no command name for errors */
+ bind_array_var_internal (var, ind, akey, val, iflags);
+ last_ind++;
+
+ if (free_val)
+ free (val);
+ }
+}
+
+/* Perform a compound array assignment: VAR->name=( VALUE ). The
+ VALUE has already had the parentheses stripped. */
+SHELL_VAR *
+assign_array_var_from_string (var, value, flags)
+ SHELL_VAR *var;
+ char *value;
+ int flags;
+{
+ WORD_LIST *nlist;
+
+ if (value == 0)
+ return var;
+
+ nlist = expand_compound_array_assignment (var, value, flags);
+ assign_compound_array_list (var, nlist, flags);
+
+ if (nlist)
+ dispose_words (nlist);
+ return (var);
+}
+
+/* Quote globbing chars and characters in $IFS before the `=' in an assignment
+ statement (usually a compound array assignment) to protect them from
+ unwanted filename expansion or word splitting. */
+static char *
+quote_assign (string)
+ const char *string;
+{
+ size_t slen;
+ int saw_eq;
+ char *temp, *t, *subs;
+ const char *s, *send;
+ int ss, se;
+ DECLARE_MBSTATE;
+
+ slen = strlen (string);
+ send = string + slen;
+
+ t = temp = (char *)xmalloc (slen * 2 + 1);
+ saw_eq = 0;
+ for (s = string; *s; )
+ {
+ if (*s == '=')
+ saw_eq = 1;
+ if (saw_eq == 0 && *s == '[') /* looks like a subscript */
+ {
+ ss = s - string;
+ se = skipsubscript (string, ss, 0);
+ subs = substring (s, ss, se);
+ *t++ = '\\';
+ strcpy (t, subs);
+ t += se - ss;
+ *t++ = '\\';
+ *t++ = ']';
+ s += se + 1;
+ free (subs);
+ continue;
+ }
+ if (saw_eq == 0 && (glob_char_p (s) || isifs (*s)))
+ *t++ = '\\';
+
+ COPY_CHAR_P (t, s, send);
+ }
+ *t = '\0';
+ return temp;
+}
+
+/* For each word in a compound array assignment, if the word looks like
+ [ind]=value, quote globbing chars and characters in $IFS before the `='. */
+static void
+quote_array_assignment_chars (list)
+ WORD_LIST *list;
+{
+ char *nword;
+ WORD_LIST *l;
+
+ for (l = list; l; l = l->next)
+ {
+ if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0')
+ continue; /* should not happen, but just in case... */
+ /* Don't bother if it doesn't look like [ind]=value */
+ if (l->word->word[0] != '[' || mbschr (l->word->word, '=') == 0) /* ] */
+ continue;
+ nword = quote_assign (l->word->word);
+ free (l->word->word);
+ l->word->word = nword;
+ }
+}
+
+/* skipsubscript moved to subst.c to use private functions. 2009/02/24. */
+
+/* This function is called with SUB pointing to just after the beginning
+ `[' of an array subscript and removes the array element to which SUB
+ expands from array VAR. A subscript of `*' or `@' unsets the array. */
+int
+unbind_array_element (var, sub)
+ SHELL_VAR *var;
+ char *sub;
+{
+ int len;
+ arrayind_t ind;
+ char *akey;
+ ARRAY_ELEMENT *ae;
+
+ len = skipsubscript (sub, 0, 0);
+ if (sub[len] != ']' || len == 0)
+ {
+ builtin_error ("%s[%s: %s", var->name, sub, _(bash_badsub_errmsg));
+ return -1;
+ }
+ sub[len] = '\0';
+
+ if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0)
+ {
+ unbind_variable (var->name);
+ return (0);
+ }
+
+ if (assoc_p (var))
+ {
+ akey = expand_assignment_string_to_string (sub, 0); /* [ */
+ if (akey == 0 || *akey == 0)
+ {
+ builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
+ FREE (akey);
+ return -1;
+ }
+ assoc_remove (assoc_cell (var), akey);
+ free (akey);
+ }
+ else
+ {
+ ind = array_expand_index (var, sub, len+1);
+ if (ind < 0)
+ {
+ builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg));
+ return -1;
+ }
+ ae = array_remove (array_cell (var), ind);
+ if (ae)
+ array_dispose_element (ae);
+ }
+
+ return 0;
+}
+
+/* Format and output an array assignment in compound form VAR=(VALUES),
+ suitable for re-use as input. */
+void
+print_array_assignment (var, quoted)
+ SHELL_VAR *var;
+ int quoted;
+{
+ char *vstr;
+
+ vstr = array_to_assign (array_cell (var), quoted);
+
+ if (vstr == 0)
+ printf ("%s=%s\n", var->name, quoted ? "'()'" : "()");
+ else
+ {
+ printf ("%s=%s\n", var->name, vstr);
+ free (vstr);
+ }
+}
+
+/* Format and output an associative array assignment in compound form
+ VAR=(VALUES), suitable for re-use as input. */
+void
+print_assoc_assignment (var, quoted)
+ SHELL_VAR *var;
+ int quoted;
+{
+ char *vstr;
+
+ vstr = assoc_to_assign (assoc_cell (var), quoted);
+
+ if (vstr == 0)
+ printf ("%s=%s\n", var->name, quoted ? "'()'" : "()");
+ else
+ {
+ printf ("%s=%s\n", var->name, vstr);
+ free (vstr);
+ }
+}
+
+/***********************************************************************/
+/* */
+/* Utility functions to manage arrays and their contents for expansion */
+/* */
+/***********************************************************************/
+
+/* Return 1 if NAME is a properly-formed array reference v[sub]. */
+int
+valid_array_reference (name)
+ char *name;
+{
+ char *t;
+ int r, len;
+
+ t = mbschr (name, '['); /* ] */
+ if (t)
+ {
+ *t = '\0';
+ r = legal_identifier (name);
+ *t = '[';
+ if (r == 0)
+ return 0;
+ /* Check for a properly-terminated non-blank subscript. */
+ len = skipsubscript (t, 0, 0);
+ if (t[len] != ']' || len == 1)
+ return 0;
+ for (r = 1; r < len; r++)
+ if (whitespace (t[r]) == 0)
+ return 1;
+ return 0;
+ }
+ return 0;
+}
+
+/* Expand the array index beginning at S and extending LEN characters. */
+arrayind_t
+array_expand_index (var, s, len)
+ SHELL_VAR *var;
+ char *s;
+ int len;
+{
+ char *exp, *t;
+ int expok;
+ arrayind_t val;
+
+ exp = (char *)xmalloc (len);
+ strncpy (exp, s, len - 1);
+ exp[len - 1] = '\0';
+ t = expand_arith_string (exp, 0);
+ this_command_name = (char *)NULL;
+ val = evalexp (t, &expok);
+ free (t);
+ free (exp);
+ if (expok == 0)
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+
+ top_level_cleanup ();
+ jump_to_top_level (DISCARD);
+ }
+ return val;
+}
+
+/* Return the name of the variable specified by S without any subscript.
+ If SUBP is non-null, return a pointer to the start of the subscript
+ in *SUBP. If LENP is non-null, the length of the subscript is returned
+ in *LENP. This returns newly-allocated memory. */
+char *
+array_variable_name (s, subp, lenp)
+ char *s, **subp;
+ int *lenp;
+{
+ char *t, *ret;
+ int ind, ni;
+
+ t = mbschr (s, '[');
+ if (t == 0)
+ {
+ if (subp)
+ *subp = t;
+ if (lenp)
+ *lenp = 0;
+ return ((char *)NULL);
+ }
+ ind = t - s;
+ ni = skipsubscript (s, ind, 0);
+ if (ni <= ind + 1 || s[ni] != ']')
+ {
+ err_badarraysub (s);
+ if (subp)
+ *subp = t;
+ if (lenp)
+ *lenp = 0;
+ return ((char *)NULL);
+ }
+
+ *t = '\0';
+ ret = savestring (s);
+ *t++ = '['; /* ] */
+
+ if (subp)
+ *subp = t;
+ if (lenp)
+ *lenp = ni - ind;
+
+ return ret;
+}
+
+/* Return the variable specified by S without any subscript. If SUBP is
+ non-null, return a pointer to the start of the subscript in *SUBP.
+ If LENP is non-null, the length of the subscript is returned in *LENP. */
+SHELL_VAR *
+array_variable_part (s, subp, lenp)
+ char *s, **subp;
+ int *lenp;
+{
+ char *t;
+ SHELL_VAR *var;
+
+ t = array_variable_name (s, subp, lenp);
+ if (t == 0)
+ return ((SHELL_VAR *)NULL);
+ var = find_variable (t);
+
+ free (t);
+ return (var == 0 || invisible_p (var)) ? (SHELL_VAR *)0 : var;
+}
+
+#define INDEX_ERROR() \
+ do \
+ { \
+ if (var) \
+ err_badarraysub (var->name); \
+ else \
+ { \
+ t[-1] = '\0'; \
+ err_badarraysub (s); \
+ t[-1] = '['; /* ] */\
+ } \
+ return ((char *)NULL); \
+ } \
+ while (0)
+
+/* Return a string containing the elements in the array and subscript
+ described by S. If the subscript is * or @, obeys quoting rules akin
+ to the expansion of $* and $@ including double quoting. If RTYPE
+ is non-null it gets 1 if the array reference is name[*], 2 if the
+ reference is name[@], and 0 otherwise. */
+static char *
+array_value_internal (s, quoted, flags, rtype, indp)
+ char *s;
+ int quoted, flags, *rtype;
+ arrayind_t *indp;
+{
+ int len;
+ arrayind_t ind;
+ char *akey;
+ char *retval, *t, *temp;
+ WORD_LIST *l;
+ SHELL_VAR *var;
+
+ var = array_variable_part (s, &t, &len);
+
+ /* Expand the index, even if the variable doesn't exist, in case side
+ effects are needed, like ${w[i++]} where w is unset. */
+#if 0
+ if (var == 0)
+ return (char *)NULL;
+#endif
+
+ if (len == 0)
+ return ((char *)NULL); /* error message already printed */
+
+ /* [ */
+ akey = 0;
+ if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
+ {
+ if (rtype)
+ *rtype = (t[0] == '*') ? 1 : 2;
+ if ((flags & AV_ALLOWALL) == 0)
+ {
+ err_badarraysub (s);
+ return ((char *)NULL);
+ }
+ else if (var == 0 || value_cell (var) == 0) /* XXX - check for invisible_p(var) ? */
+ return ((char *)NULL);
+ else if (array_p (var) == 0 && assoc_p (var) == 0)
+ l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL);
+ else if (assoc_p (var))
+ {
+ l = assoc_to_word_list (assoc_cell (var));
+ if (l == (WORD_LIST *)NULL)
+ return ((char *)NULL);
+ }
+ else
+ {
+ l = array_to_word_list (array_cell (var));
+ if (l == (WORD_LIST *)NULL)
+ return ((char *) NULL);
+ }
+
+ if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ {
+ temp = string_list_dollar_star (l);
+ retval = quote_string (temp); /* XXX - leak here */
+ free (temp);
+ }
+ else /* ${name[@]} or unquoted ${name[*]} */
+ retval = string_list_dollar_at (l, quoted); /* XXX - leak here */
+
+ dispose_words (l);
+ }
+ else
+ {
+ if (rtype)
+ *rtype = 0;
+ if (var == 0 || array_p (var) || assoc_p (var) == 0)
+ {
+ if ((flags & AV_USEIND) == 0 || indp == 0)
+ {
+ ind = array_expand_index (var, t, len);
+ if (ind < 0)
+ {
+ /* negative subscripts to indexed arrays count back from end */
+ if (var && array_p (var))
+ ind = array_max_index (array_cell (var)) + 1 + ind;
+ if (ind < 0)
+ INDEX_ERROR();
+ }
+ if (indp)
+ *indp = ind;
+ }
+ else if (indp)
+ ind = *indp;
+ }
+ else if (assoc_p (var))
+ {
+ t[len - 1] = '\0';
+ akey = expand_assignment_string_to_string (t, 0); /* [ */
+ t[len - 1] = ']';
+ if (akey == 0 || *akey == 0)
+ {
+ FREE (akey);
+ INDEX_ERROR();
+ }
+ }
+
+ if (var == 0 || value_cell (var) == 0) /* XXX - check invisible_p(var) ? */
+ {
+ FREE (akey);
+ return ((char *)NULL);
+ }
+ if (array_p (var) == 0 && assoc_p (var) == 0)
+ return (ind == 0 ? value_cell (var) : (char *)NULL);
+ else if (assoc_p (var))
+ {
+ retval = assoc_reference (assoc_cell (var), akey);
+ free (akey);
+ }
+ else
+ retval = array_reference (array_cell (var), ind);
+ }
+
+ return retval;
+}
+
+/* Return a string containing the elements described by the array and
+ subscript contained in S, obeying quoting for subscripts * and @. */
+char *
+array_value (s, quoted, flags, rtype, indp)
+ char *s;
+ int quoted, flags, *rtype;
+ arrayind_t *indp;
+{
+ return (array_value_internal (s, quoted, flags|AV_ALLOWALL, rtype, indp));
+}
+
+/* Return the value of the array indexing expression S as a single string.
+ If (FLAGS & AV_ALLOWALL) is 0, do not allow `@' and `*' subscripts. This
+ is used by other parts of the shell such as the arithmetic expression
+ evaluator in expr.c. */
+char *
+get_array_value (s, flags, rtype, indp)
+ char *s;
+ int flags, *rtype;
+ arrayind_t *indp;
+{
+ return (array_value_internal (s, 0, flags, rtype, indp));
+}
+
+char *
+array_keys (s, quoted)
+ char *s;
+ int quoted;
+{
+ int len;
+ char *retval, *t, *temp;
+ WORD_LIST *l;
+ SHELL_VAR *var;
+
+ var = array_variable_part (s, &t, &len);
+
+ /* [ */
+ if (var == 0 || ALL_ELEMENT_SUB (t[0]) == 0 || t[1] != ']')
+ return (char *)NULL;
+
+ if (var_isset (var) == 0 || invisible_p (var))
+ return (char *)NULL;
+
+ if (array_p (var) == 0 && assoc_p (var) == 0)
+ l = add_string_to_list ("0", (WORD_LIST *)NULL);
+ else if (assoc_p (var))
+ l = assoc_keys_to_word_list (assoc_cell (var));
+ else
+ l = array_keys_to_word_list (array_cell (var));
+ if (l == (WORD_LIST *)NULL)
+ return ((char *) NULL);
+
+ if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+ {
+ temp = string_list_dollar_star (l);
+ retval = quote_string (temp);
+ free (temp);
+ }
+ else /* ${!name[@]} or unquoted ${!name[*]} */
+ retval = string_list_dollar_at (l, quoted);
+
+ dispose_words (l);
+ return retval;
+}
+#endif /* ARRAY_VARS */
This file is declare.def, from which is created declare.c.
It implements the builtins "declare" and "local" in Bash.
-Copyright (C) 1987-2010 Free Software Foundation, Inc.
+Copyright (C) 1987-2012 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
$BUILTIN declare
$FUNCTION declare_builtin
-$SHORT_DOC declare [-aAfFgilrtux] [-p] [name[=value] ...]
+$SHORT_DOC declare [-aAfFgilnrtux] [-p] [name[=value] ...]
Set variable values and attributes.
Declare variables and give them attributes. If no NAMEs are given,
-A to make NAMEs associative arrays (if supported)
-i to make NAMEs have the `integer' attribute
-l to convert NAMEs to lower case on assignment
+ -n make NAME a reference to the variable named by its value
-r to make NAMEs readonly
-t to make NAMEs have the `trace' attribute
-u to convert NAMEs to upper case on assignment
}
#if defined (ARRAY_VARS)
-# define DECLARE_OPTS "+acfgilprtuxAF"
+# define DECLARE_OPTS "+acfgilnprtuxAF"
#else
-# define DECLARE_OPTS "+cfgilprtuxF"
+# define DECLARE_OPTS "+cfgilnprtuxF"
#endif
/* The workhorse function. */
int local_var;
{
int flags_on, flags_off, *flags;
- int any_failed, assign_error, pflag, nodefs, opt, mkglobal;
+ int any_failed, assign_error, pflag, nodefs, opt, mkglobal, onref, offref;
char *t, *subscript_start;
- SHELL_VAR *var;
+ SHELL_VAR *var, *refvar;
FUNCTION_DEF *shell_fn;
flags_on = flags_off = any_failed = assign_error = pflag = nodefs = mkglobal = 0;
+ refvar = (SHELL_VAR *)NULL;
reset_internal_getopt ();
while ((opt = internal_getopt (list, DECLARE_OPTS)) != EOF)
{
case 'i':
*flags |= att_integer;
break;
+ case 'n':
+ *flags |= att_nameref;
+ break;
case 'r':
*flags |= att_readonly;
break;
/* XXX - this has consequences when we're making a local copy of a
variable that was in the temporary environment. Watch out
for this. */
+ refvar = (SHELL_VAR *)NULL;
if (variable_context && mkglobal == 0 && ((flags_on & att_function) == 0))
{
#if defined (ARRAY_VARS)
var = make_local_array_variable (name, making_array_special);
else
#endif
- var = make_local_variable (name);
+#if 0
+ /* XXX - this doesn't work right yet. */
+ /* See below for rationale for doing this. */
+ if (flags_on & att_nameref)
+ {
+ /* See if we are trying to modify an existing nameref variable */
+ var = find_variable_last_nameref (name);
+ if (var && nameref_p (var) == 0)
+ var = make_local_variable (name);
+ }
+ else if (flags_off & att_nameref)
+ {
+ var = (SHELL_VAR *)NULL;
+ /* See if we are trying to modify an existing nameref variable */
+ refvar = find_variable_last_nameref (name);
+ if (refvar && nameref_p (refvar) == 0)
+ refvar = 0;
+ if (refvar)
+ var = make_local_variable (nameref_cell (refvar));
+ if (var == 0)
+ var = make_local_variable (name);
+ }
+ else
+#endif
+ var = make_local_variable (name);
if (var == 0)
{
any_failed++;
else /* declare -[aAirx] name [name...] */
{
/* Non-null if we just created or fetched a local variable. */
+ /* Here's what ksh93 seems to do. If we are modifying an existing
+ nameref variable, we don't follow the nameref chain past the last
+ nameref, and we set the nameref variable's value so future
+ references to that variable will return the value of the variable
+ we're assigning right now. */
+ if (var == 0 && (flags_on & att_nameref))
+ {
+ /* See if we are trying to modify an existing nameref variable */
+ var = mkglobal ? find_global_variable_last_nameref (name) : find_variable_last_nameref (name);
+ if (var && nameref_p (var) == 0)
+ var = 0;
+ }
+ /* However, if we're turning off the nameref attribute on an existing
+ nameref variable, we first follow the nameref chain to the end,
+ modify the value of the variable this nameref variable references,
+ *CHANGING ITS VALUE AS A SIDE EFFECT* then turn off the nameref
+ flag *LEAVING THE NAMEREF VARIABLE'S VALUE UNCHANGED* */
+ else if (var == 0 && (flags_off & att_nameref))
+ {
+ /* See if we are trying to modify an existing nameref variable */
+ refvar = mkglobal ? find_global_variable_last_nameref (name) : find_variable_last_nameref (name);
+ if (refvar && nameref_p (refvar) == 0)
+ refvar = 0;
+ if (refvar)
+ var = mkglobal ? find_global_variable (nameref_cell (refvar)) : find_variable (nameref_cell (refvar));
+ }
+
if (var == 0)
var = mkglobal ? find_global_variable (name) : find_variable (name);
var = convert_var_to_array (var);
#endif /* ARRAY_VARS */
+ /* XXX - we note that we are turning on nameref attribute and defer
+ setting it until the assignment has been made so we don't do an
+ inadvertent nameref lookup. Might have to do the same thing for
+ flags_off&att_nameref. */
+ /* XXX - ksh93 makes it an error to set a readonly nameref variable
+ using a single typeset command. */
+ onref = (flags_on & att_nameref);
+ flags_on &= ~att_nameref;
+ if (array_p (var) || assoc_p (var)
+ || (offset && compound_array_assign)
+ || simple_array_assign)
+ onref = 0; /* array variables may not be namerefs */
+
+ /* ksh93 seems to do this */
+ offref = (flags_off & att_nameref);
+ flags_off &= ~att_nameref;
+
VSETATTR (var, flags_on);
VUNSETATTR (var, flags_off);
if (var == 0) /* some kind of assignment error */
{
assign_error++;
+ flags_on |= onref;
+ flags_off |= offref;
NEXT_VARIABLE ();
}
}
}
}
+ /* Turn on nameref attribute we deferred above. */
+ /* XXX - should we turn on the noassign attribute for consistency with
+ ksh93 when we turn on the nameref attribute? */
+ VSETATTR (var, onref);
+ flags_on |= onref;
+ VUNSETATTR (var, offref);
+ flags_off |= offref;
+ /* Yuck. ksh93 compatibility */
+ if (refvar)
+ VUNSETATTR (refvar, flags_off);
+
stupidly_hack_special_variables (name);
NEXT_VARIABLE ();
--- /dev/null
+This file is declare.def, from which is created declare.c.
+It implements the builtins "declare" and "local" in Bash.
+
+Copyright (C) 1987-2012 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/>.
+
+$PRODUCES declare.c
+
+$BUILTIN declare
+$FUNCTION declare_builtin
+$SHORT_DOC declare [-aAfFgilnrtux] [-p] [name[=value] ...]
+Set variable values and attributes.
+
+Declare variables and give them attributes. If no NAMEs are given,
+display the attributes and values of all variables.
+
+Options:
+ -f restrict action or display to function names and definitions
+ -F restrict display to function names only (plus line number and
+ source file when debugging)
+ -g create global variables when used in a shell function; otherwise
+ ignored
+ -p display the attributes and value of each NAME
+
+Options which set attributes:
+ -a to make NAMEs indexed arrays (if supported)
+ -A to make NAMEs associative arrays (if supported)
+ -i to make NAMEs have the `integer' attribute
+ -l to convert NAMEs to lower case on assignment
+ -n make NAME a reference to the variable named by its value
+ -r to make NAMEs readonly
+ -t to make NAMEs have the `trace' attribute
+ -u to convert NAMEs to upper case on assignment
+ -x to make NAMEs export
+
+Using `+' instead of `-' turns off the given attribute.
+
+Variables with the integer attribute have arithmetic evaluation (see
+the `let' command) performed when the variable is assigned a value.
+
+When used in a function, `declare' makes NAMEs local, as with the `local'
+command. The `-g' option suppresses this behavior.
+
+Exit Status:
+Returns success unless an invalid option is supplied or an error occurs.
+$END
+
+$BUILTIN typeset
+$FUNCTION declare_builtin
+$SHORT_DOC typeset [-aAfFgilrtux] [-p] name[=value] ...
+Set variable values and attributes.
+
+Obsolete. See `help declare'.
+$END
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "common.h"
+#include "builtext.h"
+#include "bashgetopt.h"
+
+extern int array_needs_making;
+extern int posixly_correct;
+
+static int declare_internal __P((register WORD_LIST *, int));
+
+/* Declare or change variable attributes. */
+int
+declare_builtin (list)
+ register WORD_LIST *list;
+{
+ return (declare_internal (list, 0));
+}
+
+$BUILTIN local
+$FUNCTION local_builtin
+$SHORT_DOC local [option] name[=value] ...
+Define local variables.
+
+Create a local variable called NAME, and give it VALUE. OPTION can
+be any option accepted by `declare'.
+
+Local variables can only be used within a function; they are visible
+only to the function where they are defined and its children.
+
+Exit Status:
+Returns success unless an invalid option is supplied, an error occurs,
+or the shell is not executing a function.
+$END
+int
+local_builtin (list)
+ register WORD_LIST *list;
+{
+ if (variable_context)
+ return (declare_internal (list, 1));
+ else
+ {
+ builtin_error (_("can only be used in a function"));
+ return (EXECUTION_FAILURE);
+ }
+}
+
+#if defined (ARRAY_VARS)
+# define DECLARE_OPTS "+acfgilnprtuxAF"
+#else
+# define DECLARE_OPTS "+cfgilnprtuxF"
+#endif
+
+/* The workhorse function. */
+static int
+declare_internal (list, local_var)
+ register WORD_LIST *list;
+ int local_var;
+{
+ int flags_on, flags_off, *flags;
+ int any_failed, assign_error, pflag, nodefs, opt, mkglobal, onref, offref;
+ char *t, *subscript_start;
+ SHELL_VAR *var, *refvar;
+ FUNCTION_DEF *shell_fn;
+
+ flags_on = flags_off = any_failed = assign_error = pflag = nodefs = mkglobal = 0;
+ refvar = (SHELL_VAR *)NULL;
+ reset_internal_getopt ();
+ while ((opt = internal_getopt (list, DECLARE_OPTS)) != EOF)
+ {
+ flags = list_opttype == '+' ? &flags_off : &flags_on;
+
+ switch (opt)
+ {
+ case 'a':
+#if defined (ARRAY_VARS)
+ *flags |= att_array;
+ break;
+#else
+ builtin_usage ();
+ return (EX_USAGE);
+#endif
+ case 'A':
+#if defined (ARRAY_VARS)
+ *flags |= att_assoc;
+ break;
+#else
+ builtin_usage ();
+ return (EX_USAGE);
+#endif
+ case 'p':
+ if (local_var == 0)
+ pflag++;
+ break;
+ case 'F':
+ nodefs++;
+ *flags |= att_function;
+ break;
+ case 'f':
+ *flags |= att_function;
+ break;
+ case 'g':
+ if (flags == &flags_on)
+ mkglobal = 1;
+ break;
+ case 'i':
+ *flags |= att_integer;
+ break;
+ case 'n':
+ *flags |= att_nameref;
+ break;
+ case 'r':
+ *flags |= att_readonly;
+ break;
+ case 't':
+ *flags |= att_trace;
+ break;
+ case 'x':
+ *flags |= att_exported;
+ array_needs_making = 1;
+ break;
+#if defined (CASEMOD_ATTRS)
+# if defined (CASEMOD_CAPCASE)
+ case 'c':
+ *flags |= att_capcase;
+ if (flags == &flags_on)
+ flags_off |= att_uppercase|att_lowercase;
+ break;
+# endif
+ case 'l':
+ *flags |= att_lowercase;
+ if (flags == &flags_on)
+ flags_off |= att_capcase|att_uppercase;
+ break;
+ case 'u':
+ *flags |= att_uppercase;
+ if (flags == &flags_on)
+ flags_off |= att_capcase|att_lowercase;
+ break;
+#endif /* CASEMOD_ATTRS */
+ default:
+ builtin_usage ();
+ return (EX_USAGE);
+ }
+ }
+
+ list = loptend;
+
+ /* If there are no more arguments left, then we just want to show
+ some variables. */
+ if (list == 0) /* declare -[aAfFirtx] */
+ {
+ /* Show local variables defined at this context level if this is
+ the `local' builtin. */
+ if (local_var)
+ {
+ register SHELL_VAR **vlist;
+ register int i;
+
+ vlist = all_local_variables ();
+
+ if (vlist)
+ {
+ for (i = 0; vlist[i]; i++)
+ print_assignment (vlist[i]);
+
+ free (vlist);
+ }
+ }
+ else if (pflag && (flags_on == 0 || flags_on == att_function))
+ show_all_var_attributes (flags_on == 0, nodefs);
+ else if (flags_on == 0)
+ return (set_builtin ((WORD_LIST *)NULL));
+ else
+ set_or_show_attributes ((WORD_LIST *)NULL, flags_on, nodefs);
+
+ return (sh_chkwrite (EXECUTION_SUCCESS));
+ }
+
+ if (pflag) /* declare -p [-aAfFirtx] name [name...] */
+ {
+ for (any_failed = 0; list; list = list->next)
+ {
+ pflag = show_name_attributes (list->word->word, nodefs);
+ if (pflag)
+ {
+ sh_notfound (list->word->word);
+ any_failed++;
+ }
+ }
+ return (sh_chkwrite (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS));
+ }
+
+#define NEXT_VARIABLE() free (name); list = list->next; continue
+
+ /* There are arguments left, so we are making variables. */
+ while (list) /* declare [-aAfFirx] name [name ...] */
+ {
+ char *value, *name;
+ int offset, aflags;
+#if defined (ARRAY_VARS)
+ int making_array_special, compound_array_assign, simple_array_assign;
+#endif
+
+ name = savestring (list->word->word);
+ offset = assignment (name, 0);
+ aflags = 0;
+
+ if (offset) /* declare [-aAfFirx] name=value */
+ {
+ name[offset] = '\0';
+ value = name + offset + 1;
+ if (name[offset - 1] == '+')
+ {
+ aflags |= ASS_APPEND;
+ name[offset - 1] = '\0';
+ }
+ }
+ else
+ value = "";
+
+#if defined (ARRAY_VARS)
+ compound_array_assign = simple_array_assign = 0;
+ subscript_start = (char *)NULL;
+ if (t = strchr (name, '[')) /* ] */
+ {
+ /* If offset != 0 we have already validated any array reference */
+ if (offset == 0 && valid_array_reference (name) == 0)
+ {
+ sh_invalidid (name);
+ assign_error++;
+ NEXT_VARIABLE ();
+ }
+ subscript_start = t;
+ *t = '\0';
+ making_array_special = 1;
+ }
+ else
+ making_array_special = 0;
+#endif
+
+ /* If we're in posix mode or not looking for a shell function (since
+ shell function names don't have to be valid identifiers when the
+ shell's not in posix mode), check whether or not the argument is a
+ valid, well-formed shell identifier. */
+ if ((posixly_correct || (flags_on & att_function) == 0) && legal_identifier (name) == 0)
+ {
+ sh_invalidid (name);
+ assign_error++;
+ NEXT_VARIABLE ();
+ }
+
+ /* If VARIABLE_CONTEXT has a non-zero value, then we are executing
+ inside of a function. This means we should make local variables,
+ not global ones. */
+
+ /* XXX - this has consequences when we're making a local copy of a
+ variable that was in the temporary environment. Watch out
+ for this. */
+ refvar = (SHELL_VAR *)NULL;
+ if (variable_context && mkglobal == 0 && ((flags_on & att_function) == 0))
+ {
+#if defined (ARRAY_VARS)
+ if (flags_on & att_assoc)
+ var = make_local_assoc_variable (name);
+ else if ((flags_on & att_array) || making_array_special)
+ var = make_local_array_variable (name, making_array_special);
+ else
+#endif
+#if 0
+ /* XXX - this doesn't work right yet. */
+ /* See below for rationale for doing this. */
+ if (flags_on & att_nameref)
+ {
+ /* See if we are trying to modify an existing nameref variable */
+ var = find_variable_last_nameref (name);
+ if (var && nameref_p (var) == 0)
+ var = make_local_variable (name);
+ }
+ else if (flags_off & att_nameref)
+ {
+ var = (SHELL_VAR *)NULL;
+ /* See if we are trying to modify an existing nameref variable */
+ refvar = find_variable_last_nameref (name);
+ if (refvar && nameref_p (refvar) == 0)
+ refvar = 0;
+ if (refvar)
+ var = make_local_variable (nameref_cell (refvar));
+ if (var == 0)
+ var = make_local_variable (name);
+ }
+ else
+#endif
+ var = make_local_variable (name);
+ if (var == 0)
+ {
+ any_failed++;
+ NEXT_VARIABLE ();
+ }
+ }
+ else
+ var = (SHELL_VAR *)NULL;
+
+ /* If we are declaring a function, then complain about it in some way.
+ We don't let people make functions by saying `typeset -f foo=bar'. */
+
+ /* There should be a way, however, to let people look at a particular
+ function definition by saying `typeset -f foo'. */
+
+ if (flags_on & att_function)
+ {
+ if (offset) /* declare -f [-rix] foo=bar */
+ {
+ builtin_error (_("cannot use `-f' to make functions"));
+ free (name);
+ return (EXECUTION_FAILURE);
+ }
+ else /* declare -f [-rx] name [name...] */
+ {
+ var = find_function (name);
+
+ if (var)
+ {
+ if (readonly_p (var) && (flags_off & att_readonly))
+ {
+ builtin_error (_("%s: readonly function"), name);
+ any_failed++;
+ NEXT_VARIABLE ();
+ }
+
+ /* declare -[Ff] name [name...] */
+ if (flags_on == att_function && flags_off == 0)
+ {
+#if defined (DEBUGGER)
+ if (nodefs && debugging_mode)
+ {
+ shell_fn = find_function_def (var->name);
+ if (shell_fn)
+ printf ("%s %d %s\n", var->name, shell_fn->line, shell_fn->source_file);
+ else
+ printf ("%s\n", var->name);
+ }
+ else
+#endif /* DEBUGGER */
+ {
+ t = nodefs ? var->name
+ : named_function_string (name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL);
+ printf ("%s\n", t);
+ any_failed = sh_chkwrite (any_failed);
+ }
+ }
+ else /* declare -[fF] -[rx] name [name...] */
+ {
+ VSETATTR (var, flags_on);
+ VUNSETATTR (var, flags_off);
+ }
+ }
+ else
+ any_failed++;
+ NEXT_VARIABLE ();
+ }
+ }
+ else /* declare -[aAirx] name [name...] */
+ {
+ /* Non-null if we just created or fetched a local variable. */
+ /* Here's what ksh93 seems to do. If we are modifying an existing
+ nameref variable, we don't follow the nameref chain past the last
+ nameref, and we set the nameref variable's value so future
+ references to that variable will return the value of the variable
+ we're assigning right now. */
+ if (var == 0 && (flags_on & att_nameref))
+ {
+ /* See if we are trying to modify an existing nameref variable */
+ var = mkglobal ? find_global_variable_last_nameref (name) : find_variable_last_nameref (name);
+ if (var && nameref_p (var) == 0)
+ var = 0;
+ }
+ /* However, if we're turning off the nameref attribute on an existing
+ nameref variable, we first follow the nameref chain to the end,
+ modify the value of the variable this nameref variable references,
+ *CHANGING ITS VALUE AS A SIDE EFFECT* then turn off the nameref
+ flag *LEAVING THE NAMEREF VARIABLE'S VALUE UNCHANGED* */
+ else if (var == 0 && (flags_off & att_nameref))
+ {
+ /* See if we are trying to modify an existing nameref variable */
+ refvar = mkglobal ? find_global_variable_last_nameref (name) : find_variable_last_nameref (name);
+ if (refvar && nameref_p (refvar) == 0)
+ refvar = 0;
+ if (refvar)
+ var = mkglobal ? find_global_variable (nameref_cell (refvar)) : find_variable (nameref_cell (refvar));
+ }
+
+ if (var == 0)
+ var = mkglobal ? find_global_variable (name) : find_variable (name);
+
+ if (var == 0)
+ {
+#if defined (ARRAY_VARS)
+ if (flags_on & att_assoc)
+ var = make_new_assoc_variable (name);
+ else if ((flags_on & att_array) || making_array_special)
+ var = make_new_array_variable (name);
+ else
+#endif
+
+ if (offset)
+ var = bind_variable (name, "", 0);
+ else
+ {
+ var = bind_variable (name, (char *)NULL, 0);
+ VSETATTR (var, att_invisible);
+ }
+ }
+
+ /* Cannot use declare +r to turn off readonly attribute. */
+ if (readonly_p (var) && (flags_off & att_readonly))
+ {
+ sh_readonly (name);
+ any_failed++;
+ NEXT_VARIABLE ();
+ }
+
+ /* Cannot use declare to assign value to readonly or noassign
+ variable. */
+ if ((readonly_p (var) || noassign_p (var)) && offset)
+ {
+ if (readonly_p (var))
+ sh_readonly (name);
+ assign_error++;
+ NEXT_VARIABLE ();
+ }
+
+#if defined (ARRAY_VARS)
+ if ((making_array_special || (flags_on & (att_array|att_assoc)) || array_p (var) || assoc_p (var)) && offset)
+ {
+ int vlen;
+ vlen = STRLEN (value);
+
+ if (value[0] == '(' && value[vlen-1] == ')')
+ compound_array_assign = 1;
+ else
+ simple_array_assign = 1;
+ }
+
+ /* Cannot use declare +a name or declare +A name to remove an
+ array variable. */
+ if (((flags_off & att_array) && array_p (var)) || ((flags_off & att_assoc) && assoc_p (var)))
+ {
+ builtin_error (_("%s: cannot destroy array variables in this way"), name);
+ any_failed++;
+ NEXT_VARIABLE ();
+ }
+
+ if ((flags_on & att_array) && assoc_p (var))
+ {
+ builtin_error (_("%s: cannot convert associative to indexed array"), name);
+ any_failed++;
+ NEXT_VARIABLE ();
+ }
+ if ((flags_on & att_assoc) && array_p (var))
+ {
+ builtin_error (_("%s: cannot convert indexed to associative array"), name);
+ any_failed++;
+ NEXT_VARIABLE ();
+ }
+
+ /* declare -A name[[n]] makes name an associative array variable. */
+ if (flags_on & att_assoc)
+ {
+ if (assoc_p (var) == 0)
+ var = convert_var_to_assoc (var);
+ }
+ /* declare -a name[[n]] or declare name[n] makes name an indexed
+ array variable. */
+ else if ((making_array_special || (flags_on & att_array)) && array_p (var) == 0 && assoc_p (var) == 0)
+ var = convert_var_to_array (var);
+#endif /* ARRAY_VARS */
+
+ /* XXX - we note that we are turning on nameref attribute and defer
+ setting it until the assignment has been made so we don't do an
+ inadvertent nameref lookup. Might have to do the same thing for
+ flags_off&att_nameref. */
+ /* XXX - ksh93 makes it an error to have a readonly nameref variable */
+ onref = (flags_on & att_nameref);
+ flags_on &= ~att_nameref;
+ if (array_p (var) || assoc_p (var)
+ || (offset && compound_array_assign)
+ || simple_array_assign)
+ onref = 0; /* array variables may not be namerefs */
+
+ /* ksh93 seems to do this */
+ offref = (flags_off & att_nameref);
+ flags_off &= ~att_nameref;
+
+ VSETATTR (var, flags_on);
+ VUNSETATTR (var, flags_off);
+
+#if defined (ARRAY_VARS)
+ if (offset && compound_array_assign)
+ assign_array_var_from_string (var, value, aflags);
+ else if (simple_array_assign && subscript_start)
+ {
+ /* declare [-aA] name[N]=value */
+ *subscript_start = '['; /* ] */
+ var = assign_array_element (name, value, 0); /* XXX - not aflags */
+ *subscript_start = '\0';
+ if (var == 0) /* some kind of assignment error */
+ {
+ assign_error++;
+ flags_on |= onref;
+ flags_off |= offref;
+ NEXT_VARIABLE ();
+ }
+ }
+ else if (simple_array_assign)
+ {
+ /* let bind_{array,assoc}_variable take care of this. */
+ if (assoc_p (var))
+ bind_assoc_variable (var, name, savestring ("0"), value, aflags);
+ else
+ bind_array_variable (name, 0, value, aflags);
+ }
+ else
+#endif
+ /* bind_variable_value duplicates the essential internals of
+ bind_variable() */
+ if (offset)
+ bind_variable_value (var, value, aflags);
+
+ /* If we found this variable in the temporary environment, as with
+ `var=value declare -x var', make sure it is treated identically
+ to `var=value export var'. Do the same for `declare -r' and
+ `readonly'. Preserve the attributes, except for att_tempvar. */
+ /* XXX -- should this create a variable in the global scope, or
+ modify the local variable flags? ksh93 has it modify the
+ global scope.
+ Need to handle case like in set_var_attribute where a temporary
+ variable is in the same table as the function local vars. */
+ if ((flags_on & (att_exported|att_readonly)) && tempvar_p (var))
+ {
+ SHELL_VAR *tv;
+ char *tvalue;
+
+ tv = find_tempenv_variable (var->name);
+ if (tv)
+ {
+ tvalue = var_isset (var) ? savestring (value_cell (var)) : savestring ("");
+ tv = bind_variable (var->name, tvalue, 0);
+ tv->attributes |= var->attributes & ~att_tempvar;
+ if (tv->context > 0)
+ VSETATTR (tv, att_propagate);
+ free (tvalue);
+ }
+ VSETATTR (var, att_propagate);
+ }
+ }
+
+ /* Turn on nameref attribute we deferred above. */
+ /* XXX - should we turn on the noassign attribute for consistency with
+ ksh93 when we turn on the nameref attribute? */
+ VSETATTR (var, onref);
+ flags_on |= onref;
+ VUNSETATTR (var, offref);
+ flags_off |= offref;
+ /* Yuck. ksh93 compatibility */
+ if (refvar)
+ VUNSETATTR (refvar, flags_off);
+
+ stupidly_hack_special_variables (name);
+
+ NEXT_VARIABLE ();
+ }
+
+ return (assign_error ? EX_BADASSIGN
+ : ((any_failed == 0) ? EXECUTION_SUCCESS
+ : EXECUTION_FAILURE));
+}
if (var && readonly_p (var))
{
builtin_error (_("%s: cannot unset: readonly %s"),
- name, unset_function ? "function" : "variable");
+ var->name, unset_function ? "function" : "variable");
NEXT_VARIABLE ();
}
{
if (array_p (var) == 0 && assoc_p (var) == 0)
{
- builtin_error (_("%s: not an array variable"), name);
+ builtin_error (_("%s: not an array variable"), var->name);
NEXT_VARIABLE ();
}
else
--- /dev/null
+This file is set.def, from which is created set.c.
+It implements the "set" and "unset" builtins in Bash.
+
+Copyright (C) 1987-2011 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/>.
+
+$PRODUCES set.c
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "../flags.h"
+#include "common.h"
+#include "bashgetopt.h"
+
+#if defined (READLINE)
+# include "../input.h"
+# include "../bashline.h"
+# include <readline/readline.h>
+#endif
+
+#if defined (HISTORY)
+# include "../bashhist.h"
+#endif
+
+extern int posixly_correct, ignoreeof, eof_encountered_limit;
+#if defined (HISTORY)
+extern int dont_save_function_defs;
+#endif
+#if defined (READLINE)
+extern int no_line_editing;
+#endif /* READLINE */
+
+$BUILTIN set
+$FUNCTION set_builtin
+$SHORT_DOC set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]
+Set or unset values of shell options and positional parameters.
+
+Change the value of shell attributes and positional parameters, or
+display the names and values of shell variables.
+
+Options:
+ -a Mark variables which are modified or created for export.
+ -b Notify of job termination immediately.
+ -e Exit immediately if a command exits with a non-zero status.
+ -f Disable file name generation (globbing).
+ -h Remember the location of commands as they are looked up.
+ -k All assignment arguments are placed in the environment for a
+ command, not just those that precede the command name.
+ -m Job control is enabled.
+ -n Read commands but do not execute them.
+ -o option-name
+ Set the variable corresponding to option-name:
+ allexport same as -a
+ braceexpand same as -B
+#if defined (READLINE)
+ emacs use an emacs-style line editing interface
+#endif /* READLINE */
+ errexit same as -e
+ errtrace same as -E
+ functrace same as -T
+ hashall same as -h
+#if defined (BANG_HISTORY)
+ histexpand same as -H
+#endif /* BANG_HISTORY */
+#if defined (HISTORY)
+ history enable command history
+#endif
+ ignoreeof the shell will not exit upon reading EOF
+ interactive-comments
+ allow comments to appear in interactive commands
+ keyword same as -k
+#if defined (JOB_CONTROL)
+ monitor same as -m
+#endif
+ noclobber same as -C
+ noexec same as -n
+ noglob same as -f
+ nolog currently accepted but ignored
+#if defined (JOB_CONTROL)
+ notify same as -b
+#endif
+ nounset same as -u
+ onecmd same as -t
+ physical same as -P
+ pipefail the return value of a pipeline is the status of
+ the last command to exit with a non-zero status,
+ or zero if no command exited with a non-zero status
+ posix change the behavior of bash where the default
+ operation differs from the Posix standard to
+ match the standard
+ privileged same as -p
+ verbose same as -v
+#if defined (READLINE)
+ vi use a vi-style line editing interface
+#endif /* READLINE */
+ xtrace same as -x
+ -p Turned on whenever the real and effective user ids do not match.
+ Disables processing of the $ENV file and importing of shell
+ functions. Turning this option off causes the effective uid and
+ gid to be set to the real uid and gid.
+ -t Exit after reading and executing one command.
+ -u Treat unset variables as an error when substituting.
+ -v Print shell input lines as they are read.
+ -x Print commands and their arguments as they are executed.
+#if defined (BRACE_EXPANSION)
+ -B the shell will perform brace expansion
+#endif /* BRACE_EXPANSION */
+ -C If set, disallow existing regular files to be overwritten
+ by redirection of output.
+ -E If set, the ERR trap is inherited by shell functions.
+#if defined (BANG_HISTORY)
+ -H Enable ! style history substitution. This flag is on
+ by default when the shell is interactive.
+#endif /* BANG_HISTORY */
+ -P If set, do not resolve symbolic links when executing commands
+ such as cd which change the current directory.
+ -T If set, the DEBUG trap is inherited by shell functions.
+ -- Assign any remaining arguments to the positional parameters.
+ If there are no remaining arguments, the positional parameters
+ are unset.
+ - Assign any remaining arguments to the positional parameters.
+ The -x and -v options are turned off.
+
+Using + rather than - causes these flags to be turned off. The
+flags can also be used upon invocation of the shell. The current
+set of flags may be found in $-. The remaining n ARGs are positional
+parameters and are assigned, in order, to $1, $2, .. $n. If no
+ARGs are given, all shell variables are printed.
+
+Exit Status:
+Returns success unless an invalid option is given.
+$END
+
+typedef int setopt_set_func_t __P((int, char *));
+typedef int setopt_get_func_t __P((char *));
+
+static void print_minus_o_option __P((char *, int, int));
+static void print_all_shell_variables __P((void));
+
+static int set_ignoreeof __P((int, char *));
+static int set_posix_mode __P((int, char *));
+
+#if defined (READLINE)
+static int set_edit_mode __P((int, char *));
+static int get_edit_mode __P((char *));
+#endif
+
+#if defined (HISTORY)
+static int bash_set_history __P((int, char *));
+#endif
+
+static const char * const on = "on";
+static const char * const off = "off";
+
+/* A struct used to match long options for set -o to the corresponding
+ option letter or internal variable. The functions can be called to
+ dynamically generate values. */
+const struct {
+ char *name;
+ int letter;
+ int *variable;
+ setopt_set_func_t *set_func;
+ setopt_get_func_t *get_func;
+} o_options[] = {
+ { "allexport", 'a', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+#if defined (BRACE_EXPANSION)
+ { "braceexpand",'B', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+#endif
+#if defined (READLINE)
+ { "emacs", '\0', (int *)NULL, set_edit_mode, get_edit_mode },
+#endif
+ { "errexit", 'e', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ { "errtrace", 'E', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ { "functrace", 'T', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ { "hashall", 'h', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+#if defined (BANG_HISTORY)
+ { "histexpand", 'H', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+#endif /* BANG_HISTORY */
+#if defined (HISTORY)
+ { "history", '\0', &enable_history_list, bash_set_history, (setopt_get_func_t *)NULL },
+#endif
+ { "ignoreeof", '\0', &ignoreeof, set_ignoreeof, (setopt_get_func_t *)NULL },
+ { "interactive-comments", '\0', &interactive_comments, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ { "keyword", 'k', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+#if defined (JOB_CONTROL)
+ { "monitor", 'm', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+#endif
+ { "noclobber", 'C', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ { "noexec", 'n', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ { "noglob", 'f', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+#if defined (HISTORY)
+ { "nolog", '\0', &dont_save_function_defs, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+#endif
+#if defined (JOB_CONTROL)
+ { "notify", 'b', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+#endif /* JOB_CONTROL */
+ { "nounset", 'u', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ { "onecmd", 't', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ { "physical", 'P', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ { "pipefail", '\0', &pipefail_opt, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ { "posix", '\0', &posixly_correct, set_posix_mode, (setopt_get_func_t *)NULL },
+ { "privileged", 'p', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ { "verbose", 'v', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+#if defined (READLINE)
+ { "vi", '\0', (int *)NULL, set_edit_mode, get_edit_mode },
+#endif
+ { "xtrace", 'x', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+ {(char *)NULL, 0 , (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL },
+};
+
+#define N_O_OPTIONS (sizeof (o_options) / sizeof (o_options[0]))
+
+#define GET_BINARY_O_OPTION_VALUE(i, name) \
+ ((o_options[i].get_func) ? (*o_options[i].get_func) (name) \
+ : (*o_options[i].variable))
+
+#define SET_BINARY_O_OPTION_VALUE(i, onoff, name) \
+ ((o_options[i].set_func) ? (*o_options[i].set_func) (onoff, name) \
+ : (*o_options[i].variable = (onoff == FLAG_ON)))
+
+int
+minus_o_option_value (name)
+ char *name;
+{
+ register int i;
+ int *on_or_off;
+
+ for (i = 0; o_options[i].name; i++)
+ {
+ if (STREQ (name, o_options[i].name))
+ {
+ if (o_options[i].letter)
+ {
+ on_or_off = find_flag (o_options[i].letter);
+ return ((on_or_off == FLAG_UNKNOWN) ? -1 : *on_or_off);
+ }
+ else
+ return (GET_BINARY_O_OPTION_VALUE (i, name));
+ }
+ }
+
+ return (-1);
+}
+
+#define MINUS_O_FORMAT "%-15s\t%s\n"
+
+static void
+print_minus_o_option (name, value, pflag)
+ char *name;
+ int value, pflag;
+{
+ if (pflag == 0)
+ printf (MINUS_O_FORMAT, name, value ? on : off);
+ else
+ printf ("set %co %s\n", value ? '-' : '+', name);
+}
+
+void
+list_minus_o_opts (mode, reusable)
+ int mode, reusable;
+{
+ register int i;
+ int *on_or_off, value;
+
+ for (i = 0; o_options[i].name; i++)
+ {
+ if (o_options[i].letter)
+ {
+ value = 0;
+ on_or_off = find_flag (o_options[i].letter);
+ if (on_or_off == FLAG_UNKNOWN)
+ on_or_off = &value;
+ if (mode == -1 || mode == *on_or_off)
+ print_minus_o_option (o_options[i].name, *on_or_off, reusable);
+ }
+ else
+ {
+ value = GET_BINARY_O_OPTION_VALUE (i, o_options[i].name);
+ if (mode == -1 || mode == value)
+ print_minus_o_option (o_options[i].name, value, reusable);
+ }
+ }
+}
+
+char **
+get_minus_o_opts ()
+{
+ char **ret;
+ int i;
+
+ ret = strvec_create (N_O_OPTIONS + 1);
+ for (i = 0; o_options[i].name; i++)
+ ret[i] = o_options[i].name;
+ ret[i] = (char *)NULL;
+ return ret;
+}
+
+static int
+set_ignoreeof (on_or_off, option_name)
+ int on_or_off;
+ char *option_name;
+{
+ ignoreeof = on_or_off == FLAG_ON;
+ unbind_variable ("ignoreeof");
+ if (ignoreeof)
+ bind_variable ("IGNOREEOF", "10", 0);
+ else
+ unbind_variable ("IGNOREEOF");
+ sv_ignoreeof ("IGNOREEOF");
+ return 0;
+}
+
+static int
+set_posix_mode (on_or_off, option_name)
+ int on_or_off;
+ char *option_name;
+{
+ posixly_correct = on_or_off == FLAG_ON;
+ if (posixly_correct == 0)
+ unbind_variable ("POSIXLY_CORRECT");
+ else
+ bind_variable ("POSIXLY_CORRECT", "y", 0);
+ sv_strict_posix ("POSIXLY_CORRECT");
+ return (0);
+}
+
+#if defined (READLINE)
+/* Magic. This code `knows' how readline handles rl_editing_mode. */
+static int
+set_edit_mode (on_or_off, option_name)
+ int on_or_off;
+ char *option_name;
+{
+ int isemacs;
+
+ if (on_or_off == FLAG_ON)
+ {
+ rl_variable_bind ("editing-mode", option_name);
+
+ if (interactive)
+ with_input_from_stdin ();
+ no_line_editing = 0;
+ }
+ else
+ {
+ isemacs = rl_editing_mode == 1;
+ if ((isemacs && *option_name == 'e') || (!isemacs && *option_name == 'v'))
+ {
+ if (interactive)
+ with_input_from_stream (stdin, "stdin");
+ no_line_editing = 1;
+ }
+ }
+ return 1-no_line_editing;
+}
+
+static int
+get_edit_mode (name)
+ char *name;
+{
+ return (*name == 'e' ? no_line_editing == 0 && rl_editing_mode == 1
+ : no_line_editing == 0 && rl_editing_mode == 0);
+}
+#endif /* READLINE */
+
+#if defined (HISTORY)
+static int
+bash_set_history (on_or_off, option_name)
+ int on_or_off;
+ char *option_name;
+{
+ if (on_or_off == FLAG_ON)
+ {
+ enable_history_list = 1;
+ bash_history_enable ();
+ if (history_lines_this_session == 0)
+ load_history ();
+ }
+ else
+ {
+ enable_history_list = 0;
+ bash_history_disable ();
+ }
+ return (1 - enable_history_list);
+}
+#endif
+
+int
+set_minus_o_option (on_or_off, option_name)
+ int on_or_off;
+ char *option_name;
+{
+ register int i;
+
+ for (i = 0; o_options[i].name; i++)
+ {
+ if (STREQ (option_name, o_options[i].name))
+ {
+ if (o_options[i].letter == 0)
+ {
+ SET_BINARY_O_OPTION_VALUE (i, on_or_off, option_name);
+ return (EXECUTION_SUCCESS);
+ }
+ else
+ {
+ if (change_flag (o_options[i].letter, on_or_off) == FLAG_ERROR)
+ {
+ sh_invalidoptname (option_name);
+ return (EXECUTION_FAILURE);
+ }
+ else
+ return (EXECUTION_SUCCESS);
+ }
+
+ }
+ }
+
+ sh_invalidoptname (option_name);
+ return (EX_USAGE);
+}
+
+static void
+print_all_shell_variables ()
+{
+ SHELL_VAR **vars;
+
+ vars = all_shell_variables ();
+ if (vars)
+ {
+ print_var_list (vars);
+ free (vars);
+ }
+
+ /* POSIX.2 does not allow function names and definitions to be output when
+ `set' is invoked without options (PASC Interp #202). */
+ if (posixly_correct == 0)
+ {
+ vars = all_shell_functions ();
+ if (vars)
+ {
+ print_func_list (vars);
+ free (vars);
+ }
+ }
+}
+
+void
+set_shellopts ()
+{
+ char *value;
+ char tflag[N_O_OPTIONS];
+ int vsize, i, vptr, *ip, exported;
+ SHELL_VAR *v;
+
+ for (vsize = i = 0; o_options[i].name; i++)
+ {
+ tflag[i] = 0;
+ if (o_options[i].letter)
+ {
+ ip = find_flag (o_options[i].letter);
+ if (ip && *ip)
+ {
+ vsize += strlen (o_options[i].name) + 1;
+ tflag[i] = 1;
+ }
+ }
+ else if (GET_BINARY_O_OPTION_VALUE (i, o_options[i].name))
+ {
+ vsize += strlen (o_options[i].name) + 1;
+ tflag[i] = 1;
+ }
+ }
+
+ value = (char *)xmalloc (vsize + 1);
+
+ for (i = vptr = 0; o_options[i].name; i++)
+ {
+ if (tflag[i])
+ {
+ strcpy (value + vptr, o_options[i].name);
+ vptr += strlen (o_options[i].name);
+ value[vptr++] = ':';
+ }
+ }
+
+ if (vptr)
+ vptr--; /* cut off trailing colon */
+ value[vptr] = '\0';
+
+ v = find_variable ("SHELLOPTS");
+
+ /* Turn off the read-only attribute so we can bind the new value, and
+ note whether or not the variable was exported. */
+ if (v)
+ {
+ VUNSETATTR (v, att_readonly);
+ exported = exported_p (v);
+ }
+ else
+ exported = 0;
+
+ v = bind_variable ("SHELLOPTS", value, 0);
+
+ /* Turn the read-only attribute back on, and turn off the export attribute
+ if it was set implicitly by mark_modified_vars and SHELLOPTS was not
+ exported before we bound the new value. */
+ VSETATTR (v, att_readonly);
+ if (mark_modified_vars && exported == 0 && exported_p (v))
+ VUNSETATTR (v, att_exported);
+
+ free (value);
+}
+
+void
+parse_shellopts (value)
+ char *value;
+{
+ char *vname;
+ int vptr;
+
+ vptr = 0;
+ while (vname = extract_colon_unit (value, &vptr))
+ {
+ set_minus_o_option (FLAG_ON, vname);
+ free (vname);
+ }
+}
+
+void
+initialize_shell_options (no_shellopts)
+ int no_shellopts;
+{
+ char *temp;
+ SHELL_VAR *var;
+
+ if (no_shellopts == 0)
+ {
+ var = find_variable ("SHELLOPTS");
+ /* set up any shell options we may have inherited. */
+ if (var && imported_p (var))
+ {
+ temp = (array_p (var) || assoc_p (var)) ? (char *)NULL : savestring (value_cell (var));
+ if (temp)
+ {
+ parse_shellopts (temp);
+ free (temp);
+ }
+ }
+ }
+
+ /* Set up the $SHELLOPTS variable. */
+ set_shellopts ();
+}
+
+/* Reset the values of the -o options that are not also shell flags. This is
+ called from execute_cmd.c:initialize_subshell() when setting up a subshell
+ to run an executable shell script without a leading `#!'. */
+void
+reset_shell_options ()
+{
+#if defined (HISTORY)
+ remember_on_history = enable_history_list = 1;
+#endif
+ ignoreeof = 0;
+}
+
+/* Set some flags from the word values in the input list. If LIST is empty,
+ then print out the values of the variables instead. If LIST contains
+ non-flags, then set $1 - $9 to the successive words of LIST. */
+int
+set_builtin (list)
+ WORD_LIST *list;
+{
+ int on_or_off, flag_name, force_assignment, opts_changed, rv, r;
+ register char *arg;
+ char s[3];
+
+ if (list == 0)
+ {
+ print_all_shell_variables ();
+ return (sh_chkwrite (EXECUTION_SUCCESS));
+ }
+
+ /* Check validity of flag arguments. */
+ rv = EXECUTION_SUCCESS;
+ reset_internal_getopt ();
+ while ((flag_name = internal_getopt (list, optflags)) != -1)
+ {
+ switch (flag_name)
+ {
+ case '?':
+ builtin_usage ();
+ return (list_optopt == '?' ? EXECUTION_SUCCESS : EX_USAGE);
+ default:
+ break;
+ }
+ }
+
+ /* Do the set command. While the list consists of words starting with
+ '-' or '+' treat them as flags, otherwise, start assigning them to
+ $1 ... $n. */
+ for (force_assignment = opts_changed = 0; list; )
+ {
+ arg = list->word->word;
+
+ /* If the argument is `--' or `-' then signal the end of the list
+ and remember the remaining arguments. */
+ if (arg[0] == '-' && (!arg[1] || (arg[1] == '-' && !arg[2])))
+ {
+ list = list->next;
+
+ /* `set --' unsets the positional parameters. */
+ if (arg[1] == '-')
+ force_assignment = 1;
+
+ /* Until told differently, the old shell behaviour of
+ `set - [arg ...]' being equivalent to `set +xv [arg ...]'
+ stands. Posix.2 says the behaviour is marked as obsolescent. */
+ else
+ {
+ change_flag ('x', '+');
+ change_flag ('v', '+');
+ opts_changed = 1;
+ }
+
+ break;
+ }
+
+ if ((on_or_off = *arg) && (on_or_off == '-' || on_or_off == '+'))
+ {
+ while (flag_name = *++arg)
+ {
+ if (flag_name == '?')
+ {
+ builtin_usage ();
+ return (EXECUTION_SUCCESS);
+ }
+ else if (flag_name == 'o') /* -+o option-name */
+ {
+ char *option_name;
+ WORD_LIST *opt;
+
+ opt = list->next;
+
+ if (opt == 0)
+ {
+ list_minus_o_opts (-1, (on_or_off == '+'));
+ rv = sh_chkwrite (rv);
+ continue;
+ }
+
+ option_name = opt->word->word;
+
+ if (option_name == 0 || *option_name == '\0' ||
+ *option_name == '-' || *option_name == '+')
+ {
+ list_minus_o_opts (-1, (on_or_off == '+'));
+ continue;
+ }
+ list = list->next; /* Skip over option name. */
+
+ opts_changed = 1;
+ if ((r = set_minus_o_option (on_or_off, option_name)) != EXECUTION_SUCCESS)
+ {
+ set_shellopts ();
+ return (r);
+ }
+ }
+ else if (change_flag (flag_name, on_or_off) == FLAG_ERROR)
+ {
+ s[0] = on_or_off;
+ s[1] = flag_name;
+ s[2] = '\0';
+ sh_invalidopt (s);
+ builtin_usage ();
+ set_shellopts ();
+ return (EXECUTION_FAILURE);
+ }
+ opts_changed = 1;
+ }
+ }
+ else
+ {
+ break;
+ }
+ list = list->next;
+ }
+
+ /* Assigning $1 ... $n */
+ if (list || force_assignment)
+ remember_args (list, 1);
+ /* Set up new value of $SHELLOPTS */
+ if (opts_changed)
+ set_shellopts ();
+ return (rv);
+}
+
+$BUILTIN unset
+$FUNCTION unset_builtin
+$SHORT_DOC unset [-f] [-v] [name ...]
+Unset values and attributes of shell variables and functions.
+
+For each NAME, remove the corresponding variable or function.
+
+Options:
+ -f treat each NAME as a shell function
+ -v treat each NAME as a shell variable
+
+Without options, unset first tries to unset a variable, and if that fails,
+tries to unset a function.
+
+Some variables cannot be unset; also see `readonly'.
+
+Exit Status:
+Returns success unless an invalid option is given or a NAME is read-only.
+$END
+
+#define NEXT_VARIABLE() any_failed++; list = list->next; continue;
+
+int
+unset_builtin (list)
+ WORD_LIST *list;
+{
+ int unset_function, unset_variable, unset_array, opt, any_failed;
+ char *name;
+
+ unset_function = unset_variable = unset_array = any_failed = 0;
+
+ reset_internal_getopt ();
+ while ((opt = internal_getopt (list, "fv")) != -1)
+ {
+ switch (opt)
+ {
+ case 'f':
+ unset_function = 1;
+ break;
+ case 'v':
+ unset_variable = 1;
+ break;
+ default:
+ builtin_usage ();
+ return (EX_USAGE);
+ }
+ }
+
+ list = loptend;
+
+ if (unset_function && unset_variable)
+ {
+ builtin_error (_("cannot simultaneously unset a function and a variable"));
+ return (EXECUTION_FAILURE);
+ }
+
+ while (list)
+ {
+ SHELL_VAR *var;
+ int tem;
+#if defined (ARRAY_VARS)
+ char *t;
+#endif
+
+ name = list->word->word;
+
+#if defined (ARRAY_VARS)
+ unset_array = 0;
+ if (!unset_function && valid_array_reference (name))
+ {
+ t = strchr (name, '[');
+ *t++ = '\0';
+ unset_array++;
+ }
+#endif
+ /* Get error checking out of the way first. The low-level functions
+ just perform the unset, relying on the caller to verify. */
+
+ /* Bash allows functions with names which are not valid identifiers
+ to be created when not in posix mode, so check only when in posix
+ mode when unsetting a function. */
+ if (((unset_function && posixly_correct) || !unset_function) && legal_identifier (name) == 0)
+ {
+ sh_invalidid (name);
+ NEXT_VARIABLE ();
+ }
+
+ /* Only search for functions here if -f supplied. */
+ var = unset_function ? find_function (name) : find_variable (name);
+
+ /* Some variables (but not functions yet) cannot be unset, period. */
+ if (var && unset_function == 0 && non_unsettable_p (var))
+ {
+ builtin_error (_("%s: cannot unset"), name);
+ NEXT_VARIABLE ();
+ }
+
+ /* Posix.2 says try variables first, then functions. If we would
+ find a function after unsuccessfully searching for a variable,
+ note that we're acting on a function now as if -f were
+ supplied. The readonly check below takes care of it. */
+ if (var == 0 && unset_variable == 0 && unset_function == 0)
+ {
+ if (var = find_function (name))
+ unset_function = 1;
+ }
+
+ /* Posix.2 says that unsetting readonly variables is an error. */
+ if (var && readonly_p (var))
+ {
+ builtin_error (_("%s: cannot unset: readonly %s"),
+ name, unset_function ? "function" : "variable");
+ NEXT_VARIABLE ();
+ }
+
+
+ /* Unless the -f option is supplied, the name refers to a variable. */
+#if defined (ARRAY_VARS)
+ if (var && unset_array)
+ {
+ if (array_p (var) == 0 && assoc_p (var) == 0)
+ {
+ builtin_error (_("%s: not an array variable"), name);
+ NEXT_VARIABLE ();
+ }
+ else
+ {
+ tem = unbind_array_element (var, t);
+ if (tem == -1)
+ any_failed++;
+ }
+ }
+ else
+#endif /* ARRAY_VARS */
+ tem = unset_function ? unbind_func (name) : unbind_variable (name);
+
+ /* This is what Posix.2 says: ``If neither -f nor -v
+ is specified, the name refers to a variable; if a variable by
+ that name does not exist, a function by that name, if any,
+ shall be unset.'' */
+ if (tem == -1 && unset_function == 0 && unset_variable == 0)
+ tem = unbind_func (name);
+
+ /* SUSv3, POSIX.1-2001 say: ``Unsetting a variable or function that
+ was not previously set shall not be considered an error.'' */
+
+ if (unset_function == 0)
+ stupidly_hack_special_variables (name);
+
+ list = list->next;
+ }
+
+ return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS);
+}
This file is setattr.def, from which is created setattr.c.
It implements the builtins "export" and "readonly", in Bash.
-Copyright (C) 1987-2010 Free Software Foundation, Inc.
+Copyright (C) 1987-2012 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
if (integer_p (var))
flags[i++] = 'i';
+ if (nameref_p (var))
+ flags[i++] = 'n';
+
if (readonly_p (var))
flags[i++] = 'r';
--- /dev/null
+This file is setattr.def, from which is created setattr.c.
+It implements the builtins "export" and "readonly", in Bash.
+
+Copyright (C) 1987-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/>.
+
+$PRODUCES setattr.c
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "common.h"
+#include "bashgetopt.h"
+
+extern int posixly_correct;
+extern int array_needs_making;
+extern char *this_command_name;
+extern sh_builtin_func_t *this_shell_builtin;
+
+#ifdef ARRAY_VARS
+extern int declare_builtin __P((WORD_LIST *));
+#endif
+
+#define READONLY_OR_EXPORT \
+ (this_shell_builtin == readonly_builtin || this_shell_builtin == export_builtin)
+
+$BUILTIN export
+$FUNCTION export_builtin
+$SHORT_DOC export [-fn] [name[=value] ...] or export -p
+Set export attribute for shell variables.
+
+Marks each NAME for automatic export to the environment of subsequently
+executed commands. If VALUE is supplied, assign VALUE before exporting.
+
+Options:
+ -f refer to shell functions
+ -n remove the export property from each NAME
+ -p display a list of all exported variables and functions
+
+An argument of `--' disables further option processing.
+
+Exit Status:
+Returns success unless an invalid option is given or NAME is invalid.
+$END
+
+/* For each variable name in LIST, make that variable appear in the
+ environment passed to simple commands. If there is no LIST, then
+ print all such variables. An argument of `-n' says to remove the
+ exported attribute from variables named in LIST. An argument of
+ -f indicates that the names present in LIST refer to functions. */
+int
+export_builtin (list)
+ register WORD_LIST *list;
+{
+ return (set_or_show_attributes (list, att_exported, 0));
+}
+
+$BUILTIN readonly
+$FUNCTION readonly_builtin
+$SHORT_DOC readonly [-aAf] [name[=value] ...] or readonly -p
+Mark shell variables as unchangeable.
+
+Mark each NAME as read-only; the values of these NAMEs may not be
+changed by subsequent assignment. If VALUE is supplied, assign VALUE
+before marking as read-only.
+
+Options:
+ -a refer to indexed array variables
+ -A refer to associative array variables
+ -f refer to shell functions
+ -p display a list of all readonly variables and functions
+
+An argument of `--' disables further option processing.
+
+Exit Status:
+Returns success unless an invalid option is given or NAME is invalid.
+$END
+
+/* For each variable name in LIST, make that variable readonly. Given an
+ empty LIST, print out all existing readonly variables. */
+int
+readonly_builtin (list)
+ register WORD_LIST *list;
+{
+ return (set_or_show_attributes (list, att_readonly, 0));
+}
+
+#if defined (ARRAY_VARS)
+# define ATTROPTS "aAfnp"
+#else
+# define ATTROPTS "fnp"
+#endif
+
+/* For each variable name in LIST, make that variable have the specified
+ ATTRIBUTE. An arg of `-n' says to remove the attribute from the the
+ remaining names in LIST (doesn't work for readonly). */
+int
+set_or_show_attributes (list, attribute, nodefs)
+ register WORD_LIST *list;
+ int attribute, nodefs;
+{
+ register SHELL_VAR *var;
+ int assign, undo, any_failed, assign_error, opt;
+ int functions_only, arrays_only, assoc_only;
+ int aflags;
+ char *name;
+#if defined (ARRAY_VARS)
+ WORD_LIST *nlist, *tlist;
+ WORD_DESC *w;
+#endif
+
+ functions_only = arrays_only = assoc_only = 0;
+ undo = any_failed = assign_error = 0;
+ /* Read arguments from the front of the list. */
+ reset_internal_getopt ();
+ while ((opt = internal_getopt (list, ATTROPTS)) != -1)
+ {
+ switch (opt)
+ {
+ case 'n':
+ undo = 1;
+ break;
+ case 'f':
+ functions_only = 1;
+ break;
+#if defined (ARRAY_VARS)
+ case 'a':
+ arrays_only = 1;
+ break;
+ case 'A':
+ assoc_only = 1;
+ break;
+#endif
+ case 'p':
+ break;
+ default:
+ builtin_usage ();
+ return (EX_USAGE);
+ }
+ }
+ list = loptend;
+
+ if (list)
+ {
+ if (attribute & att_exported)
+ array_needs_making = 1;
+
+ /* Cannot undo readonly status, silently disallowed. */
+ if (undo && (attribute & att_readonly))
+ attribute &= ~att_readonly;
+
+ while (list)
+ {
+ name = list->word->word;
+
+ if (functions_only) /* xxx -f name */
+ {
+ var = find_function (name);
+ if (var == 0)
+ {
+ builtin_error (_("%s: not a function"), name);
+ any_failed++;
+ }
+ else
+ SETVARATTR (var, attribute, undo);
+
+ list = list->next;
+ continue;
+ }
+
+ /* xxx [-np] name[=value] */
+ assign = assignment (name, 0);
+
+ aflags = 0;
+ if (assign)
+ {
+ name[assign] = '\0';
+ if (name[assign - 1] == '+')
+ {
+ aflags |= ASS_APPEND;
+ name[assign - 1] = '\0';
+ }
+ }
+
+ if (legal_identifier (name) == 0)
+ {
+ sh_invalidid (name);
+ if (assign)
+ assign_error++;
+ else
+ any_failed++;
+ list = list->next;
+ continue;
+ }
+
+ if (assign) /* xxx [-np] name=value */
+ {
+ name[assign] = '=';
+ if (aflags & ASS_APPEND)
+ name[assign - 1] = '+';
+#if defined (ARRAY_VARS)
+ /* Let's try something here. Turn readonly -a xxx=yyy into
+ declare -ra xxx=yyy and see what that gets us. */
+ if (arrays_only || assoc_only)
+ {
+ tlist = list->next;
+ list->next = (WORD_LIST *)NULL;
+ w = arrays_only ? make_word ("-ra") : make_word ("-rA");
+ nlist = make_word_list (w, list);
+ opt = declare_builtin (nlist);
+ if (opt != EXECUTION_SUCCESS)
+ assign_error++;
+ list->next = tlist;
+ dispose_word (w);
+ free (nlist);
+ }
+ else
+#endif
+ /* This word has already been expanded once with command
+ and parameter expansion. Call do_assignment_no_expand (),
+ which does not do command or parameter substitution. If
+ the assignment is not performed correctly, flag an error. */
+ if (do_assignment_no_expand (name) == 0)
+ assign_error++;
+ name[assign] = '\0';
+ if (aflags & ASS_APPEND)
+ name[assign - 1] = '\0';
+ }
+
+ set_var_attribute (name, attribute, undo);
+ list = list->next;
+ }
+ }
+ else
+ {
+ SHELL_VAR **variable_list;
+ register int i;
+
+ if ((attribute & att_function) || functions_only)
+ {
+ variable_list = all_shell_functions ();
+ if (attribute != att_function)
+ attribute &= ~att_function; /* so declare -xf works, for example */
+ }
+ else
+ variable_list = all_shell_variables ();
+
+#if defined (ARRAY_VARS)
+ if (attribute & att_array)
+ {
+ arrays_only++;
+ if (attribute != att_array)
+ attribute &= ~att_array;
+ }
+ else if (attribute & att_assoc)
+ {
+ assoc_only++;
+ if (attribute != att_assoc)
+ attribute &= ~att_assoc;
+ }
+#endif
+
+ if (variable_list)
+ {
+ for (i = 0; var = variable_list[i]; i++)
+ {
+#if defined (ARRAY_VARS)
+ if (arrays_only && array_p (var) == 0)
+ continue;
+ else if (assoc_only && assoc_p (var) == 0)
+ continue;
+#endif
+ if ((var->attributes & attribute))
+ {
+ show_var_attributes (var, READONLY_OR_EXPORT, nodefs);
+ if (any_failed = sh_chkwrite (any_failed))
+ break;
+ }
+ }
+ free (variable_list);
+ }
+ }
+
+ return (assign_error ? EX_BADASSIGN
+ : ((any_failed == 0) ? EXECUTION_SUCCESS
+ : EXECUTION_FAILURE));
+}
+
+/* Show all variable variables (v == 1) or functions (v == 0) with
+ attributes. */
+int
+show_all_var_attributes (v, nodefs)
+ int v, nodefs;
+{
+ SHELL_VAR **variable_list, *var;
+ int any_failed;
+ register int i;
+
+ variable_list = v ? all_shell_variables () : all_shell_functions ();
+ if (variable_list == 0)
+ return (EXECUTION_SUCCESS);
+
+ for (i = any_failed = 0; var = variable_list[i]; i++)
+ {
+ show_var_attributes (var, READONLY_OR_EXPORT, nodefs);
+ if (any_failed = sh_chkwrite (any_failed))
+ break;
+ }
+ free (variable_list);
+ return (any_failed == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+}
+
+/* Show the attributes for shell variable VAR. If NODEFS is non-zero,
+ don't show function definitions along with the name. If PATTR is
+ non-zero, it indicates we're being called from `export' or `readonly'.
+ In POSIX mode, this prints the name of the calling builtin (`export'
+ or `readonly') instead of `declare', and doesn't print function defs
+ when called by `export' or `readonly'. */
+int
+show_var_attributes (var, pattr, nodefs)
+ SHELL_VAR *var;
+ int pattr, nodefs;
+{
+ char flags[16], *x;
+ int i;
+
+ i = 0;
+
+ /* pattr == 0 means we are called from `declare'. */
+ if (pattr == 0 || posixly_correct == 0)
+ {
+#if defined (ARRAY_VARS)
+ if (array_p (var))
+ flags[i++] = 'a';
+
+ if (assoc_p (var))
+ flags[i++] = 'A';
+#endif
+
+ if (function_p (var))
+ flags[i++] = 'f';
+
+ if (integer_p (var))
+ flags[i++] = 'i';
+
+ if (nameref_p (var))
+ flags[i++] = 'n';
+
+ if (readonly_p (var))
+ flags[i++] = 'r';
+
+ if (trace_p (var))
+ flags[i++] = 't';
+
+ if (exported_p (var))
+ flags[i++] = 'x';
+
+ if (capcase_p (var))
+ flags[i++] = 'c';
+
+ if (lowercase_p (var))
+ flags[i++] = 'l';
+
+ if (uppercase_p (var))
+ flags[i++] = 'u';
+ }
+ else
+ {
+#if defined (ARRAY_VARS)
+ if (array_p (var))
+ flags[i++] = 'a';
+
+ if (assoc_p (var))
+ flags[i++] = 'A';
+#endif
+
+ if (function_p (var))
+ flags[i++] = 'f';
+ }
+
+ flags[i] = '\0';
+
+ /* If we're printing functions with definitions, print the function def
+ first, then the attributes, instead of printing output that can't be
+ reused as input to recreate the current state. */
+ if (function_p (var) && nodefs == 0 && (pattr == 0 || posixly_correct == 0))
+ {
+ printf ("%s\n", named_function_string (var->name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL));
+ nodefs++;
+ if (pattr == 0 && i == 1 && flags[0] == 'f')
+ return 0; /* don't print `declare -f name' */
+ }
+
+ if (pattr == 0 || posixly_correct == 0)
+ printf ("declare -%s ", i ? flags : "-");
+ else if (i)
+ printf ("%s -%s ", this_command_name, flags);
+ else
+ printf ("%s ", this_command_name);
+
+#if defined (ARRAY_VARS)
+ if (array_p (var))
+ print_array_assignment (var, 1);
+ else if (assoc_p (var))
+ print_assoc_assignment (var, 1);
+ else
+#endif
+ /* force `readonly' and `export' to not print out function definitions
+ when in POSIX mode. */
+ if (nodefs || (function_p (var) && pattr != 0 && posixly_correct))
+ printf ("%s\n", var->name);
+ else if (function_p (var))
+ printf ("%s\n", named_function_string (var->name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL));
+ else if (invisible_p (var) || var_isset (var) == 0)
+ printf ("%s\n", var->name);
+ else
+ {
+ x = sh_double_quote (value_cell (var));
+ printf ("%s=%s\n", var->name, x);
+ free (x);
+ }
+ return (0);
+}
+
+int
+show_name_attributes (name, nodefs)
+ char *name;
+ int nodefs;
+{
+ SHELL_VAR *var;
+
+ var = find_variable_tempenv (name);
+
+ if (var && invisible_p (var) == 0)
+ {
+ show_var_attributes (var, READONLY_OR_EXPORT, nodefs);
+ return (0);
+ }
+ else
+ return (1);
+}
+
+void
+set_var_attribute (name, attribute, undo)
+ char *name;
+ int attribute, undo;
+{
+ SHELL_VAR *var, *tv;
+ char *tvalue;
+
+ if (undo)
+ var = find_variable (name);
+ else
+ {
+ tv = find_tempenv_variable (name);
+ /* XXX -- need to handle case where tv is a temp variable in a
+ function-scope context, since function_env has been merged into
+ the local variables table. */
+ if (tv && tempvar_p (tv))
+ {
+ tvalue = var_isset (tv) ? savestring (value_cell (tv)) : savestring ("");
+
+ var = bind_variable (tv->name, tvalue, 0);
+ var->attributes |= tv->attributes & ~att_tempvar;
+ VSETATTR (tv, att_propagate);
+ if (var->context != 0)
+ VSETATTR (var, att_propagate);
+ SETVARATTR (tv, attribute, undo); /* XXX */
+
+ stupidly_hack_special_variables (tv->name);
+
+ free (tvalue);
+ }
+ else
+ {
+ var = find_variable_notempenv (name);
+ if (var == 0)
+ {
+ var = bind_variable (name, (char *)NULL, 0);
+ VSETATTR (var, att_invisible);
+ }
+ else if (var->context != 0)
+ VSETATTR (var, att_propagate);
+ }
+ }
+
+ if (var)
+ SETVARATTR (var, attribute, undo);
+
+ if (var && (exported_p (var) || (attribute & att_exported)))
+ array_needs_making++; /* XXX */
+}
/* Save this command unless it's a trap command and we're not running
a debug trap. */
-#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);
#endif
this_command_name = (char *)NULL;
- v = bind_variable (identifier, list->word->word, 0);
+ /* 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;
extern char *glob_argv_flags;
#endif
+extern int job_control; /* XXX */
+
extern int close __P((int));
/* Static functions defined and used in this file. */
command->value.Simple->flags |= CMD_STDIN_REDIR;
line_number_for_err_trap = line_number = command->value.Simple->line;
-itrace("execute_command_internal: set line_number_for_err_trap = %d", line_number_for_err_trap);
exec_result =
execute_simple_command (command->value.Simple, pipe_in, pipe_out,
asynchronous, fds_to_close);
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)
{
-itrace("execute_command_internal: line_number = %d line_number_for_err_trap = %d save_line_number = %d", line_number, line_number_for_err_trap, save_line_number);
last_command_exit_value = exec_result;
line_number = line_number_for_err_trap;
run_error_trap ();
if (ignore_return && cmd)
cmd->flags |= CMD_IGNORE_RETURN;
-#if defined (JOB_CONTROL)
lastpipe_flag = 0;
+
begin_unwind_frame ("lastpipe-exec");
lstdin = -1;
/* If the `lastpipe' option is set with shopt, and job control is not
}
if (prev >= 0)
add_unwind_protect (close, prev);
-#endif
exec_result = execute_command_internal (cmd, asynchronous, prev, pipe_out, fds_to_close);
-#if defined (JOB_CONTROL)
if (lstdin > 0)
restore_stdin (lstdin);
-#endif
if (prev >= 0)
close (prev);
unfreeze_jobs_list ();
}
-#if defined (JOB_CONTROL)
discard_unwind_frame ("lastpipe-exec");
-#endif
return (exec_result);
}
/* Save this command unless it's a trap command and we're not running
a debug trap. */
-#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);
#include "stdc.h"
#include "memalloc.h"
+#include <signal.h>
+
#include "shell.h"
#include "glob.h"
lose = 1;
break;
}
- run_pending_traps ();
+ else if (signal_is_pending (SIGINT)) /* XXX - make SIGINT traps responsive */
+ {
+ lose = 1;
+ break;
+ }
dp = readdir (d);
if (dp == NULL)
FREE (tmplink);
}
- QUIT;
- run_pending_traps ();
+ /* Don't call QUIT; here; let higher layers deal with it. */
return ((char **)NULL);
}
/* Get a key from the buffer of characters to be read.
Return the key in KEY.
- Result is KEY if there was a key, or 0 if there wasn't. */
+ Result is non-zero if there was a key, or 0 if there wasn't. */
static int
rl_get_char (key)
int *key;
int
rl_read_key ()
{
- int c;
+ int c, r;
if (rl_pending_input)
{
{
while (rl_event_hook)
{
- if (rl_gather_tyi () < 0) /* XXX - EIO */
+ if (rl_get_char (&c) != 0)
+ break;
+
+ if ((r = rl_gather_tyi ()) < 0) /* XXX - EIO */
{
rl_done = 1;
return ('\n');
}
+ else if (r > 0) /* read something */
+ continue;
+
RL_CHECK_SIGNALS ();
- if (rl_get_char (&c) != 0)
- break;
if (rl_done) /* XXX - experimental */
return ('\n');
(*rl_event_hook) ();
static SigHandler *rl_set_sighandler PARAMS((int, SigHandler *, sighandler_cxt *));
static void rl_maybe_set_sighandler PARAMS((int, SigHandler *, sighandler_cxt *));
+static void rl_maybe_restore_sighandler PARAMS((int, sighandler_cxt *));
static RETSIGTYPE rl_signal_handler PARAMS((int));
static RETSIGTYPE _rl_handle_signal PARAMS((int));
return (ohandler->sa_handler);
}
+/* Set disposition of SIG to HANDLER, returning old state in OHANDLER. Don't
+ change disposition if OHANDLER indicates the signal was ignored. */
static void
rl_maybe_set_sighandler (sig, handler, ohandler)
int sig;
rl_sigaction (sig, ohandler, &dummy);
}
+/* Set the disposition of SIG to HANDLER, if HANDLER->sa_handler indicates the
+ signal was not being ignored. MUST only be called for signals whose
+ disposition was changed using rl_maybe_set_sighandler or for which the
+ SIG_IGN check was performed inline (e.g., SIGALRM below). */
+static void
+rl_maybe_restore_sighandler (sig, handler)
+ int sig;
+ sighandler_cxt *handler;
+{
+ sighandler_cxt dummy;
+
+ sigemptyset (&dummy.sa_mask);
+ if (handler->sa_handler != SIG_IGN)
+ rl_sigaction (sig, handler, &dummy);
+}
+
int
rl_set_signals ()
{
{
sigemptyset (&dummy.sa_mask);
- rl_sigaction (SIGINT, &old_int, &dummy);
- rl_sigaction (SIGTERM, &old_term, &dummy);
- rl_sigaction (SIGHUP, &old_hup, &dummy);
+ /* Since rl_maybe_set_sighandler doesn't override a SIG_IGN handler,
+ we should in theory not have to restore a handler where
+ old_xxx.sa_handler == SIG_IGN. That's what rl_maybe_restore_sighandler
+ does. Fewer system calls should reduce readline's per-line
+ overhead */
+ rl_maybe_restore_sighandler (SIGINT, &old_int);
+ rl_maybe_restore_sighandler (SIGTERM, &old_term);
+ rl_maybe_restore_sighandler (SIGHUP, &old_hup);
#if defined (SIGQUIT)
- rl_sigaction (SIGQUIT, &old_quit, &dummy);
+ rl_maybe_restore_sighandler (SIGQUIT, &old_quit);
#endif
#if defined (SIGALRM)
- rl_sigaction (SIGALRM, &old_alrm, &dummy);
+ rl_maybe_restore_sighandler (SIGALRM, &old_alrm);
#endif
#if defined (SIGTSTP)
- rl_sigaction (SIGTSTP, &old_tstp, &dummy);
+ rl_maybe_restore_sighandler (SIGTSTP, &old_tstp);
#endif /* SIGTSTP */
#if defined (SIGTTOU)
- rl_sigaction (SIGTTOU, &old_ttou, &dummy);
+ rl_maybe_restore_sighandler (SIGTTOU, &old_ttou);
#endif /* SIGTTOU */
#if defined (SIGTTIN)
- rl_sigaction (SIGTTIN, &old_ttin, &dummy);
+ rl_maybe_restore_sighandler (SIGTTIN, &old_ttin);
#endif /* SIGTTIN */
signals_set_flag = 0;
stupidly_hack_special_variables (name);
-#if 1
/* Return 1 if the assignment seems to have been performed correctly. */
if (entry == 0 || readonly_p (entry))
retval = 0; /* assignment failure */
VUNSETATTR (entry, att_invisible);
ASSIGN_RETURN (retval);
-#else
- if (entry)
- VUNSETATTR (entry, att_invisible);
-
- ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0);
-#endif
}
/* Perform the assignment statement in STRING, and expand the
#if defined (ARRAY_VARS)
else if (valid_array_reference (name))
{
+expand_arrayref:
/* XXX - does this leak if name[@] or name[*]? */
temp = array_value (name, quoted, 0, &atype, &ind);
if (atype == 0 && temp)
else
temp = (char *)NULL;
}
+#if defined (ARRAY_VARS)
+ /* Handle expanding nameref whose value is x[n] */
+ else if (var = find_variable_last_nameref (name))
+ {
+ temp = nameref_cell (var);
+ if (temp && *temp && valid_array_reference (temp))
+ {
+ name = temp;
+ goto expand_arrayref;
+ }
+ temp = (char *)NULL;
+ }
+#endif
else
temp = (char *)NULL;
{
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;
+ }
+ }
w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND, 0);
t = w->word;
goto return0;
}
+#if defined (ARRAY_VARS)
+ else if (var = find_variable_last_nameref (temp1))
+ {
+ temp = nameref_cell (var);
+ if (temp && *temp && valid_array_reference (temp))
+ {
+ tdesc = parameter_brace_expand_word (temp, SPECIAL_VAR (temp, 0), quoted, pflags, (int *)NULL);
+ if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
+ return (tdesc);
+ ret = tdesc;
+ goto return0;
+ }
+ else
+ temp = (char *)NULL;
+ }
+#endif
temp = (char *)NULL;
--- /dev/null
+/* 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-2012 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];
+
+#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;
+
+#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 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_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_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_NOGLOB)
+ {
+ f &= ~W_NOGLOB;
+ fprintf (stderr, "W_NOGLOB%s", f ? "|" : "");
+ }
+ if (f & W_NOSPLIT)
+ {
+ f &= ~W_NOSPLIT;
+ fprintf (stderr, "W_NOSPLIT%s", f ? "|" : "");
+ }
+ if (f & W_GLOBEXP)
+ {
+ f &= ~W_GLOBEXP;
+ fprintf (stderr, "W_GLOBEXP%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;
+ }
+
+#if 0
+ /* Pass the contents of 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);
+ /* skip_XXX_quoted leaves index one past close quote */
+ continue;
+ }
+#else /* XXX - bash-4.2 */
+ /* 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 > 41 && 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;
+ }
+#endif
+
+ /* 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_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_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;
+ WORD_LIST *list;
+
+ mklocal = flags & ASS_MKLOCAL;
+ mkassoc = flags & ASS_MKASSOC;
+
+ 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);
+ 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_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;
+{
+ return (expand_string_if_necessary (string, quoted, expand_string));
+}
+
+#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 */
+ }
+ 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))
+ {
+#if 0
+ for (p1 = end; p1 >= p; p1--)
+#else
+ 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--)
+#endif
+ {
+ 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);
+
+#if 0
+ for (p = end; p >= string; p--)
+#else
+ for (p = (mlen == -1) ? end : string + mlen; p >= string; p--)
+#endif
+ {
+ c = *p; *p = '\0';
+ if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0)
+ {
+ *p = c;
+ *sp = string;
+ *ep = p;
+ return 1;
+ }
+ *p = c;
+#if 1
+ /* If MLEN != -1, we have a fixed length pattern. */
+ if (mlen != -1)
+ break;
+#endif
+ }
+
+ return (0);
+
+ case MATCH_END:
+#if 0
+ for (p = string; p <= end; p++)
+#else
+ for (p = end - ((mlen == -1) ? len : mlen); p <= end; p++)
+#endif
+ {
+ if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
+ {
+ *sp = p;
+ *ep = end;
+ return 1;
+ }
+#if 1
+ /* If MLEN != -1, we have a fixed length pattern. */
+ if (mlen != -1)
+ break;
+#endif
+ }
+
+ 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++)
+ {
+#if 1
+ n2 = simple ? (*wpat == wstring[n]) : match_pattern_wchar (wpat, wstring + n);
+#else
+ n2 = match_pattern_wchar (wpat, wstring + n);
+#endif
+ if (n2)
+ {
+#if 0
+ for (n1 = wstrlen; n1 >= n; n1--)
+#else
+ n1 = (mlen == -1) ? wstrlen : n + mlen;
+ if (n1 > wstrlen)
+ break;
+
+ for ( ; n1 >= n; n1--)
+#endif
+ {
+ 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 1
+ /* If MLEN != -1, we have a fixed length pattern. */
+ if (mlen != -1)
+ break;
+#endif
+ }
+ }
+ }
+
+ return (0);
+
+ case MATCH_BEG:
+ if (match_pattern_wchar (wpat, wstring) == 0)
+ return (0);
+
+#if 0
+ for (n = wstrlen; n >= 0; n--)
+#else
+ for (n = (mlen == -1) ? wstrlen : mlen; n >= 0; n--)
+#endif
+ {
+ 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 1
+ /* If MLEN != -1, we have a fixed length pattern. */
+ if (mlen != -1)
+ break;
+#endif
+ }
+
+ return (0);
+
+ case MATCH_END:
+#if 0
+ for (n = 0; n <= wstrlen; n++)
+#else
+ for (n = wstrlen - ((mlen == -1) ? wstrlen : mlen); n <= wstrlen; n++)
+#endif
+ {
+ if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
+ {
+ *sp = indices[n];
+ *ep = indices[wstrlen];
+ return 1;
+ }
+#if 1
+ /* If MLEN != -1, we have a fixed length pattern. */
+ if (mlen != -1)
+ break;
+#endif
+ }
+
+ 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 0
+ slen = STRLEN (string);
+ mslen = MBSLEN (string);
+ plen = STRLEN (pat);
+ mplen = MBSLEN (pat);
+ if (slen == mslen && plen == mplen)
+#else
+ if (mbsmbchar (string) == 0 && mbsmbchar (pat) == 0)
+#endif
+ 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);
+ 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 */
+
+ 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 */
+
+ 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:
+
+ 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)
+ exit_immediately_on_error = 0;
+
+ 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 (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 (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 || (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)
+ 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);
+ 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[*]? */
+ 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;
+ }
+#if defined (ARRAY_VARS)
+ /* Handle expanding nameref whose value is x[n] */
+ else if (var = find_variable_last_nameref (name))
+ {
+ temp = nameref_cell (var);
+ if (temp && *temp && valid_array_reference (temp))
+ {
+ name = temp;
+ goto expand_arrayref;
+ }
+ temp = (char *)NULL;
+ }
+#endif
+ else
+ temp = (char *)NULL;
+
+ if (ret == 0)
+ {
+ ret = alloc_word_desc ();
+ ret->word = temp;
+ ret->flags |= rflags;
+ }
+ return ret;
+}
+
+/* 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 */
+ 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;
+ }
+ }
+
+ 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);
+
+ 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);
+
+ /* 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;
+ char *temp;
+#if defined (ARRAY_VARS)
+ SHELL_VAR *v;
+#endif
+ arrayind_t lind;
+
+ /* This sets vtype to VT_VARIABLE or VT_POSPARMS */
+ vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0';
+ if (vtype == VT_POSPARMS && varname[0] == '*')
+ vtype |= VT_STARSUB;
+ *varp = (SHELL_VAR *)NULL;
+
+#if defined (ARRAY_VARS)
+ if (valid_array_reference (varname))
+ {
+ v = array_variable_part (varname, &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 && (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 (varname, 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 (varname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
+ }
+ }
+ else if ((v = find_variable (varname)) && (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;
+ }
+
+ 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;
+
+#if 0
+ if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
+ ret = string_list_dollar_star (quote_list (save));
+ else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB)
+ ret = string_list_dollar_star (save);
+ else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
+ ret = string_list_dollar_at (save, qflags);
+ else
+ ret = string_list_dollar_star (save);
+#else
+ ret = string_list_pos_params (pchar, save, qflags);
+#endif
+
+ 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. */
+ rep = expand_string_if_necessary (rep, quoted & ~(Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT), 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), &ind);
+
+ if (tdesc)
+ {
+ temp = tdesc->word;
+ tflag = tdesc->flags;
+ dispose_word_desc (tdesc);
+ }
+ else
+ temp = (char *)0;
+
+#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);
+
+ /* 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 1
+# 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);
+#else
+ temp = string_list_dollar_at (list, quoted);
+#endif
+ 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;
+ }
+#if defined (ARRAY_VARS)
+ else if (var = find_variable_last_nameref (temp1))
+ {
+ temp = nameref_cell (var);
+ if (temp && *temp && valid_array_reference (temp))
+ {
+ tdesc = parameter_brace_expand_word (temp, SPECIAL_VAR (temp, 0), quoted, pflags, (int *)NULL);
+ if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
+ return (tdesc);
+ ret = tdesc;
+ goto return0;
+ }
+ else
+ temp = (char *)NULL;
+ }
+#endif
+
+ 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 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;
+ 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, "ed_dollar_at,
+ &had_quoted_null, pflags);
+ has_dollar_at += temp_has_dollar_at;
+
+ 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);
+ }
+ 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 0
+ if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
+#else
+ if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+#endif
+ 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)
+ {
+#if 0
+ if (quoted_dollar_at && (word->flags & W_NOSPLIT2))
+ temp = string_list_internal (quote_list (list), " ");
+ else
+#endif
+ /* 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 0
+ if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
+#else
+ if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+#endif
+ 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_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 (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_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";
+
+ /* 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]);
+ tword->flags |= W_GLOBEXP; /* XXX */
+ 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_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;
+
+ if (tlist->word->flags & W_ASSIGNASSOC)
+ make_internal_declare (tlist->word->word, "-A");
+
+ 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);
+ }
+#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. */
+static WORD_LIST *
+expand_word_list_internal (list, eflags)
+ WORD_LIST *list;
+ int eflags;
+{
+ WORD_LIST *new_list, *temp_list;
+ int tint;
+
+ 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;
+ }
+
+#if 0
+ tint = list_length (new_list) + 1;
+ RESIZE_MALLOCED_BUFFER (glob_argv_flags, 0, tint, glob_argv_flags_size, 16);
+ for (tint = 0, temp_list = new_list; temp_list; temp_list = temp_list->next)
+ glob_argv_flags[tint++] = (temp_list->word->flags & W_GLOBEXP) ? '1' : '0';
+ glob_argv_flags[tint] = '\0';
+#endif
+
+ return (new_list);
+}
-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
./errors.tests: line 52: unset: `/bin/sh': not a valid identifier
./errors.tests: line 55: unset: cannot simultaneously unset a function and a variable
./errors.tests: line 58: declare: -z: invalid option
-declare: usage: declare [-aAfFgilrtux] [-p] [name[=value] ...]
+declare: usage: declare [-aAfFgilnrtux] [-p] [name[=value] ...]
./errors.tests: line 60: declare: `-z': not a valid identifier
./errors.tests: line 61: declare: `/bin/sh': not a valid identifier
./errors.tests: line 65: declare: cannot use `-f' to make functions
--- /dev/null
+one
+two
+three
+declare -n fee="flip"
+declare -n foo="bar"
+turning off nameref attribute on foo
+bar
+after +n foo bar = other
+one
+two
+one
+expect <one>
+argv[1] = <one>
+expect <two>
+argv[1] = <two>
+expect <bar>
+bar
+expect <one>
+one
+expect <one>
+argv[1] = <one>
+changevar: expect <two>
+argv[1] = <two>
+expect <two>
+argv[1] = <two>
+changevar: expect <three four five>
+argv[1] = <three four five>
+expect <three four five>
+argv[1] = <three four five>
+./nameref.tests: line 92: bar: readonly variable
+./nameref.tests: line 93: foo: readonly variable
+one
+one
+./nameref.tests: line 105: foo: readonly variable
+./nameref.tests: line 102: foo: readonly variable
+one
+one
+bar
+
+./nameref2.sub: line 5: foo: readonly variable
+
+expect <unset>
+argv[1] = <unset>
+expect <unset>
+argv[1] = <unset>
+./nameref3.sub: line 15: unset: bar: cannot unset: readonly variable
+expect <two>
+two
+expect <two>
+two
+three
+unset
+four
+0
+expect <a b>
+a b
+expect <foo>
+foo
+1 3 5 7 9
+9
+1 3 42 7 9
+1 3 42 7 9
+9
+1 3 44 7 9
+unset
+expect <a b c d e>
+argv[1] = <a b c d e>
+expect <zero> <one> <seven> <three> <four>
+argv[1] = <zero>
+argv[2] = <one>
+argv[3] = <seven>
+argv[4] = <three>
+argv[5] = <four>
+16
+expect <4>
+4
+expect <4>
+4
+expect <4>
+4
+expect <one>
+one
+expect <one>
+one
+expect <one>
+one
+expect <four>
+four
+errors = 0
+1
+2
+v1: 1
+v2: 2
+ref -> first, value: I am first
+ref -> second, value: I am in the middle
+ref -> third, value: I am last
+final state: ref -> third, value: I am last
+ref -> one, value: 1
+ref -> two, value: 2
+ref -> three, value: 3
+final state: ref -> three, value: 3
+./nameref5.sub: line 43: unset: three: cannot unset: readonly variable
+ref -> one, value: 1
+ref -> two, value: 2
+ref -> three, value: 3
+final state: ref -> three, value: 3
--- /dev/null
+# basic nameref tests
+bar=one
+flow=two
+flip=three
+
+foo=bar
+typeset -n foo
+
+typeset -n fee=flow
+
+echo ${foo}
+echo ${fee}
+
+typeset -n fee=flip
+echo ${fee}
+
+typeset -n
+
+echo turning off nameref attribute on foo
+typeset +n foo=other
+echo ${foo}
+echo after +n foo bar = $bar
+
+unset foo bar fee
+
+bar=one
+
+foo=bar
+typeset -n foo
+
+foo=two printf "%s\n" $foo
+foo=two eval 'printf "%s\n" $foo'
+
+foo=two echo $foo
+
+unset foo bar
+# other basic assignment tests
+bar=one
+
+echo "expect <one>"
+recho ${bar}
+typeset -n foo=bar
+foo=two
+
+echo "expect <two>"
+recho ${bar}
+
+# this appears to be a ksh93 bug; it doesn't unset foo here and messes up
+# later
+unset foo bar
+
+# initial tests of working inside shell functions
+echoval()
+{
+ typeset -n ref=$1
+ printf "%s\n" $ref
+}
+
+foo=bar
+bar=one
+echo "expect <$foo>"
+echoval foo
+echo "expect <$bar>"
+echoval bar
+
+unset foo bar
+changevar()
+{
+ typeset -n v=$1
+
+ shift
+ v="$@"
+ echo "changevar: expect <$@>"
+ recho "$v"
+}
+
+bar=one
+
+echo "expect <one>"
+recho ${bar}
+changevar bar two
+echo "expect <two>"
+recho $bar
+
+changevar bar three four five
+echo "expect <three four five>"
+recho "$bar"
+
+unset foo bar
+readonly foo=one
+typeset -n bar=foo
+bar=4
+foo=4
+
+echo $foo
+echo $bar
+
+assignvar()
+{
+ typeset -n ref=$1
+ shift
+ ref="$@"
+}
+
+readonly foo=one
+
+assignvar foo two three four
+echo $foo
+
+${THIS_SH} ./nameref1.sub
+${THIS_SH} ./nameref2.sub
+${THIS_SH} ./nameref3.sub
+${THIS_SH} ./nameref4.sub
+${THIS_SH} ./nameref5.sub
--- /dev/null
+# basic nameref tests
+bar=one
+flow=two
+flip=three
+
+foo=bar
+typeset -n foo
+
+typeset -n fee=flow
+
+echo ${foo}
+echo ${fee}
+
+typeset -n fee=flip
+echo ${fee}
+
+typeset -n
+
+echo turning off nameref attribute on foo
+typeset +n foo=other
+echo ${foo}
+echo after +n foo bar = $bar
+
+unset foo bar fee
+
+bar=one
+
+foo=bar
+typeset -n foo
+
+foo=two printf "%s\n" $foo
+foo=two eval 'printf "%s\n" $foo'
+
+foo=two echo $foo
+
+unset foo bar
+# other basic assignment tests
+bar=one
+
+echo "expect <one>"
+recho ${bar}
+typeset -n foo=bar
+foo=two
+
+echo "expect <two>"
+recho ${bar}
+
+# this appears to be a ksh93 bug; it doesn't unset foo here and messes up
+# later
+unset foo bar
+
+# initial tests of working inside shell functions
+echoval()
+{
+ typeset -n ref=$1
+ printf "%s\n" $ref
+}
+
+foo=bar
+bar=one
+echo "expect <$foo>"
+echoval foo
+echo "expect <$bar>"
+echoval bar
+
+unset foo bar
+changevar()
+{
+ typeset -n v=$1
+
+ shift
+ v="$@"
+ echo "changevar: expect <$@>"
+ recho "$v"
+}
+
+bar=one
+
+echo "expect <one>"
+recho ${bar}
+changevar bar two
+echo "expect <two>"
+recho $bar
+
+changevar bar three four five
+echo "expect <three four five>"
+recho "$bar"
+
+unset foo bar
+readonly foo=one
+typeset -n bar=foo
+bar=4
+foo=4
+
+echo $foo
+echo $bar
+
+assignvar()
+{
+ typeset -n ref=$1
+ shift
+ ref="$@"
+}
+
+readonly foo=one
+
+assignvar foo two three four
+echo $foo
--- /dev/null
+# indirect referencing of a nameref returns the variable name it references
+unset foo bar
+
+bar=one
+foo=bar
+
+typeset -n foo
+
+echo ${foo}
+echo ${!foo}
+
+# this is a current incompatibility
+echo ${!foo[0]}
--- /dev/null
+# test readonly nameref variables
+# ksh93 allows this but not typeset -rn ref=foo?
+typeset -n ref=foo
+readonly ref
+foo=4
+
+echo $ref
--- /dev/null
+# nameref requires changes to unset
+bar=one
+typeset -n foo=bar
+
+# normal unset unsets both nameref and variable it references
+unset foo
+echo "expect <unset>"
+recho ${bar-unset}
+echo "expect <unset>"
+recho ${foo-unset}
+
+readonly bar=two
+typeset -n foo=bar
+
+unset foo # this should fail because bar is readonly
+
+echo "expect <two>"
+echo ${bar-unset}
+echo "expect <two>"
+echo ${foo-unset}
+
+# one question is what happens when you unset the underlying variable
+qux=three
+typeset -n ref
+ref=qux
+
+echo $ref
+unset qux
+echo ${ref-unset}
+qux=four
+echo ${ref-unset}
--- /dev/null
+# test suite cribbed from ksh93 nameref tests
+typeset -i errors=0
+ckval()
+{
+ typeset -n one=$1
+
+ if [[ $one != $2 ]]; then
+ echo "one=$one != 2=$2"
+ (( errors++ ))
+ fi
+}
+
+ckref()
+{
+ typeset -n one=$1 two=$2
+
+ if [[ $one != $two ]]; then
+ echo "one=$one != two=$two"
+ (( errors++ ))
+ fi
+}
+
+name=first
+
+ckref name name
+
+func1()
+{
+ typeset -n color=$1
+ func2 color
+}
+
+func2()
+{
+ typeset color=$1
+ set -- ${color[@]}
+ printf "<%s>" "$@"
+ echo
+}
+
+typeset -A color
+color[apple]=red
+color[grape]=purple
+color[banana]=yellow
+
+# XXX
+#func1 color
+
+unset foo bar
+export bar=foo
+typeset -n foo=bar
+ckval foo foo
+
+# XXX - need to see if we can do checks for self-referencing at assignment
+# time
+command typeset -n xx=yy
+command typeset -n yy=xx
+echo $?
+
+unset foo bar
+set foo
+typeset -n bar=$1
+foo=hello
+ckval bar hello
+
+# XXX -- another self-referencing error?
+# ksh93 makes this another invalid self-reference
+unset foo bar
+bar=123
+foobar()
+{
+ typeset -n foo=bar
+ typeset -n foo=bar
+
+ ckval foo 123
+}
+
+typeset -n short=long
+short=( a b )
+echo "expect <a b>"
+echo ${long[@]}
+unset short long
+
+# assignment to a previously-unset variable
+typeset -n short=long
+short=foo
+echo "expect <foo>"
+echo ${long}
+unset short long
+
+unset foo bar
+
+# simple array references and assignments
+typeset -n foo=bar
+bar=( 1 3 5 7 9)
+echo ${foo[@]}
+echo ${foo[4]}
+foo[2]=42
+echo ${bar[@]}
+
+barfunc()
+{
+ typeset -n v=$1
+ echo ${v[@]}
+ echo ${v[4]}
+ v[2]=44
+ echo ${bar[@]}
+}
+barfunc bar
+
+unset -f foobar
+unset foo bar
+
+# should ref at global scope survive call to foobar()?
+unset ref x
+typeset -n ref
+x=42
+foobar()
+{
+ local xxx=3
+ ref=xxx
+ return 0
+}
+echo ${ref-unset}
+ref=x
+foobar
+ckval ref xxx
+ckval x xxx
+
+# assignment in a function to something possibly out of scope
+assignvar()
+{
+ typeset -n v=$1
+ shift
+ v="$@"
+}
+
+assignvar lex a b c d e
+echo "expect <a b c d e>"
+recho "${lex}"
+
+unset foo bar short long
+
+typeset -n foo='x[2]'
+
+x=(zero one two three four)
+foo=seven
+
+echo "expect <zero> <one> <seven> <three> <four>"
+recho "${x[@]}"
+
+unset ref x
+typeset -n ref
+ref=x
+# make sure nameref to a previously-unset variable creates the variable
+ref=42
+ckval x 42
+
+# make sure they work inside arithmetic expressions
+unset foo bar ref x xxx
+typeset -i ivar
+typeset -n iref=ivar
+
+ivar=4+3
+ckval ivar 7
+iref+=5
+ckval ivar 12
+echo $(( iref+4 ))
+(( iref=17 ))
+ckval ivar 17
+
+typeset +n iref
+unset iref ivar
+
+typeset +n foo bar
+unset foo bar
+
+# should the reference do immediate evaluation or deferred?
+set -- one two three four
+bar=4
+# XXX - what does foo get set to here?
+typeset -n foo='bar[0]'
+echo "expect <4>"
+echo ${bar[0]}
+echo "expect <4>"
+echo ${foo}
+echo "expect <4>"
+echo $foo
+ckval foo $bar
+
+# Need to add code and tests for nameref to array subscripts
+bar=(one two three four)
+
+typeset -n foo='bar[0]'
+typeset -n qux='bar[3]'
+echo "expect <one>"
+echo ${bar[0]}
+echo "expect <one>"
+echo ${foo}
+echo "expect <one>"
+echo $foo
+ckval foo $bar
+
+echo "expect <four>"
+echo $qux
+ckval qux ${bar[3]}
+
+# Need to add code and tests for `for' loop nameref variables
+
+echo errors = $errors
+exit $errors
--- /dev/null
+# nameref variables as for loop index variables are special
+v1=1
+v2=2
+
+# simple for loop
+for v in v1 v2
+do
+ typeset -n ref=$v
+ echo $ref
+done
+unset v
+
+set -- first second third fourth fifth
+
+# unless you put a ${!v} in the for loop, ksh93 misbehaves
+typeset -n v=v1
+for v in v1 v2; do
+ echo "${!v}: $v"
+done
+unset v
+
+# example cribbed from ksh93 o'reilly book
+first="I am first"
+second="I am in the middle"
+third="I am last"
+
+typeset -n ref=first
+for ref in first second third ; do
+ echo "ref -> ${!ref}, value: $ref"
+done
+echo final state: "ref -> ${!ref}, value: $ref"
+
+readonly one=1
+readonly two=2
+readonly three=3
+
+typeset -n ref=one
+for ref in one two three; do
+ echo "ref -> ${!ref}, value: $ref"
+done
+echo final state: "ref -> ${!ref}, value: $ref"
+
+unset ref
+typeset -n ref=one
+readonly ref
+
+for ref in one two three; do
+ echo "ref -> ${!ref}, value: $ref"
+done
+echo final state: "ref -> ${!ref}, value: $ref"
--- /dev/null
+echo "warning: some of these tests will fail if arrays have not" >&2
+echo "warning: been compiled into the shell" >&2
+${THIS_SH} ./nameref.tests > /tmp/xx 2>&1
+diff /tmp/xx nameref.right && rm -f /tmp/xx
--- /dev/null
+echo "warning: all of these tests will fail if arrays have not" >&2
+echo "warning: been compiled into the shell" >&2
+${THIS_SH} ./assoc.tests > /tmp/xx 2>&1
+diff /tmp/xx assoc.right && rm -f /tmp/xx
return (sigmodes[sig] & SIG_TRAPPED);
}
+int
+signal_is_pending (sig)
+ int sig;
+{
+ return (pending_traps[sig]);
+}
+
int
signal_is_special (sig)
int sig;
extern int maybe_call_trap_handler __P((int));
extern int signal_is_special __P((int));
extern int signal_is_trapped __P((int));
+extern int signal_is_pending __P((int));
extern int signal_is_ignored __P((int));
extern int signal_is_hard_ignored __P((int));
extern void set_signal_ignored __P((int));
/* variables.c -- Functions for hacking shell variables. */
-/* Copyright (C) 1987-2011 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2012 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
static int visible_array_vars __P((SHELL_VAR *));
#endif
+static SHELL_VAR *find_nameref_at_context __P((SHELL_VAR *, VAR_CONTEXT *));
+static SHELL_VAR *find_variable_nameref_context __P((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **));
+static SHELL_VAR *find_variable_last_nameref_context __P((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **));
+
static SHELL_VAR *bind_tempenv_variable __P((const char *, char *));
static void push_temp_var __P((PTR_T));
static void propagate_temp_var __P((PTR_T));
return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
}
+/* Look up and resolve the chain of nameref variables starting at V all the
+ way to NULL or non-nameref. */
+SHELL_VAR *
+find_variable_nameref (v)
+ SHELL_VAR *v;
+{
+ int level;
+ char *newname;
+
+ level = 0;
+ while (v && nameref_p (v))
+ {
+ level++;
+ if (level > NAMEREF_MAX)
+ return ((SHELL_VAR *)0); /* error message here? */
+ newname = nameref_cell (v);
+ if (newname == 0 || *newname == '\0')
+ return ((SHELL_VAR *)0);
+ v = find_variable_internal (newname, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+ }
+ return v;
+}
+
+/* Resolve the chain of nameref variables for NAME. XXX - could change later */
+SHELL_VAR *
+find_variable_last_nameref (name)
+ const char *name;
+{
+ SHELL_VAR *v, *nv;
+ char *newname;
+ int level;
+
+ nv = v = find_variable_noref (name);
+ level = 0;
+ while (v && nameref_p (v))
+ {
+ level++;
+ if (level > NAMEREF_MAX)
+ return ((SHELL_VAR *)0); /* error message here? */
+ newname = nameref_cell (v);
+ if (newname == 0 || *newname == '\0')
+ return ((SHELL_VAR *)0);
+ nv = v;
+ v = find_variable_internal (newname, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+ }
+ return nv;
+}
+
+/* Resolve the chain of nameref variables for NAME. XXX - could change later */
+SHELL_VAR *
+find_global_variable_last_nameref (name)
+ const char *name;
+{
+ SHELL_VAR *v, *nv;
+ char *newname;
+ int level;
+
+ nv = v = find_global_variable_noref (name);
+ level = 0;
+ while (v && nameref_p (v))
+ {
+ level++;
+ if (level > NAMEREF_MAX)
+ return ((SHELL_VAR *)0); /* error message here? */
+ newname = nameref_cell (v);
+ if (newname == 0 || *newname == '\0')
+ return ((SHELL_VAR *)0);
+ nv = v;
+ v = find_global_variable_noref (newname);
+ }
+ return nv;
+}
+
+static SHELL_VAR *
+find_nameref_at_context (v, vc)
+ SHELL_VAR *v;
+ VAR_CONTEXT *vc;
+{
+ SHELL_VAR *nv, *nv2;
+ VAR_CONTEXT *nvc;
+ char *newname;
+ int level;
+
+ nv = v;
+ level = 1;
+ while (nv && nameref_p (nv))
+ {
+ level++;
+ if (level > NAMEREF_MAX)
+ return ((SHELL_VAR *)NULL);
+ newname = nameref_cell (nv);
+ if (newname == 0 || *newname == '\0')
+ return ((SHELL_VAR *)NULL);
+ nv2 = hash_lookup (newname, vc->table);
+ if (nv2 == 0)
+ break;
+ nv = nv2;
+ }
+ return nv;
+}
+
+/* Do nameref resolution from the VC, which is the local context for some
+ function or builtin, `up' the chain to the global variables context. If
+ NVCP is not NULL, return the variable context where we finally ended the
+ nameref resolution (so the bind_variable_internal can use the correct
+ variable context and hash table). */
+static SHELL_VAR *
+find_variable_nameref_context (v, vc, nvcp)
+ SHELL_VAR *v;
+ VAR_CONTEXT *vc;
+ VAR_CONTEXT **nvcp;
+{
+ SHELL_VAR *nv, *nv2;
+ VAR_CONTEXT *nvc;
+
+ /* Look starting at the current context all the way `up' */
+ for (nv = v, nvc = vc; nvc; nvc = nvc->down)
+ {
+ nv2 = find_nameref_at_context (nv, nvc);
+ if (nv2 == 0)
+ continue;
+ nv = nv2;
+ if (*nvcp)
+ *nvcp = nvc;
+ }
+ return (nameref_p (nv) ? (SHELL_VAR *)NULL : nv);
+}
+
+/* Do nameref resolution from the VC, which is the local context for some
+ function or builtin, `up' the chain to the global variables context. If
+ NVCP is not NULL, return the variable context where we finally ended the
+ nameref resolution (so the bind_variable_internal can use the correct
+ variable context and hash table). */
+static SHELL_VAR *
+find_variable_last_nameref_context (v, vc, nvcp)
+ SHELL_VAR *v;
+ VAR_CONTEXT *vc;
+ VAR_CONTEXT **nvcp;
+{
+ SHELL_VAR *nv, *nv2;
+ VAR_CONTEXT *nvc;
+
+ /* Look starting at the current context all the way `up' */
+ for (nv = v, nvc = vc; nvc; nvc = nvc->down)
+ {
+ nv2 = find_nameref_at_context (nv, nvc);
+ if (nv2 == 0)
+ continue;
+ nv = nv2;
+ if (*nvcp)
+ *nvcp = nvc;
+ }
+ return (nameref_p (nv) ? nv : (SHELL_VAR *)NULL);
+}
+
/* Find a variable, forcing a search of the temporary environment first */
SHELL_VAR *
find_variable_tempenv (name)
SHELL_VAR *var;
var = find_variable_internal (name, 1);
+ if (var && nameref_p (var))
+ var = find_variable_nameref (var);
return (var);
}
SHELL_VAR *var;
var = find_variable_internal (name, 0);
+ if (var && nameref_p (var))
+ var = find_variable_nameref (var);
return (var);
}
{
SHELL_VAR *var;
+ var = var_lookup (name, global_variables);
+ if (var && nameref_p (var))
+ var = find_variable_nameref (var);
+
+ if (var == 0)
+ return ((SHELL_VAR *)NULL);
+
+ return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
+}
+
+SHELL_VAR *
+find_global_variable_noref (name)
+ const char *name;
+{
+ SHELL_VAR *var;
+
var = var_lookup (name, global_variables);
if (var == 0)
SHELL_VAR *var;
var = var_lookup (name, shell_variables);
+ if (var && nameref_p (var))
+ var = find_variable_nameref (var);
if (var == 0)
return ((SHELL_VAR *)NULL);
find_variable (name)
const char *name;
{
- return (find_variable_internal (name, (expanding_redir == 0 && (assigning_in_environment || executing_builtin))));
+ SHELL_VAR *v;
+
+ v = find_variable_internal (name, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+ if (v && nameref_p (v))
+ v = find_variable_nameref (v);
+ return v;
+}
+
+SHELL_VAR *
+find_variable_noref (name)
+ const char *name;
+{
+ SHELL_VAR *v;
+
+ v = find_variable_internal (name, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+ return v;
}
/* Look up the function entry whose name matches STRING.
SHELL_VAR *entry;
entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table);
+ /* Follow the nameref chain here if this is the global variables table */
+ if (entry && nameref_p (entry) && (invisible_p (entry) == 0) && table == global_variables->table)
+ {
+ entry = find_global_variable (entry->name);
+ /* Let's see if we have a nameref referencing a variable that hasn't yet
+ been created. */
+ if (entry == 0)
+ entry = find_variable_last_nameref (name); /* XXX */
+ if (entry == 0) /* just in case */
+ return (entry);
+ }
- if (entry == 0)
+ /* The first clause handles `declare -n ref; ref=x;' */
+ if (entry && invisible_p (entry) && nameref_p (entry))
+ goto assign_value;
+ else if (entry && nameref_p (entry))
+ {
+ newval = nameref_cell (entry);
+#if defined (ARRAY_VARS)
+ /* declare -n foo=x[2] */
+ if (valid_array_reference (newval))
+ /* XXX - should it be aflags? */
+ entry = assign_array_element (newval, make_variable_value (entry, value, 0), aflags);
+ else
+#endif
+ {
+ entry = make_new_variable (newval, table);
+ var_setvalue (entry, make_variable_value (entry, value, 0));
+ }
+ }
+ else if (entry == 0)
{
entry = make_new_variable (name, table);
var_setvalue (entry, make_variable_value (entry, value, 0)); /* XXX */
}
else
{
+assign_value:
if (readonly_p (entry) || noassign_p (entry))
{
if (readonly_p (entry))
char *value;
int flags;
{
- SHELL_VAR *v;
- VAR_CONTEXT *vc;
+ SHELL_VAR *v, *nv;
+ VAR_CONTEXT *vc, *nvc;
+ int level;
if (shell_variables == 0)
create_variable_tables ();
if (vc_isfuncenv (vc) || vc_isbltnenv (vc))
{
v = hash_lookup (name, vc->table);
+ nvc = vc;
+ if (v && nameref_p (v))
+ {
+ nv = find_variable_nameref_context (v, vc, &nvc);
+ if (nv == 0)
+ {
+ nv = find_variable_last_nameref_context (v, vc, &nvc);
+ if (nv && nameref_p (nv))
+ return (bind_variable_internal (nameref_cell (nv), value, nvc->table, 0, flags));
+ else
+ v = nv;
+ }
+ else
+ v = nv;
+ }
if (v)
- return (bind_variable_internal (name, value, vc->table, 0, flags));
+ return (bind_variable_internal (v->name, value, nvc->table, 0, flags));
}
}
+ /* bind_variable_internal will handle nameref resolution in this case */
return (bind_variable_internal (name, value, global_variables->table, 0, flags));
}
else if (assoc_p (var))
var_setassoc (copy, assoc_copy (assoc_cell (var)));
#endif
- else if (value_cell (var))
+ else if (nameref_cell (var)) /* XXX - nameref */
+ var_setref (copy, savestring (nameref_cell (var)));
+ else if (value_cell (var)) /* XXX - nameref */
var_setvalue (copy, savestring (value_cell (var)));
else
var_setvalue (copy, (char *)NULL);
else if (assoc_p (var))
assoc_dispose (assoc_cell (var));
#endif
+ else if (nameref_p (var))
+ FREE (nameref_cell (var));
else
FREE (value_cell (var));
}
free (var);
}
-/* Unset the shell variable referenced by NAME. */
+/* Unset the shell variable referenced by NAME. Unsetting a nameref variable
+ unsets both the variable and the variable it resolves to. */
int
unbind_variable (name)
const char *name;
{
- return makunbound (name, shell_variables);
+ SHELL_VAR *v, *nv;
+ int r;
+
+ v = var_lookup (name, shell_variables);
+ nv = (v && nameref_p (v)) ? find_variable_nameref (v) : (SHELL_VAR *)NULL;
+
+ r = nv ? makunbound (nv->name, shell_variables) : 0;
+ if (r == 0)
+ r = makunbound (name, shell_variables);
+
+ return r;
}
/* Unset the shell function named NAME. */
{
SHELL_VAR *v;
- v = find_variable (name);
+ if (v = find_variable (name))
+ {
+ if (history_comment_char == 0)
+ history_comment_char = '#';
+ }
history_write_timestamps = (v != 0);
}
#endif /* HISTORY */
--- /dev/null
+/* variables.c -- Functions for hacking shell variables. */
+
+/* Copyright (C) 1987-2012 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 "posixstat.h"
+#include "posixtime.h"
+
+#if defined (__QNX__)
+# if defined (__QNXNTO__)
+# include <sys/netmgr.h>
+# else
+# include <sys/vc.h>
+# endif /* !__QNXNTO__ */
+#endif /* __QNX__ */
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "chartypes.h"
+#if defined (HAVE_PWD_H)
+# include <pwd.h>
+#endif
+#include "bashansi.h"
+#include "bashintl.h"
+
+#define NEED_XTRACE_SET_DECL
+
+#include "shell.h"
+#include "flags.h"
+#include "execute_cmd.h"
+#include "findcmd.h"
+#include "mailcheck.h"
+#include "input.h"
+#include "hashcmd.h"
+#include "pathexp.h"
+#include "alias.h"
+
+#include "builtins/getopt.h"
+#include "builtins/common.h"
+
+#if defined (READLINE)
+# include "bashline.h"
+# include <readline/readline.h>
+#else
+# include <tilde/tilde.h>
+#endif
+
+#if defined (HISTORY)
+# include "bashhist.h"
+# include <readline/history.h>
+#endif /* HISTORY */
+
+#if defined (PROGRAMMABLE_COMPLETION)
+# include "pcomplete.h"
+#endif
+
+#define TEMPENV_HASH_BUCKETS 4 /* must be power of two */
+
+#define ifsname(s) ((s)[0] == 'I' && (s)[1] == 'F' && (s)[2] == 'S' && (s)[3] == '\0')
+
+extern char **environ;
+
+/* Variables used here and defined in other files. */
+extern int posixly_correct;
+extern int line_number, line_number_base;
+extern int subshell_environment, indirection_level, subshell_level;
+extern int build_version, patch_level;
+extern int expanding_redir;
+extern int last_command_exit_value;
+extern char *dist_version, *release_status;
+extern char *shell_name;
+extern char *primary_prompt, *secondary_prompt;
+extern char *current_host_name;
+extern sh_builtin_func_t *this_shell_builtin;
+extern SHELL_VAR *this_shell_function;
+extern char *the_printed_command_except_trap;
+extern char *this_command_name;
+extern char *command_execution_string;
+extern time_t shell_start_time;
+extern int assigning_in_environment;
+extern int executing_builtin;
+extern int funcnest_max;
+
+#if defined (READLINE)
+extern int no_line_editing;
+extern int perform_hostname_completion;
+#endif
+
+/* The list of shell variables that the user has created at the global
+ scope, or that came from the environment. */
+VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL;
+
+/* The current list of shell variables, including function scopes */
+VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL;
+
+/* The list of shell functions that the user has created, or that came from
+ the environment. */
+HASH_TABLE *shell_functions = (HASH_TABLE *)NULL;
+
+#if defined (DEBUGGER)
+/* The table of shell function definitions that the user defined or that
+ came from the environment. */
+HASH_TABLE *shell_function_defs = (HASH_TABLE *)NULL;
+#endif
+
+/* The current variable context. This is really a count of how deep into
+ executing functions we are. */
+int variable_context = 0;
+
+/* The set of shell assignments which are made only in the environment
+ for a single command. */
+HASH_TABLE *temporary_env = (HASH_TABLE *)NULL;
+
+/* Set to non-zero if an assignment error occurs while putting variables
+ into the temporary environment. */
+int tempenv_assign_error;
+
+/* Some funky variables which are known about specially. Here is where
+ "$*", "$1", and all the cruft is kept. */
+char *dollar_vars[10];
+WORD_LIST *rest_of_args = (WORD_LIST *)NULL;
+
+/* The value of $$. */
+pid_t dollar_dollar_pid;
+
+/* Non-zero means that we have to remake EXPORT_ENV. */
+int array_needs_making = 1;
+
+/* The number of times BASH has been executed. This is set
+ by initialize_variables (). */
+int shell_level = 0;
+
+/* An array which is passed to commands as their environment. It is
+ manufactured from the union of the initial environment and the
+ shell variables that are marked for export. */
+char **export_env = (char **)NULL;
+static int export_env_index;
+static int export_env_size;
+
+#if defined (READLINE)
+static int winsize_assignment; /* currently assigning to LINES or COLUMNS */
+#endif
+
+/* Some forward declarations. */
+static void create_variable_tables __P((void));
+
+static void set_machine_vars __P((void));
+static void set_home_var __P((void));
+static void set_shell_var __P((void));
+static char *get_bash_name __P((void));
+static void initialize_shell_level __P((void));
+static void uidset __P((void));
+#if defined (ARRAY_VARS)
+static void make_vers_array __P((void));
+#endif
+
+static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t, char *));
+#if defined (ARRAY_VARS)
+static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t, char *));
+#endif
+static SHELL_VAR *get_self __P((SHELL_VAR *));
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *init_dynamic_array_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
+static SHELL_VAR *init_dynamic_assoc_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int));
+#endif
+
+static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_seconds __P((SHELL_VAR *));
+static SHELL_VAR *init_seconds_var __P((void));
+
+static int brand __P((void));
+static void sbrand __P((unsigned long)); /* set bash random number generator. */
+static void seedrand __P((void)); /* seed generator randomly */
+static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_random __P((SHELL_VAR *));
+
+static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_lineno __P((SHELL_VAR *));
+
+static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_subshell __P((SHELL_VAR *));
+
+static SHELL_VAR *get_bashpid __P((SHELL_VAR *));
+
+#if defined (HISTORY)
+static SHELL_VAR *get_histcmd __P((SHELL_VAR *));
+#endif
+
+#if defined (READLINE)
+static SHELL_VAR *get_comp_wordbreaks __P((SHELL_VAR *));
+static SHELL_VAR *assign_comp_wordbreaks __P((SHELL_VAR *, char *, arrayind_t, char *));
+#endif
+
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t, char *));
+static SHELL_VAR *get_dirstack __P((SHELL_VAR *));
+#endif
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *get_groupset __P((SHELL_VAR *));
+
+static SHELL_VAR *build_hashcmd __P((SHELL_VAR *));
+static SHELL_VAR *get_hashcmd __P((SHELL_VAR *));
+static SHELL_VAR *assign_hashcmd __P((SHELL_VAR *, char *, arrayind_t, char *));
+# if defined (ALIAS)
+static SHELL_VAR *build_aliasvar __P((SHELL_VAR *));
+static SHELL_VAR *get_aliasvar __P((SHELL_VAR *));
+static SHELL_VAR *assign_aliasvar __P((SHELL_VAR *, char *, arrayind_t, char *));
+# endif
+#endif
+
+static SHELL_VAR *get_funcname __P((SHELL_VAR *));
+static SHELL_VAR *init_funcname_var __P((void));
+
+static void initialize_dynamic_variables __P((void));
+
+static SHELL_VAR *hash_lookup __P((const char *, HASH_TABLE *));
+static SHELL_VAR *new_shell_variable __P((const char *));
+static SHELL_VAR *make_new_variable __P((const char *, HASH_TABLE *));
+static SHELL_VAR *bind_variable_internal __P((const char *, char *, HASH_TABLE *, int, int));
+
+static void dispose_variable_value __P((SHELL_VAR *));
+static void free_variable_hash_data __P((PTR_T));
+
+static VARLIST *vlist_alloc __P((int));
+static VARLIST *vlist_realloc __P((VARLIST *, int));
+static void vlist_add __P((VARLIST *, SHELL_VAR *, int));
+
+static void flatten __P((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int));
+
+static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **));
+
+static SHELL_VAR **vapply __P((sh_var_map_func_t *));
+static SHELL_VAR **fapply __P((sh_var_map_func_t *));
+
+static int visible_var __P((SHELL_VAR *));
+static int visible_and_exported __P((SHELL_VAR *));
+static int export_environment_candidate __P((SHELL_VAR *));
+static int local_and_exported __P((SHELL_VAR *));
+static int variable_in_context __P((SHELL_VAR *));
+#if defined (ARRAY_VARS)
+static int visible_array_vars __P((SHELL_VAR *));
+#endif
+
+static SHELL_VAR *find_nameref_at_context __P((SHELL_VAR *, VAR_CONTEXT *));
+static SHELL_VAR *find_variable_nameref_context __P((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **));
+static SHELL_VAR *find_variable_last_nameref_context __P((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **));
+
+static SHELL_VAR *bind_tempenv_variable __P((const char *, char *));
+static void push_temp_var __P((PTR_T));
+static void propagate_temp_var __P((PTR_T));
+static void dispose_temporary_env __P((sh_free_func_t *));
+
+static inline char *mk_env_string __P((const char *, const char *));
+static char **make_env_array_from_var_list __P((SHELL_VAR **));
+static char **make_var_export_array __P((VAR_CONTEXT *));
+static char **make_func_export_array __P((void));
+static void add_temp_array_to_env __P((char **, int, int));
+
+static int n_shell_variables __P((void));
+static int set_context __P((SHELL_VAR *));
+
+static void push_func_var __P((PTR_T));
+static void push_exported_var __P((PTR_T));
+
+static inline int find_special_var __P((const char *));
+
+static void
+create_variable_tables ()
+{
+ if (shell_variables == 0)
+ {
+ shell_variables = global_variables = new_var_context ((char *)NULL, 0);
+ shell_variables->scope = 0;
+ shell_variables->table = hash_create (0);
+ }
+
+ if (shell_functions == 0)
+ shell_functions = hash_create (0);
+
+#if defined (DEBUGGER)
+ if (shell_function_defs == 0)
+ shell_function_defs = hash_create (0);
+#endif
+}
+
+/* Initialize the shell variables from the current environment.
+ If PRIVMODE is nonzero, don't import functions from ENV or
+ parse $SHELLOPTS. */
+void
+initialize_shell_variables (env, privmode)
+ char **env;
+ int privmode;
+{
+ char *name, *string, *temp_string;
+ int c, char_index, string_index, string_length, ro;
+ SHELL_VAR *temp_var;
+
+ create_variable_tables ();
+
+ for (string_index = 0; string = env[string_index++]; )
+ {
+ char_index = 0;
+ name = string;
+ while ((c = *string++) && c != '=')
+ ;
+ if (string[-1] == '=')
+ char_index = string - name - 1;
+
+ /* If there are weird things in the environment, like `=xxx' or a
+ string without an `=', just skip them. */
+ if (char_index == 0)
+ continue;
+
+ /* ASSERT(name[char_index] == '=') */
+ name[char_index] = '\0';
+ /* Now, name = env variable name, string = env variable value, and
+ char_index == strlen (name) */
+
+ temp_var = (SHELL_VAR *)NULL;
+
+ /* If exported function, define it now. Don't import functions from
+ the environment in privileged mode. */
+ if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))
+ {
+ string_length = strlen (string);
+ temp_string = (char *)xmalloc (3 + string_length + char_index);
+
+ strcpy (temp_string, name);
+ temp_string[char_index] = ' ';
+ strcpy (temp_string + char_index + 1, string);
+
+ parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST);
+
+ /* Ancient backwards compatibility. Old versions of bash exported
+ functions like name()=() {...} */
+ if (name[char_index - 1] == ')' && name[char_index - 2] == '(')
+ name[char_index - 2] = '\0';
+
+ if (temp_var = find_function (name))
+ {
+ VSETATTR (temp_var, (att_exported|att_imported));
+ array_needs_making = 1;
+ }
+ else
+ {
+ last_command_exit_value = 1;
+ report_error (_("error importing function definition for `%s'"), name);
+ }
+
+ /* ( */
+ if (name[char_index - 1] == ')' && name[char_index - 2] == '\0')
+ name[char_index - 2] = '('; /* ) */
+ }
+#if defined (ARRAY_VARS)
+# if 0
+ /* Array variables may not yet be exported. */
+ else if (*string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')')
+ {
+ string_length = 1;
+ temp_string = extract_array_assignment_list (string, &string_length);
+ temp_var = assign_array_from_string (name, temp_string);
+ FREE (temp_string);
+ VSETATTR (temp_var, (att_exported | att_imported));
+ array_needs_making = 1;
+ }
+# endif
+#endif
+#if 0
+ else if (legal_identifier (name))
+#else
+ else
+#endif
+ {
+ ro = 0;
+ if (posixly_correct && STREQ (name, "SHELLOPTS"))
+ {
+ temp_var = find_variable ("SHELLOPTS");
+ ro = temp_var && readonly_p (temp_var);
+ if (temp_var)
+ VUNSETATTR (temp_var, att_readonly);
+ }
+ temp_var = bind_variable (name, string, 0);
+ if (temp_var)
+ {
+ if (legal_identifier (name))
+ VSETATTR (temp_var, (att_exported | att_imported));
+ else
+ VSETATTR (temp_var, (att_exported | att_imported | att_invisible));
+ if (ro)
+ VSETATTR (temp_var, att_readonly);
+ array_needs_making = 1;
+ }
+ }
+
+ name[char_index] = '=';
+ /* temp_var can be NULL if it was an exported function with a syntax
+ error (a different bug, but it still shouldn't dump core). */
+ if (temp_var && function_p (temp_var) == 0) /* XXX not yet */
+ {
+ CACHE_IMPORTSTR (temp_var, name);
+ }
+ }
+
+ set_pwd ();
+
+ /* Set up initial value of $_ */
+ temp_var = set_if_not ("_", dollar_vars[0]);
+
+ /* Remember this pid. */
+ dollar_dollar_pid = getpid ();
+
+ /* Now make our own defaults in case the vars that we think are
+ important are missing. */
+ temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE);
+#if 0
+ set_auto_export (temp_var); /* XXX */
+#endif
+
+ temp_var = set_if_not ("TERM", "dumb");
+#if 0
+ set_auto_export (temp_var); /* XXX */
+#endif
+
+#if defined (__QNX__)
+ /* set node id -- don't import it from the environment */
+ {
+ char node_name[22];
+# if defined (__QNXNTO__)
+ netmgr_ndtostr(ND2S_LOCAL_STR, ND_LOCAL_NODE, node_name, sizeof(node_name));
+# else
+ qnx_nidtostr (getnid (), node_name, sizeof (node_name));
+# endif
+ temp_var = bind_variable ("NODE", node_name, 0);
+ set_auto_export (temp_var);
+ }
+#endif
+
+ /* set up the prompts. */
+ if (interactive_shell)
+ {
+#if defined (PROMPT_STRING_DECODE)
+ set_if_not ("PS1", primary_prompt);
+#else
+ if (current_user.uid == -1)
+ get_current_user_info ();
+ set_if_not ("PS1", current_user.euid == 0 ? "# " : primary_prompt);
+#endif
+ set_if_not ("PS2", secondary_prompt);
+ }
+ set_if_not ("PS4", "+ ");
+
+ /* Don't allow IFS to be imported from the environment. */
+ temp_var = bind_variable ("IFS", " \t\n", 0);
+ setifs (temp_var);
+
+ /* Magic machine types. Pretty convenient. */
+ set_machine_vars ();
+
+ /* Default MAILCHECK for interactive shells. Defer the creation of a
+ default MAILPATH until the startup files are read, because MAIL
+ names a mail file if MAILPATH is not set, and we should provide a
+ default only if neither is set. */
+ if (interactive_shell)
+ {
+ temp_var = set_if_not ("MAILCHECK", posixly_correct ? "600" : "60");
+ VSETATTR (temp_var, att_integer);
+ }
+
+ /* Do some things with shell level. */
+ initialize_shell_level ();
+
+ set_ppid ();
+
+ /* Initialize the `getopts' stuff. */
+ temp_var = bind_variable ("OPTIND", "1", 0);
+ VSETATTR (temp_var, att_integer);
+ getopts_reset (0);
+ bind_variable ("OPTERR", "1", 0);
+ sh_opterr = 1;
+
+ if (login_shell == 1 && posixly_correct == 0)
+ set_home_var ();
+
+ /* Get the full pathname to THIS shell, and set the BASH variable
+ to it. */
+ name = get_bash_name ();
+ temp_var = bind_variable ("BASH", name, 0);
+ free (name);
+
+ /* Make the exported environment variable SHELL be the user's login
+ shell. Note that the `tset' command looks at this variable
+ to determine what style of commands to output; if it ends in "csh",
+ then C-shell commands are output, else Bourne shell commands. */
+ set_shell_var ();
+
+ /* Make a variable called BASH_VERSION which contains the version info. */
+ bind_variable ("BASH_VERSION", shell_version_string (), 0);
+#if defined (ARRAY_VARS)
+ make_vers_array ();
+#endif
+
+ if (command_execution_string)
+ bind_variable ("BASH_EXECUTION_STRING", command_execution_string, 0);
+
+ /* Find out if we're supposed to be in Posix.2 mode via an
+ environment variable. */
+ temp_var = find_variable ("POSIXLY_CORRECT");
+ if (!temp_var)
+ temp_var = find_variable ("POSIX_PEDANTIC");
+ if (temp_var && imported_p (temp_var))
+ sv_strict_posix (temp_var->name);
+
+#if defined (HISTORY)
+ /* Set history variables to defaults, and then do whatever we would
+ do if the variable had just been set. Do this only in the case
+ that we are remembering commands on the history list. */
+ if (remember_on_history)
+ {
+ name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history", 0);
+
+ set_if_not ("HISTFILE", name);
+ free (name);
+ }
+#endif /* HISTORY */
+
+ /* Seed the random number generator. */
+ seedrand ();
+
+ /* Handle some "special" variables that we may have inherited from a
+ parent shell. */
+ if (interactive_shell)
+ {
+ temp_var = find_variable ("IGNOREEOF");
+ if (!temp_var)
+ temp_var = find_variable ("ignoreeof");
+ if (temp_var && imported_p (temp_var))
+ sv_ignoreeof (temp_var->name);
+ }
+
+#if defined (HISTORY)
+ if (interactive_shell && remember_on_history)
+ {
+ sv_history_control ("HISTCONTROL");
+ sv_histignore ("HISTIGNORE");
+ sv_histtimefmt ("HISTTIMEFORMAT");
+ }
+#endif /* HISTORY */
+
+#if defined (READLINE) && defined (STRICT_POSIX)
+ /* POSIXLY_CORRECT will only be 1 here if the shell was compiled
+ -DSTRICT_POSIX */
+ if (interactive_shell && posixly_correct && no_line_editing == 0)
+ rl_prefer_env_winsize = 1;
+#endif /* READLINE && STRICT_POSIX */
+
+ /*
+ * 24 October 2001
+ *
+ * I'm tired of the arguing and bug reports. Bash now leaves SSH_CLIENT
+ * and SSH2_CLIENT alone. I'm going to rely on the shell_level check in
+ * isnetconn() to avoid running the startup files more often than wanted.
+ * That will, of course, only work if the user's login shell is bash, so
+ * I've made that behavior conditional on SSH_SOURCE_BASHRC being defined
+ * in config-top.h.
+ */
+#if 0
+ temp_var = find_variable ("SSH_CLIENT");
+ if (temp_var && imported_p (temp_var))
+ {
+ VUNSETATTR (temp_var, att_exported);
+ array_needs_making = 1;
+ }
+ temp_var = find_variable ("SSH2_CLIENT");
+ if (temp_var && imported_p (temp_var))
+ {
+ VUNSETATTR (temp_var, att_exported);
+ array_needs_making = 1;
+ }
+#endif
+
+ /* Get the user's real and effective user ids. */
+ uidset ();
+
+ temp_var = find_variable ("BASH_XTRACEFD");
+ if (temp_var && imported_p (temp_var))
+ sv_xtracefd (temp_var->name);
+
+ /* Initialize the dynamic variables, and seed their values. */
+ initialize_dynamic_variables ();
+}
+
+/* **************************************************************** */
+/* */
+/* Setting values for special shell variables */
+/* */
+/* **************************************************************** */
+
+static void
+set_machine_vars ()
+{
+ SHELL_VAR *temp_var;
+
+ temp_var = set_if_not ("HOSTTYPE", HOSTTYPE);
+ temp_var = set_if_not ("OSTYPE", OSTYPE);
+ temp_var = set_if_not ("MACHTYPE", MACHTYPE);
+
+ temp_var = set_if_not ("HOSTNAME", current_host_name);
+}
+
+/* Set $HOME to the information in the password file if we didn't get
+ it from the environment. */
+
+/* This function is not static so the tilde and readline libraries can
+ use it. */
+char *
+sh_get_home_dir ()
+{
+ if (current_user.home_dir == 0)
+ get_current_user_info ();
+ return current_user.home_dir;
+}
+
+static void
+set_home_var ()
+{
+ SHELL_VAR *temp_var;
+
+ temp_var = find_variable ("HOME");
+ if (temp_var == 0)
+ temp_var = bind_variable ("HOME", sh_get_home_dir (), 0);
+#if 0
+ VSETATTR (temp_var, att_exported);
+#endif
+}
+
+/* Set $SHELL to the user's login shell if it is not already set. Call
+ get_current_user_info if we haven't already fetched the shell. */
+static void
+set_shell_var ()
+{
+ SHELL_VAR *temp_var;
+
+ temp_var = find_variable ("SHELL");
+ if (temp_var == 0)
+ {
+ if (current_user.shell == 0)
+ get_current_user_info ();
+ temp_var = bind_variable ("SHELL", current_user.shell, 0);
+ }
+#if 0
+ VSETATTR (temp_var, att_exported);
+#endif
+}
+
+static char *
+get_bash_name ()
+{
+ char *name;
+
+ if ((login_shell == 1) && RELPATH(shell_name))
+ {
+ if (current_user.shell == 0)
+ get_current_user_info ();
+ name = savestring (current_user.shell);
+ }
+ else if (ABSPATH(shell_name))
+ name = savestring (shell_name);
+ else if (shell_name[0] == '.' && shell_name[1] == '/')
+ {
+ /* Fast path for common case. */
+ char *cdir;
+ int len;
+
+ cdir = get_string_value ("PWD");
+ if (cdir)
+ {
+ len = strlen (cdir);
+ name = (char *)xmalloc (len + strlen (shell_name) + 1);
+ strcpy (name, cdir);
+ strcpy (name + len, shell_name + 1);
+ }
+ else
+ name = savestring (shell_name);
+ }
+ else
+ {
+ char *tname;
+ int s;
+
+ tname = find_user_command (shell_name);
+
+ if (tname == 0)
+ {
+ /* Try the current directory. If there is not an executable
+ there, just punt and use the login shell. */
+ s = file_status (shell_name);
+ if (s & FS_EXECABLE)
+ {
+ tname = make_absolute (shell_name, get_string_value ("PWD"));
+ if (*shell_name == '.')
+ {
+ name = sh_canonpath (tname, PATH_CHECKDOTDOT|PATH_CHECKEXISTS);
+ if (name == 0)
+ name = tname;
+ else
+ free (tname);
+ }
+ else
+ name = tname;
+ }
+ else
+ {
+ if (current_user.shell == 0)
+ get_current_user_info ();
+ name = savestring (current_user.shell);
+ }
+ }
+ else
+ {
+ name = full_pathname (tname);
+ free (tname);
+ }
+ }
+
+ return (name);
+}
+
+void
+adjust_shell_level (change)
+ int change;
+{
+ char new_level[5], *old_SHLVL;
+ intmax_t old_level;
+ SHELL_VAR *temp_var;
+
+ old_SHLVL = get_string_value ("SHLVL");
+ if (old_SHLVL == 0 || *old_SHLVL == '\0' || legal_number (old_SHLVL, &old_level) == 0)
+ old_level = 0;
+
+ shell_level = old_level + change;
+ if (shell_level < 0)
+ shell_level = 0;
+ else if (shell_level > 1000)
+ {
+ internal_warning (_("shell level (%d) too high, resetting to 1"), shell_level);
+ shell_level = 1;
+ }
+
+ /* We don't need the full generality of itos here. */
+ if (shell_level < 10)
+ {
+ new_level[0] = shell_level + '0';
+ new_level[1] = '\0';
+ }
+ else if (shell_level < 100)
+ {
+ new_level[0] = (shell_level / 10) + '0';
+ new_level[1] = (shell_level % 10) + '0';
+ new_level[2] = '\0';
+ }
+ else if (shell_level < 1000)
+ {
+ new_level[0] = (shell_level / 100) + '0';
+ old_level = shell_level % 100;
+ new_level[1] = (old_level / 10) + '0';
+ new_level[2] = (old_level % 10) + '0';
+ new_level[3] = '\0';
+ }
+
+ temp_var = bind_variable ("SHLVL", new_level, 0);
+ set_auto_export (temp_var);
+}
+
+static void
+initialize_shell_level ()
+{
+ adjust_shell_level (1);
+}
+
+/* If we got PWD from the environment, update our idea of the current
+ working directory. In any case, make sure that PWD exists before
+ checking it. It is possible for getcwd () to fail on shell startup,
+ and in that case, PWD would be undefined. If this is an interactive
+ login shell, see if $HOME is the current working directory, and if
+ that's not the same string as $PWD, set PWD=$HOME. */
+
+void
+set_pwd ()
+{
+ SHELL_VAR *temp_var, *home_var;
+ char *temp_string, *home_string;
+
+ home_var = find_variable ("HOME");
+ home_string = home_var ? value_cell (home_var) : (char *)NULL;
+
+ temp_var = find_variable ("PWD");
+ if (temp_var && imported_p (temp_var) &&
+ (temp_string = value_cell (temp_var)) &&
+ same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL))
+ set_working_directory (temp_string);
+ else if (home_string && interactive_shell && login_shell &&
+ same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL))
+ {
+ set_working_directory (home_string);
+ temp_var = bind_variable ("PWD", home_string, 0);
+ set_auto_export (temp_var);
+ }
+ else
+ {
+ temp_string = get_working_directory ("shell-init");
+ if (temp_string)
+ {
+ temp_var = bind_variable ("PWD", temp_string, 0);
+ set_auto_export (temp_var);
+ free (temp_string);
+ }
+ }
+
+ /* According to the Single Unix Specification, v2, $OLDPWD is an
+ `environment variable' and therefore should be auto-exported.
+ Make a dummy invisible variable for OLDPWD, and mark it as exported. */
+ temp_var = bind_variable ("OLDPWD", (char *)NULL, 0);
+ VSETATTR (temp_var, (att_exported | att_invisible));
+}
+
+/* Make a variable $PPID, which holds the pid of the shell's parent. */
+void
+set_ppid ()
+{
+ char namebuf[INT_STRLEN_BOUND(pid_t) + 1], *name;
+ SHELL_VAR *temp_var;
+
+ name = inttostr (getppid (), namebuf, sizeof(namebuf));
+ temp_var = find_variable ("PPID");
+ if (temp_var)
+ VUNSETATTR (temp_var, (att_readonly | att_exported));
+ temp_var = bind_variable ("PPID", name, 0);
+ VSETATTR (temp_var, (att_readonly | att_integer));
+}
+
+static void
+uidset ()
+{
+ char buff[INT_STRLEN_BOUND(uid_t) + 1], *b;
+ register SHELL_VAR *v;
+
+ b = inttostr (current_user.uid, buff, sizeof (buff));
+ v = find_variable ("UID");
+ if (v == 0)
+ {
+ v = bind_variable ("UID", b, 0);
+ VSETATTR (v, (att_readonly | att_integer));
+ }
+
+ if (current_user.euid != current_user.uid)
+ b = inttostr (current_user.euid, buff, sizeof (buff));
+
+ v = find_variable ("EUID");
+ if (v == 0)
+ {
+ v = bind_variable ("EUID", b, 0);
+ VSETATTR (v, (att_readonly | att_integer));
+ }
+}
+
+#if defined (ARRAY_VARS)
+static void
+make_vers_array ()
+{
+ SHELL_VAR *vv;
+ ARRAY *av;
+ char *s, d[32], b[INT_STRLEN_BOUND(int) + 1];
+
+ unbind_variable ("BASH_VERSINFO");
+
+ vv = make_new_array_variable ("BASH_VERSINFO");
+ av = array_cell (vv);
+ strcpy (d, dist_version);
+ s = strchr (d, '.');
+ if (s)
+ *s++ = '\0';
+ array_insert (av, 0, d);
+ array_insert (av, 1, s);
+ s = inttostr (patch_level, b, sizeof (b));
+ array_insert (av, 2, s);
+ s = inttostr (build_version, b, sizeof (b));
+ array_insert (av, 3, s);
+ array_insert (av, 4, release_status);
+ array_insert (av, 5, MACHTYPE);
+
+ VSETATTR (vv, att_readonly);
+}
+#endif /* ARRAY_VARS */
+
+/* Set the environment variables $LINES and $COLUMNS in response to
+ a window size change. */
+void
+sh_set_lines_and_columns (lines, cols)
+ int lines, cols;
+{
+ char val[INT_STRLEN_BOUND(int) + 1], *v;
+
+#if defined (READLINE)
+ /* If we are currently assigning to LINES or COLUMNS, don't do anything. */
+ if (winsize_assignment)
+ return;
+#endif
+
+ v = inttostr (lines, val, sizeof (val));
+ bind_variable ("LINES", v, 0);
+
+ v = inttostr (cols, val, sizeof (val));
+ bind_variable ("COLUMNS", v, 0);
+}
+
+/* **************************************************************** */
+/* */
+/* Printing variables and values */
+/* */
+/* **************************************************************** */
+
+/* Print LIST (a list of shell variables) to stdout in such a way that
+ they can be read back in. */
+void
+print_var_list (list)
+ register SHELL_VAR **list;
+{
+ register int i;
+ register SHELL_VAR *var;
+
+ for (i = 0; list && (var = list[i]); i++)
+ if (invisible_p (var) == 0)
+ print_assignment (var);
+}
+
+/* Print LIST (a list of shell functions) to stdout in such a way that
+ they can be read back in. */
+void
+print_func_list (list)
+ register SHELL_VAR **list;
+{
+ register int i;
+ register SHELL_VAR *var;
+
+ for (i = 0; list && (var = list[i]); i++)
+ {
+ printf ("%s ", var->name);
+ print_var_function (var);
+ printf ("\n");
+ }
+}
+
+/* Print the value of a single SHELL_VAR. No newline is
+ output, but the variable is printed in such a way that
+ it can be read back in. */
+void
+print_assignment (var)
+ SHELL_VAR *var;
+{
+ if (var_isset (var) == 0)
+ return;
+
+ if (function_p (var))
+ {
+ printf ("%s", var->name);
+ print_var_function (var);
+ printf ("\n");
+ }
+#if defined (ARRAY_VARS)
+ else if (array_p (var))
+ print_array_assignment (var, 0);
+ else if (assoc_p (var))
+ print_assoc_assignment (var, 0);
+#endif /* ARRAY_VARS */
+ else
+ {
+ printf ("%s=", var->name);
+ print_var_value (var, 1);
+ printf ("\n");
+ }
+}
+
+/* Print the value cell of VAR, a shell variable. Do not print
+ the name, nor leading/trailing newline. If QUOTE is non-zero,
+ and the value contains shell metacharacters, quote the value
+ in such a way that it can be read back in. */
+void
+print_var_value (var, quote)
+ SHELL_VAR *var;
+ int quote;
+{
+ char *t;
+
+ if (var_isset (var) == 0)
+ return;
+
+ if (quote && posixly_correct == 0 && ansic_shouldquote (value_cell (var)))
+ {
+ t = ansic_quote (value_cell (var), 0, (int *)0);
+ printf ("%s", t);
+ free (t);
+ }
+ else if (quote && sh_contains_shell_metas (value_cell (var)))
+ {
+ t = sh_single_quote (value_cell (var));
+ printf ("%s", t);
+ free (t);
+ }
+ else
+ printf ("%s", value_cell (var));
+}
+
+/* Print the function cell of VAR, a shell variable. Do not
+ print the name, nor leading/trailing newline. */
+void
+print_var_function (var)
+ SHELL_VAR *var;
+{
+ char *x;
+
+ if (function_p (var) && var_isset (var))
+ {
+ x = named_function_string ((char *)NULL, function_cell(var), FUNC_MULTILINE|FUNC_EXTERNAL);
+ printf ("%s", x);
+ }
+}
+
+/* **************************************************************** */
+/* */
+/* Dynamic Variables */
+/* */
+/* **************************************************************** */
+
+/* DYNAMIC VARIABLES
+
+ These are variables whose values are generated anew each time they are
+ referenced. These are implemented using a pair of function pointers
+ in the struct variable: assign_func, which is called from bind_variable
+ and, if arrays are compiled into the shell, some of the functions in
+ arrayfunc.c, and dynamic_value, which is called from find_variable.
+
+ assign_func is called from bind_variable_internal, if
+ bind_variable_internal discovers that the variable being assigned to
+ has such a function. The function is called as
+ SHELL_VAR *temp = (*(entry->assign_func)) (entry, value, ind)
+ and the (SHELL_VAR *)temp is returned as the value of bind_variable. It
+ is usually ENTRY (self). IND is an index for an array variable, and
+ unused otherwise.
+
+ dynamic_value is called from find_variable_internal to return a `new'
+ value for the specified dynamic varible. If this function is NULL,
+ the variable is treated as a `normal' shell variable. If it is not,
+ however, then this function is called like this:
+ tempvar = (*(var->dynamic_value)) (var);
+
+ Sometimes `tempvar' will replace the value of `var'. Other times, the
+ shell will simply use the string value. Pretty object-oriented, huh?
+
+ Be warned, though: if you `unset' a special variable, it loses its
+ special meaning, even if you subsequently set it.
+
+ The special assignment code would probably have been better put in
+ subst.c: do_assignment_internal, in the same style as
+ stupidly_hack_special_variables, but I wanted the changes as
+ localized as possible. */
+
+#define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \
+ do \
+ { \
+ v = bind_variable (var, (val), 0); \
+ v->dynamic_value = gfunc; \
+ v->assign_func = afunc; \
+ } \
+ while (0)
+
+#define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \
+ do \
+ { \
+ v = make_new_array_variable (var); \
+ v->dynamic_value = gfunc; \
+ v->assign_func = afunc; \
+ } \
+ while (0)
+
+#define INIT_DYNAMIC_ASSOC_VAR(var, gfunc, afunc) \
+ do \
+ { \
+ v = make_new_assoc_variable (var); \
+ v->dynamic_value = gfunc; \
+ v->assign_func = afunc; \
+ } \
+ while (0)
+
+static SHELL_VAR *
+null_assign (self, value, unused, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t unused;
+ char *key;
+{
+ return (self);
+}
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *
+null_array_assign (self, value, ind, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t ind;
+ char *key;
+{
+ return (self);
+}
+#endif
+
+/* Degenerate `dynamic_value' function; just returns what's passed without
+ manipulation. */
+static SHELL_VAR *
+get_self (self)
+ SHELL_VAR *self;
+{
+ return (self);
+}
+
+#if defined (ARRAY_VARS)
+/* A generic dynamic array variable initializer. Intialize array variable
+ NAME with dynamic value function GETFUNC and assignment function SETFUNC. */
+static SHELL_VAR *
+init_dynamic_array_var (name, getfunc, setfunc, attrs)
+ char *name;
+ sh_var_value_func_t *getfunc;
+ sh_var_assign_func_t *setfunc;
+ int attrs;
+{
+ SHELL_VAR *v;
+
+ v = find_variable (name);
+ if (v)
+ return (v);
+ INIT_DYNAMIC_ARRAY_VAR (name, getfunc, setfunc);
+ if (attrs)
+ VSETATTR (v, attrs);
+ return v;
+}
+
+static SHELL_VAR *
+init_dynamic_assoc_var (name, getfunc, setfunc, attrs)
+ char *name;
+ sh_var_value_func_t *getfunc;
+ sh_var_assign_func_t *setfunc;
+ int attrs;
+{
+ SHELL_VAR *v;
+
+ v = find_variable (name);
+ if (v)
+ return (v);
+ INIT_DYNAMIC_ASSOC_VAR (name, getfunc, setfunc);
+ if (attrs)
+ VSETATTR (v, attrs);
+ return v;
+}
+#endif
+
+/* The value of $SECONDS. This is the number of seconds since shell
+ invocation, or, the number of seconds since the last assignment + the
+ value of the last assignment. */
+static intmax_t seconds_value_assigned;
+
+static SHELL_VAR *
+assign_seconds (self, value, unused, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t unused;
+ char *key;
+{
+ if (legal_number (value, &seconds_value_assigned) == 0)
+ seconds_value_assigned = 0;
+ shell_start_time = NOW;
+ return (self);
+}
+
+static SHELL_VAR *
+get_seconds (var)
+ SHELL_VAR *var;
+{
+ time_t time_since_start;
+ char *p;
+
+ time_since_start = NOW - shell_start_time;
+ p = itos(seconds_value_assigned + time_since_start);
+
+ FREE (value_cell (var));
+
+ VSETATTR (var, att_integer);
+ var_setvalue (var, p);
+ return (var);
+}
+
+static SHELL_VAR *
+init_seconds_var ()
+{
+ SHELL_VAR *v;
+
+ v = find_variable ("SECONDS");
+ if (v)
+ {
+ if (legal_number (value_cell(v), &seconds_value_assigned) == 0)
+ seconds_value_assigned = 0;
+ }
+ INIT_DYNAMIC_VAR ("SECONDS", (v ? value_cell (v) : (char *)NULL), get_seconds, assign_seconds);
+ return v;
+}
+
+/* The random number seed. You can change this by setting RANDOM. */
+static unsigned long rseed = 1;
+static int last_random_value;
+static int seeded_subshell = 0;
+
+/* A linear congruential random number generator based on the example
+ one in the ANSI C standard. This one isn't very good, but a more
+ complicated one is overkill. */
+
+/* Returns a pseudo-random number between 0 and 32767. */
+static int
+brand ()
+{
+ /* From "Random number generators: good ones are hard to find",
+ Park and Miller, Communications of the ACM, vol. 31, no. 10,
+ October 1988, p. 1195. filtered through FreeBSD */
+ long h, l;
+
+ /* Can't seed with 0. */
+ if (rseed == 0)
+ rseed = 123459876;
+ h = rseed / 127773;
+ l = rseed % 127773;
+ rseed = 16807 * l - 2836 * h;
+#if 0
+ if (rseed < 0)
+ rseed += 0x7fffffff;
+#endif
+ return ((unsigned int)(rseed & 32767)); /* was % 32768 */
+}
+
+/* Set the random number generator seed to SEED. */
+static void
+sbrand (seed)
+ unsigned long seed;
+{
+ rseed = seed;
+ last_random_value = 0;
+}
+
+static void
+seedrand ()
+{
+ struct timeval tv;
+
+ gettimeofday (&tv, NULL);
+ sbrand (tv.tv_sec ^ tv.tv_usec ^ getpid ());
+}
+
+static SHELL_VAR *
+assign_random (self, value, unused, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t unused;
+ char *key;
+{
+ sbrand (strtoul (value, (char **)NULL, 10));
+ if (subshell_environment)
+ seeded_subshell = getpid ();
+ return (self);
+}
+
+int
+get_random_number ()
+{
+ int rv, pid;
+
+ /* Reset for command and process substitution. */
+ pid = getpid ();
+ if (subshell_environment && seeded_subshell != pid)
+ {
+ seedrand ();
+ seeded_subshell = pid;
+ }
+
+ do
+ rv = brand ();
+ while (rv == last_random_value);
+ return rv;
+}
+
+static SHELL_VAR *
+get_random (var)
+ SHELL_VAR *var;
+{
+ int rv;
+ char *p;
+
+ rv = get_random_number ();
+ last_random_value = rv;
+ p = itos (rv);
+
+ FREE (value_cell (var));
+
+ VSETATTR (var, att_integer);
+ var_setvalue (var, p);
+ return (var);
+}
+
+static SHELL_VAR *
+assign_lineno (var, value, unused, key)
+ SHELL_VAR *var;
+ char *value;
+ arrayind_t unused;
+ char *key;
+{
+ intmax_t new_value;
+
+ if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0)
+ new_value = 0;
+ line_number = line_number_base = new_value;
+ return var;
+}
+
+/* Function which returns the current line number. */
+static SHELL_VAR *
+get_lineno (var)
+ SHELL_VAR *var;
+{
+ char *p;
+ int ln;
+
+ ln = executing_line_number ();
+ p = itos (ln);
+ FREE (value_cell (var));
+ var_setvalue (var, p);
+ return (var);
+}
+
+static SHELL_VAR *
+assign_subshell (var, value, unused, key)
+ SHELL_VAR *var;
+ char *value;
+ arrayind_t unused;
+ char *key;
+{
+ intmax_t new_value;
+
+ if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0)
+ new_value = 0;
+ subshell_level = new_value;
+ return var;
+}
+
+static SHELL_VAR *
+get_subshell (var)
+ SHELL_VAR *var;
+{
+ char *p;
+
+ p = itos (subshell_level);
+ FREE (value_cell (var));
+ var_setvalue (var, p);
+ return (var);
+}
+
+static SHELL_VAR *
+get_bashpid (var)
+ SHELL_VAR *var;
+{
+ int pid;
+ char *p;
+
+ pid = getpid ();
+ p = itos (pid);
+
+ FREE (value_cell (var));
+ VSETATTR (var, att_integer|att_readonly);
+ var_setvalue (var, p);
+ return (var);
+}
+
+static SHELL_VAR *
+get_bash_command (var)
+ SHELL_VAR *var;
+{
+ char *p;
+
+ if (the_printed_command_except_trap)
+ p = savestring (the_printed_command_except_trap);
+ else
+ {
+ p = (char *)xmalloc (1);
+ p[0] = '\0';
+ }
+ FREE (value_cell (var));
+ var_setvalue (var, p);
+ return (var);
+}
+
+#if defined (HISTORY)
+static SHELL_VAR *
+get_histcmd (var)
+ SHELL_VAR *var;
+{
+ char *p;
+
+ p = itos (history_number ());
+ FREE (value_cell (var));
+ var_setvalue (var, p);
+ return (var);
+}
+#endif
+
+#if defined (READLINE)
+/* When this function returns, VAR->value points to malloced memory. */
+static SHELL_VAR *
+get_comp_wordbreaks (var)
+ SHELL_VAR *var;
+{
+ /* If we don't have anything yet, assign a default value. */
+ if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0)
+ enable_hostname_completion (perform_hostname_completion);
+
+ FREE (value_cell (var));
+ var_setvalue (var, savestring (rl_completer_word_break_characters));
+
+ return (var);
+}
+
+/* When this function returns, rl_completer_word_break_characters points to
+ malloced memory. */
+static SHELL_VAR *
+assign_comp_wordbreaks (self, value, unused, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t unused;
+ char *key;
+{
+ if (rl_completer_word_break_characters &&
+ rl_completer_word_break_characters != rl_basic_word_break_characters)
+ free (rl_completer_word_break_characters);
+
+ rl_completer_word_break_characters = savestring (value);
+ return self;
+}
+#endif /* READLINE */
+
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+static SHELL_VAR *
+assign_dirstack (self, value, ind, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t ind;
+ char *key;
+{
+ set_dirstack_element (ind, 1, value);
+ return self;
+}
+
+static SHELL_VAR *
+get_dirstack (self)
+ SHELL_VAR *self;
+{
+ ARRAY *a;
+ WORD_LIST *l;
+
+ l = get_directory_stack (0);
+ a = array_from_word_list (l);
+ array_dispose (array_cell (self));
+ dispose_words (l);
+ var_setarray (self, a);
+ return self;
+}
+#endif /* PUSHD AND POPD && ARRAY_VARS */
+
+#if defined (ARRAY_VARS)
+/* We don't want to initialize the group set with a call to getgroups()
+ unless we're asked to, but we only want to do it once. */
+static SHELL_VAR *
+get_groupset (self)
+ SHELL_VAR *self;
+{
+ register int i;
+ int ng;
+ ARRAY *a;
+ static char **group_set = (char **)NULL;
+
+ if (group_set == 0)
+ {
+ group_set = get_group_list (&ng);
+ a = array_cell (self);
+ for (i = 0; i < ng; i++)
+ array_insert (a, i, group_set[i]);
+ }
+ return (self);
+}
+
+static SHELL_VAR *
+build_hashcmd (self)
+ SHELL_VAR *self;
+{
+ HASH_TABLE *h;
+ int i;
+ char *k, *v;
+ BUCKET_CONTENTS *item;
+
+ h = assoc_cell (self);
+ if (h)
+ assoc_dispose (h);
+
+ if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0)
+ {
+ var_setvalue (self, (char *)NULL);
+ return self;
+ }
+
+ h = assoc_create (hashed_filenames->nbuckets);
+ for (i = 0; i < hashed_filenames->nbuckets; i++)
+ {
+ for (item = hash_items (i, hashed_filenames); item; item = item->next)
+ {
+ k = savestring (item->key);
+ v = pathdata(item)->path;
+ assoc_insert (h, k, v);
+ }
+ }
+
+ var_setvalue (self, (char *)h);
+ return self;
+}
+
+static SHELL_VAR *
+get_hashcmd (self)
+ SHELL_VAR *self;
+{
+ build_hashcmd (self);
+ return (self);
+}
+
+static SHELL_VAR *
+assign_hashcmd (self, value, ind, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t ind;
+ char *key;
+{
+ phash_insert (key, value, 0, 0);
+ return (build_hashcmd (self));
+}
+
+#if defined (ALIAS)
+static SHELL_VAR *
+build_aliasvar (self)
+ SHELL_VAR *self;
+{
+ HASH_TABLE *h;
+ int i;
+ char *k, *v;
+ BUCKET_CONTENTS *item;
+
+ h = assoc_cell (self);
+ if (h)
+ assoc_dispose (h);
+
+ if (aliases == 0 || HASH_ENTRIES (aliases) == 0)
+ {
+ var_setvalue (self, (char *)NULL);
+ return self;
+ }
+
+ h = assoc_create (aliases->nbuckets);
+ for (i = 0; i < aliases->nbuckets; i++)
+ {
+ for (item = hash_items (i, aliases); item; item = item->next)
+ {
+ k = savestring (item->key);
+ v = ((alias_t *)(item->data))->value;
+ assoc_insert (h, k, v);
+ }
+ }
+
+ var_setvalue (self, (char *)h);
+ return self;
+}
+
+static SHELL_VAR *
+get_aliasvar (self)
+ SHELL_VAR *self;
+{
+ build_aliasvar (self);
+ return (self);
+}
+
+static SHELL_VAR *
+assign_aliasvar (self, value, ind, key)
+ SHELL_VAR *self;
+ char *value;
+ arrayind_t ind;
+ char *key;
+{
+ add_alias (key, value);
+ return (build_aliasvar (self));
+}
+#endif /* ALIAS */
+
+#endif /* ARRAY_VARS */
+
+/* If ARRAY_VARS is not defined, this just returns the name of any
+ currently-executing function. If we have arrays, it's a call stack. */
+static SHELL_VAR *
+get_funcname (self)
+ SHELL_VAR *self;
+{
+#if ! defined (ARRAY_VARS)
+ char *t;
+ if (variable_context && this_shell_function)
+ {
+ FREE (value_cell (self));
+ t = savestring (this_shell_function->name);
+ var_setvalue (self, t);
+ }
+#endif
+ return (self);
+}
+
+void
+make_funcname_visible (on_or_off)
+ int on_or_off;
+{
+ SHELL_VAR *v;
+
+ v = find_variable ("FUNCNAME");
+ if (v == 0 || v->dynamic_value == 0)
+ return;
+
+ if (on_or_off)
+ VUNSETATTR (v, att_invisible);
+ else
+ VSETATTR (v, att_invisible);
+}
+
+static SHELL_VAR *
+init_funcname_var ()
+{
+ SHELL_VAR *v;
+
+ v = find_variable ("FUNCNAME");
+ if (v)
+ return v;
+#if defined (ARRAY_VARS)
+ INIT_DYNAMIC_ARRAY_VAR ("FUNCNAME", get_funcname, null_array_assign);
+#else
+ INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign);
+#endif
+ VSETATTR (v, att_invisible|att_noassign);
+ return v;
+}
+
+static void
+initialize_dynamic_variables ()
+{
+ SHELL_VAR *v;
+
+ v = init_seconds_var ();
+
+ INIT_DYNAMIC_VAR ("BASH_COMMAND", (char *)NULL, get_bash_command, (sh_var_assign_func_t *)NULL);
+ INIT_DYNAMIC_VAR ("BASH_SUBSHELL", (char *)NULL, get_subshell, assign_subshell);
+
+ INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random);
+ VSETATTR (v, att_integer);
+ INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno);
+ VSETATTR (v, att_integer);
+
+ INIT_DYNAMIC_VAR ("BASHPID", (char *)NULL, get_bashpid, null_assign);
+ VSETATTR (v, att_integer|att_readonly);
+
+#if defined (HISTORY)
+ INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL);
+ VSETATTR (v, att_integer);
+#endif
+
+#if defined (READLINE)
+ INIT_DYNAMIC_VAR ("COMP_WORDBREAKS", (char *)NULL, get_comp_wordbreaks, assign_comp_wordbreaks);
+#endif
+
+#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS)
+ v = init_dynamic_array_var ("DIRSTACK", get_dirstack, assign_dirstack, 0);
+#endif /* PUSHD_AND_POPD && ARRAY_VARS */
+
+#if defined (ARRAY_VARS)
+ v = init_dynamic_array_var ("GROUPS", get_groupset, null_array_assign, att_noassign);
+
+# if defined (DEBUGGER)
+ v = init_dynamic_array_var ("BASH_ARGC", get_self, null_array_assign, att_noassign|att_nounset);
+ v = init_dynamic_array_var ("BASH_ARGV", get_self, null_array_assign, att_noassign|att_nounset);
+# endif /* DEBUGGER */
+ v = init_dynamic_array_var ("BASH_SOURCE", get_self, null_array_assign, att_noassign|att_nounset);
+ v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, att_noassign|att_nounset);
+
+ v = init_dynamic_assoc_var ("BASH_CMDS", get_hashcmd, assign_hashcmd, att_nofree);
+# if defined (ALIAS)
+ v = init_dynamic_assoc_var ("BASH_ALIASES", get_aliasvar, assign_aliasvar, att_nofree);
+# endif
+#endif
+
+ v = init_funcname_var ();
+}
+
+/* **************************************************************** */
+/* */
+/* Retrieving variables and values */
+/* */
+/* **************************************************************** */
+
+/* How to get a pointer to the shell variable or function named NAME.
+ HASHED_VARS is a pointer to the hash table containing the list
+ of interest (either variables or functions). */
+
+static SHELL_VAR *
+hash_lookup (name, hashed_vars)
+ const char *name;
+ HASH_TABLE *hashed_vars;
+{
+ BUCKET_CONTENTS *bucket;
+
+ bucket = hash_search (name, hashed_vars, 0);
+ return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL);
+}
+
+SHELL_VAR *
+var_lookup (name, vcontext)
+ const char *name;
+ VAR_CONTEXT *vcontext;
+{
+ VAR_CONTEXT *vc;
+ SHELL_VAR *v;
+
+ v = (SHELL_VAR *)NULL;
+ for (vc = vcontext; vc; vc = vc->down)
+ if (v = hash_lookup (name, vc->table))
+ break;
+
+ return v;
+}
+
+/* Look up the variable entry named NAME. If SEARCH_TEMPENV is non-zero,
+ then also search the temporarily built list of exported variables.
+ The lookup order is:
+ temporary_env
+ shell_variables list
+*/
+
+SHELL_VAR *
+find_variable_internal (name, force_tempenv)
+ const char *name;
+ int force_tempenv;
+{
+ SHELL_VAR *var;
+ int search_tempenv;
+
+ var = (SHELL_VAR *)NULL;
+
+ /* If explicitly requested, first look in the temporary environment for
+ the variable. This allows constructs such as "foo=x eval 'echo $foo'"
+ to get the `exported' value of $foo. This happens if we are executing
+ a function or builtin, or if we are looking up a variable in a
+ "subshell environment". */
+ search_tempenv = force_tempenv || (expanding_redir == 0 && subshell_environment);
+
+ if (search_tempenv && temporary_env)
+ var = hash_lookup (name, temporary_env);
+
+ if (var == 0)
+ var = var_lookup (name, shell_variables);
+
+ if (var == 0)
+ return ((SHELL_VAR *)NULL);
+
+ return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
+}
+
+/* Look up and resolve the chain of nameref variables starting at V all the
+ way to NULL or non-nameref. */
+SHELL_VAR *
+find_variable_nameref (v)
+ SHELL_VAR *v;
+{
+ int level;
+ char *newname;
+
+ level = 0;
+ while (v && nameref_p (v))
+ {
+ level++;
+ if (level > NAMEREF_MAX)
+ return ((SHELL_VAR *)0); /* error message here? */
+ newname = nameref_cell (v);
+ if (newname == 0 || *newname == '\0')
+ return ((SHELL_VAR *)0);
+ v = find_variable_internal (newname, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+ }
+ return v;
+}
+
+/* Resolve the chain of nameref variables for NAME. XXX - could change later */
+SHELL_VAR *
+find_variable_last_nameref (name)
+ const char *name;
+{
+ SHELL_VAR *v, *nv;
+ char *newname;
+ int level;
+
+ nv = v = find_variable_noref (name);
+ level = 0;
+ while (v && nameref_p (v))
+ {
+ level++;
+ if (level > NAMEREF_MAX)
+ return ((SHELL_VAR *)0); /* error message here? */
+ newname = nameref_cell (v);
+ if (newname == 0 || *newname == '\0')
+ return ((SHELL_VAR *)0);
+ nv = v;
+ v = find_variable_internal (newname, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+ }
+ return nv;
+}
+
+/* Resolve the chain of nameref variables for NAME. XXX - could change later */
+SHELL_VAR *
+find_global_variable_last_nameref (name)
+ const char *name;
+{
+ SHELL_VAR *v, *nv;
+ char *newname;
+ int level;
+
+ nv = v = find_global_variable_noref (name);
+ level = 0;
+ while (v && nameref_p (v))
+ {
+ level++;
+ if (level > NAMEREF_MAX)
+ return ((SHELL_VAR *)0); /* error message here? */
+ newname = nameref_cell (v);
+ if (newname == 0 || *newname == '\0')
+ return ((SHELL_VAR *)0);
+ nv = v;
+ v = find_global_variable_noref (newname);
+ }
+ return nv;
+}
+
+static SHELL_VAR *
+find_nameref_at_context (v, vc)
+ SHELL_VAR *v;
+ VAR_CONTEXT *vc;
+{
+ SHELL_VAR *nv, *nv2;
+ VAR_CONTEXT *nvc;
+ char *newname;
+ int level;
+
+ nv = v;
+ level = 1;
+ while (nv && nameref_p (nv))
+ {
+ level++;
+ if (level > NAMEREF_MAX)
+ return ((SHELL_VAR *)NULL);
+ newname = nameref_cell (nv);
+ if (newname == 0 || *newname == '\0')
+ return ((SHELL_VAR *)NULL);
+ nv2 = hash_lookup (newname, vc->table);
+ if (nv2 == 0)
+ break;
+ nv = nv2;
+ }
+ return nv;
+}
+
+/* Do nameref resolution from the VC, which is the local context for some
+ function or builtin, `up' the chain to the global variables context. If
+ NVCP is not NULL, return the variable context where we finally ended the
+ nameref resolution (so the bind_variable_internal can use the correct
+ variable context and hash table). */
+static SHELL_VAR *
+find_variable_nameref_context (v, vc, nvcp)
+ SHELL_VAR *v;
+ VAR_CONTEXT *vc;
+ VAR_CONTEXT **nvcp;
+{
+ SHELL_VAR *nv, *nv2;
+ VAR_CONTEXT *nvc;
+
+ /* Look starting at the current context all the way `up' */
+ for (nv = v, nvc = vc; nvc; nvc = nvc->down)
+ {
+ nv2 = find_nameref_at_context (nv, nvc);
+ if (nv2 == 0)
+ continue;
+ nv = nv2;
+ if (*nvcp)
+ *nvcp = nvc;
+ }
+ return (nameref_p (nv) ? (SHELL_VAR *)NULL : nv);
+}
+
+/* Do nameref resolution from the VC, which is the local context for some
+ function or builtin, `up' the chain to the global variables context. If
+ NVCP is not NULL, return the variable context where we finally ended the
+ nameref resolution (so the bind_variable_internal can use the correct
+ variable context and hash table). */
+static SHELL_VAR *
+find_variable_last_nameref_context (v, vc, nvcp)
+ SHELL_VAR *v;
+ VAR_CONTEXT *vc;
+ VAR_CONTEXT **nvcp;
+{
+ SHELL_VAR *nv, *nv2;
+ VAR_CONTEXT *nvc;
+
+ /* Look starting at the current context all the way `up' */
+ for (nv = v, nvc = vc; nvc; nvc = nvc->down)
+ {
+ nv2 = find_nameref_at_context (nv, nvc);
+ if (nv2 == 0)
+ continue;
+ nv = nv2;
+ if (*nvcp)
+ *nvcp = nvc;
+ }
+ return (nameref_p (nv) ? nv : (SHELL_VAR *)NULL);
+}
+
+/* Find a variable, forcing a search of the temporary environment first */
+SHELL_VAR *
+find_variable_tempenv (name)
+ const char *name;
+{
+ SHELL_VAR *var;
+
+ var = find_variable_internal (name, 1);
+ if (var && nameref_p (var))
+ var = find_variable_nameref (var);
+ return (var);
+}
+
+/* Find a variable, not forcing a search of the temporary environment first */
+SHELL_VAR *
+find_variable_notempenv (name)
+ const char *name;
+{
+ SHELL_VAR *var;
+
+ var = find_variable_internal (name, 0);
+ if (var && nameref_p (var))
+ var = find_variable_nameref (var);
+ return (var);
+}
+
+SHELL_VAR *
+find_global_variable (name)
+ const char *name;
+{
+ SHELL_VAR *var;
+
+ var = var_lookup (name, global_variables);
+ if (var && nameref_p (var))
+ var = find_variable_nameref (var);
+
+ if (var == 0)
+ return ((SHELL_VAR *)NULL);
+
+ return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
+}
+
+SHELL_VAR *
+find_global_variable_noref (name)
+ const char *name;
+{
+ SHELL_VAR *var;
+
+ var = var_lookup (name, global_variables);
+
+ if (var == 0)
+ return ((SHELL_VAR *)NULL);
+
+ return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
+}
+
+SHELL_VAR *
+find_shell_variable (name)
+ const char *name;
+{
+ SHELL_VAR *var;
+
+ var = var_lookup (name, shell_variables);
+ if (var && nameref_p (var))
+ var = find_variable_nameref (var);
+
+ if (var == 0)
+ return ((SHELL_VAR *)NULL);
+
+ return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var);
+}
+
+/* Look up the variable entry named NAME. Returns the entry or NULL. */
+SHELL_VAR *
+find_variable (name)
+ const char *name;
+{
+ SHELL_VAR *v;
+
+ v = find_variable_internal (name, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+ if (v && nameref_p (v))
+ v = find_variable_nameref (v);
+ return v;
+}
+
+SHELL_VAR *
+find_variable_noref (name)
+ const char *name;
+{
+ SHELL_VAR *v;
+
+ v = find_variable_internal (name, (expanding_redir == 0 && (assigning_in_environment || executing_builtin)));
+ return v;
+}
+
+/* Look up the function entry whose name matches STRING.
+ Returns the entry or NULL. */
+SHELL_VAR *
+find_function (name)
+ const char *name;
+{
+ return (hash_lookup (name, shell_functions));
+}
+
+/* Find the function definition for the shell function named NAME. Returns
+ the entry or NULL. */
+FUNCTION_DEF *
+find_function_def (name)
+ const char *name;
+{
+#if defined (DEBUGGER)
+ return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs));
+#else
+ return ((FUNCTION_DEF *)0);
+#endif
+}
+
+/* Return the value of VAR. VAR is assumed to have been the result of a
+ lookup without any subscript, if arrays are compiled into the shell. */
+char *
+get_variable_value (var)
+ SHELL_VAR *var;
+{
+ if (var == 0)
+ return ((char *)NULL);
+#if defined (ARRAY_VARS)
+ else if (array_p (var))
+ return (array_reference (array_cell (var), 0));
+ else if (assoc_p (var))
+ return (assoc_reference (assoc_cell (var), "0"));
+#endif
+ else
+ return (value_cell (var));
+}
+
+/* Return the string value of a variable. Return NULL if the variable
+ doesn't exist. Don't cons a new string. This is a potential memory
+ leak if the variable is found in the temporary environment. Since
+ functions and variables have separate name spaces, returns NULL if
+ var_name is a shell function only. */
+char *
+get_string_value (var_name)
+ const char *var_name;
+{
+ SHELL_VAR *var;
+
+ var = find_variable (var_name);
+ return ((var) ? get_variable_value (var) : (char *)NULL);
+}
+
+/* This is present for use by the tilde and readline libraries. */
+char *
+sh_get_env_value (v)
+ const char *v;
+{
+ return get_string_value (v);
+}
+
+/* **************************************************************** */
+/* */
+/* Creating and setting variables */
+/* */
+/* **************************************************************** */
+
+/* Set NAME to VALUE if NAME has no value. */
+SHELL_VAR *
+set_if_not (name, value)
+ char *name, *value;
+{
+ SHELL_VAR *v;
+
+ if (shell_variables == 0)
+ create_variable_tables ();
+
+ v = find_variable (name);
+ if (v == 0)
+ v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH, 0);
+ return (v);
+}
+
+/* Create a local variable referenced by NAME. */
+SHELL_VAR *
+make_local_variable (name)
+ const char *name;
+{
+ SHELL_VAR *new_var, *old_var;
+ VAR_CONTEXT *vc;
+ int was_tmpvar;
+ char *tmp_value;
+
+ /* local foo; local foo; is a no-op. */
+ old_var = find_variable (name);
+ if (old_var && local_p (old_var) && old_var->context == variable_context)
+ {
+ VUNSETATTR (old_var, att_invisible);
+ return (old_var);
+ }
+
+ was_tmpvar = old_var && tempvar_p (old_var);
+ if (was_tmpvar)
+ tmp_value = value_cell (old_var);
+
+ for (vc = shell_variables; vc; vc = vc->down)
+ if (vc_isfuncenv (vc) && vc->scope == variable_context)
+ break;
+
+ if (vc == 0)
+ {
+ internal_error (_("make_local_variable: no function context at current scope"));
+ return ((SHELL_VAR *)NULL);
+ }
+ else if (vc->table == 0)
+ vc->table = hash_create (TEMPENV_HASH_BUCKETS);
+
+ /* Since this is called only from the local/declare/typeset code, we can
+ call builtin_error here without worry (of course, it will also work
+ for anything that sets this_command_name). Variables with the `noassign'
+ attribute may not be made local. The test against old_var's context
+ level is to disallow local copies of readonly global variables (since I
+ believe that this could be a security hole). Readonly copies of calling
+ function local variables are OK. */
+ if (old_var && (noassign_p (old_var) ||
+ (readonly_p (old_var) && old_var->context == 0)))
+ {
+ if (readonly_p (old_var))
+ sh_readonly (name);
+ return ((SHELL_VAR *)NULL);
+ }
+
+ if (old_var == 0)
+ new_var = make_new_variable (name, vc->table);
+ else
+ {
+ new_var = make_new_variable (name, vc->table);
+
+ /* If we found this variable in one of the temporary environments,
+ inherit its value. Watch to see if this causes problems with
+ things like `x=4 local x'. */
+ if (was_tmpvar)
+ var_setvalue (new_var, savestring (tmp_value));
+
+ new_var->attributes = exported_p (old_var) ? att_exported : 0;
+ }
+
+ vc->flags |= VC_HASLOCAL;
+
+ new_var->context = variable_context;
+ VSETATTR (new_var, att_local);
+
+ if (ifsname (name))
+ setifs (new_var);
+
+ return (new_var);
+}
+
+/* Create a new shell variable with name NAME. */
+static SHELL_VAR *
+new_shell_variable (name)
+ const char *name;
+{
+ SHELL_VAR *entry;
+
+ entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+
+ entry->name = savestring (name);
+ var_setvalue (entry, (char *)NULL);
+ CLEAR_EXPORTSTR (entry);
+
+ entry->dynamic_value = (sh_var_value_func_t *)NULL;
+ entry->assign_func = (sh_var_assign_func_t *)NULL;
+
+ entry->attributes = 0;
+
+ /* Always assume variables are to be made at toplevel!
+ make_local_variable has the responsibilty of changing the
+ variable context. */
+ entry->context = 0;
+
+ return (entry);
+}
+
+/* Create a new shell variable with name NAME and add it to the hash table
+ TABLE. */
+static SHELL_VAR *
+make_new_variable (name, table)
+ const char *name;
+ HASH_TABLE *table;
+{
+ SHELL_VAR *entry;
+ BUCKET_CONTENTS *elt;
+
+ entry = new_shell_variable (name);
+
+ /* Make sure we have a shell_variables hash table to add to. */
+ if (shell_variables == 0)
+ create_variable_tables ();
+
+ elt = hash_insert (savestring (name), table, HASH_NOSRCH);
+ elt->data = (PTR_T)entry;
+
+ return entry;
+}
+
+#if defined (ARRAY_VARS)
+SHELL_VAR *
+make_new_array_variable (name)
+ char *name;
+{
+ SHELL_VAR *entry;
+ ARRAY *array;
+
+ entry = make_new_variable (name, global_variables->table);
+ array = array_create ();
+
+ var_setarray (entry, array);
+ VSETATTR (entry, att_array);
+ return entry;
+}
+
+SHELL_VAR *
+make_local_array_variable (name, assoc_ok)
+ char *name;
+ int assoc_ok;
+{
+ SHELL_VAR *var;
+ ARRAY *array;
+
+ var = make_local_variable (name);
+ if (var == 0 || array_p (var) || (assoc_ok && assoc_p (var)))
+ return var;
+
+ array = array_create ();
+
+ dispose_variable_value (var);
+ var_setarray (var, array);
+ VSETATTR (var, att_array);
+ return var;
+}
+
+SHELL_VAR *
+make_new_assoc_variable (name)
+ char *name;
+{
+ SHELL_VAR *entry;
+ HASH_TABLE *hash;
+
+ entry = make_new_variable (name, global_variables->table);
+ hash = assoc_create (0);
+
+ var_setassoc (entry, hash);
+ VSETATTR (entry, att_assoc);
+ return entry;
+}
+
+SHELL_VAR *
+make_local_assoc_variable (name)
+ char *name;
+{
+ SHELL_VAR *var;
+ HASH_TABLE *hash;
+
+ var = make_local_variable (name);
+ if (var == 0 || assoc_p (var))
+ return var;
+
+ dispose_variable_value (var);
+ hash = assoc_create (0);
+
+ var_setassoc (var, hash);
+ VSETATTR (var, att_assoc);
+ return var;
+}
+#endif
+
+char *
+make_variable_value (var, value, flags)
+ SHELL_VAR *var;
+ char *value;
+ int flags;
+{
+ char *retval, *oval;
+ intmax_t lval, rval;
+ int expok, olen, op;
+
+ /* If this variable has had its type set to integer (via `declare -i'),
+ then do expression evaluation on it and store the result. The
+ functions in expr.c (evalexp()) and bind_int_variable() are responsible
+ for turning off the integer flag if they don't want further
+ evaluation done. */
+ if (integer_p (var))
+ {
+ if (flags & ASS_APPEND)
+ {
+ oval = value_cell (var);
+ lval = evalexp (oval, &expok); /* ksh93 seems to do this */
+ if (expok == 0)
+ {
+ top_level_cleanup ();
+ jump_to_top_level (DISCARD);
+ }
+ }
+ rval = evalexp (value, &expok);
+ if (expok == 0)
+ {
+ top_level_cleanup ();
+ jump_to_top_level (DISCARD);
+ }
+ if (flags & ASS_APPEND)
+ rval += lval;
+ retval = itos (rval);
+ }
+#if defined (CASEMOD_ATTRS)
+ else if (capcase_p (var) || uppercase_p (var) || lowercase_p (var))
+ {
+ if (flags & ASS_APPEND)
+ {
+ oval = get_variable_value (var);
+ if (oval == 0) /* paranoia */
+ oval = "";
+ olen = STRLEN (oval);
+ retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1);
+ strcpy (retval, oval);
+ if (value)
+ strcpy (retval+olen, value);
+ }
+ else if (*value)
+ retval = savestring (value);
+ else
+ {
+ retval = (char *)xmalloc (1);
+ retval[0] = '\0';
+ }
+ op = capcase_p (var) ? CASE_CAPITALIZE
+ : (uppercase_p (var) ? CASE_UPPER : CASE_LOWER);
+ oval = sh_modcase (retval, (char *)0, op);
+ free (retval);
+ retval = oval;
+ }
+#endif /* CASEMOD_ATTRS */
+ else if (value)
+ {
+ if (flags & ASS_APPEND)
+ {
+ oval = get_variable_value (var);
+ if (oval == 0) /* paranoia */
+ oval = "";
+ olen = STRLEN (oval);
+ retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1);
+ strcpy (retval, oval);
+ if (value)
+ strcpy (retval+olen, value);
+ }
+ else if (*value)
+ retval = savestring (value);
+ else
+ {
+ retval = (char *)xmalloc (1);
+ retval[0] = '\0';
+ }
+ }
+ else
+ retval = (char *)NULL;
+
+ return retval;
+}
+
+/* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the
+ temporary environment (but usually is not). */
+static SHELL_VAR *
+bind_variable_internal (name, value, table, hflags, aflags)
+ const char *name;
+ char *value;
+ HASH_TABLE *table;
+ int hflags, aflags;
+{
+ char *newval;
+ SHELL_VAR *entry;
+
+ entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table);
+ /* Follow the nameref chain here if this is the global variables table */
+ if (entry && nameref_p (entry) && (invisible_p (entry) == 0) && table == global_variables->table)
+ {
+ entry = find_global_variable (entry->name);
+ /* Let's see if we have a nameref referencing a variable that hasn't yet
+ been created. */
+ if (entry == 0)
+ entry = find_variable_last_nameref (name); /* XXX */
+ if (entry == 0) /* just in case */
+ return (entry);
+ }
+
+ /* The first clause handles `declare -n ref; ref=x;' */
+ if (entry && invisible_p (entry) && nameref_p (entry))
+ goto assign_value;
+ else if (entry && nameref_p (entry))
+ {
+ newval = nameref_cell (entry);
+#if defined (ARRAY_VARS)
+ /* declare -n foo=x[2] */
+ if (valid_array_reference (newval))
+ /* XXX - should it be aflags? */
+ entry = assign_array_element (newval, make_variable_value (entry, value, 0), aflags);
+ else
+#endif
+ {
+ entry = make_new_variable (newval, table);
+ var_setvalue (entry, make_variable_value (entry, value, 0));
+ }
+ }
+ else if (entry == 0)
+ {
+ entry = make_new_variable (name, table);
+ var_setvalue (entry, make_variable_value (entry, value, 0)); /* XXX */
+ }
+ else if (entry->assign_func) /* array vars have assign functions now */
+ {
+ INVALIDATE_EXPORTSTR (entry);
+ newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : value;
+ if (assoc_p (entry))
+ entry = (*(entry->assign_func)) (entry, newval, -1, savestring ("0"));
+ else if (array_p (entry))
+ entry = (*(entry->assign_func)) (entry, newval, 0, 0);
+ else
+ entry = (*(entry->assign_func)) (entry, newval, -1, 0);
+ if (newval != value)
+ free (newval);
+ return (entry);
+ }
+ else
+ {
+assign_value:
+ if (readonly_p (entry) || noassign_p (entry))
+ {
+ if (readonly_p (entry))
+ err_readonly (name);
+ return (entry);
+ }
+
+ /* Variables which are bound are visible. */
+ VUNSETATTR (entry, att_invisible);
+
+#if defined (ARRAY_VARS)
+ if (assoc_p (entry) || array_p (entry))
+ newval = make_array_variable_value (entry, 0, "0", value, aflags);
+ else
+#endif
+
+ newval = make_variable_value (entry, value, aflags); /* XXX */
+
+ /* Invalidate any cached export string */
+ INVALIDATE_EXPORTSTR (entry);
+
+#if defined (ARRAY_VARS)
+ /* XXX -- this bears looking at again -- XXX */
+ /* If an existing array variable x is being assigned to with x=b or
+ `read x' or something of that nature, silently convert it to
+ x[0]=b or `read x[0]'. */
+ if (assoc_p (entry))
+ {
+ assoc_insert (assoc_cell (entry), savestring ("0"), newval);
+ free (newval);
+ }
+ else if (array_p (entry))
+ {
+ array_insert (array_cell (entry), 0, newval);
+ free (newval);
+ }
+ else
+#endif
+ {
+ FREE (value_cell (entry));
+ var_setvalue (entry, newval);
+ }
+ }
+
+ if (mark_modified_vars)
+ VSETATTR (entry, att_exported);
+
+ if (exported_p (entry))
+ array_needs_making = 1;
+
+ return (entry);
+}
+
+/* Bind a variable NAME to VALUE. This conses up the name
+ and value strings. If we have a temporary environment, we bind there
+ first, then we bind into shell_variables. */
+
+SHELL_VAR *
+bind_variable (name, value, flags)
+ const char *name;
+ char *value;
+ int flags;
+{
+ SHELL_VAR *v, *nv;
+ VAR_CONTEXT *vc, *nvc;
+ int level;
+
+ if (shell_variables == 0)
+ create_variable_tables ();
+
+ /* If we have a temporary environment, look there first for the variable,
+ and, if found, modify the value there before modifying it in the
+ shell_variables table. This allows sourced scripts to modify values
+ given to them in a temporary environment while modifying the variable
+ value that the caller sees. */
+ if (temporary_env)
+ bind_tempenv_variable (name, value);
+
+ /* XXX -- handle local variables here. */
+ for (vc = shell_variables; vc; vc = vc->down)
+ {
+ if (vc_isfuncenv (vc) || vc_isbltnenv (vc))
+ {
+ v = hash_lookup (name, vc->table);
+ nvc = vc;
+ if (v && nameref_p (v))
+ {
+ nv = find_variable_nameref_context (v, vc, &nvc);
+ if (nv == 0)
+ {
+ nv = find_variable_last_nameref_context (v, vc, &nvc);
+ if (nv && nameref_p (nv))
+ return (bind_variable_internal (nameref_cell (nv), value, nvc->table, 0, flags));
+ else
+ v = nv;
+ }
+ else
+ v = nv;
+ }
+ if (v)
+ return (bind_variable_internal (v->name, value, nvc->table, 0, flags));
+ }
+ }
+ /* bind_variable_internal will handle nameref resolution in this case */
+ return (bind_variable_internal (name, value, global_variables->table, 0, flags));
+}
+
+/* Make VAR, a simple shell variable, have value VALUE. Once assigned a
+ value, variables are no longer invisible. This is a duplicate of part
+ of the internals of bind_variable. If the variable is exported, or
+ all modified variables should be exported, mark the variable for export
+ and note that the export environment needs to be recreated. */
+SHELL_VAR *
+bind_variable_value (var, value, aflags)
+ SHELL_VAR *var;
+ char *value;
+ int aflags;
+{
+ char *t;
+
+ VUNSETATTR (var, att_invisible);
+
+ if (var->assign_func)
+ {
+ /* If we're appending, we need the old value, so use
+ make_variable_value */
+ t = (aflags & ASS_APPEND) ? make_variable_value (var, value, aflags) : value;
+ (*(var->assign_func)) (var, t, -1, 0);
+ if (t != value && t)
+ free (t);
+ }
+ else
+ {
+ t = make_variable_value (var, value, aflags);
+ FREE (value_cell (var));
+ var_setvalue (var, t);
+ }
+
+ INVALIDATE_EXPORTSTR (var);
+
+ if (mark_modified_vars)
+ VSETATTR (var, att_exported);
+
+ if (exported_p (var))
+ array_needs_making = 1;
+
+ return (var);
+}
+
+/* Bind/create a shell variable with the name LHS to the RHS.
+ This creates or modifies a variable such that it is an integer.
+
+ This used to be in expr.c, but it is here so that all of the
+ variable binding stuff is localized. Since we don't want any
+ recursive evaluation from bind_variable() (possible without this code,
+ since bind_variable() calls the evaluator for variables with the integer
+ attribute set), we temporarily turn off the integer attribute for each
+ variable we set here, then turn it back on after binding as necessary. */
+
+SHELL_VAR *
+bind_int_variable (lhs, rhs)
+ char *lhs, *rhs;
+{
+ register SHELL_VAR *v;
+ int isint, isarr, implicitarray;
+
+ isint = isarr = implicitarray = 0;
+#if defined (ARRAY_VARS)
+ if (valid_array_reference (lhs))
+ {
+ isarr = 1;
+ v = array_variable_part (lhs, (char **)0, (int *)0);
+ }
+ else
+#endif
+ v = find_variable (lhs);
+
+ if (v)
+ {
+ isint = integer_p (v);
+ VUNSETATTR (v, att_integer);
+#if defined (ARRAY_VARS)
+ if (array_p (v) && isarr == 0)
+ implicitarray = 1;
+#endif
+ }
+
+#if defined (ARRAY_VARS)
+ if (isarr)
+ v = assign_array_element (lhs, rhs, 0);
+ else if (implicitarray)
+ v = bind_array_variable (lhs, 0, rhs, 0);
+ else
+#endif
+ v = bind_variable (lhs, rhs, 0);
+
+ if (v && isint)
+ VSETATTR (v, att_integer);
+
+ return (v);
+}
+
+SHELL_VAR *
+bind_var_to_int (var, val)
+ char *var;
+ intmax_t val;
+{
+ char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p;
+
+ p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0);
+ return (bind_int_variable (var, p));
+}
+
+/* Do a function binding to a variable. You pass the name and
+ the command to bind to. This conses the name and command. */
+SHELL_VAR *
+bind_function (name, value)
+ const char *name;
+ COMMAND *value;
+{
+ SHELL_VAR *entry;
+
+ entry = find_function (name);
+ if (entry == 0)
+ {
+ BUCKET_CONTENTS *elt;
+
+ elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH);
+ entry = new_shell_variable (name);
+ elt->data = (PTR_T)entry;
+ }
+ else
+ INVALIDATE_EXPORTSTR (entry);
+
+ if (var_isset (entry))
+ dispose_command (function_cell (entry));
+
+ if (value)
+ var_setfunc (entry, copy_command (value));
+ else
+ var_setfunc (entry, 0);
+
+ VSETATTR (entry, att_function);
+
+ if (mark_modified_vars)
+ VSETATTR (entry, att_exported);
+
+ VUNSETATTR (entry, att_invisible); /* Just to be sure */
+
+ if (exported_p (entry))
+ array_needs_making = 1;
+
+#if defined (PROGRAMMABLE_COMPLETION)
+ set_itemlist_dirty (&it_functions);
+#endif
+
+ return (entry);
+}
+
+#if defined (DEBUGGER)
+/* Bind a function definition, which includes source file and line number
+ information in addition to the command, into the FUNCTION_DEF hash table.*/
+void
+bind_function_def (name, value)
+ const char *name;
+ FUNCTION_DEF *value;
+{
+ FUNCTION_DEF *entry;
+ BUCKET_CONTENTS *elt;
+ COMMAND *cmd;
+
+ entry = find_function_def (name);
+ if (entry)
+ {
+ dispose_function_def_contents (entry);
+ entry = copy_function_def_contents (value, entry);
+ }
+ else
+ {
+ cmd = value->command;
+ value->command = 0;
+ entry = copy_function_def (value);
+ value->command = cmd;
+
+ elt = hash_insert (savestring (name), shell_function_defs, HASH_NOSRCH);
+ elt->data = (PTR_T *)entry;
+ }
+}
+#endif /* DEBUGGER */
+
+/* Add STRING, which is of the form foo=bar, to the temporary environment
+ HASH_TABLE (temporary_env). The functions in execute_cmd.c are
+ responsible for moving the main temporary env to one of the other
+ temporary environments. The expansion code in subst.c calls this. */
+int
+assign_in_env (word, flags)
+ WORD_DESC *word;
+ int flags;
+{
+ int offset;
+ char *name, *temp, *value;
+ SHELL_VAR *var;
+ const char *string;
+
+ string = word->word;
+
+ offset = assignment (string, 0);
+ name = savestring (string);
+ value = (char *)NULL;
+
+ if (name[offset] == '=')
+ {
+ name[offset] = 0;
+
+ /* ignore the `+' when assigning temporary environment */
+ if (name[offset - 1] == '+')
+ name[offset - 1] = '\0';
+
+ var = find_variable (name);
+ if (var && (readonly_p (var) || noassign_p (var)))
+ {
+ if (readonly_p (var))
+ err_readonly (name);
+ free (name);
+ return (0);
+ }
+
+ temp = name + offset + 1;
+ value = expand_assignment_string_to_string (temp, 0);
+ }
+
+ if (temporary_env == 0)
+ temporary_env = hash_create (TEMPENV_HASH_BUCKETS);
+
+ var = hash_lookup (name, temporary_env);
+ if (var == 0)
+ var = make_new_variable (name, temporary_env);
+ else
+ FREE (value_cell (var));
+
+ if (value == 0)
+ {
+ value = (char *)xmalloc (1); /* like do_assignment_internal */
+ value[0] = '\0';
+ }
+
+ var_setvalue (var, value);
+ var->attributes |= (att_exported|att_tempvar);
+ var->context = variable_context; /* XXX */
+
+ INVALIDATE_EXPORTSTR (var);
+ var->exportstr = mk_env_string (name, value);
+
+ array_needs_making = 1;
+
+#if 0
+ if (ifsname (name))
+ setifs (var);
+else
+#endif
+ if (flags)
+ stupidly_hack_special_variables (name);
+
+ if (echo_command_at_execute)
+ /* The Korn shell prints the `+ ' in front of assignment statements,
+ so we do too. */
+ xtrace_print_assignment (name, value, 0, 1);
+
+ free (name);
+ return 1;
+}
+
+/* **************************************************************** */
+/* */
+/* Copying variables */
+/* */
+/* **************************************************************** */
+
+#ifdef INCLUDE_UNUSED
+/* Copy VAR to a new data structure and return that structure. */
+SHELL_VAR *
+copy_variable (var)
+ SHELL_VAR *var;
+{
+ SHELL_VAR *copy = (SHELL_VAR *)NULL;
+
+ if (var)
+ {
+ copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR));
+
+ copy->attributes = var->attributes;
+ copy->name = savestring (var->name);
+
+ if (function_p (var))
+ var_setfunc (copy, copy_command (function_cell (var)));
+#if defined (ARRAY_VARS)
+ else if (array_p (var))
+ var_setarray (copy, array_copy (array_cell (var)));
+ else if (assoc_p (var))
+ var_setassoc (copy, assoc_copy (assoc_cell (var)));
+#endif
+ else if (nameref_cell (var)) /* XXX - nameref */
+ var_setref (copy, savestring (nameref_cell (var)));
+ else if (value_cell (var)) /* XXX - nameref */
+ var_setvalue (copy, savestring (value_cell (var)));
+ else
+ var_setvalue (copy, (char *)NULL);
+
+ copy->dynamic_value = var->dynamic_value;
+ copy->assign_func = var->assign_func;
+
+ copy->exportstr = COPY_EXPORTSTR (var);
+
+ copy->context = var->context;
+ }
+ return (copy);
+}
+#endif
+
+/* **************************************************************** */
+/* */
+/* Deleting and unsetting variables */
+/* */
+/* **************************************************************** */
+
+/* Dispose of the information attached to VAR. */
+static void
+dispose_variable_value (var)
+ SHELL_VAR *var;
+{
+ if (function_p (var))
+ dispose_command (function_cell (var));
+#if defined (ARRAY_VARS)
+ else if (array_p (var))
+ array_dispose (array_cell (var));
+ else if (assoc_p (var))
+ assoc_dispose (assoc_cell (var));
+#endif
+ else if (nameref_p (var))
+ FREE (value_cell (var));
+ else
+ FREE (value_cell (var));
+}
+
+void
+dispose_variable (var)
+ SHELL_VAR *var;
+{
+ if (var == 0)
+ return;
+
+ if (nofree_p (var) == 0)
+ dispose_variable_value (var);
+
+ FREE_EXPORTSTR (var);
+
+ free (var->name);
+
+ if (exported_p (var))
+ array_needs_making = 1;
+
+ free (var);
+}
+
+/* Unset the shell variable referenced by NAME. Unsetting a nameref variable
+ unsets both the variable and the variable it resolves to. */
+int
+unbind_variable (name)
+ const char *name;
+{
+ SHELL_VAR *v, *nv;
+ int r;
+
+ v = var_lookup (name, shell_variables);
+ nv = (v && nameref_p (v)) ? find_variable_nameref (v) : (SHELL_VAR *)NULL;
+
+ r = nv ? makunbound (nv->name, shell_variables) : 0;
+ if (r == 0)
+ r = makunbound (name, shell_variables);
+
+ return r;
+}
+
+/* Unset the shell function named NAME. */
+int
+unbind_func (name)
+ const char *name;
+{
+ BUCKET_CONTENTS *elt;
+ SHELL_VAR *func;
+
+ elt = hash_remove (name, shell_functions, 0);
+
+ if (elt == 0)
+ return -1;
+
+#if defined (PROGRAMMABLE_COMPLETION)
+ set_itemlist_dirty (&it_functions);
+#endif
+
+ func = (SHELL_VAR *)elt->data;
+ if (func)
+ {
+ if (exported_p (func))
+ array_needs_making++;
+ dispose_variable (func);
+ }
+
+ free (elt->key);
+ free (elt);
+
+ return 0;
+}
+
+#if defined (DEBUGGER)
+int
+unbind_function_def (name)
+ const char *name;
+{
+ BUCKET_CONTENTS *elt;
+ FUNCTION_DEF *funcdef;
+
+ elt = hash_remove (name, shell_function_defs, 0);
+
+ if (elt == 0)
+ return -1;
+
+ funcdef = (FUNCTION_DEF *)elt->data;
+ if (funcdef)
+ dispose_function_def (funcdef);
+
+ free (elt->key);
+ free (elt);
+
+ return 0;
+}
+#endif /* DEBUGGER */
+
+/* Make the variable associated with NAME go away. HASH_LIST is the
+ hash table from which this variable should be deleted (either
+ shell_variables or shell_functions).
+ Returns non-zero if the variable couldn't be found. */
+int
+makunbound (name, vc)
+ const char *name;
+ VAR_CONTEXT *vc;
+{
+ BUCKET_CONTENTS *elt, *new_elt;
+ SHELL_VAR *old_var;
+ VAR_CONTEXT *v;
+ char *t;
+
+ for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down)
+ if (elt = hash_remove (name, v->table, 0))
+ break;
+
+ if (elt == 0)
+ return (-1);
+
+ old_var = (SHELL_VAR *)elt->data;
+
+ if (old_var && exported_p (old_var))
+ array_needs_making++;
+
+ /* If we're unsetting a local variable and we're still executing inside
+ the function, just mark the variable as invisible. The function
+ eventually called by pop_var_context() will clean it up later. This
+ must be done so that if the variable is subsequently assigned a new
+ value inside the function, the `local' attribute is still present.
+ We also need to add it back into the correct hash table. */
+ if (old_var && local_p (old_var) && variable_context == old_var->context)
+ {
+ if (nofree_p (old_var))
+ var_setvalue (old_var, (char *)NULL);
+#if defined (ARRAY_VARS)
+ else if (array_p (old_var))
+ array_dispose (array_cell (old_var));
+ else if (assoc_p (old_var))
+ assoc_dispose (assoc_cell (old_var));
+#endif
+ else
+ FREE (value_cell (old_var));
+ /* Reset the attributes. Preserve the export attribute if the variable
+ came from a temporary environment. Make sure it stays local, and
+ make it invisible. */
+ old_var->attributes = (exported_p (old_var) && tempvar_p (old_var)) ? att_exported : 0;
+ VSETATTR (old_var, att_local);
+ VSETATTR (old_var, att_invisible);
+ var_setvalue (old_var, (char *)NULL);
+ INVALIDATE_EXPORTSTR (old_var);
+
+ new_elt = hash_insert (savestring (old_var->name), v->table, 0);
+ new_elt->data = (PTR_T)old_var;
+ stupidly_hack_special_variables (old_var->name);
+
+ free (elt->key);
+ free (elt);
+ return (0);
+ }
+
+ /* Have to save a copy of name here, because it might refer to
+ old_var->name. If so, stupidly_hack_special_variables will
+ reference freed memory. */
+ t = savestring (name);
+
+ free (elt->key);
+ free (elt);
+
+ dispose_variable (old_var);
+ stupidly_hack_special_variables (t);
+ free (t);
+
+ return (0);
+}
+
+/* Get rid of all of the variables in the current context. */
+void
+kill_all_local_variables ()
+{
+ VAR_CONTEXT *vc;
+
+ for (vc = shell_variables; vc; vc = vc->down)
+ if (vc_isfuncenv (vc) && vc->scope == variable_context)
+ break;
+ if (vc == 0)
+ return; /* XXX */
+
+ if (vc->table && vc_haslocals (vc))
+ {
+ delete_all_variables (vc->table);
+ hash_dispose (vc->table);
+ }
+ vc->table = (HASH_TABLE *)NULL;
+}
+
+static void
+free_variable_hash_data (data)
+ PTR_T data;
+{
+ SHELL_VAR *var;
+
+ var = (SHELL_VAR *)data;
+ dispose_variable (var);
+}
+
+/* Delete the entire contents of the hash table. */
+void
+delete_all_variables (hashed_vars)
+ HASH_TABLE *hashed_vars;
+{
+ hash_flush (hashed_vars, free_variable_hash_data);
+}
+
+/* **************************************************************** */
+/* */
+/* Setting variable attributes */
+/* */
+/* **************************************************************** */
+
+#define FIND_OR_MAKE_VARIABLE(name, entry) \
+ do \
+ { \
+ entry = find_variable (name); \
+ if (!entry) \
+ { \
+ entry = bind_variable (name, "", 0); \
+ if (!no_invisible_vars && entry) entry->attributes |= att_invisible; \
+ } \
+ } \
+ while (0)
+
+/* Make the variable associated with NAME be readonly.
+ If NAME does not exist yet, create it. */
+void
+set_var_read_only (name)
+ char *name;
+{
+ SHELL_VAR *entry;
+
+ FIND_OR_MAKE_VARIABLE (name, entry);
+ VSETATTR (entry, att_readonly);
+}
+
+#ifdef INCLUDE_UNUSED
+/* Make the function associated with NAME be readonly.
+ If NAME does not exist, we just punt, like auto_export code below. */
+void
+set_func_read_only (name)
+ const char *name;
+{
+ SHELL_VAR *entry;
+
+ entry = find_function (name);
+ if (entry)
+ VSETATTR (entry, att_readonly);
+}
+
+/* Make the variable associated with NAME be auto-exported.
+ If NAME does not exist yet, create it. */
+void
+set_var_auto_export (name)
+ char *name;
+{
+ SHELL_VAR *entry;
+
+ FIND_OR_MAKE_VARIABLE (name, entry);
+ set_auto_export (entry);
+}
+
+/* Make the function associated with NAME be auto-exported. */
+void
+set_func_auto_export (name)
+ const char *name;
+{
+ SHELL_VAR *entry;
+
+ entry = find_function (name);
+ if (entry)
+ set_auto_export (entry);
+}
+#endif
+
+/* **************************************************************** */
+/* */
+/* Creating lists of variables */
+/* */
+/* **************************************************************** */
+
+static VARLIST *
+vlist_alloc (nentries)
+ int nentries;
+{
+ VARLIST *vlist;
+
+ vlist = (VARLIST *)xmalloc (sizeof (VARLIST));
+ vlist->list = (SHELL_VAR **)xmalloc ((nentries + 1) * sizeof (SHELL_VAR *));
+ vlist->list_size = nentries;
+ vlist->list_len = 0;
+ vlist->list[0] = (SHELL_VAR *)NULL;
+
+ return vlist;
+}
+
+static VARLIST *
+vlist_realloc (vlist, n)
+ VARLIST *vlist;
+ int n;
+{
+ if (vlist == 0)
+ return (vlist = vlist_alloc (n));
+ if (n > vlist->list_size)
+ {
+ vlist->list_size = n;
+ vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *));
+ }
+ return vlist;
+}
+
+static void
+vlist_add (vlist, var, flags)
+ VARLIST *vlist;
+ SHELL_VAR *var;
+ int flags;
+{
+ register int i;
+
+ for (i = 0; i < vlist->list_len; i++)
+ if (STREQ (var->name, vlist->list[i]->name))
+ break;
+ if (i < vlist->list_len)
+ return;
+
+ if (i >= vlist->list_size)
+ vlist = vlist_realloc (vlist, vlist->list_size + 16);
+
+ vlist->list[vlist->list_len++] = var;
+ vlist->list[vlist->list_len] = (SHELL_VAR *)NULL;
+}
+
+/* Map FUNCTION over the variables in VAR_HASH_TABLE. Return an array of the
+ variables for which FUNCTION returns a non-zero value. A NULL value
+ for FUNCTION means to use all variables. */
+SHELL_VAR **
+map_over (function, vc)
+ sh_var_map_func_t *function;
+ VAR_CONTEXT *vc;
+{
+ VAR_CONTEXT *v;
+ VARLIST *vlist;
+ SHELL_VAR **ret;
+ int nentries;
+
+ for (nentries = 0, v = vc; v; v = v->down)
+ nentries += HASH_ENTRIES (v->table);
+
+ if (nentries == 0)
+ return (SHELL_VAR **)NULL;
+
+ vlist = vlist_alloc (nentries);
+
+ for (v = vc; v; v = v->down)
+ flatten (v->table, function, vlist, 0);
+
+ ret = vlist->list;
+ free (vlist);
+ return ret;
+}
+
+SHELL_VAR **
+map_over_funcs (function)
+ sh_var_map_func_t *function;
+{
+ VARLIST *vlist;
+ SHELL_VAR **ret;
+
+ if (shell_functions == 0 || HASH_ENTRIES (shell_functions) == 0)
+ return ((SHELL_VAR **)NULL);
+
+ vlist = vlist_alloc (HASH_ENTRIES (shell_functions));
+
+ flatten (shell_functions, function, vlist, 0);
+
+ ret = vlist->list;
+ free (vlist);
+ return ret;
+}
+
+/* Flatten VAR_HASH_TABLE, applying FUNC to each member and adding those
+ elements for which FUNC succeeds to VLIST->list. FLAGS is reserved
+ for future use. Only unique names are added to VLIST. If FUNC is
+ NULL, each variable in VAR_HASH_TABLE is added to VLIST. If VLIST is
+ NULL, FUNC is applied to each SHELL_VAR in VAR_HASH_TABLE. If VLIST
+ and FUNC are both NULL, nothing happens. */
+static void
+flatten (var_hash_table, func, vlist, flags)
+ HASH_TABLE *var_hash_table;
+ sh_var_map_func_t *func;
+ VARLIST *vlist;
+ int flags;
+{
+ register int i;
+ register BUCKET_CONTENTS *tlist;
+ int r;
+ SHELL_VAR *var;
+
+ if (var_hash_table == 0 || (HASH_ENTRIES (var_hash_table) == 0) || (vlist == 0 && func == 0))
+ return;
+
+ for (i = 0; i < var_hash_table->nbuckets; i++)
+ {
+ for (tlist = hash_items (i, var_hash_table); tlist; tlist = tlist->next)
+ {
+ var = (SHELL_VAR *)tlist->data;
+
+ r = func ? (*func) (var) : 1;
+ if (r && vlist)
+ vlist_add (vlist, var, flags);
+ }
+ }
+}
+
+void
+sort_variables (array)
+ SHELL_VAR **array;
+{
+ qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp);
+}
+
+static int
+qsort_var_comp (var1, var2)
+ SHELL_VAR **var1, **var2;
+{
+ int result;
+
+ if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0)
+ result = strcmp ((*var1)->name, (*var2)->name);
+
+ return (result);
+}
+
+/* Apply FUNC to each variable in SHELL_VARIABLES, adding each one for
+ which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */
+static SHELL_VAR **
+vapply (func)
+ sh_var_map_func_t *func;
+{
+ SHELL_VAR **list;
+
+ list = map_over (func, shell_variables);
+ if (list /* && posixly_correct */)
+ sort_variables (list);
+ return (list);
+}
+
+/* Apply FUNC to each variable in SHELL_FUNCTIONS, adding each one for
+ which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */
+static SHELL_VAR **
+fapply (func)
+ sh_var_map_func_t *func;
+{
+ SHELL_VAR **list;
+
+ list = map_over_funcs (func);
+ if (list /* && posixly_correct */)
+ sort_variables (list);
+ return (list);
+}
+
+/* Create a NULL terminated array of all the shell variables. */
+SHELL_VAR **
+all_shell_variables ()
+{
+ return (vapply ((sh_var_map_func_t *)NULL));
+}
+
+/* Create a NULL terminated array of all the shell functions. */
+SHELL_VAR **
+all_shell_functions ()
+{
+ return (fapply ((sh_var_map_func_t *)NULL));
+}
+
+static int
+visible_var (var)
+ SHELL_VAR *var;
+{
+ return (invisible_p (var) == 0);
+}
+
+SHELL_VAR **
+all_visible_functions ()
+{
+ return (fapply (visible_var));
+}
+
+SHELL_VAR **
+all_visible_variables ()
+{
+ return (vapply (visible_var));
+}
+
+/* Return non-zero if the variable VAR is visible and exported. Array
+ variables cannot be exported. */
+static int
+visible_and_exported (var)
+ SHELL_VAR *var;
+{
+ return (invisible_p (var) == 0 && exported_p (var));
+}
+
+/* Candidate variables for the export environment are either valid variables
+ with the export attribute or invalid variables inherited from the initial
+ environment and simply passed through. */
+static int
+export_environment_candidate (var)
+ SHELL_VAR *var;
+{
+ return (exported_p (var) && (invisible_p (var) == 0 || imported_p (var)));
+}
+
+/* Return non-zero if VAR is a local variable in the current context and
+ is exported. */
+static int
+local_and_exported (var)
+ SHELL_VAR *var;
+{
+ return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context && exported_p (var));
+}
+
+SHELL_VAR **
+all_exported_variables ()
+{
+ return (vapply (visible_and_exported));
+}
+
+SHELL_VAR **
+local_exported_variables ()
+{
+ return (vapply (local_and_exported));
+}
+
+static int
+variable_in_context (var)
+ SHELL_VAR *var;
+{
+ return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context);
+}
+
+SHELL_VAR **
+all_local_variables ()
+{
+ VARLIST *vlist;
+ SHELL_VAR **ret;
+ VAR_CONTEXT *vc;
+
+ vc = shell_variables;
+ for (vc = shell_variables; vc; vc = vc->down)
+ if (vc_isfuncenv (vc) && vc->scope == variable_context)
+ break;
+
+ if (vc == 0)
+ {
+ internal_error (_("all_local_variables: no function context at current scope"));
+ return (SHELL_VAR **)NULL;
+ }
+ if (vc->table == 0 || HASH_ENTRIES (vc->table) == 0 || vc_haslocals (vc) == 0)
+ return (SHELL_VAR **)NULL;
+
+ vlist = vlist_alloc (HASH_ENTRIES (vc->table));
+
+ flatten (vc->table, variable_in_context, vlist, 0);
+
+ ret = vlist->list;
+ free (vlist);
+ if (ret)
+ sort_variables (ret);
+ return ret;
+}
+
+#if defined (ARRAY_VARS)
+/* Return non-zero if the variable VAR is visible and an array. */
+static int
+visible_array_vars (var)
+ SHELL_VAR *var;
+{
+ return (invisible_p (var) == 0 && array_p (var));
+}
+
+SHELL_VAR **
+all_array_variables ()
+{
+ return (vapply (visible_array_vars));
+}
+#endif /* ARRAY_VARS */
+
+char **
+all_variables_matching_prefix (prefix)
+ const char *prefix;
+{
+ SHELL_VAR **varlist;
+ char **rlist;
+ int vind, rind, plen;
+
+ plen = STRLEN (prefix);
+ varlist = all_visible_variables ();
+ for (vind = 0; varlist && varlist[vind]; vind++)
+ ;
+ if (varlist == 0 || vind == 0)
+ return ((char **)NULL);
+ rlist = strvec_create (vind + 1);
+ for (vind = rind = 0; varlist[vind]; vind++)
+ {
+ if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen))
+ rlist[rind++] = savestring (varlist[vind]->name);
+ }
+ rlist[rind] = (char *)0;
+ free (varlist);
+
+ return rlist;
+}
+
+/* **************************************************************** */
+/* */
+/* Managing temporary variable scopes */
+/* */
+/* **************************************************************** */
+
+/* Make variable NAME have VALUE in the temporary environment. */
+static SHELL_VAR *
+bind_tempenv_variable (name, value)
+ const char *name;
+ char *value;
+{
+ SHELL_VAR *var;
+
+ var = temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL;
+
+ if (var)
+ {
+ FREE (value_cell (var));
+ var_setvalue (var, savestring (value));
+ INVALIDATE_EXPORTSTR (var);
+ }
+
+ return (var);
+}
+
+/* Find a variable in the temporary environment that is named NAME.
+ Return the SHELL_VAR *, or NULL if not found. */
+SHELL_VAR *
+find_tempenv_variable (name)
+ const char *name;
+{
+ return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL);
+}
+
+char **tempvar_list;
+int tvlist_ind;
+
+/* Push the variable described by (SHELL_VAR *)DATA down to the next
+ variable context from the temporary environment. */
+static void
+push_temp_var (data)
+ PTR_T data;
+{
+ SHELL_VAR *var, *v;
+ HASH_TABLE *binding_table;
+
+ var = (SHELL_VAR *)data;
+
+ binding_table = shell_variables->table;
+ if (binding_table == 0)
+ {
+ if (shell_variables == global_variables)
+ /* shouldn't happen */
+ binding_table = shell_variables->table = global_variables->table = hash_create (0);
+ else
+ binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS);
+ }
+
+ v = bind_variable_internal (var->name, value_cell (var), binding_table, 0, 0);
+
+ /* XXX - should we set the context here? It shouldn't matter because of how
+ assign_in_env works, but might want to check. */
+ if (binding_table == global_variables->table) /* XXX */
+ var->attributes &= ~(att_tempvar|att_propagate);
+ else
+ {
+ var->attributes |= att_propagate;
+ if (binding_table == shell_variables->table)
+ shell_variables->flags |= VC_HASTMPVAR;
+ }
+ v->attributes |= var->attributes;
+
+ if (find_special_var (var->name) >= 0)
+ tempvar_list[tvlist_ind++] = savestring (var->name);
+
+ dispose_variable (var);
+}
+
+static void
+propagate_temp_var (data)
+ PTR_T data;
+{
+ SHELL_VAR *var;
+
+ var = (SHELL_VAR *)data;
+ if (tempvar_p (var) && (var->attributes & att_propagate))
+ push_temp_var (data);
+ else
+ {
+ if (find_special_var (var->name) >= 0)
+ tempvar_list[tvlist_ind++] = savestring (var->name);
+ dispose_variable (var);
+ }
+}
+
+/* Free the storage used in the hash table for temporary
+ environment variables. PUSHF is a function to be called
+ to free each hash table entry. It takes care of pushing variables
+ to previous scopes if appropriate. PUSHF stores names of variables
+ that require special handling (e.g., IFS) on tempvar_list, so this
+ function can call stupidly_hack_special_variables on all the
+ variables in the list when the temporary hash table is destroyed. */
+static void
+dispose_temporary_env (pushf)
+ sh_free_func_t *pushf;
+{
+ int i;
+
+ tempvar_list = strvec_create (HASH_ENTRIES (temporary_env) + 1);
+ tempvar_list[tvlist_ind = 0] = 0;
+
+ hash_flush (temporary_env, pushf);
+ hash_dispose (temporary_env);
+ temporary_env = (HASH_TABLE *)NULL;
+
+ tempvar_list[tvlist_ind] = 0;
+
+ array_needs_making = 1;
+
+#if 0
+ sv_ifs ("IFS"); /* XXX here for now -- check setifs in assign_in_env */
+#endif
+ for (i = 0; i < tvlist_ind; i++)
+ stupidly_hack_special_variables (tempvar_list[i]);
+
+ strvec_dispose (tempvar_list);
+ tempvar_list = 0;
+ tvlist_ind = 0;
+}
+
+void
+dispose_used_env_vars ()
+{
+ if (temporary_env)
+ {
+ dispose_temporary_env (propagate_temp_var);
+ maybe_make_export_env ();
+ }
+}
+
+/* Take all of the shell variables in the temporary environment HASH_TABLE
+ and make shell variables from them at the current variable context. */
+void
+merge_temporary_env ()
+{
+ if (temporary_env)
+ dispose_temporary_env (push_temp_var);
+}
+
+/* **************************************************************** */
+/* */
+/* Creating and manipulating the environment */
+/* */
+/* **************************************************************** */
+
+static inline char *
+mk_env_string (name, value)
+ const char *name, *value;
+{
+ int name_len, value_len;
+ char *p;
+
+ name_len = strlen (name);
+ value_len = STRLEN (value);
+ p = (char *)xmalloc (2 + name_len + value_len);
+ strcpy (p, name);
+ p[name_len] = '=';
+ if (value && *value)
+ strcpy (p + name_len + 1, value);
+ else
+ p[name_len + 1] = '\0';
+ return (p);
+}
+
+#ifdef DEBUG
+/* Debugging */
+static int
+valid_exportstr (v)
+ SHELL_VAR *v;
+{
+ char *s;
+
+ s = v->exportstr;
+ if (s == 0)
+ {
+ internal_error (_("%s has null exportstr"), v->name);
+ return (0);
+ }
+ if (legal_variable_starter ((unsigned char)*s) == 0)
+ {
+ internal_error (_("invalid character %d in exportstr for %s"), *s, v->name);
+ return (0);
+ }
+ for (s = v->exportstr + 1; s && *s; s++)
+ {
+ if (*s == '=')
+ break;
+ if (legal_variable_char ((unsigned char)*s) == 0)
+ {
+ internal_error (_("invalid character %d in exportstr for %s"), *s, v->name);
+ return (0);
+ }
+ }
+ if (*s != '=')
+ {
+ internal_error (_("no `=' in exportstr for %s"), v->name);
+ return (0);
+ }
+ return (1);
+}
+#endif
+
+static char **
+make_env_array_from_var_list (vars)
+ SHELL_VAR **vars;
+{
+ register int i, list_index;
+ register SHELL_VAR *var;
+ char **list, *value;
+
+ list = strvec_create ((1 + strvec_len ((char **)vars)));
+
+#define USE_EXPORTSTR (value == var->exportstr)
+
+ for (i = 0, list_index = 0; var = vars[i]; i++)
+ {
+#if defined (__CYGWIN__)
+ /* We don't use the exportstr stuff on Cygwin at all. */
+ INVALIDATE_EXPORTSTR (var);
+#endif
+ if (var->exportstr)
+ value = var->exportstr;
+ else if (function_p (var))
+ value = named_function_string ((char *)NULL, function_cell (var), 0);
+#if defined (ARRAY_VARS)
+ else if (array_p (var))
+# if 0
+ value = array_to_assignment_string (array_cell (var));
+# else
+ continue; /* XXX array vars cannot yet be exported */
+# endif
+ else if (assoc_p (var))
+# if 0
+ value = assoc_to_assignment_string (assoc_cell (var));
+# else
+ continue; /* XXX associative array vars cannot yet be exported */
+# endif
+#endif
+ else
+ value = value_cell (var);
+
+ if (value)
+ {
+ /* Gee, I'd like to get away with not using savestring() if we're
+ using the cached exportstr... */
+ list[list_index] = USE_EXPORTSTR ? savestring (value)
+ : mk_env_string (var->name, value);
+
+ if (USE_EXPORTSTR == 0)
+ SAVE_EXPORTSTR (var, list[list_index]);
+
+ list_index++;
+#undef USE_EXPORTSTR
+
+#if 0 /* not yet */
+#if defined (ARRAY_VARS)
+ if (array_p (var) || assoc_p (var))
+ free (value);
+#endif
+#endif
+ }
+ }
+
+ list[list_index] = (char *)NULL;
+ return (list);
+}
+
+/* Make an array of assignment statements from the hash table
+ HASHED_VARS which contains SHELL_VARs. Only visible, exported
+ variables are eligible. */
+static char **
+make_var_export_array (vcxt)
+ VAR_CONTEXT *vcxt;
+{
+ char **list;
+ SHELL_VAR **vars;
+
+#if 0
+ vars = map_over (visible_and_exported, vcxt);
+#else
+ vars = map_over (export_environment_candidate, vcxt);
+#endif
+
+ if (vars == 0)
+ return (char **)NULL;
+
+ list = make_env_array_from_var_list (vars);
+
+ free (vars);
+ return (list);
+}
+
+static char **
+make_func_export_array ()
+{
+ char **list;
+ SHELL_VAR **vars;
+
+ vars = map_over_funcs (visible_and_exported);
+ if (vars == 0)
+ return (char **)NULL;
+
+ list = make_env_array_from_var_list (vars);
+
+ free (vars);
+ return (list);
+}
+
+/* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */
+#define add_to_export_env(envstr,do_alloc) \
+do \
+ { \
+ if (export_env_index >= (export_env_size - 1)) \
+ { \
+ export_env_size += 16; \
+ export_env = strvec_resize (export_env, export_env_size); \
+ environ = export_env; \
+ } \
+ export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \
+ export_env[export_env_index] = (char *)NULL; \
+ } while (0)
+
+/* Add ASSIGN to EXPORT_ENV, or supercede a previous assignment in the
+ array with the same left-hand side. Return the new EXPORT_ENV. */
+char **
+add_or_supercede_exported_var (assign, do_alloc)
+ char *assign;
+ int do_alloc;
+{
+ register int i;
+ int equal_offset;
+
+ equal_offset = assignment (assign, 0);
+ if (equal_offset == 0)
+ return (export_env);
+
+ /* If this is a function, then only supersede the function definition.
+ We do this by including the `=() {' in the comparison, like
+ initialize_shell_variables does. */
+ if (assign[equal_offset + 1] == '(' &&
+ strncmp (assign + equal_offset + 2, ") {", 3) == 0) /* } */
+ equal_offset += 4;
+
+ for (i = 0; i < export_env_index; i++)
+ {
+ if (STREQN (assign, export_env[i], equal_offset + 1))
+ {
+ free (export_env[i]);
+ export_env[i] = do_alloc ? savestring (assign) : assign;
+ return (export_env);
+ }
+ }
+ add_to_export_env (assign, do_alloc);
+ return (export_env);
+}
+
+static void
+add_temp_array_to_env (temp_array, do_alloc, do_supercede)
+ char **temp_array;
+ int do_alloc, do_supercede;
+{
+ register int i;
+
+ if (temp_array == 0)
+ return;
+
+ for (i = 0; temp_array[i]; i++)
+ {
+ if (do_supercede)
+ export_env = add_or_supercede_exported_var (temp_array[i], do_alloc);
+ else
+ add_to_export_env (temp_array[i], do_alloc);
+ }
+
+ free (temp_array);
+}
+
+/* Make the environment array for the command about to be executed, if the
+ array needs making. Otherwise, do nothing. If a shell action could
+ change the array that commands receive for their environment, then the
+ code should `array_needs_making++'.
+
+ The order to add to the array is:
+ temporary_env
+ list of var contexts whose head is shell_variables
+ shell_functions
+
+ This is the shell variable lookup order. We add only new variable
+ names at each step, which allows local variables and variables in
+ the temporary environments to shadow variables in the global (or
+ any previous) scope.
+*/
+
+static int
+n_shell_variables ()
+{
+ VAR_CONTEXT *vc;
+ int n;
+
+ for (n = 0, vc = shell_variables; vc; vc = vc->down)
+ n += HASH_ENTRIES (vc->table);
+ return n;
+}
+
+int
+chkexport (name)
+ char *name;
+{
+ SHELL_VAR *v;
+
+ v = find_variable (name);
+ if (v && exported_p (v))
+ {
+ array_needs_making = 1;
+ maybe_make_export_env ();
+ return 1;
+ }
+ return 0;
+}
+
+void
+maybe_make_export_env ()
+{
+ register char **temp_array;
+ int new_size;
+ VAR_CONTEXT *tcxt;
+
+ if (array_needs_making)
+ {
+ if (export_env)
+ strvec_flush (export_env);
+
+ /* Make a guess based on how many shell variables and functions we
+ have. Since there will always be array variables, and array
+ variables are not (yet) exported, this will always be big enough
+ for the exported variables and functions. */
+ new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 +
+ HASH_ENTRIES (temporary_env);
+ if (new_size > export_env_size)
+ {
+ export_env_size = new_size;
+ export_env = strvec_resize (export_env, export_env_size);
+ environ = export_env;
+ }
+ export_env[export_env_index = 0] = (char *)NULL;
+
+ /* Make a dummy variable context from the temporary_env, stick it on
+ the front of shell_variables, call make_var_export_array on the
+ whole thing to flatten it, and convert the list of SHELL_VAR *s
+ to the form needed by the environment. */
+ if (temporary_env)
+ {
+ tcxt = new_var_context ((char *)NULL, 0);
+ tcxt->table = temporary_env;
+ tcxt->down = shell_variables;
+ }
+ else
+ tcxt = shell_variables;
+
+ temp_array = make_var_export_array (tcxt);
+ if (temp_array)
+ add_temp_array_to_env (temp_array, 0, 0);
+
+ if (tcxt != shell_variables)
+ free (tcxt);
+
+#if defined (RESTRICTED_SHELL)
+ /* Restricted shells may not export shell functions. */
+ temp_array = restricted ? (char **)0 : make_func_export_array ();
+#else
+ temp_array = make_func_export_array ();
+#endif
+ if (temp_array)
+ add_temp_array_to_env (temp_array, 0, 0);
+
+ array_needs_making = 0;
+ }
+}
+
+/* This is an efficiency hack. PWD and OLDPWD are auto-exported, so
+ we will need to remake the exported environment every time we
+ change directories. `_' is always put into the environment for
+ every external command, so without special treatment it will always
+ cause the environment to be remade.
+
+ If there is no other reason to make the exported environment, we can
+ just update the variables in place and mark the exported environment
+ as no longer needing a remake. */
+void
+update_export_env_inplace (env_prefix, preflen, value)
+ char *env_prefix;
+ int preflen;
+ char *value;
+{
+ char *evar;
+
+ evar = (char *)xmalloc (STRLEN (value) + preflen + 1);
+ strcpy (evar, env_prefix);
+ if (value)
+ strcpy (evar + preflen, value);
+ export_env = add_or_supercede_exported_var (evar, 0);
+}
+
+/* We always put _ in the environment as the name of this command. */
+void
+put_command_name_into_env (command_name)
+ char *command_name;
+{
+ update_export_env_inplace ("_=", 2, command_name);
+}
+
+#if 0 /* UNUSED -- it caused too many problems */
+void
+put_gnu_argv_flags_into_env (pid, flags_string)
+ intmax_t pid;
+ char *flags_string;
+{
+ char *dummy, *pbuf;
+ int l, fl;
+
+ pbuf = itos (pid);
+ l = strlen (pbuf);
+
+ fl = strlen (flags_string);
+
+ dummy = (char *)xmalloc (l + fl + 30);
+ dummy[0] = '_';
+ strcpy (dummy + 1, pbuf);
+ strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_");
+ dummy[l + 27] = '=';
+ strcpy (dummy + l + 28, flags_string);
+
+ free (pbuf);
+
+ export_env = add_or_supercede_exported_var (dummy, 0);
+}
+#endif
+
+/* **************************************************************** */
+/* */
+/* Managing variable contexts */
+/* */
+/* **************************************************************** */
+
+/* Allocate and return a new variable context with NAME and FLAGS.
+ NAME can be NULL. */
+
+VAR_CONTEXT *
+new_var_context (name, flags)
+ char *name;
+ int flags;
+{
+ VAR_CONTEXT *vc;
+
+ vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT));
+ vc->name = name ? savestring (name) : (char *)NULL;
+ vc->scope = variable_context;
+ vc->flags = flags;
+
+ vc->up = vc->down = (VAR_CONTEXT *)NULL;
+ vc->table = (HASH_TABLE *)NULL;
+
+ return vc;
+}
+
+/* Free a variable context and its data, including the hash table. Dispose
+ all of the variables. */
+void
+dispose_var_context (vc)
+ VAR_CONTEXT *vc;
+{
+ FREE (vc->name);
+
+ if (vc->table)
+ {
+ delete_all_variables (vc->table);
+ hash_dispose (vc->table);
+ }
+
+ free (vc);
+}
+
+/* Set VAR's scope level to the current variable context. */
+static int
+set_context (var)
+ SHELL_VAR *var;
+{
+ return (var->context = variable_context);
+}
+
+/* Make a new variable context with NAME and FLAGS and a HASH_TABLE of
+ temporary variables, and push it onto shell_variables. This is
+ for shell functions. */
+VAR_CONTEXT *
+push_var_context (name, flags, tempvars)
+ char *name;
+ int flags;
+ HASH_TABLE *tempvars;
+{
+ VAR_CONTEXT *vc;
+
+ vc = new_var_context (name, flags);
+ vc->table = tempvars;
+ if (tempvars)
+ {
+ /* Have to do this because the temp environment was created before
+ variable_context was incremented. */
+ flatten (tempvars, set_context, (VARLIST *)NULL, 0);
+ vc->flags |= VC_HASTMPVAR;
+ }
+ vc->down = shell_variables;
+ shell_variables->up = vc;
+
+ return (shell_variables = vc);
+}
+
+static void
+push_func_var (data)
+ PTR_T data;
+{
+ SHELL_VAR *var, *v;
+
+ var = (SHELL_VAR *)data;
+
+ if (tempvar_p (var) && (posixly_correct || (var->attributes & att_propagate)))
+ {
+ /* Make sure we have a hash table to store the variable in while it is
+ being propagated down to the global variables table. Create one if
+ we have to */
+ if ((vc_isfuncenv (shell_variables) || vc_istempenv (shell_variables)) && shell_variables->table == 0)
+ shell_variables->table = hash_create (0);
+ /* XXX - should we set v->context here? */
+ v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0);
+ if (shell_variables == global_variables)
+ var->attributes &= ~(att_tempvar|att_propagate);
+ else
+ shell_variables->flags |= VC_HASTMPVAR;
+ v->attributes |= var->attributes;
+ }
+ else
+ stupidly_hack_special_variables (var->name); /* XXX */
+
+ dispose_variable (var);
+}
+
+/* Pop the top context off of VCXT and dispose of it, returning the rest of
+ the stack. */
+void
+pop_var_context ()
+{
+ VAR_CONTEXT *ret, *vcxt;
+
+ vcxt = shell_variables;
+ if (vc_isfuncenv (vcxt) == 0)
+ {
+ internal_error (_("pop_var_context: head of shell_variables not a function context"));
+ return;
+ }
+
+ if (ret = vcxt->down)
+ {
+ ret->up = (VAR_CONTEXT *)NULL;
+ shell_variables = ret;
+ if (vcxt->table)
+ hash_flush (vcxt->table, push_func_var);
+ dispose_var_context (vcxt);
+ }
+ else
+ internal_error (_("pop_var_context: no global_variables context"));
+}
+
+/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and
+ all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */
+void
+delete_all_contexts (vcxt)
+ VAR_CONTEXT *vcxt;
+{
+ VAR_CONTEXT *v, *t;
+
+ for (v = vcxt; v != global_variables; v = t)
+ {
+ t = v->down;
+ dispose_var_context (v);
+ }
+
+ delete_all_variables (global_variables->table);
+ shell_variables = global_variables;
+}
+
+/* **************************************************************** */
+/* */
+/* Pushing and Popping temporary variable scopes */
+/* */
+/* **************************************************************** */
+
+VAR_CONTEXT *
+push_scope (flags, tmpvars)
+ int flags;
+ HASH_TABLE *tmpvars;
+{
+ return (push_var_context ((char *)NULL, flags, tmpvars));
+}
+
+static void
+push_exported_var (data)
+ PTR_T data;
+{
+ SHELL_VAR *var, *v;
+
+ var = (SHELL_VAR *)data;
+
+ /* If a temp var had its export attribute set, or it's marked to be
+ propagated, bind it in the previous scope before disposing it. */
+ /* XXX - This isn't exactly right, because all tempenv variables have the
+ export attribute set. */
+#if 0
+ if (exported_p (var) || (var->attributes & att_propagate))
+#else
+ if (tempvar_p (var) && exported_p (var) && (var->attributes & att_propagate))
+#endif
+ {
+ var->attributes &= ~att_tempvar; /* XXX */
+ v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0);
+ if (shell_variables == global_variables)
+ var->attributes &= ~att_propagate;
+ v->attributes |= var->attributes;
+ }
+ else
+ stupidly_hack_special_variables (var->name); /* XXX */
+
+ dispose_variable (var);
+}
+
+void
+pop_scope (is_special)
+ int is_special;
+{
+ VAR_CONTEXT *vcxt, *ret;
+
+ vcxt = shell_variables;
+ if (vc_istempscope (vcxt) == 0)
+ {
+ internal_error (_("pop_scope: head of shell_variables not a temporary environment scope"));
+ return;
+ }
+
+ ret = vcxt->down;
+ if (ret)
+ ret->up = (VAR_CONTEXT *)NULL;
+
+ shell_variables = ret;
+
+ /* Now we can take care of merging variables in VCXT into set of scopes
+ whose head is RET (shell_variables). */
+ FREE (vcxt->name);
+ if (vcxt->table)
+ {
+ if (is_special)
+ hash_flush (vcxt->table, push_func_var);
+ else
+ hash_flush (vcxt->table, push_exported_var);
+ hash_dispose (vcxt->table);
+ }
+ free (vcxt);
+
+ sv_ifs ("IFS"); /* XXX here for now */
+}
+
+/* **************************************************************** */
+/* */
+/* Pushing and Popping function contexts */
+/* */
+/* **************************************************************** */
+
+static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL;
+static int dollar_arg_stack_slots;
+static int dollar_arg_stack_index;
+
+/* XXX - we might want to consider pushing and popping the `getopts' state
+ when we modify the positional parameters. */
+void
+push_context (name, is_subshell, tempvars)
+ char *name; /* function name */
+ int is_subshell;
+ HASH_TABLE *tempvars;
+{
+ if (is_subshell == 0)
+ push_dollar_vars ();
+ variable_context++;
+ push_var_context (name, VC_FUNCENV, tempvars);
+}
+
+/* Only called when subshell == 0, so we don't need to check, and can
+ unconditionally pop the dollar vars off the stack. */
+void
+pop_context ()
+{
+ pop_dollar_vars ();
+ variable_context--;
+ pop_var_context ();
+
+ sv_ifs ("IFS"); /* XXX here for now */
+}
+
+/* Save the existing positional parameters on a stack. */
+void
+push_dollar_vars ()
+{
+ if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots)
+ {
+ dollar_arg_stack = (WORD_LIST **)
+ xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10)
+ * sizeof (WORD_LIST *));
+ }
+ dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args ();
+ dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+}
+
+/* Restore the positional parameters from our stack. */
+void
+pop_dollar_vars ()
+{
+ if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+ return;
+
+ remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1);
+ dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
+ dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+ set_dollar_vars_unchanged ();
+}
+
+void
+dispose_saved_dollar_vars ()
+{
+ if (!dollar_arg_stack || dollar_arg_stack_index == 0)
+ return;
+
+ dispose_words (dollar_arg_stack[dollar_arg_stack_index]);
+ dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL;
+}
+
+/* Manipulate the special BASH_ARGV and BASH_ARGC variables. */
+
+void
+push_args (list)
+ WORD_LIST *list;
+{
+#if defined (ARRAY_VARS) && defined (DEBUGGER)
+ SHELL_VAR *bash_argv_v, *bash_argc_v;
+ ARRAY *bash_argv_a, *bash_argc_a;
+ WORD_LIST *l;
+ arrayind_t i;
+ char *t;
+
+ GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a);
+ GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a);
+
+ for (l = list, i = 0; l; l = l->next, i++)
+ array_push (bash_argv_a, l->word->word);
+
+ t = itos (i);
+ array_push (bash_argc_a, t);
+ free (t);
+#endif /* ARRAY_VARS && DEBUGGER */
+}
+
+/* Remove arguments from BASH_ARGV array. Pop top element off BASH_ARGC
+ array and use that value as the count of elements to remove from
+ BASH_ARGV. */
+void
+pop_args ()
+{
+#if defined (ARRAY_VARS) && defined (DEBUGGER)
+ SHELL_VAR *bash_argv_v, *bash_argc_v;
+ ARRAY *bash_argv_a, *bash_argc_a;
+ ARRAY_ELEMENT *ce;
+ intmax_t i;
+
+ GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a);
+ GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a);
+
+ ce = array_shift (bash_argc_a, 1, 0);
+ if (ce == 0 || legal_number (element_value (ce), &i) == 0)
+ i = 0;
+
+ for ( ; i > 0; i--)
+ array_pop (bash_argv_a);
+ array_dispose_element (ce);
+#endif /* ARRAY_VARS && DEBUGGER */
+}
+
+/*************************************************
+ * *
+ * Functions to manage special variables *
+ * *
+ *************************************************/
+
+/* Extern declarations for variables this code has to manage. */
+extern int eof_encountered, eof_encountered_limit, ignoreeof;
+
+#if defined (READLINE)
+extern int hostname_list_initialized;
+#endif
+
+/* An alist of name.function for each special variable. Most of the
+ functions don't do much, and in fact, this would be faster with a
+ switch statement, but by the end of this file, I am sick of switch
+ statements. */
+
+#define SET_INT_VAR(name, intvar) intvar = find_variable (name) != 0
+
+/* This table will be sorted with qsort() the first time it's accessed. */
+struct name_and_function {
+ char *name;
+ sh_sv_func_t *function;
+};
+
+static struct name_and_function special_vars[] = {
+ { "BASH_XTRACEFD", sv_xtracefd },
+
+#if defined (READLINE)
+# if defined (STRICT_POSIX)
+ { "COLUMNS", sv_winsize },
+# endif
+ { "COMP_WORDBREAKS", sv_comp_wordbreaks },
+#endif
+
+ { "FUNCNEST", sv_funcnest },
+
+ { "GLOBIGNORE", sv_globignore },
+
+#if defined (HISTORY)
+ { "HISTCONTROL", sv_history_control },
+ { "HISTFILESIZE", sv_histsize },
+ { "HISTIGNORE", sv_histignore },
+ { "HISTSIZE", sv_histsize },
+ { "HISTTIMEFORMAT", sv_histtimefmt },
+#endif
+
+#if defined (__CYGWIN__)
+ { "HOME", sv_home },
+#endif
+
+#if defined (READLINE)
+ { "HOSTFILE", sv_hostfile },
+#endif
+
+ { "IFS", sv_ifs },
+ { "IGNOREEOF", sv_ignoreeof },
+
+ { "LANG", sv_locale },
+ { "LC_ALL", sv_locale },
+ { "LC_COLLATE", sv_locale },
+ { "LC_CTYPE", sv_locale },
+ { "LC_MESSAGES", sv_locale },
+ { "LC_NUMERIC", sv_locale },
+ { "LC_TIME", sv_locale },
+
+#if defined (READLINE) && defined (STRICT_POSIX)
+ { "LINES", sv_winsize },
+#endif
+
+ { "MAIL", sv_mail },
+ { "MAILCHECK", sv_mail },
+ { "MAILPATH", sv_mail },
+
+ { "OPTERR", sv_opterr },
+ { "OPTIND", sv_optind },
+
+ { "PATH", sv_path },
+ { "POSIXLY_CORRECT", sv_strict_posix },
+
+#if defined (READLINE)
+ { "TERM", sv_terminal },
+ { "TERMCAP", sv_terminal },
+ { "TERMINFO", sv_terminal },
+#endif /* READLINE */
+
+ { "TEXTDOMAIN", sv_locale },
+ { "TEXTDOMAINDIR", sv_locale },
+
+#if defined (HAVE_TZSET)
+ { "TZ", sv_tz },
+#endif
+
+#if defined (HISTORY) && defined (BANG_HISTORY)
+ { "histchars", sv_histchars },
+#endif /* HISTORY && BANG_HISTORY */
+
+ { "ignoreeof", sv_ignoreeof },
+
+ { (char *)0, (sh_sv_func_t *)0 }
+};
+
+#define N_SPECIAL_VARS (sizeof (special_vars) / sizeof (special_vars[0]) - 1)
+
+static int
+sv_compare (sv1, sv2)
+ struct name_and_function *sv1, *sv2;
+{
+ int r;
+
+ if ((r = sv1->name[0] - sv2->name[0]) == 0)
+ r = strcmp (sv1->name, sv2->name);
+ return r;
+}
+
+static inline int
+find_special_var (name)
+ const char *name;
+{
+ register int i, r;
+
+ for (i = 0; special_vars[i].name; i++)
+ {
+ r = special_vars[i].name[0] - name[0];
+ if (r == 0)
+ r = strcmp (special_vars[i].name, name);
+ if (r == 0)
+ return i;
+ else if (r > 0)
+ /* Can't match any of rest of elements in sorted list. Take this out
+ if it causes problems in certain environments. */
+ break;
+ }
+ return -1;
+}
+
+/* The variable in NAME has just had its state changed. Check to see if it
+ is one of the special ones where something special happens. */
+void
+stupidly_hack_special_variables (name)
+ char *name;
+{
+ static int sv_sorted = 0;
+ int i;
+
+ if (sv_sorted == 0) /* shouldn't need, but it's fairly cheap. */
+ {
+ qsort (special_vars, N_SPECIAL_VARS, sizeof (special_vars[0]),
+ (QSFUNC *)sv_compare);
+ sv_sorted = 1;
+ }
+
+ i = find_special_var (name);
+ if (i != -1)
+ (*(special_vars[i].function)) (name);
+}
+
+/* Special variables that need hooks to be run when they are unset as part
+ of shell reinitialization should have their sv_ functions run here. */
+void
+reinit_special_variables ()
+{
+#if defined (READLINE)
+ sv_comp_wordbreaks ("COMP_WORDBREAKS");
+#endif
+ sv_globignore ("GLOBIGNORE");
+ sv_opterr ("OPTERR");
+}
+
+void
+sv_ifs (name)
+ char *name;
+{
+ SHELL_VAR *v;
+
+ v = find_variable ("IFS");
+ setifs (v);
+}
+
+/* What to do just after the PATH variable has changed. */
+void
+sv_path (name)
+ char *name;
+{
+ /* hash -r */
+ phash_flush ();
+}
+
+/* What to do just after one of the MAILxxxx variables has changed. NAME
+ is the name of the variable. This is called with NAME set to one of
+ MAIL, MAILCHECK, or MAILPATH. */
+void
+sv_mail (name)
+ char *name;
+{
+ /* If the time interval for checking the files has changed, then
+ reset the mail timer. Otherwise, one of the pathname vars
+ to the users mailbox has changed, so rebuild the array of
+ filenames. */
+ if (name[4] == 'C') /* if (strcmp (name, "MAILCHECK") == 0) */
+ reset_mail_timer ();
+ else
+ {
+ free_mail_files ();
+ remember_mail_dates ();
+ }
+}
+
+void
+sv_funcnest (name)
+ char *name;
+{
+ SHELL_VAR *v;
+ intmax_t num;
+
+ v = find_variable (name);
+ if (v == 0)
+ funcnest_max = 0;
+ else if (legal_number (value_cell (v), &num) == 0)
+ funcnest_max = 0;
+ else
+ funcnest_max = num;
+}
+
+/* What to do when GLOBIGNORE changes. */
+void
+sv_globignore (name)
+ char *name;
+{
+ if (privileged_mode == 0)
+ setup_glob_ignore (name);
+}
+
+#if defined (READLINE)
+void
+sv_comp_wordbreaks (name)
+ char *name;
+{
+ SHELL_VAR *sv;
+
+ sv = find_variable (name);
+ if (sv == 0)
+ reset_completer_word_break_chars ();
+}
+
+/* What to do just after one of the TERMxxx variables has changed.
+ If we are an interactive shell, then try to reset the terminal
+ information in readline. */
+void
+sv_terminal (name)
+ char *name;
+{
+ if (interactive_shell && no_line_editing == 0)
+ rl_reset_terminal (get_string_value ("TERM"));
+}
+
+void
+sv_hostfile (name)
+ char *name;
+{
+ SHELL_VAR *v;
+
+ v = find_variable (name);
+ if (v == 0)
+ clear_hostname_list ();
+ else
+ hostname_list_initialized = 0;
+}
+
+#if defined (STRICT_POSIX)
+/* In strict posix mode, we allow assignments to LINES and COLUMNS (and values
+ found in the initial environment) to override the terminal size reported by
+ the kernel. */
+void
+sv_winsize (name)
+ char *name;
+{
+ SHELL_VAR *v;
+ intmax_t xd;
+ int d;
+
+ if (posixly_correct == 0 || interactive_shell == 0 || no_line_editing)
+ return;
+
+ v = find_variable (name);
+ if (v == 0 || var_isnull (v))
+ rl_reset_screen_size ();
+ else
+ {
+ if (legal_number (value_cell (v), &xd) == 0)
+ return;
+ winsize_assignment = 1;
+ d = xd; /* truncate */
+ if (name[0] == 'L') /* LINES */
+ rl_set_screen_size (d, -1);
+ else /* COLUMNS */
+ rl_set_screen_size (-1, d);
+ winsize_assignment = 0;
+ }
+}
+#endif /* STRICT_POSIX */
+#endif /* READLINE */
+
+/* Update the value of HOME in the export environment so tilde expansion will
+ work on cygwin. */
+#if defined (__CYGWIN__)
+sv_home (name)
+ char *name;
+{
+ array_needs_making = 1;
+ maybe_make_export_env ();
+}
+#endif
+
+#if defined (HISTORY)
+/* What to do after the HISTSIZE or HISTFILESIZE variables change.
+ If there is a value for this HISTSIZE (and it is numeric), then stifle
+ the history. Otherwise, if there is NO value for this variable,
+ unstifle the history. If name is HISTFILESIZE, and its value is
+ numeric, truncate the history file to hold no more than that many
+ lines. */
+void
+sv_histsize (name)
+ char *name;
+{
+ char *temp;
+ intmax_t num;
+ int hmax;
+
+ temp = get_string_value (name);
+
+ if (temp && *temp)
+ {
+ if (legal_number (temp, &num))
+ {
+ hmax = num;
+ if (hmax < 0 && name[4] == 'S')
+ unstifle_history (); /* unstifle history if HISTSIZE < 0 */
+ else if (name[4] == 'S')
+ {
+ stifle_history (hmax);
+ hmax = where_history ();
+ if (history_lines_this_session > hmax)
+ history_lines_this_session = hmax;
+ }
+ else if (hmax >= 0) /* truncate HISTFILE if HISTFILESIZE >= 0 */
+ {
+ history_truncate_file (get_string_value ("HISTFILE"), hmax);
+ if (hmax <= history_lines_in_file)
+ history_lines_in_file = hmax;
+ }
+ }
+ }
+ else if (name[4] == 'S')
+ unstifle_history ();
+}
+
+/* What to do after the HISTIGNORE variable changes. */
+void
+sv_histignore (name)
+ char *name;
+{
+ setup_history_ignore (name);
+}
+
+/* What to do after the HISTCONTROL variable changes. */
+void
+sv_history_control (name)
+ char *name;
+{
+ char *temp;
+ char *val;
+ int tptr;
+
+ history_control = 0;
+ temp = get_string_value (name);
+
+ if (temp == 0 || *temp == 0)
+ return;
+
+ tptr = 0;
+ while (val = extract_colon_unit (temp, &tptr))
+ {
+ if (STREQ (val, "ignorespace"))
+ history_control |= HC_IGNSPACE;
+ else if (STREQ (val, "ignoredups"))
+ history_control |= HC_IGNDUPS;
+ else if (STREQ (val, "ignoreboth"))
+ history_control |= HC_IGNBOTH;
+ else if (STREQ (val, "erasedups"))
+ history_control |= HC_ERASEDUPS;
+
+ free (val);
+ }
+}
+
+#if defined (BANG_HISTORY)
+/* Setting/unsetting of the history expansion character. */
+void
+sv_histchars (name)
+ char *name;
+{
+ char *temp;
+
+ temp = get_string_value (name);
+ if (temp)
+ {
+ history_expansion_char = *temp;
+ if (temp[0] && temp[1])
+ {
+ history_subst_char = temp[1];
+ if (temp[2])
+ history_comment_char = temp[2];
+ }
+ }
+ else
+ {
+ history_expansion_char = '!';
+ history_subst_char = '^';
+ history_comment_char = '#';
+ }
+}
+#endif /* BANG_HISTORY */
+
+void
+sv_histtimefmt (name)
+ char *name;
+{
+ SHELL_VAR *v;
+
+ if (v = find_variable (name))
+ {
+ if (history_comment_char == 0)
+ history_comment_char = '#';
+ }
+ history_write_timestamps = (v != 0);
+}
+#endif /* HISTORY */
+
+#if defined (HAVE_TZSET)
+void
+sv_tz (name)
+ char *name;
+{
+ if (chkexport (name))
+ tzset ();
+}
+#endif
+
+/* If the variable exists, then the value of it can be the number
+ of times we actually ignore the EOF. The default is small,
+ (smaller than csh, anyway). */
+void
+sv_ignoreeof (name)
+ char *name;
+{
+ SHELL_VAR *tmp_var;
+ char *temp;
+
+ eof_encountered = 0;
+
+ tmp_var = find_variable (name);
+ ignoreeof = tmp_var != 0;
+ temp = tmp_var ? value_cell (tmp_var) : (char *)NULL;
+ if (temp)
+ eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10;
+ set_shellopts (); /* make sure `ignoreeof' is/is not in $SHELLOPTS */
+}
+
+void
+sv_optind (name)
+ char *name;
+{
+ char *tt;
+ int s;
+
+ tt = get_string_value ("OPTIND");
+ if (tt && *tt)
+ {
+ s = atoi (tt);
+
+ /* According to POSIX, setting OPTIND=1 resets the internal state
+ of getopt (). */
+ if (s < 0 || s == 1)
+ s = 0;
+ }
+ else
+ s = 0;
+ getopts_reset (s);
+}
+
+void
+sv_opterr (name)
+ char *name;
+{
+ char *tt;
+
+ tt = get_string_value ("OPTERR");
+ sh_opterr = (tt && *tt) ? atoi (tt) : 1;
+}
+
+void
+sv_strict_posix (name)
+ char *name;
+{
+ SET_INT_VAR (name, posixly_correct);
+ posix_initialize (posixly_correct);
+#if defined (READLINE)
+ if (interactive_shell)
+ posix_readline_initialize (posixly_correct);
+#endif /* READLINE */
+ set_shellopts (); /* make sure `posix' is/is not in $SHELLOPTS */
+}
+
+void
+sv_locale (name)
+ char *name;
+{
+ char *v;
+ int r;
+
+ v = get_string_value (name);
+ if (name[0] == 'L' && name[1] == 'A') /* LANG */
+ r = set_lang (name, v);
+ else
+ r = set_locale_var (name, v); /* LC_*, TEXTDOMAIN* */
+
+#if 1
+ if (r == 0 && posixly_correct)
+ last_command_exit_value = 1;
+#endif
+}
+
+#if defined (ARRAY_VARS)
+void
+set_pipestatus_array (ps, nproc)
+ int *ps;
+ int nproc;
+{
+ SHELL_VAR *v;
+ ARRAY *a;
+ ARRAY_ELEMENT *ae;
+ register int i;
+ char *t, tbuf[INT_STRLEN_BOUND(int) + 1];
+
+ v = find_variable ("PIPESTATUS");
+ if (v == 0)
+ v = make_new_array_variable ("PIPESTATUS");
+ if (array_p (v) == 0)
+ return; /* Do nothing if not an array variable. */
+ a = array_cell (v);
+
+ if (a == 0 || array_num_elements (a) == 0)
+ {
+ for (i = 0; i < nproc; i++) /* was ps[i] != -1, not i < nproc */
+ {
+ t = inttostr (ps[i], tbuf, sizeof (tbuf));
+ array_insert (a, i, t);
+ }
+ return;
+ }
+
+ /* Fast case */
+ if (array_num_elements (a) == nproc && nproc == 1)
+ {
+ ae = element_forw (a->head);
+ free (element_value (ae));
+ ae->value = itos (ps[0]);
+ }
+ else if (array_num_elements (a) <= nproc)
+ {
+ /* modify in array_num_elements members in place, then add */
+ ae = a->head;
+ for (i = 0; i < array_num_elements (a); i++)
+ {
+ ae = element_forw (ae);
+ free (element_value (ae));
+ ae->value = itos (ps[i]);
+ }
+ /* add any more */
+ for ( ; i < nproc; i++)
+ {
+ t = inttostr (ps[i], tbuf, sizeof (tbuf));
+ array_insert (a, i, t);
+ }
+ }
+ else
+ {
+ /* deleting elements. it's faster to rebuild the array. */
+ array_flush (a);
+ for (i = 0; ps[i] != -1; i++)
+ {
+ t = inttostr (ps[i], tbuf, sizeof (tbuf));
+ array_insert (a, i, t);
+ }
+ }
+}
+
+ARRAY *
+save_pipestatus_array ()
+{
+ SHELL_VAR *v;
+ ARRAY *a, *a2;
+
+ v = find_variable ("PIPESTATUS");
+ if (v == 0 || array_p (v) == 0 || array_cell (v) == 0)
+ return ((ARRAY *)NULL);
+
+ a = array_cell (v);
+ a2 = array_copy (array_cell (v));
+
+ return a2;
+}
+
+void
+restore_pipestatus_array (a)
+ ARRAY *a;
+{
+ SHELL_VAR *v;
+ ARRAY *a2;
+
+ v = find_variable ("PIPESTATUS");
+ /* XXX - should we still assign even if existing value is NULL? */
+ if (v == 0 || array_p (v) == 0 || array_cell (v) == 0)
+ return;
+
+ a2 = array_cell (v);
+ var_setarray (v, a);
+
+ array_dispose (a2);
+}
+#endif
+
+void
+set_pipestatus_from_exit (s)
+ int s;
+{
+#if defined (ARRAY_VARS)
+ static int v[2] = { 0, -1 };
+
+ v[0] = s;
+ set_pipestatus_array (v, 1);
+#endif
+}
+
+void
+sv_xtracefd (name)
+ char *name;
+{
+ SHELL_VAR *v;
+ char *t, *e;
+ int fd;
+ FILE *fp;
+
+ v = find_variable (name);
+ if (v == 0)
+ {
+ xtrace_reset ();
+ return;
+ }
+
+ t = value_cell (v);
+ if (t == 0 || *t == 0)
+ xtrace_reset ();
+ else
+ {
+ fd = (int)strtol (t, &e, 10);
+ if (e != t && *e == '\0' && sh_validfd (fd))
+ {
+ fp = fdopen (fd, "w");
+ if (fp == 0)
+ internal_error (_("%s: %s: cannot open as FILE"), name, value_cell (v));
+ else
+ xtrace_set (fd, fp);
+ }
+ else
+ internal_error (_("%s: %s: invalid value for trace file descriptor"), name, value_cell (v));
+ }
+}
/* variables.h -- data structures for shell variables. */
-/* Copyright (C) 1987-2010 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2012 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
#define uppercase_p(var) ((((var)->attributes) & (att_uppercase)))
#define lowercase_p(var) ((((var)->attributes) & (att_lowercase)))
#define capcase_p(var) ((((var)->attributes) & (att_capcase)))
+#define nameref_p(var) ((((var)->attributes) & (att_nameref)))
#define invisible_p(var) ((((var)->attributes) & (att_invisible)))
#define non_unsettable_p(var) ((((var)->attributes) & (att_nounset)))
#define function_cell(var) (COMMAND *)((var)->value)
#define array_cell(var) (ARRAY *)((var)->value)
#define assoc_cell(var) (HASH_TABLE *)((var)->value)
+#define nameref_cell(var) ((var)->value) /* so it can change later */
+
+#define NAMEREF_MAX 8 /* only 8 levels of nameref indirection */
#define var_isnull(var) ((var)->value == 0)
#define var_isset(var) ((var)->value != 0)
#define var_setfunc(var, func) ((var)->value = (char *)(func))
#define var_setarray(var, arr) ((var)->value = (char *)(arr))
#define var_setassoc(var, arr) ((var)->value = (char *)(arr))
+#define var_setref(var, str) ((var)->value = (str))
/* Make VAR be auto-exported. */
#define set_auto_export(var) \
extern SHELL_VAR *find_function __P((const char *));
extern FUNCTION_DEF *find_function_def __P((const char *));
extern SHELL_VAR *find_variable __P((const char *));
+extern SHELL_VAR *find_variable_noref __P((const char *));
+extern SHELL_VAR *find_variable_last_nameref __P((const char *));
+extern SHELL_VAR *find_global_variable_last_nameref __P((const char *));
+extern SHELL_VAR *find_variable_nameref __P((SHELL_VAR *));
extern SHELL_VAR *find_variable_internal __P((const char *, int));
extern SHELL_VAR *find_variable_tempenv __P((const char *));
extern SHELL_VAR *find_variable_notempenv __P((const char *));
extern SHELL_VAR *find_global_variable __P((const char *));
+extern SHELL_VAR *find_global_variable_noref __P((const char *));
extern SHELL_VAR *find_shell_variable __P((const char *));
extern SHELL_VAR *find_tempenv_variable __P((const char *));
extern SHELL_VAR *copy_variable __P((SHELL_VAR *));
--- /dev/null
+/* variables.h -- data structures for shell variables. */
+
+/* Copyright (C) 1987-2012 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 (_VARIABLES_H_)
+#define _VARIABLES_H_
+
+#include "stdc.h"
+#include "array.h"
+#include "assoc.h"
+
+/* Shell variables and functions are stored in hash tables. */
+#include "hashlib.h"
+
+#include "conftypes.h"
+
+/* A variable context. */
+typedef struct var_context {
+ char *name; /* empty or NULL means global context */
+ int scope; /* 0 means global context */
+ int flags;
+ struct var_context *up; /* previous function calls */
+ struct var_context *down; /* down towards global context */
+ HASH_TABLE *table; /* variables at this scope */
+} VAR_CONTEXT;
+
+/* Flags for var_context->flags */
+#define VC_HASLOCAL 0x01
+#define VC_HASTMPVAR 0x02
+#define VC_FUNCENV 0x04 /* also function if name != NULL */
+#define VC_BLTNENV 0x08 /* builtin_env */
+#define VC_TEMPENV 0x10 /* temporary_env */
+
+#define VC_TEMPFLAGS (VC_FUNCENV|VC_BLTNENV|VC_TEMPENV)
+
+/* Accessing macros */
+#define vc_isfuncenv(vc) (((vc)->flags & VC_FUNCENV) != 0)
+#define vc_isbltnenv(vc) (((vc)->flags & VC_BLTNENV) != 0)
+#define vc_istempenv(vc) (((vc)->flags & (VC_TEMPFLAGS)) == VC_TEMPENV)
+
+#define vc_istempscope(vc) (((vc)->flags & (VC_TEMPENV|VC_BLTNENV)) != 0)
+
+#define vc_haslocals(vc) (((vc)->flags & VC_HASLOCAL) != 0)
+#define vc_hastmpvars(vc) (((vc)->flags & VC_HASTMPVAR) != 0)
+
+/* What a shell variable looks like. */
+
+typedef struct variable *sh_var_value_func_t __P((struct variable *));
+typedef struct variable *sh_var_assign_func_t __P((struct variable *, char *, arrayind_t, char *));
+
+/* For the future */
+union _value {
+ char *s; /* string value */
+ intmax_t i; /* int value */
+ COMMAND *f; /* function */
+ ARRAY *a; /* array */
+ HASH_TABLE *h; /* associative array */
+ double d; /* floating point number */
+#if defined (HAVE_LONG_DOUBLE)
+ long double ld; /* long double */
+#endif
+ struct variable *v; /* possible indirect variable use */
+ void *opaque; /* opaque data for future use */
+};
+
+typedef struct variable {
+ char *name; /* Symbol that the user types. */
+ char *value; /* Value that is returned. */
+ char *exportstr; /* String for the environment. */
+ sh_var_value_func_t *dynamic_value; /* Function called to return a `dynamic'
+ value for a variable, like $SECONDS
+ or $RANDOM. */
+ sh_var_assign_func_t *assign_func; /* Function called when this `special
+ variable' is assigned a value in
+ bind_variable. */
+ int attributes; /* export, readonly, array, invisible... */
+ int context; /* Which context this variable belongs to. */
+} SHELL_VAR;
+
+typedef struct _vlist {
+ SHELL_VAR **list;
+ int list_size; /* allocated size */
+ int list_len; /* current number of entries */
+} VARLIST;
+
+/* The various attributes that a given variable can have. */
+/* First, the user-visible attributes */
+#define att_exported 0x0000001 /* export to environment */
+#define att_readonly 0x0000002 /* cannot change */
+#define att_array 0x0000004 /* value is an array */
+#define att_function 0x0000008 /* value is a function */
+#define att_integer 0x0000010 /* internal representation is int */
+#define att_local 0x0000020 /* variable is local to a function */
+#define att_assoc 0x0000040 /* variable is an associative array */
+#define att_trace 0x0000080 /* function is traced with DEBUG trap */
+#define att_uppercase 0x0000100 /* word converted to uppercase on assignment */
+#define att_lowercase 0x0000200 /* word converted to lowercase on assignment */
+#define att_capcase 0x0000400 /* word capitalized on assignment */
+#define att_nameref 0x0000800 /* word is a name reference */
+
+#define user_attrs (att_exported|att_readonly|att_integer|att_local|att_trace|att_uppercase|att_lowercase|att_capcase|att_nameref)
+
+#define attmask_user 0x0000fff
+
+/* Internal attributes used for bookkeeping */
+#define att_invisible 0x0001000 /* cannot see */
+#define att_nounset 0x0002000 /* cannot unset */
+#define att_noassign 0x0004000 /* assignment not allowed */
+#define att_imported 0x0008000 /* came from environment */
+#define att_special 0x0010000 /* requires special handling */
+#define att_nofree 0x0020000 /* do not free value on unset */
+
+#define attmask_int 0x00ff000
+
+/* Internal attributes used for variable scoping. */
+#define att_tempvar 0x0100000 /* variable came from the temp environment */
+#define att_propagate 0x0200000 /* propagate to previous scope */
+
+#define attmask_scope 0x0f00000
+
+#define exported_p(var) ((((var)->attributes) & (att_exported)))
+#define readonly_p(var) ((((var)->attributes) & (att_readonly)))
+#define array_p(var) ((((var)->attributes) & (att_array)))
+#define function_p(var) ((((var)->attributes) & (att_function)))
+#define integer_p(var) ((((var)->attributes) & (att_integer)))
+#define local_p(var) ((((var)->attributes) & (att_local)))
+#define assoc_p(var) ((((var)->attributes) & (att_assoc)))
+#define trace_p(var) ((((var)->attributes) & (att_trace)))
+#define uppercase_p(var) ((((var)->attributes) & (att_uppercase)))
+#define lowercase_p(var) ((((var)->attributes) & (att_lowercase)))
+#define capcase_p(var) ((((var)->attributes) & (att_capcase)))
+#define nameref_p(var) ((((var)->attributes) & (att_nameref)))
+
+#define invisible_p(var) ((((var)->attributes) & (att_invisible)))
+#define non_unsettable_p(var) ((((var)->attributes) & (att_nounset)))
+#define noassign_p(var) ((((var)->attributes) & (att_noassign)))
+#define imported_p(var) ((((var)->attributes) & (att_imported)))
+#define specialvar_p(var) ((((var)->attributes) & (att_special)))
+#define nofree_p(var) ((((var)->attributes) & (att_nofree)))
+
+#define tempvar_p(var) ((((var)->attributes) & (att_tempvar)))
+
+/* Acessing variable values: rvalues */
+#define value_cell(var) ((var)->value)
+#define function_cell(var) (COMMAND *)((var)->value)
+#define array_cell(var) (ARRAY *)((var)->value)
+#define assoc_cell(var) (HASH_TABLE *)((var)->value)
+#define nameref_cell(var) ((var)->value) /* so it can change later */
+
+#define NAMEREF_MAX 8 /* only 8 levels of nameref indirection */
+
+#define var_isnull(var) ((var)->value == 0)
+#define var_isset(var) ((var)->value != 0)
+
+/* Assigning variable values: lvalues */
+#define var_setvalue(var, str) ((var)->value = (str))
+#define var_setfunc(var, func) ((var)->value = (char *)(func))
+#define var_setarray(var, arr) ((var)->value = (char *)(arr))
+#define var_setassoc(var, arr) ((var)->value = (char *)(arr))
+#define var_setref(var, str) ((var)->value = (str))
+
+/* Make VAR be auto-exported. */
+#define set_auto_export(var) \
+ do { (var)->attributes |= att_exported; array_needs_making = 1; } while (0)
+
+#define SETVARATTR(var, attr, undo) \
+ ((undo == 0) ? ((var)->attributes |= (attr)) \
+ : ((var)->attributes &= ~(attr)))
+
+#define VSETATTR(var, attr) ((var)->attributes |= (attr))
+#define VUNSETATTR(var, attr) ((var)->attributes &= ~(attr))
+
+#define VGETFLAGS(var) ((var)->attributes)
+
+#define VSETFLAGS(var, flags) ((var)->attributes = (flags))
+#define VCLRFLAGS(var) ((var)->attributes = 0)
+
+/* Macros to perform various operations on `exportstr' member of a SHELL_VAR. */
+#define CLEAR_EXPORTSTR(var) (var)->exportstr = (char *)NULL
+#define COPY_EXPORTSTR(var) ((var)->exportstr) ? savestring ((var)->exportstr) : (char *)NULL
+#define SET_EXPORTSTR(var, value) (var)->exportstr = (value)
+#define SAVE_EXPORTSTR(var, value) (var)->exportstr = (value) ? savestring (value) : (char *)NULL
+
+#define FREE_EXPORTSTR(var) \
+ do { if ((var)->exportstr) free ((var)->exportstr); } while (0)
+
+#define CACHE_IMPORTSTR(var, value) \
+ (var)->exportstr = savestring (value)
+
+#define INVALIDATE_EXPORTSTR(var) \
+ do { \
+ if ((var)->exportstr) \
+ { \
+ free ((var)->exportstr); \
+ (var)->exportstr = (char *)NULL; \
+ } \
+ } while (0)
+
+/* Stuff for hacking variables. */
+typedef int sh_var_map_func_t __P((SHELL_VAR *));
+
+/* Where we keep the variables and functions */
+extern VAR_CONTEXT *global_variables;
+extern VAR_CONTEXT *shell_variables;
+
+extern HASH_TABLE *shell_functions;
+extern HASH_TABLE *temporary_env;
+
+extern int variable_context;
+extern char *dollar_vars[];
+extern char **export_env;
+
+extern void initialize_shell_variables __P((char **, int));
+extern SHELL_VAR *set_if_not __P((char *, char *));
+
+extern void sh_set_lines_and_columns __P((int, int));
+extern void set_pwd __P((void));
+extern void set_ppid __P((void));
+extern void make_funcname_visible __P((int));
+
+extern SHELL_VAR *var_lookup __P((const char *, VAR_CONTEXT *));
+
+extern SHELL_VAR *find_function __P((const char *));
+extern FUNCTION_DEF *find_function_def __P((const char *));
+extern SHELL_VAR *find_variable __P((const char *));
+extern SHELL_VAR *find_variable_noref __P((const char *));
+extern SHELL_VAR *find_variable_last_nameref __P((const char *));
+extern SHELL_VAR *find_variable_nameref __P((SHELL_VAR *));
+extern SHELL_VAR *find_variable_internal __P((const char *, int));
+extern SHELL_VAR *find_variable_tempenv __P((const char *));
+extern SHELL_VAR *find_variable_notempenv __P((const char *));
+extern SHELL_VAR *find_global_variable __P((const char *));
+extern SHELL_VAR *find_global_variable_noref __P((const char *));
+extern SHELL_VAR *find_shell_variable __P((const char *));
+extern SHELL_VAR *find_tempenv_variable __P((const char *));
+extern SHELL_VAR *copy_variable __P((SHELL_VAR *));
+extern SHELL_VAR *make_local_variable __P((const char *));
+extern SHELL_VAR *bind_variable __P((const char *, char *, int));
+extern SHELL_VAR *bind_function __P((const char *, COMMAND *));
+
+extern void bind_function_def __P((const char *, FUNCTION_DEF *));
+
+extern SHELL_VAR **map_over __P((sh_var_map_func_t *, VAR_CONTEXT *));
+SHELL_VAR **map_over_funcs __P((sh_var_map_func_t *));
+
+extern SHELL_VAR **all_shell_variables __P((void));
+extern SHELL_VAR **all_shell_functions __P((void));
+extern SHELL_VAR **all_visible_variables __P((void));
+extern SHELL_VAR **all_visible_functions __P((void));
+extern SHELL_VAR **all_exported_variables __P((void));
+extern SHELL_VAR **local_exported_variables __P((void));
+extern SHELL_VAR **all_local_variables __P((void));
+#if defined (ARRAY_VARS)
+extern SHELL_VAR **all_array_variables __P((void));
+#endif
+extern char **all_variables_matching_prefix __P((const char *));
+
+extern char **make_var_array __P((HASH_TABLE *));
+extern char **add_or_supercede_exported_var __P((char *, int));
+
+extern char *get_variable_value __P((SHELL_VAR *));
+extern char *get_string_value __P((const char *));
+extern char *sh_get_env_value __P((const char *));
+extern char *make_variable_value __P((SHELL_VAR *, char *, int));
+
+extern SHELL_VAR *bind_variable_value __P((SHELL_VAR *, char *, int));
+extern SHELL_VAR *bind_int_variable __P((char *, char *));
+extern SHELL_VAR *bind_var_to_int __P((char *, intmax_t));
+
+extern int assign_in_env __P((WORD_DESC *, int));
+
+extern int unbind_variable __P((const char *));
+extern int unbind_func __P((const char *));
+extern int unbind_function_def __P((const char *));
+extern int makunbound __P((const char *, VAR_CONTEXT *));
+extern int kill_local_variable __P((const char *));
+extern void delete_all_variables __P((HASH_TABLE *));
+extern void delete_all_contexts __P((VAR_CONTEXT *));
+
+extern VAR_CONTEXT *new_var_context __P((char *, int));
+extern void dispose_var_context __P((VAR_CONTEXT *));
+extern VAR_CONTEXT *push_var_context __P((char *, int, HASH_TABLE *));
+extern void pop_var_context __P((void));
+extern VAR_CONTEXT *push_scope __P((int, HASH_TABLE *));
+extern void pop_scope __P((int));
+
+extern void push_context __P((char *, int, HASH_TABLE *));
+extern void pop_context __P((void));
+extern void push_dollar_vars __P((void));
+extern void pop_dollar_vars __P((void));
+extern void dispose_saved_dollar_vars __P((void));
+
+extern void push_args __P((WORD_LIST *));
+extern void pop_args __P((void));
+
+extern void adjust_shell_level __P((int));
+extern void non_unsettable __P((char *));
+extern void dispose_variable __P((SHELL_VAR *));
+extern void dispose_used_env_vars __P((void));
+extern void dispose_function_env __P((void));
+extern void dispose_builtin_env __P((void));
+extern void merge_temporary_env __P((void));
+extern void merge_builtin_env __P((void));
+extern void kill_all_local_variables __P((void));
+
+extern void set_var_read_only __P((char *));
+extern void set_func_read_only __P((const char *));
+extern void set_var_auto_export __P((char *));
+extern void set_func_auto_export __P((const char *));
+
+extern void sort_variables __P((SHELL_VAR **));
+
+extern int chkexport __P((char *));
+extern void maybe_make_export_env __P((void));
+extern void update_export_env_inplace __P((char *, int, char *));
+extern void put_command_name_into_env __P((char *));
+extern void put_gnu_argv_flags_into_env __P((intmax_t, char *));
+
+extern void print_var_list __P((SHELL_VAR **));
+extern void print_func_list __P((SHELL_VAR **));
+extern void print_assignment __P((SHELL_VAR *));
+extern void print_var_value __P((SHELL_VAR *, int));
+extern void print_var_function __P((SHELL_VAR *));
+
+#if defined (ARRAY_VARS)
+extern SHELL_VAR *make_new_array_variable __P((char *));
+extern SHELL_VAR *make_local_array_variable __P((char *, int));
+
+extern SHELL_VAR *make_new_assoc_variable __P((char *));
+extern SHELL_VAR *make_local_assoc_variable __P((char *));
+
+extern void set_pipestatus_array __P((int *, int));
+extern ARRAY *save_pipestatus_array __P((void));
+extern void restore_pipestatus_array __P((ARRAY *));
+#endif
+
+extern void set_pipestatus_from_exit __P((int));
+
+/* The variable in NAME has just had its state changed. Check to see if it
+ is one of the special ones where something special happens. */
+extern void stupidly_hack_special_variables __P((char *));
+
+/* Reinitialize some special variables that have external effects upon unset
+ when the shell reinitializes itself. */
+extern void reinit_special_variables __P((void));
+
+extern int get_random_number __P((void));
+
+/* The `special variable' functions that get called when a particular
+ variable is set. */
+extern void sv_ifs __P((char *));
+extern void sv_path __P((char *));
+extern void sv_mail __P((char *));
+extern void sv_funcnest __P((char *));
+extern void sv_globignore __P((char *));
+extern void sv_ignoreeof __P((char *));
+extern void sv_strict_posix __P((char *));
+extern void sv_optind __P((char *));
+extern void sv_opterr __P((char *));
+extern void sv_locale __P((char *));
+extern void sv_xtracefd __P((char *));
+
+#if defined (READLINE)
+extern void sv_comp_wordbreaks __P((char *));
+extern void sv_terminal __P((char *));
+extern void sv_hostfile __P((char *));
+extern void sv_winsize __P((char *));
+#endif
+
+#if defined (__CYGWIN__)
+extern void sv_home __P((char *));
+#endif
+
+#if defined (HISTORY)
+extern void sv_histsize __P((char *));
+extern void sv_histignore __P((char *));
+extern void sv_history_control __P((char *));
+# if defined (BANG_HISTORY)
+extern void sv_histchars __P((char *));
+# endif
+extern void sv_histtimefmt __P((char *));
+#endif /* HISTORY */
+
+#if defined (HAVE_TZSET)
+extern void sv_tz __P((char *));
+#endif
+
+#endif /* !_VARIABLES_H_ */