lib/readline/bind.c
- sv_histsize: if argument evaluates to a value < 0, unstifle the
history
+
+ 11/22
+ -----
+redir.c
+ - do_redirection_internal: if we have REDIR_VARASSIGN set in the
+ redirection flags and we set up `redirector' using fcntl or dup2,
+ don't add a redirect to make sure it stays open. Let the
+ script programmer manage the file handle. Fixes bug reported by
+ Sam Liddicott <sam@liddicott.com>
+
+ 11/24
+ -----
+jobs.c
+ - wait_for_any_job: new function, waits for an unspecified background
+ job to exit and returns its exit status. Returns -1 on no background
+ jobs or no children or other errors. Calls wait_for with new
+ sentinel value ANY_PID
+ - wait_for: changes to handle argument of ANY_PID: don't look up or
+ try to modify the child struct, only go through the wait loop once.
+ Return -1 if waitpid returns no children
+
+jobs.h
+ - ANY_PID: new define
+
+builtins/wait.def
+ - new option: -n. Means to wait for the next job and return its exit
+ status. Returns 127 if there are no background jobs (or no
+ children). Feature most recently requested by Elliott Forney
+ <idfah@cs.colostate.edu>
+
+doc/{bash.1,bashref.texi}
+ - document new `wait -n' option
+
+execute_cmd.c
+ - execute_command_internal: save make_command_string () result in a
+ temp variable before calling savestring() on it; avoids evaluating
+ make_command_string() result twice. Fix from John E. Malmberg
+ <wb8tyw@qsl.net>
+
+ 11/28
+ -----
+
+builtins/declare.def
+ - declare_internal: if an array variable is declared using `declare -a'
+ or `declare -A', but not assigned a value, set the `invisible'
+ attribute so the variable does not show up as set. Fix for bug
+ about variable initialization reported by Tim Friske <me@timfriske.com>
+
+builtins/{mapfile,read}.def
+ - after calling find_or_make_array_variable, make sure the invisible
+ flag is turned off, in case the variable was declared previously
+ using `declare -a' or `declare -A'. Side effect of above change to
+ declare_internal
+
+subst.c
+ - shell_expand_word_list: handle the W_ASSNGLOBAL flag and put -g into
+ the list of options passed to make_internal_declare as appropriate.
+ Fix for bug reported by Tim Friske <me@timfriske.com>
+
+ 11/30
+ -----
+test.c
+ - unary_op: make sure -v and -n check that the variable is not marked
+ as invisible before calling var_isset. Fix for bug reported by Tim
+ Friske <me@timfriske.com>
+
+ 12/2
+ ----
+subst.c
+ - process_substitute: turn off the `expanding_redir' flag, which
+ controls whether or not variables.c:find_variable_internal uses the
+ temporary environment to find variables. We want to use the
+ temp environment, since we don't have to worry about order of
+ evaluation in a subshell. Fixes bug reported by Andrey Borzenkov
+ <arvidjaar@gmail.com>
last element of a pipeline (or not in a pipeline), rather than for
every child. Fixes difference in behavior between /dev/fd and
FIFOs reported by Zev Weiss <zev@bewilderbeest.net>
+ - execute_null_command: do the same thing in the parent branch after
+ make_child
+
+ 11/14
+ -----
+subst.c
+ - parameter_brace_expand: a variable is null if it's special ($@, $*),
+ the expansion occurs within double quotes, and the expansion turns
+ into a quoted null. Fixes debian bug 692447 reported by
+ Matrosov Dmitriy <sgf.dma@gmail.com>
+
+jobs.c
+ - run_sigchld_trap: make sure `running_trap' sentinel is set
+ appropriately
+ - waitchld: only run the sigchld trap if we're not in a signal
+ handler, not running a trap, and executing the wait builtin.
+ Otherwise, queue for later handling. We still run one instance
+ of the trap handler per exited child. Bulk of fix for bug
+ reported by Elliott Forney <idfah@cs.colostate.edu>
+
+trap.c
+ - queue_sigchld_trap: set catch_flag so run_pending_traps notices,
+ and set trapped_signal_received for completeness. Rest of fix
+ for bug reported by Elliott Forney <idfah@cs.colostate.edu>
+
+lib/malloc/malloc.c
+ - block_signals: renamed to _malloc_block_signals, made public
+ - unblock_signals: renamed to _malloc_unblock_signals, made public
+
+lib/malloc/imalloc.h
+ - extern declarations for _malloc_{un,}block_signals
+
+lib/malloc/table.c
+ - mregister_alloc, mregister_free: block signals around table
+ manipulation
+
+ 11/15
+ -----
+trap.c
+ - run_pending_traps: set SIG_INPROGRESS flag around calls to
+ run_sigchld_handler so other parts of the shell know that the
+ SIGCHLD trap handler is executing
+ - run_pending_traps: if we get a situation where we are looking at
+ running a SIGCHLD trap but the trap string is IMPOSSIBLE_TRAP_HANDLER
+ and the SIG_INPROGRESS flag is set, just skip it. This is possible
+ if run_pending_traps is called from a SIGCHLD trap handler run by
+ run_sigchld_trap
+
+doc/bash.1,lib/readline/doc/{rluser.texi,readline.3}
+ - corrected description of the effect of `set history-size 0'. Report
+ from Vesa-Matti J Kari <vmkari@cc.helsinki.fi>
+
+include/stdc.h
+ - CPP_STRING: new define, replaces __STRING
+
+lib/malloc/{malloc.c,imalloc.h}
+ - replace __STRING with CPP_STRING
+
+ 11/16
+ -----
+lib/readline/bind.c
+ - sv_histsize: if argument evaluates to a value < 0, unstifle the
+ history
+
+ 11/22
+ -----
+redir.c
+ - do_redirection_internal: if we have REDIR_VARASSIGN set in the
+ redirection flags and we set up `redirector' using fcntl or dup2,
+ don't add a redirect to make sure it stays open. Let the
+ script programmer manage the file handle. Fixes bug reported by
+ Sam Liddicott <sam@liddicott.com>
+
+ 11/24
+ -----
+jobs.c
+ - wait_for_any_job: new function, waits for an unspecified background
+ job to exit and returns its exit status. Returns -1 on no background
+ jobs or no children or other errors. Calls wait_for with new
+ sentinel value ANY_PID
+ - wait_for: changes to handle argument of ANY_PID: don't look up or
+ try to modify the child struct, only go through the wait loop once.
+ Return -1 if waitpid returns no children
+
+jobs.h
+ - ANY_PID: new define
+
+builtins/wait.def
+ - new option: -n. Means to wait for the next job and return its exit
+ status. Returns 127 if there are no background jobs (or no
+ children). Feature most recently requested by Elliott Forney
+ <idfah@cs.colostate.edu>
+
+doc/{bash.1,bashref.texi}
+ - document new `wait -n' option
+
+execute_cmd.c
+ - execute_command_internal: save make_command_string () result in a
+ temp variable before calling savestring() on it; avoids evaluating
+ make_command_string() result twice. Fix from John E. Malmberg
+ <wb8tyw@qsl.net>
+
+ 11/28
+ -----
+
+builtins/declare.def
+ - declare_internal: if an array variable is declared using `declare -a'
+ or `declare -A', but not assigned a value, set the `invisible'
+ attribute so the variable does not show up as set. Fix for bug
+ about variable initialization reported by Tim Friske <me@timfriske.com>
+
+builtins/{mapfile,read}.def
+ - after calling find_or_make_array_variable, make sure the invisible
+ flag is turned off, in case the variable was declared previously
+ using `declare -a' or `declare -A'. Side effect of above change to
+ declare_internal
+
+subst.c
+ - shell_expand_word_list: handle the W_ASSNGLOBAL flag and put -g into
+ the list of options passed to make_internal_declare as appropriate.
+ Fix for bug reported by Tim Friske <me@timfriske.com>
+
+ 11/30
+ -----
+test.c
+ - unary_op: make sure -v and -n check that the variable is not marked
+ as invisible before calling var_isset. Fix for bug reported by Tim
+ Friske <me@timfriske.com>
{
#if defined (ARRAY_VARS)
if (flags_on & att_assoc)
- var = make_new_assoc_variable (name);
+ {
+ var = make_new_assoc_variable (name);
+ if (offset == 0)
+ VSETATTR (var, att_invisible);
+ }
else if ((flags_on & att_array) || making_array_special)
- var = make_new_array_variable (name);
+ {
+ var = make_new_array_variable (name);
+ if (offset == 0)
+ VSETATTR (var, att_invisible);
+ }
else
#endif
builtin_error (_("%s: not an indexed array"), array_name);
return (EXECUTION_FAILURE);
}
+ else if (invisible_p (entry))
+ VUNSETATTR (entry, att_invisible); /* no longer invisible */
if (flags & MAPF_CLEARARRAY)
array_flush (array_cell (entry));
xfree (input_string);
return EXECUTION_FAILURE; /* existing associative array */
}
+ else if (invisible_p (var))
+ VUNSETATTR (var, att_invisible);
array_flush (array_cell (var));
alist = list_string (input_string, ifs_chars, 0);
char *name, *value;
{
SHELL_VAR *v;
+
#if defined (ARRAY_VARS)
if (valid_array_reference (name) == 0)
v = bind_variable (name, value, 0);
--- /dev/null
+This file is read.def, from which is created read.c.
+It implements the builtin "read" in Bash.
+
+Copyright (C) 1987-2012 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES read.c
+
+$BUILTIN read
+$FUNCTION read_builtin
+$SHORT_DOC read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
+Read a line from the standard input and split it into fields.
+
+Reads a single line from the standard input, or from file descriptor FD
+if the -u option is supplied. The line is split into fields as with word
+splitting, and the first word is assigned to the first NAME, the second
+word to the second NAME, and so on, with any leftover words assigned to
+the last NAME. Only the characters found in $IFS are recognized as word
+delimiters.
+
+If no NAMEs are supplied, the line read is stored in the REPLY variable.
+
+Options:
+ -a array assign the words read to sequential indices of the array
+ variable ARRAY, starting at zero
+ -d delim continue until the first character of DELIM is read, rather
+ than newline
+ -e use Readline to obtain the line in an interactive shell
+ -i text Use TEXT as the initial text for Readline
+ -n nchars return after reading NCHARS characters rather than waiting
+ for a newline, but honor a delimiter if fewer than NCHARS
+ characters are read before the delimiter
+ -N nchars return only after reading exactly NCHARS characters, unless
+ EOF is encountered or read times out, ignoring any delimiter
+ -p prompt output the string PROMPT without a trailing newline before
+ attempting to read
+ -r do not allow backslashes to escape any characters
+ -s do not echo input coming from a terminal
+ -t timeout time out and return failure if a complete line of input is
+ not read within TIMEOUT seconds. The value of the TMOUT
+ variable is the default timeout. TIMEOUT may be a
+ fractional number. If TIMEOUT is 0, read returns immediately,
+ without trying to read any data, returning success only if
+ input is available on the specified file descriptor. The
+ exit status is greater than 128 if the timeout is exceeded
+ -u fd read from file descriptor FD instead of the standard input
+
+Exit Status:
+The return code is zero, unless end-of-file is encountered, read times out
+(in which case it's greater than 128), a variable assignment error occurs,
+or an invalid file descriptor is supplied as the argument to -u.
+$END
+
+#include <config.h>
+
+#include "bashtypes.h"
+#include "posixstat.h"
+
+#include <stdio.h>
+
+#include "bashansi.h"
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <signal.h>
+#include <errno.h>
+
+#ifdef __CYGWIN__
+# include <fcntl.h>
+# include <io.h>
+#endif
+
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "common.h"
+#include "bashgetopt.h"
+
+#include <shtty.h>
+
+#if defined (READLINE)
+#include "../bashline.h"
+#include <readline/readline.h>
+#endif
+
+#if defined (BUFFERED_INPUT)
+# include "input.h"
+#endif
+
+#include "shmbutil.h"
+
+#if !defined(errno)
+extern int errno;
+#endif
+
+extern void run_pending_traps __P((void));
+
+extern int posixly_correct;
+extern int trapped_signal_received;
+
+struct ttsave
+{
+ int fd;
+ TTYSTRUCT *attrs;
+};
+
+#if defined (READLINE)
+static void reset_attempted_completion_function __P((char *));
+static int set_itext __P((void));
+static char *edit_line __P((char *, char *));
+static void set_eol_delim __P((int));
+static void reset_eol_delim __P((char *));
+#endif
+static SHELL_VAR *bind_read_variable __P((char *, char *));
+#if defined (HANDLE_MULTIBYTE)
+static int read_mbchar __P((int, char *, int, int, int));
+#endif
+static void ttyrestore __P((struct ttsave *));
+
+static sighandler sigalrm __P((int));
+static void reset_alarm __P((void));
+
+static procenv_t alrmbuf;
+static int sigalrm_seen, reading;
+static SigHandler *old_alrm;
+static unsigned char delim;
+
+/* In most cases, SIGALRM just sets a flag that we check periodically. This
+ avoids problems with the semi-tricky stuff we do with the xfree of
+ input_string at the top of the unwind-protect list (see below). */
+#define CHECK_ALRM \
+ do { \
+ if (sigalrm_seen) \
+ longjmp (alrmbuf, 1); \
+ } while (0)
+
+static sighandler
+sigalrm (s)
+ int s;
+{
+ sigalrm_seen = 1;
+ if (reading) /* do the longjmp if we get SIGALRM while in read() */
+ longjmp (alrmbuf, 1);
+}
+
+static void
+reset_alarm ()
+{
+ set_signal_handler (SIGALRM, old_alrm);
+ falarm (0, 0);
+}
+
+/* Read the value of the shell variables whose names follow.
+ The reading is done from the current input stream, whatever
+ that may be. Successive words of the input line are assigned
+ to the variables mentioned in LIST. The last variable in LIST
+ gets the remainder of the words on the line. If no variables
+ are mentioned in LIST, then the default variable is $REPLY. */
+int
+read_builtin (list)
+ WORD_LIST *list;
+{
+ register char *varname;
+ int size, i, nr, pass_next, saw_escape, eof, opt, retval, code, print_ps2;
+ int input_is_tty, input_is_pipe, unbuffered_read, skip_ctlesc, skip_ctlnul;
+ int raw, edit, nchars, silent, have_timeout, ignore_delim, fd, lastsig, t_errno;
+ unsigned int tmsec, tmusec;
+ long ival, uval;
+ intmax_t intval;
+ char c;
+ char *input_string, *orig_input_string, *ifs_chars, *prompt, *arrayname;
+ char *e, *t, *t1, *ps2, *tofree;
+ struct stat tsb;
+ SHELL_VAR *var;
+ TTYSTRUCT ttattrs, ttset;
+ struct ttsave termsave;
+#if defined (ARRAY_VARS)
+ WORD_LIST *alist;
+#endif
+#if defined (READLINE)
+ char *rlbuf, *itext;
+ int rlind;
+#endif
+
+ USE_VAR(size);
+ USE_VAR(i);
+ USE_VAR(pass_next);
+ USE_VAR(print_ps2);
+ USE_VAR(saw_escape);
+ USE_VAR(input_is_pipe);
+/* USE_VAR(raw); */
+ USE_VAR(edit);
+ USE_VAR(tmsec);
+ USE_VAR(tmusec);
+ USE_VAR(nchars);
+ USE_VAR(silent);
+ USE_VAR(ifs_chars);
+ USE_VAR(prompt);
+ USE_VAR(arrayname);
+#if defined (READLINE)
+ USE_VAR(rlbuf);
+ USE_VAR(rlind);
+ USE_VAR(itext);
+#endif
+ USE_VAR(list);
+ USE_VAR(ps2);
+ USE_VAR(lastsig);
+
+ sigalrm_seen = reading = 0;
+
+ i = 0; /* Index into the string that we are reading. */
+ raw = edit = 0; /* Not reading raw input by default. */
+ silent = 0;
+ arrayname = prompt = (char *)NULL;
+ fd = 0; /* file descriptor to read from */
+
+#if defined (READLINE)
+ rlbuf = itext = (char *)0;
+ rlind = 0;
+#endif
+
+ tmsec = tmusec = 0; /* no timeout */
+ nr = nchars = input_is_tty = input_is_pipe = unbuffered_read = have_timeout = 0;
+ delim = '\n'; /* read until newline */
+ ignore_delim = 0;
+
+ reset_internal_getopt ();
+ while ((opt = internal_getopt (list, "ersa:d:i:n:p:t:u:N:")) != -1)
+ {
+ switch (opt)
+ {
+ case 'r':
+ raw = 1;
+ break;
+ case 'p':
+ prompt = list_optarg;
+ break;
+ case 's':
+ silent = 1;
+ break;
+ case 'e':
+#if defined (READLINE)
+ edit = 1;
+#endif
+ break;
+ case 'i':
+#if defined (READLINE)
+ itext = list_optarg;
+#endif
+ break;
+#if defined (ARRAY_VARS)
+ case 'a':
+ arrayname = list_optarg;
+ break;
+#endif
+ case 't':
+ code = uconvert (list_optarg, &ival, &uval);
+ if (code == 0 || ival < 0 || uval < 0)
+ {
+ builtin_error (_("%s: invalid timeout specification"), list_optarg);
+ return (EXECUTION_FAILURE);
+ }
+ else
+ {
+ have_timeout = 1;
+ tmsec = ival;
+ tmusec = uval;
+ }
+ break;
+ case 'N':
+ ignore_delim = 1;
+ delim = -1;
+ case 'n':
+ code = legal_number (list_optarg, &intval);
+ if (code == 0 || intval < 0 || intval != (int)intval)
+ {
+ sh_invalidnum (list_optarg);
+ return (EXECUTION_FAILURE);
+ }
+ else
+ nchars = intval;
+ break;
+ case 'u':
+ code = legal_number (list_optarg, &intval);
+ if (code == 0 || intval < 0 || intval != (int)intval)
+ {
+ builtin_error (_("%s: invalid file descriptor specification"), list_optarg);
+ return (EXECUTION_FAILURE);
+ }
+ else
+ fd = intval;
+ if (sh_validfd (fd) == 0)
+ {
+ builtin_error (_("%d: invalid file descriptor: %s"), fd, strerror (errno));
+ return (EXECUTION_FAILURE);
+ }
+ break;
+ case 'd':
+ delim = *list_optarg;
+ break;
+ default:
+ builtin_usage ();
+ return (EX_USAGE);
+ }
+ }
+ list = loptend;
+
+ /* `read -t 0 var' tests whether input is available with select/FIONREAD,
+ and fails if those are unavailable */
+ if (have_timeout && tmsec == 0 && tmusec == 0)
+#if 0
+ return (EXECUTION_FAILURE);
+#else
+ return (input_avail (fd) ? EXECUTION_SUCCESS : EXECUTION_FAILURE);
+#endif
+
+ /* If we're asked to ignore the delimiter, make sure we do. */
+ if (ignore_delim)
+ delim = -1;
+
+ /* IF IFS is unset, we use the default of " \t\n". */
+ ifs_chars = getifs ();
+ if (ifs_chars == 0) /* XXX - shouldn't happen */
+ ifs_chars = "";
+ /* If we want to read exactly NCHARS chars, don't split on IFS */
+ if (ignore_delim)
+ ifs_chars = "";
+ for (skip_ctlesc = skip_ctlnul = 0, e = ifs_chars; *e; e++)
+ skip_ctlesc |= *e == CTLESC, skip_ctlnul |= *e == CTLNUL;
+
+ input_string = (char *)xmalloc (size = 112); /* XXX was 128 */
+ input_string[0] = '\0';
+
+ /* $TMOUT, if set, is the default timeout for read. */
+ if (have_timeout == 0 && (e = get_string_value ("TMOUT")))
+ {
+ code = uconvert (e, &ival, &uval);
+ if (code == 0 || ival < 0 || uval < 0)
+ tmsec = tmusec = 0;
+ else
+ {
+ tmsec = ival;
+ tmusec = uval;
+ }
+ }
+
+ begin_unwind_frame ("read_builtin");
+
+#if defined (BUFFERED_INPUT)
+ if (interactive == 0 && default_buffered_input >= 0 && fd_is_bash_input (fd))
+ sync_buffered_stream (default_buffered_input);
+#endif
+
+ input_is_tty = isatty (fd);
+ if (input_is_tty == 0)
+#ifndef __CYGWIN__
+ input_is_pipe = (lseek (fd, 0L, SEEK_CUR) < 0) && (errno == ESPIPE);
+#else
+ input_is_pipe = 1;
+#endif
+
+ /* If the -p, -e or -s flags were given, but input is not coming from the
+ terminal, turn them off. */
+ if ((prompt || edit || silent) && input_is_tty == 0)
+ {
+ prompt = (char *)NULL;
+#if defined (READLINE)
+ itext = (char *)NULL;
+#endif
+ edit = silent = 0;
+ }
+
+#if defined (READLINE)
+ if (edit)
+ add_unwind_protect (xfree, rlbuf);
+#endif
+
+ pass_next = 0; /* Non-zero signifies last char was backslash. */
+ saw_escape = 0; /* Non-zero signifies that we saw an escape char */
+
+ if (tmsec > 0 || tmusec > 0)
+ {
+ /* Turn off the timeout if stdin is a regular file (e.g. from
+ input redirection). */
+ if ((fstat (fd, &tsb) < 0) || S_ISREG (tsb.st_mode))
+ tmsec = tmusec = 0;
+ }
+
+ if (tmsec > 0 || tmusec > 0)
+ {
+ code = setjmp (alrmbuf);
+ if (code)
+ {
+ sigalrm_seen = 0;
+ /* Tricky. The top of the unwind-protect stack is the free of
+ input_string. We want to run all the rest and use input_string,
+ so we have to remove it from the stack. */
+ orig_input_string = 0;
+
+ remove_unwind_protect ();
+ run_unwind_frame ("read_builtin");
+ input_string[i] = '\0'; /* make sure it's terminated */
+ retval = 128+SIGALRM;
+ goto assign_vars;
+ }
+ old_alrm = set_signal_handler (SIGALRM, sigalrm);
+ add_unwind_protect (reset_alarm, (char *)NULL);
+#if defined (READLINE)
+ if (edit)
+ add_unwind_protect (reset_attempted_completion_function, (char *)NULL);
+#endif
+ falarm (tmsec, tmusec);
+ }
+
+ /* If we've been asked to read only NCHARS chars, or we're using some
+ character other than newline to terminate the line, do the right
+ thing to readline or the tty. */
+ if (nchars > 0 || delim != '\n')
+ {
+#if defined (READLINE)
+ if (edit)
+ {
+ if (nchars > 0)
+ {
+ unwind_protect_int (rl_num_chars_to_read);
+ rl_num_chars_to_read = nchars;
+ }
+ if (delim != '\n')
+ {
+ set_eol_delim (delim);
+ add_unwind_protect (reset_eol_delim, (char *)NULL);
+ }
+ }
+ else
+#endif
+ if (input_is_tty)
+ {
+ /* ttsave() */
+ termsave.fd = fd;
+ ttgetattr (fd, &ttattrs);
+ termsave.attrs = &ttattrs;
+
+ ttset = ttattrs;
+ i = silent ? ttfd_cbreak (fd, &ttset) : ttfd_onechar (fd, &ttset);
+ if (i < 0)
+ sh_ttyerror (1);
+ add_unwind_protect ((Function *)ttyrestore, (char *)&termsave);
+ }
+ }
+ else if (silent) /* turn off echo but leave term in canonical mode */
+ {
+ /* ttsave (); */
+ termsave.fd = fd;
+ ttgetattr (fd, &ttattrs);
+ termsave.attrs = &ttattrs;
+
+ ttset = ttattrs;
+ i = ttfd_noecho (fd, &ttset); /* ttnoecho (); */
+ if (i < 0)
+ sh_ttyerror (1);
+
+ add_unwind_protect ((Function *)ttyrestore, (char *)&termsave);
+ }
+
+ /* This *must* be the top unwind-protect on the stack, so the manipulation
+ of the unwind-protect stack after the realloc() works right. */
+ add_unwind_protect (xfree, input_string);
+
+ CHECK_ALRM;
+ unbuffered_read = (nchars > 0) || (delim != '\n') || input_is_pipe;
+
+ if (prompt && edit == 0)
+ {
+ fprintf (stderr, "%s", prompt);
+ fflush (stderr);
+ }
+
+#if defined (__CYGWIN__) && defined (O_TEXT)
+ setmode (0, O_TEXT);
+#endif
+
+ ps2 = 0;
+ for (print_ps2 = eof = retval = 0;;)
+ {
+ CHECK_ALRM;
+
+#if defined (READLINE)
+ if (edit)
+ {
+ if (rlbuf && rlbuf[rlind] == '\0')
+ {
+ xfree (rlbuf);
+ rlbuf = (char *)0;
+ }
+ if (rlbuf == 0)
+ {
+ reading = 1;
+ rlbuf = edit_line (prompt ? prompt : "", itext);
+ reading = 0;
+ rlind = 0;
+ }
+ if (rlbuf == 0)
+ {
+ eof = 1;
+ break;
+ }
+ c = rlbuf[rlind++];
+ }
+ else
+ {
+#endif
+
+ if (print_ps2)
+ {
+ if (ps2 == 0)
+ ps2 = get_string_value ("PS2");
+ fprintf (stderr, "%s", ps2 ? ps2 : "");
+ fflush (stderr);
+ print_ps2 = 0;
+ }
+
+#if 0
+ if (posixly_correct == 0)
+ interrupt_immediately++;
+#endif
+ reading = 1;
+ if (unbuffered_read)
+ retval = posixly_correct ? zreadintr (fd, &c, 1) : zread (fd, &c, 1);
+ else
+ retval = posixly_correct ? zreadcintr (fd, &c) : zreadc (fd, &c);
+ reading = 0;
+#if 0
+ if (posixly_correct == 0)
+ interrupt_immediately--;
+#endif
+
+ if (retval <= 0)
+ {
+ if (retval < 0 && errno == EINTR)
+ {
+ lastsig = LASTSIG();
+ if (lastsig == 0)
+ lastsig = trapped_signal_received;
+ run_pending_traps (); /* because interrupt_immediately is not set */
+ }
+ else
+ lastsig = 0;
+ CHECK_TERMSIG;
+ eof = 1;
+ break;
+ }
+
+ CHECK_ALRM;
+
+#if defined (READLINE)
+ }
+#endif
+
+ CHECK_ALRM;
+ if (i + 4 >= size) /* XXX was i + 2; use i + 4 for multibyte/read_mbchar */
+ {
+ char *t;
+ t = (char *)xrealloc (input_string, size += 128);
+
+ /* Only need to change unwind-protect if input_string changes */
+ if (t != input_string)
+ {
+ input_string = t;
+ remove_unwind_protect ();
+ add_unwind_protect (xfree, input_string);
+ }
+ }
+
+ /* If the next character is to be accepted verbatim, a backslash
+ newline pair still disappears from the input. */
+ if (pass_next)
+ {
+ pass_next = 0;
+ if (c == '\n')
+ {
+ i--; /* back up over the CTLESC */
+ if (interactive && input_is_tty && raw == 0)
+ print_ps2 = 1;
+ }
+ else
+ goto add_char;
+ continue;
+ }
+
+ /* This may cause problems if IFS contains CTLESC */
+ if (c == '\\' && raw == 0)
+ {
+ pass_next++;
+ if (skip_ctlesc == 0)
+ {
+ saw_escape++;
+ input_string[i++] = CTLESC;
+ }
+ continue;
+ }
+
+ if ((unsigned char)c == delim)
+ break;
+
+ if (c == '\0' && delim != '\0')
+ continue; /* skip NUL bytes in input */
+
+ if ((skip_ctlesc == 0 && c == CTLESC) || (skip_ctlnul == 0 && c == CTLNUL))
+ {
+ saw_escape++;
+ input_string[i++] = CTLESC;
+ }
+
+add_char:
+ input_string[i++] = c;
+ CHECK_ALRM;
+
+#if defined (HANDLE_MULTIBYTE)
+ if (nchars > 0 && MB_CUR_MAX > 1 && is_basic (c) == 0)
+ {
+ input_string[i] = '\0'; /* for simplicity and debugging */
+ i += read_mbchar (fd, input_string, i, c, unbuffered_read);
+ }
+#endif
+
+ nr++;
+
+ if (nchars > 0 && nr >= nchars)
+ break;
+ }
+ input_string[i] = '\0';
+ CHECK_ALRM;
+
+ if (retval < 0)
+ {
+ t_errno = errno;
+ if (errno != EINTR)
+ builtin_error (_("read error: %d: %s"), fd, strerror (errno));
+ run_unwind_frame ("read_builtin");
+ return ((t_errno != EINTR) ? EXECUTION_FAILURE : 128+lastsig);
+ }
+
+ if (tmsec > 0 || tmusec > 0)
+ reset_alarm ();
+
+ if (nchars > 0 || delim != '\n')
+ {
+#if defined (READLINE)
+ if (edit)
+ {
+ if (nchars > 0)
+ rl_num_chars_to_read = 0;
+ if (delim != '\n')
+ reset_eol_delim ((char *)NULL);
+ }
+ else
+#endif
+ if (input_is_tty)
+ ttyrestore (&termsave);
+ }
+ else if (silent)
+ ttyrestore (&termsave);
+
+ if (unbuffered_read == 0)
+ zsyncfd (fd);
+
+ discard_unwind_frame ("read_builtin");
+
+ retval = eof ? EXECUTION_FAILURE : EXECUTION_SUCCESS;
+
+assign_vars:
+
+#if defined (ARRAY_VARS)
+ /* If -a was given, take the string read, break it into a list of words,
+ an assign them to `arrayname' in turn. */
+ if (arrayname)
+ {
+ if (legal_identifier (arrayname) == 0)
+ {
+ sh_invalidid (arrayname);
+ xfree (input_string);
+ return (EXECUTION_FAILURE);
+ }
+
+ var = find_or_make_array_variable (arrayname, 1);
+ if (var == 0)
+ {
+ xfree (input_string);
+ return EXECUTION_FAILURE; /* readonly or noassign */
+ }
+ if (assoc_p (var))
+ {
+ builtin_error (_("%s: cannot convert associative to indexed array"), arrayname);
+ xfree (input_string);
+ return EXECUTION_FAILURE; /* existing associative array */
+ }
+ else if (invisible_p (var))
+ VUNSETATTR (var, att_invisible);
+ array_flush (array_cell (var));
+
+ alist = list_string (input_string, ifs_chars, 0);
+ if (alist)
+ {
+ if (saw_escape)
+ dequote_list (alist);
+ else
+ word_list_remove_quoted_nulls (alist);
+ assign_array_var_from_word_list (var, alist, 0);
+ dispose_words (alist);
+ }
+ xfree (input_string);
+ return (retval);
+ }
+#endif /* ARRAY_VARS */
+
+ /* If there are no variables, save the text of the line read to the
+ variable $REPLY. ksh93 strips leading and trailing IFS whitespace,
+ so that `read x ; echo "$x"' and `read ; echo "$REPLY"' behave the
+ same way, but I believe that the difference in behaviors is useful
+ enough to not do it. Without the bash behavior, there is no way
+ to read a line completely without interpretation or modification
+ unless you mess with $IFS (e.g., setting it to the empty string).
+ If you disagree, change the occurrences of `#if 0' to `#if 1' below. */
+ if (list == 0)
+ {
+#if 0
+ orig_input_string = input_string;
+ for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++)
+ ;
+ input_string = t;
+ input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape);
+#endif
+
+ if (saw_escape)
+ {
+ t = dequote_string (input_string);
+ var = bind_variable ("REPLY", t, 0);
+ free (t);
+ }
+ else
+ var = bind_variable ("REPLY", input_string, 0);
+ VUNSETATTR (var, att_invisible);
+
+ xfree (input_string);
+ return (retval);
+ }
+
+ /* This code implements the Posix.2 spec for splitting the words
+ read and assigning them to variables. */
+ orig_input_string = input_string;
+
+ /* Remove IFS white space at the beginning of the input string. If
+ $IFS is null, no field splitting is performed. */
+ for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++)
+ ;
+ input_string = t;
+ for (; list->next; list = list->next)
+ {
+ varname = list->word->word;
+#if defined (ARRAY_VARS)
+ if (legal_identifier (varname) == 0 && valid_array_reference (varname) == 0)
+#else
+ if (legal_identifier (varname) == 0)
+#endif
+ {
+ sh_invalidid (varname);
+ xfree (orig_input_string);
+ return (EXECUTION_FAILURE);
+ }
+
+ /* If there are more variables than words read from the input,
+ the remaining variables are set to the empty string. */
+ if (*input_string)
+ {
+ /* This call updates INPUT_STRING. */
+ t = get_word_from_string (&input_string, ifs_chars, &e);
+ if (t)
+ *e = '\0';
+ /* Don't bother to remove the CTLESC unless we added one
+ somewhere while reading the string. */
+ if (t && saw_escape)
+ {
+ t1 = dequote_string (t);
+ var = bind_read_variable (varname, t1);
+ xfree (t1);
+ }
+ else
+ var = bind_read_variable (varname, t ? t : "");
+ }
+ else
+ {
+ t = (char *)0;
+ var = bind_read_variable (varname, "");
+ }
+
+ FREE (t);
+ if (var == 0)
+ {
+ xfree (orig_input_string);
+ return (EXECUTION_FAILURE);
+ }
+
+ stupidly_hack_special_variables (varname);
+ VUNSETATTR (var, att_invisible);
+ }
+
+ /* Now assign the rest of the line to the last variable argument. */
+#if defined (ARRAY_VARS)
+ if (legal_identifier (list->word->word) == 0 && valid_array_reference (list->word->word) == 0)
+#else
+ if (legal_identifier (list->word->word) == 0)
+#endif
+ {
+ sh_invalidid (list->word->word);
+ xfree (orig_input_string);
+ return (EXECUTION_FAILURE);
+ }
+
+#if 0
+ /* This has to be done this way rather than using string_list
+ and list_string because Posix.2 says that the last variable gets the
+ remaining words and their intervening separators. */
+ input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape);
+#else
+ /* Check whether or not the number of fields is exactly the same as the
+ number of variables. */
+ tofree = NULL;
+ if (*input_string)
+ {
+ t1 = input_string;
+ t = get_word_from_string (&input_string, ifs_chars, &e);
+ if (*input_string == 0)
+ tofree = input_string = t;
+ else
+ {
+ input_string = strip_trailing_ifs_whitespace (t1, ifs_chars, saw_escape);
+ tofree = t;
+ }
+ }
+#endif
+
+ if (saw_escape && input_string && *input_string)
+ {
+ t = dequote_string (input_string);
+ var = bind_read_variable (list->word->word, t);
+ xfree (t);
+ }
+ else
+ var = bind_read_variable (list->word->word, input_string ? input_string : "");
+
+ if (var)
+ {
+ stupidly_hack_special_variables (list->word->word);
+ VUNSETATTR (var, att_invisible);
+ }
+ else
+ retval = EXECUTION_FAILURE;
+
+ FREE (tofree);
+ xfree (orig_input_string);
+
+ return (retval);
+}
+
+static SHELL_VAR *
+bind_read_variable (name, value)
+ char *name, *value;
+{
+ SHELL_VAR *v;
+
+itrace("bind_read_variable: name = %s value = %s", name, value);
+#if defined (ARRAY_VARS)
+ if (valid_array_reference (name) == 0)
+ v = bind_variable (name, value, 0);
+ else
+ v = assign_array_element (name, value, 0);
+#else /* !ARRAY_VARS */
+ v = bind_variable (name, value, 0);
+#endif /* !ARRAY_VARS */
+ return (v == 0 ? v
+ : ((readonly_p (v) || noassign_p (v)) ? (SHELL_VAR *)NULL : v));
+}
+
+#if defined (HANDLE_MULTIBYTE)
+static int
+read_mbchar (fd, string, ind, ch, unbuffered)
+ int fd;
+ char *string;
+ int ind, ch, unbuffered;
+{
+ char mbchar[MB_LEN_MAX + 1];
+ int i, n, r;
+ char c;
+ size_t ret;
+ mbstate_t ps, ps_back;
+ wchar_t wc;
+
+ memset (&ps, '\0', sizeof (mbstate_t));
+ memset (&ps_back, '\0', sizeof (mbstate_t));
+
+ mbchar[0] = ch;
+ i = 1;
+ for (n = 0; n <= MB_LEN_MAX; n++)
+ {
+ ps_back = ps;
+ ret = mbrtowc (&wc, mbchar, i, &ps);
+ if (ret == (size_t)-2)
+ {
+ ps = ps_back;
+ /* We don't want to be interrupted during a multibyte char read */
+ if (unbuffered)
+ r = zread (fd, &c, 1);
+ else
+ r = zreadc (fd, &c);
+ if (r < 0)
+ goto mbchar_return;
+ mbchar[i++] = c;
+ continue;
+ }
+ else if (ret == (size_t)-1 || ret == (size_t)0 || ret > (size_t)0)
+ break;
+ }
+
+mbchar_return:
+ if (i > 1) /* read a multibyte char */
+ /* mbchar[0] is already string[ind-1] */
+ for (r = 1; r < i; r++)
+ string[ind+r-1] = mbchar[r];
+ return i - 1;
+}
+#endif
+
+
+static void
+ttyrestore (ttp)
+ struct ttsave *ttp;
+{
+ ttsetattr (ttp->fd, ttp->attrs);
+}
+
+#if defined (READLINE)
+static rl_completion_func_t *old_attempted_completion_function = 0;
+static rl_hook_func_t *old_startup_hook;
+static char *deftext;
+
+static void
+reset_attempted_completion_function (cp)
+ char *cp;
+{
+ if (rl_attempted_completion_function == 0 && old_attempted_completion_function)
+ rl_attempted_completion_function = old_attempted_completion_function;
+}
+
+static int
+set_itext ()
+{
+ int r1, r2;
+
+ r1 = r2 = 0;
+ if (old_startup_hook)
+ r1 = (*old_startup_hook) ();
+ if (deftext)
+ {
+ r2 = rl_insert_text (deftext);
+ deftext = (char *)NULL;
+ rl_startup_hook = old_startup_hook;
+ old_startup_hook = (rl_hook_func_t *)NULL;
+ }
+ return (r1 || r2);
+}
+
+static char *
+edit_line (p, itext)
+ char *p;
+ char *itext;
+{
+ char *ret;
+ int len;
+
+ if (bash_readline_initialized == 0)
+ initialize_readline ();
+
+ old_attempted_completion_function = rl_attempted_completion_function;
+ rl_attempted_completion_function = (rl_completion_func_t *)NULL;
+ if (itext)
+ {
+ old_startup_hook = rl_startup_hook;
+ rl_startup_hook = set_itext;
+ deftext = itext;
+ }
+
+ ret = readline (p);
+
+ rl_attempted_completion_function = old_attempted_completion_function;
+ old_attempted_completion_function = (rl_completion_func_t *)NULL;
+
+ if (ret == 0)
+ return ret;
+ len = strlen (ret);
+ ret = (char *)xrealloc (ret, len + 2);
+ ret[len++] = delim;
+ ret[len] = '\0';
+ return ret;
+}
+
+static int old_delim_ctype;
+static rl_command_func_t *old_delim_func;
+static int old_newline_ctype;
+static rl_command_func_t *old_newline_func;
+
+static unsigned char delim_char;
+
+static void
+set_eol_delim (c)
+ int c;
+{
+ Keymap cmap;
+
+ if (bash_readline_initialized == 0)
+ initialize_readline ();
+ cmap = rl_get_keymap ();
+
+ /* Change newline to self-insert */
+ old_newline_ctype = cmap[RETURN].type;
+ old_newline_func = cmap[RETURN].function;
+ cmap[RETURN].type = ISFUNC;
+ cmap[RETURN].function = rl_insert;
+
+ /* Bind the delimiter character to accept-line. */
+ old_delim_ctype = cmap[c].type;
+ old_delim_func = cmap[c].function;
+ cmap[c].type = ISFUNC;
+ cmap[c].function = rl_newline;
+
+ delim_char = c;
+}
+
+static void
+reset_eol_delim (cp)
+ char *cp;
+{
+ Keymap cmap;
+
+ cmap = rl_get_keymap ();
+
+ cmap[RETURN].type = old_newline_ctype;
+ cmap[RETURN].function = old_newline_func;
+
+ cmap[delim_char].type = old_delim_ctype;
+ cmap[delim_char].function = old_delim_func;
+}
+#endif
$FUNCTION wait_builtin
$DEPENDS_ON JOB_CONTROL
$PRODUCES wait.c
-$SHORT_DOC wait [id ...]
+$SHORT_DOC wait [-n] [id ...]
Wait for job completion and return exit status.
Waits for each process identified by an ID, which may be a process ID or a
status is zero. If ID is a a job specification, waits for all processes
in that job's pipeline.
+If the -n option is supplied, waits for the next job to terminate and
+returns its exit status.
+
Exit Status:
Returns the status of the last ID; fails if ID is invalid or an invalid
option is given.
wait_builtin (list)
WORD_LIST *list;
{
- int status, code;
+ int status, code, opt, nflag;
volatile int old_interrupt_immediately;
USE_VAR(list);
- if (no_options (list))
- return (EX_USAGE);
+ nflag = 0;
+ reset_internal_getopt ();
+ while ((opt = internal_getopt (list, "n")) != -1)
+ {
+ switch (opt)
+ {
+#if defined (JOB_CONTROL)
+ case 'n':
+ nflag = 1;
+ break;
+#endif
+ default:
+ builtin_usage ();
+ return (EXECUTION_FAILURE);
+ }
+ }
list = loptend;
old_interrupt_immediately = interrupt_immediately;
/* We support jobs or pids.
wait <pid-or-job> [pid-or-job ...] */
+#if defined (JOB_CONTROL)
+ if (nflag)
+ {
+ status = wait_for_any_job ();
+ if (status < 0)
+ status = 127;
+ WAIT_RETURN (status);
+ }
+#endif
+
/* But wait without any arguments means to wait for all of the shell's
currently active background processes. */
if (list == 0)
.\" Case Western Reserve University
.\" chet@po.cwru.edu
.\"
-.\" Last Change: Thu Nov 15 21:03:47 EST 2012
+.\" Last Change: Sat Nov 24 15:07:12 EST 2012
.\"
.\" bash_builtins, strip all but Built-Ins section
.if \n(zZ=1 .ig zZ
.if \n(zY=1 .ig zY
-.TH BASH 1 "2012 November 15" "GNU Bash 4.2"
+.TH BASH 1 "2012 November 24" "GNU Bash 4.2"
.\"
.\" There's some problem with having a `@'
.\" in a tagged paragraph with the BSD man macros.
.I name
is readonly.
.TP
-\fBwait\fP [\fIn ...\fP]
+\fBwait\fP [\fB\--n\fP] [\fIn ...\fP]
Wait for each specified process and return its termination status.
Each
.I n
in that job's pipeline are waited for. If
.I n
is not given, all currently active child processes
-are waited for, and the return status is zero. If
+are waited for, and the return status is zero.
+If the \fB\--n\fP option is supplied, \fBwait\fP waits for any job to
+terminate and returns its exit status.
+If
.I n
specifies a non-existent process or job, the return status is
127. Otherwise, the return status is the exit status of the last
If a job spec is given, all processes in the job are waited for.
If no arguments are given, all currently active child processes are
waited for, and the return status is zero.
+If the @option{-n} option is supplied, @code{wait} waits for any job to
+terminate and returns its exit status.
If neither @var{jobspec} nor @var{pid} specifies an active child process
of the shell, the return status is 127.
Copyright (C) 1988-2012 Free Software Foundation, Inc.
@end ignore
-@set LASTCHANGE Sun Oct 7 17:58:01 EDT 2012
+@set LASTCHANGE Sat Nov 24 15:06:49 EST 2012
@set EDITION 4.2
@set VERSION 4.2
-@set UPDATED 7 October 2012
-@set UPDATED-MONTH October 2012
+@set UPDATED 24 November 2012
+@set UPDATED-MONTH November 2012
return(EX_USAGE);
}
+ /* Skip over `--' */
+ if (list->word && ISOPTION (list->word->word, '-'))
+ list = list->next;
+
if (*list->word->word == '-' || list->next) {
builtin_usage ();
return (EX_USAGE);
{
int exec_result, user_subshell, invert, ignore_return, was_error_trap;
REDIRECT *my_undo_list, *exec_undo_list;
+ char *tcmd;
volatile int last_pid;
volatile int save_line_number;
#if defined (PROCESS_SUBSTITUTION)
/* Fork a subshell, turn off the subshell bit, turn off job
control and call execute_command () on the command again. */
line_number_for_err_trap = line_number;
- paren_pid = make_child (savestring (make_command_string (command)),
- asynchronous);
+ tcmd = make_command_string (command);
+ paren_pid = make_child (savestring (tcmd), asynchronous);
if (user_subshell && signal_is_trapped (ERROR_TRAP) &&
signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0)
close (rpipe[1]);
close (wpipe[0]);
+ /* XXX - possibly run Coproc->name through word expansion? */
cp = coproc_alloc (command->value.Coproc->name, coproc_pid);
cp->c_rfd = rpipe[0];
cp->c_wfd = wpipe[1];
}
#if defined (DEBUG)
-# if 0
itrace ("realloc_jobs_list: resize jobs list from %d to %d", js.j_jobslots, nsize);
itrace ("realloc_jobs_list: j_lastj changed from %d to %d", js.j_lastj, (j > 0) ? j - 1 : 0);
itrace ("realloc_jobs_list: j_njobs changed from %d to %d", js.j_njobs, j);
itrace ("realloc_jobs_list: js.j_ndead %d js.c_reaped %d", js.j_ndead, js.c_reaped);
-# endif
#endif
js.j_firstj = 0;
reset_current ();
#ifdef DEBUG
-/* itrace ("realloc_jobs_list: reset js.j_current (%d) and js.j_previous (%d)", js.j_current, js.j_previous);*/
+ itrace ("realloc_jobs_list: reset js.j_current (%d) and js.j_previous (%d)", js.j_current, js.j_previous);
#endif
UNBLOCK_CHILD (oset);
realloc_jobs_list ();
#ifdef DEBUG
-/* itrace("compact_jobs_list: returning %d", (js.j_lastj || jobs[js.j_lastj]) ? js.j_lastj + 1 : 0); */
+ itrace("compact_jobs_list: returning %d", (js.j_lastj || jobs[js.j_lastj]) ? js.j_lastj + 1 : 0);
#endif
return ((js.j_lastj || jobs[js.j_lastj]) ? js.j_lastj + 1 : 0);
/* In the case that this code is interrupted, and we longjmp () out of it,
we are relying on the code in throw_to_top_level () to restore the
top-level signal mask. */
+ child = 0;
BLOCK_CHILD (set, oset);
/* Ignore interrupts while waiting for a job run without job control
job = NO_JOB;
do
{
- FIND_CHILD (pid, child);
+ if (pid != ANY_PID)
+ FIND_CHILD (pid, child);
/* If this child is part of a job, then we are really waiting for the
job to finish. Otherwise, we are waiting for the child to finish.
has already exited before this is called, sigchld_handler will have
called waitchld and the state will be set to JDEAD. */
- if (PRUNNING(child) || (job != NO_JOB && RUNNING (job)))
+ if (pid == ANY_PID || PRUNNING(child) || (job != NO_JOB && RUNNING (job)))
{
#if defined (WAITPID_BROKEN) /* SCOv4 */
sigset_t suspend_set;
# endif
queue_sigchld = 1;
waiting_for_child++;
- r = waitchld (pid, 1);
+ r = waitchld (pid, 1); /* XXX */
waiting_for_child--;
# if defined (MUST_UNBLOCK_CHLD)
sigaction (SIGCHLD, &oact, (struct sigaction *)NULL);
if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin)
{
termination_state = -1;
+ /* XXX - restore sigint handler here? */
goto wait_for_return;
}
if it exists, as JDEAD. */
if (r == -1 && errno == ECHILD)
{
- child->running = PS_DONE;
- WSTATUS (child->status) = 0; /* XXX -- can't find true status */
+ if (child)
+ {
+ child->running = PS_DONE;
+ WSTATUS (child->status) = 0; /* XXX -- can't find true status */
+ }
js.c_living = 0; /* no living child processes */
if (job != NO_JOB)
{
js.c_reaped++;
js.j_ndead++;
}
+ if (pid == ANY_PID)
+ {
+ termination_state = -1;
+ break;
+ }
}
#endif /* WAITPID_BROKEN */
}
/* Check for a trapped signal interrupting the wait builtin and jump out */
CHECK_WAIT_INTR;
+
+ if (pid == ANY_PID)
+ /* XXX - could set child but we don't have a handle on what waitchld
+ reaps. Leave termination_state alone. */
+ goto wait_for_return;
}
while (PRUNNING (child) || (job != NO_JOB && RUNNING (job)));
return r;
}
+/* Wait for any background job started by this shell to finish. Very
+ similar to wait_for_background_pids(). Returns the exit status of
+ the next exiting job, -1 if there are no background jobs. The caller
+ is responsible for translating -1 into the right return value. */
+int
+wait_for_any_job ()
+{
+ pid_t pid;
+ int i, r, waited_for;
+ sigset_t set, oset;
+
+ if (jobs_list_frozen)
+ return -1;
+
+ /* First see if there are any unnotified dead jobs that we can report on */
+ BLOCK_CHILD (set, oset);
+ for (i = 0; i < js.j_jobslots; i++)
+ {
+ if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i) == 0)
+ {
+return_job:
+ r = job_exit_status (i);
+ notify_of_job_status (); /* XXX */
+ delete_job (i, 0);
+#if defined (COPROCESS_SUPPORT)
+ coproc_reap ();
+#endif
+ UNBLOCK_CHILD (oset);
+ return r;
+ }
+ }
+ UNBLOCK_CHILD (oset);
+
+ /* At this point, we have no dead jobs in the jobs table. Wait until we
+ get one, even if it takes multiple pids exiting. */
+ for (waited_for = 0;;)
+ {
+ /* Make sure there is a background job to wait for */
+ BLOCK_CHILD (set, oset);
+ for (i = 0; i < js.j_jobslots; i++)
+ if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0)
+ break;
+ if (i == js.j_jobslots)
+ {
+ UNBLOCK_CHILD (oset);
+ return -1;
+ }
+
+ UNBLOCK_CHILD (oset);
+
+ QUIT;
+ CHECK_TERMSIG;
+ CHECK_WAIT_INTR;
+
+ errno = 0;
+ r = wait_for (ANY_PID); /* special sentinel value for wait_for */
+ if (r == -1 && errno == ECHILD)
+ mark_all_jobs_as_dead ();
+
+ /* Now we see if we have any dead jobs and return the first one */
+ BLOCK_CHILD (set, oset);
+ for (i = 0; i < js.j_jobslots; i++)
+ if (jobs[i] && DEADJOB (i))
+ goto return_job;
+ UNBLOCK_CHILD (oset);
+ }
+
+ return -1;
+}
+
/* Print info about dead jobs, and then delete them from the list
of known jobs. This does not actually delete jobs when the
shell is not interactive, because the dead jobs are not marked
/* A value which cannot be a process ID. */
#define NO_PID (pid_t)-1
+#define ANY_PID (pid_t)-1
+
/* System calls. */
#if !defined (HAVE_UNISTD_H)
extern pid_t fork (), getpid (), getpgrp ();
extern void wait_for_background_pids __P((void));
extern int wait_for __P((pid_t));
extern int wait_for_job __P((int));
+extern int wait_for_any_job __P((void));
extern void notify_and_cleanup __P((void));
extern void reap_dead_jobs __P((void));
{
/* oops. table is full. punt. */
fprintf (stderr, _("register_alloc: alloc table is full with FIND_ALLOC?\n"));
- if (blocked_sigs)
- _malloc_unblock_signals (&set, &oset);
+ if (blocked_sigs)
+ _malloc_unblock_signals (&set, &oset);
return;
}
#if 0
fprintf (stderr, "register_free: %p not in allocation table?\n", mem);
#endif
- if (blocked_sigs)
- _malloc_unblock_signals (&set, &oset);
+ if (blocked_sigs)
+ _malloc_unblock_signals (&set, &oset);
return;
}
if (tentry->flags & MT_FREE)
REDIRECTION_ERROR (redirector, r, fd);
}
- if (flags & RX_UNDOABLE)
+ if ((flags & RX_UNDOABLE) && (redirect->rflags & REDIR_VARASSIGN) == 0)
{
/* Only setup to undo it if the thing to undo is active. */
if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1))
r = add_undo_redirect (redirector, ri, -1);
else
r = add_undo_close_redirect (redirector);
- if (r < 0 && (redirect->rflags & REDIR_VARASSIGN))
- close (redirector);
REDIRECTION_ERROR (r, errno, fd);
}
if (flags & RX_ACTIVE)
{
- if (flags & RX_UNDOABLE)
+ if ((flags & RX_UNDOABLE) && (redirect->rflags & REDIR_VARASSIGN) == 0)
{
/* Only setup to undo it if the thing to undo is active. */
if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1))
r = add_undo_redirect (redirector, ri, -1);
else
r = add_undo_close_redirect (redirector);
- if (r < 0 && (redirect->rflags & REDIR_VARASSIGN))
- close (redirector);
REDIRECTION_ERROR (r, errno, fd);
}
if ((flags & RX_ACTIVE) && (redir_fd != redirector))
{
- if (flags & RX_UNDOABLE)
+ if ((flags & RX_UNDOABLE) && (redirect->rflags & REDIR_VARASSIGN) == 0)
{
/* Only setup to undo it if the thing to undo is active. */
if (fcntl (redirector, F_GETFD, 0) != -1)
r = add_undo_redirect (redirector, ri, redir_fd);
else
r = add_undo_close_redirect (redirector);
- if (r < 0 && (redirect->rflags & REDIR_VARASSIGN))
- close (redirector);
REDIRECTION_ERROR (r, errno, -1);
}
#if defined (BUFFERED_INPUT)
}
r = 0;
+ /* XXX - only if REDIR_VARASSIGN not set? */
if ((flags & RX_UNDOABLE) && (fcntl (redirector, F_GETFD, 0) != -1))
{
r = add_undo_redirect (redirector, ri, -1);
dev_fd_list[parent_pipe_fd] = 0;
#endif /* HAVE_DEV_FD */
+ /* subshells shouldn't have this flag, which controls using the temporary
+ environment for variable lookups. */
+ expanding_redir = 0;
+
result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST));
#if !defined (HAVE_DEV_FD)
var_is_null = check_nullness && (var_is_set == 0 || *temp == 0);
/* XXX - this may not need to be restricted to special variables */
if (check_nullness)
- var_is_null |= var_is_special && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp);
+ var_is_null |= var_is_set && var_is_special && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp);
/* Get the rest of the stuff inside the braces. */
if (c && c != RBRACE)
if (tlist->word->flags & W_NOBRACE)
{
-itrace("brace_expand_word_list: %s: W_NOBRACE", tlist->word->word);
+/*itrace("brace_expand_word_list: %s: W_NOBRACE", tlist->word->word);*/
PREPEND_LIST (tlist, output_list);
continue;
}
{
int t;
- if (tlist->word->flags & W_ASSIGNASSOC)
+ if ((tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL)) == (W_ASSIGNASSOC|W_ASSNGLOBAL))
+ make_internal_declare (tlist->word->word, "-gA");
+ else if (tlist->word->flags & W_ASSIGNASSOC)
make_internal_declare (tlist->word->word, "-A");
+ else if (tlist->word->flags & W_ASSNGLOBAL)
+ make_internal_declare (tlist->word->word, "-g");
t = do_word_assignment (tlist->word, 0);
if (t == 0)
if (nfds == 0)
return;
-itrace("unlink_fifo_list");
for (i = 0; nfds && i < totfds; i++)
unlink_fifo (i);
dev_fd_list[parent_pipe_fd] = 0;
#endif /* HAVE_DEV_FD */
+ /* subshells shouldn't have this flag, which controls using the temporary
+ environment for variable lookups. */
+ expanding_redir = 0;
+
result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST));
#if !defined (HAVE_DEV_FD)
var_is_set = temp != (char *)0;
var_is_null = check_nullness && (var_is_set == 0 || *temp == 0);
+ /* XXX - this may not need to be restricted to special variables */
+ if (check_nullness)
+ var_is_null |= var_is_set && var_is_special && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp);
/* Get the rest of the stuff inside the braces. */
if (c && c != RBRACE)
{
int t;
- if (tlist->word->flags & W_ASSIGNASSOC)
+ if ((tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL)) == (W_ASSIGNASSOC|W_ASSNGLOBAL))
+ make_internal_declare (tlist->word->word, "-gA");
+ else if (tlist->word->flags & W_ASSIGNASSOC)
make_internal_declare (tlist->word->word, "-A");
+ else if (tlist->word->flags & W_ASSNGLOBAL)
+ make_internal_declare (tlist->word->word, "-g");
t = do_word_assignment (tlist->word, 0);
if (t == 0)
case 'v':
v = find_variable (arg);
- return (v && var_isset (v) ? TRUE : FALSE);
+ return (v && invisible_p (v) == 0 && var_isset (v) ? TRUE : FALSE);
case 'R':
v = find_variable (arg);
- return (v && var_isset (v) && nameref_p (v) ? TRUE : FALSE);
+ return (v && invisible_p (v) == 0 && var_isset (v) && nameref_p (v) ? TRUE : FALSE);
}
/* We can't actually get here, but this shuts up gcc. */
--- /dev/null
+/* test.c - GNU test program (ksb and mjb) */
+
+/* Modified to run with the GNU shell Apr 25, 1988 by bfox. */
+
+/* Copyright (C) 1987-2010 Free Software Foundation, Inc.
+
+ This file is part of GNU Bash, the Bourne Again SHell.
+
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bash is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Define PATTERN_MATCHING to get the csh-like =~ and !~ pattern-matching
+ binary operators. */
+/* #define PATTERN_MATCHING */
+
+#if defined (HAVE_CONFIG_H)
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include "bashtypes.h"
+
+#if !defined (HAVE_LIMITS_H) && defined (HAVE_SYS_PARAM_H)
+# include <sys/param.h>
+#endif
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <errno.h>
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+#if !defined (_POSIX_VERSION) && defined (HAVE_SYS_FILE_H)
+# include <sys/file.h>
+#endif /* !_POSIX_VERSION */
+#include "posixstat.h"
+#include "filecntl.h"
+#include "stat-time.h"
+
+#include "bashintl.h"
+
+#include "shell.h"
+#include "pathexp.h"
+#include "test.h"
+#include "builtins/common.h"
+
+#include <glob/strmatch.h>
+
+#if !defined (STRLEN)
+# define STRLEN(s) ((s)[0] ? ((s)[1] ? ((s)[2] ? strlen(s) : 2) : 1) : 0)
+#endif
+
+#if !defined (STREQ)
+# define STREQ(a, b) ((a)[0] == (b)[0] && strcmp ((a), (b)) == 0)
+#endif /* !STREQ */
+#define STRCOLLEQ(a, b) ((a)[0] == (b)[0] && strcoll ((a), (b)) == 0)
+
+#if !defined (R_OK)
+#define R_OK 4
+#define W_OK 2
+#define X_OK 1
+#define F_OK 0
+#endif /* R_OK */
+
+#define EQ 0
+#define NE 1
+#define LT 2
+#define GT 3
+#define LE 4
+#define GE 5
+
+#define NT 0
+#define OT 1
+#define EF 2
+
+/* The following few defines control the truth and false output of each stage.
+ TRUE and FALSE are what we use to compute the final output value.
+ SHELL_BOOLEAN is the form which returns truth or falseness in shell terms.
+ Default is TRUE = 1, FALSE = 0, SHELL_BOOLEAN = (!value). */
+#define TRUE 1
+#define FALSE 0
+#define SHELL_BOOLEAN(value) (!(value))
+
+#define TEST_ERREXIT_STATUS 2
+
+static procenv_t test_exit_buf;
+static int test_error_return;
+#define test_exit(val) \
+ do { test_error_return = val; longjmp (test_exit_buf, 1); } while (0)
+
+extern int sh_stat __P((const char *, struct stat *));
+
+static int pos; /* The offset of the current argument in ARGV. */
+static int argc; /* The number of arguments present in ARGV. */
+static char **argv; /* The argument list. */
+static int noeval;
+
+static void test_syntax_error __P((char *, char *)) __attribute__((__noreturn__));
+static void beyond __P((void)) __attribute__((__noreturn__));
+static void integer_expected_error __P((char *)) __attribute__((__noreturn__));
+
+static int unary_operator __P((void));
+static int binary_operator __P((void));
+static int two_arguments __P((void));
+static int three_arguments __P((void));
+static int posixtest __P((void));
+
+static int expr __P((void));
+static int term __P((void));
+static int and __P((void));
+static int or __P((void));
+
+static int filecomp __P((char *, char *, int));
+static int arithcomp __P((char *, char *, int, int));
+static int patcomp __P((char *, char *, int));
+
+static void
+test_syntax_error (format, arg)
+ char *format, *arg;
+{
+ builtin_error (format, arg);
+ test_exit (TEST_ERREXIT_STATUS);
+}
+
+/*
+ * beyond - call when we're beyond the end of the argument list (an
+ * error condition)
+ */
+static void
+beyond ()
+{
+ test_syntax_error (_("argument expected"), (char *)NULL);
+}
+
+/* Syntax error for when an integer argument was expected, but
+ something else was found. */
+static void
+integer_expected_error (pch)
+ char *pch;
+{
+ test_syntax_error (_("%s: integer expression expected"), pch);
+}
+
+/* Increment our position in the argument list. Check that we're not
+ past the end of the argument list. This check is supressed if the
+ argument is FALSE. Made a macro for efficiency. */
+#define advance(f) do { ++pos; if (f && pos >= argc) beyond (); } while (0)
+#define unary_advance() do { advance (1); ++pos; } while (0)
+
+/*
+ * expr:
+ * or
+ */
+static int
+expr ()
+{
+ if (pos >= argc)
+ beyond ();
+
+ return (FALSE ^ or ()); /* Same with this. */
+}
+
+/*
+ * or:
+ * and
+ * and '-o' or
+ */
+static int
+or ()
+{
+ int value, v2;
+
+ value = and ();
+ if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'o' && !argv[pos][2])
+ {
+ advance (0);
+ v2 = or ();
+ return (value || v2);
+ }
+
+ return (value);
+}
+
+/*
+ * and:
+ * term
+ * term '-a' and
+ */
+static int
+and ()
+{
+ int value, v2;
+
+ value = term ();
+ if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'a' && !argv[pos][2])
+ {
+ advance (0);
+ v2 = and ();
+ return (value && v2);
+ }
+ return (value);
+}
+
+/*
+ * term - parse a term and return 1 or 0 depending on whether the term
+ * evaluates to true or false, respectively.
+ *
+ * term ::=
+ * '-'('a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'k'|'p'|'r'|'s'|'u'|'w'|'x') filename
+ * '-'('G'|'L'|'O'|'S'|'N') filename
+ * '-t' [int]
+ * '-'('z'|'n') string
+ * '-o' option
+ * string
+ * string ('!='|'='|'==') string
+ * <int> '-'(eq|ne|le|lt|ge|gt) <int>
+ * file '-'(nt|ot|ef) file
+ * '(' <expr> ')'
+ * int ::=
+ * positive and negative integers
+ */
+static int
+term ()
+{
+ int value;
+
+ if (pos >= argc)
+ beyond ();
+
+ /* Deal with leading `not's. */
+ if (argv[pos][0] == '!' && argv[pos][1] == '\0')
+ {
+ value = 0;
+ while (pos < argc && argv[pos][0] == '!' && argv[pos][1] == '\0')
+ {
+ advance (1);
+ value = 1 - value;
+ }
+
+ return (value ? !term() : term());
+ }
+
+ /* A paren-bracketed argument. */
+ if (argv[pos][0] == '(' && argv[pos][1] == '\0') /* ) */
+ {
+ advance (1);
+ value = expr ();
+ if (argv[pos] == 0) /* ( */
+ test_syntax_error (_("`)' expected"), (char *)NULL);
+ else if (argv[pos][0] != ')' || argv[pos][1]) /* ( */
+ test_syntax_error (_("`)' expected, found %s"), argv[pos]);
+ advance (0);
+ return (value);
+ }
+
+ /* are there enough arguments left that this could be dyadic? */
+ if ((pos + 3 <= argc) && test_binop (argv[pos + 1]))
+ value = binary_operator ();
+
+ /* Might be a switch type argument */
+ else if (argv[pos][0] == '-' && argv[pos][2] == '\0')
+ {
+ if (test_unop (argv[pos]))
+ value = unary_operator ();
+ else
+ test_syntax_error (_("%s: unary operator expected"), argv[pos]);
+ }
+ else
+ {
+ value = argv[pos][0] != '\0';
+ advance (0);
+ }
+
+ return (value);
+}
+
+static int
+stat_mtime (fn, st, ts)
+ char *fn;
+ struct stat *st;
+ struct timespec *ts;
+{
+ int r;
+
+ r = sh_stat (fn, st);
+ if (r < 0)
+ return r;
+ *ts = get_stat_mtime (st);
+ return 0;
+}
+
+static int
+filecomp (s, t, op)
+ char *s, *t;
+ int op;
+{
+ struct stat st1, st2;
+ struct timespec ts1, ts2;
+ int r1, r2;
+
+ if ((r1 = stat_mtime (s, &st1, &ts1)) < 0)
+ {
+ if (op == EF)
+ return (FALSE);
+ }
+ if ((r2 = stat_mtime (t, &st2, &ts2)) < 0)
+ {
+ if (op == EF)
+ return (FALSE);
+ }
+
+ switch (op)
+ {
+ case OT: return (r1 < r2 || (r2 == 0 && timespec_cmp (ts1, ts2) < 0));
+ case NT: return (r1 > r2 || (r1 == 0 && timespec_cmp (ts1, ts2) > 0));
+ case EF: return (same_file (s, t, &st1, &st2));
+ }
+ return (FALSE);
+}
+
+static int
+arithcomp (s, t, op, flags)
+ char *s, *t;
+ int op, flags;
+{
+ intmax_t l, r;
+ int expok;
+
+ if (flags & TEST_ARITHEXP)
+ {
+ l = evalexp (s, &expok);
+ if (expok == 0)
+ return (FALSE); /* should probably longjmp here */
+ r = evalexp (t, &expok);
+ if (expok == 0)
+ return (FALSE); /* ditto */
+ }
+ else
+ {
+ if (legal_number (s, &l) == 0)
+ integer_expected_error (s);
+ if (legal_number (t, &r) == 0)
+ integer_expected_error (t);
+ }
+
+ switch (op)
+ {
+ case EQ: return (l == r);
+ case NE: return (l != r);
+ case LT: return (l < r);
+ case GT: return (l > r);
+ case LE: return (l <= r);
+ case GE: return (l >= r);
+ }
+
+ return (FALSE);
+}
+
+static int
+patcomp (string, pat, op)
+ char *string, *pat;
+ int op;
+{
+ int m;
+
+ m = strmatch (pat, string, FNMATCH_EXTFLAG|FNMATCH_IGNCASE);
+ return ((op == EQ) ? (m == 0) : (m != 0));
+}
+
+int
+binary_test (op, arg1, arg2, flags)
+ char *op, *arg1, *arg2;
+ int flags;
+{
+ int patmatch;
+
+ patmatch = (flags & TEST_PATMATCH);
+
+ if (op[0] == '=' && (op[1] == '\0' || (op[1] == '=' && op[2] == '\0')))
+ return (patmatch ? patcomp (arg1, arg2, EQ) : STREQ (arg1, arg2));
+ else if ((op[0] == '>' || op[0] == '<') && op[1] == '\0')
+ {
+ if (shell_compatibility_level > 40 && flags & TEST_LOCALE)
+ return ((op[0] == '>') ? (strcoll (arg1, arg2) > 0) : (strcoll (arg1, arg2) < 0));
+ else
+ return ((op[0] == '>') ? (strcmp (arg1, arg2) > 0) : (strcmp (arg1, arg2) < 0));
+ }
+ else if (op[0] == '!' && op[1] == '=' && op[2] == '\0')
+ return (patmatch ? patcomp (arg1, arg2, NE) : (STREQ (arg1, arg2) == 0));
+
+
+ else if (op[2] == 't')
+ {
+ switch (op[1])
+ {
+ case 'n': return (filecomp (arg1, arg2, NT)); /* -nt */
+ case 'o': return (filecomp (arg1, arg2, OT)); /* -ot */
+ case 'l': return (arithcomp (arg1, arg2, LT, flags)); /* -lt */
+ case 'g': return (arithcomp (arg1, arg2, GT, flags)); /* -gt */
+ }
+ }
+ else if (op[1] == 'e')
+ {
+ switch (op[2])
+ {
+ case 'f': return (filecomp (arg1, arg2, EF)); /* -ef */
+ case 'q': return (arithcomp (arg1, arg2, EQ, flags)); /* -eq */
+ }
+ }
+ else if (op[2] == 'e')
+ {
+ switch (op[1])
+ {
+ case 'n': return (arithcomp (arg1, arg2, NE, flags)); /* -ne */
+ case 'g': return (arithcomp (arg1, arg2, GE, flags)); /* -ge */
+ case 'l': return (arithcomp (arg1, arg2, LE, flags)); /* -le */
+ }
+ }
+
+ return (FALSE); /* should never get here */
+}
+
+
+static int
+binary_operator ()
+{
+ int value;
+ char *w;
+
+ w = argv[pos + 1];
+ if ((w[0] == '=' && (w[1] == '\0' || (w[1] == '=' && w[2] == '\0'))) || /* =, == */
+ ((w[0] == '>' || w[0] == '<') && w[1] == '\0') || /* <, > */
+ (w[0] == '!' && w[1] == '=' && w[2] == '\0')) /* != */
+ {
+ value = binary_test (w, argv[pos], argv[pos + 2], 0);
+ pos += 3;
+ return (value);
+ }
+
+#if defined (PATTERN_MATCHING)
+ if ((w[0] == '=' || w[0] == '!') && w[1] == '~' && w[2] == '\0')
+ {
+ value = patcomp (argv[pos], argv[pos + 2], w[0] == '=' ? EQ : NE);
+ pos += 3;
+ return (value);
+ }
+#endif
+
+ if ((w[0] != '-' || w[3] != '\0') || test_binop (w) == 0)
+ {
+ test_syntax_error (_("%s: binary operator expected"), w);
+ /* NOTREACHED */
+ return (FALSE);
+ }
+
+ value = binary_test (w, argv[pos], argv[pos + 2], 0);
+ pos += 3;
+ return value;
+}
+
+static int
+unary_operator ()
+{
+ char *op;
+ intmax_t r;
+
+ op = argv[pos];
+ if (test_unop (op) == 0)
+ return (FALSE);
+
+ /* the only tricky case is `-t', which may or may not take an argument. */
+ if (op[1] == 't')
+ {
+ advance (0);
+ if (pos < argc)
+ {
+ if (legal_number (argv[pos], &r))
+ {
+ advance (0);
+ return (unary_test (op, argv[pos - 1]));
+ }
+ else
+ return (FALSE);
+ }
+ else
+ return (unary_test (op, "1"));
+ }
+
+ /* All of the unary operators take an argument, so we first call
+ unary_advance (), which checks to make sure that there is an
+ argument, and then advances pos right past it. This means that
+ pos - 1 is the location of the argument. */
+ unary_advance ();
+ return (unary_test (op, argv[pos - 1]));
+}
+
+int
+unary_test (op, arg)
+ char *op, *arg;
+{
+ intmax_t r;
+ struct stat stat_buf;
+ SHELL_VAR *v;
+
+ switch (op[1])
+ {
+ case 'a': /* file exists in the file system? */
+ case 'e':
+ return (sh_stat (arg, &stat_buf) == 0);
+
+ case 'r': /* file is readable? */
+ return (sh_eaccess (arg, R_OK) == 0);
+
+ case 'w': /* File is writeable? */
+ return (sh_eaccess (arg, W_OK) == 0);
+
+ case 'x': /* File is executable? */
+ return (sh_eaccess (arg, X_OK) == 0);
+
+ case 'O': /* File is owned by you? */
+ return (sh_stat (arg, &stat_buf) == 0 &&
+ (uid_t) current_user.euid == (uid_t) stat_buf.st_uid);
+
+ case 'G': /* File is owned by your group? */
+ return (sh_stat (arg, &stat_buf) == 0 &&
+ (gid_t) current_user.egid == (gid_t) stat_buf.st_gid);
+
+ case 'N':
+ return (sh_stat (arg, &stat_buf) == 0 &&
+ stat_buf.st_atime <= stat_buf.st_mtime);
+
+ case 'f': /* File is a file? */
+ if (sh_stat (arg, &stat_buf) < 0)
+ return (FALSE);
+
+ /* -f is true if the given file exists and is a regular file. */
+#if defined (S_IFMT)
+ return (S_ISREG (stat_buf.st_mode) || (stat_buf.st_mode & S_IFMT) == 0);
+#else
+ return (S_ISREG (stat_buf.st_mode));
+#endif /* !S_IFMT */
+
+ case 'd': /* File is a directory? */
+ return (sh_stat (arg, &stat_buf) == 0 && (S_ISDIR (stat_buf.st_mode)));
+
+ case 's': /* File has something in it? */
+ return (sh_stat (arg, &stat_buf) == 0 && stat_buf.st_size > (off_t) 0);
+
+ case 'S': /* File is a socket? */
+#if !defined (S_ISSOCK)
+ return (FALSE);
+#else
+ return (sh_stat (arg, &stat_buf) == 0 && S_ISSOCK (stat_buf.st_mode));
+#endif /* S_ISSOCK */
+
+ case 'c': /* File is character special? */
+ return (sh_stat (arg, &stat_buf) == 0 && S_ISCHR (stat_buf.st_mode));
+
+ case 'b': /* File is block special? */
+ return (sh_stat (arg, &stat_buf) == 0 && S_ISBLK (stat_buf.st_mode));
+
+ case 'p': /* File is a named pipe? */
+#ifndef S_ISFIFO
+ return (FALSE);
+#else
+ return (sh_stat (arg, &stat_buf) == 0 && S_ISFIFO (stat_buf.st_mode));
+#endif /* S_ISFIFO */
+
+ case 'L': /* Same as -h */
+ case 'h': /* File is a symbolic link? */
+#if !defined (S_ISLNK) || !defined (HAVE_LSTAT)
+ return (FALSE);
+#else
+ return ((arg[0] != '\0') &&
+ (lstat (arg, &stat_buf) == 0) && S_ISLNK (stat_buf.st_mode));
+#endif /* S_IFLNK && HAVE_LSTAT */
+
+ case 'u': /* File is setuid? */
+ return (sh_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISUID) != 0);
+
+ case 'g': /* File is setgid? */
+ return (sh_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISGID) != 0);
+
+ case 'k': /* File has sticky bit set? */
+#if !defined (S_ISVTX)
+ /* This is not Posix, and is not defined on some Posix systems. */
+ return (FALSE);
+#else
+ return (sh_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISVTX) != 0);
+#endif
+
+ case 't': /* File fd is a terminal? */
+ if (legal_number (arg, &r) == 0)
+ return (FALSE);
+ return ((r == (int)r) && isatty ((int)r));
+
+ case 'n': /* True if arg has some length. */
+ return (arg[0] != '\0');
+
+ case 'z': /* True if arg has no length. */
+ return (arg[0] == '\0');
+
+ case 'o': /* True if option `arg' is set. */
+ return (minus_o_option_value (arg) == 1);
+
+ case 'v':
+ v = find_variable (arg);
+ return (v && invisible_p (v) == 0 && var_isset (v) ? TRUE : FALSE);
+
+ case 'R':
+ v = find_variable (arg);
+ return (v && var_isset (v) && nameref_p (v) ? TRUE : FALSE);
+ }
+
+ /* We can't actually get here, but this shuts up gcc. */
+ return (FALSE);
+}
+
+/* Return TRUE if OP is one of the test command's binary operators. */
+int
+test_binop (op)
+ char *op;
+{
+ if (op[0] == '=' && op[1] == '\0')
+ return (1); /* '=' */
+ else if ((op[0] == '<' || op[0] == '>') && op[1] == '\0') /* string <, > */
+ return (1);
+ else if ((op[0] == '=' || op[0] == '!') && op[1] == '=' && op[2] == '\0')
+ return (1); /* `==' and `!=' */
+#if defined (PATTERN_MATCHING)
+ else if (op[2] == '\0' && op[1] == '~' && (op[0] == '=' || op[0] == '!'))
+ return (1);
+#endif
+ else if (op[0] != '-' || op[2] == '\0' || op[3] != '\0')
+ return (0);
+ else
+ {
+ if (op[2] == 't')
+ switch (op[1])
+ {
+ case 'n': /* -nt */
+ case 'o': /* -ot */
+ case 'l': /* -lt */
+ case 'g': /* -gt */
+ return (1);
+ default:
+ return (0);
+ }
+ else if (op[1] == 'e')
+ switch (op[2])
+ {
+ case 'q': /* -eq */
+ case 'f': /* -ef */
+ return (1);
+ default:
+ return (0);
+ }
+ else if (op[2] == 'e')
+ switch (op[1])
+ {
+ case 'n': /* -ne */
+ case 'g': /* -ge */
+ case 'l': /* -le */
+ return (1);
+ default:
+ return (0);
+ }
+ else
+ return (0);
+ }
+}
+
+/* Return non-zero if OP is one of the test command's unary operators. */
+int
+test_unop (op)
+ char *op;
+{
+ if (op[0] != '-' || op[2] != 0)
+ return (0);
+
+ switch (op[1])
+ {
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f': case 'g': case 'h': case 'k': case 'n':
+ case 'o': case 'p': case 'r': case 's': case 't':
+ case 'u': case 'v': case 'w': case 'x': case 'z':
+ case 'G': case 'L': case 'O': case 'S': case 'N':
+ return (1);
+ }
+
+ return (0);
+}
+
+static int
+two_arguments ()
+{
+ if (argv[pos][0] == '!' && argv[pos][1] == '\0')
+ return (argv[pos + 1][0] == '\0');
+ else if (argv[pos][0] == '-' && argv[pos][2] == '\0')
+ {
+ if (test_unop (argv[pos]))
+ return (unary_operator ());
+ else
+ test_syntax_error (_("%s: unary operator expected"), argv[pos]);
+ }
+ else
+ test_syntax_error (_("%s: unary operator expected"), argv[pos]);
+
+ return (0);
+}
+
+#define ANDOR(s) (s[0] == '-' && !s[2] && (s[1] == 'a' || s[1] == 'o'))
+
+/* This could be augmented to handle `-t' as equivalent to `-t 1', but
+ POSIX requires that `-t' be given an argument. */
+#define ONE_ARG_TEST(s) ((s)[0] != '\0')
+
+static int
+three_arguments ()
+{
+ int value;
+
+ if (test_binop (argv[pos+1]))
+ {
+ value = binary_operator ();
+ pos = argc;
+ }
+ else if (ANDOR (argv[pos+1]))
+ {
+ if (argv[pos+1][1] == 'a')
+ value = ONE_ARG_TEST(argv[pos]) && ONE_ARG_TEST(argv[pos+2]);
+ else
+ value = ONE_ARG_TEST(argv[pos]) || ONE_ARG_TEST(argv[pos+2]);
+ pos = argc;
+ }
+ else if (argv[pos][0] == '!' && argv[pos][1] == '\0')
+ {
+ advance (1);
+ value = !two_arguments ();
+ }
+ else if (argv[pos][0] == '(' && argv[pos+2][0] == ')')
+ {
+ value = ONE_ARG_TEST(argv[pos+1]);
+ pos = argc;
+ }
+ else
+ test_syntax_error (_("%s: binary operator expected"), argv[pos+1]);
+
+ return (value);
+}
+
+/* This is an implementation of a Posix.2 proposal by David Korn. */
+static int
+posixtest ()
+{
+ int value;
+
+ switch (argc - 1) /* one extra passed in */
+ {
+ case 0:
+ value = FALSE;
+ pos = argc;
+ break;
+
+ case 1:
+ value = ONE_ARG_TEST(argv[1]);
+ pos = argc;
+ break;
+
+ case 2:
+ value = two_arguments ();
+ pos = argc;
+ break;
+
+ case 3:
+ value = three_arguments ();
+ break;
+
+ case 4:
+ if (argv[pos][0] == '!' && argv[pos][1] == '\0')
+ {
+ advance (1);
+ value = !three_arguments ();
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ value = expr ();
+ }
+
+ return (value);
+}
+
+/*
+ * [:
+ * '[' expr ']'
+ * test:
+ * test expr
+ */
+int
+test_command (margc, margv)
+ int margc;
+ char **margv;
+{
+ int value;
+ int code;
+
+ USE_VAR(margc);
+
+ code = setjmp (test_exit_buf);
+
+ if (code)
+ return (test_error_return);
+
+ argv = margv;
+
+ if (margv[0] && margv[0][0] == '[' && margv[0][1] == '\0')
+ {
+ --margc;
+
+ if (margv[margc] && (margv[margc][0] != ']' || margv[margc][1]))
+ test_syntax_error (_("missing `]'"), (char *)NULL);
+
+ if (margc < 2)
+ test_exit (SHELL_BOOLEAN (FALSE));
+ }
+
+ argc = margc;
+ pos = 1;
+
+ if (pos >= argc)
+ test_exit (SHELL_BOOLEAN (FALSE));
+
+ noeval = 0;
+ value = posixtest ();
+
+ if (pos != argc)
+ test_syntax_error (_("too many arguments"), (char *)NULL);
+
+ test_exit (SHELL_BOOLEAN (value));
+}
+recho "A${*:-w}R"
+recho "A${*-w}R"
+recho "A${*}R"
+
set -- ""
recho "A${*:-w}R"
--- /dev/null
+set -- ""
+
+recho "A${*:-w}R"
+recho "A${*-w}R"
+recho "A${*}R"
exec {v}> $TMPFILE;
echo $v
}
-11
+10
line 1
line 2
line 3
echo $v
}
-11
+10
foo 1
foo 2
foo 3
-11
+10
/bin/bash
/bin/csh
/bin/ksh
/bin/zsh
./vredir3.sub: line 4: v: ambiguous redirect
after
-11 12
+10 11
a
a
swizzle is a function
exec {stdin}<&$fd0;
exec {stdout}>&$fd1
}
-13 11
+12 10
a
a
swizzle is a function
{
wait_signal_received = sig;
if (interrupt_immediately)
-{
-itrace("trap_handler: interrupt_immediately = 1: calling longjmp to wait_intr_buf: sig = %d", sig);
longjmp (wait_intr_buf, 1);
-}
}
#if defined (READLINE)
return ((SHELL_VAR *)NULL);
}
-itrace("make_local_variable: name = %s old_var = %p variable_context = %d", name, old_var, variable_context);
if (old_var == 0)
new_var = make_new_variable (name, vc->table);
else
setifs (var);
else
#endif
+if (ifsname (name))
+ itrace("assign_in_env: name = %s value = <%s> flags = %d context = %d", name, value, flags, variable_context);
+
if (flags)
stupidly_hack_special_variables (name);
int is_subshell;
HASH_TABLE *tempvars;
{
-itrace("push_context: %s: subshell = %d variable_context = %d", name, is_subshell, variable_context);
if (is_subshell == 0)
push_dollar_vars ();
variable_context++;
void
pop_context ()
{
-itrace("pop_context: variable_context = %d", variable_context);
pop_dollar_vars ();
variable_context--;
pop_var_context ();