]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
commit bash-20100615 snapshot
authorChet Ramey <chet.ramey@case.edu>
Tue, 13 Dec 2011 03:01:09 +0000 (22:01 -0500)
committerChet Ramey <chet.ramey@case.edu>
Tue, 13 Dec 2011 03:01:09 +0000 (22:01 -0500)
33 files changed:
CWRU/CWRU.chlog
CWRU/CWRU.chlog~
MANIFEST
MANIFEST~
builtins/Makefile.in
builtins/Makefile.in~ [new file with mode: 0644]
execute_cmd.c
execute_cmd.c~
parse.y
parse.y.new [new file with mode: 0644]
parse.y.save
parse.y~
po/zh_CN.po
subst.c
subst.c~
tests/builtins.right
tests/builtins.tests
tests/builtins.tests~ [new file with mode: 0644]
tests/builtins3.sub [new file with mode: 0644]
tests/new-exp.right
tests/new-exp.tests
tests/new-exp.tests~ [new file with mode: 0644]
tests/posixexp.right
tests/posixexp.tests
tests/posixexp.tests~ [new file with mode: 0644]
tests/posixpipe.right [new file with mode: 0644]
tests/posixpipe.tests [new file with mode: 0644]
tests/posixpipe.tests~ [new file with mode: 0644]
tests/run-minimal
tests/run-minimal~ [new file with mode: 0644]
tests/run-posixpipe [new file with mode: 0644]
tests/run-posixpipe~ [new file with mode: 0644]
tests/trap.right

index 3790fca9fe3e9aeef42bada45e7ff6c7e3b8760f..3c810ba64714187087a6bb557b3be73d5878805c 100644 (file)
@@ -10109,4 +10109,42 @@ variables.c
 lib/readline/complete.c
        - change get_y_or_n to always return 1 when in callback mode, so we
          don't do a blocking read.  Have to wait until readline-7.0 to add
-         a state, since that will change public interface
+         a state so we can use callbacks, since that will change public
+         interface
+
+                                  6/17
+                                  ----
+subst.c
+       - fix memory leak in parameter_brace_expand: when performing pattern
+         removal with parameter_brace_remove_pattern, make sure `name' is
+         freed.  Fixes bug reported by oyvindh@dhampir.no
+
+                                  6/23
+                                  ----
+{parse.y,subst.c}
+       - make the ${param//pat/rep}, ${param^pat}, and ${param,pat} expansions
+         require single quotes and double quotes to match when within double
+         quotes.  This way every expansion except the Posix ones behaves as
+         bash has always behaved
+
+subst.c
+       - change remove_upattern and remove_wpattern to return their first
+         argument if nothing matches, change callers to allocate memory
+         appropriately
+       - change remove_pattern to short-circuit and return copy of PARAM
+         if remove_wpattern returns its first argument (indicating no match)
+         rather than convert back to multibyte string, allocating new memory
+         twice and calling wcsrtombs
+
+                                  6/24
+                                  ----
+execute_cmd.c
+       - add missing initializers for sh_coproc to eliminate a compiler
+         warning.  Patch from Werner Fink <werner@suse.de>
+
+                                  6/27
+                                  ----
+parse.y
+       - add `TIMEIGN' token to handle `time -p -- ...'.  Pointed out by
+         Laszlo Ersek <lacos@caesar.elte.hu> on austin-group list
+
index 88d41469a068644fcb371b3e8c5392ed9eb3d15e..afc06d6eda53cda56b2eca922bc1d300854f9f49 100644 (file)
@@ -10103,3 +10103,41 @@ variables.c
        - make sure initialize_shell_variables calls sv_xtracefd if
          BASH_XTRACEFD is inherited in the shell environment.  Fixes but
          reported by <jsunx1@bellsouth.net>
+
+                                  6/13
+                                  ----
+lib/readline/complete.c
+       - change get_y_or_n to always return 1 when in callback mode, so we
+         don't do a blocking read.  Have to wait until readline-7.0 to add
+         a state so we can use callbacks, since that will change public
+         interface
+
+                                  6/17
+                                  ----
+subst.c
+       - fix memory leak in parameter_brace_expand: when performing pattern
+         removal with parameter_brace_remove_pattern, make sure `name' is
+         freed.  Fixes bug reported by oyvindh@dhampir.no
+
+                                  6/23
+                                  ----
+{parse.y,subst.c}
+       - make the ${param//pat/rep}, ${param^pat}, and ${param,pat} expansions
+         require single quotes and double quotes to match when within double
+         quotes.  This way every expansion except the Posix ones behaves as
+         bash has always behaved
+
+subst.c
+       - change remove_upattern and remove_wpattern to return their first
+         argument if nothing matches, change callers to allocate memory
+         appropriately
+       - change remove_pattern to short-circuit and return copy of PARAM
+         if remove_wpattern returns its first argument (indicating no match)
+         rather than convert back to multibyte string, allocating new memory
+         twice and calling wcsrtombs
+
+                                  6/24
+                                  ----
+execute_cmd.c
+       - add missing initializers for sh_coproc to eliminate a compiler
+         warning.  Patch from Werner Fink <werner@suse.de>
index 84e789af0a06997fdd360304d22aba76255daa3a..d8c2d99f0ed8df984341f4955a534972975564a3 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -778,6 +778,7 @@ 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
@@ -941,6 +942,8 @@ tests/posixexp.tests        f
 tests/posixexp.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
@@ -1032,6 +1035,7 @@ tests/run-nquote5 f
 tests/run-posix2       f
 tests/run-posixexp     f
 tests/run-posixpat     f
+tests/run-posixpipe    f
 tests/run-precedence   f
 tests/run-printf       f
 tests/run-quote                f
index c9e3a71c6919ac4aa46dfdf754db35797f9b63c7..a626098bfd81fb2a357260eaacfac8ce42d224d7 100644 (file)
--- a/MANIFEST~
+++ b/MANIFEST~
@@ -439,6 +439,7 @@ 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
@@ -940,6 +941,8 @@ tests/posixexp.tests        f
 tests/posixexp.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
@@ -1031,6 +1034,7 @@ tests/run-nquote5 f
 tests/run-posix2       f
 tests/run-posixexp     f
 tests/run-posixpat     f
+tests/run-posixpipe    f
 tests/run-precedence   f
 tests/run-printf       f
 tests/run-quote                f
index 42f8cb8229d902b3b869e5d8799e9dfcf70da703..91dc4983f986492d9f089b56634133c64b850e35 100644 (file)
@@ -334,6 +334,7 @@ evalstring.o: $(topdir)/externs.h $(topdir)/jobs.h $(topdir)/builtins.h
 evalstring.o: $(topdir)/flags.h $(topdir)/input.h $(topdir)/execute_cmd.h
 evalstring.o: $(topdir)/bashhist.h $(srcdir)/common.h
 evalstring.o: $(topdir)/trap.h $(topdir)/redir.h ../pathnames.h
+evalstring.o: $(BUILD_DIR)/y.tab.h
 getopt.o: ../config.h $(BASHINCDIR)/memalloc.h
 getopt.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/bashjmp.h $(topdir)/command.h
 getopt.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/error.h $(topdir)/variables.h $(topdir)/conftypes.h
diff --git a/builtins/Makefile.in~ b/builtins/Makefile.in~
new file mode 100644 (file)
index 0000000..42f8cb8
--- /dev/null
@@ -0,0 +1,659 @@
+# This Makefile for building libbuiltins.a is in -*- text -*- for Emacs.
+#
+# Copyright (C) 1996-2009 Free Software Foundation, Inc.     
+
+#   This program 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.
+
+#   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+PACKAGE = @PACKAGE_NAME@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+
+#
+SHELL = @MAKE_SHELL@
+RANLIB = @RANLIB@
+CC = @CC@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+AR = @AR@
+ARFLAGS = @ARFLAGS@
+RM = rm -f
+CP = cp
+
+EXEEXT = @EXEEXT@
+
+prefix = @prefix@
+
+srcdir = @srcdir@
+VPATH = .:@srcdir@
+topdir = @top_srcdir@
+
+datarootdir = @datarootdir@
+
+includedir = @includedir@
+datadir = @datadir@
+localedir = @localedir@
+
+# Support an alternate destination root directory for package building
+DESTDIR =
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+BUILD_DIR = @BUILD_DIR@
+
+LIBBUILD = ${BUILD_DIR}/lib
+
+PROFILE_FLAGS = @PROFILE_FLAGS@
+CFLAGS = @CFLAGS@
+CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@ @CROSS_COMPILE@
+CPPFLAGS = @CPPFLAGS@
+CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@
+LOCAL_CFLAGS = @LOCAL_CFLAGS@ ${DEBUG}
+DEFS = @DEFS@
+LOCAL_DEFS = @LOCAL_DEFS@
+
+LIBS = @LIBS@
+LDFLAGS = @LDFLAGS@ $(LOCAL_LDFLAGS) $(CFLAGS)
+LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@ $(LOCAL_LDFLAGS) $(CFLAGS_FOR_BUILD)
+LOCAL_LDFLAGS = @LOCAL_LDFLAGS@
+#LIBS_FOR_BUILD = @LIBS_FOR_BUILD@
+LIBS_FOR_BUILD = $(LIBS)
+
+BASHINCDIR = ${topdir}/include
+
+RL_INCLUDEDIR = @RL_INCLUDEDIR@
+
+INTL_LIBSRC = ${topdir}/lib/intl
+INTL_BUILDDIR = ${LIBBUILD}/intl
+INTL_LIBDIR = ${INTL_BUILDDIR}
+INTL_LIBRARY = ${INTL_BUILDDIR}/libintl.a
+INTL_INC = @INTL_INC@
+INTL_DEP = @INTL_DEP@
+LIBINTL_H = @LIBINTL_H@
+
+HELPDIR = @HELPDIR@
+MKDIRS = ${topdir}/support/mkdirs
+
+INCLUDES = -I. -I.. @RL_INCLUDE@ -I$(topdir) -I$(BASHINCDIR) -I$(topdir)/lib -I$(srcdir) ${INTL_INC}
+
+BASE_CCFLAGS = ${PROFILE_FLAGS} $(DEFS) $(LOCAL_DEFS) $(SYSTEM_FLAGS) \
+        ${INCLUDES} $(LOCAL_CFLAGS)
+
+CCFLAGS = $(BASE_CCFLAGS) $(CPPFLAGS) $(CFLAGS)
+
+CCFLAGS_FOR_BUILD = $(BASE_CCFLAGS) $(CPPFLAGS_FOR_BUILD) $(CFLAGS_FOR_BUILD)
+
+GCC_LINT_FLAGS = -Wall -Wshadow -Wpointer-arith -Wcast-qual \
+                -Wcast-align -Wstrict-prototypes -Wconversion \
+                -Wmissing-prototypes -Wtraditional -Wredundant-decls -pedantic
+
+MKBUILTINS = mkbuiltins$(EXEEXT)
+DIRECTDEFINE = -D $(srcdir)
+HELPDIRDEFINE = @HELPDIRDEFINE@
+HELPSTRINGS = @HELPSTRINGS@
+
+# xxx this is bad style
+RL_LIBSRC = $(topdir)/lib/readline
+
+.SUFFIXES:
+.SUFFIXES: .def .c .o
+# How to make a .o file from a .def file.
+.def.o:
+       $(RM) $@
+       ./$(MKBUILTINS) $(DIRECTDEFINE) $<
+       $(CC) -c $(CCFLAGS) $*.c || ( $(RM) $*.c ; exit 1 )
+       $(RM) $*.c
+
+# How to make a .c file from a .def file.
+.def.c:
+       $(RM) $@
+       ./$(MKBUILTINS) $(DIRECTDEFINE) $<
+
+# default rule for making a .o file from a .c file
+.c.o:
+       $(RM) $@
+       $(CC) -c $(CCFLAGS) $<
+
+DEFSRC =  $(srcdir)/alias.def $(srcdir)/bind.def $(srcdir)/break.def \
+         $(srcdir)/builtin.def $(srcdir)/caller.def \
+         $(srcdir)/cd.def $(srcdir)/colon.def \
+         $(srcdir)/command.def $(srcdir)/declare.def $(srcdir)/echo.def \
+         $(srcdir)/enable.def $(srcdir)/eval.def $(srcdir)/getopts.def \
+         $(srcdir)/exec.def $(srcdir)/exit.def $(srcdir)/fc.def \
+         $(srcdir)/fg_bg.def $(srcdir)/hash.def $(srcdir)/help.def \
+         $(srcdir)/history.def $(srcdir)/jobs.def $(srcdir)/kill.def \
+         $(srcdir)/let.def $(srcdir)/read.def $(srcdir)/return.def \
+         $(srcdir)/set.def $(srcdir)/setattr.def $(srcdir)/shift.def \
+         $(srcdir)/source.def $(srcdir)/suspend.def $(srcdir)/test.def \
+         $(srcdir)/times.def $(srcdir)/trap.def $(srcdir)/type.def \
+         $(srcdir)/ulimit.def $(srcdir)/umask.def $(srcdir)/wait.def \
+         $(srcdir)/reserved.def $(srcdir)/pushd.def $(srcdir)/shopt.def \
+         $(srcdir)/printf.def $(srcdir)/complete.def $(srcdir)/mapfile.def
+
+STATIC_SOURCE = common.c evalstring.c evalfile.c getopt.c bashgetopt.c \
+               getopt.h 
+
+OFILES = builtins.o \
+       alias.o bind.o break.o builtin.o caller.o cd.o colon.o command.o \
+       common.o declare.o echo.o enable.o eval.o evalfile.o \
+       evalstring.o exec.o exit.o fc.o fg_bg.o hash.o help.o history.o \
+       jobs.o kill.o let.o mapfile.o \
+       pushd.o read.o return.o set.o setattr.o shift.o source.o \
+       suspend.o test.o times.o trap.o type.o ulimit.o umask.o \
+       wait.o getopts.o shopt.o printf.o getopt.o bashgetopt.o complete.o
+
+CREATED_FILES = builtext.h builtins.c psize.aux pipesize.h
+
+all: $(MKBUILTINS) libbuiltins.a
+
+libbuiltins.a: $(MKBUILTINS) $(OFILES) builtins.o
+       $(RM) $@
+       $(AR) $(ARFLAGS) $@ $(OFILES)
+       -$(RANLIB) $@
+
+builtext.h builtins.c: $(MKBUILTINS) $(DEFSRC)
+       @-if test -f builtins.c; then mv -f builtins.c old-builtins.c; fi
+       @-if test -f builtext.h; then mv -f builtext.h old-builtext.h; fi
+       ./$(MKBUILTINS) -externfile builtext.h -structfile builtins.c \
+           -noproduction $(DIRECTDEFINE) $(HELPDIRDEFINE) $(HELPSTRINGS) $(DEFSRC)
+       @-if cmp -s old-builtext.h builtext.h 2>/dev/null; then \
+               mv old-builtext.h builtext.h; \
+        else \
+               $(RM) old-builtext.h; \
+        fi
+       @-if cmp -s old-builtins.c builtins.c 2>/dev/null; then \
+               mv old-builtins.c builtins.c; \
+        else \
+               $(RM) old-builtins.c; \
+        fi
+
+helpdoc:       $(MKBUILTINS) $(DEFSRC)
+       ./$(MKBUILTINS) ${HELPDIRDEFINE} -noproduction $(DIRECTDEFINE) $(DEFSRC)
+
+install-help:
+       @-if test -n "${HELPDIR}" && test -d helpfiles ; then \
+               test -d $(DESTDIR)${HELPDIR} || ${SHELL} ${MKDIRS} $(DESTDIR)$(HELPDIR) ;\
+               ( for f in helpfiles/*; do \
+                       echo installing $$f; \
+                       ${INSTALL_DATA} $$f $(DESTDIR)$(HELPDIR); \
+                 done; ) ; \
+       fi
+
+install:       @HELPINSTALL@
+
+mkbuiltins.o: ../config.h
+mkbuiltins.o: mkbuiltins.c
+       $(RM) $@
+       $(CC_FOR_BUILD) -c $(CCFLAGS_FOR_BUILD) $<
+
+mkbuiltins$(EXEEXT): mkbuiltins.o
+       $(CC_FOR_BUILD) $(LDFLAGS_FOR_BUILD) -o $(MKBUILTINS) mkbuiltins.o $(LIBS_FOR_BUILD)
+
+# rules for deficient makes, like SunOS
+mkbuiltins.o: mkbuiltins.c
+builtins.o: builtins.c
+common.o: common.c
+bashgetopt.o: bashgetopt.c
+getopt.o: getopt.c
+evalstring.o: evalstring.c          
+evalfile.o: evalfile.c  
+
+ulimit.o: pipesize.h
+
+pipesize.h:    psize.aux
+       $(SHELL) $(srcdir)/psize.sh > $@
+
+psize.aux:     psize.c
+       $(CC_FOR_BUILD) $(CCFLAGS_FOR_BUILD) -o $@ $(srcdir)/psize.c
+
+documentation: builtins.texi
+
+builtins.texi: $(MKBUILTINS)
+       ./$(MKBUILTINS) -documentonly $(DEFSRC)
+
+clean:
+       $(RM) $(OFILES) $(CREATED_FILES) $(MKBUILTINS) mkbuiltins.o libbuiltins.a
+       -test -d helpfiles && $(RM) -r helpfiles
+
+mostlyclean: 
+       $(RM) $(OFILES) libbuiltins.a
+
+distclean maintainer-clean: clean
+       $(RM) Makefile
+
+$(OFILES):     $(MKBUILTINS) ../config.h
+
+../version.h: ../config.h ../Makefile Makefile
+       -( cd ${BUILD_DIR} && ${MAKE} ${MFLAGS} version.h )
+
+# maintainer special - for now
+po:    builtins.c
+       xgettext -L C -o $(topdir)/po/builtins.pot --keyword='N_' builtins.c 2>/dev/null
+
+${LIBINTL_H}:
+       @echo making $@ in ${INTL_BUILDDIR}
+       @(cd ${INTL_BUILDDIR} && \
+               $(MAKE) $(MFLAGS) libintl.h) || exit 1
+
+# dependencies
+
+alias.o: alias.def
+bind.o: bind.def
+break.o: break.def
+builtin.o: builtin.def
+caller.o: caller.def
+cd.o: cd.def
+colon.o: colon.def
+command.o: command.def
+declare.o: declare.def
+echo.o: echo.def
+enable.o: enable.def
+eval.o: eval.def
+exec.o: exec.def
+exit.o: exit.def
+fc.o: fc.def
+fg_bg.o: fg_bg.def
+hash.o: hash.def
+help.o: help.def
+history.o: history.def
+jobs.o: jobs.def
+kill.o: kill.def
+let.o: let.def
+mapfile.o: mapfile.def
+printf.o: printf.def
+pushd.o: pushd.def
+read.o: read.def
+return.o: return.def
+set.o: set.def
+setattr.o: setattr.def
+shift.o: shift.def
+shopt.o: shopt.def
+source.o: source.def
+suspend.o: suspend.def
+test.o: test.def
+times.o: times.def
+trap.o: trap.def
+type.o: type.def
+ulimit.o: ulimit.def
+umask.o: umask.def
+wait.o: wait.def
+getopts.o: getopts.def
+reserved.o: reserved.def
+complete.o: complete.def
+
+# C files
+bashgetopt.o: ../config.h $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h
+bashgetopt.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/bashjmp.h
+bashgetopt.o: $(topdir)/command.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/error.h
+bashgetopt.o: $(topdir)/variables.h $(topdir)/conftypes.h $(topdir)/quit.h $(BASHINCDIR)/maxpath.h
+bashgetopt.o: $(topdir)/unwind_prot.h $(topdir)/dispose_cmd.h
+bashgetopt.o: $(topdir)/make_cmd.h $(topdir)/subst.h $(topdir)/sig.h
+bashgetopt.o: ../pathnames.h $(topdir)/externs.h $(srcdir)/common.h
+bashgetopt.o: $(BASHINCDIR)/chartypes.h
+common.o: $(topdir)/bashtypes.h $(BASHINCDIR)/posixstat.h $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h
+common.o: $(topdir)/shell.h $(topdir)/syntax.h ../config.h $(topdir)/bashjmp.h $(BASHINCDIR)/posixjmp.h
+common.o: $(topdir)/sig.h $(topdir)/command.h
+common.o: $(topdir)/general.h $(topdir)/xmalloc.h $(BASHINCDIR)/stdc.h $(BASHINCDIR)/memalloc.h
+common.o: $(topdir)/variables.h $(topdir)/conftypes.h $(topdir)/input.h
+common.o: $(topdir)/siglist.h $(topdir)/bashhist.h $(topdir)/quit.h
+common.o: $(topdir)/unwind_prot.h $(BASHINCDIR)/maxpath.h $(topdir)/jobs.h
+common.o: $(topdir)/builtins.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+common.o: $(topdir)/subst.h $(topdir)/execute_cmd.h $(topdir)/error.h
+common.o: $(topdir)/externs.h ../pathnames.h ./builtext.h
+common.o: $(BASHINCDIR)/chartypes.h
+evalfile.o: $(topdir)/bashtypes.h $(BASHINCDIR)/posixstat.h ${BASHINCDIR}/filecntl.h
+evalfile.o: $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h
+evalfile.o: $(topdir)/shell.h $(topdir)/syntax.h ../config.h $(topdir)/bashjmp.h
+evalfile.o: $(topdir)/command.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/error.h
+evalfile.o: $(topdir)/variables.h $(topdir)/conftypes.h $(topdir)/quit.h $(BASHINCDIR)/maxpath.h
+evalfile.o: $(topdir)/unwind_prot.h $(topdir)/dispose_cmd.h
+evalfile.o: $(topdir)/make_cmd.h $(topdir)/subst.h $(topdir)/sig.h
+evalfile.o: ../pathnames.h $(topdir)/externs.h
+evalfile.o: $(topdir)/jobs.h $(topdir)/builtins.h $(topdir)/flags.h
+evalfile.o: $(topdir)/input.h $(topdir)/execute_cmd.h
+evalfile.o: $(topdir)/bashhist.h $(srcdir)/common.h
+evalstring.o: ../config.h $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h
+evalstring.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/bashjmp.h $(BASHINCDIR)/posixjmp.h
+evalstring.o: $(topdir)/sig.h $(topdir)/command.h $(topdir)/siglist.h
+evalstring.o: $(BASHINCDIR)/memalloc.h $(topdir)/variables.h $(topdir)/conftypes.h $(topdir)/input.h
+evalstring.o: $(topdir)/quit.h $(topdir)/unwind_prot.h
+evalstring.o: $(BASHINCDIR)/maxpath.h $(topdir)/jobs.h $(topdir)/builtins.h
+evalstring.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+evalstring.o: $(topdir)/externs.h $(topdir)/jobs.h $(topdir)/builtins.h
+evalstring.o: $(topdir)/flags.h $(topdir)/input.h $(topdir)/execute_cmd.h
+evalstring.o: $(topdir)/bashhist.h $(srcdir)/common.h
+evalstring.o: $(topdir)/trap.h $(topdir)/redir.h ../pathnames.h
+getopt.o: ../config.h $(BASHINCDIR)/memalloc.h
+getopt.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/bashjmp.h $(topdir)/command.h
+getopt.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/error.h $(topdir)/variables.h $(topdir)/conftypes.h
+getopt.o: $(topdir)/quit.h $(BASHINCDIR)/maxpath.h $(topdir)/unwind_prot.h
+getopt.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+getopt.o: $(topdir)/sig.h ../pathnames.h $(topdir)/externs.h
+getopt.o: $(srcdir)/getopt.h 
+mkbuiltins.o: ../config.h $(topdir)/bashtypes.h $(BASHINCDIR)/posixstat.h
+mkbuiltins.o: ${BASHINCDIR}/filecntl.h
+mkbuiltins.o: $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h
+
+# def files
+alias.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+alias.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(BASHINCDIR)/maxpath.h
+alias.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+alias.o: $(topdir)/subst.h $(topdir)/externs.h $(srcdir)/common.h
+alias.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+alias.o: ../pathnames.h
+bind.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+bind.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+bind.o: $(topdir)/subst.h $(topdir)/externs.h $(srcdir)/bashgetopt.h
+bind.o: $(topdir)/general.h $(topdir)/xmalloc.h $(BASHINCDIR)/maxpath.h $(topdir)/bashline.h
+bind.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h 
+bind.o: ../pathnames.h
+break.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h 
+break.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+break.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+break.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+break.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+break.o: ../pathnames.h
+builtin.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+builtin.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/externs.h
+builtin.o: $(topdir)/quit.h $(srcdir)/common.h $(BASHINCDIR)/maxpath.h
+builtin.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+builtin.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h 
+builtin.o: $(srcdir)/bashgetopt.h ../pathnames.h
+caller.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+caller.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/quit.h $(topdir)/dispose_cmd.h
+caller.o: $(topdir)/make_cmd.h $(topdir)/subst.h $(topdir)/externs.h
+caller.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+caller.o: $(srcdir)/common.h $(BASHINCDIR)/maxpath.h ./builtext.h
+caller.o: ${BASHINCDIR}/chartypes.h $(topdir)/bashtypes.h ../pathnames.h
+cd.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+cd.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/quit.h $(topdir)/dispose_cmd.h
+cd.o: $(topdir)/make_cmd.h $(topdir)/subst.h $(topdir)/externs.h
+cd.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+cd.o: $(srcdir)/common.h $(BASHINCDIR)/maxpath.h ../pathnames.h
+command.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+command.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/externs.h
+command.o: $(topdir)/quit.h $(srcdir)/bashgetopt.h $(BASHINCDIR)/maxpath.h
+command.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+command.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h 
+command.o: ../pathnames.h
+declare.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+declare.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+declare.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+declare.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+declare.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+declare.o: $(topdir)/arrayfunc.h $(srcdir)/bashgetopt.h
+declare.o: ./builtext.h ../pathnames.h
+echo.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+echo.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/subst.h $(topdir)/externs.h
+echo.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+echo.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+echo.o: $(BASHINCDIR)/maxpath.h ../pathnames.h
+enable.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+enable.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+enable.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+enable.o: $(topdir)/subst.h $(topdir)/externs.h
+enable.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+enable.o: $(BASHINCDIR)/maxpath.h ../pathnames.h
+enable.o: $(topdir)/pcomplete.h
+eval.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+eval.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+eval.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+eval.o: $(topdir)/subst.h $(topdir)/externs.h 
+eval.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+eval.o: $(BASHINCDIR)/maxpath.h ../pathnames.h
+exec.o: $(topdir)/bashtypes.h
+exec.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+exec.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+exec.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+exec.o: $(topdir)/subst.h $(topdir)/externs.h $(topdir)/flags.h
+exec.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+exec.o: $(srcdir)/common.h $(topdir)/execute_cmd.h $(BASHINCDIR)/maxpath.h
+exec.o: $(topdir)/findcmd.h $(topdir)/jobs.h ../pathnames.h
+exit.o: $(topdir)/bashtypes.h
+exit.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+exit.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+exit.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+exit.o: $(topdir)/subst.h $(topdir)/externs.h  $(topdir)/jobs.h
+exit.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+exit.o: $(BASHINCDIR)/maxpath.h ./builtext.h ../pathnames.h
+fc.o: $(topdir)/bashtypes.h $(BASHINCDIR)/posixstat.h
+fc.o: $(topdir)/builtins.h $(topdir)/command.h $(srcdir)/bashgetopt.h
+fc.o: $(topdir)/bashhist.h
+fc.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+fc.o: $(topdir)/general.h $(topdir)/xmalloc.h $(BASHINCDIR)/maxpath.h
+fc.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+fc.o: $(topdir)/subst.h $(topdir)/externs.h $(topdir)/shell.h $(topdir)/syntax.h
+fc.o: $(topdir)/flags.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+fc.o: $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h $(BASHINCDIR)/chartypes.h
+fc.o: ../pathnames.h
+fg_bg.o: $(topdir)/bashtypes.h $(srcdir)/bashgetopt.h
+fg_bg.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+fg_bg.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+fg_bg.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+fg_bg.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+fg_bg.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+fg_bg.o: $(topdir)/jobs.h ../pathnames.h
+getopts.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+getopts.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+getopts.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+getopts.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+getopts.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+getopts.o: ../pathnames.h
+hash.o: $(topdir)/builtins.h $(topdir)/command.h $(topdir)/quit.h
+hash.o: $(topdir)/findcmd.h $(topdir)/hashlib.h
+hash.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+hash.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+hash.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+hash.o: $(srcdir)/common.h $(BASHINCDIR)/maxpath.h ../pathnames.h
+help.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+help.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+help.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+help.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+help.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+help.o: ${srcdir}/common.h ../pathnames.h
+history.o: $(topdir)/bashtypes.h
+history.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+history.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+history.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+history.o: $(topdir)/subst.h $(topdir)/externs.h 
+history.o: ${BASHINCDIR}/filecntl.h $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h
+history.o: $(topdir)/variables.h $(topdir)/conftypes.h $(topdir)/bashhist.h $(BASHINCDIR)/maxpath.h
+history.o: ../pathnames.h
+inlib.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+inlib.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+inlib.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+inlib.o: $(BASHINCDIR)/maxpath.h $(topdir)/subst.h $(topdir)/externs.h
+inlib.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h ../pathnames.h
+jobs.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+jobs.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/quit.h $(srcdir)/bashgetopt.h
+jobs.o: $(BASHINCDIR)/maxpath.h $(topdir)/externs.h $(topdir)/jobs.h
+jobs.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+jobs.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h 
+jobs.o: ../pathnames.h
+kill.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+kill.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/subst.h $(topdir)/externs.h
+kill.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+kill.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/trap.h $(topdir)/unwind_prot.h
+kill.o: $(topdir)/variables.h $(topdir)/conftypes.h $(BASHINCDIR)/maxpath.h
+kill.o: $(topdir)/jobs.h ../pathnames.h
+let.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+let.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+let.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+let.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+let.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+let.o: ../pathnames.h
+printf.o: ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/bashjmp.h
+printf.o: $(topdir)/command.h $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+printf.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+printf.o: $(topdir)/subst.h $(topdir)/externs.h $(topdir)/sig.h
+printf.o: ../pathnames.h $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h
+printf.o: $(topdir)/variables.h $(topdir)/conftypes.h $(BASHINCDIR)/stdc.h $(srcdir)/bashgetopt.h
+printf.o: $(topdir)/bashtypes.h ${srcdir}/common.h $(BASHINCDIR)/chartypes.h
+printf.o: ../pathnames.h
+pushd.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+pushd.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+pushd.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+pushd.o: $(topdir)/subst.h $(topdir)/externs.h 
+pushd.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+pushd.o: $(BASHINCDIR)/maxpath.h $(srcdir)/common.h ./builtext.h
+pushd.o: ../pathnames.h
+read.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+read.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+read.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+read.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+read.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+read.o: $(BASHINCDIR)/shtty.h
+read.o: $(topdir)/arrayfunc.h ../pathnames.h
+return.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+return.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+return.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+return.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+return.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+return.o: ../pathnames.h
+set.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+set.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/subst.h $(topdir)/externs.h
+set.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+set.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+set.o: $(BASHINCDIR)/maxpath.h $(topdir)/error.h
+set.o: $(topdir)/arrayfunc.h ../pathnames.h
+setattr.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+setattr.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(BASHINCDIR)/maxpath.h
+setattr.o: $(topdir)/quit.h $(srcdir)/common.h $(srcdir)/bashgetopt.h
+setattr.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+setattr.o: $(topdir)/externs.h 
+setattr.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h 
+setattr.o: $(topdir)/arrayfunc.h ../pathnames.h
+shift.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+shift.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+shift.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+shift.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+shift.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+shift.o: ../pathnames.h
+shopt.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+shopt.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h 
+shopt.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+shopt.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+shopt.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+shopt.o: $(srcdir)/common.h $(srcdir)/bashgetopt.h ../pathnames.h
+shopt.o: $(topdir)/bashhist.h
+source.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+source.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/findcmd.h
+source.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+source.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+source.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+source.o: $(srcdir)/bashgetopt.h $(topdir)/flags.h $(topdir)/trap.h
+source.o: ../pathnames.h
+suspend.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+suspend.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+suspend.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+suspend.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+suspend.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+suspend.o: $(topdir)/jobs.h ../pathnames.h
+test.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+test.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+test.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+test.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+test.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+test.o: $(topdir)/test.h ../pathnames.h
+times.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+times.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+times.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+times.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+times.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+times.o: $(BASHINCDIR)/posixtime.h ../pathnames.h
+trap.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+trap.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/externs.h
+trap.o: $(topdir)/quit.h $(srcdir)/common.h $(BASHINCDIR)/maxpath.h
+trap.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+trap.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+trap.o: $(topdir)/findcmd.h ../pathnames.h
+type.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+type.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+type.o: $(topdir)/quit.h $(srcdir)/common.h $(BASHINCDIR)/maxpath.h
+type.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+type.o: $(topdir)/externs.h $(topdir)/hashcmd.h ../pathnames.h
+type.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h 
+ulimit.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+ulimit.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+ulimit.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+ulimit.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+ulimit.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+ulimit.o: ../pathnames.h
+umask.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+umask.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+umask.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+umask.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+umask.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+umask.o: $(BASHINCDIR)/chartypes.h ../pathnames.h
+wait.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+wait.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+wait.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+wait.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+wait.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+wait.o: $(topdir)/jobs.h
+wait.o: $(BASHINCDIR)/chartypes.h ../pathnames.h
+
+complete.o: ../config.h ../pathnames.h
+complete.o: ${topdir}/shell.h $(topdir)/syntax.h ${topdir}/bashjmp.h ${BASHINCDIR}/posixjmp.h ${topdir}/sig.h  
+complete.o: ${topdir}/unwind_prot.h ${topdir}/variables.h
+complete.o: ${topdir}/bashtypes.h ${topdir}/bashansi.h ${BASHINCDIR}/ansi_stdlib.h
+complete.o: ${topdir}/builtins.h
+complete.o: ${topdir}/pcomplete.h
+complete.o: ${srcdir}/common.h ${srcdir}/bashgetopt.h
+mapfile.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+mapfile.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+mapfile.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+mapfile.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+mapfile.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/variables.h $(topdir)/conftypes.h
+mapfile.o: $(topdir)/arrayfunc.h ../pathnames.h
+
+#bind.o: $(RL_LIBSRC)chardefs.h $(RL_LIBSRC)readline.h $(RL_LIBSRC)keymaps.h
+
+# libintl dependencies
+bind.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+break.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+caller.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+cd.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+common.c: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+complete.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+declare.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+enable.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+evalfile.c: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+exec.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+exit.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+fc.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+fg_bg.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+getopt.c: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+hash.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+help.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+history.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+inlib.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+jobs.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+kill.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+let.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+mapfile.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+mkbuiltins.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+printf.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+pushd.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+read.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+return.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+set.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+setattr.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+shift.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+shopt.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+source.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+suspend.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+type.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+ulimit.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+umask.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
index b4e7977990ae4e3daf035c9e43273310118071d8..61b58177ff231e6dc49c0d25dc394df0f0889f4f 100644 (file)
@@ -1548,7 +1548,7 @@ static struct cpelement *cpl_search __P((pid_t));
 static struct cpelement *cpl_searchbyname __P((char *));
 static void cpl_prune __P((void));
 
-Coproc sh_coproc = { 0, NO_PID, -1, -1, 0, 0 };
+Coproc sh_coproc = { 0, NO_PID, -1, -1, 0, 0, 0, 0 };
 
 cplist_t coproc_list = {0, 0, 0};
 
index 416bef5accb3cab662a55e394cbf2013b0fe8c84..b4e7977990ae4e3daf035c9e43273310118071d8 100644 (file)
@@ -136,6 +136,7 @@ static int builtin_status __P((int));
 
 static int execute_for_command __P((FOR_COM *));
 #if defined (SELECT_COMMAND)
+static int displen __P((const char *));
 static int print_index_and_element __P((int, int, WORD_LIST *));
 static void indent __P((int, int));
 static void print_select_list __P((WORD_LIST *, int, int, int));
@@ -2641,7 +2642,7 @@ static int LINES, COLS, tabsize;
 
 static int
 displen (s)
-     char *s;
+     const char *s;
 {
 #if defined (HANDLE_MULTIBYTE)
   wchar_t *wcstr;
diff --git a/parse.y b/parse.y
index 0e90d2d54b17a80843afcadac35e4d7be265822d..c61b5d3b312f9226be446db8977fe86af7cf9266 100644 (file)
--- a/parse.y
+++ b/parse.y
@@ -336,7 +336,7 @@ static REDIRECTEE redir;
    third group are recognized only under special circumstances. */
 %token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION COPROC
 %token COND_START COND_END COND_ERROR
-%token IN BANG TIME TIMEOPT
+%token IN BANG TIME TIMEOPT TIMEIGN
 
 /* More general tokens. yylex () knows how to make these. */
 %token <word> WORD ASSIGNMENT_WORD REDIR_WORD
@@ -1272,6 +1272,8 @@ timespec: TIME
                        { $$ = CMD_TIME_PIPELINE; }
        |       TIME TIMEOPT
                        { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
+       |       TIME TIMEOPT TIMEIGN
+                       { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
        ;
 %%
 
@@ -2068,6 +2070,7 @@ STRING_INT_ALIST word_token_alist[] = {
 /* other tokens that can be returned by read_token() */
 STRING_INT_ALIST other_token_alist[] = {
   /* Multiple-character tokens with special values */
+  { "--", TIMEIGN },
   { "-p", TIMEOPT },
   { "&&", AND_AND },
   { "||", OR_OR },
@@ -2671,6 +2674,7 @@ time_command_acceptable ()
     case BANG:         /* ! time pipeline */
     case TIME:         /* time time pipeline */
     case TIMEOPT:      /* time -p time pipeline */
+    case TIMEIGN:      /* time -p -- ... */
       return 1;
     default:
       return 0;
@@ -2697,6 +2701,7 @@ time_command_acceptable ()
        `}' is recognized if there is an unclosed `{' present.
 
        `-p' is returned as TIMEOPT if the last read token was TIME.
+       `--' is returned as TIMEIGN if the last read token was TIMEOPT.
 
        ']]' is returned as COND_END if the parser is currently parsing
        a conditional expression ((parser_state & PST_CONDEXPR) != 0)
@@ -2782,6 +2787,9 @@ special_case_tokens (tokstr)
   /* Handle -p after `time'. */
   if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2])
     return (TIMEOPT);
+  /* Handle -- after `time -p'. */
+  if (last_read_token == TIMEOPT && tokstr[0] == '-' && tokstr[1] == '-' && !tokstr[2])
+    return (TIMEIGN);
 #endif
 
 #if defined (COND_COMMAND) /* [[ */
@@ -3241,10 +3249,21 @@ parse_matched_pair (qc, open, close, lenp, flags)
         since they share the same defines. */
       if (flags & P_DOLBRACE)
         {
-         if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '%' && retind > 0)
+          /* ${param%[%]word} */
+         if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '%' && retind > 1)
            dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param#[#]word} */
          else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '#' && retind > 1)
            dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param/[/]pat/rep} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '/' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param^[^]pat} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '^' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param,[,]pat} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == ',' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
          else if MBTEST(dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", ch) != 0)
            dolbrace_state = DOLBRACE_OP;
          else if MBTEST(dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", ch) == 0)
@@ -4757,6 +4776,7 @@ reserved_word_acceptable (toksym)
     case THEN:
     case TIME:
     case TIMEOPT:
+    case TIMEIGN:
     case COPROC:
     case UNTIL:
     case WHILE:
diff --git a/parse.y.new b/parse.y.new
new file mode 100644 (file)
index 0000000..2f86495
--- /dev/null
@@ -0,0 +1,5985 @@
+/* parse.y - Yacc grammar for bash. */
+
+/* Copyright (C) 1989-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/>.
+*/
+
+%{
+#include "config.h"
+
+#include "bashtypes.h"
+#include "bashansi.h"
+
+#include "filecntl.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#if defined (HAVE_LOCALE_H)
+#  include <locale.h>
+#endif
+
+#include <stdio.h>
+#include "chartypes.h"
+#include <signal.h>
+
+#include "memalloc.h"
+
+#include "bashintl.h"
+
+#define NEED_STRFTIME_DECL     /* used in externs.h */
+
+#include "shell.h"
+#include "trap.h"
+#include "flags.h"
+#include "parser.h"
+#include "mailcheck.h"
+#include "test.h"
+#include "builtins.h"
+#include "builtins/common.h"
+#include "builtins/builtext.h"
+
+#include "shmbutil.h"
+
+#if defined (READLINE)
+#  include "bashline.h"
+#  include <readline/readline.h>
+#endif /* READLINE */
+
+#if defined (HISTORY)
+#  include "bashhist.h"
+#  include <readline/history.h>
+#endif /* HISTORY */
+
+#if defined (JOB_CONTROL)
+#  include "jobs.h"
+#endif /* JOB_CONTROL */
+
+#if defined (ALIAS)
+#  include "alias.h"
+#else
+typedef void *alias_t;
+#endif /* ALIAS */
+
+#if defined (PROMPT_STRING_DECODE)
+#  ifndef _MINIX
+#    include <sys/param.h>
+#  endif
+#  include <time.h>
+#  if defined (TM_IN_SYS_TIME)
+#    include <sys/types.h>
+#    include <sys/time.h>
+#  endif /* TM_IN_SYS_TIME */
+#  include "maxpath.h"
+#endif /* PROMPT_STRING_DECODE */
+
+#define RE_READ_TOKEN  -99
+#define NO_EXPANSION   -100
+
+#ifdef DEBUG
+#  define YYDEBUG 1
+#else
+#  define YYDEBUG 0
+#endif
+
+#if defined (HANDLE_MULTIBYTE)
+#  define last_shell_getc_is_singlebyte \
+       ((shell_input_line_index > 1) \
+               ? shell_input_line_property[shell_input_line_index - 1] \
+               : 1)
+#  define MBTEST(x)    ((x) && last_shell_getc_is_singlebyte)
+#else
+#  define last_shell_getc_is_singlebyte        1
+#  define MBTEST(x)    ((x))
+#endif
+
+#if defined (EXTENDED_GLOB)
+extern int extended_glob;
+#endif
+
+extern int eof_encountered;
+extern int no_line_editing, running_under_emacs;
+extern int current_command_number;
+extern int sourcelevel, parse_and_execute_level;
+extern int posixly_correct;
+extern int last_command_exit_value;
+extern pid_t last_command_subst_pid;
+extern char *shell_name, *current_host_name;
+extern char *dist_version;
+extern int patch_level;
+extern int dump_translatable_strings, dump_po_strings;
+extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin;
+#if defined (BUFFERED_INPUT)
+extern int bash_input_fd_changed;
+#endif
+
+extern int errno;
+/* **************************************************************** */
+/*                                                                 */
+/*                 "Forward" declarations                          */
+/*                                                                 */
+/* **************************************************************** */
+
+#ifdef DEBUG
+static void debug_parser __P((int));
+#endif
+
+static int yy_getc __P((void));
+static int yy_ungetc __P((int));
+
+#if defined (READLINE)
+static int yy_readline_get __P((void));
+static int yy_readline_unget __P((int));
+#endif
+
+static int yy_string_get __P((void));
+static int yy_string_unget __P((int));
+static void rewind_input_string __P((void));
+static int yy_stream_get __P((void));
+static int yy_stream_unget __P((int));
+
+static int shell_getc __P((int));
+static void shell_ungetc __P((int));
+static void discard_until __P((int));
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+static void push_string __P((char *, int, alias_t *));
+static void pop_string __P((void));
+static void free_string_list __P((void));
+#endif
+
+static char *read_a_line __P((int));
+
+static int reserved_word_acceptable __P((int));
+static int yylex __P((void));
+static int alias_expand_token __P((char *));
+static int time_command_acceptable __P((void));
+static int special_case_tokens __P((char *));
+static int read_token __P((int));
+static char *parse_matched_pair __P((int, int, int, int *, int));
+static char *parse_comsub __P((int, int, int, int *, int));
+#if defined (ARRAY_VARS)
+static char *parse_compound_assignment __P((int *));
+#endif
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+static int parse_dparen __P((int));
+static int parse_arith_cmd __P((char **, int));
+#endif
+#if defined (COND_COMMAND)
+static void cond_error __P((void));
+static COND_COM *cond_expr __P((void));
+static COND_COM *cond_or __P((void));
+static COND_COM *cond_and __P((void));
+static COND_COM *cond_term __P((void));
+static int cond_skip_newlines __P((void));
+static COMMAND *parse_cond_command __P((void));
+#endif
+#if defined (ARRAY_VARS)
+static int token_is_assignment __P((char *, int));
+static int token_is_ident __P((char *, int));
+#endif
+static int read_token_word __P((int));
+static void discard_parser_constructs __P((int));
+
+static char *error_token_from_token __P((int));
+static char *error_token_from_text __P((void));
+static void print_offending_line __P((void));
+static void report_syntax_error __P((char *));
+
+static void handle_eof_input_unit __P((void));
+static void prompt_again __P((void));
+#if 0
+static void reset_readline_prompt __P((void));
+#endif
+static void print_prompt __P((void));
+
+#if defined (HANDLE_MULTIBYTE)
+static void set_line_mbstate __P((void));
+static char *shell_input_line_property = NULL;
+#else
+#  define set_line_mbstate()
+#endif
+
+extern int yyerror __P((const char *));
+
+#ifdef DEBUG
+extern int yydebug;
+#endif
+
+/* Default prompt strings */
+char *primary_prompt = PPROMPT;
+char *secondary_prompt = SPROMPT;
+
+/* PROMPT_STRING_POINTER points to one of these, never to an actual string. */
+char *ps1_prompt, *ps2_prompt;
+
+/* Handle on the current prompt string.  Indirectly points through
+   ps1_ or ps2_prompt. */
+char **prompt_string_pointer = (char **)NULL;
+char *current_prompt_string;
+
+/* Non-zero means we expand aliases in commands. */
+int expand_aliases = 0;
+
+/* If non-zero, the decoded prompt string undergoes parameter and
+   variable substitution, command substitution, arithmetic substitution,
+   string expansion, process substitution, and quote removal in
+   decode_prompt_string. */
+int promptvars = 1;
+
+/* If non-zero, $'...' and $"..." are expanded when they appear within
+   a ${...} expansion, even when the expansion appears within double
+   quotes. */
+int extended_quote = 1;
+
+/* The number of lines read from input while creating the current command. */
+int current_command_line_count;
+
+/* The number of lines in a command saved while we run parse_and_execute */
+int saved_command_line_count;
+
+/* The token that currently denotes the end of parse. */
+int shell_eof_token;
+
+/* The token currently being read. */
+int current_token;
+
+/* The current parser state. */
+int parser_state;
+
+/* Variables to manage the task of reading here documents, because we need to
+   defer the reading until after a complete command has been collected. */
+static REDIRECT *redir_stack[10];
+int need_here_doc;
+
+/* Where shell input comes from.  History expansion is performed on each
+   line when the shell is interactive. */
+static char *shell_input_line = (char *)NULL;
+static int shell_input_line_index;
+static int shell_input_line_size;      /* Amount allocated for shell_input_line. */
+static int shell_input_line_len;       /* strlen (shell_input_line) */
+
+/* Either zero or EOF. */
+static int shell_input_line_terminator;
+
+/* The line number in a script on which a function definition starts. */
+static int function_dstart;
+
+/* The line number in a script on which a function body starts. */
+static int function_bstart;
+
+/* The line number in a script at which an arithmetic for command starts. */
+static int arith_for_lineno;
+
+/* The decoded prompt string.  Used if READLINE is not defined or if
+   editing is turned off.  Analogous to current_readline_prompt. */
+static char *current_decoded_prompt;
+
+/* The last read token, or NULL.  read_token () uses this for context
+   checking. */
+static int last_read_token;
+
+/* The token read prior to last_read_token. */
+static int token_before_that;
+
+/* The token read prior to token_before_that. */
+static int two_tokens_ago;
+
+static int global_extglob;
+
+/* The line number in a script where the word in a `case WORD', `select WORD'
+   or `for WORD' begins.  This is a nested command maximum, since the array
+   index is decremented after a case, select, or for command is parsed. */
+#define MAX_CASE_NEST  128
+static int word_lineno[MAX_CASE_NEST];
+static int word_top = -1;
+
+/* If non-zero, it is the token that we want read_token to return
+   regardless of what text is (or isn't) present to be read.  This
+   is reset by read_token.  If token_to_read == WORD or
+   ASSIGNMENT_WORD, yylval.word should be set to word_desc_to_read. */
+static int token_to_read;
+static WORD_DESC *word_desc_to_read;
+
+static REDIRECTEE source;
+static REDIRECTEE redir;
+%}
+
+%union {
+  WORD_DESC *word;             /* the word that we read. */
+  int number;                  /* the number that we read. */
+  WORD_LIST *word_list;
+  COMMAND *command;
+  REDIRECT *redirect;
+  ELEMENT element;
+  PATTERN_LIST *pattern;
+}
+
+/* Reserved words.  Members of the first group are only recognized
+   in the case that they are preceded by a list_terminator.  Members
+   of the second group are for [[...]] commands.  Members of the
+   third group are recognized only under special circumstances. */
+%token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION COPROC
+%token COND_START COND_END COND_ERROR
+%token IN BANG TIME TIMEOPT TIMEIGN
+
+/* More general tokens. yylex () knows how to make these. */
+%token <word> WORD ASSIGNMENT_WORD REDIR_WORD
+%token <number> NUMBER
+%token <word_list> ARITH_CMD ARITH_FOR_EXPRS
+%token <command> COND_CMD
+%token AND_AND OR_OR GREATER_GREATER LESS_LESS LESS_AND LESS_LESS_LESS
+%token GREATER_AND SEMI_SEMI SEMI_AND SEMI_SEMI_AND
+%token LESS_LESS_MINUS AND_GREATER AND_GREATER_GREATER LESS_GREATER
+%token GREATER_BAR BAR_AND
+
+/* The types that the various syntactical units return. */
+
+%type <command> inputunit command pipeline pipeline_command
+%type <command> list list0 list1 compound_list simple_list simple_list1
+%type <command> simple_command shell_command
+%type <command> for_command select_command case_command group_command
+%type <command> arith_command
+%type <command> cond_command
+%type <command> arith_for_command
+%type <command> coproc
+%type <command> function_def function_body if_command elif_clause subshell
+%type <redirect> redirection redirection_list
+%type <element> simple_command_element
+%type <word_list> word_list pattern
+%type <pattern> pattern_list case_clause_sequence case_clause
+%type <number> timespec
+%type <number> list_terminator
+
+%start inputunit
+
+%left '&' ';' '\n' yacc_EOF
+%left AND_AND OR_OR
+%right '|' BAR_AND
+%%
+
+inputunit:     simple_list simple_list_terminator
+                       {
+                         /* Case of regular command.  Discard the error
+                            safety net,and return the command just parsed. */
+                         global_command = $1;
+                         eof_encountered = 0;
+                         /* discard_parser_constructs (0); */
+                         if (parser_state & PST_CMDSUBST)
+                           parser_state |= PST_EOFTOKEN;
+                         YYACCEPT;
+                       }
+       |       '\n'
+                       {
+                         /* Case of regular command, but not a very
+                            interesting one.  Return a NULL command. */
+                         global_command = (COMMAND *)NULL;
+                         if (parser_state & PST_CMDSUBST)
+                           parser_state |= PST_EOFTOKEN;
+                         YYACCEPT;
+                       }
+       |       error '\n'
+                       {
+                         /* Error during parsing.  Return NULL command. */
+                         global_command = (COMMAND *)NULL;
+                         eof_encountered = 0;
+                         /* discard_parser_constructs (1); */
+                         if (interactive && parse_and_execute_level == 0)
+                           {
+                             YYACCEPT;
+                           }
+                         else
+                           {
+                             YYABORT;
+                           }
+                       }
+       |       yacc_EOF
+                       {
+                         /* Case of EOF seen by itself.  Do ignoreeof or
+                            not. */
+                         global_command = (COMMAND *)NULL;
+                         handle_eof_input_unit ();
+                         YYACCEPT;
+                       }
+       ;
+
+word_list:     WORD
+                       { $$ = make_word_list ($1, (WORD_LIST *)NULL); }
+       |       word_list WORD
+                       { $$ = make_word_list ($2, $1); }
+       ;
+
+redirection:   '>' WORD
+                       {
+                         source.dest = 1;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_output_direction, redir, 0);
+                       }
+       |       '<' WORD
+                       {
+                         source.dest = 0;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_input_direction, redir, 0);
+                       }
+       |       NUMBER '>' WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_output_direction, redir, 0);
+                       }
+       |       NUMBER '<' WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_input_direction, redir, 0);
+                       }
+       |       REDIR_WORD '>' WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_output_direction, redir, REDIR_VARASSIGN);
+                       }
+       |       REDIR_WORD '<' WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_input_direction, redir, REDIR_VARASSIGN);
+                       }
+       |       GREATER_GREATER WORD
+                       {
+                         source.dest = 1;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_appending_to, redir, 0);
+                       }
+       |       NUMBER GREATER_GREATER WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_appending_to, redir, 0);
+                       }
+       |       REDIR_WORD GREATER_GREATER WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_appending_to, redir, REDIR_VARASSIGN);
+                       }
+       |       GREATER_BAR WORD
+                       {
+                         source.dest = 1;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_output_force, redir, 0);
+                       }
+       |       NUMBER GREATER_BAR WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_output_force, redir, 0);
+                       }
+       |       REDIR_WORD GREATER_BAR WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_output_force, redir, REDIR_VARASSIGN);
+                       }
+       |       LESS_GREATER WORD
+                       {
+                         source.dest = 0;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_input_output, redir, 0);
+                       }
+       |       NUMBER LESS_GREATER WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_input_output, redir, 0);
+                       }
+       |       REDIR_WORD LESS_GREATER WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_input_output, redir, REDIR_VARASSIGN);
+                       }
+       |       LESS_LESS WORD
+                       {
+                         source.dest = 0;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_reading_until, redir, 0);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       NUMBER LESS_LESS WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_reading_until, redir, 0);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       REDIR_WORD LESS_LESS WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_reading_until, redir, REDIR_VARASSIGN);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       LESS_LESS_MINUS WORD
+                       {
+                         source.dest = 0;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_deblank_reading_until, redir, 0);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       NUMBER LESS_LESS_MINUS WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_deblank_reading_until, redir, 0);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       REDIR_WORD  LESS_LESS_MINUS WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_deblank_reading_until, redir, REDIR_VARASSIGN);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       LESS_LESS_LESS WORD
+                       {
+                         source.dest = 0;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_reading_string, redir, 0);
+                       }
+       |       NUMBER LESS_LESS_LESS WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_reading_string, redir, 0);
+                       }
+       |       REDIR_WORD LESS_LESS_LESS WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_reading_string, redir, REDIR_VARASSIGN);
+                       }
+       |       LESS_AND NUMBER
+                       {
+                         source.dest = 0;
+                         redir.dest = $2;
+                         $$ = make_redirection (source, r_duplicating_input, redir, 0);
+                       }
+       |       NUMBER LESS_AND NUMBER
+                       {
+                         source.dest = $1;
+                         redir.dest = $3;
+                         $$ = make_redirection (source, r_duplicating_input, redir, 0);
+                       }
+       |       REDIR_WORD LESS_AND NUMBER
+                       {
+                         source.filename = $1;
+                         redir.dest = $3;
+                         $$ = make_redirection (source, r_duplicating_input, redir, REDIR_VARASSIGN);
+                       }
+       |       GREATER_AND NUMBER
+                       {
+                         source.dest = 1;
+                         redir.dest = $2;
+                         $$ = make_redirection (source, r_duplicating_output, redir, 0);
+                       }
+       |       NUMBER GREATER_AND NUMBER
+                       {
+                         source.dest = $1;
+                         redir.dest = $3;
+                         $$ = make_redirection (source, r_duplicating_output, redir, 0);
+                       }
+       |       REDIR_WORD GREATER_AND NUMBER
+                       {
+                         source.filename = $1;
+                         redir.dest = $3;
+                         $$ = make_redirection (source, r_duplicating_output, redir, REDIR_VARASSIGN);
+                       }
+       |       LESS_AND WORD
+                       {
+                         source.dest = 0;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_duplicating_input_word, redir, 0);
+                       }
+       |       NUMBER LESS_AND WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_duplicating_input_word, redir, 0);
+                       }
+       |       REDIR_WORD LESS_AND WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_duplicating_input_word, redir, REDIR_VARASSIGN);
+                       }
+       |       GREATER_AND WORD
+                       {
+                         source.dest = 1;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_duplicating_output_word, redir, 0);
+                       }
+       |       NUMBER GREATER_AND WORD
+                       {
+                         source.dest = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_duplicating_output_word, redir, 0);
+                       }
+       |       REDIR_WORD GREATER_AND WORD
+                       {
+                         source.filename = $1;
+                         redir.filename = $3;
+                         $$ = make_redirection (source, r_duplicating_output_word, redir, REDIR_VARASSIGN);
+                       }
+       |       GREATER_AND '-'
+                       {
+                         source.dest = 1;
+                         redir.dest = 0;
+                         $$ = make_redirection (source, r_close_this, redir, 0);
+                       }
+       |       NUMBER GREATER_AND '-'
+                       {
+                         source.dest = $1;
+                         redir.dest = 0;
+                         $$ = make_redirection (source, r_close_this, redir, 0);
+                       }
+       |       REDIR_WORD GREATER_AND '-'
+                       {
+                         source.filename = $1;
+                         redir.dest = 0;
+                         $$ = make_redirection (source, r_close_this, redir, REDIR_VARASSIGN);
+                       }
+       |       LESS_AND '-'
+                       {
+                         source.dest = 0;
+                         redir.dest = 0;
+                         $$ = make_redirection (source, r_close_this, redir, 0);
+                       }
+       |       NUMBER LESS_AND '-'
+                       {
+                         source.dest = $1;
+                         redir.dest = 0;
+                         $$ = make_redirection (source, r_close_this, redir, 0);
+                       }
+       |       REDIR_WORD LESS_AND '-'
+                       {
+                         source.filename = $1;
+                         redir.dest = 0;
+                         $$ = make_redirection (source, r_close_this, redir, REDIR_VARASSIGN);
+                       }
+       |       AND_GREATER WORD
+                       {
+                         source.dest = 1;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_err_and_out, redir, 0);
+                       }
+       |       AND_GREATER_GREATER WORD
+                       {
+                         source.dest = 1;
+                         redir.filename = $2;
+                         $$ = make_redirection (source, r_append_err_and_out, redir, 0);
+                       }
+       ;
+
+simple_command_element: WORD
+                       { $$.word = $1; $$.redirect = 0; }
+       |       ASSIGNMENT_WORD
+                       { $$.word = $1; $$.redirect = 0; }
+       |       redirection
+                       { $$.redirect = $1; $$.word = 0; }
+       ;
+
+redirection_list: redirection
+                       {
+                         $$ = $1;
+                       }
+       |       redirection_list redirection
+                       {
+                         register REDIRECT *t;
+
+                         for (t = $1; t->next; t = t->next)
+                           ;
+                         t->next = $2;
+                         $$ = $1;
+                       }
+       ;
+
+simple_command:        simple_command_element
+                       { $$ = make_simple_command ($1, (COMMAND *)NULL); }
+       |       simple_command simple_command_element
+                       { $$ = make_simple_command ($2, $1); }
+       ;
+
+command:       simple_command
+                       { $$ = clean_simple_command ($1); }
+       |       shell_command
+                       { $$ = $1; }
+       |       shell_command redirection_list
+                       {
+                         COMMAND *tc;
+
+                         tc = $1;
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = $2;
+                           }
+                         else
+                           tc->redirects = $2;
+                         $$ = $1;
+                       }
+       |       function_def
+                       { $$ = $1; }
+       |       coproc
+                       { $$ = $1; }
+       ;
+
+shell_command: for_command
+                       { $$ = $1; }
+       |       case_command
+                       { $$ = $1; }
+       |       WHILE compound_list DO compound_list DONE
+                       { $$ = make_while_command ($2, $4); }
+       |       UNTIL compound_list DO compound_list DONE
+                       { $$ = make_until_command ($2, $4); }
+       |       select_command
+                       { $$ = $1; }
+       |       if_command
+                       { $$ = $1; }
+       |       subshell
+                       { $$ = $1; }
+       |       group_command
+                       { $$ = $1; }
+       |       arith_command
+                       { $$ = $1; }
+       |       cond_command
+                       { $$ = $1; }
+       |       arith_for_command
+                       { $$ = $1; }
+       ;
+
+for_command:   FOR WORD newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD ';' newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD ';' newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN word_list list_terminator newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN word_list list_terminator newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN list_terminator newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN list_terminator newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       ;
+
+arith_for_command:     FOR ARITH_FOR_EXPRS list_terminator newline_list DO compound_list DONE
+                               {
+                                 $$ = make_arith_for_command ($2, $6, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       |               FOR ARITH_FOR_EXPRS list_terminator newline_list '{' compound_list '}'
+                               {
+                                 $$ = make_arith_for_command ($2, $6, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       |               FOR ARITH_FOR_EXPRS DO compound_list DONE
+                               {
+                                 $$ = make_arith_for_command ($2, $4, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       |               FOR ARITH_FOR_EXPRS '{' compound_list '}'
+                               {
+                                 $$ = make_arith_for_command ($2, $4, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       ;
+
+select_command:        SELECT WORD newline_list DO list DONE
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD newline_list '{' list '}'
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD ';' newline_list DO list DONE
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD ';' newline_list '{' list '}'
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD newline_list IN word_list list_terminator newline_list DO list DONE
+                       {
+                         $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD newline_list IN word_list list_terminator newline_list '{' list '}'
+                       {
+                         $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       ;
+
+case_command:  CASE WORD newline_list IN newline_list ESAC
+                       {
+                         $$ = make_case_command ($2, (PATTERN_LIST *)NULL, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       CASE WORD newline_list IN case_clause_sequence newline_list ESAC
+                       {
+                         $$ = make_case_command ($2, $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       CASE WORD newline_list IN case_clause ESAC
+                       {
+                         $$ = make_case_command ($2, $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       ;
+
+function_def:  WORD '(' ')' newline_list function_body
+                       { $$ = make_function_def ($1, $5, function_dstart, function_bstart); }
+
+       |       FUNCTION WORD '(' ')' newline_list function_body
+                       { $$ = make_function_def ($2, $6, function_dstart, function_bstart); }
+
+       |       FUNCTION WORD newline_list function_body
+                       { $$ = make_function_def ($2, $4, function_dstart, function_bstart); }
+       ;
+
+function_body: shell_command
+                       { $$ = $1; }
+       |       shell_command redirection_list
+                       {
+                         COMMAND *tc;
+
+                         tc = $1;
+                         /* According to Posix.2 3.9.5, redirections
+                            specified after the body of a function should
+                            be attached to the function and performed when
+                            the function is executed, not as part of the
+                            function definition command. */
+                         /* XXX - I don't think it matters, but we might
+                            want to change this in the future to avoid
+                            problems differentiating between a function
+                            definition with a redirection and a function
+                            definition containing a single command with a
+                            redirection.  The two are semantically equivalent,
+                            though -- the only difference is in how the
+                            command printing code displays the redirections. */
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = $2;
+                           }
+                         else
+                           tc->redirects = $2;
+                         $$ = $1;
+                       }
+       ;
+
+subshell:      '(' compound_list ')'
+                       {
+                         $$ = make_subshell_command ($2);
+                         $$->flags |= CMD_WANT_SUBSHELL;
+                       }
+       ;
+
+coproc:                COPROC shell_command
+                       {
+                         $$ = make_coproc_command ("COPROC", $2);
+                         $$->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
+                       }
+       |       COPROC shell_command redirection_list
+                       {
+                         COMMAND *tc;
+
+                         tc = $2;
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = $3;
+                           }
+                         else
+                           tc->redirects = $3;
+                         $$ = make_coproc_command ("COPROC", $2);
+                         $$->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
+                       }
+       |       COPROC WORD shell_command
+                       {
+                         $$ = make_coproc_command ($2->word, $3);
+                         $$->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
+                       }
+       |       COPROC WORD shell_command redirection_list
+                       {
+                         COMMAND *tc;
+
+                         tc = $3;
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = $4;
+                           }
+                         else
+                           tc->redirects = $4;
+                         $$ = make_coproc_command ($2->word, $3);
+                         $$->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
+                       }
+       |       COPROC simple_command
+                       {
+                         $$ = make_coproc_command ("COPROC", clean_simple_command ($2));
+                         $$->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
+                       }
+       ;
+
+if_command:    IF compound_list THEN compound_list FI
+                       { $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
+       |       IF compound_list THEN compound_list ELSE compound_list FI
+                       { $$ = make_if_command ($2, $4, $6); }
+       |       IF compound_list THEN compound_list elif_clause FI
+                       { $$ = make_if_command ($2, $4, $5); }
+       ;
+
+
+group_command: '{' compound_list '}'
+                       { $$ = make_group_command ($2); }
+       ;
+
+arith_command: ARITH_CMD
+                       { $$ = make_arith_command ($1); }
+       ;
+
+cond_command:  COND_START COND_CMD COND_END
+                       { $$ = $2; }
+       ; 
+
+elif_clause:   ELIF compound_list THEN compound_list
+                       { $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
+       |       ELIF compound_list THEN compound_list ELSE compound_list
+                       { $$ = make_if_command ($2, $4, $6); }
+       |       ELIF compound_list THEN compound_list elif_clause
+                       { $$ = make_if_command ($2, $4, $5); }
+       ;
+
+case_clause:   pattern_list
+       |       case_clause_sequence pattern_list
+                       { $2->next = $1; $$ = $2; }
+       ;
+
+pattern_list:  newline_list pattern ')' compound_list
+                       { $$ = make_pattern_list ($2, $4); }
+       |       newline_list pattern ')' newline_list
+                       { $$ = make_pattern_list ($2, (COMMAND *)NULL); }
+       |       newline_list '(' pattern ')' compound_list
+                       { $$ = make_pattern_list ($3, $5); }
+       |       newline_list '(' pattern ')' newline_list
+                       { $$ = make_pattern_list ($3, (COMMAND *)NULL); }
+       ;
+
+case_clause_sequence:  pattern_list SEMI_SEMI
+                       { $$ = $1; }
+       |       case_clause_sequence pattern_list SEMI_SEMI
+                       { $2->next = $1; $$ = $2; }
+       |       pattern_list SEMI_AND
+                       { $1->flags |= CASEPAT_FALLTHROUGH; $$ = $1; }
+       |       case_clause_sequence pattern_list SEMI_AND
+                       { $2->flags |= CASEPAT_FALLTHROUGH; $2->next = $1; $$ = $2; }
+       |       pattern_list SEMI_SEMI_AND
+                       { $1->flags |= CASEPAT_TESTNEXT; $$ = $1; }
+       |       case_clause_sequence pattern_list SEMI_SEMI_AND
+                       { $2->flags |= CASEPAT_TESTNEXT; $2->next = $1; $$ = $2; }      
+       ;
+
+pattern:       WORD
+                       { $$ = make_word_list ($1, (WORD_LIST *)NULL); }
+       |       pattern '|' WORD
+                       { $$ = make_word_list ($3, $1); }
+       ;
+
+/* A list allows leading or trailing newlines and
+   newlines as operators (equivalent to semicolons).
+   It must end with a newline or semicolon.
+   Lists are used within commands such as if, for, while.  */
+
+list:          newline_list list0
+                       {
+                         $$ = $2;
+                         if (need_here_doc)
+                           gather_here_documents ();
+                        }
+       ;
+
+compound_list: list
+       |       newline_list list1
+                       {
+                         $$ = $2;
+                       }
+       ;
+
+list0:         list1 '\n' newline_list
+       |       list1 '&' newline_list
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, (COMMAND *)NULL, '&');
+                         else
+                           $$ = command_connect ($1, (COMMAND *)NULL, '&');
+                       }
+       |       list1 ';' newline_list
+
+       ;
+
+list1:         list1 AND_AND newline_list list1
+                       { $$ = command_connect ($1, $4, AND_AND); }
+       |       list1 OR_OR newline_list list1
+                       { $$ = command_connect ($1, $4, OR_OR); }
+       |       list1 '&' newline_list list1
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, $4, '&');
+                         else
+                           $$ = command_connect ($1, $4, '&');
+                       }
+       |       list1 ';' newline_list list1
+                       { $$ = command_connect ($1, $4, ';'); }
+       |       list1 '\n' newline_list list1
+                       { $$ = command_connect ($1, $4, ';'); }
+       |       pipeline_command
+                       { $$ = $1; }
+       ;
+
+simple_list_terminator:        '\n'
+       |       yacc_EOF
+       ;
+
+list_terminator:'\n'
+               { $$ = '\n'; }
+       |       ';'
+               { $$ = ';'; }
+       |       yacc_EOF
+               { $$ = yacc_EOF; }
+       ;
+
+newline_list:
+       |       newline_list '\n'
+       ;
+
+/* A simple_list is a list that contains no significant newlines
+   and no leading or trailing newlines.  Newlines are allowed
+   only following operators, where they are not significant.
+
+   This is what an inputunit consists of.  */
+
+simple_list:   simple_list1
+                       {
+                         $$ = $1;
+                         if (need_here_doc)
+                           gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
+                       }
+       |       simple_list1 '&'
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, (COMMAND *)NULL, '&');
+                         else
+                           $$ = command_connect ($1, (COMMAND *)NULL, '&');
+                         if (need_here_doc)
+                           gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
+                       }
+       |       simple_list1 ';'
+                       {
+                         $$ = $1;
+                         if (need_here_doc)
+                           gather_here_documents ();
+                         if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
+                           {
+                             global_command = $1;
+                             eof_encountered = 0;
+                             rewind_input_string ();
+                             YYACCEPT;
+                           }
+                       }
+       ;
+
+simple_list1:  simple_list1 AND_AND newline_list simple_list1
+                       { $$ = command_connect ($1, $4, AND_AND); }
+       |       simple_list1 OR_OR newline_list simple_list1
+                       { $$ = command_connect ($1, $4, OR_OR); }
+       |       simple_list1 '&' simple_list1
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, $3, '&');
+                         else
+                           $$ = command_connect ($1, $3, '&');
+                       }
+       |       simple_list1 ';' simple_list1
+                       { $$ = command_connect ($1, $3, ';'); }
+
+       |       pipeline_command
+                       { $$ = $1; }
+       ;
+
+pipeline_command: pipeline
+                       { $$ = $1; }                    
+       |       BANG pipeline_command
+                       {
+                         if ($2)
+                           $2->flags ^= CMD_INVERT_RETURN;     /* toggle */
+                         $$ = $2;
+                       }
+       |       timespec pipeline_command
+                       {
+                         if ($2)
+                           $2->flags |= $1;
+                         $$ = $2;
+                       }
+       |       timespec list_terminator
+                       {
+                         ELEMENT x;
+
+                         /* Boy, this is unclean.  `time' by itself can
+                            time a null command.  We cheat and push a
+                            newline back if the list_terminator was a newline
+                            to avoid the double-newline problem (one to
+                            terminate this, one to terminate the command) */
+                         x.word = 0;
+                         x.redirect = 0;
+                         $$ = make_simple_command (x, (COMMAND *)NULL);
+                         $$->flags |= $1;
+                         /* XXX - let's cheat and push a newline back */
+                         if ($2 == '\n')
+                           token_to_read = '\n';
+                       }
+       |       BANG list_terminator
+                       {
+                         ELEMENT x;
+
+                         /* This is just as unclean.  Posix says that `!'
+                            by itself should be equivalent to `false'.
+                            We cheat and push a
+                            newline back if the list_terminator was a newline
+                            to avoid the double-newline problem (one to
+                            terminate this, one to terminate the command) */
+                         x.word = 0;
+                         x.redirect = 0;
+                         $$ = make_simple_command (x, (COMMAND *)NULL);
+                         $$->flags |= CMD_INVERT_RETURN;
+                         /* XXX - let's cheat and push a newline back */
+                         if ($2 == '\n')
+                           token_to_read = '\n';
+                       }
+       ;
+
+pipeline:      pipeline '|' newline_list pipeline
+                       { $$ = command_connect ($1, $4, '|'); }
+       |       pipeline BAR_AND newline_list pipeline
+                       {
+                         /* Make cmd1 |& cmd2 equivalent to cmd1 2>&1 | cmd2 */
+                         COMMAND *tc;
+                         REDIRECTEE rd, sd;
+                         REDIRECT *r;
+
+                         tc = $1->type == cm_simple ? (COMMAND *)$1->value.Simple : $1;
+                         sd.dest = 2;
+                         rd.dest = 1;
+                         r = make_redirection (sd, r_duplicating_output, rd, 0);
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = r;
+                           }
+                         else
+                           tc->redirects = r;
+
+                         $$ = command_connect ($1, $4, '|');
+                       }
+       |       command
+                       { $$ = $1; }
+       ;
+
+timespec:      TIME
+                       { $$ = CMD_TIME_PIPELINE; }
+       |       TIME TIMEOPT
+                       { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
+       |       TIME TIMEOPT TIMEIGN
+                       { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
+       ;
+%%
+
+/* Initial size to allocate for tokens, and the
+   amount to grow them by. */
+#define TOKEN_DEFAULT_INITIAL_SIZE 496
+#define TOKEN_DEFAULT_GROW_SIZE 512
+
+/* Should we call prompt_again? */
+#define SHOULD_PROMPT() \
+  (interactive && (bash_input.type == st_stdin || bash_input.type == st_stream))
+
+#if defined (ALIAS)
+#  define expanding_alias() (pushed_string_list && pushed_string_list->expander)
+#else
+#  define expanding_alias() 0
+#endif
+
+/* Global var is non-zero when end of file has been reached. */
+int EOF_Reached = 0;
+
+#ifdef DEBUG
+static void
+debug_parser (i)
+     int i;
+{
+#if YYDEBUG != 0
+  yydebug = i;
+#endif
+}
+#endif
+
+/* yy_getc () returns the next available character from input or EOF.
+   yy_ungetc (c) makes `c' the next character to read.
+   init_yy_io (get, unget, type, location) makes the function GET the
+   installed function for getting the next character, makes UNGET the
+   installed function for un-getting a character, sets the type of stream
+   (either string or file) from TYPE, and makes LOCATION point to where
+   the input is coming from. */
+
+/* Unconditionally returns end-of-file. */
+int
+return_EOF ()
+{
+  return (EOF);
+}
+
+/* Variable containing the current get and unget functions.
+   See ./input.h for a clearer description. */
+BASH_INPUT bash_input;
+
+/* Set all of the fields in BASH_INPUT to NULL.  Free bash_input.name if it
+   is non-null, avoiding a memory leak. */
+void
+initialize_bash_input ()
+{
+  bash_input.type = st_none;
+  FREE (bash_input.name);
+  bash_input.name = (char *)NULL;
+  bash_input.location.file = (FILE *)NULL;
+  bash_input.location.string = (char *)NULL;
+  bash_input.getter = (sh_cget_func_t *)NULL;
+  bash_input.ungetter = (sh_cunget_func_t *)NULL;
+}
+
+/* Set the contents of the current bash input stream from
+   GET, UNGET, TYPE, NAME, and LOCATION. */
+void
+init_yy_io (get, unget, type, name, location)
+     sh_cget_func_t *get;
+     sh_cunget_func_t *unget;
+     enum stream_type type;
+     const char *name;
+     INPUT_STREAM location;
+{
+  bash_input.type = type;
+  FREE (bash_input.name);
+  bash_input.name = name ? savestring (name) : (char *)NULL;
+
+  /* XXX */
+#if defined (CRAY)
+  memcpy((char *)&bash_input.location.string, (char *)&location.string, sizeof(location));
+#else
+  bash_input.location = location;
+#endif
+  bash_input.getter = get;
+  bash_input.ungetter = unget;
+}
+
+char *
+yy_input_name ()
+{
+  return (bash_input.name ? bash_input.name : "stdin");
+}
+
+/* Call this to get the next character of input. */
+static int
+yy_getc ()
+{
+  return (*(bash_input.getter)) ();
+}
+
+/* Call this to unget C.  That is, to make C the next character
+   to be read. */
+static int
+yy_ungetc (c)
+     int c;
+{
+  return (*(bash_input.ungetter)) (c);
+}
+
+#if defined (BUFFERED_INPUT)
+#ifdef INCLUDE_UNUSED
+int
+input_file_descriptor ()
+{
+  switch (bash_input.type)
+    {
+    case st_stream:
+      return (fileno (bash_input.location.file));
+    case st_bstream:
+      return (bash_input.location.buffered_fd);
+    case st_stdin:
+    default:
+      return (fileno (stdin));
+    }
+}
+#endif
+#endif /* BUFFERED_INPUT */
+
+/* **************************************************************** */
+/*                                                                 */
+/*               Let input be read from readline ().               */
+/*                                                                 */
+/* **************************************************************** */
+
+#if defined (READLINE)
+char *current_readline_prompt = (char *)NULL;
+char *current_readline_line = (char *)NULL;
+int current_readline_line_index = 0;
+
+static int
+yy_readline_get ()
+{
+  SigHandler *old_sigint;
+  int line_len;
+  unsigned char c;
+
+  if (!current_readline_line)
+    {
+      if (!bash_readline_initialized)
+       initialize_readline ();
+
+#if defined (JOB_CONTROL)
+      if (job_control)
+       give_terminal_to (shell_pgrp, 0);
+#endif /* JOB_CONTROL */
+
+      old_sigint = (SigHandler *)IMPOSSIBLE_TRAP_HANDLER;
+      if (signal_is_ignored (SIGINT) == 0)
+       {
+         interrupt_immediately++;
+         old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler);
+       }
+      terminate_immediately = 1;
+
+      current_readline_line = readline (current_readline_prompt ?
+                                         current_readline_prompt : "");
+
+      terminate_immediately = 0;
+      if (signal_is_ignored (SIGINT) == 0)
+       {
+         interrupt_immediately--;
+         if (old_sigint != IMPOSSIBLE_TRAP_HANDLER)
+           set_signal_handler (SIGINT, old_sigint);
+       }
+
+#if 0
+      /* Reset the prompt to the decoded value of prompt_string_pointer. */
+      reset_readline_prompt ();
+#endif
+
+      if (current_readline_line == 0)
+       return (EOF);
+
+      current_readline_line_index = 0;
+      line_len = strlen (current_readline_line);
+
+      current_readline_line = (char *)xrealloc (current_readline_line, 2 + line_len);
+      current_readline_line[line_len++] = '\n';
+      current_readline_line[line_len] = '\0';
+    }
+
+  if (current_readline_line[current_readline_line_index] == 0)
+    {
+      free (current_readline_line);
+      current_readline_line = (char *)NULL;
+      return (yy_readline_get ());
+    }
+  else
+    {
+      c = current_readline_line[current_readline_line_index++];
+      return (c);
+    }
+}
+
+static int
+yy_readline_unget (c)
+     int c;
+{
+  if (current_readline_line_index && current_readline_line)
+    current_readline_line[--current_readline_line_index] = c;
+  return (c);
+}
+
+void
+with_input_from_stdin ()
+{
+  INPUT_STREAM location;
+
+  if (bash_input.type != st_stdin && stream_on_stack (st_stdin) == 0)
+    {
+      location.string = current_readline_line;
+      init_yy_io (yy_readline_get, yy_readline_unget,
+                 st_stdin, "readline stdin", location);
+    }
+}
+
+#else  /* !READLINE */
+
+void
+with_input_from_stdin ()
+{
+  with_input_from_stream (stdin, "stdin");
+}
+#endif /* !READLINE */
+
+/* **************************************************************** */
+/*                                                                 */
+/*   Let input come from STRING.  STRING is zero terminated.       */
+/*                                                                 */
+/* **************************************************************** */
+
+static int
+yy_string_get ()
+{
+  register char *string;
+  register unsigned char c;
+
+  string = bash_input.location.string;
+
+  /* If the string doesn't exist, or is empty, EOF found. */
+  if (string && *string)
+    {
+      c = *string++;
+      bash_input.location.string = string;
+      return (c);
+    }
+  else
+    return (EOF);
+}
+
+static int
+yy_string_unget (c)
+     int c;
+{
+  *(--bash_input.location.string) = c;
+  return (c);
+}
+
+void
+with_input_from_string (string, name)
+     char *string;
+     const char *name;
+{
+  INPUT_STREAM location;
+
+  location.string = string;
+  init_yy_io (yy_string_get, yy_string_unget, st_string, name, location);
+}
+
+/* Count the number of characters we've consumed from bash_input.location.string
+   and read into shell_input_line, but have not returned from shell_getc.
+   That is the true input location.  Rewind bash_input.location.string by
+   that number of characters, so it points to the last character actually
+   consumed by the parser. */
+static void
+rewind_input_string ()
+{
+  int xchars;
+
+  /* number of unconsumed characters in the input -- XXX need to take newlines
+     into account, e.g., $(...\n) */
+  xchars = shell_input_line_len - shell_input_line_index;
+  if (bash_input.location.string[-1] == '\n')
+    xchars++;
+
+  /* XXX - how to reflect bash_input.location.string back to string passed to
+     parse_and_execute or xparse_dolparen?  xparse_dolparen needs to know how
+     far into the string we parsed.  parse_and_execute knows where bash_input.
+     location.string is, and how far from orig_string that is -- that's the
+     number of characters the command consumed. */
+
+  /* bash_input.location.string - xchars should be where we parsed to */
+  /* need to do more validation on xchars value for sanity -- test cases. */
+  bash_input.location.string -= xchars;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                  Let input come from STREAM.                    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* These two functions used to test the value of the HAVE_RESTARTABLE_SYSCALLS
+   define, and just use getc/ungetc if it was defined, but since bash
+   installs its signal handlers without the SA_RESTART flag, some signals
+   (like SIGCHLD, SIGWINCH, etc.) received during a read(2) will not cause
+   the read to be restarted.  We need to restart it ourselves. */
+
+static int
+yy_stream_get ()
+{
+  int result;
+
+  result = EOF;
+  if (bash_input.location.file)
+    {
+      if (interactive)
+       {
+         interrupt_immediately++;
+         terminate_immediately++;
+       }
+      result = getc_with_restart (bash_input.location.file);
+      if (interactive)
+       {
+         interrupt_immediately--;
+         terminate_immediately--;
+       }
+    }
+  return (result);
+}
+
+static int
+yy_stream_unget (c)
+     int c;
+{
+  return (ungetc_with_restart (c, bash_input.location.file));
+}
+
+void
+with_input_from_stream (stream, name)
+     FILE *stream;
+     const char *name;
+{
+  INPUT_STREAM location;
+
+  location.file = stream;
+  init_yy_io (yy_stream_get, yy_stream_unget, st_stream, name, location);
+}
+
+typedef struct stream_saver {
+  struct stream_saver *next;
+  BASH_INPUT bash_input;
+  int line;
+#if defined (BUFFERED_INPUT)
+  BUFFERED_STREAM *bstream;
+#endif /* BUFFERED_INPUT */
+} STREAM_SAVER;
+
+/* The globally known line number. */
+int line_number = 0;
+
+#if defined (COND_COMMAND)
+static int cond_lineno;
+static int cond_token;
+#endif
+
+STREAM_SAVER *stream_list = (STREAM_SAVER *)NULL;
+
+void
+push_stream (reset_lineno)
+     int reset_lineno;
+{
+  STREAM_SAVER *saver = (STREAM_SAVER *)xmalloc (sizeof (STREAM_SAVER));
+
+  xbcopy ((char *)&bash_input, (char *)&(saver->bash_input), sizeof (BASH_INPUT));
+
+#if defined (BUFFERED_INPUT)
+  saver->bstream = (BUFFERED_STREAM *)NULL;
+  /* If we have a buffered stream, clear out buffers[fd]. */
+  if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0)
+    saver->bstream = set_buffered_stream (bash_input.location.buffered_fd,
+                                         (BUFFERED_STREAM *)NULL);
+#endif /* BUFFERED_INPUT */
+
+  saver->line = line_number;
+  bash_input.name = (char *)NULL;
+  saver->next = stream_list;
+  stream_list = saver;
+  EOF_Reached = 0;
+  if (reset_lineno)
+    line_number = 0;
+}
+
+void
+pop_stream ()
+{
+  if (!stream_list)
+    EOF_Reached = 1;
+  else
+    {
+      STREAM_SAVER *saver = stream_list;
+
+      EOF_Reached = 0;
+      stream_list = stream_list->next;
+
+      init_yy_io (saver->bash_input.getter,
+                 saver->bash_input.ungetter,
+                 saver->bash_input.type,
+                 saver->bash_input.name,
+                 saver->bash_input.location);
+
+#if defined (BUFFERED_INPUT)
+      /* If we have a buffered stream, restore buffers[fd]. */
+      /* If the input file descriptor was changed while this was on the
+        save stack, update the buffered fd to the new file descriptor and
+        re-establish the buffer <-> bash_input fd correspondence. */
+      if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0)
+       {
+         if (bash_input_fd_changed)
+           {
+             bash_input_fd_changed = 0;
+             if (default_buffered_input >= 0)
+               {
+                 bash_input.location.buffered_fd = default_buffered_input;
+                 saver->bstream->b_fd = default_buffered_input;
+                 SET_CLOSE_ON_EXEC (default_buffered_input);
+               }
+           }
+         /* XXX could free buffered stream returned as result here. */
+         set_buffered_stream (bash_input.location.buffered_fd, saver->bstream);
+       }
+#endif /* BUFFERED_INPUT */
+
+      line_number = saver->line;
+
+      FREE (saver->bash_input.name);
+      free (saver);
+    }
+}
+
+/* Return 1 if a stream of type TYPE is saved on the stack. */
+int
+stream_on_stack (type)
+     enum stream_type type;
+{
+  register STREAM_SAVER *s;
+
+  for (s = stream_list; s; s = s->next)
+    if (s->bash_input.type == type)
+      return 1;
+  return 0;
+}
+
+/* Save the current token state and return it in a malloced array. */
+int *
+save_token_state ()
+{
+  int *ret;
+
+  ret = (int *)xmalloc (4 * sizeof (int));
+  ret[0] = last_read_token;
+  ret[1] = token_before_that;
+  ret[2] = two_tokens_ago;
+  ret[3] = current_token;
+  return ret;
+}
+
+void
+restore_token_state (ts)
+     int *ts;
+{
+  if (ts == 0)
+    return;
+  last_read_token = ts[0];
+  token_before_that = ts[1];
+  two_tokens_ago = ts[2];
+  current_token = ts[3];
+}
+
+/*
+ * This is used to inhibit alias expansion and reserved word recognition
+ * inside case statement pattern lists.  A `case statement pattern list' is:
+ *
+ *     everything between the `in' in a `case word in' and the next ')'
+ *     or `esac'
+ *     everything between a `;;' and the next `)' or `esac'
+ */
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+
+#define END_OF_ALIAS 0
+
+/*
+ * Pseudo-global variables used in implementing token-wise alias expansion.
+ */
+
+/*
+ * Pushing and popping strings.  This works together with shell_getc to
+ * implement alias expansion on a per-token basis.
+ */
+
+typedef struct string_saver {
+  struct string_saver *next;
+  int expand_alias;  /* Value to set expand_alias to when string is popped. */
+  char *saved_line;
+#if defined (ALIAS)
+  alias_t *expander;   /* alias that caused this line to be pushed. */
+#endif
+  int saved_line_size, saved_line_index, saved_line_terminator;
+} STRING_SAVER;
+
+STRING_SAVER *pushed_string_list = (STRING_SAVER *)NULL;
+
+/*
+ * Push the current shell_input_line onto a stack of such lines and make S
+ * the current input.  Used when expanding aliases.  EXPAND is used to set
+ * the value of expand_next_token when the string is popped, so that the
+ * word after the alias in the original line is handled correctly when the
+ * alias expands to multiple words.  TOKEN is the token that was expanded
+ * into S; it is saved and used to prevent infinite recursive expansion.
+ */
+static void
+push_string (s, expand, ap)
+     char *s;
+     int expand;
+     alias_t *ap;
+{
+  STRING_SAVER *temp = (STRING_SAVER *)xmalloc (sizeof (STRING_SAVER));
+
+  temp->expand_alias = expand;
+  temp->saved_line = shell_input_line;
+  temp->saved_line_size = shell_input_line_size;
+  temp->saved_line_index = shell_input_line_index;
+  temp->saved_line_terminator = shell_input_line_terminator;
+#if defined (ALIAS)
+  temp->expander = ap;
+#endif
+  temp->next = pushed_string_list;
+  pushed_string_list = temp;
+
+#if defined (ALIAS)
+  if (ap)
+    ap->flags |= AL_BEINGEXPANDED;
+#endif
+
+  shell_input_line = s;
+  shell_input_line_size = strlen (s);
+  shell_input_line_index = 0;
+  shell_input_line_terminator = '\0';
+#if 0
+  parser_state &= ~PST_ALEXPNEXT;      /* XXX */
+#endif
+
+  set_line_mbstate ();
+}
+
+/*
+ * Make the top of the pushed_string stack be the current shell input.
+ * Only called when there is something on the stack.  Called from shell_getc
+ * when it thinks it has consumed the string generated by an alias expansion
+ * and needs to return to the original input line.
+ */
+static void
+pop_string ()
+{
+  STRING_SAVER *t;
+
+  FREE (shell_input_line);
+  shell_input_line = pushed_string_list->saved_line;
+  shell_input_line_index = pushed_string_list->saved_line_index;
+  shell_input_line_size = pushed_string_list->saved_line_size;
+  shell_input_line_terminator = pushed_string_list->saved_line_terminator;
+
+  if (pushed_string_list->expand_alias)
+    parser_state |= PST_ALEXPNEXT;
+  else
+    parser_state &= ~PST_ALEXPNEXT;
+
+  t = pushed_string_list;
+  pushed_string_list = pushed_string_list->next;
+
+#if defined (ALIAS)
+  if (t->expander)
+    t->expander->flags &= ~AL_BEINGEXPANDED;
+#endif
+
+  free ((char *)t);
+
+  set_line_mbstate ();
+}
+
+static void
+free_string_list ()
+{
+  register STRING_SAVER *t, *t1;
+
+  for (t = pushed_string_list; t; )
+    {
+      t1 = t->next;
+      FREE (t->saved_line);
+#if defined (ALIAS)
+      if (t->expander)
+       t->expander->flags &= ~AL_BEINGEXPANDED;
+#endif
+      free ((char *)t);
+      t = t1;
+    }
+  pushed_string_list = (STRING_SAVER *)NULL;
+}
+
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+void
+free_pushed_string_input ()
+{
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  free_string_list ();
+#endif
+}
+
+/* Return a line of text, taken from wherever yylex () reads input.
+   If there is no more input, then we return NULL.  If REMOVE_QUOTED_NEWLINE
+   is non-zero, we remove unquoted \<newline> pairs.  This is used by
+   read_secondary_line to read here documents. */
+static char *
+read_a_line (remove_quoted_newline)
+     int remove_quoted_newline;
+{
+  static char *line_buffer = (char *)NULL;
+  static int buffer_size = 0;
+  int indx, c, peekc, pass_next;
+
+#if defined (READLINE)
+  if (no_line_editing && SHOULD_PROMPT ())
+#else
+  if (SHOULD_PROMPT ())
+#endif
+    print_prompt ();
+
+  pass_next = indx = 0;
+  while (1)
+    {
+      /* Allow immediate exit if interrupted during input. */
+      QUIT;
+
+      c = yy_getc ();
+
+      /* Ignore null bytes in input. */
+      if (c == 0)
+       {
+#if 0
+         internal_warning ("read_a_line: ignored null byte in input");
+#endif
+         continue;
+       }
+
+      /* If there is no more input, then we return NULL. */
+      if (c == EOF)
+       {
+         if (interactive && bash_input.type == st_stream)
+           clearerr (stdin);
+         if (indx == 0)
+           return ((char *)NULL);
+         c = '\n';
+       }
+
+      /* `+2' in case the final character in the buffer is a newline. */
+      RESIZE_MALLOCED_BUFFER (line_buffer, indx, 2, buffer_size, 128);
+
+      /* IF REMOVE_QUOTED_NEWLINES is non-zero, we are reading a
+        here document with an unquoted delimiter.  In this case,
+        the line will be expanded as if it were in double quotes.
+        We allow a backslash to escape the next character, but we
+        need to treat the backslash specially only if a backslash
+        quoting a backslash-newline pair appears in the line. */
+      if (pass_next)
+       {
+         line_buffer[indx++] = c;
+         pass_next = 0;
+       }
+      else if (c == '\\' && remove_quoted_newline)
+       {
+         QUIT;
+         peekc = yy_getc ();
+         if (peekc == '\n')
+           {
+             line_number++;
+             continue; /* Make the unquoted \<newline> pair disappear. */
+           }
+         else
+           {
+             yy_ungetc (peekc);
+             pass_next = 1;
+             line_buffer[indx++] = c;          /* Preserve the backslash. */
+           }
+       }
+      else
+       line_buffer[indx++] = c;
+
+      if (c == '\n')
+       {
+         line_buffer[indx] = '\0';
+         return (line_buffer);
+       }
+    }
+}
+
+/* Return a line as in read_a_line (), but insure that the prompt is
+   the secondary prompt.  This is used to read the lines of a here
+   document.  REMOVE_QUOTED_NEWLINE is non-zero if we should remove
+   newlines quoted with backslashes while reading the line.  It is
+   non-zero unless the delimiter of the here document was quoted. */
+char *
+read_secondary_line (remove_quoted_newline)
+     int remove_quoted_newline;
+{
+  char *ret;
+  int n, c;
+
+  prompt_string_pointer = &ps2_prompt;
+  if (SHOULD_PROMPT())
+    prompt_again ();
+  ret = read_a_line (remove_quoted_newline);
+#if defined (HISTORY)
+  if (ret && remember_on_history && (parser_state & PST_HEREDOC))
+    {
+      /* To make adding the the here-document body right, we need to rely
+        on history_delimiting_chars() returning \n for the first line of
+        the here-document body and the null string for the second and
+        subsequent lines, so we avoid double newlines.
+        current_command_line_count == 2 for the first line of the body. */
+
+      current_command_line_count++;
+      maybe_add_history (ret);
+    }
+#endif /* HISTORY */
+  return ret;
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                             YYLEX ()                            */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Reserved words.  These are only recognized as the first word of a
+   command. */
+STRING_INT_ALIST word_token_alist[] = {
+  { "if", IF },
+  { "then", THEN },
+  { "else", ELSE },
+  { "elif", ELIF },
+  { "fi", FI },
+  { "case", CASE },
+  { "esac", ESAC },
+  { "for", FOR },
+#if defined (SELECT_COMMAND)
+  { "select", SELECT },
+#endif
+  { "while", WHILE },
+  { "until", UNTIL },
+  { "do", DO },
+  { "done", DONE },
+  { "in", IN },
+  { "function", FUNCTION },
+#if defined (COMMAND_TIMING)
+  { "time", TIME },
+#endif
+  { "{", '{' },
+  { "}", '}' },
+  { "!", BANG },
+#if defined (COND_COMMAND)
+  { "[[", COND_START },
+  { "]]", COND_END },
+#endif
+#if defined (COPROCESS_SUPPORT)
+  { "coproc", COPROC },
+#endif
+  { (char *)NULL, 0}
+};
+
+/* other tokens that can be returned by read_token() */
+STRING_INT_ALIST other_token_alist[] = {
+  /* Multiple-character tokens with special values */
+  { "--", TIMEIGN },
+  { "-p", TIMEOPT },
+  { "&&", AND_AND },
+  { "||", OR_OR },
+  { ">>", GREATER_GREATER },
+  { "<<", LESS_LESS },
+  { "<&", LESS_AND },
+  { ">&", GREATER_AND },
+  { ";;", SEMI_SEMI },
+  { ";&", SEMI_AND },
+  { ";;&", SEMI_SEMI_AND },
+  { "<<-", LESS_LESS_MINUS },
+  { "<<<", LESS_LESS_LESS },
+  { "&>", AND_GREATER },
+  { "&>>", AND_GREATER_GREATER },
+  { "<>", LESS_GREATER },
+  { ">|", GREATER_BAR },
+  { "|&", BAR_AND },
+  { "EOF", yacc_EOF },
+  /* Tokens whose value is the character itself */
+  { ">", '>' },
+  { "<", '<' },
+  { "-", '-' },
+  { "{", '{' },
+  { "}", '}' },
+  { ";", ';' },
+  { "(", '(' },
+  { ")", ')' },
+  { "|", '|' },
+  { "&", '&' },
+  { "newline", '\n' },
+  { (char *)NULL, 0}
+};
+
+/* others not listed here:
+       WORD                    look at yylval.word
+       ASSIGNMENT_WORD         look at yylval.word
+       NUMBER                  look at yylval.number
+       ARITH_CMD               look at yylval.word_list
+       ARITH_FOR_EXPRS         look at yylval.word_list
+       COND_CMD                look at yylval.command
+*/
+
+/* These are used by read_token_word, but appear up here so that shell_getc
+   can use them to decide when to add otherwise blank lines to the history. */
+
+/* The primary delimiter stack. */
+struct dstack dstack = {  (char *)NULL, 0, 0 };
+
+/* A temporary delimiter stack to be used when decoding prompt strings.
+   This is needed because command substitutions in prompt strings (e.g., PS2)
+   can screw up the parser's quoting state. */
+static struct dstack temp_dstack = { (char *)NULL, 0, 0 };
+
+/* Macro for accessing the top delimiter on the stack.  Returns the
+   delimiter or zero if none. */
+#define current_delimiter(ds) \
+  (ds.delimiter_depth ? ds.delimiters[ds.delimiter_depth - 1] : 0)
+
+#define push_delimiter(ds, character) \
+  do \
+    { \
+      if (ds.delimiter_depth + 2 > ds.delimiter_space) \
+       ds.delimiters = (char *)xrealloc \
+         (ds.delimiters, (ds.delimiter_space += 10) * sizeof (char)); \
+      ds.delimiters[ds.delimiter_depth] = character; \
+      ds.delimiter_depth++; \
+    } \
+  while (0)
+
+#define pop_delimiter(ds)      ds.delimiter_depth--
+
+/* Return the next shell input character.  This always reads characters
+   from shell_input_line; when that line is exhausted, it is time to
+   read the next line.  This is called by read_token when the shell is
+   processing normal command input. */
+
+/* This implements one-character lookahead/lookbehind across physical input
+   lines, to avoid something being lost because it's pushed back with
+   shell_ungetc when we're at the start of a line. */
+static int eol_ungetc_lookahead = 0;
+
+static int
+shell_getc (remove_quoted_newline)
+     int remove_quoted_newline;
+{
+  register int i;
+  int c;
+  unsigned char uc;
+
+  QUIT;
+
+  if (sigwinch_received)
+    {
+      sigwinch_received = 0;
+      get_new_window_size (0, (int *)0, (int *)0);
+    }
+      
+  if (eol_ungetc_lookahead)
+    {
+      c = eol_ungetc_lookahead;
+      eol_ungetc_lookahead = 0;
+      return (c);
+    }
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  /* If shell_input_line[shell_input_line_index] == 0, but there is
+     something on the pushed list of strings, then we don't want to go
+     off and get another line.  We let the code down below handle it. */
+
+  if (!shell_input_line || ((!shell_input_line[shell_input_line_index]) &&
+                           (pushed_string_list == (STRING_SAVER *)NULL)))
+#else /* !ALIAS && !DPAREN_ARITHMETIC */
+  if (!shell_input_line || !shell_input_line[shell_input_line_index])
+#endif /* !ALIAS && !DPAREN_ARITHMETIC */
+    {
+      line_number++;
+
+    restart_read:
+
+      /* Allow immediate exit if interrupted during input. */
+      QUIT;
+
+      i = 0;
+      shell_input_line_terminator = 0;
+
+      /* If the shell is interatctive, but not currently printing a prompt
+         (interactive_shell && interactive == 0), we don't want to print
+         notifies or cleanup the jobs -- we want to defer it until we do
+         print the next prompt. */
+      if (interactive_shell == 0 || SHOULD_PROMPT())
+       {
+#if defined (JOB_CONTROL)
+      /* This can cause a problem when reading a command as the result
+        of a trap, when the trap is called from flush_child.  This call
+        had better not cause jobs to disappear from the job table in
+        that case, or we will have big trouble. */
+         notify_and_cleanup ();
+#else /* !JOB_CONTROL */
+         cleanup_dead_jobs ();
+#endif /* !JOB_CONTROL */
+       }
+
+#if defined (READLINE)
+      if (no_line_editing && SHOULD_PROMPT())
+#else
+      if (SHOULD_PROMPT())
+#endif
+       print_prompt ();
+
+      if (bash_input.type == st_stream)
+       clearerr (stdin);
+
+      while (1)
+       {
+         c = yy_getc ();
+
+         /* Allow immediate exit if interrupted during input. */
+         QUIT;
+
+         if (c == '\0')
+           {
+#if 0
+             internal_warning ("shell_getc: ignored null byte in input");
+#endif
+             continue;
+           }
+
+         RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256);
+
+         if (c == EOF)
+           {
+             if (bash_input.type == st_stream)
+               clearerr (stdin);
+
+             if (i == 0)
+               shell_input_line_terminator = EOF;
+
+             shell_input_line[i] = '\0';
+             break;
+           }
+
+         shell_input_line[i++] = c;
+
+         if (c == '\n')
+           {
+             shell_input_line[--i] = '\0';
+             current_command_line_count++;
+             break;
+           }
+       }
+
+      shell_input_line_index = 0;
+      shell_input_line_len = i;                /* == strlen (shell_input_line) */
+
+      set_line_mbstate ();
+
+#if defined (HISTORY)
+      if (remember_on_history && shell_input_line && shell_input_line[0])
+       {
+         char *expansions;
+#  if defined (BANG_HISTORY)
+         int old_hist;
+
+         /* If the current delimiter is a single quote, we should not be
+            performing history expansion, even if we're on a different
+            line from the original single quote. */
+         old_hist = history_expansion_inhibited;
+         if (current_delimiter (dstack) == '\'')
+           history_expansion_inhibited = 1;
+#  endif
+         expansions = pre_process_line (shell_input_line, 1, 1);
+#  if defined (BANG_HISTORY)
+         history_expansion_inhibited = old_hist;
+#  endif
+         if (expansions != shell_input_line)
+           {
+             free (shell_input_line);
+             shell_input_line = expansions;
+             shell_input_line_len = shell_input_line ?
+                                       strlen (shell_input_line) : 0;
+             if (shell_input_line_len == 0)
+               current_command_line_count--;
+
+             /* We have to force the xrealloc below because we don't know
+                the true allocated size of shell_input_line anymore. */
+             shell_input_line_size = shell_input_line_len;
+
+             set_line_mbstate ();
+           }
+       }
+      /* Try to do something intelligent with blank lines encountered while
+        entering multi-line commands.  XXX - this is grotesque */
+      else if (remember_on_history && shell_input_line &&
+              shell_input_line[0] == '\0' &&
+              current_command_line_count > 1)
+       {
+         if (current_delimiter (dstack))
+           /* We know shell_input_line[0] == 0 and we're reading some sort of
+              quoted string.  This means we've got a line consisting of only
+              a newline in a quoted string.  We want to make sure this line
+              gets added to the history. */
+           maybe_add_history (shell_input_line);
+         else
+           {
+             char *hdcs;
+             hdcs = history_delimiting_chars (shell_input_line);
+             if (hdcs && hdcs[0] == ';')
+               maybe_add_history (shell_input_line);
+           }
+       }
+
+#endif /* HISTORY */
+
+      if (shell_input_line)
+       {
+         /* Lines that signify the end of the shell's input should not be
+            echoed. */
+         if (echo_input_at_read && (shell_input_line[0] ||
+                                    shell_input_line_terminator != EOF))
+           fprintf (stderr, "%s\n", shell_input_line);
+       }
+      else
+       {
+         shell_input_line_size = 0;
+         prompt_string_pointer = &current_prompt_string;
+         if (SHOULD_PROMPT ())
+           prompt_again ();
+         goto restart_read;
+       }
+
+      /* Add the newline to the end of this string, iff the string does
+        not already end in an EOF character.  */
+      if (shell_input_line_terminator != EOF)
+       {
+         if (shell_input_line_len + 3 > shell_input_line_size)
+           shell_input_line = (char *)xrealloc (shell_input_line,
+                                       1 + (shell_input_line_size += 2));
+
+         shell_input_line[shell_input_line_len] = '\n';
+         shell_input_line[shell_input_line_len + 1] = '\0';
+
+         set_line_mbstate ();
+       }
+    }
+
+next_alias_char:
+  uc = shell_input_line[shell_input_line_index];
+
+  if (uc)
+    shell_input_line_index++;
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  /* If UC is NULL, we have reached the end of the current input string.  If
+     pushed_string_list is non-empty, it's time to pop to the previous string
+     because we have fully consumed the result of the last alias expansion.
+     Do it transparently; just return the next character of the string popped
+     to. */
+pop_alias:
+  if (uc == 0 && (pushed_string_list != (STRING_SAVER *)NULL))
+    {
+      pop_string ();
+      uc = shell_input_line[shell_input_line_index];
+      if (uc)
+       shell_input_line_index++;
+    }
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+  if MBTEST(uc == '\\' && remove_quoted_newline && shell_input_line[shell_input_line_index] == '\n')
+    {
+       if (SHOULD_PROMPT ())
+         prompt_again ();
+       line_number++;
+       /* What do we do here if we're expanding an alias whose definition
+          includes an escaped newline?  If that's the last character in the
+          alias expansion, we just pop the pushed string list (recall that
+          we inhibit the appending of a space in mk_alexpansion() if newline
+          is the last character).  If it's not the last character, we need
+          to consume the quoted newline and move to the next character in
+          the expansion. */
+       if (expanding_alias () && shell_input_line[shell_input_line_index+1] == '\0')
+         {
+           uc = 0;
+           goto pop_alias;
+         }
+       else if (expanding_alias () && shell_input_line[shell_input_line_index+1] != '\0')
+         {
+           shell_input_line_index++;   /* skip newline */
+           goto next_alias_char;       /* and get next character */
+         }
+       else        
+         goto restart_read;
+    }
+
+  if (uc == 0 && shell_input_line_terminator == EOF)
+    return ((shell_input_line_index != 0) ? '\n' : EOF);
+
+  return (uc);
+}
+
+/* Put C back into the input for the shell.  This might need changes for
+   HANDLE_MULTIBYTE around EOLs.  Since we (currently) never push back a
+   character different than we read, shell_input_line_property doesn't need
+   to change when manipulating shell_input_line.  The define for
+   last_shell_getc_is_singlebyte should take care of it, though. */
+static void
+shell_ungetc (c)
+     int c;
+{
+  if (shell_input_line && shell_input_line_index)
+    shell_input_line[--shell_input_line_index] = c;
+  else
+    eol_ungetc_lookahead = c;
+}
+
+#ifdef INCLUDE_UNUSED
+/* Back the input pointer up by one, effectively `ungetting' a character. */
+static void
+shell_ungetchar ()
+{
+  if (shell_input_line && shell_input_line_index)
+    shell_input_line_index--;
+}
+#endif
+
+/* Discard input until CHARACTER is seen, then push that character back
+   onto the input stream. */
+static void
+discard_until (character)
+     int character;
+{
+  int c;
+
+  while ((c = shell_getc (0)) != EOF && c != character)
+    ;
+
+  if (c != EOF)
+    shell_ungetc (c);
+}
+
+void
+execute_variable_command (command, vname)
+     char *command, *vname;
+{
+  char *last_lastarg;
+  sh_parser_state_t ps;
+
+  save_parser_state (&ps);
+  last_lastarg = get_string_value ("_");
+  if (last_lastarg)
+    last_lastarg = savestring (last_lastarg);
+
+  parse_and_execute (savestring (command), vname, SEVAL_NONINT|SEVAL_NOHIST);
+
+  restore_parser_state (&ps);
+  bind_variable ("_", last_lastarg, 0);
+  FREE (last_lastarg);
+
+  if (token_to_read == '\n')   /* reset_parser was called */
+    token_to_read = 0;
+}
+
+/* Place to remember the token.  We try to keep the buffer
+   at a reasonable size, but it can grow. */
+static char *token = (char *)NULL;
+
+/* Current size of the token buffer. */
+static int token_buffer_size;
+
+/* Command to read_token () explaining what we want it to do. */
+#define READ 0
+#define RESET 1
+#define prompt_is_ps1 \
+      (!prompt_string_pointer || prompt_string_pointer == &ps1_prompt)
+
+/* Function for yyparse to call.  yylex keeps track of
+   the last two tokens read, and calls read_token.  */
+static int
+yylex ()
+{
+  if (interactive && (current_token == 0 || current_token == '\n'))
+    {
+      /* Before we print a prompt, we might have to check mailboxes.
+        We do this only if it is time to do so. Notice that only here
+        is the mail alarm reset; nothing takes place in check_mail ()
+        except the checking of mail.  Please don't change this. */
+      if (prompt_is_ps1 && time_to_check_mail ())
+       {
+         check_mail ();
+         reset_mail_timer ();
+       }
+
+      /* Avoid printing a prompt if we're not going to read anything, e.g.
+        after resetting the parser with read_token (RESET). */
+      if (token_to_read == 0 && SHOULD_PROMPT ())
+       prompt_again ();
+    }
+
+  two_tokens_ago = token_before_that;
+  token_before_that = last_read_token;
+  last_read_token = current_token;
+  current_token = read_token (READ);
+
+  if ((parser_state & PST_EOFTOKEN) && current_token == shell_eof_token)
+    {
+      current_token = yacc_EOF;
+      if (bash_input.type == st_string)
+       rewind_input_string ();
+    }
+  parser_state &= ~PST_EOFTOKEN;
+
+  return (current_token);
+}
+
+/* When non-zero, we have read the required tokens
+   which allow ESAC to be the next one read. */
+static int esacs_needed_count;
+
+void
+gather_here_documents ()
+{
+  int r;
+
+  r = 0;
+  while (need_here_doc)
+    {
+      parser_state |= PST_HEREDOC;
+      make_here_document (redir_stack[r++], line_number);
+      parser_state &= ~PST_HEREDOC;
+      need_here_doc--;
+    }
+}
+
+/* When non-zero, an open-brace used to create a group is awaiting a close
+   brace partner. */
+static int open_brace_count;
+
+#define command_token_position(token) \
+  (((token) == ASSIGNMENT_WORD) || (parser_state&PST_REDIRLIST) || \
+   ((token) != SEMI_SEMI && (token) != SEMI_AND && (token) != SEMI_SEMI_AND && reserved_word_acceptable(token)))
+
+#define assignment_acceptable(token) \
+  (command_token_position(token) && ((parser_state & PST_CASEPAT) == 0))
+
+/* Check to see if TOKEN is a reserved word and return the token
+   value if it is. */
+#define CHECK_FOR_RESERVED_WORD(tok) \
+  do { \
+    if (!dollar_present && !quoted && \
+       reserved_word_acceptable (last_read_token)) \
+      { \
+       int i; \
+       for (i = 0; word_token_alist[i].word != (char *)NULL; i++) \
+         if (STREQ (tok, word_token_alist[i].word)) \
+           { \
+             if ((parser_state & PST_CASEPAT) && (word_token_alist[i].token != ESAC)) \
+               break; \
+             if (word_token_alist[i].token == TIME && time_command_acceptable () == 0) \
+               break; \
+             if (word_token_alist[i].token == ESAC) \
+               parser_state &= ~(PST_CASEPAT|PST_CASESTMT); \
+             else if (word_token_alist[i].token == CASE) \
+               parser_state |= PST_CASESTMT; \
+             else if (word_token_alist[i].token == COND_END) \
+               parser_state &= ~(PST_CONDCMD|PST_CONDEXPR); \
+             else if (word_token_alist[i].token == COND_START) \
+               parser_state |= PST_CONDCMD; \
+             else if (word_token_alist[i].token == '{') \
+               open_brace_count++; \
+             else if (word_token_alist[i].token == '}' && open_brace_count) \
+               open_brace_count--; \
+             return (word_token_alist[i].token); \
+           } \
+      } \
+  } while (0)
+
+#if defined (ALIAS)
+
+    /* OK, we have a token.  Let's try to alias expand it, if (and only if)
+       it's eligible.
+
+       It is eligible for expansion if EXPAND_ALIASES is set, and
+       the token is unquoted and the last token read was a command
+       separator (or expand_next_token is set), and we are currently
+       processing an alias (pushed_string_list is non-empty) and this
+       token is not the same as the current or any previously
+       processed alias.
+
+       Special cases that disqualify:
+        In a pattern list in a case statement (parser_state & PST_CASEPAT). */
+
+static char *
+mk_alexpansion (s)
+     char *s;
+{
+  int l;
+  char *r;
+
+  l = strlen (s);
+  r = xmalloc (l + 2);
+  strcpy (r, s);
+  /* If the last character in the alias is a newline, don't add a trailing
+     space to the expansion.  Works with shell_getc above. */
+  if (r[l - 1] != ' ' && r[l - 1] != '\n')
+    r[l++] = ' ';
+  r[l] = '\0';
+  return r;
+}
+
+static int
+alias_expand_token (tokstr)
+     char *tokstr;
+{
+  char *expanded;
+  alias_t *ap;
+
+  if (((parser_state & PST_ALEXPNEXT) || command_token_position (last_read_token)) &&
+       (parser_state & PST_CASEPAT) == 0)
+    {
+      ap = find_alias (tokstr);
+
+      /* Currently expanding this token. */
+      if (ap && (ap->flags & AL_BEINGEXPANDED))
+       return (NO_EXPANSION);
+
+      /* mk_alexpansion puts an extra space on the end of the alias expansion,
+         so the lookahead by the parser works right.  If this gets changed,
+         make sure the code in shell_getc that deals with reaching the end of
+         an expanded alias is changed with it. */
+      expanded = ap ? mk_alexpansion (ap->value) : (char *)NULL;
+
+      if (expanded)
+       {
+         push_string (expanded, ap->flags & AL_EXPANDNEXT, ap);
+         return (RE_READ_TOKEN);
+       }
+      else
+       /* This is an eligible token that does not have an expansion. */
+       return (NO_EXPANSION);
+    }
+  return (NO_EXPANSION);
+}
+#endif /* ALIAS */
+
+static int
+time_command_acceptable ()
+{
+#if defined (COMMAND_TIMING)
+  switch (last_read_token)
+    {
+    case 0:
+    case ';':
+    case '\n':
+    case AND_AND:
+    case OR_OR:
+    case '&':
+    case DO:
+    case THEN:
+    case ELSE:
+    case '{':          /* } */
+    case '(':          /* ) */
+    case BANG:         /* ! time pipeline */
+    case TIME:         /* time time pipeline */
+    case TIMEOPT:      /* time -p time pipeline */
+    case TIMEIGN:      /* time -p -- ... */
+      return 1;
+    default:
+      return 0;
+    }
+#else
+  return 0;
+#endif /* COMMAND_TIMING */
+}
+
+/* Handle special cases of token recognition:
+       IN is recognized if the last token was WORD and the token
+       before that was FOR or CASE or SELECT.
+
+       DO is recognized if the last token was WORD and the token
+       before that was FOR or SELECT.
+
+       ESAC is recognized if the last token caused `esacs_needed_count'
+       to be set
+
+       `{' is recognized if the last token as WORD and the token
+       before that was FUNCTION, or if we just parsed an arithmetic
+       `for' command.
+
+       `}' is recognized if there is an unclosed `{' present.
+
+       `-p' is returned as TIMEOPT if the last read token was TIME.
+       `--' is returned as TIMEIGN if the last read token was TIMEOPT.
+
+       ']]' is returned as COND_END if the parser is currently parsing
+       a conditional expression ((parser_state & PST_CONDEXPR) != 0)
+
+       `time' is returned as TIME if and only if it is immediately
+       preceded by one of `;', `\n', `||', `&&', or `&'.
+*/
+
+static int
+special_case_tokens (tokstr)
+     char *tokstr;
+{
+  if ((last_read_token == WORD) &&
+#if defined (SELECT_COMMAND)
+      ((token_before_that == FOR) || (token_before_that == CASE) || (token_before_that == SELECT)) &&
+#else
+      ((token_before_that == FOR) || (token_before_that == CASE)) &&
+#endif
+      (tokstr[0] == 'i' && tokstr[1] == 'n' && tokstr[2] == 0))
+    {
+      if (token_before_that == CASE)
+       {
+         parser_state |= PST_CASEPAT;
+         esacs_needed_count++;
+       }
+      return (IN);
+    }
+
+  if (last_read_token == WORD &&
+#if defined (SELECT_COMMAND)
+      (token_before_that == FOR || token_before_that == SELECT) &&
+#else
+      (token_before_that == FOR) &&
+#endif
+      (tokstr[0] == 'd' && tokstr[1] == 'o' && tokstr[2] == '\0'))
+    return (DO);
+
+  /* Ditto for ESAC in the CASE case.
+     Specifically, this handles "case word in esac", which is a legal
+     construct, certainly because someone will pass an empty arg to the
+     case construct, and we don't want it to barf.  Of course, we should
+     insist that the case construct has at least one pattern in it, but
+     the designers disagree. */
+  if (esacs_needed_count)
+    {
+      esacs_needed_count--;
+      if (STREQ (tokstr, "esac"))
+       {
+         parser_state &= ~PST_CASEPAT;
+         return (ESAC);
+       }
+    }
+
+  /* The start of a shell function definition. */
+  if (parser_state & PST_ALLOWOPNBRC)
+    {
+      parser_state &= ~PST_ALLOWOPNBRC;
+      if (tokstr[0] == '{' && tokstr[1] == '\0')               /* } */
+       {
+         open_brace_count++;
+         function_bstart = line_number;
+         return ('{');                                 /* } */
+       }
+    }
+
+  /* We allow a `do' after a for ((...)) without an intervening
+     list_terminator */
+  if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == 'd' && tokstr[1] == 'o' && !tokstr[2])
+    return (DO);
+  if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == '{' && tokstr[1] == '\0')     /* } */
+    {
+      open_brace_count++;
+      return ('{');                    /* } */
+    }
+
+  if (open_brace_count && reserved_word_acceptable (last_read_token) && tokstr[0] == '}' && !tokstr[1])
+    {
+      open_brace_count--;              /* { */
+      return ('}');
+    }
+
+#if defined (COMMAND_TIMING)
+  /* Handle -p after `time'. */
+  if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2])
+    return (TIMEOPT);
+#if 0
+  /* Handle -- after `time -p'. */
+  if (last_read_token == TIMEOPT && tokstr[0] == '-' && tokstr[1] == '-' && !tokstr[2])
+    return (TIMEIGN);
+#endif
+#endif
+
+#if defined (COND_COMMAND) /* [[ */
+  if ((parser_state & PST_CONDEXPR) && tokstr[0] == ']' && tokstr[1] == ']' && tokstr[2] == '\0')
+    return (COND_END);
+#endif
+
+  return (-1);
+}
+
+/* Called from shell.c when Control-C is typed at top level.  Or
+   by the error rule at top level. */
+void
+reset_parser ()
+{
+  dstack.delimiter_depth = 0;  /* No delimiters found so far. */
+  open_brace_count = 0;
+
+#if defined (EXTENDED_GLOB)
+  /* Reset to global value of extended glob */
+  if (parser_state & PST_EXTPAT)
+    extended_glob = global_extglob;
+#endif
+
+  parser_state = 0;
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  if (pushed_string_list)
+    free_string_list ();
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+  if (shell_input_line)
+    {
+      free (shell_input_line);
+      shell_input_line = (char *)NULL;
+      shell_input_line_size = shell_input_line_index = 0;
+    }
+
+  FREE (word_desc_to_read);
+  word_desc_to_read = (WORD_DESC *)NULL;
+
+  current_token = '\n';                /* XXX */
+  last_read_token = '\n';
+  token_to_read = '\n';
+}
+
+/* Read the next token.  Command can be READ (normal operation) or
+   RESET (to normalize state). */
+static int
+read_token (command)
+     int command;
+{
+  int character;               /* Current character. */
+  int peek_char;               /* Temporary look-ahead character. */
+  int result;                  /* The thing to return. */
+
+  if (command == RESET)
+    {
+      reset_parser ();
+      return ('\n');
+    }
+
+  if (token_to_read)
+    {
+      result = token_to_read;
+      if (token_to_read == WORD || token_to_read == ASSIGNMENT_WORD)
+       {
+         yylval.word = word_desc_to_read;
+         word_desc_to_read = (WORD_DESC *)NULL;
+       }
+      token_to_read = 0;
+      return (result);
+    }
+
+#if defined (COND_COMMAND)
+  if ((parser_state & (PST_CONDCMD|PST_CONDEXPR)) == PST_CONDCMD)
+    {
+      cond_lineno = line_number;
+      parser_state |= PST_CONDEXPR;
+      yylval.command = parse_cond_command ();
+      if (cond_token != COND_END)
+       {
+         cond_error ();
+         return (-1);
+       }
+      token_to_read = COND_END;
+      parser_state &= ~(PST_CONDEXPR|PST_CONDCMD);
+      return (COND_CMD);
+    }
+#endif
+
+#if defined (ALIAS)
+  /* This is a place to jump back to once we have successfully expanded a
+     token with an alias and pushed the string with push_string () */
+ re_read_token:
+#endif /* ALIAS */
+
+  /* Read a single word from input.  Start by skipping blanks. */
+  while ((character = shell_getc (1)) != EOF && shellblank (character))
+    ;
+
+  if (character == EOF)
+    {
+      EOF_Reached = 1;
+      return (yacc_EOF);
+    }
+
+  if MBTEST(character == '#' && (!interactive || interactive_comments))
+    {
+      /* A comment.  Discard until EOL or EOF, and then return a newline. */
+      discard_until ('\n');
+      shell_getc (0);
+      character = '\n';        /* this will take the next if statement and return. */
+    }
+
+  if (character == '\n')
+    {
+      /* If we're about to return an unquoted newline, we can go and collect
+        the text of any pending here document. */
+      if (need_here_doc)
+       gather_here_documents ();
+
+#if defined (ALIAS)
+      parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+
+      parser_state &= ~PST_ASSIGNOK;
+
+      return (character);
+    }
+
+  if (parser_state & PST_REGEXP)
+    goto tokword;
+
+  /* Shell meta-characters. */
+  if MBTEST(shellmeta (character) && ((parser_state & PST_DBLPAREN) == 0))
+    {
+#if defined (ALIAS)
+      /* Turn off alias tokenization iff this character sequence would
+        not leave us ready to read a command. */
+      if (character == '<' || character == '>')
+       parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+
+      parser_state &= ~PST_ASSIGNOK;
+
+      peek_char = shell_getc (1);
+      if (character == peek_char)
+       {
+         switch (character)
+           {
+           case '<':
+             /* If '<' then we could be at "<<" or at "<<-".  We have to
+                look ahead one more character. */
+             peek_char = shell_getc (1);
+             if MBTEST(peek_char == '-')
+               return (LESS_LESS_MINUS);
+             else if MBTEST(peek_char == '<')
+               return (LESS_LESS_LESS);
+             else
+               {
+                 shell_ungetc (peek_char);
+                 return (LESS_LESS);
+               }
+
+           case '>':
+             return (GREATER_GREATER);
+
+           case ';':
+             parser_state |= PST_CASEPAT;
+#if defined (ALIAS)
+             parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+
+             peek_char = shell_getc (1);
+             if MBTEST(peek_char == '&')
+               return (SEMI_SEMI_AND);
+             else
+               {
+                 shell_ungetc (peek_char);
+                 return (SEMI_SEMI);
+               }
+
+           case '&':
+             return (AND_AND);
+
+           case '|':
+             return (OR_OR);
+
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+           case '(':           /* ) */
+             result = parse_dparen (character);
+             if (result == -2)
+               break;
+             else
+               return result;
+#endif
+           }
+       }
+      else if MBTEST(character == '<' && peek_char == '&')
+       return (LESS_AND);
+      else if MBTEST(character == '>' && peek_char == '&')
+       return (GREATER_AND);
+      else if MBTEST(character == '<' && peek_char == '>')
+       return (LESS_GREATER);
+      else if MBTEST(character == '>' && peek_char == '|')
+       return (GREATER_BAR);
+      else if MBTEST(character == '&' && peek_char == '>')
+       {
+         peek_char = shell_getc (1);
+         if MBTEST(peek_char == '>')
+           return (AND_GREATER_GREATER);
+         else
+           {
+             shell_ungetc (peek_char);
+             return (AND_GREATER);
+           }
+       }
+      else if MBTEST(character == '|' && peek_char == '&')
+       return (BAR_AND);
+      else if MBTEST(character == ';' && peek_char == '&')
+       {
+         parser_state |= PST_CASEPAT;
+#if defined (ALIAS)
+         parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+         return (SEMI_AND);
+       }
+
+      shell_ungetc (peek_char);
+
+      /* If we look like we are reading the start of a function
+        definition, then let the reader know about it so that
+        we will do the right thing with `{'. */
+      if MBTEST(character == ')' && last_read_token == '(' && token_before_that == WORD)
+       {
+         parser_state |= PST_ALLOWOPNBRC;
+#if defined (ALIAS)
+         parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+         function_dstart = line_number;
+       }
+
+      /* case pattern lists may be preceded by an optional left paren.  If
+        we're not trying to parse a case pattern list, the left paren
+        indicates a subshell. */
+      if MBTEST(character == '(' && (parser_state & PST_CASEPAT) == 0) /* ) */
+       parser_state |= PST_SUBSHELL;
+      /*(*/
+      else if MBTEST((parser_state & PST_CASEPAT) && character == ')')
+       parser_state &= ~PST_CASEPAT;
+      /*(*/
+      else if MBTEST((parser_state & PST_SUBSHELL) && character == ')')
+       parser_state &= ~PST_SUBSHELL;
+
+#if defined (PROCESS_SUBSTITUTION)
+      /* Check for the constructs which introduce process substitution.
+        Shells running in `posix mode' don't do process substitution. */
+      if MBTEST(posixly_correct || ((character != '>' && character != '<') || peek_char != '(')) /*)*/
+#endif /* PROCESS_SUBSTITUTION */
+       return (character);
+    }
+
+  /* Hack <&- (close stdin) case.  Also <&N- (dup and close). */
+  if MBTEST(character == '-' && (last_read_token == LESS_AND || last_read_token == GREATER_AND))
+    return (character);
+
+tokword:
+  /* Okay, if we got this far, we have to read a word.  Read one,
+     and then check it against the known ones. */
+  result = read_token_word (character);
+#if defined (ALIAS)
+  if (result == RE_READ_TOKEN)
+    goto re_read_token;
+#endif
+  return result;
+}
+
+/*
+ * Match a $(...) or other grouping construct.  This has to handle embedded
+ * quoted strings ('', ``, "") and nested constructs.  It also must handle
+ * reprompting the user, if necessary, after reading a newline, and returning
+ * correct error values if it reads EOF.
+ */
+#define P_FIRSTCLOSE   0x0001
+#define P_ALLOWESC     0x0002
+#define P_DQUOTE       0x0004
+#define P_COMMAND      0x0008  /* parsing a command, so look for comments */
+#define P_BACKQUOTE    0x0010  /* parsing a backquoted command substitution */
+#define P_ARRAYSUB     0x0020  /* parsing a [...] array subscript for assignment */
+#define P_DOLBRACE     0x0040  /* parsing a ${...} construct */
+
+/* Lexical state while parsing a grouping construct or $(...). */
+#define LEX_WASDOL     0x001
+#define LEX_CKCOMMENT  0x002
+#define LEX_INCOMMENT  0x004
+#define LEX_PASSNEXT   0x008
+#define LEX_RESWDOK    0x010
+#define LEX_CKCASE     0x020
+#define LEX_INCASE     0x040
+#define LEX_INHEREDOC  0x080
+#define LEX_HEREDELIM  0x100           /* reading here-doc delimiter */
+#define LEX_STRIPDOC   0x200           /* <<- strip tabs from here doc delim */
+#define LEX_INWORD     0x400
+
+#define COMSUB_META(ch)                ((ch) == ';' || (ch) == '&' || (ch) == '|')
+
+#define CHECK_NESTRET_ERROR() \
+  do { \
+    if (nestret == &matched_pair_error) \
+      { \
+       free (ret); \
+       return &matched_pair_error; \
+      } \
+  } while (0)
+
+#define APPEND_NESTRET() \
+  do { \
+    if (nestlen) \
+      { \
+       RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64); \
+       strcpy (ret + retind, nestret); \
+       retind += nestlen; \
+      } \
+  } while (0)
+
+static char matched_pair_error;
+
+static char *
+parse_matched_pair (qc, open, close, lenp, flags)
+     int qc;   /* `"' if this construct is within double quotes */
+     int open, close;
+     int *lenp, flags;
+{
+  int count, ch, tflags;
+  int nestlen, ttranslen, start_lineno;
+  char *ret, *nestret, *ttrans;
+  int retind, retsize, rflags;
+  int dolbrace_state;
+
+  dolbrace_state = (flags & P_DOLBRACE) ? DOLBRACE_PARAM : 0;
+
+/*itrace("parse_matched_pair[%d]: open = %c close = %c flags = %d", line_number, open, close, flags);*/
+  count = 1;
+  tflags = 0;
+
+  if ((flags & P_COMMAND) && qc != '`' && qc != '\'' && qc != '"' && (flags & P_DQUOTE) == 0)
+    tflags |= LEX_CKCOMMENT;
+
+  /* RFLAGS is the set of flags we want to pass to recursive calls. */
+  rflags = (qc == '"') ? P_DQUOTE : (flags & P_DQUOTE);
+
+  ret = (char *)xmalloc (retsize = 64);
+  retind = 0;
+
+  start_lineno = line_number;
+  while (count)
+    {
+      ch = shell_getc (qc != '\'' && (tflags & LEX_PASSNEXT) == 0);
+
+      if (ch == EOF)
+       {
+         free (ret);
+         parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close);
+         EOF_Reached = 1;      /* XXX */
+         return (&matched_pair_error);
+       }
+
+      /* Possible reprompting. */
+      if (ch == '\n' && SHOULD_PROMPT ())
+       prompt_again ();
+
+      /* Don't bother counting parens or doing anything else if in a comment
+        or part of a case statement */
+      if (tflags & LEX_INCOMMENT)
+       {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+
+         if (ch == '\n')
+           tflags &= ~LEX_INCOMMENT;
+
+         continue;
+       }
+
+      /* Not exactly right yet, should handle shell metacharacters, too.  If
+        any changes are made to this test, make analogous changes to subst.c:
+        extract_delimited_string(). */
+      else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || shellblank (ret[retind - 1])))
+       tflags |= LEX_INCOMMENT;
+
+      if (tflags & LEX_PASSNEXT)               /* last char was backslash */
+       {
+         tflags &= ~LEX_PASSNEXT;
+         if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
+           {
+             if (retind > 0)
+               retind--;       /* swallow previously-added backslash */
+             continue;
+           }
+
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         if MBTEST(ch == CTLESC || ch == CTLNUL)
+           ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+      /* If we're reparsing the input (e.g., from parse_string_to_word_list),
+        we've already prepended CTLESC to single-quoted results of $'...'.
+        We may want to do this for other CTLESC-quoted characters in
+        reparse, too. */
+      else if MBTEST((parser_state & PST_REPARSE) && open == '\'' && (ch == CTLESC || ch == CTLNUL))
+       {
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+         continue;
+       }
+      else if MBTEST(ch == CTLESC || ch == CTLNUL)     /* special shell escapes */
+       {
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+      else if MBTEST(ch == close)              /* ending delimiter */
+       count--;
+      /* handle nested ${...} specially. */
+      else if MBTEST(open != close && (tflags & LEX_WASDOL) && open == '{' && ch == open) /* } */
+       count++;
+      else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && ch == open)      /* nested begin */
+       count++;
+
+      /* Add this character. */
+      RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+      ret[retind++] = ch;
+
+      /* If we just read the ending character, don't bother continuing. */
+      if (count == 0)
+       break;
+
+      if (open == '\'')                        /* '' inside grouping construct */
+       {
+         if MBTEST((flags & P_ALLOWESC) && ch == '\\')
+           tflags |= LEX_PASSNEXT;
+         continue;
+       }
+
+      if MBTEST(ch == '\\')                    /* backslashes */
+       tflags |= LEX_PASSNEXT;
+
+      /* Based on which dolstate is currently in (param, op, or word),
+        decide what the op is.  We're really only concerned if it's % or
+        #, so we can turn on a flag that says whether or not we should
+        treat single quotes as special when inside a double-quoted
+        ${...}. This logic must agree with subst.c:extract_dollar_brace_string
+        since they share the same defines. */
+      if (flags & P_DOLBRACE)
+        {
+          /* ${param%[%]word} */
+         if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '%' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param#[#]word} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '#' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param/[/]pat/rep} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '/' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param^[^]pat} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '^' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param,[,]pat} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == ',' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", ch) != 0)
+           dolbrace_state = DOLBRACE_OP;
+         else if MBTEST(dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", ch) == 0)
+           dolbrace_state = DOLBRACE_WORD;
+        }
+
+      /* The big hammer.  Single quotes aren't special in double quotes.  The
+         problem is that Posix used to say the single quotes are semi-special:
+         within a double-quoted ${...} construct "an even number of
+         unescaped double-quotes or single-quotes, if any, shall occur." */
+      /* This was changed in Austin Group Interp 221 */
+      if MBTEST(posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && (flags & P_DQUOTE) && (flags & P_DOLBRACE) && ch == '\'')
+       continue;
+
+      /* Could also check open == '`' if we want to parse grouping constructs
+        inside old-style command substitution. */
+      if (open != close)               /* a grouping construct */
+       {
+         if MBTEST(shellquote (ch))
+           {
+             /* '', ``, or "" inside $(...) or other grouping construct. */
+             push_delimiter (dstack, ch);
+             if MBTEST((tflags & LEX_WASDOL) && ch == '\'')    /* $'...' inside group */
+               nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC|rflags);
+             else
+               nestret = parse_matched_pair (ch, ch, ch, &nestlen, rflags);
+             pop_delimiter (dstack);
+             CHECK_NESTRET_ERROR ();
+
+             if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0))
+               {
+                 /* Translate $'...' here. */
+                 ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen);
+                 xfree (nestret);
+
+                 if ((rflags & P_DQUOTE) == 0)
+                   {
+                     nestret = sh_single_quote (ttrans);
+                     free (ttrans);
+                     nestlen = strlen (nestret);
+                   }
+                 else
+                   {
+                     nestret = ttrans;
+                     nestlen = ttranslen;
+                   }
+                 retind -= 2;          /* back up before the $' */
+               }
+             else if MBTEST((tflags & LEX_WASDOL) && ch == '"' && (extended_quote || (rflags & P_DQUOTE) == 0))
+               {
+                 /* Locale expand $"..." here. */
+                 ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen);
+                 xfree (nestret);
+
+                 nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 free (ttrans);
+                 nestlen = ttranslen + 2;
+                 retind -= 2;          /* back up before the $" */
+               }
+
+             APPEND_NESTRET ();
+             FREE (nestret);
+           }
+         else if ((flags & P_ARRAYSUB) && (tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '['))      /* ) } ] */
+           goto parse_dollar_word;
+       }
+      /* Parse an old-style command substitution within double quotes as a
+        single word. */
+      /* XXX - sh and ksh93 don't do this - XXX */
+      else if MBTEST(open == '"' && ch == '`')
+       {
+         nestret = parse_matched_pair (0, '`', '`', &nestlen, rflags);
+
+         CHECK_NESTRET_ERROR ();
+         APPEND_NESTRET ();
+
+         FREE (nestret);
+       }
+      else if MBTEST(open != '`' && (tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '['))    /* ) } ] */
+       /* check for $(), $[], or ${} inside quoted string. */
+       {
+parse_dollar_word:
+         if (open == ch)       /* undo previous increment */
+           count--;
+         if (ch == '(')                /* ) */
+           nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
+         else if (ch == '{')           /* } */
+           nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags);
+         else if (ch == '[')           /* ] */
+           nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags);
+
+         CHECK_NESTRET_ERROR ();
+         APPEND_NESTRET ();
+
+         FREE (nestret);
+       }
+      if MBTEST(ch == '$')
+       tflags |= LEX_WASDOL;
+      else
+       tflags &= ~LEX_WASDOL;
+    }
+
+  ret[retind] = '\0';
+  if (lenp)
+    *lenp = retind;
+/*itrace("parse_matched_pair[%d]: returning %s", line_number, ret);*/
+  return ret;
+}
+
+/* Parse a $(...) command substitution.  This is messier than I'd like, and
+   reproduces a lot more of the token-reading code than I'd like. */
+static char *
+parse_comsub (qc, open, close, lenp, flags)
+     int qc;   /* `"' if this construct is within double quotes */
+     int open, close;
+     int *lenp, flags;
+{
+  int count, ch, peekc, tflags, lex_rwlen, lex_wlen, lex_firstind;
+  int nestlen, ttranslen, start_lineno;
+  char *ret, *nestret, *ttrans, *heredelim;
+  int retind, retsize, rflags, hdlen;
+
+  /* Posix interp 217 says arithmetic expressions have precedence, so
+     assume $(( introduces arithmetic expansion and parse accordingly. */
+  peekc = shell_getc (0);
+  shell_ungetc (peekc);
+  if (peekc == '(')
+    return (parse_matched_pair (qc, open, close, lenp, 0));
+
+/*itrace("parse_comsub: qc = `%c' open = %c close = %c", qc, open, close);*/
+  count = 1;
+  tflags = LEX_RESWDOK;
+
+  if ((flags & P_COMMAND) && qc != '\'' && qc != '"' && (flags & P_DQUOTE) == 0)
+    tflags |= LEX_CKCASE;
+  if ((tflags & LEX_CKCASE) && (interactive == 0 || interactive_comments))
+    tflags |= LEX_CKCOMMENT;
+
+  /* RFLAGS is the set of flags we want to pass to recursive calls. */
+  rflags = (flags & P_DQUOTE);
+
+  ret = (char *)xmalloc (retsize = 64);
+  retind = 0;
+
+  start_lineno = line_number;
+  lex_rwlen = lex_wlen = 0;
+
+  heredelim = 0;
+  lex_firstind = -1;
+
+  while (count)
+    {
+comsub_readchar:
+      ch = shell_getc (qc != '\'' && (tflags & LEX_PASSNEXT) == 0);
+
+      if (ch == EOF)
+       {
+eof_error:
+         free (ret);
+         FREE (heredelim);
+         parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close);
+         EOF_Reached = 1;      /* XXX */
+         return (&matched_pair_error);
+       }
+
+      /* If we hit the end of a line and are reading the contents of a here
+        document, and it's not the same line that the document starts on,
+        check for this line being the here doc delimiter.  Otherwise, if
+        we're in a here document, mark the next character as the beginning
+        of a line. */
+      if (ch == '\n')
+       {
+         if ((tflags & LEX_HEREDELIM) && heredelim)
+           {
+             tflags &= ~LEX_HEREDELIM;
+             tflags |= LEX_INHEREDOC;
+             lex_firstind = retind + 1;
+           }
+         else if (tflags & LEX_INHEREDOC)
+           {
+             int tind;
+             tind = lex_firstind;
+             while ((tflags & LEX_STRIPDOC) && ret[tind] == '\t')
+               tind++;
+             if (STREQN (ret + tind, heredelim, hdlen))
+               {
+                 tflags &= ~(LEX_STRIPDOC|LEX_INHEREDOC);
+/*itrace("parse_comsub:%d: found here doc end `%s'", line_number, ret + tind);*/
+                 free (heredelim);
+                 heredelim = 0;
+                 lex_firstind = -1;
+               }
+             else
+               lex_firstind = retind + 1;
+           }
+       }
+
+      /* Possible reprompting. */
+      if (ch == '\n' && SHOULD_PROMPT ())
+       prompt_again ();
+
+      /* XXX -- possibly allow here doc to be delimited by ending right
+        paren. */
+      if ((tflags & LEX_INHEREDOC) && ch == close && count == 1)
+       {
+         int tind;
+/*itrace("parse_comsub: in here doc, ch == close, retind - firstind = %d hdlen = %d retind = %d", retind-lex_firstind, hdlen, retind);*/
+         tind = lex_firstind;
+         while ((tflags & LEX_STRIPDOC) && ret[tind] == '\t')
+           tind++;
+         if (retind-tind == hdlen && STREQN (ret + tind, heredelim, hdlen))
+           {
+             tflags &= ~(LEX_STRIPDOC|LEX_INHEREDOC);
+/*itrace("parse_comsub:%d: found here doc end `%s'", line_number, ret + tind);*/
+             free (heredelim);
+             heredelim = 0;
+             lex_firstind = -1;
+           }
+       }
+
+      /* Don't bother counting parens or doing anything else if in a comment */
+      if (tflags & (LEX_INCOMMENT|LEX_INHEREDOC))
+       {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+
+         if ((tflags & LEX_INCOMMENT) && ch == '\n')
+{
+/*itrace("parse_comsub:%d: lex_incomment -> 0 ch = `%c'", line_number, ch);*/
+           tflags &= ~LEX_INCOMMENT;
+}
+
+         continue;
+       }
+
+      if (tflags & LEX_PASSNEXT)               /* last char was backslash */
+       {
+/*itrace("parse_comsub:%d: lex_passnext -> 0 ch = `%c' (%d)", line_number, ch, __LINE__);*/
+         tflags &= ~LEX_PASSNEXT;
+         if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
+           {
+             if (retind > 0)
+               retind--;       /* swallow previously-added backslash */
+             continue;
+           }
+
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         if MBTEST(ch == CTLESC || ch == CTLNUL)
+           ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+
+      /* If this is a shell break character, we are not in a word.  If not,
+        we either start or continue a word. */
+      if MBTEST(shellbreak (ch))
+       {
+         tflags &= ~LEX_INWORD;
+/*itrace("parse_comsub:%d: lex_inword -> 0 ch = `%c' (%d)", line_number, ch, __LINE__);*/
+       }
+      else
+       {
+         if (tflags & LEX_INWORD)
+           {
+             lex_wlen++;
+/*itrace("parse_comsub:%d: lex_inword == 1 ch = `%c' lex_wlen = %d (%d)", line_number, ch, lex_wlen, __LINE__);*/
+           }         
+         else
+           {
+/*itrace("parse_comsub:%d: lex_inword -> 1 ch = `%c' (%d)", line_number, ch, __LINE__);*/
+             tflags |= LEX_INWORD;
+             lex_wlen = 0;
+           }
+       }
+
+      /* Skip whitespace */
+      if MBTEST(shellblank (ch) && lex_rwlen == 0)
+        {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+         continue;
+        }
+
+      /* Either we are looking for the start of the here-doc delimiter
+        (lex_firstind == -1) or we are reading one (lex_firstind >= 0).
+        If this character is a shell break character and we are reading
+        the delimiter, save it and note that we are now reading a here
+        document.  If we've found the start of the delimiter, note it by
+        setting lex_firstind.  Backslashes can quote shell metacharacters
+        in here-doc delimiters. */
+      if (tflags & LEX_HEREDELIM)
+       {
+         if (lex_firstind == -1 && shellbreak (ch) == 0)
+           lex_firstind = retind;
+#if 0
+         else if (heredelim && (tflags & LEX_PASSNEXT) == 0 && ch == '\n')
+           {
+             tflags |= LEX_INHEREDOC;
+             tflags &= ~LEX_HEREDELIM;
+             lex_firstind = retind + 1;
+           }
+#endif
+         else if (lex_firstind >= 0 && (tflags & LEX_PASSNEXT) == 0 && shellbreak (ch))
+           {
+             if (heredelim == 0)
+               {
+                 nestret = substring (ret, lex_firstind, retind);
+                 heredelim = string_quote_removal (nestret, 0);
+                 free (nestret);
+                 hdlen = STRLEN(heredelim);
+/*itrace("parse_comsub:%d: found here doc delimiter `%s' (%d)", line_number, heredelim, hdlen);*/
+               }
+             if (ch == '\n')
+               {
+                 tflags |= LEX_INHEREDOC;
+                 tflags &= ~LEX_HEREDELIM;
+                 lex_firstind = retind + 1;
+               }
+             else
+               lex_firstind = -1;
+           }
+       }
+
+      /* Meta-characters that can introduce a reserved word.  Not perfect yet. */
+      if MBTEST((tflags & LEX_RESWDOK) == 0 && (tflags & LEX_CKCASE) && (tflags & LEX_INCOMMENT) == 0 && (shellmeta(ch) || ch == '\n'))
+       {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+         peekc = shell_getc (1);
+         if (ch == peekc && (ch == '&' || ch == '|' || ch == ';'))     /* two-character tokens */
+           {
+             RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+             ret[retind++] = peekc;
+/*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch);*/
+             tflags |= LEX_RESWDOK;
+             lex_rwlen = 0;
+             continue;
+           }
+         else if (ch == '\n' || COMSUB_META(ch))
+           {
+             shell_ungetc (peekc);
+/*itrace("parse_comsub:%d: set lex_reswordok = 1, ch = `%c'", line_number, ch);*/
+             tflags |= LEX_RESWDOK;
+             lex_rwlen = 0;
+             continue;
+           }
+         else if (ch == EOF)
+           goto eof_error;
+         else
+           {
+             /* `unget' the character we just added and fall through */
+             retind--;
+             shell_ungetc (peekc);
+           }
+       }
+
+      /* If we can read a reserved word, try to read one. */
+      if (tflags & LEX_RESWDOK)
+       {
+         if MBTEST(islower (ch))
+           {
+             /* Add this character. */
+             RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+             ret[retind++] = ch;
+             lex_rwlen++;
+             continue;
+           }
+         else if MBTEST(lex_rwlen == 4 && shellbreak (ch))
+           {
+             if (STREQN (ret + retind - 4, "case", 4))
+{
+               tflags |= LEX_INCASE;
+/*itrace("parse_comsub:%d: found `case', lex_incase -> 1 lex_reswdok -> 0", line_number);*/
+}
+             else if (STREQN (ret + retind - 4, "esac", 4))
+{
+               tflags &= ~LEX_INCASE;
+/*itrace("parse_comsub:%d: found `esac', lex_incase -> 0 lex_reswdok -> 0", line_number);*/
+}              
+             tflags &= ~LEX_RESWDOK;
+           }
+         else if MBTEST((tflags & LEX_CKCOMMENT) && ch == '#' && (lex_rwlen == 0 || ((tflags & LEX_INWORD) && lex_wlen == 0)))
+           ;   /* don't modify LEX_RESWDOK if we're starting a comment */
+         else if MBTEST((tflags & LEX_INCASE) && ch != '\n')
+           /* If we can read a reserved word and we're in case, we're at the
+              point where we can read a new pattern list or an esac.  We
+              handle the esac case above.  If we read a newline, we want to
+              leave LEX_RESWDOK alone.  If we read anything else, we want to
+              turn off LEX_RESWDOK, since we're going to read a pattern list. */
+{
+           tflags &= ~LEX_RESWDOK;
+/*itrace("parse_comsub:%d: lex_incase == 1 found `%c', lex_reswordok -> 0", line_number, ch);*/
+}
+         else if MBTEST(shellbreak (ch) == 0)
+{
+           tflags &= ~LEX_RESWDOK;
+/*itrace("parse_comsub:%d: found `%c', lex_reswordok -> 0", line_number, ch);*/
+}
+       }
+
+      /* Might be the start of a here-doc delimiter */
+      if MBTEST((tflags & LEX_INCOMMENT) == 0 && (tflags & LEX_CKCASE) && ch == '<')
+       {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+         peekc = shell_getc (1);
+         if (peekc == EOF)
+           goto eof_error;
+         if (peekc == ch)
+           {
+             RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+             ret[retind++] = peekc;
+             peekc = shell_getc (1);
+             if (peekc == EOF)
+               goto eof_error;
+             if (peekc == '-')
+               {
+                 RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+                 ret[retind++] = peekc;
+                 tflags |= LEX_STRIPDOC;
+               }
+             else
+               shell_ungetc (peekc);
+             if (peekc != '<')
+               {
+                 tflags |= LEX_HEREDELIM;
+                 lex_firstind = -1;
+               }
+             continue;
+           }
+         else
+           ch = peekc;         /* fall through and continue XXX */
+       }
+      else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (((tflags & LEX_RESWDOK) && lex_rwlen == 0) || ((tflags & LEX_INWORD) && lex_wlen == 0)))
+{
+/*itrace("parse_comsub:%d: lex_incomment -> 1 (%d)", line_number, __LINE__);*/
+       tflags |= LEX_INCOMMENT;
+}
+
+      if MBTEST(ch == CTLESC || ch == CTLNUL)  /* special shell escapes */
+       {
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+#if 0
+      else if MBTEST((tflags & LEX_INCASE) && ch == close && close == ')')
+        tflags &= ~LEX_INCASE;         /* XXX */
+#endif
+      else if MBTEST(ch == close && (tflags & LEX_INCASE) == 0)                /* ending delimiter */
+{
+       count--;
+/*itrace("parse_comsub:%d: found close: count = %d", line_number, count);*/
+}
+      else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && (tflags & LEX_INCASE) == 0 && ch == open)        /* nested begin */
+{
+       count++;
+/*itrace("parse_comsub:%d: found open: count = %d", line_number, count);*/
+}
+
+      /* Add this character. */
+      RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+      ret[retind++] = ch;
+
+      /* If we just read the ending character, don't bother continuing. */
+      if (count == 0)
+       break;
+
+      if MBTEST(ch == '\\')                    /* backslashes */
+       tflags |= LEX_PASSNEXT;
+
+      if MBTEST(shellquote (ch))
+        {
+          /* '', ``, or "" inside $(...). */
+          push_delimiter (dstack, ch);
+          if MBTEST((tflags & LEX_WASDOL) && ch == '\'')       /* $'...' inside group */
+           nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC|rflags);
+         else
+           nestret = parse_matched_pair (ch, ch, ch, &nestlen, rflags);
+         pop_delimiter (dstack);
+         CHECK_NESTRET_ERROR ();
+
+         if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0))
+           {
+             /* Translate $'...' here. */
+             ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen);
+             xfree (nestret);
+
+             if ((rflags & P_DQUOTE) == 0)
+               {
+                 nestret = sh_single_quote (ttrans);
+                 free (ttrans);
+                 nestlen = strlen (nestret);
+               }
+             else
+               {
+                 nestret = ttrans;
+                 nestlen = ttranslen;
+               }
+             retind -= 2;              /* back up before the $' */
+           }
+         else if MBTEST((tflags & LEX_WASDOL) && ch == '"' && (extended_quote || (rflags & P_DQUOTE) == 0))
+           {
+             /* Locale expand $"..." here. */
+             ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen);
+             xfree (nestret);
+
+             nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+             free (ttrans);
+             nestlen = ttranslen + 2;
+             retind -= 2;              /* back up before the $" */
+           }
+
+         APPEND_NESTRET ();
+         FREE (nestret);
+       }
+      else if MBTEST((tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '['))   /* ) } ] */
+       /* check for $(), $[], or ${} inside command substitution. */
+       {
+         if ((tflags & LEX_INCASE) == 0 && open == ch) /* undo previous increment */
+           count--;
+         if (ch == '(')                /* ) */
+           nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE);
+         else if (ch == '{')           /* } */
+           nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags);
+         else if (ch == '[')           /* ] */
+           nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags);
+
+         CHECK_NESTRET_ERROR ();
+         APPEND_NESTRET ();
+
+         FREE (nestret);
+       }
+      if MBTEST(ch == '$')
+       tflags |= LEX_WASDOL;
+      else
+       tflags &= ~LEX_WASDOL;
+    }
+
+  FREE (heredelim);
+  ret[retind] = '\0';
+  if (lenp)
+    *lenp = retind;
+/*itrace("parse_comsub:%d: returning `%s'", line_number, ret);*/
+  return ret;
+}
+
+/* XXX - this needs to handle functionality like subst.c:no_longjmp_on_fatal_error;
+   maybe extract_command_subst should handle it. */
+char *
+xparse_dolparen (base, string, indp, flags)
+     char *base;
+     char *string;
+     int *indp;
+     int flags;
+{
+  sh_parser_state_t ps;
+  int orig_ind, nc, sflags;
+  char *ret, *s, *ep, *ostring;
+
+  /*yydebug = 1;*/
+  orig_ind = *indp;
+  ostring = string;
+
+  sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE;
+  if (flags & SX_NOLONGJMP)
+    sflags |= SEVAL_NOLONGJMP;
+  save_parser_state (&ps);
+
+  /*(*/
+  parser_state |= PST_CMDSUBST|PST_EOFTOKEN;   /* allow instant ')' */ /*(*/
+  shell_eof_token = ')';
+  parse_string (string, "command substitution", sflags, &ep);
+
+  restore_parser_state (&ps);
+  reset_parser ();
+  if (interactive)
+    token_to_read = 0;
+
+  /* Need to find how many characters parse_and_execute consumed, update
+     *indp, if flags != 0, copy the portion of the string parsed into RET
+     and return it.  If flags & 1 (EX_NOALLOC) we can return NULL. */
+
+  /*(*/
+  if (ep[-1] != ')')
+    {
+#if DEBUG
+      if (ep[-1] != '\n')
+       itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep);
+#endif
+      while (ep > ostring && ep[-1] == '\n') ep--;
+    }
+
+  nc = ep - ostring;
+  *indp = ep - base - 1;
+
+  /*(*/
+#if DEBUG
+  if (base[*indp] != ')')
+    itrace("xparse_dolparen:%d: base[%d] != RPAREN (%d), base = `%s'", line_number, *indp, base[*indp], base);
+#endif
+
+  if (flags & SX_NOALLOC) 
+    return (char *)NULL;
+
+  if (nc == 0)
+    {
+      ret = xmalloc (1);
+      ret[0] = '\0';
+    }
+  else
+    ret = substring (ostring, 0, nc - 1);
+
+  return ret;
+}
+
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+/* Parse a double-paren construct.  It can be either an arithmetic
+   command, an arithmetic `for' command, or a nested subshell.  Returns
+   the parsed token, -1 on error, or -2 if we didn't do anything and
+   should just go on. */
+static int
+parse_dparen (c)
+     int c;
+{
+  int cmdtyp, sline;
+  char *wval;
+  WORD_DESC *wd;
+
+#if defined (ARITH_FOR_COMMAND)
+  if (last_read_token == FOR)
+    {
+      arith_for_lineno = line_number;
+      cmdtyp = parse_arith_cmd (&wval, 0);
+      if (cmdtyp == 1)
+       {
+         wd = alloc_word_desc ();
+         wd->word = wval;
+         yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL);
+         return (ARITH_FOR_EXPRS);
+       }
+      else
+       return -1;              /* ERROR */
+    }
+#endif
+
+#if defined (DPAREN_ARITHMETIC)
+  if (reserved_word_acceptable (last_read_token))
+    {
+      sline = line_number;
+
+      cmdtyp = parse_arith_cmd (&wval, 0);
+      if (cmdtyp == 1) /* arithmetic command */
+       {
+         wd = alloc_word_desc ();
+         wd->word = wval;
+         wd->flags = W_QUOTED|W_NOSPLIT|W_NOGLOB|W_DQUOTE;
+         yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL);
+         return (ARITH_CMD);
+       }
+      else if (cmdtyp == 0)    /* nested subshell */
+       {
+         push_string (wval, 0, (alias_t *)NULL);
+         if ((parser_state & PST_CASEPAT) == 0)
+           parser_state |= PST_SUBSHELL;
+         return (c);
+       }
+      else                     /* ERROR */
+       return -1;
+    }
+#endif
+
+  return -2;                   /* XXX */
+}
+
+/* We've seen a `(('.  Look for the matching `))'.  If we get it, return 1.
+   If not, assume it's a nested subshell for backwards compatibility and
+   return 0.  In any case, put the characters we've consumed into a locally-
+   allocated buffer and make *ep point to that buffer.  Return -1 on an
+   error, for example EOF. */
+static int
+parse_arith_cmd (ep, adddq)
+     char **ep;
+     int adddq;
+{
+  int exp_lineno, rval, c;
+  char *ttok, *tokstr;
+  int ttoklen;
+
+  exp_lineno = line_number;
+  ttok = parse_matched_pair (0, '(', ')', &ttoklen, 0);
+  rval = 1;
+  if (ttok == &matched_pair_error)
+    return -1;
+  /* Check that the next character is the closing right paren.  If
+     not, this is a syntax error. ( */
+  c = shell_getc (0);
+  if MBTEST(c != ')')
+    rval = 0;
+
+  tokstr = (char *)xmalloc (ttoklen + 4);
+
+  /* if ADDDQ != 0 then (( ... )) -> "..." */
+  if (rval == 1 && adddq)      /* arith cmd, add double quotes */
+    {
+      tokstr[0] = '"';
+      strncpy (tokstr + 1, ttok, ttoklen - 1);
+      tokstr[ttoklen] = '"';
+      tokstr[ttoklen+1] = '\0';
+    }
+  else if (rval == 1)          /* arith cmd, don't add double quotes */
+    {
+      strncpy (tokstr, ttok, ttoklen - 1);
+      tokstr[ttoklen-1] = '\0';
+    }
+  else                         /* nested subshell */
+    {
+      tokstr[0] = '(';
+      strncpy (tokstr + 1, ttok, ttoklen - 1);
+      tokstr[ttoklen] = ')';
+      tokstr[ttoklen+1] = c;
+      tokstr[ttoklen+2] = '\0';
+    }
+
+  *ep = tokstr;
+  FREE (ttok);
+  return rval;
+}
+#endif /* DPAREN_ARITHMETIC || ARITH_FOR_COMMAND */
+
+#if defined (COND_COMMAND)
+static void
+cond_error ()
+{
+  char *etext;
+
+  if (EOF_Reached && cond_token != COND_ERROR)         /* [[ */
+    parser_error (cond_lineno, _("unexpected EOF while looking for `]]'"));
+  else if (cond_token != COND_ERROR)
+    {
+      if (etext = error_token_from_token (cond_token))
+       {
+         parser_error (cond_lineno, _("syntax error in conditional expression: unexpected token `%s'"), etext);
+         free (etext);
+       }
+      else
+       parser_error (cond_lineno, _("syntax error in conditional expression"));
+    }
+}
+
+static COND_COM *
+cond_expr ()
+{
+  return (cond_or ());  
+}
+
+static COND_COM *
+cond_or ()
+{
+  COND_COM *l, *r;
+
+  l = cond_and ();
+  if (cond_token == OR_OR)
+    {
+      r = cond_or ();
+      l = make_cond_node (COND_OR, (WORD_DESC *)NULL, l, r);
+    }
+  return l;
+}
+
+static COND_COM *
+cond_and ()
+{
+  COND_COM *l, *r;
+
+  l = cond_term ();
+  if (cond_token == AND_AND)
+    {
+      r = cond_and ();
+      l = make_cond_node (COND_AND, (WORD_DESC *)NULL, l, r);
+    }
+  return l;
+}
+
+static int
+cond_skip_newlines ()
+{
+  while ((cond_token = read_token (READ)) == '\n')
+    {
+      if (SHOULD_PROMPT ())
+       prompt_again ();
+    }
+  return (cond_token);
+}
+
+#define COND_RETURN_ERROR() \
+  do { cond_token = COND_ERROR; return ((COND_COM *)NULL); } while (0)
+
+static COND_COM *
+cond_term ()
+{
+  WORD_DESC *op;
+  COND_COM *term, *tleft, *tright;
+  int tok, lineno;
+  char *etext;
+
+  /* Read a token.  It can be a left paren, a `!', a unary operator, or a
+     word that should be the first argument of a binary operator.  Start by
+     skipping newlines, since this is a compound command. */
+  tok = cond_skip_newlines ();
+  lineno = line_number;
+  if (tok == COND_END)
+    {
+      COND_RETURN_ERROR ();
+    }
+  else if (tok == '(')
+    {
+      term = cond_expr ();
+      if (cond_token != ')')
+       {
+         if (term)
+           dispose_cond_node (term);           /* ( */
+         if (etext = error_token_from_token (cond_token))
+           {
+             parser_error (lineno, _("unexpected token `%s', expected `)'"), etext);
+             free (etext);
+           }
+         else
+           parser_error (lineno, _("expected `)'"));
+         COND_RETURN_ERROR ();
+       }
+      term = make_cond_node (COND_EXPR, (WORD_DESC *)NULL, term, (COND_COM *)NULL);
+      (void)cond_skip_newlines ();
+    }
+  else if (tok == BANG || (tok == WORD && (yylval.word->word[0] == '!' && yylval.word->word[1] == '\0')))
+    {
+      if (tok == WORD)
+       dispose_word (yylval.word);     /* not needed */
+      term = cond_term ();
+      if (term)
+       term->flags |= CMD_INVERT_RETURN;
+    }
+  else if (tok == WORD && yylval.word->word[0] == '-' && yylval.word->word[2] == 0 && test_unop (yylval.word->word))
+    {
+      op = yylval.word;
+      tok = read_token (READ);
+      if (tok == WORD)
+       {
+         tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+         term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL);
+       }
+      else
+       {
+         dispose_word (op);
+         if (etext = error_token_from_token (tok))
+           {
+             parser_error (line_number, _("unexpected argument `%s' to conditional unary operator"), etext);
+             free (etext);
+           }
+         else
+           parser_error (line_number, _("unexpected argument to conditional unary operator"));
+         COND_RETURN_ERROR ();
+       }
+
+      (void)cond_skip_newlines ();
+    }
+  else if (tok == WORD)                /* left argument to binary operator */
+    {
+      /* lhs */
+      tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+
+      /* binop */
+      tok = read_token (READ);
+      if (tok == WORD && test_binop (yylval.word->word))
+       {
+         op = yylval.word;
+         if (op->word[0] == '=' && (op->word[1] == '\0' || (op->word[1] == '=' && op->word[2] == '\0')))
+           parser_state |= PST_EXTPAT;
+         else if (op->word[0] == '!' && op->word[1] == '=' && op->word[2] == '\0')
+           parser_state |= PST_EXTPAT;
+       }
+#if defined (COND_REGEXP)
+      else if (tok == WORD && STREQ (yylval.word->word, "=~"))
+       {
+         op = yylval.word;
+         parser_state |= PST_REGEXP;
+       }
+#endif
+      else if (tok == '<' || tok == '>')
+       op = make_word_from_token (tok);  /* ( */
+      /* There should be a check before blindly accepting the `)' that we have
+        seen the opening `('. */
+      else if (tok == COND_END || tok == AND_AND || tok == OR_OR || tok == ')')
+       {
+         /* Special case.  [[ x ]] is equivalent to [[ -n x ]], just like
+            the test command.  Similarly for [[ x && expr ]] or
+            [[ x || expr ]] or [[ (x) ]]. */
+         op = make_word ("-n");
+         term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL);
+         cond_token = tok;
+         return (term);
+       }
+      else
+       {
+         if (etext = error_token_from_token (tok))
+           {
+             parser_error (line_number, _("unexpected token `%s', conditional binary operator expected"), etext);
+             free (etext);
+           }
+         else
+           parser_error (line_number, _("conditional binary operator expected"));
+         dispose_cond_node (tleft);
+         COND_RETURN_ERROR ();
+       }
+
+      /* rhs */
+      if (parser_state & PST_EXTPAT)
+       extended_glob = 1;
+      tok = read_token (READ);
+      if (parser_state & PST_EXTPAT)
+       extended_glob = global_extglob;
+      parser_state &= ~(PST_REGEXP|PST_EXTPAT);
+
+      if (tok == WORD)
+       {
+         tright = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+         term = make_cond_node (COND_BINARY, op, tleft, tright);
+       }
+      else
+       {
+         if (etext = error_token_from_token (tok))
+           {
+             parser_error (line_number, _("unexpected argument `%s' to conditional binary operator"), etext);
+             free (etext);
+           }
+         else
+           parser_error (line_number, _("unexpected argument to conditional binary operator"));
+         dispose_cond_node (tleft);
+         dispose_word (op);
+         COND_RETURN_ERROR ();
+       }
+
+      (void)cond_skip_newlines ();
+    }
+  else
+    {
+      if (tok < 256)
+       parser_error (line_number, _("unexpected token `%c' in conditional command"), tok);
+      else if (etext = error_token_from_token (tok))
+       {
+         parser_error (line_number, _("unexpected token `%s' in conditional command"), etext);
+         free (etext);
+       }
+      else
+       parser_error (line_number, _("unexpected token %d in conditional command"), tok);
+      COND_RETURN_ERROR ();
+    }
+  return (term);
+}      
+
+/* This is kind of bogus -- we slip a mini recursive-descent parser in
+   here to handle the conditional statement syntax. */
+static COMMAND *
+parse_cond_command ()
+{
+  COND_COM *cexp;
+
+  global_extglob = extended_glob;
+  cexp = cond_expr ();
+  return (make_cond_command (cexp));
+}
+#endif
+
+#if defined (ARRAY_VARS)
+/* When this is called, it's guaranteed that we don't care about anything
+   in t beyond i.  We do save and restore the chars, though. */
+static int
+token_is_assignment (t, i)
+     char *t;
+     int i;
+{
+  unsigned char c, c1;
+  int r;
+
+  c = t[i]; c1 = t[i+1];
+  t[i] = '='; t[i+1] = '\0';
+  r = assignment (t, (parser_state & PST_COMPASSIGN) != 0);
+  t[i] = c; t[i+1] = c1;
+  return r;
+}
+
+/* XXX - possible changes here for `+=' */
+static int
+token_is_ident (t, i)
+     char *t;
+     int i;
+{
+  unsigned char c;
+  int r;
+
+  c = t[i];
+  t[i] = '\0';
+  r = legal_identifier (t);
+  t[i] = c;
+  return r;
+}
+#endif
+
+static int
+read_token_word (character)
+     int character;
+{
+  /* The value for YYLVAL when a WORD is read. */
+  WORD_DESC *the_word;
+
+  /* Index into the token that we are building. */
+  int token_index;
+
+  /* ALL_DIGITS becomes zero when we see a non-digit. */
+  int all_digit_token;
+
+  /* DOLLAR_PRESENT becomes non-zero if we see a `$'. */
+  int dollar_present;
+
+  /* COMPOUND_ASSIGNMENT becomes non-zero if we are parsing a compound
+     assignment. */
+  int compound_assignment;
+
+  /* QUOTED becomes non-zero if we see one of ("), ('), (`), or (\). */
+  int quoted;
+
+  /* Non-zero means to ignore the value of the next character, and just
+     to add it no matter what. */
+ int pass_next_character;
+
+  /* The current delimiting character. */
+  int cd;
+  int result, peek_char;
+  char *ttok, *ttrans;
+  int ttoklen, ttranslen;
+  intmax_t lvalue;
+
+  if (token_buffer_size < TOKEN_DEFAULT_INITIAL_SIZE)
+    token = (char *)xrealloc (token, token_buffer_size = TOKEN_DEFAULT_INITIAL_SIZE);
+
+  token_index = 0;
+  all_digit_token = DIGIT (character);
+  dollar_present = quoted = pass_next_character = compound_assignment = 0;
+
+  for (;;)
+    {
+      if (character == EOF)
+       goto got_token;
+
+      if (pass_next_character)
+       {
+         pass_next_character = 0;
+         goto got_escaped_character;
+       }
+
+      cd = current_delimiter (dstack);
+
+      /* Handle backslashes.  Quote lots of things when not inside of
+        double-quotes, quote some things inside of double-quotes. */
+      if MBTEST(character == '\\')
+       {
+         peek_char = shell_getc (0);
+
+         /* Backslash-newline is ignored in all cases except
+            when quoted with single quotes. */
+         if (peek_char == '\n')
+           {
+             character = '\n';
+             goto next_character;
+           }
+         else
+           {
+             shell_ungetc (peek_char);
+
+             /* If the next character is to be quoted, note it now. */
+             if (cd == 0 || cd == '`' ||
+                 (cd == '"' && peek_char >= 0 && (sh_syntaxtab[peek_char] & CBSDQUOTE)))
+               pass_next_character++;
+
+             quoted = 1;
+             goto got_character;
+           }
+       }
+
+      /* Parse a matched pair of quote characters. */
+      if MBTEST(shellquote (character))
+       {
+         push_delimiter (dstack, character);
+         ttok = parse_matched_pair (character, character, character, &ttoklen, (character == '`') ? P_COMMAND : 0);
+         pop_delimiter (dstack);
+         if (ttok == &matched_pair_error)
+           return -1;          /* Bail immediately. */
+         RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                 token_buffer_size, TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = character;
+         strcpy (token + token_index, ttok);
+         token_index += ttoklen;
+         all_digit_token = 0;
+         quoted = 1;
+         dollar_present |= (character == '"' && strchr (ttok, '$') != 0);
+         FREE (ttok);
+         goto next_character;
+       }
+
+#ifdef COND_REGEXP
+      /* When parsing a regexp as a single word inside a conditional command,
+        we need to special-case characters special to both the shell and
+        regular expressions.  Right now, that is only '(' and '|'. */ /*)*/
+      if MBTEST((parser_state & PST_REGEXP) && (character == '(' || character == '|'))         /*)*/
+       {
+         if (character == '|')
+           goto got_character;
+
+         push_delimiter (dstack, character);
+         ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+         pop_delimiter (dstack);
+         if (ttok == &matched_pair_error)
+           return -1;          /* Bail immediately. */
+         RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                 token_buffer_size, TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = character;
+         strcpy (token + token_index, ttok);
+         token_index += ttoklen;
+         FREE (ttok);
+         dollar_present = all_digit_token = 0;
+         goto next_character;
+       }
+#endif /* COND_REGEXP */
+
+#ifdef EXTENDED_GLOB
+      /* Parse a ksh-style extended pattern matching specification. */
+      if MBTEST(extended_glob && PATTERN_CHAR (character))
+       {
+         peek_char = shell_getc (1);
+         if MBTEST(peek_char == '(')           /* ) */
+           {
+             push_delimiter (dstack, peek_char);
+             ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+             pop_delimiter (dstack);
+             if (ttok == &matched_pair_error)
+               return -1;              /* Bail immediately. */
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             token[token_index++] = character;
+             token[token_index++] = peek_char;
+             strcpy (token + token_index, ttok);
+             token_index += ttoklen;
+             FREE (ttok);
+             dollar_present = all_digit_token = 0;
+             goto next_character;
+           }
+         else
+           shell_ungetc (peek_char);
+       }
+#endif /* EXTENDED_GLOB */
+
+      /* If the delimiter character is not single quote, parse some of
+        the shell expansions that must be read as a single word. */
+      if (shellexp (character))
+       {
+         peek_char = shell_getc (1);
+         /* $(...), <(...), >(...), $((...)), ${...}, and $[...] constructs */
+         if MBTEST(peek_char == '(' || \
+               ((peek_char == '{' || peek_char == '[') && character == '$'))   /* ) ] } */
+           {
+             if (peek_char == '{')             /* } */
+               ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE|P_DOLBRACE);
+             else if (peek_char == '(')                /* ) */
+               {
+                 /* XXX - push and pop the `(' as a delimiter for use by
+                    the command-oriented-history code.  This way newlines
+                    appearing in the $(...) string get added to the
+                    history literally rather than causing a possibly-
+                    incorrect `;' to be added. ) */
+                 push_delimiter (dstack, peek_char);
+                 ttok = parse_comsub (cd, '(', ')', &ttoklen, P_COMMAND);
+                 pop_delimiter (dstack);
+               }
+             else
+               ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0);
+             if (ttok == &matched_pair_error)
+               return -1;              /* Bail immediately. */
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             token[token_index++] = character;
+             token[token_index++] = peek_char;
+             strcpy (token + token_index, ttok);
+             token_index += ttoklen;
+             FREE (ttok);
+             dollar_present = 1;
+             all_digit_token = 0;
+             goto next_character;
+           }
+         /* This handles $'...' and $"..." new-style quoted strings. */
+         else if MBTEST(character == '$' && (peek_char == '\'' || peek_char == '"'))
+           {
+             int first_line;
+
+             first_line = line_number;
+             push_delimiter (dstack, peek_char);
+             ttok = parse_matched_pair (peek_char, peek_char, peek_char,
+                                        &ttoklen,
+                                        (peek_char == '\'') ? P_ALLOWESC : 0);
+             pop_delimiter (dstack);
+             if (ttok == &matched_pair_error)
+               return -1;
+             if (peek_char == '\'')
+               {
+                 ttrans = ansiexpand (ttok, 0, ttoklen - 1, &ttranslen);
+                 free (ttok);
+
+                 /* Insert the single quotes and correctly quote any
+                    embedded single quotes (allowed because P_ALLOWESC was
+                    passed to parse_matched_pair). */
+                 ttok = sh_single_quote (ttrans);
+                 free (ttrans);
+                 ttranslen = strlen (ttok);
+                 ttrans = ttok;
+               }
+             else
+               {
+                 /* Try to locale-expand the converted string. */
+                 ttrans = localeexpand (ttok, 0, ttoklen - 1, first_line, &ttranslen);
+                 free (ttok);
+
+                 /* Add the double quotes back */
+                 ttok = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 free (ttrans);
+                 ttranslen += 2;
+                 ttrans = ttok;
+               }
+
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             strcpy (token + token_index, ttrans);
+             token_index += ttranslen;
+             FREE (ttrans);
+             quoted = 1;
+             all_digit_token = 0;
+             goto next_character;
+           }
+         /* This could eventually be extended to recognize all of the
+            shell's single-character parameter expansions, and set flags.*/
+         else if MBTEST(character == '$' && peek_char == '$')
+           {
+             ttok = (char *)xmalloc (3);
+             ttok[0] = ttok[1] = '$';
+             ttok[2] = '\0';
+             RESIZE_MALLOCED_BUFFER (token, token_index, 3,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             strcpy (token + token_index, ttok);
+             token_index += 2;
+             dollar_present = 1;
+             all_digit_token = 0;
+             FREE (ttok);
+             goto next_character;
+           }
+         else
+           shell_ungetc (peek_char);
+       }
+
+#if defined (ARRAY_VARS)
+      /* Identify possible array subscript assignment; match [...].  If
+        parser_state&PST_COMPASSIGN, we need to parse [sub]=words treating
+        `sub' as if it were enclosed in double quotes. */
+      else if MBTEST(character == '[' &&               /* ] */
+                    ((token_index > 0 && assignment_acceptable (last_read_token) && token_is_ident (token, token_index)) ||
+                     (token_index == 0 && (parser_state&PST_COMPASSIGN))))
+        {
+         ttok = parse_matched_pair (cd, '[', ']', &ttoklen, P_ARRAYSUB);
+         if (ttok == &matched_pair_error)
+           return -1;          /* Bail immediately. */
+         RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                 token_buffer_size,
+                                 TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = character;
+         strcpy (token + token_index, ttok);
+         token_index += ttoklen;
+         FREE (ttok);
+         all_digit_token = 0;
+         goto next_character;
+        }
+      /* Identify possible compound array variable assignment. */
+      else if MBTEST(character == '=' && token_index > 0 && (assignment_acceptable (last_read_token) || (parser_state & PST_ASSIGNOK)) && token_is_assignment (token, token_index))
+       {
+         peek_char = shell_getc (1);
+         if MBTEST(peek_char == '(')           /* ) */
+           {
+             ttok = parse_compound_assignment (&ttoklen);
+
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 4,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+
+             token[token_index++] = '=';
+             token[token_index++] = '(';
+             if (ttok)
+               {
+                 strcpy (token + token_index, ttok);
+                 token_index += ttoklen;
+               }
+             token[token_index++] = ')';
+             FREE (ttok);
+             all_digit_token = 0;
+             compound_assignment = 1;
+#if 1
+             goto next_character;
+#else
+             goto got_token;           /* ksh93 seems to do this */
+#endif
+           }
+         else
+           shell_ungetc (peek_char);
+       }
+#endif
+
+      /* When not parsing a multi-character word construct, shell meta-
+        characters break words. */
+      if MBTEST(shellbreak (character))
+       {
+         shell_ungetc (character);
+         goto got_token;
+       }
+
+    got_character:
+
+      if (character == CTLESC || character == CTLNUL)
+       token[token_index++] = CTLESC;
+
+    got_escaped_character:
+
+      all_digit_token &= DIGIT (character);
+      dollar_present |= character == '$';
+
+      token[token_index++] = character;
+
+      RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size,
+                             TOKEN_DEFAULT_GROW_SIZE);
+
+    next_character:
+      if (character == '\n' && SHOULD_PROMPT ())
+       prompt_again ();
+
+      /* We want to remove quoted newlines (that is, a \<newline> pair)
+        unless we are within single quotes or pass_next_character is
+        set (the shell equivalent of literal-next). */
+      cd = current_delimiter (dstack);
+      character = shell_getc (cd != '\'' && pass_next_character == 0);
+    }  /* end for (;;) */
+
+got_token:
+
+  token[token_index] = '\0';
+
+  /* Check to see what thing we should return.  If the last_read_token
+     is a `<', or a `&', or the character which ended this token is
+     a '>' or '<', then, and ONLY then, is this input token a NUMBER.
+     Otherwise, it is just a word, and should be returned as such. */
+  if MBTEST(all_digit_token && (character == '<' || character == '>' || \
+                   last_read_token == LESS_AND || \
+                   last_read_token == GREATER_AND))
+      {
+       if (legal_number (token, &lvalue) && (int)lvalue == lvalue)
+         yylval.number = lvalue;
+       else
+         yylval.number = -1;
+       return (NUMBER);
+      }
+
+  /* Check for special case tokens. */
+  result = (last_shell_getc_is_singlebyte) ? special_case_tokens (token) : -1;
+  if (result >= 0)
+    return result;
+
+#if defined (ALIAS)
+  /* Posix.2 does not allow reserved words to be aliased, so check for all
+     of them, including special cases, before expanding the current token
+     as an alias. */
+  if MBTEST(posixly_correct)
+    CHECK_FOR_RESERVED_WORD (token);
+
+  /* Aliases are expanded iff EXPAND_ALIASES is non-zero, and quoting
+     inhibits alias expansion. */
+  if (expand_aliases && quoted == 0)
+    {
+      result = alias_expand_token (token);
+      if (result == RE_READ_TOKEN)
+       return (RE_READ_TOKEN);
+      else if (result == NO_EXPANSION)
+       parser_state &= ~PST_ALEXPNEXT;
+    }
+
+  /* If not in Posix.2 mode, check for reserved words after alias
+     expansion. */
+  if MBTEST(posixly_correct == 0)
+#endif
+    CHECK_FOR_RESERVED_WORD (token);
+
+  the_word = (WORD_DESC *)xmalloc (sizeof (WORD_DESC));
+  the_word->word = (char *)xmalloc (1 + token_index);
+  the_word->flags = 0;
+  strcpy (the_word->word, token);
+  if (dollar_present)
+    the_word->flags |= W_HASDOLLAR;
+  if (quoted)
+    the_word->flags |= W_QUOTED;               /*(*/
+  if (compound_assignment && token[token_index-1] == ')')
+    the_word->flags |= W_COMPASSIGN;
+  /* A word is an assignment if it appears at the beginning of a
+     simple command, or after another assignment word.  This is
+     context-dependent, so it cannot be handled in the grammar. */
+  if (assignment (token, (parser_state & PST_COMPASSIGN) != 0))
+    {
+      the_word->flags |= W_ASSIGNMENT;
+      /* Don't perform word splitting on assignment statements. */
+      if (assignment_acceptable (last_read_token) || (parser_state & PST_COMPASSIGN) != 0)
+       the_word->flags |= W_NOSPLIT;
+    }
+
+  if (command_token_position (last_read_token))
+    {
+      struct builtin *b;
+      b = builtin_address_internal (token, 0);
+      if (b && (b->flags & ASSIGNMENT_BUILTIN))
+       parser_state |= PST_ASSIGNOK;
+      else if (STREQ (token, "eval") || STREQ (token, "let"))
+       parser_state |= PST_ASSIGNOK;
+    }
+
+  yylval.word = the_word;
+
+  if (token[0] == '{' && token[token_index-1] == '}' &&
+      (character == '<' || character == '>'))
+    {
+      /* can use token; already copied to the_word */
+      token[token_index-1] = '\0';
+      if (legal_identifier (token+1))
+       {
+         strcpy (the_word->word, token+1);
+/*itrace("read_token_word: returning REDIR_WORD for %s", the_word->word);*/
+         return (REDIR_WORD);
+       }
+    }
+
+  result = ((the_word->flags & (W_ASSIGNMENT|W_NOSPLIT)) == (W_ASSIGNMENT|W_NOSPLIT))
+               ? ASSIGNMENT_WORD : WORD;
+
+  switch (last_read_token)
+    {
+    case FUNCTION:
+      parser_state |= PST_ALLOWOPNBRC;
+      function_dstart = line_number;
+      break;
+    case CASE:
+    case SELECT:
+    case FOR:
+      if (word_top < MAX_CASE_NEST)
+       word_top++;
+      word_lineno[word_top] = line_number;
+      break;
+    }
+
+  return (result);
+}
+
+/* Return 1 if TOKSYM is a token that after being read would allow
+   a reserved word to be seen, else 0. */
+static int
+reserved_word_acceptable (toksym)
+     int toksym;
+{
+  switch (toksym)
+    {
+    case '\n':
+    case ';':
+    case '(':
+    case ')':
+    case '|':
+    case '&':
+    case '{':
+    case '}':          /* XXX */
+    case AND_AND:
+    case BANG:
+    case BAR_AND:
+    case DO:
+    case DONE:
+    case ELIF:
+    case ELSE:
+    case ESAC:
+    case FI:
+    case IF:
+    case OR_OR:
+    case SEMI_SEMI:
+    case SEMI_AND:
+    case SEMI_SEMI_AND:
+    case THEN:
+    case TIME:
+    case TIMEOPT:
+    case TIMEIGN:
+    case COPROC:
+    case UNTIL:
+    case WHILE:
+    case 0:
+      return 1;
+    default:
+#if defined (COPROCESS_SUPPORT)
+      if (last_read_token == WORD && token_before_that == COPROC)
+       return 1;
+#endif
+      return 0;
+    }
+}
+    
+/* Return the index of TOKEN in the alist of reserved words, or -1 if
+   TOKEN is not a shell reserved word. */
+int
+find_reserved_word (tokstr)
+     char *tokstr;
+{
+  int i;
+  for (i = 0; word_token_alist[i].word; i++)
+    if (STREQ (tokstr, word_token_alist[i].word))
+      return i;
+  return -1;
+}
+
+#if 0
+#if defined (READLINE)
+/* Called after each time readline is called.  This insures that whatever
+   the new prompt string is gets propagated to readline's local prompt
+   variable. */
+static void
+reset_readline_prompt ()
+{
+  char *temp_prompt;
+
+  if (prompt_string_pointer)
+    {
+      temp_prompt = (*prompt_string_pointer)
+                       ? decode_prompt_string (*prompt_string_pointer)
+                       : (char *)NULL;
+
+      if (temp_prompt == 0)
+       {
+         temp_prompt = (char *)xmalloc (1);
+         temp_prompt[0] = '\0';
+       }
+
+      FREE (current_readline_prompt);
+      current_readline_prompt = temp_prompt;
+    }
+}
+#endif /* READLINE */
+#endif /* 0 */
+
+#if defined (HISTORY)
+/* A list of tokens which can be followed by newlines, but not by
+   semi-colons.  When concatenating multiple lines of history, the
+   newline separator for such tokens is replaced with a space. */
+static const int no_semi_successors[] = {
+  '\n', '{', '(', ')', ';', '&', '|',
+  CASE, DO, ELSE, IF, SEMI_SEMI, SEMI_AND, SEMI_SEMI_AND, THEN, UNTIL,
+  WHILE, AND_AND, OR_OR, IN,
+  0
+};
+
+/* If we are not within a delimited expression, try to be smart
+   about which separators can be semi-colons and which must be
+   newlines.  Returns the string that should be added into the
+   history entry.  LINE is the line we're about to add; it helps
+   make some more intelligent decisions in certain cases. */
+char *
+history_delimiting_chars (line)
+     const char *line;
+{
+  static int last_was_heredoc = 0;     /* was the last entry the start of a here document? */
+  register int i;
+
+  if ((parser_state & PST_HEREDOC) == 0)
+    last_was_heredoc = 0;
+
+  if (dstack.delimiter_depth != 0)
+    return ("\n");
+
+  /* We look for current_command_line_count == 2 because we are looking to
+     add the first line of the body of the here document (the second line
+     of the command).  We also keep LAST_WAS_HEREDOC as a private sentinel
+     variable to note when we think we added the first line of a here doc
+     (the one with a "<<" somewhere in it) */
+  if (parser_state & PST_HEREDOC)
+    {
+      if (last_was_heredoc)
+       {
+         last_was_heredoc = 0;
+         return "\n";
+       }
+      return (current_command_line_count == 2 ? "\n" : "");
+    }
+
+  /* First, handle some special cases. */
+  /*(*/
+  /* If we just read `()', assume it's a function definition, and don't
+     add a semicolon.  If the token before the `)' was not `(', and we're
+     not in the midst of parsing a case statement, assume it's a
+     parenthesized command and add the semicolon. */
+  /*)(*/
+  if (token_before_that == ')')
+    {
+      if (two_tokens_ago == '(')       /*)*/   /* function def */
+       return " ";
+      /* This does not work for subshells inside case statement
+        command lists.  It's a suboptimal solution. */
+      else if (parser_state & PST_CASESTMT)    /* case statement pattern */
+       return " ";
+      else     
+       return "; ";                            /* (...) subshell */
+    }
+  else if (token_before_that == WORD && two_tokens_ago == FUNCTION)
+    return " ";                /* function def using `function name' without `()' */
+
+  /* If we're not in a here document, but we think we're about to parse one,
+     and we would otherwise return a `;', return a newline to delimit the
+     line with the here-doc delimiter */
+  else if ((parser_state & PST_HEREDOC) == 0 && current_command_line_count > 1 && last_read_token == '\n' && strstr (line, "<<"))
+    {
+      last_was_heredoc = 1;
+      return "\n";
+    }
+
+  else if (token_before_that == WORD && two_tokens_ago == FOR)
+    {
+      /* Tricky.  `for i\nin ...' should not have a semicolon, but
+        `for i\ndo ...' should.  We do what we can. */
+      for (i = shell_input_line_index; whitespace (shell_input_line[i]); i++)
+       ;
+      if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n')
+       return " ";
+      return ";";
+    }
+  else if (two_tokens_ago == CASE && token_before_that == WORD && (parser_state & PST_CASESTMT))
+    return " ";
+
+  for (i = 0; no_semi_successors[i]; i++)
+    {
+      if (token_before_that == no_semi_successors[i])
+       return (" ");
+    }
+
+  return ("; ");
+}
+#endif /* HISTORY */
+
+/* Issue a prompt, or prepare to issue a prompt when the next character
+   is read. */
+static void
+prompt_again ()
+{
+  char *temp_prompt;
+
+  if (interactive == 0 || expanding_alias ())  /* XXX */
+    return;
+
+  ps1_prompt = get_string_value ("PS1");
+  ps2_prompt = get_string_value ("PS2");
+
+  if (!prompt_string_pointer)
+    prompt_string_pointer = &ps1_prompt;
+
+  temp_prompt = *prompt_string_pointer
+                       ? decode_prompt_string (*prompt_string_pointer)
+                       : (char *)NULL;
+
+  if (temp_prompt == 0)
+    {
+      temp_prompt = (char *)xmalloc (1);
+      temp_prompt[0] = '\0';
+    }
+
+  current_prompt_string = *prompt_string_pointer;
+  prompt_string_pointer = &ps2_prompt;
+
+#if defined (READLINE)
+  if (!no_line_editing)
+    {
+      FREE (current_readline_prompt);
+      current_readline_prompt = temp_prompt;
+    }
+  else
+#endif /* READLINE */
+    {
+      FREE (current_decoded_prompt);
+      current_decoded_prompt = temp_prompt;
+    }
+}
+
+int
+get_current_prompt_level ()
+{
+  return ((current_prompt_string && current_prompt_string == ps2_prompt) ? 2 : 1);
+}
+
+void
+set_current_prompt_level (x)
+     int x;
+{
+  prompt_string_pointer = (x == 2) ? &ps2_prompt : &ps1_prompt;
+  current_prompt_string = *prompt_string_pointer;
+}
+      
+static void
+print_prompt ()
+{
+  fprintf (stderr, "%s", current_decoded_prompt);
+  fflush (stderr);
+}
+
+/* Return a string which will be printed as a prompt.  The string
+   may contain special characters which are decoded as follows:
+
+       \a      bell (ascii 07)
+       \d      the date in Day Mon Date format
+       \e      escape (ascii 033)
+       \h      the hostname up to the first `.'
+       \H      the hostname
+       \j      the number of active jobs
+       \l      the basename of the shell's tty device name
+       \n      CRLF
+       \r      CR
+       \s      the name of the shell
+       \t      the time in 24-hour hh:mm:ss format
+       \T      the time in 12-hour hh:mm:ss format
+       \@      the time in 12-hour hh:mm am/pm format
+       \A      the time in 24-hour hh:mm format
+       \D{fmt} the result of passing FMT to strftime(3)
+       \u      your username
+       \v      the version of bash (e.g., 2.00)
+       \V      the release of bash, version + patchlevel (e.g., 2.00.0)
+       \w      the current working directory
+       \W      the last element of $PWD
+       \!      the history number of this command
+       \#      the command number of this command
+       \$      a $ or a # if you are root
+       \nnn    character code nnn in octal
+       \\      a backslash
+       \[      begin a sequence of non-printing chars
+       \]      end a sequence of non-printing chars
+*/
+#define PROMPT_GROWTH 48
+char *
+decode_prompt_string (string)
+     char *string;
+{
+  WORD_LIST *list;
+  char *result, *t;
+  struct dstack save_dstack;
+  int last_exit_value, last_comsub_pid;
+#if defined (PROMPT_STRING_DECODE)
+  int result_size, result_index;
+  int c, n, i;
+  char *temp, octal_string[4];
+  struct tm *tm;  
+  time_t the_time;
+  char timebuf[128];
+  char *timefmt;
+
+  result = (char *)xmalloc (result_size = PROMPT_GROWTH);
+  result[result_index = 0] = 0;
+  temp = (char *)NULL;
+
+  while (c = *string++)
+    {
+      if (posixly_correct && c == '!')
+       {
+         if (*string == '!')
+           {
+             temp = savestring ("!");
+             goto add_string;
+           }
+         else
+           {
+#if !defined (HISTORY)
+               temp = savestring ("1");
+#else /* HISTORY */
+               temp = itos (history_number ());
+#endif /* HISTORY */
+               string--;       /* add_string increments string again. */
+               goto add_string;
+           }
+       }
+      if (c == '\\')
+       {
+         c = *string;
+
+         switch (c)
+           {
+           case '0':
+           case '1':
+           case '2':
+           case '3':
+           case '4':
+           case '5':
+           case '6':
+           case '7':
+             strncpy (octal_string, string, 3);
+             octal_string[3] = '\0';
+
+             n = read_octal (octal_string);
+             temp = (char *)xmalloc (3);
+
+             if (n == CTLESC || n == CTLNUL)
+               {
+                 temp[0] = CTLESC;
+                 temp[1] = n;
+                 temp[2] = '\0';
+               }
+             else if (n == -1)
+               {
+                 temp[0] = '\\';
+                 temp[1] = '\0';
+               }
+             else
+               {
+                 temp[0] = n;
+                 temp[1] = '\0';
+               }
+
+             for (c = 0; n != -1 && c < 3 && ISOCTAL (*string); c++)
+               string++;
+
+             c = 0;            /* tested at add_string: */
+             goto add_string;
+
+           case 'd':
+           case 't':
+           case 'T':
+           case '@':
+           case 'A':
+             /* Make the current time/date into a string. */
+             (void) time (&the_time);
+             tm = localtime (&the_time);
+
+             if (c == 'd')
+               n = strftime (timebuf, sizeof (timebuf), "%a %b %d", tm);
+             else if (c == 't')
+               n = strftime (timebuf, sizeof (timebuf), "%H:%M:%S", tm);
+             else if (c == 'T')
+               n = strftime (timebuf, sizeof (timebuf), "%I:%M:%S", tm);
+             else if (c == '@')
+               n = strftime (timebuf, sizeof (timebuf), "%I:%M %p", tm);
+             else if (c == 'A')
+               n = strftime (timebuf, sizeof (timebuf), "%H:%M", tm);
+
+             if (n == 0)
+               timebuf[0] = '\0';
+             else
+               timebuf[sizeof(timebuf) - 1] = '\0';
+
+             temp = savestring (timebuf);
+             goto add_string;
+
+           case 'D':           /* strftime format */
+             if (string[1] != '{')             /* } */
+               goto not_escape;
+
+             (void) time (&the_time);
+             tm = localtime (&the_time);
+             string += 2;                      /* skip { */
+             timefmt = xmalloc (strlen (string) + 3);
+             for (t = timefmt; *string && *string != '}'; )
+               *t++ = *string++;
+             *t = '\0';
+             c = *string;      /* tested at add_string */
+             if (timefmt[0] == '\0')
+               {
+                 timefmt[0] = '%';
+                 timefmt[1] = 'X';     /* locale-specific current time */
+                 timefmt[2] = '\0';
+               }
+             n = strftime (timebuf, sizeof (timebuf), timefmt, tm);
+             free (timefmt);
+
+             if (n == 0)
+               timebuf[0] = '\0';
+             else
+               timebuf[sizeof(timebuf) - 1] = '\0';
+
+             if (promptvars || posixly_correct)
+               /* Make sure that expand_prompt_string is called with a
+                  second argument of Q_DOUBLE_QUOTES if we use this
+                  function here. */
+               temp = sh_backslash_quote_for_double_quotes (timebuf);
+             else
+               temp = savestring (timebuf);
+             goto add_string;
+             
+           case 'n':
+             temp = (char *)xmalloc (3);
+             temp[0] = no_line_editing ? '\n' : '\r';
+             temp[1] = no_line_editing ? '\0' : '\n';
+             temp[2] = '\0';
+             goto add_string;
+
+           case 's':
+             temp = base_pathname (shell_name);
+             temp = savestring (temp);
+             goto add_string;
+
+           case 'v':
+           case 'V':
+             temp = (char *)xmalloc (16);
+             if (c == 'v')
+               strcpy (temp, dist_version);
+             else
+               sprintf (temp, "%s.%d", dist_version, patch_level);
+             goto add_string;
+
+           case 'w':
+           case 'W':
+             {
+               /* Use the value of PWD because it is much more efficient. */
+               char t_string[PATH_MAX];
+               int tlen;
+
+               temp = get_string_value ("PWD");
+
+               if (temp == 0)
+                 {
+                   if (getcwd (t_string, sizeof(t_string)) == 0)
+                     {
+                       t_string[0] = '.';
+                       tlen = 1;
+                     }
+                   else
+                     tlen = strlen (t_string);
+                 }
+               else
+                 {
+                   tlen = sizeof (t_string) - 1;
+                   strncpy (t_string, temp, tlen);
+                 }
+               t_string[tlen] = '\0';
+
+#if defined (MACOSX)
+               /* Convert from "fs" format to "input" format */
+               temp = fnx_fromfs (t_string, strlen (t_string));
+               if (temp != t_string)
+                 strcpy (t_string, temp);
+#endif
+
+#define ROOT_PATH(x)   ((x)[0] == '/' && (x)[1] == 0)
+#define DOUBLE_SLASH_ROOT(x)   ((x)[0] == '/' && (x)[1] == '/' && (x)[2] == 0)
+               /* Abbreviate \W as ~ if $PWD == $HOME */
+               if (c == 'W' && (((t = get_string_value ("HOME")) == 0) || STREQ (t, t_string) == 0))
+                 {
+                   if (ROOT_PATH (t_string) == 0 && DOUBLE_SLASH_ROOT (t_string) == 0)
+                     {
+                       t = strrchr (t_string, '/');
+                       if (t)
+                         memmove (t_string, t + 1, strlen (t) - 1);
+                     }
+                 }
+#undef ROOT_PATH
+#undef DOUBLE_SLASH_ROOT
+               else
+                 /* polite_directory_format is guaranteed to return a string
+                    no longer than PATH_MAX - 1 characters. */
+                 strcpy (t_string, polite_directory_format (t_string));
+
+               temp = trim_pathname (t_string, PATH_MAX - 1);
+               /* If we're going to be expanding the prompt string later,
+                  quote the directory name. */
+               if (promptvars || posixly_correct)
+                 /* Make sure that expand_prompt_string is called with a
+                    second argument of Q_DOUBLE_QUOTES if we use this
+                    function here. */
+                 temp = sh_backslash_quote_for_double_quotes (t_string);
+               else
+                 temp = savestring (t_string);
+
+               goto add_string;
+             }
+
+           case 'u':
+             if (current_user.user_name == 0)
+               get_current_user_info ();
+             temp = savestring (current_user.user_name);
+             goto add_string;
+
+           case 'h':
+           case 'H':
+             temp = savestring (current_host_name);
+             if (c == 'h' && (t = (char *)strchr (temp, '.')))
+               *t = '\0';
+             goto add_string;
+
+           case '#':
+             temp = itos (current_command_number);
+             goto add_string;
+
+           case '!':
+#if !defined (HISTORY)
+             temp = savestring ("1");
+#else /* HISTORY */
+             temp = itos (history_number ());
+#endif /* HISTORY */
+             goto add_string;
+
+           case '$':
+             t = temp = (char *)xmalloc (3);
+             if ((promptvars || posixly_correct) && (current_user.euid != 0))
+               *t++ = '\\';
+             *t++ = current_user.euid == 0 ? '#' : '$';
+             *t = '\0';
+             goto add_string;
+
+           case 'j':
+             temp = itos (count_all_jobs ());
+             goto add_string;
+
+           case 'l':
+#if defined (HAVE_TTYNAME)
+             temp = (char *)ttyname (fileno (stdin));
+             t = temp ? base_pathname (temp) : "tty";
+             temp = savestring (t);
+#else
+             temp = savestring ("tty");
+#endif /* !HAVE_TTYNAME */
+             goto add_string;
+
+#if defined (READLINE)
+           case '[':
+           case ']':
+             if (no_line_editing)
+               {
+                 string++;
+                 break;
+               }
+             temp = (char *)xmalloc (3);
+             n = (c == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
+             i = 0;
+             if (n == CTLESC || n == CTLNUL)
+               temp[i++] = CTLESC;
+             temp[i++] = n;
+             temp[i] = '\0';
+             goto add_string;
+#endif /* READLINE */
+
+           case '\\':
+           case 'a':
+           case 'e':
+           case 'r':
+             temp = (char *)xmalloc (2);
+             if (c == 'a')
+               temp[0] = '\07';
+             else if (c == 'e')
+               temp[0] = '\033';
+             else if (c == 'r')
+               temp[0] = '\r';
+             else                      /* (c == '\\') */
+               temp[0] = c;
+             temp[1] = '\0';
+             goto add_string;
+
+           default:
+not_escape:
+             temp = (char *)xmalloc (3);
+             temp[0] = '\\';
+             temp[1] = c;
+             temp[2] = '\0';
+
+           add_string:
+             if (c)
+               string++;
+             result =
+               sub_append_string (temp, result, &result_index, &result_size);
+             temp = (char *)NULL; /* Freed in sub_append_string (). */
+             result[result_index] = '\0';
+             break;
+           }
+       }
+      else
+       {
+         RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, PROMPT_GROWTH);
+         result[result_index++] = c;
+         result[result_index] = '\0';
+       }
+    }
+#else /* !PROMPT_STRING_DECODE */
+  result = savestring (string);
+#endif /* !PROMPT_STRING_DECODE */
+
+  /* Save the delimiter stack and point `dstack' to temp space so any
+     command substitutions in the prompt string won't result in screwing
+     up the parser's quoting state. */
+  save_dstack = dstack;
+  dstack = temp_dstack;
+  dstack.delimiter_depth = 0;
+
+  /* Perform variable and parameter expansion and command substitution on
+     the prompt string. */
+  if (promptvars || posixly_correct)
+    {
+      last_exit_value = last_command_exit_value;
+      last_comsub_pid = last_command_subst_pid;
+      list = expand_prompt_string (result, Q_DOUBLE_QUOTES, 0);
+      free (result);
+      result = string_list (list);
+      dispose_words (list);
+      last_command_exit_value = last_exit_value;
+      last_command_subst_pid = last_comsub_pid;
+    }
+  else
+    {
+      t = dequote_string (result);
+      free (result);
+      result = t;
+    }
+
+  dstack = save_dstack;
+
+  return (result);
+}
+
+/************************************************
+ *                                             *
+ *             ERROR HANDLING                  *
+ *                                             *
+ ************************************************/
+
+/* Report a syntax error, and restart the parser.  Call here for fatal
+   errors. */
+int
+yyerror (msg)
+     const char *msg;
+{
+  report_syntax_error ((char *)NULL);
+  reset_parser ();
+  return (0);
+}
+
+static char *
+error_token_from_token (tok)
+     int tok;
+{
+  char *t;
+
+  if (t = find_token_in_alist (tok, word_token_alist, 0))
+    return t;
+
+  if (t = find_token_in_alist (tok, other_token_alist, 0))
+    return t;
+
+  t = (char *)NULL;
+  /* This stuff is dicy and needs closer inspection */
+  switch (current_token)
+    {
+    case WORD:
+    case ASSIGNMENT_WORD:
+      if (yylval.word)
+       t = savestring (yylval.word->word);
+      break;
+    case NUMBER:
+      t = itos (yylval.number);
+      break;
+    case ARITH_CMD:
+      if (yylval.word_list)
+        t = string_list (yylval.word_list);
+      break;
+    case ARITH_FOR_EXPRS:
+      if (yylval.word_list)
+       t = string_list_internal (yylval.word_list, " ; ");
+      break;
+    case COND_CMD:
+      t = (char *)NULL;                /* punt */
+      break;
+    }
+
+  return t;
+}
+
+static char *
+error_token_from_text ()
+{
+  char *msg, *t;
+  int token_end, i;
+
+  t = shell_input_line;
+  i = shell_input_line_index;
+  token_end = 0;
+  msg = (char *)NULL;
+
+  if (i && t[i] == '\0')
+    i--;
+
+  while (i && (whitespace (t[i]) || t[i] == '\n'))
+    i--;
+
+  if (i)
+    token_end = i + 1;
+
+  while (i && (member (t[i], " \n\t;|&") == 0))
+    i--;
+
+  while (i != token_end && (whitespace (t[i]) || t[i] == '\n'))
+    i++;
+
+  /* Return our idea of the offending token. */
+  if (token_end || (i == 0 && token_end == 0))
+    {
+      if (token_end)
+       msg = substring (t, i, token_end);
+      else     /* one-character token */
+       {
+         msg = (char *)xmalloc (2);
+         msg[0] = t[i];
+         msg[1] = '\0';
+       }
+    }
+
+  return (msg);
+}
+
+static void
+print_offending_line ()
+{
+  char *msg;
+  int token_end;
+
+  msg = savestring (shell_input_line);
+  token_end = strlen (msg);
+  while (token_end && msg[token_end - 1] == '\n')
+    msg[--token_end] = '\0';
+
+  parser_error (line_number, "`%s'", msg);
+  free (msg);
+}
+
+/* Report a syntax error with line numbers, etc.
+   Call here for recoverable errors.  If you have a message to print,
+   then place it in MESSAGE, otherwise pass NULL and this will figure
+   out an appropriate message for you. */
+static void
+report_syntax_error (message)
+     char *message;
+{
+  char *msg;
+
+  if (message)
+    {
+      parser_error (line_number, "%s", message);
+      if (interactive && EOF_Reached)
+       EOF_Reached = 0;
+      last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE;
+      return;
+    }
+
+  /* If the line of input we're reading is not null, try to find the
+     objectionable token.  First, try to figure out what token the
+     parser's complaining about by looking at current_token. */
+  if (current_token != 0 && EOF_Reached == 0 && (msg = error_token_from_token (current_token)))
+    {
+      parser_error (line_number, _("syntax error near unexpected token `%s'"), msg);
+      free (msg);
+
+      if (interactive == 0)
+       print_offending_line ();
+
+      last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE;
+      return;
+    }
+
+  /* If looking at the current token doesn't prove fruitful, try to find the
+     offending token by analyzing the text of the input line near the current
+     input line index and report what we find. */
+  if (shell_input_line && *shell_input_line)
+    {
+      msg = error_token_from_text ();
+      if (msg)
+       {
+         parser_error (line_number, _("syntax error near `%s'"), msg);
+         free (msg);
+       }
+
+      /* If not interactive, print the line containing the error. */
+      if (interactive == 0)
+        print_offending_line ();
+    }
+  else
+    {
+      msg = EOF_Reached ? _("syntax error: unexpected end of file") : _("syntax error");
+      parser_error (line_number, "%s", msg);
+      /* When the shell is interactive, this file uses EOF_Reached
+        only for error reporting.  Other mechanisms are used to
+        decide whether or not to exit. */
+      if (interactive && EOF_Reached)
+       EOF_Reached = 0;
+    }
+
+  last_command_exit_value = parse_and_execute_level ? EX_BADSYNTAX : EX_BADUSAGE;
+}
+
+/* ??? Needed function. ??? We have to be able to discard the constructs
+   created during parsing.  In the case of error, we want to return
+   allocated objects to the memory pool.  In the case of no error, we want
+   to throw away the information about where the allocated objects live.
+   (dispose_command () will actually free the command.) */
+static void
+discard_parser_constructs (error_p)
+     int error_p;
+{
+}
+
+/************************************************
+ *                                             *
+ *             EOF HANDLING                    *
+ *                                             *
+ ************************************************/
+
+/* Do that silly `type "bye" to exit' stuff.  You know, "ignoreeof". */
+
+/* A flag denoting whether or not ignoreeof is set. */
+int ignoreeof = 0;
+
+/* The number of times that we have encountered an EOF character without
+   another character intervening.  When this gets above the limit, the
+   shell terminates. */
+int eof_encountered = 0;
+
+/* The limit for eof_encountered. */
+int eof_encountered_limit = 10;
+
+/* If we have EOF as the only input unit, this user wants to leave
+   the shell.  If the shell is not interactive, then just leave.
+   Otherwise, if ignoreeof is set, and we haven't done this the
+   required number of times in a row, print a message. */
+static void
+handle_eof_input_unit ()
+{
+  if (interactive)
+    {
+      /* shell.c may use this to decide whether or not to write out the
+        history, among other things.  We use it only for error reporting
+        in this file. */
+      if (EOF_Reached)
+       EOF_Reached = 0;
+
+      /* If the user wants to "ignore" eof, then let her do so, kind of. */
+      if (ignoreeof)
+       {
+         if (eof_encountered < eof_encountered_limit)
+           {
+             fprintf (stderr, _("Use \"%s\" to leave the shell.\n"),
+                      login_shell ? "logout" : "exit");
+             eof_encountered++;
+             /* Reset the parsing state. */
+             last_read_token = current_token = '\n';
+             /* Reset the prompt string to be $PS1. */
+             prompt_string_pointer = (char **)NULL;
+             prompt_again ();
+             return;
+           }
+       }
+
+      /* In this case EOF should exit the shell.  Do it now. */
+      reset_parser ();
+      exit_builtin ((WORD_LIST *)NULL);
+    }
+  else
+    {
+      /* We don't write history files, etc., for non-interactive shells. */
+      EOF_Reached = 1;
+    }
+}
+
+/************************************************
+ *                                             *
+ *     STRING PARSING FUNCTIONS                *
+ *                                             *
+ ************************************************/
+
+/* It's very important that these two functions treat the characters
+   between ( and ) identically. */
+
+static WORD_LIST parse_string_error;
+
+/* Take a string and run it through the shell parser, returning the
+   resultant word list.  Used by compound array assignment. */
+WORD_LIST *
+parse_string_to_word_list (s, flags, whom)
+     char *s;
+     int flags;
+     const char *whom;
+{
+  WORD_LIST *wl;
+  int tok, orig_current_token, orig_line_number, orig_input_terminator;
+  int orig_line_count;
+  int old_echo_input, old_expand_aliases;
+#if defined (HISTORY)
+  int old_remember_on_history, old_history_expansion_inhibited;
+#endif
+
+#if defined (HISTORY)
+  old_remember_on_history = remember_on_history;
+#  if defined (BANG_HISTORY)
+  old_history_expansion_inhibited = history_expansion_inhibited;
+#  endif
+  bash_history_disable ();
+#endif
+
+  orig_line_number = line_number;
+  orig_line_count = current_command_line_count;
+  orig_input_terminator = shell_input_line_terminator;
+  old_echo_input = echo_input_at_read;
+  old_expand_aliases = expand_aliases;
+
+  push_stream (1);
+  last_read_token = WORD;              /* WORD to allow reserved words here */
+  current_command_line_count = 0;
+  echo_input_at_read = expand_aliases = 0;
+
+  with_input_from_string (s, whom);
+  wl = (WORD_LIST *)NULL;
+
+  if (flags & 1)
+    parser_state |= PST_COMPASSIGN|PST_REPARSE;
+
+  while ((tok = read_token (READ)) != yacc_EOF)
+    {
+      if (tok == '\n' && *bash_input.location.string == '\0')
+       break;
+      if (tok == '\n')         /* Allow newlines in compound assignments */
+       continue;
+      if (tok != WORD && tok != ASSIGNMENT_WORD)
+       {
+         line_number = orig_line_number + line_number - 1;
+         orig_current_token = current_token;
+         current_token = tok;
+         yyerror (NULL);       /* does the right thing */
+         current_token = orig_current_token;
+         if (wl)
+           dispose_words (wl);
+         wl = &parse_string_error;
+         break;
+       }
+      wl = make_word_list (yylval.word, wl);
+    }
+  
+  last_read_token = '\n';
+  pop_stream ();
+
+#if defined (HISTORY)
+  remember_on_history = old_remember_on_history;
+#  if defined (BANG_HISTORY)
+  history_expansion_inhibited = old_history_expansion_inhibited;
+#  endif /* BANG_HISTORY */
+#endif /* HISTORY */
+
+  echo_input_at_read = old_echo_input;
+  expand_aliases = old_expand_aliases;
+
+  current_command_line_count = orig_line_count;
+  shell_input_line_terminator = orig_input_terminator;
+
+  if (flags & 1)
+    parser_state &= ~(PST_COMPASSIGN|PST_REPARSE);
+
+  if (wl == &parse_string_error)
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      if (interactive_shell == 0 && posixly_correct)
+       jump_to_top_level (FORCE_EOF);
+      else
+       jump_to_top_level (DISCARD);
+    }
+
+  return (REVERSE_LIST (wl, WORD_LIST *));
+}
+
+static char *
+parse_compound_assignment (retlenp)
+     int *retlenp;
+{
+  WORD_LIST *wl, *rl;
+  int tok, orig_line_number, orig_token_size, orig_last_token, assignok;
+  char *saved_token, *ret;
+
+  saved_token = token;
+  orig_token_size = token_buffer_size;
+  orig_line_number = line_number;
+  orig_last_token = last_read_token;
+
+  last_read_token = WORD;      /* WORD to allow reserved words here */
+
+  token = (char *)NULL;
+  token_buffer_size = 0;
+
+  assignok = parser_state&PST_ASSIGNOK;                /* XXX */
+
+  wl = (WORD_LIST *)NULL;      /* ( */
+  parser_state |= PST_COMPASSIGN;
+
+  while ((tok = read_token (READ)) != ')')
+    {
+      if (tok == '\n')                 /* Allow newlines in compound assignments */
+       {
+         if (SHOULD_PROMPT ())
+           prompt_again ();
+         continue;
+       }
+      if (tok != WORD && tok != ASSIGNMENT_WORD)
+       {
+         current_token = tok;  /* for error reporting */
+         if (tok == yacc_EOF)  /* ( */
+           parser_error (orig_line_number, _("unexpected EOF while looking for matching `)'"));
+         else
+           yyerror(NULL);      /* does the right thing */
+         if (wl)
+           dispose_words (wl);
+         wl = &parse_string_error;
+         break;
+       }
+      wl = make_word_list (yylval.word, wl);
+    }
+
+  FREE (token);
+  token = saved_token;
+  token_buffer_size = orig_token_size;
+
+  parser_state &= ~PST_COMPASSIGN;
+
+  if (wl == &parse_string_error)
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      last_read_token = '\n';  /* XXX */
+      if (interactive_shell == 0 && posixly_correct)
+       jump_to_top_level (FORCE_EOF);
+      else
+       jump_to_top_level (DISCARD);
+    }
+
+  last_read_token = orig_last_token;           /* XXX - was WORD? */
+
+  if (wl)
+    {
+      rl = REVERSE_LIST (wl, WORD_LIST *);
+      ret = string_list (rl);
+      dispose_words (rl);
+    }
+  else
+    ret = (char *)NULL;
+
+  if (retlenp)
+    *retlenp = (ret && *ret) ? strlen (ret) : 0;
+
+  if (assignok)
+    parser_state |= PST_ASSIGNOK;
+
+  return ret;
+}
+
+/************************************************
+ *                                             *
+ *   SAVING AND RESTORING PARTIAL PARSE STATE   *
+ *                                             *
+ ************************************************/
+
+sh_parser_state_t *
+save_parser_state (ps)
+     sh_parser_state_t *ps;
+{
+  if (ps == 0)
+    ps = (sh_parser_state_t *)xmalloc (sizeof (sh_parser_state_t));
+  if (ps == 0)
+    return ((sh_parser_state_t *)NULL);
+
+  ps->parser_state = parser_state;
+  ps->token_state = save_token_state ();
+
+  ps->input_line_terminator = shell_input_line_terminator;
+  ps->eof_encountered = eof_encountered;
+
+  ps->current_command_line_count = current_command_line_count;
+
+#if defined (HISTORY)
+  ps->remember_on_history = remember_on_history;
+#  if defined (BANG_HISTORY)
+  ps->history_expansion_inhibited = history_expansion_inhibited;
+#  endif
+#endif
+
+  ps->last_command_exit_value = last_command_exit_value;
+#if defined (ARRAY_VARS)
+  ps->pipestatus = save_pipestatus_array ();
+#endif
+    
+  ps->last_shell_builtin = last_shell_builtin;
+  ps->this_shell_builtin = this_shell_builtin;
+
+  ps->expand_aliases = expand_aliases;
+  ps->echo_input_at_read = echo_input_at_read;
+
+  return (ps);
+}
+
+void
+restore_parser_state (ps)
+     sh_parser_state_t *ps;
+{
+  if (ps == 0)
+    return;
+
+  parser_state = ps->parser_state;
+  if (ps->token_state)
+    {
+      restore_token_state (ps->token_state);
+      free (ps->token_state);
+    }
+
+  shell_input_line_terminator = ps->input_line_terminator;
+  eof_encountered = ps->eof_encountered;
+
+  current_command_line_count = ps->current_command_line_count;
+
+#if defined (HISTORY)
+  remember_on_history = ps->remember_on_history;
+#  if defined (BANG_HISTORY)
+  history_expansion_inhibited = ps->history_expansion_inhibited;
+#  endif
+#endif
+
+  last_command_exit_value = ps->last_command_exit_value;
+#if defined (ARRAY_VARS)
+  restore_pipestatus_array (ps->pipestatus);
+#endif
+
+  last_shell_builtin = ps->last_shell_builtin;
+  this_shell_builtin = ps->this_shell_builtin;
+
+  expand_aliases = ps->expand_aliases;
+  echo_input_at_read = ps->echo_input_at_read;
+}
+
+/************************************************
+ *                                             *
+ *     MULTIBYTE CHARACTER HANDLING            *
+ *                                             *
+ ************************************************/
+
+#if defined (HANDLE_MULTIBYTE)
+static void
+set_line_mbstate ()
+{
+  int i, previ, len, c;
+  mbstate_t mbs, prevs;
+  size_t mbclen;
+
+  if (shell_input_line == NULL)
+    return;
+  len = strlen (shell_input_line);     /* XXX - shell_input_line_len ? */
+  FREE (shell_input_line_property);
+  shell_input_line_property = (char *)xmalloc (len + 1);
+
+  memset (&prevs, '\0', sizeof (mbstate_t));
+  for (i = previ = 0; i < len; i++)
+    {
+      mbs = prevs;
+
+      c = shell_input_line[i];
+      if (c == EOF)
+       {
+         int j;
+         for (j = i; j < len; j++)
+           shell_input_line_property[j] = 1;
+         break;
+       }
+
+      mbclen = mbrlen (shell_input_line + previ, i - previ + 1, &mbs);
+      if (mbclen == 1 || mbclen == (size_t)-1)
+       {
+         mbclen = 1;
+         previ = i + 1;
+       }
+      else if (mbclen == (size_t)-2)
+        mbclen = 0;
+      else if (mbclen > 1)
+       {
+         mbclen = 0;
+         previ = i + 1;
+         prevs = mbs;
+       }
+      else
+       {
+         /* XXX - what to do if mbrlen returns 0? (null wide character) */
+         int j;
+         for (j = i; j < len; j++)
+           shell_input_line_property[j] = 1;
+         break;
+       }
+
+      shell_input_line_property[i] = mbclen;
+    }
+}
+#endif /* HANDLE_MULTIBYTE */
index 857873231ffe482dabecfebede2441fe8e7a446d..509d13b31ab7050951fa6c7d3e8a316aece5522a 100644 (file)
@@ -1189,31 +1189,19 @@ simple_list1:   simple_list1 AND_AND newline_list simple_list1
        ;
 
 pipeline_command: pipeline
-                       { $$ = $1; }
-       |       BANG pipeline
+                       { $$ = $1; }                    
+       |       BANG pipeline_command
                        {
                          if ($2)
-                           $2->flags |= CMD_INVERT_RETURN;
+                           $2->flags ^= CMD_INVERT_RETURN;     /* toggle */
                          $$ = $2;
                        }
-       |       timespec pipeline
+       |       timespec pipeline_command
                        {
                          if ($2)
                            $2->flags |= $1;
                          $$ = $2;
                        }
-       |       timespec BANG pipeline
-                       {
-                         if ($3)
-                           $3->flags |= $1|CMD_INVERT_RETURN;
-                         $$ = $3;
-                       }
-       |       BANG timespec pipeline
-                       {
-                         if ($3)
-                           $3->flags |= $2|CMD_INVERT_RETURN;
-                         $$ = $3;
-                       }
        |       timespec list_terminator
                        {
                          ELEMENT x;
@@ -1231,7 +1219,24 @@ pipeline_command: pipeline
                          if ($2 == '\n')
                            token_to_read = '\n';
                        }
-                       
+       |       BANG list_terminator
+                       {
+                         ELEMENT x;
+
+                         /* This is just as unclean.  Posix says that `!'
+                            by itself should be equivalent to `false'.
+                            We cheat and push a
+                            newline back if the list_terminator was a newline
+                            to avoid the double-newline problem (one to
+                            terminate this, one to terminate the command) */
+                         x.word = 0;
+                         x.redirect = 0;
+                         $$ = make_simple_command (x, (COMMAND *)NULL);
+                         $$->flags |= CMD_INVERT_RETURN;
+                         /* XXX - let's cheat and push a newline back */
+                         if ($2 == '\n')
+                           token_to_read = '\n';
+                       }
        ;
 
 pipeline:      pipeline '|' newline_list pipeline
@@ -2663,6 +2668,9 @@ time_command_acceptable ()
     case ELSE:
     case '{':          /* } */
     case '(':          /* ) */
+    case BANG:         /* ! time pipeline */
+    case TIME:         /* time time pipeline */
+    case TIMEOPT:      /* time -p time pipeline */
       return 1;
     default:
       return 0;
@@ -2776,13 +2784,6 @@ special_case_tokens (tokstr)
     return (TIMEOPT);
 #endif
 
-#if 0
-#if defined (COMMAND_TIMING)
-  if (STREQ (token, "time") && ((parser_state & PST_CASEPAT) == 0) && time_command_acceptable ())
-    return (TIME);
-#endif /* COMMAND_TIMING */
-#endif
-
 #if defined (COND_COMMAND) /* [[ */
   if ((parser_state & PST_CONDEXPR) && tokstr[0] == ']' && tokstr[1] == ']' && tokstr[2] == '\0')
     return (COND_END);
@@ -3232,7 +3233,6 @@ parse_matched_pair (qc, open, close, lenp, flags)
       if MBTEST(ch == '\\')                    /* backslashes */
        tflags |= LEX_PASSNEXT;
 
-#if 0  /* XXX - bash-4.2 */
       /* Based on which dolstate is currently in (param, op, or word),
         decide what the op is.  We're really only concerned if it's % or
         #, so we can turn on a flag that says whether or not we should
@@ -3241,26 +3241,34 @@ parse_matched_pair (qc, open, close, lenp, flags)
         since they share the same defines. */
       if (flags & P_DOLBRACE)
         {
-         if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '%' && retind > 0)
+          /* ${param%[%]word} */
+         if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '%' && retind > 1)
            dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param#[#]word} */
          else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '#' && retind > 1)
            dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param/[/]pat/rep} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '/' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param^[^]pat} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '^' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param,[,]pat} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == ',' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
          else if MBTEST(dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", ch) != 0)
            dolbrace_state = DOLBRACE_OP;
          else if MBTEST(dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", ch) == 0)
            dolbrace_state = DOLBRACE_WORD;
         }
-#endif
 
-#if 0 /* XXX - bash-4.2 */
       /* The big hammer.  Single quotes aren't special in double quotes.  The
          problem is that Posix used to say the single quotes are semi-special:
          within a double-quoted ${...} construct "an even number of
          unescaped double-quotes or single-quotes, if any, shall occur." */
-      /* This was changed in Interp 221 */
-      if MBTEST(/*posixly_correct && shell_compatibility_level > 41 &&*/ dolbrace_state == DOLBRACE_QUOTE && (flags & P_DQUOTE) && (flags & P_DOLBRACE) && ch == '\'')
+      /* This was changed in Austin Group Interp 221 */
+      if MBTEST(posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && (flags & P_DQUOTE) && (flags & P_DOLBRACE) && ch == '\'')
        continue;
-#endif
 
       /* Could also check open == '`' if we want to parse grouping constructs
         inside old-style command substitution. */
@@ -3370,13 +3378,12 @@ parse_comsub (qc, open, close, lenp, flags)
   char *ret, *nestret, *ttrans, *heredelim;
   int retind, retsize, rflags, hdlen;
 
-#if 0  /* XXX - bash-4.2 -- jwm@horde.net */
-  /* Assume $(( introduces arithmetic command and parse accordingly. */
+  /* Posix interp 217 says arithmetic expressions have precedence, so
+     assume $(( introduces arithmetic expansion and parse accordingly. */
   peekc = shell_getc (0);
   shell_ungetc (peekc);
   if (peekc == '(')
     return (parse_matched_pair (qc, open, close, lenp, 0));
-#endif
 
 /*itrace("parse_comsub: qc = `%c' open = %c close = %c", qc, open, close);*/
   count = 1;
@@ -5220,7 +5227,7 @@ decode_prompt_string (string)
                      {
                        t = strrchr (t_string, '/');
                        if (t)
-                         strcpy (t_string, t + 1);
+                         memmove (t_string, t + 1, strlen (t) - 1);
                      }
                  }
 #undef ROOT_PATH
index 35e81241a0112ec4acf8c2a1e44bbcca85ccc863..93435ac74cb7fa3eef5f5e53ae013d908f4bcbfd 100644 (file)
--- a/parse.y~
+++ b/parse.y~
@@ -336,7 +336,7 @@ static REDIRECTEE redir;
    third group are recognized only under special circumstances. */
 %token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION COPROC
 %token COND_START COND_END COND_ERROR
-%token IN BANG TIME TIMEOPT
+%token IN BANG TIME TIMEOPT TIMEIGN
 
 /* More general tokens. yylex () knows how to make these. */
 %token <word> WORD ASSIGNMENT_WORD REDIR_WORD
@@ -1272,6 +1272,8 @@ timespec: TIME
                        { $$ = CMD_TIME_PIPELINE; }
        |       TIME TIMEOPT
                        { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
+       |       TIME TIMEOPT TIMEIGN
+                       { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
        ;
 %%
 
@@ -2068,6 +2070,7 @@ STRING_INT_ALIST word_token_alist[] = {
 /* other tokens that can be returned by read_token() */
 STRING_INT_ALIST other_token_alist[] = {
   /* Multiple-character tokens with special values */
+  { "--", TIMEIGN },
   { "-p", TIMEOPT },
   { "&&", AND_AND },
   { "||", OR_OR },
@@ -2513,6 +2516,7 @@ yylex ()
   if ((parser_state & PST_EOFTOKEN) && current_token == shell_eof_token)
     {
       current_token = yacc_EOF;
+itrace("read_token: read shell_eof_token, returning yacc_EOF");
       if (bash_input.type == st_string)
        rewind_input_string ();
     }
@@ -2671,6 +2675,7 @@ time_command_acceptable ()
     case BANG:         /* ! time pipeline */
     case TIME:         /* time time pipeline */
     case TIMEOPT:      /* time -p time pipeline */
+    case TIMEIGN:      /* time -p -- ... */
       return 1;
     default:
       return 0;
@@ -2697,6 +2702,7 @@ time_command_acceptable ()
        `}' is recognized if there is an unclosed `{' present.
 
        `-p' is returned as TIMEOPT if the last read token was TIME.
+       `--' is returned as TIMEIGN if the last read token was TIMEOPT.
 
        ']]' is returned as COND_END if the parser is currently parsing
        a conditional expression ((parser_state & PST_CONDEXPR) != 0)
@@ -2782,6 +2788,9 @@ special_case_tokens (tokstr)
   /* Handle -p after `time'. */
   if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2])
     return (TIMEOPT);
+  /* Handle -- after `time -p'. */
+  if (last_read_token == TIMEOPT && tokstr[0] == '-' && tokstr[1] == '-' && !tokstr[2])
+    return (TIMEIGN);
 #endif
 
 #if defined (COND_COMMAND) /* [[ */
@@ -3241,10 +3250,21 @@ parse_matched_pair (qc, open, close, lenp, flags)
         since they share the same defines. */
       if (flags & P_DOLBRACE)
         {
-         if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '%' && retind > 0)
+          /* ${param%[%]word} */
+         if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '%' && retind > 1)
            dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param#[#]word} */
          else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '#' && retind > 1)
            dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param/[/]pat/rep} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '/' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param^[^]pat} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '^' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
+          /* ${param,[,]pat} */
+         else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == ',' && retind > 1)
+           dolbrace_state = DOLBRACE_QUOTE;
          else if MBTEST(dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", ch) != 0)
            dolbrace_state = DOLBRACE_OP;
          else if MBTEST(dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", ch) == 0)
@@ -3256,7 +3276,7 @@ parse_matched_pair (qc, open, close, lenp, flags)
          within a double-quoted ${...} construct "an even number of
          unescaped double-quotes or single-quotes, if any, shall occur." */
       /* This was changed in Austin Group Interp 221 */
-      if MBTEST(posixly_correct && /*shell_compatibility_level > 41 &&*/ dolbrace_state != DOLBRACE_QUOTE && (flags & P_DQUOTE) && (flags & P_DOLBRACE) && ch == '\'')
+      if MBTEST(posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && (flags & P_DQUOTE) && (flags & P_DOLBRACE) && ch == '\'')
        continue;
 
       /* Could also check open == '`' if we want to parse grouping constructs
@@ -4721,6 +4741,9 @@ got_token:
       break;
     }
 
+if (result == WORD)
+  itrace("read_token: returning WORD: `%s'", yylval.word->word);
+
   return (result);
 }
 
@@ -4757,6 +4780,7 @@ reserved_word_acceptable (toksym)
     case THEN:
     case TIME:
     case TIMEOPT:
+    case TIMEIGN:
     case COPROC:
     case UNTIL:
     case WHILE:
index 823d3edc00808d6ee823df419d9c6752986833ec..de33804c12b97ef48d458142549b7825eedfbce4 100644 (file)
@@ -8,7 +8,7 @@ msgstr ""
 "Project-Id-Version: bash 4.1\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2009-12-30 08:25-0500\n"
-"PO-Revision-Date: 2010-06-13 14:31+0800\n"
+"PO-Revision-Date: 2010-06-24 16:27+0800\n"
 "Last-Translator: Alyex.ye <alyex.ye@gmail.com>\n"
 "Language-Team: Chinese (simplified) <i18n-zh@googlegroups.com>\n"
 "MIME-Version: 1.0\n"
@@ -2284,6 +2284,21 @@ msgid ""
 "    alias returns true unless a NAME is supplied for which no alias has been\n"
 "    defined."
 msgstr ""
+"定义或显示别名。\n"
+"    \n"
+"    不带参数时, `alias' 以可重用的格式\n"
+"    `alias 名称=值'在标准输出设备上打印别名列表。\n"
+"    \n"
+"    否则,对于每个给定值的名称定义一个别名。\n"
+"    值末尾的空格会使下一个词被检测\n"
+"    作为别名替换展开。\n"
+"    \n"
+"    选项:\n"
+"      -p\t以可重用的格式打印所有的已定义的别名\n"
+"    \n"
+"    退出状态:\n"
+"    除非一个没有定义的名字被作为参数提供,否则 alias \n"
+"    返回值为真。"
 
 #: builtins.c:276
 msgid ""
@@ -2335,6 +2350,37 @@ msgid ""
 "    Exit Status:\n"
 "    bind returns 0 unless an unrecognized option is given or an error occurs."
 msgstr ""
+"设定行读取键绑定和变量。\n"
+"    \n"
+"    绑定一个键序列到一个行读取函数或者宏,或者设定一个\n"
+"    行读取变量。非选项参数的语法和 ~/.inputrc 文件中的等\n"
+"    同,但是必须作为一个参数被传递,\n"
+"    例如,bind '\"\\C-x\\C-r\": re-read-init-file'.\n"
+"    \n"
+"    选项:\n"
+"      -m  键映射         在此命令执行过程中使用指定的键映射。\n"
+"                         可以被接受的键映射名字有 emacs、\n"
+"                         emacs-standard、emacs-meta、emacs-ctlx、vi、\n"
+"                         vi-move、vi-command、和 vi-insert。\n"
+"      -l                 列出函数名称。\n"
+"      -P                 列出函数名称和绑定。\n"
+"      -p                 以可以重新用作输入的格式列出函数名称和\n"
+"                         绑定。\n"
+"      -S                 列出可以启动宏的键序列以及它们的值\n"
+"      -s                 以可以重新用作输入的格式列出可以启动宏\n"
+"                         的键以及它们的值。\n"
+"      -V                 列出变量名成和它们的值\n"
+"      -v                 以可以重新用作输入的格式列出变量的名称\n"
+"                         和它们的值\n"
+"      -q  函数名  查询指定的函数可以由哪些键启动。\n"
+"      -u  函数名  反绑定所有绑定至指定函数的键。\n"
+"      -r  键序列         取消指定键序列的绑定。\n"
+"      -f  文件名       从指定文件中读取键绑定。\n"
+"      -x  键序列:shell命令\t当指定的键序列被输入时,执行\n"
+"    \t\t\t\t指定的shell命令\n"
+"    \n"
+"    退出状态:\n"
+"    除非使用了无法辨认的选项或者错误发生,否则返回0."
 
 #: builtins.c:326
 msgid ""
@@ -2346,6 +2392,13 @@ msgid ""
 "    Exit Status:\n"
 "    The exit status is 0 unless N is not greater than or equal to 1."
 msgstr ""
+"退出 for、while、或 until 循环\n"
+"    \n"
+"    退出一个 FOR、WHILE 或 UNTIL 循环。如果指定了N,则打破N重\n"
+"    循环\n"
+"    \n"
+"    退出状态:\n"
+"    退出状态为0除非 N 不大于或等于 1。"
 
 #: builtins.c:338
 msgid ""
@@ -2357,6 +2410,13 @@ msgid ""
 "    Exit Status:\n"
 "    The exit status is 0 unless N is not greater than or equal to 1."
 msgstr ""
+"继续 for、while、或 until 循环。\n"
+"    \n"
+"    继续当前 FOR、WHILE 或 UNTIL 循环的下一步。\n"
+"    如果指定了 N, 则继续当前的第 N 重循环。\n"
+"    \n"
+"    退出状态:\n"
+"    退出状态为 0 除非 N 不大于或等于1。"
 
 #: builtins.c:350
 msgid ""
@@ -2370,6 +2430,15 @@ msgid ""
 "    Returns the exit status of SHELL-BUILTIN, or false if SHELL-BUILTIN is\n"
 "    not a shell builtin.."
 msgstr ""
+"执行 shell 内嵌。\n"
+"    \n"
+"    带参数 ARGs 执行 SHELL-BUILTIN 内嵌,并且不做命令查询\n"
+"    在希望以 shell 函数的形式来重新实现 shell 内嵌,\n"
+"    并且希望在函数之内执行该 shell 内嵌的情况下有用处。\n"
+"    \n"
+"    退出状态:\n"
+"    以 SHELL-BUILTIN 内嵌的退出状态为准,或者如果 SHELL-BUILTIN不是一个 shell 内嵌时\n"
+"    为假。."
 
 #: builtins.c:365
 msgid ""
@@ -2386,6 +2455,18 @@ msgid ""
 "    Returns 0 unless the shell is not executing a shell function or EXPR\n"
 "    is invalid."
 msgstr ""
+"返回当前子调用的上下文。\n"
+"    \n"
+"    不带有 EXPR 时,返回 \"$line $filename\"。带有 EXPR 时,返回\n"
+"    \"$line $subroutine $filename\";这个额外的信息可以被用于提供\n"
+"    栈追踪。\n"
+"    \n"
+"    EXPR 的值 显示了到当前调用帧需要回去多少个调用帧;顶部帧\n"
+"    是第 0 帧。\n"
+"    \n"
+"    退出状态:\n"
+"    除非 shell 不在执行一个 shell 函数或者 EXPR 无效,否则返回结\n"
+"    果为0。"
 
 #: builtins.c:383
 msgid ""
@@ -2413,6 +2494,29 @@ msgid ""
 "    Exit Status:\n"
 "    Returns 0 if the directory is changed; non-zero otherwise."
 msgstr ""
+"改变 shell 工作目录。\n"
+"    \n"
+"    改变当前目录至 DIR 目录。默认的 DIR 目录是 shell 变量 HOME\n"
+"    的值。\n"
+"    \n"
+"    变量 CDPATH 定义了含有 DIR 的目录的搜索路径。\n"
+"    CDPATH 变量中不同的目录名称由分号(:)分隔。\n"
+"    一个空的目录名称和当前目录是一样的。如果 DIR 变量由\n"
+"    斜杠 (/) 打头,则 CDPATH 变量不会被使用。\n"
+"    \n"
+"    如果路径找不到,并且 shell 选项`cdable_vars' 被设定,\n"
+"    则参数词被假定为一个变量名。如果该变量有值,则它的值\n"
+"    被当作 DIR 目录。\n"
+"    \n"
+"    选项:\n"
+"        -L\t强制跟随符号链接。\n"
+"        -P\t使用物理目录结构而不跟随符号链接\n"
+"    \n"
+"    \n"
+"    默认情况下跟随符号链接,如同指定 `-L'。\n"
+"    \n"
+"    退出状态:\n"
+"    如果目录改变则返回0,否则非零。"
 
 #: builtins.c:411
 msgid ""
@@ -2429,6 +2533,18 @@ msgid ""
 "    Returns 0 unless an invalid option is given or the current directory\n"
 "    cannot be read."
 msgstr ""
+"打印当前工作目录的名字。\n"
+"    \n"
+"    选项:\n"
+"      -L\t打印 $PWD 变量的值,如果它命名了当前的\n"
+"    \t工作目录\n"
+"      -P\t打印当前的物理路径,不带有任何的符号链接\n"
+"    \n"
+"    默认情况下,`pwd' 的行为和带 `-L' 选项一致\n"
+"    \n"
+"    退出状态:\n"
+"    除非使用了无效选项或者当前目录不可读,否则\n"
+"    返回状态为0。"
 
 #: builtins.c:428
 msgid ""
@@ -2487,6 +2603,20 @@ msgid ""
 "    Exit Status:\n"
 "    Returns exit status of COMMAND, or failure if COMMAND is not found."
 msgstr ""
+"执行一个简单命令或者显示命令的相关信息。\n"
+"    \n"
+"    带 ARGS 参数运行 COMMAND 命令且抑制 shell 函数查询,或显示\n"
+"    指定的 COMMAND 命令的信息。可以在存在相同名称的函数定义的\n"
+"    情况下被用于启动磁盘上的命令。\n"
+"    \n"
+"    选项:\n"
+"      -p\t使用 PATH 变量的一个默认值以确保所有的标准工具\n"
+"    \t都能被找到。\n"
+"      -v\t打印 COMMAND 命令的描述,和 `type' 内嵌相似\n"
+"      -V\t打印每个 COMMAND 命令的详细描述\n"
+"    \n"
+"    退出状态\n"
+"    返回 COMMAND 命令的返回状态,或者当找不到 COMMAND 命令时失败。"
 
 #: builtins.c:476
 msgid ""
@@ -2522,6 +2652,37 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless an invalid option is supplied or an error occurs."
 msgstr ""
+"设定变量值和属性。\n"
+"    \n"
+"    声明变量并且赋予它们属性。如果没用给定名称,\n"
+"    则显示所有变量的属性和值。\n"
+"    \n"
+"    选项:\n"
+"      -f\t限制动作或显示函数名称和定义\n"
+"      -F\t限制仅显示函数名称(以及行号和调试时\n"
+"    \t源文件名)\n"
+"      -p\t显示每个 NAME 变量的属性和值\n"
+"    \n"
+"    设定属性的选项:\n"
+"      -a\t使 NAME 成为下标数组(如果支持)\n"
+"      -A\t使 NAME 成为关联数组 (如果支持)\n"
+"      -i\t使 NAME 带有 `integer' (整数)属性\n"
+"      -l\t将 NAME 在赋值时转为小写\n"
+"      -r\t将 NAME 变为只读\n"
+"      -t\t使 NAME 带有 `trace' (追踪)属性\n"
+"      -u\t将 NAME 在赋值时转为大写\n"
+"      -x\t将 NAME 导出\n"
+"    \n"
+"    用 `+' 代替 `-' 会关闭指定选项。\n"
+"    \n"
+"    带有整数属性的变量在赋值时将使用算术估值(见\n"
+"    `let' 命令)\n"
+"    \n"
+"    在函数中使用时,`declare' 使 NAME 成为本地变量,和 `local'\n"
+"    命令一致。\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功除非使用了无效选项或者发生错误。"
 
 #: builtins.c:512
 msgid ""
@@ -2547,6 +2708,17 @@ msgid ""
 "    Returns success unless an invalid option is supplied, an error occurs,\n"
 "    or the shell is not executing a function."
 msgstr ""
+"定义本地变量。\n"
+"    \n"
+"    创建一个以 NAME 为名称的变量,并且将 VALUE 赋值给它。\n"
+"    OPTION 选项可以是任何能被 `declare' 接受的选项。\n"
+"    \n"
+"    本地变量只能在函数内部被使用,它们只能在定义它们的函数内\n"
+"    部以及子函数中可见。\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功除非使用了无效的选项、发生了错误或者 shell 不在\n"
+"    执行一个函数。"
 
 #: builtins.c:537
 msgid ""
@@ -2578,6 +2750,33 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless a write error occurs."
 msgstr ""
+"将参数写到标准输出。\n"
+"    \n"
+"    在标准输出上显示 ARG 参数后跟一个换行。\n"
+"    \n"
+"    选项:\n"
+"      -n\t不要追加换行\n"
+"      -e\t启用下列反斜杠转义的解释\n"
+"      -E\t显式地抑制对于反斜杠转义的解释\n"
+"    \n"
+"    `echo' 对下列反斜杠字符进行转义:\n"
+"      \\a\t警告(响铃)\n"
+"      \\b\t退格\n"
+"      \\c\t抑制更多的输出\n"
+"      \\e\t转义字符\n"
+"      \\f\t格式提供\n"
+"      \\n\t换行\n"
+"      \\r\t回车\n"
+"      \\t\t横向制表符\n"
+"      \\v\t纵向制表符\n"
+"      \\\\\t反斜杠\n"
+"      \\0nnn\t以 NNN (八进制)为 ASCII 码的字符。 NNN 可以是\n"
+"    \t0到3个八进制数字\n"
+"      \\xHH\t以 HH (十六进制)为值的八比特字符。HH可以是\n"
+"    \t一个或两个十六进制数字\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功除非有写错误发生。"
 
 #: builtins.c:571
 msgid ""
@@ -2591,6 +2790,15 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless a write error occurs."
 msgstr ""
+"写参数至标准输出设备。\n"
+"    \n"
+"    在标准输出设备上显示 ARGs 参数和一个换行。\n"
+"    \n"
+"    选项:\n"
+"      -n\t不附加换行\n"
+"    \n"
+"    退出状态:\n"
+"    除非写错误发生,否则返回成功。"
 
 #: builtins.c:586
 msgid ""
@@ -2618,6 +2826,29 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless NAME is not a shell builtin or an error occurs."
 msgstr ""
+"启用和禁用 shell 内嵌。\n"
+"    \n"
+"    启用和禁用 shell 的内嵌命令。禁用使您能够执行一个和内嵌\n"
+"    命令同名的磁盘上的命令,而无须使用完整的路径名。\n"
+"    \n"
+"    \n"
+"    选项:\n"
+"      -a\t打印一个内嵌的列表,并显示其中每一个是否启用\n"
+"      -n\t禁用每一个 NAME 内嵌或者显示一个被禁用的内嵌的列表\n"
+"      -p\t以可重用的格式打印一个内嵌的列表\n"
+"      -s\t仅打印Posix `special'  内嵌的名称\n"
+"    \n"
+"    控制动态加载的选项:\n"
+"      -f\t从共享对象 FILENAME 文件中加载 NAME 内嵌\n"
+"      -d\t删除以 -f 选项加载的内嵌\n"
+"    \n"
+"    不带选项时,每一个 NAME 内嵌都被启用。\n"
+"    \n"
+"    如果要使用 $PATH 中找到的 `test' 而不是 shell 内嵌的版本,\n"
+"    输入 `enable -n test'。\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功,除非 NAME 不是一个 shell 内嵌或者有错误发生。"
 
 #: builtins.c:614
 msgid ""
@@ -2629,6 +2860,13 @@ msgid ""
 "    Exit Status:\n"
 "    Returns exit status of command or success if command is null."
 msgstr ""
+"将参数作为 shell 命令执行。\n"
+"    \n"
+"    将 ARGs 合成一个字符串,用结果作为 shell 的输入,\n"
+"    并且执行得到的命令。\n"
+"    \n"
+"    退出状态:\n"
+"    以命令的状态退出,或者在命令为空的情况下返回成功。"
 
 #: builtins.c:626
 msgid ""
@@ -2670,6 +2908,43 @@ msgid ""
 "    Returns success if an option is found; fails if the end of options is\n"
 "    encountered or an error occurs."
 msgstr ""
+"解析选项参数。\n"
+"    \n"
+"    Getopts 被 shell 过程用于解析可定位的参数作为选项。\n"
+"    \n"
+"    \n"
+"    OPTSTRING 字符串包含待识别的选项字母;如果一个字母后面跟\n"
+"    着分号,则该选项期待一个参数,而该参数应用空格与选项分开。\n"
+"    \n"
+"    \n"
+"    每次启动时,getopts 会将下一个选项放到 shell 变量 $name\n"
+"    中,如果 name 变量不存在则先将其初始化,而下一个待处\n"
+"    理的参数序号放入 shell 变量 OPTIND 中。OPTIND 变量在每\n"
+"    次 shell 或者 shell 脚本启动时都被初始化为1。当一个选项要\n"
+"    求有一个参数时,getopts 将参数放入 shell 变量 OPTARG\n"
+"    中。\n"
+"    \n"
+"    getopts 有两种报告错误的方法。如果 OPTSTRING 变量的第\n"
+"    一个字符是冒号,getopts 使用沉默错误报告。在这种模式\n"
+"    下,不会打印错误消息。如果看到了一个无效的选项,\n"
+"    getopts 将找到的选项字符放至 OPTARG 变量中。如果一个必\n"
+"    须的选项没有找到,getopts 放一个 ':' 到 NAME 变量中并且设\n"
+"    置 OPTARG 变量为找到的选项字符。如果 getopts 不在沉默模\n"
+"    式中,并且遇到了一个无效的选项,getopts 放置一个 '?' 到 NAME \n"
+"    变量中并且反设定 OPTARG变量。如果必须的选项没有找到,\n"
+"    一个'?'会被放入 NAME变量中,OPTARG 将被反设定,并且会\n"
+"    打印一个诊断信息。\n"
+"    \n"
+"    如果 shell 变量 OPTERR 的值为0,getopts 禁用\n"
+"    错误信息的打印,即使 OPTSTRING 变量的第一个字符不是一\n"
+"    个冒号。OPTERR 的默认值为1.\n"
+"    \n"
+"    Getopts 通常解析可定位的参数($0 - $9),不过如果提供了\n"
+"    更多的参数,它们反而会被解析。\n"
+"    \n"
+"    退出状态:\n"
+"    如果一个选项被找到则返回成功;如果遇到了选项的结尾或者\n"
+"    有错误发生则返回失败。"
 
 #: builtins.c:668
 msgid ""
@@ -2690,6 +2965,22 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless COMMAND is not found or a redirection error occurs."
 msgstr ""
+"使用指定命令替换 shell。\n"
+"    \n"
+"    执行 COMMAND 命令,以指定的程序替换这个 shell。\n"
+"    ARGUMENTS 参数成为 COMMAND命令的参数。如果\n"
+"    没有指定COMMAND 命令,则任何的重定向在当前 shell 中生效。\n"
+"    \n"
+"    选项:\n"
+"      -a 名称\t作为第0个参数传递给 COMMAND 命令\n"
+"      -c\t\t在一个空环境中执行 COMMAND 命令\n"
+"      -l\t\t在COMMAND 命令的第0个参数中加一个短线\n"
+"    \n"
+"    如果命令不能被执行,则退出一个非交互式的 shell,除非\n"
+"   shell 选项`execfail' 已经设定。\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功除非 COMMAND 命令没有找到或者出现一个重定向错误。"
 
 #: builtins.c:689
 msgid ""
@@ -2741,6 +3032,29 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success or status of executed command; non-zero if an error occurs."
 msgstr ""
+"从历史列表中显示或者执行命令。\n"
+"    \n"
+"    fc 被用于从历史列表中列出或者重新编辑并执行命令。\n"
+"    FIRST 和 LAST 变量可以是数字用于指定范围,或者 FIRST 可以是\n"
+"    字符串,意味着以这个字符串打头的最近的一个命令。\n"
+"    \n"
+"    \n"
+"    选项:\n"
+"      -e ENAME\t选择使用哪个编辑器。默认的是 FCEDIT, 然后是 EDITOR,\n"
+"    \t\t然后是 vi\n"
+"      -l \t列出行而不编辑\n"
+"      -n\t列举时省略行号\n"
+"      -r\t反转行的顺序(最新行在前)\n"
+"    \n"
+"    用 `fc -s [模式=替换 ...] [命令]' 的格式,COMMAND 命令会在 OLD=NEW\n"
+"    替换之后被重新执行。\n"
+"    \n"
+"    r='fc -s' 是一个有用的别名,这样的话输入 `r cc'会执行最后一个以 `cc'\n"
+"    开头的命令,输入 `r'会重新执行最后一个命令。\n"
+"    \n"
+"    \n"
+"    退出状态:\n"
+"    返回成功,或者执行的命令的状态;如果错误发生则返回非零。"
 
 #: builtins.c:738
 msgid ""
@@ -2753,6 +3067,14 @@ msgid ""
 "    Exit Status:\n"
 "    Status of command placed in foreground, or failure if an error occurs."
 msgstr ""
+"将任务移至前台。\n"
+"    \n"
+"    将以 JOB_SPEC 标识的任务放至前台,使其成为\n"
+"    当前任务。如果 JOB_SPEC 不存在,shell 观念中的当前任务 \n"
+"    将被使用。\n"
+"    \n"
+"    退出状态:\n"
+"    放至前台的命令状态,或者当错误发生时为失败。"
 
 #: builtins.c:753
 msgid ""
@@ -2765,6 +3087,14 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless job control is not enabled or an error occurs."
 msgstr ""
+"移动任务至后台。\n"
+"    \n"
+"    将 JOB_SPEC 标识的任务放至后台,就像它们\n"
+"    是带 `&' 启动的一样。如果 JOB_SPEC 不存在,shell 观念中的\n"
+"    当前任务将会被使用。\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功除非任务管理没有启用或者错误发生。"
 
 #: builtins.c:767
 msgid ""
@@ -2788,6 +3118,25 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless NAME is not found or an invalid option is given."
 msgstr ""
+"记住或显示程序位置。\n"
+"    \n"
+"    确定并记住每一个给定 NAME 名称的命令的完整路径。如果\n"
+"    不提供参数,则显示已经记住的命令的信息。\n"
+"    \n"
+"    选项:\n"
+"      -d\t\t忘记每一个已经记住的命令的位置\n"
+"      -l\t\t以可作为输入重用的格式显示\n"
+"      -p pathname\t使用 pathname 路径作为 NAME 命令的全路径\n"
+"      -r\t\t忘记所有记住的位置\n"
+"      -t\t\t打印记住的每一个 NAME 名称的位置,如果指定了多个\n"
+"    \t\tNAME 名称,则每个位置前面会加上相应的 NAME 名称\n"
+"    \t\t\n"
+"    参数:\n"
+"      NAME\t\t每个 NAME 名称会在 $PATH 路径变量中被搜索,并\n"
+"    \t\t且添加到记住的命令的列表中。\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功,除非 NAME 命令没有找到或者使用了无效的选项。"
 
 #: builtins.c:792
 msgid ""
@@ -2809,6 +3158,23 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless PATTERN is not found or an invalid option is given."
 msgstr ""
+"显示内嵌命令的相关信息。\n"
+"    \n"
+"    显示内嵌命令的简略信息。如果指定了 PATTERN 模式,\n"
+"    给出所有匹配 PATTERN 模式的命令的详细帮助,否则打\n"
+"    印一个帮助主题列表\n"
+"    \n"
+"    选项:\n"
+"      -d\t输出每个主题的简短描述\n"
+"      -m\t以伪 man 手册的格式显示使用方法\n"
+"      -s\t为每一个匹配 PATTERN 模式的主题仅显示一个用法\n"
+"    \t简介\n"
+"    \n"
+"    参数:\n"
+"      PATTERN\tPattern 模式指定一个帮助主题\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功,除非 PATTERN 模式没有找到或者使用了无效选项。"
 
 #: builtins.c:816
 msgid ""
@@ -2842,6 +3208,35 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless an invalid option is given or an error occurs."
 msgstr ""
+"显示或操纵历史列表。\n"
+"    \n"
+"    带行号显示历史列表,将每个被修改的条目加上前缀 `*'。\n"
+"    参数 N 会仅列出最后的 N 个条目。\n"
+"    \n"
+"    选项:\n"
+"      -c\t删除所有条目从而清空历史列表。\n"
+"      -d 偏移量\t从指定位置删除历史列表。\n"
+"    \n"
+"      -a\t将当前绘画的历史行追加到历史文件中\n"
+"      -n\t从历史文件中读取所有未被读取的行\n"
+"      -r\t读取历史文件并将内容追加到历史列表中\n"
+"    \t中\n"
+"      -w\t将当前历史写入到历史文件中\n"
+"    \t并追加到历史列表中\n"
+"    \n"
+"      -p\t对每一个 ARG 参数展开历史并显示结果\n"
+"    \t而不存储到历史列表中\n"
+"      -s\t以单条记录追加 ARG 到历史列表中\n"
+"    \n"
+"    如果给定了 FILENAME 文件名,则它将被作为历史文件。否则\n"
+"    如果 $HISTFILE 变量有值的话使用之,不然使用 ~/.bash_history 文件。\n"
+"    \n"
+"    如果 $HISTTIMEFORMAT 变量被设定并且不为空,它的值会被用于\n"
+"    strftime(3) 的格式字符串来打印与每一个显示的历史条目想关联的时\n"
+"    间戳,否则不打印时间戳。\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功,除非使用了无效的选项或者发生错误。"
 
 #: builtins.c:852
 msgid ""
@@ -2866,6 +3261,24 @@ msgid ""
 "    Returns success unless an invalid option is given or an error occurs.\n"
 "    If -x is used, returns the exit status of COMMAND."
 msgstr ""
+"显示任务状态。\n"
+"    \n"
+"    列出活动的任务。JPBSPEC 限制仅输出指定的任务。\n"
+"    不带选项时,所有活动任务的状态都会显示。\n"
+"    \n"
+"    选项:\n"
+"      -l\t在正常信息基础上列出进程号\n"
+"      -n\t列出上次通告之后改变了状态的进程\n"
+"      -p\t仅列出进程号\n"
+"      -r\t限制仅输出运行中的任务\n"
+"      -s\t限制仅输出停止的任务\n"
+"    \n"
+"    如果使用了 -x 选项,ARG 参数中的所有任务申明会被替换为该任务\n"
+"    的进程组头领的进程号,然后执行 COMMAND 命令。\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功,除非使用了无效的选项或者有错误发生。\n"
+"    如果使用 -x选项,则返回 COMMAND 命令的退出状态。"
 
 #: builtins.c:879
 msgid ""
@@ -2883,6 +3296,19 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless an invalid option or JOBSPEC is given."
 msgstr ""
+"从当前 shell 中删除任务。\n"
+"    \n"
+"    从活动任务表中删除每一个 JOBSPEC 参数。不带任何\n"
+"    JOBSPEC 参数时,shell 使用观念中的当前任务。\n"
+"    \n"
+"    选项:\n"
+"      -a\t如果不提供 JOBSPEC 参数,则删除所有任务\n"
+"      -h\t标识每个 JOBSPEC 任务,从而当 shell 接收到 SIGHUP\n"
+"    \t信号时不发送 SIGHUP 给指定任务\n"
+"      -r\t仅删除运行中的任务\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功除非使用了无效的选项或者 JOBSPEC 申明。"
 
 #: builtins.c:898
 msgid ""
@@ -2949,6 +3375,46 @@ msgid ""
 "    Exit Status:\n"
 "    If the last ARG evaluates to 0, let returns 1; let returns 0 otherwise.."
 msgstr ""
+"估值算数表达式。\n"
+"    \n"
+"    将每个 ARG 参赛作为算数表达式来估值。估值的计算以等宽的整\n"
+"    数完成,不带溢出检测,不过除 0 是被置陷阱的并且会报一个错\n"
+"    误。下列操作符被按照相同的算数优先级组合。列表的顺序按照\n"
+"    优先级从高至低。\n"
+"    \n"
+"    \n"
+"    \tid++, id--\t变量后置加,后置减\n"
+"    \t++id, --id\t变量前置加,前置减\n"
+"    \t-, +\t\t一元减法,一元加法\n"
+"    \t!, ~\t\t逻辑和比特取反\n"
+"    \t**\t\t指数\n"
+"    \t*, /, %\t\t乘法,除法,取余数\n"
+"    \t+, -\t\t增加,减少\n"
+"    \t<<, >>\t\t向左和向右比特移位\n"
+"    \t<=, >=, <, >\t比较\n"
+"    \t==, !=\t\t等于,不等于\n"
+"    \t&\t\t比特与\n"
+"    \t^\t\t比特异或\n"
+"    \t|\t\t比特或\n"
+"    \t&&\t\t逻辑与\n"
+"    \t||\t\t逻辑或\n"
+"    \texpr ? expr : expr\n"
+"    \t\t\t条件操作符\n"
+"    \t=, *=, /=, %=,\n"
+"    \t+=, -=, <<=, >>=,\n"
+"    \t&=, ^=, |=\t赋值\n"
+"    \n"
+"    Shell 变量允许作为操作数。表达式中的变量的名称会被取代\n"
+"    以值(强制转换为等宽的整数)。表达式中的变量不需要打开\n"
+"    整数属性。\n"
+"    \n"
+"    \n"
+"    操作符按照优先级进行估值。括号中的子表达式将被先估值,\n"
+"    并有可能重载上述的表达式规则。\n"
+"    \n"
+"    \n"
+"    退出状态:\n"
+"    如果最后一个 ARG 参数估值为0,则 let 返回1; 否则 let 返回0。"
 
 #: builtins.c:966
 msgid ""
@@ -3003,6 +3469,14 @@ msgid ""
 "    Exit Status:\n"
 "    Returns N, or failure if the shell is not executing a function or script."
 msgstr ""
+"从一个 shell 函数返回。\n"
+"    \n"
+"    使一个函数或者被引用的脚本以指定的返回值 N 退出。\n"
+"    如果 N 被省略,则返回状态就是\n"
+"    函数或脚本中的最后一个执行的命令的状态。\n"
+"    \n"
+"    退出状态:\n"
+"    返回 N,或者如果 shell 不在执行一个函数或引用脚本时,失败。"
 
 #: builtins.c:1022
 msgid ""
@@ -3434,6 +3908,15 @@ msgid ""
 "    Returns the status of ID; fails if ID is invalid or an invalid option is\n"
 "    given."
 msgstr ""
+"等待进程完成并且返回退出状态。\n"
+"    \n"
+"    等待指定进程并报告它的终止状态。如果\n"
+"    没有提供PID,则当前所有的活跃子进程都会被等待,\n"
+"    并且返回码为零。PID必须为进程号。\n"
+"    \n"
+"    退出状态:\n"
+"    返回进程ID的状态;如果ID是无效的进程号或者指定了无效\n"
+"    的选项则失败。"
 
 #: builtins.c:1481
 msgid ""
@@ -3500,6 +3983,18 @@ msgid ""
 "    Exit Status:\n"
 "    The return status is the return status of PIPELINE."
 msgstr ""
+"报告管道执行的消耗时间。\n"
+"    \n"
+"    执行 PIPELINE 并且打印 PIPELINE 终结时实际时间、用户 CPU 时间和系统\n"
+"    CPU 时间的总结。\n"
+"    \n"
+"    选项:\n"
+"      -p\t用可迁移的 POSIX 格式打印用时总结。\n"
+"    \n"
+"    TIMEFORMAT 变量的值被作为输出格式。\n"
+"    \n"
+"    退出状态:\n"
+"    返回状态即PIPELINE 的返回状态。"
 
 #: builtins.c:1551
 msgid ""
@@ -3511,6 +4006,13 @@ msgid ""
 "    Exit Status:\n"
 "    Returns the status of the last command executed."
 msgstr ""
+"基于模式匹配来执行命令。\n"
+"    \n"
+"    基于 PATTERN 模式匹配的词 WORD,有选择的执行 COMMANDS 命令。\n"
+"    `|' 用于分隔多个模式。\n"
+"    \n"
+"    退出状态:\n"
+"    返回最后一个执行的命令的状态。"
 
 #: builtins.c:1563
 msgid ""
@@ -3538,6 +4040,13 @@ msgid ""
 "    Exit Status:\n"
 "    Returns the status of the last command executed."
 msgstr ""
+"只要测试成功即执行命令。\n"
+"    \n"
+"    只要在 `while' COMMANDS 中的最终命令返回结果为0,则\n"
+"    展开并执行 COMMANDS 命令。\n"
+"    \n"
+"    退出状态:\n"
+"    返回最后一个执行的命令的状态。"
 
 #: builtins.c:1592
 msgid ""
@@ -3549,6 +4058,13 @@ msgid ""
 "    Exit Status:\n"
 "    Returns the status of the last command executed."
 msgstr ""
+"当测试不同过时执行命令。\n"
+"    \n"
+"    `until' COMMANDS 命令的最终命令返回状态不为 0 时,\n"
+"    展开并执行 COMMANDS 命令。\n"
+"    \n"
+"    退出状态:\n"
+"    返回最后一个执行的命令的状态。"
 
 #: builtins.c:1604
 msgid ""
@@ -3562,6 +4078,15 @@ msgid ""
 "    Exit Status:\n"
 "    Returns the exit status of COMMAND."
 msgstr ""
+"创建一个以 NAME 为名的副进程。\n"
+"    \n"
+"    异步执行 COMMANDS 命令,在执行 shell 中的数组变量 NAME\n"
+"    的 0 号和 1 号元素作为文件描述符,以一个管道连接命令\n"
+"    分别作为命令的标准输出和输入设备。\n"
+"    默认的 NAME 是 \"COPROC\"。\n"
+"    \n"
+"    退出状态:\n"
+"    返回 COMMAND 命令的退出状态。"
 
 #: builtins.c:1618
 msgid ""
@@ -3575,6 +4100,15 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless NAME is readonly."
 msgstr ""
+"定义 shell 函数。\n"
+"    \n"
+"    创建一个以 NAME 为名的 shell 函数。当作为一个简单的命令启用时,\n"
+"    NAME 函数执行调用 shell 的上下文中的 COMMANDs 命令。当 NAME\n"
+"    被启用时,参数作为 $1...$n 被传递给函数,函数的名字储存在变量\n"
+"    $FUNCNAME 中。\n"
+"    \n"
+"    退出状态:\n"
+"    返回成功除非 NAME 为只读。"
 
 #: builtins.c:1632
 msgid ""
@@ -3586,6 +4120,13 @@ msgid ""
 "    Exit Status:\n"
 "    Returns the status of the last command executed."
 msgstr ""
+"将命令组合为一个单元。\n"
+"    \n"
+"    运行组中的命令集合。这是对整个命令集合\n"
+"    做重定向的方法之一。\n"
+"    \n"
+"    退出状态:\n"
+"    返回最后一个执行的命令的状态。"
 
 #: builtins.c:1644
 msgid ""
@@ -3600,6 +4141,16 @@ msgid ""
 "    Exit Status:\n"
 "    Returns the status of the resumed job."
 msgstr ""
+"在前台继续任务\n"
+"    \n"
+"    对于 JOB_SPEC 参数来说和 `fg' 命令等同。继续一个\n"
+"    停止的或者后台任务。JOB_SPEC 可以指定一个任务\n"
+"    名字或任务号。在 JOB_SPEC 后加上一个 `&' 将会把\n"
+"    任务放至后台,就像任务声明被作为 `bg' 命令的参数\n"
+"    执行一样。\n"
+"    \n"
+"    退出状态:\n"
+"    返回被继续的任务的状态。"
 
 #: builtins.c:1659
 msgid ""
@@ -3802,6 +4353,22 @@ msgid ""
 "    Returns success if OPTNAME is enabled; fails if an invalid option is\n"
 "    given or OPTNAME is disabled."
 msgstr ""
+"设定和反设定 shell 选项。\n"
+"    \n"
+"    改变每个 shell 选项 OPTNAME的设定。不带参数\n"
+"    时,列出所有 shell 选项并标注每一个选项是否被\n"
+"    设定。\n"
+"    \n"
+"    选项:\n"
+"      -o\t限制 OPTNAME 为定义用于`set -o' 的选项\n"
+"      -p\t打印每个 shell 选项并标注它的状态。\n"
+"      -q\t抑制输出\n"
+"      -s\t启用(设定)每个 OPTNAME 选项\n"
+"      -u\t禁用(反设定)每个 OPTNAME 选项\n"
+"    \n"
+"    退出状态:\n"
+"    如果 OPTNAME 选项被启用则返回成功;如果是\n"
+"    无效的选项或OPTNAME 被禁用则失败。"
 
 #: builtins.c:1868
 msgid ""
@@ -3864,6 +4431,14 @@ msgid ""
 "    Exit Status:\n"
 "    Returns success unless an invalid option is supplied or an error occurs."
 msgstr ""
+"依据选项显示可能的补完。\n"
+"    \n"
+"    意图在能产生可能的补完的 shell 函数内部使用。\n"
+"    如果提供了可选的 WORD 参数,则产生按照 WORD\n"
+"    进行的匹配。\n"
+"    \n"
+"    退出状态:\n"
+"    除非使用了无效选项或者错误发生,否则返回成功。"
 
 #: builtins.c:1938
 msgid ""
diff --git a/subst.c b/subst.c
index bad3d3d9da4104efa62e0fcb0ada63c5a39912cd..e6cb89dd4e6260777a1779639732c2109574df00 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -1479,10 +1479,16 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
 
       /* This logic must agree with parse.y:parse_matched_pair, since they
         share the same defines. */
-      if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 0)
+      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)
@@ -3759,6 +3765,7 @@ mb_getcharlens (string, len)
 #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;
@@ -3827,10 +3834,11 @@ remove_upattern (param, pattern, op)
        break;
     }
 
-  return (savestring (param)); /* no match, return original string */
+  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;
@@ -3896,7 +3904,7 @@ remove_wpattern (wparam, wstrlen, wpattern, op)
        break;
     }
 
-  return (wcsdup (wparam));    /* no match, return original string */
+  return (wparam);     /* no match, return original string */
 }
 #endif /* HANDLE_MULTIBYTE */
 
@@ -3905,6 +3913,8 @@ 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 */
@@ -3917,18 +3927,29 @@ remove_pattern (param, pattern, op)
       size_t n;
       wchar_t *wparam, *wpattern;
       mbstate_t ps;
-      char *xret;
 
       n = xdupmbstowcs (&wpattern, NULL, pattern);
       if (n == (size_t)-1)
-       return (remove_upattern (param, pattern, op));
+       {
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
+       }
       n = xdupmbstowcs (&wparam, NULL, param);
       if (n == (size_t)-1)
        {
          free (wpattern);
-         return (remove_upattern (param, pattern, op));
+         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);
@@ -3943,7 +3964,10 @@ remove_pattern (param, pattern, op)
     }
   else
 #endif
-    return (remove_upattern (param, pattern, op));
+    {
+      xret = remove_upattern (param, pattern, op);
+      return ((xret == param) ? savestring (param) : xret);
+    }
 }
 
 /* Return 1 of the first character of STRING could match the first
@@ -7155,6 +7179,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
       temp1 = parameter_brace_remove_pattern (name, temp, value, c, quoted);
       free (temp);
       free (value);
+      free (name);
 
       ret = alloc_word_desc ();
       ret->word = temp1;
index b9945fe7bcd49e58a4f50cbb643d2e28a0144ec3..626ec80f86de4ef06a0caab70b22e768126baa62 100644 (file)
--- a/subst.c~
+++ b/subst.c~
@@ -1462,7 +1462,7 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
       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)
+         if (posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE)
            ADVANCE_CHAR (string, slen, i);
          else
            {
@@ -1479,10 +1479,16 @@ extract_dollar_brace_string (string, sindex, quoted, flags)
 
       /* This logic must agree with parse.y:parse_matched_pair, since they
         share the same defines. */
-      if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 0)
+      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)
@@ -3759,6 +3765,7 @@ mb_getcharlens (string, len)
 #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;
@@ -3827,10 +3834,11 @@ remove_upattern (param, pattern, op)
        break;
     }
 
-  return (savestring (param)); /* no match, return original string */
+  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;
@@ -3896,7 +3904,7 @@ remove_wpattern (wparam, wstrlen, wpattern, op)
        break;
     }
 
-  return (wcsdup (wparam));    /* no match, return original string */
+  return (wparam);     /* no match, return original string */
 }
 #endif /* HANDLE_MULTIBYTE */
 
@@ -3905,6 +3913,8 @@ 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 */
@@ -3917,18 +3927,27 @@ remove_pattern (param, pattern, op)
       size_t n;
       wchar_t *wparam, *wpattern;
       mbstate_t ps;
-      char *xret;
 
       n = xdupmbstowcs (&wpattern, NULL, pattern);
       if (n == (size_t)-1)
-       return (remove_upattern (param, pattern, op));
+       {
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
+       }
       n = xdupmbstowcs (&wparam, NULL, param);
       if (n == (size_t)-1)
        {
          free (wpattern);
-         return (remove_upattern (param, pattern, op));
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
        }
       oret = ret = remove_wpattern (wparam, n, wpattern, op);
+      if (ret == wparam)
+        {
+          free (wparam);
+          free (wpattern);
+          return (savestring (param));
+        }
 
       free (wparam);
       free (wpattern);
@@ -3943,7 +3962,10 @@ remove_pattern (param, pattern, op)
     }
   else
 #endif
-    return (remove_upattern (param, pattern, op));
+    {
+      xret = remove_upattern (param, pattern, op);
+      return ((xret == param) ? savestring (param) : xret);
+    }
 }
 
 /* Return 1 of the first character of STRING could match the first
@@ -7155,6 +7177,7 @@ parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, conta
       temp1 = parameter_brace_remove_pattern (name, temp, value, c, quoted);
       free (temp);
       free (value);
+      free (name);
 
       ret = alloc_word_desc ();
       ret->word = temp1;
index 30e10ca94c96b2b437ec245d0cd4476eb2a66372..38f196a317f57052bb44fb95f322bfbec250fcd8 100644 (file)
@@ -138,4 +138,7 @@ a
 
 
 b
-./builtins.tests: line 251: exit: status: numeric argument required
+before: f = 4
+inside
+after: f = 8 bar = 4
+./builtins.tests: line 254: exit: status: numeric argument required
index 6d20ae92a38a2e09dca822cad368d8eb45b42aaf..7ce378e989327437a0435af8e3a75d40c31d9913 100644 (file)
@@ -247,6 +247,9 @@ kill -l ${sigone/SIG/}
 # test behavior of shopt xpg_echo
 ${THIS_SH} ./builtins2.sub
 
+# test behavior of declare -g
+${THIS_SH} ./builtins3.sub
+
 # this must be last -- it is a fatal error
 exit status
 
diff --git a/tests/builtins.tests~ b/tests/builtins.tests~
new file mode 100644 (file)
index 0000000..6d20ae9
--- /dev/null
@@ -0,0 +1,253 @@
+# tests for miscellaneous builtins not tested elsewhere
+set +p
+set +o posix
+
+ulimit -c 0 2>/dev/null
+
+# check that break breaks loops
+for i in a b c; do echo $i; break; echo bad-$i; done
+echo end-1
+for i in a b c; do echo $i; break 1; echo bad-$i; done
+echo end-2
+for i in a b c; do
+       for j in x y z; do
+               echo $i:$j
+               break
+               echo bad-$i
+       done
+       echo end-$i
+done
+echo end-3
+
+# check that break breaks nested loops
+for i in a b c; do
+       for j in x y z; do
+               echo $i:$j
+               break 2
+               echo bad-$i
+       done
+       echo end-$i
+done
+echo end
+
+# check that continue continues loops
+for i in a b c; do echo $i; continue; echo bad-$i ; done
+echo end-1
+for i in a b c; do echo $i; continue 1; echo bad-$i; done
+echo end-2
+for i in a b c; do
+       for j in x y z; do
+               echo $i:$j
+               continue
+               echo bad-$i-$j
+       done
+       echo end-$i
+done
+echo end-3
+
+# check that continue breaks out of nested loops
+for i in a b c; do
+       for j in x y z; do
+               echo $i:$j
+               continue 2
+               echo bad-$i-$j
+       done
+       echo end-$i
+done
+echo end
+
+# check that `eval' re-evaluates arguments, but `builtin' and `command' do not
+AVAR='$BVAR'
+BVAR=foo
+
+echo $AVAR
+builtin echo $AVAR
+command echo $AVAR
+eval echo \$AVAR
+eval echo $AVAR
+
+# test out eval with a temp environment
+AVAR=bar eval echo \$AVAR
+BVAR=xxx eval echo $AVAR
+
+unset -v AVAR BVAR
+
+# test umask
+mask=$(umask)
+umask 022
+umask
+umask -S
+umask -S u=rwx,g=rwx,o=rx >/dev/null # 002
+umask
+umask -S
+umask -p
+umask -p -S
+umask 0
+umask -S
+umask ${mask}  # restore original mask
+
+# builtin/command without arguments should do nothing.  maybe someday they will
+builtin
+command
+
+# test enable
+enable -ps
+
+enable -aps ; enable -nps
+
+enable -n test
+case "$(type -t test)" in
+builtin)       echo oops -- enable -n test failed ;;
+*)     echo enable -n test worked ;;
+esac
+
+enable test
+case "$(type -t test)" in
+builtin)       echo enable test worked ;;
+*)     echo oops -- enable test failed ;;
+esac
+
+# test options to exec
+(exec -a specialname ${THIS_SH} -c 'echo $0' )
+(exec -l -a specialname ${THIS_SH} -c 'echo $0' )
+# test `clean' environment.  if /bin/sh is bash, and the script version of
+# printenv is run, there will be variables in the environment that bash
+# sets on startup.  Also test code that prefixes argv[0] with a dash.
+(export FOO=BAR ; exec -c -l printenv ) | grep FOO
+(FOO=BAR exec -c printenv ) | grep FOO
+
+(export FOO=BAR ; exec printenv ) | grep FOO
+(FOO=BAR exec printenv ) | grep FOO
+
+# ok, forget everything about hashed commands
+hash -r
+hash
+
+# this had better succeed, since command -p guarantees we will find the
+# standard utilties
+command -p hash rm
+
+# check out source/.
+
+# sourcing a zero-length-file had better not be an error
+rm -f /tmp/zero-length-file
+cp /dev/null /tmp/zero-length-file
+. /tmp/zero-length-file
+echo $?
+rm /tmp/zero-length-file
+
+AVAR=AVAR
+
+. ./source1.sub
+AVAR=foo . ./source1.sub
+
+. ./source2.sub
+echo $?
+
+set -- a b c
+. ./source3.sub
+
+# make sure source with arguments does not change the shell's positional
+# parameters, but that the sourced file sees the arguments as its
+# positional parameters
+echo "$@"
+. ./source3.sub x y z
+echo "$@"
+
+# but if the sourced script sets the positional parameters explicitly, they
+# should be reflected in the calling shell's positional parameters.  this
+# also tests one of the shopt options that controls source using $PATH to
+# find the script
+echo "$@"
+shopt -u sourcepath
+. source4.sub
+echo "$@"
+
+# this is complicated when the sourced scripts gets its own positional
+# parameters from arguments to `.'
+set -- a b c
+echo "$@"
+. source4.sub x y z
+echo "$@"
+
+# test out cd and $CDPATH
+${THIS_SH} ./builtins1.sub
+
+# test behavior of `.' when given a non-existant file argument
+${THIS_SH} ./source5.sub
+
+# test bugs in sourcing non-regular files, fixed post-bash-3.2
+${THIS_SH} ./source6.sub
+
+# in posix mode, assignment statements preceding special builtins are
+# reflected in the shell environment.  `.' and `eval' need special-case
+# code.
+set -o posix
+echo $AVAR
+AVAR=foo . ./source1.sub
+echo $AVAR
+
+AVAR=AVAR
+echo $AVAR
+AVAR=foo eval echo \$AVAR
+echo $AVAR
+
+AVAR=AVAR
+echo $AVAR
+AVAR=foo :
+echo $AVAR
+set +o posix
+
+# but assignment statements preceding `export' are always reflected in 
+# the environment
+foo="" export foo
+declare -p foo
+unset foo
+
+# assignment statements preceding `declare' should be displayed correctly,
+# but not persist after the command
+FOO='$$' declare -p FOO
+declare -p FOO
+unset FOO
+
+# except for `declare -x', which should be equivalent to `export'
+FOO='$$' declare -x FOO
+declare -p FOO
+unset FOO
+
+# test out kill -l.  bash versions prior to 2.01 did `kill -l num' wrong
+sigone=$(kill -l | sed -n 's:^ 1) *\([^        ]*\)[   ].*$:\1:p')
+
+case "$(kill -l 1)" in
+${sigone/SIG/})        echo ok;;
+*)     echo oops -- kill -l failure;;
+esac
+
+# kill -l and trap -l should display exactly the same output
+sigonea=$(trap -l | sed -n 's:^ 1) *\([^       ]*\)[   ].*$:\1:p')
+
+if [ "$sigone" != "$sigonea" ]; then
+       echo oops -- kill -l and trap -l differ
+fi
+
+# POSIX.2 says that exit statuses > 128 are mapped to signal names by
+# subtracting 128 so you can find out what signal killed a process
+case "$(kill -l $(( 128 + 1)) )" in
+${sigone/SIG/})        echo ok;;
+*)     echo oops -- kill -l 129 failure;;
+esac
+
+# out-of-range signal numbers should report the argument in the error
+# message, not 128 less than the argument
+kill -l 4096
+
+# kill -l NAME should return the signal number
+kill -l ${sigone/SIG/}
+
+# test behavior of shopt xpg_echo
+${THIS_SH} ./builtins2.sub
+
+# this must be last -- it is a fatal error
+exit status
+
+echo after bad exit
diff --git a/tests/builtins3.sub b/tests/builtins3.sub
new file mode 100644 (file)
index 0000000..5596c56
--- /dev/null
@@ -0,0 +1,14 @@
+# declare -g added in bash-4.2
+f=4
+
+foo()
+{
+       declare -g f=8
+       declare -g bar=4
+
+       echo inside
+}
+
+echo before: f = $f
+foo
+echo after: f = $f bar = $bar
index c86c546b7376e3d867fcf0ba725b91ffb561412c..35e40d16f6c7db535f1a9ca43b976881ef3e8162 100644 (file)
@@ -233,7 +233,7 @@ argv[1] = <oneonetwo>
 argv[1] = <onetwo>
 argv[1] = <two>
 argv[1] = <oneonetwo>
-./new-exp.tests: line 421: -2: substring expression < 0
+argv[1] = <a>
 argv[1] = <defghi>
 argv[1] = <efghi>
 argv[1] = <e*docrine>
@@ -395,13 +395,13 @@ argv[6] = <w>
 argv[7] = <x>
 argv[8] = <y>
 argv[9] = <z>
-./new-exp.tests: line 480: $9: unbound variable
-./new-exp.tests: line 481: 9: unbound variable
-./new-exp.tests: line 482: UNSET: unbound variable
-./new-exp.tests: line 483: UNSET: unbound variable
+./new-exp.tests: line 482: $9: unbound variable
+./new-exp.tests: line 483: 9: unbound variable
 ./new-exp.tests: line 484: UNSET: unbound variable
 ./new-exp.tests: line 485: UNSET: unbound variable
 ./new-exp.tests: line 486: UNSET: unbound variable
+./new-exp.tests: line 487: UNSET: unbound variable
+./new-exp.tests: line 488: UNSET: unbound variable
 argv[1] = <5>
 argv[1] = <#>
 argv[1] = <#>
@@ -430,7 +430,7 @@ Case05---3---A:B:C---
 Case06---1---A B C::---
 Case07---3---A:B:C---
 Case08---3---A:B:C---
-./new-exp.tests: line 506: ${$(($#-1))}: bad substitution
+./new-exp.tests: line 508: ${$(($#-1))}: bad substitution
 argv[1] = <a>
 argv[2] = <b>
 argv[3] = <c>
@@ -447,7 +447,7 @@ argv[1] = <a>
 argv[1] = <a>
 argv[2] = <b>
 argv[1] = <>
-./new-exp.tests: line 525: $(($# - 2)): substring expression < 0
+./new-exp.tests: line 527: $(($# - 2)): substring expression < 0
 argv[1] = <bin>
 argv[2] = <bin>
 argv[3] = <ucb>
@@ -539,4 +539,4 @@ bar ()
 }
 argv[1] = </>
 argv[1] = </>
-./new-exp.tests: line 576: ABXD: parameter unset
+./new-exp.tests: line 578: ABXD: parameter unset
index 8c43b661a0bca15d207a6f2365f00b3f90d3692c..6c59364250dcdb1e0312040e631d89c7ceab3205 100644 (file)
@@ -417,8 +417,10 @@ recho $c
 c=${var:4}
 expect nothing
 recho $c
-expect '<./new-exp.tests: -2: substring expression < 0>'
+# as of bash-4.2, negative LENGTH means offset from the end
 c=${var:0:-2}
+expect '<a>'
+recho $c
 
 var=abcdefghi
 c=${var:3:12}
diff --git a/tests/new-exp.tests~ b/tests/new-exp.tests~
new file mode 100644 (file)
index 0000000..cd73019
--- /dev/null
@@ -0,0 +1,577 @@
+# must do this because posix mode causes process substitution to be disabled
+# and flagged as a syntax error, which causes the shell to exit
+set +o posix
+
+expect()
+{
+        echo expect "$@"
+}
+
+HOME=/usr/homes/chet   # to make the check against new-exp.right work
+expect '<foo bar>'
+recho "${undef-"foo bar"}"     # should be foo bar
+expect '<foo>'
+recho "${und="foo"}"           # should be foo
+
+expect "<$HOME>"
+recho ${HOME-"}"}
+expect "<$HOME>"
+recho "${HOME-'}'}"
+expect "<$HOME>"
+recho "${HOME-"}"}"
+
+expect $0: 'HOME: }: syntax error: operand expected (error token is "}")'
+recho "${HOME:`echo }`}"       # should be a math error -- bad substring substitution
+
+expect unset
+_ENV=oops
+x=${_ENV[(_$-=0)+(_=1)-_${-%%*i*}]}
+echo ${x:-unset}
+
+expect "<$HOME>"
+recho ${HOME}
+expect "<$HOME>"
+recho ${HOME:-`echo }`}
+expect "<$HOME>"
+recho ${HOME:-`echo "}"`}
+expect "<$HOME>"
+recho "${HOME:-`echo "}"`}"
+expect "<$HOME>"
+recho "$(echo "${HOME}")"
+expect "<$HOME>"
+recho "$(echo "$(echo ${HOME})")"
+expect "<$HOME>"
+recho "$(echo "$(echo "${HOME}")")"
+
+P=*@*
+expect '<*@>'
+recho "${P%"*"}"       # 
+expect '<*@>'
+recho "${P%'*'}"       # 
+expect '<@*>'
+recho "${P#\*}"                # should be @*
+
+expect '<)>'
+recho "$(echo ")")"    # should be )
+expect '<")">'
+recho "$(echo "\")\"")"        # should be ")"
+
+foo='abcd   '
+expect '<-abcd> <->'
+recho -${foo}-         # should be -abcd -
+expect '<-abcd> <->'
+recho -${foo% *}-      # should be -abcd -
+expect '<-abcd->'
+recho -${foo%% *}-     # should be -abcd-
+
+foo=bar
+expect '<bar foo>'
+echo -n $foo' ' ; echo foo
+
+expect '<bar foo>'
+echo -n $foo" " ; echo foo
+
+expect '<bar foo>'
+echo -n "$foo " ; echo foo
+
+expect '<barfoo>'
+echo -e "$foo\c " ; echo foo
+
+expect '<barfoo>'
+echo -e $foo"\c " ; echo foo
+
+# make sure backslashes are preserved in front of characters that are not
+# valid backslash escapes
+expect '<\x>'
+echo -e '\x'
+
+# substring tests
+z=abcdefghijklmnop
+expect '<abcd>'
+recho ${z:0:4}
+
+expect '<efg> <nop>'
+recho ${z:4:3} ${z:${#z}-3:3}
+
+expect '<efg> <nop>'
+recho ${z:4:3} ${z: -3:3}
+
+expect '<hijklmnop>'
+recho ${z:7:30}
+
+expect '<abcdefghijklmnop>'
+recho ${z:0:100}
+
+expect '<abcdefghijklmnop>'
+recho ${z:0:${#z}}
+
+set 'ab cd' 'ef' 'gh ij' 'kl mn' 'op'
+expect '<ab cd> <ef>'
+recho "${@:1:2}"
+
+expect '<gh ij> <kl mn>'
+recho "${@:3:2}"
+
+expect '<gh ij> <kl mn> <op>'
+recho "${@:3:4}"
+
+expect '<ab cd> <ef> <gh ij> <kl mn> <op>'
+recho "${@:1:$#}"
+
+# code to ad-hoc parse arithmetic expressions in substring expansions was
+# broken until post-2.04
+base=/home/chet/foo//bar
+string1=$base/abcabcabc
+x=1 j=4
+
+expect '</home/chet/foo//bar/abcabcabc>'
+recho ${string1:0}
+
+expect '<home/chet/foo//bar/abcabcabc>'
+recho ${string1:1}
+
+expect '<home>'
+recho ${string1:(j?1:0):j}
+
+expect '<home>'
+recho ${string1:j?1:0:j}
+
+expect '<home>'
+recho ${string1:(j?(x?1:0):0):j}
+
+expect '<home>'
+recho ${string1:j?(x?1:0):0:j}
+
+unset base string1 x j
+
+# indirect variable references
+expect '<abcdefghijklmnop>'
+recho ${!9:-$z}
+
+ef=4
+expect '<4>'
+recho ${!2}
+
+expect '<op>'
+recho ${!#}
+
+set a b c d e
+a=
+expect '<abcdefghijklmnop>'
+recho ${a:-$z}
+expect '<abcdefghijklmnop>'
+recho ${!1:-$z}
+
+expect nothing
+recho ${a-$z}
+expect nothing
+recho ${!1-$z}
+
+set -u
+expect $0: ABX: unbound variable
+( recho ${ABX} )
+set +u
+
+expect $0: '$6: cannot assign in this way'
+recho ${6="arg6"}
+
+v=abcde
+
+# sed-like variable substitution
+expect '<xxcde>'
+recho ${v/a[a-z]/xx}
+expect '<axxde>'
+recho ${v/a??/axx}
+expect '<abxyz>'
+recho ${v/c??/xyz}
+expect '<abbcde>'
+recho ${v/#a/ab}
+expect '<abcde>'
+recho ${v/#d/ab}
+expect '<abcabe>'
+recho ${v/d/ab}
+expect '<abcdlast>'
+recho ${v/%?/last}
+expect '<abcde>'
+recho ${v/%x/last}
+
+av=(abcd efgh ijkl mnop qrst uvwx)
+
+expect '<xxcd>'
+recho ${av/??/xx}
+expect '<abxx>'
+recho ${av/%??/xx}
+expect '<xxgh>'
+recho ${av[1]/??/xx}
+expect '<efgh>'
+recho ${av[1]/%ab/xx}
+expect '<xxfgh>'
+recho ${av[1]/#?/xx}
+expect '<zagh>'
+recho ${av[1]/??/za}
+expect '<zaza>'
+recho ${av[1]//??/za}
+expect '<zagh>'
+recho ${av[1]/#??/za}
+expect '<efza>'
+recho ${av[1]/%??/za}
+
+expect '<yyy> <yyy> <yyy> <yyy> <yyy> <yyy>'
+recho ${av[@]/*/yyy}
+expect '<yyy> <yyy> <yyy> <yyy> <yyy> <yyy>'
+recho ${av[@]/#*/yyy}
+expect '<yyy> <yyy> <yyy> <yyy> <yyy> <yyy>'
+recho ${av[@]/%*/yyy}
+expect '<yyy> <efgh> <ijkl> <mnop> <qrst> <uvwx>'
+recho ${av[@]/a*/yyy}
+expect '<abxx> <efxx> <ijxx> <mnxx> <qrxx> <uvxx>'
+recho ${av[@]/%??/xx}
+
+set abcd efgh ijkl mnop qrst uvwx
+
+expect '<xxcd>'
+recho ${1/??/xx}
+expect '<xxcd> <xxgh> <xxkl> <xxop> <xxst> <xxwx>'
+recho ${@/??/xx}
+expect '<xxcd> <xxgh> <xxkl> <xxop> <xxst> <xxwx>'
+recho ${@/%??/xx}
+expect '<zaza>'
+recho ${3//??/za}
+expect '<efza>'
+recho ${3/%??/za}
+expect '<zaza> <zaza> <zaza> <zaza> <zaza> <zaza>'
+recho ${@//??/za}
+expect '<zacd> <zagh> <zakl> <zaop> <zast> <zawx>'
+recho ${@/#??/za}
+expect '<yyy> <yyy> <yyy> <yyy> <yyy> <yyy>'
+recho ${@//*/yyy}
+expect '<yyy> <efgh> <ijkl> <mnop> <qrst> <uvwx>'
+recho ${@//a*/yyy}
+expect '<abcd> <efgh> <ijkl> <mnop> <qrst> <uvwyyy>'
+recho ${@/%x*/yyy}
+
+expect a newline
+echo $abmcde
+
+# sneaky way to replace a newline in a variable value with something else
+AVAR=$'This\nstring\nhas\nmultiple\nlines.'
+echo "${AVAR}"
+
+eval BVAR=\"\${AVAR//$'\n'/-}\"
+echo "$BVAR"
+
+unset AVAR BVAR
+
+# run process substitution tests in a subshell so that syntax errors
+# caused by a shell not implementing process substitution (e.g., one
+# built on a NeXT) will not cause the whole test to exit prematurely
+${THIS_SH} ./new-exp1.sub
+
+# run the tests of $(<filename) in a subshell to avoid cluttering up
+# this script
+${THIS_SH} ./new-exp2.sub
+
+expect '<6>'
+recho ${#:-foo}
+expect $0: '${#:}: bad substitution'
+echo ${#:}
+
+expect "<'>"
+recho "'"
+expect '<">'
+recho '"'
+expect '<"hello">'
+recho "\"hello\""
+
+shift $#
+unset foo
+z=abcdef
+z1='abc def'
+
+expect '<>'
+recho ${foo:-""}
+expect nothing
+recho ${foo:-"$@"}
+expect '<>'
+recho "${foo:-$@}"
+
+# unset var
+expect '<>'
+recho ${foo:-"$zbcd"}
+expect nothing
+recho ${foo:-$zbcd}
+
+# set var
+expect '<abcdef>'
+recho ${foo:-"$z"}
+expect '<abc def>'
+recho ${foo:-"$z1"}
+
+expect '<abcdef>'
+recho ${foo:-$z}
+expect '<abc> <def>'
+recho ${foo:-$z1}
+
+expect '<abcdef>'
+recho "${foo:-$z}"
+expect '<abc def>'
+recho "${foo:-$z1}"
+
+expect '<abcdef>'
+recho "${foo:-"$z"}"
+# this disagrees with sh and ksh, but I think it is right according
+# to posix.2.
+expect '<abc def>'
+recho "${foo:-"$z1"}"
+
+set ab cd ef gh
+expect '<ab> <cd> <ef> <gh>'
+recho ${foo:-"$@"}
+expect '<ab> <cd> <ef> <gh>'
+recho "${foo:-$@}"
+expect '<ab> <cd> <ef> <gh>'
+recho "${foo:-"$@"}"
+
+shift $#
+expect nothing
+recho $xxx"$@"
+expect nothing
+recho ${foo:-$xxx"$@"}
+expect '<>'
+recho "${foo:-$xxx$@}"
+expect '<>'
+recho "${foo:-$xxx"$@"}"
+
+expect nothing
+recho $xxx"$@"
+expect nothing
+recho "$xxx$@"
+expect nothing
+recho "$@"$xxx
+
+expect '<>'
+recho $xxx""
+expect '<>'
+recho $xxx''
+expect '<>'
+recho ''$xxx
+expect '<>'
+recho ""$xxx
+
+AB='abcdefghijklmnopqrstuvwxyz'
+
+recho ${AB:7:15}
+recho ${AB:15:7}
+
+recho ${AB:20}
+
+recho ${AB:0}
+recho ${AB:0:20}
+
+recho ${AB:10:7}
+recho ${AB:10:3+4}
+recho ${AB:20/2:3+4}
+
+set 1 2 3 4 5 6
+recho \""${*:2:2}"\"
+
+IFS=:
+recho \""${*:2:2}"\"
+
+IFS=$' \t\n'
+
+z=123456
+
+recho \""${z:2:2}"\"
+recho \""${z:2}"\"
+recho \""${z:2:4}"\"
+recho \""${z:2:6}"\"
+
+set $'\1' $'\2' $'\177'
+
+recho $*
+recho $@
+
+recho ${*}
+recho ${@}
+
+xx=one/two/two
+recho ${xx%/*}
+recho ${xx/\/two}
+
+yy=oneonetwo
+recho ${yy//one}
+recho ${yy/\/one}
+
+xx=oneonetwo
+
+recho ${xx/one}
+recho ${xx//one}
+recho ${xx/\/one}
+
+# out-of-range substrings
+var=abc
+c=${var:3}
+expect nothing
+recho $c
+c=${var:4}
+expect nothing
+recho $c
+# as of bash-4.2, negative LENGTH means offset from the end
+expect '<a>'
+c=${var:0:-2}
+
+var=abcdefghi
+c=${var:3:12}
+recho $c
+c=${var:4:20}
+recho $c
+
+# make sure null patterns work
+xxx=endocrine
+yyy=n
+unset zzz
+
+recho ${xxx/$yyy/*}
+recho ${xxx//$yyy/*}
+
+recho ${xxx/$zzz/*}
+recho ${xxx//$zzz/*}
+
+recho ${xxx//%${zzz}/}
+recho ${xxx//%${zzz}}
+recho ${xxx//#${zzz}/}
+recho ${xxx//#${zzz}}
+
+# another case that caused a core dump in bash-2.0
+XPATH=/usr/bin:/bin:/usr/local/bin:/usr/gnu/bin::/usr/bin/X11:/sbin:/usr/sbin
+
+recho ${XPATH//:/ }
+
+xx=(ar as at au av aw ax ay az)
+
+recho ${xx[@]/a/}
+recho ${xx[@]//a/}
+
+recho ${xx[*]/a/}
+recho ${xx[*]//a/}
+
+recho ${xx[@]%?}
+recho ${xx[*]%?}
+
+recho ${xx[@]#?}
+recho ${xx[*]#?}
+
+set -- ar as at au av aw ax ay az
+
+recho ${@/a/}
+recho ${@//a/}
+
+recho ${*/a/}
+recho ${*//a/}
+
+recho ${@%?}
+recho ${*%?}
+
+recho ${@#?}
+recho ${*#?}
+
+shift $#
+set -u
+( recho $9 ; echo after 1)
+( recho ${9} ; echo after 2)
+( recho $UNSET ; echo after 3)
+( recho ${UNSET} ; echo after 4)
+( recho "$UNSET" ; echo after 5)
+( recho "${UNSET}" ; echo after 6)
+( recho "${#UNSET}" ; echo after 7)
+set +u
+
+RECEIVED="12345"
+recho "${RECEIVED:$((${#RECEIVED}-1)):1}"
+RECEIVED="12345#"
+recho "${RECEIVED:$((${#RECEIVED}-1)):1}"
+RECEIVED="#"
+recho "${RECEIVED:$((${#RECEIVED}-1)):1}"
+RECEIVED=""
+recho "${RECEIVED:$((${#RECEIVED}-1)):1}"
+
+# tests of new prefix expansion ${!prefix*}
+${THIS_SH} ./new-exp3.sub
+
+# bug with indirect expansion through bash-2.05b
+${THIS_SH} ./new-exp4.sub
+
+# these caused errors and core dumps in versions before bash-2.04
+c=""
+echo ${c//${$(($#-1))}/x/}
+
+set a b c d e f g
+recho "$@"
+
+set -- ${@:1:$(($# - 2))}
+recho "$@"
+
+set a b
+recho ${@:1:$(($# - 2))}
+
+recho ${@:1:0}
+recho ${@:1:1}
+recho ${@:1:2}
+
+recho "${*:1:0}"
+
+# this is an error -- negative expression
+set a
+recho ${@:1:$(($# - 2))}
+
+XPATH=/bin:/usr/bin:/usr/ucb:/usr/local/bin:.:/sbin:/usr/sbin
+set $( IFS=: ; echo $XPATH ) 
+
+recho ${@##*/}
+recho ${@%%[!/]*}
+
+recho ${@#/*}
+recho ${@%*/}
+
+set /full/path/to/x16 /another/full/path
+
+recho ${1%/*}
+recho ${1%%[!/]*}
+recho ${1#*/}
+recho ${1##*/}
+
+${THIS_SH} ./new-exp5.sub
+
+unset var
+var=blah
+
+# these had better agree
+echo ${var[@]:3}
+echo ${var:3}
+echo ${var[@]/#/--}
+echo ${var/#/--}
+echo ${var[@]##?}
+echo ${var##?}
+
+unset var
+var=(abcde abcfg abchi)
+
+# problems with anchoring pattern replacements
+echo ${var[*]//#abc/foo}
+echo ${var[*]/#abc/foo}
+unset var
+
+${THIS_SH} ./new-exp6.sub
+
+${THIS_SH} ./new-exp7.sub
+
+# problems with stray CTLNUL in bash-4.0-alpha
+unset a
+a=/a
+recho "/${a%/*}"
+recho "/${a///a/}"
+
+# this must be last!
+expect $0: 'ABXD: parameter unset'
+recho ${ABXD:?"parameter unset"}
index 85d29e7fa84946edea588d716113983508e85285..e1898319be54428efb3e69a46b2c7da7d1bf7567 100644 (file)
@@ -1,3 +1,14 @@
+a
+b
+a b
+argv[1] = <a>
+argv[2] = <b>
+argv[3] = <x>
+argv[4] = <a>
+argv[5] = <b>
+argv[1] = <a\ b>
+argv[2] = <x>
+argv[3] = <a\ b>
 argv[1] = <foo 'bar' baz>
 argv[1] = <a b  c d>
 argv[1] = <a b  c d>
@@ -26,5 +37,5 @@ argv[1] = <'bar>
 argv[1] = <foo 'bar baz>
 argv[1] = <z}>
 argv[1] = <''z}>
-./posixexp.tests: line 56: unexpected EOF while looking for matching `}'
-./posixexp.tests: line 59: syntax error: unexpected end of file
+./posixexp.tests: line 68: unexpected EOF while looking for matching `}'
+./posixexp.tests: line 69: syntax error: unexpected end of file
index bd4a09d74af70be9f1e72ee7487587881bd3e3ff..9a5b64cd776fd5af3971da74475b86aa44bd3246 100644 (file)
@@ -1,3 +1,15 @@
+unset a
+printf "%s\n" ${a:=a\ b}
+echo "$a"
+
+unset v
+recho ${v=a\ b} x ${v=c\ d}
+
+unset v
+recho "${v=a\ b}" x "${v=c\ d}"
+
+unset a v
+
 recho "foo ${IFS+'bar'} baz"
 recho "a ${IFS+b  c} d"
 
@@ -54,5 +66,3 @@ recho "${IFS+'}'z}"
 # this will be an error
 foo=bar
 echo "${foo:-"a}"
-
-
diff --git a/tests/posixexp.tests~ b/tests/posixexp.tests~
new file mode 100644 (file)
index 0000000..018c087
--- /dev/null
@@ -0,0 +1,56 @@
+recho "foo ${IFS+'bar'} baz"
+recho "a ${IFS+b  c} d"
+
+recho "a ${IFS+"b  c"} d"
+
+u=x
+recho "foo ${IFS+a$u{{{\}b} c ${IFS+d{}} bar" ${IFS-e{}} baz
+
+a=foo
+recho "${IFS+'$a'}"
+recho "${IFS+"'$a'"}"
+
+recho ${IFS+'$a'}
+recho ${IFS+"'$a'"}
+
+unset a u
+x='foo*bar'
+
+recho "${x##"}"}"
+recho "${x##'}'}"
+recho "${x##'}"
+
+recho "${x:-'}'}"
+
+foo="x'a'y"
+recho "${foo%*'a'*}"
+unset x
+
+unset u
+v=w
+printf '<%s> ' ${u+x} . ${v+x} . "${u+x}" . "${v+x}" .; echo
+printf '<%s> ' ${u-x} . ${v-x} . "${u-x}" . "${v-x}" .; echo
+printf '<%s> ' ${u=x} . ${v=x} . "${u=x}" . "${v=x}" .; echo
+printf '<%s> ' ${u?x} . ${v?x} . "${u?x}" . "${v?x}" .; echo
+printf '<%s> ' ${u#x} . ${v#x} . "${u#x}" . "${v#x}" .; echo
+printf '<%s> ' ${u%x} . ${v%x} . "${u%x}" . "${v%x}" .; echo
+printf '<%s> ' ${u:+x} . ${v:+x} . "${u:+x}" . "${v:+x}" .; echo
+printf '<%s> ' ${u:-x} . ${v:-x} . "${u:-x}" . "${v:-x}" .; echo
+printf '<%s> ' ${u:=x} . ${v:=x} . "${u:=x}" . "${v:=x}" .; echo
+printf '<%s> ' ${u:?x} . ${v:?x} . "${u:?x}" . "${v:?x}" .; echo
+# these are invalid substitution operators
+#printf '<%s> ' ${u:#x} . ${v:#x} . "${u:#x}" . "${v:#x}" .; echo
+#printf '<%s> ' ${u:%x} . ${v:%x} . "${u:%x}" . "${v:%x}" .; echo
+
+unset foo
+set -o posix
+
+recho "${IFS+'bar}"
+recho "foo ${IFS+'bar} baz"
+
+recho ${IFS+'}'z}
+recho "${IFS+'}'z}"
+
+# this will be an error
+foo=bar
+echo "${foo:-"a}"
diff --git a/tests/posixpipe.right b/tests/posixpipe.right
new file mode 100644 (file)
index 0000000..8f6c9cb
--- /dev/null
@@ -0,0 +1,41 @@
+1
+0
+a
+real 0.00
+user 0.00
+sys 0.00
+1
+a
+real 0.00
+user 0.00
+sys 0.00
+1
+tfunc is a function
+tfunc () 
+{ 
+    time 
+}
+1
+0
+1
+a
+real 0.00
+user 0.00
+sys 0.00
+0
+a
+real 0.00
+user 0.00
+sys 0.00
+0
+a
+real 0.00
+user 0.00
+sys 0.00
+0
+1
+0
+a
+real 0.00
+user 0.00
+sys 0.00
diff --git a/tests/posixpipe.tests b/tests/posixpipe.tests
new file mode 100644 (file)
index 0000000..a2f047a
--- /dev/null
@@ -0,0 +1,43 @@
+# Test timed and negated pipelines in bash-4.2 and later
+export TIMEFORMAT=$'real %2R\nuser %2U\nsys %2S'
+
+!
+echo $?
+
+! !
+echo $?
+
+time ! echo a
+echo $?
+
+! time echo a
+echo $?
+
+tfunc()
+{
+       time
+}
+
+type tfunc
+
+! true
+echo $?
+! ! true
+echo $?
+! ! ! true
+echo $?
+
+time time echo a
+echo $?
+
+time time -p echo a
+echo $?
+time -p time echo a
+echo $?
+
+!
+echo $?
+! !
+echo $?
+
+time -p -- echo a
diff --git a/tests/posixpipe.tests~ b/tests/posixpipe.tests~
new file mode 100644 (file)
index 0000000..4922b74
--- /dev/null
@@ -0,0 +1,41 @@
+# Test timed and negated pipelines in bash-4.2 and later
+export TIMEFORMAT=$'real %2R\nuser %2U\nsys %2S'
+
+!
+echo $?
+
+! !
+echo $?
+
+time ! echo a
+echo $?
+
+! time echo a
+echo $?
+
+tfunc()
+{
+       time
+}
+
+type tfunc
+
+! true
+echo $?
+! ! true
+echo $?
+! ! ! true
+echo $?
+
+time time echo a
+echo $?
+
+time time -p echo a
+echo $?
+time -p time echo a
+echo $?
+
+!
+echo $?
+! !
+echo $?
index 0054cda858ea09dd44a5b830a26d22f25e3aece9..de983447831550125a231fe464d65201fc0eabee 100644 (file)
@@ -27,7 +27,7 @@ do
        *.orig|*~) ;;
        run-dollars|run-execscript|run-func|run-getopts|run-heredoc) echo $x ; sh $x ;;
        run-ifs-tests|run-input-test|run-invert|run-more-exp|run-nquote) echo $x ; sh $x ;;
-       run-ifs-posix|run-posix2|run-posixpat) echo $x ; sh $x ;;
+       run-ifs-posix|run-posix2|run-posixpat|run-posixpipe) echo $x ; sh $x ;;
        run-precedence|run-quote|run-read|run-rhs-exp|run-strip|run-tilde) echo $x ; sh $x ;;
        *)      ;;
        esac
diff --git a/tests/run-minimal~ b/tests/run-minimal~
new file mode 100644 (file)
index 0000000..0054cda
--- /dev/null
@@ -0,0 +1,36 @@
+#! /bin/sh
+#
+# run-minimal - a version of run-all for shells configured with
+#              --enable-minimal-config
+#
+PATH=.:$PATH   # just to get the right version of printenv
+export PATH
+
+# unset BASH_ENV only if it is set
+[ "${BASH_ENV+set}" = "set" ] && unset BASH_ENV
+# ditto for SHELLOPTS
+#[ "${SHELLOPTS+set}" = "set" ] && unset SHELLOPTS
+
+: ${THIS_SH:=../bash}
+export THIS_SH
+
+${THIS_SH} ./version.mini
+
+rm -f /tmp/xx
+
+echo Testing ${THIS_SH}
+echo Any output from any test, unless otherwise noted, indicates a possible anomaly
+for x in run-*
+do
+       case $x in
+       $0)     ;;
+       *.orig|*~) ;;
+       run-dollars|run-execscript|run-func|run-getopts|run-heredoc) echo $x ; sh $x ;;
+       run-ifs-tests|run-input-test|run-invert|run-more-exp|run-nquote) echo $x ; sh $x ;;
+       run-ifs-posix|run-posix2|run-posixpat) echo $x ; sh $x ;;
+       run-precedence|run-quote|run-read|run-rhs-exp|run-strip|run-tilde) echo $x ; sh $x ;;
+       *)      ;;
+       esac
+done
+
+exit 0
diff --git a/tests/run-posixpipe b/tests/run-posixpipe
new file mode 100644 (file)
index 0000000..c574535
--- /dev/null
@@ -0,0 +1,2 @@
+${THIS_SH} ./posixpipe.tests > /tmp/xx 2>&1
+diff /tmp/xx posixpipe.right && rm -f /tmp/xx
diff --git a/tests/run-posixpipe~ b/tests/run-posixpipe~
new file mode 100644 (file)
index 0000000..e5a1c3e
--- /dev/null
@@ -0,0 +1,2 @@
+${THIS_SH} ./posixexp.tests > /tmp/xx 2>&1
+diff /tmp/xx posixexp.right && rm -f /tmp/xx
index c8417acf4e689e3ffa8016c3eabbc51761260975..d1879a42a9af60d35a49ede1d8bcd07df2bacfbf 100644 (file)
@@ -53,6 +53,7 @@ trap -- 'echo aborting' SIGQUIT
 trap -- 'echo aborting' SIGABRT
 trap -- 'echo aborting' SIGTERM
 0
+trap -- '' SIGUSR2
 ERRTRAP
 ERRTRAP
 ERRTRAP