From 898cc92e1b58f706269e97aa7b148ca55583c4c1 Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Sat, 3 Dec 2011 13:33:56 -0500 Subject: [PATCH] commit bash-20040729 snapshot --- CWRU/CWRU.chlog | 88 + CWRU/CWRU.chlog~ | 9765 ++++++++++++++++++++++++++++++++++++++++ MANIFEST | 2 +- arrayfunc.c | 2 +- arrayfunc.c~ | 763 ++++ bashline.c | 15 +- bashline.c~ | 3053 +++++++++++++ builtins/Makefile.in | 3 +- builtins/Makefile.in~ | 621 +++ builtins/command.def | 2 +- builtins/command.def~ | 207 + builtins/fc.def | 16 +- builtins/fc.def~ | 633 +++ builtins/hash.def | 2 +- builtins/hash.def~ | 268 ++ builtins/reserved.def | 7 +- builtins/reserved.def~ | 201 + builtins/trap.def | 11 +- builtins/trap.def~ | 263 ++ doc/bashref.texi | 11 +- doc/bashref.texi~ | 7273 ++++++++++++++++++++++++++++++ doc/version.texi | 6 +- doc/version.texi~ | 10 + general.c | 7 +- general.c~ | 964 ++++ include/shmbutil.h | 6 + include/shmbutil.h~ | 438 ++ jobs.c | 12 +- jobs.c~ | 3535 +++++++++++++++ lib/readline/misc.c | 2 + lib/readline/misc.c~ | 508 +++ locale.c | 21 +- locale.c~ | 479 ++ sig.c | 47 +- sig.c~ | 525 +++ subst.c | 28 +- subst.c~ | 7410 ++++++++++++++++++++++++++++++ tests/RUN-ONE-TEST | 2 +- tests/RUN-ONE-TEST~ | 9 + tests/histexp.right | 2 +- tests/histexp.tests | 2 + tests/history.right | 4 +- tests/history.tests | 2 + variables.c | 1 + variables.c~ | 4090 +++++++++++++++++ 45 files changed, 41252 insertions(+), 64 deletions(-) create mode 100644 CWRU/CWRU.chlog~ create mode 100644 arrayfunc.c~ create mode 100644 bashline.c~ create mode 100644 builtins/Makefile.in~ create mode 100644 builtins/command.def~ create mode 100644 builtins/fc.def~ create mode 100644 builtins/hash.def~ create mode 100644 builtins/reserved.def~ create mode 100644 builtins/trap.def~ create mode 100644 doc/bashref.texi~ create mode 100644 doc/version.texi~ create mode 100644 general.c~ create mode 100644 include/shmbutil.h~ create mode 100644 jobs.c~ create mode 100644 lib/readline/misc.c~ create mode 100644 locale.c~ create mode 100644 sig.c~ create mode 100644 subst.c~ create mode 100755 tests/RUN-ONE-TEST~ create mode 100644 variables.c~ diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 3268ef268..ecfcc0cdb 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -9675,3 +9675,91 @@ general.c externs.h - add declaration for zcatfd + +tests/{history,histexp}.tests + - unset HISTFILESIZE to avoid problems if a value of 0 is inherited + from the environment + + 7/30 + ---- +bashline.c + - small changes to glob_expand_word to perform tilde expansion before + attempting globbing + +builtins/Makefile.in + - fix the install-help target to not cd into the `helpfiles' + subdirectory, so a value of $INSTALL_DATA containing a relative + pathname (e.g., .././support/install.sh) remains valid + + 7/31 + ---- +subst.c + - new function, mbstrlen(s), returns length of a multibyte character + string + +include/shmbutil.h + - new macro, MB_STRLEN(s), calls mbstrlen or STRLEN as appropriate + +builtins/trap.def + - small change so that a first argument that's a valid signal number + (digits only -- no symbolic names) will be treated as a signal and + reverted back to the original handling disposition. Fixes debian + complaints + +subst.c + - call MB_STRLEN instead of STRLEN where appropriate in + parameter_brace_expand_length to handle multibyte characters properly + - call MB_STRLEN instead of strlen in verify_substring_values so that + negative substrings of strings with multibyte chars work properly + + 8/1 + --- +jobs.c + - describe_pid needs to write to stderr, not stdout (POSIX) + - start_job, since it's only used by builtins (fg/bg), needs to write + its output to stdout, not stderr (POSIX) + +sig.c + - add an `orig_flags' member to struct terminating_signal so the + original signal handling flags (SA_RESTART, etc.) can be preserved + on POSIX systems + - make sure to preserve the signal flags state in + initialize_terminating_signals and reset them for child processes + in reset_terminating_signals + +builtins/fc.def + - fixed an off-by-one error that caused `fc -l' to list one too many + history entries + - in posix mode, `fc' should not list any indication as to whether or + not history lines have been modified (POSIX) + - when in posix mode, the default editor for `fc' should be `ed' (POSIX) + +doc/bashref.texi + - updated the description of `trap' behavior when given a first + argument that is a valid signal number + - noted that `fc -l' won't indicate whether a history entry has been + modified if the shell is in posix mode + +builtins/command.def + - fixed bug: `command -v' is supposed to be silent if a command is not + found + +builtins/hash.def + - `hash' should print its `hash table empty' message to stderr + +lib/readline/misc.c + - back out 7/7 change to _rl_maybe_save_line; it breaks emacs-mode ^P + +general.c + - changed base_pathname so that it will return reasonable results for + non-absolute pathnames -- this is what is intended by all of its + callers + +arrayfunc.c + - fix array_variable_part to return NULL if it finds an invisible + variable in the hash table. Fixes seg fault caused by referring to + unset local variable using array notation + +{locale,variables}.c + - support LC_TIME as a special locale variable so HISTTIMEFORMAT tracks + the current locale diff --git a/CWRU/CWRU.chlog~ b/CWRU/CWRU.chlog~ new file mode 100644 index 000000000..ecfcc0cdb --- /dev/null +++ b/CWRU/CWRU.chlog~ @@ -0,0 +1,9765 @@ + 4/9/2001 + -------- +[bash-2.05 released] + + 4/10 + ---- +redir.c + - check return value of fclose() in write_here_document() for error + returns; don't just rely on fwrite() failing + +support/bashbug.sh + - set TMPDIR to /tmp if it's null or unset + - use $TMPDIR in the TEMP tempfile name template + - fixed the call to `mktemp', if it exists, to make it more portable + +jobs.c + - if WCONTINUED is not defined, define it to 0 and add a define for + WIFCONTINUED(wstatus) which expands to 0 + - add WCONTINUED to the flags passed to waitpid(2) in waitchld() + - don't increment children_exited if waitpid's status is WIFCONTINUED, + since we don't want to call a SIGCHLD trap handler in this case + - in waitchld(), we set child->running to 1 if WIFCONTINUED(status) + is non-zero + - make sure pretty_print_job doesn't check for the core dump bit if + the process has been continued; it's only valid if the job is dead + - in set_job_status_and_cleanup, set the job to JRUNNING if job_state + is non-zero and the job was previously marked as JSTOPPED + +configure.in + - add -DBROKEN_DIRENT_D_INO to interix LOCAL_CFLAGS + +lib/glob/glob.c + - if BROKEN_DIRENT_D_INO is defined, define REAL_DIR_ENTRY to 1 + +jobs.c + - in kill_pid, we only need to block and unblock SIGCHLD if the + `group' argument is non-zero, since otherwise we just call `kill' + on the pid argument + +version.c + - update copyright date to 2001 + +bashline.c + - prog_complete_return needs to take a `const char *' as its first + argument + - history_completion_generator needs to take a `const char *' as + its first argument, and `text' needs to be a `const char *' + + 4/11 + ---- +redir.c + - fixed a weird typo in redir_special_open, case RF_DEVFD, added + call to all_digits before call to legal_number + - fixed do_redirection_internal to call legal_number instead of atol(3) + when translating r_duplicating_{in,out}put_word, so it handles + overflow better + - produce an error message in redirection_error for out-of-range + file descriptors + - change allocation strategy in redirection_error so we don't have to + malloc scratch memory if redirection_expand() fails + +jobs.h + - added defines for `running' member of a struct process + +general.c + - fix legal_number to return 0 when strtol(3) reports overflow or + underflow + +parse.y + - changed read_token_word to call legal_number instead of atoi(3) + +input.c + - return -1/EBADF from close_buffered_fd if fd is < 0 + +command.h + - fixed bogus comment about IS_DESCRIPTOR in description of the + REDIRECTEE struct + +print_cmd.c + - change cprintf's 'd' modifier code to display negative numbers as + an out-of-range value. We can do this only because the only use + of %d is to output file descriptor numbers in redirections + +support/mksignames.c + - need to include config.h to get a possible value for + UNUSABLE_RT_SIGNALS + + 4/16 + ---- +lib/readline/doc/rluser.texinfo + - corrected a small error in one description of M-DEL + + 4/17 + ---- +stringlib.c + - need to initialize `ind' before calls to RESIZE_MALLOCED_BUFFER + in strcreplace() + +support/bashversion.c + - new file, prints bash version information + +Makefile.in + - rules for building bashversion and linking it to version.o + + 4/24 + ---- +conftypes.h + - new file with HOSTTYPE, OSTYPE, MACHTYPE, etc. defines from + variables.h + +variables.h, version.c + - include conftypes.h + +patchlevel.h + - new file, contains define for PATCHLEVEL. Doing away with the old + scheme of having the information in configure.in + +version.c + - include patchlevel.h + +Makefile.in + - run bashversion -p to find patch level rather than have configure + substitute in a value + - pass -S ${top_srcdir} to support/mkversion.sh + +support/mkversion.sh + - don't put PATCHLEVEL define into version.h, but accept and ignore + a -p option + - take a new -S srcdir option + - find the patch level by parsing it out of patchlevel.h + +configure.in + - hard-code BASHVERS assignment instead of reading it from a file + - remove BASHPATCH; don't substitute it + +_distribution,_patchlevel + - removed + + 4/26 + ---- +shell.c + - call init_noninteractive() in open_shell_script if forced_interactive + is non-zero (the shell was started with -i) and fd_is_tty is 0 + (the script file is a real file, not something like /dev/stdin), + since it wasn't done earlier + +builtins/printf.def + - change for POSIX.2 compliance when conversion errors are encountered + when processing %d, %u, and floating point conversion operators + (print a warning message, return the value accumulated at the time + of the error -- which is always 0 -- and exit with a non-zero status) + +command.h + - added CMD_COMMAND_BUILTIN for use by the `command' builtin and the + code in execute_cmd.c + +builtins/command.def + - add CMD_COMMAND_BUILTIN to the created command's flags + + 5/1 + --- +configure.in + - add call to AC_C_CONST to test `const' compiler behavior + - add call to AC_C_INLINE to test `inline' compiler behavior + - add call to AC_C_STRINGIZE to test cpp #x stringizing operator + +config.h.in + - add `#undef const' for configure to substitute + - add `#undef inline' for configure to substitute + - add `#undef HAVE_STRINGIZE' for configure to substitute + +include/stdc.h + - remove code that defines or undefines `const' and `inline' + - change the __STRING macro to be defined depending on the value + of HAVE_STRINGIZE + +lib/malloc/malloc.c + - change the __STRING macro to be defined depending on the value + of HAVE_STRINGIZE + +lib/readline/{readline,rlprivate}.h + - moved rl_get_termcap to readline.h, making it a public function + +lib/readline/readline.h + - new #define, RL_READLINE_VERSION, hex-encoded library version + number, currently set to 0x0402 + - new public int variable, rl_readline_version + +lib/readline/readline.c + - #define RL_READLINE_VERSION if it is not already defined (which it + should be in readline.h) + - initialize rl_readline_version to RL_READLINE_VERSION + +lib/readline/doc/rltech.texinfo + - documented rl_get_termcap + - documented rl_readline_version + +jobs.c + - job_exit_status should return an int, not a WAIT (undetected + before because on most POSIX-like systems a WAIT is really an int) + +builtins/evalfile.c + - added FEVAL_REGFILE (file must be a regular file) to accepted + _evalfile flags + - fc_execute_file() adds FEVAL_REGFILE to _evalfile flags. This + means that startup files and files read with `.' no longer need + to be regular files + + 5/2 + --- + +lib/termcap/Makefile.in + - fix target for installed termcap library (normally unused) + +lib/tilde/Makefile.in + - fix install target to install in $(libdir) (normally unused) + +Makefile.in + - don't make $(man3dir) since there's nothing installed there + +Makefile.in,doc/Makefile.in + - change `man1ext' to `.1', `man3ext' to `.3' + - change appropriate install targets to use new values of man[13]ext + - use `test ...' instead of `[...]' + - add support for DESTDIR root installation prefix, for package + building (installdirs, install, install-strip, uninstall targets) + +builtins/common.c + - new function int get_exitstat(WORD_LIST *list) returns an eight-bit + exit status value for use in return, exit, logout builtins + +builtins/common.h + - extern declaration for get_exitstat() + +builtins/{exit,return}.def + - call get_exitstat where appropriate + +builtins/printf.def + - add support for "'" flag character as posix 1003.2-200x d6 says + - fix core dump when user-supplied field width or precision is 0 + - fix to printstr() to handle zero-length precision with `%b' format + specifier (printf '%.0b-%.0s\n' foo bar) + - fix to printstr() to treat a negative field width as a positive + field width with left-justification + - fix to mklong to avoid static buffers, which can always be overrun + by someone sufficiently motivated + +bashline.c + - change var in add_host_name to type `size_t' for passing to xrealloc + + 5/3 + --- +execute_cmd.c + - change restore_signal_mask to accept a sigset_t *, since a sigset_t + may not fit into a pointer, change call + +unwind_prot.c + - use a union UWP in restore_variable when restoring a variable whose + size is the same as sizeof(int), the reverse of the method used to + store it in unwind_protect_int + +builtins/printf.def + - use a #define LENMODS containing the length modifiers instead of + testing against each possible modifier character, save any mod + character found + - add support for ISO C99 length specifiers `j', `t', and `z' + - if `L' modifier is supplied with a floating point conversion char, + pass a `long double' to printf if HAVE_LONG_DOUBLE is defined + +configure.in,config.h.in + - call AC_C_LONG_DOUBLE to check for `long double'; define + HAVE_LONG_DOUBLE if supported + +bashline.c + - fix an inadvertantly-unclosed comment in attempt_shell_completion + - make set_saved_history return a value + - make dynamic_complete_history return a useful value + +{make_cmd,execute_cmd,shell,subst,trap,variables,input,unwind_prot,test, +pcomplete}.c + - removed some declared-but-unused variables + +builtins/{cd,enable,fc,set,setattr,type,umask,printf,complete}.def + - removed some declared-but-unused variables + +lib/sh/{zread,netopen}.c + - removed some declared-but-unused variables + +execute_cmd.c + - in execute_arith_command, use a long variable to hold the result + of evalexp(), since that's what it returns + +builtins/evalstring.c + - make cat_file return -1 on a read or write error + +lib/sh/stringlib.c + - make merge_stringlists() return the right value + + 5/7 + --- +pcomplete.c + - remove typo that caused empty declaration (;;) + +parse.y + - fix yyerror() to accept a single string argument; fix callers + +trap.c + - cast pointer to long instead of int when printing message with + internal_warning() in run_pending_traps() + +subst.c + - fix process_substitute to handle stdin being closed + +test.c + - change `while' to `if' in and() and or(), since the loop isn't + actually performed -- there's an unconditional `return' in the + loop body + - check for integer overflow of arguments to `-t' + +lib/sh/netopen.c + - change _getserv() to reject negative port/service numbers + +expr.c + - fix strlong() to not convert the base specification from long to + int before checking for overflow, since truncation on machines + where sizeof(int) != sizeof(long) may mask errors + +builtins/{jobs,kill,wait}.def + - use legal_number instead of atoi when converting strings to pid_t; + check for numeric overflow + +input.c + - fix for cygwin in b_fill_buffer -- off-by-one error when checking + buffer for \r\n termination + +general.h + - new #define INT_STRLEN_BOUND(t), computes max length of string + representing integer value of type T, possibly including a sign + character + - include if it's present + +{execute_cmd,findcmd,test}.c + - don't include , since general.h does it now + +{execute_cmd,lib/sh/itos,pcomplete,print_cmd,subst,variables}.c + - use INT_STRLEN_BOUND instead of static array sizes when converting + various strings to integer values + +shell.h + - struct fd_bitmap now uses an `int' size, since it's bounded by + the number of file descriptors, which must fit into an `int' + +execute_cmd.c + - FD_BITMAP_DEFAULT_SIZE is now 32, not 32L + - new_fd_bitmap takes an `int' size parameter, not a `long' + +execute_cmd.h + - change prototype for new_fd_bitmap() + +test.c + - fix test_stat to check for overflow when parsing the integer file + descriptor number; return ENOENT instead of EBADF for files that + are not open + +hashlib.c + - don't discard the upper 32 bits of the random value, if present + +lib/readline/shell.c + - use the same INT_STRLEN_BOUND mechanism to decide how much space to + allocated in sh_set_lines_and_columns + + 5/8 + --- +aclocal.m4 + - add check for libtinfo (termcap-specific portion of ncurses-5.2) to + BASH_CHECK_LIB_TERMCAP + - new macro, RL_LIB_READLINE_VERSION, checks version of installed + readline library and (optionally) writes version #defines to + config.h. Bash doesn't use the version defines + +configure.in + - call RL_LIB_READLINE_VERSION instead of support/rlvers.sh + +execute_cmd.c + - fix execute_shell_script and the WHITECHAR and STRINGCHAR macros + to check array bounds before indexing into the sample string + +unwind_prot.[ch] + - import new versions submitted by Paul Eggert + with a couple of changes for backwards compatibility, so the rest + of the source doesn't need to be changed yet + +jobs.c + - use unwind_protect_var on last_made_pid in run_sigchld_trap + +builtins/bind.def + - use unwind_protect_var on rl_outstream + +general.c + - rework print_rlimtype to use INT_STRLEN_BOUND and handle the + most negative number correctly + +expr.c + - `tokval' should have been a `long', since all arithmetic is done + as longs + +builtins/history.def + - consolidate tests for valid history position in one block to + avoid duplicate code and strings + +builtins/ulimit.def + - fix check for overflow when setting limit to work when int is 32 + bits and RLIMTYPE is 64 + +lib/sh/tmpfile.c + - don't truncate the result of time(3) to int; just use time_t, + since it's being assigned to an `unsigned long' + +mailcheck.c + - use legal_number instead of atoi in time_to_check_mail() to catch + more numeric errors; consolidate error checking in one block + - last_time_mail_checked should be a time_t + + 5/9 + --- +builtins/set.def + - recognize `set [-+]o nolog' if HISTORY is defined + +bashline.c + - new variable `dont_save_function_defs', set by `set -o nolog'; + currently ignored + +command.h + - the `dest' member of a REDIRECTEE is now an `int' + +parse.y,redir.c + - changed uses of `redir.test' (where redir is a REDIRECTEE) since + it's now an int + +lib/readline/rlstdc.h + - don't mess around with `const', rely on configure to supply a + proper definition if the compiler doesn't support it + +lib/tilde/tilde.h + - include if HAVE_CONFIG_H is defined + - don't mess around with `const', rely on configure + +builtins/shopt.def + - new read-only `shopt' option, login_shell, non-zero if shell is a + login shell (as decided by shell.c) + - new function set_login_shell(), sets shopt private value of + login_shell + +builtins/common.h + - new extern declaration for set_login_shell + +shell.c + - call set_login_shell after setting value of login_shell (in + main() and set_shell_name()) + +parse.y + - added new `\A' prompt string escape sequence: time in 24-hour + HH:MM format + +configure.in, config.h.in + - check for , define HAVE_GRP_H if found + +builtins/complete.def + - add new `-A group/-g' option to complete group names + +pcomplete.h + - new define for CA_GROUP, used with group name completion + +pcomplete.c + - add code to support CA_GROUP group name completion + +bashline.c + - new function, bash_groupname_completion_function(), supports + programmable completion of group names + +bashline.h + - extern declaration for bash_groupname_completion_function + +lib/readline/bind.c + - new inputrc variable, `match-hidden-files', controls completion + matching files beginning with a `.' (on Unix) + +lib/readline/complete.c + - new variable, _rl_match_hidden_files, mirrors `match-hidden-files' + inputrc variable + +lib/readline/rlprivate.h + - extern declaration for _rl_match_hidden_files + +builtins/hash.def + - new `-t' option to list hash values for each filename argument + +builtins/read.def + - alarm(3) takes an `unsigned int' argument, not int + - check for arithmetic overflow with -t and -n options + +input.c + - check for read error before doing \r\n translation on cygwin in + b_fill_buffer + - reset bp->b_used to 0 instead of leaving it at -1 on read error + in b_fill_buffer + +builtins/shopt.def + - new functions, shopt_setopt(name, mode) and + shopt_listopt(name, mode) to give the rest of the shell an easy + interface + +builtins/common.h + - extern declarations for shopt_setopt and shopt_listopt + +shell.c + - new invocation options -O and +O, to list or set/unset shopt + options like +o/-o sets and unsets `set -o' options + +doc/{bash.1,bashref.texi} + - document `set -o nolog' + - document `login_shell' shopt option + - document new `\A' prompt string escape sequence + - document new `-t' option to `hash' + - document new `[+-]O' invocation option + +doc/bashref.texi + - add text to `Invoking Bash' section defining a login shell; text + taken from man page + +doc/bash.1, lib/readline/doc/rluser.texinfo + - documented new complete/compgen `-A group/-g' option + +lib/readline/doc/{rluser.texinfo,readline.3}, doc/bash.1 + - documented new `match-hidden-files' inputrc variable + + 5/10 + ---- +configure.in + - fix AC_CHECK_PROG(ar, ...) + - add AC_CHECK_TYPE for ssize_t + +config.h.in + - new #undef for ssize_t + +lib/sh/zread.c + - int -> ssize_t fixes to mirror modern declarations of read and write + - the `off' variable in zsyncfd should be an off_t since it computes + a file offset + - the local buffer `lbuf' is now char, since it's not nice to pass + unsigned char * to read(2), and the values from it are assigned to + a char anyway + - lind and lused are now size_t, since they index into a buffer + - set lused to 0 on read error + +lib/sh/zwrite.c + - change second argument to type `char *', since ISO C says you have + to pass a `char *' to `write' + +externs.h + - fix extern declarations of zread, zread1, zreadc, and zwrite + - prototype extern declaration of qsort_string_compare + - add extern declaration for history_delimiting_chars() from parse.y + +input.h + - b_used and b_inputp members ofr struct BSTREAM are now size_t + +builtins/evalstring.c + - the number of chars read with zread in cat_file should be assigned + to a variable of type ssize_t + +input.c + - the number of chars read with zread in b_fill_buffer should be + assigned to a variable of type ssize_t + - `localbuf' is now type char[], since POSIX says you shouldn't pass + unsigned char * to read(2) + - in getc_with_restart(), use a variable of type unsigned char to + get a value from the local buffer and return it + - in ungetc_with_restart, explicitly return the character arg passed + to avoid relying on localbuf being unsigned char + +subst.c + - the number of chars read with zread in read_comsub should be + assigned to a variable of type ssize_t + +mksyntax.c + - instead of casting to unsigned char * in addcstr, use a variable + of type unsigned char and let the compiler do the work + +parse.y + - instead of casting to unsigned char * in yy_readline_get, use a + variable of type unsigned char and let the compiler do the work + - ditto for yy_string_get and shell_getc (cast to unsigned char) + +subst.c + - instead of casting to unsigned char when assigning to ifscmap in + expand_word_internal, use a variable of type unsigned char and + let the compiler do the work + +lib/sh/strtrans.c + - instead of casting to unsigned char in ansic_quote, use a variable + of type unsigned char and let the compiler do the work + +builtins/evalstring.c + - remove extern declarations for zwrite and run_trap_cleanup; they're + in externs.h + - prototype cat_file forward declaration + +Makefile.in + - remove -I$(includedir) from INCLUDES and SUBDIR_INCLUDES + +aclocal.m4 + - change RL_LIB_READLINE_VERSION to set RL_PREFIX, RL_LIBDIR, + and RL_INCLUDEDIR to what it used to test the installed readline + library version for use by the caller + - change RL_LIB_READLINE_VERSION to not compute ac_cv_rl_prefix if + the caller has already assigned it a value + - rename _rl_prefix -> ac_cv_rl_prefix, _rl_libdir -> ac_cv_rl_libdir, + _rl_includedir -> ac_cv_rl_includedir + +configure.in + - change testing of whether to use the value of + $opt_with_installed_readline to be != no, to allow the user to + specify a prefix where the installed readline library may be found + - if --with-installed-readline=PREFIX is supplied, set ac_cv_rl_prefix + to PREFIX before calling RL_LIB_READLINE_VERSION + - if --with-installed-readline[=PREFIX] is supplied, don't set + RL_LIBDIR and RL_INCLUDEDIR; let RL_LIB_READLINE_VERSION take care + of it, set RL_INCLUDE=-I${RL_INCLUDEDIR} + - if --with-installed-readline[=PREFIX] is supplied, and we're + linking with the history library, assign $RL_LIBDIR to HIST_LIBDIR + so we use the same version of the installed readline and history + libraries + +Makefile.in, builtins/Makefile.in + - have configure substitute RL_INCLUDEDIR, set RL_INCLUDEDIR variable + +doc/bashref.texi + - updated description of --with-installed-readline configure option + +general.c + - moved QSFUNC typedef here from builtins/common.c + +{alias,bashline,variables,lib/sh/stringvec}.c + - cast fourth argument to qsort to (QSFUNC *) + +alias.c + - prototype forward declaration of qsort_alias_compare + +bashhist.c + - include for extern declaration of glob_pattern_p + - remove extern declaration of history_delimiting_chars; it's now + in externs.h + - prototype forward declarations of histignore_item_func, + maybe_add_history, and bash_add_history + +bracecomp.c + - remove extern declaration for sh_backslash_quote; it's in externs.h + +braces.c + - remove extern declaration for extract_command_subst; it's in subst.h + - prototype forward declarations for expand_amble, array_concat, and + brace_gobbler + +error.c + - prototype extern declaration of give_terminal_to, fix bad call + +{execute_cmd,expr,findcmd,jobs,mailcheck,nojobs,pcomplete,print_cmd,redir, +shell}.c + - prototype all static forward function declarations + +pcomplete.c + - changed some function parameters to `const char *' to avoid discarding + const qualifier + +make_cmd.c + - make_bare_word, make_word_flags, and make_word now take a + `const char *' string argument + +make_cmd.h + - changed extern declarations for make_bare_word and make_word + +print_cmd.c + - cprintf now takes a `const char *' as its first argument, like + xprintf and printf + - the conditional define for xprintf should have been HAVE_VPRINTF, + not HAVE_VFPRINTF + +shell.c + - in isnetconn(), the return value of sizeof() is size_t + +aclocal.m4 + - add inclusion of stddef.h if STDC_HEADERS is defined to 1 in + BASH_CHECK_TYPE + +configure.in + - add a call to BASH_CHECK_TYPE for socklen_t (type of third argument + to getpeername(2)) + + 5/11 + ---- +lib/readline/bind.c + - make `useq' a char array to pass to rl_macro_bind in + rl_parse_and_bind + +lib/readline/{{bind,isearch}.c,rlprivate.h} + - _rl_isearch_terminators is now a char *, not unsigned char * + +{subst,variables,lib/sh/tmpfile}.c + - dollar_dollar_pid is now a `pid_t' instead of `int' + +variables.c + - sbrand() now takes an `unsigned long' to set the seed value + - changed last_random_value to type int, since it's always between + 0 and 32767 + - use strtoul to convert the value in assign_random instead of atoi + - take out casts in any arguments to sbrand() + - take out cast to int in call to inttostr in set_ppid() + +subst.c + - don't cast last_asynchronous_pid when passing to itos() + +{sig,subst}.c + - prototype all static forward function declarations + + 5/14 + ---- +{test,trap,variables}.c + - prototype all static forward function declarations + +variables.c + - free_variable_hash_data() now takes a PTR_T, a `generic pointer' + +builtins/{alias,bind,break,cd,complete,declare,enable,exit,fc,fg_bg,help, +history,jobs,pushd,read,set,trap,umask, + - prototype all static forward function declarations + +builtins/read.def + - reset_eol_delim now takes a `char *' arg, since that's what the + unwind_protect functions pass it, and it ignores its arguments + anyway + +lib/readline/{histsearch,input,kill,rltty,search,vi_mode}.c + - prototype all static forward function declarations + +lib/tilde/tilde.c + - prototype all static forward function declarations + - tilde_find_prefix, tilde_find_suffix, isolate_tilde_prefix, and + glue_prefix_and_suffix now take `const char *' arguments where + appropriate + +configure.in,config.h.in + - check for vsnprintf, define HAVE_VSNPRINTF if found + +lib/readline/display.c + - use vsnprintf() in rl_message if it's available; if we don't, at + least set the last character in msg_buf to 0 to avoid overrun -- + we really can't do anything about overflow at this point. if it's + available, this fixes buffer overflow problems in rl_message + + 5/15 + ---- +lib/readline/histexpand.c + - in get_history_word_specifier, allow any character to terminate + a `:first-' modifier, not just `:' and null. This is what csh + appears to do. This allows things like `!:0- xyzzy' to replace the + last argument with xyzzy + + 5/18 + ---- +configure.in, config.h.in + - check for , define HAVE_STDINT_H if found + - check for intmax_t in , define intmax_t as long if not + found + + 5/21 + ---- +builtins/kill.def + - change to use strerror() for error message when kill(2) fails + +aclocal.m4 + - new macro, BASH_C_LONG_LONG, check for `long long' + +configure.in, config.h.in + - call BASH_C_LONG_LONG, define HAVE_LONG_LONG if found + +lib/sh/snprintf.c + - new file, with implementations of snprintf, vsnprintf, asprintf, + and vasprintf, derived from inetutils version + +Makefile.in, lib/sh/Makefile.in + - add snprintf.c/snprintf.o + +configure.in, config.h.in + - add checks for snprintf, asprintf, vasprintf, with appropriate + cpp defines + +lib/readline/{rldefs,xmalloc}.h, lib/readline/xmalloc.c + - xmalloc and xrealloc now take `size_t' arguments, like their bash + counterparts + +externs.h,lib/sh/itos.c + - inttostr and itos now take `long' arguments + - inttostr takes a `size_t' argument for the buffer size + +{expr,lib/malloc/malloc,variables,general}.c + - fixed calls to itos() by removing casts, etc. + +subst.[ch] + - get_dollar_var_value now takes a long, not an int + - sub_append_number now takes a long, not an int + +subst.c + - in parameter_brace_expand_word, use a long and legal_number to + translate ${N}, to avoid overflow + - in parameter_brace_expand_length, use a long and legal_number to + translate ${#N}, to avoid overflow + - in do_array_element_assignment, array_expand_index, + array_value_internal, use arrayind_t instead of int + - let verify_substring_values take long * arguments for the return + value of evalexp() + - pass long * arguments to verify_substring_values in + parameter_brace_substring + - parameter_brace_expand_length now returns `long' + - parameter_brace_expand now uses a long variable for the return + value of parameter_brace_expand_length + - param_expand now uses a long variable for the return value from + evalexp + - array_length reference now returns an `arrayind_t', since it can + return the num_elements member of an array, which is of type + arrayind_t + +subst.h + - array_expand_index now returns an `arrayind_t' + +array.[ch] + - array_subrange now takes arrayind_t arguments, not `int' + - dup_array_subrange now uses arrayind_t local variable to do + array indexing + - use long to print array indices in print_element + +variables.c + - null_array_assign, assign_dirstack, bind_array_variable + now take arrayind_t arguments as array indices + - assign_array_var_from_word_list, assign_array_var_from_string, + unbind_array_element now use arrayind_t local variables for + array indexing + +variables.h + - change extern declaration of bind_array_variable + +builtins/common.[ch] + - get_numeric_arg now returns a `long', since it usually returns + the value of legal_number() + +builtins/{shift,break}.def + - use long variables for the return value of get_numeric_arg + +builtins/history.def + - convert string argument to int only if it's in range + +builtins/pushd.def + - set_dirstack_element and get_dirstack_element now take `long' + index arguments + - get_dirstack_index now takes a `long' index argument, since it's + passed the converted value from legal_number + +lib/sh/timeval.c + - in print_timeval, don't assume that the number of minutes fits into + an int, since it's just seconds/60. + +lib/sh/clock.c + - ditto for print_clock_t + + 5/22 + ---- +shell.c + - since the -O option settings may possibly be overridden by the + normal shell initialization or posix initialization, save the + invocation options on an alist (with add_shopt_to_alist) and + process them after basic initialization (with run_shopt_alist) + + 5/23 + ---- +trap.h + - new define, BASH_NSIG, all system signals plus special bash traps + +trap.c, builtins/trap.def + - use BASH_NSIG for array bounds and loops where appropriate + +trap.c + - change decode_signal to disallow numeric signal numbers above + NSIG -- this means you can only reference special traps like + DEBUG by name + - new SPECIAL_TRAP(s) macro to test whether s is one of the special + bash traps (currently DEBUG and EXIT) + - change reset_or_restore_signal_handlers so command substitution + doesn't inherit the debug trap (like ksh93), and child processes + don't have to rely on initialize_traps being run to get rid of + any debug trap + +support/mksignames.c + - add extra "ERR" signal name, value NSIG+1, allocate space for it + and write it out in signal_names[] + +trap.h + - new define: ERROR_TRAP == NSIG+1, change BASH_NSIG to NSIG+2 + - extern declarations for set_error_trap, run_error_trap + - new define: TRAP_STRING(s), expands to trap_list[s] if signal S + is trapped and not ignored, NULL otherwise + +trap.c + - add ERROR_TRAP to SPECIAL_TRAPS define + - initialize ERROR_TRAP stuff in initialize_traps + - new function: set_error_trap(command), sets the ERR trap string + - new function: run_error_trap(command), runs the ERR trap string + - set trap string for ERROR_TRAP to NULL in free_trap_strings + - change reset_or_restore_signal_handlers so child processes don't + inherit the ERR trap + - add case to call run_error_trap in maybe_call_trap_handler + +execute_cmd.c + - in execute_command_internal, keep track of ERR trap and call it if + necessary + - use TRAP_STRING to get the value of debug and error traps + - in execute_function, arrange things so the ERR trap is not inherited + by shell functions, and is saved and restored like the DEBUG trap + +doc/{bash.1,bashref.texi} + - documented new ERR trap + +tests/{trap.{tests,right},trap2.sub,trap2a.sub} + - added ERR trap tests + +subst.c + - on machines without /dev/fd, change the named pipe fifo list to a + list of structs containing pathname and proc information + - change unlink_fifo_list to kill the proc in the fifo list with + signal 0 and not remove the fifo if the proc is still alive. This + should fix the problem on those backward systems without /dev/fd + where fifos were removed when a job using process substitution was + suspended + + 5/24 + ---- +examples/loadables/getconf.h + - new file, with basic defines needed to make getconf work minimally + on POSIX systems without the necessary definitions + +examples/loadables/getconf.c + - replacement functions for confstr, sysconf, pathconf for systems + that lack them, providing a minimal posix interface + - heavily augmented getconf, now supports all POSIX.1-200x, + POSIX.2-200x, Solaris 7, AIX 4.2 getconf variables + + 5/29 + ---- +builtins/setattr.def + - make `readonly', `export', and `declare' print `invisible' variables + as just a command and variable name, without a value, when listing + all variables (as POSIX.2-200x d6 requires) + + 5/30 + ---- + +configure.in + - upgraded to autoconf-2.50 on main development machine, so require + autoconf-2.50 in preparation for using some if its new features + - call AC_C_PROTOTYPES + - remove call to AC_EXEEXT, which now does the wrong thing + - changed AC_INIT to new flavor + - added call to AC_CONFIG_SRCDIR + - AC_CONFIG_HEADER -> AC_CONFIG_HEADERS + - AC_RETSIGTYPE -> AC_TYPE_SIGNAL + +configure.in, aclocal.m4, config.h.in + - removed call to BASH_LARGE_FILE_SUPPORT, use AC_SYS_LARGEFILE + standard support, with new macros _FILE_OFFSET_BITS and + _LARGE_FILES + - removed definition of BASH_LARGE_FILE_SUPPORT + +doc/bashref.texi + - document new `--enable-largefile' configure option + +lib/readline/readline.c + - change rl_set_prompt to call rl_expand_prompt unconditionally, so + local_prompt and local_prompt_prefix get set correctly + + 6/6 + --- +lib/readline/complete.c + - don't append `/' or ` ' to a match when completing a symlink that + resolves to a directory, unless the match doesn't add anything + to the word. This means that a tab will complete the word up to + the full name, but not add anything, and a subsequent tab will add + a slash. Change to append_to_match; callers changed + +hashlib.c + - new function, hash_table_nentries (table), returns the number of + items in TABLE + +hashlib.h + - extern declaration for hash_table_nentries + +configure.in + - configure without bash malloc on openbsd; they claim it needs + eight-bit alignment (which the bash malloc provides, but...) + + 7/2 + --- +stringlib.c + - only call RESIZE_MALLOCED_BUFFER from strsub() if the replacement + string length is > 0, avoid possible hangs if replacement is null + +subst.c + - don't include input.h; no longer needed + +configure.in + - remove calls to AC_SYS_RESTARTABLE_SYSCALLS and + BASH_SYS_RESTARTABLE_SYSCALLS; the results are no longer used + +config.h.in + - remove define for HAVE_RESTARTABLE_SYSCALLS + +aclocal.m4 + - removed definition of BASH_SYS_RESTARTABLE_SYSCALLS; no longer used + +execute_cmd.c + - changed select command so `return' no longer terminates the select + command, so it can be used to return from an enclosing function. + This is as ksh (88 and 93) does it + +lib/readline/vi_mode.c + - fix trivial typo in declaration of vi_motion; `t' appears twice; + the second instance should be `T' + + 7/3 + --- +configure.in + - don't add -static to LDFLAGS on Solaris 2.x. This means that the + auxiliary programs will be built as dynamic executables, but that + should do no harm + + 7/5 + --- +lib/glob/fnmatch.c + - fix the code that processes **(pattern) to short-circuit if the + pattern is ill-formed or lacks a trailing `)' -- this fixes the + segfault on **(/*) + +Makefile.in, builtins/Makefile.in + - split CCFLAGS into CCFLAGS_FOR_BUILD and CFLAGS, to aid in + cross-compilation + - build programs that use $(CC_FOR_BUILD) using $(CCFLAGS_FOR_BUILD) + +configure.in, config.h.in + - check for getaddrinfo(3), define HAVE_GETADDRINFO if found + +lib/sh/netopen.c + - implemented a version of _netopen (_netopen6) that uses + getaddrinfo(3) if available, use if HAVE_GETADDRINFO is defined. + old _netopen is _netopen4; _netopen now calls either _netopen6 + or _netopen4 as appropriate + + 7/9 + --- +builtins/exit.def + - don't source ~/.bash_logout if subshell_environment is non-zero + +execute_command.c + - in execute_until_or_while, handle the case where `breaking' is + set in the loop test (e.g., by the job control code when a job + is stopped with SIGTSTP), but the return value from the test is + something that would cause the loop to break. Need to decrement + `breaking' in this case + + 7/10 + ---- +execute_cmd.c + - in execute_in_subshell, make sure a command of type cm_subshell + inherits its `enclosing' command's CMD_IGNORE_RETURN flag + +variables.c + - in maybe_make_export_env, don't allow restricted shells to put + exported functions in the export environment + + 7/11 + ---- +lib/glob/strmatch.h + - renamed old fnmatch.h + - changed guard #ifdef to _STRMATCH_H + - include system if HAVE_LIBC_FNM_EXTMATCH is defined + +lib/glob/strmatch.c + - renamed old fnmatch.c + - include "strmatch.h" + - if HAVE_LIBC_FNM_EXTMATCH is defined, define a dummy version of + strmatch() that just calls fnmatch(3) + +lib/glob/glob.c + - include "strmatch.h" + - fnmatch -> strmatch + +Makefile.in, lib/glob/Makefile.in + - fnmatch -> strmatch + +{bashhist,execute_cmd,pathexp,pcomplete,shell,stringlib,subst,test}.c, +pathexp.h,builtins/help.def + - include + - fnmatch -> strmatch + +execute_cmd.c + - broke the code that parses the interpreter name from a #! line + out from execute_shell_script to a new function, getinterp() + - call getinterp from execute_shell_script + - use return value from getinterp in error message about bad + #! interpreter in shell_execve + + 7/12 + ---- +lib/readline/isearch.c + - the last isearch string is now remembered in a new static variable, + last_isearch_string + - if ^R^R is typed, readline now searches for the remembered isearch + string, if one exists + + 7/24 + ---- +pcomplete.h + - extern declaration for completions_to_stringlist() + + 7/25 + ---- +builtins/complete.def + - make compgen handle -o default option + - make compgen return success only if sl->list_len is non-zero, + indicating that there are items on the list + + 7/31 + ---- +execute_cmd.c + - in execute_connection, force stdin to /dev/null for asynchronous + commands if job control is not active, not just if the shell is + running a shell script (since you can run `set -m' in a script) + +lib/readline/rltty.c + - make sure _rl_tty_restore_signals resets `tty_sigs_disabled' on + successful restoration of the terminal modes + - make sure _rl_tty_disable_signals turns off IXON so that ^S and + ^Q can be read by rl_quoted_insert + + 8/1 + --- +aclocal.m4 + - new check for FNM_EXTMATCH being defined in , as Ullrich + Drepper intends to do for new versions of GNU libc + +config.h.in + - new definition for HAVE_LIBC_FNM_EXTMATCH + +configure.in + - check for fnmatch, but don't define anything in config.h + - call BASH_FUNC_FNMATCH_EXTMATCH to check for FNM_EXTMATCH + + 8/2 + --- +alias.h + - remove bogus extern declaration for xmalloc() + - include "stdc.h" + - add prototype declarations for all extern function declarations + +xmalloc.c,lib/readline/xmalloc.c + - fix xmalloc to return a PTR_T + - fix xrealloc to return a PTR_T and take a PTR_T as first argument + +include/ansi_stdlib.h + - extern declarations for malloc and realloc have them return PTR_T + +xmalloc.h + - new file, with extern declarations for functions in xmalloc.c + +general.h + - removed extern declarations for functions in xmalloc.c + - include xmalloc.h + +Makefile.in,builtins/Makefile.in + - update dependencies to include xmalloc.h + +parse.y,{alias,array,bashline,bracecomp,execute_cmd,findcmd,flags,general, +hashcmd,locale,mailcheck,make_cmd,pathexp,pcomplete,print_cmd,stringlib, +subst,unwind_prot,variables}.c +builtins/{common,evalfile}.c +builtins/{cd,command,enable,exec,printf,read,set}.def +lib/sh/{makepath,netopen,pathphys,setlinebuf,shquote,snprintf,stringlist, +strtrans,tmpfile}.c +lib/readline/{util,terminal,shell,readline,macro,kill,isearch,input, +histfile,histexpand,display,complete,bind}.c + - make sure all calls to xmalloc are cast to the right return value + +siglist.c + - include xmalloc.h + +parse.y,{alias,bashline,bracecomp,expr,make_cmd,nojobs,print_cmd,subst}.c +builtins/{fc,printf,read}.def +lib/sh/snprintf.c, lib/tilde/tilde.c +lib/readline/{bind,display,histexpand,isearch,macro,util,vi_mode}.c + - make sure all calls to xrealloc are cast to the right return value + +lib/sh/{netopen,setlinebuf,shquote,snprintf}.c, lib/tilde/tilde.c + - include xmalloc.h, remove extern declaration of xmalloc + +lib/readline/xmalloc.h + - xmalloc and xrealloc should return PTR_T + +lib/readline/rldefs.h + - don't include an extern declaration for xmalloc + + 8/7 + --- +support/shobj-conf + - fixed up commented-out stanzas for HP's unbundled C compiler on + HP/UX + +support/bashbug.sh + - force the subject to be changed from the default + +lib/readline/doc/{rluser.texinfo,readline.3}, doc/bash.1 + - document that transpose-words swaps the last two words on the line + if point is at the end of the line + + 8/9 + --- +stringlib.c + - fix possible infinite recursion problem with null pattern in + strsub() + +hashlib.c + - new function copy_hash_table to copy a hash table using a caller- + supplied function to copy item data (defaults to savestring()) + +hashlib.h + - new extern declaration for copy_hash_table + +builtins/declare.def + - changes so that declare [-a] var=value assigns `value' to element 0 + of array variable `var' like ksh93 + - change so that declare [-a] var[N]=value assigns `value' to element + N of array variable `var' like ksh93 + + 8/13 + ---- +arrayfunc.c + - new file, for miscellaneous array functions + +arrayfunc.h + - new file, extern declarations for functions in arrayfunc.c + +variables.c + - move convert_var_to_array, bind_array_variable, + assign_array_from_string, assign_array_var_from_word_list, + assign_array_var_from_string, quote_array_assignment_chars, + skipsubscript, unbind_array_element, print_array_assignment + to arrayfunc.c + +shell.h + - include arrayfunc.h after variables.h + +variables.h + - remove above extern function declarations moved to arrayfunc.h + - add extern declaration for var_lookup + +Makefile.in + - add arrayfunc.c, arrayfunc.h in appropriate places + - add arrayfunc.h to dependencies + +subst.c + - move valid_array_reference, array_expand_index, array_variable_part, + array_value_internal, array_value (now global), get_array_value, + do_array_element_assignment to arrayfunc.c + +subst.h + - extern declarations for functions above moved to arrayfunc.h + +arrayfunc.h + - extern declarations for above functions from subst.c + +subst.[ch] + - string_list_dollar_star and string_list_dollar_at are now global + functions + - quote_escapes is now a global function + +subst.c + - maybe_expand_string -> expand_string_if_necessary + - expand_string_to_string -> expand_string_to_string_internal + - new functions: expand_string_to_string and + expand_string_unsplit_to_string, which call + expand_string_to_string_internal with expand_string and + expand_string_unsplit as the FUNC arguments, respectively + +arrayfunc.c + - change array_expand_index to call expand_string_to_string instead + of maybe_expand_string + + 8/14 + ---- +shell.c + - in execute_env_file, call expand_string_unsplit_to_string + +mailcheck.c + - in check_mail, call expand_string_to_string + +variables.c + - in assign_in_env, call expand_string_unsplit_to_string + +arrayfunc.c + - new function, array_variable_name, splits an array reference into + a name (which is returned as a new string) and subscript + - change array_variable_part to just call array_variable_name and + look up the string returned with find_variable + - new function, find_or_make_array_variable (name, flags) which will + look up an array variable and convert a string variable to an + array if necessary. The FLAGS argument, if non-zero, says to + check the readonly and noassign attributes and fail if either is set + +builtins/read.def + - make `read -a aname' honor any readonly status of `aname' + - read -a now calls find_or_make_array_variable with FLAGS value 1 + +arrayfunc.[ch], subst.c, builtins/{declare,read}.def + - do_array_element_assignment -> assign_array_element + + 8/20 + ---- +parse.y + - changed `for' command grammar to allow missing word list after `IN' + token, like latest POSIX drafts require + +lib/sh/tmpfile.c + - in sh_mktmpname(), check for filenum == 0 and init to non-zero number + in this case. it can happen on arithmetic overflow + +support/mkversion.sh + - added `[0-9].[0-9][0-9][a-z]' as an acceptable value for a + distribution to allow for intermediate versions, like 2.05a + +support/config.guess + - removed the addition of the output of `/usr/bin/objformat' when + creating the canonical name on FreeBSD machines, so the canonical + name is once again `freebsd4.2' instead of `freebsdelf4.2' + + 8/22 + ---- +lib/readline/{rlstdc,history,keymaps,readline,rldefs,rlprivate,rlshell, +rltypedefs,xmalloc}.h +lib/readline/{bind,compat,complete,display,funmap,histexpand,histsearch, +input,isearch,kill,nls,parens,readline,rltty,search,shell,signals,vi_mode + - changed __P to PARAMS + +lib/tilde/tilde.[ch] + - changed __P to PARAMS + +{Makefile,configure}.in + - changed the version number to 2.05a + - changed the release status to `alpha1' + + 8/23 + ---- +support/shobj-conf + - support for building shared libraries on Darwin/MacOS X + +siglist.h + - extern declaration for strsignal() to compensate for lack of + a definition in some system include files + +jobs.c + - remove casts from strsignal() calls + +[bash-2.05a-alpha1 frozen] + + 8/27 + ---- +[bash-2.05a-alpha1 released] + + 8/27 + ---- +execute_cmd.c + - fix eval_arith_for_expr to handle the case where the expanded + word list is NULL, returning 0 in this case + +print_cmd.c + - in print_function_def, make sure that func_redirects is assigned + a value before being used + + 8/28 + ---- +alias.c + - include for definition of isalpha() + +bashhist.h + - add prototypes for extern function declarations + +flags.c + - include bashhist.h for extern function declarations + +mksyntax.c + - include if HAVE_UNISTD_H is defined in config.h + +parse.y + - include test.h for extern function declarations + +externs.h + - change extern declaration for setlinebuf to sh_setlinebuf + +stringlib.c + - include for extern function declarations + +variables.h + - add function prototypes for all of the sv_* functions + +builtins/common.h + - add extern declarations for set_shellopts() and parse_shellopts() + from builtins/set.def + +variables.c + - include "hashcmd.h" for extern declaration for flush_hashed_filenames + - include "pathexp.h" for extern declaration for setup_glob_ignore + +lib/malloc/malloc.c + - cast to `long' instead of `int' in memalign for 64-bit machines + +{pcomplete,trap}.c + - changed printf escape sequences used to print pointers to %p + +lib/readline/undo.c + - include "xmalloc.h" for extern function declaration + +input.h + - add function prototypes to extern declarations for getc_with_restart + and ungetc_with_restart + +variables.[ch] + - changed type of `function' member of `struct name_and_function' to + `sv_func_t', which is defined and prototyped in variables.h + - map_over now takes an `sh_var_map_func_t *' + +shell.h + - start of a set of function pointer typedefs like those in + lib/readline/rltypedefs.h + +hashlib.[ch] + - second paramter to flush_hash_table is now an `sh_free_func_t *' + +trap.c + - parameter to reset_or_restore_signal_handlers is now an + `sh_resetsig_func_t *' + +pcomplete.h, pcomplib.c + - function pointer argument to print_all_compspecs is now an + `sh_csprint_func_t *' + - function pointer `list_getter' element of an `ITEMLIST' is now + prototyped with __P((...)) instead of using `Function *' + +jobs.[ch] + - `j_cleanup' member of a JOB is now an `sh_vptrfunc_t *' + +alias.c + - map_over_aliases now takes an `sh_alias_map_func_t *' + - free_alias_data now takes a `PTR_T' + +pathexp.c + - function pointer argument to ignore_globbed_names is now an + `sh_ignore_func_t *' + +bashline.c + - function pointer argument to _ignore_completion_names is now an + `sh_ignore_func_t *' + +pathexp.h,{bashhist,bashline.c + - `item_func' member of a `struct ignorevar' is now an + `sh_iv_item_func_t *' + +builtins/evalfile.c + - `errfunc' is now an `sh_vmsg_func_t *' + +jobs.c + - map_over_job now takes an `sh_job_map_func_t *' as its first argument + +array.[ch] + - function pointer argument to array_walk is now an + `sh_ae_map_func_t *' + +general.c + - tilde_expansion_preexpansion_hook has type `tilde_hook_func_t *', + and so the assignment in tilde_initialize doesn't need a cast + +list.c + - map_over_words now takes an `sh_icpfunc_t *' as its second argument + +input.h + - the `getter' and `ungetter' function pointer members of a + BASH_INPUT are now of types `sh_cget_func_t *' and + `sh_cunget_func_t *' respectively + - init_yy_io now takes an `sh_cget_func_t *' as its first argument and + an `sh_cunget_func_t *' as its second + +parse.y + - init_yy_io now takes an `sh_cget_func_t *' as its first argument and + an `sh_cunget_func_t *' as its second + - initialize_bash_input casts bash_input.getter and bash_input.ungetter + appropriately + +builtins/mkbuiltins.c + - make the extern function definitions written to builtext.h have + prototypes with __P((...)) + - include "stdc.h" + - change Function to mk_handler_func_t + - fixed comment_handler to take the right number of args + - prototyped all the handler functions with __P((...)) + +builtins.h + - the `function' member of a struct builtin is now of type + `sh_builtin_func_t *' + +builtins/common.[ch] + - last_shell_builtin, this_shell_builtin are now of type + `sh_builtin_func_t *' + - find_shell_builtin, builtin_address, find_special_builtin now return + `sh_builtin_func_t *' + +builtins/exit.def, {execute_cmd,jobs,nojobs,variables}.c, parse.y + - changed all declarations of last_shell_builtin and this_shell_builtin + +execute_cmd.c + - execute_builtin, execute_builtin_or_function, + execute_subshell_builtin_or_function now take an + `sh_builtin_func_t *' instead of a `Function *' for argument + - changed appropriate variables from `Function *' to + `sh_builtin_func_t *' + +builtins/{bind,builtin,enable,read,setattr}.def + - replaced uses of `Function *' in variable declarations with + appropriate types (sh_builtin_func_t * or rl_command_func_t *) + +builtins/set.def + - set_func and get_func members of binary_o_options are now of types + `setopt_set_func_t *' and `setopt_get_func_t *', which are + prototyped + +builtins/shopt.def + - set_func member of shopt_vars is now of type `shopt_set_func_t *' + +bashline.c + - enable_hostname_completion now returns `int' (the old value of + perform_hostname_completion) + +[The only use of Function and VFunction now is for unwind-protects] + + 9/4 + --- +lib/sh/getcwd.c + - use const define from config.h rather than `CONST' + - use PTR_T define from xmalloc.h rather than `PTR' + - include xmalloc.h for PTR_T + - remove PATH_MAX define, rely on value from maxpath.h + +{general,mailcheck}.c, lib/sh/{pathcanon,pathphys}.c + - don't include maxpath.h directly; it's already included by shell.h + +lib/sh/mailstat.c + - new `mailstat()' implementation, to stat a mailbox file for + mail checking. handles maildir-style mail directories with one + file per message and creates a dummy stat struct from them + +lib/sh/Makefile.in + - add mailstat.c and mailstat.o in the appropriate places + +lib/malloc/malloc.c + - augmented implementation with wrapper functions that pass in file + and line number information from cpp. currently unused, but a + placeholder for future debugging and use tracking + +lib/malloc/shmalloc.h + - new file, extern declarations for allocation wrapper functions for + use by the shell (and others, I guess) + +xmalloc.[ch] + - wrapper functions for xmalloc, xfree, xrealloc (sh_ prefixed) that + pass cpp line number information through to the malloc functions, + if USING_BASH_MALLOC is defined + + 9/5 + --- +lib/malloc/gmalloc.c + - removed; no longer part of distribution + +lib/malloc/Makefile.in + - removed references to gmalloc.[co] + +configure.in, doc/bashref.texi + - removed references to `--with-glibc-malloc' configure option + +{configure,Makefile}.in + - changed the way bash malloc is configured into the Makefile, making + it more like how readline is configured. If the bash malloc is + not configured in, nothing in lib/malloc will be built + + 9/6 + --- +lib/malloc/imalloc.h + - new file, some internal malloc definitions + +lib/malloc/mstats.h + - new file, definitions for malloc statistics structs and functions + +lib/malloc/trace.c + - new file, malloc tracing functions (currently just print messages + to stderr), code is #ifdef MALLOC_TRACE + +lib/malloc/stats.c + - new file, moved malloc stats code from malloc.c to here + +lib/malloc/malloc.c + - moved some definitions to imalloc.h + - moved stats code to stats.c + - malloc tracing calls added to internal_{malloc,realloc,free}, all + #ifdef MALLOC_TRACE + +lib/malloc/Makefile.in, Makefile.in + - added {imalloc,mstats}.h, {trace,stats}.c + +parse.y + - changed decode_prompt_string to save and restore $? + (last_command_exit_value) around calls to expand_prompt_string(), + so command substitutions in PS1, etc. don't change $? + +{array,subst}.c + - a couple more arrayind_t fixes from Paul Eggert + +configure.in + - remove redundant check for wait3(2) + +redir.h + - fixed a typo (stdin_redirs -> stdin_redirects) + + 9/10 + ---- +execute_cmd.c + - remove check for \n and \r from WHITESPACE macro, since those + chars are not whitespace as returned by the whitespace(c) macro + - getinterp now takes a `char *' as first arg, not unsigned char * + - execute_shell_script now takes a `char *' as first arg, not + unsigned char * + - fix typo in forward declaration for `initialize_subshell' + +general.[ch] + - check_binary_file now takes a (char *) argument, not unsigned char * + - pass unsigned char to isspace and isprint because of ISO C fuckup + - bash_tilde_expand now takes a `const char *' as its argument + +builtins/evalfile.c, shell.c + - buffer passed to check_binary_file is char, not unsigned char + +parse.y + - fix extern declaration for yyerror() + - yyerror now takes a `const char *' as first arg + +{error,jobs}.c + - fixes to printf-style functions to handle pids wider than an int + +lib/readline/{isearch,vi_mode}.c + - fix call to rl_message in rl_display_search (remove extra arg) + +variables.c + - fix missing argument to builtin_error in make_local_variable + +builtins/getopts.def + - since getopts takes no options, change while loop calling + internal_getopts to a simple `if' check + +builtins/printf.def + - since printf takes no options, change while loop calling + internal_getopts to a simple `if' check + +lib/readline/bind.c + - remove _SET_BELL macro, expand code inline + +lib/readline/input.c + - change _rl_input_available to use either select or FIONREAD, + but not both + +lib/readline/readline.c + - fix rl_digit_loop to remove unreachable code at end of loop + +{bashhist,bashline,expr,jobs,redir,shell}.c, builtins/fc.def, lib/sh/snprintf.c + - bracket unused functions with #ifdef INCLUDE_UNUSED/#endif + - remove some unused variables + +execute_cmd.c + - remove #ifdef'd code that allowed `return' to terminate a select + statement + +expr.c + - remove some extraneous tests from strlong() + +array.h + - arrayind_t is now a long, since shell arithmetic is performed as + longs + - remove second declaration of new_array_element + +builtins/printf.def + - in mklong, xrealloc cannot return NULL, so don't check for it + - remove some #if 0 code + - fix core dump triggered by a format specification with more than + one `*' + - remove `foundmod', since its value mirrors `modchar != 0' + - include "common.h" for builtin_{error,usage} declarations + +Makefile.in,builtins/Makefile.in + - updated some dependencies due to new include files + +pcomplete.c + - include "execute_cmd.h" for declaration of execute_shell_function + +arrayfunc.c + - include for printf + - include "builtins/common.h" for builtin_error declaration + +builtins/evalstring.c + - include "../trap.h" for run_trap_cleanup declaration + +builtins/help.def + - include "common.h" instead of locally declaring builtin_error + and builtin_usage + +error.h + - add extern declaration for itrace() + - add prototype to extern declaration of get_name_for_error + - file_error now takes a `const char *' as first argument + +externs.h + - added prototype for sh_setlinebuf declaration, bracketed with + NEED_SH_SETLINEBUF_DECL so we don't need stdio.h everywhere + - add extern declaration for parse.y:return_EOF() + +shell.c + - add NEED_SH_SETLINEBUF_DECL before including shell.h + +lib/readline/callback.c + - include or "ansi_stdlib.h" for abort declaration + +quit.h + - remove declaration of throw_to_top_level + +subst.c + - remove unused extern declaration for getopts_reset + +lib/sh/netopen.c + - include for legal_number, etc. + - add prototype for inet_aton extern declaration + +lib/sh/clock.c + - include for __P declaration + - add extern declaration for get_clk_tck + +support/mkversion.sh + - changed so that extern function declarations for functions in + version.c (moved from externs.h) are in the generated version.h + +shell.h + - include version.h + +version.c + - various `char *' version variables are now `const char *' + +general.h + - add prototype for same_file, bracketed with _POSIXSTAT_H + #ifdef, since that's what include/posixstat.h defines + +builtins/common.[ch] + - _evalfile, maybe_execute_file, source_file, and fc_execute_file + now take a `const char *' as their first argument + +eval.c + - removed extern declaration of yyparse; it's in externs.h + +parse.y + - added prototypes to static forward function declarations + - changed local `all_digits' variable in read_token_word () to + all_digit_token to avoid clash with all_digits() function in + general.c + +{bashhist,copy_cmd,make_cmd,hashlib,mailcheck}.c + - added prototypes for static function declarations + +shell.h + - add extern declarations for interactive, interactive_shell, + changed c files with extern declarations + +pcomplete.c + - changed it_init_aliases to avoid shadowing global variable + `aliases' + +bashline.c,pathexp.c,general.h + - sh_ignore_func_t is now a pointer to a function taking a + `const char *'; users changed + +configure.in + - test for + +config.h.in + - add #undef HAVE_STRINGS_H + +bashansi.h + - change like recommended in autoconf manual + + 9/11 + ---- +[a date which will live in infamy. prayers for the victims.] + +execute_cmd.c + - don't use an absolute index into abuf in mkfmt, use + sizeof(abuf) to compute last index + +builtins/common.c + - fix read_octal to do a better job of detecting overflow while + iterating through the string + +builtins/umask.def + - change octal-print mode to print 4 digits, like other shells + - cast umask to unsigned long to avoid problems on systems where + it's wider than an int (POSIX doesn't guarantee that mode_t is + no wider than an int, but real-world systems use int) + +builtins/printf.def + - mklong can never return NULL (it uses xrealloc), so the mainline + doesn't need to check for NULL returns + - new function, getldouble (long double *), to get long doubles + - mklong now takes a `char *' as its second argument, the modifier(s) + to use + - changed use of `modchar' to handle more than a single modifier + character + - changed to handle `long double' and `L' formats better, rather + than discarding long double information + - since printf now follows the POSIX.2 rules for conversion errors, + we can dispense with the status returns from the get* functions + - make the get* functions as similar in structure as possible, + removing type casts, etc. + +lib/sh/timeval.c,execute_cmd.c + - change some instances of `long' to `time_t', for systems where + a time_t is bigger than a long + +jobs.c + - include "posixtime.h" instead of + +config.h.in + - add defines for HAVE_DECL_CONFSTR, HAVE_DECL_STRTOLD, + HAVE_DECL_SBRK, HAVE_DECL_PRINTF + - remove defines for SBRK_DECLARED and PRINTF_DECLARED + - add _GNU_SOURCE define + +configure.in + - add AC_CHECK_DECLS for strtold, confstr, sbrk, printf + - remove call to BASH_FUNC_SBRK_DECLARED + - remove call to BASH_FUNC_PRINTF + +xmalloc.c, lib/malloc/malloc.c + - change check of SBRK_DECLARED to HAVE_SBRK_DECL + +print_cmd.c + - change PRINTF_DECLARED to HAVE_DECL_PRINTF + +builtins/evalstring.c, builtins/common.h + - parse_and_execute now takes a `const char *' as its second argument + +input.h,parse.y + - with_input_from_* functions now take a `const char *' as their + second argument + - init_yy_io now takes a `const char *' as its fourth argument + +parse.y,externs.h + - parse_string_to_word_list now takes a `const char *' as its second + argument + +tests/builtins.right + - change output to account for extra digit in umask output + +pcomplib.c + - free_progcomp now takes a PTR_T argument + +builtins/bashgetopt.h + - include + - add prototypes to extern declarations + +builtins/shopt.def + - add prototypes to static function declarations + +builtins/{fc,umask,wait}.def, builtins/{bashgetopt,common}.c + - include for isdigit macro (referenced by `digit(x)') + +lib/readline/complete.c + - added more static function declarations with prototypes + + 9/12 + ---- +lib/sh/tmpfile.c + - use `^' instead of `*' in sh_mktmpname to make filenames a bit + more random + +include/stdc.h,lib/readline/rldstdc.h + - add __attribute__ definition + +builtins/common.h + - add printf __attribute__ to declaration of builtin_error + +error.h + - add printf __attribute__ to declaration of programming_error, + report_error, parser_error, fatal_error, sys_error, internal_error, + internal_warning + +lib/readline/readline.h + - add printf __attribute__ to declaration of rl_message + +pcomplete.c + - add printf __attribute__ to declaration of debug_printf + +print_cmd.c + - add printf __attribute__ to declarations of cprintf, xprintf + +include/chartypes.h + - new file, includes and defines macros that check for + safe (ascii) arguments before calling the regular ctype macros + +{alias,bashline,execute_cmd,expr,findcmd,general,locale,mksyntax,stringlib,subst,variables}.c +parse.y +builtins/{bashgetopt,common}.c +builtins/{fc,printf,umask,wait}.def +lib/glob/strmatch.c +lib/sh/{oslib,pathcanon,pathphys,snprintf,strcasecmp,strindex,stringvec,strtod,strtol,strtrans}.c +examples/loadables/{head,sleep}.c + - include "chartypes.h" or instead of + +Makefile.in,{builtins,lib/{glob,sh}}/Makefile.in + - update dependencies to include chartypes.h + +lib/sh/inet_aton.c + - use `unsigned char' instead of `char' to pass to ctype.h functions + +lib/sh/netopen.c + - check for '0' <= host[0] <= '9' in _getaddr instead of using + isdigit + +subst.c,lib/sh/shquote.c + - change array subscripts into sh_syntaxtab from `char' to + `unsigned char' + +{alias,bashline,execute_cmd,expr,general,subst}.c, parse.y +builtins/{fc,printf,umask,wait}.def builtins/{bashgetopt,common}.c +lib/sh/{pathcanon,pathphys,snprintf,strcasecmp,strindex,strtod,strtol,strtrans}.c +examples/loadables/{head,sleep}.c + - change to use some of the new macros in chartypes.h + - remove old local macro definitions now provided by chartypes.h + +general.h + - remove definition of isletter, ISOCTAL, digit, digit_value + - change legal_variable_starter and legal_variable_char to use + chartypes.h macros + - change ABSPATH to use chartypes.h macros + +lib/readline/util.c + - change to use Paul Eggert's FUNCTION_FOR_MACRO define to define + function replacements for macros in chardefs.h + +lib/readline/chardefs.h + - added some of the same macros as in chartypes.h + - change _rl_lowercase_p, _rl_uppercase_p, _rl_digit_p, + _rl_to_upper, _rl_to_lower to use new IS* macros + - added _rl_isident macro from vi_mode.c:isident + +lib/readline/{bind,complete,nls}.c + - change to use some of the new macros from chardefs.h + +lib/readline/vi_mode.c + - isident -> _rl_isident + - remove local defines of macros in chardefs.h + +lib/sh/strtol.c + - updated to new version, modified from glibc 2.2.4 and sh-utils-2.0. + This one can do strtoll and strtoull, if necessary + + 9/13 + ---- +builtins/ulimit.def + - changed get_limit so it retrieves both hard and soft limits + instead of one or the other + - changed callers of get_limit + - changed getmaxvm to take soft limit, hard limit as arguments + - changed getmaxuprc to just take a single argument, the value + - changed calls to printone() to pass soft limit or hard limit + depending on `mode' instead of using old current_limit variable + - moved check for out-of-range limits in ulimit_internal into the + block that converts a string argument to a value of type rlim_t + - changed RESOURCE_LIMITS struct to break the description into a + description string and separate scale factor string + - changed print_all_limits to print a single error message if + get_limit fails, including limits[i].description now that the + scale factor has been removed from the description string + - removed DESCFMT define, since it's now used only in printone() + - changed printone to print the option character associated with a + particular limit if we're printing multiple limits + - changed calls to builtin_error to print the description associated + with a limit if setting or getting the limit fails + - added support for new POSIX 1003.1-200x rlim_t values: + RLIM_SAVED_CUR and RLIM_SAVED_MAX, which expand to the current + soft and hard limits, whatever they are + - changed printone to print `hard' or `soft' if the current limit is + RLIM_SAVED_MAX or RLIM_SAVED_CUR, respectively + - changed ulimit_internal to handle new `hard' and `soft' arguments + - changed help text do describe the special limit arguments `hard', + `soft', and `unlimited' + +doc/{bash.1,bashref.texi} + - documented new `hard' and `soft' limit arguments to `ulimit' + +hashlib.[ch] + - find_hash_item now takes a `const char *' is its first argument + - hash_string now takes a `const char *' is its first argument + - remove_hash_item now takes a `const char *' as its first argument + +pcomplib.c + - removed cast from first argument to find_hash_item in find_compspec + +general.[ch] + - absolute_program now takes a `const char *' as its argument + - absolute_pathname now takes a `const char *' as its argument + +lib/glob/glob.[ch] + - glob_pattern_p now takes a `const char *' as its argument + +bashline.c + - removed cast from first argument to absolute_program in + command_word_completion_function + - removed cast from first argument to glob_pattern_p in + attempt_shell_completion + +findcmd.[ch] + - find_absolute_program, find_user_command, find_path_file, + search_for_command, user_command_matches now take a + `const char *' as their first argument + - file_status, executable_file, is_directory, executable_or_directory + now take a `const char *' as their argument + - _find_user_command_internal, find_user_command_internal, + find_user_command_in_path + +lib/sh/makepath.c, externs.h + - changed sh_makepath so it takes `const char *' for its first + two arguments + +hashcmd.[ch] + - find_hashed_filename now takes a `const char *' as its first arg + - remove_hashed_filename now takes a `const char *' as its first arg + +variables.[ch] + - new_shell_variable, var_lookup, shell_var_from_env_string, + find_name_in_env_array, bind_function, makunbound, + bind_name_in_env_array, bind_tempenv_variable, bind_variable + now take a `const char *' as their first arg + - find_function, make_new_variable, find_tempenv_variable, + find_variable_internal, find_variable, set_func_read_only, + set_func_auto_export, all_variables_matching_prefix, assign_in_env, + assignment, kill_local_variable, make_local_variable, unbind_variable + now take a `const char *' as their arg + - mk_env_string now takes `const char *' arguments + +arrayfunc.[ch] + - skipsubscript now takes a `const char *' as its argument + + 9/17 + ---- +lib/readline/complete.c + - attempt to preserve case of what the user typed in + compute_lcd_of_matches if we're ignoring case in completion + +builtins/{let,pushd}.def,{execute_cmd,expr}.c + - change some 0L constants to 0 and let the compiler sort it out + + 9/18 + ---- +lib/malloc/alloca.c + - alloca now takes a `size_t' argument + +include/memalloc.h + - if we're providing an extern function declaration for alloca, + use `void *' and prototype if __STDC__ is defined + - if HAVE_ALLOCA_H is defined, but C_ALLOCA is defined, don't + define HAVE_ALLOCA + + 9/19 + ---- +subst.c + - do_assignment_internal, do_assignment, and do_assignment_no_expand + now take a `const char *' as their first argument + +general.h + - a `sh_assign_func_t' is now a function taking a `const char *' and + returning int + +hashcmd.c + - free_filename_data now takes a `PTR_T' argument to agree with the + typedef for `sh_free_func_t' + +lib/sh/snprintf.c + - use TYPE_MAXIMUM define like strtol.c instead of huge constants + + 9/20 + ---- +lib/sh/snprintf.c + - don't bother to compile the bulk of the body unless HAVE_SNPRINTF + or HAVE_ASPRINTF is not defined + + 9/24 + ---- +flags.c + - ignore `set -n' if the shell was started interactively + +lib/readline/readline.c + - initialize readline_echoing_p to 0; let the terminal-specific code + in rltty.c set it appropriately + +lib/malloc/malloc.c + - changed internal_memalign() slightly to avoid compiler warnings about + negating an unsigned variable (-alignment -> (~alignment + 1)) + + 9/27 + ---- +lib/readline/readline.c + - changed rl_newline to set _rl_history_saved_point appropriately + for the {previous,next}_history code + +lib/readline/rlprivate.h + - extern declaration for _rl_history_preserve_point + +lib/readline/bind.c + - new bindable variable, `history-preserve-point', sets value of + _rl_history_preserve_point + + 10/1 + ---- +lib/malloc/table.c + - new file, with a map of allocated (and freed) memory for debugging + multiple frees, etc. Indexed by hash on values returned by + malloc(); holds size, file and line number info for last alloc or + free and a couple of statistics pointers + +lib/malloc/malloc.c + - a few cleanups; added calls for registering allocations and frees + if MALLOC_REGISTER is defined + - replaced MALLOC_RETURN with explicit MALLOC_NOTRACE define + - reordered fields in `struct...minfo' in `union mhead' to restore + eight-byte alignment + - added explicit checks for underflow in free and realloc since + checking mh_magic2 is not sufficient to detect everything (it's + no longer the last field in the struct, and thus not the bytes + immediately preceding what's returned to the user) + - new function, xbotch, for printing file and line number info for + the failed assertion before calling botch() (programming_error()) + +configure.in + - replaced call to BASH_C_LONG_LONG with call to + AC_CHECK_TYPES([long long]) + - moved the C compiler tests before the tests for various + system types, so we can know whether we have `long long' + before testing for 64-bit types + - if we have `long long', check for sizeof(long long) and save value + +aclocal.m4 + - changed BASH_TYPE_BITS64_T to check `long long' before `long', but + after `double' + + 10/2 + ---- +lib/malloc/malloc.c + - made malloc and realloc both agree on the rounding for a request of + size N (round up to nearest multiple of 8 after adjusting for + malloc overhead); uses new ALLOCATED_BYTES macro + - realloc and free now use new IN_BUCKET macro for underflow checks + +execute_cmd.c + - fixed time_command() to use `time_t' instead of `long' to hold + time stamps + +lib/sh/clock.c + - clock_t_to_secs now takes a `time_t *' second argument + - fixed print_clock_t to call clock_t_to_secs with right arguments + +lib/sh/timeval.c + - fixed print_timeval to make `minutes' a `long' and make its + structure identical to print_clock_t + +redir.c + - changed redirection_error to check for EBADF and use the file + descriptor being redirected from in the error message if it + is >= 0 + +Makefile.in + - changed release status to `beta1' + +lib/glob/collsyms.h + - added a few ASCII symbols to the posix_collsyms array + + 10/3 + ---- +aclocal.m4 + - fixed typo in BASH_TYPE_BITS64_T + +configure.in + - added check for unsigned chars with AC_C_CHAR_UNSIGNED + +config.h.in + - added PROTOTYPES and __CHAR_UNSIGNED__ #defines + +general.h + - if CHAR_MAX is not define by , provide a definition + +builtins/printf.def + - change tescape() to mask \0 and \x escape sequences with 0xFF + - change tescape() to process at most two hex digits after a `\x' + +lib/sh/strtrans.c + - change strtrans() to mask \0 and \x escape sequences with 0xFF + - change strtrans() to process at most two hex digits after a `\x'. + This affects `echo -e' and $'...' processing + +lib/readline/bind.c + - changed rl_translate_keyseq() to process at most two hex digits + after a `\x' + +lib/readline/doc/{rluser.texinfo,readline.3}, doc/bash.1 + - changed documentation for key binding escape sequences to specify + that at most two hex digits after \x are translated + - changed documentation for key binding to specify that the result + of \nnn or \xhh escapes is an eight-bit value, not just ASCII + +doc/{bash.1,bashref.texi} + - changed documentation of $'...' to specify that at most two hex + digits after \x are translated + - changed `echo' documentation to specify that at most two hex + digits after \x are translated + - changed documentation for `echo' and $'...' to specify that the + result of \nnn or \xhh escapes is an eight-bit value, not just ASCII + + 10/4 + ---- +lib/malloc/malloc.c + - changed interface for xbotch to pass memory address and error code + as two additional arguments + - call mregister_describe_mem from xbotch to get the last allocation + or free before the botch + +configure.in + - call AC_CHECK_DECLS([strsignal]) + +config.h.in + - add HAVE_DECL_STRSIGNAL + +siglist.h + - make declaration of strsignal() dependent on !HAVE_DECL_STRSIGNAL + + 10/5 + ---- +support/texi2html + - upgraded to version 1.64 + + 10/9 + ---- +aclocal.m4 + - added check for `long long' to BASH_TYPE_PTRDIFF_T + +configure.in + - replaced call to BASH_HAVE_TIOCGWINSZ with AC_HEADER_TIOCGWINSZ + +aclocal.m4 + - replaced body of BASH_STRUCT_TERMIOS_LDISC with call to + AC_CHECK_MEMBER(struct termios.c_line, ...) + - replaced body of BASH_STRUCT_TERMIO_LDISC with call to + AC_CHECK_MEMBER(struct termios.c_line, ...) + +[bash-2.05a-beta1 frozen] + + 10/10 + ----- +lib/sh/snprintf.c + - fixed exponent() to not smash the trailing zeros in the fraction + when using %g or %G with an `alternate form' + - fixed exponent() to handle the optional precision with %g and %G + correctly (number of significant digits before the exponent) + + 10/11 + ----- +expr.c + - fixed strlong() to correct the values of `@' and `_' when + translating base-64 constants (64#@ == 62 and 64#_ == 64), for + compatibility with ksh + +lib/sh/itos.c + - added a slightly more flexible fmtlong() function that takes a + base argument and flags (for future use) + - rewrote itos and inttostr in terms of fmtlong + +lib/sh/fmtulong.c + - new file, converts unsigned long to string. hooks for `unsigned + long long' in the future. unused as yet + + 10/15 + ----- +lib/readline/rltty.c + - change the SET_SPECIAL macro to avoid possible (but highly + unlikely) negative array subscripts + +error.h + - add __attribute__ to extern declaration of itrace (even though the + function isn't defined in released versions of bash) + +bashansi.h + - include if HAVE_STRINGS_H is defined, to get any extra + function declarations provided therein + +copy_cmd.c + - fix typo in forward declaration for copy_arith_for_command + +lib/malloc/stats.c + - make the accumulators in _print_malloc_stats be `unsigned long' + instead of `int' + +externs.h, sig.h + - add `__noreturn__' gcc attribute to exit_shell and jump_to_top_level + declarations + +lib/sh/mailstat.c, support/bashversion.c + - include for some string function declarations + +lib/malloc/shmalloc.h + - added extern declarations of functions that do malloc debugging + +lib/readline/{isearch,readline,vi_mode}.c + - make sure we index into _rl_keymap with a non-negative index + +parse.y + - make sure we index into sh_syntaxtab with a non-negative index + +lib/readline/vi_mode.c + - bound the vi_mark_chars array with the number of characters between + 'a' and 'z' rather than using a fixed amount + - don't use _rl_lowercase_p when deciding whether the char read by + rl_vi_set_mark is a valid mark; just use 'a' <= char <= 'z' + +lib/readline/chardefs.h + - conditionally include memory.h and strings.h as in general.h + - replace ISASCII with IN_CTYPE_DOMAIN like other GNU software + - add defines for ISPRINT(c), ISLOWER(c) and ISUPPER(c) + - fix defines for _rl_lowercase_p, _rl_uppercase_p, _rl_digit_p, + _rl_pure_alphabetic, ALPHABETIC, _rl_to_upper, _rl_to_lower, + and _rl_isident to work on systems with signed chars + +include/chartypes.h + - replace ISASCII with IN_CTYPE_DOMAIN like other GNU software + +lib/sh/{strcasecmp,strtod,strtol}.c + - don't pass possibly-negative characters to tolower() or toupper() + +lib/glob/strmatch.c + - don't bother testing for isupper in FOLD; rely on TOLOWER macro + from to do it + - don't use local definitions of isblank, et al.; rely on macros + from + +lib/readline/{display,readline}.c, mksyntax.c + - use new ISPRINT macro instead of isprint() + +builtins/{kill.def,mkbuiltins.c},{error,execute_cmd,jobs,nojobs,subst}.c + - don't assume that a pid_t fits into an int for printing and other + uses + +variables.[ch] + - the unused put_gnu_argv_flags_into_env now takes a `long' pid + argument + +configure.in, config.h.in + - call AC_STRUCT_ST_BLOCKS, define HAVE_STRUCT_STAT_ST_BLOCKS if found + - check for strtoull(), define HAVE_STRTOULL if found + - check for uintmax_t, define to `unsigned long' if not found + +lib/sh/mailstat.c + - don't use st_blocks member of struct stat unless + HAVE_STRUCT_STAT_ST_BLOCKS is defined; otherwise use the st_nlink + field to return the total number of messages in a maildir-style + mail directory + +general.h,{alias,expr,general,subst,variables}.c +builtins/{printf,read}.def +lib/readline/{bind,complete,nls}.c +lib/sh/{pathcanon,pathphys,shquote,snprintf,strindex,strtod,strtol,strtrans}.c + - cast args to ctype macros to unsigned char for systems with signed + chars; other fixes for signed chars + +lib/sh/{fmtullong,strtoull.c} + - new files, more support for `long long' + +Makefile.in, lib/sh/Makefile.in + - make fmtullong.o and strtoull.o part of libsh + +lib/sh/itos.c + - remove local copy of fmtlong; use fmtulong instead + - new functions: uitos, uinttostr work on `unsigned long' + +lib/sh/snprintf.c + - fixes to make `unsigned long long' work (%llu) + - fixes to make unsigned formats not print the sign when given + an unsigned long that is greater than LONG_MAX + +externs.h + - extern declarations for fmtulong, fmtulloing, strtoull + - extern declarations for uitos, uinttostr + + 10/16 + ----- +configure.in + - move header checks before function checks + - move c compiler tests before header checks + - check for with BASH_HEADER_INTTYPES + - change type checks for intmax_t, uintmax_t to not attempt to + include + - check for strtoimax, strtoumax, strtoll, strtol, strtoull, strtoul + with BASH_CHECK_DECL (for declarations in header files) and + AC_REPLACE_FUNCS (for availability and LIBOBJS substitution) + - remove check for have_long_long around sizeof check for long long + (since autoconf will give it a size of 0 if the type isn't found) + +config.h.in + - add a define for HAVE_INTTYPES_H + - add a define for HAVE_UNSIGNED_LONG_LONG + - add defines for HAVE_STRTOIMAX, HAVE_STRTOUMAX, HAVE_STRTOLL + +aclocal.m4 + - new func, BASH_HEADER_INTTYPES, which just calls AC_CHECK_HEADERS + on ; separate so it can be AC_REQUIREd + - AC_REQUIRE([BASH_HEADER_INTTYPES]) in BASH_CHECK_TYPE + - include in BASH_CHECK_TYPE if HAVE_INTTYPES_H is + defined + - change AC_DEFINE to AC_DEFINE_UNQUOTED in BASH_CHECK_TYPE + - new `long long' checking macros: BASH_TYPE_LONG_LONG and + BASH_TYPE_UNSIGNED_LONG_LONG + - new BASH_CHECK_DECL + +lib/sh/{strto[iu]max,strtoll}.c, lib/sh/Makefile.in, Makefile.in + - new files + +externs.h + - extern declarations for strtoll, strtoimax, strtoumax + +lib/malloc/alloca.c + - include for size_t + +builtins/printf.def + - new functions: getllong, getullong, getintmax, getuintmax; return + long long, unsigned long long, intmax_t, uintmax_t respectively + - builtin printf now handles `ll' and `j' length modifiers directly + +lib/sh/Makefile.in + - use LIBOBJS to decide whether or not the strto* functions are + needed + + 10/17 + ----- +configure.in + - call AC_REPLACE_FUNCS(rename) + - move getcwd, strpbrk, strcasecmp, strerror, strtod + from AC_CHECK_FUNCS to AC_REPLACE_FUNCS + - only call BASH_FUNC_GETCWD if $ac_func_getcwd == "yes" + - call BASH_CHECK_SYS_SIGLIST + - if we don't have vprintf but have _doprnt, call AC_LIBOBJ(vprint) + +lib/sh/Makefile.in + - remove rename, getcwd, inet_aton, strpbrk, strcasecmp, strerror, + strtod, vprint from OBJECTS; picked up from LIBOBJS + +aclocal.m4 + - change BASH_FUNC_GETCWD to call AC_LIBOBJ(getcwd) if the libc + getcwd(3) calls popen(3) + - change BASH_FUNC_INET_ATON to call AC_LIBOBJ(inet_aton) if it's + not found in libc or as a #define even with the special includes + - BASH_KERNEL_RLIMIT_CHECK -> BASH_CHECK_KERNEL_RLIMIT + - BASH_DEFAULT_MAILDIR -> BASH_SYS_DEFAULT_MAILDIR + - BASH_JOB_CONTROL_MISSING -> BASH_SYS_JOB_CONTROL_MISSING + - BASH_REINSTALL_SIGHANDLERS -> BASH_SYS_REINSTALL_SIGHANDLERS + - BASH_SIGNAL_CHECK -> BASH_SYS_SIGNAL_VINTAGE + - BASH_DUP2_CLOEXEC_CHECK -> BASH_FUNC_DUP2_CLOEXEC_CHECK + - BASH_PGRP_SYNC -> BASH_SYS_PGRP_SYNC + - BASH_RLIMIT_TYPE -> BASH_TYPE_RLIMIT + - BASH_FUNC_PRINTF -> BASH_DECL_PRINTF + - BASH_FUNC_SBRK_DECLARED -> BASH_DECL_SBRK + - BASH_MISC_SPEED_T -> BASH_CHECK_SPEED_T + - BASH_CHECK_SOCKLIB -> BASH_CHECK_LIB_SOCKET + - new macro, BASH_CHECK_SYS_SIGLIST, encapsulates all the checks for + sys_siglist, _sys_siglist, and strsignal(), sets SIGLIST_O to + siglist.o if appropriate + +Makefile.in + - use SIGLIST_O variable to decide whether or not we need siglist.o + +{execute_cmd,subst}.c + - change a couple of instances of ISDIGIT to DIGIT, where we really, + really only want ascii digits + +ansi_stdlib.h + - don't need a declaration for atol() + + 10/18 + ----- + +aclocal.m4 + - new macro, BASH_FUNC_PRINTF_A_FORMAT, checks for printf support + for %a, %A conversion specifiers, defines HAVE_PRINTF_A_FORMAT + if successful + +configure.in + - call AC_CHECK_FUNCS for isascii + - call BASH_FUNC_PRINTF_A_FORMAT + +config.h.in + - add a define for HAVE_ISASCII + - add a define for HAVE_PRINTF_A_FORMAT + +lib/sh/snprintf.c + - for long double output, fall back to sprintf using ldfallback() + function for floating point formats + - support %a, %A using dfallback() or ldfallback() if + HAVE_PRINTF_A_FORMAT is defined + - fix bug in vasprintf that returned wrong value in its first + argument if the buffer holding the result string got reallocated + - fixed PUT_CHAR macro to increment the counter even if we've + exceeded the buffer size, for the return value from + vsnprintf/snprintf + - fix vsnprintf_internal to not use counter < length as a loop + condition, but always process the entire format string (for + the return value from vsnprintf/snprintf) + +builtins/printf.def + - support %a, %A if HAVE_PRINTF_A_FORMAT is defined + +include/typemax.h + - new file, with the TYPE_MAXIMUM stuff that's duplicated in several + files in lib/sh + +lib/sh/{fmtulong,strtol,snprintf}.c + - include instead of having the definitions in each file + +lib/sh/Makefile.in + - updated dependencies for typemax.h + + 10/22 + ----- +configure.in + - call AC_CHECK_FUNCS on ctype.h functions/macros that bash redefines + in chartypes.h + +config.h.in + - defines for HAVE_IS{ASCII,BLANK,GRAPH,PRINT,SPACE,XDIGIT} + +include/chartypes.h, lib/glob/strmatch.c, lib/readline/chardefs.h + - don't redefine some is* ctype macros/functions if HAVE_ISXXX is + defined (meaning that an appropriate function, but not a macro, + exists) + +lib/sh/strtrans.c + - new function, ansic_shouldquote, returns 1 if argument string + contains non-printing chars that should be quoted with $'...' + +externs.h + - new declaration for ansic_shouldquote() + +variables.c + - change print_var_value to ansi C quote the string if we're not in + posix mode and the variable's value contains non-printing chars, + to use the regular shell single quoting if the value contains + shell meta-characters, and to just output the string otherwise + +lib/sh/shquote.c + - add `break' to `case '~':' to avoid fallthrough and extra test + +doc/bashref.texi + - note that in POSIX mode, `set' displays variable values that + include nonprinting characters without quoting, unless they + contain shell metacharacters + +builtins/printf.def, lib/sh/snprintf.c + - handle `F' conversion specifier as equivalent to 'f' + +parse.y, {nojobs,variables}.c + - a couple of cleanups for when building a minimal configuration + +nojobs.c + - new function: stop_making_children(), just sets + already_making_children to 0 (like stop_pipeline) + +subst.c + - call stop_making_children from subst.c:command_substitute if + JOB_CONTROL is not defined. This fixes the bug where the wrong + process is waited for (and its status returned) when using + command substitution in a null command in a shell function + +builtins/printf.def + - new variable `tw' used to keep track of the total number of + characters written by a single call to `printf' -- to be + used for the `%n' conversion, which will be added later. It + gets reset each time we reuse the format string, which is what + ksh93 seems to do + + 10/23 + ----- +variables.c + - new function, bind_var_to_int (char *var, long val) + +variables.h + - extern declaration for bind_var_to_int + +lib/sh/netopen.c + - use gai_strerror() for error messages when getaddrinfo() fails + - use PF_INET if DEBUG is defined, since IPv6 doesn't work for me + +Makefile.in + - pass DEBUG=${DEBUG} down to makes in some subdirectories + +{builtins,lib/{glob,sh}}/Makefile.in + - append ${DEBUG} to LOCAL_CFLAGS value, passed by top-level Makefile + +builtins/printf.def + - added support for %n format conversion char (number of chars printed + so far from current format string) + + 10/24 + ----- +variables.c + - if posixly_correct is set, the default value of $MAILCHECK is 600 + - use legal_number instead of atoi in adjust_shell_level + - treat non-numeric assignments to SECONDS as 0 in assign_seconds + - new function, init_funcname_var; sets FUNCNAME as a dynamic variable + if it's not set in the initial environment + - new function, init_groups_var; sets GROUPS as a dynamic array + variable if it's not set in the initial environment + - new function, init_dirstack_var; sets DIRSTACK as a dynamic array + variable if it's not set in the initial environment + - new function, init_seconds_var; sets SECONDS as a dynamic + variable using any valid integer value in the initial environment + as the initial value, as if an assignment had been performed + - call init_funcname_var, init_groups_var, init_dirstack_var, + init_seconds_var from initialize_dynamic_variables + - non-numeric values assigned to LINENO are treated as 0 + - change initialize_shell_variables to not auto-export PATH or TERM + - change set_home_var to not auto-export HOME + - change set_shell_var to not auto-export SHELL + - broke the code that sets HOSTNAME, HOSTTYPE, MACHTYPE, OSTYPE + out into a separate function, set_machine_vars; none of those + variables are auto-exported + - bash no longer un-exports SSH_CLIENT or SSH2_CLIENT + +shell.c + - changed isnetconn() to check SSH_CLIENT and SSH2_CLIENT only if + SSH_SOURCE_BASHRC is defined in config-top.h + +config-top.h + - added a commented-out definition for SSH_SOURCE_BASHRC + + 10/25 + ----- + +Makefile.in + - changed RELSTATUS to `rc1' (release candidate 1) + + 10/29 + ----- +locale.c + - fixed an `=' vs. `==' typo in set_locale_var when parsing + LC_NUMERIC + +doc/{bash.1,bashref.texi} + - document what bash does with $POSIXLY_CORRECT + +doc/builtins.1 + - some updates + +builtins/psize.sh + - some mktemp(1) changes + +lib/readline/readline.c + - change rl_backward to check for rl_point < 0 and reset to 0 if so + +lib/readline/util.c + - don't compile in _rl_strpbrk if HAVE_STRPBRK is defined + +lib/readline/rlprivate.h + - remove extern declaration of _rl_strpbrk + +lib/readline/rldefs.h + - #define _rl_strpbrk as strpbrk if HAVE_STRPBRK is define, otherwise + add extern declaration of _rl_strpbrk from rlprivate.h + +{mailcheck,shell,variables}.c + - make sure to include posixtime.h to get any prototype for time(3) + in scope + +{array,eval,execute_cmd,mksyntax,subst}.c, parse.y +builtins/common.c +lib/sh/pathcanon.c + - a few changes as the result of `gcc -Wall' patches from solar + designer + +builtins/read.def, parse.y + - change some calls to free() to xfree() + +builtins/set.def + - make sure unset_builtin() resets unset_array to 0 each time through + the loop, because it's set (and used) depending on the current + argument + +shell.h + - new define, USE_VAR, to force the compiler to not put a particular + variable in a register -- helpful if registers are not restored + by setjmp/longjmp + +builtins/{evalfile.c,{read,wait}.def}, {eval,execute_cmd,shell,test}.c + - use USE_VAR for some variables + +subst.c + - fixed a case in expand_word_internal where a NULL pointer could + have been passed to free() (though free() should ignore it) + - fixed a case at the end of expand_word_internal where LIST could + have been used uninitialized (it makes gcc happy, though it + doesn't happen in practice) + +test.c + - give test_syntax_error(), beyond(), and integer_expected_error() + the `__noreturn__' attribute for gcc + +unwind_prot.c + - in clear_unwind_protect_list(), convert `flags' to `long' (via + assignment to a `long' variable) before casting to `char *', in + case pointers and longs are 64 bits and ints are 32 (makes no + difference on 32-bit machines) + + 10/30 + ----- +print_cmd.c + - fixed cprintf to avoid gcc warning about assigning const pointer + to non-const (discarding type qualifier) + +{make_cmd,pcomplete,test}.c,parse.y + - some minor changes to shut up gcc warnings + +lib/sh/tmpfile.c + - fixed sh_mktmpfp to avoid file descriptor leaks in the case that + sh_mktmpfd succeeds but fdopen fails for some reason + - change sh_mktmpfd to use the same scheme for computing `filenum' + as sh_mktmpname + - change get_sys_tmpdir to prefer P_tmpdir if P_tmpdir is defined + - changed sh_mktmpname and sh_mktmpfd to avoid trying to assign to + `nameroot' if `nameroot == 0' (duh) + - add code to sh_mktmpfd to use mkstemp(3) if USE_MKSTEMP is defined + - add code to sh_mktmpname to use mktemp(3) if USE_MKTEMP is defined + +support/{fixlinks,mkclone} + - use mktemp if it's available for the symlink test + - use $TMPDIR instead of hardcoding /tmp; default to /tmp + - use a better filename for the symlink test instead of `z' + +support/bashbug.sh + - more changes inspired by a patch from solar designer + +lib/malloc/Makefile.in + - new target `alloca', which builds libmalloc.a with alloca.o only + (for systems without alloca that are configured --without-bash-malloc) + +configure.in + - if we don't have a working alloca and are not configured to build + the bash malloc library, make a malloc library containing only + alloca.o + +aclocal.m4 + - slight change to RL_LIB_READLINE_VERSION to deal with minor version + numbers with a letter appended (like 4.2a) + + 10/31 + ----- +doc/{bash.1,bashref.texi} + - slight change to note that only interactive shells resend a SIGHUP + to all jobs before exiting + +externs.h + - declare strto[ui]max only if NEED_STRTOIMAX_DECL is defined. This + keeps picky compilers from choking because intmax_t is not defined + (MacOS X 10.1) + +builtins/printf.def + - #define NEED_STRTOIMAX_DECL before including shell.h + + 11/1 + ---- +general.c + - check in bash_tilde_expand() for an unquoted tilde-prefix; don't + bother passing the string to tilde_expand unless the prefix is + unquoted + +shell.c + - fix a problem with $LINENO when executing commands supplied with + the -c invocation option when ONESHOT is defined + +[bash-2.05a-rc1 frozen] + +builtins/printf.def + - fix the %n conversion to require that the variable name supplied + be a valid shell identifier + +variables.c + - improve random number generator slightly by using the upper 16 + bits of the running random number instead of the lower 16, which + are incrementally more random + + 11/2 + ---- +configure.in + - if RL_INCLUDEDIR ends up being /usr/include, don't put + -I$(RL_INCLUDEDIR) into CFLAGS + + 11/5 + ---- +doc/{bash.1,bashref.texi} + - correct description of POSIXLY_CORRECT to note that the shell enters + posix mode *before* the startup files are read if POSIXLY_CORRECT + is in the initial environment + +variables.c + - fix function prologues for init_dirstack_var and init_groups_var + to agree with caller (no arguments) + +jobs.c + - fix forward function declarations for pipe_read and pipe_close + +subst.c + - removed `inline' attribute from skip_double_quoted because it can + potentially be called recursively + +bashline.c + - quick fix to bashline.c:attempt_shell_completion programmable + completion code to just punt if the end of the command word found + by find_cmd_end is <= the start found by find_cmd_start (the bug + is probably in find_cmd_start -- fix later) + +pcomplete.c + - fix gen_matches_from_itemlist to return if the stringlist is null + after any cleaning or initialization, before trying to use it + - fix GEN_COMPS to only bother to try to append the STRINGLIST + returned by gen_matches_from_itemlist to `glist' if it's non-NULL + +lib/sh/stringlist.c + - make copy_stringlist return NULL if the STRINGLIST * passed as an + argument is NULL + - make append_stringlist call copy_stringlist only if M2 is non-NULL; + otherwise just return NULL if m1 is NULL + - make word_list_to_stringlist return 0 immediately if the passed + LIST argument is NULL + - make realloc_stringlist call alloc_stringlist if the passed + STRINGLIST argument (`sl') is 0, just like realloc calls malloc + +subst.c + - in skip_to_delim(), if we have an unclosed ${, and it's at the end + of the string (string[i] == '{', string[i+1] == '{' and + string[i+2] == 0, return si (i +2) immediately without bothering + to call extract_dollar_brace_string or extract_delimited_string + - in skip_to_delim(), if string[i] is 0 after a call to + extract_dollar_brace_string or extract_delimited_string (meaning we + have an unclosed ${ or other expansion, return i immediately without + doing a `continue' (which will increment i past the end of string) + - in split_at_delims, don't increment te by 1 if it's pointing to a + delimiter. this has the effect of skipping the first delimiter + char in a possibly multi-character delimiter, and ignoring + single-char delimiters like `>' + +configure.in + - use AC_CHECK_MEMBERS([struct stat.st_blocks]) instead of a call to + AC_STRUCT_ST_BLOCKS to avoid configure changing LIBOBJS if the test + fails + +general.c + - introduce two new variables: bash_tilde_{prefixes,suffixes}, set + to the additional prefixes and suffixes bash wants to pass to the + tilde expansion code (reserved for post-bash-2.05a fix) + +aclocal.m4 + - add missing `test' in BASH_CHECK_SYS_SIGLIST + + 11/7 + ---- +lib/readline/vi_mode.c + - fix rl_vi_goto_mark to explicitly check that the desired mark is + between 'a' and 'z', since some locales have lowercase letters + outside that range, which could cause a negative subscript + +include/chartypes.h + - remove superfluous `#undef ISASCII' + +lib/sh/strto[iu]max.c + - changes from Paul Eggert to work around buggy compilers and catch + configuration errors at compile time + +aclocal.m4 + - new macro, BASH_C_LONG_DOUBLE, identical to AC_C_LONG_DOUBLE but + with a fix for Irix 5.3 (not called, since I'm not sure it's the + right thing to do -- the C standard allows double and long double + to be the same size) + +lib/sh/snprintf.c + - only try to write the trailing NUL in vsnprintf_internal if + data->length is >= 0, since if it's not, we probably don't have + a buffer + +Makefile.in + - changed RELSTATUS to `release' + + 11/8 + ---- +lib/sh/strtol.c + - make sure chars passed to toupper are cast to unsigned + +unwind_prot.c + - change clear_unwind_protect_list to not require a cast from `int' + to `char *' + +lib/readline/chardefs.h + - make _rl_digit_p succeed only for ascii digits, since that's what + most callers assume + + 11/13 + ----- +doc/bashref.texi + - added `ERR' trap and [-+]O invocation option to section listing + differences from the Bourne shell + + 11/15 + ----- +[bash-2.05a released] + + 11/19 + ----- +include/stdc.h + - new define, INLINE, defined as `inline' for gcc and empty otherwise + +subst.c + - make skip_double_quoted, sub_append_string have INLINE attribute + +trap.c + - use BASH_NSIG as upper limit for signal names in signal_name() + +lib/readline/bind.c + - use RL_COMMENT_BEGIN_DEFAULT in output for rl-comment-begin value + +error.c + - fix sys_error to save value of errno around calls to fprintf + +doc/Makefile.in + - added rules to create PDF files from postscript and dvi input + +MANIFEST.doc + - added {article,bash,bashref,rose94}.pdf + +doc/bash.1 + - rearranged some `.PD 0' and `.TP' directives so man2html will + handle them better (shouldn't affect groff output) + +support/man2html.c + - small fix to handle quoted string arguments to directives like + `.BR' without mangling the output + + 11/20 + ----- +{arrayfunc,variables}.c + - changed calling sequence for dynamic array variable `assign' + functions to (SHELL_VAR *self, char *value, arrayind_t ind) + - changed calling sequence for dynamic variable assign functions + to the same as array variable assign_func. Now this can be + prototyped + +variables.h + - the assign_func member of a `struct variable' is now of type + `sh_var_assign_func_t', which is prototyped + - the dynamic_value member of a `struct variable' is now of type + `sh_var_value_func_t', which is prototyped + +variables.c + - changed to use `sh_var_assign_func_t' and `sh_var_value_func_t' + +builtins/cd.def + - when in posix mode, if the new directory name formed by PWD and + the argument passed by the user cannot be canonicalized, and the + -P option has not been supplied, return failure immediately + - if canonicalization failed, but the fallback to the directory + name specified by the user succeeds, reset the current working + directory + +lib/readline/{input.c,rlprivate.h} + - renamed rl_unget_char to _rl_unget_char; made library global + +lib/readline/{{bind,readline}.c,{keymaps,rlprivate}.h} + - support for `key subsequences'; allows a key sequence and a function + mapped to a subsequence of that key sequence. Primarily to allow + arrow keys to be bound in readline vi insert mode, while preserving + the ESC function to switch to command mode. + +lib/readline/{input.c,rlprivate.h} + - new function, _rl_input_queued(T), does a check with select or + FIONREAD with a timeout of `T' (which is generally 0) + +lib/readline/readline.c + - change _rl_dispatch_subseq to test for input in the queue if we + get ESC while in vi insertion mode if the keymap entry type for + ESC is ISKMAP. If _rl_input_queued returns non-zero, we assume + that an arrow key sequence has been pressed and go ahead with the + subsequence. If it returns zero, we assume that the user pressed + ESC to switch into command mode, and dispatch to that right away. + This avoids forcing the user to press another key before switching + into command mode + + 11/21 + ----- +lib/readline/readline.c + - bind common arrow key sequences in vi insertion keymap + +lib/readline/terminal.c + - bind termcap definition's arrow keys in vi insertion keymap + +lib/readline/bind.c + - check for rl_vi_movement_mode in _rl_bind_if_unbound, so + binding the arrow keys can work + +lib/readline/readline.c + - since _rl_bind_if_unbound does the check of what's currently + bound to the key sequence, the check in bind_arrow_keys_internal + was redundant + - bind_arrow_keys_internal now takes a Keymap argument and handles + saving and restoring _rl_keymap; changed bind_arrow_keys + accordingly + +builtins/fc.def + - fix from Paul Eggert to substitute the nearest history number in + range if an out-of-range value is supplied. POSIX requires this + +lib/sh/pathcanon.c + - fix from Corrina Vinschen for the special `cygdrive' prefix on + Cygwin + +bashhist.c + - split the history adding code into more pieces: + check_history_control (char *line) checks LINE against the value + of HISTCONTROL, returning 1 if LINE should be saved and 0 if not + + check_add_history (char *line) calls check_history_control and + history_should_ignore (line) and saves the line with + bash_add_history if the checks indicate that it should be saved + + maybe_add_history just calls check_add_history to set the value + of first_line_saved + +bashhist.h + - extern declaration for check_add_history() + +shell.c + - don't call load_history() from the interactive shell startup + code if history_lines_this_session is > 0, indicating that we've + already saved some lines in the history and that we probably + don't want to overwrite them + +builtins/history.def + - call check_add_history from push_history, so `history -s xx' + works even when in a compound command whose first line has not + been saved. (Caveat: in a compound command when the first + line has been saved, the line supplied to history -s will become + part of the compound command's history entry. Of course, the + delete_history call could remove the compound command from the + history entirely) + +bashline.c + - use sh_makepath instead of xmalloc/sprintf in + command_word_completion_function + +lib/readline/complete.c + - get_y_or_n now takes an int FOR_PAGER argument; caller changed + If FOR_PAGER is non-zero, get_y_or_n returns appropriate values + for a more-like pager: `newline' or `return' return 2; `q' or + `Q' return 0 + - there is now a mini internal more-like pager for displaying a + list of completions that exceeds the screen height (new function + _rl_internal_pager, called from rl_display_match_list) + + 11/24 + ----- +command.h + - new flag, W_TILDEEXP, says to do tilde expansion on an + assignment word + +execute_cmd.c + - fix_assignment_words now sets W_TILDEEXP for assignment word + arguments to `assignment builtins' + +general.c + - bash_tilde_expand now takes a second argument indicating whether + or not it's being invoked in an `assignment context' + +general.h + - change extern declaration for bash_tilde_expand + +{bashline,execute_cmd,findcmd,general,variables}.c +builtins/evalfile.c +lib/sh/makepath.c + - fix callers of bash_tilde_expand appropriately + +subst.c + - fix callers of bash_tilde_expansion appropriately + - add (currently commented-out) code that would tilde expand assignment + statement arguments to assignment builtins (W_TILDEEXP flag set) + even when the shell is in posix mode + +bashline.c + - fix attempt_shell_completion to turn off + rl_filename_completion_desired when doing command name completion, + so no slash gets appended to the name if there happens to be a + directory with the same name in the current directory + + 11/26 + ----- +lib/readline/rltech.texinfo + - a couple of additions to the rl_stuff_char description + +parse.y + - turn off echo_input_at_read in parse_string_to_word_list, so `set -v' + doesn't give extra lines of output when doing compound array + assignment + +subst.c + - fix split_at_delims to handle skipping over a `\n' if it's a + delimiter (use spctabnl(c) instead of whitespace(c)) + + 11/27 + ----- +support/config.{guess,sub} + - updated (with bash changes) to latest version from gnu.org + +sig.h + - add prototype for set_signal_handler declaration + +builtins/setattr.def + - add prototype to extern declaration of declare_builtin + +builtins/times.def + - add no_options call, since times takes no options + +lib/sh/spell.c + - add prototypes to forward declarations for midist and spdist + +lib/sh/strtrans.c + - add explicit int return type to ansic_shouldquote declaration + +lib/readline/rldefs.h, lib/readline/{macro,readline,util,undo}.c + - move define for SWAP to rldefs.h, removed from various C files + +lib/readline/vi_mode.c + - removed define for exchange(), changed to use SWAP instead + +lib/readline/bind.c + - added some static forward function declarations + - find_boolean_var, find_string_var now take a `const char *' argument + +lib/readline/signals.c + - added static forward declaration for rl_maybe_set_sighandler + +lib/readline/readline.c + - add some common key bindings for the HOME and END keys in + bind_arrow_keys_internal + +lib/readline/terminal.c + - fetch the `@7' termcap string; it's sent by the END key + - attempt to bind the terminal's END key to rl_end_of_line in + bind_termcap_arrow_keys; I don't know why I was using `kH' + instead of `@7' + +doc/builtins.1 + - remove `case', `for', `if', `until', `while' from NAME section; + those are not shell builtins + + 11/28 + ----- +stringlib.c + - new function, find_token_in_alist, takes a token value and an + ALIST argument, and returns the string correspoinding to the + token if found in the alist + +externs.h + - new extern declaration for find_token_in_alist() + +subst.c + - string_list_internal is no longer static + +subst.h + - new extern declaration for string_list_internal() + +parse.y + - new alist array of other tokens returned by read_token which are + not reserved words in word_token_alist[] + - reworked error reporting: new functions print_offending_line, + which prints the line containing the syntax error, + error_token_from_token, which takes the current token and tries to + figure out its textual representation, and error_token_from_text, + which does the old job of finding the bad token by analyzing the + text of shell_input_line at the current index + - report_syntax_error now tries to figure out the token that caused + the syntax error by first looking at current_token and falling + back to the old method of textual analysis if that fails + - report_syntax_error doesn't say the token resulting from the textual + analysis of the input line is an `unexpected token'; it just + says there is a `syntax error near xxx' + - changed conditional command error reporting to use the value + returned by error_token_from_token if it's not null instead of + just using the token value in the message, since current_token + ends up being set to -1, and the text of the message from + report_syntax_error might not be exactly right + - change parse_string_to_word_list to set current_token to the + offending token returned by read_token before calling yyerror() + to make the error reporting do the right thing + +aclocal.m4 + - fixed typo in BASH_CHECK_LIB_TERMCAP + +configure.in + - add check for isinf(3); define HAVE_ISINF_IN_LIBC if found + +config.h.in + - add define for HAVE_ISINF_IN_LIBC + +lib/sh/snprintf.c + - check for Inf and NaN, using isinf and isnan if they're found in + libc + - use the current locale for thousands separator and decimal point + - recognize "'" flag; not implemented yet + - fix for snprintf/vsnprintf with length of 0 and string argument of + 0 with non-zero length + +builtins/read.def + - TMOUT is now the default timeout for `read' (and select) if set, + like ksh93 when reading from the terminal + - edit_line (called by read -e) now just does readline's filename + completion by setting rl_attempted_completion_function to NULL, + since e.g., doing command completion for the first word on the + line wasn't really useful + +execute_cmd.c + - changed select_command to return failure status if select_query + returns NULL, indicating that read_builtin returned + EXECUTION_FAILURE + +doc/{bash.1,bashref.texi} + - documented new TMOUT behavior + - slight change to the description of the test `-ef' option + +doc/bashref.texi + - added item to posix mode section describing failure behavior of + cd when invoked in logical mode and the pathname formed by + combining $PWD and the directory argument does not refer to an + existing directory + + 11/29 + ----- +execute_cmd.c + - fix execute_function to call dispose_function_env after + merge_function_env if the shell is in posix mode (fixes debian + bash bug #117673) + +lib/readline/readline.c + - rl_forward -> rl_forward_char; rl_forward function for compatibility + - rl_backward -> rl_backward_char; rl_forward function for + compatibility + - new functions, rl_forward_byte, rl_backward_byte, for future use + +lib/readline/readline.h + - extern declarations for rl_forward_char, rl_backward_char, + rl_forward_byte, rl_backward_byte + +lib/readline/{emacs_keymap,funmap,vi_keymap,vi_mode + - rl_forward -> rl_forward_char + - rl_backward -> rl_backward_char + +lib/readline/funmap.c + - new bindable names, `backward-byte' and `forward-byte' + +aclocal.m4 + - new function, BASH_CHECK_MULTIBYTE, encapsulates checks for + multibyte code + +config.h.in + - add necessary defines for multibyte include files and functions + +configure.in + - add call to BASH_CHECK_MULTIBYTE + +config-bot.h + - add code to define HANDLE_MULTIBYTE if prerequisites are met + +lib/sh/xstrchr.c + - new file, xstrchr() is strchr(3) that handles multibyte characters + +bashhist.c + - first_line_saved -> current_command_first_line_saved; variable is + now global + +bashhist.h + - extern declaration for current_command_first_line_saved + + 11/30 + ----- +bashhist.c + - break the code that actually calls add_history out of + bash_add_history into a new function, really_add_history; + bash_add_history now calls really_add_history + - check_add_history takes a second `force' argument telling it + whether to call bash_add_history (force == 0) or really_add_history + (force != 0) + +builtins/history.def + - in push_history, call delete_last_history if the current command + has more than one line, the first line was saved, and + command-oriented history is active. This takes care of deleting + the right history element if `history -s' is used within a + compound or multiline command + - in push_history, call check_add_history with second argument of 1 + to skip check of current_command_line_count and add the arguments + to history -s as a single separate history entry + + 12/3 + ---- +lib/readline/complete.c + - append a slash to completed names which are symlinks to directories + if the new variable _rl_complete_mark_symlink_dirs is non-zero + +lib/readline/rlprivate.h + - extern declaration for _rl_complete_mark_symlink_dirs + +lib/readline/bind.c + - new bindable variable, `mark-symlinked-directories', mirrors the + value of _rl_complete_mark_symlink_dirs + +doc/bash.1, lib/readline/doc/{readline.3,rluser.texinfo} + - documented new `mark-symlinked-directories' variable + + 12/4 + ---- +variables.[ch] + - set_pipestatus_array now takes a second argument with the number + of processes in the array + - changed set_pipestatus_array to just modify the value in place if + the existing array has one element and the new array has one + element, and to modify existing values in place if new array has + more elements than existing array + +variables.c, jobs.c + - changed set_pipestatus_array callers + +jobs.c + - moved call to setjstatus() from set_job_status_and_cleanup to + wait_for, since set_job_status_and_cleanup is part of the SIGCHLD + signal handler call path, and race conditions accessing the + PIPESTATUS array will result for things like + + while true; do date; done | cat > /dev/null + + 12/5 + ---- +xmalloc.h + - don't redefine xmalloc, xrealloc, and xfree if DISABLE_MALLOC_WRAPPERS + is #defined + +config.h.in + - #undef for DISABLE_MALLOC_WRAPPERS + +configure.in + - define DISABLE_MALLOC_WRAPPERS if the --with-purify option is + supplied + +lib/malloc/trace.c + - new function, malloc_trace_bin(N), traces allocations and frees + to bucket N (uses the same type of bitmap as `busy') + +lib/malloc/table.c + - fix wraparound search problem in find_entry when searching for a + free entry when the table is full + + 12/6 + ---- +lib/malloc/table.c + - keep an `overflow bucket' around to use when the table is full, + so find_entry always returns a valid pointer when FIND_ALLOC + is set + - new static variable to keep a count of the number of MT_ALLOC + entries in the mem_table + +lib/sh/{oslib,clktck}.c + - if HAVE_LIMITS_H is defined, include + +lib/sh/oslib.c + - new function, getmaxgroups() returns max number of simultaneous + groups + - new function, getmaxchild(), returns max number of simultaneous + user processes + +general.c + - removed forest of #defines for getmaxgroups() + +externs.h + - new extern declaration for getmaxgroups() + - new extern declaration for getmaxchild() + - new extern declaration for isnetconn() + +lib/sh/netconn.c,shell.c + - new file, isnetconn() from shell.c moved here + +Makefile.in, lib/sh/Makefile.in + - necessary changes for netconn.c + +builtins/ulimit.def + - changed getmaxuprc() to just call getmaxchild() and massage the + return value appropriately + +{jobs,nojobs}.c + - use the value returned by getmaxchild() in + mark_dead_jobs_as_notified instead of static CHILD_MAX + +jobs.c + - new function, compact_jobs_list, removes some number of jobs from + the jobs table and reallocates the table, copying the jobs that + are left from the old table to the new. Compaction happens from + the beginning of the list and removes dead jobs, and we make sure + to keep the last CHILD_MAX jobs as POSIX.2 requires + - call compact_jobs_list from stop_pipeline if we're in a subshell, + there are no free jobs in the jobs table, and the jobs table is + at or above some maximum limit + +execute_cmd.c + - change eval_arith_for_expr to set this_command_name to `((' before + calling evalexp, since it might be changed by evaluating the + loop body between evalexp calls + +trap.c + - change reset_signal to turn off the SIG_TRAPPED flag for the + given signal, so shell builtins and functions running in command + substitutions don't run the signal handlers (traps are not supposed + to be inherited by command substitutions) + +parse.y + - changed parse_string_to_word_list to turn off alias expansion + while parsing the array assignment + + 12/9 + ---- +alias.c + - fix add_alias so that redefining an alias's value also resets the + EXPANDNEXT flag + + 12/10 + ----- +parse.y + - new function, token_is_assignment, called to check whether the text + before `=' makes up a valid assignment token before trying to parse + a compound assignment statement + - new function, parse_compound_assignment, to parse a compound + assignment statement instead of using parse_matched_pair; handles + comments and error reporting in the parser instead of waiting until + expansion time + - changed parse_compound_assignment and parse_string_to_word_list to + allow reserved words in compound array assignments + +lib/readline/doc/rltech.texinfo + - changed the documentation for rl_callback_read_char and + rl_callback_handler_remove to say what happens to the terminal + settings and what needs to be done to reset them + + 12/11 + ----- +bashline.c + - add emacs_edit_and_execute_command, bound to C-xC-e, like vi-mode + `v' command + - add bindable command name `edit-and-execute-command', bound to + run emacs_edit_and_execute_command() + +lib/glob/strmatch.c + - add support for ksh93-like [:word:] character class (isalnum + `_') + +doc/{bash.1,bashref.texi} + - add note to section describing lists to clarify that a sequence of + one or more newlines may be used to delimit a command, equivalent + to a semicolon + - document new [:word:] pattern matching character class + +doc/bash.1, lib/readline/doc/rluser.texinfo + - document `edit-and-execute-command' and its default emacs-mode + binding + +include/chartypes.h + - add defines for TOCTRL and UNCTRL if they're not already defined + +lib/readline/chardefs.h + - #undef UNCTRL if it's defined to avoid cpp redefinition warnings + +lib/sh/strtrans.c + - add \cX (Control-X) escape for $'...' to ansicstr() + - change ansic_quote() to allocate at least four chars for each char + in the string argument, to account for \0xx octal values + - change ansic_quote() to no longer call sprintf for non-printable + characters; just translate the string to octal directly + +print_cmd.c + - change xtrace_print_word_list to call ansic_quote() if + ansic_shouldquote() indicates that there are nonprinting characters + in a word + +builtins/type.def + - changed deprecated long option parsing to just replace the word + in the list with the equivalent short option (-type -> -t) instead + of removing words from the list + - changed describe_command to take a single flags argument instead + of two int args; changed caller + - type now has two new options: -f suppresses function lookup (like + command), and -P forces a PATH search for the name(s) + +builtins/common.h + - flags for describe_command are here + - changed extern declaration of describe_command + +builtins/command.def + - changed call to describe_command to use flags from common.h, and + the right number of arguments + +doc/{bash.1,bashref.texi} + - documented new -f and -P options to `type' + + 12/12 + ----- +lib/readline/rldefs.h + - fixed prototype for _rl_strnicmp + +execute_cmd.c + - select_query now takes a new argument, an int flag saying whether + or not to print the menu the first time through the loop. An + empty line in response to the prompt will always cause the menu + to be reprinted + - changed execute_select_command to cause select_query to reprint + the menu only if REPLY is set to NULL, if KSH_COMPATIBLE_SELECT + is defined + +config-top.h + - define KSH_COMPATIBLE_SELECT, with a comment about its meaning + +lib/readline/readline.c + - change rl_insert_comment to toggle if given an explicit numeric + argument: if the first characters on the line don't specify a + comment, insert one; if they do, delete the comment text + +doc/bash.1, lib/readline/doc/{readline.3,rluser.texinfo} + - documented new behavior of insert-comment with a numeric argument + + 12/13 + ----- +lib/malloc/watch.c + - new file, implements watchpoint functions + +lib/malloc/watch.h + - new file, define some `events' for watchpoints and extern function + and variable declarations for watchpoint code + +lib/malloc/imalloc.h + - #define MALLOC_WATCH if MALLOC_DEBUG is defined + - add __P define as in include/stdc.h if not already defined + +lib/malloc/malloc.c + - remove __P define, now in imalloc.h + - include watch.h if MALLOC_WATCH is defined + - added calls to _malloc_ckwatch in internal_malloc, internal_free, + and internal_realloc + +include/stdc.h + - augment __P define to allow prototypes if PROTOTYPES is defined + +lib/readline/rlstdc.h + - augment PARAMS define to allow prototypes if PROTOTYPES is defined + +lib/malloc/Makefile.in, Makefile.in + necessary changes to include watch.c in libmalloc + +lib/readline/readline.c + - fix rl_delete_text to make sure that the starting position is >= 0 + - _rl_init_line_state (called by readline via readline_initialize) + now sets rl_mark to 0 + - rl_get_{next,previous}_history set rl_mark to 0 if rl_point is at + the end of the line and rl_end otherwise in emacs mode + +lib/readline/kill.c + - rl_yank_nth_arg_internal and rl_paste_clipboard now set the mark + at point before calling rl_insert_text, like rl_yank + - rl_kill_full_line now resets rl_mark to 0 + - rl_kill_line and rl_backward_kill_line now set rl_mark to the + point after the kill in emacs mode + - rl_kill_word and rl_backward_kill_word now set rl_mark to the + point after the kill in emacs mode + - rl_unix_word_rubout and rl_unix_line_discard now set rl_mark to + the point after the kill in emacs mode + +lib/readline/search.c + - noninc_search saves and restores the mark, since it can be changed + while reading the search string + - noninc_dosearch sets the mark at the end of the line, making the + region bound the `inserted' text since rl_point is set to 0 + - rl_history_search_internal sets the mark at the end of the line, + for the same reason + +lib/readline/isearch.c + - rl_search_history now saves and restores the mark + - if no matching lines are found at all when doing an isearch, leave + point where it was instead of moving it to the end of the line + + 12/17 + ----- +lib/readline/rlmbutil.h + - new file, place for multi-byte character defines and extern + declarations + +lib/readline/{bind.c,readline.c,rlprivate.h} + - new bindable variable, `byte-oriented', tracks value of + rl_byte_oriented variable + +lib/readline/mbutil.c + - new file, with multibyte char utility functions + +lib/readline/{complete,display,readline,util,vi_mode}.c + - new code for multibyte characters, derived from IBM patch + + 12/18 + ----- +lib/sh/tmpfile.c + - include posixtime.h for time() extern declaration + +support/bashversion.c + - include if it's available + +lib/readline/{histexpand,input,isearch,search}.c + - new code for multibyte characters, derived from IBM patch + +lib/readline/readline.h + - include rltypedefs.h + + 12/19 + ----- +lib/readline/complete.c + - slight change to mark-directories code to avoid adding a slash if + point is at the end of the line (rl_line_buffer[rl_point] == '\0') + and the previous character was a slash + - change printable_part to not return empty pathnames, which could + happen when completing filenames and a filename with a trailing + slash was passed as the argument. If the portion following the + trailing slash is NULL, ignore it and look for a previous slash. + If there's no previous slash, just return the filename argument + - new variable, rl_completion_mark_symlink_dirs, mirrors the value + of (user-settable with a variable) _rl_complete_mark_symlink_dirs + but may be modified by application-specific completion functions + when appropriate (set in rl_complete_internal and rl_menu_complete) + +lib/readline/readline.h + - extern declaration for rl_completion_mark_symlink_dirs + +pcomplete.c + - if one of the actions is CA_DIRECTORY, set + rl_completion_mark_symlink_dirs to indicate that we want the + trailing slash (might have to relax this) + +lib/readline/doc/rltech.texinfo + - documented rl_completion_mark_symlink_dirs variable + +lib/readline/doc/rluser.texinfo, doc/bash.1 + - documented the fact that `complete -d' and `complete -o dirnames' + force readline to append a slash to symlinks to directories + +builtins/enable.def + - changed enable_shell_builtin to disallow enabling disabled + builtins in a restricted shell + +doc/{bash.1,bashref.texi} + - documented new enable behavior in restricted shells + +doc/Makefile.in + - new rule to make an `RBASH' file documenting the restrictions + imposed by a restricted shell + +expr.c + - broke the code that evaluates variables and returns results out + of readtok() into a new function: expr_streval() + - expr_streval() now performs the standard unset variable error + behavior if `set -u' has been executed and it's asked to look + up an unset variable + - broke the code that frees up the expression context stack into + a new function: expr_unwind() + +variables.c + - fixed bind_int_variable so it handles array element assignment, + so expressions like `b[7]++' and `b[0] = 42' work right + - new function, get_variable_value, returns the string value of + the SHELL_VAR * passed as an argument + - get_string_value now calls get_variable_value with a non-null + result from find_variable + + 12/20 + ----- +lib/readline/rlmbutil.h, mbutil.c + - combined _rl_find_next_mbchar and _rl_find_next_nonzero_mbchar into + a single function + - combined _rl_find_prev_mbchar and _rl_find_prev_nonzero_mbchar into + a single function + +lib/readline/{display,readline,vi_mode}.c + - changed callers of _rl_find_next_mbchar and + _rl_find_next_nonzero_mbchar + +lib/readline/{complete,display,histexpand,readline,vi_mode}.c + - changed callers of _rl_find_prev_mbchar and + _rl_find_prev_nonzero_mbchar + + 12/20 + ----- +lib/sh/mktime.c + - new file, from glibc/gawk, compiled in if system doesn't have a + working mktime(3) + +lib/sh/strftime.c + - new file, from gawk, compiled in if system doesn't have a + working strftime(3) + +lib/sh/Makefile.in, Makefile.in + - changes for mktime.c, strftime.c + +configure.in + - call AC_FUNC_MKTIME, AC_STRUCT_TM, AC_STRUCT_TIMEZONE + - call AC_REPLACE_FUNC(strftime) + +config.h.in + - add defines for TM_IN_SYS_TIME, HAVE_TZSET, HAVE_TM_ZONE, + HAVE_STRUCT_TM_TM_ZONE, HAVE_STRFTIME + +externs.h + - provide an extern declaration for strftime if HAVE_STRFTIME is + not defined and NEED_STRFTIME_DECL is + +lib/tilde/tilde.h + - header files should not include + +parse.y + - replace code in decode_prompt_string that chops up value returned + by ctime(3) with calls to strftime -- as a result, the expansion + of \@ has changed slightly (since it depends on the locale) + - added new \D{format} prompt string escape; `format' is passed to + strftime(3). Empty format is the same as `%X' (locale-specific + representation of the current time) + - combined cases for '\\', '\a', '\e', and '\r' in same case branch + in decode_prompt_string + +doc/{bash.1,bashref.texi} + - documented new \D{format} prompt string expansion + +builtins/printf.def + - use ISO C PRIdMAX instead of INTMAX_CONV + - pass length of format modifiers to mklong instead of computing it + with strlen() + +lib/sh/{fmtulong,fmtullong}.c + - changes from Paul Eggert to make more general + +arrayfunc.c + - when converting a variable to an array, make sure to unset the + dynamic_value and assign_func members of the struct variable, + since they're not valid anymore + + 12/27 + ----- +configure.in + - use AC_HELP_STRING in AC_ARG_WITH and AC_ARG_ENABLE + - remove AC_ARG_ENABLE for largefile, since AC_SYS_LARGEFILE adds + one + + 1/2/2002 + -------- +{alias,bashline,execute_cmd,general,shell,subst,variables,arrayfunc}.c,general.h + - changed some calls to strchr to calls to xstrchr for multibyte + characters + +include/shmbutil.h + - add extern declaration for xstrchr to avoid including externs.h + where it's not appropriate + +{braces,make_cmd,pathexp,subst,arrayfunc}.c, lib/sh/xstrchr.c + - include shmbutil.h + +{stringlib,subst}.c, {externs,subst}.h + - moved substring() from subst.c to stringlib.c, moved declaration + from subst.h to externs.h + +lib/sh/xmbsrtowcs.c + - new file, replacement function for mbsrtowcs + +lib/sh/Makefile.in + - add entries for xmbsrtowcs.c + +Makefile.in + - add dependencies on shmbutil.h to appropriate object files + +lib/glob/strmatch.c + - break character-class testing out into separate function: + is_cclass, in prep for multibyte changes + +{braces,make_cmd}.c + - changes for multibyte characters + +builtins/printf.def + - changes from Paul Eggert to just use intmax_t everywhere an + int/long/quad is needed and print with "%ld" if the number + fits in a long and %PRIdMAX otherwise + - remove getlong, getulong, getllong, getullong, since they're + no longer needed + - use a new type `floatmax_t' to print floating point numbers, the + widest-available floating point type (like `intmax_t'); new + function `getfloatmax' that calls strtold or strtod as appropriate + - remove getdouble, getldouble, since they're no longer needed + +lib/sh/fmtumax.c + - new file, string-to-[u]intmax_t conversion, just includes + fmtulong.c with the right defines + +Makefile.in, lib/sh/Makefile.in + - additions for fmtumax.c + +bashtypes.h + - include if it's available + +expr.c + - arithmetic is now in intmax_t instead of long + +externs.h + - extern declaration for fmtumax + - change extern declarations for evalexp, itos, inttostr, + uitos, uinttostr since they now return or use intmax_t instead + of long + +{execute_cmd,general,mailcheck,subst,variables}.c, parse.y +{array,general,subst,test,variables}.h +lib/sh/{itos,netopen}.c +builtins/{bashgetopt,common}.c, builtins/common.h +builtins/{break,fc,history,jobs,let,printf,pushd,read,shift,wait}.def + - changes for intmax_t shell arithmetic conversion + +doc/{bashref.texi,bash.1} + - documented long->intmax_t shell arithmetic conversion + +sig.c + - in initialize_terminating_signals, if we've already trapped a + terminating signal, don't reset the signal handler for it + + 1/3 + --- +{arrayfunc,pathexp}.c, parse.y + - changes for multibyte chars + +parse.y, lib/sh/strtrans.c + - moved ansiexpand from parse.y to lib/sh/strtrans.c + +parse.y, locale.c + - moved mk_msgstr and localeexpand from parse.y to locale.c + +parse.y + - new function, yy_input_name, returns name of input file from + bash_input.name + - broke the code that parses ((...)) constructs out of read_token + into a new function, parse_dparen() + +externs.h + - new extern declaration for ansiexpand(), mk_msgstr(), and + localeexpand() + +input.h + - new extern declaration for yy_input_name() + +{error,locale}.c + - use yy_input_name for error and other messages + +execute_cmd.c + - change shell_execve to make sure that the file is executable + before looking at the interpreter to find out why the execve() + failed (avoids misleading error message) + +lib/glob/glob.c + - move code that matches leading `.' and skips those filenames into + a separate function: skipname(), so there can be unibyte and + multibyte versions of that function + + 1/7 + --- +subst.c + - more changes for multibyte characters + +print_cmd.c + - change semicolon() so it doesn't output a `;' immediately after a + newline, since that results in a null command, which is a syntax + error + +variables.c + - fix indirection_level_string to turn off set -x while evaluating + PS4 + + 1/8 + --- +builtins/set.def + - make -o options into one struct, instead of separate structs for + option names corresponding to flags and non-flag option names. + This has the side effect of sorting the option names in output + +lib/glob/glob.c + - new function, mbskipname(), multibyte char version of skipname() + - removed all #ifndef SHELL code, this will never be used outside + the shell + +include/posixdir.h + - move REAL_DIR_ENTRY define here from lib/glob/glob.c + +lib/glob/glob_loop.c + - new file, included in glob.c for unibyte and multibyte versions of + glob_pattern_p + - added some forward static function declarations with prototypes + - more changes for multibyte character handling + +lib/glob/Makefile.in + - make glob.c depend on glob_loop.c + - changes for xmbsrtowcs.[co] + +lib/glob/xmbsrtowcs.c + - moved here from lib/sh, since the matching functions use it, and + libglob.a is linked after libsh.a + + 1/9 + --- +lib/glob/smatch.c + - new file, with strmatch (now xstrmatch) and associated functions, + with changes for multibyte chars + +lib/glob/sm_loop.c + - new file, included by smatch.c, with `generic' versions of matching + functions that are compiled twice: once each for single-byte and + multibyte characters + +lib/glob/strmatch.c + - strip out everything except strmatch(), which either calls fnmatch + (if HAVE_LIBC_FNM_EXTMATCH is defined) or xstrmatch + +lib/glob/collsyms.c + - changes for multibyte chars + +lib/glob/Makefile.in, Makefile.in + - changes for new source files + + 1/10 + ---- +lib/readline/complete.c + - new function, rl_completion_mode (rl_command_func_t *func), returns + the appropriate value to pass to rl_complete_internal depending on + FUNC and the value of `show-all-if-ambiguous'. This allows + application completion functions to present the same interface as + rl_complete + +lib/readline/readline.h + - new extern declaration for rl_completion_mode() + +lib/readline/doc/rltech.texinfo + - documented rl_completion_mode + +lib/readline/readline.[ch] + - bumped the version number to 4.3, changing the relevant cpp defines + +configure.in + - require that an installed readline version be at least readline-4.3 + +bashline.c + - converted bash-specific completion functions to use + rl_completion_mode instead of passing TAB unconditionally + +builtins/bashgetopt.c + - the `#' option specifier now means a required numeric argument, + not an optional one + +builtins/type.def + - when converting [-]-{path,type,all} to -[pta], don't bother + freeing and reallocating the option string; just change opt[1] + and null opt[2] + +lib/sh/snprintf.c + - support %ls/%S and %lc/%C for wide strings and characters, + respectively, if HANDLE_MULTIBYTE is defined + +mailcheck.c + - don't print a message about new mail if the file has not grown, + even if the access time is less than the modification time + + 1/14 + ---- +lib/readline/readline.c + - new function, rl_replace_line, to replace the readline line buffer + with the text supplied as an argument + - new function, rl_replace_from_history, replaces readline line + buffer with text from history entry passed as argument (undocumented, + not in readline.h because it requires a definition of + HIST_ENTRY for the prototype) + +lib/readline/readlne.h + - new extern declaration for rl_replace_line + +lib/readline/doc/rltech.texinfo + - documented rl_replace_line + +lib/readline/{isearch,readline,search}.c + - use rl_replace_line and rl_replace_from_history where appropriate + +lib/readline/readline.c + - broke the code that sets point after moving through the history + (_rl_history_preserve_point and _rl_history_saved_point) out + into a separate function, _rl_history_set_point() + +lib/readline/{complete.c,rlprivate.h} + - find_completion_word -> _rl_find_completion_word + - free_match_list -> _rl_free_match_list + +lib/readline/complete.c + - postprocess_matches and _rl_free_match_list now return immediately + if passed a null match list + +variables.c + - new function, find_local_variable, finds a local variable by name + at the current variable context + - in find_variable_internal, call find_local_variable before searching + any of the temporary environments if variable_context > 0 (meaning + we're in a shell function). This lets a local variable + override a variable whose value was passed in the `function + environment' + + 1/15 + ---- +variables.h, execute_cmd.c + - declare variables describing the temporary environments in + variables.h instead of in C files + +findcmd.c, builtins/setattr.def + - instead of calling find_tempenv_variable, use find_variable_internal + and check whether the returned SHELL_VAR * has the tempvar + attribute + +variables.c + - tentative change to lookup order in find_variable_internal so that + function local variables are found before variables in + function_env when executing a shell function + - change make_local_variable to handle making a local variable when + a variable with the same name already appears in one of the + temporary environments + - broke the body of make_var_array out into a new function: + static char **make_env_array_from_var_list (SHELL_VAR **vars) + - new function, make_var_array_internal, takes a hash table to look + in and a pointer to a mapping function and returns a char ** + environment-style list + - make_var_array now just calls make_var_array_internal + - new mapping function, local_and_exported, returns all local variables + in the current variable context with the export attribute set + - new function, make_local_export_array, returns an environment-style + char ** array of exported local variables in current context + - change environment creation order in maybe_make_export_env to + add variables to the environment in opposite order that + find_variable_internal uses. This means that local variables in + shell functions override variables with the same name in the + function_env + - change make_local_variable to set the initial value of the + variable it creates to NULL to make the `is set' and `is null' + tests that the expansion code does work right + - change make_local_variable to inherit the value of a variable with + the same name from the temporary enviroment + + 1/16 + ---- +Makefile.in + - link bashversion with buildversion.o instead of version.o, for + cross-compiling. version.o is for the target system; + buildversion.o is for the build system + +error.c + - add line numbers to internal_error() messages if the shell is + not interactive and running a shell script or a -c command + - report_error now prints non-zero line numbers for non-interactive + shells + +test.c + - test_syntax_error now calls builtin_error() instead of printing + its own messages + +builtins/common.c + - builtin_error now prints line numbers if a non-interactive shell + is running a shell script or a -c command + +print_cmd.c + - in cprintf, remove free_argp, since it's not used + +builtins/history.def + - make `history -n' increment the number of history lines in this + session by the number of lines read from the history file + +arrayfunc.c + - fix array_value_internal to expand the subscript even if the + variable is unset, so side effects produced by the arithmetic + evaluation will take place + +lib/readline/doc/{rluser,rltech}.texinfo + - some fixes for printing in @smallbook format from Brian + Youmans + + 1/17 + ---- +jobs.h + - new PRUNNING, PSTOPPED, PDEADPROC defines for PROCESSes, analogous + to RUNNING, STOPPED, and DEADJOB defines for jobs + +jobs.c + - use PS_RUNNING, PS_DONE, PS_STOPPED values for `running' field + of a PROCESS + - find_pipeline and find_job now take an additional flags argument + that, if non-zero, means to find only running processes; changed + all callers + - changed calls to find_pipeline and find_job made from waitchld + to find only running processes + - find_pipeline takes a third argument: an int *. If it looks in + the jobs list to find the pid, and the arg is non-null, it passes + the job index back to the caller. Used to avoid calls to + find_pipeline immediately followed by find_job with the same PID + +nojobs.c + - a couple of changes to make sure that set_pid_status is never + called with a pid argument of 0 or -1 + +trap.c + - change trap_handler to longjmp to wait_intr_buf (set by wait_builtin) + if a signal is received for which a trap has been set during + execution of the wait builtin (need to include builtins.h and + builtins/builtext.h and declare some extern variables for the + right things to check) + - new variable to keep track of which signal caused the longjmp to + wait_intr_buf, set by trap_handler (wait_signal_received) + +builtins/wait.def + - set the return value of wait when a longjmp(wait_intr_buf, 1) is + done to 128 + wait_signal_received + +{jobs,nojobs}.c + - set wait_signal_received to SIGINT in wait_sigint_handler before + the longjmp(wait_intr_buf, 1) + + 1/18 + ---- +bashline.c + - turn off rl_filename_completion_desired when completing a command + name with a single match only if the first char of that match is + not a `/' + - if there are multiple identical matches for a command name in + attempt_shell_completion, turn off rl_filename_completion_desired + if the first char is not a `/' to avoid readline appending a + slash if there's a directory with the same name in the current + directory + + 1/22 + ---- +lib/readline/complete.c + - new variable, _rl_page_completions, to control whether we want to + run the internal pager when listing completions (defaults to 1) + +lib/readline/rlprivate.h + - extern declaration for _rl_page_completions + +lib/readline/bind.c + - new bindable variable, `page-completions', controls value of + _rl_page_completions + +lib/readline/doc/{rluser.texinfo,readline.3}, doc/bash.1 + - documented `page-completions' variable + +Makefile.in + - use $(INSTALL_SCRIPT) instead of $(INSTALL_PROGRAM) to install + `bashbug' + +aclocal.m4 + - fix small quoting problem in RL_LIB_READLINE_VERSION macro + +lib/readline/terminal.c + - fetch and save terminal's `vs' and `ve' cursor control attributes + - fetch and save terminal's `kI' attribute (string sent by Insert) + - new function, _rl_set_cursor, sets cursor to normal (insert mode) + or very visible (overwrite mode) + +lib/readline/readline.c + - new global variable, rl_insert_mode + - new function to toggle overwrite mode, rl_overwrite_mode + - each new line starts in insert mode + - switching to vi mode or emacs mode resets to insert mode + - reset cursor to normal before returning line + - _rl_replace_text now returns the number of characters inserted, + the return value from rl_insert_text + - new function, _rl_insert_or_replace_text (const char *string, int insert), + either inserts STRING or replaces the number of chars in STRING + with STRING starting at rl_point, depending on value of INSERT + - renamed rl_insert to _rl_insert_char, rl_insert just calls + _rl_insert_char with the same arguments when in insert mode + - new function, _rl_overwrite_char, handles self-insert in overwrite + mode. Does multibyte chars by reading an entire multibyte character + before entering overwrite loop + - new function, _rl_overwrite_rubout, handles RUBOUT when in + overwrite mode, called from rl_rubout + - new function, _rl_rubout_char, old body of rl_rubout; rl_rubout + calls this when not in overwrite mode + +lib/readline/readline.h + - extern declarations for rl_insert_mode and rl_overwrite_mode() + +lib/readline/rldefs.h + - define constants for values of rl_insert_mode + +lib/readline/rlprivate.h + - extern declarations for _rl_set_cursor and _rl_set_insert_mode + - change type of _rl_replace_text to return int + - extern declarations for _rl_insert_char, _rl_rubout_char + +lib/readline/funmap.c + - new bindable name `overwrite-mode', bound to rl_overwrite_mode + +lib/readline/rlconf.h + - define CURSOR_MODE if you want the cursor to show insert or + overwrite mode (only available if both `vs' and `ve' capabilities + are present) + +lib/readline/{complete,parens,readline,search,vi_mode}.c + - change calls to rl_insert to _rl_insert_char + +lib/readline/{readline,search}.c + - change calls to rl_rubout to _rl_rubout_char to avoid overwrite + mode problems + +lib/readline/vi_mode.c + - fix rl_vi_overstrike to just call _rl_overwrite_char, which + handles multibyte chars + +lib/readline/doc/{rluser.texinfo,readline.3}, doc/bash.1 + - document new `overwrite-mode' command + + 1/23 + ---- +lib/readline/readline.c + - return 0 immediately from rl_insert_text if the string to insert + is NULL or "" + +bashline.c + - if a numeric argument is given to one of the bash-specific glob + pattern completion functions (including TAB), append a `*' to + the word before generating matches + - in attempt_shell_completion, when doing glob completion, only + set the match list to NULL if rl_completion_type == TAB and + there is more than one completion. This permits listing completions + with double tabs and displaying ambiguous completions + - new function, bash_glob_complete_word, appends a `*' to the word + to be completed and then globs it. It uses a new filename + quoting function (bash_glob_quote_filename) to avoid quoting + globbing characters in the filename if there are no matches or + multiple matches + +lib/readline/complete.c + - set completion_changed_buffer to 0 in rl_complete_internal if + no matches were produced by the completion generator function + - new variable, rl_completion_suppress_append, suppresses appending + of rl_completion_append_character. Settable by application + completion functions, always 0 when application completion + functions are called (set to 0 by rl_complete_internal and + rl_menu_complete) + - broke the code that assigns default values to readline completion + variables out of rl_complete_internal and rl_menu_complete into + a new function, set_completion_defaults (int what_to_do) + +lib/readline/readline.h + - extern declaration for rl_completion_suppress_append + +lib/readline/doc/rluser.texinfo, doc/bash.1 + - documented behavior of glob-expand-word and glob-list-expansions + when supplied a numeric argument + - documented glob-complete-word + +lib/readline/doc/rltech.texinfo + - documented rl_completion_suppress_append + + 1/24 + ---- +lib/readline/text.c + - new file, text and character handling functions from readline.c + +lib/readline/misc.c + - new file, miscellanous bindable functions and their supporting + code from readline.c + +Makefile.in, lib/readline/Makefile.in + - changes for text.c, misc.c + +lib/readline/bind.c + - change ISKMAP case of rl_invoking_keyseqs_in_map to output + ESC as "\M-" instead of "\e" -- it's closer to the documentation + - change _rl_get_keyname to output ESC as \e instead of \C-[ + (it's easier to understand) + +pcomplete.h + - new flag, COPT_NOSPACE + +builtins/complete.def + - new `-o nospace' option for complete and compgen (though it doesn't + really do anything for compgen, since that doesn't hand anything + off to readline) + +bashline.c + - if a programmable completion specifies COPT_NOSPACE, set + rl_completion_suppress_append = 1 + +lib/readline/doc/rluser.texinfo + - documented new `-o nospace' option to complete and compgen + +doc/{bash.1,bashref.texi} + - documented $'\cX' escape sequence (forgot to before) + + 1/28 + ---- +variables.c + - make_new_variable now takes the HASH_TABLE * as its second + argument; changed callers + - new function, bind_variable_in_table, takes the HASH_TABLE * as + its third paramter; bind_variable calls bind_variable_in_table + with shell_variables as third argument + +variables.h + - new struct var_context, variable context (per-scope -- global, + function local, etc.) + +variables.[ch],builtins/common.[ch] + - moved functions that push and pop a variable context from + builtins/common.c to variables.c; move extern function + declarations to variables.h + - new function, all_local_variables + - variable_in_context is now static, used only by all_local_variables + +variables.[ch],execute_cmd.c + - push_context now takes the function name as an argument for + future use + - push_context takes an indication of whether or not the function is + executing in a subshell and saves the positional parameters only + if not in a subshell + - new functions for managing a stack of variable contexts and + scopes: new_var_context, dispose_var_context, push_var_context, + pop_var_context, push_scope, pop_scope + +builtins/declare.def + - call all_local_variables instead of map_over (...) in declare_internal + - don't call make_local_variable if we're looking at functions + ((flags_on & att_function) != 0), since it's wasted + - make sure VAR is set to NULL if check for variable_context fails + and we didn't just create or fetch a local variable in + declare_internal + - in non-function branch of declare_internal, only call find_variable + if VAR is NULL -- if it's not null, we just created or fetched a + local variable and don't need to do it again + + 1/29 + ---- +variables.[ch] + - the temporary environments (temporary_env, builtin_env, function_env) + are now HASH_TABLEs instead of argv-style arrays of strings (this + is an intermediate step on the way to the new lcc-inspired symbol + table scope structure) + - new internal attribute for variables: att_propagate. This means + to propagate the value out of the temporary environment up the + (for now implicit) chain of variable scopes when the containing + temporary environment is deleted + +variables.c + - assign_in_env now adds to the HASH_TABLE temporary_env instead + of making environment-style strings in an array of strings + - changed the way the temporary environments are merged into the + shell variable table to account for the new HASH_TABLE temp + environments + - changed the way the export environment is created due to the new + structure of the temporary environments + - new function, bind_variable_internal (name, value, table), binds + NAME to have VALUE in TABLE without searching the temporary + environments + - removed: shell_var_from_env_string, bind_name_in_env_array + - variable_in_context now checks the att_local attribute and makes + sure the variable is not invisible + - local_and_exported now makes sure the variable is not invisible + +execute_cmd.c + - we no longer need to copy the temporary environment to function_env + or builtin_env, we can simply use variable assignments + +{findcmd,subst,variables}.c, builtins/{declare,setattr}.def + - since variables from the temporary environments are no longer turned + into SHELL_VARs on the fly, don't dispose the SHELL_VAR returned + by find_variable or find_variable_internal + - need to savestring() the value returned by find_variable if it has + the tempvar attribute before calling bind_variable on it, because + bind_variable will search and bind into the temporary environments + and will free the old value before binding the new. For temporary + environments, these two pointers will be the same, and + bind_tempenv_variable will end up using freed memory + +builtins/{declare,setattr}.def + - set the att_propagate attribute when exporting or making readonly + variables from the temp environment (i.e., `var=value declare -x var' + or `var=value export var' sets the propagate attribute on the entry + for `var' in the temporary environment HASH_TABLE) + +lib/readline/isearch.c + - ^W when reading isearch string yanks the current word out of the + current line into the search string, skipping the portion already + matched + - ^Y when reading isearch string yanks the rest of the current line + into the search string, skipping the portion already matched + + 1/30 + ---- +{print_cmd,variables}.c + - moved indirection_level_string() from variables.c to print_cmd.c + +{externs,variables}.h + - moved extern declaration of indirection_level_string to externs.h + +{general,variables}.c + - moved assignment() from variables.c to general.c + +{general,variables}.h + - moved extern declaration of assignment() to general.h + +{externs,input}.h + - moved extern declaration of decode_prompt_string to externs.h + +print_cmd.c + - include flags.h, don't include stdc.h + +variables.c + - moved some functions around to group functions better + - changed new_shell_variable to explicitly initialize each member + of the created struct variable instead of calling bzero() + - make_new_variable now just calls new_shell_variable instead + of duplicating what it does + - removed some code in bind_function that duplicated what + new_variable does on the newly-created SHELL_VAR + - since there are no local function variables (functions are always + made at the global scope), kill_all_local_variables() doesn't + need to consider functions + + 1/31 + ---- +variables.c + - sort the array of special variables + - short-circuit the search in stupidly_hack_special_variables if + the passed name can't be found in the rest of the array + (that is, if name[0] < special_vars[i].name[0]) + +lib/readline/history.c + - unstifle_history() was returning values exactly opposite of + the documentation + +lib/readline/doc/{hsuser.texinfo,history.3} + - clarified the unstifle_history() documentation a little + + 2/4 + --- +variables.c + - in bind_variable, don't call bind_tempenv_variable after a + find_tempenv_variable succeeds -- just change the value inline. + There's no reason to look it up twice + - change makunbound to only call stupidly_hack_special_variables + if we're not unsetting a function + +variables.[ch] + - new function, unbind_function, like makunbound but doesn't mess + with previous contexts or calling stupidly_hack_special_variables + +builtins/set.def + - change unset_builtin to call either unbind_func or unbind_variable + +builtins/getopts.def + - call unbind_variable(name) instead of makunbound(name, shell_variables) + + 2/5 + --- +lib/glob/sm_loop.c + - use malloc instead of xmalloc in BRACKMATCH and handle failures + +error.c + - add extern declaration of executing_line_number with prototype, + since execute_cmd.h can't be included without including other + files + +lib/readline/parens.c + - include + +lib/malloc/stats.c + - include + - add extern declaration of malloc_free_blocks() with prototype + +pathexp.c + - added some forward declarations with prototypes for static functions + +lib/readline/rlprivate.h + - removed declarations of rl_untranslate_keyseq, rl_discard_argument, + rl_stop_output, rl_alphabetic since they appear in readline.h + + 2/6 + --- +{arrayfunc,execute_cmd,pcomplete,shell}.c + - change calls to makunbound(name, shell_variables) to + unbind_variable (name) + + 2/7 + --- +builtins/getopt.c + - don't defer incrementing of OPTIND when an invalid option is + encountered until the next call to sh_getopt() -- what if OPTIND + is reset before that next call? This means that OPTIND is always + incremented to the next option to be handled when an option is + returned, whether it's valid or not. This is what POSIX-2002 + says to do. + +syntax.h + - new #define, CSUBSTOP + +mksyntax.c + - add "-=?+" with value CSUBSTOP to the syntax table. These are the + valid expansion operators OP in ${param[:]OPword} + +subst.c + - use table lookup for CSUBSTOP in VALID_PARAM_EXPAND_CHAR + - new flags for the string extraction functions: EX_NOALLOC. This + indicates that the functions are being used only to skip over + strings and the result won't be used, so the substring shouldn't + be allocated, copied, and freed + - new flag for string_extract: EX_VARNAME. This serves the same + purpose as the old `varname' parameter. parameter_brace_expand() + changed appropriately + - extract_delimited_string and extract_dollar_brace_string now take + an additional `flags' argument, which may include EX_NOALLOC + - changed callers of extract_delimited_string and + extract_dollar_brace_string appropriately + - string_extract now understands EX_NOALLOC; callers changed + - some smaller code cleanups + - converted char_is_quoted(), unclosed_pair(), and skip_to_delim() + to understand multibyte characters + + 2/11 + ---- +variables.[ch] + - moved to a symbol organization inspired by lcc. The basic structure + is no longer a HASH_TABLE, but a VAR_CONTEXT, which includes a hash + table as one of its members. VAR_CONTEXTs are linked together to do + variable scoping. One nice thing about this is that the entire + symbol table doesn't need to be searched at function scope exit to + remove local variables. Fixes problems with only one instance of + builtin_env and function_env, even though it really is a stack + - shell_variables is now a VAR_CONTEXT *, with a global_variables + variable that points to the bottom of the stack for fast access + - function-scope local variables (assignments specified on the command + line before a function call) and function-local variables (declared + with the `local' builtin) have been unified in the same variable + context, replacing function_env + - assignment statements preceding the `.' and `eval' builtins are now + a separate variable scope VAR_CONTEXT, replacing builtin_env + - temporary_env (a HASH_TABLE) is now the only separate environment + - changes to export environment creation, variable binding, variable + lookup, local variable propagation all changed to work with the + new symbol table/scope structure + - a SHELL_VAR no longer has a `prev_context' member; it's not needed + +execute_cmd.c + - changes to push_context calls to include any temporary variables in + temporary_env; pop_context takes care of propagating any temporary + variables if necessary + - calls to push_scope if `eval' or `.' is called with a list of + preceding variable assignments, and pop_scope called at end of + builtin's execution. pop_scope takes care of merging temporary + variables into the shell environment when appropriate + +builtins/{setattr,declare}.def + - changes to account for variable assignments preceding `local', + `export', `readonly', `declare', etc. to work with the new + variable scoping implementation + +shell.c + - since shell_variables is now a VAR_CONTEXT, call + delete_all_contexts() when the shell is reinitializing instead of + delete_all_variables() + +builtins/common.c + - new function, get_job_by_name(), used by execute_simple_command() + for the `auto_resume' stuff and get_job_spec() + +builtins/common.h + - new set of #defined constants for flags argument to + get_job_by_name() + + 2/12 + ---- +command.h + - new redirection operator: r_reading_string for `here strings' + +parse.y + - new token, LESS_LESS_LESS, for new redirection `here string' + operator: [N]<<< word + - recognize LESS_LESS_LESS and create the appropriate redirection + +{dispose_cmd,copy_cmd,make_cmd,print_cmd}.c + - recognize r_reading_string and do the right thing (dispose_redirects, + copy_redirect, print_redirection, and make_redirection, respectively) + +redir.c + - here_document_to_fd now takes the redirection operator as its + second argument + - new function, write_here_string, expands a here string and writes it + to the here document file descriptor + - here_document_to_fd calls write_here_string for r_reading_string + operator + - handle r_reading_string in do_redirection_internal() and + stdin_redirection() + + 2/18 + ---- +doc/{bash.1,bashref.texi} + - documented here strings + +{configure,Makefile}.in + - bumped version number up to bash-2.05b and the release status + to alpha1 + +expr.c + - make expr_streval understand that variables with the `invisible' + attribute are really unset, and accessing such a variable when + `set -u' is set should be an error + +variables.h + - new accessor macros: var_isset(var) and var_isnull(var), test + whether var->value is NULL + +{eval,subst,variables}.c, builtins/{declare,setattr}.def + - be more consistent about using value_cell(var) instead of + directly referencing var->value + - use var_isset and var_isnull where appropriate + +builtins/help.def + - augmented a couple of help strings with pointers to `info' and + `man -k' + + 2/14 + ---- +variables.h + - new macros to use when setting variable values directly instead of + through bind_variable and its siblings + +{arrayfunc,variables}.c + - use var_setarray and other lvalue macros instead of assigning to + var->value directly + +builtins/setattr.def + - change show_var_attributes to show function definitions separately + from function attributes. This allows the output of `declare -f' + (with other flags), `export -f', and `readonly -f' to be reused as + shell input, instead of the old + + declare -f[flags] func() + { + foo + } + + which has syntax errors. When in posix mode, `export -fp' and + `readonly -fp' still don't print function definitions + + 2/16 + ---- +parse.y + - comment out calls to discard_parser_constructs; no need to call + empty functions + + 2/18 + ---- +lib/sh/memset.c + - replacement function for memset(3) + +lib/sh/Makefile.in, Makefile.in + - additions for memset.c + +configure.in,config.h.in + - check for memset, define HAVE_MEMSET if found, add memset.o to + LIBOBJS if not + +lib/malloc/malloc.c + - removed zmemset(), replaced with calls to memset(3) + +{subst,execute_cmd,lib/sh/netopen}.c + - replaced calls to bzero with calls to memset + +subst.c + - word_split() now takes a second argument: the value of $IFS, so + it doesn't have to look up IFS every time + - word_list_split() now calls getifs() and passes the result to + each call to word_split() as its second arg + - do a quick scan for CTLNUL in remove_quoted_nulls before allocating + new string, copying old string to it, copying over original string + and freeing new string + +eval.c + - don't bother calling dispose_used_env_vars if temporary_env is NULL + +execute_cmd.c + - fix fix_assignment_words to only look up the builtin corresponding + to the first word if one of the words in the list is marked as + W_ASSIGNMENT + +hashlib.c + - renamed hash_string to hash_bucket, which better reflects what it + does + - extracted the portion of hash_bucket that computes the hash out + into a new hash_string() + - made new body of hash_bucket into a macro HASH_BUCKET; function + just calls the macro + - calls to hash_bucket in this file now call HASH_BUCKET macro + - in add_hash_item, just add a new item at the front of the appropriate + bucket list instead of at the end + +hashcmd.h + - reduced FILENAME_HASH_BUCKETS to 53 from 107 + + 2/19 + ---- +hashlib.[ch] + - find_hash_item, remove_hash_item, add_hash_item all take a new + third `flags' argument + - add_hash_item doesn't call find_hash_item if HASH_NOSRCH passed in + flags arg + - find_hash_item will create a new hash table entry if HASH_CREATE is + passed in flags arg + - new function, hash_walk, takes a pointer to a function and a table + and calls the function for each item in the table. If the function + returns < 0, the walk is terminated + - fixed flush_hash_table to set table->nentries to 0 after freeing + all entries + - BUCKET_CONTENTS now has a new `khash' member, what key hashes to; + set by HASH_BUCKET macro (which calls hash_string), assigned in + find_hash_item (HASH_CREATE) and add_hash_item + - find_hash_item and remove_hash_item check `khash' against the + hash of the string argument before calling strcmp + +{alias,hashlib,hashcmd,pcomplib,variables}.c + - changed all calls to {find,remove,add}_hash_item + +builtins/hash.def + - return immediately from print_hashed_commands if there are no + entries in the hash table (this eliminates need for `any_printed' + variable) + - change print_hashed_commands to use hash_walk + +alias.c + - short-circuit all_aliases and map_over_aliases if + HASH_ENTRIES(aliases) == 0 + - simplify map_over_aliases by just allocating enough room in the + returned list for all entries in the aliases hash table, instead + of doing the check and xrealloc + - add_alias now calls add_hash_item with HASH_NOSRCH argument + +pcomplete.h + - sh_csprint_func_t is no more; use hash_wfunc instead + +pcomplib.c + - short-circuit print_all_compspecs if HASH_ENTRIES(prog_completes) + is 0 + - print_all_compspecs now takes a `hash_wfunc *' argument + - print_all_compspecs now just calls hash_walk + +builtins/complete.def + - new function, print_compitem, takes a BUCKET_CONTENTS *, extracts + the right info, and calls print_one_completion + +variables.c + - short-circuit map_over_funcs if HASH_ENTRIES(shell_functions) == 0 + - short-circuit flatten if the passed table has no entries + - bind_variable_internal takes a new fourth argument: `hflags', + to pass to hash table functions + - make_new_variable now passes HASH_NOSRCH flag to add_hash_item + - set_if_not now calls bind_variable_internal and passes + HASH_NOSRCH as flags argument + - bind_function now calls add_hash_item with HASH_NOSRCH argument + - fixed make_local_variable: old_var == 0 && was_tmpvar can never + be true + - if we didn't find an old variable in make_local_variable, call + bind_variable_internal with HASH_NOSRCH argument + - fix push_temp_var to reset variable context to 0 if binding into + global_variables->table + +parse.y + - fix to parse_compound_assignment to avoid core dumps on empty + compound array assignments + +subst.c + - getifs() is now global so read_builtin can call it + +subst.h + - extern declaration for getifs() + + 2/20 + ---- +hashlib.c + - changed hash_string to use a better hash function + - changed HASH_BUCKET to use masking rather than modulus to hash a + string to a bucket -- HASH TABLES MUST NOW BE SIZED BY POWERS + OF TWO + +hashlib.h + - DEFAULT_HASH_BUCKETS is now 64 + +hashcmd.h + - FILENAME_HASH_BUCKETS is now 64 + +pcomplib.c + - COMPLETE_HASH_BUCKETS is now 32 + +variables.c + - TEMPENV_HASH_BUCKETS is now 4 + +alias.c + - new define, ALIAS_HASH_BUCKETS, set to 16, used to size alias table + +hashlib.c + - removed initialize_hash_table; folded code into make_hash_table + - fixed copy_bucket_array to copy the `khash' member of an item + - renamed functions to be more systematic and easier for me: + make_hash_table -> hash_create + hash_table_nentries -> hash_size + copy_hash_table -> hash_copy + find_hash_item -> hash_search + remove_hash_item -> hash_remove + add_hash_item -> hash_insert + flush_hash_table -> hash_flush + dispose_hash_table -> hash_dispose + print_table_stats -> hash_pstats + get_hash_bucket -> hash_items + - changed hash_search to short-circuit if table->nentries == 0 and + HASH_CREATE has not been passed in the flags argument + +{alias,variables,hashcmd,pcomplib}.c + - renamed calls to all renamed functions from hashlib.c + +builtins/kill.def + - don't drop a leading `-' in a pid argument + - call kill_pid with an explicit third argument of 1 if the pid + argument to kill is < -1, rather than rely on the behavior of + kill(2) + + 2/21 + ---- +subst.c + - quoted_strchr is no longer declared `inline' + - skip_double_quoted is no longer declared `inline' + - string_extract_double_quoted is no longer declared `inline' + +lib/readline/input.c + - rl_gather_tyi is now an `int' valued function; returns the number + of characters read (0 or 1) or -1 on error + - if rl_gather_tyi() returns -1 to rl_read_key(), set rl_done to 1 + and return a newline; something is wrong with the input fd + + 2/25 + ---- +variables.[ch] + - IFS is now a special variable + - new special var function, sv_ifs(), called when IFS is set or unset + - call setifs() when IFS is first set in initialize_shell_variables + - call setifs() from make_local_variable and assign_in_env if + appropriate + - if assign_in_env() is called with a var assignment like `VAR=', + make the value in the new SHELL_VAR created be "" like + do_assignment_internal does, since certain parts of the shell use + a NULL value as evidence that the variable is unset (though + attributes may have been assigned) + - if push_temp_var pushes something up to the global_variables table, + make sure that the context is set to 0 + - new function dispose_temporary_env, called by both + dispose_used_env_vars and merge_temporary_env with different `free + func' function pointers; calls sv_ifs after disposing the temporary + environment + - push_exported_var now calls bind_variable_internal instead of + bind_variable + - pop_scope and pop_context now call sv_ifs + +subst.[ch] + - new global variables used to keep track of IFS state, to avoid + having to call find_variable("IFS") all the time: + + ifs_var the SHELL_VAR for IFS + ifs_value ifs_var ? value_cell (ifs_var) : " \t\n" + ifs_cmap bitmap of characters in ifs_value + ifs_firstc first character in ifs_value + + - new function setifs(), sets the aforementioned ifs variables each + time IFS is set or unset, and at nested scope exit + - instead of calling getifs() from inside subst.c, use ifs_value + - getifs() now just returns ifs_value + - use ifs_firstc in string_list_dollar_star() + - only call member() in issep() if separators is more than one char + - don't cache a bitmap every time expand_word_internal() is called; + use ifs_cmap instead + - new macro, isifs(c), checks whether C is in ifs_cmap + +builtins/read.def + - use issep() and isifs() macros instead of looking at $IFS directly + +syntax.h + - make sure macros that access sh_syntaxtab cast the argument to + `unsigned char' before array access + - new macros: issyntype(c, type) and notsyntype(c, type), check + sh_syntaxtab[c] for a particular flag value `type' + + 2/26 + ---- +hashlib.h + - the `data' member of a `BUCKET_CONTENTS' is now a PTR_T + +{hashlib,alias,variables,hashcmd,pcomplib}.c + - removed some casts when assigning to and using `data' member of a + `BUCKET_CONTENTS' + +subst.c + - in split_at_delims, call make_word_list instead of allocating and + initializing a WORD_LIST * directly + +make_cmd.[ch] + - add_string_to_list is now just a macro that calls make_word_list + - make_simple_command now calls make_word_list instead of allocating + a WORD_LIST * directly + + 2/27 + ---- +copy_cmd.c + - copy_word now calls make_bare_word to allocate the copy + - copy_word_list now calls make_word_list to allocate the copy + +shell.h + - include `ocache.h' for simple object caching + - call cmd_init() to initialize the WORD_DESC and WORD_LIST object + caches + +{make,dispose}_cmd.c + - allocate WORD_DESC * and WORD_LIST * vars from their respective + ocaches, and return them to the cache when disposing + +jobs.c + - renamed old `waiting_for_job' variable to `queue_sigchld', which + better reflects its intent: sigchld_handler does not call waitchld + if `queue_sigchld' is non-zero, it simply increments the count of + waiting children + - cleanup_dead_jobs now just sets and clears queue_sigchld instead of + blocking and unblocking SIGCHLD; it calls waitchld at the end if + `sigchld' is non-zero, but that's not really necessary + - in setjstatus, only call xrealloc if `statsize' is less than the + number of processes passed -- no reason to do it if they're the + same + + 2/28 + ---- +sig.[ch] + - reinitialize_signals is no more; initialize_signals takes an + argument saying whether or not we are reinitializing + +builtins/exec.def + - reinitialize_signals() -> initialize_signals(1) + +test.c + - fix filecomp() to work right when one file has a non-positive + timestamp and the other file does not exist + +doc/{bash.1,bashref.texi} + - document what happens for test's -nt and -ot operators when one + file operand exists and the other does not + +jobs.c + - if we haven't messed with SIGTTOU, just manipulate queue_sigchld + in notify_of_job_status instead of calling sigprocmask() + - list_one_job now calls pretty_print_job directly instead of going + through print_job + - pretty_print_job now must be called with SIGCHLD blocked or held + instead of blocking SIGCHLD itself + - changed start_job so that it doesn't call UNBLOCK_CHILD and then + immediately call BLOCK_CHILD again (explicitly or via last_pid()), + call find_last_pid instead of last_pid and then UNBLOCK_CHILD + - changed wait_for_job the same way + - find_last_pid now takes a second argument: block; uses BLOCK_CHILD + if `block' is 1, not otherwise. Changed existing calls: + find_last_pid(j) -> find_last_pid(j, 0) + last_pid(j) -> find_last_pid(j, 1) + `last_pid()' is now gone + - rewrote wait_for_background_pids(); it was a little strange + +copy_cmd.c + - copy_if_command: don't copy null false_case commands + - copy_simple_command: don't copy a null redirection list + +subst.c + - in get_word_from_string and list_string, just check for " \t\n" + directly rather than calling strcmp + - in get_word_from_string and strip_trailing_ifs_whitespace, use + isifs() instead of issep(), since they're never called with + separators != $IFS + - change issep() to call isifs if separators is longer than one + character, since it's never called with anything but "", " ", + or $IFS + + 3/1 + --- +sig.h + - enclose the BLOCK_SIGNAL macro in a do {...} while (0) loop, at it + should have been all along + +lib/readline/doc/rltech.texinfo + - document that readline defaults to stdin/stdout if rl_instream/ + rl_outstream are NULL + +lib/readline/terminal.c + - if an application is using a custom redisplay function, + rl_resize_terminal just calls rl_forced_update_display to tell + (*rl_redisplay_func) to update the display, otherwise call + _rl_redisplay_after_sigwinch + +lib/readline/readline.c + - change readline_internal_setup() so the change to vi insertion mode + happens even if readline_echoing_p is 0 + - don't print the prompt to rl_outstream in readline_internal_setup + if we're not echoing and the caller has defined a custom redisplay + function -- let the redisplay function deal with it + +configure.in + - new option: --enable-mem-scramble, controls memory scrambling on + free() (on by default; only affects use of bash malloc) + +config.h.in + - new option MEMSCRAMBLE, controlled by --enable-mem-scramble + + 3/5 + --- +parse.y + - added ksh-like behavior of [...] to read_token_word: if a `[' is + seen in an assignment context and the previous characters in the + token form a valid identifier, parse the [...] with + parse_matched_pair to allow spaces (and newlines) in the subscript + +bashline.c + - new function bash_servicename_completion_function, for completing + service names from /etc/services + +bashline.h + - new extern declaration for bash_servicename_completion_function + +builtins/complete.def + - allow new `-s/-A service' option to complete and compgen builtins + +pcomplete.h + - new CA_SERVICE define, new ITEMLIST variable it_services + +pcomplete.c + - add callback to bash_servicename_completion_function to generate + list of matching service names for completion + +doc/bash.1,lib/readline/doc/rluser.texinfo + - documented new `-s/-A service' option to complete and compgen + + 3/6 + --- +builtins/read.def + - change hard-coded `0' to new variable `fd' (initially 0) in + preparation for adding `-u fd' option + +bashline.c + - bash_directory_completion_hook calls expand_prompt_string instead + of expand_string (it does the right thing). This keeps expansion + errors from causing a longjmp, which shouldn't happen because of + completion + - command_subst_completion_function was augmented very slightly to + do filename completion on a non-command-word in a command + substitution + - command_subst_completion_function now skips over the lcd that + rl_completion_matches puts in matches[0] if there is more than + one possible completion + + 3/7 + --- +builtins/read.def + - only add the unwind_protect to free `rlbuf' if `edit' is non-zero, + since we won't be using readline otherwise + +lib/sh/zread.c + - renamed zread1 -> zreadintr + +redir.c + - small change to redirection_error() to make a slightly better + guess about the invalid file descriptor if the redirection op is + r_duplicating_input or r_duplicating_output + +include/stdc.h + - new macro, SH_VA_START, to encapsulate the difference between + stdarg va_start and varargs va_start + +{error,pcomplete,print_cmd}.c,builtins/common.c,lib/sh/snprintf.c + - use SH_VA_START + + 3/8 + --- +builtins/read.def + - support for the ksh-like `-u fd' option + +general.c + - new function sh_validfd(fd), returns 1 if fd is a valid open file + descriptor + +general.h + - extern decl for sh_validfd + +bashline.c + - don't call posix_readline_initialize() from initialize_readline(); + sv_strict_posix() should already have taken care of it + + 3/11 + ---- +{error,pcomplete,print_cmd}.c, builtins/common.c + - removed non-varargs versions of functions + +builtins/printf.def + - if the string argument to %q has non-printing characters, call + ansic_quote to quote it rather than sh_backslash_quote + +variables.h + - new attribute: att_trace (and corresponding trace_p() macro). + Functions with this attribute will inherit the DEBUG trap. + Currently ignored for variables + +builtins/declare.def + - new `-t' option to declare/typeset toggle the `att_trace' attribute + +builtins/setattr.def + - check for att_trace and output `-t' flag in show_var_attributes + +execute_cmd.c + - if a function is being traced (it has the `-t' attribute set), + don't turn off the DEBUG trap when it executes + +doc/{bash.1,bashref.texi} + - document the new `-t' option to declare/typeset + + 3/12 + ---- +execute_cmd.c + - don't execute the debug trap in the `cm_simple:' case of + execute_command_internal; run it in execute_simple_command so we + get the line number information right when executing in a shell + function + - run a DEBUG trap before executing ((...)) arithmetic commands, + like ksh93 + - run a DEBUG trap before executing [[...]] conditional commands, + like ksh93 + +eval.c + - add a static forward declaration for alrm_catcher() + +general.c + - add static forward declarations for bash_special_tilde_expansions, + unquoted_tilde_word, initialize_group_array + +variables.h + - add extern declarations for sh_get_env_value, map_over_funcs, + local_exported_variables + +variables.c + - add static forward declarations for dispose_temporary_env, + make_func_export_array + +bashhist.c + - add static forward declaration for check_history_control + +configure.in + - add a call to AC_CHECK_DECLS for strcpy + +config.h.in + - add placeholder for HAVE_DECL_STRCPY define, set by configure + +general.h + - don't declare strcpy if HAVE_DECL_STRCPY is defined with a non-zero + value + +sig.h + - add prototype to typedef of SigHandler + +lib/readline/histlib.h + - removed extern declaration of strcpy() + - include string.h/strings.h directly in histlib.h instead of source + files + +lib/readline/{histexpand,histfile,history,histsearch}.c + - don't include string.h/strings.h now that histlib.h includes it + +lib/tilde/tilde.c + - removed extern declaration of strcpy(), rely on string.h/strings.h + +command.h + - four new redirection types: r_move_input, r_move_output, + r_move_input_word, r_move_output_word, for + [N]<&word- and [N]>&word- from ksh93 + +print_cmd.c + - changes to print r_move_input[_word] and r_move_output[_word] + +copy_cmd.c + - changes to copy r_move_input[_word] and r_move_output[_word] + +dispose_cmd.c + - changes to dispose r_move_input_word and r_move_output_word + +make_cmd.c + - changes to make r_move_input[_word] and r_move_output[_word] from + r_duplicating_{input,output}_word, which is how the new redirs + are passed by the parser + +redir.c + - changes to make r_move_input[_word] and r_move_output[_word] do + the right thing when executed + +builtins/read.def + - print an error message and return failure immediately if zread/zreadc + return < 0 + +doc/{bash.1,bashref.texi} + - documented new [n]<&word- and [n]>&word- redirections + + 3/13 + ---- +lib/readline/isearch.c + - enabled code to allow chars bound to rl_rubout to delete characters + from the incremental search string + +shell.c + - add `-l' invocation option to parse_shell_options; equivalent to + `--login' + - fixed set_login_shell to check first char of base pathname of argv0 + for `-', like other shells + - move the check for make_login_shell after the call to + parse_shell_options because the `-l' option might set it + +doc/{bash.1,bashref.texi} + - documented new `-l' invocation option + +array.c + - new function, array_shift, shifts an array left by a specified + number of elements + - array_walk is now compiled in by default + - array_to_assignment_string now takes a second argument: int quoted. + If non-zero, the result is single-quoted before being returned + - quoted_array_assignment_string has been removed + +array.[ch] + - renamed most of the array functions so that all have an array_ + prefix and are more systematically named + - array_slice now preserves the indicies from the original array + - change array_to_assign to use a static buffer for expanding the + array indices, instead of malloc/free + +{arrayfunc,subst,variables}.c, builtins/read.def + - changed calls to various array functions to use new names + +lib/sh/stringvec.c, externs.h + - renamed all of the functions to have a strvec_ prefix and to have + a more sensible name scheme + - strvec_search's arguments are now supplied in reverse order, so + the char **array is first, like the other functions + - new function, strvec_resize, xrealloc for strvecs + +{alias,array,bracecomp,braces,bashline,execute_cmd,findcmd,general,pathexp, +pcomplete,variables}.c +lib/sh/stringlist.c +builtins/{bind,complete,exec,getopts,pushd,set}.def + - change calls to all functions from lib/sh/stringvec.c + - use strvec_resize where appropriate + +externs.h + - only declare dup2() if HAVE_DUP2 is undefined or DUP2_BROKEN is + defined + +lib/readline/{macro,readline,util}.c, lib/readline/rlprivate.h + - _rl_defining_kbd_macro is gone, use RL_ISSTATE(RL_STATE_MACRODEF) + +lib/readline/readline.h + - new struct readline_state, encapsulates most of readline's internal + state in case you need reentrancy or nested calls to readline() + - extern declarations for rl_save_state, rl_restore_state + +lib/readline/readline.c + - add (undocumented) int rl_save_state (struct readline_state *), + int rl_restore_state (struct readline_state *) + + 3/14 + ---- +array.[ch] + - new function, array_rshift, shifts an array right by a specified + number of elements, optionally inserting a new element 0 + +examples/bashdb/bashdb + - new single-file version of bash debugger, originally modified from + version in bash-2.04 by Gary Vaughan (the old debugger still + appears in examples/obashdb). This version has a more gdb-like + command set + +examples/bashdb/bashdb.el + - new emacs bashdb debugger mode from Masatake YAMATO + +execute_cmd.c + - don't make $LINENO relative to function start unless the shell is + currently interactive -- this is what ksh93 does and what I + believe to be the intent of POSIX.2 (this required changing some + of the test checks because the output has changed) + - run the debug trap for each command in an arithmetic for expression, + like ksh93 does + +lib/readline/vi_mode.c + - redid rl_vi_subst (binding func for `s' and `S') in terms of + rl_vi_change_to: `S' == `cc' and `s' == `c '. This makes undo + work right + + 3/18 + ---- +hashlib.c + - fixed hash_walk to return if the item function returns < 0, instead + of breaking out of the current hash chain + +array.c + - fixed array_walk to return if the item function returns < 0, like + hash_walk + +lib/sh/stringlist.c, externs.h + - new function: strlist_walk, takes a stringlist and a pointer to an + item func. Like other _walk funcs, if item func returns < 0 the + walk is cancelled + - new function: strlist_flush, frees items in the contained list + with strvec_flush + - renamed functions to have a strlist_ prefix and be more systematic + +pcomplib.c,pcomplete.h + - removed redundant `progcomp_initialized' variable + - renamed functions to have `progcomp_' or `compspec_' prefixes + like the hash library + +{bashline,pcomplete}.c,builtins/complete.def + - fixed calls to stringlist functions to use new names + - fixed calls to functions in pcomplib.c to use new names + +pcomplete.c + - made the debugging code #ifdef DEBUG -- it should be mature enough + +builtins/hash.def,parse.y + - use REVERSE_LIST(x, t) instead of (t)reverse_list(x) + +list.c,{externs,general}.h + - renamed the list functions to have a list_ prefix, changed callers + +externs.h,{execute_cmd,stringlib,subst}.c,builtins/common.c,lib/sh/stringvec.c + - word_list_to_argv -> strvec_from_word_list + - argv_to_word_list -> strvec_to_word_list + - moved functions to lib/sh/stringvec.c + +lib/sh/stringvec.c + - changed name of second argument to strvec_from_word_list from `copy' + to `alloc' so the use of `copy' between strvec_from_word_list and + strvec_to_word_list isn't as confusing + - changed name and sense of second argument to + strvec_to_word_list from `copy' to `alloc' for the same reason -- + now both functions agree on semantics of second argument + +lib/sh/stringlist.c + - ditto for strlist_from_word_list and strlist_to_word_list + +subst.c + - changed callers of strvec_to_word_list + + 3/19 + ---- +builtins/hash.def + - added `-l' option to list table or individual targets in reusable + format + - added `-d' option to remove one or more names from the table of + hashed commands (provides `unhash' or `unalias -t' functionality) + +doc/{bash.1,bashref.texi} + - documented new `-l' and `-d' options to `hash' + +hashcmd.[ch] + - renamed functions to have a `phash_' prefix and follow new naming + convention + - phash_remove now returns an int: 1 if command not in hash table, + 0 if filename removed OK + +{findcmd,variables}.c, builtins/{hash,type}.def + - changed callers to use new names from hashcmd.c + +builtins/common.[ch] + - new function, sh_notfound(s), prints standard `not found' message + - new function, sh_invalidid(s), prints standard `invalid identifier' + message + - new function, sh_restricted(s), prints standard `restricted' message + for restricted shells + - new function, sh_invalidnum(s), prints standard `invalid number' + message + - renamed bad_option to sh_invalidopt, changed to print + `invalid option' instead of `unknown option' + - new function, sh_invalidoptname, prints standard `invalid option + name' for long options + - new function, sh_badjob (s), prints standard `no such job' message + - new function, sh_invalidsig (s), prints standard `invalid signal + specification' message + - new function, sh_nojobs (s), prints standard `no job control' message + - new function, sh_needarg (s), prints standard `option requires an + argument' message + - new function, sh_neednumarg (s), prints standard `numeric + argument required' message + - new function, sh_badpid(s), prints standard `not a pid...' message + - new function, sh_erange (s, desc) prints standard `out of range' + message, optionally using `desc' to say what the argument is + +builtins/{alias,command,declare,exec,hash,type}.def + - call sh_notfound() instead of calling builtin_error directly + +builtins/{declare,getopts,read,set,setattr}.def + - call sh_invalidid() instead of calling builtin_error directly + +builtins/{cd,command,enable,exec,hash,source}.def + - call sh_restricted() instead of calling builtin_error directly + +builtins/{printf,read,ulimit}.def, builtins/common.c + - call sh_invalidnum instead of calling builtin_error directly + +builtins/{complete,declare,pushd,set}.def, builtins/bashgetopt.c + - call sh_invalidopt instead of bad_option or builtin_error directly + +builtins/{complete,set,shopt}.def + - call sh_invalidoptname instead of builtin_error directly + +builtins/{fg_bg,jobs,kill,wait}.def + - call sh_badjob instead of calling builtin_error directly + +builtins/common.c, builtins/{kill,signal}.def + - call sh_invalidsig instead of calling builtin_error directly + +builtins/{fg_bg,suspend,wait}.def + - call sh_nojobs instead of calling builtin_error directly + +builtins/{common,bashgetopt}.c, builtins/{hash,kill}.def + - call sh_neednumarg and sh_needarg where required + +builtins/{kill,wait}.def + - call sh_badpid where required + +builtins/{break,fc,history,pushd,shift,ulimit,umask}.def + - call sh_erange where appropriate + +builtins/printf.def + - new static function, printf_erange, prints standard out-of-range + warning message + +builtins/set.def + - changed so that calls to sh_invalidopt always include the leading + `+' or `-' + +builtins/shopt.def + - changed SHOPT_ERROR macro to shopt_error function + +builtins/bind.def + - regularized error messages to `bind: object: error string' like + other error messages + +builtins.h + - the `short_doc' member of a `struct builtin' is now of type + `const char *' + - the strings in `long_doc' array of a struct builtin are now const + +builtins/mkbuiltins.c + - changes for new `const' members of struct builtin + + 3/20 + ---- +lib/readline/histfile.c + - use pointers instead of indexing into buffer when reading the + contents of the history file in read_history_range and + history_truncate_file + + 3/21 + ---- +lib/readline/histfile.c + - new file, with code to mmap the history file for reading and + writing (depends on HAVE_MMAP, currently nothing checks for that) + + 3/25 + ---- +error.[ch] + - new function, err_badarraysub(s), calls report_error with standard + `bad array subscript' message + - new function, err_unboundvar(s), calls report_error with standard + `unbound variable' message + - new function, err_readonly(s), calls report_error with standard + `readonly variable' message + +{arrayfunc,subst}.c + - call err_badarraysub where appropriate + +{expr,subst}.c + - call err_unboundvar where appropriate + +{arrayfunc,variables}.c + - call err_readonly where appropriate + +shell.c + - changed text of bad option error messages to be the same as that + printed for builtin errors + +builtins/common.c + - changed sh_invalidopt to print the invalid option before the rest + of the error message (required some tests to be modified) + - new function, sh_readonly, calls builtin_error with standard + `readonly variable' message + +variables.c,builtins/declare.def + - call sh_readonly where appropriate + +lib/sh/stringvec.c + - added strvec_remove (sv, s), removes S from SV and shuffles rest of + elements down 1 + +lib/sh/stringlist.c + - added strlist_remove(sl, s), just calls strvec_remove on the + component list + +externs.h + - new extern declarations for strvec_remove and strlist_remove + - fixed extern declaration for strvec_search; the arguments were + reversed (unimportant, it's not compiled into the shell) + +subst.c + - change param_expand to call quote_escapes on values retrieved when + expanding the positional parameters + - change parameter_brace_expand_word to quote escapes on values + retrieved when expanding the positional parameters + - fix parameter_brace_substring to quote escape characters on unquoted + substrings extracted from variable values (needed to separate case + VT_VARIABLE from VT_ARRAYMEMBER for this, since, because + get_var_and_type calls array_value for VT_ARRAYMEMBER, we need to + skip over quoted characters in an already-appropriately-quoted + string to find the substring we want) + - fix parameter_brace_substring to quote escape characters in the + value returned by pos_params when expanding subsets of the + positional parameters and not within double quotes (in which case + pos_params() quotes the string for us) + - fix parameter_brace_substring to quote escape characters in the + value returned by array_subrange when expanding subsets of an + array and not within double quotes (in which case + array_subrange() quotes the string for us) + - new function, quoted_strlen(s), does strlen(s) while skipping over + characters quoted with CTLESC (#ifdef INCLUDE_UNUSED, since it's + not used yet) + - changed pos_params() so it always returns a list whose members are + quoted strings if (quoted&(Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) != 0 + +arrayfunc.c + - fix array_value to consistently call quote_escapes, even when a + non-array variable is being subscripted with element 0, in which + case we return the variable value + +lib/sh/strtrans.c + - make the for_echo parameter to ansicstr a `flags' parameter that + has its old `for echo' meaning if flags&1 is non-zero (which is + consistent with the old code) + - Added code to the `flags' parameter to ansicstr so that if flags&2 + is non-zero, CTLESC and CTLNUL are escaped with CTLESC in the + expanded string + - change ansiexpand() to call ansicstr with a `flags' parameter of 2 + + 3/26 + ---- +lib/readline/histfile.c + - when reading and writing the history file, use malloc instead of + xmalloc and handle failures gracefully, so the application doesn't + abort if the history file or history list is too big + + 3/27 + ---- +arrayfunc.c + - changed array_value_internal to take an additional `int *' + parameter, in which is returned the type of array indexing + performed (array[@] vs. array or array[index]) + - changed array_value and get_array_value to take a corresponding + extra parameter and pass it to array_value_internal + - changed array_value_internal to no longer return newly-allocated + memory or quote CTLESC and CTLNUL in the returned string if + `simple' array indexing (subscript not `@' or `*') is being + performed. This makes it more like a variable lookup + +arrayfunc.h + - changed prototypes for array_value and get_array_value + +expr.c + - added new parameter to call to get_array_value in expr_streval + - don't need to free memory returned by get_array_value any more + +subst.c + - quote_escapes now works with multibyte characters + - dequote_string now works with multibyte characters + - dequote_escapes is now needed, so it's compiled in, and it + now works with multibyte characters + - remove_quoted_escapes now just calls dequote_escapes and copies the + result over the argument string + - remove_quoted_nulls now returns its char * argument, parallels + remove_quoted_escapes + - parameter_brace_expand_word now passes the new argument to + array_value and quotes CTLESC and CTLNUL in the result if it's a + `simple' array expansion by calling quote_escapes + - get_var_and_type now returns VT_ARRAYMEMBER for references like + ${array} where `array' is an array variable (just like ${array[0]}). + Documented (in comment) that a VT_VARIABLE return value means that + quote_escapes has been called at some point + - changed callers of get_var_and_type to no longer free value if + VT_ARRAYMEMBER is returned as type + - changed parameter_brace_substring and parameter_brace_patsub to + call dequote_escapes on the value from get_var_and_type if the + type is VT_VARIABLE, since the substring and pattern substitution + code doesn't understand CTLESC quoting + - parameter_brace_substring no longer needs to call quoted_substring + for the VT_ARRAYMEMBER case + - changed parameter_brace_patsub to call quote_escapes on the result + of pat_subst for the VT_VARIABLE and VT_ARRAYMEMBER cases, and to + quote the returned string in the VT_ARRAYVAR and VT_POSPARAMS cases + if the `MATCH_QUOTED' flag isn't set (if it is, the pattern + substitution functions perform any necessary quoting) + - quoted_substring is no longer used; it's now #ifdef INCLUDE_UNUSED + +lib/malloc/mstats.h + - new member in _malstats: u_bits32_t bytesreq, the total number of + bytes requested by the caller via calls to malloc() and realloc() + +lib/malloc/stats.c + - print bytesreq member in _print_malloc_stats + - don't print statistics for buckets for which nmal == 0 (no mallocs) + +lib/malloc/malloc.c + - modified internal_malloc, internal_realloc to keep running total of + number of bytes requested by calling application + +shell.c + - sh_exit is now compiled in; exit_shell calls sh_exit + +error.c + - changed fatal_error, report_error, parser_error to call sh_exit + + 3/28 + ---- +subst.[ch] + - changed Q_NOQUOTE to Q_PATQUOTE; it makes the intent more clear + +subst.c + - moved code from parameter_brace_expand into a new function that + dispatches for pattern substitution: parameter_brace_remove_pattern + - changed structure of parameter_brace_remove_pattern to be like + parameter_brace_patsub and its ilk: call get_var_and_type to + isolate the variable name, move the pattern isolation code out of + the various *_remove_pattern functions into + parameter_brace_remove_pattern and pass the results to the various + functions, use a switch on the return value from get_var_and_type + to decide which function to call, regularized the arguments to the + separate pattern removal functions + - parameter_brace_remove_pattern now properly quotes escape chars in + the returned value + - changed get_var_and_type to call dequote_escapes on the `value' + parameter for case VT_VARIABLE and return the result in *valp, + so the calling functions don't have to do it themselves; changed + callers appropriately + - fixed getpattern() where it broke posix compliance: if you enclose + a pattern removal spec in double quotes, the outer double quotes + have no effect on the pattern (POSIX.1-200x 2.6.2). This uncovered + a bug in the test suite (!) + +pathexp.c + - fixed a problem with quote_string_for_globbing where it would change + consecutive CTLESC chars all to \ instead of changing every other + quoted char + + 3/31 + ---- +lib/malloc/{malloc,stats}.c + - moved declaration of _mstats to malloc.c so stats.o doesn't get + linked into the shell if the stats functions aren't called + + 4/2 + --- +lib/glob/smatch.c + - introduce `XCHAR' define, which is the type of arguments passed to + strcoll/strcmp/strlen and their wide-character equivalents, added + appropriate casts + - static arrays in single-byte version of rangecmp() are `char', not + `unsigned char', so compilers don't complain about calls to strcoll + +lib/glob/sm_loop.c + - casts for `XCHAR' and `XCHAR *' arguments to libc functions + - use prototype declaration for BRACKMATCH if `PROTOTYPES' is defined + to avoid problems with type promotion (unsigned char -> int) + +lib/glob/collsyms.h + - `name' member of struct _COLLSYM is now of type `XCHAR *', since + some compilers don't like `unsigned char *' initializers from + constant strings + +[bash-2.05b-alpha1 released] + + 4/3 + --- +builtins/{evalstring.c,common.h} + - new flag for parse_and_execute, SEVAL_NOFREE, means to not free + the argument string when finished + +lib/readline/text.c + - fixed a trivial typo in _rl_insert_char when reading multibyte + char sequences + - replace calls to ding() with rl_ding() + +include/chartypes.h + - remove SIGN_EXTEND_CHAR and TOASCII macros; they're unused + +make_cmd.c + - include dispose_cmd.h for extern function declarations + +lib/glob/glob.c + - include `shmbutil.h' and `xmalloc.h' for extern function declarations + +lib/glob/smatch.c + - include `xmalloc.h' for extern function declarations + +shell.c + - fix maybe_make_restricted to use its argument instead of global + `shell_name' + +version.c + - update copyright message to include this year + +lib/readline/display.c + - fixes from Jiro SEKIBA to fix autowrapping + when using multibyte characters + +lib/glob/sm_loop.c + - fixed a problem in BRACKMATCH where not enough memory was allocated + to hold a multibyte character when parsing POSIX.2 char class names + +support/config.{guess,sub} + - updated via patch from Paul Eggert with latest GNU additions + +variables.c + - var_lookup should use its `vcontext' argument instead of + unconditionally using `shell_variables' + + 4/4 + --- +builtins/bind.def,doc/{bash.1,bashref.texi} + - changed the usage summary and help text to make it clear that any + readline command that may appear in ~/.inputrc may be supplied as + one of the non-option arguments to `bind' + +builtins/mkbuiltins.c + - added support for `-H' option, which means to write long documentation + for each builtin to a separate file in the `helpfiles' directory + +builtins/Makefile.in + - new target `helpdoc', just creates long doc files in helpfiles + directory + +lib/sh/zcatfd.c + - new file, with zcatfd(int fd, int ofd, char *fn); dumps data from + FD to OFD + +Makefile.in,lib/sh/Makefile.in + - added zcatfd.c, zcatfd.o member of libsh.a + +builtins/evalstring.c + - changed cat_file to call zcatfd(fd, 1, fn) + +builtins/{shopt,colon}.def + - removed the $DOCNAME directive for `shopt', `true', and `false'; + just use the names + - changed $DOCNAME for `:' to just be `colon' instead of + `colon_builtin' + +builtins/reserved.def + - added help entries for ((, [[, `for ((' + +builtins/let.def + - add id++, id--, ++id, --id, ** to help text + + 4/8 + --- +builtins/bashgetopt.[ch] + - changed to allow options beginning with `+', enabled by a leading + `+' in the option string + - new variable, list_opttype, set to `-' or `+' + +builtins/{common.c,{builtin,eval,exit,fg_bg,let,printf,pushd,return,source,wait}.def + - changes to allow a `--' option for every builtin that accepts + operands but not options, as per posix.1-2001 + +builtins/{declare,setattr}.def + - use internal_getopt for parsing options, now that it supports `+' + +builtins/set.def + - use internal_getopt for initial option parse, now that it supports + a leading `+' + + +{configure,Makefile}.in, builtins/{Makefile.in,help.def,mkbuiltins.c} + - support for a new configure option, ``--enable-separate-helpfiles'', + moves the `long' help text to separate help files, installed by + default into ${datadir}/bash, one file per builtin. Off by + default -- it saves 47K, but it's only 47K, and it's in the text + segment + +flags.c + - build internal_getopt() option string argument from flags array at + runtime in shell.c + +shell.c + - new variable to control writing malloc stats at exit: + malloc_trace_at_exit, 0 by default + +lib/malloc/malloc.c + - heavily updated: + o partial page allocated on first call to malloc to make + subsequent sbrks page-aligned no longer wasted + o begin and end range guards are now the same value: the chunk + requested + o coalescing code was changed to attempt to coalesce first two + adjacent blocks on the free list; enabled by default + o blocks of size 32 are now candidates for larger block + splitting, since 32 is the most popular size + o blocks of size 32 are now candidates for smaller block + coalescing + o the IN_BUCKET check was changed to just make sure that the + size isn't too big for the bucket, since the `busy block' + checking code may increase the bucket by one or more, + meaning that the old check would fail and cause a panic when + a chunk allocated in such a way was freed + o bin sizes are now precomputed and looked up in an array + rather than being computed at runtime + o moved the _mstats declaration here to avoid the stats code + being linked in even when no stats functions were called + (only matters if MALLOC_DEBUG is defined) + o malloc now keeps track of the address of the top of the heap + and will return large chunks to the system with calls to + sbrk with a negative argument when freeing the top chunk. + Two thresholds: LESSCORE_FRC means to unconditionally return + memory to the system; LESSCORE_MIN means to return memory if + there's at least one block already on the free list + +lib/malloc/mstats.h + - stats struct now keeps track of number of block coalesces by bin, + and the number of times memory was returned to the system by bin + +lib/malloc/stats.c + - trace_malloc_stats now takes a second argument: the name of the file + to write to. The first `%p' in the template file name is replaced + by the pid + + 4/9 + --- +lib/malloc/imalloc.h + - added some macros derived from dlmalloc and glibc malloc to inline + memcpy and memset if the requested size is <= 32 bytes + +lib/malloc/malloc.c + - use MALLOC_MEMSET instead of memset in internal_{malloc,free} + +include/ocache.h + - use OC_MEMSET (variant of MALLOC_MEMSET) in ocache_free + +configure.in, config.h.in + - check for getservent(), define HAVE_GETSERVENT if found + +bashline.c + - punt immediately from bash_servicename_completion_function if + HAVE_GETSERVENT is not defined (cygwin seems to not define it) + - include "input.h" for extern save_token_state() and + restore_token_state() declarations + - change bash_execute_unix_command to call parse_and_execute with + SEVAL_NOHIST flag so the command doesn't get saved on the history + list + - change bash_execute_unix_command to save and restore the current + command line count and the token state (last_read_token, etc.). + Everything else is saved by either parse_and_execute directly or + the call it makes to push_stream(). The shell_input_line stuff + doesn't need to be saved and restored; it's not computed until + readline() returns + + 4/10 + ---- +lib/glob/glob.[ch] + - glob_filename and glob_vector now take an additional `flags' arg + - define GX_MARKDIRS as possible flag value for glob_filename and + glob_vector + +lib/sh/snprintf.c + - fixed some bugs with handling of `g' and `G' formats + - make sure numtoa returns the fractional part correctly when passed 0 + - implemented thousands grouping for `'' flag character + +lib/sh/rename.c + - a few changes to make it more bulletproof + + 4/11 + ---- +lib/glob/glob.c + - added the couple of dozen lines of code to glob_dir_to_array to + finish implementing GX_MARKDIRS + +builtins/set.def + - changed unset builtin so that it no longer considers unsetting an + unset variable or function to be an error + +lib/readline/display.c + - fix to rl_redisplay for a problem which caused display to be messed + up when the last line of a multi-line prompt (possibly containing + invisible characters) was longer than the screen width + + 4/15 + ---- +aclocal.m4 + - use AC_DEFINE_UNQUOTED in BASH_SYS_DEFAULT_MAIL_DIR instead of + enumerating all of the possible values and using AC_DEFINE + + 4/16 + ---- +Makefile.in, {builtins,support}/Makefile.in + - new variables, CFLAGS_FOR_BUILD and CPPFLAGS_FOR_BUILD, substituted + by `configure' + - changed CCFLAGS_FOR_BUILD to BASE_CCFLAGS, removing $(CPPFLAGS); + CCFLAGS and CCFLAGS_FOR_BUILD now include $(BASE_CCFLAGS) with + (possibly) different values for CPPFLAGS and CFLAGS + - GCC_LINT_CFLAGS now includes $(BASE_CCFLAGS) and $(CPPFLAGS) + instead of CCFLAGS_FOR_BUILD + - new variable, LDFLAGS_FOR_BUILD, right now equivalent to LDFLAGS + - remove $(CPPFLAGS) from recipes for buildversion, mksignames, and + mksyntax + +configure.in + - compute and substitute CFLAGS_FOR_BUILD, CPPFLAGS_FOR_BUILD, and + LDFLAGS_FOR_BUILD + - changed qnx to use LOCAL_LDFLAGS and LOCAL_LIBS instead of putting + everything in LOCAL_LDFLAGS + +builtins/Makefile.in + - remove $(PROFILE_FLAGS) from recipe for building `mkbuiltins' + - use LDFLAGS_FOR_BUILD instead of LDFLAGS in recipe for building + `mkbuiltins' + +Makefile.in + - use $(CC_FOR_BUILD) and $(CCFLAGS_FOR_BUILD) to build auxiliary + test programs (printenv, recho, zecho) + +support/Makefile.in + - use CC_FOR_BUILD and CCFLAGS_FOR_BUILD in recipe for building + `man2html' + +lib/tilde/Makefile.in + - substitute PROFILE_FLAGS, use PROFILE_FLAGS in $(CCFLAGS) + + 4/25 + ---- +Makefile.in, configure.in + - moved RELSTATUS to configure.in; configure substitutes it into + the generated Makefile + +lib/sh/snprintf.c + - fix wchars() to deal with systems where MB_CUR_MAX is not a + constant expression + + 5/2 + --- +lib/sh/shquote.c + - add `,' to list of chars that are backslash-quoted. It doesn't + hurt normal usage and prevents filenames with commas from being + inappropriately split by brace expansion after using + complete-into-braces + + 5/6 + --- +lib/sh/xstrchr.c + - we only need the check of MB_CUR_MAX and the slow code for a + few encodings, and even then only for a subset of the charset + +arrayfunc.c + - some speedups for skipsubscript and multibyte chars from Bruno Haible + +locale.c + - changed set_lang to call setlocale(LC_ALL, ...) if LC_ALL doesn't + already have a value, but doesn't change any shell variables + +include/shmbutil.h + - major speedups from Bruno Haible, mostly concerned with reducing + the number of strlen(3) calls + +subst.c + - change callers of macros in shmbutil.h to add extra argument as + necessary + - skip_single_quoted and skip_double_quoted take another argument: + the length of the string; mostly useful when using multibyte chars + - many speedups from precomputing string lengths at function start + - fixed a small bug in de_backslash in the midst of rewriting for + better efficiency + +{braces,make_cmd,pathexp}.c + - change callers of macros in shmbutil.h to add extra argument as + necessary + +pathexp.c + - fix a one-too-far problem with multibyte chars in + unquoted_glob_pattern_p + +braces.c + - brace_gobbler takes a new argument, the length of the passed string + - expand_amble takes a new argument, the length of the passed string + + 5/7 + --- +subst.c + - modified remove_quoted_nulls to eliminate the memory allocation and + do the copy in place using the same strategy as de_backslash + +lib/readline/{rldefs.h,complete.c} + - new define RL_QF_OTHER_QUOTE, so _rl_find_completion_word can note + that it found a quoting character other than \'" that appears in + rl_completer_quote_characters + + 5/9 + --- +jobs.c + - save and restore old value of jobs_list_frozen when calling trap + handlers from set_job_status_and_cleanup to avoid seg faults when + running recursive trap handlers + + 5/10 + ---- +builtins/common.h + - new #defines to use for value of changed_dollar_vars (provides + information about the caller who wants to blow away the old dollar + variables) + +builtins/common.c + - changed set_dollar_vars_changed to set changed_dollar_vars to one + of the ARGS_* values depending on the caller and environment + +builtins/source.def + - source restores the positional parameters unless the `set' builtin + was called to specify a new set while not executing a shell function + + 5/13 + ---- +POSIX + - new file, was in CWRU/POSIX.NOTES + +doc/{Makefile.in,Makefile} + - changed `posix' rule to modify ../POSIX + +doc/mkposix + - write to `POSIX' by default + +lib/sh/strtrans.c + - when ansicstr is parsing a format string for `echo -e' (or the + equivalent xpg_echo option is enabled), obey the POSIX-2001/SUSv3 + standard and accept 0-3 octal digits after a leading `0' + +doc/{bash.1,bashref.texi} + - updated `echo' description to note that up to three octal digits + are now accepted following `\0' + + 5/16 + ---- +doc/Makefile.in + - remove the generated documentation on `make distclean' if the + build directory and source directory are not the same + +Makefile.in + - descend into `support' subdirectory on a `make clean' and + `make distclean' + - remove parser-built, y.tab[ch] on a `make distclean' if the build + directory and source directory are not the same + +support/Makefile.in + - support various `clean' targets and remove man2html.o and man2html + +{configure,Makefile}.in + - move values for DEBUG and MALLOC_DEBUG into configure.in; on by + default for development versions; off by default for releases + (off for profiling, too) + + 5/21 + ---- +parse.y + - modified the grammar to allow a simple_list followed by yacc_EOF + to terminate a command. This fixes problems with things like + a backslash-newline at the end of an `eval'd string + - change handle_eof_input_unit() to reset the token state before + calling prompt_again(), in case the prompt to be evaluated contains + a command substitution + + 5/23 + ---- +lib/readline/vi_mode.c + - fix `r' command (rl_vi_change_char) when HANDLE_MULTIBYTE is defined + but MB_CUR_MAX == 1 + + 5/24 + ---- +lib/malloc/watch.c + - don't try to print `file' argument to _watch_warn if it's null + +lib/malloc/malloc.c + - changed guard checking code in internal_{malloc,free,realloc} to + access memory as (char *) and copy into a union instead of + casting and dereferencing a pointer to u_bits32_t, since that + results in unaligned accesses which will cause Sparcs to upchuck + + 5/30 + ---- +[bash-2.05b-beta1 released] + +lib/readline/text.c + - fixed a problem with rl_transpose_chars on systems supporting + multibyte characters with a locale that doesn't have any multibyte + chars + + 6/4 + --- +expr.c + - fix a/=0 and a%=0 to throw evaluation errors rather than core dumps + +lib/readline/display.c + - fix core dump when line wrapping a multibyte character (line + accidentally dropped from the original patch) + +lib/readline/mbutil.c + - fix reversed return value from _rl_is_mbchar_matched; fixes problem + with backward-char-search + + 6/10 + ---- +lib/sh/getenv.c + - fix getenv to not free value returned by find_tempenv_variable + - add setenv, putenv, unsetenv for completeness + + 6/12 + ---- +shell.c + - change init_noninteractive to init expand_aliases to the value of + posixly_correct + - don't initialize expand_aliases to posixly_correct anywhere else. + This allows the -O expand_aliases invocation option to work correctly + +general.c + - fix move_to_high_fd to not try the dup2 unless the fd loop results + in an fd > 3; just return the passed file descriptor otherwise + - use HIGH_FD_MAX, defined in general.h, instead of hard-coded 256 + as highest file descriptor to try + +subst.c + - in process_substitute, call move_to_high_fd with `maxfd' parameter + of -1 instead of 64, so move_to_high_fd will use its maximum + + 6/21 + ---- +lib/malloc/malloc.c + - don't bother calling MALLOC_MEMSET if the requested size is 0 + +builtins/setattr.def + - note in short doc that export and readonly can take assignment + statements as arguments + +error.c + - new function, error_prolog(), to capture common error message + prefix code (except for parser errors) + + 6/25 + ---- +aclocal.m4 + - add tests for standard-conforming declarations for putenv and + unsetenv in system header files + +{configure,config.h}.in + - call BASH_FUNC_STD_PUTENV and BASH_FUNC_STD_UNSETENV, define + HAVE_STD_GETENV and HAVE_STD_UNSETENV, respectively, if they + succeed + +lib/sh/getenv.c + - change putenv and unsetenv to take differing prototypes in + stdlib.h into account + + 6/27 + ---- +[bash-2.05b-beta2 released] + + 6/28 + ---- +builtins/common.c + - fix get_job_spec so that %N works when N is the size of the jobs + list (%8 means job 8, but the 7th member of the jobs array, so + it's OK if N == job_slots because the function returns N-1) + + 7/1 + --- +shell.c + - turn off line editing if $EMACS is set to `t' + + 7/10 + ---- +builtins/set.def + - remove mention of `-i' from long help doc, since it has no effect + + 7/17 + ---- +[bash-2.05b released] + + 7/18 + ---- + +lib/malloc/malloc.c + - make sure that the `free_return' label has a non-empty statement + to branch to + + 7/19 + ---- +locale.c + - only call setlocale() from set_lang() if HAVE_SETLOCALE is defined; + otherwise just return 0 + +lib/readline/mbutil.c + - only try to memset `ps' in _rl_get_char_len if it's non-NULL. Ditto + for _rl_adjust_point + + 7/23 + ---- +execute_cmd.c + - fix for executing_line_number() when compiling without conditional + commands, dparen arithmetic or the arithmetic for command + + + 7/24 + ---- +support/Makefile.in + - fix maintainer-clean, distclean, mostlyclean targets + +builtins/common.c + - fix bug in sh_nojobs where it doesn't pass the right number of args + to builtin_error + +bashline.c + - when using command completion and trying to avoid appending a slash + if there's a directory with the same name in the current directory, + use absolute_pathname() instead of just checking whether the first + char of the match is a slash to catch things like ./ and ../ + +examples/complete/bashcc-1.0.1.tar.gz + - a package of completions for Clear Case, from Richard S. Smith + (http://www.rssnet.org/bashcc.html) + +input.c + - fix check_bash_input to call sync_buffered_stream if the passed fd + is 0 and the shell is currently reading input from fd 0 -- all it + should cost is maybe an additional read system call, and it fixes + the bug where an input redirection to a builtin inside a script + which is being read from stdin causes the already-read-and-buffered + part of the script to be thrown away, e.g.: + + bash < x1 + + where x1 is + + hostname + read Input < t.in + echo $Input + echo xxx + +execute_cmd.c + - in initialize_subshell(), call unset_bash_input (0) to not mess with + fd 0 if that's where bash thinks it's reading input from. Fixes + bug reported by jg@cs.tu-berlin.de on 17 July 2002. Should be a way + to check whether or not the current fd 0 at the time of the call has + not been redirected, like in the bug report. Also might eventually + want to throw in a sync_buffered_stream if bash is reading input + from fd 0 in a non-interactive shell into a buffered stream, so the + stream is sync'd -- might be necessary for some uses + + 7/25 + ---- +lib/readline/signals.c + - make sure rl_catch_sigwinch is declared even if SIGWINCH is not + defined, so the readline state saving and restoring functions in + readline.c are always the same size even if SIGWINCH is not defined, + and undefined references don't occur when SIGWINCH is not defined + + 7/30 + ---- +bashline.c + - augment patch from 7/24 to not disable rl_filename_completion_desired + if the first char of the match is `~' + +lib/readline/bind.c + - when creating `shadow' keymaps `bound' to ANYOTHERKEY, don't bind + a key whose type is ISFUNC but whose function is the `fake' + rl_do_lowercase_version (fixes debian bash bug #154123) + +lib/readline/readline.c + - don't call _rl_vi_set_last from _rl_dispatch_subseq if + key == ANYOTHERKEY (when truncated to `sizeof(char)', it will be 0, + which strchr will find in `vi_textmod') + + 7/31 + ---- +lib/readline/input.c + - fix rl_gather_tyi to only slurp up one line of available input, even + if more than one line is available (fixes debian bash bug #144585) + + 8/3 + --- +bashline.c + - better fix for command completion problem -- test for directory + explicitly with test_for_directory before turning off + rl_filename_completion_desired, since that's the case we're trying + to protect against + + 8/5 + --- +include/shmbutil.h + - fix ADVANCE_CHAR macro to advance the string pointer if mbrlen + returns 0, indicating that the null wide character (wide string + terminator) was found (debian bash bug #155436) + +lib/readline/mbutil.c + - fix _rl_adjust_point to increment the string pointer if mbrlen + returns 0 + +support/shobj-conf + - fix for the `-install_name' value in SHLIB_XLDFLAGS assignment for + Darwin from the fink folks + + 8/6 + --- +builtins/exit.def + - broke code that runs ~/.bash_logout out into a separate function: + bash_logout() + +builtins/common.h + - extern declaration for bash_logout() + +eval.c + - call bash_logout() from alrm_catcher(), so timed-out login shells + run ~/.bash_logout before processing the exit trap + +lib/sh/strtrans.c + - implemented $'\x{hexdigits}' expansion from ksh93 + +configure.in + - define RECYCLES_PIDS in LOCAL_CFLAGS for cygwin; don't bother to + link with -luser32 + +examples/loadables/strftime.c + - new loadable builtin, interface to strftime(3) + + 8/7 + --- +parse.y + - parse_arith_cmd now takes a second argument, a flag saying whether + or not to add double quotes to a parsed arithmetic command; changed + callers + - changed parse_dparen so it tells parse_arith_cmd to not add the + double quotes and therefore doesn't need to remove them + - change parse_dparen to add W_NOGLOB|W_NOSPLIT|W_QUOTED flags to word + created when parsing (( ... )) arithmetic command, since the double + quotes are no longer added + +make_cmd.c + - in make_arith_for_expr, set the flags on the created word to + W_NOGLOB|W_NOSPLIT|W_QUOTED + +execute_cmd.c + - change execute_arith_command to expand the expression with + expand_words_no_vars, like the arithmetic for command code does + - fix execute_arith_command to handle the case where the expanded + expression results in a NULL word without crashing + +tests/{arith-for,cprint}.tests + - change expected output to account for no longer adding quotes to + ((...)) commands + + 8/8 + --- +print_cmd.c + - take out the space after printing the `((' and before printing the + `))' in print_arith_command, print_arith_for_command, and + xtrace_print_arith_cmd + +tests/{arith-for,cprint}.tests + - change expected output to account for no longer adding leading and + trailing spaces when printing ((...)) and arithmetic for commands + + 8/17 + ---- +subst.c + - fix issep() define to handle case where separators[0] == '\0', in + which case it always returns false + +lib/readline/histexpand.c + - fix off-by-one error in history_expand_internal when using the `g' + modifier that causes it to skip every other match when matching a + single character (reported by gjyun90@resl.auto.inha.ac.kr) + +doc/{bash.1,bashref.texi} + - make sure that the name=word form of argument to declare/typeset, + export, and readonly is documented in the description + + 8/30 + ---- +lib/readline/histexpand.c + - make history_expand_internal understand double quotes, because + single quotes are not special inside double quotes, according to + our shell-like quoting conventions. We don't want unmatched + single quotes inside double-quoted strings inhibiting history + expansion + - make `a' modifier equivalent to `g' modifier for compatibility with + the BSD csh + - add a `G' modifier that performs a given substitution once per word + (tokenized as the shell would do it) like the BSD csh `g' modifier + + 8/31 + ---- +braces.c + - when compiling for the shell, treat ${...} like \{...} instead of + trying to peek backward when we see a `{'. This makes it easier + to handle things like \${, which should be brace expanded because + the $ is quoted + + 9/7 + --- +aclocal.m4 + - redirect stdin from /dev/null in BASH_CHECK_DEV_FD before testing + the readability of /dev/fd/0, so we're dealing with a known quantity + + 9/11 + ---- +[prayers for the victims of 9/11/01] + +shell.c + - fix maybe_make_restricted to handle a restricted login shell with a + base pathname of `-rbash' and skip over any leading `-' + + 9/13 + ---- +builtins/evalstring.c + - in parse_and_execute, make sure we don't try to run unwind-protects + back to `pe_dispose' after a longjmp back to top_level if the + pe_dispose frame hasn't been initialized + +lib/readline/display.c + - fix problem with prompt overwriting previous output when the output + doesn't contain a newline in a multi-byte locale. This also should + fix the problem of bash slowing down drastically on long lines when + using a multi-byte locale, because it no longer tries to rewrite the + entire line each time. Patch from Jiro SEKIBA + +parse.y + - move the typedef for alias_t that is compiled in if ALIAS is not + defined up before the prototype for push_string, since that takes + an alias_t * parameter + +lib/readline/terminal.c + - bind the termcap description's left and right arrow keys to + rl_backward_char and rl_forward_char, respectively, instead of + rl_forward and rl_backward (which are just there for backwards + compatibility) + +aclocal.m4 + - when testing readability of /dev/stdin, redirect stdin from /dev/null + to make sure it's a readable file + + 9/17 + ---- +config-bot.h + - don't test __STDC__ when deciding whether or not to use stdarg.h; + just use it if it's present + +tests/read2.sub + - redirect from /dev/tty when using `read -t' + + 9/20 + ---- +builtins/history.def + - when reading `new' entries from the history file with `history -n', + fix increment of history_lines_this_session by taking any change + in history_base into account + +lib/sh/pathphys.c + - changes to sh_physpath to deal with pathnames that end up being + longer than PATH_MAX without dumping core + +lib/readline/doc/{history.3,hsuser.texinfo},doc/ bash.1 + - documented new `a' and `G' history modifiers + + 9/25 + ---- +lib/readline/misc.c + - when traversing the history list with arrow keys in vi insertion + mode, put the cursor at the end of the line (like in emacs mode) + +mksyntax.c + - don't try to use \a and \v unless __STDC__ is defined; use the + ascii integer equivalents otherwise + - include "config.h" in the generated syntax.c file for a possible + definition of `const' + +doc/{bash.1,bashref.texi} + - document the meaning of a null directory in $PATH + + 9/26 + ---- +parse.y + - fix set_line_mbstate to handle case where mbrlen() returns 0, + indicating the null wide character + - fix set_line_mbstate so we don't directly compare a char variable + to EOF, since char can (and is) unsigned on some machines + +bashline.c + - change bash_execute_unix_command to save a little bit more state: + last_shell_builtin, this_shell_builtin, last_command_exit_value + + 9/27 + ---- +execute_cmd.c + - tentative change to execute_simple_command to avoid freeing freed + memory in the case where bash forks early but still ends up calling + execute_disk_command, without passing newly-allocated memory to + make_child. This may fix the core dumps with the linux-from-scratch + folks + + 9/28 + ---- +Makefile.in,{builtins,lib/sh}/Makefile.in + - fix up dependencies, mostly on ${BUILD_DIR}/version.h, so that + parallel makes work with GNU and BSD makes + +shell.h + - new struct to save partial parsing state when doing things like + bash_execute_unix_command and other operations that execute + commands while a line is being entered and parsed + +parse.y + - new functions, save_parser_state() and restore_parser_state(), to + save and restore partial parsing state + +bashline.c + - change bash_execute_unix_command to call {save,restore}_parser_state + +builtins/jobs.def + - change execute_list_with_replacements to eliminate a run_unwind_frame + in favor of calling the cleanup explicitly and discarding the frame + +execute_cmd.c + - change execute_for_command to avoid a run_unwind_frame in the case + where the loop variable is readonly or otherwise not assignable + - change execute_select_command and execute_simple_command to use + discard_unwind_frame by running the cleanup code explicitly, instead + of using run_unwind_frame + - make sure execute_select_command decreases loop_level even on error + + 9/30 + ---- +doc/{bash.1,bashref.texi} + - fixed description of `unset' now that unsetting a previously-unset + variable is no longer an error + + 10/3 + ---- +{configure,config.h}.in + - augment check for strtold with additional check to detect the + horribly broken hp/ux 11.x implementation that returns `long_double'; + defines STRTOLD_BROKEN if so + +builtins/printf.def + - define floatmax_t as `double' if STRTOLD_BROKEN is defined + + 10/5 + ---- +lib/readline/keymaps.c + - don't automatically bind uppercase keys to rl_do_lowercase_version + in rl_make_bare_keymap + +lib/readline/readline.c + - explicitly check for ANYOTHERKEY binding to rl_do_lowercase_version + and dispatch to lowercase of key when a prefix is not matched + + 10/12 + ----- +bashline.c + - set COMP_WORDBREAKS in enable_hostname_completion to the value + of rl_completer_word_break_characters + +variables.c + - new special variable COMP_WORDBREAKS, controls the value of + rl_completer_word_break_characters + +variables.h + - new extern declaration for sv_comp_wordbreaks() + +subst.c + - change split_at_delims to behave more like shell word splitting if + the passed value for the delimiters is NULL, indicating that the + function is to use $IFS to split + +{execute_cmd,jobs,test,findcmd,input,make_cmd,redir,shell}.c +builtins/mkbuiltins.c,builtins/{fc,history,source,umask}.def +lib/sh/netconn.c +lib/termcap/termcap.c +lib/readline/histfile.c + - make sure all inclusions of are protected by + HAVE_SYS_FILE_H + +bashline.c + - don't turn off rl_filename_completion_desired in + attempt_shell_completion if the partial pathname contains a slash. + This still doesn't solve the problem of partial pathname completion + starting with a directory in the current directory without a + leading `./'. There's no way to tell the difference between that + and a file found in $PATH (which may contain `.') at the point that + attempt_shell_completion acts + + 10/18 + ----- +locale.c + - don't set lc_all to the default locale when LC_ALL is being unset + - new function, reset_locale_vars(), called to recompute the correct + locale variable values when LC_ALL is unset + - changed set_lang to not set LC_ALL, which it never should have been + doing in the first place, and to maintain a local variable `lang' + corresponding to $LANG + - change get_locale_var to use the precedence posix.2 specifies: + LC_ALL overrides individual variables; LANG, if set, is the default + - change set_locale_var to call get_locale_var to get the appropriate + value for the variable being set or unset + - call get_locale_var instead of using passed value in set_locale_var + to get the defaulting and precedence right + +lib/readline/nls.c + - new function, _rl_get_locale_var(), which does the same thing as + locale.c:get_locale_var(), with the right precedence and defaulting, + using sh_get_env_value to get the right bash variable values + - if HAVE_SETLOCALE is defined, _rl_init_eightbit first calls + _rl_get_locale_var to get the right value for LC_CTYPE, and uses + that in the call to setlocale. If _rl_get_locale_var returns NULL, + call setlocale() to get the current international environment, and, + finally, if that returns null, call setlocale with a second argument + of "" to force the implementation's `native' environment + +pcomplete.c + - change gen_wordlist_completions to dequote the text before comparing + it against the expanded word list + - changed gen_matches_from_itemlist to do the same thing + +bashline.c + - new global function, bash_dequote_word, calls bash_dequote_filename + on the text passed. Used by the programmable completion code + +lib/readline/histfile.c + - make sure that whenever read_history_range returns a non-zero value + that it sets errno to some useful value + + 10/19 + ----- +variables.c + - COMP_WORDBREAKS is now a dynamic variable, mirroring value of + rl_completer_word_break_characters. Makes sure that the variable + always points to dynamic memory if it's not null or the readline + default + +bashline.c + - change enable_hostname_completion to manage a dynamic value of + rl_completer_word_break_characters, since assignments to + COMP_WORDBREAKS can change its value unpredictably + +lib/readline/{complete.c,readline.h} + - rl_completer_word_break_characters no longer has `const' attribute + +bashline.c + - clean up necessary places due to rl_completer_word_break_characters + no longer being `const' + +doc/{bash.1,bashref.texi} + - document new COMP_WORDBREAKS variable + + 10/21 + ----- +print_cmd.c + - fix indirection_level_string to handle the case where the decoded + $PS4 is null without seg faulting + + 10/22 + ----- +builtins/shift.def + - make sure that there is actually an argument when reporting a shift + count that exceeds the number of positional paramters and + shift_verbose is enabled + +lib/readline/rltty.c + - change SET_SPECIAL to call a new function, set_special_char, since + it contains a block. It's called infrequently, so the performance + impact of making it a function should be negligible, and it helps + debugging + + 10/29 + ----- +bashline.c + - make sure the editor in VI_EDIT_COMMAND and EMACS_EDIT_COMMAND is + quoted; it might contain spaces (e.g., `emacs -nw') + +aclocal.m4 + - cache ac_cv_rl_version in RL_LIB_READLINE_VERSION macro + +configure.in + - change logic that sets RL_INCLUDEDIR so that it doesn't try to set + a bogus include path if the argument to --with-installed-readline + is `yes' -- helps with cross-compiling + +lib/readline/histexpand.c + - fix history_tokenize_word so that it handles <( and >( better + + 10/30 + ----- +redir.c + - fix write_here_string so it handles the case where `herestr' expands + to NULL without seg faulting + + 10/31 + ----- +mailcheck.c + - reverse logic flip from bash-2.05 that handled systems that don't + change the atime when the mailbox is accessed; make sure the file + is bigger before we report new mail. This is the case in the vast + majority of cases. Reported by jim@jtan.com + + 11/5 + ---- +parse.y + - change action for `for x; { list; }' and corresponding `select' + production to use \"$@\" instead of just $@, as it is with all the + other actions + + 11/9 + ---- +parse.y + - new flag for parse_matched_pair: P_DQUOTE, indicating that the + pair of characters being matched is between double quotes + - parse_matched_pair now passes P_DQUOTE down to recursive calls: + if the open char to be matched is a `"' or the passed-in flags + include P_DQUOTE, set the local `rflags' variable to P_DQUOTE and + pass `rflags' down to recursive calls + - if `rflags' includes P_DQUOTE, don't try to ansiexpand $'...' or + locale expand $"..."; consistent with other quoting constructs + + 11/11 + ----- +doc/{bash.1,bashref.texi} + - explicitly note that variables referenced in arithmetic expressions + without using `$' evaluate to 0 if they are null or unset + - note that a null variable value evaluates to 0 when used in an + arithmetic context, like when a variable with the `-i' attribute is + assigned a null value + - document the ${!prefix@} expansion as equivalent to ${!prefix*} + + 11/12 + ----- +doc/{bash.1,bashref.texi} + - note that the value of an arithmetic expression is as in C + - change the wording to note that `arithmetic evaluation' (not + arithmetic expansion, which has a different meaning) is performed + on the value assigned to a variable whose integer attribute is set + + 11/13 + ----- +execute_cmd.c + - fix execute_disk_command so it calls exit() after printing the error + message in a restricted shell context if the shell has already forked + (nofork != 0 && there are no pipes) + + 11/19 + ----- +builtins/type.def + - don't report on aliases unless expand_aliases is set and the parser + is performing alias expansion; changed tests/type.tests and + tests/type.right accordingly + + 11/25 + ----- +general.c + - fix for full pathnames including drive letters on cygwin from + Corinna (convert to posix-style paths, which the rest of the + code handles much better) + +lib/readline/text.c + - fixes to overwrite mode from jimmy@is-vn.bg: + o in _rl_overwrite_char, do the overwrite mode self-insert + as one group, even when overwriting more than 1 char + o in _rl_overwrite_char, do the insert before the delete so + that an undo positions the cursor on the character restored, + not to the right of it + o in _rl_overwrite_rubout, don't do rl_insert_char(' ') unless + rl_point < rl_end. Since overwrite-mode self-insert acts as + in insert-mode when at eol, make rubout behave like + insert-mode rubout + + 11/30 + ----- +lib/readline/misc.c + - call rl_replace_line with `1' as second parameter if we're going to + immediately overwrite the undo list + +lib/readline/search.c + - in make_history_line_current, use _rl_replace_text to make the line + replacement an undoable operation. Affects all non-incremental + search functions. + +parse.y + - make behavior introduced on 11/9 dependent on extended_quote + variable, controllable by extquote shopt option. Default setting is + on for backwards compatibility + +builtins/shopt.def + - new `extquote' option to control extended_quote variable + + 12/3 + ---- +jobs.c + - change message printed when attempting to put a background job in + the background with `bg' to include the job id and make the + statement declarative + + 12/10 + ----- +bashhist.h + - define explicit flag values for history_control + +variables.c + - change sv_history_control to use new flag values + - change sv_history_control to parse $HISTCONTROL as a colon-separated + list of values for the history_control variable + +bashhist.c + - change check_history_control to use new flag values and restructure + to remove case statement + - new function hc_erasedups(line); removes all entries matching LINE + from the history list + - call hc_erasedups() from check_add_history after we've determined + that we're saving the line + +doc/{bash.1,bashref.texi} + - documented new options available for $HISTCONTROL and that it can + be a colon-separated list of history control options + + 12/11 + ----- +subst.c + - fix pat_subst() to not increment `e' (pointer to the end of the + matched portion of the string) until after we're sure we're going + around the loop again; fixes problem with empty replacements for + a pattern that doesn't match (bug reported by Don Coleman + ) + + 12/17 + ----- +lib/readline/display.c + - fixes to multibyte redisplay from jir@yamato.ibm.com (Jiro SEKIBA): + o speed up calculation of first difference between old and new + lines in the common case + o don't try to see if we're in the middle of a multbyte char + in update_line (we'll see how this one works out) + + 12/18 + ----- +doc/bashref.texi + - make it clear that the `command-list' function definition may be + terminated by an ampersand before the closing brace + + 12/28 + ----- +redir.c + - set `expanding_redir' flag when expanding words in a redirection + +subst.c + - new function, exp_jump_to_top_level(), to do any word expansion + cleanup before a call to jump_to_top_level from within that file; + sets expanding_redir back to 0 before jump_to_top_level + +variables.c + - in find_variable(), don't call find_variable_internal with a second + parameter of 1 if expanding_redir is non-zero + - in find_variable_internal(), don't search the temporary env if + subshell_environment includes SUBSHELL_FORK (indicating a simple + command) and expanding_redir is non-zero + +parse.y + - increment line_number when we read a \ pair + +array.c + - added array_unshift_element and array_shift_element (which just call + array_shift and array_rshift, respectively), for bash debugger + support + + 1/4/2003 + -------- +doc/{bash.1,bashref.texi} + - note in the section describing the execution environment passed to + children that subshells inherit shell functions marked for export + - note in the section describing shell functions the possibility + that exported functions may result in two entries in the environment + with the same name + +parse.y + - when pushing an alias expansion onto the pushed_string list, append + a space to the expanded definition to make the parser's lookahead + work without using the `mustpop' hack in shell_getc + + 1/8 + --- +shell.c + - change calls to exit() with EX_USAGE as a parameter to use + EX_BADUSAGE instead, since EX_USAGE is defined as 258 and is + technically out of range + + 1/14 + ---- +aclocal.m4 + - check for the termcap functions in libc first: if we don't have + to link in another library, let's not do it + - change the test for mbstate_t to use AC_TRY_COMPILE instead of + AC_TRY_RUN + +doc/{bash.1,bashref.texi} + - document that bash turns line editing off if environment variable + EMACS is set to `t' when it starts up + +doc/bash.1 + - minor change to give the ftp url for the latest version of bash in + the bug reports section + +lib/readline/histexpand.c + - in get_history_event, cast a couple of `const char *' variables to + `char *' in function call parameter lists to avoid compiler warnings + + 1/21 + ---- +builtins/cd.def + - change `cd -' so it prints the current working directory after a + successful chdir even when the shell is not interactive + + 1/31 + ---- +lib/readline/doc/rltech.texinfo + - clarified exactly what is meant by the term `application-specific + completion function', made its use consistent, and documented + what variables are changed before such a function is called + +lib/readline/input.c + - new function, _rl_pushed_input_available(), returns non-zero if + there are characters in the input queue managed by rl_get_char + and _rl_unget_char + +lib/readline/rlprivate.h + - new extern declaration for _rl_pushed_input_available + +lib/readline/callback.c + - change rl_callback_read_char to check _rl_pushed_input_available + and loop if there's something there, so characters don't languish + until more keyboard input is read + +execute_cmd.c + - new variable, last_command_exit_signal, non-zero if + last_command_exit_value result from wait_for was result of a signal + +nojobs.c + - keep track of whether or not a given pid was killed by a signal with + a new flag in the pid_list array + - new function int find_termsig_by_pid(pid_t pid) to get the + terminating signal, if any, for a particular pid + - new function int get_termsig(WAIT status) returns the terminating + signal corresponding to status + - set last_command_exit_signal in wait_for and the various wait_for_xx + functions + +jobs.c + - new functions, process_exit_signal and job_exit_signal, return the + signal that killed a given process or job, if a signal caused its + death + - set last_command_exit_signal in wait_for by calling job_exit_signal + or process_exit_signal appropriately + +subst.c + - don't resend SIGINT to ourselves unless last_command_exit_signal is + SIGINT and last_command_exit_value == 128 + SIGINT. This fixes the + $(exit 130) bug reported by Paul Jarc + +expr.c + - new function, expr_bind_variable, calls bind_int_variable and + then stupidly_hack_special_variables. This fixes the + `let OPTIND=1' bug + +bashline.c + - change history_and_alias_expand_line and shell_expand_line to call + history_expand_line_internal so calls to pre_process_line are + localized + - change history_expand_line_internal and cleanup_expansion_error to + temporarily turn off hist_verify before calling pre_process_line + to avoid the effects described by teirllm@dms.auburn.edu + +parse.y + - don't unconditionally turn off PST_ALEXPNEXT in push_string. This + fixes the multiple alias expansion bug reported by Paul Jarc. + +lib/readline/vi_mode.c + - change rl_vi_subst to push `l' instead of ` ' -- it should be + equivalent, but this has been reported to fix a problem in multibyte + locales + +lib/readline/readline.h + - new state flag value RL_STATE_TTYCSAVED, indicates that save_tty_chars + has been called. Since it's only used and visible internally, it's + undocumented + +lib/readline/rltty.h + - changed all of the members of _rl_tty_chars struct to `unsigned char' + +lib/readline/rltty.c + - set the RL_STATE_TTYCSAVED after save_tty_chars is called + - new function, rl_tty_unset_default_bindings(), resets bindings for + everything rl_tty_set_default_bindings() messes with back to + rl_insert, so rl_tty_set_default_bindings can be called again with + possible changes + - new function that does the bulk of the work for + rltty_set_default_bindings: _rl_bind_tty_special_chars() + - change prepare_terminal_settings so that it can track changes to the + terminal special chars made by stty(1): unset the bindings with + rl_tty_unset_default_bindings before calling save_tty_chars, and + _rl_tty_set_default_bindings after, with the new values from + get_tty_settings(). This implements a long-standing request, most + recently made by Tim Waugh of Red Hat. + +lib/readline/readline.h + - extern declaration for rl_tty_unset_default_bindings() + +lib/readline/readline.c + - new function, reset_default_bindings, calls + rl_tty_unset_default_bindings() to reset the terminal special chars + back to rl_insert and then read the new ones + +lib/readline/doc/rltech.texinfo + - documented rl_tty_unset_default_bindings() + + 2/1 + --- +[prayers and condolences to the families of the space shuttle crew members] + +aclocal.m4 + - add checks for mbrtowc and mbrlen in BASH_CHECK_MULTIBYTE + - new check, BASH_FUNC_CTYPE_NONASCII, checks whether or not the ctype + functions handle non-ascii characters correctly + +config.h.in + - add HAVE_MBRTOWC and HAVE_MBRLEN + - add NO_MULTIBYTE_SUPPORT for new configure argument + - add CTYPE_NON_ASCII + +config-bot.h, lib/readline/rlmbutil.h + - make sure that mbrtowc, mbrlen, and wcwidth are all present before + turning on HANDLE_MULTIBYTE + - turn off multibyte chars if NO_MULTIBYTE_SUPPORT is defined + +configure.in + - new argument --enable-multibyte (enabled by default), allows + multibyte support to be turned off even on systems that support it + +lib/readline/chardefs.h + - define NON_NEGATIVE as 1 if CTYPE_NON_ASCII is defined + + 2/3 + --- +config.h.in + - add HAVE_WCTOMB + +aclocal.m4 + - check for wctomb in BASH_CHECK_MULTIBYTE + + 2/4 + --- +lib/readline/vi_mode.c + - in _rl_vi_change_mbchar_case, make sure the result from wctomb() + is NULL-terminated before trying to insert it with rl_insert_text() + + 2/5 + --- +lib/readline/display.c + - fix to update_line to avoid problems on systems with multibyte + characters when moving between history lines when the new line + has more glyphs but fewer bytes (twaugh@redhat.com) + +lib/readline/vi_mode.c + - use wcrtomb() instead of wctomb() in _rl_vi_change_mbchar_case + +pcomplete.c + - fix init_itemlist_from_varlist to handle the case where the + `varlist' is NULL + +doc/{bash.1,bashref.texi} + - clarified when a simple command may fail without the shell exiting + when -e is set + + 2/13 + ---- +parse.y + - when bash is started with --nolineediting, ignore \[ and \] when + decoding the prompt string + +subst.c + - fix remove_quoted_nulls so that a string with a CTLESC appearing + after a CTLNUL (which was removed) does not leave characters in + the string inappropriately + + 2/14 + ---- +builtins/common.h + - new flag value for parse_and_execute(): SEVAL_RESETLINE, which + allows the caller to specify whether or not the internal idea + of the line number should be reset to 1 + +builtins/evalstring.c + - parse_and_execute() now tells push_string to reset the line + number only if the SEVAL_RESETLINE flag is set by the caller + + 2/15 + ---- +builtins/evalfile.c + - pass SEVAL_RESETLINE from _evalfile() to parse_and_execute() + +subst.c + - if the shell is currently interactive, pass SEVAL_RESETLINE to + parse_and_execute() when doing command substitution + +jobs.c + - add SEVAL_RESETLINE to parse_and_execute while running SIGCHLD trap + +command.h + - add `line' members to case_com, for_com, select_com + - rearranged order of members in some of the command structs, so + `flags' and `line' are first + - added a `source_file' member to the function_def struct; keeps + track of where the function was defined + +doc/Makefile.in + - add some new suffix rules: .dvi.ps + +doc/{bash.1,bashref.texi} + - added text to the description of the `trap' builtin tightening up + the language describing when the ERR trap will be run + +error.c + - if $BASH_SOURCE (internally-maintained) exists, use BASH_SOURCE[0] + in get_name_for_error if the shell is not interactive + +array.h + - new convenience defines: array_push and array_pop + +variables.c + - change get_funcname to return this_shell_function->name only if + arrays have not been compiled into the shell + - change init_funcname_var to initialize FUNCNAME as an array variable + if we have arrays + - new function: get_self(SHELL_VAR *self), a degenerate `dynamic_value' + function for dynamic variables + - new function: init_dynamic_array_var(), a generic dynamic array + variable initializer to handle the common case + - use init_dynamic_array_var() instead of explicit init_dirstack_var() + - use init_dynamic_array_var() instead of explicit init_groups_var() + - new dynamic array variables: BASH_ARGC, BASH_ARGV, BASH_SOURCE, + BASH_LINENO, initialized with init_dynamic_array_var + +shell.c + - initialize BASH_LINENO, BASH_SOURCE, FUNCNAME in open_shell_script + +{execute_cmd,trap}.c + - take out trap_line_number, since parse_and_execute doesn't reset the + line number any more when running the trap commands + +make_cmd.c + - augment make_function_def to get source file name and call + bind_function_def to save the entire FUNCTION_DEF + +variables.c + - new hash table: shell_function_defs, keeps table of shell function + definitions including source file and line number info corresponding + to shell_functions table + - new functions: find_function_def and bind_function_def to manage + the shell_function_defs hash table + - new function: unbind_function_def to remove a function definition + from the shell_function_defs table (right now uncalled) + +variables.h + - extern declaration for bind_function_def, find_function_def + - new extern declaration for unbind_function_def + +execute_cmd.c + - in function prologue and epilogue, push and pop FUNCNAME, + BASH_SOURCE, and BASH_LINENO information + +dispose_cmd.c + - broke the code that disposes a FUNCTION_DEF out into two new + functions: dispose_function_def and dispose_function_def_contents + +dispose_cmd.h + - new extern declarations for dispose_function_def_contents and + dispose_function_def + +copy_cmd.c + - move body of copy_function_def (other than allocating a new + FUNCTION_DEF) to copy_function_def_contents + - make sure to copy the new source_file member of a function_def in + copy_function_def_contents + - copy_function_def is no longer static, copy_function_def_contents + is not either + +command.h + - new extern declaration for copy_function_def_contents and + copy_function_def + +parse.y + - keep a stack of line numbers where case, select, and for commands + start, with a maximum nesting level of 128; increment when reading + word after `for', `select' or `case' in read_token_word; decrement + in grammar actions after parsing a complete for, arith_for, select, + or case command + - create for, case, arith_for, and select commands with an extra + line number (word_lineno[word_top]) argument + +make_cmd.c + - make_for_or_select, make_for_command, make_case_command, and + make_select_command all take an extra `line_number' argument + +make_cmd.h + - corresponding changes to extern declarations for those functions + + 2/16 + ---- +{execute_cmd,shell,variables}.c + - follow each call to remember_args with a call to push_args or + pop_args to manage the BASH_ARGV and BASH_ARGC arrays. Only set + when the shell is started to run shell script or runs a shell + function. Doesn't handle `set' or `shift' yet, nor `source'. + +execute_cmd.c + - keep track of the level of subshells with a new variable, manipulated + in execute_in_subshell + - set currently_executing_command in execute_command_internal(), + even if we're running a trap + - better line number management when executing simple commands, + conditional commands, for commands in execute_command_internal() + and the various functions that implement the commands + (execute_cond_command, execute_for_command, execute_etc.) + +variables.c + - new dynamic variable BASH_SUBSHELL, with new get_subshell and + assign_subshell functions to manipulate it + - new functions push_args (WORD_LIST *list) and pop_args (void) to + manage the BASH_ARGC and BASH_ARGV dynamic array variables + +variables.h + - new extern declarations for push_args and pop_args + +builtins/evalfile.c + - in _evalfile, do appropriate things to the FUNCNAME, BASH_ARGV, + BASH_ARGC, BASH_SOURCE, and BASH_LINENO variables + +support/mksignames.c + - add another fake signal for `trap'; make NSIG+2 == `RETURN' + +trap.c + - _run_trap_internal now returns an int: the exit value of the command + run as the result of the trap + - run_debug_trap now returns an int: the exit value of the command + run as the result of the trap + - RETURN is a new special trap + - new function: set_return_trap(char *command) interface for the rest + of the shell, like set_{debug,error}_trap + - new function: run_return_trap() + - command substitution and other child processes don't inherit the + return trap + +trap.h + - new extern declaration for set_return_trap() and run_return_trap + - new defines for RETURN_TRAP; increment BASH_NSIG + - change extern declaration for run_debug_trap() since it now returns + an int + +shell.c + - new invocation long option: --debugger, turns on debugging and + sets internal `debugging_mode' variable + +execute_cmd.c + - new code to save return trap when executing a shell function, so + shell functions don't inherit it + - run debug trap before binding the variable and running the action + list in a `for' command + - run debug trap before binding the selection variable and running + the query in a `select' command + - run debug trap before running matcher in a `case' command + +builtins/set.def + - new `set -o functrace' (set -T), causes DEBUG trap to be inherited + by shell functions + - new `set -o errtrace' (set -E), causes ERR trap to be inherited + by shell functions + +flags.c + - new flags -E and -T, control error_trace_mode and + function_trace_mode respectively + +flags.h + - new extern declarations for error_trace_mode and function_trace_mode + + 2/17 + ---- +doc/bashref.texi + - changed the `dircategory' as per Karl Berry's suggestion + +doc/texinfo.tex + - update to version of 2003/02/04 from texinfo.org + +support/texi2dvi + - update to version 1.14 from texinfo-4.5 distribution + + 2/20 + ---- +support/config.{guess,sub} + - update to versions of 2002/11/30 + +lib/readline/doc/manvers.texinfo + - renamed to version.texi to match other GNU software + - UPDATE-MONTH variable is now `UPDATED-MONTH' + +lib/readline/doc/{hist,rlman,rluserman}.texinfo + - include version.texi + +doc/version.texi + - new file, with standard stuff matching other GNU distributions + +{doc,lib/readline/doc}/Makefile.in + - include right stuff for `version.texi' + +lib/readline/doc/{rluserman,rlman,hist}.texinfo + - use @copying and @insertcopying and @ifnottex instead of @ifinfo + - add FDL as an appendix entitled `Copying This Manual' + +lib/readline/doc/{rltech,rluser,hstech,hsuser}.texi + - changed the suffix from `texinfo' to `texi' + +lib/readline/doc/{rlman,rluserman}.texinfo, doc/bashref.texi + - include rltech.texi,rluser.texi,hstech.texi, and hsuser.texi + +lib/readline/doc/Makefile.in,doc/Makefile.in + - made appropriate changes for {{rl,hs}tech,{rl,hs}user}.texi + +lib/readline/doc/{rlman,rluserman}.texinfo + - changed the suffix from `texinfo' to `texi' + +lib/readline/doc/hist.texinfo + - renamed to history.texi + + 2/25 + ---- +pathnames.h.in + - moved pathnames.h here so value of DEBUGGER_START_FILE can be + substituted by configure + +aclocal.m4 + - added AM_PATH_LISPDIR for debugger + +configure.in + - added some variables: `bashvers', `relstatus' to use info in more + than one place + - call AM_PATH_LISPDIR + - new option: --enable-debugger, sets DEBUGGER cpp option + - new option with AC_ARG_VAR: DEBUGGER_START_FILE + - make `pathnames.h' a file generated by configure + +Makefile.in + - add rule to create pathnames.h + +builtins/declare.def + - added extra line number and source file name to `declare -F' output + if `--debugger' is used at startup + +builtins/evalfile.c + - call run_return_trap from source_file before returning the result + from _evalfile() + +execute_cmd.c + - call run_return_trap in execute_function before restoring the old + context + +builtins/source.def + - arrange to save and restore DEBUG traps when sourcing files if + function_trace_mode (set -o functrace) is not set + +print_cmd.c + - broke print_for_command, print_select_command, print_case_command + into two functions each: one to print the `header' and one for + the body + - print_cond_command is no longer static + - print_arith_command now takes a WORD_LIST *, since it doesn't + actually do anything with the ARITH_COM it's passed except print + the enclosed WORD_LIST + - print_arith_command is no longer static + +externs.h + - extern declarations for print_{for,select,case}_command_head, + print_cond_command, print_arith_command + +{.,builtins,lib/sh}/Makefile.in + - corrected dependencies on pathnames.h, since it's now created in + the build directory + + 3/5 + --- +lib/glob/glob.c + - handle alloca() failing (it's supposed to return NULL) + - use malloc() (with its attendent bookkeeping) instead of alloca() + in glob_filename() + +subst.c + - check whether shell_glob_filename returns NULL in + glob_expand_word_list + - change parameter_brace_expand_rhs to handle cases like + ${a[2]:=value} by properly creating the array element instead of a + variable named `a[2]' (reported by ) + +variables.c + - change bind_int_variable to use valid_array_reference instead + of looking for `[' + +lib/readline/vi_mode.c + - check for `a' in _rl_vi_done_inserting so the text inserted by an + `a' command can be reinserted with a `.' + +lib/readline/readline.c + - when entering vi insertion mode in readline_internal_setup(), make + sure that _rl_vi_last_key_before_insert is set to `i' so that undo + groups and redo work better (reported by ) + +lib/glob/sm_loop.c + - handle ?(...) in a pattern immediately following a `*', instead of + ignoring the `(' and treating the `?' as a single-char match, as + long as FNM_EXTFLAG is set (reported by ) + +aclocal.m4 + - new test for presence of struct timezone, BASH_STRUCT_TIMEZONE + +config.h.in + - add HAVE_STRUCT_TIMEZONE + +configure.in + - call BASH_STRUCT_TIMEZONE + +execute_cmd.c + - don't try to use `struct timezone' in calls to gettimeofday unless + HAVE_STRUCT_TIMEZONE is defined; use (void *)NULL otherwise + + 3/20 + ---- +execute_cmd.c + - new variable, the_printed_command_except_trap, saves the command + being executed before a trap is executed, for the debugger + +trap.c + - if in debugging mode, let command substitutions and other child + processes inherit the DEBUG and ERR traps if the `functrace' + (which is really a bad name, given this semantic) or `errtrace' + options, respectively, have been set + +shell.c + - local_pending_command renamed to command_execution_string; no longer + static + +variables.c + - new dynamic variable, BASH_COMMAND, set to the command string + currently executing, or the one that caused a trap to execute + (mapped to the_printed_command_except_trap) + - new variable, BASH_EXECUTION_STRING, set to the argument to the + -c invocation option, if the shell was started that way + + 3/22 + ---- +execute_cmd.c + - changed execute_for_command, eval_arith_for_expr, + execute_select_command, execute_arith_command, execute_cond_command, + execute_simple_command to implement new DEBUG trap semantics + for the debugger: if the DEBUG trap commands return a non-zero + status and debugging_mode is non-zero, we skip the command to be + executed + +trap.c + - change run_debug_trap for the debugger: if we're in the debugger + and the DEBUG trap returns 2 while we're in a function or sourced + script, we force a `return' + +shell.c + - new function, start_debugger(), that sources the debugger start file + and turns the debugger on + +builtins/shopt.def + - new settable option, `extdebug', turns on debugging_mode, as if + --debugger had been supplied at invocation (but does not source + debugger startup file) + +trap.c + - make sure that run_exit_trap arranges for `returns' to come back + there, too, so a `return' executed by an `exit' invoked within a + shell function behaves correctly + +support/shobj-conf + - change darwin/MacOS X stanza based on advice from mac os x developers + +lib/sh/mailstat.c + - set the atime member of the synthesized stat struct to 0 if `cur/' + is empty, rather than leaving it undefined + + 3/24 + ---- +builtins/caller.def + - new builtin to provide a call stack for the debugger + +builtins/evalfile.c + - added a second `flags' argument to source_file() + - new flag value for flags argument to _evalfile(): FEVAL_NOPUSHARGS. + If included in flags arg, it means to not manipulate the BASH_ARGV + and BASH_ARGC arrays + +builtins/common.h + - change prototype for source_file() + +builtins/source.def + - add flag value to call to source_file(): set to 1 if we replaced + the positional parameters + - add call to push_args if additional arguments supplied to the + source builtin + - add call to pop_args in maybe_pop_dollar_vars + +execute_cmd.c + - run the debug trap in execute_function so the debugger can stop + before the first command in a function body is executed + - modify subshell_level before executing a builtin or function in a + subshell + - print `for', `select', `case' command heads when set -x is enabled + +print_cmd.c + - `xtrace_print_word_list' now takes an additional flags argument, + which, if non-zero, says to print indirection_level_string() + - new functions to print for, select, and case command heads when + set -x is enabled + - add spaces after `((' and before `))' in xtrace_print_arith_command + +externs.h + - changed extern declaration for xtrace_print_word_list + - new declarations for xtrace_print_{for,case,select}_command_head() + +subst.c + - modify subshell_level when executing a command substitution + + 3/25 + ---- +execute_cmd.c + - use `line_number' in executing_line_number instead of looking into + the current command if it's a simple command; rearrange code to + make this simpler to compile in and out + - need to save and restore value of currently_executing_command around + calls to debug trap and return trap in execute_function + +make_cmd.c + - make sure make_arith_for_command() disposes the WORD_LIST * it is + passed, since nothing else does and it's not used directly + + 3/28 + ---- +Makefile.in + - fixed dependencies for `error.o' on shell.h and version.h -- makes + parallel makes (gmake -j 4) work correctly + +doc/{bash.1,bashref.texi} + - documented all new features added to support the debugger + + 4/1 + --- +lib/sh/shquote.c + - make sure CTLESC and CTLNUL characters are escaped with CTLESC + by sh_double_quote, sh_backslash_quote and + sh_backslash_quote_for_double_quotes + Fixes vulnerability reported by svdb@stack.nl + +shell.h + - new `pipestatus' member of sh_parser_state_t, to save and restore + $PIPESTATUS + +parse.y + - changes to save_parser_state and restore_parser_state to save and + restore $PIPESTATUS + +builtins/read.def + - add a call to word_list_remove_quoted_nulls before assigning the + word list read from standard input to an array variable. Fixes + bug reported by holzhey@ppprs1.phy.tu-dresden.de + + 4/3 + --- +execute_cmd.c + - in execute_null_command, if redirections are supplied, make sure + things like 3 for possible definitions of intmax_t, uintmax_t + (reported by ro@techfak.uni-bielefeld.de) + + 7/30 + ---- +parse.y + - remove checking for `time' reserved word from special_case_tokens(); + use regular mechanism in CHECK_FOR_RESERVED_WORD. This allows `time' + to be aliased. (Reported by Glenn Morris + ) + + 7/31 + ---- +lib/readline/history.h + - extern declaration for history_write_timestamps + +lib/readline/histfile.c + - don't write timestamps to the history file in history_do_write + unless history_write_timestamps is set to non-zero by the application + (set to 0 by default) + +lib/readline/doc/{hstech.texi,history.3} + - document history_write_timestamps + +variables.[ch] + - new special variable function, HISTTIMEFORMAT; special function + sets history_write_timestamps to 1 if HISTTIMEFORMAT is set + + 8/4 + --- +builtins/history.def + - added support for printing time stamps based on the value of the + HISTTIMEFORMAT variable when displaying history entries + +doc/{bash.1,bashref.texi} + - added description of new HISTTIMEFORMAT variable + + 8/5 + --- +config-top.h + - remove /usr/ucb from any default paths + +mailcheck.c + - make_default_mailpath now returns NULL if DEFAULT_MAIL_DIRECTORY + is not defined + - remember_mail_dates now returns if make_default_mailpath returns + NULL + +config-bot.h + - reorganized the sections; provide an explicit placeholder for + builders to #undef any feature defines they don't want that + configure creates for them, like the default mail path + + 8/9 + --- +config.h.in + - add HAVE_REGEX_H, HAVE_REGCOMP, HAVE_REGEXEC for detection of POSIX.2 + regular expression functions + - add COND_REGEXP define to enable and disable the =~ operator for + matching extended regular expressions in [[...]] commands + +configure.in + - new option, --enable-cond-regexp, enables =~ and code to perform + regular expression matching in [[...]] + +config-bot.h + - undef COND_REGEXP if the OS doesn't provide posix regexp support + +doc/bashref.texi + - documnent new --enable-cond-regexp option to configure + + 8/18 + ---- +support/shobj-conf + - support for shared objects on FreeBSD-gnu (from Robert Millan) + + 8/25 + ---- +lib/sh/shmatch.c + - new file, shell interface to posix extended regular expression + matching + +externs.h + - new extern declarations for functions in shmatch.c + +execute_cmd.c + - incorporate code into execute_cond_node that does extended regular + expression matching for the =~ operator + +parse.y + - add `=~' to the list of binary operators accepted by the conditional + command parser + +doc/{bash.1,bashref.texi} + - documented =~ conditional binary operator and the BASH_REMATCH + variable + + 8/27 + ---- +lib/readline/display.c + - take multibyte characters into account when looking for quoted + substrings on which to do completion (fix from jir@yamato.ibm.com) + +lib/readline/util.c + - fix typo in _rl_strpbrk + +lib/readline/rldefs.h + - use function version of _rl_strpbrk in multibyte locales, because + it understands to skip over special characters in multibyte + character sequences + + 8/28 + ---- +jobs.c + - in wait_for, check for window size changes if a job that exits due + to a signal or is stopped was in the foreground, not just if it's + the current job + + 9/10 + ---- +support/config.{guess,sub} + - add support to recognize FreeBSD running on the amd64 + +subst.c + - if the new `fail_glob_expansion' variable is non-zero, globbing that + fails to match anything causes an expansion error + +builtins/shopt.def + - new `failglob' expansion: if enabled, failed globs cause an error + +test/shopt.right + - take `failglob' into account + +doc/{bash.1,bashref.texi} + - documented new `failglob' option and its effects + + 9/12 + ---- +findcmd.c + - fix file_status to treat the mode bits and uid right -- in particular, + don't assume the `other' bits always apply. Bug reported by + ; fix inspired by + +command.h + - new word flag: W_NOCOMSUB, meaning to not perform command + substitution on a word + +subst.c + - new flag for param_expand: PF_NOCOMSUB. If non-zero, $(...) + command substitutions are not expanded, but returned unchanged + - change expand_word_internal to pass through `` command substitutions + unchanged if (word->flags & W_NOCOMSUB) != 0 + - change expand_word_internal to pass PF_NOCOMSUB to param_expand + if (word->flags & W_NOCOMSUB) != 0 + +builtins/shopt.def + - rename set_interactive_comments to set_shellopts_after_change, which + more accurately reflects its purpose + +syntax.h + - add a define for isblank() in case the system doesn't provide one + +jobs.c + - change raw_job_exit_status to understand `pipefail', using the new + `pipefail_opt' variable + +flags.[ch] + - declare pipefail_opt + - reset pipefail_opt to 0 in reset_shell_flags + +builtins/set.def + - add `set -o pipefail' and document it in help output + +doc/{bash.1,bashref.texi} + - document `set -o pipefail' and the effect of the pipefail option + +mksyntax.c,syntax.h + - sh_syntaxtab is no longer `const' + - new generated variable, sh_syntabsiz, set to number of entries in + sh_syntaxtab, written to generated syntax.c + +locale.c + - new function, locale_setblanks(), sets each member of the current + locale's class to have the CSHBRK flag in sh_syntaxtab + + 9/17 + ---- +arrayfunc.c + - change convert_var_to_array to not set array[0] to a NULL value + (if the scalar variable had no value; e.g., after being created + with `local arrayvar') + +lib/readline/display.c + - save and restore the value of prompt_invis_chars_first_line in + rl_{save,restore}_prompt, and reinitialize it to 0 before printing + something in the message area + +lib/readline/bind.c + - new functions: rl_bind_keyseq_if_unbound_in_map(ks, func, kmap); + binds key sequence KS to function FUNC in keymap KMAP, and + rl_bind_keyseq_if_unbound (ks, func); binds key sequence KS to + function FUNC in the current keymap + +lib/readline/readline.h + - extern function declarations for rl_bind_keyseq_if_unbound_in_map and + rl_bind_keyseq_if_unbound + +lib/readline/{readline,terminal}.c + - _rl_bind_if_unbound -> rl_bind_keyseq_if_unbound + +lib/readline/{bind.c,rlprivate.h} + - remove _rl_bind_if_unbound + + 9/18 + ---- +lib/readline/doc/rltech.texi + - document rl_bind_keyseq_if_unbound and + rl_bind_keyseq_if_unbound_in_map + + 9/19 + ---- +lib/readline/bind.c + - new functions rl_bind_key_if_unbound_in_map and + rl_bind_key_if_unbound; analogous to (and implemented in terms of) + keyseq functions + - rl_bind_keyseq_in_map: a new function, equivalent to rl_set_key + (which remains for backwards compatibility); changed callers to + use it + - new function, rl_bind_keyseq, equivalent to rl_bind_keyseq_in_map + with a third argument of _rl_keymap + +lib/readline/readline.h + - extern declarations for rl_bind_key_if_unbound_in_map and + rl_bind_key_if_unbound + - extern declarations for rl_bind_keyseq_in_map and rl_bind_keyseq + +lib/readline/doc/rltech.texi + - document rl_bind_keyseq and rl_bind_keyseq_in_map + +configure.in + - require at least readline-5.0 + +config-bot.h + - define SYS_SIGLIST_DECLARED if it's not defined, but + HAVE_DECL_SYS_SIGLIST is, to deal with differences between + autoconf versions + +bashline.c + - use rl_bind_key_if_unbound_in_map when binding bash keybindings in + initialize_readline(), so inputrc files can override them + + 9/22 + ---- +lib/readline/histsearch.c + - do better bounds checking for history_offset and history_length in + history_search_internal + +builtins/history.def + - in delete_last_history(), make sure we don't leave the history + offset longer than the history length after calling delete_histent + + 9/23 + ---- +jobs.c + - small change to notify_of_job_status so job status messages get + printed even if the shell was started to run `-c command'. The + old behavior was intentional, but I cannot remember why, so we'll + try it the other way for a while (debian bash bug #211693) + + 9/24 + ---- +jobs.c + - slightly modify change from 9/23 so that jobs started to run + command substitutions don't print job status messages + + 9/25 + ---- +lib/readline/search.c + - when reading a non-incremental search string from the terminal, + use a separate undo list rather than chaining it to the undo list + from the rest of the line, since the whole undo list will get + freed when the search string is complete + +lib/readline/readline.h + - changed the defines guarding the stdarg prototype for rl_message to + match what's actually used in display.c, where it's defined + + 9/26 + ---- +[bash-3.0-alpha released] + + 9/29 + ---- +lib/sh/shmatch.c + - fix to build correctly when arrays are not compiled into the shell + +subst.c + - fix command substitution to run any exit trap defined in the + command substitution before returning; the exit trap is not inherited + from the calling shell + +lib/readline/shell.c + - change sh_set_lines_and_columns to free the memory allocated and + passed to setenv(), since setenv is specified by POSIX to allocate + new memory and copy its arguments + +jobs.c + - change logic in make_child so that every child process attempts to + set the terminal's process group to the pipeline's process group + when PGRP_PIPE is defined, just like when it's undefined. This is + reported to fix some tricky synchronization problems on Red Hat + Enterprise Linux 3. Fix from Ernie Petrides . + + 9/30 + ---- +builtins/printf.def + - tescape no longer needs a `trans_squote' argument, since it's the + same as the `sawc' argument. The `sawc' argument now means to do + the %b argument processing if non-null + - fix processing of octal constants for %b arguments (\0 followed by + up to three octal digits) and other escape sequences (\ followed by + up to three octal digits) + - hex constants `\xHHH' are now allowed to contain any positive + number of digits; previously they were restricted to two [removed] + - allow two new escape sequences: \" and \?, for compatibility with + ksh93 and ANSI C + +doc/{bash.1,bashref.texi} + - documented processing that printf performs for arguments to %b + escape sequences + +lib/sh/strtrans.c + - add \" and \? to escape sequences recognized by `echo -e' + + 10/1 + ---- +version.c + - use snprintf instead of sprintf if configure tells us we have it + + 10/3 + ---- +subst.c + - in list_remove_pattern, take into account the fact that one of the + list elements may be NULL, and don't free the result of + remove_pattern() without checking + - in remove_pattern, return savestring(param) if *param == '\0', + since callers expect to free() non-null return values + + 10/4 + ---- +subst.c + - change verify_substring_values to make it clearer that the first + offset deals with array indices and the second deals with numbers + of elements, when doing array subranges with ${a[@]:e1:e2} + +array.c + - change array_subrange to make it explicit that the second offset + argument is a count of the desired number of elements, not an + ending index. This deals with sparse arrays correctly. + + 10/6 + ---- +variables.c + - fix memory leak in assign_in_env + + 10/8 + ---- +subst.c + - in parameter_brace_expand, check that the last characters are `]}' + before checking for ${!array[@]} + +execute_cmd.c,builtins/source.def + - push and pop the args (BASH_ARGV and BASH_ARGC) when executing a + shell function or sourcing a script only when in debugging mode + + 10/11 + ----- +arrayfunc.c + - make sure array_variable_name returns values for the SUBP and LENP + arguments if they're non-null, since callers expect to use them + even if the array subscript is bad + +error.c + - call exit_shell instead of sh_exit from parser_error and + report_error so the right things happen (running exit trap, doing + the right interactive cleanup, etc.) + +lib/readline/complete.c + - new variable, rl_completion_quote_character, set to any quote char + readline thinks it finds before any application completion + function is called + - new variable, rl_completion_suppress_quote, settable by an + application-specific completion function. If set to non-zero, the + completion code does not append a closing quote in append_to_match + +lib/readline/readline.h + - extern declarations for rl_completion_quote_character and + rl_completion_suppress_quote + +bashline.c + - set rl_completion_suppress_quote in command_subst_completion_function + because that would be inserted before any closing "`" or ")", which + is somewhat disconcerting + +lib/readline/doc/rltech.texi + - documented rl_completion_suppress_quote and + rl_completion_quote_character + + 10/13 + ----- +bashhist.c + - use sv_histchars instead of setting history_comment_char directly in + bash_initialize_history so assignments to $histchars made in + ~/.bashrc are honored + + 10/21 + ----- +trap.c + - make sure run_exit_trap sets `running_trap' appropriately + - new variable, trap_saved_exit_value, set to last_command_exit_value + before running any trap commands; available to the rest of the + shell; use trap_saved_exit_value to replace some function-local + variables + +builtins/exit.def + - if the shell is running the exit trap, and no argument is given + to `exit', use trap_saved_exit_value as the exit status instead + of the last command exit value (which could be the previous command + run in the exit trap), as required by POSIX.2 + + 10/25 + ----- +doc/{bash.1,bashref.texi} + - add `alias' to the list of documented `assignment statement' builtins + + 11/1 + ---- +doc/bash.1 + - remove the `.' from the sample $PATH value + +parse.y + - make sure parse_compound_assignment prompts with $PS2 if it reads + a newline while parsing the compound assignment statement. Bug + reported by Stephane Chazelas + - parse_string_to_word_list now takes a new second argument: `int flags' + - new parser state flag: PST_COMPASSIGN; indicates that the shell is + parsing a compound assignment statement + - parse_string_to_word_list turns on PST_COMPASSIGN if `flags' arg + has low bit set + - turn PST_COMPASSIGN on and off in parse_compound_assignment + +externs.h + - change prototype declaration for parse_string_to_word_list + +arrayfunc.c + - change call to parse_string_to_word_list to add new flags arg + +general.c + - assignment() takes a new `flags' second argument + - if `flags' is non-zero, accept `[' as a legal assignment statement + starter character (for parsing compound array assignments) + +general.h + - add new argument to prototype declaration for assignment() + +parse.y,{subst,variables}.c, builtins/{setattr,declare}.def + - change calls to assignment() (parse.y calls with flags == 1 when + parser_state inlcudes PST_COMPASSIGN) + +arrayfunc.c + - in assign_array_var_from_string(), don't treat an expanded word + of the form [ind]=value specially unless the W_ASSIGNMENT flag is + set. This means that words that are the result of expansions but + happen to have the same format as compound assignment statement + words will not be treated as such. For instance + + v='[12]=foobar' + a=( $v ) + + will result in a[0]='[12]=foobar' instead of a[12]=foobar. This + is closer to how `regular' assignment statements are treated and + compatible with ksh93. Bug reported by Stephane Chazelas + +shell.c + - new --protected argument, disables command substitution when used + with --wordexp (like --wordexp, it remains undocumented) + - change run_wordexp to turn on the W_NOCOMSUB flag in each word + to be expanded if protected_mode is set + + 11/7 + ---- +doc/{bash.1,bashref.texi} + - clarified the language concerning inherited signal dispositions and + when traps are run + +support/shobj-conf + - slight changes to the darwin (Mac OS X) stanza for MacOS X 10.3 + (for the readline shared library builds, which shares this script) + +lib/readline/histexpand.c + - change to make `^' behave as equivalent to word one, as csh does, + and as the documentation states + +lib/readline/display.c + - in update_line, make sure to use col_lendiff in all calculations + where the cursor position is concerned (like when calculating + the value of _rl_last_c_pos). Fixes bug reported by Andreas + Schwab + + 11/12 + ----- +trap.c + - make _run_trap_internal catch `return' builtin longjmps and clean + up before longjmping on to where the return was intended to go + (fixes bug with not turning off SIG_INPROGRESS flag when `return' + executed in trap command) + + 11/18 + ----- +builtins/cd.def + - in posix mode, set errno to ENOTDIR if canonicalization fails, + unless the canonicalization functions leave it set to ENOENT + + 11/25 + ----- +make_cmd.c + - in make_simple_command, don't blindly dereference element.redirect + +parse.y + - the list_terminator production now has an `int' value so it can be + used in other grammar productions + - add a rule that makes `time' on a line by itself time a null + command (this is iffy) + + 11/28 + ----- +subst.c + - change the pattern substitution code (${var//pat/rep}) to use the + same pattern expansion function (getpattern()) as the pattern + removal expansions. This has the effect of no longer performing + quote removal on the pattern before trying to match it. This + fixes an incompatibility with ksh93 reported on comp.unix.shell + +nojobs.c + - add replacement function for siginterrupt on the off chance that a + system has posix signals but lacks siginterrrupt + +lib/readline/display.c + - fix from Tim Waugh at Red Hat to speed up inserting characters into + long lines in a UTF-8 environment by optimizing the calculation of + the first difference between old and new lines by checking to see + whether the old line is a subset of the new + + 11/29 + ----- +lib/malloc/stats.c + - break code that opens file (and interprets %p) into separate function + _imalloc_fopen(char *s, char *fn, char *def, char *defbuf, size_t defsiz) + for use by rest of library + - default stats file is now `stats.PID' + +lib/malloc/trace.c + - new function, malloc_set_tracefn (char *s, char *fn), sets tracing + to the file named by FN (with %p interpolated as the pid), using + some default if FN is NULL + +lib/malloc/shmalloc.h + - new extern declaration for malloc_set_tracefn + + 12/4 + ---- +execute_cmd.c + - combined several common strings from do_piping() into one + dup_error() function + +builtins/common.[ch] + - new function, `sh_notbuiltin(s)' prints error message about s not + being a shell builtin + +builtins/{builtin,enable}.def + - call sh_notbuiltin instead of using literal string + +{arrayfunc,expr,error}.c + - use one string variable for `bad array subscript' error message; use + in calls to various error reporting functions + +Makefile.in + - add variables for localedir and the PACKAGE_* variables, auto-set + by configure + +configure.in + - un-cache values for gettext, textdomain, and bindtextdomain if they're + not in libc but in libintl so the right variables get set + +bashintl.h + - add necessary defines for marking strings to be translated using + gettext + +locale.c + - set textdomain and directory in set_default_locale + - don't call textdomain with the value of $TEXTDOMAIN, since we don't + want to override the default domain ("bash") + - don't call bindtextdomain unless default_domain already has a value + - when translating $"..." strings, use dgettext with the script's + default domain (value of $TEXTDOMAIN) + + 12/9 + ---- +builtins/mkbuiltins.c + - include "bashintl.h" in the generated "builtins.c" + +support/{config.rpath,mkinstalldirs} + - new files to support gettext i18n + +ABOUT-NLS + - new readme file for gettext internationalization + +po/{Makefile.in.in,Rules-quot,boldquot.sed,en@boldquot.header,en@quot.header,insert-header.sin,quot.sed,remove-potcdate.sin} +po/{POTFILES.in,bash.pot} + - new files for gettext + +lib/intl + - new directory, with libintl stuff from gettext + +aclocal.m4 + - add m4 files from gettext distribution needed by libintl + +configure.in + - create po/Makefile.in and lib/intl/Makefile in AC_OUTPUT + - add call to AM_GNU_GETTEXT to initialize gettext stuff + +Makefile.in + - use mkinstalldirs instead of mkdirs in the `installdirs' target + - changes for intl/ and po/ subdirectories in build and install + - changes to have libintl linked in, as determined by configure + - changes to have libintl built, just in case it's used (though I'd + rather not) + + 12/10 + ----- +config.h.in + - additional #defines required by the libintl library + - add ENABLE_NLS define for AM_GNU_GETTEXT + - take out defines for HAVE_{BINDTEXTDOMAIN,GETTEXT,TEXTDOMAIN} + +configure.in + - removed old tests for libintl and gettext/textdomain/bindtextdomain + +locale.c + - remove HAVE_GETTEXT code; we have gettext unconditionally now + +bashintl.h + - change to include "gettext.h" and remove the conditional code based + on whether or not gettext is present + + 12/16 + ----- +lib/readline/vi_mode.c + - fix problem with rl_vi_eWord that caused it to skip over the last + character of a word if invoked while point was on the next-to-last + character + + 12/18 + ----- +{arrayfunc,bashhist,bashline,error,eval,execute_cmd,expr,general,input,jobs}.c +{mailcheck,make_cmd,nojobs,pcomplete,pcomplib,print_cmd,redir,shell,sig}.c +{subst,test,trap,variables,version,xmalloc}.c +parse.y +builtins/{common,evalfile,getopt}.c +builtins/{bind,break,caller,cd,complete,declare,enable,exec,exit,fc,fg_bg}.def +builtins/{hash,help,history,jobs,kill,printf,pushd,read,return,set,setattr}.def +builtins/{shift,shopt,source,suspend,type,ulimit,umask}.def +lib/sh/{fmtulong,netopen}.c + - include "bashintl.h" for gettext defines + +Makefile.in + - add `-DBUILDTOOL' to CFLAGS for buildversion.o + +bashintl.h + - if `BUILDTOOL' is defined, define ENABLE_NLS to 0 so we don't have + to compile and link in the gettext stuff + +Makefile.in,lib/sh/Makefile.in,builtins/Makefile.in + - update dependencies on bashintl.h and include/gettext.h + + 12/19 + ----- +{arrayfunc,bashhist,bashline,error,eval,execute_cmd,expr,general,input,jobs}.c +{mailcheck,make_cmd,nojobs,pcomplete,pcomplib,print_cmd,redir,shell,sig}.c +{subst,test,trap,variables,version,xmalloc}.c +builtins/{common,evalfile,getopt}.c +builtins/{bind,break,caller,cd,complete,declare,enable,exec,exit,fc,fg_bg}.def +builtins/{hash,help,history,jobs,kill,let,printf,pushd,read,return,set}.def +builtins/{setattr,shift,shopt,source,suspend,type,ulimit,umask}.def +lib/sh/{fmtulong,netopen}.c +lib/malloc/{malloc,stats,table,watch}.c + - mark up strings in source files for gettext processing + +lib/malloc/imalloc.h + - include "bashintl.h" if SHELL is defined, otherwise make _(x) an + identity define + +lib/malloc/Makefile.in + - add dependencies on ${topdir}/bashintl.h and ${BASHINCDIR}/gettext.h + + 12/21 + ----- +bashline.c + - make sure we index into rl_line_buffer with indexes > 0 in + attempt_shell_completion + + 12/31 + ----- +Makefile.in + - descend into `po' and run make recursively for the various clean + targets + + 1/4 + --- +include/shmbutil.h + - two new macros: BACKUP_CHAR(str, strsize, i), which backs up one + multibyte character in STR starting at index I, and + BACKUP_CHAR_P(str, strsize, p), which backs up one multibyte + character in STR starting at P, which is a char * + + 1/6 + --- +pcomplete.c + - in pcomp_filename_completion_function, use the quote character + readline found (and assigned to rl_complete_quote_character) when + dequoting the filename by a completion call from readline (when + rl_dispatching != 0) + +bashline.c + - ditto for bash_directory_completion_matches + + 1/7 + --- +lib/readline/complete.c + - new variable, rl_completion_found_quote, set to non-zero value if + readline finds what it thinks is quoting in the word to be completed + +lib/readline/readline.h + - extern declaration for rl_completion_found_quote + + 1/8 + --- +lib/readline/doc/rltech.texi + - documented rl_completion_found_quote + +lib/readline/complete.c + - in compute_lcd_of_matches, if it looks like what the user typed was + dequoted before generating filename matches, dequote the user's + text again before figuring out the case-insensitive lcd + + 1/9 + --- +lib/readline/display.c + - fix from Edward Catmur to logic that handles + invisible characters in prompt string. Original code was wrong + about local_prompt_prefix; it gave incorrect results when prompt + contained invisible characters after a line break + + 1/10 + ---- +subst.c + - new function, mb_substring(), does character (possibly multibyte) + oriented rather than strictly byte-oriented substring extraction. + The passed indices, rather than strictly indexing into the string, + indicate character positions that need to be calculated. From + Tim Waugh + - change parameter_brace_substring to use mb_substring if necessary + +included/shmbutil.h + - new define SADD_MBQCHAR_BODY, common code for adding a quoted + (preceded by CTLESC) multibyte character to an accumulating string + in the subst.c expansion code + +subst.c + - use SADD_MBQCHAR_BODY in expand_word_internal + - new static function, mb_getcharlens, allocates and returns an array + of character lengths for (possibly multibyte) characters in the + argument string + - change pattern matching operations to use while loops instead of + for loops to handle multibyte characters better (no more simple + increment or decrement) + - change pattern matching operations to use multibyte character + operations instead of simple increments and decrements. Don't + use BACKUP_CHAR_P -- use the mblen array instead, because that + avoids the N**2 behavior of having to count from the beginning + of the string each time you want to back up one character. Changes + to remove_pattern and match_pattern + + 1/12 + ---- +lib/readline/display.c + - make expand_prompt count multbyte characters in the prompt string + by using _rl_find_next_mbchar (and copying possibly more than one + byte) instead of a simple increment and single byte copy + + 1/13 + ---- +lib/readline/display.c + - expand_prompt takes a new reference argument -- it returns + the actual count of (possibly multibyte) characters displayed + on the screen + - don't short-circuit in expand_prompt unless we're not going to + be using any multibyte characters + - change calls to expand_prompt to pass an argument for the + number of physical characters the prompt occupies + (prompt_physical_chars) + - initialize `lpos' (the physical cursor position) from + prompt_physical_chars in rl_redisplay + +lib/readline/mbutil.c + - in _rl_find_prev_mbchar_internal, if mbrtowc returns -1 or -2, and + we assume that the character is a single-byte char, make sure we + update `prev' so it doesn't get lost. Fixes problems encountered + when a non-ascii char is the last char on the line and we're moving + back past it with ^B, and other display problems caused by the same + situation + + 1/15 + ---- +lib/readline/doc/rltech.texi + - document RL_PROMPT_START_IGNORE and RL_PROMPT_END_IGNORE in the + description of rl_expand_prompt() + + 1/20 + ---- +bashline.c + - in initialize_readline, make sure M-C-j and M-C-m are still bound to + vi-editing-mode before unbinding them -- they may have been rebound + in an inputrc + +variables.c + - in unbind_variable, unset attributes other than `local' and exported + (if the variable came from a temporary environment) when unsetting a + local variable inside a function + + 1/21 + ---- +configure.in + - add libintl build directory to the list of include directories if + it's being built (using INTL_BUILDDIR) + +Makefile.in,{builtins,lib/{sh,malloc}}/Makefile.in + - substitute LIBBUILD as ${BUILD_DIR}/${LIBSUBDIR} + - define INTL_BUILDDIR as ${LIBBUILD}/intl + +{builtins,lib/sh}/Makefile.in + - make sure INTL_INC is added to the list of include directories + - make sure INTL_LIBSRC is defined with the correct value + +{configure,Makefile,{builtins,lib/sh}/Makefile}.in + - substitute LIBINTL_H as ${INTL_BUILDDIR}/libintl.h + +Makefile.in,builtins/Makefile.iin + - all files depending on bashintl.h also depend on ${LIBINTL_H} + (which may be empty) + +Makefile.in + - make a rule telling how to build lib/intl/libintl.h if necessary + + 1/24 + ---- +builtins/read.def + - make sure that the array name supplied as an argument to -a is a + valid identifier + +parse.y + - make the \W expansion abbreviate $HOME with a ~ (seems to be more + useful) + +doc/{bash.1,bashref.texi} + - document new behavior of \W + +subst.c + - make sure parameter_brace_expand_rhs uses the first character of + $IFS when making the string to return from the expanded word + (which, in the case of "$@" or $@, contains multiple words that + need to be separated) + + 1/25 + ---- +builtins/common.c + - change get_job_spec to make `%' by itself or an empty argument + return NO_JOB + +jobs.h + - new possible value for a job spec return value: BAD_JOBSPEC + (for syntactically invalid specs, like the empty string) + +shell.c + - in open_shell_script, check to see whether or not we can find and + open the filename argument before setting dollar_vars[0] or + manipulating BASH_SOURCE, so the error messages come out better + +subst.c + - in string_list_internal, short-circuit right away to savestring() + if the list only has a single element + + 1/28 + ---- +lib/readline/rltypedefs.h + - new set of typedefs for functions returning char * with various + arguments (standard set) + +lib/readline/complete.c + - new function pointer, rl_completion_word_break_hook, called by + _rl_find_completion_word, used to set word break characters at + completion time, allowing them to be position-based + +lib/readline/doc/rltech.texi + - documented rl_completion_word_break_hook + +lib/readline/kill.c + - added new rl_unix_filename_rubout, which deletes one filename + component in a Unix pathname backward (delimiters are whitespace + and `/') + +lib/readline/readline.h + - extern declaration for rl_unix_filename_rubout + +lib/readline/funmap.c + - new bindable readline command `unix-filename-rubout' + +lib/readline/doc/{readline.3,rluser.texi},doc/bash.1 + - documented `unix-filename-rubout' + + 1/29 + ---- +lib/readline/histexpand.c + - change history_tokenize_internal to handle non-whitespace delimiter + characters by creating separate fields (like the shell does when + splitting on $IFS) + + 1/30 + ---- +lib/glob/xmbsrtowcs.c + - new function, xdupmbstowcs, for convenience: calls xmbsrtowcs + while allocating memory for the new wide character string + - some small efficiency improvments to xmbsrtowcs + +include/shmbutil.h + - extern declaration for xdupmbstowcs + +lib/glob/strmatch.h + - include config.h for definition of HANDLE_MULTIBYTE + - remove the HAVE_LIBC_FNM_EXTMATCH tests + - new extern declaration for wcsmatch(whchar_t *, wchar_t *, int) + +configure.in + - remove call to BASH_FUNC_FNMATCH_EXTMATCH; it's no longer used + +lib/glob/smatch.c + - simplify xstrmatch() by using xdupmbstowcs() instead of inline code + +lib/glob/glob.c + - modify mbskipname() to avoid the use of alloca + - simplify mbskipname() by using xdupmbstowcs() instead of inline code + - simplify glob_pattern_p() by using xdupmbstowcs() instead of + inline code + - fix memory leak in wdequote_pathname + - simplify wdequote_pathname() by using xdupmbstowcs() instead of + inline code + +lib/glob/strmatch.c + - new function, wcsmatch(), `exported' wide-character equivalent of + strmatch() + +subst.c + - old match_pattern is now match_upattern + - match_pattern now either calls match_upattern or converts + mbstrings to wide chars and calls match_wpattern + - match_upattern reverted to old non-multibyte code + - new function: match_pattern_wchar, wide character version of + match_pattern_char + + 2/1 + --- +subst.c + - old remove_pattern is now remove_upattern + - remove_upattern reverted to old non-multibyte code (pre-Waugh patch) + - new multibyte version of remove_pattern: remove_wpattern + - remove_pattern now calls either remove_upattern or converts a + multibyte string to a wide character string and calls + remove_wpattern + - new function, wcsdup, wide-character version of strdup(3) + + 2/4 + --- +print_cmd.c + - temporarily translate a >&filename redirection from + r_duplicating_output_word to r_err_and_out (as the expansion code + in redir.c does) so it prints without a leading `1' (file + descriptor) + + 2/5 + --- +aclocal.m4 + - add a check for wcsdup to BASH_CHECK_MULTIBYTE + +config.h.in + - add HAVE_WCSDUP define + + 2/9 + --- +builtins/shift.def + - fix a call to sh_erange that possibly dereferences a NULL pointer + + 2/12 + ---- +general.c + - start at a general set of file property checking functions: + file_isdir(), file_iswdir() (is writable directory) + +general.h + - extern declarations for new functions + +lib/sh/tmpfile.c + - use file_iswdir() to make sure the temporary directory used for + here documents and other temp files is writable in get_sys_tmpdir() + + 2/17 + ---- +bashline.c + - fix conditional binding of emacs-mode M-~ -- there is a default + binding for it (rl_tilde_expand), so a straight call to + rl_bind_key_if_unbound_in_map doesn't do the right thing + + 2/27 + ---- +[bash-3.0-beta1 released] + + 2/29 + ---- +subst.c + - fixed expansion so referencing $a, when a is an array variable + without an element assigned to index 0, exits the shell when + `-u' is enabled + +expr.c + - make the exponentiation operator (**) associative, so things like + 2**3**4 work right (change `if' to `while') + + 3/3 + --- +lib/sh/strftime.c + - SCO Unix 3.2, like Solaris, requires that the system's `timezone' + variable be declared as long + +lib/readline/{bind,histfile,input,parens}.c + - changes for Tandem (including `floss.h' (?)) + + 3/4 + --- +subst.c + - change param_expand to quote the entire expanded string instead + of just the escape characters if the expansion appears between + double quotes or in a here-document (for simple variable expansions + or expansions of positional parameters) + + 3/8 + --- +subst.c + - analogous changes to parameter_brace_expand_word to fix the same + quoting problem as on 3/4; fix callers to understand that the + value returned might be quoted now and should be dequoted if + necessary + - add a `quoted' argument to get_var_and_type, change callers + - change today's fix and fix from 3/4 to not call quote_string if the + value is "" (because quote_string turns that into CTLNUL\0) + + 3/9 + --- +builtins/cd.def + - resetpwd() now takes a `caller' argument so it can be used by pwd + as well as cd + - change pwd_builtin to call resetpwd() if sh_physpath() fails to + return a valid pathname + + 3/14 + ---- +expr.c + - reworked exp0 and readtok() to make post-increment and post-decrement + into real tokens, which may be separated from their accompanying + variables by whitesapce + - made analogous changes to readtok() to make pre-increment and + pre-decrement work when separated from their accompanying identifier + by whitespace + + 3/18 + ---- +lib/readline/misc.c + - in rl_maybe_unsave_line, don't force rl_replace_line to clear + the undo_list, since it might point directly at an undo list + from a history entry (to which we have no handle) + + 3/19 + ---- +lib/readline/display.c + - rl_save_prompt and rl_restore_prompt now save and restore the value + of prompt_physical_chars + - set prompt_physical_chars in rl_redisplay when expand_prompt has + not been called (e.g., when rl_display_prompt is set and is not + equal to rl_prompt, like when searching) + +lib/readline/histexpand.c + - don't call add_history in history_expand if the `:p' modifier is + supplied; leave that to the calling application. This means that + `history -p', for example, will not add anything to the history + list (as documented), nor will history expansions invoked by + emacs-mode M-C-e line editing + +config-bot.h + - check whether HAVE_DECL_SYS_SIGLIST is defined to 1 rather than just + defined, to work around newer versions of autoconf defining it to 0 + +config.h.in + - change default status of HAVE_MALLOC to #undef instead of #define + +bashhist.c + - extern declarations for rl_done and rl_dispatching + - don't call re_edit from pre_process_line unless rl_dispatcing is zero, + so we don't call it from something like shell-expand-line + - change pre_process_line to add an expanded history specification + that returned `print only' to the history list, since history_expand + no longer does it (and, when using readline, do it only when + rl_dispatching is zero) + + 3/22 + ---- +config.h.in,aclocal.m4 + - change bash-specific functions that look in struct dirent to define + HAVE_STRUCT_DIRENT_xxx, like AC_CHECK_MEMBERS does (though the + functions are otherwise the same) + - new function, BASH_STRUCT_DIRENT_D_NAMLEN, define + HAVE_STRUCT_DIRENT_D_NAMLEN if struct dirent has a `d_namlen' member + +configure.in + - call BASH_STRUCT_DIRENT_D_NAMLEN + +include/posixdir.h + - use new and renamed HAVE_STRUCT_DIRENT_D_xxx defines + + 4/7 + --- +builtins/cd.def + - ensure that we print out a non-null pathname after getting a + directory from CDPATH and canonicalizing it (e.g., if the result + exceeds PATH_MAX in length and the_current_working_directory is + set to NULL) + + 4/12 + ---- +print_cmd.c + - new function to print out assignment statements when `set -x' has + been enabled: xtrace_print_assignment + +externs.h + - extern declaration for xtrace_print_assignment + + 4/13 + ---- +{subst,variables}.c + - call xtrace_print_assignment instead of using inline code + +jobs.c + - if turning on job control when it was previously off, set + pipeline_pgrp to 0 in set_job_control so make_child puts + subsequent children in their own process group + + 4/14 + ---- +general.c + - new function, legal_alias_name, called to decide whether an + argument to add_alias is a valid alias name -- essentially any + character except one which must be quoted to the shell parser + and `/' + +general.h + - new extern declaration for legal_alias_name + +builtins/alias.def + - `unalias' now returns failure status if no NAME arguments are + supplied and -a is not given + - call legal_alias_name to make sure alias name is valid before + calling add_alias from alias_builtin + + 4/19 + ---- +include/shmbutil.h + - include for definition of HANDLE_MULTIBYTE rather than + duplicating logic + + 4/20 + ---- + +doc/{bash.1,bashref.texi} + - make sure $0 is consistently referred to as a `special parameter' + - document which characters are now not allowed in alias names + + 4/23 + ---- + +builtins/{jobs,kill,wait}.def + - removed requirement that job control be enabled to use job control + notation, as SUSv3 implies + +subst.c + - based on a message from David Korn, change param_expand to not call + string_list_dollar_star if the only quoting is Q_HERE_DOCUMENT -- + quoted here documents are like double quoting, but not exactly + - analogous changes to list_remove_pattern and pos_params + + 4/24 + ---- +lib/readline/doc/rluser.texi + - fix error in description of emacs-mode C-xC-e command (uses $VISUAL + instead of $FCEDIT) + + 4/28 + ---- +support/bashbug.sh + - integrate a patch from Stefan Nordhausen + that reduces race conditions + by using a temporary directory inside $TMPDIR and creating the + temp files in that + + 4/30 + ---- +builtins/common.c + - use pathconf(".", _PC_PATH_MAX) where available to size the argument + to getcwd() rather than using a straight PATH_MAX + +builtins/cd.def + - if get_working_directory fails and returns null (causing resetpwd + to return NULL), use set_working_directory to set $PWD to the + absolute pathname for which chdir just succeeded + + 5/1 + --- +lib/readline/vi_mode.c + - in rl_vi_change_to, call _rl_vi_set_last with rl_numeric_arg instead + of `last', since they're equal at call time and rl_vi_domove can + change rl_numeric_arg (which vi apparently updates). Fixes redo bug + of `c2....' reported by Marion Berryman + + 5/4 + --- +parse.y + - fix decode_prompt_string to properly deal with strftime() returning 0 + + 5/6 + --- +variables.c + - in make_local_array_variable, return an already-existing local array + variable immediately rather than creating a new array (causing a + memory leak) + + 5/8 + --- +lib/readline/vi_mode.c + - change rl_vi_domove to set rl_explicit_arg before calling + rl_digit_loop1 so that multi-digit numeric arguments work right + - _rl_vi_last_command is no longer static + +lib/readline/rlprivate.h + - new extern declaration for _rl_vi_last_command + +lib/readline/text.c + - change rl_newline to only call _rl_vi_reset_last if the last command + (_rl_vi_last_command) is not a text modification command. This lets + the last-command and last-argument work across command lines + + 5/13 + ---- +builtins/common.c + - use getcwd(0,0) rather than providing a fixed pathname with a fixed + length (PATH_MAX) so getcwd() will allocate sufficient memory + +aclocal.m4 + - change BASH_FUNC_GETCWD to check whether or not getcwd(0,0) will + allocate memory for the returned value -- nobody implements that + and getcwd-via-popen, so it should capture the old test as well + + 5/27 + ---- + +builtins/trap.def + - the historical behavior of assuming that a signal's handling should + be set to its original disposition is only in effect if a single + argument is given, otherwise the first argument is assumed to be a + command to execute + - when in posix mode, if trap gets a single argument, display an + error message and return EX_USAGE + - change the help message and usage string to better explain trap's + behavior + +doc/{bash.1,bashref.texi} + - describe the new default behavior when the first argument is a + signal spec + - note that signal names are case insensitive and the SIG prefix is + optional in arguments to `trap' + - note that signal name arguments to `kill' are case-insensitive + +lib/readline/display.c + - make sure rl_on_new_line_with_prompt sets rl_display_prompt to + rl_prompt (just to make sure it's set) + - have rl_on_new_line_with_prompt use local_prompt if it's set + + 6/2 + --- +subst.c + - in string_extract_double_quoted, cope with extract_delimited_string + returning NULL, as it can when attempting completion on an unclosed + command substitution + + 6/24 + ---- +lib/readline/complete.c + - change print_filename to add a `/' to listed directory names if + `mark-directories' has been enabled + +builtins/umask.def + - make sure that the mask passed to parse_symbolic_mode has all but + the operative low eight bits masked off, to avoid complementing + all 0s to -1, which is the error return code. This makes things + like `a=rwx' and `ugo=rwx' work and turn off the umask + + 6/26 + ---- +builtins/getopts.def + - when `getopts' reaches the end of options, unset OPTARG before + returning EOF. In response to a bug report from Apple + +configure.in + - when cross-compiling, don't set CPPFLAGS_FOR_BUILD and + LDFLAGS_FOR_BUILD from CPPFLAGS and LDFLAGS, respectively, since + those are for the target instead of the build platform (report + from robert@schwebel.de) + +shell.c + - a shell whose standard error (but not standard output) is directed + to a terminal should be interactive, according to POSIX/SUS. This + means that sh > sh.out will start an interactive shell. Bug report + from llattanzi@apple.com + +doc/{bash.1,bashref.texi} + - change mention of standard output to standard error in definition + of interactive shell + +lib/readline/vi_mode.c + - new convenience function, rl_vi_start_inserting, calls + _rl_vi_set_last to save the last textmod command state and then calls + rl_vi_insertion_mode to enter insert mode + - change functions to use rl_vi_start_inserting + +lib/readline/readline.h + - extern declaration for rl_vi_start_inserting + +bashline.c + - new function for vi-mode completion, bash_vi_complete. Does + filename expansion as POSIX specifies, unlike the default readline + bindings (which don't know about globbing). Bound to `\', `*', + and `=' in vi command keymap. Internals very similar to + rl_vi_complete; just calls bash glob expansion functions + +lib/readline/vi_mode.c + - change rl_vi_change_char so that an entire change is a single + undoable event, rather than each individual change in a [count]r + command + - fix rl_vi_change_char so that replacing characters up to EOL works + rather than generating rl_ding + - fix rl_vi_change_case so that replacing characters up to EOL works + rather than generating rl_ding + + 6/28 + ---- +builtins/echo.def + - call clearerr(stdout) before writing anything and testing its + failure or success + + 6/29 + ---- +bashline.c + - only set rl_explicit_arg in bash_glob_complete_word if readline is + in emacs mode; let bash_vi_complete take care of setting it in vi + mode + - fix bash_vi_complete to only set rl_explicit_arg unless the posix + conditions are met: no globbing characters in the vi `bigword' + being completed + + 6/30 + ---- +[bash-3.0-rc1 released] + + 7/1 + --- +lib/readline/complete.c + - make sure `extension_char' is initialized before deciding whether + or not the append a `/' to a possible completion when visible-stats + is not enabled + + 7/2 + --- +subst.c + - fix a boundary overrun in string_extract_double_quoted that could + occur when the word completion code attempts to expand an incomplete + construct (like a quoted unclosed command substitution) + + 7/4 + --- +subst.c + - set tempenv_assign_error to non-zero if an assignment to the + temporary environment fails for some reason (e.g., attempted + assignment to a readonly variable) + +execute_cmd.c + - fix execute_simple_command to force a non-interactive shell in + POSIX mode to exit if an assignment to the temporary environment + preceding a special builtin fails (bug report from + llattanzi@apple.com) + + 7/5 + --- +bashline.c + - in bash_directory_completion_hook, don't perform word expansions + if the filename appears to have been completed from the file + system rather than typed in by the user. Bug reported by Tim + Waugh + + 7/7 + --- +lib/readline/misc.c + - if _rl_maybe_save_line is being asked to save a line other than + what's already saved, free up the current saved line and save the + current contents of rl_line_buffer. Bug reported by + llattanzi@apple.com + + 7/12 + ---- +lib/readline/input.c + - do better EOF detection in rl_gather_tyi -- if a read returns 0 when + the fd is in non-blocking mode, stuff an EOF into the input stream + (reported by mattias@virtutech.se) + + 7/13 + ---- +lib/readline/vi_mode.c + - make sure rl_vi_put honors `count' arguments and yanks things + multiple times if requested + + 7/16 + ---- +builtins/umask.def + - make sure that the `who' part of the umask symbolic mode argument + defaults to `a' if it's missing + +flags.c + - make sure that maybe_make_restricted only gets called after the + shell is initialized, so `bash -r' doesn't result in inappropriate + error messages + +[bash-3.0 frozen] + + 7/27 + ---- +doc/Makefile.in + - small fixes + +[bash-3.0-released] + + 7/28 + ---- +array.c + - in array_insert(), make sure the value to be added is non-NULL before + calling savestring() on it + +builtins/reserved.def + - fix description of `CDPATH' + +lib/readline/display.c + - when expanding a prompt that spans multiple lines with embedded + newlines, set prompt_physical_chars from the portion after the + final newline, not the preceding portion. Bug reported by + "Ralf S. Engelschall" + +make_cmd.c + - explicitly declare `lineno' in function prologue for make_case_command + +builtins/evalfile.c + - include `trap.h' for declaration for run_return_trap + +bashline.c + - fix a `return' without a value in enable_hostname_completion + +general.c + - include test.h for extern declaration for test_eaccess + +externs.h + - add declaration for zcatfd + +tests/{history,histexp}.tests + - unset HISTFILESIZE to avoid problems if a value of 0 is inherited + from the environment + + 7/30 + ---- +bashline.c + - small changes to glob_expand_word to perform tilde expansion before + attempting globbing + +builtins/Makefile.in + - fix the install-help target to not cd into the `helpfiles' + subdirectory, so a value of $INSTALL_DATA containing a relative + pathname (e.g., .././support/install.sh) remains valid + + 7/31 + ---- +subst.c + - new function, mbstrlen(s), returns length of a multibyte character + string + +include/shmbutil.h + - new macro, MB_STRLEN(s), calls mbstrlen or STRLEN as appropriate + +builtins/trap.def + - small change so that a first argument that's a valid signal number + (digits only -- no symbolic names) will be treated as a signal and + reverted back to the original handling disposition. Fixes debian + complaints + +subst.c + - call MB_STRLEN instead of STRLEN where appropriate in + parameter_brace_expand_length to handle multibyte characters properly + - call MB_STRLEN instead of strlen in verify_substring_values so that + negative substrings of strings with multibyte chars work properly + + 8/1 + --- +jobs.c + - describe_pid needs to write to stderr, not stdout (POSIX) + - start_job, since it's only used by builtins (fg/bg), needs to write + its output to stdout, not stderr (POSIX) + +sig.c + - add an `orig_flags' member to struct terminating_signal so the + original signal handling flags (SA_RESTART, etc.) can be preserved + on POSIX systems + - make sure to preserve the signal flags state in + initialize_terminating_signals and reset them for child processes + in reset_terminating_signals + +builtins/fc.def + - fixed an off-by-one error that caused `fc -l' to list one too many + history entries + - in posix mode, `fc' should not list any indication as to whether or + not history lines have been modified (POSIX) + - when in posix mode, the default editor for `fc' should be `ed' (POSIX) + +doc/bashref.texi + - updated the description of `trap' behavior when given a first + argument that is a valid signal number + - noted that `fc -l' won't indicate whether a history entry has been + modified if the shell is in posix mode + +builtins/command.def + - fixed bug: `command -v' is supposed to be silent if a command is not + found + +builtins/hash.def + - `hash' should print its `hash table empty' message to stderr + +lib/readline/misc.c + - back out 7/7 change to _rl_maybe_save_line; it breaks emacs-mode ^P + +general.c + - changed base_pathname so that it will return reasonable results for + non-absolute pathnames -- this is what is intended by all of its + callers + +arrayfunc.c + - fix array_variable_part to return NULL if it finds an invisible + variable in the hash table. Fixes seg fault caused by referring to + unset local variable using array notation + +{locale,variables}.c + - support LC_TIME as a special locale variable so HISTTIMEFORMAT tracks + the current locale diff --git a/MANIFEST b/MANIFEST index b5b377d63..652eb754a 100644 --- a/MANIFEST +++ b/MANIFEST @@ -758,7 +758,7 @@ tests/histexp.tests f tests/histexp.right f tests/history.tests f tests/history.right f -tests/history.list f +tests/history.list f 444 tests/ifs.tests f tests/ifs.right f tests/input-line.sh f diff --git a/arrayfunc.c b/arrayfunc.c index a00f17f86..0d644b121 100644 --- a/arrayfunc.c +++ b/arrayfunc.c @@ -611,7 +611,7 @@ array_variable_part (s, subp, lenp) var = find_variable (t); free (t); - return var; + return (var == 0 || invisible_p (var)) ? (SHELL_VAR *)0 : var; } /* Return a string containing the elements in the array and subscript diff --git a/arrayfunc.c~ b/arrayfunc.c~ new file mode 100644 index 000000000..a00f17f86 --- /dev/null +++ b/arrayfunc.c~ @@ -0,0 +1,763 @@ +/* arrayfunc.c -- High-level array functions used by other parts of the shell. */ + +/* Copyright (C) 2001-2003 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#if defined (ARRAY_VARS) + +#if defined (HAVE_UNISTD_H) +# include +#endif +#include + +#include "bashintl.h" + +#include "shell.h" + +#include "shmbutil.h" + +#include "builtins/common.h" + +extern char *this_command_name; +extern int last_command_exit_value; + +static void quote_array_assignment_chars __P((WORD_LIST *)); +static char *array_value_internal __P((char *, int, int, int *)); + +/* Standard error message to use when encountering an invalid array subscript */ +char *bash_badsub_errmsg = N_("bad array subscript"); + +/* **************************************************************** */ +/* */ +/* Functions to manipulate array variables and perform assignments */ +/* */ +/* **************************************************************** */ + +/* Convert a shell variable to an array variable. The original value is + saved as array[0]. */ +SHELL_VAR * +convert_var_to_array (var) + SHELL_VAR *var; +{ + char *oldval; + ARRAY *array; + + oldval = value_cell (var); + array = array_create (); + if (oldval) + array_insert (array, 0, oldval); + + FREE (value_cell (var)); + var_setarray (var, array); + + /* these aren't valid anymore */ + var->dynamic_value = (sh_var_value_func_t *)NULL; + var->assign_func = (sh_var_assign_func_t *)NULL; + + INVALIDATE_EXPORTSTR (var); + + VSETATTR (var, att_array); + VUNSETATTR (var, att_invisible); + + return var; +} + +/* Perform an array assignment name[ind]=value. If NAME already exists and + is not an array, and IND is 0, perform name=value instead. If NAME exists + and is not an array, and IND is not 0, convert it into an array with the + existing value as name[0]. + + If NAME does not exist, just create an array variable, no matter what + IND's value may be. */ +SHELL_VAR * +bind_array_variable (name, ind, value) + char *name; + arrayind_t ind; + char *value; +{ + SHELL_VAR *entry; + char *newval; + + entry = var_lookup (name, shell_variables); + + if (entry == (SHELL_VAR *) 0) + entry = make_new_array_variable (name); + else if (readonly_p (entry) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name); + return (entry); + } + else if (array_p (entry) == 0) + entry = convert_var_to_array (entry); + + /* ENTRY is an array variable, and ARRAY points to the value. */ + newval = make_variable_value (entry, value); + if (entry->assign_func) + (*entry->assign_func) (entry, newval, ind); + else + array_insert (array_cell (entry), ind, newval); + FREE (newval); + + return (entry); +} + +/* Parse NAME, a lhs of an assignment statement of the form v[s], and + assign VALUE to that array element by calling bind_array_variable(). */ +SHELL_VAR * +assign_array_element (name, value) + char *name, *value; +{ + char *sub, *vname; + arrayind_t ind; + int sublen; + SHELL_VAR *entry; + + vname = array_variable_name (name, &sub, &sublen); + + if (vname == 0) + return ((SHELL_VAR *)NULL); + + if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1)) + { + free (vname); + err_badarraysub (name); + return ((SHELL_VAR *)NULL); + } + + ind = array_expand_index (sub, sublen); + if (ind < 0) + { + free (vname); + err_badarraysub (name); + return ((SHELL_VAR *)NULL); + } + + entry = bind_array_variable (vname, ind, value); + + free (vname); + return (entry); +} + +/* Find the array variable corresponding to NAME. If there is no variable, + create a new array variable. If the variable exists but is not an array, + convert it to an indexed array. If CHECK_FLAGS is non-zero, an existing + variable is checked for the readonly or noassign attribute in preparation + for assignment (e.g., by the `read' builtin). */ +SHELL_VAR * +find_or_make_array_variable (name, check_flags) + char *name; + int check_flags; +{ + SHELL_VAR *var; + + var = find_variable (name); + + if (var == 0) + var = make_new_array_variable (name); + else if (check_flags && (readonly_p (var) || noassign_p (var))) + { + if (readonly_p (var)) + err_readonly (name); + return ((SHELL_VAR *)NULL); + } + else if (array_p (var) == 0) + var = convert_var_to_array (var); + + return (var); +} + +/* Perform a compound assignment statement for array NAME, where VALUE is + the text between the parens: NAME=( VALUE ) */ +SHELL_VAR * +assign_array_from_string (name, value) + char *name, *value; +{ + SHELL_VAR *var; + + var = find_or_make_array_variable (name, 1); + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (assign_array_var_from_string (var, value)); +} + +/* Sequentially assign the indices of indexed array variable VAR from the + words in LIST. */ +SHELL_VAR * +assign_array_var_from_word_list (var, list) + SHELL_VAR *var; + WORD_LIST *list; +{ + register arrayind_t i; + register WORD_LIST *l; + ARRAY *a; + + for (a = array_cell (var), l = list, i = 0; l; l = l->next, i++) + if (var->assign_func) + (*var->assign_func) (var, l->word->word, i); + else + array_insert (a, i, l->word->word); + return var; +} + +/* Perform a compound array assignment: VAR->name=( VALUE ). The + VALUE has already had the parentheses stripped. */ +SHELL_VAR * +assign_array_var_from_string (var, value) + SHELL_VAR *var; + char *value; +{ + ARRAY *a; + WORD_LIST *list, *nlist; + char *w, *val, *nval; + int ni, len; + arrayind_t ind, last_ind; + + if (value == 0) + return var; + + /* If this is called from declare_builtin, value[0] == '(' and + xstrchr(value, ')') != 0. In this case, we need to extract + the value from between the parens before going on. */ + if (*value == '(') /*)*/ + { + ni = 1; + val = extract_array_assignment_list (value, &ni); + if (val == 0) + return var; + } + else + val = value; + + /* Expand the value string into a list of words, performing all the + shell expansions including pathname generation and word splitting. */ + /* First we split the string on whitespace, using the shell parser + (ksh93 seems to do this). */ + list = parse_string_to_word_list (val, 1, "array assign"); + + /* If we're using [subscript]=value, we need to quote each [ and ] to + prevent unwanted filename expansion. */ + if (list) + quote_array_assignment_chars (list); + + /* Now that we've split it, perform the shell expansions on each + word in the list. */ + nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL; + + dispose_words (list); + + if (val != value) + free (val); + + a = array_cell (var); + + /* Now that we are ready to assign values to the array, kill the existing + value. */ + if (a) + array_flush (a); + + for (last_ind = 0, list = nlist; list; list = list->next) + { + w = list->word->word; + + /* We have a word of the form [ind]=value */ + if ((list->word->flags & W_ASSIGNMENT) && w[0] == '[') + { + len = skipsubscript (w, 0); + + if (w[len] != ']' || w[len+1] != '=') + { + nval = make_variable_value (var, w); + if (var->assign_func) + (*var->assign_func) (var, nval, last_ind); + else + array_insert (a, last_ind, nval); + FREE (nval); + last_ind++; + continue; + } + + if (len == 1) + { + err_badarraysub (w); + continue; + } + + if (ALL_ELEMENT_SUB (w[1]) && len == 2) + { + report_error (_("%s: cannot assign to non-numeric index"), w); + continue; + } + + ind = array_expand_index (w + 1, len); + if (ind < 0) + { + err_badarraysub (w); + continue; + } + last_ind = ind; + val = w + len + 2; + } + else /* No [ind]=value, just a stray `=' */ + { + ind = last_ind; + val = w; + } + + if (integer_p (var)) + this_command_name = (char *)NULL; /* no command name for errors */ + nval = make_variable_value (var, val); + if (var->assign_func) + (*var->assign_func) (var, nval, ind); + else + array_insert (a, ind, nval); + FREE (nval); + last_ind++; + } + + dispose_words (nlist); + return (var); +} + +/* For each word in a compound array assignment, if the word looks like + [ind]=value, quote the `[' and `]' before the `=' to protect them from + unwanted filename expansion. */ +static void +quote_array_assignment_chars (list) + WORD_LIST *list; +{ + char *s, *t, *nword; + int saw_eq; + WORD_LIST *l; + + for (l = list; l; l = l->next) + { + if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0') + continue; /* should not happen, but just in case... */ + /* Don't bother if it doesn't look like [ind]=value */ + if (l->word->word[0] != '[' || xstrchr (l->word->word, '=') == 0) /* ] */ + continue; + s = nword = (char *)xmalloc (strlen (l->word->word) * 2 + 1); + saw_eq = 0; + for (t = l->word->word; *t; ) + { + if (*t == '=') + saw_eq = 1; + if (saw_eq == 0 && (*t == '[' || *t == ']')) + *s++ = '\\'; + *s++ = *t++; + } + *s = '\0'; + free (l->word->word); + l->word->word = nword; + } +} + +/* This function assumes s[i] == '['; returns with s[ret] == ']' if + an array subscript is correctly parsed. */ +int +skipsubscript (s, i) + const char *s; + int i; +{ + int count, c; +#if defined (HANDLE_MULTIBYTE) + mbstate_t state, state_bak; + size_t slength, mblength; + size_t mb_cur_max; +#endif + +#if defined (HANDLE_MULTIBYTE) + memset (&state, '\0', sizeof (mbstate_t)); + slength = strlen (s + i); + mb_cur_max = MB_CUR_MAX; +#endif + + count = 1; + while (count) + { + /* Advance one (possibly multibyte) character in S starting at I. */ +#if defined (HANDLE_MULTIBYTE) + if (mb_cur_max > 1) + { + state_bak = state; + mblength = mbrlen (s + i, slength, &state); + + if (MB_INVALIDCH (mblength)) + { + state = state_bak; + i++; + slength--; + } + else if (MB_NULLWCH (mblength)) + return i; + else + { + i += mblength; + slength -= mblength; + } + } + else +#endif + ++i; + + c = s[i]; + + if (c == 0) + break; + else if (c == '[') + count++; + else if (c == ']') + count--; + } + + return i; +} + +/* This function is called with SUB pointing to just after the beginning + `[' of an array subscript and removes the array element to which SUB + expands from array VAR. A subscript of `*' or `@' unsets the array. */ +int +unbind_array_element (var, sub) + SHELL_VAR *var; + char *sub; +{ + int len; + arrayind_t ind; + ARRAY_ELEMENT *ae; + + len = skipsubscript (sub, 0); + if (sub[len] != ']' || len == 0) + { + builtin_error ("%s[%s: %s", var->name, sub, _(bash_badsub_errmsg)); + return -1; + } + sub[len] = '\0'; + + if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0) + { + unbind_variable (var->name); + return (0); + } + ind = array_expand_index (sub, len+1); + if (ind < 0) + { + builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg)); + return -1; + } + ae = array_remove (array_cell (var), ind); + if (ae) + array_dispose_element (ae); + return 0; +} + +/* Format and output an array assignment in compound form VAR=(VALUES), + suitable for re-use as input. */ +void +print_array_assignment (var, quoted) + SHELL_VAR *var; + int quoted; +{ + char *vstr; + + vstr = array_to_assign (array_cell (var), quoted); + + if (vstr == 0) + printf ("%s=%s\n", var->name, quoted ? "'()'" : "()"); + else + { + printf ("%s=%s\n", var->name, vstr); + free (vstr); + } +} + +/***********************************************************************/ +/* */ +/* Utility functions to manage arrays and their contents for expansion */ +/* */ +/***********************************************************************/ + +/* Return 1 if NAME is a properly-formed array reference v[sub]. */ +int +valid_array_reference (name) + char *name; +{ + char *t; + int r, len; + + t = xstrchr (name, '['); /* ] */ + if (t) + { + *t = '\0'; + r = legal_identifier (name); + *t = '['; + if (r == 0) + return 0; + /* Check for a properly-terminated non-blank subscript. */ + len = skipsubscript (t, 0); + if (t[len] != ']' || len == 1) + return 0; + for (r = 1; r < len; r++) + if (whitespace (t[r]) == 0) + return 1; + return 0; + } + return 0; +} + +/* Expand the array index beginning at S and extending LEN characters. */ +arrayind_t +array_expand_index (s, len) + char *s; + int len; +{ + char *exp, *t; + int expok; + arrayind_t val; + + exp = (char *)xmalloc (len); + strncpy (exp, s, len - 1); + exp[len - 1] = '\0'; + t = expand_string_to_string (exp, 0); + this_command_name = (char *)NULL; + val = evalexp (t, &expok); + free (t); + free (exp); + if (expok == 0) + { + last_command_exit_value = EXECUTION_FAILURE; + jump_to_top_level (DISCARD); + } + return val; +} + +/* Return the name of the variable specified by S without any subscript. + If SUBP is non-null, return a pointer to the start of the subscript + in *SUBP. If LENP is non-null, the length of the subscript is returned + in *LENP. This returns newly-allocated memory. */ +char * +array_variable_name (s, subp, lenp) + char *s, **subp; + int *lenp; +{ + char *t, *ret; + int ind, ni; + + t = xstrchr (s, '['); + if (t == 0) + { + if (subp) + *subp = t; + if (lenp) + *lenp = 0; + return ((char *)NULL); + } + ind = t - s; + ni = skipsubscript (s, ind); + if (ni <= ind + 1 || s[ni] != ']') + { + err_badarraysub (s); + if (subp) + *subp = t; + if (lenp) + *lenp = 0; + return ((char *)NULL); + } + + *t = '\0'; + ret = savestring (s); + *t++ = '['; /* ] */ + + if (subp) + *subp = t; + if (lenp) + *lenp = ni - ind; + + return ret; +} + +/* Return the variable specified by S without any subscript. If SUBP is + non-null, return a pointer to the start of the subscript in *SUBP. + If LENP is non-null, the length of the subscript is returned in *LENP. */ +SHELL_VAR * +array_variable_part (s, subp, lenp) + char *s, **subp; + int *lenp; +{ + char *t; + SHELL_VAR *var; + + t = array_variable_name (s, subp, lenp); + if (t == 0) + return ((SHELL_VAR *)NULL); + var = find_variable (t); + + free (t); + return var; +} + +/* Return a string containing the elements in the array and subscript + described by S. If the subscript is * or @, obeys quoting rules akin + to the expansion of $* and $@ including double quoting. If RTYPE + is non-null it gets 1 if the array reference is name[@] or name[*] + and 0 otherwise. */ +static char * +array_value_internal (s, quoted, allow_all, rtype) + char *s; + int quoted, allow_all, *rtype; +{ + int len; + arrayind_t ind; + char *retval, *t, *temp; + WORD_LIST *l; + SHELL_VAR *var; + + var = array_variable_part (s, &t, &len); + + /* Expand the index, even if the variable doesn't exist, in case side + effects are needed, like ${w[i++]} where w is unset. */ +#if 0 + if (var == 0) + return (char *)NULL; +#endif + + if (len == 0) + return ((char *)NULL); /* error message already printed */ + + /* [ */ + if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') + { + if (rtype) + *rtype = 1; + if (allow_all == 0) + { + err_badarraysub (s); + return ((char *)NULL); + } + else if (var == 0) + return ((char *)NULL); + else if (array_p (var) == 0) + l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL); + else + { + l = array_to_word_list (array_cell (var)); + if (l == (WORD_LIST *)NULL) + return ((char *) NULL); + } + + if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + { + temp = string_list_dollar_star (l); + retval = quote_string (temp); + free (temp); + } + else /* ${name[@]} or unquoted ${name[*]} */ + retval = string_list_dollar_at (l, quoted); + + dispose_words (l); + } + else + { + if (rtype) + *rtype = 0; + ind = array_expand_index (t, len); + if (ind < 0) + { + if (var) + err_badarraysub (var->name); + else + { + t[-1] = '\0'; + err_badarraysub (s); + t[-1] = '['; /* ] */ + } + return ((char *)NULL); + } + if (var == 0) + return ((char *)NULL); + if (array_p (var) == 0) + return (ind == 0 ? value_cell (var) : (char *)NULL); + retval = array_reference (array_cell (var), ind); + } + + return retval; +} + +/* Return a string containing the elements described by the array and + subscript contained in S, obeying quoting for subscripts * and @. */ +char * +array_value (s, quoted, rtype) + char *s; + int quoted, *rtype; +{ + return (array_value_internal (s, quoted, 1, rtype)); +} + +/* Return the value of the array indexing expression S as a single string. + If ALLOW_ALL is 0, do not allow `@' and `*' subscripts. This is used + by other parts of the shell such as the arithmetic expression evaluator + in expr.c. */ +char * +get_array_value (s, allow_all, rtype) + char *s; + int allow_all, *rtype; +{ + return (array_value_internal (s, 0, allow_all, rtype)); +} + +char * +array_keys (s, quoted) + char *s; + int quoted; +{ + int len; + char *retval, *t, *temp; + WORD_LIST *l; + SHELL_VAR *var; + + var = array_variable_part (s, &t, &len); + + /* [ */ + if (var == 0 || ALL_ELEMENT_SUB (t[0]) == 0 || t[1] != ']') + return (char *)NULL; + + if (array_p (var) == 0) + l = add_string_to_list ("0", (WORD_LIST *)NULL); + else + { + l = array_keys_to_word_list (array_cell (var)); + if (l == (WORD_LIST *)NULL) + return ((char *) NULL); + } + + if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + { + temp = string_list_dollar_star (l); + retval = quote_string (temp); + free (temp); + } + else /* ${!name[@]} or unquoted ${!name[*]} */ + retval = string_list_dollar_at (l, quoted); + + dispose_words (l); + return retval; +} +#endif /* ARRAY_VARS */ diff --git a/bashline.c b/bashline.c index 6e8c81410..667a97eba 100644 --- a/bashline.c +++ b/bashline.c @@ -2513,7 +2513,7 @@ glob_complete_word (text, state) static char **matches = (char **)NULL; static int ind; int glen; - char *ret; + char *ret, *ttext; if (state == 0) { @@ -2523,17 +2523,22 @@ glob_complete_word (text, state) FREE (globorig); FREE (globtext); + ttext = bash_tilde_expand (text, 0); + if (rl_explicit_arg) { - globorig = savestring (text); - glen = strlen (text); + globorig = savestring (ttext); + glen = strlen (ttext); globtext = (char *)xmalloc (glen + 2); - strcpy (globtext, text); + strcpy (globtext, ttext); globtext[glen] = '*'; globtext[glen+1] = '\0'; } else - globtext = globorig = savestring (text); + globtext = globorig = savestring (ttext); + + if (ttext != text) + free (ttext); matches = shell_glob_filename (globtext); if (GLOB_FAILED (matches)) diff --git a/bashline.c~ b/bashline.c~ new file mode 100644 index 000000000..6e8c81410 --- /dev/null +++ b/bashline.c~ @@ -0,0 +1,3053 @@ +/* bashline.c -- Bash's interface to the readline library. */ + +/* Copyright (C) 1987-2004 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Bash is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with Bash; see the file COPYING. If not, write to the Free + Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#if defined (READLINE) + +#include "bashtypes.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (HAVE_GRP_H) +# include +#endif + +#if defined (HAVE_NETDB_H) +# include +#endif + +#include +#include "chartypes.h" +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "input.h" +#include "builtins.h" +#include "bashhist.h" +#include "bashline.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "pathexp.h" +#include "builtins/common.h" +#include +#include +#include + +#include + +#if defined (ALIAS) +# include "alias.h" +#endif + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +/* These should agree with the defines for emacs_mode and vi_mode in + rldefs.h, even though that's not a public readline header file. */ +#ifndef EMACS_EDITING_MODE +# define NO_EDITING_MODE -1 +# define EMACS_EDITING_MODE 1 +# define VI_EDITING_MODE 0 +#endif + +#if defined (BRACE_COMPLETION) +extern int bash_brace_completion __P((int, int)); +#endif /* BRACE_COMPLETION */ + +/* Forward declarations */ + +/* Functions bound to keys in Readline for Bash users. */ +static int shell_expand_line __P((int, int)); +static int display_shell_version __P((int, int)); +static int operate_and_get_next __P((int, int)); + +static int bash_ignore_filenames __P((char **)); +static int bash_ignore_everything __P((char **)); + +#if defined (BANG_HISTORY) +static char *history_expand_line_internal __P((char *)); +static int history_expand_line __P((int, int)); +static int tcsh_magic_space __P((int, int)); +#endif /* BANG_HISTORY */ +#ifdef ALIAS +static int alias_expand_line __P((int, int)); +#endif +#if defined (BANG_HISTORY) && defined (ALIAS) +static int history_and_alias_expand_line __P((int, int)); +#endif + +/* Helper functions for Readline. */ +static int bash_directory_completion_hook __P((char **)); +static int filename_completion_ignore __P((char **)); +static int bash_push_line __P((void)); + +static void cleanup_expansion_error __P((void)); +static void maybe_make_readline_line __P((char *)); +static void set_up_new_line __P((char *)); + +static int check_redir __P((int)); +static char **attempt_shell_completion __P((const char *, int, int)); +static char *variable_completion_function __P((const char *, int)); +static char *hostname_completion_function __P((const char *, int)); +static char *command_subst_completion_function __P((const char *, int)); + +static void build_history_completion_array __P((void)); +static char *history_completion_generator __P((const char *, int)); +static int dynamic_complete_history __P((int, int)); + +static void initialize_hostname_list __P((void)); +static void add_host_name __P((char *)); +static void snarf_hosts_from_file __P((char *)); +static char **hostnames_matching __P((char *)); + +static void _ignore_completion_names __P((char **, sh_ignore_func_t *)); +static int name_is_acceptable __P((const char *)); +static int test_for_directory __P((const char *)); +static int return_zero __P((const char *)); + +static char *bash_dequote_filename __P((char *, int)); +static char *quote_word_break_chars __P((char *)); +static char *bash_quote_filename __P((char *, int, char *)); + +static int bash_execute_unix_command __P((int, int)); +static void init_unix_command_map __P((void)); +static int isolate_sequence __P((char *, int, int, int *)); + +static int set_saved_history __P((void)); + +#if defined (ALIAS) +static int posix_edit_macros __P((int, int)); +#endif + +#if defined (PROGRAMMABLE_COMPLETION) +static int find_cmd_start __P((int)); +static int find_cmd_end __P((int)); +static char *find_cmd_name __P((int)); +static char *prog_complete_return __P((const char *, int)); + +static char **prog_complete_matches; +#endif + +/* Variables used here but defined in other files. */ +#if defined (BANG_HISTORY) +extern int hist_verify; +#endif + +extern int current_command_line_count, last_command_exit_value; +extern int posixly_correct, no_symbolic_links; +extern char *current_prompt_string, *ps1_prompt; +extern STRING_INT_ALIST word_token_alist[]; +extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin; + +/* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual + completion functions which indicate what type of completion should be + done (at or before point) that can be bound to key sequences with + the readline library. */ +#define SPECIFIC_COMPLETION_FUNCTIONS + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) +static int bash_specific_completion __P((int, rl_compentry_func_t *)); + +static int bash_complete_filename_internal __P((int)); +static int bash_complete_username_internal __P((int)); +static int bash_complete_hostname_internal __P((int)); +static int bash_complete_variable_internal __P((int)); +static int bash_complete_command_internal __P((int)); + +static int bash_complete_filename __P((int, int)); +static int bash_possible_filename_completions __P((int, int)); +static int bash_complete_username __P((int, int)); +static int bash_possible_username_completions __P((int, int)); +static int bash_complete_hostname __P((int, int)); +static int bash_possible_hostname_completions __P((int, int)); +static int bash_complete_variable __P((int, int)); +static int bash_possible_variable_completions __P((int, int)); +static int bash_complete_command __P((int, int)); +static int bash_possible_command_completions __P((int, int)); + +static char *glob_complete_word __P((const char *, int)); +static int bash_glob_completion_internal __P((int)); +static int bash_glob_complete_word __P((int, int)); +static int bash_glob_expand_word __P((int, int)); +static int bash_glob_list_expansions __P((int, int)); + +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ + +static int edit_and_execute_command __P((int, int, int, char *)); +#if defined (VI_MODE) +static int vi_edit_and_execute_command __P((int, int)); +static int bash_vi_complete __P((int, int)); +#endif +static int emacs_edit_and_execute_command __P((int, int)); + +/* Non-zero once initalize_readline () has been called. */ +int bash_readline_initialized = 0; + +/* If non-zero, we do hostname completion, breaking words at `@' and + trying to complete the stuff after the `@' from our own internal + host list. */ +int perform_hostname_completion = 1; + +/* If non-zero, we don't do command completion on an empty line. */ +int no_empty_command_completion; + +/* Set FORCE_FIGNORE if you want to honor FIGNORE even if it ignores the + only possible matches. Set to 0 if you want to match filenames if they + are the only possible matches, even if FIGNORE says to. */ +int force_fignore = 1; + +static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:"; +static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:"; +/* )) */ + +static rl_hook_func_t *old_rl_startup_hook = (rl_hook_func_t *)NULL; + +/* What kind of quoting is performed by bash_quote_filename: + COMPLETE_DQUOTE = double-quoting the filename + COMPLETE_SQUOTE = single_quoting the filename + COMPLETE_BSQUOTE = backslash-quoting special chars in the filename +*/ +#define COMPLETE_DQUOTE 1 +#define COMPLETE_SQUOTE 2 +#define COMPLETE_BSQUOTE 3 +static int completion_quoting_style = COMPLETE_BSQUOTE; + +/* Change the readline VI-mode keymaps into or out of Posix.2 compliance. + Called when the shell is put into or out of `posix' mode. */ +void +posix_readline_initialize (on_or_off) + int on_or_off; +{ + if (on_or_off) + rl_variable_bind ("comment-begin", "#"); +#if defined (VI_MODE) + rl_bind_key_in_map (CTRL ('I'), on_or_off ? rl_insert : rl_complete, vi_insertion_keymap); +#endif +} + +/* When this function returns, rl_completer_word_break_characters points to + dynamically allocated memory. */ +int +enable_hostname_completion (on_or_off) + int on_or_off; +{ + int old_value; + char *at, *nv, *nval; + + old_value = perform_hostname_completion; + + if (on_or_off) + { + perform_hostname_completion = 1; + rl_special_prefixes = "$@"; + } + else + { + perform_hostname_completion = 0; + rl_special_prefixes = "$"; + } + + /* Now we need to figure out how to appropriately modify and assign + rl_completer_word_break_characters depending on whether we want + hostname completion on or off. */ + + /* If this is the first time this has been called + (bash_readline_initialized == 0), use the sames values as before, but + allocate new memory for rl_completer_word_break_characters. */ + + if (bash_readline_initialized == 0 && + (rl_completer_word_break_characters == 0 || + rl_completer_word_break_characters == rl_basic_word_break_characters)) + { + if (on_or_off) + rl_completer_word_break_characters = savestring (bash_completer_word_break_characters); + else + rl_completer_word_break_characters = savestring (bash_nohostname_word_break_characters); + } + else + { + /* See if we have anything to do. */ + at = strchr (rl_completer_word_break_characters, '@'); + if ((at == 0 && on_or_off == 0) || (at != 0 && on_or_off != 0)) + return old_value; + + /* We have something to do. Do it. */ + nval = (char *)xmalloc (strlen (rl_completer_word_break_characters) + 1 + on_or_off); + + if (on_or_off == 0) + { + /* Turn it off -- just remove `@' from word break chars. We want + to remove all occurrences of `@' from the char list, so we loop + rather than just copy the rest of the list over AT. */ + for (nv = nval, at = rl_completer_word_break_characters; *at; ) + if (*at != '@') + *nv++ = *at++; + else + at++; + *nv = '\0'; + } + else + { + nval[0] = '@'; + strcpy (nval + 1, rl_completer_word_break_characters); + } + + free (rl_completer_word_break_characters); + rl_completer_word_break_characters = nval; + } + + return (old_value); +} + +/* Called once from parse.y if we are going to use readline. */ +void +initialize_readline () +{ + rl_command_func_t *func; + char kseq[2]; + + if (bash_readline_initialized) + return; + + rl_terminal_name = get_string_value ("TERM"); + rl_instream = stdin; + rl_outstream = stderr; + + /* Allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name = "Bash"; + + /* Add bindable names before calling rl_initialize so they may be + referenced in the various inputrc files. */ + rl_add_defun ("shell-expand-line", shell_expand_line, -1); +#ifdef BANG_HISTORY + rl_add_defun ("history-expand-line", history_expand_line, -1); + rl_add_defun ("magic-space", tcsh_magic_space, -1); +#endif + +#ifdef ALIAS + rl_add_defun ("alias-expand-line", alias_expand_line, -1); +# ifdef BANG_HISTORY + rl_add_defun ("history-and-alias-expand-line", history_and_alias_expand_line, -1); +# endif +#endif + + /* Backwards compatibility. */ + rl_add_defun ("insert-last-argument", rl_yank_last_arg, -1); + + rl_add_defun ("operate-and-get-next", operate_and_get_next, -1); + rl_add_defun ("display-shell-version", display_shell_version, -1); + rl_add_defun ("edit-and-execute-command", emacs_edit_and_execute_command, -1); + +#if defined (BRACE_COMPLETION) + rl_add_defun ("complete-into-braces", bash_brace_completion, -1); +#endif + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) + rl_add_defun ("complete-filename", bash_complete_filename, -1); + rl_add_defun ("possible-filename-completions", bash_possible_filename_completions, -1); + rl_add_defun ("complete-username", bash_complete_username, -1); + rl_add_defun ("possible-username-completions", bash_possible_username_completions, -1); + rl_add_defun ("complete-hostname", bash_complete_hostname, -1); + rl_add_defun ("possible-hostname-completions", bash_possible_hostname_completions, -1); + rl_add_defun ("complete-variable", bash_complete_variable, -1); + rl_add_defun ("possible-variable-completions", bash_possible_variable_completions, -1); + rl_add_defun ("complete-command", bash_complete_command, -1); + rl_add_defun ("possible-command-completions", bash_possible_command_completions, -1); + rl_add_defun ("glob-complete-word", bash_glob_complete_word, -1); + rl_add_defun ("glob-expand-word", bash_glob_expand_word, -1); + rl_add_defun ("glob-list-expansions", bash_glob_list_expansions, -1); +#endif + + rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1); + + /* Bind defaults before binding our custom shell keybindings. */ + if (RL_ISSTATE(RL_STATE_INITIALIZED) == 0) + rl_initialize (); + + /* Bind up our special shell functions. */ + rl_bind_key_if_unbound_in_map (CTRL('E'), shell_expand_line, emacs_meta_keymap); + +#ifdef BANG_HISTORY + rl_bind_key_if_unbound_in_map ('^', history_expand_line, emacs_meta_keymap); +#endif + + rl_bind_key_if_unbound_in_map (CTRL ('O'), operate_and_get_next, emacs_standard_keymap); + rl_bind_key_if_unbound_in_map (CTRL ('V'), display_shell_version, emacs_ctlx_keymap); + + /* In Bash, the user can switch editing modes with "set -o [vi emacs]", + so it is not necessary to allow C-M-j for context switching. Turn + off this occasionally confusing behaviour. */ + kseq[0] = CTRL('J'); + kseq[1] = '\0'; + func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL); + if (func == rl_vi_editing_mode) + rl_unbind_key_in_map (CTRL('J'), emacs_meta_keymap); + kseq[0] = CTRL('M'); + func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL); + if (func == rl_vi_editing_mode) + rl_unbind_key_in_map (CTRL('M'), emacs_meta_keymap); +#if defined (VI_MODE) + rl_unbind_key_in_map (CTRL('E'), vi_movement_keymap); +#endif + +#if defined (BRACE_COMPLETION) + rl_bind_key_if_unbound_in_map ('{', bash_brace_completion, emacs_meta_keymap); /*}*/ +#endif /* BRACE_COMPLETION */ + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) + rl_bind_key_if_unbound_in_map ('/', bash_complete_filename, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('/', bash_possible_filename_completions, emacs_ctlx_keymap); + + /* Have to jump through hoops here because there is a default binding for + M-~ (rl_tilde_expand) */ + kseq[0] = '~'; + kseq[1] = '\0'; + func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL); + if (func == 0 || func == rl_tilde_expand) + rl_bind_keyseq_in_map (kseq, bash_complete_username, emacs_meta_keymap); + + rl_bind_key_if_unbound_in_map ('~', bash_possible_username_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('@', bash_complete_hostname, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('@', bash_possible_hostname_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('$', bash_complete_variable, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('$', bash_possible_variable_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('!', bash_complete_command, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('!', bash_possible_command_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('g', bash_glob_complete_word, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('*', bash_glob_expand_word, emacs_ctlx_keymap); + rl_bind_key_if_unbound_in_map ('g', bash_glob_list_expansions, emacs_ctlx_keymap); + +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ + + rl_bind_key_if_unbound_in_map (TAB, dynamic_complete_history, emacs_meta_keymap); + + /* Tell the completer that we want a crack first. */ + rl_attempted_completion_function = attempt_shell_completion; + + /* Tell the completer that we might want to follow symbolic links or + do other expansion on directory names. */ + rl_directory_completion_hook = bash_directory_completion_hook; + + /* Tell the filename completer we want a chance to ignore some names. */ + rl_ignore_some_completions_function = filename_completion_ignore; + + /* Bind C-xC-e to invoke emacs and run result as commands. */ + rl_bind_key_if_unbound_in_map (CTRL ('E'), emacs_edit_and_execute_command, emacs_ctlx_keymap); +#if defined (VI_MODE) + rl_bind_key_if_unbound_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap); +# if defined (ALIAS) + rl_bind_key_if_unbound_in_map ('@', posix_edit_macros, vi_movement_keymap); +# endif + + rl_bind_key_in_map ('\\', bash_vi_complete, vi_movement_keymap); + rl_bind_key_in_map ('*', bash_vi_complete, vi_movement_keymap); + rl_bind_key_in_map ('=', bash_vi_complete, vi_movement_keymap); +#endif + + rl_completer_quote_characters = "'\""; + + /* This sets rl_completer_word_break_characters and rl_special_prefixes + to the appropriate values, depending on whether or not hostname + completion is enabled. */ + enable_hostname_completion (perform_hostname_completion); + + /* characters that need to be quoted when appearing in filenames. */ + rl_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:{"; /*}*/ + rl_filename_quoting_function = bash_quote_filename; + rl_filename_dequoting_function = bash_dequote_filename; + rl_char_is_quoted_p = char_is_quoted; + +#if 0 + /* This is superfluous and makes it impossible to use tab completion in + vi mode even when explicitly binding it in ~/.inputrc. sv_strict_posix() + should already have called posix_readline_initialize() when + posixly_correct was set. */ + if (posixly_correct) + posix_readline_initialize (1); +#endif + + bash_readline_initialized = 1; +} + +/* On Sun systems at least, rl_attempted_completion_function can end up + getting set to NULL, and rl_completion_entry_function set to do command + word completion if Bash is interrupted while trying to complete a command + word. This just resets all the completion functions to the right thing. + It's called from throw_to_top_level(). */ +void +bashline_reinitialize () +{ + tilde_initialize (); + rl_attempted_completion_function = attempt_shell_completion; + rl_completion_entry_function = NULL; + rl_directory_completion_hook = bash_directory_completion_hook; + rl_ignore_some_completions_function = filename_completion_ignore; +} + +/* Contains the line to push into readline. */ +static char *push_to_readline = (char *)NULL; + +/* Push the contents of push_to_readline into the + readline buffer. */ +static int +bash_push_line () +{ + if (push_to_readline) + { + rl_insert_text (push_to_readline); + free (push_to_readline); + push_to_readline = (char *)NULL; + rl_startup_hook = old_rl_startup_hook; + } + return 0; +} + +/* Call this to set the initial text for the next line to read + from readline. */ +int +bash_re_edit (line) + char *line; +{ + FREE (push_to_readline); + + push_to_readline = savestring (line); + old_rl_startup_hook = rl_startup_hook; + rl_startup_hook = bash_push_line; + + return (0); +} + +static int +display_shell_version (count, c) + int count, c; +{ + rl_crlf (); + show_shell_version (0); + putc ('\r', rl_outstream); + fflush (rl_outstream); + rl_on_new_line (); + rl_redisplay (); + return 0; +} + +/* **************************************************************** */ +/* */ +/* Readline Stuff */ +/* */ +/* **************************************************************** */ + +/* If the user requests hostname completion, then simply build a list + of hosts, and complete from that forever more, or at least until + HOSTFILE is unset. */ + +/* THIS SHOULD BE A STRINGLIST. */ +/* The kept list of hostnames. */ +static char **hostname_list = (char **)NULL; + +/* The physical size of the above list. */ +static int hostname_list_size; + +/* The number of hostnames in the above list. */ +static int hostname_list_length; + +/* Whether or not HOSTNAME_LIST has been initialized. */ +int hostname_list_initialized = 0; + +/* Initialize the hostname completion table. */ +static void +initialize_hostname_list () +{ + char *temp; + + temp = get_string_value ("HOSTFILE"); + if (temp == 0) + temp = get_string_value ("hostname_completion_file"); + if (temp == 0) + temp = DEFAULT_HOSTS_FILE; + + snarf_hosts_from_file (temp); + + if (hostname_list) + hostname_list_initialized++; +} + +/* Add NAME to the list of hosts. */ +static void +add_host_name (name) + char *name; +{ + if (hostname_list_length + 2 > hostname_list_size) + { + hostname_list_size = (hostname_list_size + 32) - (hostname_list_size % 32); + hostname_list = strvec_resize (hostname_list, hostname_list_size); + } + + hostname_list[hostname_list_length++] = savestring (name); + hostname_list[hostname_list_length] = (char *)NULL; +} + +#define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c)) + +static void +snarf_hosts_from_file (filename) + char *filename; +{ + FILE *file; + char *temp, buffer[256], name[256]; + register int i, start; + + file = fopen (filename, "r"); + if (file == 0) + return; + + while (temp = fgets (buffer, 255, file)) + { + /* Skip to first character. */ + for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++) + ; + + /* If comment or blank line, ignore. */ + if (buffer[i] == '\0' || buffer[i] == '#') + continue; + + /* If `preprocessor' directive, do the include. */ + if (strncmp (buffer + i, "$include ", 9) == 0) + { + char *incfile, *t; + + /* Find start of filename. */ + for (incfile = buffer + i + 9; *incfile && whitespace (*incfile); incfile++) + ; + + /* Find end of filename. */ + for (t = incfile; *t && cr_whitespace (*t) == 0; t++) + ; + + *t = '\0'; + + snarf_hosts_from_file (incfile); + continue; + } + + /* Skip internet address if present. */ + if (DIGIT (buffer[i])) + for (; buffer[i] && cr_whitespace (buffer[i]) == 0; i++); + + /* Gobble up names. Each name is separated with whitespace. */ + while (buffer[i]) + { + for (; cr_whitespace (buffer[i]); i++) + ; + if (buffer[i] == '\0' || buffer[i] == '#') + break; + + /* Isolate the current word. */ + for (start = i; buffer[i] && cr_whitespace (buffer[i]) == 0; i++) + ; + if (i == start) + continue; + strncpy (name, buffer + start, i - start); + name[i - start] = '\0'; + add_host_name (name); + } + } + fclose (file); +} + +/* Return the hostname list. */ +char ** +get_hostname_list () +{ + if (hostname_list_initialized == 0) + initialize_hostname_list (); + return (hostname_list); +} + +void +clear_hostname_list () +{ + register int i; + + if (hostname_list_initialized == 0) + return; + for (i = 0; i < hostname_list_length; i++) + free (hostname_list[i]); + hostname_list_length = 0; +} + +/* Return a NULL terminated list of hostnames which begin with TEXT. + Initialize the hostname list the first time if neccessary. + The array is malloc ()'ed, but not the individual strings. */ +static char ** +hostnames_matching (text) + char *text; +{ + register int i, len, nmatch, rsize; + char **result; + + if (hostname_list_initialized == 0) + initialize_hostname_list (); + + if (hostname_list_initialized == 0) + return ((char **)NULL); + + /* Special case. If TEXT consists of nothing, then the whole list is + what is desired. */ + if (*text == '\0') + { + result = strvec_create (1 + hostname_list_length); + for (i = 0; i < hostname_list_length; i++) + result[i] = hostname_list[i]; + result[i] = (char *)NULL; + return (result); + } + + /* Scan until found, or failure. */ + len = strlen (text); + result = (char **)NULL; + for (i = nmatch = rsize = 0; i < hostname_list_length; i++) + { + if (STREQN (text, hostname_list[i], len) == 0) + continue; + + /* OK, it matches. Add it to the list. */ + if (nmatch >= (rsize - 1)) + { + rsize = (rsize + 16) - (rsize % 16); + result = strvec_resize (result, rsize); + } + + result[nmatch++] = hostname_list[i]; + } + if (nmatch) + result[nmatch] = (char *)NULL; + return (result); +} + +/* The equivalent of the Korn shell C-o operate-and-get-next-history-line + editing command. */ +static int saved_history_line_to_use = -1; + +static int +set_saved_history () +{ + if (saved_history_line_to_use >= 0) + rl_get_previous_history (history_length - saved_history_line_to_use, 0); + saved_history_line_to_use = -1; + rl_startup_hook = old_rl_startup_hook; + return (0); +} + +static int +operate_and_get_next (count, c) + int count, c; +{ + int where; + + /* Accept the current line. */ + rl_newline (1, c); + + /* Find the current line, and find the next line to use. */ + where = where_history (); + + if ((history_is_stifled () && (history_length >= history_max_entries)) || + (where >= history_length - 1)) + saved_history_line_to_use = where; + else + saved_history_line_to_use = where + 1; + + old_rl_startup_hook = rl_startup_hook; + rl_startup_hook = set_saved_history; + + return 0; +} + +/* This vi mode command causes VI_EDIT_COMMAND to be run on the current + command being entered (if no explicit argument is given), otherwise on + a command from the history file. */ + +#define VI_EDIT_COMMAND "fc -e \"${VISUAL:-${EDITOR:-vi}}\"" +#define EMACS_EDIT_COMMAND "fc -e \"${VISUAL:-${EDITOR:-emacs}}\"" + +static int +edit_and_execute_command (count, c, editing_mode, edit_command) + int count, c, editing_mode; + char *edit_command; +{ + char *command; + int r, cclc, rrs; + + rrs = rl_readline_state; + cclc = current_command_line_count; + + /* Accept the current line. */ + rl_newline (1, c); + + if (rl_explicit_arg) + { + command = (char *)xmalloc (strlen (edit_command) + 8); + sprintf (command, "%s %d", edit_command, count); + } + else + { + /* Take the command we were just editing, add it to the history file, + then call fc to operate on it. We have to add a dummy command to + the end of the history because fc ignores the last command (assumes + it's supposed to deal with the command before the `fc'). */ + using_history (); + bash_add_history (rl_line_buffer); + bash_add_history (""); + history_lines_this_session++; + using_history (); + command = savestring (edit_command); + } + + /* Now, POSIX.1-2001 and SUSv3 say that the commands executed from the + temporary file should be placed into the history. We don't do that + yet. */ + r = parse_and_execute (command, (editing_mode == VI_EDITING_MODE) ? "v" : "C-xC-e", SEVAL_NOHIST); + + current_command_line_count = cclc; + + /* Now erase the contents of the current line and undo the effects of the + rl_accept_line() above. We don't even want to make the text we just + executed available for undoing. */ + rl_line_buffer[0] = '\0'; /* XXX */ + rl_point = rl_end = 0; + rl_done = 0; + rl_readline_state = rrs; + + rl_forced_update_display (); + + return r; +} + +#if defined (VI_MODE) +static int +vi_edit_and_execute_command (count, c) + int count, c; +{ + return (edit_and_execute_command (count, c, VI_EDITING_MODE, VI_EDIT_COMMAND)); +} +#endif /* VI_MODE */ + +static int +emacs_edit_and_execute_command (count, c) + int count, c; +{ + return (edit_and_execute_command (count, c, EMACS_EDITING_MODE, EMACS_EDIT_COMMAND)); +} + +#if defined (ALIAS) +static int +posix_edit_macros (count, key) + int count, key; +{ + int c; + char alias_name[3], *alias_value, *macro; + + c = rl_read_key (); + alias_name[0] = '_'; + alias_name[1] = c; + alias_name[2] = '\0'; + + alias_value = get_alias_value (alias_name); + if (alias_value && *alias_value) + { + macro = savestring (alias_value); + rl_push_macro_input (macro); + } + return 0; +} +#endif + +/* **************************************************************** */ +/* */ +/* How To Do Shell Completion */ +/* */ +/* **************************************************************** */ + +#define COMMAND_SEPARATORS ";|&{(`" +/* )} */ + +static int +check_redir (ti) + int ti; +{ + register int this_char, prev_char; + + /* Handle the two character tokens `>&', `<&', and `>|'. + We are not in a command position after one of these. */ + this_char = rl_line_buffer[ti]; + prev_char = rl_line_buffer[ti - 1]; + + if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) || + (this_char == '|' && prev_char == '>')) + return (1); + else if ((this_char == '{' && prev_char == '$') || /* } */ + (char_is_quoted (rl_line_buffer, ti))) + return (1); + return (0); +} + +#if defined (PROGRAMMABLE_COMPLETION) +/* + * XXX - because of the <= start test, and setting os = s+1, this can + * potentially return os > start. This is probably not what we want to + * happen, but fix later after 2.05a-release. + */ +static int +find_cmd_start (start) + int start; +{ + register int s, os; + + os = 0; + while (((s = skip_to_delim (rl_line_buffer, os, COMMAND_SEPARATORS)) <= start) && + rl_line_buffer[s]) + os = s+1; + return os; +} + +static int +find_cmd_end (end) + int end; +{ + register int e; + + e = skip_to_delim (rl_line_buffer, end, COMMAND_SEPARATORS); + return e; +} + +static char * +find_cmd_name (start) + int start; +{ + char *name; + register int s, e; + + for (s = start; whitespace (rl_line_buffer[s]); s++) + ; + + /* skip until a shell break character */ + e = skip_to_delim (rl_line_buffer, s, "()<>;&| \t\n"); + + name = substring (rl_line_buffer, s, e); + + return (name); +} + +static char * +prog_complete_return (text, matchnum) + const char *text; + int matchnum; +{ + static int ind; + + if (matchnum == 0) + ind = 0; + + if (prog_complete_matches == 0 || prog_complete_matches[ind] == 0) + return (char *)NULL; + return (prog_complete_matches[ind++]); +} + +#endif /* PROGRAMMABLE_COMPLETION */ + +/* Do some completion on TEXT. The indices of TEXT in RL_LINE_BUFFER are + at START and END. Return an array of matches, or NULL if none. */ +static char ** +attempt_shell_completion (text, start, end) + const char *text; + int start, end; +{ + int in_command_position, ti, saveti, qc; + char **matches, *command_separator_chars; + + command_separator_chars = COMMAND_SEPARATORS; + matches = (char **)NULL; + rl_ignore_some_completions_function = filename_completion_ignore; + + /* Determine if this could be a command word. It is if it appears at + the start of the line (ignoring preceding whitespace), or if it + appears after a character that separates commands. It cannot be a + command word if we aren't at the top-level prompt. */ + ti = start - 1; + saveti = qc = -1; + + while ((ti > -1) && (whitespace (rl_line_buffer[ti]))) + ti--; + +#if 1 + /* If this is an open quote, maybe we're trying to complete a quoted + command name. */ + if (ti >= 0 && (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\'')) + { + qc = rl_line_buffer[ti]; + saveti = ti--; + while (ti > -1 && (whitespace (rl_line_buffer[ti]))) + ti--; + } +#endif + + in_command_position = 0; + if (ti < 0) + { + /* Only do command completion at the start of a line when we + are prompting at the top level. */ + if (current_prompt_string == ps1_prompt) + in_command_position++; + } + else if (member (rl_line_buffer[ti], command_separator_chars)) + { + in_command_position++; + + if (check_redir (ti) == 1) + in_command_position = 0; + } + else + { + /* This still could be in command position. It is possible + that all of the previous words on the line are variable + assignments. */ + } + + /* Check that we haven't incorrectly flagged a closed command substitution + as indicating we're in a command position. */ + if (in_command_position && ti >= 0 && rl_line_buffer[ti] == '`' && + *text != '`' && unclosed_pair (rl_line_buffer, end, "`") == 0) + in_command_position = 0; + + /* Special handling for command substitution. If *TEXT is a backquote, + it can be the start or end of an old-style command substitution, or + unmatched. If it's unmatched, both calls to unclosed_pair will + succeed. */ + if (*text == '`' && + (in_command_position || (unclosed_pair (rl_line_buffer, start, "`") && + unclosed_pair (rl_line_buffer, end, "`")))) + matches = rl_completion_matches (text, command_subst_completion_function); + +#if defined (PROGRAMMABLE_COMPLETION) + /* Attempt programmable completion. */ + if (!matches && in_command_position == 0 && prog_completion_enabled && + (progcomp_size () > 0) && current_prompt_string == ps1_prompt) + { + int s, e, foundcs; + char *n; + + /* XXX - don't free the members */ + if (prog_complete_matches) + free (prog_complete_matches); + prog_complete_matches = (char **)NULL; + + s = find_cmd_start (start); + e = find_cmd_end (end); + n = find_cmd_name (s); + if (e > s) + prog_complete_matches = programmable_completions (n, text, s, e, &foundcs); + else + foundcs = 0; + FREE (n); + /* XXX - if we found a COMPSPEC for the command, just return whatever + the programmable completion code returns, and disable the default + filename completion that readline will do unless the COPT_DEFAULT + option has been set with the `-o default' option to complete. */ + if (foundcs) + { + /* If the user specified that the compspec returns filenames, make + sure that readline knows it. */ + if (foundcs & COPT_FILENAMES) + rl_filename_completion_desired = 1; + /* If the user doesn't want a space appended, tell readline. */ + if (foundcs & COPT_NOSPACE) + rl_completion_suppress_append = 1; + /* Turn what the programmable completion code returns into what + readline wants. I should have made compute_lcd_of_matches + external... */ + matches = rl_completion_matches (text, prog_complete_return); + if ((foundcs & COPT_DEFAULT) == 0) + rl_attempted_completion_over = 1; /* no default */ + if (matches || ((foundcs & COPT_BASHDEFAULT) == 0)) + return (matches); + } + } +#endif + + if (matches == 0) + matches = bash_default_completion (text, start, end, qc, in_command_position); + + return matches; +} + +char ** +bash_default_completion (text, start, end, qc, in_command_position) + const char *text; + int start, end, qc, in_command_position; +{ + char **matches; + + matches = (char **)NULL; + + /* New posix-style command substitution or variable name? */ + if (!matches && *text == '$') + { + if (qc != '\'' && text[1] == '(') /* ) */ + matches = rl_completion_matches (text, command_subst_completion_function); + else + matches = rl_completion_matches (text, variable_completion_function); + } + + /* If the word starts in `~', and there is no slash in the word, then + try completing this word as a username. */ + if (!matches && *text == '~' && !xstrchr (text, '/')) + matches = rl_completion_matches (text, rl_username_completion_function); + + /* Another one. Why not? If the word starts in '@', then look through + the world of known hostnames for completion first. */ + if (!matches && perform_hostname_completion && *text == '@') + matches = rl_completion_matches (text, hostname_completion_function); + + /* And last, (but not least) if this word is in a command position, then + complete over possible command names, including aliases, functions, + and command names. */ + if (!matches && in_command_position) + { + if (start == 0 && end == 0 && text[0] == '\0' && no_empty_command_completion) + { + matches = (char **)NULL; + rl_ignore_some_completions_function = bash_ignore_everything; + } + else + { +#define CMD_IS_DIR(x) (absolute_pathname(x) == 0 && absolute_program(x) == 0 && *(x) != '~' && test_for_directory (x)) + + matches = rl_completion_matches (text, command_word_completion_function); + + /* If we are attempting command completion and nothing matches, we + do not want readline to perform filename completion for us. We + still want to be able to complete partial pathnames, so set the + completion ignore function to something which will remove + filenames and leave directories in the match list. */ + if (matches == (char **)NULL) + rl_ignore_some_completions_function = bash_ignore_filenames; +#if 0 + else if (matches[1] == 0 && CMD_IS_DIR(matches[0])) + /* Turn off rl_filename_completion_desired so readline doesn't + append a slash if there is a directory with the same name + in the current directory, or other filename-specific things. + If the name begins with a slash, we're either completing a + full pathname or a directory pathname, and readline won't be + looking in the current directory anyway, so there's no + conflict. */ + rl_filename_completion_desired = 0; + else if (matches[0] && matches[1] && STREQ (matches[0], matches[1]) && CMD_IS_DIR (matches[0])) + /* There are multiple instances of the same match (duplicate + completions haven't yet been removed). In this case, all of + the matches will be the same, and the duplicate removal code + will distill them all down to one. We turn off + rl_filename_completion_desired for the same reason as above. + Remember: we only care if there's eventually a single unique + completion. If there are multiple completions this won't + make a difference and the problem won't occur. */ + rl_filename_completion_desired = 0; +#endif + } + } + + /* This could be a globbing pattern, so try to expand it using pathname + expansion. */ + if (!matches && glob_pattern_p (text)) + { + matches = rl_completion_matches (text, glob_complete_word); + /* A glob expression that matches more than one filename is problematic. + If we match more than one filename, punt. */ + if (matches && matches[1] && rl_completion_type == TAB) + { + strvec_dispose (matches); + matches = (char **)0; + } + } + + return (matches); +} + +/* This is the function to call when the word to complete is in a position + where a command word can be found. It grovels $PATH, looking for commands + that match. It also scans aliases, function names, and the shell_builtin + table. */ +char * +command_word_completion_function (hint_text, state) + const char *hint_text; + int state; +{ + static char *hint = (char *)NULL; + static char *path = (char *)NULL; + static char *val = (char *)NULL; + static char *filename_hint = (char *)NULL; + static int path_index, hint_len, istate; + static int mapping_over, local_index; + static SHELL_VAR **varlist = (SHELL_VAR **)NULL; +#if defined (ALIAS) + static alias_t **alias_list = (alias_t **)NULL; +#endif /* ALIAS */ + + /* We have to map over the possibilities for command words. If we have + no state, then make one just for that purpose. */ + if (!state) + { + if (hint) + free (hint); + + mapping_over = 0; + val = (char *)NULL; + + /* If this is an absolute program name, do not check it against + aliases, reserved words, functions or builtins. We must check + whether or not it is unique, and, if so, whether that filename + is executable. */ + if (absolute_program (hint_text)) + { + /* Perform tilde expansion on what's passed, so we don't end up + passing filenames with tildes directly to stat(). */ + if (*hint_text == '~') + hint = bash_tilde_expand (hint_text, 0); + else + hint = savestring (hint_text); + hint_len = strlen (hint); + + if (filename_hint) + free (filename_hint); + filename_hint = savestring (hint); + + mapping_over = 4; + istate = 0; + goto inner; + } + + hint = savestring (hint_text); + hint_len = strlen (hint); + + path = get_string_value ("PATH"); + path_index = 0; + + /* Initialize the variables for each type of command word. */ + local_index = 0; + + if (varlist) + free (varlist); + + varlist = all_visible_functions (); + +#if defined (ALIAS) + if (alias_list) + free (alias_list); + + alias_list = all_aliases (); +#endif /* ALIAS */ + } + + /* mapping_over says what we are currently hacking. Note that every case + in this list must fall through when there are no more possibilities. */ + + switch (mapping_over) + { + case 0: /* Aliases come first. */ +#if defined (ALIAS) + while (alias_list && alias_list[local_index]) + { + register char *alias; + + alias = alias_list[local_index++]->name; + + if (STREQN (alias, hint, hint_len)) + return (savestring (alias)); + } +#endif /* ALIAS */ + local_index = 0; + mapping_over++; + + case 1: /* Then shell reserved words. */ + { + while (word_token_alist[local_index].word) + { + register char *reserved_word; + + reserved_word = word_token_alist[local_index++].word; + + if (STREQN (reserved_word, hint, hint_len)) + return (savestring (reserved_word)); + } + local_index = 0; + mapping_over++; + } + + case 2: /* Then function names. */ + while (varlist && varlist[local_index]) + { + register char *varname; + + varname = varlist[local_index++]->name; + + if (STREQN (varname, hint, hint_len)) + return (savestring (varname)); + } + local_index = 0; + mapping_over++; + + case 3: /* Then shell builtins. */ + for (; local_index < num_shell_builtins; local_index++) + { + /* Ignore it if it doesn't have a function pointer or if it + is not currently enabled. */ + if (!shell_builtins[local_index].function || + (shell_builtins[local_index].flags & BUILTIN_ENABLED) == 0) + continue; + + if (STREQN (shell_builtins[local_index].name, hint, hint_len)) + { + int i = local_index++; + + return (savestring (shell_builtins[i].name)); + } + } + local_index = 0; + mapping_over++; + } + + /* Repeatedly call filename_completion_function while we have + members of PATH left. Question: should we stat each file? + Answer: we call executable_file () on each file. */ + outer: + + istate = (val != (char *)NULL); + + if (!istate) + { + char *current_path; + + /* Get the next directory from the path. If there is none, then we + are all done. */ + if (!path || !path[path_index] || + (current_path = extract_colon_unit (path, &path_index)) == 0) + return ((char *)NULL); + + if (*current_path == 0) + { + free (current_path); + current_path = savestring ("."); + } + + if (*current_path == '~') + { + char *t; + + t = bash_tilde_expand (current_path, 0); + free (current_path); + current_path = t; + } + + if (filename_hint) + free (filename_hint); + + filename_hint = sh_makepath (current_path, hint, 0); + + free (current_path); + } + + inner: + val = rl_filename_completion_function (filename_hint, istate); + istate = 1; + + if (val == 0) + { + /* If the hint text is an absolute program, then don't bother + searching through PATH. */ + if (absolute_program (hint)) + return ((char *)NULL); + + goto outer; + } + else + { + int match, freetemp; + char *temp; + + if (absolute_program (hint)) + { + match = strncmp (val, hint, hint_len) == 0; + /* If we performed tilde expansion, restore the original + filename. */ + if (*hint_text == '~') + { + int l, tl, vl; + vl = strlen (val); + tl = strlen (hint_text); + l = vl - hint_len; /* # of chars added */ + temp = (char *)xmalloc (l + 2 + tl); + strcpy (temp, hint_text); + strcpy (temp + tl, val + vl - l); + } + else + temp = savestring (val); + freetemp = 1; + } + else + { + temp = strrchr (val, '/'); + + if (temp) + { + temp++; + freetemp = match = strncmp (temp, hint, hint_len) == 0; + if (match) + temp = savestring (temp); + } + else + freetemp = match = 0; + } + + /* If we have found a match, and it is an executable file or a + directory name, return it. */ + if (match && executable_or_directory (val)) + { + free (val); + val = ""; /* So it won't be NULL. */ + return (temp); + } + else + { + if (freetemp) + free (temp); + free (val); + goto inner; + } + } +} + +/* Completion inside an unterminated command substitution. */ +static char * +command_subst_completion_function (text, state) + const char *text; + int state; +{ + static char **matches = (char **)NULL; + static const char *orig_start; + static char *filename_text = (char *)NULL; + static int cmd_index, start_len; + char *value; + + if (state == 0) + { + if (filename_text) + free (filename_text); + orig_start = text; + if (*text == '`') + text++; + else if (*text == '$' && text[1] == '(') /* ) */ + text += 2; + /* If the text was quoted, suppress any quote character that the + readline completion code would insert. */ + rl_completion_suppress_quote = 1; + start_len = text - orig_start; + filename_text = savestring (text); + if (matches) + free (matches); + + /* + * At this point we can entertain the idea of re-parsing + * `filename_text' into a (possibly incomplete) command name and + * arguments, and doing completion based on that. This is + * currently very rudimentary, but it is a small improvement. + */ + for (value = filename_text + strlen (filename_text) - 1; value > filename_text; value--) + if (whitespace (*value) || member (*value, COMMAND_SEPARATORS)) + break; + if (value <= filename_text) + matches = rl_completion_matches (filename_text, command_word_completion_function); + else + { + value++; + start_len += value - filename_text; + if (whitespace (value[-1])) + matches = rl_completion_matches (value, rl_filename_completion_function); + else + matches = rl_completion_matches (value, command_word_completion_function); + } + + /* If there is more than one match, rl_completion_matches has already + put the lcd in matches[0]. Skip over it. */ + cmd_index = matches && matches[0] && matches[1]; + } + + if (!matches || !matches[cmd_index]) + { + rl_filename_quoting_desired = 0; /* disable quoting */ + return ((char *)NULL); + } + else + { + value = (char *)xmalloc (1 + start_len + strlen (matches[cmd_index])); + + if (start_len == 1) + value[0] = *orig_start; + else + strncpy (value, orig_start, start_len); + + strcpy (value + start_len, matches[cmd_index]); + + cmd_index++; + return (value); + } +} + +/* Okay, now we write the entry_function for variable completion. */ +static char * +variable_completion_function (text, state) + const char *text; + int state; +{ + static char **varlist = (char **)NULL; + static int varlist_index; + static char *varname = (char *)NULL; + static int namelen; + static int first_char, first_char_loc; + + if (!state) + { + if (varname) + free (varname); + + first_char_loc = 0; + first_char = text[0]; + + if (first_char == '$') + first_char_loc++; + + if (text[first_char_loc] == '{') + first_char_loc++; + + varname = savestring (text + first_char_loc); + + namelen = strlen (varname); + if (varlist) + strvec_dispose (varlist); + + varlist = all_variables_matching_prefix (varname); + varlist_index = 0; + } + + if (!varlist || !varlist[varlist_index]) + { + return ((char *)NULL); + } + else + { + char *value; + + value = (char *)xmalloc (4 + strlen (varlist[varlist_index])); + + if (first_char_loc) + { + value[0] = first_char; + if (first_char_loc == 2) + value[1] = '{'; + } + + strcpy (value + first_char_loc, varlist[varlist_index]); + if (first_char_loc == 2) + strcat (value, "}"); + + varlist_index++; + return (value); + } +} + +/* How about a completion function for hostnames? */ +static char * +hostname_completion_function (text, state) + const char *text; + int state; +{ + static char **list = (char **)NULL; + static int list_index = 0; + static int first_char, first_char_loc; + + /* If we don't have any state, make some. */ + if (state == 0) + { + FREE (list); + + list = (char **)NULL; + + first_char_loc = 0; + first_char = *text; + + if (first_char == '@') + first_char_loc++; + + list = hostnames_matching ((char *)text+first_char_loc); + list_index = 0; + } + + if (list && list[list_index]) + { + char *t; + + t = (char *)xmalloc (2 + strlen (list[list_index])); + *t = first_char; + strcpy (t + first_char_loc, list[list_index]); + list_index++; + return (t); + } + + return ((char *)NULL); +} + +/* + * A completion function for service names from /etc/services (or wherever). + */ +char * +bash_servicename_completion_function (text, state) + const char *text; + int state; +{ +#if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GETSERVENT) + return ((char *)NULL); +#else + static char *sname = (char *)NULL; + static struct servent *srvent; + static int snamelen, firstc; + char *value; + char **alist, *aentry; + int afound; + + if (state == 0) + { + FREE (sname); + firstc = *text; + + sname = savestring (text); + snamelen = strlen (sname); + setservent (0); + } + + while (srvent = getservent ()) + { + afound = 0; + if (snamelen == 0 || (STREQN (sname, srvent->s_name, snamelen))) + break; + /* Not primary, check aliases */ + for (alist = srvent->s_aliases; aentry = *alist; alist++) + { + if (STREQN (sname, aentry, snamelen)) + { + afound = 1; + break; + } + } + + if (afound) + break; + } + + if (srvent == 0) + { + endservent (); + return ((char *)NULL); + } + + value = afound ? savestring (aentry) : savestring (srvent->s_name); + return value; +#endif +} + +/* + * A completion function for group names from /etc/group (or wherever). + */ +char * +bash_groupname_completion_function (text, state) + const char *text; + int state; +{ +#if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GRP_H) + return ((char *)NULL); +#else + static char *gname = (char *)NULL; + static struct group *grent; + static int gnamelen; + char *value; + + if (state == 0) + { + FREE (gname); + gname = savestring (text); + gnamelen = strlen (gname); + + setgrent (); + } + + while (grent = getgrent ()) + { + if (gnamelen == 0 || (STREQN (gname, grent->gr_name, gnamelen))) + break; + } + + if (grent == 0) + { + endgrent (); + return ((char *)NULL); + } + + value = savestring (grent->gr_name); + return (value); +#endif +} + +/* Functions to perform history and alias expansions on the current line. */ + +#if defined (BANG_HISTORY) +/* Perform history expansion on the current line. If no history expansion + is done, pre_process_line() returns what it was passed, so we need to + allocate a new line here. */ +static char * +history_expand_line_internal (line) + char *line; +{ + char *new_line; + int old_verify; + + old_verify = hist_verify; + hist_verify = 0; + new_line = pre_process_line (line, 0, 0); + hist_verify = old_verify; + + return (new_line == line) ? savestring (line) : new_line; +} +#endif + +/* There was an error in expansion. Let the preprocessor print + the error here. */ +static void +cleanup_expansion_error () +{ + char *to_free; +#if defined (BANG_HISTORY) + int old_verify; + + old_verify = hist_verify; + hist_verify = 0; +#endif + + fprintf (rl_outstream, "\r\n"); + to_free = pre_process_line (rl_line_buffer, 1, 0); +#if defined (BANG_HISTORY) + hist_verify = old_verify; +#endif + if (to_free != rl_line_buffer) + FREE (to_free); + putc ('\r', rl_outstream); + rl_forced_update_display (); +} + +/* If NEW_LINE differs from what is in the readline line buffer, add an + undo record to get from the readline line buffer contents to the new + line and make NEW_LINE the current readline line. */ +static void +maybe_make_readline_line (new_line) + char *new_line; +{ + if (strcmp (new_line, rl_line_buffer) != 0) + { + rl_point = rl_end; + + rl_add_undo (UNDO_BEGIN, 0, 0, 0); + rl_delete_text (0, rl_point); + rl_point = rl_end = rl_mark = 0; + rl_insert_text (new_line); + rl_add_undo (UNDO_END, 0, 0, 0); + } +} + +/* Make NEW_LINE be the current readline line. This frees NEW_LINE. */ +static void +set_up_new_line (new_line) + char *new_line; +{ + int old_point, at_end; + + old_point = rl_point; + at_end = rl_point == rl_end; + + /* If the line was history and alias expanded, then make that + be one thing to undo. */ + maybe_make_readline_line (new_line); + free (new_line); + + /* Place rl_point where we think it should go. */ + if (at_end) + rl_point = rl_end; + else if (old_point < rl_end) + { + rl_point = old_point; + if (!whitespace (rl_line_buffer[rl_point])) + rl_forward_word (1, 0); + } +} + +#if defined (ALIAS) +/* Expand aliases in the current readline line. */ +static int +alias_expand_line (count, ignore) + int count, ignore; +{ + char *new_line; + + new_line = alias_expand (rl_line_buffer); + + if (new_line) + { + set_up_new_line (new_line); + return (0); + } + else + { + cleanup_expansion_error (); + return (1); + } +} +#endif + +#if defined (BANG_HISTORY) +/* History expand the line. */ +static int +history_expand_line (count, ignore) + int count, ignore; +{ + char *new_line; + + new_line = history_expand_line_internal (rl_line_buffer); + + if (new_line) + { + set_up_new_line (new_line); + return (0); + } + else + { + cleanup_expansion_error (); + return (1); + } +} + +/* Expand history substitutions in the current line and then insert a + space (hopefully close to where we were before). */ +static int +tcsh_magic_space (count, ignore) + int count, ignore; +{ + int dist_from_end, old_point; + + old_point = rl_point; + dist_from_end = rl_end - rl_point; + if (history_expand_line (count, ignore) == 0) + { + /* Try a simple heuristic from Stephen Gildea . + This works if all expansions were before rl_point or if no expansions + were performed. */ + rl_point = (old_point == 0) ? old_point : rl_end - dist_from_end; + rl_insert (1, ' '); + return (0); + } + else + return (1); +} +#endif + +/* History and alias expand the line. */ +static int +history_and_alias_expand_line (count, ignore) + int count, ignore; +{ + char *new_line; + + new_line = history_expand_line_internal (rl_line_buffer); + +#if defined (ALIAS) + if (new_line) + { + char *alias_line; + + alias_line = alias_expand (new_line); + free (new_line); + new_line = alias_line; + } +#endif /* ALIAS */ + + if (new_line) + { + set_up_new_line (new_line); + return (0); + } + else + { + cleanup_expansion_error (); + return (1); + } +} + +/* History and alias expand the line, then perform the shell word + expansions by calling expand_string. This can't use set_up_new_line() + because we want the variable expansions as a separate undo'able + set of operations. */ +static int +shell_expand_line (count, ignore) + int count, ignore; +{ + char *new_line; + WORD_LIST *expanded_string; + + new_line = history_expand_line_internal (rl_line_buffer); + +#if defined (ALIAS) + if (new_line) + { + char *alias_line; + + alias_line = alias_expand (new_line); + free (new_line); + new_line = alias_line; + } +#endif /* ALIAS */ + + if (new_line) + { + int old_point = rl_point; + int at_end = rl_point == rl_end; + + /* If the line was history and alias expanded, then make that + be one thing to undo. */ + maybe_make_readline_line (new_line); + free (new_line); + + /* If there is variable expansion to perform, do that as a separate + operation to be undone. */ + new_line = savestring (rl_line_buffer); + expanded_string = expand_string (new_line, 0); + FREE (new_line); + if (expanded_string == 0) + { + new_line = (char *)xmalloc (1); + new_line[0] = '\0'; + } + else + { + new_line = string_list (expanded_string); + dispose_words (expanded_string); + } + + maybe_make_readline_line (new_line); + free (new_line); + + /* Place rl_point where we think it should go. */ + if (at_end) + rl_point = rl_end; + else if (old_point < rl_end) + { + rl_point = old_point; + if (!whitespace (rl_line_buffer[rl_point])) + rl_forward_word (1, 0); + } + return 0; + } + else + { + cleanup_expansion_error (); + return 1; + } +} + +/* If FIGNORE is set, then don't match files with the given suffixes when + completing filenames. If only one of the possibilities has an acceptable + suffix, delete the others, else just return and let the completer + signal an error. It is called by the completer when real + completions are done on filenames by the completer's internal + function, not for completion lists (M-?) and not on "other" + completion types, such as hostnames or commands. */ + +static struct ignorevar fignore = +{ + "FIGNORE", + (struct ign *)0, + 0, + (char *)0, + (sh_iv_item_func_t *) 0, +}; + +static void +_ignore_completion_names (names, name_func) + char **names; + sh_ignore_func_t *name_func; +{ + char **newnames; + int idx, nidx; + char **oldnames; + int oidx; + + /* If there is only one completion, see if it is acceptable. If it is + not, free it up. In any case, short-circuit and return. This is a + special case because names[0] is not the prefix of the list of names + if there is only one completion; it is the completion itself. */ + if (names[1] == (char *)0) + { + if (force_fignore) + if ((*name_func) (names[0]) == 0) + { + free (names[0]); + names[0] = (char *)NULL; + } + + return; + } + + /* Allocate space for array to hold list of pointers to matching + filenames. The pointers are copied back to NAMES when done. */ + for (nidx = 1; names[nidx]; nidx++) + ; + newnames = strvec_create (nidx + 1); + + if (force_fignore == 0) + { + oldnames = strvec_create (nidx - 1); + oidx = 0; + } + + newnames[0] = names[0]; + for (idx = nidx = 1; names[idx]; idx++) + { + if ((*name_func) (names[idx])) + newnames[nidx++] = names[idx]; + else if (force_fignore == 0) + oldnames[oidx++] = names[idx]; + else + free (names[idx]); + } + + newnames[nidx] = (char *)NULL; + + /* If none are acceptable then let the completer handle it. */ + if (nidx == 1) + { + if (force_fignore) + { + free (names[0]); + names[0] = (char *)NULL; + } + else + free (oldnames); + + free (newnames); + return; + } + + if (force_fignore == 0) + { + while (oidx) + free (oldnames[--oidx]); + free (oldnames); + } + + /* If only one is acceptable, copy it to names[0] and return. */ + if (nidx == 2) + { + free (names[0]); + names[0] = newnames[1]; + names[1] = (char *)NULL; + free (newnames); + return; + } + + /* Copy the acceptable names back to NAMES, set the new array end, + and return. */ + for (nidx = 1; newnames[nidx]; nidx++) + names[nidx] = newnames[nidx]; + names[nidx] = (char *)NULL; + free (newnames); +} + +static int +name_is_acceptable (name) + const char *name; +{ + struct ign *p; + int nlen; + + for (nlen = strlen (name), p = fignore.ignores; p->val; p++) + { + if (nlen > p->len && p->len > 0 && STREQ (p->val, &name[nlen - p->len])) + return (0); + } + + return (1); +} + +#if 0 +static int +ignore_dot_names (name) + char *name; +{ + return (name[0] != '.'); +} +#endif + +static int +filename_completion_ignore (names) + char **names; +{ +#if 0 + if (glob_dot_filenames == 0) + _ignore_completion_names (names, ignore_dot_names); +#endif + + setup_ignore_patterns (&fignore); + + if (fignore.num_ignores == 0) + return 0; + + _ignore_completion_names (names, name_is_acceptable); + + return 0; +} + +/* Return 1 if NAME is a directory. */ +static int +test_for_directory (name) + const char *name; +{ + struct stat finfo; + char *fn; + + fn = bash_tilde_expand (name, 0); + if (stat (fn, &finfo) != 0) + { + free (fn); + return 0; + } + free (fn); + return (S_ISDIR (finfo.st_mode)); +} + +/* Remove files from NAMES, leaving directories. */ +static int +bash_ignore_filenames (names) + char **names; +{ + _ignore_completion_names (names, test_for_directory); + return 0; +} + +static int +return_zero (name) + const char *name; +{ + return 0; +} + +static int +bash_ignore_everything (names) + char **names; +{ + _ignore_completion_names (names, return_zero); + return 0; +} + +/* Handle symbolic link references and other directory name + expansions while hacking completion. */ +static int +bash_directory_completion_hook (dirname) + char **dirname; +{ + char *local_dirname, *new_dirname, *t; + int return_value, should_expand_dirname; + WORD_LIST *wl; + struct stat sb; + + return_value = should_expand_dirname = 0; + local_dirname = *dirname; + +#if 0 + should_expand_dirname = xstrchr (local_dirname, '$') || xstrchr (local_dirname, '`'); +#else + if (xstrchr (local_dirname, '$')) + should_expand_dirname = 1; + else + { + t = xstrchr (local_dirname, '`'); + if (t && unclosed_pair (local_dirname, strlen (local_dirname), "`") == 0) + should_expand_dirname = 1; + } +#endif + +#if defined (HAVE_LSTAT) + if (should_expand_dirname && lstat (local_dirname, &sb) == 0) +#else + if (should_expand_dirname && stat (local_dirname, &sb) == 0) +#endif + should_expand_dirname = 0; + + if (should_expand_dirname) + { + new_dirname = savestring (local_dirname); + wl = expand_prompt_string (new_dirname, 0); /* does the right thing */ + if (wl) + { + *dirname = string_list (wl); + /* Tell the completer to replace the directory name only if we + actually expanded something. */ + return_value = STREQ (local_dirname, *dirname) == 0; + free (local_dirname); + free (new_dirname); + dispose_words (wl); + local_dirname = *dirname; + } + else + { + free (new_dirname); + free (local_dirname); + *dirname = (char *)xmalloc (1); + **dirname = '\0'; + return 1; + } + } + + if (!no_symbolic_links && (local_dirname[0] != '.' || local_dirname[1])) + { + char *temp1, *temp2; + int len1, len2; + + t = get_working_directory ("symlink-hook"); + temp1 = make_absolute (local_dirname, t); + free (t); + temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + /* If we can't canonicalize, bail. */ + if (temp2 == 0) + { + free (temp1); + return 1; + } + len1 = strlen (temp1); + if (temp1[len1 - 1] == '/') + { + len2 = strlen (temp2); + temp2 = (char *)xrealloc (temp2, len2 + 2); + temp2[len2] = '/'; + temp2[len2 + 1] = '\0'; + } + free (local_dirname); + *dirname = temp2; + free (temp1); + } + return (return_value); +} + +static char **history_completion_array = (char **)NULL; +static int harry_size; +static int harry_len; + +static void +build_history_completion_array () +{ + register int i, j; + HIST_ENTRY **hlist; + char **tokens; + + /* First, clear out the current dynamic history completion list. */ + if (harry_size) + { + strvec_dispose (history_completion_array); + history_completion_array = (char **)NULL; + harry_size = 0; + harry_len = 0; + } + + /* Next, grovel each line of history, making each shell-sized token + a separate entry in the history_completion_array. */ + hlist = history_list (); + + if (hlist) + { + for (i = 0; hlist[i]; i++) + { + /* Separate each token, and place into an array. */ + tokens = history_tokenize (hlist[i]->line); + + for (j = 0; tokens && tokens[j]; j++) + { + if (harry_len + 2 > harry_size) + history_completion_array = strvec_resize (history_completion_array, harry_size += 10); + + history_completion_array[harry_len++] = tokens[j]; + history_completion_array[harry_len] = (char *)NULL; + } + free (tokens); + } + + /* Sort the complete list of tokens. */ + qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)strvec_strcmp); + } +} + +static char * +history_completion_generator (hint_text, state) + const char *hint_text; + int state; +{ + static int local_index, len; + static const char *text; + + /* If this is the first call to the generator, then initialize the + list of strings to complete over. */ + if (state == 0) + { + local_index = 0; + build_history_completion_array (); + text = hint_text; + len = strlen (text); + } + + while (history_completion_array && history_completion_array[local_index]) + { + if (strncmp (text, history_completion_array[local_index++], len) == 0) + return (savestring (history_completion_array[local_index - 1])); + } + return ((char *)NULL); +} + +static int +dynamic_complete_history (count, key) + int count, key; +{ + int r; + + rl_compentry_func_t *orig_func; + rl_completion_func_t *orig_attempt_func; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + rl_completion_entry_function = history_completion_generator; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + + /* XXX - use rl_completion_mode here? */ + if (rl_last_func == dynamic_complete_history) + r = rl_complete_internal ('?'); + else + r = rl_complete_internal (TAB); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + return r; +} + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) +static int +bash_complete_username (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_username_internal (rl_completion_mode (bash_complete_username)); +} + +static int +bash_possible_username_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_username_internal ('?'); +} + +static int +bash_complete_username_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, rl_username_completion_function); +} + +static int +bash_complete_filename (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_filename_internal (rl_completion_mode (bash_complete_filename)); +} + +static int +bash_possible_filename_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_filename_internal ('?'); +} + +static int +bash_complete_filename_internal (what_to_do) + int what_to_do; +{ + rl_compentry_func_t *orig_func; + rl_completion_func_t *orig_attempt_func; + rl_icppfunc_t *orig_dir_func; + /*const*/ char *orig_rl_completer_word_break_characters; + int r; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + orig_dir_func = rl_directory_completion_hook; + orig_rl_completer_word_break_characters = rl_completer_word_break_characters; + rl_completion_entry_function = rl_filename_completion_function; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + rl_directory_completion_hook = (rl_icppfunc_t *)NULL; + rl_completer_word_break_characters = " \t\n\"\'"; + + r = rl_complete_internal (what_to_do); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + rl_directory_completion_hook = orig_dir_func; + rl_completer_word_break_characters = orig_rl_completer_word_break_characters; + + return r; +} + +static int +bash_complete_hostname (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_hostname_internal (rl_completion_mode (bash_complete_hostname)); +} + +static int +bash_possible_hostname_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_hostname_internal ('?'); +} + +static int +bash_complete_variable (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_variable_internal (rl_completion_mode (bash_complete_variable)); +} + +static int +bash_possible_variable_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_variable_internal ('?'); +} + +static int +bash_complete_command (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_command_internal (rl_completion_mode (bash_complete_command)); +} + +static int +bash_possible_command_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_command_internal ('?'); +} + +static int +bash_complete_hostname_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, hostname_completion_function); +} + +static int +bash_complete_variable_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, variable_completion_function); +} + +static int +bash_complete_command_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, command_word_completion_function); +} + +static char *globtext; +static char *globorig; + +static char * +glob_complete_word (text, state) + const char *text; + int state; +{ + static char **matches = (char **)NULL; + static int ind; + int glen; + char *ret; + + if (state == 0) + { + rl_filename_completion_desired = 1; + FREE (matches); + if (globorig != globtext) + FREE (globorig); + FREE (globtext); + + if (rl_explicit_arg) + { + globorig = savestring (text); + glen = strlen (text); + globtext = (char *)xmalloc (glen + 2); + strcpy (globtext, text); + globtext[glen] = '*'; + globtext[glen+1] = '\0'; + } + else + globtext = globorig = savestring (text); + + matches = shell_glob_filename (globtext); + if (GLOB_FAILED (matches)) + matches = (char **)NULL; + ind = 0; + } + + ret = matches ? matches[ind] : (char *)NULL; + ind++; + return ret; +} + +static int +bash_glob_completion_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, glob_complete_word); +} + +/* A special quoting function so we don't end up quoting globbing characters + in the word if there are no matches or multiple matches. */ +static char * +bash_glob_quote_filename (s, rtype, qcp) + char *s; + int rtype; + char *qcp; +{ + if (globorig && qcp && *qcp == '\0' && STREQ (s, globorig)) + return (savestring (s)); + else + return (bash_quote_filename (s, rtype, qcp)); +} + +static int +bash_glob_complete_word (count, key) + int count, key; +{ + int r; + rl_quote_func_t *orig_quoting_function; + + if (rl_editing_mode == EMACS_EDITING_MODE) + rl_explicit_arg = 1; /* force `*' append */ + orig_quoting_function = rl_filename_quoting_function; + rl_filename_quoting_function = bash_glob_quote_filename; + + r = bash_glob_completion_internal (rl_completion_mode (bash_glob_complete_word)); + + rl_filename_quoting_function = orig_quoting_function; + return r; +} + +static int +bash_glob_expand_word (count, key) + int count, key; +{ + return bash_glob_completion_internal ('*'); +} + +static int +bash_glob_list_expansions (count, key) + int count, key; +{ + return bash_glob_completion_internal ('?'); +} + +static int +bash_specific_completion (what_to_do, generator) + int what_to_do; + rl_compentry_func_t *generator; +{ + rl_compentry_func_t *orig_func; + rl_completion_func_t *orig_attempt_func; + int r; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + rl_completion_entry_function = generator; + rl_attempted_completion_function = NULL; + + r = rl_complete_internal (what_to_do); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + + return r; +} + +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ + +#if defined (VI_MODE) +/* Completion, from vi mode's point of view. This is a modified version of + rl_vi_complete which uses the bash globbing code to implement what POSIX + specifies, which is to append a `*' and attempt filename generation (which + has the side effect of expanding any globbing characters in the word). */ +static int +bash_vi_complete (count, key) + int count, key; +{ +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) + int p, r; + char *t; + + if ((rl_point < rl_end) && (!whitespace (rl_line_buffer[rl_point]))) + { + if (!whitespace (rl_line_buffer[rl_point + 1])) + rl_vi_end_word (1, 'E'); + rl_point++; + } + + /* Find boundaries of current word, according to vi definition of a + `bigword'. */ + t = 0; + if (rl_point > 0) + { + p = rl_point; + rl_vi_bWord (1, 'B'); + r = rl_point; + rl_point = p; + p = r; + + t = substring (rl_line_buffer, p, rl_point); + } + + if (t && glob_pattern_p (t) == 0) + rl_explicit_arg = 1; /* XXX - force glob_complete_word to append `*' */ + FREE (t); + + if (key == '*') /* Expansion and replacement. */ + r = bash_glob_expand_word (count, key); + else if (key == '=') /* List possible completions. */ + r = bash_glob_list_expansions (count, key); + else if (key == '\\') /* Standard completion */ + r = bash_glob_complete_word (count, key); + else + r = rl_complete (0, key); + + if (key == '*' || key == '\\') + rl_vi_start_inserting (key, 1, 1); + + return (r); +#else + return rl_vi_complete (count, key); +#endif /* !SPECIFIC_COMPLETION_FUNCTIONS */ +} +#endif /* VI_MODE */ + +/* Filename quoting for completion. */ +/* A function to strip unquoted quote characters (single quotes, double + quotes, and backslashes). It allows single quotes to appear + within double quotes, and vice versa. It should be smarter. */ +static char * +bash_dequote_filename (text, quote_char) + char *text; + int quote_char; +{ + char *ret, *p, *r; + int l, quoted; + + l = strlen (text); + ret = (char *)xmalloc (l + 1); + for (quoted = quote_char, p = text, r = ret; p && *p; p++) + { + /* Allow backslash-quoted characters to pass through unscathed. */ + if (*p == '\\') + { + *r++ = *++p; + if (*p == '\0') + break; + continue; + } + /* Close quote. */ + if (quoted && *p == quoted) + { + quoted = 0; + continue; + } + /* Open quote. */ + if (quoted == 0 && (*p == '\'' || *p == '"')) + { + quoted = *p; + continue; + } + *r++ = *p; + } + *r = '\0'; + return ret; +} + +/* Quote characters that the readline completion code would treat as + word break characters with backslashes. Pass backslash-quoted + characters through without examination. */ +static char * +quote_word_break_chars (text) + char *text; +{ + char *ret, *r, *s; + int l; + + l = strlen (text); + ret = (char *)xmalloc ((2 * l) + 1); + for (s = text, r = ret; *s; s++) + { + /* Pass backslash-quoted characters through, including the backslash. */ + if (*s == '\\') + { + *r++ = '\\'; + *r++ = *++s; + if (*s == '\0') + break; + continue; + } + /* OK, we have an unquoted character. Check its presence in + rl_completer_word_break_characters. */ + if (xstrchr (rl_completer_word_break_characters, *s)) + *r++ = '\\'; + *r++ = *s; + } + *r = '\0'; + return ret; +} + +/* Quote a filename using double quotes, single quotes, or backslashes + depending on the value of completion_quoting_style. If we're + completing using backslashes, we need to quote some additional + characters (those that readline treats as word breaks), so we call + quote_word_break_chars on the result. This returns newly-allocated + memory. */ +static char * +bash_quote_filename (s, rtype, qcp) + char *s; + int rtype; + char *qcp; +{ + char *rtext, *mtext, *ret; + int rlen, cs; + + rtext = (char *)NULL; + + /* If RTYPE == MULT_MATCH, it means that there is + more than one match. In this case, we do not add + the closing quote or attempt to perform tilde + expansion. If RTYPE == SINGLE_MATCH, we try + to perform tilde expansion, because single and double + quotes inhibit tilde expansion by the shell. */ + + mtext = s; + if (mtext[0] == '~' && rtype == SINGLE_MATCH) + mtext = bash_tilde_expand (s, 0); + + cs = completion_quoting_style; + /* Might need to modify the default completion style based on *qcp, + since it's set to any user-provided opening quote. We also change + to single-quoting if there is no user-provided opening quote and + the word being completed contains newlines, since those are not + quoted correctly using backslashes (a backslash-newline pair is + special to the shell parser). */ + if (*qcp == '\0' && cs == COMPLETE_BSQUOTE && xstrchr (mtext, '\n')) + cs = COMPLETE_SQUOTE; + else if (*qcp == '"') + cs = COMPLETE_DQUOTE; + else if (*qcp == '\'') + cs = COMPLETE_SQUOTE; +#if defined (BANG_HISTORY) + else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE && + history_expansion_inhibited == 0 && xstrchr (mtext, '!')) + cs = COMPLETE_BSQUOTE; + + if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE && + history_expansion_inhibited == 0 && xstrchr (mtext, '!')) + { + cs = COMPLETE_BSQUOTE; + *qcp = '\0'; + } +#endif + + switch (cs) + { + case COMPLETE_DQUOTE: + rtext = sh_double_quote (mtext); + break; + case COMPLETE_SQUOTE: + rtext = sh_single_quote (mtext); + break; + case COMPLETE_BSQUOTE: + rtext = sh_backslash_quote (mtext); + break; + } + + if (mtext != s) + free (mtext); + + /* We may need to quote additional characters: those that readline treats + as word breaks that are not quoted by backslash_quote. */ + if (rtext && cs == COMPLETE_BSQUOTE) + { + mtext = quote_word_break_chars (rtext); + free (rtext); + rtext = mtext; + } + + /* Leave the opening quote intact. The readline completion code takes + care of avoiding doubled opening quotes. */ + rlen = strlen (rtext); + ret = (char *)xmalloc (rlen + 1); + strcpy (ret, rtext); + + /* If there are multiple matches, cut off the closing quote. */ + if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE) + ret[rlen - 1] = '\0'; + free (rtext); + return ret; +} + +/* Support for binding readline key sequences to Unix commands. */ +static Keymap cmd_xmap; + +static int +bash_execute_unix_command (count, key) + int count; /* ignored */ + int key; +{ + Keymap ckmap; /* current keymap */ + Keymap xkmap; /* unix command executing keymap */ + register int i; + char *cmd; + sh_parser_state_t ps; + + /* First, we need to find the right command to execute. This is tricky, + because we might have already indirected into another keymap. */ + ckmap = rl_get_keymap (); + if (ckmap != rl_executing_keymap) + { + /* bogus. we have to search. only handle one level of indirection. */ + for (i = 0; i < KEYMAP_SIZE; i++) + { + if (ckmap[i].type == ISKMAP && (Keymap)ckmap[i].function == rl_executing_keymap) + break; + } + if (i < KEYMAP_SIZE) + xkmap = (Keymap)cmd_xmap[i].function; + else + { + rl_crlf (); + internal_error (_("bash_execute_unix_command: cannot find keymap for command")); + rl_forced_update_display (); + return 1; + } + } + else + xkmap = cmd_xmap; + + cmd = (char *)xkmap[key].function; + + if (cmd == 0) + { + rl_ding (); + return 1; + } + + rl_crlf (); /* move to a new line */ + + save_parser_state (&ps); + + cmd = savestring (cmd); + parse_and_execute (cmd, "bash_execute_unix_command", SEVAL_NOHIST); + + restore_parser_state (&ps); + + /* and restore the readline buffer and display after command execution. */ + rl_forced_update_display (); + return 0; +} + +static void +init_unix_command_map () +{ + cmd_xmap = rl_make_bare_keymap (); +} + +static int +isolate_sequence (string, ind, need_dquote, startp) + char *string; + int ind, need_dquote, *startp; +{ + register int i; + int c, passc, delim; + + for (i = ind; string[i] && whitespace (string[i]); i++) + ; + /* NEED_DQUOTE means that the first non-white character *must* be `"'. */ + if (need_dquote && string[i] != '"') + { + builtin_error (_("%s: first non-whitespace character is not `\"'"), string); + return -1; + } + + /* We can have delimited strings even if NEED_DQUOTE == 0, like the command + string to bind the key sequence to. */ + delim = (string[i] == '"' || string[i] == '\'') ? string[i] : 0; + + if (startp) + *startp = delim ? ++i : i; + + for (passc = 0; c = string[i]; i++) + { + if (passc) + { + passc = 0; + continue; + } + if (c == '\\') + { + passc++; + continue; + } + if (c == delim) + break; + } + + if (delim && string[i] != delim) + { + builtin_error (_("no closing `%c' in %s"), delim, string); + return -1; + } + + return i; +} + +int +bind_keyseq_to_unix_command (line) + char *line; +{ + Keymap kmap; + char *kseq, *value; + int i, kstart; + + if (cmd_xmap == 0) + init_unix_command_map (); + + kmap = rl_get_keymap (); + + /* We duplicate some of the work done by rl_parse_and_bind here, but + this code only has to handle `"keyseq": ["]command["]' and can + generate an error for anything else. */ + i = isolate_sequence (line, 0, 1, &kstart); + if (i < 0) + return -1; + + /* Create the key sequence string to pass to rl_generic_bind */ + kseq = substring (line, kstart, i); + + for ( ; line[i] && line[i] != ':'; i++) + ; + if (line[i] != ':') + { + builtin_error (_("%s: missing colon separator"), line); + return -1; + } + + i = isolate_sequence (line, i + 1, 0, &kstart); + if (i < 0) + return -1; + + /* Create the value string containing the command to execute. */ + value = substring (line, kstart, i); + + /* Save the command to execute and the key sequence in the CMD_XMAP */ + rl_generic_bind (ISMACR, kseq, value, cmd_xmap); + + /* and bind the key sequence in the current keymap to a function that + understands how to execute from CMD_XMAP */ + rl_bind_keyseq_in_map (kseq, bash_execute_unix_command, kmap); + + return 0; +} + +/* Used by the programmable completion code. Complete TEXT as a filename, + but return only directories as matches. Dequotes the filename before + attempting to find matches. */ +char ** +bash_directory_completion_matches (text) + const char *text; +{ + char **m1; + char *dfn; + int qc; + +#if 0 + qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0; +#else + qc = rl_dispatching ? rl_completion_quote_character : 0; +#endif + dfn = bash_dequote_filename ((char *)text, qc); + m1 = rl_completion_matches (dfn, rl_filename_completion_function); + free (dfn); + + if (m1 == 0 || m1[0] == 0) + return m1; + /* We don't bother recomputing the lcd of the matches, because it will just + get thrown away by the programmable completion code and recomputed + later. */ + (void)bash_ignore_filenames (m1); + return m1; +} + +char * +bash_dequote_text (text) + const char *text; +{ + char *dtxt; + int qc; + + qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0; + dtxt = bash_dequote_filename ((char *)text, qc); + return (dtxt); +} +#endif /* READLINE */ diff --git a/builtins/Makefile.in b/builtins/Makefile.in index 4bb4ea5ea..df648c5e2 100644 --- a/builtins/Makefile.in +++ b/builtins/Makefile.in @@ -178,8 +178,7 @@ helpdoc: $(MKBUILTINS) $(DEFSRC) install-help: @-if test -n "${HELPDIR}" && test -d helpfiles ; then \ test -d ${HELPDIR} || ${SHELL} ${MKDIRS} $(DESTDIR)$(HELPDIR) ;\ - ( cd helpfiles ; \ - for f in *; do \ + ( for f in helpfiles/*; do \ echo installing $$f; \ ${INSTALL_DATA} $$f $(DESTDIR)$(HELPDIR); \ done; ) ; \ diff --git a/builtins/Makefile.in~ b/builtins/Makefile.in~ new file mode 100644 index 000000000..4bb4ea5ea --- /dev/null +++ b/builtins/Makefile.in~ @@ -0,0 +1,621 @@ +# This Makefile for building libbuiltins.a is in -*- text -*- for Emacs. +# +# Copyright (C) 1996-2003 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 2, 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +PACKAGE = @PACKAGE@ +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@ +includedir = @includedir@ +datadir = @datadir@ +localedir = $(datadir)/locale + +# 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@ +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) +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_INC = @INTL_INC@ +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@ + +# 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 + +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 \ + 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) $(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 ${HELPDIR} || ${SHELL} ${MKDIRS} $(DESTDIR)$(HELPDIR) ;\ + ( cd helpfiles ; \ + for f in *; 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 + +# 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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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 +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: $(BASHINCDIR)/chartypes.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 + +complete.o: ../config.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 + +#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 +mkbuiltins.c: ${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 diff --git a/builtins/command.def b/builtins/command.def index dcbbec148..73d395277 100644 --- a/builtins/command.def +++ b/builtins/command.def @@ -101,7 +101,7 @@ command_builtin (list) { found = describe_command (list->word->word, verbose); - if (found == 0) + if (found == 0 && verbose != CDESC_REUSABLE) sh_notfound (list->word->word); any_found += found; diff --git a/builtins/command.def~ b/builtins/command.def~ new file mode 100644 index 000000000..47b871aad --- /dev/null +++ b/builtins/command.def~ @@ -0,0 +1,207 @@ +This file is command.def, from which is created command.c. +It implements the builtin "command" in Bash. + +Copyright (C) 1987-2002 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2, or (at your option) any later +version. + +Bash is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with Bash; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +$PRODUCES command.c + +$BUILTIN command +$FUNCTION command_builtin +$SHORT_DOC command [-pVv] command [arg ...] +Runs COMMAND with ARGS ignoring shell functions. If you have a shell +function called `ls', and you wish to call the command `ls', you can +say "command ls". If the -p option is given, a default value is used +for PATH that is guaranteed to find all of the standard utilities. If +the -V or -v option is given, a string is printed describing COMMAND. +The -V option produces a more verbose description. +$END + +#include + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "../bashansi.h" + +#include "../shell.h" +#include "../execute_cmd.h" +#include "../flags.h" +#include "bashgetopt.h" +#include "common.h" + +#if defined (_CS_PATH) && defined (HAVE_CONFSTR) && !HAVE_DECL_CONFSTR +extern size_t confstr __P((int, char *, size_t)); +#endif + +extern int subshell_environment, posixly_correct; + +static void restore_path __P((char *)); +static char *get_standard_path __P((void)); + +/* Run the commands mentioned in LIST without paying attention to shell + functions. */ +int +command_builtin (list) + WORD_LIST *list; +{ + int result, verbose, use_standard_path, opt; + char *old_path, *standard_path; + COMMAND *command; + + verbose = use_standard_path = 0; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "pvV")) != -1) + { + switch (opt) + { + case 'p': + use_standard_path = 1; + break; + case 'V': + verbose = CDESC_SHORTDESC; /* look in common.h for constants */ + break; + case 'v': + verbose = CDESC_REUSABLE; /* ditto */ + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + if (list == 0) + return (EXECUTION_SUCCESS); + + if (verbose) + { + int found, any_found; + + for (any_found = 0; list; list = list->next) + { + found = describe_command (list->word->word, verbose); + + if (found == 0 && (posixly_correct == 0 || verbose != CDESC_REUSABLE)) + sh_notfound (list->word->word); + + any_found += found; + } + return (any_found ? EXECUTION_SUCCESS : EXECUTION_FAILURE); + } + +#if defined (RESTRICTED_SHELL) + if (use_standard_path && restricted) + { + sh_restricted ("-p"); + return (EXECUTION_FAILURE); + } +#endif + + begin_unwind_frame ("command_builtin"); + + /* We don't want this to be reparsed (consider command echo 'foo &'), so + just make a simple_command structure and call execute_command with it. */ + if (use_standard_path) + { + old_path = get_string_value ("PATH"); + /* If old_path is NULL, $PATH is unset. If so, we want to make sure + it's unset after this command completes. */ + if (old_path) + old_path = savestring (old_path); + add_unwind_protect ((Function *)restore_path, old_path); + + standard_path = get_standard_path (); + bind_variable ("PATH", standard_path ? standard_path : ""); + FREE (standard_path); + } + +#define COMMAND_BUILTIN_FLAGS (CMD_NO_FUNCTIONS | CMD_INHIBIT_EXPANSION | CMD_COMMAND_BUILTIN) + + command = make_bare_simple_command (); + command->value.Simple->words = (WORD_LIST *)copy_word_list (list); + command->value.Simple->redirects = (REDIRECT *)NULL; + command->flags |= COMMAND_BUILTIN_FLAGS; + command->value.Simple->flags |= COMMAND_BUILTIN_FLAGS; +#if 0 + /* This breaks for things like ( cd /tmp ; command z ababa ; echo next ) + or $(command echo a ; command echo b;) or even + { command echo a; command echo b; } & */ + /* If we're in a subshell, see if we can get away without forking + again, since we've already forked to run this builtin. */ + if (subshell_environment) + { + command->flags |= CMD_NO_FORK; + command->value.Simple->flags |= CMD_NO_FORK; + } +#endif + add_unwind_protect ((char *)dispose_command, command); + result = execute_command (command); + + run_unwind_frame ("command_builtin"); + + return (result); +} + +/* Restore the value of the $PATH variable after replacing it when + executing `command -p'. */ +static void +restore_path (var) + char *var; +{ + if (var) + { + bind_variable ("PATH", var); + free (var); + } + else + unbind_variable ("PATH"); +} + +/* Return a value for PATH that is guaranteed to find all of the standard + utilities. This uses Posix.2 configuration variables, if present. It + uses a value defined in config.h as a last resort. */ +static char * +get_standard_path () +{ +#if defined (_CS_PATH) && defined (HAVE_CONFSTR) + char *p; + size_t len; + + len = (size_t)confstr (_CS_PATH, (char *)NULL, (size_t)0); + if (len > 0) + { + p = (char *)xmalloc (len + 2); + *p = '\0'; + confstr (_CS_PATH, p, len); + return (p); + } + else + return (savestring (STANDARD_UTILS_PATH)); +#else /* !_CS_PATH || !HAVE_CONFSTR */ +# if defined (CS_PATH) + return (savestring (CS_PATH)); +# else + return (savestring (STANDARD_UTILS_PATH)); +# endif /* !CS_PATH */ +#endif /* !_CS_PATH || !HAVE_CONFSTR */ +} diff --git a/builtins/fc.def b/builtins/fc.def index 93c7ae3df..a2bf881ee 100644 --- a/builtins/fc.def +++ b/builtins/fc.def @@ -84,6 +84,7 @@ extern int errno; extern int echo_input_at_read; extern int current_command_line_count; extern int literal_history; +extern int posixly_correct; extern int unlink __P((const char *)); @@ -155,6 +156,7 @@ static void fc_addhist __P((char *)); /* String to execute on a file that we want to edit. */ #define FC_EDIT_COMMAND "${FCEDIT:-${EDITOR:-vi}}" +#define POSIX_FC_EDIT_COMMAND "${FCEDIT:-${EDITOR:-ed}}" int fc_builtin (list) @@ -166,7 +168,7 @@ fc_builtin (list) int histbeg, histend, last_hist, retval, opt; FILE *stream; REPL *rlist, *rl; - char *ename, *command, *newcom; + char *ename, *command, *newcom, *fcedit; HIST_ENTRY **hlist; char *fn; @@ -302,7 +304,7 @@ fc_builtin (list) if (listing) { histend = last_hist; - histbeg = histend - 16; + histbeg = histend - 16 + 1; /* +1 because loop below uses >= */ if (histbeg < 0) histbeg = 0; } @@ -347,7 +349,12 @@ fc_builtin (list) if (numbering) fprintf (stream, "%d", i + history_base); if (listing) - fprintf (stream, "\t%c", histdata (i) ? '*' : ' '); + { + if (posixly_correct) + fputs ("\t", stream); + else + fprintf (stream, "\t%c", histdata (i) ? '*' : ' '); + } fprintf (stream, "%s\n", histline (i)); } @@ -364,7 +371,8 @@ fc_builtin (list) } else { - command = (char *)xmalloc (3 + strlen (FC_EDIT_COMMAND) + strlen (fn)); + fcedit = posixly_correct ? POSIX_FC_EDIT_COMMAND : FC_EDIT_COMMAND; + command = (char *)xmalloc (3 + strlen (fcedit) + strlen (fn)); sprintf (command, "%s %s", FC_EDIT_COMMAND, fn); } retval = parse_and_execute (command, "fc", SEVAL_NOHIST); diff --git a/builtins/fc.def~ b/builtins/fc.def~ new file mode 100644 index 000000000..93c7ae3df --- /dev/null +++ b/builtins/fc.def~ @@ -0,0 +1,633 @@ +This file is fc.def, from which is created fc.c. +It implements the builtin "fc" in Bash. + +Copyright (C) 1987-2003 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2, or (at your option) any later +version. + +Bash is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with Bash; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +$PRODUCES fc.c + +$BUILTIN fc +$FUNCTION fc_builtin +$DEPENDS_ON HISTORY +$SHORT_DOC fc [-e ename] [-nlr] [first] [last] or fc -s [pat=rep] [cmd] +fc is used to list or edit and re-execute commands from the history list. +FIRST and LAST can be numbers specifying the range, or FIRST can be a +string, which means the most recent command beginning with that +string. + + -e ENAME selects which editor to use. Default is FCEDIT, then EDITOR, + then vi. + + -l means list lines instead of editing. + -n means no line numbers listed. + -r means reverse the order of the lines (making it newest listed first). + +With the `fc -s [pat=rep ...] [command]' format, the command is +re-executed after the substitution OLD=NEW is performed. + +A useful alias to use with this is r='fc -s', so that typing `r cc' +runs the last command beginning with `cc' and typing `r' re-executes +the last command. +$END + +#include + +#if defined (HISTORY) +#ifndef _MINIX +# include +#endif +#include "../bashtypes.h" +#include "posixstat.h" +#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include + +#include "../bashansi.h" +#include "../bashintl.h" +#include + +#include "../shell.h" +#include "../builtins.h" +#include "../flags.h" +#include "../bashhist.h" +#include "maxpath.h" +#include +#include "bashgetopt.h" +#include "common.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +extern int echo_input_at_read; +extern int current_command_line_count; +extern int literal_history; + +extern int unlink __P((const char *)); + +extern FILE *sh_mktmpfp __P((char *, int, char **)); + +/* **************************************************************** */ +/* */ +/* The K*rn shell style fc command (Fix Command) */ +/* */ +/* **************************************************************** */ + +/* fc builtin command (fix command) for Bash for those who + like K*rn-style history better than csh-style. + + fc [-e ename] [-nlr] [first] [last] + + FIRST and LAST can be numbers specifying the range, or FIRST can be + a string, which means the most recent command beginning with that + string. + + -e ENAME selects which editor to use. Default is FCEDIT, then EDITOR, + then the editor which corresponds to the current readline editing + mode, then vi. + + -l means list lines instead of editing. + -n means no line numbers listed. + -r means reverse the order of the lines (making it newest listed first). + + fc -e - [pat=rep ...] [command] + fc -s [pat=rep ...] [command] + + Equivalent to !command:sg/pat/rep execpt there can be multiple PAT=REP's. +*/ + +/* Data structure describing a list of global replacements to perform. */ +typedef struct repl { + struct repl *next; + char *pat; + char *rep; +} REPL; + +/* Accessors for HIST_ENTRY lists that are called HLIST. */ +#define histline(i) (hlist[(i)]->line) +#define histdata(i) (hlist[(i)]->data) + +#define FREE_RLIST() \ + do { \ + for (rl = rlist; rl; ) { \ + REPL *r; \ + r = rl->next; \ + if (rl->pat) \ + free (rl->pat); \ + if (rl->rep) \ + free (rl->rep); \ + free (rl); \ + rl = r; \ + } \ + } while (0) + +static char *fc_dosubs __P((char *, REPL *)); +static char *fc_gethist __P((char *, HIST_ENTRY **)); +static int fc_gethnum __P((char *, HIST_ENTRY **)); +static int fc_number __P((WORD_LIST *)); +static void fc_replhist __P((char *)); +#ifdef INCLUDE_UNUSED +static char *fc_readline __P((FILE *)); +static void fc_addhist __P((char *)); +#endif + +/* String to execute on a file that we want to edit. */ +#define FC_EDIT_COMMAND "${FCEDIT:-${EDITOR:-vi}}" + +int +fc_builtin (list) + WORD_LIST *list; +{ + register int i; + register char *sep; + int numbering, reverse, listing, execute; + int histbeg, histend, last_hist, retval, opt; + FILE *stream; + REPL *rlist, *rl; + char *ename, *command, *newcom; + HIST_ENTRY **hlist; + char *fn; + + numbering = 1; + reverse = listing = execute = 0; + ename = (char *)NULL; + + /* Parse out the options and set which of the two forms we're in. */ + reset_internal_getopt (); + lcurrent = list; /* XXX */ + while (fc_number (loptend = lcurrent) == 0 && + (opt = internal_getopt (list, ":e:lnrs")) != -1) + { + switch (opt) + { + case 'n': + numbering = 0; + break; + + case 'l': + listing = 1; + break; + + case 'r': + reverse = 1; + break; + + case 's': + execute = 1; + break; + + case 'e': + ename = list_optarg; + break; + + default: + builtin_usage (); + return (EX_USAGE); + } + } + + list = loptend; + + if (ename && (*ename == '-') && (ename[1] == '\0')) + execute = 1; + + /* The "execute" form of the command (re-run, with possible string + substitutions). */ + if (execute) + { + rlist = (REPL *)NULL; + while (list && ((sep = (char *)strchr (list->word->word, '=')) != NULL)) + { + *sep++ = '\0'; + rl = (REPL *)xmalloc (sizeof (REPL)); + rl->next = (REPL *)NULL; + rl->pat = savestring (list->word->word); + rl->rep = savestring (sep); + + if (rlist == NULL) + rlist = rl; + else + { + rl->next = rlist; + rlist = rl; + } + list = list->next; + } + + /* If we have a list of substitutions to do, then reverse it + to get the replacements in the proper order. */ + + rlist = REVERSE_LIST (rlist, REPL *); + + hlist = history_list (); + + /* If we still have something in list, it is a command spec. + Otherwise, we use the most recent command in time. */ + command = fc_gethist (list ? list->word->word : (char *)NULL, hlist); + + if (command == NULL) + { + builtin_error (_("no command found")); + if (rlist) + FREE_RLIST (); + + return (EXECUTION_FAILURE); + } + + if (rlist) + { + newcom = fc_dosubs (command, rlist); + free (command); + FREE_RLIST (); + command = newcom; + } + + fprintf (stderr, "%s\n", command); + fc_replhist (command); /* replace `fc -s' with command */ + return (parse_and_execute (command, "fc", SEVAL_NOHIST)); + } + + /* This is the second form of the command (the list-or-edit-and-rerun + form). */ + hlist = history_list (); + if (hlist == 0) + return (EXECUTION_SUCCESS); + for (i = 0; hlist[i]; i++); + + /* With the Bash implementation of history, the current command line + ("fc blah..." and so on) is already part of the history list by + the time we get to this point. This just skips over that command + and makes the last command that this deals with be the last command + the user entered before the fc. We need to check whether the + line was actually added (HISTIGNORE may have caused it to not be), + so we check hist_last_line_added. */ + + last_hist = i - 1 - hist_last_line_added; + + if (list) + { + histbeg = fc_gethnum (list->word->word, hlist); + list = list->next; + + if (list) + histend = fc_gethnum (list->word->word, hlist); + else + histend = listing ? last_hist : histbeg; + } + else + { + /* The default for listing is the last 16 history items. */ + if (listing) + { + histend = last_hist; + histbeg = histend - 16; + if (histbeg < 0) + histbeg = 0; + } + else + /* For editing, it is the last history command. */ + histbeg = histend = last_hist; + } + + /* We print error messages for line specifications out of range. */ + if ((histbeg < 0) || (histend < 0)) + { + sh_erange ((char *)NULL, _("history specification")); + return (EXECUTION_FAILURE); + } + + if (histend < histbeg) + { + i = histend; + histend = histbeg; + histbeg = i; + + reverse = 1; + } + + if (listing) + stream = stdout; + else + { + numbering = 0; + stream = sh_mktmpfp ("bash-fc", MT_USERANDOM|MT_USETMPDIR, &fn); + if (stream == 0) + { + builtin_error (_("%s: cannot open temp file: %s"), fn ? fn : "", strerror (errno)); + FREE (fn); + return (EXECUTION_FAILURE); + } + } + + for (i = reverse ? histend : histbeg; reverse ? i >= histbeg : i <= histend; reverse ? i-- : i++) + { + QUIT; + if (numbering) + fprintf (stream, "%d", i + history_base); + if (listing) + fprintf (stream, "\t%c", histdata (i) ? '*' : ' '); + fprintf (stream, "%s\n", histline (i)); + } + + if (listing) + return (EXECUTION_SUCCESS); + + fclose (stream); + + /* Now edit the file of commands. */ + if (ename) + { + command = (char *)xmalloc (strlen (ename) + strlen (fn) + 2); + sprintf (command, "%s %s", ename, fn); + } + else + { + command = (char *)xmalloc (3 + strlen (FC_EDIT_COMMAND) + strlen (fn)); + sprintf (command, "%s %s", FC_EDIT_COMMAND, fn); + } + retval = parse_and_execute (command, "fc", SEVAL_NOHIST); + if (retval != EXECUTION_SUCCESS) + { + unlink (fn); + free (fn); + return (EXECUTION_FAILURE); + } + + /* Make sure parse_and_execute doesn't turn this off, even though a + call to parse_and_execute farther up the function call stack (e.g., + if this is called by vi_edit_and_execute_command) may have already + called bash_history_disable. */ + remember_on_history = 1; + + /* Turn on the `v' flag while fc_execute_file runs so the commands + will be echoed as they are read by the parser. */ + begin_unwind_frame ("fc builtin"); + add_unwind_protect ((Function *)xfree, fn); + add_unwind_protect (unlink, fn); + unwind_protect_int (echo_input_at_read); + echo_input_at_read = 1; + + retval = fc_execute_file (fn); + + run_unwind_frame ("fc builtin"); + + return (retval); +} + +/* Return 1 if LIST->word->word is a legal number for fc's use. */ +static int +fc_number (list) + WORD_LIST *list; +{ + char *s; + + if (list == 0) + return 0; + s = list->word->word; + if (*s == '-') + s++; + return (legal_number (s, (intmax_t *)NULL)); +} + +/* Return an absolute index into HLIST which corresponds to COMMAND. If + COMMAND is a number, then it was specified in relative terms. If it + is a string, then it is the start of a command line present in HLIST. */ +static int +fc_gethnum (command, hlist) + char *command; + HIST_ENTRY **hlist; +{ + int sign = 1, n, clen; + register int i, j; + register char *s; + + /* Count history elements. */ + for (i = 0; hlist[i]; i++); + + /* With the Bash implementation of history, the current command line + ("fc blah..." and so on) is already part of the history list by + the time we get to this point. This just skips over that command + and makes the last command that this deals with be the last command + the user entered before the fc. We need to check whether the + line was actually added (HISTIGNORE may have caused it to not be), + so we check hist_last_line_added. */ + i -= 1 + hist_last_line_added; + + /* No specification defaults to most recent command. */ + if (command == NULL) + return (i); + + /* Otherwise, there is a specification. It can be a number relative to + the current position, or an absolute history number. */ + s = command; + + /* Handle possible leading minus sign. */ + if (s && (*s == '-')) + { + sign = -1; + s++; + } + + if (s && DIGIT(*s)) + { + n = atoi (s); + n *= sign; + + /* If the value is negative or zero, then it is an offset from + the current history item. */ + if (n < 0) + { + n += i + 1; + return (n < 0 ? 0 : n); + } + else if (n == 0) + return (i); + else + { + n -= history_base; + return (i < n ? i : n); + } + } + + clen = strlen (command); + for (j = i; j >= 0; j--) + { + if (STREQN (command, histline (j), clen)) + return (j); + } + return (-1); +} + +/* Locate the most recent history line which begins with + COMMAND in HLIST, and return a malloc()'ed copy of it. */ +static char * +fc_gethist (command, hlist) + char *command; + HIST_ENTRY **hlist; +{ + int i; + + if (!hlist) + return ((char *)NULL); + + i = fc_gethnum (command, hlist); + + if (i >= 0) + return (savestring (histline (i))); + else + return ((char *)NULL); +} + +#ifdef INCLUDE_UNUSED +/* Read the edited history lines from STREAM and return them + one at a time. This can read unlimited length lines. The + caller should free the storage. */ +static char * +fc_readline (stream) + FILE *stream; +{ + register int c; + int line_len = 0, lindex = 0; + char *line = (char *)NULL; + + while ((c = getc (stream)) != EOF) + { + if ((lindex + 2) >= line_len) + line = (char *)xrealloc (line, (line_len += 128)); + + if (c == '\n') + { + line[lindex++] = '\n'; + line[lindex++] = '\0'; + return (line); + } + else + line[lindex++] = c; + } + + if (!lindex) + { + if (line) + free (line); + + return ((char *)NULL); + } + + if (lindex + 2 >= line_len) + line = (char *)xrealloc (line, lindex + 3); + + line[lindex++] = '\n'; /* Finish with newline if none in file */ + line[lindex++] = '\0'; + return (line); +} +#endif + +/* Perform the SUBS on COMMAND. + SUBS is a list of substitutions, and COMMAND is a simple string. + Return a pointer to a malloc'ed string which contains the substituted + command. */ +static char * +fc_dosubs (command, subs) + char *command; + REPL *subs; +{ + register char *new, *t; + register REPL *r; + + for (new = savestring (command), r = subs; r; r = r->next) + { + t = strsub (new, r->pat, r->rep, 1); + free (new); + new = t; + } + return (new); +} + +/* Use `command' to replace the last entry in the history list, which, + by this time, is `fc blah...'. The intent is that the new command + become the history entry, and that `fc' should never appear in the + history list. This way you can do `r' to your heart's content. */ +static void +fc_replhist (command) + char *command; +{ + register int i; + HIST_ENTRY **hlist, *histent, *discard; + int n; + + if (command == 0 || *command == '\0') + return; + + hlist = history_list (); + + if (hlist == NULL) + return; + + for (i = 0; hlist[i]; i++); + i--; + + /* History_get () takes a parameter that should be + offset by history_base. */ + + histent = history_get (history_base + i); /* Don't free this */ + if (histent == NULL) + return; + + n = strlen (command); + + if (command[n - 1] == '\n') + command[n - 1] = '\0'; + + if (command && *command) + { + discard = remove_history (i); + if (discard) + { + FREE (discard->line); + free ((char *) discard); + } + maybe_add_history (command); /* Obeys HISTCONTROL setting. */ + } +} + +#ifdef INCLUDE_UNUSED +/* Add LINE to the history, after removing a single trailing newline. */ +static void +fc_addhist (line) + char *line; +{ + register int n; + + n = strlen (line); + + if (line[n - 1] == '\n') + line[n - 1] = '\0'; + + if (line && *line) + maybe_add_history (line); +} +#endif + +#endif /* HISTORY */ diff --git a/builtins/hash.def b/builtins/hash.def index d311ac90b..9425c0370 100644 --- a/builtins/hash.def +++ b/builtins/hash.def @@ -125,7 +125,7 @@ hash_builtin (list) if (list == 0 && expunge_hash_table == 0) { if (print_hashed_commands (list_portably) == 0) - printf (_("%s: hash table empty\n"), this_command_name); + fprintf (stderr, _("%s: hash table empty\n"), this_command_name); return (EXECUTION_SUCCESS); } diff --git a/builtins/hash.def~ b/builtins/hash.def~ new file mode 100644 index 000000000..d311ac90b --- /dev/null +++ b/builtins/hash.def~ @@ -0,0 +1,268 @@ +This file is hash.def, from which is created hash.c. +It implements the builtin "hash" in Bash. + +Copyright (C) 1987-2003 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2, or (at your option) any later +version. + +Bash is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with Bash; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +$PRODUCES hash.c + +$BUILTIN hash +$FUNCTION hash_builtin +$SHORT_DOC hash [-lr] [-p pathname] [-dt] [name ...] +For each NAME, the full pathname of the command is determined and +remembered. If the -p option is supplied, PATHNAME is used as the +full pathname of NAME, and no path search is performed. The -r +option causes the shell to forget all remembered locations. The -d +option causes the shell to forget the remembered location of each NAME. +If the -t option is supplied the full pathname to which each NAME +corresponds is printed. If multiple NAME arguments are supplied with +-t, the NAME is printed before the hashed full pathname. The -l option +causes output to be displayed in a format that may be reused as input. +If no arguments are given, information about remembered commands is displayed. +$END + +#include + +#include + +#include "../bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#include "../bashansi.h" +#include "../bashintl.h" + +#include "../shell.h" +#include "../builtins.h" +#include "../flags.h" +#include "../findcmd.h" +#include "../hashcmd.h" +#include "common.h" +#include "bashgetopt.h" + +extern int dot_found_in_search; +extern char *this_command_name; + +static int add_hashed_command __P((char *, int)); +static int print_hash_info __P((BUCKET_CONTENTS *)); +static int print_portable_hash_info __P((BUCKET_CONTENTS *)); +static int print_hashed_commands __P((int)); +static int list_hashed_filename_targets __P((WORD_LIST *, int)); + +/* Print statistics on the current state of hashed commands. If LIST is + not empty, then rehash (or hash in the first place) the specified + commands. */ +int +hash_builtin (list) + WORD_LIST *list; +{ + int expunge_hash_table, list_targets, list_portably, delete, opt; + char *w, *pathname; + + if (hashing_enabled == 0) + { + builtin_error (_("hashing disabled")); + return (EXECUTION_FAILURE); + } + + expunge_hash_table = list_targets = list_portably = delete = 0; + pathname = (char *)NULL; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "dlp:rt")) != -1) + { + switch (opt) + { + case 'd': + delete = 1; + break; + case 'l': + list_portably = 1; + break; + case 'p': + pathname = list_optarg; + break; + case 'r': + expunge_hash_table = 1; + break; + case 't': + list_targets = 1; + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + /* hash -t requires at least one argument. */ + if (list == 0 && list_targets) + { + sh_needarg ("-t"); + return (EXECUTION_FAILURE); + } + + /* We want hash -r to be silent, but hash -- to print hashing info, so + we test expunge_hash_table. */ + if (list == 0 && expunge_hash_table == 0) + { + if (print_hashed_commands (list_portably) == 0) + printf (_("%s: hash table empty\n"), this_command_name); + + return (EXECUTION_SUCCESS); + } + + if (expunge_hash_table) + phash_flush (); + + /* If someone runs `hash -r -t xyz' he will be disappointed. */ + if (list_targets) + return (list_hashed_filename_targets (list, list_portably)); + +#if defined (RESTRICTED_SHELL) + if (restricted && pathname && strchr (pathname, '/')) + { + sh_restricted (pathname); + return (EXECUTION_FAILURE); + } +#endif + + for (opt = EXECUTION_SUCCESS; list; list = list->next) + { + /* Add, remove or rehash the specified commands. */ + w = list->word->word; + if (pathname) + { + if (is_directory (pathname)) + { +#ifdef EISDIR + builtin_error ("%s: %s", pathname, strerror (EISDIR)); +#else + builtin_error ("%s: is a directory", pathname); +#endif + opt = EXECUTION_FAILURE; + } + else + phash_insert (w, pathname, 0, 0); + } + else if (absolute_program (w)) + continue; + else if (delete && phash_remove (w)) + { + sh_notfound (w); + opt = EXECUTION_FAILURE; + } + else if (add_hashed_command (w, 0)) + opt = EXECUTION_FAILURE; + } + + fflush (stdout); + return (opt); +} + +static int +add_hashed_command (w, quiet) + char *w; + int quiet; +{ + int rv; + char *full_path; + + rv = 0; + if (find_function (w) == 0 && find_shell_builtin (w) == 0) + { + full_path = find_user_command (w); + if (full_path && executable_file (full_path)) + phash_insert (w, full_path, dot_found_in_search, 0); + else + { + if (quiet == 0) + sh_notfound (w); + rv++; + } + FREE (full_path); + } + return (rv); +} + +/* Print information about current hashed info. */ +static int +print_hash_info (item) + BUCKET_CONTENTS *item; +{ + printf ("%4d\t%s\n", item->times_found, pathdata(item)->path); + return 0; +} + +static int +print_portable_hash_info (item) + BUCKET_CONTENTS *item; +{ + printf ("builtin hash -p %s %s\n", pathdata(item)->path, item->key); + return 0; +} + +static int +print_hashed_commands (fmt) + int fmt; +{ + if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0) + return (0); + + if (fmt == 0) + printf ("hits\tcommand\n"); + hash_walk (hashed_filenames, fmt ? print_portable_hash_info : print_hash_info); + return (1); +} + +static int +list_hashed_filename_targets (list, fmt) + WORD_LIST *list; + int fmt; +{ + int all_found, multiple; + char *target; + WORD_LIST *l; + + all_found = 1; + multiple = list->next != 0; + + for (l = list; l; l = l->next) + { + target = phash_search (l->word->word); + if (target == 0) + { + all_found = 0; + sh_notfound (l->word->word); + continue; + } + if (fmt) + printf ("builtin hash -p %s %s\n", target, l->word->word); + else + { + if (multiple) + printf ("%s\t", l->word->word); + printf ("%s\n", target); + } + } + + return (all_found ? EXECUTION_SUCCESS : EXECUTION_FAILURE); +} diff --git a/builtins/reserved.def b/builtins/reserved.def index 50a942f87..47846bb31 100644 --- a/builtins/reserved.def +++ b/builtins/reserved.def @@ -109,11 +109,10 @@ $END $BUILTIN % $DOCNAME fg_percent -$SHORT_DOC %[DIGITS | WORD] [&] +$SHORT_DOC %JOBSPEC [&] This is similar to the `fg' command. Resume a stopped or background -job. If you specifiy DIGITS, then that job is used. If you specify -WORD, then the job whose name begins with WORD is used. Following the -job specification with a `&' places the job in the background. +job specified by %JOBSPEC. Following the job specification with a `&' +places the job in the background. $END $BUILTIN (( ... )) diff --git a/builtins/reserved.def~ b/builtins/reserved.def~ new file mode 100644 index 000000000..50a942f87 --- /dev/null +++ b/builtins/reserved.def~ @@ -0,0 +1,201 @@ +This file is reserved.def, in which the shell reserved words are defined. +It has no direct C file production, but defines builtins for the Bash +builtin help command. + +Copyright (C) 1987-2002 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2, or (at your option) any later +version. + +Bash is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with Bash; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +$BUILTIN for +$SHORT_DOC for NAME [in WORDS ... ;] do COMMANDS; done +The `for' loop executes a sequence of commands for each member in a +list of items. If `in WORDS ...;' is not present, then `in "$@"' is +assumed. For each element in WORDS, NAME is set to that element, and +the COMMANDS are executed. +$END + +$BUILTIN for (( +$DOCNAME arith_for +$SHORT_DOC for (( exp1; exp2; exp3 )); do COMMANDS; done +Equivalent to + (( EXP1 )) + while (( EXP2 )); do + COMMANDS + (( EXP3 )) + done +EXP1, EXP2, and EXP3 are arithmetic expressions. If any expression is +omitted, it behaves as if it evaluates to 1. +$END + +$BUILTIN select +$SHORT_DOC select NAME [in WORDS ... ;] do COMMANDS; done +The WORDS are expanded, generating a list of words. The +set of expanded words is printed on the standard error, each +preceded by a number. If `in WORDS' is not present, `in "$@"' +is assumed. The PS3 prompt is then displayed and a line read +from the standard input. If the line consists of the number +corresponding to one of the displayed words, then NAME is set +to that word. If the line is empty, WORDS and the prompt are +redisplayed. If EOF is read, the command completes. Any other +value read causes NAME to be set to null. The line read is saved +in the variable REPLY. COMMANDS are executed after each selection +until a break command is executed. +$END + +$BUILTIN time +$SHORT_DOC time [-p] PIPELINE +Execute PIPELINE and print a summary of the real time, user CPU time, +and system CPU time spent executing PIPELINE when it terminates. +The return status is the return status of PIPELINE. The `-p' option +prints the timing summary in a slightly different format. This uses +the value of the TIMEFORMAT variable as the output format. +$END + +$BUILTIN case +$SHORT_DOC case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac +Selectively execute COMMANDS based upon WORD matching PATTERN. The +`|' is used to separate multiple patterns. +$END + +$BUILTIN if +$SHORT_DOC if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi +The if COMMANDS are executed. If the exit status is zero, then the then +COMMANDS are executed. Otherwise, each of the elif COMMANDS are executed +in turn, and if the exit status is zero, the corresponding then COMMANDS +are executed and the if command completes. Otherwise, the else COMMANDS +are executed, if present. The exit status is the exit status of the last +command executed, or zero if no condition tested true. +$END + +$BUILTIN while +$SHORT_DOC while COMMANDS; do COMMANDS; done +Expand and execute COMMANDS as long as the final command in the +`while' COMMANDS has an exit status of zero. +$END + +$BUILTIN until +$SHORT_DOC until COMMANDS; do COMMANDS; done +Expand and execute COMMANDS as long as the final command in the +`until' COMMANDS has an exit status which is not zero. +$END + +$BUILTIN function +$SHORT_DOC function NAME { COMMANDS ; } or NAME () { COMMANDS ; } +Create a simple command invoked by NAME which runs COMMANDS. +Arguments on the command line along with NAME are passed to the +function as $0 .. $n. +$END + +$BUILTIN { ... } +$DOCNAME grouping_braces +$SHORT_DOC { COMMANDS ; } +Run a set of commands in a group. This is one way to redirect an +entire set of commands. +$END + +$BUILTIN % +$DOCNAME fg_percent +$SHORT_DOC %[DIGITS | WORD] [&] +This is similar to the `fg' command. Resume a stopped or background +job. If you specifiy DIGITS, then that job is used. If you specify +WORD, then the job whose name begins with WORD is used. Following the +job specification with a `&' places the job in the background. +$END + +$BUILTIN (( ... )) +$DOCNAME arith +$SHORT_DOC (( expression )) +The EXPRESSION is evaluated according to the rules for arithmetic +evaluation. Equivalent to "let EXPRESSION". +$END + +$BUILTIN [[ ... ]] +$DOCNAME conditional +$SHORT_DOC [[ expression ]] +Returns a status of 0 or 1 depending on the evaluation of the conditional +expression EXPRESSION. Expressions are composed of the same primaries used +by the `test' builtin, and may be combined using the following operators + + ( EXPRESSION ) Returns the value of EXPRESSION + ! EXPRESSION True if EXPRESSION is false; else false + EXPR1 && EXPR2 True if both EXPR1 and EXPR2 are true; else false + EXPR1 || EXPR2 True if either EXPR1 or EXPR2 is true; else false + +When the `==' and `!=' operators are used, the string to the right of the +operator is used as a pattern and pattern matching is performed. The +&& and || operators do not evaluate EXPR2 if EXPR1 is sufficient to +determine the expression's value. +$END + +$BUILTIN variables +$DOCNAME variable_help +$SHORT_DOC variables - Some variable names and meanings +BASH_VERSION Version information for this Bash. +CDPATH A colon-separated list of directories to search + for directries given as arguments to `cd'. +GLOBIGNORE A colon-separated list of patterns describing filenames to + be ignored by pathname expansion. +#if defined (HISTORY) +HISTFILE The name of the file where your command history is stored. +HISTFILESIZE The maximum number of lines this file can contain. +HISTSIZE The maximum number of history lines that a running + shell can access. +#endif /* HISTORY */ +HOME The complete pathname to your login directory. +HOSTNAME The name of the current host. +HOSTTYPE The type of CPU this version of Bash is running under. +IGNOREEOF Controls the action of the shell on receipt of an EOF + character as the sole input. If set, then the value + of it is the number of EOF characters that can be seen + in a row on an empty line before the shell will exit + (default 10). When unset, EOF signifies the end of input. +MACHTYPE A string describing the current system Bash is running on. +MAILCHECK How often, in seconds, Bash checks for new mail. +MAILPATH A colon-separated list of filenames which Bash checks + for new mail. +OSTYPE The version of Unix this version of Bash is running on. +PATH A colon-separated list of directories to search when + looking for commands. +PROMPT_COMMAND A command to be executed before the printing of each + primary prompt. +PS1 The primary prompt string. +PS2 The secondary prompt string. +PWD The full pathname of the current directory. +SHELLOPTS A colon-separated list of enabled shell options. +TERM The name of the current terminal type. +TIMEFORMAT The output format for timing statistics displayed by the + `time' reserved word. +auto_resume Non-null means a command word appearing on a line by + itself is first looked for in the list of currently + stopped jobs. If found there, that job is foregrounded. + A value of `exact' means that the command word must + exactly match a command in the list of stopped jobs. A + value of `substring' means that the command word must + match a substring of the job. Any other value means that + the command must be a prefix of a stopped job. +#if defined (HISTORY) +# if defined (BANG_HISTORY) +histchars Characters controlling history expansion and quick + substitution. The first character is the history + substitution character, usually `!'. The second is + the `quick substitution' character, usually `^'. The + third is the `history comment' character, usually `#'. +# endif /* BANG_HISTORY */ +HISTIGNORE A colon-separated list of patterns used to decide which + commands should be saved on the history list. +#endif /* HISTORY */ +$END diff --git a/builtins/trap.def b/builtins/trap.def index 9dd746fba..1531d208d 100644 --- a/builtins/trap.def +++ b/builtins/trap.def @@ -87,7 +87,7 @@ int trap_builtin (list) WORD_LIST *list; { - int list_signal_names, display, result, opt; + int list_signal_names, display, result, opt, first_signal; list_signal_names = display = 0; result = EXECUTION_SUCCESS; @@ -118,14 +118,19 @@ trap_builtin (list) else { char *first_arg; - int operation, sig; + int operation, sig, first_signal; operation = SET; first_arg = list->word->word; + first_signal = first_arg && *first_arg && all_digits (first_arg) && signal_object_p (first_arg, opt); + + /* Backwards compatibility */ + if (first_signal) + operation = REVERT; /* When in posix mode, the historical behavior of looking for a missing first argument is disabled. To revert to the original signal handling disposition, use `-' as the first argument. */ - if (posixly_correct == 0 && first_arg && *first_arg && + else if (posixly_correct == 0 && first_arg && *first_arg && (*first_arg != '-' || first_arg[1]) && signal_object_p (first_arg, opt) && list->next == 0) operation = REVERT; diff --git a/builtins/trap.def~ b/builtins/trap.def~ new file mode 100644 index 000000000..c43bf7ad7 --- /dev/null +++ b/builtins/trap.def~ @@ -0,0 +1,263 @@ +This file is trap.def, from which is created trap.c. +It implements the builtin "trap" in Bash. + +Copyright (C) 1987-2004 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2, or (at your option) any later +version. + +Bash is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with Bash; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +$PRODUCES trap.c + +$BUILTIN trap +$FUNCTION trap_builtin +$SHORT_DOC trap [-lp] [[arg] signal_spec ...] +The command ARG is to be read and executed when the shell receives +signal(s) SIGNAL_SPEC. If ARG is absent (and a single SIGNAL_SPEC +is supplied) or `-', each specified signal is reset to its original +value. If ARG is the null string each SIGNAL_SPEC is ignored by the +shell and by the commands it invokes. If a SIGNAL_SPEC is EXIT (0) +the command ARG is executed on exit from the shell. If a SIGNAL_SPEC +is DEBUG, ARG is executed after every simple command. If the`-p' option +is supplied then the trap commands associated with each SIGNAL_SPEC are +displayed. If no arguments are supplied or if only `-p' is given, trap +prints the list of commands associated with each signal. Each SIGNAL_SPEC +is either a signal name in or a signal number. Signal names +are case insensitive and the SIG prefix is optional. `trap -l' prints +a list of signal names and their corresponding numbers. Note that a +signal can be sent to the shell with "kill -signal $$". +$END + +#include + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "../bashtypes.h" +#include +#include +#include "../bashansi.h" + +#include "../shell.h" +#include "../trap.h" +#include "common.h" +#include "bashgetopt.h" + +static void showtrap __P((int)); +static int display_traps __P((WORD_LIST *)); + +/* The trap command: + + trap + trap + trap -l + trap -p [sigspec ...] + trap [--] + + Set things up so that ARG is executed when SIGNAL(s) N is recieved. + If ARG is the empty string, then ignore the SIGNAL(s). If there is + no ARG, then set the trap for SIGNAL(s) to its original value. Just + plain "trap" means to print out the list of commands associated with + each signal number. Single arg of "-l" means list the signal names. */ + +/* Possible operations to perform on the list of signals.*/ +#define SET 0 /* Set this signal to first_arg. */ +#define REVERT 1 /* Revert to this signals original value. */ +#define IGNORE 2 /* Ignore this signal. */ + +extern int posixly_correct; + +int +trap_builtin (list) + WORD_LIST *list; +{ + int list_signal_names, display, result, opt, first_signal; + + list_signal_names = display = 0; + result = EXECUTION_SUCCESS; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "lp")) != -1) + { + switch (opt) + { + case 'l': + list_signal_names++; + break; + case 'p': + display++; + break; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + opt = DSIG_NOCASE|DSIG_SIGPREFIX; /* flags for decode_signal */ + + if (list_signal_names) + return (display_signal_list ((WORD_LIST *)NULL, 1)); + else if (display || list == 0) + return (display_traps (list)); + else + { + char *first_arg; + int operation, sig, first_signal; + + operation = SET; + first_arg = list->word->word; + first_signal = first_arg && *first_arg && all_digits (first_arg) && signal_object_p (first_arg, opt); + + /* When in posix mode, the historical behavior of looking for a + missing first argument is disabled. To revert to the original + signal handling disposition, use `-' as the first argument. */ + if (posixly_correct == 0 && first_arg && *first_arg && + (*first_arg != '-' || first_arg[1]) && + signal_object_p (first_arg, opt) && list->next == 0) + operation = REVERT; + else + { + list = list->next; + if (list == 0) + { + builtin_usage (); + return (EX_USAGE); + } + else if (*first_arg == '\0') + operation = IGNORE; + else if (first_arg[0] == '-' && !first_arg[1]) + operation = REVERT; + } + + while (list) + { + sig = decode_signal (list->word->word, opt); + + if (sig == NO_SIG) + { + sh_invalidsig (list->word->word); + result = EXECUTION_FAILURE; + } + else + { + switch (operation) + { + case SET: + set_signal (sig, first_arg); + break; + + case REVERT: + restore_default_signal (sig); + + /* Signals that the shell treats specially need special + handling. */ + switch (sig) + { + case SIGINT: + if (interactive) + set_signal_handler (SIGINT, sigint_sighandler); + else + set_signal_handler (SIGINT, termination_unwind_protect); + break; + + case SIGQUIT: + /* Always ignore SIGQUIT. */ + set_signal_handler (SIGQUIT, SIG_IGN); + break; + case SIGTERM: +#if defined (JOB_CONTROL) + case SIGTTIN: + case SIGTTOU: + case SIGTSTP: +#endif /* JOB_CONTROL */ + if (interactive) + set_signal_handler (sig, SIG_IGN); + break; + } + break; + + case IGNORE: + ignore_signal (sig); + break; + } + } + list = list->next; + } + } + + return (result); +} + +static void +showtrap (i) + int i; +{ + char *t, *p, *sn; + + p = trap_list[i]; + + if (p == (char *)DEFAULT_SIG) + return; + + t = (p == (char *)IGNORE_SIG) ? (char *)NULL : sh_single_quote (p); + sn = signal_name (i); + /* Make sure that signals whose names are unknown (for whatever reason) + are printed as signal numbers. */ + if (STREQN (sn, "SIGJUNK", 7) || STREQN (sn, "unknown", 7)) + printf ("trap -- %s %d\n", t ? t : "''", i); + else if (posixly_correct) + { + if (STREQN (sn, "SIG", 3)) + printf ("trap -- %s %s\n", t ? t : "''", sn+3); + else + printf ("trap -- %s %s\n", t ? t : "''", sn); + } + else + printf ("trap -- %s %s\n", t ? t : "''", sn); + + FREE (t); +} + +static int +display_traps (list) + WORD_LIST *list; +{ + int result, i; + + if (list == 0) + { + for (i = 0; i < BASH_NSIG; i++) + showtrap (i); + return (EXECUTION_SUCCESS); + } + + for (result = EXECUTION_SUCCESS; list; list = list->next) + { + i = decode_signal (list->word->word, DSIG_NOCASE|DSIG_SIGPREFIX); + if (i == NO_SIG) + { + sh_invalidsig (list->word->word); + result = EXECUTION_FAILURE; + } + else + showtrap (i); + } + + return (result); +} diff --git a/doc/bashref.texi b/doc/bashref.texi index 3b33021b3..6edef623a 100644 --- a/doc/bashref.texi +++ b/doc/bashref.texi @@ -5953,7 +5953,8 @@ The @code{trap} builtin displays signal names without the leading @item The @code{trap} builtin doesn't check the first argument for a possible signal specification and revert the signal handling to the original -disposition if it is. If users want to reset the handler for a given +disposition if it is, unless that argument consists solely of digits and +is a valid signal number. If users want to reset the handler for a given signal to the original disposition, they should use @samp{-} as the first argument. @@ -5988,6 +5989,14 @@ When the @code{cd} builtin is invoked in @var{logical} mode, and the pathname constructed from @code{$PWD} and the directory name supplied as an argument does not refer to an existing directory, @code{cd} will fail instead of falling back to @var{physical} mode. + +@item +When listing the history, the @code{fc} builtin does not include an +indication of whether or not a history entry has been modified. + +@item +The default editor used by @code{fc} is @code{ed}. + @end enumerate There is other @sc{posix} 1003.2 behavior that Bash does not implement. diff --git a/doc/bashref.texi~ b/doc/bashref.texi~ new file mode 100644 index 000000000..f0bd04628 --- /dev/null +++ b/doc/bashref.texi~ @@ -0,0 +1,7273 @@ +\input texinfo.tex @c -*- texinfo -*- +@c %**start of header +@setfilename bashref.info +@settitle Bash Reference Manual +@c %**end of header + +@setchapternewpage odd + +@include version.texi + +@copying +This text is a brief description of the features that are present in +the Bash shell (version @value{VERSION}, @value{UPDATED}). + +This is Edition @value{EDITION}, last updated @value{UPDATED}, +of @cite{The GNU Bash Reference Manual}, +for @code{Bash}, Version @value{VERSION}. + +Copyright @copyright{} 1988-2004 Free Software Foundation, Inc. + +Permission is granted to make and distribute verbatim copies of +this manual provided the copyright notice and this permission notice +are preserved on all copies. + +@quotation +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.1 or +any later version published by the Free Software Foundation; with no +Invariant Sections, with the Front-Cover texts being ``A GNU Manual,'' +and with the Back-Cover Texts as in (a) below. A copy of the license is +included in the section entitled ``GNU Free Documentation License.'' + +(a) The FSF's Back-Cover Text is: ``You have freedom to copy and modify +this GNU Manual, like GNU software. Copies published by the Free +Software Foundation raise funds for GNU development.'' +@end quotation +@end copying + +@defcodeindex bt +@defcodeindex rw +@set BashFeatures + +@dircategory Basics +@direntry +* Bash: (bash). The GNU Bourne-Again SHell. +@end direntry + +@finalout + +@titlepage +@title Bash Reference Manual +@subtitle Reference Documentation for Bash +@subtitle Edition @value{EDITION}, for @code{Bash} Version @value{VERSION}. +@subtitle @value{UPDATED-MONTH} +@author Chet Ramey, Case Western Reserve University +@author Brian Fox, Free Software Foundation + +@page +@vskip 0pt plus 1filll +@insertcopying + +@sp 1 +Published by the Free Software Foundation @* +59 Temple Place, Suite 330, @* +Boston, MA 02111-1307 @* +USA @* + +@end titlepage + +@contents + +@ifnottex +@node Top, Introduction, (dir), (dir) +@top Bash Features + +This text is a brief description of the features that are present in +the Bash shell (version @value{VERSION}, @value{UPDATED}).. + +This is Edition @value{EDITION}, last updated @value{UPDATED}, +of @cite{The GNU Bash Reference Manual}, +for @code{Bash}, Version @value{VERSION}. + +Bash contains features that appear in other popular shells, and some +features that only appear in Bash. Some of the shells that Bash has +borrowed concepts from are the Bourne Shell (@file{sh}), the Korn Shell +(@file{ksh}), and the C-shell (@file{csh} and its successor, +@file{tcsh}). The following menu breaks the features up into +categories based upon which one of these other shells inspired the +feature. + +This manual is meant as a brief introduction to features found in +Bash. The Bash manual page should be used as the definitive +reference on shell behavior. + +@menu +* Introduction:: An introduction to the shell. + +* Definitions:: Some definitions used in the rest of this + manual. + +* Basic Shell Features:: The shell "building blocks". + +* Shell Builtin Commands:: Commands that are a part of the shell. + +* Shell Variables:: Variables used or set by Bash. + +* Bash Features:: Features found only in Bash. + +* Job Control:: A chapter describing what job control is + and how Bash allows you to use it. + +* Using History Interactively:: Chapter dealing with history expansion + rules. + +* Command Line Editing:: Chapter describing the command line + editing features. + +* Installing Bash:: How to build and install Bash on your system. + +* Reporting Bugs:: How to report bugs in Bash. + +* Major Differences From The Bourne Shell:: A terse list of the differences + between Bash and historical + versions of /bin/sh. + +* Copying This Manual:: Copying this manual. + +* Builtin Index:: Index of Bash builtin commands. + +* Reserved Word Index:: Index of Bash reserved words. + +* Variable Index:: Quick reference helps you find the + variable you want. + +* Function Index:: Index of bindable Readline functions. + +* Concept Index:: General index for concepts described in + this manual. +@end menu +@end ifnottex + +@node Introduction +@chapter Introduction +@menu +* What is Bash?:: A short description of Bash. + +* What is a shell?:: A brief introduction to shells. +@end menu + +@node What is Bash? +@section What is Bash? + +Bash is the shell, or command language interpreter, +for the @sc{gnu} operating system. +The name is an acronym for the @samp{Bourne-Again SHell}, +a pun on Stephen Bourne, the author of the direct ancestor of +the current Unix shell @code{sh}, +which appeared in the Seventh Edition Bell Labs Research version +of Unix. + +Bash is largely compatible with @code{sh} and incorporates useful +features from the Korn shell @code{ksh} and the C shell @code{csh}. +It is intended to be a conformant implementation of the @sc{ieee} +@sc{posix} Shell and Tools specification (@sc{ieee} Working Group 1003.2). +It offers functional improvements over @code{sh} for both interactive and +programming use. + +While the @sc{gnu} operating system provides other shells, including +a version of @code{csh}, Bash is the default shell. +Like other @sc{gnu} software, Bash is quite portable. It currently runs +on nearly every version of Unix and a few other operating systems @minus{} +independently-supported ports exist for @sc{ms-dos}, @sc{os/2}, +and Windows platforms. + +@node What is a shell? +@section What is a shell? + +At its base, a shell is simply a macro processor that executes +commands. The term macro processor means functionality where text +and symbols are expanded to create larger expressions. + +A Unix shell is both a command interpreter and a programming +language. As a command interpreter, the shell provides the user +interface to the rich set of @sc{gnu} utilities. The programming +language features allow these utilitites to be combined. +Files containing commands can be created, and become +commands themselves. These new commands have the same status as +system commands in directories such as @file{/bin}, allowing users +or groups to establish custom environments to automate their common +tasks. + +Shells may be used interactively or non-interactively. In +interactive mode, they accept input typed from the keyboard. +When executing non-interactively, shells execute commands read +from a file. + +A shell allows execution of @sc{gnu} commands, both synchronously and +asynchronously. +The shell waits for synchronous commands to complete before accepting +more input; asynchronous commands continue to execute in parallel +with the shell while it reads and executes additional commands. +The @dfn{redirection} constructs permit +fine-grained control of the input and output of those commands. +Moreover, the shell allows control over the contents of commands' +environments. + +Shells also provide a small set of built-in +commands (@dfn{builtins}) implementing functionality impossible +or inconvenient to obtain via separate utilities. +For example, @code{cd}, @code{break}, @code{continue}, and +@code{exec}) cannot be implemented outside of the shell because +they directly manipulate the shell itself. +The @code{history}, @code{getopts}, @code{kill}, or @code{pwd} +builtins, among others, could be implemented in separate utilities, +but they are more convenient to use as builtin commands. +All of the shell builtins are described in +subsequent sections. + +While executing commands is essential, most of the power (and +complexity) of shells is due to their embedded programming +languages. Like any high-level language, the shell provides +variables, flow control constructs, quoting, and functions. + +Shells offer features geared specifically for +interactive use rather than to augment the programming language. +These interactive features include job control, command line +editing, command history and aliases. Each of these features is +described in this manual. + +@node Definitions +@chapter Definitions +These definitions are used throughout the remainder of this manual. + +@table @code + +@item POSIX +@cindex POSIX +A family of open system standards based on Unix. Bash +is concerned with @sc{posix} 1003.2, the Shell and Tools Standard. + +@item blank +A space or tab character. + +@item builtin +@cindex builtin +A command that is implemented internally by the shell itself, rather +than by an executable program somewhere in the file system. + +@item control operator +@cindex control operator +A @code{word} that performs a control function. It is a @code{newline} +or one of the following: +@samp{||}, @samp{&&}, @samp{&}, @samp{;}, @samp{;;}, +@samp{|}, @samp{(}, or @samp{)}. + +@item exit status +@cindex exit status +The value returned by a command to its caller. The value is restricted +to eight bits, so the maximum value is 255. + +@item field +@cindex field +A unit of text that is the result of one of the shell expansions. After +expansion, when executing a command, the resulting fields are used as +the command name and arguments. + +@item filename +@cindex filename +A string of characters used to identify a file. + +@item job +@cindex job +A set of processes comprising a pipeline, and any processes descended +from it, that are all in the same process group. + +@item job control +@cindex job control +A mechanism by which users can selectively stop (suspend) and restart +(resume) execution of processes. + +@item metacharacter +@cindex metacharacter +A character that, when unquoted, separates words. A metacharacter is +a @code{blank} or one of the following characters: +@samp{|}, @samp{&}, @samp{;}, @samp{(}, @samp{)}, @samp{<}, or +@samp{>}. + +@item name +@cindex name +@cindex identifier +A @code{word} consisting solely of letters, numbers, and underscores, +and beginning with a letter or underscore. @code{Name}s are used as +shell variable and function names. +Also referred to as an @code{identifier}. + +@item operator +@cindex operator, shell +A @code{control operator} or a @code{redirection operator}. +@xref{Redirections}, for a list of redirection operators. + +@item process group +@cindex process group +A collection of related processes each having the same process +group @sc{id}. + +@item process group ID +@cindex process group ID +A unique identifer that represents a @code{process group} +during its lifetime. + +@item reserved word +@cindex reserved word +A @code{word} that has a special meaning to the shell. Most reserved +words introduce shell flow control constructs, such as @code{for} and +@code{while}. + +@item return status +@cindex return status +A synonym for @code{exit status}. + +@item signal +@cindex signal +A mechanism by which a process may be notified by the kernel +of an event occurring in the system. + +@item special builtin +@cindex special builtin +A shell builtin command that has been classified as special by the +@sc{posix} 1003.2 standard. + +@item token +@cindex token +A sequence of characters considered a single unit by the shell. It is +either a @code{word} or an @code{operator}. + +@item word +@cindex word +A @code{token} that is not an @code{operator}. +@end table + +@node Basic Shell Features +@chapter Basic Shell Features +@cindex Bourne shell + +Bash is an acronym for @samp{Bourne-Again SHell}. +The Bourne shell is +the traditional Unix shell originally written by Stephen Bourne. +All of the Bourne shell builtin commands are available in Bash, +The rules for evaluation and quoting are taken from the @sc{posix} +specification for the `standard' Unix shell. + +This chapter briefly summarizes the shell's `building blocks': +commands, control structures, shell functions, shell @i{parameters}, +shell expansions, +@i{redirections}, which are a way to direct input and output from +and to named files, and how the shell executes commands. + +@menu +* Shell Syntax:: What your input means to the shell. +* Shell Commands:: The types of commands you can use. +* Shell Functions:: Grouping commands by name. +* Shell Parameters:: How the shell stores values. +* Shell Expansions:: How Bash expands parameters and the various + expansions available. +* Redirections:: A way to control where input and output go. +* Executing Commands:: What happens when you run a command. +* Shell Scripts:: Executing files of shell commands. +@end menu + +@node Shell Syntax +@section Shell Syntax +@menu +* Shell Operation:: The basic operation of the shell. + +* Quoting:: How to remove the special meaning from characters. + +* Comments:: How to specify comments. +@end menu + +When the shell reads input, it proceeds through a +sequence of operations. If the input indicates the beginning of a +comment, the shell ignores the comment symbol (@samp{#}), and the rest +of that line. + +Otherwise, roughly speaking, the shell reads its input and +divides the input into words and operators, employing the quoting rules +to select which meanings to assign various words and characters. + +The shell then parses these tokens into commands and other constructs, +removes the special meaning of certain words or characters, expands +others, redirects input and output as needed, executes the specified +command, waits for the command's exit status, and makes that exit status +available for further inspection or processing. + +@node Shell Operation +@subsection Shell Operation + +The following is a brief description of the shell's operation when it +reads and executes a command. Basically, the shell does the +following: + +@enumerate +@item +Reads its input from a file (@pxref{Shell Scripts}), from a string +supplied as an argument to the @option{-c} invocation option +(@pxref{Invoking Bash}), or from the user's terminal. + +@item +Breaks the input into words and operators, obeying the quoting rules +described in @ref{Quoting}. These tokens are separated by +@code{metacharacters}. Alias expansion is performed by this step +(@pxref{Aliases}). + +@item +Parses the tokens into simple and compound commands +(@pxref{Shell Commands}). + +@item +Performs the various shell expansions (@pxref{Shell Expansions}), breaking +the expanded tokens into lists of filenames (@pxref{Filename Expansion}) +and commands and arguments. + +@item +Performs any necessary redirections (@pxref{Redirections}) and removes +the redirection operators and their operands from the argument list. + +@item +Executes the command (@pxref{Executing Commands}). + +@item +Optionally waits for the command to complete and collects its exit +status (@pxref{Exit Status}). + +@end enumerate + +@node Quoting +@subsection Quoting +@cindex quoting +@menu +* Escape Character:: How to remove the special meaning from a single + character. +* Single Quotes:: How to inhibit all interpretation of a sequence + of characters. +* Double Quotes:: How to suppress most of the interpretation of a + sequence of characters. +* ANSI-C Quoting:: How to expand ANSI-C sequences in quoted strings. + +* Locale Translation:: How to translate strings into different languages. +@end menu + +Quoting is used to remove the special meaning of certain +characters or words to the shell. Quoting can be used to +disable special treatment for special characters, to prevent +reserved words from being recognized as such, and to prevent +parameter expansion. + +Each of the shell metacharacters (@pxref{Definitions}) +has special meaning to the shell and must be quoted if it is to +represent itself. +When the command history expansion facilities are being used, the +@var{history expansion} character, usually @samp{!}, must be quoted +to prevent history expansion. @xref{Bash History Facilities}, for +more details concerning history expansion. + +There are three quoting mechanisms: the +@var{escape character}, single quotes, and double quotes. + +@node Escape Character +@subsubsection Escape Character +A non-quoted backslash @samp{\} is the Bash escape character. +It preserves the literal value of the next character that follows, +with the exception of @code{newline}. If a @code{\newline} pair +appears, and the backslash itself is not quoted, the @code{\newline} +is treated as a line continuation (that is, it is removed from +the input stream and effectively ignored). + +@node Single Quotes +@subsubsection Single Quotes + +Enclosing characters in single quotes (@samp{'}) preserves the literal value +of each character within the quotes. A single quote may not occur +between single quotes, even when preceded by a backslash. + +@node Double Quotes +@subsubsection Double Quotes + +Enclosing characters in double quotes (@samp{"}) preserves the literal value +of all characters within the quotes, with the exception of +@samp{$}, @samp{`}, and @samp{\}. +The characters @samp{$} and @samp{`} +retain their special meaning within double quotes (@pxref{Shell Expansions}). +The backslash retains its special meaning only when followed by one of +the following characters: +@samp{$}, @samp{`}, @samp{"}, @samp{\}, or @code{newline}. +Within double quotes, backslashes that are followed by one of these +characters are removed. Backslashes preceding characters without a +special meaning are left unmodified. +A double quote may be quoted within double quotes by preceding it with +a backslash. +When command history is being used, the double quote may not be used to +quote the history expansion character. + +The special parameters @samp{*} and @samp{@@} have special meaning +when in double quotes (@pxref{Shell Parameter Expansion}). + +@node ANSI-C Quoting +@subsubsection ANSI-C Quoting +@cindex quoting, ANSI + +Words of the form @code{$'@var{string}'} are treated specially. The +word expands to @var{string}, with backslash-escaped characters replaced +as specified by the ANSI C standard. Backslash escape sequences, if +present, are decoded as follows: + +@table @code +@item \a +alert (bell) +@item \b +backspace +@item \e +an escape character (not ANSI C) +@item \f +form feed +@item \n +newline +@item \r +carriage return +@item \t +horizontal tab +@item \v +vertical tab +@item \\ +backslash +@item \' +single quote +@item \@var{nnn} +the eight-bit character whose value is the octal value @var{nnn} +(one to three digits) +@item \x@var{HH} +the eight-bit character whose value is the hexadecimal value @var{HH} +(one or two hex digits) +@item \c@var{x} +a control-@var{x} character +@end table + +@noindent +The expanded result is single-quoted, as if the dollar sign had not +been present. + +@node Locale Translation +@subsubsection Locale-Specific Translation +@cindex localization +@cindex internationalization +@cindex native languages +@cindex translation, native languages + +A double-quoted string preceded by a dollar sign (@samp{$}) will cause +the string to be translated according to the current locale. +If the current locale is @code{C} or @code{POSIX}, the dollar sign +is ignored. +If the string is translated and replaced, the replacement is +double-quoted. + +@vindex LC_MESSAGES +@vindex TEXTDOMAIN +@vindex TEXTDOMAINDIR +Some systems use the message catalog selected by the @env{LC_MESSAGES} +shell variable. Others create the name of the message catalog from the +value of the @env{TEXTDOMAIN} shell variable, possibly adding a +suffix of @samp{.mo}. If you use the @env{TEXTDOMAIN} variable, you +may need to set the @env{TEXTDOMAINDIR} variable to the location of +the message catalog files. Still others use both variables in this +fashion: +@env{TEXTDOMAINDIR}/@env{LC_MESSAGES}/LC_MESSAGES/@env{TEXTDOMAIN}.mo. + +@node Comments +@subsection Comments +@cindex comments, shell + +In a non-interactive shell, or an interactive shell in which the +@code{interactive_comments} option to the @code{shopt} +builtin is enabled (@pxref{Bash Builtins}), +a word beginning with @samp{#} +causes that word and all remaining characters on that line to +be ignored. An interactive shell without the @code{interactive_comments} +option enabled does not allow comments. The @code{interactive_comments} +option is on by default in interactive shells. +@xref{Interactive Shells}, for a description of what makes +a shell interactive. + +@node Shell Commands +@section Shell Commands +@cindex commands, shell + +A simple shell command such as @code{echo a b c} consists of the command +itself followed by arguments, separated by spaces. + +More complex shell commands are composed of simple commands arranged together +in a variety of ways: in a pipeline in which the output of one command +becomes the input of a second, in a loop or conditional construct, or in +some other grouping. + +@menu +* Simple Commands:: The most common type of command. +* Pipelines:: Connecting the input and output of several + commands. +* Lists:: How to execute commands sequentially. +* Compound Commands:: Shell commands for control flow. +@end menu + +@node Simple Commands +@subsection Simple Commands +@cindex commands, simple + +A simple command is the kind of command encountered most often. +It's just a sequence of words separated by @code{blank}s, terminated +by one of the shell's control operators (@pxref{Definitions}). The +first word generally specifies a command to be executed, with the +rest of the words being that command's arguments. + +The return status (@pxref{Exit Status}) of a simple command is +its exit status as provided +by the @sc{posix} 1003.1 @code{waitpid} function, or 128+@var{n} if +the command was terminated by signal @var{n}. + +@node Pipelines +@subsection Pipelines +@cindex pipeline +@cindex commands, pipelines + +A @code{pipeline} is a sequence of simple commands separated by +@samp{|}. + +@rwindex time +@rwindex ! +@cindex command timing +The format for a pipeline is +@example +[@code{time} [@code{-p}]] [@code{!}] @var{command1} [@code{|} @var{command2} @dots{}] +@end example + +@noindent +The output of each command in the pipeline is connected via a pipe +to the input of the next command. +That is, each command reads the previous command's output. + +The reserved word @code{time} causes timing statistics +to be printed for the pipeline once it finishes. +The statistics currently consist of elapsed (wall-clock) time and +user and system time consumed by the command's execution. +The @option{-p} option changes the output format to that specified +by @sc{posix}. +The @env{TIMEFORMAT} variable may be set to a format string that +specifies how the timing information should be displayed. +@xref{Bash Variables}, for a description of the available formats. +The use of @code{time} as a reserved word permits the timing of +shell builtins, shell functions, and pipelines. An external +@code{time} command cannot time these easily. + +If the pipeline is not executed asynchronously (@pxref{Lists}), the +shell waits for all commands in the pipeline to complete. + +Each command in a pipeline is executed in its own subshell +(@pxref{Command Execution Environment}). The exit +status of a pipeline is the exit status of the last command in the +pipeline, unless the @code{pipefail} option is enabled +(@pxref{The Set Builtin}). +If @code{pipefail} is enabled, the pipeline's return status is the +value of the last (rightmost) command to exit with a non-zero status, +or zero if all commands exit successfully. +If the reserved word @samp{!} precedes the pipeline, the +exit status is the logical negation of the exit status as described +above. +The shell waits for all commands in the pipeline to terminate before +returning a value. + +@node Lists +@subsection Lists of Commands +@cindex commands, lists + +A @code{list} is a sequence of one or more pipelines separated by one +of the operators @samp{;}, @samp{&}, @samp{&&}, or @samp{||}, +and optionally terminated by one of @samp{;}, @samp{&}, or a +@code{newline}. + +Of these list operators, @samp{&&} and @samp{||} +have equal precedence, followed by @samp{;} and @samp{&}, +which have equal precedence. + +A sequence of one or more newlines may appear in a @code{list} +to delimit commands, equivalent to a semicolon. + +If a command is terminated by the control operator @samp{&}, +the shell executes the command asynchronously in a subshell. +This is known as executing the command in the @var{background}. +The shell does not wait for the command to finish, and the return +status is 0 (true). +When job control is not active (@pxref{Job Control}), +the standard input for asynchronous commands, in the absence of any +explicit redirections, is redirected from @code{/dev/null}. + +Commands separated by a @samp{;} are executed sequentially; the shell +waits for each command to terminate in turn. The return status is the +exit status of the last command executed. + +The control operators @samp{&&} and @samp{||} +denote @sc{and} lists and @sc{or} lists, respectively. +An @sc{and} list has the form +@example +@var{command1} && @var{command2} +@end example + +@noindent +@var{command2} is executed if, and only if, @var{command1} +returns an exit status of zero. + +An @sc{or} list has the form +@example +@var{command1} || @var{command2} +@end example + +@noindent +@var{command2} is executed if, and only if, @var{command1} +returns a non-zero exit status. + +The return status of +@sc{and} and @sc{or} lists is the exit status of the last command +executed in the list. + +@node Compound Commands +@subsection Compound Commands +@cindex commands, compound + +@menu +* Looping Constructs:: Shell commands for iterative action. +* Conditional Constructs:: Shell commands for conditional execution. +* Command Grouping:: Ways to group commands. +@end menu + +Compound commands are the shell programming constructs. +Each construct begins with a reserved word or control operator and is +terminated by a corresponding reserved word or operator. +Any redirections (@pxref{Redirections}) associated with a compound command +apply to all commands within that compound command unless explicitly overridden. + +Bash provides looping constructs, conditional commands, and mechanisms +to group commands and execute them as a unit. + +@node Looping Constructs +@subsubsection Looping Constructs +@cindex commands, looping + +Bash supports the following looping constructs. + +Note that wherever a @samp{;} appears in the description of a +command's syntax, it may be replaced with one or more newlines. + +@table @code +@item until +@rwindex until +@rwindex do +@rwindex done +The syntax of the @code{until} command is: +@example +until @var{test-commands}; do @var{consequent-commands}; done +@end example +Execute @var{consequent-commands} as long as +@var{test-commands} has an exit status which is not zero. +The return status is the exit status of the last command executed +in @var{consequent-commands}, or zero if none was executed. + +@item while +@rwindex while +The syntax of the @code{while} command is: +@example +while @var{test-commands}; do @var{consequent-commands}; done +@end example + +Execute @var{consequent-commands} as long as +@var{test-commands} has an exit status of zero. +The return status is the exit status of the last command executed +in @var{consequent-commands}, or zero if none was executed. + +@item for +@rwindex for +The syntax of the @code{for} command is: + +@example +for @var{name} [in @var{words} @dots{}]; do @var{commands}; done +@end example +Expand @var{words}, and execute @var{commands} once for each member +in the resultant list, with @var{name} bound to the current member. +If @samp{in @var{words}} is not present, the @code{for} command +executes the @var{commands} once for each positional parameter that is +set, as if @samp{in "$@@"} had been specified +(@pxref{Special Parameters}). +The return status is the exit status of the last command that executes. +If there are no items in the expansion of @var{words}, no commands are +executed, and the return status is zero. + +An alternate form of the @code{for} command is also supported: + +@example +for (( @var{expr1} ; @var{expr2} ; @var{expr3} )) ; do @var{commands} ; done +@end example +First, the arithmetic expression @var{expr1} is evaluated according +to the rules described below (@pxref{Shell Arithmetic}). +The arithmetic expression @var{expr2} is then evaluated repeatedly +until it evaluates to zero. +Each time @var{expr2} evaluates to a non-zero value, @var{commands} are +executed and the arithmetic expression @var{expr3} is evaluated. +If any expression is omitted, it behaves as if it evaluates to 1. +The return value is the exit status of the last command in @var{list} +that is executed, or false if any of the expressions is invalid. + +@end table + +The @code{break} and @code{continue} builtins (@pxref{Bourne Shell Builtins}) +may be used to control loop execution. + +@node Conditional Constructs +@subsubsection Conditional Constructs +@cindex commands, conditional + +@table @code +@item if +@rwindex if +@rwindex then +@rwindex else +@rwindex elif +@rwindex fi +The syntax of the @code{if} command is: + +@example +if @var{test-commands}; then + @var{consequent-commands}; +[elif @var{more-test-commands}; then + @var{more-consequents};] +[else @var{alternate-consequents};] +fi +@end example + +The @var{test-commands} list is executed, and if its return status is zero, +the @var{consequent-commands} list is executed. +If @var{test-commands} returns a non-zero status, each @code{elif} list +is executed in turn, and if its exit status is zero, +the corresponding @var{more-consequents} is executed and the +command completes. +If @samp{else @var{alternate-consequents}} is present, and +the final command in the final @code{if} or @code{elif} clause +has a non-zero exit status, then @var{alternate-consequents} is executed. +The return status is the exit status of the last command executed, or +zero if no condition tested true. + +@item case +@rwindex case +@rwindex in +@rwindex esac +The syntax of the @code{case} command is: + +@example +@code{case @var{word} in [ [(] @var{pattern} [| @var{pattern}]@dots{}) @var{command-list} ;;]@dots{} esac} +@end example + +@code{case} will selectively execute the @var{command-list} corresponding to +the first @var{pattern} that matches @var{word}. +The @samp{|} is used to separate multiple patterns, and the @samp{)} +operator terminates a pattern list. +A list of patterns and an associated command-list is known +as a @var{clause}. Each clause must be terminated with @samp{;;}. +The @var{word} undergoes tilde expansion, parameter expansion, command +substitution, arithmetic expansion, and quote removal before matching is +attempted. Each @var{pattern} undergoes tilde expansion, parameter +expansion, command substitution, and arithmetic expansion. + +There may be an arbitrary number of @code{case} clauses, each terminated +by a @samp{;;}. The first pattern that matches determines the +command-list that is executed. + +Here is an example using @code{case} in a script that could be used to +describe one interesting feature of an animal: + +@example +echo -n "Enter the name of an animal: " +read ANIMAL +echo -n "The $ANIMAL has " +case $ANIMAL in + horse | dog | cat) echo -n "four";; + man | kangaroo ) echo -n "two";; + *) echo -n "an unknown number of";; +esac +echo " legs." +@end example + +@noindent +The return status is zero if no @var{pattern} is matched. Otherwise, the +return status is the exit status of the @var{command-list} executed. + +@item select +@rwindex select + +The @code{select} construct allows the easy generation of menus. +It has almost the same syntax as the @code{for} command: + +@example +select @var{name} [in @var{words} @dots{}]; do @var{commands}; done +@end example + +The list of words following @code{in} is expanded, generating a list +of items. The set of expanded words is printed on the standard +error output stream, each preceded by a number. If the +@samp{in @var{words}} is omitted, the positional parameters are printed, +as if @samp{in "$@@"} had been specifed. +The @env{PS3} prompt is then displayed and a line is read from the +standard input. +If the line consists of a number corresponding to one of the displayed +words, then the value of @var{name} is set to that word. +If the line is empty, the words and prompt are displayed again. +If @code{EOF} is read, the @code{select} command completes. +Any other value read causes @var{name} to be set to null. +The line read is saved in the variable @env{REPLY}. + +The @var{commands} are executed after each selection until a +@code{break} command is executed, at which +point the @code{select} command completes. + +Here is an example that allows the user to pick a filename from the +current directory, and displays the name and index of the file +selected. + +@example +select fname in *; +do + echo you picked $fname \($REPLY\) + break; +done +@end example + +@item ((@dots{})) +@example +(( @var{expression} )) +@end example + +The arithmetic @var{expression} is evaluated according to the rules +described below (@pxref{Shell Arithmetic}). +If the value of the expression is non-zero, the return status is 0; +otherwise the return status is 1. This is exactly equivalent to +@example +let "@var{expression}" +@end example +@noindent +@xref{Bash Builtins}, for a full description of the @code{let} builtin. + +@item [[@dots{}]] +@rwindex [[ +@rwindex ]] +@example +[[ @var{expression} ]] +@end example + +Return a status of 0 or 1 depending on the evaluation of +the conditional expression @var{expression}. +Expressions are composed of the primaries described below in +@ref{Bash Conditional Expressions}. +Word splitting and filename expansion are not performed on the words +between the @samp{[[} and @samp{]]}; tilde expansion, parameter and +variable expansion, arithmetic expansion, command substitution, process +substitution, and quote removal are performed. +Conditional operators such as @samp{-f} must be unquoted to be recognized +as primaries. + +When the @samp{==} and @samp{!=} operators are used, the string to the +right of the operator is considered a pattern and matched according +to the rules described below in @ref{Pattern Matching}. +The return value is 0 if the string matches or does not match +the pattern, respectively, and 1 otherwise. +Any part of the pattern may be quoted to force it to be matched as a +string. + +An additional binary operator, @samp{=~}, is available, with the same +precedence as @samp{==} and @samp{!=}. +When it is used, the string to the right of the operator is considered +an extended regular expression and matched accordingly (as in @i{regex}3)). +The return value is 0 if the string matches +the pattern, and 1 otherwise. +If the regular expression is syntactically incorrect, the conditional +expression's return value is 2. +If the shell option @code{nocaseglob} +(see the description of @code{shopt} in @ref{Bash Builtins}) +is enabled, the match is performed without regard to the case +of alphabetic characters. +Substrings matched by parenthesized subexpressions within the regular +expression are saved in the array variable @code{BASH_REMATCH}. +The element of @code{BASH_REMATCH} with index 0 is the portion of the string +matching the entire regular expression. +The element of @code{BASH_REMATCH} with index @var{n} is the portion of the +string matching the @var{n}th parenthesized subexpression. + +Expressions may be combined using the following operators, listed +in decreasing order of precedence: + +@table @code +@item ( @var{expression} ) +Returns the value of @var{expression}. +This may be used to override the normal precedence of operators. + +@item ! @var{expression} +True if @var{expression} is false. + +@item @var{expression1} && @var{expression2} +True if both @var{expression1} and @var{expression2} are true. + +@item @var{expression1} || @var{expression2} +True if either @var{expression1} or @var{expression2} is true. +@end table +@noindent +The @code{&&} and @code{||} operators do not evaluate @var{expression2} if the +value of @var{expression1} is sufficient to determine the return +value of the entire conditional expression. + +@end table + +@node Command Grouping +@subsubsection Grouping Commands +@cindex commands, grouping + +Bash provides two ways to group a list of commands to be executed +as a unit. When commands are grouped, redirections may be applied +to the entire command list. For example, the output of all the +commands in the list may be redirected to a single stream. + +@table @code +@item () +@example +( @var{list} ) +@end example + +Placing a list of commands between parentheses causes a subshell +environment to be created (@pxref{Command Execution Environment}), and each +of the commands in @var{list} to be executed in that subshell. Since the +@var{list} is executed in a subshell, variable assignments do not remain in +effect after the subshell completes. + +@item @{@} +@rwindex @{ +@rwindex @} +@example +@{ @var{list}; @} +@end example + +Placing a list of commands between curly braces causes the list to +be executed in the current shell context. No subshell is created. +The semicolon (or newline) following @var{list} is required. +@end table + +In addition to the creation of a subshell, there is a subtle difference +between these two constructs due to historical reasons. The braces +are @code{reserved words}, so they must be separated from the @var{list} +by @code{blank}s. The parentheses are @code{operators}, and are +recognized as separate tokens by the shell even if they are not separated +from the @var{list} by whitespace. + +The exit status of both of these constructs is the exit status of +@var{list}. + +@node Shell Functions +@section Shell Functions +@cindex shell function +@cindex functions, shell + +Shell functions are a way to group commands for later execution +using a single name for the group. They are executed just like +a "regular" command. +When the name of a shell function is used as a simple command name, +the list of commands associated with that function name is executed. +Shell functions are executed in the current +shell context; no new process is created to interpret them. + +Functions are declared using this syntax: +@rwindex function +@example +[ @code{function} ] @var{name} () @var{compound-command} [ @var{redirections} ] +@end example + +This defines a shell function named @var{name}. The reserved +word @code{function} is optional. +If the @code{function} reserved +word is supplied, the parentheses are optional. +The @var{body} of the function is the compound command +@var{compound-command} (@pxref{Compound Commands}). +That command is usually a @var{list} enclosed between @{ and @}, but +may be any compound command listed above. +@var{compound-command} is executed whenever @var{name} is specified as the +name of a command. +Any redirections (@pxref{Redirections}) associated with the shell function +are performed when the function is executed. + +The exit status of a function definition is zero unless a syntax error +occurs or a readonly function with the same name already exists. +When executed, the exit status of a function is the exit status of the +last command executed in the body. + +Note that for historical reasons, in the most common usage the curly braces +that surround the body of the function must be separated from the body by +@code{blank}s or newlines. +This is because the braces are reserved words and are only recognized +as such when they are separated by whitespace. +Also, when using the braces, the @var{list} must be terminated by a semicolon, +a @samp{&}, or a newline. + +When a function is executed, the arguments to the +function become the positional parameters +during its execution (@pxref{Positional Parameters}). +The special parameter @samp{#} that expands to the number of +positional parameters is updated to reflect the change. +Special parameter @code{0} is unchanged. +The first element of the @env{FUNCNAME} variable is set to the +name of the function while the function is executing. +All other aspects of the shell execution +environment are identical between a function and its caller +with the exception that the @env{DEBUG} trap +below) is not inherited unless the function has been given the +@code{trace} attribute using the @code{declare} builtin or +the @code{-o functrace} option has been enabled with +the @code{set} builtin, +(in which case all functions inherit the @code{DEBUG} trap). +@xref{Bourne Shell Builtins}, for the description of the +@code{trap} builtin. + +If the builtin command @code{return} +is executed in a function, the function completes and +execution resumes with the next command after the function +call. +Any command associated with the @code{RETURN} trap is executed +before execution resumes. +When a function completes, the values of the +positional parameters and the special parameter @samp{#} +are restored to the values they had prior to the function's +execution. If a numeric argument is given to @code{return}, +that is the function's return status; otherwise the function's +return status is the exit status of the last command executed +before the @code{return}. + +Variables local to the function may be declared with the +@code{local} builtin. These variables are visible only to +the function and the commands it invokes. + +Function names and definitions may be listed with the +@option{-f} option to the @code{declare} or @code{typeset} +builtin commands (@pxref{Bash Builtins}). +The @option{-F} option to @code{declare} or @code{typeset} +will list the function names only +(and optionally the source file and line number, if the @code{extdebug} +shell option is enabled). +Functions may be exported so that subshells +automatically have them defined with the +@option{-f} option to the @code{export} builtin +(@pxref{Bourne Shell Builtins}). +Note that shell functions and variables with the same name may result +in multiple identically-named entries in the environment passed to the +shell's children. +Care should be taken in cases where this may cause a problem. + +Functions may be recursive. No limit is placed on the number of +recursive calls. + +@node Shell Parameters +@section Shell Parameters +@cindex parameters +@cindex variable, shell +@cindex shell variable + +@menu +* Positional Parameters:: The shell's command-line arguments. +* Special Parameters:: Parameters denoted by special characters. +@end menu + +A @var{parameter} is an entity that stores values. +It can be a @code{name}, a number, or one of the special characters +listed below. +A @var{variable} is a parameter denoted by a @code{name}. +A variable has a @var{value} and zero or more @var{attributes}. +Attributes are assigned using the @code{declare} builtin command +(see the description of the @code{declare} builtin in @ref{Bash Builtins}). + +A parameter is set if it has been assigned a value. The null string is +a valid value. Once a variable is set, it may be unset only by using +the @code{unset} builtin command. + +A variable may be assigned to by a statement of the form +@example +@var{name}=[@var{value}] +@end example +@noindent +If @var{value} +is not given, the variable is assigned the null string. All +@var{value}s undergo tilde expansion, parameter and variable expansion, +command substitution, arithmetic expansion, and quote +removal (detailed below). If the variable has its @code{integer} +attribute set, then @var{value} +is evaluated as an arithmetic expression even if the @code{$((@dots{}))} +expansion is not used (@pxref{Arithmetic Expansion}). +Word splitting is not performed, with the exception +of @code{"$@@"} as explained below. +Filename expansion is not performed. +Assignment statements may also appear as arguments to the +@code{alias}, +@code{declare}, @code{typeset}, @code{export}, @code{readonly}, +and @code{local} builtin commands. + +@node Positional Parameters +@subsection Positional Parameters +@cindex parameters, positional + +A @var{positional parameter} is a parameter denoted by one or more +digits, other than the single digit @code{0}. Positional parameters are +assigned from the shell's arguments when it is invoked, +and may be reassigned using the @code{set} builtin command. +Positional parameter @code{N} may be referenced as @code{$@{N@}}, or +as @code{$N} when @code{N} consists of a single digit. +Positional parameters may not be assigned to with assignment statements. +The @code{set} and @code{shift} builtins are used to set and +unset them (@pxref{Shell Builtin Commands}). +The positional parameters are +temporarily replaced when a shell function is executed +(@pxref{Shell Functions}). + +When a positional parameter consisting of more than a single +digit is expanded, it must be enclosed in braces. + +@node Special Parameters +@subsection Special Parameters +@cindex parameters, special + +The shell treats several parameters specially. These parameters may +only be referenced; assignment to them is not allowed. + +@vtable @code + +@item * +Expands to the positional parameters, starting from one. When the +expansion occurs within double quotes, it expands to a single word +with the value of each parameter separated by the first character +of the @env{IFS} +special variable. That is, @code{"$*"} is equivalent +to @code{"$1@var{c}$2@var{c}@dots{}"}, where @var{c} +is the first character of the value of the @code{IFS} +variable. +If @env{IFS} is unset, the parameters are separated by spaces. +If @env{IFS} is null, the parameters are joined without intervening +separators. + + +@item @@ +Expands to the positional parameters, starting from one. When the +expansion occurs within double quotes, each parameter expands to a +separate word. That is, @code{"$@@"} is equivalent to +@code{"$1" "$2" @dots{}}. +When there are no positional parameters, @code{"$@@"} and +@code{$@@} +expand to nothing (i.e., they are removed). + +@item # +Expands to the number of positional parameters in decimal. + +@item ? +Expands to the exit status of the most recently executed foreground +pipeline. + +@item - +(A hyphen.) Expands to the current option flags as specified upon +invocation, by the @code{set} +builtin command, or those set by the shell itself +(such as the @option{-i} option). + +@item $ +Expands to the process @sc{id} of the shell. In a @code{()} subshell, it +expands to the process @sc{id} of the invoking shell, not the subshell. + +@item ! +Expands to the process @sc{id} of the most recently executed background +(asynchronous) command. + +@item 0 +Expands to the name of the shell or shell script. This is set at +shell initialization. If Bash is invoked with a file of commands +(@pxref{Shell Scripts}), @code{$0} is set to the name of that file. +If Bash is started with the @option{-c} option (@pxref{Invoking Bash}), +then @code{$0} is set to the first argument after the string to be +executed, if one is present. Otherwise, it is set +to the filename used to invoke Bash, as given by argument zero. + +@item _ +(An underscore.) +At shell startup, set to the absolute filename of the shell or shell +script being executed as passed in the argument list. +Subsequently, expands to the last argument to the previous command, +after expansion. +Also set to the full pathname of each command executed and placed in +the environment exported to that command. +When checking mail, this parameter holds the name of the mail file. +@end vtable + +@node Shell Expansions +@section Shell Expansions +@cindex expansion + +Expansion is performed on the command line after it has been split into +@code{token}s. There are seven kinds of expansion performed: +@itemize @bullet +@item brace expansion +@item tilde expansion +@item parameter and variable expansion +@item command substitution +@item arithmetic expansion +@item word splitting +@item filename expansion +@end itemize + +@menu +* Brace Expansion:: Expansion of expressions within braces. +* Tilde Expansion:: Expansion of the ~ character. +* Shell Parameter Expansion:: How Bash expands variables to their values. +* Command Substitution:: Using the output of a command as an argument. +* Arithmetic Expansion:: How to use arithmetic in shell expansions. +* Process Substitution:: A way to write and read to and from a + command. +* Word Splitting:: How the results of expansion are split into separate + arguments. +* Filename Expansion:: A shorthand for specifying filenames matching patterns. +* Quote Removal:: How and when quote characters are removed from + words. +@end menu + +The order of expansions is: brace expansion, tilde expansion, +parameter, variable, and arithmetic expansion and +command substitution +(done in a left-to-right fashion), word splitting, and filename +expansion. + +On systems that can support it, there is an additional expansion +available: @var{process substitution}. This is performed at the +same time as parameter, variable, and arithmetic expansion and +command substitution. + +Only brace expansion, word splitting, and filename expansion +can change the number of words of the expansion; other expansions +expand a single word to a single word. +The only exceptions to this are the expansions of +@code{"$@@"} (@pxref{Special Parameters}) and @code{"$@{@var{name}[@@]@}"} +(@pxref{Arrays}). + +After all expansions, @code{quote removal} (@pxref{Quote Removal}) +is performed. + +@node Brace Expansion +@subsection Brace Expansion +@cindex brace expansion +@cindex expansion, brace + +Brace expansion is a mechanism by which arbitrary strings may be generated. +This mechanism is similar to +@var{filename expansion} (@pxref{Filename Expansion}), +but the file names generated need not exist. +Patterns to be brace expanded take the form of an optional @var{preamble}, +followed by either a series of comma-separated strings or a sequnce expression +between a pair of braces, +followed by an optional @var{postscript}. +The preamble is prefixed to each string contained within the braces, and +the postscript is then appended to each resulting string, expanding left +to right. + +Brace expansions may be nested. +The results of each expanded string are not sorted; left to right order +is preserved. +For example, +@example +bash$ echo a@{d,c,b@}e +ade ace abe +@end example + +A sequence expression takes the form @code{@{@var{x}..@var{y}@}}, +where @var{x} and @var{y} are either integers or single characters. +When integers are supplied, the expression expands to each number between +@var{x} and @var{y}, inclusive. +When characters are supplied, the expression expands to each character +lexicographically between @var{x} and @var{y}, inclusive. Note that +both @var{x} and @var{y} must be of the same type. + +Brace expansion is performed before any other expansions, +and any characters special to other expansions are preserved +in the result. It is strictly textual. Bash +does not apply any syntactic interpretation to the context of the +expansion or the text between the braces. +To avoid conflicts with parameter expansion, the string @samp{$@{} +is not considered eligible for brace expansion. + +A correctly-formed brace expansion must contain unquoted opening +and closing braces, and at least one unquoted comma or a valid +sequence expression. +Any incorrectly formed brace expansion is left unchanged. + +A @{ or @samp{,} may be quoted with a backslash to prevent its +being considered part of a brace expression. +To avoid conflicts with parameter expansion, the string @samp{$@{} +is not considered eligible for brace expansion. + +This construct is typically used as shorthand when the common +prefix of the strings to be generated is longer than in the +above example: +@example +mkdir /usr/local/src/bash/@{old,new,dist,bugs@} +@end example +or +@example +chown root /usr/@{ucb/@{ex,edit@},lib/@{ex?.?*,how_ex@}@} +@end example + +@node Tilde Expansion +@subsection Tilde Expansion +@cindex tilde expansion +@cindex expansion, tilde + +If a word begins with an unquoted tilde character (@samp{~}), all of the +characters up to the first unquoted slash (or all characters, +if there is no unquoted slash) are considered a @var{tilde-prefix}. +If none of the characters in the tilde-prefix are quoted, the +characters in the tilde-prefix following the tilde are treated as a +possible @var{login name}. +If this login name is the null string, the tilde is replaced with the +value of the @env{HOME} shell variable. +If @env{HOME} is unset, the home directory of the user executing the +shell is substituted instead. +Otherwise, the tilde-prefix is replaced with the home directory +associated with the specified login name. + +If the tilde-prefix is @samp{~+}, the value of +the shell variable @env{PWD} replaces the tilde-prefix. +If the tilde-prefix is @samp{~-}, the value of the shell variable +@env{OLDPWD}, if it is set, is substituted. + +If the characters following the tilde in the tilde-prefix consist of a +number @var{N}, optionally prefixed by a @samp{+} or a @samp{-}, +the tilde-prefix is replaced with the +corresponding element from the directory stack, as it would be displayed +by the @code{dirs} builtin invoked with the characters following tilde +in the tilde-prefix as an argument (@pxref{The Directory Stack}). +If the tilde-prefix, sans the tilde, consists of a number without a +leading @samp{+} or @samp{-}, @samp{+} is assumed. + +If the login name is invalid, or the tilde expansion fails, the word is +left unchanged. + +Each variable assignment is checked for unquoted tilde-prefixes immediately +following a @samp{:} or @samp{=}. +In these cases, tilde expansion is also performed. +Consequently, one may use file names with tildes in assignments to +@env{PATH}, @env{MAILPATH}, and @env{CDPATH}, +and the shell assigns the expanded value. + +The following table shows how Bash treats unquoted tilde-prefixes: + +@table @code +@item ~ +The value of @code{$HOME} +@item ~/foo +@file{$HOME/foo} + +@item ~fred/foo +The subdirectory @code{foo} of the home directory of the user +@code{fred} + +@item ~+/foo +@file{$PWD/foo} + +@item ~-/foo +@file{$@{OLDPWD-'~-'@}/foo} + +@item ~@var{N} +The string that would be displayed by @samp{dirs +@var{N}} + +@item ~+@var{N} +The string that would be displayed by @samp{dirs +@var{N}} + +@item ~-@var{N} +The string that would be displayed by @samp{dirs -@var{N}} + +@end table + +@node Shell Parameter Expansion +@subsection Shell Parameter Expansion +@cindex parameter expansion +@cindex expansion, parameter + +The @samp{$} character introduces parameter expansion, +command substitution, or arithmetic expansion. The parameter name +or symbol to be expanded may be enclosed in braces, which +are optional but serve to protect the variable to be expanded from +characters immediately following it which could be +interpreted as part of the name. + +When braces are used, the matching ending brace is the first @samp{@}} +not escaped by a backslash or within a quoted string, and not within an +embedded arithmetic expansion, command substitution, or parameter +expansion. + +The basic form of parameter expansion is $@{@var{parameter}@}. +The value of @var{parameter} is substituted. The braces are required +when @var{parameter} +is a positional parameter with more than one digit, +or when @var{parameter} +is followed by a character that is not to be +interpreted as part of its name. + +If the first character of @var{parameter} is an exclamation point, +a level of variable indirection is introduced. +Bash uses the value of the variable formed from the rest of +@var{parameter} as the name of the variable; this variable is then +expanded and that value is used in the rest of the substitution, rather +than the value of @var{parameter} itself. +This is known as @code{indirect expansion}. +The exceptions to this are the expansions of $@{!@var{prefix*}@} +and $@{!@var{name}[@@]@} +described below. +The exclamation point must immediately follow the left brace in order to +introduce indirection. + +In each of the cases below, @var{word} is subject to tilde expansion, +parameter expansion, command substitution, and arithmetic expansion. + +When not performing substring expansion, Bash tests for a parameter +that is unset or null; omitting the colon results in a test only for a +parameter that is unset. Put another way, if the colon is included, +the operator tests for both existence and that the value is not null; +if the colon is omitted, the operator tests only for existence. + +@table @code + +@item $@{@var{parameter}:@minus{}@var{word}@} +If @var{parameter} is unset or null, the expansion of +@var{word} is substituted. Otherwise, the value of +@var{parameter} is substituted. + +@item $@{@var{parameter}:=@var{word}@} +If @var{parameter} +is unset or null, the expansion of @var{word} +is assigned to @var{parameter}. +The value of @var{parameter} is then substituted. +Positional parameters and special parameters may not be assigned to +in this way. + +@item $@{@var{parameter}:?@var{word}@} +If @var{parameter} +is null or unset, the expansion of @var{word} (or a message +to that effect if @var{word} +is not present) is written to the standard error and the shell, if it +is not interactive, exits. Otherwise, the value of @var{parameter} is +substituted. + +@item $@{@var{parameter}:+@var{word}@} +If @var{parameter} +is null or unset, nothing is substituted, otherwise the expansion of +@var{word} is substituted. + +@item $@{@var{parameter}:@var{offset}@} +@itemx $@{@var{parameter}:@var{offset}:@var{length}@} +Expands to up to @var{length} characters of @var{parameter} +starting at the character specified by @var{offset}. +If @var{length} is omitted, expands to the substring of +@var{parameter} starting at the character specified by @var{offset}. +@var{length} and @var{offset} are arithmetic expressions +(@pxref{Shell Arithmetic}). +This is referred to as Substring Expansion. + +@var{length} must evaluate to a number greater than or equal to zero. +If @var{offset} evaluates to a number less than zero, the value +is used as an offset from the end of the value of @var{parameter}. +If @var{parameter} is @samp{@@}, the result is @var{length} positional +parameters beginning at @var{offset}. +If @var{parameter} is an array name indexed by @samp{@@} or @samp{*}, +the result is the @var{length} +members of the array beginning with @code{$@{@var{parameter}[@var{offset}]@}}. +Substring indexing is zero-based unless the positional parameters +are used, in which case the indexing starts at 1. + +@item $@{!@var{prefix}*@} +@itemx $@{!@var{prefix}@@@} +Expands to the names of variables whose names begin with @var{prefix}, +separated by the first character of the @env{IFS} special variable. + +@item $@{!@var{name}[@@]@} +@itemx $@{!@var{name}[*]@} +If @var{name} is an array variable, expands to the list of array indices +(keys) assigned in @var{name}. +If @var{name} is not an array, expands to 0 if @var{name} is set and null +otherwise. +When @samp{@@} is used and the expansion appears within double quotes, each +key expands to a separate word. + +@item $@{#@var{parameter}@} +The length in characters of the expanded value of @var{parameter} is +substituted. +If @var{parameter} is @samp{*} or @samp{@@}, the value substituted +is the number of positional parameters. +If @var{parameter} is an array name subscripted by @samp{*} or @samp{@@}, +the value substituted is the number of elements in the array. + +@item $@{@var{parameter}#@var{word}@} +@itemx $@{@var{parameter}##@var{word}@} +The @var{word} +is expanded to produce a pattern just as in filename +expansion (@pxref{Filename Expansion}). If the pattern matches +the beginning of the expanded value of @var{parameter}, +then the result of the expansion is the expanded value of @var{parameter} +with the shortest matching pattern (the @samp{#} case) or the +longest matching pattern (the @samp{##} case) deleted. +If @var{parameter} is @samp{@@} or @samp{*}, +the pattern removal operation is applied to each positional +parameter in turn, and the expansion is the resultant list. +If @var{parameter} is an array variable subscripted with +@samp{@@} or @samp{*}, +the pattern removal operation is applied to each member of the +array in turn, and the expansion is the resultant list. + +@item $@{@var{parameter}%@var{word}@} +@itemx $@{@var{parameter}%%@var{word}@} +The @var{word} is expanded to produce a pattern just as in +filename expansion. +If the pattern matches a trailing portion of the expanded value of +@var{parameter}, then the result of the expansion is the value of +@var{parameter} with the shortest matching pattern (the @samp{%} case) +or the longest matching pattern (the @samp{%%} case) deleted. +If @var{parameter} is @samp{@@} or @samp{*}, +the pattern removal operation is applied to each positional +parameter in turn, and the expansion is the resultant list. +If @var{parameter} +is an array variable subscripted with @samp{@@} or @samp{*}, +the pattern removal operation is applied to each member of the +array in turn, and the expansion is the resultant list. + +@item $@{@var{parameter}/@var{pattern}/@var{string}@} +@itemx $@{@var{parameter}//@var{pattern}/@var{string}@} + +The @var{pattern} is expanded to produce a pattern just as in +filename expansion. +@var{Parameter} is expanded and the longest match of @var{pattern} +against its value is replaced with @var{string}. +In the first form, only the first match is replaced. +The second form causes all matches of @var{pattern} to be +replaced with @var{string}. +If @var{pattern} begins with @samp{#}, it must match at the beginning +of the expanded value of @var{parameter}. +If @var{pattern} begins with @samp{%}, it must match at the end +of the expanded value of @var{parameter}. +If @var{string} is null, matches of @var{pattern} are deleted +and the @code{/} following @var{pattern} may be omitted. +If @var{parameter} is @samp{@@} or @samp{*}, +the substitution operation is applied to each positional +parameter in turn, and the expansion is the resultant list. +If @var{parameter} +is an array variable subscripted with @samp{@@} or @samp{*}, +the substitution operation is applied to each member of the +array in turn, and the expansion is the resultant list. + +@end table + +@node Command Substitution +@subsection Command Substitution +@cindex command substitution + +Command substitution allows the output of a command to replace +the command itself. +Command substitution occurs when a command is enclosed as follows: +@example +$(@var{command}) +@end example +@noindent +or +@example +`@var{command}` +@end example + +@noindent +Bash performs the expansion by executing @var{command} and +replacing the command substitution with the standard output of the +command, with any trailing newlines deleted. +Embedded newlines are not deleted, but they may be removed during +word splitting. +The command substitution @code{$(cat @var{file})} can be +replaced by the equivalent but faster @code{$(< @var{file})}. + +When the old-style backquote form of substitution is used, +backslash retains its literal meaning except when followed by +@samp{$}, @samp{`}, or @samp{\}. +The first backquote not preceded by a backslash terminates the +command substitution. +When using the @code{$(@var{command})} form, all characters between +the parentheses make up the command; none are treated specially. + +Command substitutions may be nested. To nest when using the backquoted +form, escape the inner backquotes with backslashes. + +If the substitution appears within double quotes, word splitting and +filename expansion are not performed on the results. + +@node Arithmetic Expansion +@subsection Arithmetic Expansion +@cindex expansion, arithmetic +@cindex arithmetic expansion + +Arithmetic expansion allows the evaluation of an arithmetic expression +and the substitution of the result. The format for arithmetic expansion is: + +@example +$(( @var{expression} )) +@end example + +The expression is treated as if it were within double quotes, but +a double quote inside the parentheses is not treated specially. +All tokens in the expression undergo parameter expansion, command +substitution, and quote removal. +Arithmetic expansions may be nested. + +The evaluation is performed according to the rules listed below +(@pxref{Shell Arithmetic}). +If the expression is invalid, Bash prints a message indicating +failure to the standard error and no substitution occurs. + +@node Process Substitution +@subsection Process Substitution +@cindex process substitution + +Process substitution is supported on systems that support named +pipes (@sc{fifo}s) or the @file{/dev/fd} method of naming open files. +It takes the form of +@example +<(@var{list}) +@end example +@noindent +or +@example +>(@var{list}) +@end example +@noindent +The process @var{list} is run with its input or output connected to a +@sc{fifo} or some file in @file{/dev/fd}. The name of this file is +passed as an argument to the current command as the result of the +expansion. If the @code{>(@var{list})} form is used, writing to +the file will provide input for @var{list}. If the +@code{<(@var{list})} form is used, the file passed as an +argument should be read to obtain the output of @var{list}. +Note that no space may appear between the @code{<} or @code{>} +and the left parenthesis, otherwise the construct would be interpreted +as a redirection. + +When available, process substitution is performed simultaneously with +parameter and variable expansion, command substitution, and arithmetic +expansion. + +@node Word Splitting +@subsection Word Splitting +@cindex word splitting + +The shell scans the results of parameter expansion, command substitution, +and arithmetic expansion that did not occur within double quotes for +word splitting. + +The shell treats each character of @env{$IFS} +as a delimiter, and splits the results of the other +expansions into words on these characters. If +@env{IFS} is unset, or its value is exactly @code{}, +the default, then any sequence of @env{IFS} +characters serves to delimit words. If @env{IFS} +has a value other than the default, then sequences of +the whitespace characters @code{space} and @code{tab} +are ignored at the beginning and end of the +word, as long as the whitespace character is in the +value of @env{IFS} (an @env{IFS} whitespace character). +Any character in @env{IFS} that is not @env{IFS} +whitespace, along with any adjacent @env{IFS} +whitespace characters, delimits a field. A sequence of @env{IFS} +whitespace characters is also treated as a delimiter. +If the value of @env{IFS} is null, no word splitting occurs. + +Explicit null arguments (@code{""} or @code{''}) are retained. +Unquoted implicit null arguments, resulting from the expansion of +parameters that have no values, are removed. +If a parameter with no value is expanded within double quotes, a +null argument results and is retained. + +Note that if no expansion occurs, no splitting +is performed. + +@node Filename Expansion +@subsection Filename Expansion +@menu +* Pattern Matching:: How the shell matches patterns. +@end menu +@cindex expansion, filename +@cindex expansion, pathname +@cindex filename expansion +@cindex pathname expansion + +After word splitting, unless the @option{-f} option has been set +(@pxref{The Set Builtin}), Bash scans each word for the characters +@samp{*}, @samp{?}, and @samp{[}. +If one of these characters appears, then the word is +regarded as a @var{pattern}, +and replaced with an alphabetically sorted list of +file names matching the pattern. If no matching file names are found, +and the shell option @code{nullglob} is disabled, the word is left +unchanged. +If the @code{nullglob} option is set, and no matches are found, the word +is removed. +If the @code{failglob} shell option is set, and no matches are found, +an error message is printed and the command is not executed. +If the shell option @code{nocaseglob} is enabled, the match is performed +without regard to the case of alphabetic characters. + +When a pattern is used for filename generation, the character @samp{.} +at the start of a filename or immediately following a slash +must be matched explicitly, unless the shell option @code{dotglob} is set. +When matching a file name, the slash character must always be +matched explicitly. +In other cases, the @samp{.} character is not treated specially. + +See the description of @code{shopt} in @ref{Bash Builtins}, +for a description of the @code{nocaseglob}, @code{nullglob}, +@code{failglob}, and @code{dotglob} options. + +The @env{GLOBIGNORE} +shell variable may be used to restrict the set of filenames matching a +pattern. If @env{GLOBIGNORE} +is set, each matching filename that also matches one of the patterns in +@env{GLOBIGNORE} is removed from the list of matches. The filenames +@file{.} and @file{..} +are always ignored when @env{GLOBIGNORE} +is set and not null. +However, setting @env{GLOBIGNORE} to a non-null value has the effect of +enabling the @code{dotglob} +shell option, so all other filenames beginning with a +@samp{.} will match. +To get the old behavior of ignoring filenames beginning with a +@samp{.}, make @samp{.*} one of the patterns in @env{GLOBIGNORE}. +The @code{dotglob} option is disabled when @env{GLOBIGNORE} +is unset. + +@node Pattern Matching +@subsubsection Pattern Matching +@cindex pattern matching +@cindex matching, pattern + +Any character that appears in a pattern, other than the special pattern +characters described below, matches itself. +The @sc{nul} character may not occur in a pattern. +A backslash escapes the following character; the +escaping backslash is discarded when matching. +The special pattern characters must be quoted if they are to be matched +literally. + +The special pattern characters have the following meanings: +@table @code +@item * +Matches any string, including the null string. +@item ? +Matches any single character. +@item [@dots{}] +Matches any one of the enclosed characters. A pair of characters +separated by a hyphen denotes a @var{range expression}; +any character that sorts between those two characters, inclusive, +using the current locale's collating sequence and character set, +is matched. If the first character following the +@samp{[} is a @samp{!} or a @samp{^} +then any character not enclosed is matched. A @samp{@minus{}} +may be matched by including it as the first or last character +in the set. A @samp{]} may be matched by including it as the first +character in the set. +The sorting order of characters in range expressions is determined by +the current locale and the value of the @env{LC_COLLATE} shell variable, +if set. + +For example, in the default C locale, @samp{[a-dx-z]} is equivalent to +@samp{[abcdxyz]}. Many locales sort characters in dictionary order, and in +these locales @samp{[a-dx-z]} is typically not equivalent to @samp{[abcdxyz]}; +it might be equivalent to @samp{[aBbCcDdxXyYz]}, for example. To obtain +the traditional interpretation of ranges in bracket expressions, you can +force the use of the C locale by setting the @env{LC_COLLATE} or +@env{LC_ALL} environment variable to the value @samp{C}. + +Within @samp{[} and @samp{]}, @var{character classes} can be specified +using the syntax +@code{[:}@var{class}@code{:]}, where @var{class} is one of the +following classes defined in the @sc{posix} 1003.2 standard: +@example +alnum alpha ascii blank cntrl digit graph lower +print punct space upper word xdigit +@end example +@noindent +A character class matches any character belonging to that class. +The @code{word} character class matches letters, digits, and the character +@samp{_}. + +Within @samp{[} and @samp{]}, an @var{equivalence class} can be +specified using the syntax @code{[=}@var{c}@code{=]}, which +matches all characters with the same collation weight (as defined +by the current locale) as the character @var{c}. + +Within @samp{[} and @samp{]}, the syntax @code{[.}@var{symbol}@code{.]} +matches the collating symbol @var{symbol}. +@end table + +If the @code{extglob} shell option is enabled using the @code{shopt} +builtin, several extended pattern matching operators are recognized. +In the following description, a @var{pattern-list} is a list of one +or more patterns separated by a @samp{|}. +Composite patterns may be formed using one or more of the following +sub-patterns: + +@table @code +@item ?(@var{pattern-list}) +Matches zero or one occurrence of the given patterns. + +@item *(@var{pattern-list}) +Matches zero or more occurrences of the given patterns. + +@item +(@var{pattern-list}) +Matches one or more occurrences of the given patterns. + +@item @@(@var{pattern-list}) +Matches exactly one of the given patterns. + +@item !(@var{pattern-list}) +Matches anything except one of the given patterns. +@end table + +@node Quote Removal +@subsection Quote Removal + +After the preceding expansions, all unquoted occurrences of the +characters @samp{\}, @samp{'}, and @samp{"} that did not +result from one of the above expansions are removed. + +@node Redirections +@section Redirections +@cindex redirection + +Before a command is executed, its input and output +may be @var{redirected} +using a special notation interpreted by the shell. +Redirection may also be used to open and close files for the +current shell execution environment. The following redirection +operators may precede or appear anywhere within a +simple command or may follow a command. +Redirections are processed in the order they appear, from +left to right. + +In the following descriptions, if the file descriptor number is +omitted, and the first character of the redirection operator is +@samp{<}, the redirection refers to the standard input (file +descriptor 0). If the first character of the redirection operator +is @samp{>}, the redirection refers to the standard output (file +descriptor 1). + +The word following the redirection operator in the following +descriptions, unless otherwise noted, is subjected to brace expansion, +tilde expansion, parameter expansion, command substitution, arithmetic +expansion, quote removal, filename expansion, and word splitting. +If it expands to more than one word, Bash reports an error. + +Note that the order of redirections is significant. For example, +the command +@example +ls > @var{dirlist} 2>&1 +@end example +@noindent +directs both standard output (file descriptor 1) and standard error +(file descriptor 2) to the file @var{dirlist}, while the command +@example +ls 2>&1 > @var{dirlist} +@end example +@noindent +directs only the standard output to file @var{dirlist}, +because the standard error was duplicated as standard output +before the standard output was redirected to @var{dirlist}. + +Bash handles several filenames specially when they are used in +redirections, as described in the following table: + +@table @code +@item /dev/fd/@var{fd} +If @var{fd} is a valid integer, file descriptor @var{fd} is duplicated. + +@item /dev/stdin +File descriptor 0 is duplicated. + +@item /dev/stdout +File descriptor 1 is duplicated. + +@item /dev/stderr +File descriptor 2 is duplicated. + +@item /dev/tcp/@var{host}/@var{port} +If @var{host} is a valid hostname or Internet address, and @var{port} +is an integer port number or service name, Bash attempts to open a TCP +connection to the corresponding socket. + +@item /dev/udp/@var{host}/@var{port} +If @var{host} is a valid hostname or Internet address, and @var{port} +is an integer port number or service name, Bash attempts to open a UDP +connection to the corresponding socket. + +@end table + +A failure to open or create a file causes the redirection to fail. + +@subsection Redirecting Input +Redirection of input causes the file whose name results from +the expansion of @var{word} +to be opened for reading on file descriptor @code{n}, +or the standard input (file descriptor 0) if @code{n} +is not specified. + +The general format for redirecting input is: +@example +[@var{n}]<@var{word} +@end example + +@subsection Redirecting Output +Redirection of output causes the file whose name results from +the expansion of @var{word} +to be opened for writing on file descriptor @var{n}, +or the standard output (file descriptor 1) if @var{n} +is not specified. If the file does not exist it is created; +if it does exist it is truncated to zero size. + +The general format for redirecting output is: +@example +[@var{n}]>[|]@var{word} +@end example + +If the redirection operator is @samp{>}, and the @code{noclobber} +option to the @code{set} builtin has been enabled, the redirection +will fail if the file whose name results from the expansion of +@var{word} exists and is a regular file. +If the redirection operator is @samp{>|}, or the redirection operator is +@samp{>} and the @code{noclobber} option is not enabled, the redirection +is attempted even if the file named by @var{word} exists. + +@subsection Appending Redirected Output +Redirection of output in this fashion +causes the file whose name results from +the expansion of @var{word} +to be opened for appending on file descriptor @var{n}, +or the standard output (file descriptor 1) if @var{n} +is not specified. If the file does not exist it is created. + +The general format for appending output is: +@example +[@var{n}]>>@var{word} +@end example + +@subsection Redirecting Standard Output and Standard Error +Bash allows both the +standard output (file descriptor 1) and +the standard error output (file descriptor 2) +to be redirected to the file whose name is the +expansion of @var{word} with this construct. + +There are two formats for redirecting standard output and +standard error: +@example +&>@var{word} +@end example +@noindent +and +@example +>&@var{word} +@end example +@noindent +Of the two forms, the first is preferred. +This is semantically equivalent to +@example +>@var{word} 2>&1 +@end example + +@subsection Here Documents +This type of redirection instructs the shell to read input from the +current source until a line containing only @var{word} +(with no trailing blanks) is seen. All of +the lines read up to that point are then used as the standard +input for a command. + +The format of here-documents is: +@example +<<[@minus{}]@var{word} + @var{here-document} +@var{delimiter} +@end example + +No parameter expansion, command substitution, arithmetic expansion, +or filename expansion is performed on +@var{word}. If any characters in @var{word} are quoted, the +@var{delimiter} is the result of quote removal on @var{word}, +and the lines in the here-document are not expanded. +If @var{word} is unquoted, +all lines of the here-document are subjected to parameter expansion, +command substitution, and arithmetic expansion. In the latter +case, the character sequence @code{\newline} is ignored, and @samp{\} +must be used to quote the characters +@samp{\}, @samp{$}, and @samp{`}. + +If the redirection operator is @samp{<<-}, +then all leading tab characters are stripped from input lines and the +line containing @var{delimiter}. +This allows here-documents within shell scripts to be indented in a +natural fashion. + +@subsection Here Strings +A variant of here documents, the format is: +@example +<<< @var{word} +@end example + +The @var{word} is expanded and supplied to the command on its standard +input. + +@subsection Duplicating File Descriptors +The redirection operator +@example +[@var{n}]<&@var{word} +@end example +@noindent +is used to duplicate input file descriptors. +If @var{word} +expands to one or more digits, the file descriptor denoted by @var{n} +is made to be a copy of that file descriptor. +If the digits in @var{word} do not specify a file descriptor open for +input, a redirection error occurs. +If @var{word} +evaluates to @samp{-}, file descriptor @var{n} is closed. If +@var{n} is not specified, the standard input (file descriptor 0) is used. + +The operator +@example +[@var{n}]>&@var{word} +@end example +@noindent +is used similarly to duplicate output file descriptors. If +@var{n} is not specified, the standard output (file descriptor 1) is used. +If the digits in @var{word} do not specify a file descriptor open for +output, a redirection error occurs. +As a special case, if @var{n} is omitted, and @var{word} does not +expand to one or more digits, the standard output and standard +error are redirected as described previously. + +@subsection Moving File Descriptors +The redirection operator +@example +[@var{n}]<&@var{digit}- +@end example +@noindent +moves the file descriptor @var{digit} to file descriptor @var{n}, +or the standard input (file descriptor 0) if @var{n} is not specified. +@var{digit} is closed after being duplicated to @var{n}. + +Similarly, the redirection operator +@example +[@var{n}]>&@var{digit}- +@end example +@noindent +moves the file descriptor @var{digit} to file descriptor @var{n}, +or the standard output (file descriptor 1) if @var{n} is not specified. + +@subsection Opening File Descriptors for Reading and Writing +The redirection operator +@example +[@var{n}]<>@var{word} +@end example +@noindent +causes the file whose name is the expansion of @var{word} +to be opened for both reading and writing on file descriptor +@var{n}, or on file descriptor 0 if @var{n} +is not specified. If the file does not exist, it is created. + +@node Executing Commands +@section Executing Commands + +@menu +* Simple Command Expansion:: How Bash expands simple commands before + executing them. + +* Command Search and Execution:: How Bash finds commands and runs them. + +* Command Execution Environment:: The environment in which Bash + executes commands that are not + shell builtins. + +* Environment:: The environment given to a command. + +* Exit Status:: The status returned by commands and how Bash + interprets it. + +* Signals:: What happens when Bash or a command it runs + receives a signal. + +@end menu + +@node Simple Command Expansion +@subsection Simple Command Expansion +@cindex command expansion + +When a simple command is executed, the shell performs the following +expansions, assignments, and redirections, from left to right. + +@enumerate +@item +The words that the parser has marked as variable assignments (those +preceding the command name) and redirections are saved for later +processing. + +@item +The words that are not variable assignments or redirections are +expanded (@pxref{Shell Expansions}). +If any words remain after expansion, the first word +is taken to be the name of the command and the remaining words are +the arguments. + +@item +Redirections are performed as described above (@pxref{Redirections}). + +@item +The text after the @samp{=} in each variable assignment undergoes tilde +expansion, parameter expansion, command substitution, arithmetic expansion, +and quote removal before being assigned to the variable. +@end enumerate + +If no command name results, the variable assignments affect the current +shell environment. Otherwise, the variables are added to the environment +of the executed command and do not affect the current shell environment. +If any of the assignments attempts to assign a value to a readonly variable, +an error occurs, and the command exits with a non-zero status. + +If no command name results, redirections are performed, but do not +affect the current shell environment. A redirection error causes the +command to exit with a non-zero status. + +If there is a command name left after expansion, execution proceeds as +described below. Otherwise, the command exits. If one of the expansions +contained a command substitution, the exit status of the command is +the exit status of the last command substitution performed. If there +were no command substitutions, the command exits with a status of zero. + +@node Command Search and Execution +@subsection Command Search and Execution +@cindex command execution +@cindex command search + +After a command has been split into words, if it results in a +simple command and an optional list of arguments, the following +actions are taken. + +@enumerate +@item +If the command name contains no slashes, the shell attempts to +locate it. If there exists a shell function by that name, that +function is invoked as described in @ref{Shell Functions}. + +@item +If the name does not match a function, the shell searches for +it in the list of shell builtins. If a match is found, that +builtin is invoked. + +@item +If the name is neither a shell function nor a builtin, +and contains no slashes, Bash searches each element of +@env{$PATH} for a directory containing an executable file +by that name. Bash uses a hash table to remember the full +pathnames of executable files to avoid multiple @env{PATH} searches +(see the description of @code{hash} in @ref{Bourne Shell Builtins}). +A full search of the directories in @env{$PATH} +is performed only if the command is not found in the hash table. +If the search is unsuccessful, the shell prints an error +message and returns an exit status of 127. + +@item +If the search is successful, or if the command name contains +one or more slashes, the shell executes the named program in +a separate execution environment. +Argument 0 is set to the name given, and the remaining arguments +to the command are set to the arguments supplied, if any. + +@item +If this execution fails because the file is not in executable +format, and the file is not a directory, it is assumed to be a +@var{shell script} and the shell executes it as described in +@ref{Shell Scripts}. + +@item +If the command was not begun asynchronously, the shell waits for +the command to complete and collects its exit status. + +@end enumerate + +@node Command Execution Environment +@subsection Command Execution Environment +@cindex execution environment + +The shell has an @var{execution environment}, which consists of the +following: + +@itemize @bullet +@item +open files inherited by the shell at invocation, as modified by +redirections supplied to the @code{exec} builtin + +@item +the current working directory as set by @code{cd}, @code{pushd}, or +@code{popd}, or inherited by the shell at invocation + +@item +the file creation mode mask as set by @code{umask} or inherited from +the shell's parent + +@item +current traps set by @code{trap} + +@item +shell parameters that are set by variable assignment or with @code{set} +or inherited from the shell's parent in the environment + +@item +shell functions defined during execution or inherited from the shell's +parent in the environment + +@item +options enabled at invocation (either by default or with command-line +arguments) or by @code{set} + +@item +options enabled by @code{shopt} + +@item +shell aliases defined with @code{alias} (@pxref{Aliases}) + +@item +various process @sc{id}s, including those of background jobs +(@pxref{Lists}), the value of @code{$$}, and the value of +@env{$PPID} + +@end itemize + +When a simple command other than a builtin or shell function +is to be executed, it +is invoked in a separate execution environment that consists of +the following. Unless otherwise noted, the values are inherited +from the shell. + +@itemize @bullet +@item +the shell's open files, plus any modifications and additions specified +by redirections to the command + +@item +the current working directory + +@item +the file creation mode mask + +@item +shell variables and functions marked for export, along with variables +exported for the command, passed in the environment (@pxref{Environment}) + +@item +traps caught by the shell are reset to the values inherited from the +shell's parent, and traps ignored by the shell are ignored + +@end itemize + +A command invoked in this separate environment cannot affect the +shell's execution environment. + +Command substitution, commands grouped with parentheses, +and asynchronous commands are invoked in a +subshell environment that is a duplicate of the shell environment, +except that traps caught by the shell are reset to the values +that the shell inherited from its parent at invocation. Builtin +commands that are invoked as part of a pipeline are also executed +in a subshell environment. Changes made to the subshell environment +cannot affect the shell's execution environment. + +If a command is followed by a @samp{&} and job control is not active, the +default standard input for the command is the empty file @file{/dev/null}. +Otherwise, the invoked command inherits the file descriptors of the calling +shell as modified by redirections. + +@node Environment +@subsection Environment +@cindex environment + +When a program is invoked it is given an array of strings +called the @var{environment}. +This is a list of name-value pairs, of the form @code{name=value}. + +Bash provides several ways to manipulate the environment. +On invocation, the shell scans its own environment and +creates a parameter for each name found, automatically marking +it for @var{export} +to child processes. Executed commands inherit the environment. +The @code{export} and @samp{declare -x} +commands allow parameters and functions to be added to and +deleted from the environment. If the value of a parameter +in the environment is modified, the new value becomes part +of the environment, replacing the old. The environment +inherited by any executed command consists of the shell's +initial environment, whose values may be modified in the shell, +less any pairs removed by the @code{unset} and @samp{export -n} +commands, plus any additions via the @code{export} and +@samp{declare -x} commands. + +The environment for any simple command +or function may be augmented temporarily by prefixing it with +parameter assignments, as described in @ref{Shell Parameters}. +These assignment statements affect only the environment seen +by that command. + +If the @option{-k} option is set (@pxref{The Set Builtin}), then all +parameter assignments are placed in the environment for a command, +not just those that precede the command name. + +When Bash invokes an external command, the variable @samp{$_} +is set to the full path name of the command and passed to that +command in its environment. + +@node Exit Status +@subsection Exit Status +@cindex exit status + +For the shell's purposes, a command which exits with a +zero exit status has succeeded. +A non-zero exit status indicates failure. +This seemingly counter-intuitive scheme is used so there +is one well-defined way to indicate success and a variety of +ways to indicate various failure modes. +When a command terminates on a fatal signal whose number is @var{N}, +Bash uses the value 128+@var{N} as the exit status. + +If a command is not found, the child process created to +execute it returns a status of 127. If a command is found +but is not executable, the return status is 126. + +If a command fails because of an error during expansion or redirection, +the exit status is greater than zero. + +The exit status is used by the Bash conditional commands +(@pxref{Conditional Constructs}) and some of the list +constructs (@pxref{Lists}). + +All of the Bash builtins return an exit status of zero if they succeed +and a non-zero status on failure, so they may be used by the +conditional and list constructs. +All builtins return an exit status of 2 to indicate incorrect usage. + +@node Signals +@subsection Signals +@cindex signal handling + +When Bash is interactive, in the absence of any traps, it ignores +@code{SIGTERM} (so that @samp{kill 0} does not kill an interactive shell), +and @code{SIGINT} +is caught and handled (so that the @code{wait} builtin is interruptible). +When Bash receives a @code{SIGINT}, it breaks out of any executing loops. +In all cases, Bash ignores @code{SIGQUIT}. +If job control is in effect (@pxref{Job Control}), Bash +ignores @code{SIGTTIN}, @code{SIGTTOU}, and @code{SIGTSTP}. + +Non-builtin commands started by Bash have signal handlers set to the +values inherited by the shell from its parent. +When job control is not in effect, asynchronous commands +ignore @code{SIGINT} and @code{SIGQUIT} in addition to these inherited +handlers. +Commands run as a result of +command substitution ignore the keyboard-generated job control signals +@code{SIGTTIN}, @code{SIGTTOU}, and @code{SIGTSTP}. + +The shell exits by default upon receipt of a @code{SIGHUP}. +Before exiting, an interactive shell resends the @code{SIGHUP} to +all jobs, running or stopped. +Stopped jobs are sent @code{SIGCONT} to ensure that they receive +the @code{SIGHUP}. +To prevent the shell from sending the @code{SIGHUP} signal to a +particular job, it should be removed +from the jobs table with the @code{disown} +builtin (@pxref{Job Control Builtins}) or marked +to not receive @code{SIGHUP} using @code{disown -h}. + +If the @code{huponexit} shell option has been set with @code{shopt} +(@pxref{Bash Builtins}), Bash sends a @code{SIGHUP} to all jobs when +an interactive login shell exits. + +If Bash is waiting for a command to complete and receives a signal +for which a trap has been set, the trap will not be executed until +the command completes. +When Bash is waiting for an asynchronous +command via the @code{wait} builtin, the reception of a signal for +which a trap has been set will cause the @code{wait} builtin to return +immediately with an exit status greater than 128, immediately after +which the trap is executed. + +@node Shell Scripts +@section Shell Scripts +@cindex shell script + +A shell script is a text file containing shell commands. When such +a file is used as the first non-option argument when invoking Bash, +and neither the @option{-c} nor @option{-s} option is supplied +(@pxref{Invoking Bash}), +Bash reads and executes commands from the file, then exits. This +mode of operation creates a non-interactive shell. The shell first +searches for the file in the current directory, and looks in the +directories in @env{$PATH} if not found there. + +When Bash runs +a shell script, it sets the special parameter @code{0} to the name +of the file, rather than the name of the shell, and the positional +parameters are set to the remaining arguments, if any are given. +If no additional arguments are supplied, the positional parameters +are unset. + +A shell script may be made executable by using the @code{chmod} command +to turn on the execute bit. When Bash finds such a file while +searching the @env{$PATH} for a command, it spawns a subshell to +execute it. In other words, executing +@example +filename @var{arguments} +@end example +@noindent +is equivalent to executing +@example +bash filename @var{arguments} +@end example + +@noindent +if @code{filename} is an executable shell script. +This subshell reinitializes itself, so that the effect is as if a +new shell had been invoked to interpret the script, with the +exception that the locations of commands remembered by the parent +(see the description of @code{hash} in @ref{Bourne Shell Builtins}) +are retained by the child. + +Most versions of Unix make this a part of the operating system's command +execution mechanism. If the first line of a script begins with +the two characters @samp{#!}, the remainder of the line specifies +an interpreter for the program. +Thus, you can specify Bash, @code{awk}, Perl, or some other +interpreter and write the rest of the script file in that language. + +The arguments to the interpreter +consist of a single optional argument following the interpreter +name on the first line of the script file, followed by the name of +the script file, followed by the rest of the arguments. Bash +will perform this action on operating systems that do not handle it +themselves. Note that some older versions of Unix limit the interpreter +name and argument to a maximum of 32 characters. + +Bash scripts often begin with @code{#! /bin/bash} (assuming that +Bash has been installed in @file{/bin}), since this ensures that +Bash will be used to interpret the script, even if it is executed +under another shell. + +@node Shell Builtin Commands +@chapter Shell Builtin Commands + +@menu +* Bourne Shell Builtins:: Builtin commands inherited from the Bourne + Shell. +* Bash Builtins:: Table of builtins specific to Bash. +* The Set Builtin:: This builtin is so overloaded it + deserves its own section. +* Special Builtins:: Builtin commands classified specially by + POSIX.2. +@end menu + +Builtin commands are contained within the shell itself. +When the name of a builtin command is used as the first word of +a simple command (@pxref{Simple Commands}), the shell executes +the command directly, without invoking another program. +Builtin commands are necessary to implement functionality impossible +or inconvenient to obtain with separate utilities. + +This section briefly the builtins which Bash inherits from +the Bourne Shell, as well as the builtin commands which are unique +to or have been extended in Bash. + +Several builtin commands are described in other chapters: builtin +commands which provide the Bash interface to the job control +facilities (@pxref{Job Control Builtins}), the directory stack +(@pxref{Directory Stack Builtins}), the command history +(@pxref{Bash History Builtins}), and the programmable completion +facilities (@pxref{Programmable Completion Builtins}). + +Many of the builtins have been extended by @sc{posix} or Bash. + +Unless otherwise noted, each builtin command documented as accepting +options preceded by @samp{-} accepts @samp{--} +to signify the end of the options. + +@node Bourne Shell Builtins +@section Bourne Shell Builtins + +The following shell builtin commands are inherited from the Bourne Shell. +These commands are implemented as specified by the @sc{posix} 1003.2 standard. + +@table @code +@item : @r{(a colon)} +@btindex : +@example +: [@var{arguments}] +@end example +Do nothing beyond expanding @var{arguments} and performing redirections. +The return status is zero. + +@item . @r{(a period)} +@btindex . +@example +. @var{filename} [@var{arguments}] +@end example +Read and execute commands from the @var{filename} argument in the +current shell context. If @var{filename} does not contain a slash, +the @env{PATH} variable is used to find @var{filename}. +When Bash is not in @sc{posix} mode, the current directory is searched +if @var{filename} is not found in @env{$PATH}. +If any @var{arguments} are supplied, they become the positional +parameters when @var{filename} is executed. Otherwise the positional +parameters are unchanged. +The return status is the exit status of the last command executed, or +zero if no commands are executed. If @var{filename} is not found, or +cannot be read, the return status is non-zero. +This builtin is equivalent to @code{source}. + +@item break +@btindex break +@example +break [@var{n}] +@end example +Exit from a @code{for}, @code{while}, @code{until}, or @code{select} loop. +If @var{n} is supplied, the @var{n}th enclosing loop is exited. +@var{n} must be greater than or equal to 1. +The return status is zero unless @var{n} is not greater than or equal to 1. + +@item cd +@btindex cd +@example +cd [-L|-P] [@var{directory}] +@end example +Change the current working directory to @var{directory}. +If @var{directory} is not given, the value of the @env{HOME} shell +variable is used. +If the shell variable @env{CDPATH} exists, it is used as a search path. +If @var{directory} begins with a slash, @env{CDPATH} is not used. + +The @option{-P} option means to not follow symbolic links; symbolic +links are followed by default or with the @option{-L} option. +If @var{directory} is @samp{-}, it is equivalent to @env{$OLDPWD}. + +If a non-empty directory name from @env{CDPATH} is used, or if +@samp{-} is the first argument, and the directory change is +successful, the absolute pathname of the new working directory is +written to the standard output. + +The return status is zero if the directory is successfully changed, +non-zero otherwise. + +@item continue +@btindex continue +@example +continue [@var{n}] +@end example +Resume the next iteration of an enclosing @code{for}, @code{while}, +@code{until}, or @code{select} loop. +If @var{n} is supplied, the execution of the @var{n}th enclosing loop +is resumed. +@var{n} must be greater than or equal to 1. +The return status is zero unless @var{n} is not greater than or equal to 1. + +@item eval +@btindex eval +@example +eval [@var{arguments}] +@end example +The arguments are concatenated together into a single command, which is +then read and executed, and its exit status returned as the exit status +of @code{eval}. +If there are no arguments or only empty arguments, the return status is +zero. + +@item exec +@btindex exec +@example +exec [-cl] [-a @var{name}] [@var{command} [@var{arguments}]] +@end example +If @var{command} +is supplied, it replaces the shell without creating a new process. +If the @option{-l} option is supplied, the shell places a dash at the +beginning of the zeroth arg passed to @var{command}. +This is what the @code{login} program does. +The @option{-c} option causes @var{command} to be executed with an empty +environment. +If @option{-a} is supplied, the shell passes @var{name} as the zeroth +argument to @var{command}. +If no @var{command} is specified, redirections may be used to affect +the current shell environment. If there are no redirection errors, the +return status is zero; otherwise the return status is non-zero. + +@item exit +@btindex exit +@example +exit [@var{n}] +@end example +Exit the shell, returning a status of @var{n} to the shell's parent. +If @var{n} is omitted, the exit status is that of the last command executed. +Any trap on @code{EXIT} is executed before the shell terminates. + +@item export +@btindex export +@example +export [-fn] [-p] [@var{name}[=@var{value}]] +@end example +Mark each @var{name} to be passed to child processes +in the environment. If the @option{-f} option is supplied, the @var{name}s +refer to shell functions; otherwise the names refer to shell variables. +The @option{-n} option means to no longer mark each @var{name} for export. +If no @var{names} are supplied, or if the @option{-p} option is given, a +list of exported names is displayed. +The @option{-p} option displays output in a form that may be reused as input. +If a variable name is followed by =@var{value}, the value of +the variable is set to @var{value}. + +The return status is zero unless an invalid option is supplied, one of +the names is not a valid shell variable name, or @option{-f} is supplied +with a name that is not a shell function. + +@item getopts +@btindex getopts +@example +getopts @var{optstring} @var{name} [@var{args}] +@end example +@code{getopts} is used by shell scripts to parse positional parameters. +@var{optstring} contains the option characters to be recognized; if a +character is followed by a colon, the option is expected to have an +argument, which should be separated from it by white space. +The colon (@samp{:}) and question mark (@samp{?}) may not be +used as option characters. +Each time it is invoked, @code{getopts} +places the next option in the shell variable @var{name}, initializing +@var{name} if it does not exist, +and the index of the next argument to be processed into the +variable @env{OPTIND}. +@env{OPTIND} is initialized to 1 each time the shell or a shell script +is invoked. +When an option requires an argument, +@code{getopts} places that argument into the variable @env{OPTARG}. +The shell does not reset @env{OPTIND} automatically; it must be manually +reset between multiple calls to @code{getopts} within the same shell +invocation if a new set of parameters is to be used. + +When the end of options is encountered, @code{getopts} exits with a +return value greater than zero. +@env{OPTIND} is set to the index of the first non-option argument, +and @code{name} is set to @samp{?}. + +@code{getopts} +normally parses the positional parameters, but if more arguments are +given in @var{args}, @code{getopts} parses those instead. + +@code{getopts} can report errors in two ways. If the first character of +@var{optstring} is a colon, @var{silent} +error reporting is used. In normal operation diagnostic messages +are printed when invalid options or missing option arguments are +encountered. +If the variable @env{OPTERR} +is set to 0, no error messages will be displayed, even if the first +character of @code{optstring} is not a colon. + +If an invalid option is seen, +@code{getopts} places @samp{?} into @var{name} and, if not silent, +prints an error message and unsets @env{OPTARG}. +If @code{getopts} is silent, the option character found is placed in +@env{OPTARG} and no diagnostic message is printed. + +If a required argument is not found, and @code{getopts} +is not silent, a question mark (@samp{?}) is placed in @var{name}, +@code{OPTARG} is unset, and a diagnostic message is printed. +If @code{getopts} is silent, then a colon (@samp{:}) is placed in +@var{name} and @env{OPTARG} is set to the option character found. + +@item hash +@btindex hash +@example +hash [-'r] [-p @var{filename}] [-dt] [@var{name}] +@end example +Remember the full pathnames of commands specified as @var{name} arguments, +so they need not be searched for on subsequent invocations. +The commands are found by searching through the directories listed in +@env{$PATH}. +The @option{-p} option inhibits the path search, and @var{filename} is +used as the location of @var{name}. +The @option{-r} option causes the shell to forget all remembered locations. +The @option{-d} option causes the shell to forget the remembered location +of each @var{name}. +If the @option{-t} option is supplied, the full pathname to which each +@var{name} corresponds is printed. If multiple @var{name} arguments are +supplied with @option{-t} the @var{name} is printed before the hashed +full pathname. +The @option{-l} option causes output to be displayed in a format +that may be reused as input. +If no arguments are given, or if only @option{-l} is supplied, +information about remembered commands is printed. +The return status is zero unless a @var{name} is not found or an invalid +option is supplied. + +@item pwd +@btindex pwd +@example +pwd [-LP] +@end example +Print the absolute pathname of the current working directory. +If the @option{-P} option is supplied, the pathname printed will not +contain symbolic links. +If the @option{-L} option is supplied, the pathname printed may contain +symbolic links. +The return status is zero unless an error is encountered while +determining the name of the current directory or an invalid option +is supplied. + +@item readonly +@btindex readonly +@example +readonly [-apf] [@var{name}[=@var{value}]] @dots{} +@end example +Mark each @var{name} as readonly. +The values of these names may not be changed by subsequent assignment. +If the @option{-f} option is supplied, each @var{name} refers to a shell +function. +The @option{-a} option means each @var{name} refers to an array variable. +If no @var{name} arguments are given, or if the @option{-p} +option is supplied, a list of all readonly names is printed. +The @option{-p} option causes output to be displayed in a format that +may be reused as input. +If a variable name is followed by =@var{value}, the value of +the variable is set to @var{value}. +The return status is zero unless an invalid option is supplied, one of +the @var{name} arguments is not a valid shell variable or function name, +or the @option{-f} option is supplied with a name that is not a shell function. + +@item return +@btindex return +@example +return [@var{n}] +@end example +Cause a shell function to exit with the return value @var{n}. +If @var{n} is not supplied, the return value is the exit status of the +last command executed in the function. +This may also be used to terminate execution of a script being executed +with the @code{.} (or @code{source}) builtin, returning either @var{n} or +the exit status of the last command executed within the script as the exit +status of the script. +Any command associated with the @code{RETURN} trap is executed +before execution resumes after the function or script. +The return status is non-zero if @code{return} is used outside a function +and not during the execution of a script by @code{.} or @code{source}. + +@item shift +@btindex shift +@example +shift [@var{n}] +@end example +Shift the positional parameters to the left by @var{n}. +The positional parameters from @var{n}+1 @dots{} @code{$#} are +renamed to @code{$1} @dots{} @code{$#}-@var{n}+1. +Parameters represented by the numbers @code{$#} to @var{n}+1 are unset. +@var{n} must be a non-negative number less than or equal to @code{$#}. +If @var{n} is zero or greater than @code{$#}, the positional parameters +are not changed. +If @var{n} is not supplied, it is assumed to be 1. +The return status is zero unless @var{n} is greater than @code{$#} or +less than zero, non-zero otherwise. + +@item test +@itemx [ +@btindex test +@btindex [ +Evaluate a conditional expression @var{expr}. +Each operator and operand must be a separate argument. +Expressions are composed of the primaries described below in +@ref{Bash Conditional Expressions}. + +When the @code{[} form is used, the last argument to the command must +be a @code{]}. + +Expressions may be combined using the following operators, listed in +decreasing order of precedence. + +@table @code +@item ! @var{expr} +True if @var{expr} is false. + +@item ( @var{expr} ) +Returns the value of @var{expr}. +This may be used to override the normal precedence of operators. + +@item @var{expr1} -a @var{expr2} +True if both @var{expr1} and @var{expr2} are true. + +@item @var{expr1} -o @var{expr2} +True if either @var{expr1} or @var{expr2} is true. +@end table + +The @code{test} and @code{[} builtins evaluate conditional +expressions using a set of rules based on the number of arguments. + +@table @asis +@item 0 arguments +The expression is false. + +@item 1 argument +The expression is true if and only if the argument is not null. + +@item 2 arguments +If the first argument is @samp{!}, the expression is true if and +only if the second argument is null. +If the first argument is one of the unary conditional operators +(@pxref{Bash Conditional Expressions}), the expression +is true if the unary test is true. +If the first argument is not a valid unary operator, the expression is +false. + +@item 3 arguments +If the second argument is one of the binary conditional +operators (@pxref{Bash Conditional Expressions}), the +result of the expression is the result of the binary test using the +first and third arguments as operands. +If the first argument is @samp{!}, the value is the negation of +the two-argument test using the second and third arguments. +If the first argument is exactly @samp{(} and the third argument is +exactly @samp{)}, the result is the one-argument test of the second +argument. +Otherwise, the expression is false. +The @samp{-a} and @samp{-o} operators are considered binary operators +in this case. + +@item 4 arguments +If the first argument is @samp{!}, the result is the negation of +the three-argument expression composed of the remaining arguments. +Otherwise, the expression is parsed and evaluated according to +precedence using the rules listed above. + +@item 5 or more arguments +The expression is parsed and evaluated according to precedence +using the rules listed above. +@end table + +@item times +@btindex times +@example +times +@end example +Print out the user and system times used by the shell and its children. +The return status is zero. + +@item trap +@btindex trap +@example +trap [-lp] [@var{arg}] [@var{sigspec} @dots{}] +@end example +The commands in @var{arg} are to be read and executed when the +shell receives signal @var{sigspec}. If @var{arg} is absent (and +there is a single @var{sigspec}) or +equal to @samp{-}, each specified signal's disposition is reset +to the value it had when the shell was started. +If @var{arg} is the null string, then the signal specified by +each @var{sigspec} is ignored by the shell and commands it invokes. +If @var{arg} is not present and @option{-p} has been supplied, +the shell displays the trap commands associated with each @var{sigspec}. +If no arguments are supplied, or +only @option{-p} is given, @code{trap} prints the list of commands +associated with each signal number in a form that may be reused as +shell input. +The @option{-l} option causes the shell to print a list of signal names +and their corresponding numbers. +Each @var{sigspec} is either a signal name or a signal number. +Signal names are case insensitive and the @code{SIG} prefix is optional. +If a @var{sigspec} +is @code{0} or @code{EXIT}, @var{arg} is executed when the shell exits. +If a @var{sigspec} is @code{DEBUG}, the command @var{arg} is executed +before every simple command, @code{for} command, @code{case} command, +@code{select} command, every arithmetic @code{for} command, and before +the first command executes in a shell function. +Refer to the description of the @code{extglob} option to the +@code{shopt} builtin (@pxref{Bash Builtins}) for details of its +effect on the @code{DEBUG} trap. +If a @var{sigspec} is @code{ERR}, the command @var{arg} +is executed whenever a simple command has a non-zero exit status, +subject to the following conditions. +The @code{ERR} trap is not executed if the failed command is part of the +command list immediately following an @code{until} or @code{while} keyword, +part of the test in an @code{if} statement, +part of a @code{&&} or @code{||} list, or if the command's return +status is being inverted using @code{!}. +These are the same conditions obeyed by the @code{errexit} option. +If a @var{sigspec} is @code{RETURN}, the command @var{arg} is executed +each time a shell function or a script executed with the @code{.} or +@code{source} builtins finishes executing. + +Signals ignored upon entry to the shell cannot be trapped or reset. +Trapped signals are reset to their original values in a child +process when it is created. + +The return status is zero unless a @var{sigspec} does not specify a +valid signal. + +@item umask +@btindex umask +@example +umask [-p] [-S] [@var{mode}] +@end example +Set the shell process's file creation mask to @var{mode}. If +@var{mode} begins with a digit, it is interpreted as an octal number; +if not, it is interpreted as a symbolic mode mask similar +to that accepted by the @code{chmod} command. If @var{mode} is +omitted, the current value of the mask is printed. If the @option{-S} +option is supplied without a @var{mode} argument, the mask is printed +in a symbolic format. +If the @option{-p} option is supplied, and @var{mode} +is omitted, the output is in a form that may be reused as input. +The return status is zero if the mode is successfully changed or if +no @var{mode} argument is supplied, and non-zero otherwise. + +Note that when the mode is interpreted as an octal number, each number +of the umask is subtracted from @code{7}. Thus, a umask of @code{022} +results in permissions of @code{755}. + +@item unset +@btindex unset +@example +unset [-fv] [@var{name}] +@end example +Each variable or function @var{name} is removed. +If no options are supplied, or the @option{-v} option is given, each +@var{name} refers to a shell variable. +If the @option{-f} option is given, the @var{name}s refer to shell +functions, and the function definition is removed. +Readonly variables and functions may not be unset. +The return status is zero unless a @var{name} is readonly. +@end table + +@node Bash Builtins +@section Bash Builtin Commands + +This section describes builtin commands which are unique to +or have been extended in Bash. +Some of these commands are specified in the @sc{posix} 1003.2 standard. + +@table @code + +@item alias +@btindex alias +@example +alias [@code{-p}] [@var{name}[=@var{value}] @dots{}] +@end example + +Without arguments or with the @option{-p} option, @code{alias} prints +the list of aliases on the standard output in a form that allows +them to be reused as input. +If arguments are supplied, an alias is defined for each @var{name} +whose @var{value} is given. If no @var{value} is given, the name +and value of the alias is printed. +Aliases are described in @ref{Aliases}. + +@item bind +@btindex bind +@example +bind [-m @var{keymap}] [-lpsvPSV] +bind [-m @var{keymap}] [-q @var{function}] [-u @var{function}] [-r @var{keyseq}] +bind [-m @var{keymap}] -f @var{filename} +bind [-m @var{keymap}] -x @var{keyseq:shell-command} +bind [-m @var{keymap}] @var{keyseq:function-name} +bind @var{readline-command} +@end example + +Display current Readline (@pxref{Command Line Editing}) +key and function bindings, +bind a key sequence to a Readline function or macro, +or set a Readline variable. +Each non-option argument is a command as it would appear in a +a Readline initialization file (@pxref{Readline Init File}), +but each binding or command must be passed as a separate argument; e.g., +@samp{"\C-x\C-r":re-read-init-file}. +Options, if supplied, have the following meanings: + +@table @code +@item -m @var{keymap} +Use @var{keymap} as the keymap to be affected by +the subsequent bindings. Acceptable @var{keymap} +names are +@code{emacs}, +@code{emacs-standard}, +@code{emacs-meta}, +@code{emacs-ctlx}, +@code{vi}, +@code{vi-move}, +@code{vi-command}, and +@code{vi-insert}. +@code{vi} is equivalent to @code{vi-command}; +@code{emacs} is equivalent to @code{emacs-standard}. + +@item -l +List the names of all Readline functions. + +@item -p +Display Readline function names and bindings in such a way that they +can be used as input or in a Readline initialization file. + +@item -P +List current Readline function names and bindings. + +@item -v +Display Readline variable names and values in such a way that they +can be used as input or in a Readline initialization file. + +@item -V +List current Readline variable names and values. + +@item -s +Display Readline key sequences bound to macros and the strings they output +in such a way that they can be used as input or in a Readline +initialization file. + +@item -S +Display Readline key sequences bound to macros and the strings they output. + +@item -f @var{filename} +Read key bindings from @var{filename}. + +@item -q @var{function} +Query about which keys invoke the named @var{function}. + +@item -u @var{function} +Unbind all keys bound to the named @var{function}. + +@item -r @var{keyseq} +Remove any current binding for @var{keyseq}. + +@item -x @var{keyseq:shell-command} +Cause @var{shell-command} to be executed whenever @var{keyseq} is +entered. + +@end table + +@noindent +The return status is zero unless an invalid option is supplied or an +error occurs. + +@item builtin +@btindex builtin +@example +builtin [@var{shell-builtin} [@var{args}]] +@end example +Run a shell builtin, passing it @var{args}, and return its exit status. +This is useful when defining a shell function with the same +name as a shell builtin, retaining the functionality of the builtin within +the function. +The return status is non-zero if @var{shell-builtin} is not a shell +builtin command. + +@item caller +@btindex caller +@example +caller [@var{expr}] +@end example +Returns the context of any active subroutine call (a shell function or +a script executed with the @code{.} or @code{source} builtins). + +Without @var{expr}, @code{caller} displays the line number and source +filename of the current subroutine call. +If a non-negative integer is supplied as @var{expr}, @code{caller} +displays the line number, subroutine name, and source file corresponding +to that position in the current execution call stack. This extra +information may be used, for example, to print a stack trace. The +current frame is frame 0. + +The return value is 0 unless the shell is not executing a subroutine +call or @var{expr} does not correspond to a valid position in the +call stack. + +@item command +@btindex command +@example +command [-pVv] @var{command} [@var{arguments} @dots{}] +@end example +Runs @var{command} with @var{arguments} ignoring any shell function +named @var{command}. +Only shell builtin commands or commands found by searching the +@env{PATH} are executed. +If there is a shell function named @code{ls}, running @samp{command ls} +within the function will execute the external command @code{ls} +instead of calling the function recursively. +The @option{-p} option means to use a default value for @env{PATH} +that is guaranteed to find all of the standard utilities. +The return status in this case is 127 if @var{command} cannot be +found or an error occurred, and the exit status of @var{command} +otherwise. + +If either the @option{-V} or @option{-v} option is supplied, a +description of @var{command} is printed. The @option{-v} option +causes a single word indicating the command or file name used to +invoke @var{command} to be displayed; the @option{-V} option produces +a more verbose description. In this case, the return status is +zero if @var{command} is found, and non-zero if not. + +@item declare +@btindex declare +@example +declare [-afFirtx] [-p] [@var{name}[=@var{value}] @dots{}] +@end example + +Declare variables and give them attributes. If no @var{name}s +are given, then display the values of variables instead. + +The @option{-p} option will display the attributes and values of each +@var{name}. +When @option{-p} is used, additional options are ignored. +The @option{-F} option inhibits the display of function definitions; +only the function name and attributes are printed. +If the @code{extdebug} shell option is enabled using @code{shopt} +(@pxref{Bash Builtins}), the source file name and line number where +the function is defined are displayed as well. +@option{-F} implies @option{-f}. +The following options can be used to restrict output to variables with +the specified attributes or to give variables attributes: + +@table @code +@item -a +Each @var{name} is an array variable (@pxref{Arrays}). + +@item -f +Use function names only. + +@item -i +The variable is to be treated as +an integer; arithmetic evaluation (@pxref{Shell Arithmetic}) is +performed when the variable is assigned a value. + +@item -r +Make @var{name}s readonly. These names cannot then be assigned values +by subsequent assignment statements or unset. + +@item -t +Give each @var{name} the @code{trace} attribute. +Traced functions inherit the @code{DEBUG} trap from the calling shell. +The trace attribute has no special meaning for variables. + +@item -x +Mark each @var{name} for export to subsequent commands via +the environment. +@end table + +Using @samp{+} instead of @samp{-} turns off the attribute instead. +When used in a function, @code{declare} makes each @var{name} local, +as with the @code{local} command. If a variable name is followed by +=@var{value}, the value of the variable is set to @var{value}. + +The return status is zero unless an invalid option is encountered, +an attempt is made to define a function using @samp{-f foo=bar}, +an attempt is made to assign a value to a readonly variable, +an attempt is made to assign a value to an array variable without +using the compound assignment syntax (@pxref{Arrays}), +one of the @var{names} is not a valid shell variable name, +an attempt is made to turn off readonly status for a readonly variable, +an attempt is made to turn off array status for an array variable, +or an attempt is made to display a non-existent function with @option{-f}. + +@item echo +@btindex echo +@example +echo [-neE] [@var{arg} @dots{}] +@end example +Output the @var{arg}s, separated by spaces, terminated with a +newline. +The return status is always 0. +If @option{-n} is specified, the trailing newline is suppressed. +If the @option{-e} option is given, interpretation of the following +backslash-escaped characters is enabled. +The @option{-E} option disables the interpretation of these escape characters, +even on systems where they are interpreted by default. +The @code{xpg_echo} shell option may be used to +dynamically determine whether or not @code{echo} expands these +escape characters by default. +@code{echo} interprets the following escape sequences: +@table @code +@item \a +alert (bell) +@item \b +backspace +@item \c +suppress trailing newline +@item \e +escape +@item \f +form feed +@item \n +new line +@item \r +carriage return +@item \t +horizontal tab +@item \v +vertical tab +@item \\ +backslash +@item \0@var{nnn} +the eight-bit character whose value is the octal value @var{nnn} +(zero to three octal digits) +@item \@var{nnn} +the eight-bit character whose value is the octal value @var{nnn} +(one to three octal digits) +@item \x@var{HH} +the eight-bit character whose value is the hexadecimal value @var{HH} +(one or two hex digits) +@end table + +@item enable +@btindex enable +@example +enable [-n] [-p] [-f @var{filename}] [-ads] [@var{name} @dots{}] +@end example +Enable and disable builtin shell commands. +Disabling a builtin allows a disk command which has the same name +as a shell builtin to be executed without specifying a full pathname, +even though the shell normally searches for builtins before disk commands. +If @option{-n} is used, the @var{name}s become disabled. Otherwise +@var{name}s are enabled. For example, to use the @code{test} binary +found via @env{$PATH} instead of the shell builtin version, type +@samp{enable -n test}. + +If the @option{-p} option is supplied, or no @var{name} arguments appear, +a list of shell builtins is printed. With no other arguments, the list +consists of all enabled shell builtins. +The @option{-a} option means to list +each builtin with an indication of whether or not it is enabled. + +The @option{-f} option means to load the new builtin command @var{name} +from shared object @var{filename}, on systems that support dynamic loading. +The @option{-d} option will delete a builtin loaded with @option{-f}. + +If there are no options, a list of the shell builtins is displayed. +The @option{-s} option restricts @code{enable} to the @sc{posix} special +builtins. If @option{-s} is used with @option{-f}, the new builtin becomes +a special builtin (@pxref{Special Builtins}). + +The return status is zero unless a @var{name} is not a shell builtin +or there is an error loading a new builtin from a shared object. + +@item help +@btindex help +@example +help [-s] [@var{pattern}] +@end example +Display helpful information about builtin commands. +If @var{pattern} is specified, @code{help} gives detailed help +on all commands matching @var{pattern}, otherwise a list of +the builtins is printed. +The @option{-s} option restricts the information displayed to a short +usage synopsis. +The return status is zero unless no command matches @var{pattern}. + +@item let +@btindex let +@example +let @var{expression} [@var{expression}] +@end example +The @code{let} builtin allows arithmetic to be performed on shell +variables. Each @var{expression} is evaluated according to the +rules given below in @ref{Shell Arithmetic}. If the +last @var{expression} evaluates to 0, @code{let} returns 1; +otherwise 0 is returned. + +@item local +@btindex local +@example +local [@var{option}] @var{name}[=@var{value}] @dots{} +@end example +For each argument, a local variable named @var{name} is created, +and assigned @var{value}. +The @var{option} can be any of the options accepted by @code{declare}. +@code{local} can only be used within a function; it makes the variable +@var{name} have a visible scope restricted to that function and its +children. The return status is zero unless @code{local} is used outside +a function, an invalid @var{name} is supplied, or @var{name} is a +readonly variable. + +@item logout +@btindex logout +@example +logout [@var{n}] +@end example +Exit a login shell, returning a status of @var{n} to the shell's +parent. + +@item printf +@btindex printf +@example +@code{printf} @var{format} [@var{arguments}] +@end example +Write the formatted @var{arguments} to the standard output under the +control of the @var{format}. +The @var{format} is a character string which contains three types of objects: +plain characters, which are simply copied to standard output, character +escape sequences, which are converted and copied to the standard output, and +format specifications, each of which causes printing of the next successive +@var{argument}. +In addition to the standard @code{printf(1)} formats, @samp{%b} causes +@code{printf} to expand backslash escape sequences in the corresponding +@var{argument}, +(except that @samp{\c} terminates output, backslashes in +@samp{\'}, @samp{\"}, and @samp{\?} are not removed, and octal escapes +beginning with @samp{\0} may contain up to four digits), +and @samp{%q} causes @code{printf} to output the +corresponding @var{argument} in a format that can be reused as shell input. + +The @var{format} is reused as necessary to consume all of the @var{arguments}. +If the @var{format} requires more @var{arguments} than are supplied, the +extra format specifications behave as if a zero value or null string, as +appropriate, had been supplied. The return value is zero on success, +non-zero on failure. + +@item read +@btindex read +@example +read [-ers] [-a @var{aname}] [-d @var{delim}] [-n @var{nchars}] [-p @var{prompt}] [-t @var{timeout}] [-u @var{fd}] [@var{name} @dots{}] +@end example +One line is read from the standard input, or from the file descriptor +@var{fd} supplied as an argument to the @option{-u} option, and the first word +is assigned to the first @var{name}, the second word to the second @var{name}, +and so on, with leftover words and their intervening separators assigned +to the last @var{name}. +If there are fewer words read from the input stream than names, +the remaining names are assigned empty values. +The characters in the value of the @env{IFS} variable +are used to split the line into words. +The backslash character @samp{\} may be used to remove any special +meaning for the next character read and for line continuation. +If no names are supplied, the line read is assigned to the +variable @env{REPLY}. +The return code is zero, unless end-of-file is encountered, @code{read} +times out, or an invalid file descriptor is supplied as the argument to +@option{-u}. +Options, if supplied, have the following meanings: + +@table @code +@item -a @var{aname} +The words are assigned to sequential indices of the array variable +@var{aname}, starting at 0. +All elements are removed from @var{aname} before the assignment. +Other @var{name} arguments are ignored. + +@item -d @var{delim} +The first character of @var{delim} is used to terminate the input line, +rather than newline. + +@item -e +Readline (@pxref{Command Line Editing}) is used to obtain the line. + +@item -n @var{nchars} +@code{read} returns after reading @var{nchars} characters rather than +waiting for a complete line of input. + +@item -p @var{prompt} +Display @var{prompt}, without a trailing newline, before attempting +to read any input. +The prompt is displayed only if input is coming from a terminal. + +@item -r +If this option is given, backslash does not act as an escape character. +The backslash is considered to be part of the line. +In particular, a backslash-newline pair may not be used as a line +continuation. + +@item -s +Silent mode. If input is coming from a terminal, characters are +not echoed. + +@item -t @var{timeout} +Cause @code{read} to time out and return failure if a complete line of +input is not read within @var{timeout} seconds. +This option has no effect if @code{read} is not reading input from the +terminal or a pipe. + +@item -u @var{fd} +Read input from file descriptor @var{fd}. + +@end table + +@item shopt +@btindex shopt +@example +shopt [-pqsu] [-o] [@var{optname} @dots{}] +@end example +Toggle the values of variables controlling optional shell behavior. +With no options, or with the @option{-p} option, a list of all settable +options is displayed, with an indication of whether or not each is set. +The @option{-p} option causes output to be displayed in a form that +may be reused as input. +Other options have the following meanings: + +@table @code +@item -s +Enable (set) each @var{optname}. + +@item -u +Disable (unset) each @var{optname}. + +@item -q +Suppresses normal output; the return status +indicates whether the @var{optname} is set or unset. +If multiple @var{optname} arguments are given with @option{-q}, +the return status is zero if all @var{optnames} are enabled; +non-zero otherwise. + +@item -o +Restricts the values of +@var{optname} to be those defined for the @option{-o} option to the +@code{set} builtin (@pxref{The Set Builtin}). +@end table + +If either @option{-s} or @option{-u} +is used with no @var{optname} arguments, the display is limited to +those options which are set or unset, respectively. + +Unless otherwise noted, the @code{shopt} options are disabled (off) +by default. + +The return status when listing options is zero if all @var{optnames} +are enabled, non-zero otherwise. When setting or unsetting options, +the return status is zero unless an @var{optname} is not a valid shell +option. + +The list of @code{shopt} options is: +@table @code +@item cdable_vars +If this is set, an argument to the @code{cd} +builtin command that +is not a directory is assumed to be the name of a variable whose +value is the directory to change to. + +@item cdspell +If set, minor errors in the spelling of a directory component in a +@code{cd} command will be corrected. +The errors checked for are transposed characters, +a missing character, and a character too many. +If a correction is found, the corrected path is printed, +and the command proceeds. +This option is only used by interactive shells. + +@item checkhash +If this is set, Bash checks that a command found in the hash +table exists before trying to execute it. If a hashed command no +longer exists, a normal path search is performed. + +@item checkwinsize +If set, Bash checks the window size after each command +and, if necessary, updates the values of +@env{LINES} and @env{COLUMNS}. + +@item cmdhist +If set, Bash +attempts to save all lines of a multiple-line +command in the same history entry. This allows +easy re-editing of multi-line commands. + +@item dotglob +If set, Bash includes filenames beginning with a `.' in +the results of filename expansion. + +@item execfail +If this is set, a non-interactive shell will not exit if +it cannot execute the file specified as an argument to the @code{exec} +builtin command. An interactive shell does not exit if @code{exec} +fails. + +@item expand_aliases +If set, aliases are expanded as described below under Aliases, +@ref{Aliases}. +This option is enabled by default for interactive shells. + +@item extdebug +If set, behavior intended for use by debuggers is enabled: + +@enumerate +@item +The @option{-F} option to the @code{declare} builtin (@pxref{Bash Builtins}) +displays the source file name and line number corresponding to each function +name supplied as an argument. + +@item +If the command run by the @code{DEBUG} trap returns a non-zero value, the +next command is skipped and not executed. + +@item +If the command run by the @code{DEBUG} trap returns a value of 2, and the +shell is executing in a subroutine (a shell function or a shell script +executed by the @code{.} or @code{source} builtins), a call to +@code{return} is simulated. +@end enumerate + +@item extglob +If set, the extended pattern matching features described above +(@pxref{Pattern Matching}) are enabled. + +@item extquote +If set, @code{$'@var{string}'} and @code{$"@var{string}"} quoting is +performed within @code{$@{@var{parameter}@}} expansions +enclosed in double quotes. This option is enabled by default. + +@item failglob +If set, patterns which fail to match filenames during pathname expansion +result in an expansion error. + +@item force_fignore +If set, the suffixes specified by the @env{FIGNORE} shell variable +cause words to be ignored when performing word completion even if +the ignored words are the only possible completions. +@xref{Bash Variables}, for a description of @env{FIGNORE}. +This option is enabled by default. + +@item gnu_errfmt +If set, shell error messages are written in the standard @sc{gnu} error +message format. + +@item histappend +If set, the history list is appended to the file named by the value +of the @env{HISTFILE} +variable when the shell exits, rather than overwriting the file. + +@item histreedit +If set, and Readline +is being used, a user is given the opportunity to re-edit a +failed history substitution. + +@item histverify +If set, and Readline +is being used, the results of history substitution are not immediately +passed to the shell parser. Instead, the resulting line is loaded into +the Readline editing buffer, allowing further modification. + +@item hostcomplete +If set, and Readline is being used, Bash will attempt to perform +hostname completion when a word containing a @samp{@@} is being +completed (@pxref{Commands For Completion}). This option is enabled +by default. + +@item huponexit +If set, Bash will send @code{SIGHUP} to all jobs when an interactive +login shell exits (@pxref{Signals}). + +@item interactive_comments +Allow a word beginning with @samp{#} +to cause that word and all remaining characters on that +line to be ignored in an interactive shell. +This option is enabled by default. + +@item lithist +If enabled, and the @code{cmdhist} +option is enabled, multi-line commands are saved to the history with +embedded newlines rather than using semicolon separators where possible. + +@item login_shell +The shell sets this option if it is started as a login shell +(@pxref{Invoking Bash}). +The value may not be changed. + +@item mailwarn +If set, and a file that Bash is checking for mail has been +accessed since the last time it was checked, the message +@code{"The mail in @var{mailfile} has been read"} is displayed. + +@item no_empty_cmd_completion +If set, and Readline is being used, Bash will not attempt to search +the @env{PATH} for possible completions when completion is attempted +on an empty line. + +@item nocaseglob +If set, Bash matches filenames in a case-insensitive fashion when +performing filename expansion. + +@item nullglob +If set, Bash allows filename patterns which match no +files to expand to a null string, rather than themselves. + +@item progcomp +If set, the programmable completion facilities +(@pxref{Programmable Completion}) are enabled. +This option is enabled by default. + +@item promptvars +If set, prompt strings undergo +parameter expansion, command substitution, arithmetic +expansion, and quote removal after being expanded +as described below (@pxref{Printing a Prompt}). +This option is enabled by default. + +@item restricted_shell +The shell sets this option if it is started in restricted mode +(@pxref{The Restricted Shell}). +The value may not be changed. +This is not reset when the startup files are executed, allowing +the startup files to discover whether or not a shell is restricted. + +@item shift_verbose +If this is set, the @code{shift} +builtin prints an error message when the shift count exceeds the +number of positional parameters. + +@item sourcepath +If set, the @code{source} builtin uses the value of @env{PATH} +to find the directory containing the file supplied as an argument. +This option is enabled by default. + +@item xpg_echo +If set, the @code{echo} builtin expands backslash-escape sequences +by default. + +@end table + +@noindent +The return status when listing options is zero if all @var{optnames} +are enabled, non-zero otherwise. +When setting or unsetting options, the return status is zero unless an +@var{optname} is not a valid shell option. + +@item source +@btindex source +@example +source @var{filename} +@end example +A synonym for @code{.} (@pxref{Bourne Shell Builtins}). + +@item type +@btindex type +@example +type [-afptP] [@var{name} @dots{}] +@end example +For each @var{name}, indicate how it would be interpreted if used as a +command name. + +If the @option{-t} option is used, @code{type} prints a single word +which is one of @samp{alias}, @samp{function}, @samp{builtin}, +@samp{file} or @samp{keyword}, +if @var{name} is an alias, shell function, shell builtin, +disk file, or shell reserved word, respectively. +If the @var{name} is not found, then nothing is printed, and +@code{type} returns a failure status. + +If the @option{-p} option is used, @code{type} either returns the name +of the disk file that would be executed, or nothing if @option{-t} +would not return @samp{file}. + +The @option{-P} option forces a path search for each @var{name}, even if +@option{-t} would not return @samp{file}. + +If a command is hashed, @option{-p} and @option{-P} print the hashed value, +not necessarily the file that appears first in @code{$PATH}. + +If the @option{-a} option is used, @code{type} returns all of the places +that contain an executable named @var{file}. +This includes aliases and functions, if and only if the @option{-p} option +is not also used. + +If the @option{-f} option is used, @code{type} does not attempt to find +shell functions, as with the @code{command} builtin. + +The return status is zero if any of the @var{names} are found, non-zero +if none are found. + +@item typeset +@btindex typeset +@example +typeset [-afFrxi] [-p] [@var{name}[=@var{value}] @dots{}] +@end example +The @code{typeset} command is supplied for compatibility with the Korn +shell; however, it has been deprecated in favor of the @code{declare} +builtin command. + +@item ulimit +@btindex ulimit +@example +ulimit [-acdflmnpstuvSH] [@var{limit}] +@end example +@code{ulimit} provides control over the resources available to processes +started by the shell, on systems that allow such control. If an +option is given, it is interpreted as follows: +@table @code +@item -S +Change and report the soft limit associated with a resource. + +@item -H +Change and report the hard limit associated with a resource. + +@item -a +All current limits are reported. + +@item -c +The maximum size of core files created. + +@item -d +The maximum size of a process's data segment. + +@item -f +The maximum size of files created by the shell. + +@item -l +The maximum size that may be locked into memory. + +@item -m +The maximum resident set size. + +@item -n +The maximum number of open file descriptors. + +@item -p +The pipe buffer size. + +@item -s +The maximum stack size. + +@item -t +The maximum amount of cpu time in seconds. + +@item -u +The maximum number of processes available to a single user. + +@item -v +The maximum amount of virtual memory available to the process. + +@end table + +If @var{limit} is given, it is the new value of the specified resource; +the special @var{limit} values @code{hard}, @code{soft}, and +@code{unlimited} stand for the current hard limit, the current soft limit, +and no limit, respectively. +Otherwise, the current value of the soft limit for the specified resource +is printed, unless the @option{-H} option is supplied. +When setting new limits, if neither @option{-H} nor @option{-S} is supplied, +both the hard and soft limits are set. +If no option is given, then @option{-f} is assumed. Values are in 1024-byte +increments, except for @option{-t}, which is in seconds, @option{-p}, +which is in units of 512-byte blocks, and @option{-n} and @option{-u}, which +are unscaled values. + +The return status is zero unless an invalid option or argument is supplied, +or an error occurs while setting a new limit. + +@item unalias +@btindex unalias +@example +unalias [-a] [@var{name} @dots{} ] +@end example + +Remove each @var{name} from the list of aliases. If @option{-a} is +supplied, all aliases are removed. +Aliases are described in @ref{Aliases}. + +@end table + +@node The Set Builtin +@section The Set Builtin + +This builtin is so complicated that it deserves its own section. + +@table @code +@item set +@btindex set +@example +set [--abefhkmnptuvxBCHP] [-o @var{option}] [@var{argument} @dots{}] +@end example + +If no options or arguments are supplied, @code{set} displays the names +and values of all shell variables and functions, sorted according to the +current locale, in a format that may be reused as input. + +When options are supplied, they set or unset shell attributes. +Options, if specified, have the following meanings: + +@table @code +@item -a +Mark variables and function which are modified or created for export +to the environment of subsequent commands. + +@item -b +Cause the status of terminated background jobs to be reported +immediately, rather than before printing the next primary prompt. + +@item -e +Exit immediately if a simple command (@pxref{Simple Commands}) exits +with a non-zero status, unless the command that fails is part of the +command list immediately following a @code{while} or @code{until} +keyword, part of the test in an @code{if} statement, +part of a @code{&&} or @code{||} list, or if the command's return +status is being inverted using @code{!}. +A trap on @code{ERR}, if set, is executed before the shell exits. + +@item -f +Disable file name generation (globbing). + +@item -h +Locate and remember (hash) commands as they are looked up for execution. +This option is enabled by default. + +@item -k +All arguments in the form of assignment statements are placed +in the environment for a command, not just those that precede +the command name. + +@item -m +Job control is enabled (@pxref{Job Control}). + +@item -n +Read commands but do not execute them; this may be used to check a +script for syntax errors. +This option is ignored by interactive shells. + +@item -o @var{option-name} + +Set the option corresponding to @var{option-name}: + +@table @code +@item allexport +Same as @code{-a}. + +@item braceexpand +Same as @code{-B}. + +@item emacs +Use an @code{emacs}-style line editing interface (@pxref{Command Line Editing}). + +@item errexit +Same as @code{-e}. + +@item errtrace +Same as @code{-E}. + +@item functrace +Same as @code{-T}. + +@item hashall +Same as @code{-h}. + +@item histexpand +Same as @code{-H}. + +@item history +Enable command history, as described in @ref{Bash History Facilities}. +This option is on by default in interactive shells. + +@item ignoreeof +An interactive shell will not exit upon reading EOF. + +@item keyword +Same as @code{-k}. + +@item monitor +Same as @code{-m}. + +@item noclobber +Same as @code{-C}. + +@item noexec +Same as @code{-n}. + +@item noglob +Same as @code{-f}. + +@item nolog +Currently ignored. + +@item notify +Same as @code{-b}. + +@item nounset +Same as @code{-u}. + +@item onecmd +Same as @code{-t}. + +@item physical +Same as @code{-P}. + +@item pipefail +If set, the return value of a pipeline is the value of the last +(rightmost) command to exit with a non-zero status, or zero if all +commands in the pipeline exit successfully. +This option is disabled by default. + +@item posix +Change the behavior of Bash where the default operation differs +from the @sc{posix} 1003.2 standard to match the standard +(@pxref{Bash POSIX Mode}). +This is intended to make Bash behave as a strict superset of that +standard. + +@item privileged +Same as @code{-p}. + +@item verbose +Same as @code{-v}. + +@item vi +Use a @code{vi}-style line editing interface. + +@item xtrace +Same as @code{-x}. +@end table + +@item -p +Turn on privileged mode. +In this mode, the @env{$BASH_ENV} and @env{$ENV} files are not +processed, shell functions are not inherited from the environment, +and the @env{SHELLOPTS} variable, if it appears in the environment, +is ignored. +If the shell is started with the effective user (group) id not equal to the +real user (group) id, and the @code{-p} option is not supplied, these actions +are taken and the effective user id is set to the real user id. +If the @code{-p} option is supplied at startup, the effective user id is +not reset. +Turning this option off causes the effective user +and group ids to be set to the real user and group ids. + +@item -t +Exit after reading and executing one command. + +@item -u +Treat unset variables as an error when performing parameter expansion. +An error message will be written to the standard error, and a non-interactive +shell will exit. + +@item -v +Print shell input lines as they are read. + +@item -x +Print a trace of simple commands, \fBfor\fP commands, \fBcase\fP +commands, \fBselect\fP commands, and arithmetic \fBfor\fP commands +and their arguments or associated word lists after they are +expanded and before they are executed. The value of the @env{PS4} +variable is expanded and the resultant value is printed before +the command and its expanded arguments. + +@item -B +The shell will perform brace expansion (@pxref{Brace Expansion}). +This option is on by default. + +@item -C +Prevent output redirection using @samp{>}, @samp{>&}, and @samp{<>} +from overwriting existing files. + +@item -E +If set, any trap on @code{ERR} is inherited by shell functions, command +substitutions, and commands executed in a subshell environment. +The @code{ERR} trap is normally not inherited in such cases. + +@item -H +Enable @samp{!} style history substitution (@pxref{History Interaction}). +This option is on by default for interactive shells. + +@item -P +If set, do not follow symbolic links when performing commands such as +@code{cd} which change the current directory. The physical directory +is used instead. By default, Bash follows +the logical chain of directories when performing commands +which change the current directory. + +For example, if @file{/usr/sys} is a symbolic link to @file{/usr/local/sys} +then: +@example +$ cd /usr/sys; echo $PWD +/usr/sys +$ cd ..; pwd +/usr +@end example + +@noindent +If @code{set -P} is on, then: +@example +$ cd /usr/sys; echo $PWD +/usr/local/sys +$ cd ..; pwd +/usr/local +@end example + +@item -T +If set, any trap on @code{DEBUG} is inherited by shell functions, command +substitutions, and commands executed in a subshell environment. +The @code{DEBUG} trap is normally not inherited in such cases. + +@item -- +If no arguments follow this option, then the positional parameters are +unset. Otherwise, the positional parameters are set to the +@var{arguments}, even if some of them begin with a @samp{-}. + +@item - +Signal the end of options, cause all remaining @var{arguments} +to be assigned to the positional parameters. The @option{-x} +and @option{-v} options are turned off. +If there are no arguments, the positional parameters remain unchanged. +@end table + +Using @samp{+} rather than @samp{-} causes these options to be +turned off. The options can also be used upon invocation of the +shell. The current set of options may be found in @code{$-}. + +The remaining N @var{arguments} are positional parameters and are +assigned, in order, to @code{$1}, @code{$2}, @dots{} @code{$N}. +The special parameter @code{#} is set to N. + +The return status is always zero unless an invalid option is supplied. +@end table + +@node Special Builtins +@section Special Builtins +@cindex special builtin + +For historical reasons, the @sc{posix} 1003.2 standard has classified +several builtin commands as @emph{special}. +When Bash is executing in @sc{posix} mode, the special builtins +differ from other builtin commands in three respects: + +@enumerate +@item +Special builtins are found before shell functions during command lookup. + +@item +If a special builtin returns an error status, a non-interactive shell exits. + +@item +Assignment statements preceding the command stay in effect in the shell +environment after the command completes. +@end enumerate + +When Bash is not executing in @sc{posix} mode, these builtins behave no +differently than the rest of the Bash builtin commands. +The Bash @sc{posix} mode is described in @ref{Bash POSIX Mode}. + +These are the @sc{posix} special builtins: +@example +@w{break : . continue eval exec exit export readonly return set} +@w{shift trap unset} +@end example + +@node Shell Variables +@chapter Shell Variables + +@menu +* Bourne Shell Variables:: Variables which Bash uses in the same way + as the Bourne Shell. +* Bash Variables:: List of variables that exist in Bash. +@end menu + +This chapter describes the shell variables that Bash uses. +Bash automatically assigns default values to a number of variables. + +@node Bourne Shell Variables +@section Bourne Shell Variables + +Bash uses certain shell variables in the same way as the Bourne shell. +In some cases, Bash assigns a default value to the variable. + +@vtable @code + +@item CDPATH +A colon-separated list of directories used as a search path for +the @code{cd} builtin command. + +@item HOME +The current user's home directory; the default for the @code{cd} builtin +command. +The value of this variable is also used by tilde expansion +(@pxref{Tilde Expansion}). + +@item IFS +A list of characters that separate fields; used when the shell splits +words as part of expansion. + +@item MAIL +If this parameter is set to a filename and the @env{MAILPATH} variable +is not set, Bash informs the user of the arrival of mail in +the specified file. + +@item MAILPATH +A colon-separated list of filenames which the shell periodically checks +for new mail. +Each list entry can specify the message that is printed when new mail +arrives in the mail file by separating the file name from the message with +a @samp{?}. +When used in the text of the message, @code{$_} expands to the name of +the current mail file. + +@item OPTARG +The value of the last option argument processed by the @code{getopts} builtin. + +@item OPTIND +The index of the last option argument processed by the @code{getopts} builtin. + +@item PATH +A colon-separated list of directories in which the shell looks for +commands. +A zero-length (null) directory name in the value of @code{PATH} indicates the +current directory. +A null directory name may appear as two adjacent colons, or as an initial +or trailing colon. + + +@item PS1 +The primary prompt string. The default value is @samp{\s-\v\$ }. +@xref{Printing a Prompt}, for the complete list of escape +sequences that are expanded before @env{PS1} is displayed. + +@item PS2 +The secondary prompt string. The default value is @samp{> }. + +@end vtable + +@node Bash Variables +@section Bash Variables + +These variables are set or used by Bash, but other shells +do not normally treat them specially. + +A few variables used by Bash are described in different chapters: +variables for controlling the job control facilities +(@pxref{Job Control Variables}). + +@vtable @code + +@item BASH +The full pathname used to execute the current instance of Bash. + +@item BASH_ARGC +An array variable whose values are the number of parameters in each +frame of the current bash execution call stack. The number of +parameters to the current subroutine (shell function or script executed +with @code{.} or @code{source}) is at the top of the stack. When a +subroutine is executed, the number of parameters passed is pushed onto +@code{BASH_ARGC}. + +@item BASH_ARGV +An array variable containing all of the parameters in the current bash +execution call stack. The final parameter of the last subroutine call +is at the top of the stack; the first parameter of the initial call is +at the bottom. When a subroutine is executed, the parameters supplied +are pushed onto @code{BASH_ARGV}. + +@item BASH_COMMAND +The command currently being executed or about to be executed, unless the +shell is executing a command as the result of a trap, +in which case it is the command executing at the time of the trap. + +@item BASH_ENV +If this variable is set when Bash is invoked to execute a shell +script, its value is expanded and used as the name of a startup file +to read before executing the script. @xref{Bash Startup Files}. + +@item BASH_EXECUTION_STRING +The command argument to the @option{-c} invocation option. + +@item BASH_LINENO +An array variable whose members are the line numbers in source files +corresponding to each member of @var{FUNCNAME}. +@code{$@{BASH_LINENO[$i]@}} is the line number in the source file where +@code{$@{FUNCNAME[$i + 1]@}} was called. +The corresponding source file name is @code{$@{BASH_SOURCE[$i + 1]@}}. +Use @code{LINENO} to obtain the current line number. + +@item BASH_REMATCH +An array variable whose members are assigned by the @samp{=~} binary +operator to the @code{[[} conditional command +(@pxref{Conditional Constructs}). +The element with index 0 is the portion of the string +matching the entire regular expression. +The element with index @var{n} is the portion of the +string matching the @var{n}th parenthesized subexpression. +This variable is read-only. + +@item BASH_SOURCE +An array variable whose members are the source filenames corresponding +to the elements in the @code{FUNCNAME} array variable. + +@item BASH_SUBSHELL +Incremented by one each time a subshell or subshell environment is spawned. +The initial value is 0. + +@item BASH_VERSINFO +A readonly array variable (@pxref{Arrays}) +whose members hold version information for this instance of Bash. +The values assigned to the array members are as follows: + +@table @code + +@item BASH_VERSINFO[0] +The major version number (the @var{release}). + +@item BASH_VERSINFO[1] +The minor version number (the @var{version}). + +@item BASH_VERSINFO[2] +The patch level. + +@item BASH_VERSINFO[3] +The build version. + +@item BASH_VERSINFO[4] +The release status (e.g., @var{beta1}). + +@item BASH_VERSINFO[5] +The value of @env{MACHTYPE}. + +@end table + +@item BASH_VERSION +The version number of the current instance of Bash. + +@item COLUMNS +Used by the @code{select} builtin command to determine the terminal width +when printing selection lists. Automatically set upon receipt of a +@code{SIGWINCH}. + +@item COMP_CWORD +An index into @env{$@{COMP_WORDS@}} of the word containing the current +cursor position. +This variable is available only in shell functions invoked by the +programmable completion facilities (@pxref{Programmable Completion}). + +@item COMP_LINE +The current command line. +This variable is available only in shell functions and external +commands invoked by the +programmable completion facilities (@pxref{Programmable Completion}). + +@item COMP_POINT +The index of the current cursor position relative to the beginning of +the current command. +If the current cursor position is at the end of the current command, +the value of this variable is equal to @code{$@{#COMP_LINE@}}. +This variable is available only in shell functions and external +commands invoked by the +programmable completion facilities (@pxref{Programmable Completion}). + +@item COMP_WORDBREAKS +The set of characters that the Readline library treats as word +separators when performing word completion. +If @code{COMP_WORDBREAKS} is unset, it loses its special properties, +even if it is subsequently reset. + +@item COMP_WORDS +An array variable consisting of the individual +words in the current command line. +This variable is available only in shell functions invoked by the +programmable completion facilities (@pxref{Programmable Completion}). + +@item COMPREPLY +An array variable from which Bash reads the possible completions +generated by a shell function invoked by the programmable completion +facility (@pxref{Programmable Completion}). + +@item DIRSTACK +An array variable containing the current contents of the directory stack. +Directories appear in the stack in the order they are displayed by the +@code{dirs} builtin. +Assigning to members of this array variable may be used to modify +directories already in the stack, but the @code{pushd} and @code{popd} +builtins must be used to add and remove directories. +Assignment to this variable will not change the current directory. +If @env{DIRSTACK} is unset, it loses its special properties, even if +it is subsequently reset. + +@item EMACS +If Bash finds this variable in the environment when the shell +starts with value @samp{t}, it assumes that the shell is running in an +emacs shell buffer and disables line editing. + +@item EUID +The numeric effective user id of the current user. This variable +is readonly. + +@item FCEDIT +The editor used as a default by the @option{-e} option to the @code{fc} +builtin command. + +@item FIGNORE +A colon-separated list of suffixes to ignore when performing +filename completion. +A file name whose suffix matches one of the entries in +@env{FIGNORE} +is excluded from the list of matched file names. A sample +value is @samp{.o:~} + +@item FUNCNAME +An array variable containing the names of all shell functions +currently in the execution call stack. +The element with index 0 is the name of any currently-executing +shell function. +The bottom-most element is "main". +This variable exists only when a shell function is executing. +Assignments to @env{FUNCNAME} have no effect and return an error status. +If @env{FUNCNAME} is unset, it loses its special properties, even if +it is subsequently reset. + +@item GLOBIGNORE +A colon-separated list of patterns defining the set of filenames to +be ignored by filename expansion. +If a filename matched by a filename expansion pattern also matches one +of the patterns in @env{GLOBIGNORE}, it is removed from the list +of matches. + +@item GROUPS +An array variable containing the list of groups of which the current +user is a member. +Assignments to @env{GROUPS} have no effect and return an error status. +If @env{GROUPS} is unset, it loses its special properties, even if it is +subsequently reset. + +@item histchars +Up to three characters which control history expansion, quick +substitution, and tokenization (@pxref{History Interaction}). +The first character is the +@var{history expansion} character, that is, the character which signifies the +start of a history expansion, normally @samp{!}. The second character is the +character which signifies `quick substitution' when seen as the first +character on a line, normally @samp{^}. The optional third character is the +character which indicates that the remainder of the line is a comment when +found as the first character of a word, usually @samp{#}. The history +comment character causes history substitution to be skipped for the +remaining words on the line. It does not necessarily cause the shell +parser to treat the rest of the line as a comment. + +@item HISTCMD +The history number, or index in the history list, of the current +command. If @env{HISTCMD} is unset, it loses its special properties, +even if it is subsequently reset. + +@item HISTCONTROL +A colon-separated list of values controlling how commands are saved on +the history list. +If the list of values includes @samp{ignorespace}, lines which begin +with a space character are not saved in the history list. +A value of @samp{ignoredups} causes lines which match the previous +history entry to not be saved. +A value of @samp{ignoreboth} is shorthand for +@samp{ignorespace} and @samp{ignoredups}. +A value of @samp{erasedups} causes all previous lines matching the +current line to be removed from the history list before that line +is saved. +Any value not in the above list is ignored. +If @env{HISTCONTROL} is unset, or does not include a valid value, +all lines read by the shell parser are saved on the history list, +subject to the value of @env{HISTIGNORE}. +The second and subsequent lines of a multi-line compound command are +not tested, and are added to the history regardless of the value of +@env{HISTCONTROL}. + +@item HISTFILE +The name of the file to which the command history is saved. The +default value is @file{~/.bash_history}. + +@item HISTFILESIZE +The maximum number of lines contained in the history file. When this +variable is assigned a value, the history file is truncated, if +necessary, to contain no more than that number of lines. +The history file is also truncated to this size after +writing it when an interactive shell exits. +The default value is 500. + +@item HISTIGNORE +A colon-separated list of patterns used to decide which command +lines should be saved on the history list. Each pattern is +anchored at the beginning of the line and must match the complete +line (no implicit @samp{*} is appended). Each pattern is tested +against the line after the checks specified by @env{HISTCONTROL} +are applied. In addition to the normal shell pattern matching +characters, @samp{&} matches the previous history line. @samp{&} +may be escaped using a backslash; the backslash is removed +before attempting a match. +The second and subsequent lines of a multi-line compound command are +not tested, and are added to the history regardless of the value of +@env{HISTIGNORE}. + +@env{HISTIGNORE} subsumes the function of @env{HISTCONTROL}. A +pattern of @samp{&} is identical to @code{ignoredups}, and a +pattern of @samp{[ ]*} is identical to @code{ignorespace}. +Combining these two patterns, separating them with a colon, +provides the functionality of @code{ignoreboth}. + +@item HISTSIZE +The maximum number of commands to remember on the history list. +The default value is 500. + +@item HISTTIMEFORMAT +If this variable is set and not null, its value is used as a format string +for @var{strftime} to print the time stamp associated with each history +entry displayed by the @code{history} builtin. +If this variable is set, time stamps are written to the history file so +they may be preserved across shell sessions. + +@item HOSTFILE +Contains the name of a file in the same format as @file{/etc/hosts} that +should be read when the shell needs to complete a hostname. +The list of possible hostname completions may be changed while the shell +is running; +the next time hostname completion is attempted after the +value is changed, Bash adds the contents of the new file to the +existing list. +If @env{HOSTFILE} is set, but has no value, Bash attempts to read +@file{/etc/hosts} to obtain the list of possible hostname completions. +When @env{HOSTFILE} is unset, the hostname list is cleared. + +@item HOSTNAME +The name of the current host. + +@item HOSTTYPE +A string describing the machine Bash is running on. + +@item IGNOREEOF +Controls the action of the shell on receipt of an @code{EOF} character +as the sole input. If set, the value denotes the number +of consecutive @code{EOF} characters that can be read as the +first character on an input line +before the shell will exit. If the variable exists but does not +have a numeric value (or has no value) then the default is 10. +If the variable does not exist, then @code{EOF} signifies the end of +input to the shell. This is only in effect for interactive shells. + +@item INPUTRC +The name of the Readline initialization file, overriding the default +of @file{~/.inputrc}. + +@item LANG +Used to determine the locale category for any category not specifically +selected with a variable starting with @code{LC_}. + +@item LC_ALL +This variable overrides the value of @env{LANG} and any other +@code{LC_} variable specifying a locale category. + +@item LC_COLLATE +This variable determines the collation order used when sorting the +results of filename expansion, and +determines the behavior of range expressions, equivalence classes, +and collating sequences within filename expansion and pattern matching +(@pxref{Filename Expansion}). + +@item LC_CTYPE +This variable determines the interpretation of characters and the +behavior of character classes within filename expansion and pattern +matching (@pxref{Filename Expansion}). + +@item LC_MESSAGES +This variable determines the locale used to translate double-quoted +strings preceded by a @samp{$} (@pxref{Locale Translation}). + +@item LC_NUMERIC +This variable determines the locale category used for number formatting. + +@item LINENO +The line number in the script or shell function currently executing. + +@item LINES +Used by the @code{select} builtin command to determine the column length +for printing selection lists. Automatically set upon receipt of a +@code{SIGWINCH}. + +@item MACHTYPE +A string that fully describes the system type on which Bash +is executing, in the standard @sc{gnu} @var{cpu-company-system} format. + +@item MAILCHECK +How often (in seconds) that the shell should check for mail in the +files specified in the @env{MAILPATH} or @env{MAIL} variables. +The default is 60 seconds. When it is time to check +for mail, the shell does so before displaying the primary prompt. +If this variable is unset, or set to a value that is not a number +greater than or equal to zero, the shell disables mail checking. + +@item OLDPWD +The previous working directory as set by the @code{cd} builtin. + +@item OPTERR +If set to the value 1, Bash displays error messages +generated by the @code{getopts} builtin command. + +@item OSTYPE +A string describing the operating system Bash is running on. + +@item PIPESTATUS +An array variable (@pxref{Arrays}) +containing a list of exit status values from the processes +in the most-recently-executed foreground pipeline (which may +contain only a single command). + +@item POSIXLY_CORRECT +If this variable is in the environment when @code{bash} starts, the shell +enters @sc{posix} mode (@pxref{Bash POSIX Mode}) before reading the +startup files, as if the @option{--posix} invocation option had been supplied. +If it is set while the shell is running, @code{bash} enables @sc{posix} mode, +as if the command +@example +@code{set -o posix} +@end example +@noindent +had been executed. + +@item PPID +The process @sc{id} of the shell's parent process. This variable +is readonly. + +@item PROMPT_COMMAND +If set, the value is interpreted as a command to execute +before the printing of each primary prompt (@env{$PS1}). + +@item PS3 +The value of this variable is used as the prompt for the +@code{select} command. If this variable is not set, the +@code{select} command prompts with @samp{#? } + +@item PS4 +The value is the prompt printed before the command line is echoed +when the @option{-x} option is set (@pxref{The Set Builtin}). +The first character of @env{PS4} is replicated multiple times, as +necessary, to indicate multiple levels of indirection. +The default is @samp{+ }. + +@item PWD +The current working directory as set by the @code{cd} builtin. + +@item RANDOM +Each time this parameter is referenced, a random integer +between 0 and 32767 is generated. Assigning a value to this +variable seeds the random number generator. + +@item REPLY +The default variable for the @code{read} builtin. + +@item SECONDS +This variable expands to the number of seconds since the +shell was started. Assignment to this variable resets +the count to the value assigned, and the expanded value +becomes the value assigned plus the number of seconds +since the assignment. + +@item SHELL +The full pathname to the shell is kept in this environment variable. +If it is not set when the shell starts, +Bash assigns to it the full pathname of the current user's login shell. + +@item SHELLOPTS +A colon-separated list of enabled shell options. Each word in +the list is a valid argument for the @option{-o} option to the +@code{set} builtin command (@pxref{The Set Builtin}). +The options appearing in @env{SHELLOPTS} are those reported +as @samp{on} by @samp{set -o}. +If this variable is in the environment when Bash +starts up, each shell option in the list will be enabled before +reading any startup files. This variable is readonly. + +@item SHLVL +Incremented by one each time a new instance of Bash is started. This is +intended to be a count of how deeply your Bash shells are nested. + +@item TIMEFORMAT +The value of this parameter is used as a format string specifying +how the timing information for pipelines prefixed with the @code{time} +reserved word should be displayed. +The @samp{%} character introduces an +escape sequence that is expanded to a time value or other +information. +The escape sequences and their meanings are as +follows; the braces denote optional portions. + +@table @code + +@item %% +A literal @samp{%}. + +@item %[@var{p}][l]R +The elapsed time in seconds. + +@item %[@var{p}][l]U +The number of CPU seconds spent in user mode. + +@item %[@var{p}][l]S +The number of CPU seconds spent in system mode. + +@item %P +The CPU percentage, computed as (%U + %S) / %R. +@end table + +The optional @var{p} is a digit specifying the precision, the number of +fractional digits after a decimal point. +A value of 0 causes no decimal point or fraction to be output. +At most three places after the decimal point may be specified; values +of @var{p} greater than 3 are changed to 3. +If @var{p} is not specified, the value 3 is used. + +The optional @code{l} specifies a longer format, including minutes, of +the form @var{MM}m@var{SS}.@var{FF}s. +The value of @var{p} determines whether or not the fraction is included. + +If this variable is not set, Bash acts as if it had the value +@example +@code{$'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'} +@end example +If the value is null, no timing information is displayed. +A trailing newline is added when the format string is displayed. + +@item TMOUT +If set to a value greater than zero, @code{TMOUT} is treated as the +default timeout for the @code{read} builtin (@pxref{Bash Builtins}). +The @code{select} command (@pxref{Conditional Constructs}) terminates +if input does not arrive after @code{TMOUT} seconds when input is coming +from a terminal. + +In an interative shell, the value is interpreted as +the number of seconds to wait for input after issuing the primary +prompt when the shell is interactive. +Bash terminates after that number of seconds if input does +not arrive. + +@item UID +The numeric real user id of the current user. This variable is readonly. + +@end vtable + +@node Bash Features +@chapter Bash Features + +This section describes features unique to Bash. + +@menu +* Invoking Bash:: Command line options that you can give + to Bash. +* Bash Startup Files:: When and how Bash executes scripts. +* Interactive Shells:: What an interactive shell is. +* Bash Conditional Expressions:: Primitives used in composing expressions for + the @code{test} builtin. +* Shell Arithmetic:: Arithmetic on shell variables. +* Aliases:: Substituting one command for another. +* Arrays:: Array Variables. +* The Directory Stack:: History of visited directories. +* Printing a Prompt:: Controlling the PS1 string. +* The Restricted Shell:: A more controlled mode of shell execution. +* Bash POSIX Mode:: Making Bash behave more closely to what + the POSIX standard specifies. +@end menu + +@node Invoking Bash +@section Invoking Bash + +@example +bash [long-opt] [-ir] [-abefhkmnptuvxdBCDHP] [-o @var{option}] [-O @var{shopt_option}] [@var{argument} @dots{}] +bash [long-opt] [-abefhkmnptuvxdBCDHP] [-o @var{option}] [-O @var{shopt_option}] -c @var{string} [@var{argument} @dots{}] +bash [long-opt] -s [-abefhkmnptuvxdBCDHP] [-o @var{option}] [-O @var{shopt_option}] [@var{argument} @dots{}] +@end example + +In addition to the single-character shell command-line options +(@pxref{The Set Builtin}), there are several multi-character +options that you can use. These options must appear on the command +line before the single-character options to be recognized. + +@table @code +@item --debugger +Arrange for the debugger profile to be executed before the shell +starts. Turns on extended debugging mode (see @ref{Bash Builtins} +for a description of the @code{extdebug} option to the @code{shopt} +builtin) and shell function tracing +(see @ref{The Set Builtin} for a description of the @code{-o functrace} +option). + +@item --dump-po-strings +A list of all double-quoted strings preceded by @samp{$} +is printed on the standard ouput +in the @sc{gnu} @code{gettext} PO (portable object) file format. +Equivalent to @option{-D} except for the output format. + +@item --dump-strings +Equivalent to @option{-D}. + +@item --help +Display a usage message on standard output and exit sucessfully. + +@item --init-file @var{filename} +@itemx --rcfile @var{filename} +Execute commands from @var{filename} (instead of @file{~/.bashrc}) +in an interactive shell. + +@item --login +Equivalent to @option{-l}. + +@item --noediting +Do not use the @sc{gnu} Readline library (@pxref{Command Line Editing}) +to read command lines when the shell is interactive. + +@item --noprofile +Don't load the system-wide startup file @file{/etc/profile} +or any of the personal initialization files +@file{~/.bash_profile}, @file{~/.bash_login}, or @file{~/.profile} +when Bash is invoked as a login shell. + +@item --norc +Don't read the @file{~/.bashrc} initialization file in an +interactive shell. This is on by default if the shell is +invoked as @code{sh}. + +@item --posix +Change the behavior of Bash where the default operation differs +from the @sc{posix} 1003.2 standard to match the standard. This +is intended to make Bash behave as a strict superset of that +standard. @xref{Bash POSIX Mode}, for a description of the Bash +@sc{posix} mode. + +@item --restricted +Make the shell a restricted shell (@pxref{The Restricted Shell}). + +@item --verbose +Equivalent to @option{-v}. Print shell input lines as they're read. + +@item --version +Show version information for this instance of +Bash on the standard output and exit successfully. + +@end table + +There are several single-character options that may be supplied at +invocation which are not available with the @code{set} builtin. + +@table @code +@item -c @var{string} +Read and execute commands from @var{string} after processing the +options, then exit. Any remaining arguments are assigned to the +positional parameters, starting with @code{$0}. + +@item -i +Force the shell to run interactively. Interactive shells are +described in @ref{Interactive Shells}. + +@item -l +Make this shell act as if it had been directly invoked by login. +When the shell is interactive, this is equivalent to starting a +login shell with @samp{exec -l bash}. +When the shell is not interactive, the login shell startup files will +be executed. +@samp{exec bash -l} or @samp{exec bash --login} +will replace the current shell with a Bash login shell. +@xref{Bash Startup Files}, for a description of the special behavior +of a login shell. + +@item -r +Make the shell a restricted shell (@pxref{The Restricted Shell}). + +@item -s +If this option is present, or if no arguments remain after option +processing, then commands are read from the standard input. +This option allows the positional parameters to be set +when invoking an interactive shell. + +@item -D +A list of all double-quoted strings preceded by @samp{$} +is printed on the standard ouput. +These are the strings that +are subject to language translation when the current locale +is not @code{C} or @code{POSIX} (@pxref{Locale Translation}). +This implies the @option{-n} option; no commands will be executed. + +@item [-+]O [@var{shopt_option}] +@var{shopt_option} is one of the shell options accepted by the +@code{shopt} builtin (@pxref{Shell Builtin Commands}). +If @var{shopt_option} is present, @option{-O} sets the value of that option; +@option{+O} unsets it. +If @var{shopt_option} is not supplied, the names and values of the shell +options accepted by @code{shopt} are printed on the standard output. +If the invocation option is @option{+O}, the output is displayed in a format +that may be reused as input. + +@item -- +A @code{--} signals the end of options and disables further option +processing. +Any arguments after the @code{--} are treated as filenames and arguments. + +@end table + +@cindex login shell +A @emph{login} shell is one whose first character of argument zero is +@samp{-}, or one invoked with the @option{--login} option. + +@cindex interactive shell +An @emph{interactive} shell is one started without non-option arguments, +unless @option{-s} is specified, +without specifying the @option{-c} option, and whose input and output are both +connected to terminals (as determined by @code{isatty(3)}), or one +started with the @option{-i} option. @xref{Interactive Shells}, for more +information. + +If arguments remain after option processing, and neither the +@option{-c} nor the @option{-s} +option has been supplied, the first argument is assumed to +be the name of a file containing shell commands (@pxref{Shell Scripts}). +When Bash is invoked in this fashion, @code{$0} +is set to the name of the file, and the positional parameters +are set to the remaining arguments. +Bash reads and executes commands from this file, then exits. +Bash's exit status is the exit status of the last command executed +in the script. If no commands are executed, the exit status is 0. + +@node Bash Startup Files +@section Bash Startup Files +@cindex startup files + +This section describs how Bash executes its startup files. +If any of the files exist but cannot be read, Bash reports an error. +Tildes are expanded in file names as described above under +Tilde Expansion (@pxref{Tilde Expansion}). + +Interactive shells are described in @ref{Interactive Shells}. + +@subsubheading Invoked as an interactive login shell, or with @option{--login} + +When Bash is invoked as an interactive login shell, or as a +non-interactive shell with the @option{--login} option, it first reads and +executes commands from the file @file{/etc/profile}, if that file exists. +After reading that file, it looks for @file{~/.bash_profile}, +@file{~/.bash_login}, and @file{~/.profile}, in that order, and reads +and executes commands from the first one that exists and is readable. +The @option{--noprofile} option may be used when the shell is started to +inhibit this behavior. + +When a login shell exits, Bash reads and executes commands from +the file @file{~/.bash_logout}, if it exists. + +@subsubheading Invoked as an interactive non-login shell + +When an interactive shell that is not a login shell is started, Bash +reads and executes commands from @file{~/.bashrc}, if that file exists. +This may be inhibited by using the @option{--norc} option. +The @option{--rcfile @var{file}} option will force Bash to read and +execute commands from @var{file} instead of @file{~/.bashrc}. + +So, typically, your @file{~/.bash_profile} contains the line +@example +@code{if [ -f ~/.bashrc ]; then . ~/.bashrc; fi} +@end example +@noindent +after (or before) any login-specific initializations. + +@subsubheading Invoked non-interactively + +When Bash is started non-interactively, to run a shell script, +for example, it looks for the variable @env{BASH_ENV} in the environment, +expands its value if it appears there, and uses the expanded value as +the name of a file to read and execute. Bash behaves as if the +following command were executed: +@example +@code{if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi} +@end example +@noindent +but the value of the @env{PATH} variable is not used to search for the +file name. + +As noted above, if a non-interactive shell is invoked with the +@option{--login} option, Bash attempts to read and execute commands from the +login shell startup files. + +@subsubheading Invoked with name @code{sh} + +If Bash is invoked with the name @code{sh}, it tries to mimic the +startup behavior of historical versions of @code{sh} as closely as +possible, while conforming to the @sc{posix} standard as well. + +When invoked as an interactive login shell, or as a non-interactive +shell with the @option{--login} option, it first attempts to read +and execute commands from @file{/etc/profile} and @file{~/.profile}, in +that order. +The @option{--noprofile} option may be used to inhibit this behavior. +When invoked as an interactive shell with the name @code{sh}, Bash +looks for the variable @env{ENV}, expands its value if it is defined, +and uses the expanded value as the name of a file to read and execute. +Since a shell invoked as @code{sh} does not attempt to read and execute +commands from any other startup files, the @option{--rcfile} option has +no effect. +A non-interactive shell invoked with the name @code{sh} does not attempt +to read any other startup files. + +When invoked as @code{sh}, Bash enters @sc{posix} mode after +the startup files are read. + +@subsubheading Invoked in @sc{posix} mode + +When Bash is started in @sc{posix} mode, as with the +@option{--posix} command line option, it follows the @sc{posix} standard +for startup files. +In this mode, interactive shells expand the @env{ENV} variable +and commands are read and executed from the file whose name is the +expanded value. +No other startup files are read. + +@subsubheading Invoked by remote shell daemon + +Bash attempts to determine when it is being run by the remote shell +daemon, usually @code{rshd}. If Bash determines it is being run by +rshd, it reads and executes commands from @file{~/.bashrc}, if that +file exists and is readable. +It will not do this if invoked as @code{sh}. +The @option{--norc} option may be used to inhibit this behavior, and the +@option{--rcfile} option may be used to force another file to be read, but +@code{rshd} does not generally invoke the shell with those options or +allow them to be specified. + +@subsubheading Invoked with unequal effective and real @sc{uid/gid}s + +If Bash is started with the effective user (group) id not equal to the +real user (group) id, and the @code{-p} option is not supplied, no startup +files are read, shell functions are not inherited from the environment, +the @env{SHELLOPTS} variable, if it appears in the environment, is ignored, +and the effective user id is set to the real user id. +If the @code{-p} option is supplied at invocation, the startup behavior is +the same, but the effective user id is not reset. + +@node Interactive Shells +@section Interactive Shells +@cindex interactive shell +@cindex shell, interactive + +@menu +* What is an Interactive Shell?:: What determines whether a shell is Interactive. +* Is this Shell Interactive?:: How to tell if a shell is interactive. +* Interactive Shell Behavior:: What changes in a interactive shell? +@end menu + +@node What is an Interactive Shell? +@subsection What is an Interactive Shell? + +An interactive shell +is one started without non-option arguments, unless @option{-s} is +specified, without specifiying the @option{-c} option, and +whose input and error output are both +connected to terminals (as determined by @code{isatty(3)}), +or one started with the @option{-i} option. + +An interactive shell generally reads from and writes to a user's +terminal. + +The @option{-s} invocation option may be used to set the positional parameters +when an interactive shell is started. + +@node Is this Shell Interactive? +@subsection Is this Shell Interactive? + +To determine within a startup script whether or not Bash is +running interactively, +test the value of the @samp{-} special parameter. +It contains @code{i} when the shell is interactive. For example: + +@example +case "$-" in +*i*) echo This shell is interactive ;; +*) echo This shell is not interactive ;; +esac +@end example + +Alternatively, startup scripts may examine the variable +@env{PS1}; it is unset in non-interactive shells, and set in +interactive shells. Thus: + +@example +if [ -z "$PS1" ]; then + echo This shell is not interactive +else + echo This shell is interactive +fi +@end example + +@node Interactive Shell Behavior +@subsection Interactive Shell Behavior + +When the shell is running interactively, it changes its behavior in +several ways. + +@enumerate +@item +Startup files are read and executed as described in @ref{Bash Startup Files}. + +@item +Job Control (@pxref{Job Control}) is enabled by default. When job +control is in effect, Bash ignores the keyboard-generated job control +signals @code{SIGTTIN}, @code{SIGTTOU}, and @code{SIGTSTP}. + +@item +Bash expands and displays @env{PS1} before reading the first line +of a command, and expands and displays @env{PS2} before reading the +second and subsequent lines of a multi-line command. + +@item +Bash executes the value of the @env{PROMPT_COMMAND} variable as a command +before printing the primary prompt, @env{$PS1} +(@pxref{Bash Variables}). + +@item +Readline (@pxref{Command Line Editing}) is used to read commands from +the user's terminal. + +@item +Bash inspects the value of the @code{ignoreeof} option to @code{set -o} +instead of exiting immediately when it receives an @code{EOF} on its +standard input when reading a command (@pxref{The Set Builtin}). + +@item +Command history (@pxref{Bash History Facilities}) +and history expansion (@pxref{History Interaction}) +are enabled by default. +Bash will save the command history to the file named by @env{$HISTFILE} +when an interactive shell exits. + +@item +Alias expansion (@pxref{Aliases}) is performed by default. + +@item +In the absence of any traps, Bash ignores @code{SIGTERM} +(@pxref{Signals}). + +@item +In the absence of any traps, @code{SIGINT} is caught and handled +((@pxref{Signals}). +@code{SIGINT} will interrupt some shell builtins. + +@item +An interactive login shell sends a @code{SIGHUP} to all jobs on exit +if the @code{hupoxexit} shell option has been enabled (@pxref{Signals}). + +@item +The @option{-n} invocation option is ignored, and @samp{set -n} has +no effect (@pxref{The Set Builtin}). + +@item +Bash will check for mail periodically, depending on the values of the +@env{MAIL}, @env{MAILPATH}, and @env{MAILCHECK} shell variables +(@pxref{Bash Variables}). + +@item +Expansion errors due to references to unbound shell variables after +@samp{set -u} has been enabled will not cause the shell to exit +(@pxref{The Set Builtin}). + +@item +The shell will not exit on expansion errors caused by @var{var} being unset +or null in @code{$@{@var{var}:?@var{word}@}} expansions +(@pxref{Shell Parameter Expansion}). + +@item +Redirection errors encountered by shell builtins will not cause the +shell to exit. + +@item +When running in @sc{posix} mode, a special builtin returning an error +status will not cause the shell to exit (@pxref{Bash POSIX Mode}). +@item +A failed @code{exec} will not cause the shell to exit +(@pxref{Bourne Shell Builtins}). + +@item +Parser syntax errors will not cause the shell to exit. + +@item +Simple spelling correction for directory arguments to the @code{cd} +builtin is enabled by default (see the description of the @code{cdspell} +option to the @code{shopt} builtin in @ref{Bash Builtins}). + +@item +The shell will check the value of the @env{TMOUT} variable and exit +if a command is not read within the specified number of seconds after +printing @env{$PS1} (@pxref{Bash Variables}). + +@end enumerate + +@node Bash Conditional Expressions +@section Bash Conditional Expressions +@cindex expressions, conditional + +Conditional expressions are used by the @code{[[} compound command +and the @code{test} and @code{[} builtin commands. + +Expressions may be unary or binary. +Unary expressions are often used to examine the status of a file. +There are string operators and numeric comparison operators as well. +If the @var{file} argument to one of the primaries is of the form +@file{/dev/fd/@var{N}}, then file descriptor @var{N} is checked. +If the @var{file} argument to one of the primaries is one of +@file{/dev/stdin}, @file{/dev/stdout}, or @file{/dev/stderr}, file +descriptor 0, 1, or 2, respectively, is checked. + +@table @code +@item -a @var{file} +True if @var{file} exists. + +@item -b @var{file} +True if @var{file} exists and is a block special file. + +@item -c @var{file} +True if @var{file} exists and is a character special file. + +@item -d @var{file} +True if @var{file} exists and is a directory. + +@item -e @var{file} +True if @var{file} exists. + +@item -f @var{file} +True if @var{file} exists and is a regular file. + +@item -g @var{file} +True if @var{file} exists and its set-group-id bit is set. + +@item -h @var{file} +True if @var{file} exists and is a symbolic link. + +@item -k @var{file} +True if @var{file} exists and its "sticky" bit is set. + +@item -p @var{file} +True if @var{file} exists and is a named pipe (FIFO). + +@item -r @var{file} +True if @var{file} exists and is readable. + +@item -s @var{file} +True if @var{file} exists and has a size greater than zero. + +@item -t @var{fd} +True if file descriptor @var{fd} is open and refers to a terminal. + +@item -u @var{file} +True if @var{file} exists and its set-user-id bit is set. + +@item -w @var{file} +True if @var{file} exists and is writable. + +@item -x @var{file} +True if @var{file} exists and is executable. + +@item -O @var{file} +True if @var{file} exists and is owned by the effective user id. + +@item -G @var{file} +True if @var{file} exists and is owned by the effective group id. + +@item -L @var{file} +True if @var{file} exists and is a symbolic link. + +@item -S @var{file} +True if @var{file} exists and is a socket. + +@item -N @var{file} +True if @var{file} exists and has been modified since it was last read. + +@item @var{file1} -nt @var{file2} +True if @var{file1} is newer (according to modification date) +than @var{file2}, or if @var{file1} exists and @var{file2} does not. + +@item @var{file1} -ot @var{file2} +True if @var{file1} is older than @var{file2}, +or if @var{file2} exists and @var{file1} does not. + +@item @var{file1} -ef @var{file2} +True if @var{file1} and @var{file2} refer to the same device and +inode numbers. + +@item -o @var{optname} +True if shell option @var{optname} is enabled. +The list of options appears in the description of the @option{-o} +option to the @code{set} builtin (@pxref{The Set Builtin}). + +@item -z @var{string} +True if the length of @var{string} is zero. + +@item -n @var{string} +@itemx @var{string} +True if the length of @var{string} is non-zero. + +@item @var{string1} == @var{string2} +True if the strings are equal. +@samp{=} may be used in place of @samp{==} for strict @sc{posix} compliance. + +@item @var{string1} != @var{string2} +True if the strings are not equal. + +@item @var{string1} < @var{string2} +True if @var{string1} sorts before @var{string2} lexicographically +in the current locale. + +@item @var{string1} > @var{string2} +True if @var{string1} sorts after @var{string2} lexicographically +in the current locale. + +@item @var{arg1} OP @var{arg2} +@code{OP} is one of +@samp{-eq}, @samp{-ne}, @samp{-lt}, @samp{-le}, @samp{-gt}, or @samp{-ge}. +These arithmetic binary operators return true if @var{arg1} +is equal to, not equal to, less than, less than or equal to, +greater than, or greater than or equal to @var{arg2}, +respectively. @var{Arg1} and @var{arg2} +may be positive or negative integers. + +@end table + +@node Shell Arithmetic +@section Shell Arithmetic +@cindex arithmetic, shell +@cindex shell arithmetic +@cindex expressions, arithmetic +@cindex evaluation, arithmetic +@cindex arithmetic evaluation + +The shell allows arithmetic expressions to be evaluated, as one of +the shell expansions or by the @code{let} and the @option{-i} option +to the @code{declare} builtins. + +Evaluation is done in fixed-width integers with no check for overflow, +though division by 0 is trapped and flagged as an error. +The operators and their precedence, associativity, and values +are the same as in the C language. +The following list of operators is grouped into levels of +equal-precedence operators. +The levels are listed in order of decreasing precedence. + +@table @code + +@item @var{id}++ @var{id}-- +variable post-increment and post-decrement + +@item ++@var{id} --@var{id} +variable pre-increment and pre-decrement + +@item - + +unary minus and plus + +@item ! ~ +logical and bitwise negation + +@item ** +exponentiation + +@item * / % +multiplication, division, remainder + +@item + - +addition, subtraction + +@item << >> +left and right bitwise shifts + +@item <= >= < > +comparison + +@item == != +equality and inequality + +@item & +bitwise AND + +@item ^ +bitwise exclusive OR + +@item | +bitwise OR + +@item && +logical AND + +@item || +logical OR + +@item expr ? expr : expr +conditional operator + +@item = *= /= %= += -= <<= >>= &= ^= |= +assignment + +@item expr1 , expr2 +comma +@end table + +Shell variables are allowed as operands; parameter expansion is +performed before the expression is evaluated. +Within an expression, shell variables may also be referenced by name +without using the parameter expansion syntax. +A shell variable that is null or unset evaluates to 0 when referenced +by name without using the parameter expansion syntax. +The value of a variable is evaluated as an arithmetic expression +when it is referenced, or when a variable which has been given the +@var{integer} attribute using @samp{declare -i} is assigned a value. +A null value evaluates to 0. +A shell variable need not have its integer attribute turned on +to be used in an expression. + +Constants with a leading 0 are interpreted as octal numbers. +A leading @samp{0x} or @samp{0X} denotes hexadecimal. Otherwise, +numbers take the form [@var{base}@code{#}]@var{n}, where @var{base} +is a decimal number between 2 and 64 representing the arithmetic +base, and @var{n} is a number in that base. If @var{base}@code{#} is +omitted, then base 10 is used. +The digits greater than 9 are represented by the lowercase letters, +the uppercase letters, @samp{@@}, and @samp{_}, in that order. +If @var{base} is less than or equal to 36, lowercase and uppercase +letters may be used interchangably to represent numbers between 10 +and 35. + +Operators are evaluated in order of precedence. Sub-expressions in +parentheses are evaluated first and may override the precedence +rules above. + +@node Aliases +@section Aliases +@cindex alias expansion + +@var{Aliases} allow a string to be substituted for a word when it is used +as the first word of a simple command. +The shell maintains a list of aliases that may be set and unset with +the @code{alias} and @code{unalias} builtin commands. + +The first word of each simple command, if unquoted, is checked to see +if it has an alias. +If so, that word is replaced by the text of the alias. +The characters @samp{/}, @samp{$}, @samp{`}, @samp{=} and any of the +shell metacharacters or quoting characters listed above may not appear +in an alias name. +The replacement text may contain any valid +shell input, including shell metacharacters. +The first word of the replacement text is tested for +aliases, but a word that is identical to an alias being expanded +is not expanded a second time. +This means that one may alias @code{ls} to @code{"ls -F"}, +for instance, and Bash does not try to recursively expand the +replacement text. If the last character of the alias value is a +space or tab character, then the next command word following the +alias is also checked for alias expansion. + +Aliases are created and listed with the @code{alias} +command, and removed with the @code{unalias} command. + +There is no mechanism for using arguments in the replacement text, +as in @code{csh}. +If arguments are needed, a shell function should be used +(@pxref{Shell Functions}). + +Aliases are not expanded when the shell is not interactive, +unless the @code{expand_aliases} shell option is set using +@code{shopt} (@pxref{Bash Builtins}). + +The rules concerning the definition and use of aliases are +somewhat confusing. Bash +always reads at least one complete line +of input before executing any +of the commands on that line. Aliases are expanded when a +command is read, not when it is executed. Therefore, an +alias definition appearing on the same line as another +command does not take effect until the next line of input is read. +The commands following the alias definition +on that line are not affected by the new alias. +This behavior is also an issue when functions are executed. +Aliases are expanded when a function definition is read, +not when the function is executed, because a function definition +is itself a compound command. As a consequence, aliases +defined in a function are not available until after that +function is executed. To be safe, always put +alias definitions on a separate line, and do not use @code{alias} +in compound commands. + +For almost every purpose, shell functions are preferred over aliases. + +@node Arrays +@section Arrays +@cindex arrays + +Bash provides one-dimensional array variables. Any variable may be used as +an array; the @code{declare} builtin will explicitly declare an array. +There is no maximum +limit on the size of an array, nor any requirement that members +be indexed or assigned contiguously. Arrays are zero-based. + +An array is created automatically if any variable is assigned to using +the syntax +@example +name[@var{subscript}]=@var{value} +@end example + +@noindent +The @var{subscript} +is treated as an arithmetic expression that must evaluate to a number +greater than or equal to zero. To explicitly declare an array, use +@example +declare -a @var{name} +@end example +@noindent +The syntax +@example +declare -a @var{name}[@var{subscript}] +@end example +@noindent +is also accepted; the @var{subscript} is ignored. Attributes may be +specified for an array variable using the @code{declare} and +@code{readonly} builtins. Each attribute applies to all members of +an array. + +Arrays are assigned to using compound assignments of the form +@example +name=(value@var{1} @dots{} value@var{n}) +@end example +@noindent +where each +@var{value} is of the form @code{[[@var{subscript}]=]}@var{string}. If +the optional subscript is supplied, that index is assigned to; +otherwise the index of the element assigned is the last index assigned +to by the statement plus one. Indexing starts at zero. +This syntax is also accepted by the @code{declare} +builtin. Individual array elements may be assigned to using the +@code{name[}@var{subscript}@code{]=}@var{value} syntax introduced above. + +Any element of an array may be referenced using +@code{$@{name[}@var{subscript}@code{]@}}. +The braces are required to avoid +conflicts with the shell's filename expansion operators. If the +@var{subscript} is @samp{@@} or @samp{*}, the word expands to all members +of the array @var{name}. These subscripts differ only when the word +appears within double quotes. If the word is double-quoted, +@code{$@{name[*]@}} expands to a single word with +the value of each array member separated by the first character of the +@env{IFS} variable, and @code{$@{name[@@]@}} expands each element of +@var{name} to a separate word. When there are no array members, +@code{$@{name[@@]@}} expands to nothing. This is analogous to the +expansion of the special parameters @samp{@@} and @samp{*}. +@code{$@{#name[}@var{subscript}@code{]@}} expands to the length of +@code{$@{name[}@var{subscript}@code{]@}}. +If @var{subscript} is @samp{@@} or +@samp{*}, the expansion is the number of elements in the array. +Referencing an array variable without a subscript is equivalent to +referencing element zero. + +The @code{unset} builtin is used to destroy arrays. +@code{unset} @var{name}[@var{subscript}] +destroys the array element at index @var{subscript}. +@code{unset} @var{name}, where @var{name} is an array, removes the +entire array. A subscript of @samp{*} or @samp{@@} also removes the +entire array. + +The @code{declare}, @code{local}, and @code{readonly} +builtins each accept a @option{-a} +option to specify an array. The @code{read} +builtin accepts a @option{-a} +option to assign a list of words read from the standard input +to an array, and can read values from the standard input into +individual array elements. The @code{set} and @code{declare} +builtins display array values in a way that allows them to be +reused as input. + +@node The Directory Stack +@section The Directory Stack +@cindex directory stack + +@menu +* Directory Stack Builtins:: Bash builtin commands to manipulate + the directory stack. +@end menu + +The directory stack is a list of recently-visited directories. The +@code{pushd} builtin adds directories to the stack as it changes +the current directory, and the @code{popd} builtin removes specified +directories from the stack and changes the current directory to +the directory removed. The @code{dirs} builtin displays the contents +of the directory stack. + +The contents of the directory stack are also visible +as the value of the @env{DIRSTACK} shell variable. + +@node Directory Stack Builtins +@subsection Directory Stack Builtins + +@table @code + +@item dirs +@btindex dirs +@example +dirs [+@var{N} | -@var{N}] [-clpv] +@end example +Display the list of currently remembered directories. Directories +are added to the list with the @code{pushd} command; the +@code{popd} command removes directories from the list. +@table @code +@item +@var{N} +Displays the @var{N}th directory (counting from the left of the +list printed by @code{dirs} when invoked without options), starting +with zero. +@item -@var{N} +Displays the @var{N}th directory (counting from the right of the +list printed by @code{dirs} when invoked without options), starting +with zero. +@item -c +Clears the directory stack by deleting all of the elements. +@item -l +Produces a longer listing; the default listing format uses a +tilde to denote the home directory. +@item -p +Causes @code{dirs} to print the directory stack with one entry per +line. +@item -v +Causes @code{dirs} to print the directory stack with one entry per +line, prefixing each entry with its index in the stack. +@end table + +@item popd +@btindex popd +@example +popd [+@var{N} | -@var{N}] [-n] +@end example + +Remove the top entry from the directory stack, and @code{cd} +to the new top directory. +When no arguments are given, @code{popd} +removes the top directory from the stack and +performs a @code{cd} to the new top directory. The +elements are numbered from 0 starting at the first directory listed with +@code{dirs}; i.e., @code{popd} is equivalent to @code{popd +0}. +@table @code +@item +@var{N} +Removes the @var{N}th directory (counting from the left of the +list printed by @code{dirs}), starting with zero. +@item -@var{N} +Removes the @var{N}th directory (counting from the right of the +list printed by @code{dirs}), starting with zero. +@item -n +Suppresses the normal change of directory when removing directories +from the stack, so that only the stack is manipulated. +@end table + +@btindex pushd +@item pushd +@example +pushd [@var{dir} | @var{+N} | @var{-N}] [-n] +@end example + +Save the current directory on the top of the directory stack +and then @code{cd} to @var{dir}. +With no arguments, @code{pushd} exchanges the top two directories. + +@table @code +@item +@var{N} +Brings the @var{N}th directory (counting from the left of the +list printed by @code{dirs}, starting with zero) to the top of +the list by rotating the stack. +@item -@var{N} +Brings the @var{N}th directory (counting from the right of the +list printed by @code{dirs}, starting with zero) to the top of +the list by rotating the stack. +@item -n +Suppresses the normal change of directory when adding directories +to the stack, so that only the stack is manipulated. +@item @var{dir} +Makes the current working directory be the top of the stack, and then +executes the equivalent of `@code{cd} @var{dir}'. +@code{cd}s to @var{dir}. +@end table + +@end table + +@node Printing a Prompt +@section Controlling the Prompt +@cindex prompting + +The value of the variable @env{PROMPT_COMMAND} is examined just before +Bash prints each primary prompt. If @env{PROMPT_COMMAND} is set and +has a non-null value, then the +value is executed just as if it had been typed on the command line. + +In addition, the following table describes the special characters which +can appear in the prompt variables: + +@table @code +@item \a +A bell character. +@item \d +The date, in "Weekday Month Date" format (e.g., "Tue May 26"). +@item \D@{@var{format}@} +The @var{format} is passed to @code{strftime}(3) and the result is inserted +into the prompt string; an empty @var{format} results in a locale-specific +time representation. The braces are required. +@item \e +An escape character. +@item \h +The hostname, up to the first `.'. +@item \H +The hostname. +@item \j +The number of jobs currently managed by the shell. +@item \l +The basename of the shell's terminal device name. +@item \n +A newline. +@item \r +A carriage return. +@item \s +The name of the shell, the basename of @code{$0} (the portion +following the final slash). +@item \t +The time, in 24-hour HH:MM:SS format. +@item \T +The time, in 12-hour HH:MM:SS format. +@item \@@ +The time, in 12-hour am/pm format. +@item \A +The time, in 24-hour HH:MM format. +@item \u +The username of the current user. +@item \v +The version of Bash (e.g., 2.00) +@item \V +The release of Bash, version + patchlevel (e.g., 2.00.0) +@item \w +The current working directory, with @env{$HOME} abbreviated with a tilde. +@item \W +The basename of @env{$PWD}, with @env{$HOME} abbreviated with a tilde. +@item \! +The history number of this command. +@item \# +The command number of this command. +@item \$ +If the effective uid is 0, @code{#}, otherwise @code{$}. +@item \@var{nnn} +The character whose ASCII code is the octal value @var{nnn}. +@item \\ +A backslash. +@item \[ +Begin a sequence of non-printing characters. This could be used to +embed a terminal control sequence into the prompt. +@item \] +End a sequence of non-printing characters. +@end table + +The command number and the history number are usually different: +the history number of a command is its position in the history +list, which may include commands restored from the history file +(@pxref{Bash History Facilities}), while the command number is +the position in the sequence of commands executed during the current +shell session. + +After the string is decoded, it is expanded via +parameter expansion, command substitution, arithmetic +expansion, and quote removal, subject to the value of the +@code{promptvars} shell option (@pxref{Bash Builtins}). + +@node The Restricted Shell +@section The Restricted Shell +@cindex restricted shell + +If Bash is started with the name @code{rbash}, or the +@option{--restricted} +or +@option{-r} +option is supplied at invocation, the shell becomes restricted. +A restricted shell is used to +set up an environment more controlled than the standard shell. +A restricted shell behaves identically to @code{bash} +with the exception that the following are disallowed or not performed: + +@itemize @bullet +@item +Changing directories with the @code{cd} builtin. +@item +Setting or unsetting the values of the @env{SHELL}, @env{PATH}, +@env{ENV}, or @env{BASH_ENV} variables. +@item +Specifying command names containing slashes. +@item +Specifying a filename containing a slash as an argument to the @code{.} +builtin command. +@item +Specifying a filename containing a slash as an argument to the @option{-p} +option to the @code{hash} builtin command. +@item +Importing function definitions from the shell environment at startup. +@item +Parsing the value of @env{SHELLOPTS} from the shell environment at startup. +@item +Redirecting output using the @samp{>}, @samp{>|}, @samp{<>}, @samp{>&}, +@samp{&>}, and @samp{>>} redirection operators. +@item +Using the @code{exec} builtin to replace the shell with another command. +@item +Adding or deleting builtin commands with the +@option{-f} and @option{-d} options to the @code{enable} builtin. +@item +Using the @code{enable} builtin command to enable disabled shell builtins. +@item +Specifying the @option{-p} option to the @code{command} builtin. +@item +Turning off restricted mode with @samp{set +r} or @samp{set +o restricted}. +@end itemize + +These restrictions are enforced after any startup files are read. + +When a command that is found to be a shell script is executed +(@pxref{Shell Scripts}), @code{rbash} turns off any restrictions in +the shell spawned to execute the script. + +@node Bash POSIX Mode +@section Bash POSIX Mode +@cindex POSIX Mode + +Starting Bash with the @option{--posix} command-line option or executing +@samp{set -o posix} while Bash is running will cause Bash to conform more +closely to the @sc{posix} 1003.2 standard by changing the behavior to +match that specified by @sc{posix} in areas where the Bash default differs. + +When invoked as @code{sh}, Bash enters @sc{posix} mode after reading the +startup files. + +The following list is what's changed when `@sc{posix} mode' is in effect: + +@enumerate +@item +When a command in the hash table no longer exists, Bash will re-search +@env{$PATH} to find the new location. This is also available with +@samp{shopt -s checkhash}. + +@item +The message printed by the job control code and builtins when a job +exits with a non-zero status is `Done(status)'. + +@item +The message printed by the job control code and builtins when a job +is stopped is `Stopped(@var{signame})', where @var{signame} is, for +example, @code{SIGTSTP}. + +@item +Reserved words may not be aliased. + +@item +The @sc{posix} 1003.2 @env{PS1} and @env{PS2} expansions of @samp{!} to +the history number and @samp{!!} to @samp{!} are enabled, +and parameter expansion is performed on the values of @env{PS1} and +@env{PS2} regardless of the setting of the @code{promptvars} option. + +@item +The @sc{posix} 1003.2 startup files are executed (@env{$ENV}) rather than +the normal Bash files. + +@item +Tilde expansion is only performed on assignments preceding a command +name, rather than on all assignment statements on the line. + +@item +The default history file is @file{~/.sh_history} (this is the +default value of @env{$HISTFILE}). + +@item +The output of @samp{kill -l} prints all the signal names on a single line, +separated by spaces, without the @samp{SIG} prefix. + +@item +The @code{kill} builtin does not accept signal names with a @samp{SIG} +prefix. + +@item +Non-interactive shells exit if @var{filename} in @code{.} @var{filename} +is not found. + +@item +Non-interactive shells exit if a syntax error in an arithmetic expansion +results in an invalid expression. + +@item +Redirection operators do not perform filename expansion on the word +in the redirection unless the shell is interactive. + +@item +Redirection operators do not perform word splitting on the word in the +redirection. + +@item +Function names must be valid shell @code{name}s. That is, they may not +contain characters other than letters, digits, and underscores, and +may not start with a digit. Declaring a function with an invalid name +causes a fatal syntax error in non-interactive shells. + +@item +@sc{posix} 1003.2 `special' builtins are found before shell functions +during command lookup. + +@item +If a @sc{posix} 1003.2 special builtin returns an error status, a +non-interactive shell exits. The fatal errors are those listed in +the POSIX.2 standard, and include things like passing incorrect options, +redirection errors, variable assignment errors for assignments preceding +the command name, and so on. + +@item +If the @code{cd} builtin finds a directory to change to +using @env{$CDPATH}, the +value it assigns to the @env{PWD} variable does not contain any +symbolic links, as if @samp{cd -P} had been executed. + +@item +If @env{CDPATH} is set, the @code{cd} builtin will not implicitly +append the current directory to it. This means that @code{cd} will +fail if no valid directory name can be constructed from +any of the entries in @env{$CDPATH}, even if the a directory with +the same name as the name given as an argument to @code{cd} exists +in the current directory. + +@item +A non-interactive shell exits with an error status if a variable +assignment error occurs when no command name follows the assignment +statements. +A variable assignment error occurs, for example, when trying to assign +a value to a readonly variable. + +@item +A non-interactive shell exits with an error status if the iteration +variable in a @code{for} statement or the selection variable in a +@code{select} statement is a readonly variable. + +@item +Process substitution is not available. + +@item +Assignment statements preceding @sc{posix} 1003.2 special builtins +persist in the shell environment after the builtin completes. + +@item +Assignment statements preceding shell function calls persist in the +shell environment after the function returns, as if a @sc{posix} +special builtin command had been executed. + +@item +The @code{export} and @code{readonly} builtin commands display their +output in the format required by @sc{posix} 1003.2. + +@item +The @code{trap} builtin displays signal names without the leading +@code{SIG}. + +@item +The @code{trap} builtin doesn't check the first argument for a possible +signal specification and revert the signal handling to the original +disposition if it is, unless that argument consists solely of digits and +is a valid signal number. If users want to reset the handler for a given +signal to the original disposition, they should use @samp{-} as the +first argument. + +@item +The @code{.} and @code{source} builtins do not search the current directory +for the filename argument if it is not found by searching @env{PATH}. + +@item +Subshells spawned to execute command substitutions inherit the value of +the @option{-e} option from the parent shell. When not in @sc{posix} mode, +Bash clears the @option{-e} option in such subshells. + +@item +Alias expansion is always enabled, even in non-interactive shells. + +@item +When the @code{alias} builtin displays alias definitions, it does not +display them with a leading @samp{alias } unless the @option{-p} option +is supplied. + +@item +When the @code{set} builtin is invoked without options, it does not display +shell function names and definitions. + +@item +When the @code{set} builtin is invoked without options, it displays +variable values without quotes, unless they contain shell metacharacters, +even if the result contains nonprinting characters. + +@item +When the @code{cd} builtin is invoked in @var{logical} mode, and the pathname +constructed from @code{$PWD} and the directory name supplied as an argument +does not refer to an existing directory, @code{cd} will fail instead of +falling back to @var{physical} mode. + +@item +When listing the history, the @code{fc} builtin does not include an +indication of whether or not a history entry has been modified. + +@end enumerate + +There is other @sc{posix} 1003.2 behavior that Bash does not implement. +Specifically: + +@enumerate +@item +Assignment statements affect the execution environment of all +builtins, not just special ones. + +@item +When a subshell is created to execute a shell script with execute permission, +but without a leading @samp{#!}, Bash sets @code{$0} to the full pathname of +the script as found by searching @code{$PATH}, rather than the command as +typed by the user. + +@item +When using @samp{.} to source a shell script found in @code{$PATH}, bash +checks execute permission bits rather than read permission bits, just as +if it were searching for a command. + +@end enumerate + +@node Job Control +@chapter Job Control + +This chapter discusses what job control is, how it works, and how +Bash allows you to access its facilities. + +@menu +* Job Control Basics:: How job control works. +* Job Control Builtins:: Bash builtin commands used to interact + with job control. +* Job Control Variables:: Variables Bash uses to customize job + control. +@end menu + +@node Job Control Basics +@section Job Control Basics +@cindex job control +@cindex foreground +@cindex background +@cindex suspending jobs + +Job control +refers to the ability to selectively stop (suspend) +the execution of processes and continue (resume) +their execution at a later point. A user typically employs +this facility via an interactive interface supplied jointly +by the system's terminal driver and Bash. + +The shell associates a @var{job} with each pipeline. It keeps a +table of currently executing jobs, which may be listed with the +@code{jobs} command. When Bash starts a job +asynchronously, it prints a line that looks +like: +@example +[1] 25647 +@end example +@noindent +indicating that this job is job number 1 and that the process @sc{id} +of the last process in the pipeline associated with this job is +25647. All of the processes in a single pipeline are members of +the same job. Bash uses the @var{job} abstraction as the +basis for job control. + +To facilitate the implementation of the user interface to job +control, the operating system maintains the notion of a current terminal +process group @sc{id}. Members of this process group (processes whose +process group @sc{id} is equal to the current terminal process group +@sc{id}) receive keyboard-generated signals such as @code{SIGINT}. +These processes are said to be in the foreground. Background +processes are those whose process group @sc{id} differs from the +terminal's; such processes are immune to keyboard-generated +signals. Only foreground processes are allowed to read from or +write to the terminal. Background processes which attempt to +read from (write to) the terminal are sent a @code{SIGTTIN} +(@code{SIGTTOU}) signal by the terminal driver, which, unless +caught, suspends the process. + +If the operating system on which Bash is running supports +job control, Bash contains facilities to use it. Typing the +@var{suspend} character (typically @samp{^Z}, Control-Z) while a +process is running causes that process to be stopped and returns +control to Bash. Typing the @var{delayed suspend} character +(typically @samp{^Y}, Control-Y) causes the process to be stopped +when it attempts to read input from the terminal, and control to +be returned to Bash. The user then manipulates the state of +this job, using the @code{bg} command to continue it in the +background, the @code{fg} command to continue it in the +foreground, or the @code{kill} command to kill it. A @samp{^Z} +takes effect immediately, and has the additional side effect of +causing pending output and typeahead to be discarded. + +There are a number of ways to refer to a job in the shell. The +character @samp{%} introduces a job name. + +Job number @code{n} may be referred to as @samp{%n}. +The symbols @samp{%%} and +@samp{%+} refer to the shell's notion of the current job, which +is the last job stopped while it was in the foreground or started +in the background. The +previous job may be referenced using @samp{%-}. In output +pertaining to jobs (e.g., the output of the @code{jobs} command), +the current job is always flagged with a @samp{+}, and the +previous job with a @samp{-}. + +A job may also be referred to +using a prefix of the name used to start it, or using a substring +that appears in its command line. For example, @samp{%ce} refers +to a stopped @code{ce} job. Using @samp{%?ce}, on the +other hand, refers to any job containing the string @samp{ce} in +its command line. If the prefix or substring matches more than one job, +Bash reports an error. + +Simply naming a job can be used to bring it into the foreground: +@samp{%1} is a synonym for @samp{fg %1}, bringing job 1 from the +background into the foreground. Similarly, @samp{%1 &} resumes +job 1 in the background, equivalent to @samp{bg %1} + +The shell learns immediately whenever a job changes state. +Normally, Bash waits until it is about to print a prompt +before reporting changes in a job's status so as to not interrupt +any other output. +If the @option{-b} option to the @code{set} builtin is enabled, +Bash reports such changes immediately (@pxref{The Set Builtin}). +Any trap on @code{SIGCHLD} is executed for each child process +that exits. + +If an attempt to exit Bash is made while jobs are stopped, the +shell prints a message warning that there are stopped jobs. +The @code{jobs} command may then be used to inspect their status. +If a second attempt to exit is made without an intervening command, +Bash does not print another warning, and the stopped jobs are terminated. + +@node Job Control Builtins +@section Job Control Builtins + +@table @code + +@item bg +@btindex bg +@example +bg [@var{jobspec}] +@end example +Resume the suspended job @var{jobspec} in the background, as if it +had been started with @samp{&}. +If @var{jobspec} is not supplied, the current job is used. +The return status is zero unless it is run when job control is not +enabled, or, when run with job control enabled, if @var{jobspec} was +not found or @var{jobspec} specifies a job that was started without +job control. + +@item fg +@btindex fg +@example +fg [@var{jobspec}] +@end example +Resume the job @var{jobspec} in the foreground and make it the current job. +If @var{jobspec} is not supplied, the current job is used. +The return status is that of the command placed into the foreground, +or non-zero if run when job control is disabled or, when run with +job control enabled, @var{jobspec} does not specify a valid job or +@var{jobspec} specifies a job that was started without job control. + +@item jobs +@btindex jobs +@example +jobs [-lnprs] [@var{jobspec}] +jobs -x @var{command} [@var{arguments}] +@end example + +The first form lists the active jobs. The options have the +following meanings: + +@table @code +@item -l +List process @sc{id}s in addition to the normal information. + +@item -n +Display information only about jobs that have changed status since +the user was last notified of their status. + +@item -p +List only the process @sc{id} of the job's process group leader. + +@item -r +Restrict output to running jobs. + +@item -s +Restrict output to stopped jobs. +@end table + +If @var{jobspec} is given, +output is restricted to information about that job. +If @var{jobspec} is not supplied, the status of all jobs is +listed. + +If the @option{-x} option is supplied, @code{jobs} replaces any +@var{jobspec} found in @var{command} or @var{arguments} with the +corresponding process group @sc{id}, and executes @var{command}, +passing it @var{argument}s, returning its exit status. + +@item kill +@btindex kill +@example +kill [-s @var{sigspec}] [-n @var{signum}] [-@var{sigspec}] @var{jobspec} or @var{pid} +kill -l [@var{exit_status}] +@end example +Send a signal specified by @var{sigspec} or @var{signum} to the process +named by job specification @var{jobspec} or process @sc{id} @var{pid}. +@var{sigspec} is either a case-insensitive signal name such as +@code{SIGINT} (with or without the @code{SIG} prefix) +or a signal number; @var{signum} is a signal number. +If @var{sigspec} and @var{signum} are not present, @code{SIGTERM} is used. +The @option{-l} option lists the signal names. +If any arguments are supplied when @option{-l} is given, the names of the +signals corresponding to the arguments are listed, and the return status +is zero. +@var{exit_status} is a number specifying a signal number or the exit +status of a process terminated by a signal. +The return status is zero if at least one signal was successfully sent, +or non-zero if an error occurs or an invalid option is encountered. + +@item wait +@btindex wait +@example +wait [@var{jobspec} or @var{pid}] +@end example +Wait until the child process specified by process @sc{id} @var{pid} or job +specification @var{jobspec} exits and return the exit status of the last +command waited for. +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 neither @var{jobspec} nor @var{pid} specifies an active child process +of the shell, the return status is 127. + +@item disown +@btindex disown +@example +disown [-ar] [-h] [@var{jobspec} @dots{}] +@end example +Without options, each @var{jobspec} is removed from the table of +active jobs. +If the @option{-h} option is given, the job is not removed from the table, +but is marked so that @code{SIGHUP} is not sent to the job if the shell +receives a @code{SIGHUP}. +If @var{jobspec} is not present, and neither the @option{-a} nor @option{-r} +option is supplied, the current job is used. +If no @var{jobspec} is supplied, the @option{-a} option means to remove or +mark all jobs; the @option{-r} option without a @var{jobspec} +argument restricts operation to running jobs. + +@item suspend +@btindex suspend +@example +suspend [-f] +@end example +Suspend the execution of this shell until it receives a +@code{SIGCONT} signal. The @option{-f} option means to suspend +even if the shell is a login shell. + +@end table + +When job control is not active, the @code{kill} and @code{wait} +builtins do not accept @var{jobspec} arguments. They must be +supplied process @sc{id}s. + +@node Job Control Variables +@section Job Control Variables + +@vtable @code + +@item auto_resume +This variable controls how the shell interacts with the user and +job control. If this variable exists then single word simple +commands without redirections are treated as candidates for resumption +of an existing job. There is no ambiguity allowed; if there is +more than one job beginning with the string typed, then +the most recently accessed job will be selected. +The name of a stopped job, in this context, is the command line +used to start it. If this variable is set to the value @samp{exact}, +the string supplied must match the name of a stopped job exactly; +if set to @samp{substring}, +the string supplied needs to match a substring of the name of a +stopped job. The @samp{substring} value provides functionality +analogous to the @samp{%?} job @sc{id} (@pxref{Job Control Basics}). +If set to any other value, the supplied string must +be a prefix of a stopped job's name; this provides functionality +analogous to the @samp{%} job @sc{id}. + +@end vtable + +@set readline-appendix +@set history-appendix +@cindex Readline, how to use +@include rluser.texi +@cindex History, how to use +@include hsuser.texi +@clear readline-appendix +@clear history-appendix + +@node Installing Bash +@chapter Installing Bash + +This chapter provides basic instructions for installing Bash on +the various supported platforms. The distribution supports the +@sc{gnu} operating systems, nearly every version of Unix, and several +non-Unix systems such as BeOS and Interix. +Other independent ports exist for +@sc{ms-dos}, @sc{os/2}, and Windows platforms. + +@menu +* Basic Installation:: Installation instructions. + +* Compilers and Options:: How to set special options for various + systems. + +* Compiling For Multiple Architectures:: How to compile Bash for more + than one kind of system from + the same source tree. + +* Installation Names:: How to set the various paths used by the installation. + +* Specifying the System Type:: How to configure Bash for a particular system. + +* Sharing Defaults:: How to share default configuration values among GNU + programs. + +* Operation Controls:: Options recognized by the configuration program. + +* Optional Features:: How to enable and disable optional features when + building Bash. +@end menu + +@node Basic Installation +@section Basic Installation +@cindex installation +@cindex configuration +@cindex Bash installation +@cindex Bash configuration + +These are installation instructions for Bash. + +The simplest way to compile Bash is: + +@enumerate +@item +@code{cd} to the directory containing the source code and type +@samp{./configure} to configure Bash for your system. If you're +using @code{csh} on an old version of System V, you might need to +type @samp{sh ./configure} instead to prevent @code{csh} from trying +to execute @code{configure} itself. + +Running @code{configure} takes some time. +While running, it prints messages telling which features it is +checking for. + +@item +Type @samp{make} to compile Bash and build the @code{bashbug} bug +reporting script. + +@item +Optionally, type @samp{make tests} to run the Bash test suite. + +@item +Type @samp{make install} to install @code{bash} and @code{bashbug}. +This will also install the manual pages and Info file. + +@end enumerate + +The @code{configure} shell script attempts to guess correct +values for various system-dependent variables used during +compilation. It uses those values to create a @file{Makefile} in +each directory of the package (the top directory, the +@file{builtins}, @file{doc}, and @file{support} directories, +each directory under @file{lib}, and several others). It also creates a +@file{config.h} file containing system-dependent definitions. +Finally, it creates a shell script named @code{config.status} that you +can run in the future to recreate the current configuration, a +file @file{config.cache} that saves the results of its tests to +speed up reconfiguring, and a file @file{config.log} containing +compiler output (useful mainly for debugging @code{configure}). +If at some point +@file{config.cache} contains results you don't want to keep, you +may remove or edit it. + +To find out more about the options and arguments that the +@code{configure} script understands, type + +@example +bash-2.04$ ./configure --help +@end example + +@noindent +at the Bash prompt in your Bash source directory. + +If you need to do unusual things to compile Bash, please +try to figure out how @code{configure} could check whether or not +to do them, and mail diffs or instructions to +@email{bash-maintainers@@gnu.org} so they can be +considered for the next release. + +The file @file{configure.in} is used to create @code{configure} +by a program called Autoconf. You only need +@file{configure.in} if you want to change it or regenerate +@code{configure} using a newer version of Autoconf. If +you do this, make sure you are using Autoconf version 2.50 or +newer. + +You can remove the program binaries and object files from the +source code directory by typing @samp{make clean}. To also remove the +files that @code{configure} created (so you can compile Bash for +a different kind of computer), type @samp{make distclean}. + +@node Compilers and Options +@section Compilers and Options + +Some systems require unusual options for compilation or linking +that the @code{configure} script does not know about. You can +give @code{configure} initial values for variables by setting +them in the environment. Using a Bourne-compatible shell, you +can do that on the command line like this: + +@example +CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure +@end example + +On systems that have the @code{env} program, you can do it like this: + +@example +env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure +@end example + +The configuration process uses GCC to build Bash if it +is available. + +@node Compiling For Multiple Architectures +@section Compiling For Multiple Architectures + +You can compile Bash for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of @code{make} that +supports the @code{VPATH} variable, such as GNU @code{make}. +@code{cd} to the +directory where you want the object files and executables to go and run +the @code{configure} script from the source directory. You may need to +supply the @option{--srcdir=PATH} argument to tell @code{configure} where the +source files are. @code{configure} automatically checks for the +source code in the directory that @code{configure} is in and in `..'. + +If you have to use a @code{make} that does not supports the @code{VPATH} +variable, you can compile Bash for one architecture at a +time in the source code directory. After you have installed +Bash for one architecture, use @samp{make distclean} before +reconfiguring for another architecture. + +Alternatively, if your system supports symbolic links, you can use the +@file{support/mkclone} script to create a build tree which has +symbolic links back to each file in the source directory. Here's an +example that creates a build directory in the current directory from a +source directory @file{/usr/gnu/src/bash-2.0}: + +@example +bash /usr/gnu/src/bash-2.0/support/mkclone -s /usr/gnu/src/bash-2.0 . +@end example + +@noindent +The @code{mkclone} script requires Bash, so you must have already built +Bash for at least one architecture before you can create build +directories for other architectures. + +@node Installation Names +@section Installation Names + +By default, @samp{make install} will install into +@file{/usr/local/bin}, @file{/usr/local/man}, etc. You can +specify an installation prefix other than @file{/usr/local} by +giving @code{configure} the option @option{--prefix=@var{PATH}}, +or by specifying a value for the @code{DESTDIR} @samp{make} +variable when running @samp{make install}. + +You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. +If you give @code{configure} the option +@option{--exec-prefix=@var{PATH}}, @samp{make install} will use +@var{PATH} as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + +@node Specifying the System Type +@section Specifying the System Type + +There may be some features @code{configure} can not figure out +automatically, but need to determine by the type of host Bash +will run on. Usually @code{configure} can figure that +out, but if it prints a message saying it can not guess the host +type, give it the @option{--host=TYPE} option. @samp{TYPE} can +either be a short name for the system type, such as @samp{sun4}, +or a canonical name with three fields: @samp{CPU-COMPANY-SYSTEM} +(e.g., @samp{i386-unknown-freebsd4.2}). + +See the file @file{support/config.sub} for the possible +values of each field. + +@node Sharing Defaults +@section Sharing Defaults + +If you want to set default values for @code{configure} scripts to +share, you can create a site shell script called +@code{config.site} that gives default values for variables like +@code{CC}, @code{cache_file}, and @code{prefix}. @code{configure} +looks for @file{PREFIX/share/config.site} if it exists, then +@file{PREFIX/etc/config.site} if it exists. Or, you can set the +@code{CONFIG_SITE} environment variable to the location of the site +script. A warning: the Bash @code{configure} looks for a site script, +but not all @code{configure} scripts do. + +@node Operation Controls +@section Operation Controls + +@code{configure} recognizes the following options to control how it +operates. + +@table @code + +@item --cache-file=@var{file} +Use and save the results of the tests in +@var{file} instead of @file{./config.cache}. Set @var{file} to +@file{/dev/null} to disable caching, for debugging +@code{configure}. + +@item --help +Print a summary of the options to @code{configure}, and exit. + +@item --quiet +@itemx --silent +@itemx -q +Do not print messages saying which checks are being made. + +@item --srcdir=@var{dir} +Look for the Bash source code in directory @var{dir}. Usually +@code{configure} can determine that directory automatically. + +@item --version +Print the version of Autoconf used to generate the @code{configure} +script, and exit. +@end table + +@code{configure} also accepts some other, not widely used, boilerplate +options. @samp{configure --help} prints the complete list. + +@node Optional Features +@section Optional Features + +The Bash @code{configure} has a number of @option{--enable-@var{feature}} +options, where @var{feature} indicates an optional part of Bash. +There are also several @option{--with-@var{package}} options, +where @var{package} is something like @samp{bash-malloc} or @samp{purify}. +To turn off the default use of a package, use +@option{--without-@var{package}}. To configure Bash without a feature +that is enabled by default, use @option{--disable-@var{feature}}. + +Here is a complete list of the @option{--enable-} and +@option{--with-} options that the Bash @code{configure} recognizes. + +@table @code +@item --with-afs +Define if you are using the Andrew File System from Transarc. + +@item --with-bash-malloc +Use the Bash version of +@code{malloc} in @file{lib/malloc/malloc.c}. This is not the same +@code{malloc} that appears in @sc{gnu} libc, but an older version +derived from the 4.2 @sc{bsd} @code{malloc}. This @code{malloc} is +very fast, but wastes some space on each allocation. +This option is enabled by default. +The @file{NOTES} file contains a list of systems for +which this should be turned off, and @code{configure} disables this +option automatically for a number of systems. + +@item --with-curses +Use the curses library instead of the termcap library. This should +be supplied if your system has an inadequate or incomplete termcap +database. + +@item --with-gnu-malloc +A synonym for @code{--with-bash-malloc}. + +@item --with-installed-readline[=@var{PREFIX}] +Define this to make Bash link with a locally-installed version of Readline +rather than the version in @file{lib/readline}. This works only with +Readline 4.3 and later versions. If @var{PREFIX} is @code{yes} or not +supplied, @code{configure} uses the values of the make variables +@code{includedir} and @code{libdir}, which are subdirectories of @code{prefix} +by default, to find the installed version of Readline if it is not in +the standard system include and library directories. +If @var{PREFIX} is @code{no}, Bash links with the version in +@file{lib/readline}. +If @var{PREFIX} is set to any other value, @code{configure} treats it as +a directory pathname and looks for +the installed version of Readline in subdirectories of that directory +(include files in @var{PREFIX}/@code{include} and the library in +@var{PREFIX}/@code{lib}). + +@item --with-purify +Define this to use the Purify memory allocation checker from Rational +Software. + +@item --enable-minimal-config +This produces a shell with minimal features, close to the historical +Bourne shell. +@end table + +There are several @option{--enable-} options that alter how Bash is +compiled and linked, rather than changing run-time features. + +@table @code +@item --enable-largefile +Enable support for @uref{http://www.sas.com/standards/large_file/x_open.20Mar96.html, +large files} if the operating system requires special compiler options +to build programs which can access large files. This is enabled by +default, if the operating system provides large file support. + +@item --enable-profiling +This builds a Bash binary that produces profiling information to be +processed by @code{gprof} each time it is executed. + +@item --enable-static-link +This causes Bash to be linked statically, if @code{gcc} is being used. +This could be used to build a version to use as root's shell. +@end table + +The @samp{minimal-config} option can be used to disable all of +the following options, but it is processed first, so individual +options may be enabled using @samp{enable-@var{feature}}. + +All of the following options except for @samp{disabled-builtins} and +@samp{xpg-echo-default} are +enabled by default, unless the operating system does not provide the +necessary support. + +@table @code +@item --enable-alias +Allow alias expansion and include the @code{alias} and @code{unalias} +builtins (@pxref{Aliases}). + +@item --enable-arith-for-command +Include support for the alternate form of the @code{for} command +that behaves like the C language @code{for} statement +(@pxref{Looping Constructs}). + +@item --enable-array-variables +Include support for one-dimensional array shell variables +(@pxref{Arrays}). + +@item --enable-bang-history +Include support for @code{csh}-like history substitution +(@pxref{History Interaction}). + +@item --enable-brace-expansion +Include @code{csh}-like brace expansion +( @code{b@{a,b@}c} @expansion{} @code{bac bbc} ). +See @ref{Brace Expansion}, for a complete description. + +@item --enable-command-timing +Include support for recognizing @code{time} as a reserved word and for +displaying timing statistics for the pipeline following @code{time} +(@pxref{Pipelines}). +This allows pipelines as well as shell builtins and functions to be timed. + +@item --enable-cond-command +Include support for the @code{[[} conditional command. +(@pxref{Conditional Constructs}). + +@item --enable-cond-regexp +Include support for matching POSIX regular expressions using the +@samp{=~} binary operator in the @code{[[} conditional command. +(@pxref{Conditional Constructs}). + +@item --enable-directory-stack +Include support for a @code{csh}-like directory stack and the +@code{pushd}, @code{popd}, and @code{dirs} builtins +(@pxref{The Directory Stack}). + +@item --enable-disabled-builtins +Allow builtin commands to be invoked via @samp{builtin xxx} +even after @code{xxx} has been disabled using @samp{enable -n xxx}. +See @ref{Bash Builtins}, for details of the @code{builtin} and +@code{enable} builtin commands. + +@item --enable-dparen-arithmetic +Include support for the @code{((@dots{}))} command +(@pxref{Conditional Constructs}). + +@item --enable-extended-glob +Include support for the extended pattern matching features described +above under @ref{Pattern Matching}. + +@item --enable-help-builtin +Include the @code{help} builtin, which displays help on shell builtins and +variables (@pxref{Bash Builtins}). + +@item --enable-history +Include command history and the @code{fc} and @code{history} +builtin commands (@pxref{Bash History Facilities}). + +@item --enable-job-control +This enables the job control features (@pxref{Job Control}), +if the operating system supports them. + +@item --enable-multibyte +This enables support for multibyte characters if the operating +system provides the necessary support. + +@item --enable-net-redirections +This enables the special handling of filenames of the form +@code{/dev/tcp/@var{host}/@var{port}} and +@code{/dev/udp/@var{host}/@var{port}} +when used in redirections (@pxref{Redirections}). + +@item --enable-process-substitution +This enables process substitution (@pxref{Process Substitution}) if +the operating system provides the necessary support. + +@item --enable-prompt-string-decoding +Turn on the interpretation of a number of backslash-escaped characters +in the @env{$PS1}, @env{$PS2}, @env{$PS3}, and @env{$PS4} prompt +strings. See @ref{Printing a Prompt}, for a complete list of prompt +string escape sequences. + +@item --enable-progcomp +Enable the programmable completion facilities +(@pxref{Programmable Completion}). +If Readline is not enabled, this option has no effect. + +@item --enable-readline +Include support for command-line editing and history with the Bash +version of the Readline library (@pxref{Command Line Editing}). + +@item --enable-restricted +Include support for a @dfn{restricted shell}. If this is enabled, Bash, +when called as @code{rbash}, enters a restricted mode. See +@ref{The Restricted Shell}, for a description of restricted mode. + +@item --enable-select +Include the @code{select} builtin, which allows the generation of simple +menus (@pxref{Conditional Constructs}). + +@item --enable-usg-echo-default +A synonym for @code{--enable-xpg-echo-default}. + +@item --enable-xpg-echo-default +Make the @code{echo} builtin expand backslash-escaped characters by default, +without requiring the @option{-e} option. +This sets the default value of the @code{xpg_echo} shell option to @code{on}, +which makes the Bash @code{echo} behave more like the version specified in +the Single Unix Specification, version 2. +@xref{Bash Builtins}, for a description of the escape sequences that +@code{echo} recognizes. + +@end table + +The file @file{config-top.h} contains C Preprocessor +@samp{#define} statements for options which are not settable from +@code{configure}. +Some of these are not meant to be changed; beware of the consequences if +you do. +Read the comments associated with each definition for more +information about its effect. + +@node Reporting Bugs +@appendix Reporting Bugs + +Please report all bugs you find in Bash. +But first, you should +make sure that it really is a bug, and that it appears in the latest +version of Bash. +The latest version of Bash is always available for FTP from +@uref{ftp://ftp.gnu.org/pub/bash/}. + +Once you have determined that a bug actually exists, use the +@code{bashbug} command to submit a bug report. +If you have a fix, you are encouraged to mail that as well! +Suggestions and `philosophical' bug reports may be mailed +to @email{bug-bash@@gnu.org} or posted to the Usenet +newsgroup @code{gnu.bash.bug}. + +All bug reports should include: +@itemize @bullet +@item +The version number of Bash. +@item +The hardware and operating system. +@item +The compiler used to compile Bash. +@item +A description of the bug behaviour. +@item +A short script or `recipe' which exercises the bug and may be used +to reproduce it. +@end itemize + +@noindent +@code{bashbug} inserts the first three items automatically into +the template it provides for filing a bug report. + +Please send all reports concerning this manual to +@email{chet@@po.CWRU.Edu}. + +@node Major Differences From The Bourne Shell +@appendix Major Differences From The Bourne Shell + +Bash implements essentially the same grammar, parameter and +variable expansion, redirection, and quoting as the Bourne Shell. +Bash uses the @sc{posix} 1003.2 standard as the specification of +how these features are to be implemented. There are some +differences between the traditional Bourne shell and Bash; this +section quickly details the differences of significance. A +number of these differences are explained in greater depth in +previous sections. +This section uses the version of @code{sh} included in SVR4.2 as +the baseline reference. + +@itemize @bullet + +@item +Bash is @sc{posix}-conformant, even where the @sc{posix} specification +differs from traditional @code{sh} behavior (@pxref{Bash POSIX Mode}). + +@item +Bash has multi-character invocation options (@pxref{Invoking Bash}). + +@item +Bash has command-line editing (@pxref{Command Line Editing}) and +the @code{bind} builtin. + +@item +Bash provides a programmable word completion mechanism +(@pxref{Programmable Completion}), and two builtin commands, +@code{complete} and @code{compgen}, to manipulate it. + +@item +Bash has command history (@pxref{Bash History Facilities}) and the +@code{history} and @code{fc} builtins to manipulate it. +The Bash history list maintains timestamp information and uses the +value of the @code{HISTTIMEFORMAT} variable to display it. + +@item +Bash implements @code{csh}-like history expansion +(@pxref{History Interaction}). + +@item +Bash has one-dimensional array variables (@pxref{Arrays}), and the +appropriate variable expansions and assignment syntax to use them. +Several of the Bash builtins take options to act on arrays. +Bash provides a number of built-in array variables. + +@item +The @code{$'@dots{}'} quoting syntax, which expands ANSI-C +backslash-escaped characters in the text between the single quotes, +is supported (@pxref{ANSI-C Quoting}). + +@item +Bash supports the @code{$"@dots{}"} quoting syntax to do +locale-specific translation of the characters between the double +quotes. The @option{-D}, @option{--dump-strings}, and @option{--dump-po-strings} +invocation options list the translatable strings found in a script +(@pxref{Locale Translation}). + +@item +Bash implements the @code{!} keyword to negate the return value of +a pipeline (@pxref{Pipelines}). +Very useful when an @code{if} statement needs to act only if a test fails. + +@item +Bash has the @code{time} reserved word and command timing (@pxref{Pipelines}). +The display of the timing statistics may be controlled with the +@env{TIMEFORMAT} variable. + +@item +Bash implements the @code{for (( @var{expr1} ; @var{expr2} ; @var{expr3} ))} +arithmetic for command, similar to the C language (@pxref{Looping Constructs}). + +@item +Bash includes the @code{select} compound command, which allows the +generation of simple menus (@pxref{Conditional Constructs}). + +@item +Bash includes the @code{[[} compound command, which makes conditional +testing part of the shell grammar (@pxref{Conditional Constructs}). + +@item +Bash includes brace expansion (@pxref{Brace Expansion}) and tilde +expansion (@pxref{Tilde Expansion}). + +@item +Bash implements command aliases and the @code{alias} and @code{unalias} +builtins (@pxref{Aliases}). + +@item +Bash provides shell arithmetic, the @code{((} compound command +(@pxref{Conditional Constructs}), +and arithmetic expansion (@pxref{Shell Arithmetic}). + +@item +Variables present in the shell's initial environment are automatically +exported to child processes. The Bourne shell does not normally do +this unless the variables are explicitly marked using the @code{export} +command. + +@item +Bash includes the @sc{posix} pattern removal @samp{%}, @samp{#}, @samp{%%} +and @samp{##} expansions to remove leading or trailing substrings from +variable values (@pxref{Shell Parameter Expansion}). + +@item +The expansion @code{$@{#xx@}}, which returns the length of @code{$@{xx@}}, +is supported (@pxref{Shell Parameter Expansion}). + +@item +The expansion @code{$@{var:}@var{offset}@code{[:}@var{length}@code{]@}}, +which expands to the substring of @code{var}'s value of length +@var{length}, beginning at @var{offset}, is present +(@pxref{Shell Parameter Expansion}). + +@item +The expansion +@code{$@{var/[/]}@var{pattern}@code{[/}@var{replacement}@code{]@}}, +which matches @var{pattern} and replaces it with @var{replacement} in +the value of @code{var}, is available (@pxref{Shell Parameter Expansion}). + +@item +The expansion @code{$@{!@var{prefix@}*}} expansion, which expands to +the names of all shell variables whose names begin with @var{prefix}, +is available (@pxref{Shell Parameter Expansion}). + +@item +Bash has @var{indirect} variable expansion using @code{$@{!word@}} +(@pxref{Shell Parameter Expansion}). + +@item +Bash can expand positional parameters beyond @code{$9} using +@code{$@{@var{num}@}}. + +@item +The @sc{posix} @code{$()} form of command substitution +is implemented (@pxref{Command Substitution}), +and preferred to the Bourne shell's @code{``} (which +is also implemented for backwards compatibility). + +@item +Bash has process substitution (@pxref{Process Substitution}). + +@item +Bash automatically assigns variables that provide information about the +current user (@env{UID}, @env{EUID}, and @env{GROUPS}), the current host +(@env{HOSTTYPE}, @env{OSTYPE}, @env{MACHTYPE}, and @env{HOSTNAME}), +and the instance of Bash that is running (@env{BASH}, +@env{BASH_VERSION}, and @env{BASH_VERSINFO}). @xref{Bash Variables}, +for details. + +@item +The @env{IFS} variable is used to split only the results of expansion, +not all words (@pxref{Word Splitting}). +This closes a longstanding shell security hole. + +@item +Bash implements the full set of @sc{posix} 1003.2 filename expansion operators, +including @var{character classes}, @var{equivalence classes}, and +@var{collating symbols} (@pxref{Filename Expansion}). + +@item +Bash implements extended pattern matching features when the @code{extglob} +shell option is enabled (@pxref{Pattern Matching}). + +@item +It is possible to have a variable and a function with the same name; +@code{sh} does not separate the two name spaces. + +@item +Bash functions are permitted to have local variables using the +@code{local} builtin, and thus useful recursive functions may be written +(@pxref{Bash Builtins}). + +@item +Variable assignments preceding commands affect only that command, even +builtins and functions (@pxref{Environment}). +In @code{sh}, all variable assignments +preceding commands are global unless the command is executed from the +file system. + +@item +Bash performs filename expansion on filenames specified as operands +to input and output redirection operators (@pxref{Redirections}). + +@item +Bash contains the @samp{<>} redirection operator, allowing a file to be +opened for both reading and writing, and the @samp{&>} redirection +operator, for directing standard output and standard error to the same +file (@pxref{Redirections}). + +@item +Bash treats a number of filenames specially when they are +used in redirection operators (@pxref{Redirections}). + +@item +Bash can open network connections to arbitrary machines and services +with the redirection operators (@pxref{Redirections}). + +@item +The @code{noclobber} option is available to avoid overwriting existing +files with output redirection (@pxref{The Set Builtin}). +The @samp{>|} redirection operator may be used to override @code{noclobber}. + +@item +The Bash @code{cd} and @code{pwd} builtins (@pxref{Bourne Shell Builtins}) +each take @option{-L} and @option{-P} options to switch between logical and +physical modes. + +@item +Bash allows a function to override a builtin with the same name, and provides +access to that builtin's functionality within the function via the +@code{builtin} and @code{command} builtins (@pxref{Bash Builtins}). + +@item +The @code{command} builtin allows selective disabling of functions +when command lookup is performed (@pxref{Bash Builtins}). + +@item +Individual builtins may be enabled or disabled using the @code{enable} +builtin (@pxref{Bash Builtins}). + +@item +The Bash @code{exec} builtin takes additional options that allow users +to control the contents of the environment passed to the executed +command, and what the zeroth argument to the command is to be +(@pxref{Bourne Shell Builtins}). + +@item +Shell functions may be exported to children via the environment +using @code{export -f} (@pxref{Shell Functions}). + +@item +The Bash @code{export}, @code{readonly}, and @code{declare} builtins can +take a @option{-f} option to act on shell functions, a @option{-p} option to +display variables with various attributes set in a format that can be +used as shell input, a @option{-n} option to remove various variable +attributes, and @samp{name=value} arguments to set variable attributes +and values simultaneously. + +@item +The Bash @code{hash} builtin allows a name to be associated with +an arbitrary filename, even when that filename cannot be found by +searching the @env{$PATH}, using @samp{hash -p} +(@pxref{Bourne Shell Builtins}). + +@item +Bash includes a @code{help} builtin for quick reference to shell +facilities (@pxref{Bash Builtins}). + +@item +The @code{printf} builtin is available to display formatted output +(@pxref{Bash Builtins}). + +@item +The Bash @code{read} builtin (@pxref{Bash Builtins}) +will read a line ending in @samp{\} with +the @option{-r} option, and will use the @env{REPLY} variable as a +default if no non-option arguments are supplied. +The Bash @code{read} builtin +also accepts a prompt string with the @option{-p} option and will use +Readline to obtain the line when given the @option{-e} option. +The @code{read} builtin also has additional options to control input: +the @option{-s} option will turn off echoing of input characters as +they are read, the @option{-t} option will allow @code{read} to time out +if input does not arrive within a specified number of seconds, the +@option{-n} option will allow reading only a specified number of +characters rather than a full line, and the @option{-d} option will read +until a particular character rather than newline. + +@item +The @code{return} builtin may be used to abort execution of scripts +executed with the @code{.} or @code{source} builtins +(@pxref{Bourne Shell Builtins}). + +@item +Bash includes the @code{shopt} builtin, for finer control of shell +optional capabilities (@pxref{Bash Builtins}), and allows these options +to be set and unset at shell invocation (@pxref{Invoking Bash}). + +@item +Bash has much more optional behavior controllable with the @code{set} +builtin (@pxref{The Set Builtin}). + +@item +The @samp{-x} (@code{xtrace}) option displays commands other than +simple commands when performing an execution trace +(@pxref{The Set Builtin}). + +@item +The @code{test} builtin (@pxref{Bourne Shell Builtins}) +is slightly different, as it implements the @sc{posix} algorithm, +which specifies the behavior based on the number of arguments. + +@item +Bash includes the @code{caller} builtin, which displays the context of +any active subroutine call (a shell function or a script executed with +the @code{.} or @code{source} builtins). This supports the bash +debugger. + +@item +The @code{trap} builtin (@pxref{Bourne Shell Builtins}) allows a +@code{DEBUG} pseudo-signal specification, similar to @code{EXIT}. +Commands specified with a @code{DEBUG} trap are executed before every +simple command, @code{for} command, @code{case} command, +@code{select} command, every arithmetic @code{for} command, and before +the first command executes in a shell function. +The @code{DEBUG} trap is not inherited by shell functions unless the +function has been given the @code{trace} attribute or the +@code{functrace} option has been enabled using the @code{shopt} builtin. +The @code{extdebug} shell option has additional effects on the +@code{DEBUG} trap. + +The @code{trap} builtin (@pxref{Bourne Shell Builtins}) allows an +@code{ERR} pseudo-signal specification, similar to @code{EXIT} and @code{DEBUG}. +Commands specified with an @code{ERR} trap are executed after a simple +command fails, with a few exceptions. +The @code{ERR} trap is not inherited by shell functions unless the +@code{-o errtrace} option to the @code{set} builtin is enabled. + +The @code{trap} builtin (@pxref{Bourne Shell Builtins}) allows a +@code{RETURN} pseudo-signal specification, similar to +@code{EXIT} and @code{DEBUG}. +Commands specified with an @code{RETURN} trap are executed before +execution resumes after a shell function or a shell script executed with +@code{.} or @code{source} returns. +The @code{RETURN} trap is not inherited by shell functions. + +@item +The Bash @code{type} builtin is more extensive and gives more information +about the names it finds (@pxref{Bash Builtins}). + +@item +The Bash @code{umask} builtin permits a @option{-p} option to cause +the output to be displayed in the form of a @code{umask} command +that may be reused as input (@pxref{Bourne Shell Builtins}). + +@item +Bash implements a @code{csh}-like directory stack, and provides the +@code{pushd}, @code{popd}, and @code{dirs} builtins to manipulate it +(@pxref{The Directory Stack}). +Bash also makes the directory stack visible as the value of the +@env{DIRSTACK} shell variable. + +@item +Bash interprets special backslash-escaped characters in the prompt +strings when interactive (@pxref{Printing a Prompt}). + +@item +The Bash restricted mode is more useful (@pxref{The Restricted Shell}); +the SVR4.2 shell restricted mode is too limited. + +@item +The @code{disown} builtin can remove a job from the internal shell +job table (@pxref{Job Control Builtins}) or suppress the sending +of @code{SIGHUP} to a job when the shell exits as the result of a +@code{SIGHUP}. + +@item +The SVR4.2 shell has two privilege-related builtins +(@code{mldmode} and @code{priv}) not present in Bash. + +@item +Bash does not have the @code{stop} or @code{newgrp} builtins. + +@item +Bash does not use the @env{SHACCT} variable or perform shell accounting. + +@item +The SVR4.2 @code{sh} uses a @env{TIMEOUT} variable like Bash uses +@env{TMOUT}. + +@end itemize + +@noindent +More features unique to Bash may be found in @ref{Bash Features}. + + +@appendixsec Implementation Differences From The SVR4.2 Shell + +Since Bash is a completely new implementation, it does not suffer from +many of the limitations of the SVR4.2 shell. For instance: + +@itemize @bullet + +@item +Bash does not fork a subshell when redirecting into or out of +a shell control structure such as an @code{if} or @code{while} +statement. + +@item +Bash does not allow unbalanced quotes. The SVR4.2 shell will silently +insert a needed closing quote at @code{EOF} under certain circumstances. +This can be the cause of some hard-to-find errors. + +@item +The SVR4.2 shell uses a baroque memory management scheme based on +trapping @code{SIGSEGV}. If the shell is started from a process with +@code{SIGSEGV} blocked (e.g., by using the @code{system()} C library +function call), it misbehaves badly. + +@item +In a questionable attempt at security, the SVR4.2 shell, +when invoked without the @option{-p} option, will alter its real +and effective @sc{uid} and @sc{gid} if they are less than some +magic threshold value, commonly 100. +This can lead to unexpected results. + +@item +The SVR4.2 shell does not allow users to trap @code{SIGSEGV}, +@code{SIGALRM}, or @code{SIGCHLD}. + +@item +The SVR4.2 shell does not allow the @env{IFS}, @env{MAILCHECK}, +@env{PATH}, @env{PS1}, or @env{PS2} variables to be unset. + +@item +The SVR4.2 shell treats @samp{^} as the undocumented equivalent of +@samp{|}. + +@item +Bash allows multiple option arguments when it is invoked (@code{-x -v}); +the SVR4.2 shell allows only one option argument (@code{-xv}). In +fact, some versions of the shell dump core if the second argument begins +with a @samp{-}. + +@item +The SVR4.2 shell exits a script if any builtin fails; Bash exits +a script only if one of the @sc{posix} 1003.2 special builtins fails, and +only for certain failures, as enumerated in the @sc{posix} 1003.2 standard. + +@item +The SVR4.2 shell behaves differently when invoked as @code{jsh} +(it turns on job control). +@end itemize + +@node Copying This Manual +@appendix Copying This Manual + +@menu +* GNU Free Documentation License:: License for copying this manual. +@end menu + +@include fdl.texi + +@node Builtin Index +@unnumbered Index of Shell Builtin Commands +@printindex bt + +@node Reserved Word Index +@unnumbered Index of Shell Reserved Words +@printindex rw + +@node Variable Index +@unnumbered Parameter and Variable Index +@printindex vr + +@node Function Index +@unnumbered Function Index +@printindex fn + +@node Concept Index +@unnumbered Concept Index +@printindex cp + +@bye diff --git a/doc/version.texi b/doc/version.texi index 0ca20b11a..ae5b6ad84 100644 --- a/doc/version.texi +++ b/doc/version.texi @@ -4,7 +4,7 @@ Copyright (C) 1988-2004 Free Software Foundation, Inc. @set EDITION 3.0 @set VERSION 3.0 -@set UPDATED 27 July 2004 -@set UPDATED-MONTH July 2004 +@set UPDATED 1 August 2004 +@set UPDATED-MONTH August 2004 -@set LASTCHANGE Tue Jul 27 09:12:07 EDT 2004 +@set LASTCHANGE Sun Aug 1 14:48:46 EDT 2004 diff --git a/doc/version.texi~ b/doc/version.texi~ new file mode 100644 index 000000000..0ca20b11a --- /dev/null +++ b/doc/version.texi~ @@ -0,0 +1,10 @@ +@ignore +Copyright (C) 1988-2004 Free Software Foundation, Inc. +@end ignore + +@set EDITION 3.0 +@set VERSION 3.0 +@set UPDATED 27 July 2004 +@set UPDATED-MONTH July 2004 + +@set LASTCHANGE Tue Jul 27 09:12:07 EDT 2004 diff --git a/general.c b/general.c index f62cccb25..1ef6d0f6d 100644 --- a/general.c +++ b/general.c @@ -566,15 +566,20 @@ absolute_program (string) } /* Return the `basename' of the pathname in STRING (the stuff after the - last '/'). If STRING is not a full pathname, simply return it. */ + last '/'). If STRING is `/', just return it. */ char * base_pathname (string) char *string; { char *p; +#if 0 if (absolute_pathname (string) == 0) return (string); +#endif + + if (string[0] == '/' && string[1] == 0) + return (string); p = (char *)strrchr (string, '/'); return (p ? ++p : string); diff --git a/general.c~ b/general.c~ new file mode 100644 index 000000000..9300bf420 --- /dev/null +++ b/general.c~ @@ -0,0 +1,964 @@ +/* general.c -- Stuff that is used by all files. */ + +/* Copyright (C) 1987-2004 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#include "bashtypes.h" +#ifndef _MINIX +# include +#endif +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "filecntl.h" +#include "bashansi.h" +#include +#include "chartypes.h" +#include + +#include "bashintl.h" + +#include "shell.h" +#include "test.h" + +#include + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +extern int expand_aliases; +extern int interrupt_immediately; +extern int interactive_comments; +extern int check_hashed_filenames; +extern int source_uses_path; +extern int source_searches_cwd; + +static char *bash_special_tilde_expansions __P((char *)); +static int unquoted_tilde_word __P((const char *)); +static void initialize_group_array __P((void)); + +/* A standard error message to use when getcwd() returns NULL. */ +char *bash_getcwd_errstr = N_("getcwd: cannot access parent directories"); + +/* Do whatever is necessary to initialize `Posix mode'. */ +void +posix_initialize (on) + int on; +{ + /* Things that should be turned on when posix mode is enabled. */ + if (on != 0) + { + interactive_comments = source_uses_path = expand_aliases = 1; + } + + /* Things that should be turned on when posix mode is disabled. */ + if (on == 0) + { + source_searches_cwd = 1; + expand_aliases = interactive_shell; + } +} + +/* **************************************************************** */ +/* */ +/* Functions to convert to and from and display non-standard types */ +/* */ +/* **************************************************************** */ + +#if defined (RLIMTYPE) +RLIMTYPE +string_to_rlimtype (s) + char *s; +{ + RLIMTYPE ret; + int neg; + + ret = 0; + neg = 0; + while (s && *s && whitespace (*s)) + s++; + if (*s == '-' || *s == '+') + { + neg = *s == '-'; + s++; + } + for ( ; s && *s && DIGIT (*s); s++) + ret = (ret * 10) + TODIGIT (*s); + return (neg ? -ret : ret); +} + +void +print_rlimtype (n, addnl) + RLIMTYPE n; + int addnl; +{ + char s[INT_STRLEN_BOUND (RLIMTYPE) + 1], *p; + + p = s + sizeof(s); + *--p = '\0'; + + if (n < 0) + { + do + *--p = '0' - n % 10; + while ((n /= 10) != 0); + + *--p = '-'; + } + else + { + do + *--p = '0' + n % 10; + while ((n /= 10) != 0); + } + + printf ("%s%s", p, addnl ? "\n" : ""); +} +#endif /* RLIMTYPE */ + +/* **************************************************************** */ +/* */ +/* Input Validation Functions */ +/* */ +/* **************************************************************** */ + +/* Return non-zero if all of the characters in STRING are digits. */ +int +all_digits (string) + char *string; +{ + register char *s; + + for (s = string; *s; s++) + if (DIGIT (*s) == 0) + return (0); + + return (1); +} + +/* Return non-zero if the characters pointed to by STRING constitute a + valid number. Stuff the converted number into RESULT if RESULT is + not null. */ +int +legal_number (string, result) + char *string; + intmax_t *result; +{ + intmax_t value; + char *ep; + + if (result) + *result = 0; + + errno = 0; + value = strtoimax (string, &ep, 10); + if (errno) + return 0; /* errno is set on overflow or underflow */ + + /* Skip any trailing whitespace, since strtoimax does not. */ + while (whitespace (*ep)) + ep++; + + /* If *string is not '\0' but *ep is '\0' on return, the entire string + is valid. */ + if (string && *string && *ep == '\0') + { + if (result) + *result = value; + /* The SunOS4 implementation of strtol() will happily ignore + overflow conditions, so this cannot do overflow correctly + on those systems. */ + return 1; + } + + return (0); +} + +/* Return 1 if this token is a legal shell `identifier'; that is, it consists + solely of letters, digits, and underscores, and does not begin with a + digit. */ +int +legal_identifier (name) + char *name; +{ + register char *s; + unsigned char c; + + if (!name || !(c = *name) || (legal_variable_starter (c) == 0)) + return (0); + + for (s = name + 1; (c = *s) != 0; s++) + { + if (legal_variable_char (c) == 0) + return (0); + } + return (1); +} + +/* Make sure that WORD is a valid shell identifier, i.e. + does not contain a dollar sign, nor is quoted in any way. Nor + does it consist of all digits. If CHECK_WORD is non-zero, + the word is checked to ensure that it consists of only letters, + digits, and underscores. */ +int +check_identifier (word, check_word) + WORD_DESC *word; + int check_word; +{ + if ((word->flags & (W_HASDOLLAR|W_QUOTED)) || all_digits (word->word)) + { + internal_error (_("`%s': not a valid identifier"), word->word); + return (0); + } + else if (check_word && legal_identifier (word->word) == 0) + { + internal_error (_("`%s': not a valid identifier"), word->word); + return (0); + } + else + return (1); +} + +/* Return 1 if STRING comprises a valid alias name. The shell accepts + essentially all characters except those which must be quoted to the + parser (which disqualifies them from alias expansion anyway) and `/'. */ +int +legal_alias_name (string, flags) + char *string; + int flags; +{ + register char *s; + + for (s = string; *s; s++) + if (shellbreak (*s) || shellxquote (*s) || shellexp (*s) || (*s == '/')) + return 0; + return 1; +} + +/* Returns non-zero if STRING is an assignment statement. The returned value + is the index of the `=' sign. */ +int +assignment (string, flags) + const char *string; + int flags; +{ + register unsigned char c; + register int newi, indx; + + c = string[indx = 0]; + +#if defined (ARRAY_VARS) + if ((legal_variable_starter (c) == 0) && (flags && c != '[')) /* ] */ +#else + if (legal_variable_starter (c) == 0) +#endif + return (0); + + while (c = string[indx]) + { + /* The following is safe. Note that '=' at the start of a word + is not an assignment statement. */ + if (c == '=') + return (indx); + +#if defined (ARRAY_VARS) + if (c == '[') + { + newi = skipsubscript (string, indx); + if (string[newi++] != ']') + return (0); + return ((string[newi] == '=') ? newi : 0); + } +#endif /* ARRAY_VARS */ + + /* Variable names in assignment statements may contain only letters, + digits, and `_'. */ + if (legal_variable_char (c) == 0) + return (0); + + indx++; + } + return (0); +} + +/* **************************************************************** */ +/* */ +/* Functions to manage files and file descriptors */ +/* */ +/* **************************************************************** */ + +/* A function to unset no-delay mode on a file descriptor. Used in shell.c + to unset it on the fd passed as stdin. Should be called on stdin if + readline gets an EAGAIN or EWOULDBLOCK when trying to read input. */ + +#if !defined (O_NDELAY) +# if defined (FNDELAY) +# define O_NDELAY FNDELAY +# endif +#endif /* O_NDELAY */ + +/* Make sure no-delay mode is not set on file descriptor FD. */ +int +sh_unset_nodelay_mode (fd) + int fd; +{ + int flags, bflags; + + if ((flags = fcntl (fd, F_GETFL, 0)) < 0) + return -1; + + bflags = 0; + + /* This is defined to O_NDELAY in filecntl.h if O_NONBLOCK is not present + and O_NDELAY is defined. */ +#ifdef O_NONBLOCK + bflags |= O_NONBLOCK; +#endif + +#ifdef O_NDELAY + bflags |= O_NDELAY; +#endif + + if (flags & bflags) + { + flags &= ~bflags; + return (fcntl (fd, F_SETFL, flags)); + } + + return 0; +} + +/* Return 1 if file descriptor FD is valid; 0 otherwise. */ +int +sh_validfd (fd) + int fd; +{ + return (fcntl (fd, F_GETFD, 0) >= 0); +} + +/* There is a bug in the NeXT 2.1 rlogind that causes opens + of /dev/tty to fail. */ + +#if defined (__BEOS__) +/* On BeOS, opening in non-blocking mode exposes a bug in BeOS, so turn it + into a no-op. This should probably go away in the future. */ +# undef O_NONBLOCK +# define O_NONBLOCK 0 +#endif /* __BEOS__ */ + +void +check_dev_tty () +{ + int tty_fd; + char *tty; + + tty_fd = open ("/dev/tty", O_RDWR|O_NONBLOCK); + + if (tty_fd < 0) + { + tty = (char *)ttyname (fileno (stdin)); + if (tty == 0) + return; + tty_fd = open (tty, O_RDWR|O_NONBLOCK); + } + close (tty_fd); +} + +/* Return 1 if PATH1 and PATH2 are the same file. This is kind of + expensive. If non-NULL STP1 and STP2 point to stat structures + corresponding to PATH1 and PATH2, respectively. */ +int +same_file (path1, path2, stp1, stp2) + char *path1, *path2; + struct stat *stp1, *stp2; +{ + struct stat st1, st2; + + if (stp1 == NULL) + { + if (stat (path1, &st1) != 0) + return (0); + stp1 = &st1; + } + + if (stp2 == NULL) + { + if (stat (path2, &st2) != 0) + return (0); + stp2 = &st2; + } + + return ((stp1->st_dev == stp2->st_dev) && (stp1->st_ino == stp2->st_ino)); +} + +/* Move FD to a number close to the maximum number of file descriptors + allowed in the shell process, to avoid the user stepping on it with + redirection and causing us extra work. If CHECK_NEW is non-zero, + we check whether or not the file descriptors are in use before + duplicating FD onto them. MAXFD says where to start checking the + file descriptors. If it's less than 20, we get the maximum value + available from getdtablesize(2). */ +int +move_to_high_fd (fd, check_new, maxfd) + int fd, check_new, maxfd; +{ + int script_fd, nfds, ignore; + + if (maxfd < 20) + { + nfds = getdtablesize (); + if (nfds <= 0) + nfds = 20; + if (nfds > HIGH_FD_MAX) + nfds = HIGH_FD_MAX; /* reasonable maximum */ + } + else + nfds = maxfd; + + for (nfds--; check_new && nfds > 3; nfds--) + if (fcntl (nfds, F_GETFD, &ignore) == -1) + break; + + if (nfds > 3 && fd != nfds && (script_fd = dup2 (fd, nfds)) != -1) + { + if (check_new == 0 || fd != fileno (stderr)) /* don't close stderr */ + close (fd); + return (script_fd); + } + + /* OK, we didn't find one less than our artificial maximum; return the + original file descriptor. */ + return (fd); +} + +/* Return non-zero if the characters from SAMPLE are not all valid + characters to be found in the first line of a shell script. We + check up to the first newline, or SAMPLE_LEN, whichever comes first. + All of the characters must be printable or whitespace. */ + +int +check_binary_file (sample, sample_len) + char *sample; + int sample_len; +{ + register int i; + unsigned char c; + + for (i = 0; i < sample_len; i++) + { + c = sample[i]; + if (c == '\n') + return (0); + + if (ISSPACE (c) == 0 && ISPRINT (c) == 0) + return (1); + } + + return (0); +} + +/* **************************************************************** */ +/* */ +/* Functions to inspect pathnames */ +/* */ +/* **************************************************************** */ + +int +file_isdir (fn) + char *fn; +{ + struct stat sb; + + return ((stat (fn, &sb) == 0) && S_ISDIR (sb.st_mode)); +} + +int +file_iswdir (fn) + char *fn; +{ + return (file_isdir (fn) && test_eaccess (fn, W_OK) == 0); +} + + +/* **************************************************************** */ +/* */ +/* Functions to manipulate pathnames */ +/* */ +/* **************************************************************** */ + +/* Turn STRING (a pathname) into an absolute pathname, assuming that + DOT_PATH contains the symbolic location of `.'. This always + returns a new string, even if STRING was an absolute pathname to + begin with. */ +char * +make_absolute (string, dot_path) + char *string, *dot_path; +{ + char *result; + + if (dot_path == 0 || ABSPATH(string)) +#ifdef __CYGWIN__ + { + char pathbuf[PATH_MAX + 1]; + + cygwin_conv_to_full_posix_path (string, pathbuf); + result = savestring (pathbuf); + } +#else + result = savestring (string); +#endif + else + result = sh_makepath (dot_path, string, 0); + + return (result); +} + +/* Return 1 if STRING contains an absolute pathname, else 0. Used by `cd' + to decide whether or not to look up a directory name in $CDPATH. */ +int +absolute_pathname (string) + const char *string; +{ + if (string == 0 || *string == '\0') + return (0); + + if (ABSPATH(string)) + return (1); + + if (string[0] == '.' && PATHSEP(string[1])) /* . and ./ */ + return (1); + + if (string[0] == '.' && string[1] == '.' && PATHSEP(string[2])) /* .. and ../ */ + return (1); + + return (0); +} + +/* Return 1 if STRING is an absolute program name; it is absolute if it + contains any slashes. This is used to decide whether or not to look + up through $PATH. */ +int +absolute_program (string) + const char *string; +{ + return ((char *)xstrchr (string, '/') != (char *)NULL); +} + +/* Return the `basename' of the pathname in STRING (the stuff after the + last '/'). If STRING is `/', just return it. */ +char * +base_pathname (string) + char *string; +{ + char *p; + +#if 0 + if (absolute_pathname (string) == 0) + return (string); +#endif + if (string[0] == '/' && string[1] == 0) + return (string); + + p = (char *)strrchr (string, '/'); + return (p ? ++p : string); +} + +/* Return the full pathname of FILE. Easy. Filenames that begin + with a '/' are returned as themselves. Other filenames have + the current working directory prepended. A new string is + returned in either case. */ +char * +full_pathname (file) + char *file; +{ + char *ret; + + file = (*file == '~') ? bash_tilde_expand (file, 0) : savestring (file); + + if (ABSPATH(file)) + return (file); + + ret = sh_makepath ((char *)NULL, file, (MP_DOCWD|MP_RMDOT)); + free (file); + + return (ret); +} + +/* A slightly related function. Get the prettiest name of this + directory possible. */ +static char tdir[PATH_MAX]; + +/* Return a pretty pathname. If the first part of the pathname is + the same as $HOME, then replace that with `~'. */ +char * +polite_directory_format (name) + char *name; +{ + char *home; + int l; + + home = get_string_value ("HOME"); + l = home ? strlen (home) : 0; + if (l > 1 && strncmp (home, name, l) == 0 && (!name[l] || name[l] == '/')) + { + strncpy (tdir + 1, name + l, sizeof(tdir) - 2); + tdir[0] = '~'; + tdir[sizeof(tdir) - 1] = '\0'; + return (tdir); + } + else + return (name); +} + +/* Given a string containing units of information separated by colons, + return the next one pointed to by (P_INDEX), or NULL if there are no more. + Advance (P_INDEX) to the character after the colon. */ +char * +extract_colon_unit (string, p_index) + char *string; + int *p_index; +{ + int i, start, len; + char *value; + + if (string == 0) + return (string); + + len = strlen (string); + if (*p_index >= len) + return ((char *)NULL); + + i = *p_index; + + /* Each call to this routine leaves the index pointing at a colon if + there is more to the path. If I is > 0, then increment past the + `:'. If I is 0, then the path has a leading colon. Trailing colons + are handled OK by the `else' part of the if statement; an empty + string is returned in that case. */ + if (i && string[i] == ':') + i++; + + for (start = i; string[i] && string[i] != ':'; i++) + ; + + *p_index = i; + + if (i == start) + { + if (string[i]) + (*p_index)++; + /* Return "" in the case of a trailing `:'. */ + value = (char *)xmalloc (1); + value[0] = '\0'; + } + else + value = substring (string, start, i); + + return (value); +} + +/* **************************************************************** */ +/* */ +/* Tilde Initialization and Expansion */ +/* */ +/* **************************************************************** */ + +#if defined (PUSHD_AND_POPD) +extern char *get_dirstack_from_string __P((char *)); +#endif + +static char **bash_tilde_prefixes; +static char **bash_tilde_suffixes; + +/* If tilde_expand hasn't been able to expand the text, perhaps it + is a special shell expansion. This function is installed as the + tilde_expansion_preexpansion_hook. It knows how to expand ~- and ~+. + If PUSHD_AND_POPD is defined, ~[+-]N expands to directories from the + directory stack. */ +static char * +bash_special_tilde_expansions (text) + char *text; +{ + char *result; + + result = (char *)NULL; + + if (text[0] == '+' && text[1] == '\0') + result = get_string_value ("PWD"); + else if (text[0] == '-' && text[1] == '\0') + result = get_string_value ("OLDPWD"); +#if defined (PUSHD_AND_POPD) + else if (DIGIT (*text) || ((*text == '+' || *text == '-') && DIGIT (text[1]))) + result = get_dirstack_from_string (text); +#endif + + return (result ? savestring (result) : (char *)NULL); +} + +/* Initialize the tilde expander. In Bash, we handle `~-' and `~+', as + well as handling special tilde prefixes; `:~" and `=~' are indications + that we should do tilde expansion. */ +void +tilde_initialize () +{ + static int times_called = 0; + + /* Tell the tilde expander that we want a crack first. */ + tilde_expansion_preexpansion_hook = bash_special_tilde_expansions; + + /* Tell the tilde expander about special strings which start a tilde + expansion, and the special strings that end one. Only do this once. + tilde_initialize () is called from within bashline_reinitialize (). */ + if (times_called++ == 0) + { + bash_tilde_prefixes = strvec_create (3); + bash_tilde_prefixes[0] = "=~"; + bash_tilde_prefixes[1] = ":~"; + bash_tilde_prefixes[2] = (char *)NULL; + + tilde_additional_prefixes = bash_tilde_prefixes; + + bash_tilde_suffixes = strvec_create (3); + bash_tilde_suffixes[0] = ":"; + bash_tilde_suffixes[1] = "=~"; /* XXX - ?? */ + bash_tilde_suffixes[2] = (char *)NULL; + + tilde_additional_suffixes = bash_tilde_suffixes; + } +} + +/* POSIX.2, 3.6.1: A tilde-prefix consists of an unquoted tilde character + at the beginning of the word, followed by all of the characters preceding + the first unquoted slash in the word, or all the characters in the word + if there is no slash...If none of the characters in the tilde-prefix are + quoted, the characters in the tilde-prefix following the tilde shell be + treated as a possible login name. */ + +#define TILDE_END(c) ((c) == '\0' || (c) == '/' || (c) == ':') + +static int +unquoted_tilde_word (s) + const char *s; +{ + const char *r; + + for (r = s; TILDE_END(*r) == 0; r++) + { + switch (*r) + { + case '\\': + case '\'': + case '"': + return 0; + } + } + return 1; +} + +/* Tilde-expand S by running it through the tilde expansion library. + ASSIGN_P is 1 if this is a variable assignment, so the alternate + tilde prefixes should be enabled (`=~' and `:~', see above). */ +char * +bash_tilde_expand (s, assign_p) + const char *s; + int assign_p; +{ + int old_immed, r; + char *ret; + + old_immed = interrupt_immediately; + interrupt_immediately = 1; + tilde_additional_prefixes = assign_p ? bash_tilde_prefixes : (char **)0; + r = (*s == '~') ? unquoted_tilde_word (s) : 1; + ret = r ? tilde_expand (s) : savestring (s); + interrupt_immediately = old_immed; + return (ret); +} + +/* **************************************************************** */ +/* */ +/* Functions to manipulate and search the group list */ +/* */ +/* **************************************************************** */ + +static int ngroups, maxgroups; + +/* The set of groups that this user is a member of. */ +static GETGROUPS_T *group_array = (GETGROUPS_T *)NULL; + +#if !defined (NOGROUP) +# define NOGROUP (gid_t) -1 +#endif + +static void +initialize_group_array () +{ + register int i; + + if (maxgroups == 0) + maxgroups = getmaxgroups (); + + ngroups = 0; + group_array = (GETGROUPS_T *)xrealloc (group_array, maxgroups * sizeof (GETGROUPS_T)); + +#if defined (HAVE_GETGROUPS) + ngroups = getgroups (maxgroups, group_array); +#endif + + /* If getgroups returns nothing, or the OS does not support getgroups(), + make sure the groups array includes at least the current gid. */ + if (ngroups == 0) + { + group_array[0] = current_user.gid; + ngroups = 1; + } + + /* If the primary group is not in the groups array, add it as group_array[0] + and shuffle everything else up 1, if there's room. */ + for (i = 0; i < ngroups; i++) + if (current_user.gid == (gid_t)group_array[i]) + break; + if (i == ngroups && ngroups < maxgroups) + { + for (i = ngroups; i > 0; i--) + group_array[i] = group_array[i - 1]; + group_array[0] = current_user.gid; + ngroups++; + } + + /* If the primary group is not group_array[0], swap group_array[0] and + whatever the current group is. The vast majority of systems should + not need this; a notable exception is Linux. */ + if (group_array[0] != current_user.gid) + { + for (i = 0; i < ngroups; i++) + if (group_array[i] == current_user.gid) + break; + if (i < ngroups) + { + group_array[i] = group_array[0]; + group_array[0] = current_user.gid; + } + } +} + +/* Return non-zero if GID is one that we have in our groups list. */ +int +#if defined (__STDC__) || defined ( _MINIX) +group_member (gid_t gid) +#else +group_member (gid) + gid_t gid; +#endif /* !__STDC__ && !_MINIX */ +{ +#if defined (HAVE_GETGROUPS) + register int i; +#endif + + /* Short-circuit if possible, maybe saving a call to getgroups(). */ + if (gid == current_user.gid || gid == current_user.egid) + return (1); + +#if defined (HAVE_GETGROUPS) + if (ngroups == 0) + initialize_group_array (); + + /* In case of error, the user loses. */ + if (ngroups <= 0) + return (0); + + /* Search through the list looking for GID. */ + for (i = 0; i < ngroups; i++) + if (gid == (gid_t)group_array[i]) + return (1); +#endif + + return (0); +} + +char ** +get_group_list (ngp) + int *ngp; +{ + static char **group_vector = (char **)NULL; + register int i; + + if (group_vector) + { + if (ngp) + *ngp = ngroups; + return group_vector; + } + + if (ngroups == 0) + initialize_group_array (); + + if (ngroups <= 0) + { + if (ngp) + *ngp = 0; + return (char **)NULL; + } + + group_vector = strvec_create (ngroups); + for (i = 0; i < ngroups; i++) + group_vector[i] = itos (group_array[i]); + + if (ngp) + *ngp = ngroups; + return group_vector; +} + +int * +get_group_array (ngp) + int *ngp; +{ + int i; + static int *group_iarray = (int *)NULL; + + if (group_iarray) + { + if (ngp) + *ngp = ngroups; + return (group_iarray); + } + + if (ngroups == 0) + initialize_group_array (); + + if (ngroups <= 0) + { + if (ngp) + *ngp = 0; + return (int *)NULL; + } + + group_iarray = (int *)xmalloc (ngroups * sizeof (int)); + for (i = 0; i < ngroups; i++) + group_iarray[i] = (int)group_array[i]; + + if (ngp) + *ngp = ngroups; + return group_iarray; +} diff --git a/include/shmbutil.h b/include/shmbutil.h index a737780cd..d296242e7 100644 --- a/include/shmbutil.h +++ b/include/shmbutil.h @@ -31,6 +31,8 @@ extern size_t xmbsrtowcs __P((wchar_t *, const char **, size_t, mbstate_t *)); extern size_t xdupmbstowcs __P((wchar_t **, char ***, const char *)); +extern size_t mbstrlen __P((const char *)); + extern char *xstrchr __P((const char *, int)); #ifndef MB_INVALIDCH @@ -38,6 +40,8 @@ extern char *xstrchr __P((const char *, int)); #define MB_NULLWCH(x) ((x) == 0) #endif +#define MB_STRLEN(s) ((MB_CUR_MAX > 1) ? mbstrlen (s) : STRLEN (s)) + #else /* !HANDLE_MULTIBYTE */ #undef MB_LEN_MAX @@ -54,6 +58,8 @@ extern char *xstrchr __P((const char *, int)); #define MB_NULLWCH(x) (0) #endif +#define MB_STRLEN(s) (STRLEN(s)) + #endif /* !HANDLE_MULTIBYTE */ /* Declare and initialize a multibyte state. Call must be terminated diff --git a/include/shmbutil.h~ b/include/shmbutil.h~ new file mode 100644 index 000000000..1d15240bd --- /dev/null +++ b/include/shmbutil.h~ @@ -0,0 +1,438 @@ +/* shmbutil.h -- utility functions for multibyte characters. */ + +/* Copyright (C) 2002-2004 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#if !defined (_SH_MBUTIL_H_) +#define _SH_MBUTIL_H_ + +#include "stdc.h" + +/* Include config.h for HANDLE_MULTIBYTE */ +#include + +#if defined (HANDLE_MULTIBYTE) + +extern size_t xmbsrtowcs __P((wchar_t *, const char **, size_t, mbstate_t *)); +extern size_t xdupmbstowcs __P((wchar_t **, char ***, const char *)); + +extern size_t mbstrlen __P((const char *)); + +extern char *xstrchr __P((const char *, int)); + +#ifndef MB_INVALIDCH +#define MB_INVALIDCH(x) ((x) == (size_t)-1 || (x) == (size_t)-2) +#define MB_NULLWCH(x) ((x) == 0) +#endif + +#define MB_STRLEN(s) ((MB_CUR_MAX > 1) ? mb_strlen (s) : STRLEN (s)) + +#else /* !HANDLE_MULTIBYTE */ + +#undef MB_LEN_MAX +#undef MB_CUR_MAX + +#define MB_LEN_MAX 1 +#define MB_CUR_MAX 1 + +#undef xstrchr +#define xstrchr(s, c) strchr(s, c) + +#ifndef MB_INVALIDCH +#define MB_INVALIDCH(x) (0) +#define MB_NULLWCH(x) (0) +#endif + +#define MB_STRLEN(s) (STRLEN(s)) + +#endif /* !HANDLE_MULTIBYTE */ + +/* Declare and initialize a multibyte state. Call must be terminated + with `;'. */ +#if defined (HANDLE_MULTIBYTE) +# define DECLARE_MBSTATE \ + mbstate_t state; \ + memset (&state, '\0', sizeof (mbstate_t)) +#else +# define DECLARE_MBSTATE +#endif /* !HANDLE_MULTIBYTE */ + +/* Initialize or reinitialize a multibyte state named `state'. Call must be + terminated with `;'. */ +#if defined (HANDLE_MULTIBYTE) +# define INITIALIZE_MBSTATE memset (&state, '\0', sizeof (mbstate_t)) +#else +# define INITIALIZE_MBSTATE +#endif /* !HANDLE_MULTIBYTE */ + +/* Advance one (possibly multi-byte) character in string _STR of length + _STRSIZE, starting at index _I. STATE must have already been declared. */ +#if defined (HANDLE_MULTIBYTE) +# define ADVANCE_CHAR(_str, _strsize, _i) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + state_bak = state; \ + mblength = mbrlen ((_str) + (_i), (_strsize) - (_i), &state); \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + (_i)++; \ + } \ + else if (mblength == 0) \ + (_i)++; \ + else \ + (_i) += mblength; \ + } \ + else \ + (_i)++; \ + } \ + while (0) +#else +# define ADVANCE_CHAR(_str, _strsize, _i) (_i)++ +#endif /* !HANDLE_MULTIBYTE */ + +/* Advance one (possibly multibyte) character in the string _STR of length + _STRSIZE. + SPECIAL: assume that _STR will be incremented by 1 after this call. */ +#if defined (HANDLE_MULTIBYTE) +# define ADVANCE_CHAR_P(_str, _strsize) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + state_bak = state; \ + mblength = mbrlen ((_str), (_strsize), &state); \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + (_str) += (mblength < 1) ? 0 : (mblength - 1); \ + } \ + } \ + while (0) +#else +# define ADVANCE_CHAR_P(_str, _strsize) +#endif /* !HANDLE_MULTIBYTE */ + +/* Back up one (possibly multi-byte) character in string _STR of length + _STRSIZE, starting at index _I. STATE must have already been declared. */ +#if defined (HANDLE_MULTIBYTE) +# define BACKUP_CHAR(_str, _strsize, _i) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _x, _p; /* _x == temp index into string, _p == prev index */ \ +\ + _x = _p = 0; \ + while (_x < (_i)) \ + { \ + state_bak = state; \ + mblength = mbrlen ((_str) + (_x), (_strsize) - (_x), &state); \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + _x++; \ + } \ + else if (mblength == 0) \ + _x++; \ + else \ + { \ + _p = _x; /* _p == start of prev mbchar */ \ + _x += mblength; \ + } \ + } \ + (_i) = _p; \ + } \ + else \ + (_i)--; \ + } \ + while (0) +#else +# define BACKUP_CHAR(_str, _strsize, _i) (_i)-- +#endif /* !HANDLE_MULTIBYTE */ + +/* Back up one (possibly multibyte) character in the string _BASE of length + _STRSIZE starting at _STR (_BASE <= _STR <= (_BASE + _STRSIZE) ). + SPECIAL: DO NOT assume that _STR will be decremented by 1 after this call. */ +#if defined (HANDLE_MULTIBYTE) +# define BACKUP_CHAR_P(_base, _strsize, _str) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + char *_x, _p; /* _x == temp pointer into string, _p == prev pointer */ \ +\ + _x = _p = _base; \ + while (_x < (_str)) \ + { \ + state_bak = state; \ + mblength = mbrlen (_x, (_strsize) - _x, &state); \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + _x++; \ + } \ + else if (mblength == 0) \ + _x++; \ + else \ + { \ + _p = _x; /* _p == start of prev mbchar */ \ + _x += mblength; \ + } \ + } \ + (_str) = _p; \ + } \ + else \ + (_str)--; \ + } \ + while (0) +#else +# define BACKUP_CHAR_P(_base, _strsize, _str) (_str)-- +#endif /* !HANDLE_MULTIBYTE */ + +/* Copy a single character from the string _SRC to the string _DST. + _SRCEND is a pointer to the end of _SRC. */ +#if defined (HANDLE_MULTIBYTE) +# define COPY_CHAR_P(_dst, _src, _srcend) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _k; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src), (_srcend) - (_src), &state); \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + for (_k = 0; _k < mblength; _k++) \ + *(_dst)++ = *(_src)++; \ + } \ + else \ + *(_dst)++ = *(_src)++; \ + } \ + while (0) +#else +# define COPY_CHAR_P(_dst, _src, _srcend) *(_dst)++ = *(_src)++ +#endif /* !HANDLE_MULTIBYTE */ + +/* Copy a single character from the string _SRC at index _SI to the string + _DST at index _DI. _SRCEND is a pointer to the end of _SRC. */ +#if defined (HANDLE_MULTIBYTE) +# define COPY_CHAR_I(_dst, _di, _src, _srcend, _si) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _k; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src)+(_si)), &state); \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + for (_k = 0; _k < mblength; _k++) \ + _dst[_di++] = _src[_si++]; \ + } \ + else \ + _dst[_di++] = _src[_si++]; \ + } \ + while (0) +#else +# define COPY_CHAR_I(_dst, _di, _src, _srcend, _si) _dst[_di++] = _src[_si++] +#endif /* !HANDLE_MULTIBYTE */ + +/**************************************************************** + * * + * The following are only guaranteed to work in subst.c * + * * + ****************************************************************/ + +#if defined (HANDLE_MULTIBYTE) +# define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _i; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_slen) - (_si), &state); \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + temp = xmalloc (mblength + 2); \ + temp[0] = _escchar; \ + for (_i = 0; _i < mblength; _i++) \ + temp[_i + 1] = _src[_si++]; \ + temp[mblength + 1] = '\0'; \ +\ + goto add_string; \ + } \ + else \ + { \ + _dst[0] = _escchar; \ + _dst[1] = _sc; \ + } \ + } \ + while (0) +#else +# define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \ + _dst[0] = _escchar; \ + _dst[1] = _sc +#endif /* !HANDLE_MULTIBYTE */ + +#if defined (HANDLE_MULTIBYTE) +# define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src) + (_si)), &state); \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + FASTCOPY(((_src) + (_si)), (_dst), mblength); \ +\ + (_dst) += mblength; \ + (_si) += mblength; \ + } \ + else \ + { \ + *(_dst)++ = _src[(_si)]; \ + (_si)++; \ + } \ + } \ + while (0) +#else +# define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \ + *(_dst)++ = _src[(_si)]; \ + (_si)++ +#endif /* !HANDLE_MULTIBYTE */ + +#if HANDLE_MULTIBYTE +# define SADD_MBCHAR(_dst, _src, _si, _srcsize) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + int i; \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \ + if (mblength == (size_t)-1 || mblength == (size_t)-2) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + if (mblength < 1) \ + mblength = 1; \ +\ + _dst = (char *)xmalloc (mblength + 1); \ + for (i = 0; i < mblength; i++) \ + (_dst)[i] = (_src)[(_si)++]; \ + (_dst)[mblength] = '\0'; \ +\ + goto add_string; \ + } \ + } \ + while (0) + +#else +# define SADD_MBCHAR(_dst, _src, _si, _srcsize) +#endif + +/* Watch out when using this -- it's just straight textual subsitution */ +#if defined (HANDLE_MULTIBYTE) +# define SADD_MBQCHAR_BODY(_dst, _src, _si, _srcsize) \ +\ + int i; \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \ + if (mblength == (size_t)-1 || mblength == (size_t)-2) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + if (mblength < 1) \ + mblength = 1; \ +\ + (_dst) = (char *)xmalloc (mblength + 2); \ + (_dst)[0] = CTLESC; \ + for (i = 0; i < mblength; i++) \ + (_dst)[i+1] = (_src)[(_si)++]; \ + (_dst)[mblength+1] = '\0'; \ +\ + goto add_string + +#endif /* HANDLE_MULTIBYTE */ +#endif /* _SH_MBUTIL_H_ */ diff --git a/jobs.c b/jobs.c index 8418267b6..f2da4ae4b 100644 --- a/jobs.c +++ b/jobs.c @@ -997,7 +997,7 @@ describe_pid (pid) job = find_job (pid, 0); if (job != NO_JOB) - printf ("[%d] %ld\n", job + 1, (long)pid); + fprintf (stderr, "[%d] %ld\n", job + 1, (long)pid); else programming_error (_("describe_pid: %ld: no such pid"), (long)pid); @@ -2311,12 +2311,12 @@ start_job (job, foreground) p = jobs[job]->pipe; if (foreground == 0) - fprintf (stderr, "[%d]%c ", job + 1, + printf ("[%d]%c ", job + 1, (job == current_job) ? '+': ((job == previous_job) ? '-' : ' ')); do { - fprintf (stderr, "%s%s", + printf ("%s%s", p->command ? p->command : "", p->next != jobs[job]->pipe? " | " : ""); p = p->next; @@ -2324,12 +2324,12 @@ start_job (job, foreground) while (p != jobs[job]->pipe); if (foreground == 0) - fprintf (stderr, " &"); + printf (" &"); if (strcmp (wd, jobs[job]->wd) != 0) - fprintf (stderr, " (wd: %s)", polite_directory_format (jobs[job]->wd)); + printf (" (wd: %s)", polite_directory_format (jobs[job]->wd)); - fprintf (stderr, "\n"); + printf ("\n"); /* Run the job. */ if (already_running == 0) diff --git a/jobs.c~ b/jobs.c~ new file mode 100644 index 000000000..7ad91c836 --- /dev/null +++ b/jobs.c~ @@ -0,0 +1,3535 @@ +/* The thing that makes children, remembers them, and contains wait loops. */ + +/* This file works with both POSIX and BSD systems. It implements job + control. */ + +/* Copyright (C) 1989-2003 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#include "bashtypes.h" +#include "trap.h" +#include +#include +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "posixtime.h" + +#if defined (HAVE_SYS_RESOURCE_H) && defined (HAVE_WAIT3) && !defined (_POSIX_VERSION) && !defined (RLIMTYPE) +# include +#endif /* !_POSIX_VERSION && HAVE_SYS_RESOURCE_H && HAVE_WAIT3 && !RLIMTYPE */ + +#if defined (HAVE_SYS_FILE_H) +# include +#endif + +#include "filecntl.h" +#include +#include + +#if defined (BUFFERED_INPUT) +# include "input.h" +#endif + +/* Need to include this up here for *_TTY_DRIVER definitions. */ +#include "shtty.h" + +/* Define this if your output is getting swallowed. It's a no-op on + machines with the termio or termios tty drivers. */ +/* #define DRAIN_OUTPUT */ + +/* For the TIOCGPGRP and TIOCSPGRP ioctl parameters on HP-UX */ +#if defined (hpux) && !defined (TERMIOS_TTY_DRIVER) +# include +#endif /* hpux && !TERMIOS_TTY_DRIVER */ + +#if !defined (STRUCT_WINSIZE_IN_SYS_IOCTL) +/* For struct winsize on SCO */ +/* sys/ptem.h has winsize but needs mblk_t from sys/stream.h */ +# if defined (HAVE_SYS_PTEM_H) && defined (TIOCGWINSZ) && defined (SIGWINCH) +# if defined (HAVE_SYS_STREAM_H) +# include +# endif +# include +# endif /* HAVE_SYS_PTEM_H && TIOCGWINSZ && SIGWINCH */ +#endif /* !STRUCT_WINSIZE_IN_SYS_IOCTL */ + +#include "bashansi.h" +#include "bashintl.h" +#include "shell.h" +#include "jobs.h" +#include "flags.h" + +#include "builtins/builtext.h" +#include "builtins/common.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#define DEFAULT_CHILD_MAX 32 +#define MAX_JOBS_IN_ARRAY 4096 /* testing */ + +/* Take care of system dependencies that must be handled when waiting for + children. The arguments to the WAITPID macro match those to the Posix.1 + waitpid() function. */ + +#if defined (ultrix) && defined (mips) && defined (_POSIX_VERSION) +# define WAITPID(pid, statusp, options) \ + wait3 ((union wait *)statusp, options, (struct rusage *)0) +#else +# if defined (_POSIX_VERSION) || defined (HAVE_WAITPID) +# define WAITPID(pid, statusp, options) \ + waitpid ((pid_t)pid, statusp, options) +# else +# if defined (HAVE_WAIT3) +# define WAITPID(pid, statusp, options) \ + wait3 (statusp, options, (struct rusage *)0) +# else +# define WAITPID(pid, statusp, options) \ + wait3 (statusp, options, (int *)0) +# endif /* HAVE_WAIT3 */ +# endif /* !_POSIX_VERSION && !HAVE_WAITPID*/ +#endif /* !(Ultrix && mips && _POSIX_VERSION) */ + +/* getpgrp () varies between systems. Even systems that claim to be + Posix.1 compatible lie sometimes (Ultrix, SunOS4, apollo). */ +#if defined (GETPGRP_VOID) +# define getpgid(p) getpgrp () +#else +# define getpgid(p) getpgrp (p) +#endif /* !GETPGRP_VOID */ + +/* If the system needs it, REINSTALL_SIGCHLD_HANDLER will reinstall the + handler for SIGCHLD. */ +#if defined (MUST_REINSTALL_SIGHANDLERS) +# define REINSTALL_SIGCHLD_HANDLER signal (SIGCHLD, sigchld_handler) +#else +# define REINSTALL_SIGCHLD_HANDLER +#endif /* !MUST_REINSTALL_SIGHANDLERS */ + +/* Some systems let waitpid(2) tell callers about stopped children. */ +#if !defined (WCONTINUED) +# define WCONTINUED 0 +#endif +#if !defined (WIFCONTINUED) +# define WIFCONTINUED(s) (0) +#endif + +/* The number of additional slots to allocate when we run out. */ +#define JOB_SLOTS 8 + +typedef int sh_job_map_func_t __P((JOB *, int, int, int)); + +#if defined (READLINE) +extern void rl_set_screen_size __P((int, int)); +#endif + +/* Variables used here but defined in other files. */ +extern int subshell_environment, line_number; +extern int posixly_correct, shell_level; +extern int interrupt_immediately; +extern int last_command_exit_value, last_command_exit_signal; +extern int loop_level, breaking; +extern int sourcelevel; +extern sh_builtin_func_t *this_shell_builtin; +extern char *shell_name, *this_command_name; +extern sigset_t top_level_mask; +extern procenv_t wait_intr_buf; +extern int wait_signal_received; +extern WORD_LIST *subst_assign_varlist; + +/* The array of known jobs. */ +JOB **jobs = (JOB **)NULL; + +/* The number of slots currently allocated to JOBS. */ +int job_slots = 0; + +/* The controlling tty for this shell. */ +int shell_tty = -1; + +/* The shell's process group. */ +pid_t shell_pgrp = NO_PID; + +/* The terminal's process group. */ +pid_t terminal_pgrp = NO_PID; + +/* The process group of the shell's parent. */ +pid_t original_pgrp = NO_PID; + +/* The process group of the pipeline currently being made. */ +pid_t pipeline_pgrp = (pid_t)0; + +#if defined (PGRP_PIPE) +/* Pipes which each shell uses to communicate with the process group leader + until all of the processes in a pipeline have been started. Then the + process leader is allowed to continue. */ +int pgrp_pipe[2] = { -1, -1 }; +#endif + +/* The job which is current; i.e. the one that `%+' stands for. */ +int current_job = NO_JOB; + +/* The previous job; i.e. the one that `%-' stands for. */ +int previous_job = NO_JOB; + +/* Last child made by the shell. */ +pid_t last_made_pid = NO_PID; + +/* Pid of the last asynchronous child. */ +pid_t last_asynchronous_pid = NO_PID; + +/* The pipeline currently being built. */ +PROCESS *the_pipeline = (PROCESS *)NULL; + +/* If this is non-zero, do job control. */ +int job_control = 1; + +/* Call this when you start making children. */ +int already_making_children = 0; + +/* If this is non-zero, $LINES and $COLUMNS are reset after every process + exits from get_tty_state(). */ +int check_window_size; + +/* Functions local to this file. */ + +static void get_new_window_size __P((int)); + +static void run_sigchld_trap __P((int)); + +static sighandler wait_sigint_handler __P((int)); +static sighandler sigchld_handler __P((int)); +static sighandler sigwinch_sighandler __P((int)); +static sighandler sigcont_sighandler __P((int)); +static sighandler sigstop_sighandler __P((int)); + +static int waitchld __P((pid_t, int)); + +static PROCESS *find_pipeline __P((pid_t, int, int *)); + +static char *current_working_directory __P((void)); +static char *job_working_directory __P((void)); +static char *j_strsignal __P((int)); +static char *printable_job_status __P((int, PROCESS *, int)); + +static pid_t find_last_pid __P((int, int)); + +static int set_new_line_discipline __P((int)); +static int map_over_jobs __P((sh_job_map_func_t *, int, int)); +static int job_last_stopped __P((int)); +static int job_last_running __P((int)); +static int most_recent_job_in_state __P((int, JOB_STATE)); +static int find_job __P((pid_t, int)); +static int print_job __P((JOB *, int, int, int)); +static int process_exit_status __P((WAIT)); +static int process_exit_signal __P((WAIT)); +static int job_exit_status __P((int)); +static int job_exit_signal __P((int)); +static int set_job_status_and_cleanup __P((int)); + +static WAIT raw_job_exit_status __P((int)); + +static void notify_of_job_status __P((void)); +static void cleanup_dead_jobs __P((void)); +static int compact_jobs_list __P((int)); +static void discard_pipeline __P((PROCESS *)); +static void add_process __P((char *, pid_t)); +static void print_pipeline __P((PROCESS *, int, int, FILE *)); +static void pretty_print_job __P((int, int, FILE *)); +static void set_current_job __P((int)); +static void reset_current __P((void)); +static void set_job_running __P((int)); +static void setjstatus __P((int)); +static void mark_all_jobs_as_dead __P((void)); +static void mark_dead_jobs_as_notified __P((int)); +static void restore_sigint_handler __P((void)); +#if defined (PGRP_PIPE) +static void pipe_read __P((int *)); +static void pipe_close __P((int *)); +#endif + +#if defined (ARRAY_VARS) +static int *pstatuses; /* list of pipeline statuses */ +static int statsize; +#endif + +/* Used to synchronize between wait_for and other functions and the SIGCHLD + signal handler. */ +static int sigchld; +static int queue_sigchld; + +#define QUEUE_SIGCHLD(os) (os) = sigchld, queue_sigchld++ + +#define UNQUEUE_SIGCHLD(os) \ + do { \ + queue_sigchld--; \ + if (queue_sigchld == 0 && os != sigchld) \ + waitchld (-1, 0); \ + } while (0) + +static SigHandler *old_tstp, *old_ttou, *old_ttin; +static SigHandler *old_cont = (SigHandler *)SIG_DFL; + +#if defined (TIOCGWINSZ) && defined (SIGWINCH) +static SigHandler *old_winch = (SigHandler *)SIG_DFL; +#endif + +/* A place to temporarily save the current pipeline. */ +static PROCESS *saved_pipeline; +static int saved_already_making_children; + +/* Set this to non-zero whenever you don't want the jobs list to change at + all: no jobs deleted and no status change notifications. This is used, + for example, when executing SIGCHLD traps, which may run arbitrary + commands. */ +static int jobs_list_frozen; + +static char retcode_name_buffer[64]; + +static long child_max = -1L; + +#if !defined (_POSIX_VERSION) + +/* These are definitions to map POSIX 1003.1 functions onto existing BSD + library functions and system calls. */ +#define setpgid(pid, pgrp) setpgrp (pid, pgrp) +#define tcsetpgrp(fd, pgrp) ioctl ((fd), TIOCSPGRP, &(pgrp)) + +pid_t +tcgetpgrp (fd) + int fd; +{ + pid_t pgrp; + + /* ioctl will handle setting errno correctly. */ + if (ioctl (fd, TIOCGPGRP, &pgrp) < 0) + return (-1); + return (pgrp); +} + +#endif /* !_POSIX_VERSION */ + +/* Return the working directory for the current process. Unlike + job_working_directory, this does not call malloc (), nor do any + of the functions it calls. This is so that it can safely be called + from a signal handler. */ +static char * +current_working_directory () +{ + char *dir; + static char d[PATH_MAX]; + + dir = get_string_value ("PWD"); + + if (dir == 0 && the_current_working_directory && no_symbolic_links) + dir = the_current_working_directory; + + if (dir == 0) + { + dir = getcwd (d, sizeof(d)); + if (dir) + dir = d; + } + + return (dir == 0) ? "" : dir; +} + +/* Return the working directory for the current process. */ +static char * +job_working_directory () +{ + char *dir; + + dir = get_string_value ("PWD"); + if (dir) + return (savestring (dir)); + + dir = get_working_directory ("job-working-directory"); + if (dir) + return (dir); + + return (savestring ("")); +} + +void +making_children () +{ + if (already_making_children) + return; + + already_making_children = 1; + start_pipeline (); +} + +void +stop_making_children () +{ + already_making_children = 0; +} + +void +cleanup_the_pipeline () +{ + if (the_pipeline) + { + discard_pipeline (the_pipeline); + the_pipeline = (PROCESS *)NULL; + } +} + +void +save_pipeline (clear) + int clear; +{ + saved_pipeline = the_pipeline; + saved_already_making_children = already_making_children; + if (clear) + the_pipeline = (PROCESS *)NULL; +} + +void +restore_pipeline (discard) + int discard; +{ + PROCESS *old_pipeline; + + old_pipeline = the_pipeline; + the_pipeline = saved_pipeline; + already_making_children = saved_already_making_children; + if (discard) + discard_pipeline (old_pipeline); +} + +/* Start building a pipeline. */ +void +start_pipeline () +{ + if (the_pipeline) + { + cleanup_the_pipeline (); + pipeline_pgrp = 0; +#if defined (PGRP_PIPE) + pipe_close (pgrp_pipe); +#endif + } + +#if defined (PGRP_PIPE) + if (job_control) + { + if (pipe (pgrp_pipe) == -1) + sys_error ("start_pipeline: pgrp pipe"); + } +#endif +} + +/* Stop building a pipeline. Install the process list in the job array. + This returns the index of the newly installed job. + DEFERRED is a command structure to be executed upon satisfactory + execution exit of this pipeline. */ +int +stop_pipeline (async, deferred) + int async; + COMMAND *deferred; +{ + register int i, j; + JOB *newjob; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + +#if defined (PGRP_PIPE) + /* The parent closes the process group synchronization pipe. */ + pipe_close (pgrp_pipe); +#endif + + cleanup_dead_jobs (); + + if (job_slots == 0) + { + job_slots = JOB_SLOTS; + jobs = (JOB **)xmalloc (job_slots * sizeof (JOB *)); + + /* Now blank out these new entries. */ + for (i = 0; i < job_slots; i++) + jobs[i] = (JOB *)NULL; + } + + /* Scan from the last slot backward, looking for the next free one. */ + /* XXX - revisit this interactive assumption */ + if (interactive) + { + for (i = job_slots; i; i--) + if (jobs[i - 1]) + break; + } + else + { + /* If we're not interactive, we don't need to monotonically increase + the job number (in fact, we don't care about the job number at all), + so we can simply scan for the first free slot. This helps to keep + us from continuously reallocating the jobs array when running + certain kinds of shell loops, and saves time spent searching. */ + for (i = 0; i < job_slots; i++) + if (jobs[i] == 0) + break; + } + + /* Do we need more room? */ + + /* First try compaction */ + if (subshell_environment && interactive_shell && i == job_slots && job_slots >= MAX_JOBS_IN_ARRAY) + i = compact_jobs_list (0); + + /* If we can't compact, reallocate */ + if (i == job_slots) + { + job_slots += JOB_SLOTS; + jobs = (JOB **)xrealloc (jobs, ((1 + job_slots) * sizeof (JOB *))); + + for (j = i; j < job_slots; j++) + jobs[j] = (JOB *)NULL; + } + + /* Add the current pipeline to the job list. */ + if (the_pipeline) + { + register PROCESS *p; + int any_alive, any_stopped; + + newjob = (JOB *)xmalloc (sizeof (JOB)); + + for (p = the_pipeline; p->next != the_pipeline; p = p->next) + ; + p->next = (PROCESS *)NULL; + newjob->pipe = REVERSE_LIST (the_pipeline, PROCESS *); + for (p = newjob->pipe; p->next; p = p->next) + ; + p->next = newjob->pipe; + + the_pipeline = (PROCESS *)NULL; + newjob->pgrp = pipeline_pgrp; + pipeline_pgrp = 0; + + newjob->flags = 0; + + /* Flag to see if in another pgrp. */ + if (job_control) + newjob->flags |= J_JOBCONTROL; + + /* Set the state of this pipeline. */ + p = newjob->pipe; + any_alive = any_stopped = 0; + do + { + any_alive |= p->running; + any_stopped |= WIFSTOPPED (p->status); + p = p->next; + } + while (p != newjob->pipe); + + newjob->state = any_alive ? JRUNNING : (any_stopped ? JSTOPPED : JDEAD); + newjob->wd = job_working_directory (); + newjob->deferred = deferred; + + newjob->j_cleanup = (sh_vptrfunc_t *)NULL; + newjob->cleanarg = (PTR_T) NULL; + + jobs[i] = newjob; + if (newjob->state == JDEAD && (newjob->flags & J_FOREGROUND)) + setjstatus (i); + } + else + newjob = (JOB *)NULL; + + if (async) + { + if (newjob) + newjob->flags &= ~J_FOREGROUND; + reset_current (); + } + else + { + if (newjob) + { + newjob->flags |= J_FOREGROUND; + /* + * !!!!! NOTE !!!!! (chet@ins.cwru.edu) + * + * The currently-accepted job control wisdom says to set the + * terminal's process group n+1 times in an n-step pipeline: + * once in the parent and once in each child. This is where + * the parent gives it away. + * + */ + if (job_control && newjob->pgrp) + give_terminal_to (newjob->pgrp, 0); + } + } + + stop_making_children (); + UNBLOCK_CHILD (oset); + return (current_job); +} + +/* Delete all DEAD jobs that the user had received notification about. */ +static void +cleanup_dead_jobs () +{ + register int i; + int os; + + if (job_slots == 0 || jobs_list_frozen) + return; + + QUEUE_SIGCHLD(os); + + for (i = 0; i < job_slots; i++) + if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i)) + delete_job (i, 0); + + UNQUEUE_SIGCHLD(os); +} + +/* Compact the jobs list by removing dead jobs. Assumed that we have filled + the jobs array to some predefined maximum. Called when the shell is not + the foreground process (subshell_environment != 0). Returns the first + available slot in the compacted list. If that value is job_slots, then + the list needs to be reallocated. The jobs array is in new memory if + this returns > 0 and < job_slots. FLAGS is reserved for future use. */ +static int +compact_jobs_list (flags) + int flags; +{ + sigset_t set, oset; + register int i, j; + int nremove, ndel; + JOB **newlist; + + if (job_slots == 0 || jobs_list_frozen) + return job_slots; + + if (child_max < 0) + child_max = getmaxchild (); + + /* Take out at most a quarter of the jobs in the jobs array, but leave at + least child_max */ + nremove = job_slots >> 2; + if ((job_slots - nremove) < child_max) + nremove = job_slots - child_max; + + /* need to increase jobs list to at least CHILD_MAX entries */ + if (nremove < 0) + return job_slots; + + BLOCK_CHILD (set, oset); + + for (ndel = i = 0; i < job_slots; i++) + if (jobs[i]) + { + if (DEADJOB (i) && (find_last_pid (i, 0) != last_asynchronous_pid)) + { + delete_job (i, 0); + ndel++; + if (ndel == nremove) + break; + } + } + + if (ndel == 0) + { + UNBLOCK_CHILD (oset); + return job_slots; + } + + newlist = (JOB **)xmalloc ((1 + job_slots) * sizeof (JOB *)); + for (i = j = 0; i < job_slots; i++) + if (jobs[i]) + newlist[j++] = jobs[i]; + + ndel = j; + for ( ; j < job_slots; j++) + newlist[j] = (JOB *)NULL; + + free (jobs); + jobs = newlist; + + UNBLOCK_CHILD (oset); + + return ndel; +} + +/* Delete the job at INDEX from the job list. Must be called + with SIGCHLD blocked. */ +void +delete_job (job_index, warn_stopped) + int job_index, warn_stopped; +{ + register JOB *temp; + + if (job_slots == 0 || jobs_list_frozen) + return; + + if (warn_stopped && subshell_environment == 0 && STOPPED (job_index)) + internal_warning (_("deleting stopped job %d with process group %ld"), job_index+1, (long)jobs[job_index]->pgrp); + + temp = jobs[job_index]; + if (job_index == current_job || job_index == previous_job) + reset_current (); + + jobs[job_index] = (JOB *)NULL; + + free (temp->wd); + discard_pipeline (temp->pipe); + + if (temp->deferred) + dispose_command (temp->deferred); + + free (temp); +} + +/* Must be called with SIGCHLD blocked. */ +void +nohup_job (job_index) + int job_index; +{ + register JOB *temp; + + if (job_slots == 0) + return; + + if (temp = jobs[job_index]) + temp->flags |= J_NOHUP; +} + +/* Get rid of the data structure associated with a process chain. */ +static void +discard_pipeline (chain) + register PROCESS *chain; +{ + register PROCESS *this, *next; + + this = chain; + do + { + next = this->next; + FREE (this->command); + free (this); + this = next; + } + while (this != chain); +} + +/* Add this process to the chain being built in the_pipeline. + NAME is the command string that will be exec'ed later. + PID is the process id of the child. */ +static void +add_process (name, pid) + char *name; + pid_t pid; +{ + PROCESS *t, *p; + + t = (PROCESS *)xmalloc (sizeof (PROCESS)); + t->next = the_pipeline; + t->pid = pid; + WSTATUS (t->status) = 0; + t->running = PS_RUNNING; + t->command = name; + the_pipeline = t; + + if (t->next == 0) + t->next = t; + else + { + p = t->next; + while (p->next != t->next) + p = p->next; + p->next = t; + } +} + +#if 0 +/* Take the last job and make it the first job. Must be called with + SIGCHLD blocked. */ +int +rotate_the_pipeline () +{ + PROCESS *p; + + if (the_pipeline->next == the_pipeline) + return; + for (p = the_pipeline; p->next != the_pipeline; p = p->next) + ; + the_pipeline = p; +} + +/* Reverse the order of the processes in the_pipeline. Must be called with + SIGCHLD blocked. */ +int +reverse_the_pipeline () +{ + PROCESS *p, *n; + + if (the_pipeline->next == the_pipeline) + return; + + for (p = the_pipeline; p->next != the_pipeline; p = p->next) + ; + p->next = (PROCESS *)NULL; + + n = REVERSE_LIST (the_pipeline, PROCESS *); + + the_pipeline = n; + for (p = the_pipeline; p->next; p = p->next) + ; + p->next = the_pipeline; +} +#endif + +/* Map FUNC over the list of jobs. If FUNC returns non-zero, + then it is time to stop mapping, and that is the return value + for map_over_jobs. FUNC is called with a JOB, arg1, arg2, + and INDEX. */ +static int +map_over_jobs (func, arg1, arg2) + sh_job_map_func_t *func; + int arg1, arg2; +{ + register int i; + int result; + sigset_t set, oset; + + if (job_slots == 0) + return 0; + + BLOCK_CHILD (set, oset); + + for (i = result = 0; i < job_slots; i++) + { + if (jobs[i]) + { + result = (*func)(jobs[i], arg1, arg2, i); + if (result) + break; + } + } + + UNBLOCK_CHILD (oset); + + return (result); +} + +/* Cause all the jobs in the current pipeline to exit. */ +void +terminate_current_pipeline () +{ + if (pipeline_pgrp && pipeline_pgrp != shell_pgrp) + { + killpg (pipeline_pgrp, SIGTERM); + killpg (pipeline_pgrp, SIGCONT); + } +} + +/* Cause all stopped jobs to exit. */ +void +terminate_stopped_jobs () +{ + register int i; + + for (i = 0; i < job_slots; i++) + { + if (jobs[i] && STOPPED (i)) + { + killpg (jobs[i]->pgrp, SIGTERM); + killpg (jobs[i]->pgrp, SIGCONT); + } + } +} + +/* Cause all jobs, running or stopped, to receive a hangup signal. If + a job is marked J_NOHUP, don't send the SIGHUP. */ +void +hangup_all_jobs () +{ + register int i; + + for (i = 0; i < job_slots; i++) + { + if (jobs[i]) + { + if ((jobs[i]->flags & J_NOHUP) == 0) + killpg (jobs[i]->pgrp, SIGHUP); + if (STOPPED (i)) + killpg (jobs[i]->pgrp, SIGCONT); + } + } +} + +void +kill_current_pipeline () +{ + stop_making_children (); + start_pipeline (); +} + +/* Return the pipeline that PID belongs to. Note that the pipeline + doesn't have to belong to a job. Must be called with SIGCHLD blocked. */ +static PROCESS * +find_pipeline (pid, running_only, jobp) + pid_t pid; + int running_only; + int *jobp; /* index into jobs list or NO_JOB */ +{ + int job; + register PROCESS *p; + + /* See if this process is in the pipeline that we are building. */ + if (jobp) + *jobp = NO_JOB; + if (the_pipeline) + { + p = the_pipeline; + do + { + /* Return it if we found it. */ + if (p->pid == pid) + { + if ((running_only && PRUNNING(p)) || (running_only == 0)) + return (p); + } + + p = p->next; + } + while (p != the_pipeline); + } + + job = find_job (pid, running_only); + if (jobp) + *jobp = job; + return (job == NO_JOB) ? (PROCESS *)NULL : jobs[job]->pipe; +} + +/* Return the job index that PID belongs to, or NO_JOB if it doesn't + belong to any job. Must be called with SIGCHLD blocked. */ +static int +find_job (pid, running_only) + pid_t pid; + int running_only; +{ + register int i; + register PROCESS *p; + + for (i = 0; i < job_slots; i++) + { + if (jobs[i]) + { + p = jobs[i]->pipe; + + do + { + if (p->pid == pid) + { + if ((running_only && PRUNNING(p)) || (running_only == 0)) + return (i); + } + + p = p->next; + } + while (p != jobs[i]->pipe); + } + } + + return (NO_JOB); +} + +/* Find a job given a PID. If BLOCK is non-zero, block SIGCHLD as + required by find_job. */ +int +get_job_by_pid (pid, block) + pid_t pid; + int block; +{ + int job; + sigset_t set, oset; + + if (block) + BLOCK_CHILD (set, oset); + + job = find_job (pid, 0); + + if (block) + UNBLOCK_CHILD (oset); + + return job; +} + +/* Print descriptive information about the job with leader pid PID. */ +void +describe_pid (pid) + pid_t pid; +{ + int job; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + job = find_job (pid, 0); + + if (job != NO_JOB) + fprintf (stderr, "[%d] %ld\n", job + 1, (long)pid); + else + programming_error (_("describe_pid: %ld: no such pid"), (long)pid); + + UNBLOCK_CHILD (oset); +} + +static char * +j_strsignal (s) + int s; +{ + char *x; + + x = strsignal (s); + if (x == 0) + { + x = retcode_name_buffer; + sprintf (x, "Signal %d", s); + } + return x; +} + +static char * +printable_job_status (j, p, format) + int j; + PROCESS *p; + int format; +{ + static char *temp; + int es; + + temp = "Done"; + + if (STOPPED (j) && format == 0) + { + if (posixly_correct == 0 || p == 0 || (WIFSTOPPED (p->status) == 0)) + temp = "Stopped"; + else + { + temp = retcode_name_buffer; + sprintf (temp, "Stopped(%s)", signal_name (WSTOPSIG (p->status))); + } + } + else if (RUNNING (j)) + temp = "Running"; + else + { + if (WIFSTOPPED (p->status)) + temp = j_strsignal (WSTOPSIG (p->status)); + else if (WIFSIGNALED (p->status)) + temp = j_strsignal (WTERMSIG (p->status)); + else if (WIFEXITED (p->status)) + { + temp = retcode_name_buffer; + es = WEXITSTATUS (p->status); + if (es == 0) + strcpy (temp, "Done"); + else if (posixly_correct) + sprintf (temp, "Done(%d)", es); + else + sprintf (temp, "Exit %d", es); + } + else + temp = "Unknown status"; + } + + return temp; +} + +/* This is the way to print out information on a job if you + know the index. FORMAT is: + + JLIST_NORMAL) [1]+ Running emacs + JLIST_LONG ) [1]+ 2378 Running emacs + -1 ) [1]+ 2378 emacs + + JLIST_NORMAL) [1]+ Stopped ls | more + JLIST_LONG ) [1]+ 2369 Stopped ls + 2367 | more + JLIST_PID_ONLY) + Just list the pid of the process group leader (really + the process group). + JLIST_CHANGED_ONLY) + Use format JLIST_NORMAL, but list only jobs about which + the user has not been notified. */ + +/* Print status for pipeline P. If JOB_INDEX is >= 0, it is the index into + the JOBS array corresponding to this pipeline. FORMAT is as described + above. Must be called with SIGCHLD blocked. + + If you're printing a pipeline that's not in the jobs array, like the + current pipeline as it's being created, pass -1 for JOB_INDEX */ +static void +print_pipeline (p, job_index, format, stream) + PROCESS *p; + int job_index, format; + FILE *stream; +{ + PROCESS *first, *last, *show; + int es, name_padding; + char *temp; + + if (p == 0) + return; + + first = last = p; + while (last->next != first) + last = last->next; + + for (;;) + { + if (p != first) + fprintf (stream, format ? " " : " |"); + + if (format != JLIST_STANDARD) + fprintf (stream, "%5ld", (long)p->pid); + + fprintf (stream, " "); + + if (format > -1 && job_index >= 0) + { + show = format ? p : last; + temp = printable_job_status (job_index, show, format); + + if (p != first) + { + if (format) + { + if (show->running == first->running && + WSTATUS (show->status) == WSTATUS (first->status)) + temp = ""; + } + else + temp = (char *)NULL; + } + + if (temp) + { + fprintf (stream, "%s", temp); + + es = STRLEN (temp); + if (es == 0) + es = 2; /* strlen ("| ") */ + name_padding = LONGEST_SIGNAL_DESC - es; + + fprintf (stream, "%*s", name_padding, ""); + + if ((WIFSTOPPED (show->status) == 0) && + (WIFCONTINUED (show->status) == 0) && + WIFCORED (show->status)) + fprintf (stream, "(core dumped) "); + } + } + + if (p != first && format) + fprintf (stream, "| "); + + if (p->command) + fprintf (stream, "%s", p->command); + + if (p == last && job_index >= 0) + { + temp = current_working_directory (); + + if (RUNNING (job_index) && (IS_FOREGROUND (job_index) == 0)) + fprintf (stream, " &"); + + if (strcmp (temp, jobs[job_index]->wd) != 0) + fprintf (stream, + " (wd: %s)", polite_directory_format (jobs[job_index]->wd)); + } + + if (format || (p == last)) + { + /* We need to add a CR only if this is an interactive shell, and + we're reporting the status of a completed job asynchronously. + We can't really check whether this particular job is being + reported asynchronously, so just add the CR if the shell is + currently interactive and asynchronous notification is enabled. */ + if (asynchronous_notification && interactive) + fprintf (stream, "\r\n"); + else + fprintf (stream, "\n"); + } + + if (p == last) + break; + p = p->next; + } + fflush (stream); +} + +/* Print information to STREAM about jobs[JOB_INDEX] according to FORMAT. + Must be called with SIGCHLD blocked or queued with queue_sigchld */ +static void +pretty_print_job (job_index, format, stream) + int job_index, format; + FILE *stream; +{ + register PROCESS *p; + + /* Format only pid information about the process group leader? */ + if (format == JLIST_PID_ONLY) + { + fprintf (stream, "%ld\n", (long)jobs[job_index]->pipe->pid); + return; + } + + if (format == JLIST_CHANGED_ONLY) + { + if (IS_NOTIFIED (job_index)) + return; + format = JLIST_STANDARD; + } + + if (format != JLIST_NONINTERACTIVE) + fprintf (stream, "[%d]%c ", job_index + 1, + (job_index == current_job) ? '+': + (job_index == previous_job) ? '-' : ' '); + + if (format == JLIST_NONINTERACTIVE) + format = JLIST_LONG; + + p = jobs[job_index]->pipe; + + print_pipeline (p, job_index, format, stream); + + /* We have printed information about this job. When the job's + status changes, waitchld () sets the notification flag to 0. */ + jobs[job_index]->flags |= J_NOTIFIED; +} + +static int +print_job (job, format, state, job_index) + JOB *job; + int format, state, job_index; +{ + if (state == -1 || (JOB_STATE)state == job->state) + pretty_print_job (job_index, format, stdout); + return (0); +} + +void +list_one_job (job, format, ignore, job_index) + JOB *job; + int format, ignore, job_index; +{ + pretty_print_job (job_index, format, stdout); +} + +void +list_stopped_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, (int)JSTOPPED); +} + +void +list_running_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, (int)JRUNNING); +} + +/* List jobs. If FORMAT is non-zero, then the long form of the information + is printed, else just a short version. */ +void +list_all_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, -1); +} + +/* Fork, handling errors. Returns the pid of the newly made child, or 0. + COMMAND is just for remembering the name of the command; we don't do + anything else with it. ASYNC_P says what to do with the tty. If + non-zero, then don't give it away. */ +pid_t +make_child (command, async_p) + char *command; + int async_p; +{ + sigset_t set, oset; + pid_t pid; + + sigemptyset (&set); + sigaddset (&set, SIGCHLD); + sigaddset (&set, SIGINT); + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + + making_children (); + +#if defined (BUFFERED_INPUT) + /* If default_buffered_input is active, we are reading a script. If + the command is asynchronous, we have already duplicated /dev/null + as fd 0, but have not changed the buffered stream corresponding to + the old fd 0. We don't want to sync the stream in this case. */ + if (default_buffered_input != -1 && + (!async_p || default_buffered_input > 0)) + sync_buffered_stream (default_buffered_input); +#endif /* BUFFERED_INPUT */ + + /* Create the child, handle severe errors. */ + if ((pid = fork ()) < 0) + { + sys_error ("fork"); + + /* Kill all of the processes in the current pipeline. */ + terminate_current_pipeline (); + + /* Discard the current pipeline, if any. */ + if (the_pipeline) + kill_current_pipeline (); + + throw_to_top_level (); /* Reset signals, etc. */ + } + + if (pid == 0) + { + /* In the child. Give this child the right process group, set the + signals to the default state for a new process. */ + pid_t mypid; + + mypid = getpid (); +#if defined (BUFFERED_INPUT) + /* Close default_buffered_input if it's > 0. We don't close it if it's + 0 because that's the file descriptor used when redirecting input, + and it's wrong to close the file in that case. */ + unset_bash_input (0); +#endif /* BUFFERED_INPUT */ + + /* Restore top-level signal mask. */ + sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); + + if (job_control) + { + /* All processes in this pipeline belong in the same + process group. */ + + if (pipeline_pgrp == 0) /* This is the first child. */ + pipeline_pgrp = mypid; + + /* Check for running command in backquotes. */ + if (pipeline_pgrp == shell_pgrp) + ignore_tty_job_signals (); + else + default_tty_job_signals (); + + /* Set the process group before trying to mess with the terminal's + process group. This is mandated by POSIX. */ + /* This is in accordance with the Posix 1003.1 standard, + section B.7.2.4, which says that trying to set the terminal + process group with tcsetpgrp() to an unused pgrp value (like + this would have for the first child) is an error. Section + B.4.3.3, p. 237 also covers this, in the context of job control + shells. */ + if (setpgid (mypid, pipeline_pgrp) < 0) + sys_error ("child setpgid (%ld to %ld)", (long)mypid, (long)pipeline_pgrp); + + /* By convention (and assumption above), if + pipeline_pgrp == shell_pgrp, we are making a child for + command substitution. + In this case, we don't want to give the terminal to the + shell's process group (we could be in the middle of a + pipeline, for example). */ + if (async_p == 0 && pipeline_pgrp != shell_pgrp) + give_terminal_to (pipeline_pgrp, 0); + +#if defined (PGRP_PIPE) + if (pipeline_pgrp == mypid) + pipe_read (pgrp_pipe); +#endif + } + else /* Without job control... */ + { + if (pipeline_pgrp == 0) + pipeline_pgrp = shell_pgrp; + + /* If these signals are set to SIG_DFL, we encounter the curious + situation of an interactive ^Z to a running process *working* + and stopping the process, but being unable to do anything with + that process to change its state. On the other hand, if they + are set to SIG_IGN, jobs started from scripts do not stop when + the shell running the script gets a SIGTSTP and stops. */ + + default_tty_job_signals (); + } + +#if defined (PGRP_PIPE) + /* Release the process group pipe, since our call to setpgid () + is done. The last call to pipe_close is done in stop_pipeline. */ + pipe_close (pgrp_pipe); +#endif /* PGRP_PIPE */ + + if (async_p) + last_asynchronous_pid = getpid (); + } + else + { + /* In the parent. Remember the pid of the child just created + as the proper pgrp if this is the first child. */ + + if (job_control) + { + if (pipeline_pgrp == 0) + { + pipeline_pgrp = pid; + /* Don't twiddle terminal pgrps in the parent! This is the bug, + not the good thing of twiddling them in the child! */ + /* give_terminal_to (pipeline_pgrp, 0); */ + } + /* This is done on the recommendation of the Rationale section of + the POSIX 1003.1 standard, where it discusses job control and + shells. It is done to avoid possible race conditions. (Ref. + 1003.1 Rationale, section B.4.3.3, page 236). */ + setpgid (pid, pipeline_pgrp); + } + else + { + if (pipeline_pgrp == 0) + pipeline_pgrp = shell_pgrp; + } + + /* Place all processes into the jobs array regardless of the + state of job_control. */ + add_process (command, pid); + + if (async_p) + last_asynchronous_pid = pid; + + last_made_pid = pid; + + /* Unblock SIGINT and SIGCHLD. */ + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + } + + return (pid); +} + +/* These two functions are called only in child processes. */ +void +ignore_tty_job_signals () +{ + set_signal_handler (SIGTSTP, SIG_IGN); + set_signal_handler (SIGTTIN, SIG_IGN); + set_signal_handler (SIGTTOU, SIG_IGN); +} + +void +default_tty_job_signals () +{ + set_signal_handler (SIGTSTP, SIG_DFL); + set_signal_handler (SIGTTIN, SIG_DFL); + set_signal_handler (SIGTTOU, SIG_DFL); +} + +/* When we end a job abnormally, or if we stop a job, we set the tty to the + state kept in here. When a job ends normally, we set the state in here + to the state of the tty. */ + +static TTYSTRUCT shell_tty_info; + +#if defined (NEW_TTY_DRIVER) +static struct tchars shell_tchars; +static struct ltchars shell_ltchars; +#endif /* NEW_TTY_DRIVER */ + +#if defined (NEW_TTY_DRIVER) && defined (DRAIN_OUTPUT) +/* Since the BSD tty driver does not allow us to change the tty modes + while simultaneously waiting for output to drain and preserving + typeahead, we have to drain the output ourselves before calling + ioctl. We cheat by finding the length of the output queue, and + using select to wait for an appropriate length of time. This is + a hack, and should be labeled as such (it's a hastily-adapted + mutation of a `usleep' implementation). It's only reason for + existing is the flaw in the BSD tty driver. */ + +static int ttspeeds[] = +{ + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, + 1800, 2400, 4800, 9600, 19200, 38400 +}; + +static void +draino (fd, ospeed) + int fd, ospeed; +{ + register int delay = ttspeeds[ospeed]; + int n; + + if (!delay) + return; + + while ((ioctl (fd, TIOCOUTQ, &n) == 0) && n) + { + if (n > (delay / 100)) + { + struct timeval tv; + + n *= 10; /* 2 bits more for conservativeness. */ + tv.tv_sec = n / delay; + tv.tv_usec = ((n % delay) * 1000000) / delay; + select (fd, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv); + } + else + break; + } +} +#endif /* NEW_TTY_DRIVER && DRAIN_OUTPUT */ + +/* Return the fd from which we are actually getting input. */ +#define input_tty() (shell_tty != -1) ? shell_tty : fileno (stderr) + +/* Fill the contents of shell_tty_info with the current tty info. */ +int +get_tty_state () +{ + int tty; + + tty = input_tty (); + if (tty != -1) + { +#if defined (NEW_TTY_DRIVER) + ioctl (tty, TIOCGETP, &shell_tty_info); + ioctl (tty, TIOCGETC, &shell_tchars); + ioctl (tty, TIOCGLTC, &shell_ltchars); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) + ioctl (tty, TCGETA, &shell_tty_info); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) + if (tcgetattr (tty, &shell_tty_info) < 0) + { +#if 0 + /* Only print an error message if we're really interactive at + this time. */ + if (interactive) + sys_error ("[%ld: %d] tcgetattr", (long)getpid (), shell_level); +#endif + return -1; + } +#endif /* TERMIOS_TTY_DRIVER */ + if (check_window_size) + get_new_window_size (0); + } + return 0; +} + +/* Make the current tty use the state in shell_tty_info. */ +int +set_tty_state () +{ + int tty; + + tty = input_tty (); + if (tty != -1) + { +#if defined (NEW_TTY_DRIVER) +# if defined (DRAIN_OUTPUT) + draino (tty, shell_tty_info.sg_ospeed); +# endif /* DRAIN_OUTPUT */ + ioctl (tty, TIOCSETN, &shell_tty_info); + ioctl (tty, TIOCSETC, &shell_tchars); + ioctl (tty, TIOCSLTC, &shell_ltchars); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) + ioctl (tty, TCSETAW, &shell_tty_info); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) + if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0) + { + /* Only print an error message if we're really interactive at + this time. */ + if (interactive) + sys_error ("[%ld: %d] tcsetattr", (long)getpid (), shell_level); + return -1; + } +#endif /* TERMIOS_TTY_DRIVER */ + } + return 0; +} + +/* Given an index into the jobs array JOB, return the pid of the last + process in that job's pipeline. This is the one whose exit status + counts. Must be called with SIGCHLD blocked or queued. */ +static pid_t +find_last_pid (job, block) + int job; + int block; +{ + register PROCESS *p; + sigset_t set, oset; + + if (block) + BLOCK_CHILD (set, oset); + + p = jobs[job]->pipe; + while (p->next != jobs[job]->pipe) + p = p->next; + + if (block) + UNBLOCK_CHILD (oset); + + return (p->pid); +} + +/* Wait for a particular child of the shell to finish executing. + This low-level function prints an error message if PID is not + a child of this shell. It returns -1 if it fails, or whatever + wait_for returns otherwise. If the child is not found in the + jobs table, it returns 127. */ +int +wait_for_single_pid (pid) + pid_t pid; +{ + register PROCESS *child; + sigset_t set, oset; + int r, job; + + BLOCK_CHILD (set, oset); + child = find_pipeline (pid, 0, (int *)NULL); + UNBLOCK_CHILD (oset); + + if (child == 0) + { + internal_error (_("wait: pid %ld is not a child of this shell"), (long)pid); + return (127); + } + + r = wait_for (pid); + + /* POSIX.2: if we just waited for a job, we can remove it from the jobs + table. */ + BLOCK_CHILD (set, oset); + job = find_job (pid, 0); + if (job != NO_JOB && jobs[job] && DEADJOB (job)) + jobs[job]->flags |= J_NOTIFIED; + UNBLOCK_CHILD (oset); + + return r; +} + +/* Wait for all of the backgrounds of this shell to finish. */ +void +wait_for_background_pids () +{ + register int i, r, waited_for; + sigset_t set, oset; + pid_t pid; + + for (waited_for = 0;;) + { + BLOCK_CHILD (set, oset); + + /* find first running job; if none running in foreground, break */ + for (i = 0; i < job_slots; i++) + if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0) + break; + + if (i == job_slots) + { + UNBLOCK_CHILD (oset); + break; + } + + /* now wait for the last pid in that job. */ + pid = find_last_pid (i, 0); + UNBLOCK_CHILD (oset); + QUIT; + errno = 0; /* XXX */ + r = wait_for_single_pid (pid); + if (r == -1) + { + /* If we're mistaken about job state, compensate. */ + if (errno == ECHILD) + mark_all_jobs_as_dead (); + } + else + waited_for++; + } + + /* POSIX.2 says the shell can discard the statuses of all completed jobs if + `wait' is called with no arguments. */ + mark_dead_jobs_as_notified (1); + cleanup_dead_jobs (); +} + +/* Make OLD_SIGINT_HANDLER the SIGINT signal handler. */ +#define INVALID_SIGNAL_HANDLER (SigHandler *)wait_for_background_pids +static SigHandler *old_sigint_handler = INVALID_SIGNAL_HANDLER; + +static void +restore_sigint_handler () +{ + if (old_sigint_handler != INVALID_SIGNAL_HANDLER) + { + set_signal_handler (SIGINT, old_sigint_handler); + old_sigint_handler = INVALID_SIGNAL_HANDLER; + } +} + +static int wait_sigint_received; + +/* Handle SIGINT while we are waiting for children in a script to exit. + The `wait' builtin should be interruptible, but all others should be + effectively ignored (i.e. not cause the shell to exit). */ +static sighandler +wait_sigint_handler (sig) + int sig; +{ + SigHandler *sigint_handler; + + if (interrupt_immediately || + (this_shell_builtin && this_shell_builtin == wait_builtin)) + { + last_command_exit_value = EXECUTION_FAILURE; + restore_sigint_handler (); + /* If we got a SIGINT while in `wait', and SIGINT is trapped, do + what POSIX.2 says (see builtins/wait.def for more info). */ + if (this_shell_builtin && this_shell_builtin == wait_builtin && + signal_is_trapped (SIGINT) && + ((sigint_handler = trap_to_sighandler (SIGINT)) == trap_handler)) + { + interrupt_immediately = 0; + trap_handler (SIGINT); /* set pending_traps[SIGINT] */ + wait_signal_received = SIGINT; + longjmp (wait_intr_buf, 1); + } + + ADDINTERRUPT; + QUIT; + } + + /* XXX - should this be interrupt_state? If it is, the shell will act + as if it got the SIGINT interrupt. */ + wait_sigint_received = 1; + + /* Otherwise effectively ignore the SIGINT and allow the running job to + be killed. */ + SIGRETURN (0); +} + +static int +process_exit_signal (status) + WAIT status; +{ + return (WIFSIGNALED (status) ? WTERMSIG (status) : 0); +} + +static int +process_exit_status (status) + WAIT status; +{ + if (WIFSIGNALED (status)) + return (128 + WTERMSIG (status)); + else if (WIFSTOPPED (status) == 0) + return (WEXITSTATUS (status)); + else + return (EXECUTION_SUCCESS); +} + +/* Return the exit status of the last process in the pipeline for job JOB. + This is the exit status of the entire job. */ +static WAIT +raw_job_exit_status (job) + int job; +{ + register PROCESS *p; + int fail; + + if (pipefail_opt) + { + fail = 0; + for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next) + if (p->status != EXECUTION_SUCCESS) fail = p->status; + return fail; + } + + for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next) + ; + return (p->status); +} + +/* Return the exit status of job JOB. This is the exit status of the last + (rightmost) process in the job's pipeline, modified if the job was killed + by a signal or stopped. */ +static int +job_exit_status (job) + int job; +{ + return (process_exit_status (raw_job_exit_status (job))); +} + +static int +job_exit_signal (job) + int job; +{ + return (process_exit_signal (raw_job_exit_status (job))); +} + +#define FIND_CHILD(pid, child) \ + do \ + { \ + child = find_pipeline (pid, 0, (int *)NULL); \ + if (child == 0) \ + { \ + give_terminal_to (shell_pgrp, 0); \ + UNBLOCK_CHILD (oset); \ + internal_error (_("wait_for: No record of process %ld"), (long)pid); \ + restore_sigint_handler (); \ + return (termination_state = 127); \ + } \ + } \ + while (0) + +/* Wait for pid (one of our children) to terminate, then + return the termination state. Returns 127 if PID is not found in + the jobs table. Returns -1 if waitchld() returns -1, indicating + that there are no unwaited-for child processes. */ +int +wait_for (pid) + pid_t pid; +{ + int job, termination_state, r; + WAIT s; + register PROCESS *child; + sigset_t set, oset; + register PROCESS *p; + + /* 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. */ + BLOCK_CHILD (set, oset); + + /* Ignore interrupts while waiting for a job run without job control + to finish. We don't want the shell to exit if an interrupt is + received, only if one of the jobs run is killed via SIGINT. If + job control is not set, the job will be run in the same pgrp as + the shell, and the shell will see any signals the job gets. */ + + /* This is possibly a race condition -- should it go in stop_pipeline? */ + wait_sigint_received = 0; + if (job_control == 0) + old_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler); + + termination_state = last_command_exit_value; + + if (interactive && job_control == 0) + QUIT; + + /* If we say wait_for (), then we have a record of this child somewhere. + If it and none of its peers are running, don't call waitchld(). */ + + job = NO_JOB; + do + { + 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. + We check for JDEAD in case the job state has been set by waitchld + after receipt of a SIGCHLD. */ + if (job == NO_JOB) + job = find_job (pid, 0); + + /* waitchld() takes care of setting the state of the job. If the job + has already exited before this is called, sigchld_handler will have + called waitchld and the state will be set to JDEAD. */ + + if (child->running || (job != NO_JOB && RUNNING (job))) + { +#if defined (WAITPID_BROKEN) /* SCOv4 */ + sigset_t suspend_set; + sigemptyset (&suspend_set); + sigsuspend (&suspend_set); +#else /* !WAITPID_BROKEN */ +# if defined (MUST_UNBLOCK_CHLD) + struct sigaction act, oact; + sigset_t nullset, chldset; + + sigemptyset (&nullset); + sigemptyset (&chldset); + sigprocmask (SIG_SETMASK, &nullset, &chldset); + act.sa_handler = SIG_DFL; + sigemptyset (&act.sa_mask); + sigemptyset (&oact.sa_mask); + act.sa_flags = 0; + sigaction (SIGCHLD, &act, &oact); +# endif + queue_sigchld = 1; + r = waitchld (pid, 1); +# if defined (MUST_UNBLOCK_CHLD) + sigaction (SIGCHLD, &oact, (struct sigaction *)NULL); + sigprocmask (SIG_SETMASK, &chldset, (sigset_t *)NULL); +# endif + queue_sigchld = 0; + if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin) + { + termination_state = -1; + goto wait_for_return; + } + + /* If child is marked as running, but waitpid() returns -1/ECHILD, + there is something wrong. Somewhere, wait should have returned + that child's pid. Mark the child as not running and the job, + if it exists, as JDEAD. */ + if (r == -1 && errno == ECHILD) + { + child->running = PS_DONE; + child->status = 0; /* XXX -- can't find true status */ + if (job != NO_JOB) + jobs[job]->state = JDEAD; + } +#endif /* WAITPID_BROKEN */ + } + + /* If the shell is interactive, and job control is disabled, see + if the foreground process has died due to SIGINT and jump out + of the wait loop if it has. waitchld has already restored the + old SIGINT signal handler. */ + if (interactive && job_control == 0) + QUIT; + } + while (child->running || (job != NO_JOB && RUNNING (job))); + + /* The exit state of the command is either the termination state of the + child, or the termination state of the job. If a job, the status + of the last child in the pipeline is the significant one. If the command + or job was terminated by a signal, note that value also. */ + termination_state = (job != NO_JOB) ? job_exit_status (job) + : process_exit_status (child->status); + last_command_exit_signal = (job != NO_JOB) ? job_exit_signal (job) + : process_exit_signal (child->status); + + if (job == NO_JOB || IS_JOBCONTROL (job)) + { + /* XXX - under what circumstances is a job not present in the jobs + table (job == NO_JOB)? + 1. command substitution + + In the case of command substitution, at least, it's probably not + the right thing to give the terminal to the shell's process group, + even though there is code in subst.c:command_substitute to work + around it. + + Things that don't: + $PROMPT_COMMAND execution + process substitution + */ +#if 0 +if (job == NO_JOB) + itrace("wait_for: job == NO_JOB, giving the terminal to shell_pgrp (%ld)", (long)shell_pgrp); +#endif + + give_terminal_to (shell_pgrp, 0); + } + + /* If the command did not exit cleanly, or the job is just + being stopped, then reset the tty state back to what it + was before this command. Reset the tty state and notify + the user of the job termination only if the shell is + interactive. Clean up any dead jobs in either case. */ + if (job != NO_JOB) + { + if (interactive_shell && subshell_environment == 0) + { + /* This used to use `child->status'. That's wrong, however, for + pipelines. `child' is the first process in the pipeline. It's + likely that the process we want to check for abnormal termination + or stopping is the last process in the pipeline, especially if + it's long-lived and the first process is short-lived. Since we + know we have a job here, we can check all the processes in this + job's pipeline and see if one of them stopped or terminated due + to a signal. We might want to change this later to just check + the last process in the pipeline. If no process exits due to a + signal, S is left as the status of the last job in the pipeline. */ + p = jobs[job]->pipe; + do + { + s = p->status; + if (WIFSIGNALED(s) || WIFSTOPPED(s)) + break; + p = p->next; + } + while (p != jobs[job]->pipe); + + if (WIFSIGNALED (s) || WIFSTOPPED (s)) + { + set_tty_state (); + + /* If the current job was stopped or killed by a signal, and + the user has requested it, get a possibly new window size */ + if (check_window_size && (job == current_job || IS_FOREGROUND (job))) + get_new_window_size (0); + } + else + get_tty_state (); + + /* If job control is enabled, the job was started with job + control, the job was the foreground job, and it was killed + by SIGINT, then print a newline to compensate for the kernel + printing the ^C without a trailing newline. */ + if (job_control && IS_JOBCONTROL (job) && IS_FOREGROUND (job) && + WIFSIGNALED (s) && WTERMSIG (s) == SIGINT) + { + /* If SIGINT is not trapped and the shell is in a for, while, + or until loop, act as if the shell received SIGINT as + well, so the loop can be broken. This doesn't call the + SIGINT signal handler; maybe it should. */ + if (signal_is_trapped (SIGINT) == 0 && loop_level) + ADDINTERRUPT; + else + { + putchar ('\n'); + fflush (stdout); + } + } + } + + /* Moved here from set_job_status_and_cleanup, which is in the SIGCHLD + signal handler path */ + if (DEADJOB (job) && IS_FOREGROUND (job) /*&& subshell_environment == 0*/) + setjstatus (job); + + /* If this job is dead, notify the user of the status. If the shell + is interactive, this will display a message on the terminal. If + the shell is not interactive, make sure we turn on the notify bit + so we don't get an unwanted message about the job's termination, + and so delete_job really clears the slot in the jobs table. */ + notify_and_cleanup (); + } + +wait_for_return: + + UNBLOCK_CHILD (oset); + + /* Restore the original SIGINT signal handler before we return. */ + restore_sigint_handler (); + + return (termination_state); +} + +/* Wait for the last process in the pipeline for JOB. Returns whatever + wait_for returns: the last process's termination state or -1 if there + are no unwaited-for child processes or an error occurs. */ +int +wait_for_job (job) + int job; +{ + pid_t pid; + int r; + sigset_t set, oset; + + BLOCK_CHILD(set, oset); + if (JOBSTATE (job) == JSTOPPED) + internal_warning (_("wait_for_job: job %d is stopped"), job+1); + + pid = find_last_pid (job, 0); + UNBLOCK_CHILD(oset); + r = wait_for (pid); + + /* POSIX.2: we can remove the job from the jobs table if we just waited + for it. */ + BLOCK_CHILD (set, oset); + if (job != NO_JOB && jobs[job] && DEADJOB (job)) + jobs[job]->flags |= J_NOTIFIED; + UNBLOCK_CHILD (oset); + + return r; +} + +/* 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 + as notified. */ +void +notify_and_cleanup () +{ + if (jobs_list_frozen) + return; + + if (interactive || interactive_shell == 0 || sourcelevel) + notify_of_job_status (); + + cleanup_dead_jobs (); +} + +/* Make dead jobs disappear from the jobs array without notification. + This is used when the shell is not interactive. */ +void +reap_dead_jobs () +{ + mark_dead_jobs_as_notified (0); + cleanup_dead_jobs (); +} + +/* Return the next closest (chronologically) job to JOB which is in + STATE. STATE can be JSTOPPED, JRUNNING. NO_JOB is returned if + there is no next recent job. */ +static int +most_recent_job_in_state (job, state) + int job; + JOB_STATE state; +{ + register int i, result; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + for (result = NO_JOB, i = job - 1; i >= 0; i--) + { + if (jobs[i] && (JOBSTATE (i) == state)) + { + result = i; + break; + } + } + + UNBLOCK_CHILD (oset); + + return (result); +} + +/* Return the newest *stopped* job older than JOB, or NO_JOB if not + found. */ +static int +job_last_stopped (job) + int job; +{ + return (most_recent_job_in_state (job, JSTOPPED)); +} + +/* Return the newest *running* job older than JOB, or NO_JOB if not + found. */ +static int +job_last_running (job) + int job; +{ + return (most_recent_job_in_state (job, JRUNNING)); +} + +/* Make JOB be the current job, and make previous be useful. Must be + called with SIGCHLD blocked. */ +static void +set_current_job (job) + int job; +{ + int candidate; + + if (current_job != job) + { + previous_job = current_job; + current_job = job; + } + + /* First choice for previous_job is the old current_job. */ + if (previous_job != current_job && + previous_job != NO_JOB && + jobs[previous_job] && + STOPPED (previous_job)) + return; + + /* Second choice: Newest stopped job that is older than + the current job. */ + candidate = NO_JOB; + if (STOPPED (current_job)) + { + candidate = job_last_stopped (current_job); + + if (candidate != NO_JOB) + { + previous_job = candidate; + return; + } + } + + /* If we get here, there is either only one stopped job, in which case it is + the current job and the previous job should be set to the newest running + job, or there are only running jobs and the previous job should be set to + the newest running job older than the current job. We decide on which + alternative to use based on whether or not JOBSTATE(current_job) is + JSTOPPED. */ + + candidate = RUNNING (current_job) ? job_last_running (current_job) + : job_last_running (job_slots); + + if (candidate != NO_JOB) + { + previous_job = candidate; + return; + } + + /* There is only a single job, and it is both `+' and `-'. */ + previous_job = current_job; +} + +/* Make current_job be something useful, if it isn't already. */ + +/* Here's the deal: The newest non-running job should be `+', and the + next-newest non-running job should be `-'. If there is only a single + stopped job, the previous_job is the newest non-running job. If there + are only running jobs, the newest running job is `+' and the + next-newest running job is `-'. Must be called with SIGCHLD blocked. */ + +static void +reset_current () +{ + int candidate; + + if (job_slots && current_job != NO_JOB && jobs[current_job] && STOPPED (current_job)) + candidate = current_job; + else + { + candidate = NO_JOB; + + /* First choice: the previous job. */ + if (previous_job != NO_JOB && jobs[previous_job] && STOPPED (previous_job)) + candidate = previous_job; + + /* Second choice: the most recently stopped job. */ + if (candidate == NO_JOB) + candidate = job_last_stopped (job_slots); + + /* Third choice: the newest running job. */ + if (candidate == NO_JOB) + candidate = job_last_running (job_slots); + } + + /* If we found a job to use, then use it. Otherwise, there + are no jobs period. */ + if (candidate != NO_JOB) + set_current_job (candidate); + else + current_job = previous_job = NO_JOB; +} + +/* Set up the job structures so we know the job and its processes are + all running. */ +static void +set_job_running (job) + int job; +{ + register PROCESS *p; + + /* Each member of the pipeline is now running. */ + p = jobs[job]->pipe; + + do + { + if (WIFSTOPPED (p->status)) + p->running = PS_RUNNING; /* XXX - could be PS_STOPPED */ + p = p->next; + } + while (p != jobs[job]->pipe); + + /* This means that the job is running. */ + JOBSTATE (job) = JRUNNING; +} + +/* Start a job. FOREGROUND if non-zero says to do that. Otherwise, + start the job in the background. JOB is a zero-based index into + JOBS. Returns -1 if it is unable to start a job, and the return + status of the job otherwise. */ +int +start_job (job, foreground) + int job, foreground; +{ + register PROCESS *p; + int already_running; + sigset_t set, oset; + char *wd; + static TTYSTRUCT save_stty; + + BLOCK_CHILD (set, oset); + + if (DEADJOB (job)) + { + internal_error (_("%s: job has terminated"), this_command_name); + UNBLOCK_CHILD (oset); + return (-1); + } + + already_running = RUNNING (job); + + if (foreground == 0 && already_running) + { + internal_error (_("%s: job %d already in background"), this_command_name, job + 1); + UNBLOCK_CHILD (oset); + return (-1); + } + + wd = current_working_directory (); + + /* You don't know about the state of this job. Do you? */ + jobs[job]->flags &= ~J_NOTIFIED; + + if (foreground) + { + set_current_job (job); + jobs[job]->flags |= J_FOREGROUND; + } + + /* Tell the outside world what we're doing. */ + p = jobs[job]->pipe; + + if (foreground == 0) + fprintf (stderr, "[%d]%c ", job + 1, + (job == current_job) ? '+': ((job == previous_job) ? '-' : ' ')); + + do + { + fprintf (stderr, "%s%s", + p->command ? p->command : "", + p->next != jobs[job]->pipe? " | " : ""); + p = p->next; + } + while (p != jobs[job]->pipe); + + if (foreground == 0) + fprintf (stderr, " &"); + + if (strcmp (wd, jobs[job]->wd) != 0) + fprintf (stderr, " (wd: %s)", polite_directory_format (jobs[job]->wd)); + + fprintf (stderr, "\n"); + + /* Run the job. */ + if (already_running == 0) + set_job_running (job); + + /* Save the tty settings before we start the job in the foreground. */ + if (foreground) + { + get_tty_state (); + save_stty = shell_tty_info; + /* Give the terminal to this job. */ + if (IS_JOBCONTROL (job)) + give_terminal_to (jobs[job]->pgrp, 0); + } + else + jobs[job]->flags &= ~J_FOREGROUND; + + /* If the job is already running, then don't bother jump-starting it. */ + if (already_running == 0) + { + jobs[job]->flags |= J_NOTIFIED; + killpg (jobs[job]->pgrp, SIGCONT); + } + + if (foreground) + { + pid_t pid; + int s; + + pid = find_last_pid (job, 0); + UNBLOCK_CHILD (oset); + s = wait_for (pid); + shell_tty_info = save_stty; + set_tty_state (); + return (s); + } + else + { + reset_current (); + UNBLOCK_CHILD (oset); + return (0); + } +} + +/* Give PID SIGNAL. This determines what job the pid belongs to (if any). + If PID does belong to a job, and the job is stopped, then CONTinue the + job after giving it SIGNAL. Returns -1 on failure. If GROUP is non-null, + then kill the process group associated with PID. */ +int +kill_pid (pid, sig, group) + pid_t pid; + int sig, group; +{ + register PROCESS *p; + int job, result; + sigset_t set, oset; + + result = EXECUTION_SUCCESS; + if (group) + { + BLOCK_CHILD (set, oset); + p = find_pipeline (pid, 0, &job); + + if (job != NO_JOB) + { + jobs[job]->flags &= ~J_NOTIFIED; + + /* Kill process in backquotes or one started without job control? */ + if (jobs[job]->pgrp == shell_pgrp) + { + p = jobs[job]->pipe; + + do + { + kill (p->pid, sig); + if (p->running == PS_DONE && (sig == SIGTERM || sig == SIGHUP)) + kill (p->pid, SIGCONT); + p = p->next; + } + while (p != jobs[job]->pipe); + } + else + { + result = killpg (jobs[job]->pgrp, sig); + if (p && STOPPED (job) && (sig == SIGTERM || sig == SIGHUP)) + killpg (jobs[job]->pgrp, SIGCONT); + /* If we're continuing a stopped job via kill rather than bg or + fg, emulate the `bg' behavior. */ + if (p && STOPPED (job) && (sig == SIGCONT)) + { + set_job_running (job); + jobs[job]->flags &= ~J_FOREGROUND; + jobs[job]->flags |= J_NOTIFIED; + } + } + } + else + result = killpg (pid, sig); + + UNBLOCK_CHILD (oset); + } + else + result = kill (pid, sig); + + return (result); +} + +/* sigchld_handler () flushes at least one of the children that we are + waiting for. It gets run when we have gotten a SIGCHLD signal. */ +static sighandler +sigchld_handler (sig) + int sig; +{ + int n, oerrno; + + oerrno = errno; + REINSTALL_SIGCHLD_HANDLER; + sigchld++; + n = 0; + if (queue_sigchld == 0) + n = waitchld (-1, 0); + errno = oerrno; + SIGRETURN (n); +} + +/* waitchld() reaps dead or stopped children. It's called by wait_for and + sigchld_handler, and runs until there aren't any children terminating any + more. + If BLOCK is 1, this is to be a blocking wait for a single child, although + an arriving SIGCHLD could cause the wait to be non-blocking. It returns + the number of children reaped, or -1 if there are no unwaited-for child + processes. */ +static int +waitchld (wpid, block) + pid_t wpid; + int block; +{ + WAIT status; + PROCESS *child; + pid_t pid; + int call_set_current, last_stopped_job, job, children_exited, waitpid_flags; + + call_set_current = children_exited = 0; + last_stopped_job = NO_JOB; + + do + { + /* We don't want to be notified about jobs stopping if job control + is not active. XXX - was interactive_shell instead of job_control */ + waitpid_flags = (job_control && subshell_environment == 0) + ? (WUNTRACED|WCONTINUED) + : 0; + if (sigchld || block == 0) + waitpid_flags |= WNOHANG; + pid = WAITPID (-1, &status, waitpid_flags); + + /* The check for WNOHANG is to make sure we decrement sigchld only + if it was non-zero before we called waitpid. */ + if (sigchld > 0 && (waitpid_flags & WNOHANG)) + sigchld--; + + /* If waitpid returns -1 with errno == ECHILD, there are no more + unwaited-for child processes of this shell. */ + if (pid < 0 && errno == ECHILD) + { + if (children_exited == 0) + return -1; + else + break; + } + + /* If waitpid returns 0, there are running children. If it returns -1, + the only other error POSIX says it can return is EINTR. */ + if (pid <= 0) + continue; /* jumps right to the test */ + + /* children_exited is used to run traps on SIGCHLD. We don't want to + run the trap if a process is just being continued. */ + if (WIFCONTINUED(status) == 0) + children_exited++; + + /* Locate our PROCESS for this pid. */ + child = find_pipeline (pid, 1, &job); /* want running procs only */ + + /* It is not an error to have a child terminate that we did + not have a record of. This child could have been part of + a pipeline in backquote substitution. Even so, I'm not + sure child is ever non-zero. */ + if (child == 0) + continue; + + while (child->pid != pid) + child = child->next; + + /* Remember status, and whether or not the process is running. */ + child->status = status; + child->running = WIFCONTINUED(status) ? PS_RUNNING : PS_DONE; + + if (job == NO_JOB) + continue; + + call_set_current += set_job_status_and_cleanup (job); + + if (STOPPED (job)) + last_stopped_job = job; + else if (DEADJOB (job) && last_stopped_job == job) + last_stopped_job = NO_JOB; + } + while ((sigchld || block == 0) && pid > (pid_t)0); + + /* If a job was running and became stopped, then set the current + job. Otherwise, don't change a thing. */ + if (call_set_current) + { + if (last_stopped_job != NO_JOB) + set_current_job (last_stopped_job); + else + reset_current (); + } + + /* Call a SIGCHLD trap handler for each child that exits, if one is set. */ + if (job_control && signal_is_trapped (SIGCHLD) && children_exited && + trap_list[SIGCHLD] != (char *)IGNORE_SIG) + run_sigchld_trap (children_exited); + + /* We have successfully recorded the useful information about this process + that has just changed state. If we notify asynchronously, and the job + that this process belongs to is no longer running, then notify the user + of that fact now. */ + if (asynchronous_notification && interactive) + notify_of_job_status (); + + return (children_exited); +} + +/* Set the status of JOB and perform any necessary cleanup if the job is + marked as JDEAD. + + Currently, the cleanup activity is restricted to handling any SIGINT + received while waiting for a foreground job to finish. */ +static int +set_job_status_and_cleanup (job) + int job; +{ + PROCESS *child; + int tstatus, job_state, any_stopped, any_tstped, call_set_current; + SigHandler *temp_handler; + + child = jobs[job]->pipe; + jobs[job]->flags &= ~J_NOTIFIED; + + call_set_current = 0; + + /* + * COMPUTE JOB STATUS + */ + + /* If all children are not running, but any of them is stopped, then + the job is stopped, not dead. */ + job_state = any_stopped = any_tstped = 0; + do + { + job_state |= child->running; + if (child->running == PS_DONE && (WIFSTOPPED (child->status))) + { + any_stopped = 1; + any_tstped |= interactive && job_control && + (WSTOPSIG (child->status) == SIGTSTP); + } + child = child->next; + } + while (child != jobs[job]->pipe); + + /* If job_state != 0, the job is still running, so don't bother with + setting the process exit status and job state unless we're + transitioning from stopped to running. */ + if (job_state != 0 && JOBSTATE(job) != JSTOPPED) + return 0; + + /* + * SET JOB STATUS + */ + + /* The job is either stopped or dead. Set the state of the job accordingly. */ + if (any_stopped) + { + jobs[job]->state = JSTOPPED; + jobs[job]->flags &= ~J_FOREGROUND; + call_set_current++; + /* Suspending a job with SIGTSTP breaks all active loops. */ + if (any_tstped && loop_level) + breaking = loop_level; + } + else if (job_state != 0) /* was stopped, now running */ + { + jobs[job]->state = JRUNNING; + call_set_current++; + } + else + { + jobs[job]->state = JDEAD; + +#if 0 + if (IS_FOREGROUND (job)) + setjstatus (job); +#endif + + /* If this job has a cleanup function associated with it, call it + with `cleanarg' as the single argument, then set the function + pointer to NULL so it is not inadvertently called twice. The + cleanup function is responsible for deallocating cleanarg. */ + if (jobs[job]->j_cleanup) + { + (*jobs[job]->j_cleanup) (jobs[job]->cleanarg); + jobs[job]->j_cleanup = (sh_vptrfunc_t *)NULL; + } + } + + /* + * CLEANUP + * + * Currently, we just do special things if we got a SIGINT while waiting + * for a foreground job to complete + */ + + if (jobs[job]->state == JDEAD) + { + /* If we're running a shell script and we get a SIGINT with a + SIGINT trap handler, but the foreground job handles it and + does not exit due to SIGINT, run the trap handler but do not + otherwise act as if we got the interrupt. */ + if (wait_sigint_received && interactive_shell == 0 && + WIFSIGNALED (child->status) == 0 && IS_FOREGROUND (job) && + signal_is_trapped (SIGINT)) + { + int old_frozen; + wait_sigint_received = 0; + last_command_exit_value = process_exit_status (child->status); + + old_frozen = jobs_list_frozen; + jobs_list_frozen = 1; + tstatus = maybe_call_trap_handler (SIGINT); + jobs_list_frozen = old_frozen; + } + + /* If the foreground job is killed by SIGINT when job control is not + active, we need to perform some special handling. + + The check of wait_sigint_received is a way to determine if the + SIGINT came from the keyboard (in which case the shell has already + seen it, and wait_sigint_received is non-zero, because keyboard + signals are sent to process groups) or via kill(2) to the foreground + process by another process (or itself). If the shell did receive the + SIGINT, it needs to perform normal SIGINT processing. */ + else if (wait_sigint_received && (WTERMSIG (child->status) == SIGINT) && + IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0) + { + int old_frozen; + + wait_sigint_received = 0; + + /* If SIGINT is trapped, set the exit status so that the trap + handler can see it. */ + if (signal_is_trapped (SIGINT)) + last_command_exit_value = process_exit_status (child->status); + + /* If the signal is trapped, let the trap handler get it no matter + what and simply return if the trap handler returns. + maybe_call_trap_handler() may cause dead jobs to be removed from + the job table because of a call to execute_command. We work + around this by setting JOBS_LIST_FROZEN. */ + old_frozen = jobs_list_frozen; + jobs_list_frozen = 1; + tstatus = maybe_call_trap_handler (SIGINT); + jobs_list_frozen = old_frozen; + if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER) + { + /* wait_sigint_handler () has already seen SIGINT and + allowed the wait builtin to jump out. We need to + call the original SIGINT handler, if necessary. If + the original handler is SIG_DFL, we need to resend + the signal to ourselves. */ + + temp_handler = old_sigint_handler; + + /* Bogus. If we've reset the signal handler as the result + of a trap caught on SIGINT, then old_sigint_handler + will point to trap_handler, which now knows nothing about + SIGINT (if we reset the sighandler to the default). + In this case, we have to fix things up. What a crock. */ + if (temp_handler == trap_handler && signal_is_trapped (SIGINT) == 0) + temp_handler = trap_to_sighandler (SIGINT); + restore_sigint_handler (); + if (temp_handler == SIG_DFL) + termination_unwind_protect (SIGINT); + else if (temp_handler != SIG_IGN) + (*temp_handler) (SIGINT); + } + } + } + + return call_set_current; +} + +/* Build the array of values for the $PIPESTATUS variable from the set of + exit statuses of all processes in the job J. */ +static void +setjstatus (j) + int j; +{ +#if defined (ARRAY_VARS) + register int i; + register PROCESS *p; + + for (i = 1, p = jobs[j]->pipe; p->next != jobs[j]->pipe; p = p->next, i++) + ; + i++; + if (statsize < i) + { + pstatuses = (int *)xrealloc (pstatuses, i * sizeof (int)); + statsize = i; + } + i = 0; + p = jobs[j]->pipe; + do + { + pstatuses[i++] = process_exit_status (p->status); + p = p->next; + } + while (p != jobs[j]->pipe); + + pstatuses[i] = -1; /* sentinel */ + set_pipestatus_array (pstatuses, i); +#endif +} + +static void +run_sigchld_trap (nchild) + int nchild; +{ + char *trap_command; + int i; + + /* Turn off the trap list during the call to parse_and_execute () + to avoid potentially infinite recursive calls. Preserve the + values of last_command_exit_value, last_made_pid, and the_pipeline + around the execution of the trap commands. */ + trap_command = savestring (trap_list[SIGCHLD]); + + begin_unwind_frame ("SIGCHLD trap"); + unwind_protect_int (last_command_exit_value); + unwind_protect_int (last_command_exit_signal); + unwind_protect_var (last_made_pid); + unwind_protect_int (interrupt_immediately); + unwind_protect_int (jobs_list_frozen); + unwind_protect_pointer (the_pipeline); + unwind_protect_pointer (subst_assign_varlist); + + /* We have to add the commands this way because they will be run + in reverse order of adding. We don't want maybe_set_sigchld_trap () + to reference freed memory. */ + add_unwind_protect (xfree, trap_command); + add_unwind_protect (maybe_set_sigchld_trap, trap_command); + + subst_assign_varlist = (WORD_LIST *)NULL; + the_pipeline = (PROCESS *)NULL; + + restore_default_signal (SIGCHLD); + jobs_list_frozen = 1; + for (i = 0; i < nchild; i++) + { + interrupt_immediately = 1; + parse_and_execute (savestring (trap_command), "trap", SEVAL_NOHIST|SEVAL_RESETLINE); + } + + run_unwind_frame ("SIGCHLD trap"); +} + +/* Function to call when you want to notify people of changes + in job status. This prints out all jobs which are pending + notification to stderr, and marks those printed as already + notified, thus making them candidates for cleanup. */ +static void +notify_of_job_status () +{ + register int job, termsig; + char *dir; + sigset_t set, oset; + WAIT s; + + if (jobs == 0 || job_slots == 0) + return; + + if (old_ttou != 0) + { + sigemptyset (&set); + sigaddset (&set, SIGCHLD); + sigaddset (&set, SIGTTOU); + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + } + else + queue_sigchld++; + + for (job = 0, dir = (char *)NULL; job < job_slots; job++) + { + if (jobs[job] && IS_NOTIFIED (job) == 0) + { + s = raw_job_exit_status (job); + termsig = WTERMSIG (s); + + /* POSIX.2 says we have to hang onto the statuses of at most the + last CHILD_MAX background processes if the shell is running a + script. If the shell is not interactive, don't print anything + unless the job was killed by a signal. */ + if (startup_state == 0 && WIFSIGNALED (s) == 0 && + ((DEADJOB (job) && IS_FOREGROUND (job) == 0) || STOPPED (job))) + continue; + +#if 0 + /* If job control is disabled, don't print the status messages. + Mark dead jobs as notified so that they get cleaned up. If + startup_state == 2, we were started to run `-c command', so + don't print anything. */ + if ((job_control == 0 && interactive_shell) || startup_state == 2) +#else + /* If job control is disabled, don't print the status messages. + Mark dead jobs as notified so that they get cleaned up. If + startup_state == 2 and subshell_environment has the + SUBSHELL_COMSUB bit turned on, we were started to run a command + substitution, so don't print anything. */ + if ((job_control == 0 && interactive_shell) || + (startup_state == 2 && (subshell_environment & SUBSHELL_COMSUB))) +#endif + { + /* POSIX.2 compatibility: if the shell is not interactive, + hang onto the job corresponding to the last asynchronous + pid until the user has been notified of its status or does + a `wait'. */ + if (DEADJOB (job) && (interactive_shell || (find_last_pid (job, 0) != last_asynchronous_pid))) + jobs[job]->flags |= J_NOTIFIED; + continue; + } + + /* Print info on jobs that are running in the background, + and on foreground jobs that were killed by anything + except SIGINT (and possibly SIGPIPE). */ + switch (JOBSTATE (job)) + { + case JDEAD: + if (interactive_shell == 0 && termsig && WIFSIGNALED (s) && + termsig != SIGINT && +#if defined (DONT_REPORT_SIGPIPE) + termsig != SIGPIPE && +#endif + signal_is_trapped (termsig) == 0) + { + /* Don't print `0' for a line number. */ + fprintf (stderr, "%s: line %d: ", get_name_for_error (), (line_number == 0) ? 1 : line_number); + pretty_print_job (job, JLIST_NONINTERACTIVE, stderr); + } + else if (IS_FOREGROUND (job)) + { +#if !defined (DONT_REPORT_SIGPIPE) + if (termsig && WIFSIGNALED (s) && termsig != SIGINT) +#else + if (termsig && WIFSIGNALED (s) && termsig != SIGINT && termsig != SIGPIPE) +#endif + { + fprintf (stderr, "%s", j_strsignal (termsig)); + + if (WIFCORED (s)) + fprintf (stderr, " (core dumped)"); + + fprintf (stderr, "\n"); + } + } + else + { + if (dir == 0) + dir = current_working_directory (); + pretty_print_job (job, JLIST_STANDARD, stderr); + if (dir && strcmp (dir, jobs[job]->wd) != 0) + fprintf (stderr, + "(wd now: %s)\n", polite_directory_format (dir)); + } + + jobs[job]->flags |= J_NOTIFIED; + break; + + case JSTOPPED: + fprintf (stderr, "\n"); + if (dir == 0) + dir = current_working_directory (); + pretty_print_job (job, JLIST_STANDARD, stderr); + if (dir && (strcmp (dir, jobs[job]->wd) != 0)) + fprintf (stderr, + "(wd now: %s)\n", polite_directory_format (dir)); + jobs[job]->flags |= J_NOTIFIED; + break; + + case JRUNNING: + case JMIXED: + break; + + default: + programming_error ("notify_of_job_status"); + } + } + } + if (old_ttou != 0) + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + else + queue_sigchld--; +} + +/* Initialize the job control mechanism, and set up the tty stuff. */ +int +initialize_job_control (force) + int force; +{ + shell_pgrp = getpgid (0); + + if (shell_pgrp == -1) + { + sys_error ("initialize_job_control: getpgrp failed"); + exit (1); + } + + /* We can only have job control if we are interactive. */ + if (interactive == 0) + { + job_control = 0; + original_pgrp = NO_PID; + shell_tty = fileno (stderr); + } + else + { + /* Get our controlling terminal. If job_control is set, or + interactive is set, then this is an interactive shell no + matter where fd 2 is directed. */ + shell_tty = dup (fileno (stderr)); /* fd 2 */ + + shell_tty = move_to_high_fd (shell_tty, 1, -1); + + /* Compensate for a bug in systems that compiled the BSD + rlogind with DEBUG defined, like NeXT and Alliant. */ + if (shell_pgrp == 0) + { + shell_pgrp = getpid (); + setpgid (0, shell_pgrp); + tcsetpgrp (shell_tty, shell_pgrp); + } + + while ((terminal_pgrp = tcgetpgrp (shell_tty)) != -1) + { + if (shell_pgrp != terminal_pgrp) + { + SigHandler *ottin; + + ottin = set_signal_handler(SIGTTIN, SIG_DFL); + kill (0, SIGTTIN); + set_signal_handler (SIGTTIN, ottin); + continue; + } + break; + } + + /* Make sure that we are using the new line discipline. */ + if (set_new_line_discipline (shell_tty) < 0) + { + sys_error ("initialize_job_control: line discipline"); + job_control = 0; + } + else + { + original_pgrp = shell_pgrp; + shell_pgrp = getpid (); + + if ((original_pgrp != shell_pgrp) && (setpgid (0, shell_pgrp) < 0)) + { + sys_error ("initialize_job_control: setpgid"); + shell_pgrp = original_pgrp; + } + + job_control = 1; + + /* If (and only if) we just set our process group to our pid, + thereby becoming a process group leader, and the terminal + is not in the same process group as our (new) process group, + then set the terminal's process group to our (new) process + group. If that fails, set our process group back to what it + was originally (so we can still read from the terminal) and + turn off job control. */ + if (shell_pgrp != original_pgrp && shell_pgrp != terminal_pgrp) + { + if (give_terminal_to (shell_pgrp, 0) < 0) + { + setpgid (0, original_pgrp); + shell_pgrp = original_pgrp; + job_control = 0; + } + } + } + if (job_control == 0) + internal_error (_("no job control in this shell")); + } + + if (shell_tty != fileno (stderr)) + SET_CLOSE_ON_EXEC (shell_tty); + + set_signal_handler (SIGCHLD, sigchld_handler); + + change_flag ('m', job_control ? '-' : '+'); + + if (interactive) + get_tty_state (); + + return job_control; +} + +#ifdef DEBUG +void +debug_print_pgrps () +{ + itrace("original_pgrp = %ld shell_pgrp = %ld terminal_pgrp = %ld", + (long)original_pgrp, (long)shell_pgrp, (long)terminal_pgrp); + itrace("tcgetpgrp(%d) -> %ld, getpgid(0) -> %ld", + shell_tty, (long)tcgetpgrp (shell_tty), (long)getpgid(0)); +} +#endif + +/* Set the line discipline to the best this system has to offer. + Return -1 if this is not possible. */ +static int +set_new_line_discipline (tty) + int tty; +{ +#if defined (NEW_TTY_DRIVER) + int ldisc; + + if (ioctl (tty, TIOCGETD, &ldisc) < 0) + return (-1); + + if (ldisc != NTTYDISC) + { + ldisc = NTTYDISC; + + if (ioctl (tty, TIOCSETD, &ldisc) < 0) + return (-1); + } + return (0); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) +# if defined (TERMIO_LDISC) && (NTTYDISC) + if (ioctl (tty, TCGETA, &shell_tty_info) < 0) + return (-1); + + if (shell_tty_info.c_line != NTTYDISC) + { + shell_tty_info.c_line = NTTYDISC; + if (ioctl (tty, TCSETAW, &shell_tty_info) < 0) + return (-1); + } +# endif /* TERMIO_LDISC && NTTYDISC */ + return (0); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) +# if defined (TERMIOS_LDISC) && defined (NTTYDISC) + if (tcgetattr (tty, &shell_tty_info) < 0) + return (-1); + + if (shell_tty_info.c_line != NTTYDISC) + { + shell_tty_info.c_line = NTTYDISC; + if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0) + return (-1); + } +# endif /* TERMIOS_LDISC && NTTYDISC */ + return (0); +#endif /* TERMIOS_TTY_DRIVER */ + +#if !defined (NEW_TTY_DRIVER) && !defined (TERMIO_TTY_DRIVER) && !defined (TERMIOS_TTY_DRIVER) + return (-1); +#endif +} + +#if defined (TIOCGWINSZ) && defined (SIGWINCH) +static void +get_new_window_size (from_sig) + int from_sig; +{ + struct winsize win; + + if ((ioctl (shell_tty, TIOCGWINSZ, &win) == 0) && + win.ws_row > 0 && win.ws_col > 0) + { +#if defined (aixpc) + shell_tty_info.c_winsize = win; /* structure copying */ +#endif + sh_set_lines_and_columns (win.ws_row, win.ws_col); +#if defined (READLINE) + rl_set_screen_size (win.ws_row, win.ws_col); +#endif + } +} + +static sighandler +sigwinch_sighandler (sig) + int sig; +{ +#if defined (MUST_REINSTALL_SIGHANDLERS) + set_signal_handler (SIGWINCH, sigwinch_sighandler); +#endif /* MUST_REINSTALL_SIGHANDLERS */ + get_new_window_size (1); + SIGRETURN (0); +} +#else +static void +get_new_window_size (from_sig) + int from_sig; +{ +} +#endif /* TIOCGWINSZ && SIGWINCH */ + +void +set_sigwinch_handler () +{ +#if defined (TIOCGWINSZ) && defined (SIGWINCH) + old_winch = set_signal_handler (SIGWINCH, sigwinch_sighandler); +#endif +} + +void +unset_sigwinch_handler () +{ +#if defined (TIOCGWINSZ) && defined (SIGWINCH) + set_signal_handler (SIGWINCH, old_winch); +#endif +} + +/* Setup this shell to handle C-C, etc. */ +void +initialize_job_signals () +{ + if (interactive) + { + set_signal_handler (SIGINT, sigint_sighandler); + set_signal_handler (SIGTSTP, SIG_IGN); + set_signal_handler (SIGTTOU, SIG_IGN); + set_signal_handler (SIGTTIN, SIG_IGN); + set_sigwinch_handler (); + } + else if (job_control) + { + old_tstp = set_signal_handler (SIGTSTP, sigstop_sighandler); + old_ttin = set_signal_handler (SIGTTIN, sigstop_sighandler); + old_ttou = set_signal_handler (SIGTTOU, sigstop_sighandler); + } + /* Leave these things alone for non-interactive shells without job + control. */ +} + +/* Here we handle CONT signals. */ +static sighandler +sigcont_sighandler (sig) + int sig; +{ + initialize_job_signals (); + set_signal_handler (SIGCONT, old_cont); + kill (getpid (), SIGCONT); + + SIGRETURN (0); +} + +/* Here we handle stop signals while we are running not as a login shell. */ +static sighandler +sigstop_sighandler (sig) + int sig; +{ + set_signal_handler (SIGTSTP, old_tstp); + set_signal_handler (SIGTTOU, old_ttou); + set_signal_handler (SIGTTIN, old_ttin); + + old_cont = set_signal_handler (SIGCONT, sigcont_sighandler); + + give_terminal_to (shell_pgrp, 0); + + kill (getpid (), sig); + + SIGRETURN (0); +} + +/* Give the terminal to PGRP. */ +int +give_terminal_to (pgrp, force) + pid_t pgrp; + int force; +{ + sigset_t set, oset; + int r; + + r = 0; + if (job_control || force) + { + sigemptyset (&set); + sigaddset (&set, SIGTTOU); + sigaddset (&set, SIGTTIN); + sigaddset (&set, SIGTSTP); + sigaddset (&set, SIGCHLD); + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + + if (tcsetpgrp (shell_tty, pgrp) < 0) + { + /* Maybe we should print an error message? */ +#if 0 + sys_error ("tcsetpgrp(%d) failed: pid %ld to pgrp %ld", + shell_tty, (long)getpid(), (long)pgrp); +#endif + r = -1; + } + else + terminal_pgrp = pgrp; + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + } + + return r; +} + +/* Clear out any jobs in the job array. This is intended to be used by + children of the shell, who should not have any job structures as baggage + when they start executing (forking subshells for parenthesized execution + and functions with pipes are the two that spring to mind). If RUNNING_ONLY + is nonzero, only running jobs are removed from the table. */ +void +delete_all_jobs (running_only) + int running_only; +{ + register int i; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + if (job_slots) + { + current_job = previous_job = NO_JOB; + + for (i = 0; i < job_slots; i++) + if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i)))) + delete_job (i, 1); + + if (running_only == 0) + { + free ((char *)jobs); + job_slots = 0; + } + } + + UNBLOCK_CHILD (oset); +} + +/* Mark all jobs in the job array so that they don't get a SIGHUP when the + shell gets one. If RUNNING_ONLY is nonzero, mark only running jobs. */ +void +nohup_all_jobs (running_only) + int running_only; +{ + register int i; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + if (job_slots) + { + for (i = 0; i < job_slots; i++) + if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i)))) + nohup_job (i); + } + + UNBLOCK_CHILD (oset); +} + +int +count_all_jobs () +{ + int i, n; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + for (i = n = 0; i < job_slots; i++) + if (jobs[i] && DEADJOB(i) == 0) + n++; + UNBLOCK_CHILD (oset); + return n; +} + +static void +mark_all_jobs_as_dead () +{ + register int i; + sigset_t set, oset; + + if (job_slots == 0) + return; + + BLOCK_CHILD (set, oset); + + for (i = 0; i < job_slots; i++) + if (jobs[i]) + jobs[i]->state = JDEAD; + + UNBLOCK_CHILD (oset); +} + +/* Mark all dead jobs as notified, so delete_job () cleans them out + of the job table properly. POSIX.2 says we need to save the + status of the last CHILD_MAX jobs, so we count the number of dead + jobs and mark only enough as notified to save CHILD_MAX statuses. */ +static void +mark_dead_jobs_as_notified (force) + int force; +{ + register int i, ndead; + sigset_t set, oset; + + if (job_slots == 0) + return; + + BLOCK_CHILD (set, oset); + + /* If FORCE is non-zero, we don't have to keep CHILD_MAX statuses + around; just run through the array. */ + if (force) + { + for (i = 0; i < job_slots; i++) + { + if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid))) + jobs[i]->flags |= J_NOTIFIED; + } + UNBLOCK_CHILD (oset); + return; + } + + /* Mark enough dead jobs as notified to keep CHILD_MAX jobs left in the + array not marked as notified. */ + + /* Count the number of dead jobs */ + for (i = ndead = 0; i < job_slots; i++) + { + if (jobs[i] && DEADJOB (i)) + ndead++; + } + + if (child_max < 0) + child_max = getmaxchild (); + if (child_max < 0) + child_max = DEFAULT_CHILD_MAX; + + /* Don't do anything if the number of dead jobs is less than CHILD_MAX and + we're not forcing a cleanup. */ + if (ndead <= child_max) + { + UNBLOCK_CHILD (oset); + return; + } + + /* Mark enough dead jobs as notified that we keep CHILD_MAX jobs in + the list. This isn't exactly right yet; changes need to be made + to stop_pipeline so we don't mark the newer jobs after we've + created CHILD_MAX slots in the jobs array. */ + for (i = 0; i < job_slots; i++) + { + if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid))) + { + jobs[i]->flags |= J_NOTIFIED; + if (--ndead <= child_max) + break; + } + } + + UNBLOCK_CHILD (oset); +} + +/* Here to allow other parts of the shell (like the trap stuff) to + unfreeze the jobs list. */ +void +unfreeze_jobs_list () +{ + jobs_list_frozen = 0; +} + +/* Allow or disallow job control to take place. Returns the old value + of job_control. */ +int +set_job_control (arg) + int arg; +{ + int old; + + old = job_control; + job_control = arg; + + /* If we're turning on job control, reset pipeline_pgrp so make_child will + put new child processes into the right pgrp */ + if (job_control != old && job_control) + pipeline_pgrp = 0; + + return (old); +} + +/* Turn off all traces of job control. This is run by children of the shell + which are going to do shellsy things, like wait (), etc. */ +void +without_job_control () +{ + stop_making_children (); + start_pipeline (); + delete_all_jobs (0); + set_job_control (0); +} + +/* If this shell is interactive, terminate all stopped jobs and + restore the original terminal process group. This is done + before the `exec' builtin calls shell_execve. */ +void +end_job_control () +{ + if (interactive_shell) /* XXX - should it be interactive? */ + { + terminate_stopped_jobs (); + + if (original_pgrp >= 0) + give_terminal_to (original_pgrp, 1); + } + + if (original_pgrp >= 0) + setpgid (0, original_pgrp); +} + +/* Restart job control by closing shell tty and reinitializing. This is + called after an exec fails in an interactive shell and we do not exit. */ +void +restart_job_control () +{ + if (shell_tty != -1) + close (shell_tty); + initialize_job_control (0); +} + +/* Set the handler to run when the shell receives a SIGCHLD signal. */ +void +set_sigchld_handler () +{ + set_signal_handler (SIGCHLD, sigchld_handler); +} + +#if defined (PGRP_PIPE) +/* Read from the read end of a pipe. This is how the process group leader + blocks until all of the processes in a pipeline have been made. */ +static void +pipe_read (pp) + int *pp; +{ + char ch; + + if (pp[1] >= 0) + { + close (pp[1]); + pp[1] = -1; + } + + if (pp[0] >= 0) + { + while (read (pp[0], &ch, 1) == -1 && errno == EINTR) + ; + } +} + +/* Close the read and write ends of PP, an array of file descriptors. */ +static void +pipe_close (pp) + int *pp; +{ + if (pp[0] >= 0) + close (pp[0]); + + if (pp[1] >= 0) + close (pp[1]); + + pp[0] = pp[1] = -1; +} + +/* Functional interface closes our local-to-job-control pipes. */ +void +close_pgrp_pipe () +{ + pipe_close (pgrp_pipe); +} + +#endif /* PGRP_PIPE */ diff --git a/lib/readline/misc.c b/lib/readline/misc.c index ab1e1337f..3a6578f31 100644 --- a/lib/readline/misc.c +++ b/lib/readline/misc.c @@ -276,12 +276,14 @@ rl_maybe_save_line () _rl_saved_line_for_history->line = savestring (rl_line_buffer); _rl_saved_line_for_history->data = (char *)rl_undo_list; } +#if 0 else if (STREQ (rl_line_buffer, _rl_saved_line_for_history->line) == 0) { free (_rl_saved_line_for_history->line); _rl_saved_line_for_history->line = savestring (rl_line_buffer); _rl_saved_line_for_history->data = (char *)rl_undo_list; /* XXX possible memleak */ } +#endif return 0; } diff --git a/lib/readline/misc.c~ b/lib/readline/misc.c~ new file mode 100644 index 000000000..ab1e1337f --- /dev/null +++ b/lib/readline/misc.c~ @@ -0,0 +1,508 @@ +/* misc.c -- miscellaneous bindable readline functions. */ + +/* Copyright (C) 1987-2004 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library, a library for + reading lines of text with interactive input and history editing. + + The GNU Readline Library is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2, or + (at your option) any later version. + + The GNU Readline Library 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. + + The GNU General Public License is often shipped with GNU software, and + is generally kept in a file called COPYING or LICENSE. If you do not + have a copy of the license, write to the Free Software Foundation, + 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ +#define READLINE_LIBRARY + +#if defined (HAVE_CONFIG_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif /* HAVE_UNISTD_H */ + +#if defined (HAVE_STDLIB_H) +# include +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if defined (HAVE_LOCALE_H) +# include +#endif + +#include + +/* System-specific feature definitions and include files. */ +#include "rldefs.h" +#include "rlmbutil.h" + +/* Some standard library routines. */ +#include "readline.h" +#include "history.h" + +#include "rlprivate.h" +#include "rlshell.h" +#include "xmalloc.h" + +static int rl_digit_loop PARAMS((void)); +static void _rl_history_set_point PARAMS((void)); + +/* Forward declarations used in this file */ +void _rl_free_history_entry PARAMS((HIST_ENTRY *)); + +/* If non-zero, rl_get_previous_history and rl_get_next_history attempt + to preserve the value of rl_point from line to line. */ +int _rl_history_preserve_point = 0; + +/* Saved target point for when _rl_history_preserve_point is set. Special + value of -1 means that point is at the end of the line. */ +int _rl_history_saved_point = -1; + +/* **************************************************************** */ +/* */ +/* Numeric Arguments */ +/* */ +/* **************************************************************** */ + +/* Handle C-u style numeric args, as well as M--, and M-digits. */ +static int +rl_digit_loop () +{ + int key, c, sawminus, sawdigits; + + rl_save_prompt (); + + RL_SETSTATE(RL_STATE_NUMERICARG); + sawminus = sawdigits = 0; + while (1) + { + if (rl_numeric_arg > 1000000) + { + sawdigits = rl_explicit_arg = rl_numeric_arg = 0; + rl_ding (); + rl_restore_prompt (); + rl_clear_message (); + RL_UNSETSTATE(RL_STATE_NUMERICARG); + return 1; + } + rl_message ("(arg: %d) ", rl_arg_sign * rl_numeric_arg); + RL_SETSTATE(RL_STATE_MOREINPUT); + key = c = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + + if (c < 0) + { + _rl_abort_internal (); + return -1; + } + + /* If we see a key bound to `universal-argument' after seeing digits, + it ends the argument but is otherwise ignored. */ + if (_rl_keymap[c].type == ISFUNC && + _rl_keymap[c].function == rl_universal_argument) + { + if (sawdigits == 0) + { + rl_numeric_arg *= 4; + continue; + } + else + { + RL_SETSTATE(RL_STATE_MOREINPUT); + key = rl_read_key (); + RL_UNSETSTATE(RL_STATE_MOREINPUT); + rl_restore_prompt (); + rl_clear_message (); + RL_UNSETSTATE(RL_STATE_NUMERICARG); + return (_rl_dispatch (key, _rl_keymap)); + } + } + + c = UNMETA (c); + + if (_rl_digit_p (c)) + { + rl_numeric_arg = rl_explicit_arg ? (rl_numeric_arg * 10) + c - '0' : c - '0'; + sawdigits = rl_explicit_arg = 1; + } + else if (c == '-' && rl_explicit_arg == 0) + { + rl_numeric_arg = sawminus = 1; + rl_arg_sign = -1; + } + else + { + /* Make M-- command equivalent to M--1 command. */ + if (sawminus && rl_numeric_arg == 1 && rl_explicit_arg == 0) + rl_explicit_arg = 1; + rl_restore_prompt (); + rl_clear_message (); + RL_UNSETSTATE(RL_STATE_NUMERICARG); + return (_rl_dispatch (key, _rl_keymap)); + } + } + + /*NOTREACHED*/ +} + +/* Add the current digit to the argument in progress. */ +int +rl_digit_argument (ignore, key) + int ignore, key; +{ + rl_execute_next (key); + return (rl_digit_loop ()); +} + +/* What to do when you abort reading an argument. */ +int +rl_discard_argument () +{ + rl_ding (); + rl_clear_message (); + _rl_init_argument (); + return 0; +} + +/* Create a default argument. */ +int +_rl_init_argument () +{ + rl_numeric_arg = rl_arg_sign = 1; + rl_explicit_arg = 0; + return 0; +} + +/* C-u, universal argument. Multiply the current argument by 4. + Read a key. If the key has nothing to do with arguments, then + dispatch on it. If the key is the abort character then abort. */ +int +rl_universal_argument (count, key) + int count, key; +{ + rl_numeric_arg *= 4; + return (rl_digit_loop ()); +} + +/* **************************************************************** */ +/* */ +/* History Utilities */ +/* */ +/* **************************************************************** */ + +/* We already have a history library, and that is what we use to control + the history features of readline. This is our local interface to + the history mechanism. */ + +/* While we are editing the history, this is the saved + version of the original line. */ +HIST_ENTRY *_rl_saved_line_for_history = (HIST_ENTRY *)NULL; + +/* Set the history pointer back to the last entry in the history. */ +void +_rl_start_using_history () +{ + using_history (); + if (_rl_saved_line_for_history) + _rl_free_history_entry (_rl_saved_line_for_history); + + _rl_saved_line_for_history = (HIST_ENTRY *)NULL; +} + +/* Free the contents (and containing structure) of a HIST_ENTRY. */ +void +_rl_free_history_entry (entry) + HIST_ENTRY *entry; +{ + if (entry == 0) + return; + if (entry->line) + free (entry->line); + free (entry); +} + +/* Perhaps put back the current line if it has changed. */ +int +rl_maybe_replace_line () +{ + HIST_ENTRY *temp; + + temp = current_history (); + /* If the current line has changed, save the changes. */ + if (temp && ((UNDO_LIST *)(temp->data) != rl_undo_list)) + { + temp = replace_history_entry (where_history (), rl_line_buffer, (histdata_t)rl_undo_list); + free (temp->line); + free (temp); + } + return 0; +} + +/* Restore the _rl_saved_line_for_history if there is one. */ +int +rl_maybe_unsave_line () +{ + if (_rl_saved_line_for_history) + { + /* Can't call with `1' because rl_undo_list might point to an undo + list from a history entry, as in rl_replace_from_history() below. */ + rl_replace_line (_rl_saved_line_for_history->line, 0); + rl_undo_list = (UNDO_LIST *)_rl_saved_line_for_history->data; + _rl_free_history_entry (_rl_saved_line_for_history); + _rl_saved_line_for_history = (HIST_ENTRY *)NULL; + rl_point = rl_end; /* rl_replace_line sets rl_end */ + } + else + rl_ding (); + return 0; +} + +/* Save the current line in _rl_saved_line_for_history. */ +int +rl_maybe_save_line () +{ + if (_rl_saved_line_for_history == 0) + { + _rl_saved_line_for_history = (HIST_ENTRY *)xmalloc (sizeof (HIST_ENTRY)); + _rl_saved_line_for_history->line = savestring (rl_line_buffer); + _rl_saved_line_for_history->data = (char *)rl_undo_list; + } + else if (STREQ (rl_line_buffer, _rl_saved_line_for_history->line) == 0) + { + free (_rl_saved_line_for_history->line); + _rl_saved_line_for_history->line = savestring (rl_line_buffer); + _rl_saved_line_for_history->data = (char *)rl_undo_list; /* XXX possible memleak */ + } + + return 0; +} + +int +_rl_free_saved_history_line () +{ + if (_rl_saved_line_for_history) + { + _rl_free_history_entry (_rl_saved_line_for_history); + _rl_saved_line_for_history = (HIST_ENTRY *)NULL; + } + return 0; +} + +static void +_rl_history_set_point () +{ + rl_point = (_rl_history_preserve_point && _rl_history_saved_point != -1) + ? _rl_history_saved_point + : rl_end; + if (rl_point > rl_end) + rl_point = rl_end; + +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode && _rl_keymap != vi_insertion_keymap) + rl_point = 0; +#endif /* VI_MODE */ + + if (rl_editing_mode == emacs_mode) + rl_mark = (rl_point == rl_end ? 0 : rl_end); +} + +void +rl_replace_from_history (entry, flags) + HIST_ENTRY *entry; + int flags; /* currently unused */ +{ + /* Can't call with `1' because rl_undo_list might point to an undo list + from a history entry, just like we're setting up here. */ + rl_replace_line (entry->line, 0); + rl_undo_list = (UNDO_LIST *)entry->data; + rl_point = rl_end; + rl_mark = 0; + +#if defined (VI_MODE) + if (rl_editing_mode == vi_mode) + { + rl_point = 0; + rl_mark = rl_end; + } +#endif +} + +/* **************************************************************** */ +/* */ +/* History Commands */ +/* */ +/* **************************************************************** */ + +/* Meta-< goes to the start of the history. */ +int +rl_beginning_of_history (count, key) + int count, key; +{ + return (rl_get_previous_history (1 + where_history (), key)); +} + +/* Meta-> goes to the end of the history. (The current line). */ +int +rl_end_of_history (count, key) + int count, key; +{ + rl_maybe_replace_line (); + using_history (); + rl_maybe_unsave_line (); + return 0; +} + +/* Move down to the next history line. */ +int +rl_get_next_history (count, key) + int count, key; +{ + HIST_ENTRY *temp; + + if (count < 0) + return (rl_get_previous_history (-count, key)); + + if (count == 0) + return 0; + + rl_maybe_replace_line (); + + /* either not saved by rl_newline or at end of line, so set appropriately. */ + if (_rl_history_saved_point == -1 && (rl_point || rl_end)) + _rl_history_saved_point = (rl_point == rl_end) ? -1 : rl_point; + + temp = (HIST_ENTRY *)NULL; + while (count) + { + temp = next_history (); + if (!temp) + break; + --count; + } + + if (temp == 0) + rl_maybe_unsave_line (); + else + { + rl_replace_from_history (temp, 0); + _rl_history_set_point (); + } + return 0; +} + +/* Get the previous item out of our interactive history, making it the current + line. If there is no previous history, just ding. */ +int +rl_get_previous_history (count, key) + int count, key; +{ + HIST_ENTRY *old_temp, *temp; + + if (count < 0) + return (rl_get_next_history (-count, key)); + + if (count == 0) + return 0; + + /* either not saved by rl_newline or at end of line, so set appropriately. */ + if (_rl_history_saved_point == -1 && (rl_point || rl_end)) + _rl_history_saved_point = (rl_point == rl_end) ? -1 : rl_point; + + /* If we don't have a line saved, then save this one. */ + rl_maybe_save_line (); + + /* If the current line has changed, save the changes. */ + rl_maybe_replace_line (); + + temp = old_temp = (HIST_ENTRY *)NULL; + while (count) + { + temp = previous_history (); + if (temp == 0) + break; + + old_temp = temp; + --count; + } + + /* If there was a large argument, and we moved back to the start of the + history, that is not an error. So use the last value found. */ + if (!temp && old_temp) + temp = old_temp; + + if (temp == 0) + rl_ding (); + else + { + rl_replace_from_history (temp, 0); + _rl_history_set_point (); + } + + return 0; +} + +/* **************************************************************** */ +/* */ +/* Editing Modes */ +/* */ +/* **************************************************************** */ +/* How to toggle back and forth between editing modes. */ +int +rl_vi_editing_mode (count, key) + int count, key; +{ +#if defined (VI_MODE) + _rl_set_insert_mode (RL_IM_INSERT, 1); /* vi mode ignores insert mode */ + rl_editing_mode = vi_mode; + rl_vi_insertion_mode (1, key); +#endif /* VI_MODE */ + + return 0; +} + +int +rl_emacs_editing_mode (count, key) + int count, key; +{ + rl_editing_mode = emacs_mode; + _rl_set_insert_mode (RL_IM_INSERT, 1); /* emacs mode default is insert mode */ + _rl_keymap = emacs_standard_keymap; + return 0; +} + +/* Function for the rest of the library to use to set insert/overwrite mode. */ +void +_rl_set_insert_mode (im, force) + int im, force; +{ +#ifdef CURSOR_MODE + _rl_set_cursor (im, force); +#endif + + rl_insert_mode = im; +} + +/* Toggle overwrite mode. A positive explicit argument selects overwrite + mode. A negative or zero explicit argument selects insert mode. */ +int +rl_overwrite_mode (count, key) + int count, key; +{ + if (rl_explicit_arg == 0) + _rl_set_insert_mode (rl_insert_mode ^ 1, 0); + else if (count > 0) + _rl_set_insert_mode (RL_IM_OVERWRITE, 0); + else + _rl_set_insert_mode (RL_IM_INSERT, 0); + + return 0; +} diff --git a/locale.c b/locale.c index 0c4772a9f..48e44ec48 100644 --- a/locale.c +++ b/locale.c @@ -71,8 +71,8 @@ set_default_locale () textdomain (PACKAGE); } -/* Set default values for LC_CTYPE, LC_COLLATE, LC_MESSAGES and LC_NUMERIC - if they are not specified in the environment, but LC_ALL is. This +/* Set default values for LC_CTYPE, LC_COLLATE, LC_MESSAGES, LC_NUMERIC and + LC_TIME if they are not specified in the environment, but LC_ALL is. This should be called from main() after parsing the environment. */ void set_default_locale_vars () @@ -109,6 +109,12 @@ set_default_locale_vars () setlocale (LC_NUMERIC, lc_all); # endif /* LC_NUMERIC */ +# if defined (LC_TIME) + val = get_string_value ("LC_TIME"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_TIME, lc_all); +# endif /* LC_TIME */ + #endif /* HAVE_SETLOCALE */ val = get_string_value ("TEXTDOMAIN"); @@ -213,7 +219,15 @@ set_locale_var (var, value) return (setlocale (LC_NUMERIC, get_locale_var ("LC_NUMERIC")) != 0); # endif /* LC_NUMERIC */ } + else if (var[3] == 'T' && var[4] == 'I') /* LC_TIME */ + { +# if defined (LC_TIME) + if (lc_all == 0 || *lc_all == '\0') + return (setlocale (LC_TIME, get_locale_var ("LC_TIME")) != 0); +# endif /* LC_TIME */ + } #endif /* HAVE_SETLOCALE */ + return (0); } @@ -285,6 +299,9 @@ reset_locale_vars () # if defined (LC_NUMERIC) setlocale (LC_NUMERIC, get_locale_var ("LC_NUMERIC")); # endif +# if defined (LC_TIME) + setlocale (LC_TIME, get_locale_var ("LC_TIME")); +# endif locale_setblanks (); diff --git a/locale.c~ b/locale.c~ new file mode 100644 index 000000000..0c4772a9f --- /dev/null +++ b/locale.c~ @@ -0,0 +1,479 @@ +/* locale.c - Miscellaneous internationalization functions. */ + +/* Copyright (C) 1996-2004 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashintl.h" +#include "bashansi.h" +#include +#include "chartypes.h" + +#include "shell.h" +#include "input.h" /* For bash_input */ + +extern int dump_translatable_strings, dump_po_strings; + +/* The current locale when the program begins */ +static char *default_locale; + +/* The current domain for textdomain(3). */ +static char *default_domain; +static char *default_dir; + +/* tracks the value of LC_ALL; used to override values for other locale + categories */ +static char *lc_all; + +/* tracks the value of LC_ALL; used to provide defaults for locale + categories */ +static char *lang; + +/* Called to reset all of the locale variables to their appropriate values + if (and only if) LC_ALL has not been assigned a value. */ +static int reset_locale_vars __P((void)); + +static void locale_setblanks __P((void)); + +/* Set the value of default_locale and make the current locale the + system default locale. This should be called very early in main(). */ +void +set_default_locale () +{ +#if defined (HAVE_SETLOCALE) + default_locale = setlocale (LC_ALL, ""); + if (default_locale) + default_locale = savestring (default_locale); +#endif /* HAVE_SETLOCALE */ + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); +} + +/* Set default values for LC_CTYPE, LC_COLLATE, LC_MESSAGES and LC_NUMERIC + if they are not specified in the environment, but LC_ALL is. This + should be called from main() after parsing the environment. */ +void +set_default_locale_vars () +{ + char *val; + int r; + +#if defined (HAVE_SETLOCALE) + +# if defined (LC_CTYPE) + val = get_string_value ("LC_CTYPE"); + if (val == 0 && lc_all && *lc_all) + { + setlocale (LC_CTYPE, lc_all); + locale_setblanks (); + } +# endif + +# if defined (LC_COLLATE) + val = get_string_value ("LC_COLLATE"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_COLLATE, lc_all); +# endif /* LC_COLLATE */ + +# if defined (LC_MESSAGES) + val = get_string_value ("LC_MESSAGES"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_MESSAGES, lc_all); +# endif /* LC_MESSAGES */ + +# if defined (LC_NUMERIC) + val = get_string_value ("LC_NUMERIC"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_NUMERIC, lc_all); +# endif /* LC_NUMERIC */ + +#endif /* HAVE_SETLOCALE */ + + val = get_string_value ("TEXTDOMAIN"); + if (val && *val) + { + FREE (default_domain); + default_domain = savestring (val); +#if 0 + /* Don't want to override the shell's textdomain as the default */ + textdomain (default_domain); +#endif + } + + val = get_string_value ("TEXTDOMAINDIR"); + if (val && *val) + { + FREE (default_dir); + default_dir = savestring (val); + if (default_domain && *default_domain) + bindtextdomain (default_domain, default_dir); + } +} + +/* Set one of the locale categories (specified by VAR) to VALUE. Returns 1 + if successful, 0 otherwise. */ +int +set_locale_var (var, value) + char *var, *value; +{ + int r; + + if (var[0] == 'T' && var[10] == 0) /* TEXTDOMAIN */ + { + FREE (default_domain); + default_domain = value ? savestring (value) : (char *)NULL; +#if 0 + /* Don't want to override the shell's textdomain as the default */ + textdomain (default_domain); +#endif + return (1); + } + else if (var[0] == 'T') /* TEXTDOMAINDIR */ + { + FREE (default_dir); + default_dir = value ? savestring (value) : (char *)NULL; + if (default_domain && *default_domain) + bindtextdomain (default_domain, default_dir); + return (1); + } + + /* var[0] == 'L' && var[1] == 'C' && var[2] == '_' */ + + else if (var[3] == 'A') /* LC_ALL */ + { + FREE (lc_all); + if (value) + lc_all = savestring (value); + else + { + lc_all = (char *)xmalloc (1); + lc_all[0] = '\0'; + } +#if defined (HAVE_SETLOCALE) + r = *lc_all ? (setlocale (LC_ALL, lc_all) != 0) : reset_locale_vars (); + locale_setblanks (); + return r; +#else + return (1); +#endif + } + +#if defined (HAVE_SETLOCALE) + else if (var[3] == 'C' && var[4] == 'T') /* LC_CTYPE */ + { +# if defined (LC_CTYPE) + if (lc_all == 0 || *lc_all == '\0') + { + r = (setlocale (LC_CTYPE, get_locale_var ("LC_CTYPE")) != 0); + locale_setblanks (); + return r; + } +# endif + } + else if (var[3] == 'C' && var[4] == 'O') /* LC_COLLATE */ + { +# if defined (LC_COLLATE) + if (lc_all == 0 || *lc_all == '\0') + return (setlocale (LC_COLLATE, get_locale_var ("LC_COLLATE")) != 0); +# endif /* LC_COLLATE */ + } + else if (var[3] == 'M' && var[4] == 'E') /* LC_MESSAGES */ + { +# if defined (LC_MESSAGES) + if (lc_all == 0 || *lc_all == '\0') + return (setlocale (LC_MESSAGES, get_locale_var ("LC_MESSAGES")) != 0); +# endif /* LC_MESSAGES */ + } + else if (var[3] == 'N' && var[4] == 'U') /* LC_NUMERIC */ + { +# if defined (LC_NUMERIC) + if (lc_all == 0 || *lc_all == '\0') + return (setlocale (LC_NUMERIC, get_locale_var ("LC_NUMERIC")) != 0); +# endif /* LC_NUMERIC */ + } +#endif /* HAVE_SETLOCALE */ + + return (0); +} + +/* Called when LANG is assigned a value. Tracks value in `lang'. Calls + reset_locale_vars() to reset any default values if LC_ALL is unset or + null. */ +int +set_lang (var, value) + char *var, *value; +{ + FREE (lang); + if (value) + lang = savestring (value); + else + { + lang = (char *)xmalloc (1); + lang[0] = '\0'; + } + + return ((lc_all == 0 || *lc_all == 0) ? reset_locale_vars () : 0); +} + +/* Get the value of one of the locale variables (LC_MESSAGES, LC_CTYPE). + The precedence is as POSIX.2 specifies: LC_ALL has precedence over + the specific locale variables, and LANG, if set, is used as the default. */ +char * +get_locale_var (var) + char *var; +{ + char *locale; + + locale = lc_all; + + if (locale == 0 || *locale == 0) + locale = get_string_value (var); + if (locale == 0 || *locale == 0) + locale = lang; + if (locale == 0 || *locale == 0) + locale = default_locale; /* system-dependent; not really portable */ + + return (locale); +} + +/* Called to reset all of the locale variables to their appropriate values + if (and only if) LC_ALL has not been assigned a value. DO NOT CALL THIS + IF LC_ALL HAS BEEN ASSIGNED A VALUE. */ +static int +reset_locale_vars () +{ +#if defined (HAVE_SETLOCALE) + char *locale; + + locale = lang; + if (locale == 0 || *locale == '\0') + locale = default_locale; + if (setlocale (LC_ALL, locale) == 0) + return 0; + +# if defined (LC_CTYPE) + setlocale (LC_CTYPE, get_locale_var ("LC_CTYPE")); +# endif +# if defined (LC_COLLATE) + setlocale (LC_COLLATE, get_locale_var ("LC_COLLATE")); +# endif +# if defined (LC_MESSAGES) + setlocale (LC_MESSAGES, get_locale_var ("LC_MESSAGES")); +# endif +# if defined (LC_NUMERIC) + setlocale (LC_NUMERIC, get_locale_var ("LC_NUMERIC")); +# endif + + locale_setblanks (); + +#endif + return 1; +} + +/* Translate the contents of STRING, a $"..." quoted string, according + to the current locale. In the `C' or `POSIX' locale, or if gettext() + is not available, the passed string is returned unchanged. The + length of the translated string is returned in LENP, if non-null. */ +char * +localetrans (string, len, lenp) + char *string; + int len, *lenp; +{ + char *locale, *t; + char *translated; + int tlen; + + /* Don't try to translate null strings. */ + if (string == 0 || *string == 0) + { + if (lenp) + *lenp = 0; + return ((char *)NULL); + } + + locale = get_locale_var ("LC_MESSAGES"); + + /* If we don't have setlocale() or the current locale is `C' or `POSIX', + just return the string. If we don't have gettext(), there's no use + doing anything else. */ + if (locale == 0 || locale[0] == '\0' || + (locale[0] == 'C' && locale[1] == '\0') || STREQ (locale, "POSIX")) + { + t = (char *)xmalloc (len + 1); + strcpy (t, string); + if (lenp) + *lenp = len; + return (t); + } + + /* Now try to translate it. */ + if (default_domain && *default_domain) + translated = dgettext (default_domain, string); + else + translated = string; + + if (translated == string) /* gettext returns its argument if untranslatable */ + { + t = (char *)xmalloc (len + 1); + strcpy (t, string); + if (lenp) + *lenp = len; + } + else + { + tlen = strlen (translated); + t = (char *)xmalloc (tlen + 1); + strcpy (t, translated); + if (lenp) + *lenp = tlen; + } + return (t); +} + +/* Change a bash string into a string suitable for inclusion in a `po' file. + This backslash-escapes `"' and `\' and changes newlines into \\\n"\n". */ +char * +mk_msgstr (string, foundnlp) + char *string; + int *foundnlp; +{ + register int c, len; + char *result, *r, *s; + + for (len = 0, s = string; s && *s; s++) + { + len++; + if (*s == '"' || *s == '\\') + len++; + else if (*s == '\n') + len += 5; + } + + r = result = (char *)xmalloc (len + 3); + *r++ = '"'; + + for (s = string; s && (c = *s); s++) + { + if (c == '\n') /* -> \n"" */ + { + *r++ = '\\'; + *r++ = 'n'; + *r++ = '"'; + *r++ = '\n'; + *r++ = '"'; + if (foundnlp) + *foundnlp = 1; + continue; + } + if (c == '"' || c == '\\') + *r++ = '\\'; + *r++ = c; + } + + *r++ = '"'; + *r++ = '\0'; + + return result; +} + +/* $"..." -- Translate the portion of STRING between START and END + according to current locale using gettext (if available) and return + the result. The caller will take care of leaving the quotes intact. + The string will be left without the leading `$' by the caller. + If translation is performed, the translated string will be double-quoted + by the caller. The length of the translated string is returned in LENP, + if non-null. */ +char * +localeexpand (string, start, end, lineno, lenp) + char *string; + int start, end, lineno, *lenp; +{ + int len, tlen, foundnl; + char *temp, *t, *t2; + + temp = (char *)xmalloc (end - start + 1); + for (tlen = 0, len = start; len < end; ) + temp[tlen++] = string[len++]; + temp[tlen] = '\0'; + + /* If we're just dumping translatable strings, don't do anything with the + string itself, but if we're dumping in `po' file format, convert it into + a form more palatable to gettext(3) and friends by quoting `"' and `\' + with backslashes and converting into `\n""'. If we find a + newline in TEMP, we first output a `msgid ""' line and then the + translated string; otherwise we output the `msgid' and translated + string all on one line. */ + if (dump_translatable_strings) + { + if (dump_po_strings) + { + foundnl = 0; + t = mk_msgstr (temp, &foundnl); + t2 = foundnl ? "\"\"\n" : ""; + + printf ("#: %s:%d\nmsgid %s%s\nmsgstr \"\"\n", + yy_input_name (), lineno, t2, t); + free (t); + } + else + printf ("\"%s\"\n", temp); + + if (lenp) + *lenp = tlen; + return (temp); + } + else if (*temp) + { + t = localetrans (temp, tlen, &len); + free (temp); + if (lenp) + *lenp = len; + return (t); + } + else + { + if (lenp) + *lenp = 0; + return (temp); + } +} + +/* Set every character in the character class to be a shell break + character for the lexical analyzer when the locale changes. */ +static void +locale_setblanks () +{ + int x; + + for (x = 0; x < sh_syntabsiz; x++) + { + if (isblank (x)) + sh_syntaxtab[x] |= CSHBRK; + else if (member (x, shell_break_chars)) + sh_syntaxtab[x] |= CSHBRK; + else + sh_syntaxtab[x] &= ~CSHBRK; + } +} diff --git a/sig.c b/sig.c index 0771f14be..15af34a4e 100644 --- a/sig.c +++ b/sig.c @@ -93,6 +93,7 @@ initialize_signals (reinit) struct termsig { int signum; SigHandler *orig_handler; + int orig_flags; }; #define NULL_HANDLER (SigHandler *)SIG_DFL @@ -102,89 +103,89 @@ struct termsig { and so forth. */ static struct termsig terminating_signals[] = { #ifdef SIGHUP -{ SIGHUP, NULL_HANDLER }, +{ SIGHUP, NULL_HANDLER, 0 }, #endif #ifdef SIGINT -{ SIGINT, NULL_HANDLER }, +{ SIGINT, NULL_HANDLER, 0 }, #endif #ifdef SIGILL -{ SIGILL, NULL_HANDLER }, +{ SIGILL, NULL_HANDLER, 0 }, #endif #ifdef SIGTRAP -{ SIGTRAP, NULL_HANDLER }, +{ SIGTRAP, NULL_HANDLER, 0 }, #endif #ifdef SIGIOT -{ SIGIOT, NULL_HANDLER }, +{ SIGIOT, NULL_HANDLER, 0 }, #endif #ifdef SIGDANGER -{ SIGDANGER, NULL_HANDLER }, +{ SIGDANGER, NULL_HANDLER, 0 }, #endif #ifdef SIGEMT -{ SIGEMT, NULL_HANDLER }, +{ SIGEMT, NULL_HANDLER, 0 }, #endif #ifdef SIGFPE -{ SIGFPE, NULL_HANDLER }, +{ SIGFPE, NULL_HANDLER, 0 }, #endif #ifdef SIGBUS -{ SIGBUS, NULL_HANDLER }, +{ SIGBUS, NULL_HANDLER, 0 }, #endif #ifdef SIGSEGV -{ SIGSEGV, NULL_HANDLER }, +{ SIGSEGV, NULL_HANDLER, 0 }, #endif #ifdef SIGSYS -{ SIGSYS, NULL_HANDLER }, +{ SIGSYS, NULL_HANDLER, 0 }, #endif #ifdef SIGPIPE -{ SIGPIPE, NULL_HANDLER }, +{ SIGPIPE, NULL_HANDLER, 0 }, #endif #ifdef SIGALRM -{ SIGALRM, NULL_HANDLER }, +{ SIGALRM, NULL_HANDLER, 0 }, #endif #ifdef SIGTERM -{ SIGTERM, NULL_HANDLER }, +{ SIGTERM, NULL_HANDLER, 0 }, #endif #ifdef SIGXCPU -{ SIGXCPU, NULL_HANDLER }, +{ SIGXCPU, NULL_HANDLER, 0 }, #endif #ifdef SIGXFSZ -{ SIGXFSZ, NULL_HANDLER }, +{ SIGXFSZ, NULL_HANDLER, 0 }, #endif #ifdef SIGVTALRM -{ SIGVTALRM, NULL_HANDLER }, +{ SIGVTALRM, NULL_HANDLER, 0 }, #endif #if 0 #ifdef SIGPROF -{ SIGPROF, NULL_HANDLER }, +{ SIGPROF, NULL_HANDLER, 0 }, #endif #endif #ifdef SIGLOST -{ SIGLOST, NULL_HANDLER }, +{ SIGLOST, NULL_HANDLER, 0 }, #endif #ifdef SIGUSR1 -{ SIGUSR1, NULL_HANDLER }, +{ SIGUSR1, NULL_HANDLER, 0 }, #endif #ifdef SIGUSR2 -{ SIGUSR2, NULL_HANDLER }, +{ SIGUSR2, NULL_HANDLER, 0 }, #endif }; @@ -192,6 +193,7 @@ static struct termsig terminating_signals[] = { #define XSIG(x) (terminating_signals[x].signum) #define XHANDLER(x) (terminating_signals[x].orig_handler) +#define XSAFLAGS(x) (terminating_signals[x].orig_flags) static int termsigs_initialized = 0; @@ -228,6 +230,7 @@ initialize_terminating_signals () sigaction (XSIG (i), &act, &oact); XHANDLER(i) = oact.sa_handler; + XSAFLAGS(i) = oact.sa_flags; /* Don't do anything with signals that are ignored at shell entry if the shell is not interactive. */ if (!interactive_shell && XHANDLER (i) == SIG_IGN) @@ -250,6 +253,7 @@ initialize_terminating_signals () continue; XHANDLER(i) = signal (XSIG (i), termination_unwind_protect); + XSAFLAGS(i) = 0; /* Don't do anything with signals that are ignored at shell entry if the shell is not interactive. */ if (!interactive_shell && XHANDLER (i) == SIG_IGN) @@ -316,6 +320,7 @@ reset_terminating_signals () continue; act.sa_handler = XHANDLER (i); + act.sa_flags = XSAFLAGS (i); sigaction (XSIG (i), &act, (struct sigaction *) NULL); } #else /* !HAVE_POSIX_SIGNALS */ diff --git a/sig.c~ b/sig.c~ new file mode 100644 index 000000000..175963906 --- /dev/null +++ b/sig.c~ @@ -0,0 +1,525 @@ +/* sig.c - interface for shell signal handlers and signal initialization. */ + +/* Copyright (C) 1994 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include + +#include "bashintl.h" + +#include "shell.h" +#if defined (JOB_CONTROL) +#include "jobs.h" +#endif /* JOB_CONTROL */ +#include "siglist.h" +#include "sig.h" +#include "trap.h" + +#include "builtins/common.h" + +#if defined (READLINE) +# include "bashline.h" +#endif + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +extern int last_command_exit_value; +extern int last_command_exit_signal; +extern int return_catch_flag; +extern int loop_level, continuing, breaking; +extern int parse_and_execute_level, shell_initialized; + +/* Non-zero after SIGINT. */ +int interrupt_state; + +/* The environment at the top-level R-E loop. We use this in + the case of error return. */ +procenv_t top_level; + +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) +/* The signal masks that this shell runs with. */ +sigset_t top_level_mask; +#endif /* JOB_CONTROL */ + +/* When non-zero, we throw_to_top_level (). */ +int interrupt_immediately = 0; + +static void initialize_shell_signals __P((void)); + +void +initialize_signals (reinit) + int reinit; +{ + initialize_shell_signals (); + initialize_job_signals (); +#if !defined (HAVE_SYS_SIGLIST) && !defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_STRSIGNAL) + if (reinit == 0) + initialize_siglist (); +#endif /* !HAVE_SYS_SIGLIST && !HAVE_UNDER_SYS_SIGLIST && !HAVE_STRSIGNAL */ +} + +/* A structure describing a signal that terminates the shell if not + caught. The orig_handler member is present so children can reset + these signals back to their original handlers. */ +struct termsig { + int signum; + SigHandler *orig_handler; + int orig_flags; +}; + +#define NULL_HANDLER (SigHandler *)SIG_DFL + +/* The list of signals that would terminate the shell if not caught. + We catch them, but just so that we can write the history file, + and so forth. */ +static struct termsig terminating_signals[] = { +#ifdef SIGHUP +{ SIGHUP, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGINT +{ SIGINT, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGILL +{ SIGILL, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGTRAP +{ SIGTRAP, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGIOT +{ SIGIOT, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGDANGER +{ SIGDANGER, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGEMT +{ SIGEMT, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGFPE +{ SIGFPE, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGBUS +{ SIGBUS, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGSEGV +{ SIGSEGV, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGSYS +{ SIGSYS, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGPIPE +{ SIGPIPE, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGALRM +{ SIGALRM, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGTERM +{ SIGTERM, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGXCPU +{ SIGXCPU, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGXFSZ +{ SIGXFSZ, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGVTALRM +{ SIGVTALRM, NULL_HANDLER, 0 }, +#endif + +#if 0 +#ifdef SIGPROF +{ SIGPROF, NULL_HANDLER, 0 }, +#endif +#endif + +#ifdef SIGLOST +{ SIGLOST, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGUSR1 +{ SIGUSR1, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGUSR2 +{ SIGUSR2, NULL_HANDLER, 0 }, +#endif +}; + +#define TERMSIGS_LENGTH (sizeof (terminating_signals) / sizeof (struct termsig)) + +#define XSIG(x) (terminating_signals[x].signum) +#define XHANDLER(x) (terminating_signals[x].orig_handler) +#define XSAFLAGS(x) (terminating_signals[x].orig_flags) + +static int termsigs_initialized = 0; + +/* Initialize signals that will terminate the shell to do some + unwind protection. For non-interactive shells, we only call + this when a trap is defined for EXIT (0). */ +void +initialize_terminating_signals () +{ + register int i; +#if defined (HAVE_POSIX_SIGNALS) + struct sigaction act, oact; +#endif + + if (termsigs_initialized) + return; + + /* The following code is to avoid an expensive call to + set_signal_handler () for each terminating_signals. Fortunately, + this is possible in Posix. Unfortunately, we have to call signal () + on non-Posix systems for each signal in terminating_signals. */ +#if defined (HAVE_POSIX_SIGNALS) + act.sa_handler = termination_unwind_protect; + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + sigemptyset (&oact.sa_mask); + for (i = 0; i < TERMSIGS_LENGTH; i++) + sigaddset (&act.sa_mask, XSIG (i)); + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* If we've already trapped it, don't do anything. */ + if (signal_is_trapped (XSIG (i))) + continue; + + sigaction (XSIG (i), &act, &oact); + XHANDLER(i) = oact.sa_handler; + /* Don't do anything with signals that are ignored at shell entry + if the shell is not interactive. */ + if (!interactive_shell && XHANDLER (i) == SIG_IGN) + { + sigaction (XSIG (i), &oact, &act); + set_signal_ignored (XSIG (i)); + } +#if defined (SIGPROF) && !defined (_MINIX) + if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) + sigaction (XSIG (i), &oact, (struct sigaction *)NULL); +#endif /* SIGPROF && !_MINIX */ + } + +#else /* !HAVE_POSIX_SIGNALS */ + + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* If we've already trapped it, don't do anything. */ + if (signal_is_trapped (XSIG (i))) + continue; + + XHANDLER(i) = signal (XSIG (i), termination_unwind_protect); + /* Don't do anything with signals that are ignored at shell entry + if the shell is not interactive. */ + if (!interactive_shell && XHANDLER (i) == SIG_IGN) + { + signal (XSIG (i), SIG_IGN); + set_signal_ignored (XSIG (i)); + } +#ifdef SIGPROF + if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) + signal (XSIG (i), XHANDLER (i)); +#endif + } + +#endif /* !HAVE_POSIX_SIGNALS */ + + termsigs_initialized = 1; +} + +static void +initialize_shell_signals () +{ + if (interactive) + initialize_terminating_signals (); + +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) + /* All shells use the signal mask they inherit, and pass it along + to child processes. Children will never block SIGCHLD, though. */ + sigemptyset (&top_level_mask); + sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &top_level_mask); +# if defined (SIGCHLD) + sigdelset (&top_level_mask, SIGCHLD); +# endif +#endif /* JOB_CONTROL || HAVE_POSIX_SIGNALS */ + + /* And, some signals that are specifically ignored by the shell. */ + set_signal_handler (SIGQUIT, SIG_IGN); + + if (interactive) + { + set_signal_handler (SIGINT, sigint_sighandler); + set_signal_handler (SIGTERM, SIG_IGN); + } +} + +void +reset_terminating_signals () +{ + register int i; +#if defined (HAVE_POSIX_SIGNALS) + struct sigaction act; +#endif + + if (termsigs_initialized == 0) + return; + +#if defined (HAVE_POSIX_SIGNALS) + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* Skip a signal if it's trapped or handled specially, because the + trap code will restore the correct value. */ + if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) + continue; + + act.sa_handler = XHANDLER (i); + sigaction (XSIG (i), &act, (struct sigaction *) NULL); + } +#else /* !HAVE_POSIX_SIGNALS */ + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) + continue; + + signal (XSIG (i), XHANDLER (i)); + } +#endif /* !HAVE_POSIX_SIGNALS */ +} +#undef XSIG +#undef XHANDLER + +/* What to do when we've been interrupted, and it is safe to handle it. */ +void +throw_to_top_level () +{ + int print_newline = 0; + + if (interrupt_state) + { + print_newline = 1; + DELINTERRUPT; + } + + if (interrupt_state) + return; + + last_command_exit_signal = (last_command_exit_value > 128) ? + (last_command_exit_value - 128) : 0; + last_command_exit_value |= 128; + + /* Run any traps set on SIGINT. */ + run_interrupt_trap (); + + /* Cleanup string parser environment. */ + while (parse_and_execute_level) + parse_and_execute_cleanup (); + +#if defined (JOB_CONTROL) + give_terminal_to (shell_pgrp, 0); +#endif /* JOB_CONTROL */ + +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) + /* This should not be necessary on systems using sigsetjmp/siglongjmp. */ + sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); +#endif + + reset_parser (); + +#if defined (READLINE) + if (interactive) + bashline_reinitialize (); +#endif /* READLINE */ + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + run_unwind_protects (); + loop_level = continuing = breaking = 0; + return_catch_flag = 0; + + if (interactive && print_newline) + { + fflush (stdout); + fprintf (stderr, "\n"); + fflush (stderr); + } + + /* An interrupted `wait' command in a script does not exit the script. */ + if (interactive || (interactive_shell && !shell_initialized) || + (print_newline && signal_is_trapped (SIGINT))) + jump_to_top_level (DISCARD); + else + jump_to_top_level (EXITPROG); +} + +/* This is just here to isolate the longjmp calls. */ +void +jump_to_top_level (value) + int value; +{ + longjmp (top_level, value); +} + +sighandler +termination_unwind_protect (sig) + int sig; +{ + if (sig == SIGINT && signal_is_trapped (SIGINT)) + run_interrupt_trap (); + +#if defined (HISTORY) + if (interactive_shell && sig != SIGABRT) + maybe_save_shell_history (); +#endif /* HISTORY */ + +#if defined (JOB_CONTROL) + if (interactive && sig == SIGHUP) + hangup_all_jobs (); + end_job_control (); +#endif /* JOB_CONTROL */ + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + run_exit_trap (); + set_signal_handler (sig, SIG_DFL); + kill (getpid (), sig); + + SIGRETURN (0); +} + +/* What we really do when SIGINT occurs. */ +sighandler +sigint_sighandler (sig) + int sig; +{ +#if defined (MUST_REINSTALL_SIGHANDLERS) + signal (sig, sigint_sighandler); +#endif + + /* interrupt_state needs to be set for the stack of interrupts to work + right. Should it be set unconditionally? */ + if (interrupt_state == 0) + ADDINTERRUPT; + + if (interrupt_immediately) + { + interrupt_immediately = 0; + throw_to_top_level (); + } + + SIGRETURN (0); +} + +/* Signal functions used by the rest of the code. */ +#if !defined (HAVE_POSIX_SIGNALS) + +#if defined (JOB_CONTROL) +/* Perform OPERATION on NEWSET, perhaps leaving information in OLDSET. */ +sigprocmask (operation, newset, oldset) + int operation, *newset, *oldset; +{ + int old, new; + + if (newset) + new = *newset; + else + new = 0; + + switch (operation) + { + case SIG_BLOCK: + old = sigblock (new); + break; + + case SIG_SETMASK: + sigsetmask (new); + break; + + default: + internal_error (_("sigprocmask: %d: invalid operation"), operation); + } + + if (oldset) + *oldset = old; +} +#endif /* JOB_CONTROL */ + +#else + +#if !defined (SA_INTERRUPT) +# define SA_INTERRUPT 0 +#endif + +#if !defined (SA_RESTART) +# define SA_RESTART 0 +#endif + +SigHandler * +set_signal_handler (sig, handler) + int sig; + SigHandler *handler; +{ + struct sigaction act, oact; + + act.sa_handler = handler; + act.sa_flags = 0; +#if 0 + if (sig == SIGALRM) + act.sa_flags |= SA_INTERRUPT; /* XXX */ + else + act.sa_flags |= SA_RESTART; /* XXX */ +#endif + sigemptyset (&act.sa_mask); + sigemptyset (&oact.sa_mask); + sigaction (sig, &act, &oact); + return (oact.sa_handler); +} +#endif /* HAVE_POSIX_SIGNALS */ diff --git a/subst.c b/subst.c index fcc024bf2..4fde3b841 100644 --- a/subst.c +++ b/subst.c @@ -4691,6 +4691,26 @@ valid_length_expression (name) legal_identifier (name + 1)); /* ${#PS1} */ } +#if defined (HANDLE_MULTIBYTE) +size_t +mbstrlen (s) + const char *s; +{ + size_t clen, nc; + mbstate_t mbs; + + nc = 0; + memset (&mbs, 0, sizeof (mbs)); + while ((clen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0 && (MB_INVALIDCH(clen) == 0)) + { + s += clen; + nc++; + } + return nc; +} +#endif + + /* Handle the parameter brace expansion that requires us to return the length of a parameter. */ static intmax_t @@ -4746,14 +4766,14 @@ parameter_brace_expand_length (name) if (legal_number (name + 1, &arg_index)) /* ${#1} */ { t = get_dollar_var_value (arg_index); - number = STRLEN (t); + number = MB_STRLEN (t); FREE (t); } #if defined (ARRAY_VARS) else if ((var = find_variable (name + 1)) && array_p (var)) { t = array_reference (array_cell (var), 0); - number = STRLEN (t); + number = MB_STRLEN (t); } #endif else /* ${#PS1} */ @@ -4766,7 +4786,7 @@ parameter_brace_expand_length (name) if (list) dispose_words (list); - number = STRLEN (t); + number = MB_STRLEN (t); FREE (t); } } @@ -4871,7 +4891,7 @@ verify_substring_values (value, substr, vtype, e1p, e2p) { case VT_VARIABLE: case VT_ARRAYMEMBER: - len = strlen (value); + len = MB_STRLEN (value); break; case VT_POSPARMS: len = number_of_args () + 1; diff --git a/subst.c~ b/subst.c~ new file mode 100644 index 000000000..02bdc57d6 --- /dev/null +++ b/subst.c~ @@ -0,0 +1,7410 @@ +/* subst.c -- The part of the shell that does parameter, command, and + globbing substitutions. */ + +/* ``Have a little faith, there's magic in the night. You ain't a + beauty, but, hey, you're alright.'' */ + +/* Copyright (C) 1987-2004 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#include "bashtypes.h" +#include +#include "chartypes.h" +#include +#include +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "posixstat.h" +#include "bashintl.h" + +#include "shell.h" +#include "flags.h" +#include "jobs.h" +#include "execute_cmd.h" +#include "filecntl.h" +#include "trap.h" +#include "pathexp.h" +#include "mailcheck.h" + +#include "shmbutil.h" + +#include "builtins/getopt.h" +#include "builtins/common.h" + +#include +#include + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +/* The size that strings change by. */ +#define DEFAULT_INITIAL_ARRAY_SIZE 112 +#define DEFAULT_ARRAY_SIZE 128 + +/* Variable types. */ +#define VT_VARIABLE 0 +#define VT_POSPARMS 1 +#define VT_ARRAYVAR 2 +#define VT_ARRAYMEMBER 3 + +#define VT_STARSUB 128 /* $* or ${array[*]} -- used to split */ + +/* Flags for quoted_strchr */ +#define ST_BACKSL 0x01 +#define ST_CTLESC 0x02 +#define ST_SQUOTE 0x04 /* unused yet */ +#define ST_DQUOTE 0x08 /* unused yet */ + +/* Flags for the string extraction functions. */ +#define EX_NOALLOC 0x01 /* just skip; don't return substring */ +#define EX_VARNAME 0x02 /* variable name; for string_extract () */ + +/* Flags for the `pflags' argument to param_expand() */ +#define PF_NOCOMSUB 0x01 /* Do not perform command substitution */ + +/* These defs make it easier to use the editor. */ +#define LBRACE '{' +#define RBRACE '}' +#define LPAREN '(' +#define RPAREN ')' + +/* Evaluates to 1 if C is one of the shell's special parameters whose length + can be taken, but is also one of the special expansion characters. */ +#define VALID_SPECIAL_LENGTH_PARAM(c) \ + ((c) == '-' || (c) == '?' || (c) == '#') + +/* Evaluates to 1 if C is one of the shell's special parameters for which an + indirect variable reference may be made. */ +#define VALID_INDIR_PARAM(c) \ + ((c) == '#' || (c) == '?' || (c) == '@' || (c) == '*') + +/* Evaluates to 1 if C is one of the OP characters that follows the parameter + in ${parameter[:]OPword}. */ +#define VALID_PARAM_EXPAND_CHAR(c) (sh_syntaxtab[(unsigned char)c] & CSUBSTOP) + +/* Evaluates to 1 if this is one of the shell's special variables. */ +#define SPECIAL_VAR(name, wi) \ + ((DIGIT (*name) && all_digits (name)) || \ + (name[1] == '\0' && (sh_syntaxtab[(unsigned char)*name] & CSPECVAR)) || \ + (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1]))) + +/* An expansion function that takes a string and a quoted flag and returns + a WORD_LIST *. Used as the type of the third argument to + expand_string_if_necessary(). */ +typedef WORD_LIST *EXPFUNC __P((char *, int)); + +/* Process ID of the last command executed within command substitution. */ +pid_t last_command_subst_pid = NO_PID; +pid_t current_command_subst_pid = NO_PID; + +/* Variables used to keep track of the characters in IFS. */ +SHELL_VAR *ifs_var; +char *ifs_value; +unsigned char ifs_cmap[UCHAR_MAX + 1]; +unsigned char ifs_firstc; + +/* Extern functions and variables from different files. */ +extern int last_command_exit_value, last_command_exit_signal; +extern int subshell_environment; +extern int subshell_level; +extern int eof_encountered; +extern int return_catch_flag, return_catch_value; +extern pid_t dollar_dollar_pid; +extern int posixly_correct; +extern char *this_command_name; +extern struct fd_bitmap *current_fds_to_close; +extern int wordexp_only; +extern int expanding_redir; +extern int tempenv_assign_error; + +/* Non-zero means to allow unmatched globbed filenames to expand to + a null file. */ +int allow_null_glob_expansion; + +/* Non-zero means to throw an error when globbing fails to match anything. */ +int fail_glob_expansion; + +#if 0 +/* Variables to keep track of which words in an expanded word list (the + output of expand_word_list_internal) are the result of globbing + expansions. GLOB_ARGV_FLAGS is used by execute_cmd.c. + (CURRENTLY UNUSED). */ +char *glob_argv_flags; +static int glob_argv_flags_size; +#endif + +static WORD_LIST expand_word_error, expand_word_fatal; +static char expand_param_error, expand_param_fatal; + +/* Tell the expansion functions to not longjmp back to top_level on fatal + errors. Enabled when doing completion and prompt string expansion. */ +static int no_longjmp_on_fatal_error = 0; + +/* Set by expand_word_unsplit; used to inhibit splitting and re-joining + $* on $IFS, primarily when doing assignment statements. */ +static int expand_no_split_dollar_star = 0; + +/* Used to hold a list of variable assignments preceding a command. Global + so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a + SIGCHLD trap. */ +WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL; + +/* A WORD_LIST of words to be expanded by expand_word_list_internal, + without any leading variable assignments. */ +static WORD_LIST *garglist = (WORD_LIST *)NULL; + +static char *quoted_substring __P((char *, int, int)); +static int quoted_strlen __P((char *)); +static char *quoted_strchr __P((char *, int, int)); + +static char *expand_string_if_necessary __P((char *, int, EXPFUNC *)); +static inline char *expand_string_to_string_internal __P((char *, int, EXPFUNC *)); +static WORD_LIST *call_expand_word_internal __P((WORD_DESC *, int, int, int *, int *)); +static WORD_LIST *expand_string_internal __P((char *, int)); +static WORD_LIST *expand_string_leave_quoted __P((char *, int)); +static WORD_LIST *expand_string_for_rhs __P((char *, int, int *, int *)); + +static WORD_LIST *list_quote_escapes __P((WORD_LIST *)); +static char *dequote_escapes __P((char *)); +static char *make_quoted_char __P((int)); +static WORD_LIST *quote_list __P((WORD_LIST *)); +static WORD_LIST *dequote_list __P((WORD_LIST *)); +static char *remove_quoted_escapes __P((char *)); +static char *remove_quoted_nulls __P((char *)); + +static int unquoted_substring __P((char *, char *)); +static int unquoted_member __P((int, char *)); + +static int do_assignment_internal __P((const char *, int)); + +static char *string_extract_verbatim __P((char *, int *, char *)); +static char *string_extract __P((char *, int *, char *, int)); +static char *string_extract_double_quoted __P((char *, int *, int)); +static inline char *string_extract_single_quoted __P((char *, int *)); +static inline int skip_single_quoted __P((char *, size_t, int)); +static int skip_double_quoted __P((char *, size_t, int)); +static char *extract_delimited_string __P((char *, int *, char *, char *, char *, int)); +static char *extract_dollar_brace_string __P((char *, int *, int, int)); + +static char *pos_params __P((char *, int, int, int)); + +static unsigned char *mb_getcharlens __P((char *, int)); + +static char *remove_upattern __P((char *, char *, int)); +#if defined (HANDLE_MULTIBYTE) +# if !defined (HAVE_WCSDUP) +static wchar_t *wcsdup __P((wchar_t *)); +# endif +static wchar_t *remove_wpattern __P((wchar_t *, size_t, wchar_t *, int)); +#endif +static char *remove_pattern __P((char *, char *, int)); + +static int match_pattern_char __P((char *, char *)); +static int match_upattern __P((char *, char *, int, char **, char **)); +#if defined (HANDLE_MULTIBYTE) +static int match_pattern_wchar __P((wchar_t *, wchar_t *)); +static int match_wpattern __P((wchar_t *, char **, size_t, wchar_t *, int, char **, char **)); +#endif +static int match_pattern __P((char *, char *, int, char **, char **)); +static int getpatspec __P((int, char *)); +static char *getpattern __P((char *, int, int)); +static char *variable_remove_pattern __P((char *, char *, int, int)); +static char *list_remove_pattern __P((WORD_LIST *, char *, int, int, int)); +static char *parameter_list_remove_pattern __P((int, char *, int, int)); +#ifdef ARRAY_VARS +static char *array_remove_pattern __P((ARRAY *, char *, int, char *, int)); +#endif +static char *parameter_brace_remove_pattern __P((char *, char *, char *, int, int)); + +static char *process_substitute __P((char *, int)); + +static char *read_comsub __P((int, int)); + +#ifdef ARRAY_VARS +static arrayind_t array_length_reference __P((char *)); +#endif + +static int valid_brace_expansion_word __P((char *, int)); +static int chk_atstar __P((char *, int, int *, int *)); + +static char *parameter_brace_expand_word __P((char *, int, int)); +static char *parameter_brace_expand_indir __P((char *, int, int, int *, int *)); +static char *parameter_brace_expand_rhs __P((char *, char *, int, int, int *, int *)); +static void parameter_brace_expand_error __P((char *, char *)); + +static int valid_length_expression __P((char *)); +static intmax_t parameter_brace_expand_length __P((char *)); + +static char *skiparith __P((char *, int)); +static int verify_substring_values __P((char *, char *, int, intmax_t *, intmax_t *)); +static int get_var_and_type __P((char *, char *, int, SHELL_VAR **, char **)); +static char *mb_substring __P((char *, int, int)); +static char *parameter_brace_substring __P((char *, char *, char *, int)); + +static char *pos_params_pat_subst __P((char *, char *, char *, int)); + +static char *parameter_brace_patsub __P((char *, char *, char *, int)); + +static char *parameter_brace_expand __P((char *, int *, int, int *, int *)); +static char *param_expand __P((char *, int *, int, int *, int *, int *, int *, int)); + +static WORD_LIST *expand_word_internal __P((WORD_DESC *, int, int, int *, int *)); + +static WORD_LIST *word_list_split __P((WORD_LIST *)); + +static void exp_jump_to_top_level __P((int)); + +static WORD_LIST *separate_out_assignments __P((WORD_LIST *)); +static WORD_LIST *glob_expand_word_list __P((WORD_LIST *, int)); +#ifdef BRACE_EXPANSION +static WORD_LIST *brace_expand_word_list __P((WORD_LIST *, int)); +#endif +static WORD_LIST *shell_expand_word_list __P((WORD_LIST *, int)); +static WORD_LIST *expand_word_list_internal __P((WORD_LIST *, int)); + +/* **************************************************************** */ +/* */ +/* Utility Functions */ +/* */ +/* **************************************************************** */ + +#ifdef INCLUDE_UNUSED +static char * +quoted_substring (string, start, end) + char *string; + int start, end; +{ + register int len, l; + register char *result, *s, *r; + + len = end - start; + + /* Move to string[start], skipping quoted characters. */ + for (s = string, l = 0; *s && l < start; ) + { + if (*s == CTLESC) + { + s++; + continue; + } + l++; + if (*s == 0) + break; + } + + r = result = (char *)xmalloc (2*len + 1); /* save room for quotes */ + + /* Copy LEN characters, including quote characters. */ + s = string + l; + for (l = 0; l < len; s++) + { + if (*s == CTLESC) + *r++ = *s++; + *r++ = *s; + l++; + if (*s == 0) + break; + } + *r = '\0'; + return result; +} +#endif + +#ifdef INCLUDE_UNUSED +/* Return the length of S, skipping over quoted characters */ +static int +quoted_strlen (s) + char *s; +{ + register char *p; + int i; + + i = 0; + for (p = s; *p; p++) + { + if (*p == CTLESC) + { + p++; + if (*p == 0) + return (i + 1); + } + i++; + } + + return i; +} +#endif + +/* Find the first occurrence of character C in string S, obeying shell + quoting rules. If (FLAGS & ST_BACKSL) is non-zero, backslash-escaped + characters are skipped. If (FLAGS & ST_CTLESC) is non-zero, characters + escaped with CTLESC are skipped. */ +static char * +quoted_strchr (s, c, flags) + char *s; + int c, flags; +{ + register char *p; + + for (p = s; *p; p++) + { + if (((flags & ST_BACKSL) && *p == '\\') + || ((flags & ST_CTLESC) && *p == CTLESC)) + { + p++; + if (*p == '\0') + return ((char *)NULL); + continue; + } + else if (*p == c) + return p; + } + return ((char *)NULL); +} + +/* Return 1 if CHARACTER appears in an unquoted portion of + STRING. Return 0 otherwise. CHARACTER must be a single-byte character. */ +static int +unquoted_member (character, string) + int character; + char *string; +{ + size_t slen; + int sindex, c; + DECLARE_MBSTATE; + + slen = strlen (string); + sindex = 0; + while (c = string[sindex]) + { + if (c == character) + return (1); + + switch (c) + { + default: + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\\': + sindex++; + if (string[sindex]) + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\'': + sindex = skip_single_quoted (string, slen, ++sindex); + break; + + case '"': + sindex = skip_double_quoted (string, slen, ++sindex); + break; + } + } + return (0); +} + +/* Return 1 if SUBSTR appears in an unquoted portion of STRING. */ +static int +unquoted_substring (substr, string) + char *substr, *string; +{ + size_t slen; + int sindex, c, sublen; + DECLARE_MBSTATE; + + if (substr == 0 || *substr == '\0') + return (0); + + slen = strlen (string); + sublen = strlen (substr); + for (sindex = 0; c = string[sindex]; ) + { + if (STREQN (string + sindex, substr, sublen)) + return (1); + + switch (c) + { + case '\\': + sindex++; + + if (string[sindex]) + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\'': + sindex = skip_single_quoted (string, slen, ++sindex); + break; + + case '"': + sindex = skip_double_quoted (string, slen, ++sindex); + break; + + default: + ADVANCE_CHAR (string, slen, sindex); + break; + } + } + return (0); +} + +/* Most of the substitutions must be done in parallel. In order + to avoid using tons of unclear goto's, I have some functions + for manipulating malloc'ed strings. They all take INDX, a + pointer to an integer which is the offset into the string + where manipulation is taking place. They also take SIZE, a + pointer to an integer which is the current length of the + character array for this string. */ + +/* Append SOURCE to TARGET at INDEX. SIZE is the current amount + of space allocated to TARGET. SOURCE can be NULL, in which + case nothing happens. Gets rid of SOURCE by freeing it. + Returns TARGET in case the location has changed. */ +INLINE char * +sub_append_string (source, target, indx, size) + char *source, *target; + int *indx, *size; +{ + if (source) + { + int srclen, n; + + srclen = STRLEN (source); + if (srclen >= (int)(*size - *indx)) + { + n = srclen + *indx; + n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE); + target = (char *)xrealloc (target, (*size = n)); + } + + FASTCOPY (source, target + *indx, srclen); + *indx += srclen; + target[*indx] = '\0'; + + free (source); + } + return (target); +} + +#if 0 +/* UNUSED */ +/* Append the textual representation of NUMBER to TARGET. + INDX and SIZE are as in SUB_APPEND_STRING. */ +char * +sub_append_number (number, target, indx, size) + intmax_t number; + int *indx, *size; + char *target; +{ + char *temp; + + temp = itos (number); + return (sub_append_string (temp, target, indx, size)); +} +#endif + +/* Extract a substring from STRING, starting at SINDEX and ending with + one of the characters in CHARLIST. Don't make the ending character + part of the string. Leave SINDEX pointing at the ending character. + Understand about backslashes in the string. If (flags & EX_VARNAME) + is non-zero, and array variables have been compiled into the shell, + everything between a `[' and a corresponding `]' is skipped over. + If (flags & EX_NOALLOC) is non-zero, don't return the substring, just + update SINDEX. */ +static char * +string_extract (string, sindex, charlist, flags) + char *string; + int *sindex; + char *charlist; + int flags; +{ + register int c, i; + size_t slen; + char *temp; + DECLARE_MBSTATE; + + slen = strlen (string + *sindex) + *sindex; + i = *sindex; + while (c = string[i]) + { + if (c == '\\') + { + if (string[i + 1]) + i++; + else + break; + } +#if defined (ARRAY_VARS) + else if ((flags & EX_VARNAME) && c == '[') + { + int ni; + /* If this is an array subscript, skip over it and continue. */ + ni = skipsubscript (string, i); + if (string[ni] == ']') + i = ni; + } +#endif + else if (MEMBER (c, charlist)) + break; + + ADVANCE_CHAR (string, slen, i); + } + + temp = (flags & EX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i); + *sindex = i; + return (temp); +} + +/* Extract the contents of STRING as if it is enclosed in double quotes. + SINDEX, when passed in, is the offset of the character immediately + following the opening double quote; on exit, SINDEX is left pointing after + the closing double quote. If STRIPDQ is non-zero, unquoted double + quotes are stripped and the string is terminated by a null byte. + Backslashes between the embedded double quotes are processed. If STRIPDQ + is zero, an unquoted `"' terminates the string. */ +static char * +string_extract_double_quoted (string, sindex, stripdq) + char *string; + int *sindex, stripdq; +{ + size_t slen; + char *send; + int j, i, t; + unsigned char c; + char *temp, *ret; /* The new string we return. */ + int pass_next, backquote, si; /* State variables for the machine. */ + int dquote; + DECLARE_MBSTATE; + + slen = strlen (string + *sindex) + *sindex; + send = string + slen; + + pass_next = backquote = dquote = 0; + temp = (char *)xmalloc (1 + slen - *sindex); + + j = 0; + i = *sindex; + while (c = string[i]) + { + /* Process a character that was quoted by a backslash. */ + if (pass_next) + { + /* Posix.2 sez: + + ``The backslash shall retain its special meaning as an escape + character only when followed by one of the characters: + $ ` " \ ''. + + If STRIPDQ is zero, we handle the double quotes here and let + expand_word_internal handle the rest. If STRIPDQ is non-zero, + we have already been through one round of backslash stripping, + and want to strip these backslashes only if DQUOTE is non-zero, + indicating that we are inside an embedded double-quoted string. */ + + /* If we are in an embedded quoted string, then don't strip + backslashes before characters for which the backslash + retains its special meaning, but remove backslashes in + front of other characters. If we are not in an + embedded quoted string, don't strip backslashes at all. + This mess is necessary because the string was already + surrounded by double quotes (and sh has some really weird + quoting rules). + The returned string will be run through expansion as if + it were double-quoted. */ + if ((stripdq == 0 && c != '"') || + (stripdq && ((dquote && (sh_syntaxtab[c] & CBSDQUOTE)) || dquote == 0))) + temp[j++] = '\\'; + pass_next = 0; + +add_one_character: + COPY_CHAR_I (temp, j, string, send, i); + continue; + } + + /* A backslash protects the next character. The code just above + handles preserving the backslash in front of any character but + a double quote. */ + if (c == '\\') + { + pass_next++; + i++; + continue; + } + + /* Inside backquotes, ``the portion of the quoted string from the + initial backquote and the characters up to the next backquote + that is not preceded by a backslash, having escape characters + removed, defines that command''. */ + if (backquote) + { + if (c == '`') + backquote = 0; + temp[j++] = c; + i++; + continue; + } + + if (c == '`') + { + temp[j++] = c; + backquote++; + i++; + continue; + } + + /* Pass everything between `$(' and the matching `)' or a quoted + ${ ... } pair through according to the Posix.2 specification. */ + if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) + { + int free_ret = 1; + + si = i + 2; + if (string[i + 1] == LPAREN) + ret = extract_delimited_string (string, &si, "$(", "(", ")", 0); /*)*/ + else + ret = extract_dollar_brace_string (string, &si, 1, 0); + + temp[j++] = '$'; + temp[j++] = string[i + 1]; + + /* Just paranoia; ret will not be 0 unless no_longjmp_on_fatal_error + is set. */ + if (ret == 0 && no_longjmp_on_fatal_error) + { + free_ret = 0; + ret = string + i + 2; + } + + for (t = 0; ret[t]; t++, j++) + temp[j] = ret[t]; + temp[j] = string[si]; + + if (string[si]) + { + j++; + i = si + 1; + } + else + i = si; + + if (free_ret) + free (ret); + continue; + } + + /* Add any character but a double quote to the quoted string we're + accumulating. */ + if (c != '"') + goto add_one_character; + + /* c == '"' */ + if (stripdq) + { + dquote ^= 1; + i++; + continue; + } + + break; + } + temp[j] = '\0'; + + /* Point to after the closing quote. */ + if (c) + i++; + *sindex = i; + + return (temp); +} + +/* This should really be another option to string_extract_double_quoted. */ +static int +skip_double_quoted (string, slen, sind) + char *string; + size_t slen; + int sind; +{ + int c, i; + char *ret; + int pass_next, backquote, si; + DECLARE_MBSTATE; + + pass_next = backquote = 0; + i = sind; + while (c = string[i]) + { + if (pass_next) + { + pass_next = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next++; + i++; + continue; + } + else if (backquote) + { + if (c == '`') + backquote = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '`') + { + backquote++; + i++; + continue; + } + else if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) + { + si = i + 2; + if (string[i + 1] == LPAREN) + ret = extract_delimited_string (string, &si, "$(", "(", ")", EX_NOALLOC); + else + ret = extract_dollar_brace_string (string, &si, 0, EX_NOALLOC); + + i = si + 1; + continue; + } + else if (c != '"') + { + ADVANCE_CHAR (string, slen, i); + continue; + } + else + break; + } + + if (c) + i++; + + return (i); +} + +/* Extract the contents of STRING as if it is enclosed in single quotes. + SINDEX, when passed in, is the offset of the character immediately + following the opening single quote; on exit, SINDEX is left pointing after + the closing single quote. */ +static inline char * +string_extract_single_quoted (string, sindex) + char *string; + int *sindex; +{ + register int i; + size_t slen; + char *t; + DECLARE_MBSTATE; + + slen = strlen (string + *sindex) + *sindex; + i = *sindex; + while (string[i] && string[i] != '\'') + ADVANCE_CHAR (string, slen, i); + + t = substring (string, *sindex, i); + + if (string[i]) + i++; + *sindex = i; + + return (t); +} + +static inline int +skip_single_quoted (string, slen, sind) + char *string; + size_t slen; + int sind; +{ + register int c; + DECLARE_MBSTATE; + + c = sind; + while (string[c] && string[c] != '\'') + ADVANCE_CHAR (string, slen, c); + + if (string[c]) + c++; + return c; +} + +/* Just like string_extract, but doesn't hack backslashes or any of + that other stuff. Obeys CTLESC quoting. Used to do splitting on $IFS. */ +static char * +string_extract_verbatim (string, sindex, charlist) + char *string; + int *sindex; + char *charlist; +{ + register int i = *sindex; + int c; + char *temp; + + if (charlist[0] == '\'' && charlist[1] == '\0') + { + temp = string_extract_single_quoted (string, sindex); + --*sindex; /* leave *sindex at separator character */ + return temp; + } + + for (i = *sindex; c = string[i]; i++) + { + if (c == CTLESC) + { + i++; + continue; + } + + if (MEMBER (c, charlist)) + break; + } + + temp = substring (string, *sindex, i); + *sindex = i; + + return (temp); +} + +/* Extract the $( construct in STRING, and return a new string. + Start extracting at (SINDEX) as if we had just seen "$(". + Make (SINDEX) get the position of the matching ")". */ +char * +extract_command_subst (string, sindex) + char *string; + int *sindex; +{ + return (extract_delimited_string (string, sindex, "$(", "(", ")", 0)); +} + +/* Extract the $[ construct in STRING, and return a new string. (]) + Start extracting at (SINDEX) as if we had just seen "$[". + Make (SINDEX) get the position of the matching "]". */ +char * +extract_arithmetic_subst (string, sindex) + char *string; + int *sindex; +{ + return (extract_delimited_string (string, sindex, "$[", "[", "]", 0)); /*]*/ +} + +#if defined (PROCESS_SUBSTITUTION) +/* Extract the <( or >( construct in STRING, and return a new string. + Start extracting at (SINDEX) as if we had just seen "<(". + Make (SINDEX) get the position of the matching ")". */ /*))*/ +char * +extract_process_subst (string, starter, sindex) + char *string; + char *starter; + int *sindex; +{ + return (extract_delimited_string (string, sindex, starter, "(", ")", 0)); +} +#endif /* PROCESS_SUBSTITUTION */ + +#if defined (ARRAY_VARS) +char * +extract_array_assignment_list (string, sindex) + char *string; + int *sindex; +{ + return (extract_delimited_string (string, sindex, "(", (char *)NULL, ")", 0)); +} +#endif + +/* Extract and create a new string from the contents of STRING, a + character string delimited with OPENER and CLOSER. SINDEX is + the address of an int describing the current offset in STRING; + it should point to just after the first OPENER found. On exit, + SINDEX gets the position of the last character of the matching CLOSER. + If OPENER is more than a single character, ALT_OPENER, if non-null, + contains a character string that can also match CLOSER and thus + needs to be skipped. */ +static char * +extract_delimited_string (string, sindex, opener, alt_opener, closer, flags) + char *string; + int *sindex; + char *opener, *alt_opener, *closer; + int flags; +{ + int i, c, si; + size_t slen; + char *t, *result; + int pass_character, nesting_level; + int len_closer, len_opener, len_alt_opener; + DECLARE_MBSTATE; + + slen = strlen (string + *sindex) + *sindex; + len_opener = STRLEN (opener); + len_alt_opener = STRLEN (alt_opener); + len_closer = STRLEN (closer); + + pass_character = 0; + + nesting_level = 1; + i = *sindex; + + while (nesting_level) + { + c = string[i]; + + if (c == 0) + break; + + if (pass_character) /* previous char was backslash */ + { + pass_character = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + + if (c == CTLESC || c == '\\') + { + pass_character++; + i++; + continue; + } + + /* Process a nested OPENER. */ + if (STREQN (string + i, opener, len_opener)) + { + si = i + len_opener; + t = extract_delimited_string (string, &si, opener, alt_opener, closer, flags|EX_NOALLOC); + i = si + 1; + continue; + } + + /* Process a nested ALT_OPENER */ + if (len_alt_opener && STREQN (string + i, alt_opener, len_alt_opener)) + { + si = i + len_alt_opener; + t = extract_delimited_string (string, &si, alt_opener, alt_opener, closer, flags|EX_NOALLOC); + i = si + 1; + continue; + } + + /* If the current substring terminates the delimited string, decrement + the nesting level. */ + if (STREQN (string + i, closer, len_closer)) + { + i += len_closer - 1; /* move to last byte of the closer */ + nesting_level--; + if (nesting_level == 0) + break; + } + + /* Pass old-style command substitution through verbatim. */ + if (c == '`') + { + si = i + 1; + t = string_extract (string, &si, "`", flags|EX_NOALLOC); + i = si + 1; + continue; + } + + /* Pass single-quoted and double-quoted strings through verbatim. */ + if (c == '\'' || c == '"') + { + si = i + 1; + i = (c == '\'') ? skip_single_quoted (string, slen, si) + : skip_double_quoted (string, slen, si); + continue; + } + + /* move past this character, which was not special. */ + ADVANCE_CHAR (string, slen, i); + } + + if (c == 0 && nesting_level) + { + if (no_longjmp_on_fatal_error == 0) + { + report_error (_("bad substitution: no closing `%s' in %s"), closer, string); + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level (DISCARD); + } + else + { + *sindex = i; + return (char *)NULL; + } + } + + si = i - *sindex - len_closer + 1; + if (flags & EX_NOALLOC) + result = (char *)NULL; + else + { + result = (char *)xmalloc (1 + si); + strncpy (result, string + *sindex, si); + result[si] = '\0'; + } + *sindex = i; + + return (result); +} + +/* Extract a parameter expansion expression within ${ and } from STRING. + Obey the Posix.2 rules for finding the ending `}': count braces while + skipping over enclosed quoted strings and command substitutions. + SINDEX is the address of an int describing the current offset in STRING; + it should point to just after the first `{' found. On exit, SINDEX + gets the position of the matching `}'. QUOTED is non-zero if this + occurs inside double quotes. */ +/* XXX -- this is very similar to extract_delimited_string -- XXX */ +static char * +extract_dollar_brace_string (string, sindex, quoted, flags) + char *string; + int *sindex, quoted, flags; +{ + register int i, c; + size_t slen; + int pass_character, nesting_level, si; + char *result, *t; + DECLARE_MBSTATE; + + pass_character = 0; + nesting_level = 1; + slen = strlen (string + *sindex) + *sindex; + + i = *sindex; + while (c = string[i]) + { + if (pass_character) + { + pass_character = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + + /* CTLESCs and backslashes quote the next character. */ + if (c == CTLESC || c == '\\') + { + pass_character++; + i++; + continue; + } + + if (string[i] == '$' && string[i+1] == LBRACE) + { + nesting_level++; + i += 2; + continue; + } + + if (c == RBRACE) + { + nesting_level--; + if (nesting_level == 0) + break; + i++; + continue; + } + + /* Pass the contents of old-style command substitutions through + verbatim. */ + if (c == '`') + { + si = i + 1; + t = string_extract (string, &si, "`", flags|EX_NOALLOC); + i = si + 1; + continue; + } + + /* Pass the contents of new-style command substitutions and + arithmetic substitutions through verbatim. */ + if (string[i] == '$' && string[i+1] == LPAREN) + { + si = i + 2; + t = extract_delimited_string (string, &si, "$(", "(", ")", flags|EX_NOALLOC); /*)*/ + i = si + 1; + continue; + } + + /* Pass the contents of single-quoted and double-quoted strings + through verbatim. */ + if (c == '\'' || c == '"') + { + si = i + 1; + i = (c == '\'') ? skip_single_quoted (string, slen, si) + : skip_double_quoted (string, slen, si); + /* skip_XXX_quoted leaves index one past close quote */ + continue; + } + + /* move past this character, which was not special. */ + ADVANCE_CHAR (string, slen, i); + } + + if (c == 0 && nesting_level) + { + if (no_longjmp_on_fatal_error == 0) + { /* { */ + report_error ("bad substitution: no closing `%s' in %s", "}", string); + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level (DISCARD); + } + else + { + *sindex = i; + return ((char *)NULL); + } + } + + result = (flags & EX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i); + *sindex = i; + + return (result); +} + +/* Remove backslashes which are quoting backquotes from STRING. Modifies + STRING, and returns a pointer to it. */ +char * +de_backslash (string) + char *string; +{ + register size_t slen; + register int i, j, prev_i; + DECLARE_MBSTATE; + + slen = strlen (string); + i = j = 0; + + /* Loop copying string[i] to string[j], i >= j. */ + while (i < slen) + { + if (string[i] == '\\' && (string[i + 1] == '`' || string[i + 1] == '\\' || + string[i + 1] == '$')) + i++; + prev_i = i; + ADVANCE_CHAR (string, slen, i); + if (j < prev_i) + do string[j++] = string[prev_i++]; while (prev_i < i); + else + j = i; + } + string[j] = '\0'; + + return (string); +} + +#if 0 +/*UNUSED*/ +/* Replace instances of \! in a string with !. */ +void +unquote_bang (string) + char *string; +{ + register int i, j; + register char *temp; + + temp = (char *)xmalloc (1 + strlen (string)); + + for (i = 0, j = 0; (temp[j] = string[i]); i++, j++) + { + if (string[i] == '\\' && string[i + 1] == '!') + { + temp[j] = '!'; + i++; + } + } + strcpy (string, temp); + free (temp); +} +#endif + +#if defined (READLINE) +/* Return 1 if the portion of STRING ending at EINDEX is quoted (there is + an unclosed quoted string), or if the character at EINDEX is quoted + by a backslash. NO_LONGJMP_ON_FATAL_ERROR is used to flag that the various + single and double-quoted string parsing functions should not return an + error if there are unclosed quotes or braces. The characters that this + recognizes need to be the same as the contents of + rl_completer_quote_characters. */ + +#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0) + +int +char_is_quoted (string, eindex) + char *string; + int eindex; +{ + int i, pass_next, c; + size_t slen; + DECLARE_MBSTATE; + + slen = strlen (string); + no_longjmp_on_fatal_error = 1; + i = pass_next = 0; + while (i <= eindex) + { + c = string[i]; + + if (pass_next) + { + pass_next = 0; + if (i >= eindex) /* XXX was if (i >= eindex - 1) */ + CQ_RETURN(1); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (c == '\'' || c == '"') + { + i = (c == '\'') ? skip_single_quoted (string, slen, ++i) + : skip_double_quoted (string, slen, ++i); + if (i > eindex) + CQ_RETURN(1); + /* no increment, the skip_xxx functions go one past end */ + } + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(0); +} + +int +unclosed_pair (string, eindex, openstr) + char *string; + int eindex; + char *openstr; +{ + int i, pass_next, openc, olen; + size_t slen; + DECLARE_MBSTATE; + + slen = strlen (string); + olen = strlen (openstr); + i = pass_next = openc = 0; + while (i <= eindex) + { + if (pass_next) + { + pass_next = 0; + if (i >= eindex) /* XXX was if (i >= eindex - 1) */ + return 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (string[i] == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (STREQN (string + i, openstr, olen)) + { + openc = 1 - openc; + i += olen; + } + else if (string[i] == '\'' || string[i] == '"') + { + i = (string[i] == '\'') ? skip_single_quoted (string, slen, i) + : skip_double_quoted (string, slen, i); + if (i > eindex) + return 0; + } + else + ADVANCE_CHAR (string, slen, i); + } + return (openc); +} + +/* Skip characters in STRING until we find a character in DELIMS, and return + the index of that character. START is the index into string at which we + begin. This is similar in spirit to strpbrk, but it returns an index into + STRING and takes a starting index. This little piece of code knows quite + a lot of shell syntax. It's very similar to skip_double_quoted and other + functions of that ilk. */ +int +skip_to_delim (string, start, delims) + char *string; + int start; + char *delims; +{ + int i, pass_next, backq, si, c; + size_t slen; + char *temp; + DECLARE_MBSTATE; + + slen = strlen (string + start) + start; + no_longjmp_on_fatal_error = 1; + i = start; + pass_next = backq = 0; + while (c = string[i]) + { + if (pass_next) + { + pass_next = 0; + if (c == 0) + CQ_RETURN(i); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (backq) + { + if (c == '`') + backq = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '`') + { + backq = 1; + i++; + continue; + } + else if (c == '\'' || c == '"') + { + i = (c == '\'') ? skip_single_quoted (string, slen, ++i) + : skip_double_quoted (string, slen, ++i); + /* no increment, the skip functions increment past the closing quote. */ + } + else if (c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE)) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + + if (string[i+1] == LPAREN) + temp = extract_delimited_string (string, &si, "$(", "(", ")", EX_NOALLOC); /* ) */ + else + temp = extract_dollar_brace_string (string, &si, 0, EX_NOALLOC); + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } + else if (member (c, delims)) + break; + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(i); +} + +/* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the + individual words. If DELIMS is NULL, the current value of $IFS is used + to split the string, and the function follows the shell field splitting + rules. SENTINEL is an index to look for. NWP, if non-NULL, + gets the number of words in the returned list. CWP, if non-NULL, gets + the index of the word containing SENTINEL. Non-whitespace chars in + DELIMS delimit separate fields. */ +WORD_LIST * +split_at_delims (string, slen, delims, sentinel, nwp, cwp) + char *string; + int slen; + char *delims; + int sentinel; + int *nwp, *cwp; +{ + int ts, te, i, nw, cw, ifs_split; + char *token, *d, *d2; + WORD_LIST *ret, *tl; + + if (string == 0 || *string == '\0') + { + if (nwp) + *nwp = 0; + if (cwp) + *cwp = 0; + return ((WORD_LIST *)NULL); + } + + d = (delims == 0) ? ifs_value : delims; + ifs_split = delims == 0; + + /* Make d2 the non-whitespace characters in delims */ + d2 = 0; + if (delims) + { + d2 = (char *)xmalloc (strlen (delims) + 1); + for (i = ts = 0; delims[i]; i++) + { + if (whitespace(delims[i]) == 0) + d2[ts++] = delims[i]; + } + d2[ts] = '\0'; + } + + ret = (WORD_LIST *)NULL; + + /* Remove sequences of whitspace characters at the start of the string, as + long as those characters are delimiters. */ + for (i = 0; member (string[i], d) && spctabnl (string[i]); i++) + ; + if (string[i] == '\0') + return (ret); + + ts = i; + nw = 0; + cw = -1; + while (1) + { + te = skip_to_delim (string, ts, d); + + /* If we have a non-whitespace delimiter character, use it to make a + separate field. This is just about what $IFS splitting does and + is closer to the behavior of the shell parser. */ + if (ts == te && d2 && member (string[ts], d2)) + { + te = ts + 1; + /* If we're using IFS splitting, the non-whitespace delimiter char + and any additional IFS whitespace delimits a field. */ + if (ifs_split) + while (member (string[te], d) && spctabnl (string[te])) + te++; + else + while (member (string[te], d2)) + te++; + } + + token = substring (string, ts, te); + + ret = add_string_to_list (token, ret); + free (token); + nw++; + + if (sentinel >= ts && sentinel <= te) + cw = nw; + + /* If the cursor is at whitespace just before word start, set the + sentinel word to the current word. */ + if (cwp && cw == -1 && sentinel == ts-1) + cw = nw; + + /* If the cursor is at whitespace between two words, make a new, empty + word, add it before (well, after, since the list is in reverse order) + the word we just added, and set the current word to that one. */ + if (cwp && cw == -1 && sentinel < ts) + { + tl = make_word_list (make_word (""), ret->next); + ret->next = tl; + cw = nw; + nw++; + } + + if (string[te] == 0) + break; + + i = te; + while (member (string[i], d) && (ifs_split || spctabnl(string[i]))) + i++; + + if (string[i]) + ts = i; + else + break; + } + + /* Special case for SENTINEL at the end of STRING. If we haven't found + the word containing SENTINEL yet, and the index we're looking for is at + the end of STRING, add an additional null argument and set the current + word pointer to that. */ + if (cwp && cw == -1 && sentinel >= slen) + { + if (whitespace (string[sentinel - 1])) + { + token = ""; + ret = add_string_to_list (token, ret); + nw++; + } + cw = nw; + } + + if (nwp) + *nwp = nw; + if (cwp) + *cwp = cw; + + return (REVERSE_LIST (ret, WORD_LIST *)); +} +#endif /* READLINE */ + +#if 0 +/* UNUSED */ +/* Extract the name of the variable to bind to from the assignment string. */ +char * +assignment_name (string) + char *string; +{ + int offset; + char *temp; + + offset = assignment (string, 0); + if (offset == 0) + return (char *)NULL; + temp = substring (string, 0, offset); + return (temp); +} +#endif + +/* **************************************************************** */ +/* */ +/* Functions to convert strings to WORD_LISTs and vice versa */ +/* */ +/* **************************************************************** */ + +/* Return a single string of all the words in LIST. SEP is the separator + to put between individual elements of LIST in the output string. */ +char * +string_list_internal (list, sep) + WORD_LIST *list; + char *sep; +{ + register WORD_LIST *t; + char *result, *r; + int word_len, sep_len, result_size; + + if (list == 0) + return ((char *)NULL); + + /* Short-circuit quickly if we don't need to separate anything. */ + if (list->next == 0) + return (savestring (list->word->word)); + + /* This is nearly always called with either sep[0] == 0 or sep[1] == 0. */ + sep_len = STRLEN (sep); + result_size = 0; + + for (t = list; t; t = t->next) + { + if (t != list) + result_size += sep_len; + result_size += strlen (t->word->word); + } + + r = result = (char *)xmalloc (result_size + 1); + + for (t = list; t; t = t->next) + { + if (t != list && sep_len) + { + if (sep_len > 1) + { + FASTCOPY (sep, r, sep_len); + r += sep_len; + } + else + *r++ = sep[0]; + } + + word_len = strlen (t->word->word); + FASTCOPY (t->word->word, r, word_len); + r += word_len; + } + + *r = '\0'; + return (result); +} + +/* Return a single string of all the words present in LIST, separating + each word with a space. */ +char * +string_list (list) + WORD_LIST *list; +{ + return (string_list_internal (list, " ")); +} + +/* Return a single string of all the words present in LIST, obeying the + quoting rules for "$*", to wit: (P1003.2, draft 11, 3.5.2) "If the + expansion [of $*] appears within a double quoted string, it expands + to a single field with the value of each parameter separated by the + first character of the IFS variable, or by a if IFS is unset." */ +char * +string_list_dollar_star (list) + WORD_LIST *list; +{ + char sep[2]; + + sep[0] = ifs_firstc; + sep[1] = '\0'; + + return (string_list_internal (list, sep)); +} + +/* Turn $@ into a string. If (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + is non-zero, the $@ appears within double quotes, and we should quote + the list before converting it into a string. If IFS is unset, and the + word is not quoted, we just need to quote CTLESC and CTLNUL characters + in the words in the list, because the default value of $IFS is + , IFS characters in the words in the list should + also be split. If IFS is null, and the word is not quoted, we need + to quote the words in the list to preserve the positional parameters + exactly. */ +char * +string_list_dollar_at (list, quoted) + WORD_LIST *list; + int quoted; +{ + char *ifs, sep[2]; + WORD_LIST *tlist; + + /* XXX this could just be ifs = ifs_value; */ + ifs = ifs_var ? value_cell (ifs_var) : (char *)0; + + sep[0] = (ifs == 0 || *ifs == 0) ? ' ' : *ifs; + sep[1] = '\0'; + + tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0)) + ? quote_list (list) + : list_quote_escapes (list); + return (string_list_internal (tlist, sep)); +} + +/* Return the list of words present in STRING. Separate the string into + words at any of the characters found in SEPARATORS. If QUOTED is + non-zero then word in the list will have its quoted flag set, otherwise + the quoted flag is left as make_word () deemed fit. + + This obeys the P1003.2 word splitting semantics. If `separators' is + exactly , then the splitting algorithm is that of + the Bourne shell, which treats any sequence of characters from `separators' + as a delimiter. If IFS is unset, which results in `separators' being set + to "", no splitting occurs. If separators has some other value, the + following rules are applied (`IFS white space' means zero or more + occurrences of , , or , as long as those characters + are in `separators'): + + 1) IFS white space is ignored at the start and the end of the + string. + 2) Each occurrence of a character in `separators' that is not + IFS white space, along with any adjacent occurrences of + IFS white space delimits a field. + 3) Any nonzero-length sequence of IFS white space delimits a field. + */ + +/* BEWARE! list_string strips null arguments. Don't call it twice and + expect to have "" preserved! */ + +/* This performs word splitting and quoted null character removal on + STRING. */ +#define issep(c) \ + (((separators)[0]) ? ((separators)[1] ? isifs(c) \ + : (c) == (separators)[0]) \ + : 0) + +WORD_LIST * +list_string (string, separators, quoted) + register char *string, *separators; + int quoted; +{ + WORD_LIST *result; + WORD_DESC *t; + char *current_word, *s; + int sindex, sh_style_split, whitesep; + + if (!string || !*string) + return ((WORD_LIST *)NULL); + + sh_style_split = separators && separators[0] == ' ' && + separators[1] == '\t' && + separators[2] == '\n' && + separators[3] == '\0'; + + /* Remove sequences of whitespace at the beginning of STRING, as + long as those characters appear in IFS. Do not do this if + STRING is quoted or if there are no separator characters. */ + if (!quoted || !separators || !*separators) + { + for (s = string; *s && spctabnl (*s) && issep (*s); s++); + + if (!*s) + return ((WORD_LIST *)NULL); + + string = s; + } + + /* OK, now STRING points to a word that does not begin with white space. + The splitting algorithm is: + extract a word, stopping at a separator + skip sequences of spc, tab, or nl as long as they are separators + This obeys the field splitting rules in Posix.2. */ + for (result = (WORD_LIST *)NULL, sindex = 0; string[sindex]; ) + { + current_word = string_extract_verbatim (string, &sindex, separators); + if (current_word == 0) + break; + + /* If we have a quoted empty string, add a quoted null argument. We + want to preserve the quoted null character iff this is a quoted + empty string; otherwise the quoted null characters are removed + below. */ + if (QUOTED_NULL (current_word)) + { + t = make_bare_word (""); + t->flags |= W_QUOTED; + free (t->word); + t->word = make_quoted_char ('\0'); + result = make_word_list (t, result); + } + else if (current_word[0] != '\0') + { + /* If we have something, then add it regardless. However, + perform quoted null character removal on the current word. */ + remove_quoted_nulls (current_word); + result = add_string_to_list (current_word, result); + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + result->word->flags |= W_QUOTED; + } + + /* If we're not doing sequences of separators in the traditional + Bourne shell style, then add a quoted null argument. */ + else if (!sh_style_split && !spctabnl (string[sindex])) + { + t = make_bare_word (""); + t->flags |= W_QUOTED; + free (t->word); + t->word = make_quoted_char ('\0'); + result = make_word_list (t, result); + } + + free (current_word); + + /* Note whether or not the separator is IFS whitespace, used later. */ + whitesep = string[sindex] && spctabnl (string[sindex]); + + /* Move past the current separator character. */ + if (string[sindex]) + sindex++; + + /* Now skip sequences of space, tab, or newline characters if they are + in the list of separators. */ + while (string[sindex] && spctabnl (string[sindex]) && issep (string[sindex])) + sindex++; + + /* If the first separator was IFS whitespace and the current character + is a non-whitespace IFS character, it should be part of the current + field delimiter, not a separate delimiter that would result in an + empty field. Look at POSIX.2, 3.6.5, (3)(b). */ + if (string[sindex] && whitesep && issep (string[sindex]) && !spctabnl (string[sindex])) + sindex++; + } + return (REVERSE_LIST (result, WORD_LIST *)); +} + +/* Parse a single word from STRING, using SEPARATORS to separate fields. + ENDPTR is set to the first character after the word. This is used by + the `read' builtin. This is never called with SEPARATORS != $IFS; + it should be simplified. + + XXX - this function is very similar to list_string; they should be + combined - XXX */ +char * +get_word_from_string (stringp, separators, endptr) + char **stringp, *separators, **endptr; +{ + register char *s; + char *current_word; + int sindex, sh_style_split, whitesep; + + if (!stringp || !*stringp || !**stringp) + return ((char *)NULL); + + s = *stringp; + + sh_style_split = separators && separators[0] == ' ' && + separators[1] == '\t' && + separators[2] == '\n' && + separators[3] == '\0'; + + /* Remove sequences of whitespace at the beginning of STRING, as + long as those characters appear in IFS. */ + if (sh_style_split || !separators || !*separators) + { + for (; *s && spctabnl (*s) && isifs (*s); s++); + + /* If the string is nothing but whitespace, update it and return. */ + if (!*s) + { + *stringp = s; + if (endptr) + *endptr = s; + return ((char *)NULL); + } + } + + /* OK, S points to a word that does not begin with white space. + Now extract a word, stopping at a separator, save a pointer to + the first character after the word, then skip sequences of spc, + tab, or nl as long as they are separators. + + This obeys the field splitting rules in Posix.2. */ + sindex = 0; + current_word = string_extract_verbatim (s, &sindex, separators); + + /* Set ENDPTR to the first character after the end of the word. */ + if (endptr) + *endptr = s + sindex; + + /* Note whether or not the separator is IFS whitespace, used later. */ + whitesep = s[sindex] && spctabnl (s[sindex]); + + /* Move past the current separator character. */ + if (s[sindex]) + sindex++; + + /* Now skip sequences of space, tab, or newline characters if they are + in the list of separators. */ + while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex])) + sindex++; + + /* If the first separator was IFS whitespace and the current character is + a non-whitespace IFS character, it should be part of the current field + delimiter, not a separate delimiter that would result in an empty field. + Look at POSIX.2, 3.6.5, (3)(b). */ + if (s[sindex] && whitesep && isifs (s[sindex]) && !spctabnl (s[sindex])) + sindex++; + + /* Update STRING to point to the next field. */ + *stringp = s + sindex; + return (current_word); +} + +/* Remove IFS white space at the end of STRING. Start at the end + of the string and walk backwards until the beginning of the string + or we find a character that's not IFS white space and not CTLESC. + Only let CTLESC escape a white space character if SAW_ESCAPE is + non-zero. */ +char * +strip_trailing_ifs_whitespace (string, separators, saw_escape) + char *string, *separators; + int saw_escape; +{ + char *s; + + s = string + STRLEN (string) - 1; + while (s > string && ((spctabnl (*s) && isifs (*s)) || + (saw_escape && *s == CTLESC && spctabnl (s[1])))) + s--; + *++s = '\0'; + return string; +} + +#if 0 +/* UNUSED */ +/* Split STRING into words at whitespace. Obeys shell-style quoting with + backslashes, single and double quotes. */ +WORD_LIST * +list_string_with_quotes (string) + char *string; +{ + WORD_LIST *list; + char *token, *s; + size_t s_len; + int c, i, tokstart, len; + + for (s = string; s && *s && spctabnl (*s); s++) + ; + if (s == 0 || *s == 0) + return ((WORD_LIST *)NULL); + + s_len = strlen (s); + tokstart = i = 0; + list = (WORD_LIST *)NULL; + while (1) + { + c = s[i]; + if (c == '\\') + { + i++; + if (s[i]) + i++; + } + else if (c == '\'') + i = skip_single_quoted (s, s_len, ++i); + else if (c == '"') + i = skip_double_quoted (s, s_len, ++i); + else if (c == 0 || spctabnl (c)) + { + /* We have found the end of a token. Make a word out of it and + add it to the word list. */ + token = substring (s, tokstart, i); + list = add_string_to_list (token, list); + free (token); + while (spctabnl (s[i])) + i++; + if (s[i]) + tokstart = i; + else + break; + } + else + i++; /* normal character */ + } + return (REVERSE_LIST (list, WORD_LIST *)); +} +#endif + +/********************************************************/ +/* */ +/* Functions to perform assignment statements */ +/* */ +/********************************************************/ + +/* Given STRING, an assignment string, get the value of the right side + of the `=', and bind it to the left side. If EXPAND is true, then + perform parameter expansion, command substitution, and arithmetic + expansion on the right-hand side. Perform tilde expansion in any + case. Do not perform word splitting on the result of expansion. */ +static int +do_assignment_internal (string, expand) + const char *string; + int expand; +{ + int offset; + char *name, *value; + SHELL_VAR *entry; +#if defined (ARRAY_VARS) + char *t; + int ni; +#endif + int assign_list = 0; + + offset = assignment (string, 0); + name = savestring (string); + value = (char *)NULL; + + if (name[offset] == '=') + { + char *temp; + + name[offset] = 0; + temp = name + offset + 1; + +#if defined (ARRAY_VARS) + if (expand && temp[0] == LPAREN && xstrchr (temp, RPAREN)) + { + assign_list = ni = 1; + value = extract_delimited_string (temp, &ni, "(", (char *)NULL, ")", 0); + } + else +#endif + + /* Perform tilde expansion. */ + if (expand && temp[0]) + { + temp = (xstrchr (temp, '~') && unquoted_member ('~', temp)) + ? bash_tilde_expand (temp, 1) + : savestring (temp); + + value = expand_string_if_necessary (temp, 0, expand_string_unsplit); + free (temp); + } + else + value = savestring (temp); + } + + if (value == 0) + { + value = (char *)xmalloc (1); + value[0] = '\0'; + } + + if (echo_command_at_execute) + xtrace_print_assignment (name, value, assign_list, 1); + +#define ASSIGN_RETURN(r) do { FREE (value); free (name); return (r); } while (0) + +#if defined (ARRAY_VARS) + if (t = xstrchr (name, '[')) /*]*/ + { + if (assign_list) + { + report_error (_("%s: cannot assign list to array member"), name); + ASSIGN_RETURN (0); + } + entry = assign_array_element (name, value); + if (entry == 0) + ASSIGN_RETURN (0); + } + else if (assign_list) + entry = assign_array_from_string (name, value); + else +#endif /* ARRAY_VARS */ + entry = bind_variable (name, value); + + stupidly_hack_special_variables (name); + + if (entry) + VUNSETATTR (entry, att_invisible); + + /* Return 1 if the assignment seems to have been performed correctly. */ + ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0); +} + +/* Perform the assignment statement in STRING, and expand the + right side by doing command and parameter expansion. */ +int +do_assignment (string) + const char *string; +{ + return do_assignment_internal (string, 1); +} + +/* Given STRING, an assignment string, get the value of the right side + of the `=', and bind it to the left side. Do not do command and + parameter substitution on the right hand side. */ +int +do_assignment_no_expand (string) + const char *string; +{ + return do_assignment_internal (string, 0); +} + +/*************************************************** + * * + * Functions to manage the positional parameters * + * * + ***************************************************/ + +/* Return the word list that corresponds to `$*'. */ +WORD_LIST * +list_rest_of_args () +{ + register WORD_LIST *list, *args; + int i; + + /* Break out of the loop as soon as one of the dollar variables is null. */ + for (i = 1, list = (WORD_LIST *)NULL; i < 10 && dollar_vars[i]; i++) + list = make_word_list (make_bare_word (dollar_vars[i]), list); + + for (args = rest_of_args; args; args = args->next) + list = make_word_list (make_bare_word (args->word->word), list); + + return (REVERSE_LIST (list, WORD_LIST *)); +} + +int +number_of_args () +{ + register WORD_LIST *list; + int n; + + for (n = 0; n < 9 && dollar_vars[n+1]; n++) + ; + for (list = rest_of_args; list; list = list->next) + n++; + return n; +} + +/* Return the value of a positional parameter. This handles values > 10. */ +char * +get_dollar_var_value (ind) + intmax_t ind; +{ + char *temp; + WORD_LIST *p; + + if (ind < 10) + temp = dollar_vars[ind] ? savestring (dollar_vars[ind]) : (char *)NULL; + else /* We want something like ${11} */ + { + ind -= 10; + for (p = rest_of_args; p && ind--; p = p->next) + ; + temp = p ? savestring (p->word->word) : (char *)NULL; + } + return (temp); +} + +/* Make a single large string out of the dollar digit variables, + and the rest_of_args. If DOLLAR_STAR is 1, then obey the special + case of "$*" with respect to IFS. */ +char * +string_rest_of_args (dollar_star) + int dollar_star; +{ + register WORD_LIST *list; + char *string; + + list = list_rest_of_args (); + string = dollar_star ? string_list_dollar_star (list) : string_list (list); + dispose_words (list); + return (string); +} + +/* Return a string containing the positional parameters from START to + END, inclusive. If STRING[0] == '*', we obey the rules for $*, + which only makes a difference if QUOTED is non-zero. If QUOTED includes + Q_HERE_DOCUMENT or Q_DOUBLE_QUOTES, this returns a quoted list, otherwise + no quoting chars are added. */ +static char * +pos_params (string, start, end, quoted) + char *string; + int start, end, quoted; +{ + WORD_LIST *save, *params, *h, *t; + char *ret; + int i; + + /* see if we can short-circuit. if start == end, we want 0 parameters. */ + if (start == end) + return ((char *)NULL); + + save = params = list_rest_of_args (); + if (save == 0) + return ((char *)NULL); + + for (i = 1; params && i < start; i++) + params = params->next; + if (params == 0) + return ((char *)NULL); + for (h = t = params; params && i < end; i++) + { + t = params; + params = params->next; + } + + t->next = (WORD_LIST *)NULL; + if (string[0] == '*') +#if 0 + ret = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? string_list_dollar_star (quote_list (h)) : string_list (h); +#else + { + if (quoted & Q_DOUBLE_QUOTES) + ret = string_list_dollar_star (quote_list (h)); + else if (quoted & Q_HERE_DOCUMENT) + ret = string_list (quote_list (h)); + else + ret = string_list (h); + } +#endif + else + ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h); + if (t != params) + t->next = params; + + dispose_words (save); + return (ret); +} + +/******************************************************************/ +/* */ +/* Functions to expand strings to strings or WORD_LISTs */ +/* */ +/******************************************************************/ + +#if defined (PROCESS_SUBSTITUTION) +#define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC) +#else +#define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC) +#endif + +/* If there are any characters in STRING that require full expansion, + then call FUNC to expand STRING; otherwise just perform quote + removal if necessary. This returns a new string. */ +static char * +expand_string_if_necessary (string, quoted, func) + char *string; + int quoted; + EXPFUNC *func; +{ + WORD_LIST *list; + size_t slen; + int i, saw_quote; + char *ret; + DECLARE_MBSTATE; + + slen = strlen (string); + i = saw_quote = 0; + while (string[i]) + { + if (EXP_CHAR (string[i])) + break; + else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"') + saw_quote = 1; + ADVANCE_CHAR (string, slen, i); + } + + if (string[i]) + { + list = (*func) (string, quoted); + if (list) + { + ret = string_list (list); + dispose_words (list); + } + else + ret = (char *)NULL; + } + else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + ret = string_quote_removal (string, quoted); + else + ret = savestring (string); + + return ret; +} + +static inline char * +expand_string_to_string_internal (string, quoted, func) + char *string; + int quoted; + EXPFUNC *func; +{ + WORD_LIST *list; + char *ret; + + if (string == 0 || *string == '\0') + return ((char *)NULL); + + list = (*func) (string, quoted); + if (list) + { + ret = string_list (list); + dispose_words (list); + } + else + ret = (char *)NULL; + + return (ret); +} + +char * +expand_string_to_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_to_string_internal (string, quoted, expand_string)); +} + +char * +expand_string_unsplit_to_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_to_string_internal (string, quoted, expand_string_unsplit)); +} + +#if defined (COND_COMMAND) +/* Just remove backslashes in STRING. Returns a new string. */ +char * +remove_backslashes (string) + char *string; +{ + char *r, *ret, *s; + + r = ret = (char *)xmalloc (strlen (string) + 1); + for (s = string; s && *s; ) + { + if (*s == '\\') + s++; + if (*s == 0) + break; + *r++ = *s++; + } + *r = '\0'; + return ret; +} + +/* This needs better error handling. */ +/* Expand W for use as an argument to a unary or binary operator in a + [[...]] expression. If SPECIAL is nonzero, this is the rhs argument + to the != or == operator, and should be treated as a pattern. In + this case, we quote the string specially for the globbing code. The + caller is responsible for removing the backslashes if the unquoted + words is needed later. */ +char * +cond_expand_word (w, special) + WORD_DESC *w; + int special; +{ + char *r, *p; + WORD_LIST *l; + + if (w->word == 0 || w->word[0] == '\0') + return ((char *)NULL); + + if (xstrchr (w->word, '~') && unquoted_member ('~', w->word)) + { + p = bash_tilde_expand (w->word, 0); + free (w->word); + w->word = p; + } + + l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0); + if (l) + { + if (special == 0) + { + dequote_list (l); + r = string_list (l); + } + else + { + p = string_list (l); + r = quote_string_for_globbing (p, QGLOB_CVTNULL); + free (p); + } + dispose_words (l); + } + else + r = (char *)NULL; + + return r; +} +#endif + +/* Call expand_word_internal to expand W and handle error returns. + A convenience function for functions that don't want to handle + any errors or free any memory before aborting. */ +static WORD_LIST * +call_expand_word_internal (w, q, i, c, e) + WORD_DESC *w; + int q, i, *c, *e; +{ + WORD_LIST *result; + + result = expand_word_internal (w, q, i, c, e); + if (result == &expand_word_error || result == &expand_word_fatal) + { + /* By convention, each time this error is returned, w->word has + already been freed (it sometimes may not be in the fatal case, + but that doesn't result in a memory leak because we're going + to exit in most cases). */ + w->word = (char *)NULL; + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF); + /* NOTREACHED */ + } + else + return (result); +} + +/* Perform parameter expansion, command substitution, and arithmetic + expansion on STRING, as if it were a word. Leave the result quoted. */ +static WORD_LIST * +expand_string_internal (string, quoted) + char *string; + int quoted; +{ + WORD_DESC td; + WORD_LIST *tresult; + + if (string == 0 || *string == 0) + return ((WORD_LIST *)NULL); + + td.flags = 0; + td.word = savestring (string); + + tresult = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + + FREE (td.word); + return (tresult); +} + +/* Expand STRING by performing parameter expansion, command substitution, + and arithmetic expansion. Dequote the resulting WORD_LIST before + returning it, but do not perform word splitting. The call to + remove_quoted_nulls () is in here because word splitting normally + takes care of quote removal. */ +WORD_LIST * +expand_string_unsplit (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *value; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + expand_no_split_dollar_star = 1; + value = expand_string_internal (string, quoted); + expand_no_split_dollar_star = 0; + + if (value) + { + if (value->word) + remove_quoted_nulls (value->word->word); + dequote_list (value); + } + return (value); +} + + +/* Expand one of the PS? prompt strings. This is a sort of combination of + expand_string_unsplit and expand_string_internal, but returns the + passed string when an error occurs. Might want to trap other calls + to jump_to_top_level here so we don't endlessly loop. */ +WORD_LIST * +expand_prompt_string (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *value; + WORD_DESC td; + + if (string == 0 || *string == 0) + return ((WORD_LIST *)NULL); + + td.flags = 0; + td.word = savestring (string); + + no_longjmp_on_fatal_error = 1; + value = expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + no_longjmp_on_fatal_error = 0; + + if (value == &expand_word_error || value == &expand_word_fatal) + { + value = make_word_list (make_bare_word (string), (WORD_LIST *)NULL); + return value; + } + FREE (td.word); + if (value) + { + if (value->word) + remove_quoted_nulls (value->word->word); + dequote_list (value); + } + return (value); +} + +/* Expand STRING just as if you were expanding a word, but do not dequote + the resultant WORD_LIST. This is called only from within this file, + and is used to correctly preserve quoted characters when expanding + things like ${1+"$@"}. This does parameter expansion, command + substitution, arithmetic expansion, and word splitting. */ +static WORD_LIST * +expand_string_leave_quoted (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *tlist; + WORD_LIST *tresult; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + tlist = expand_string_internal (string, quoted); + + if (tlist) + { + tresult = word_list_split (tlist); + dispose_words (tlist); + return (tresult); + } + return ((WORD_LIST *)NULL); +} + +/* This does not perform word splitting or dequote the WORD_LIST + it returns. */ +static WORD_LIST * +expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at) + char *string; + int quoted, *dollar_at_p, *has_dollar_at; +{ + WORD_DESC td; + WORD_LIST *tresult; + + if (string == 0 || *string == '\0') + return (WORD_LIST *)NULL; + + td.flags = 0; + td.word = string; + tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at); + return (tresult); +} + +/* Expand STRING just as if you were expanding a word. This also returns + a list of words. Note that filename globbing is *NOT* done for word + or string expansion, just when the shell is expanding a command. This + does parameter expansion, command substitution, arithmetic expansion, + and word splitting. Dequote the resultant WORD_LIST before returning. */ +WORD_LIST * +expand_string (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *result; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + result = expand_string_leave_quoted (string, quoted); + return (result ? dequote_list (result) : result); +} + +/*************************************************** + * * + * Functions to handle quoting chars * + * * + ***************************************************/ + +/* Conventions: + + A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string. + The parser passes CTLNUL as CTLESC CTLNUL. */ + +/* Quote escape characters in string s, but no other characters. This is + used to protect CTLESC and CTLNUL in variable values from the rest of + the word expansion process after the variable is expanded. */ +char * +quote_escapes (string) + char *string; +{ + register char *s, *t; + size_t slen; + char *result, *send; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + t = result = (char *)xmalloc ((slen * 2) + 1); + s = string; + + while (*s) + { + if (*s == CTLESC || *s == CTLNUL) + *t++ = CTLESC; + COPY_CHAR_P (t, s, send); + } + *t = '\0'; + return (result); +} + +static WORD_LIST * +list_quote_escapes (list) + WORD_LIST *list; +{ + register WORD_LIST *w; + char *t; + + for (w = list; w; w = w->next) + { + t = w->word->word; + w->word->word = quote_escapes (t); + free (t); + } + return list; +} + +/* Inverse of quote_escapes; remove CTLESC protecting CTLESC or CTLNUL. + + The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL. + This is necessary to make unquoted CTLESC and CTLNUL characters in the + data stream pass through properly. + + We need to remove doubled CTLESC characters inside quoted strings before + quoting the entire string, so we do not double the number of CTLESC + characters. + + Also used by parts of the pattern substitution code. */ +static char * +dequote_escapes (string) + char *string; +{ + register char *s, *t; + size_t slen; + char *result, *send; + DECLARE_MBSTATE; + + if (string == 0) + return string; + + slen = strlen (string); + send = string + slen; + + t = result = (char *)xmalloc (slen + 1); + s = string; + + if (strchr (string, CTLESC) == 0) + return (strcpy (result, s)); + + while (*s) + { + if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL)) + { + s++; + if (*s == '\0') + break; + } + COPY_CHAR_P (t, s, send); + } + *t = '\0'; + return result; +} + +/* Return a new string with the quoted representation of character C. */ +static char * +make_quoted_char (c) + int c; +{ + char *temp; + + temp = (char *)xmalloc (3); + if (c == 0) + { + temp[0] = CTLNUL; + temp[1] = '\0'; + } + else + { + temp[0] = CTLESC; + temp[1] = c; + temp[2] = '\0'; + } + return (temp); +} + +/* Quote STRING. Return a new string. */ +char * +quote_string (string) + char *string; +{ + register char *t; + size_t slen; + char *result, *send; + + if (*string == 0) + { + result = (char *)xmalloc (2); + result[0] = CTLNUL; + result[1] = '\0'; + } + else + { + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + result = (char *)xmalloc ((slen * 2) + 1); + + for (t = result; string < send; ) + { + *t++ = CTLESC; + COPY_CHAR_P (t, string, send); + } + *t = '\0'; + } + return (result); +} + +/* De-quoted quoted characters in STRING. */ +char * +dequote_string (string) + char *string; +{ + register char *s, *t; + size_t slen; + char *result, *send; + DECLARE_MBSTATE; + + slen = strlen (string); + + t = result = (char *)xmalloc (slen + 1); + + if (QUOTED_NULL (string)) + { + result[0] = '\0'; + return (result); + } + + /* If no character in the string can be quoted, don't bother examining + each character. Just return a copy of the string passed to us. */ + if (strchr (string, CTLESC) == NULL) + return (strcpy (result, string)); + + send = string + slen; + s = string; + while (*s) + { + if (*s == CTLESC) + { + s++; + if (*s == '\0') + break; + } + COPY_CHAR_P (t, s, send); + } + + *t = '\0'; + return (result); +} + +/* Quote the entire WORD_LIST list. */ +static WORD_LIST * +quote_list (list) + WORD_LIST *list; +{ + register WORD_LIST *w; + char *t; + + for (w = list; w; w = w->next) + { + t = w->word->word; + w->word->word = quote_string (t); + free (t); + w->word->flags |= W_QUOTED; + } + return list; +} + +static WORD_LIST * +dequote_list (list) + WORD_LIST *list; +{ + register char *s; + register WORD_LIST *tlist; + + for (tlist = list; tlist; tlist = tlist->next) + { + s = dequote_string (tlist->word->word); + free (tlist->word->word); + tlist->word->word = s; + } + return list; +} + +/* Remove CTLESC protecting a CTLESC or CTLNUL in place. Return the passed + string. */ +static char * +remove_quoted_escapes (string) + char *string; +{ + char *t; + + if (string) + { + t = dequote_escapes (string); + strcpy (string, t); + free (t); + } + + return (string); +} + +/* Perform quoted null character removal on STRING. We don't allow any + quoted null characters in the middle or at the ends of strings because + of how expand_word_internal works. remove_quoted_nulls () turns + STRING into an empty string iff it only consists of a quoted null, + and removes all unquoted CTLNUL characters. */ +static char * +remove_quoted_nulls (string) + char *string; +{ + register size_t slen; + register int i, j, prev_i; + DECLARE_MBSTATE; + + if (strchr (string, CTLNUL) == 0) /* XXX */ + return string; /* XXX */ + + slen = strlen (string); + i = j = 0; + + while (i < slen) + { + if (string[i] == CTLESC) + { + /* Old code had j++, but we cannot assume that i == j at this + point -- what if a CTLNUL has already been removed from the + string? We don't want to drop the CTLESC or recopy characters + that we've already copied down. */ + i++; string[j++] = CTLESC; + if (i == slen) + break; + } + else if (string[i] == CTLNUL) + i++; + + prev_i = i; + ADVANCE_CHAR (string, slen, i); + if (j < prev_i) + { + do string[j++] = string[prev_i++]; while (prev_i < i); + } + else + j = i; + } + string[j] = '\0'; + + return (string); +} + +/* Perform quoted null character removal on each element of LIST. + This modifies LIST. */ +void +word_list_remove_quoted_nulls (list) + WORD_LIST *list; +{ + register WORD_LIST *t; + + for (t = list; t; t = t->next) + remove_quoted_nulls (t->word->word); +} + +/* **************************************************************** */ +/* */ +/* Functions for Matching and Removing Patterns */ +/* */ +/* **************************************************************** */ + +#if defined (HANDLE_MULTIBYTE) +#if 0 /* Currently unused */ +static unsigned char * +mb_getcharlens (string, len) + char *string; + int len; +{ + int i, offset, last; + unsigned char *ret; + char *p; + DECLARE_MBSTATE; + + i = offset = 0; + last = 0; + ret = (unsigned char *)xmalloc (len); + memset (ret, 0, len); + while (string[last]) + { + ADVANCE_CHAR (string, len, offset); + ret[last] = offset - last; + last = offset; + } + return ret; +} +#endif +#endif + +/* Remove the portion of PARAM matched by PATTERN according to OP, where OP + can have one of 4 values: + RP_LONG_LEFT remove longest matching portion at start of PARAM + RP_SHORT_LEFT remove shortest matching portion at start of PARAM + RP_LONG_RIGHT remove longest matching portion at end of PARAM + RP_SHORT_RIGHT remove shortest matching portion at end of PARAM +*/ + +#define RP_LONG_LEFT 1 +#define RP_SHORT_LEFT 2 +#define RP_LONG_RIGHT 3 +#define RP_SHORT_RIGHT 4 + +static char * +remove_upattern (param, pattern, op) + char *param, *pattern; + int op; +{ + register int len; + register char *end; + register char *p, *ret, c; + + len = STRLEN (param); + end = param + len; + + switch (op) + { + case RP_LONG_LEFT: /* remove longest match at start */ + for (p = end; p >= param; p--) + { + c = *p; *p = '\0'; + if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + *p = c; + return (savestring (p)); + } + *p = c; + + } + break; + + case RP_SHORT_LEFT: /* remove shortest match at start */ + for (p = param; p <= end; p++) + { + c = *p; *p = '\0'; + if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + *p = c; + return (savestring (p)); + } + *p = c; + } + break; + + case RP_LONG_RIGHT: /* remove longest match at end */ + for (p = param; p <= end; p++) + { + if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + c = *p; *p = '\0'; + ret = savestring (param); + *p = c; + return (ret); + } + } + break; + + case RP_SHORT_RIGHT: /* remove shortest match at end */ + for (p = end; p >= param; p--) + { + if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + c = *p; *p = '\0'; + ret = savestring (param); + *p = c; + return (ret); + } + } + break; + } + + return (savestring (param)); /* no match, return original string */ +} + +#if defined (HANDLE_MULTIBYTE) + +#if !defined (HAVE_WCSDUP) +static wchar_t * +wcsdup (ws) + wchar_t *ws; +{ + wchar_t *ret; + size_t len; + + len = wcslen (ws); + ret = xmalloc ((len + 1) * sizeof (wchar_t)); + if (ret == 0) + return ret; + return (wcscpy (ret, ws)); +} +#endif /* !HAVE_WCSDUP */ + +static wchar_t * +remove_wpattern (wparam, wstrlen, wpattern, op) + wchar_t *wparam; + size_t wstrlen; + wchar_t *wpattern; + int op; +{ + wchar_t wc; + int n, n1; + wchar_t *ret; + + switch (op) + { + case RP_LONG_LEFT: /* remove longest match at start */ + for (n = wstrlen; n >= 0; n--) + { + wc = wparam[n]; wparam[n] = L'\0'; + if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wparam[n] = wc; + return (wcsdup (wparam + n)); + } + wparam[n] = wc; + } + break; + + case RP_SHORT_LEFT: /* remove shortest match at start */ + for (n = 0; n <= wstrlen; n++) + { + wc = wparam[n]; wparam[n] = L'\0'; + if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wparam[n] = wc; + return (wcsdup (wparam + n)); + } + wparam[n] = wc; + } + break; + + case RP_LONG_RIGHT: /* remove longest match at end */ + for (n = 0; n <= wstrlen; n++) + { + if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wc = wparam[n]; wparam[n] = L'\0'; + ret = wcsdup (wparam); + wparam[n] = wc; + return (ret); + } + } + break; + + case RP_SHORT_RIGHT: /* remove shortest match at end */ + for (n = wstrlen; n >= 0; n--) + { + if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wc = wparam[n]; wparam[n] = L'\0'; + ret = wcsdup (wparam); + wparam[n] = wc; + return (ret); + } + } + break; + } + + return (wcsdup (wparam)); /* no match, return original string */ +} +#endif /* HANDLE_MULTIBYTE */ + +static char * +remove_pattern (param, pattern, op) + char *param, *pattern; + int op; +{ + if (param == NULL) + return (param); + if (*param == '\0' || pattern == NULL || *pattern == '\0') /* minor optimization */ + return (savestring (param)); + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + { + wchar_t *ret, *oret; + size_t n; + wchar_t *wparam, *wpattern; + mbstate_t ps; + char *xret; + + n = xdupmbstowcs (&wpattern, NULL, pattern); + if (n == (size_t)-1) + return (remove_upattern (param, pattern, op)); + n = xdupmbstowcs (&wparam, NULL, param); + if (n == (size_t)-1) + { + free (wpattern); + return (remove_upattern (param, pattern, op)); + } + oret = ret = remove_wpattern (wparam, n, wpattern, op); + + free (wparam); + free (wpattern); + + n = strlen (param); + xret = xmalloc (n + 1); + memset (&ps, '\0', sizeof (mbstate_t)); + n = wcsrtombs (xret, (const wchar_t **)&ret, n, &ps); + xret[n] = '\0'; /* just to make sure */ + free (oret); + return xret; + } + else +#endif + return (remove_upattern (param, pattern, op)); +} + +/* Return 1 of the first character of STRING could match the first + character of pattern PAT. Used to avoid n2 calls to strmatch(). */ +static int +match_pattern_char (pat, string) + char *pat, *string; +{ + char c; + + if (*string == 0) + return (0); + + switch (c = *pat++) + { + default: + return (*string == c); + case '\\': + return (*string == *pat); + case '?': + return (*pat == LPAREN ? 1 : (*string != '\0')); + case '*': + return (1); + case '+': + case '!': + case '@': + return (*pat == LPAREN ? 1 : (*string == c)); + case '[': + return (*string != '\0'); + } +} + +/* Match PAT anywhere in STRING and return the match boundaries. + This returns 1 in case of a successful match, 0 otherwise. SP + and EP are pointers into the string where the match begins and + ends, respectively. MTYPE controls what kind of match is attempted. + MATCH_BEG and MATCH_END anchor the match at the beginning and end + of the string, respectively. The longest match is returned. */ +static int +match_upattern (string, pat, mtype, sp, ep) + char *string, *pat; + int mtype; + char **sp, **ep; +{ + int c, len; + register char *p, *p1; + char *end; + + len = STRLEN (string); + end = string + len; + + switch (mtype) + { + case MATCH_ANY: + for (p = string; p <= end; p++) + { + if (match_pattern_char (pat, p)) + { + for (p1 = end; p1 >= p; p1--) + { + c = *p1; *p1 = '\0'; + if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) + { + *p1 = c; + *sp = p; + *ep = p1; + return 1; + } + *p1 = c; + } + } + } + + return (0); + + case MATCH_BEG: + if (match_pattern_char (pat, string) == 0) + return (0); + + for (p = end; p >= string; p--) + { + c = *p; *p = '\0'; + if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0) + { + *p = c; + *sp = string; + *ep = p; + return 1; + } + *p = c; + } + + return (0); + + case MATCH_END: + for (p = string; p <= end; p++) + { + if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) + { + *sp = p; + *ep = end; + return 1; + } + + } + + return (0); + } + + return (0); +} + +#if defined (HANDLE_MULTIBYTE) +/* Return 1 of the first character of WSTRING could match the first + character of pattern WPAT. Wide character version. */ +static int +match_pattern_wchar (wpat, wstring) + wchar_t *wpat, *wstring; +{ + wchar_t wc; + + if (*wstring == 0) + return (0); + + switch (wc = *wpat++) + { + default: + return (*wstring == wc); + case L'\\': + return (*wstring == *wpat); + case L'?': + return (*wpat == LPAREN ? 1 : (*wstring != L'\0')); + case L'*': + return (1); + case L'+': + case L'!': + case L'@': + return (*wpat == LPAREN ? 1 : (*wstring == wc)); + case L'[': + return (*wstring != L'\0'); + } +} + +/* Match WPAT anywhere in WSTRING and return the match boundaries. + This returns 1 in case of a successful match, 0 otherwise. Wide + character version. */ +static int +match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep) + wchar_t *wstring; + char **indices; + size_t wstrlen; + wchar_t *wpat; + int mtype; + char **sp, **ep; +{ + wchar_t wc; + int len; +#if 0 + size_t n, n1; /* Apple's gcc seems to miscompile this badly */ +#else + int n, n1; +#endif + + switch (mtype) + { + case MATCH_ANY: + for (n = 0; n <= wstrlen; n++) + { + if (match_pattern_wchar (wpat, wstring + n)) + { + for (n1 = wstrlen; n1 >= n; n1--) + { + wc = wstring[n1]; wstring[n1] = L'\0'; + if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0) + { + wstring[n1] = wc; + *sp = indices[n]; + *ep = indices[n1]; + return 1; + } + wstring[n1] = wc; + } + } + } + + return (0); + + case MATCH_BEG: + if (match_pattern_wchar (wpat, wstring) == 0) + return (0); + + for (n = wstrlen; n >= 0; n--) + { + wc = wstring[n]; wstring[n] = L'\0'; + if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG) == 0) + { + wstring[n] = wc; + *sp = indices[0]; + *ep = indices[n]; + return 1; + } + wstring[n] = wc; + } + + return (0); + + case MATCH_END: + for (n = 0; n <= wstrlen; n++) + { + if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0) + { + *sp = indices[n]; + *ep = indices[wstrlen]; + return 1; + } + } + + return (0); + } + + return (0); +} +#endif /* HANDLE_MULTIBYTE */ + +static int +match_pattern (string, pat, mtype, sp, ep) + char *string, *pat; + int mtype; + char **sp, **ep; +{ +#if defined (HANDLE_MULTIBYTE) + int ret; + size_t n; + wchar_t *wstring, *wpat; + char **indices; +#endif + + if (string == 0 || *string == 0 || pat == 0 || *pat == 0) + return (0); + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + { + n = xdupmbstowcs (&wpat, NULL, pat); + if (n == (size_t)-1) + return (match_upattern (string, pat, mtype, sp, ep)); + n = xdupmbstowcs (&wstring, &indices, string); + if (n == (size_t)-1) + { + free (wpat); + return (match_upattern (string, pat, mtype, sp, ep)); + } + ret = match_wpattern (wstring, indices, n, wpat, mtype, sp, ep); + + free (wpat); + free (wstring); + free (indices); + + return (ret); + } + else +#endif + return (match_upattern (string, pat, mtype, sp, ep)); +} + +static int +getpatspec (c, value) + int c; + char *value; +{ + if (c == '#') + return ((*value == '#') ? RP_LONG_LEFT : RP_SHORT_LEFT); + else /* c == '%' */ + return ((*value == '%') ? RP_LONG_RIGHT : RP_SHORT_RIGHT); +} + +/* Posix.2 says that the WORD should be run through tilde expansion, + parameter expansion, command substitution and arithmetic expansion. + This leaves the result quoted, so quote_string_for_globbing () has + to be called to fix it up for strmatch (). If QUOTED is non-zero, + it means that the entire expression was enclosed in double quotes. + This means that quoting characters in the pattern do not make any + special pattern characters quoted. For example, the `*' in the + following retains its special meaning: "${foo#'*'}". */ +static char * +getpattern (value, quoted, expandpat) + char *value; + int quoted, expandpat; +{ + char *pat, *tword; + WORD_LIST *l; + int i; + + tword = xstrchr (value, '~') ? bash_tilde_expand (value, 0) : savestring (value); + + /* There is a problem here: how to handle single or double quotes in the + pattern string when the whole expression is between double quotes? + POSIX.2 says that enclosing double quotes do not cause the pattern to + be quoted, but does that leave us a problem with @ and array[@] and their + expansions inside a pattern? */ +#if 0 + if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword) + { + i = 0; + pat = string_extract_double_quoted (tword, &i, 1); + free (tword); + tword = pat; + } +#endif + + /* expand_string_for_rhs () leaves WORD quoted and does not perform + word splitting. */ + l = *tword ? expand_string_for_rhs (tword, + (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_PATQUOTE : quoted, + (int *)NULL, (int *)NULL) + : (WORD_LIST *)0; + free (tword); + pat = string_list (l); + dispose_words (l); + if (pat) + { + tword = quote_string_for_globbing (pat, QGLOB_CVTNULL); + free (pat); + pat = tword; + } + return (pat); +} + +#if 0 +/* Handle removing a pattern from a string as a result of ${name%[%]value} + or ${name#[#]value}. */ +static char * +variable_remove_pattern (value, pattern, patspec, quoted) + char *value, *pattern; + int patspec, quoted; +{ + char *tword; + + tword = remove_pattern (value, pattern, patspec); + + return (tword); +} +#endif + +static char * +list_remove_pattern (list, pattern, patspec, itype, quoted) + WORD_LIST *list; + char *pattern; + int patspec, itype, quoted; +{ + WORD_LIST *new, *l; + WORD_DESC *w; + char *tword; + + for (new = (WORD_LIST *)NULL, l = list; l; l = l->next) + { + tword = remove_pattern (l->word->word, pattern, patspec); + w = make_bare_word (tword); + FREE (tword); + new = make_word_list (w, new); + } + + l = REVERSE_LIST (new, WORD_LIST *); + if (itype == '*') +#if 0 + tword = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? string_list_dollar_star (l) : string_list (l); +#else + tword = (quoted & Q_DOUBLE_QUOTES) ? string_list_dollar_star (l) : string_list (l); +#endif + else + tword = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l); + + dispose_words (l); + return (tword); +} + +static char * +parameter_list_remove_pattern (itype, pattern, patspec, quoted) + int itype; + char *pattern; + int patspec, quoted; +{ + char *ret; + WORD_LIST *list; + + list = list_rest_of_args (); + if (list == 0) + return ((char *)NULL); + ret = list_remove_pattern (list, pattern, patspec, itype, quoted); + dispose_words (list); + return (ret); +} + +#if defined (ARRAY_VARS) +static char * +array_remove_pattern (a, pattern, patspec, varname, quoted) + ARRAY *a; + char *pattern; + int patspec; + char *varname; /* so we can figure out how it's indexed */ + int quoted; +{ + int itype; + char *ret; + WORD_LIST *list; + SHELL_VAR *v; + + /* compute itype from varname here */ + v = array_variable_part (varname, &ret, 0); + itype = ret[0]; + + list = array_to_word_list (a); + if (list == 0) + return ((char *)NULL); + ret = list_remove_pattern (list, pattern, patspec, itype, quoted); + dispose_words (list); + + return ret; +} +#endif /* ARRAY_VARS */ + +static char * +parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted) + char *varname, *value, *patstr; + int rtype, quoted; +{ + int vtype, patspec, starsub; + char *temp1, *val, *pattern; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + this_command_name = varname; + + vtype = get_var_and_type (varname, value, quoted, &v, &val); + if (vtype == -1) + return ((char *)NULL); + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + patspec = getpatspec (rtype, patstr); + if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT) + patstr++; + + pattern = getpattern (patstr, quoted, 1); + + temp1 = (char *)NULL; /* shut up gcc */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp1 = remove_pattern (val, pattern, patspec); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp1) + { + val = quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + temp1 = array_remove_pattern (array_cell (v), pattern, patspec, varname, quoted); + if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + { + val = quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; +#endif + case VT_POSPARMS: + temp1 = parameter_list_remove_pattern (varname[0], pattern, patspec, quoted); + if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + { + val = quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; + } + + FREE (pattern); + return temp1; +} + +/******************************************* + * * + * Functions to expand WORD_DESCs * + * * + *******************************************/ + +/* Expand WORD, performing word splitting on the result. This does + parameter expansion, command substitution, arithmetic expansion, + word splitting, and quote removal. */ + +WORD_LIST * +expand_word (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_LIST *result, *tresult; + + tresult = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); + result = word_list_split (tresult); + dispose_words (tresult); + return (result ? dequote_list (result) : result); +} + +/* Expand WORD, but do not perform word splitting on the result. This + does parameter expansion, command substitution, arithmetic expansion, + and quote removal. */ +WORD_LIST * +expand_word_unsplit (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_LIST *result; + + expand_no_split_dollar_star = 1; + result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); + expand_no_split_dollar_star = 0; + + return (result ? dequote_list (result) : result); +} + +/* Perform shell expansions on WORD, but do not perform word splitting or + quote removal on the result. */ +WORD_LIST * +expand_word_leave_quoted (word, quoted) + WORD_DESC *word; + int quoted; +{ + return (call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL)); +} + +#if defined (PROCESS_SUBSTITUTION) + +/*****************************************************************/ +/* */ +/* Hacking Process Substitution */ +/* */ +/*****************************************************************/ + +#if !defined (HAVE_DEV_FD) +/* Named pipes must be removed explicitly with `unlink'. This keeps a list + of FIFOs the shell has open. unlink_fifo_list will walk the list and + unlink all of them. add_fifo_list adds the name of an open FIFO to the + list. NFIFO is a count of the number of FIFOs in the list. */ +#define FIFO_INCR 20 + +struct temp_fifo { + char *file; + pid_t proc; +}; + +static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL; +static int nfifo; +static int fifo_list_size; + +static void +add_fifo_list (pathname) + char *pathname; +{ + if (nfifo >= fifo_list_size - 1) + { + fifo_list_size += FIFO_INCR; + fifo_list = (struct temp_fifo *)xrealloc (fifo_list, + fifo_list_size * sizeof (struct temp_fifo)); + } + + fifo_list[nfifo].file = savestring (pathname); + nfifo++; +} + +void +unlink_fifo_list () +{ + int saved, i, j; + + if (nfifo == 0) + return; + + for (i = saved = 0; i < nfifo; i++) + { + if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1)) + { + unlink (fifo_list[i].file); + free (fifo_list[i].file); + fifo_list[i].file = (char *)NULL; + fifo_list[i].proc = -1; + } + else + saved++; + } + + /* If we didn't remove some of the FIFOs, compact the list. */ + if (saved) + { + for (i = j = 0; i < nfifo; i++) + if (fifo_list[i].file) + { + fifo_list[j].file = fifo_list[i].file; + fifo_list[j].proc = fifo_list[i].proc; + j++; + } + nfifo = j; + } + else + nfifo = 0; +} + +static char * +make_named_pipe () +{ + char *tname; + + tname = sh_mktmpname ("sh-np", MT_USERANDOM); + if (mkfifo (tname, 0600) < 0) + { + free (tname); + return ((char *)NULL); + } + + add_fifo_list (tname); + return (tname); +} + +#else /* HAVE_DEV_FD */ + +/* DEV_FD_LIST is a bitmap of file descriptors attached to pipes the shell + has open to children. NFDS is a count of the number of bits currently + set in DEV_FD_LIST. TOTFDS is a count of the highest possible number + of open files. */ +static char *dev_fd_list = (char *)NULL; +static int nfds; +static int totfds; /* The highest possible number of open files. */ + +static void +add_fifo_list (fd) + int fd; +{ + if (!dev_fd_list || fd >= totfds) + { + int ofds; + + ofds = totfds; + totfds = getdtablesize (); + if (totfds < 0 || totfds > 256) + totfds = 256; + if (fd > totfds) + totfds = fd + 2; + + dev_fd_list = (char *)xrealloc (dev_fd_list, totfds); + memset (dev_fd_list + ofds, '\0', totfds - ofds); + } + + dev_fd_list[fd] = 1; + nfds++; +} + +void +unlink_fifo_list () +{ + register int i; + + if (nfds == 0) + return; + + for (i = 0; nfds && i < totfds; i++) + if (dev_fd_list[i]) + { + close (i); + dev_fd_list[i] = 0; + nfds--; + } + + nfds = 0; +} + +#if defined (NOTDEF) +print_dev_fd_list () +{ + register int i; + + fprintf (stderr, "pid %ld: dev_fd_list:", (long)getpid ()); + fflush (stderr); + + for (i = 0; i < totfds; i++) + { + if (dev_fd_list[i]) + fprintf (stderr, " %d", i); + } + fprintf (stderr, "\n"); +} +#endif /* NOTDEF */ + +static char * +make_dev_fd_filename (fd) + int fd; +{ + char *ret, intbuf[INT_STRLEN_BOUND (int) + 1], *p; + + ret = (char *)xmalloc (sizeof (DEV_FD_PREFIX) + 4); + + strcpy (ret, DEV_FD_PREFIX); + p = inttostr (fd, intbuf, sizeof (intbuf)); + strcpy (ret + sizeof (DEV_FD_PREFIX) - 1, p); + + add_fifo_list (fd); + return (ret); +} + +#endif /* HAVE_DEV_FD */ + +/* Return a filename that will open a connection to the process defined by + executing STRING. HAVE_DEV_FD, if defined, means open a pipe and return + a filename in /dev/fd corresponding to a descriptor that is one of the + ends of the pipe. If not defined, we use named pipes on systems that have + them. Systems without /dev/fd and named pipes are out of luck. + + OPEN_FOR_READ_IN_CHILD, if 1, means open the named pipe for reading or + use the read end of the pipe and dup that file descriptor to fd 0 in + the child. If OPEN_FOR_READ_IN_CHILD is 0, we open the named pipe for + writing or use the write end of the pipe in the child, and dup that + file descriptor to fd 1 in the child. The parent does the opposite. */ + +static char * +process_substitute (string, open_for_read_in_child) + char *string; + int open_for_read_in_child; +{ + char *pathname; + int fd, result; + pid_t old_pid, pid; +#if defined (HAVE_DEV_FD) + int parent_pipe_fd, child_pipe_fd; + int fildes[2]; +#endif /* HAVE_DEV_FD */ +#if defined (JOB_CONTROL) + pid_t old_pipeline_pgrp; +#endif + + if (!string || !*string || wordexp_only) + return ((char *)NULL); + +#if !defined (HAVE_DEV_FD) + pathname = make_named_pipe (); +#else /* HAVE_DEV_FD */ + if (pipe (fildes) < 0) + { + sys_error (_("cannot make pipe for process substitution")); + return ((char *)NULL); + } + /* If OPEN_FOR_READ_IN_CHILD == 1, we want to use the write end of + the pipe in the parent, otherwise the read end. */ + parent_pipe_fd = fildes[open_for_read_in_child]; + child_pipe_fd = fildes[1 - open_for_read_in_child]; + /* Move the parent end of the pipe to some high file descriptor, to + avoid clashes with FDs used by the script. */ + parent_pipe_fd = move_to_high_fd (parent_pipe_fd, 1, 64); + + pathname = make_dev_fd_filename (parent_pipe_fd); +#endif /* HAVE_DEV_FD */ + + if (!pathname) + { + sys_error (_("cannot make pipe for process substitution")); + return ((char *)NULL); + } + + old_pid = last_made_pid; + +#if defined (JOB_CONTROL) + old_pipeline_pgrp = pipeline_pgrp; + pipeline_pgrp = shell_pgrp; + save_pipeline (1); +#endif /* JOB_CONTROL */ + + pid = make_child ((char *)NULL, 1); + if (pid == 0) + { + reset_terminating_signals (); /* XXX */ + free_pushed_string_input (); + /* Cancel traps, in trap.c. */ + restore_original_signals (); + setup_async_signals (); + subshell_environment |= SUBSHELL_COMSUB; + } + +#if defined (JOB_CONTROL) + set_sigchld_handler (); + stop_making_children (); + pipeline_pgrp = old_pipeline_pgrp; +#endif /* JOB_CONTROL */ + + if (pid < 0) + { + sys_error (_("cannot make child for process substitution")); + free (pathname); +#if defined (HAVE_DEV_FD) + close (parent_pipe_fd); + close (child_pipe_fd); +#endif /* HAVE_DEV_FD */ + return ((char *)NULL); + } + + if (pid > 0) + { +#if defined (JOB_CONTROL) + restore_pipeline (1); +#endif + +#if !defined (HAVE_DEV_FD) + fifo_list[nfifo-1].proc = pid; +#endif + + last_made_pid = old_pid; + +#if defined (JOB_CONTROL) && defined (PGRP_PIPE) + close_pgrp_pipe (); +#endif /* JOB_CONTROL && PGRP_PIPE */ + +#if defined (HAVE_DEV_FD) + close (child_pipe_fd); +#endif /* HAVE_DEV_FD */ + + return (pathname); + } + + set_sigint_handler (); + +#if defined (JOB_CONTROL) + set_job_control (0); +#endif /* JOB_CONTROL */ + +#if !defined (HAVE_DEV_FD) + /* Open the named pipe in the child. */ + fd = open (pathname, open_for_read_in_child ? O_RDONLY|O_NONBLOCK : O_WRONLY); + if (fd < 0) + { + /* Two separate strings for ease of translation. */ + if (open_for_read_in_child) + sys_error (_("cannot open named pipe %s for reading"), pathname); + else + sys_error (_("cannot open named pipe %s for writing"), pathname); + + exit (127); + } + if (open_for_read_in_child) + { + if (sh_unset_nodelay_mode (fd) < 0) + { + sys_error (_("cannout reset nodelay mode for fd %d"), fd); + exit (127); + } + } +#else /* HAVE_DEV_FD */ + fd = child_pipe_fd; +#endif /* HAVE_DEV_FD */ + + if (dup2 (fd, open_for_read_in_child ? 0 : 1) < 0) + { + sys_error (_("cannot duplicate named pipe %s as fd %d"), pathname, + open_for_read_in_child ? 0 : 1); + exit (127); + } + + if (fd != (open_for_read_in_child ? 0 : 1)) + close (fd); + + /* Need to close any files that this process has open to pipes inherited + from its parent. */ + if (current_fds_to_close) + { + close_fd_bitmap (current_fds_to_close); + current_fds_to_close = (struct fd_bitmap *)NULL; + } + +#if defined (HAVE_DEV_FD) + /* Make sure we close the parent's end of the pipe and clear the slot + in the fd list so it is not closed later, if reallocated by, for + instance, pipe(2). */ + close (parent_pipe_fd); + dev_fd_list[parent_pipe_fd] = 0; +#endif /* HAVE_DEV_FD */ + + result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST)); + +#if !defined (HAVE_DEV_FD) + /* Make sure we close the named pipe in the child before we exit. */ + close (open_for_read_in_child ? 0 : 1); +#endif /* !HAVE_DEV_FD */ + + exit (result); + /*NOTREACHED*/ +} +#endif /* PROCESS_SUBSTITUTION */ + +/***********************************/ +/* */ +/* Command Substitution */ +/* */ +/***********************************/ + +static char * +read_comsub (fd, quoted) + int fd, quoted; +{ + char *istring, buf[128], *bufp; + int istring_index, istring_size, c; + ssize_t bufn; + + istring = (char *)NULL; + istring_index = istring_size = bufn = 0; + +#ifdef __CYGWIN__ + setmode (fd, O_TEXT); /* we don't want CR/LF, we want Unix-style */ +#endif + + /* Read the output of the command through the pipe. */ + while (1) + { + if (fd < 0) + break; + if (--bufn <= 0) + { + bufn = zread (fd, buf, sizeof (buf)); + if (bufn <= 0) + break; + bufp = buf; + } + c = *bufp++; + + if (c == 0) + { +#if 0 + internal_warning ("read_comsub: ignored null byte in input"); +#endif + continue; + } + + /* Add the character to ISTRING, possibly after resizing it. */ + RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE); + + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || c == CTLESC || c == CTLNUL) + istring[istring_index++] = CTLESC; + + istring[istring_index++] = c; + +#if 0 +#if defined (__CYGWIN__) + if (c == '\n' && istring_index > 1 && istring[istring_index - 2] == '\r') + { + istring_index--; + istring[istring_index - 1] = '\n'; + } +#endif +#endif + } + + if (istring) + istring[istring_index] = '\0'; + + /* If we read no output, just return now and save ourselves some + trouble. */ + if (istring_index == 0) + { + FREE (istring); + return (char *)NULL; + } + + /* Strip trailing newlines from the output of the command. */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + { + while (istring_index > 0) + { + if (istring[istring_index - 1] == '\n') + { + --istring_index; + + /* If the newline was quoted, remove the quoting char. */ + if (istring[istring_index - 1] == CTLESC) + --istring_index; + } + else + break; + } + istring[istring_index] = '\0'; + } + else + strip_trailing (istring, istring_index - 1, 1); + + return istring; +} + +/* Perform command substitution on STRING. This returns a string, + possibly quoted. */ +char * +command_substitute (string, quoted) + char *string; + int quoted; +{ + pid_t pid, old_pid, old_pipeline_pgrp; + char *istring; + int result, fildes[2], function_value, pflags, rc; + + istring = (char *)NULL; + + /* Don't fork () if there is no need to. In the case of no command to + run, just return NULL. */ + if (!string || !*string || (string[0] == '\n' && !string[1])) + return ((char *)NULL); + + if (wordexp_only && read_but_dont_execute) + { + last_command_exit_value = 125; + jump_to_top_level (EXITPROG); + } + + /* We're making the assumption here that the command substitution will + eventually run a command from the file system. Since we'll run + maybe_make_export_env in this subshell before executing that command, + the parent shell and any other shells it starts will have to remake + the environment. If we make it before we fork, other shells won't + have to. Don't bother if we have any temporary variable assignments, + though, because the export environment will be remade after this + command completes anyway, but do it if all the words to be expanded + are variable assignments. */ + if (subst_assign_varlist == 0 || garglist == 0) + maybe_make_export_env (); /* XXX */ + + /* Flags to pass to parse_and_execute() */ + pflags = interactive ? SEVAL_RESETLINE : 0; + + /* Pipe the output of executing STRING into the current shell. */ + if (pipe (fildes) < 0) + { + sys_error (_("cannot make pipe for command substitution")); + goto error_exit; + } + + old_pid = last_made_pid; +#if defined (JOB_CONTROL) + old_pipeline_pgrp = pipeline_pgrp; + /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline. */ + if ((subshell_environment & SUBSHELL_PIPE) == 0) + pipeline_pgrp = shell_pgrp; + cleanup_the_pipeline (); +#endif + + pid = make_child ((char *)NULL, 0); + if (pid == 0) + /* Reset the signal handlers in the child, but don't free the + trap strings. */ + reset_signal_handlers (); + +#if defined (JOB_CONTROL) + set_sigchld_handler (); + stop_making_children (); + pipeline_pgrp = old_pipeline_pgrp; +#else + stop_making_children (); +#endif /* JOB_CONTROL */ + + if (pid < 0) + { + sys_error (_("cannot make child for command substitution")); + error_exit: + + FREE (istring); + close (fildes[0]); + close (fildes[1]); + return ((char *)NULL); + } + + if (pid == 0) + { + set_sigint_handler (); /* XXX */ + + free_pushed_string_input (); + + if (dup2 (fildes[1], 1) < 0) + { + sys_error (_("command_substitute: cannot duplicate pipe as fd 1")); + exit (EXECUTION_FAILURE); + } + + /* If standard output is closed in the parent shell + (such as after `exec >&-'), file descriptor 1 will be + the lowest available file descriptor, and end up in + fildes[0]. This can happen for stdin and stderr as well, + but stdout is more important -- it will cause no output + to be generated from this command. */ + if ((fildes[1] != fileno (stdin)) && + (fildes[1] != fileno (stdout)) && + (fildes[1] != fileno (stderr))) + close (fildes[1]); + + if ((fildes[0] != fileno (stdin)) && + (fildes[0] != fileno (stdout)) && + (fildes[0] != fileno (stderr))) + close (fildes[0]); + + /* The currently executing shell is not interactive. */ + interactive = 0; + + /* This is a subshell environment. */ + subshell_environment |= SUBSHELL_COMSUB; + + /* When not in POSIX mode, command substitution does not inherit + the -e flag. */ + if (posixly_correct == 0) + exit_immediately_on_error = 0; + + remove_quoted_escapes (string); + + startup_state = 2; /* see if we can avoid a fork */ + /* Give command substitution a place to jump back to on failure, + so we don't go back up to main (). */ + result = setjmp (top_level); + + /* If we're running a command substitution inside a shell function, + trap `return' so we don't return from the function in the subshell + and go off to never-never land. */ + if (result == 0 && return_catch_flag) + function_value = setjmp (return_catch); + else + function_value = 0; + + if (result == ERREXIT) + rc = last_command_exit_value; + else if (result == EXITPROG) + rc = last_command_exit_value; + else if (result) + rc = EXECUTION_FAILURE; + else if (function_value) + rc = return_catch_value; + else + { + subshell_level++; + rc = parse_and_execute (string, "command substitution", pflags|SEVAL_NOHIST); + subshell_level--; + } + + last_command_exit_value = rc; + rc = run_exit_trap (); + exit (rc); + } + else + { +#if defined (JOB_CONTROL) && defined (PGRP_PIPE) + close_pgrp_pipe (); +#endif /* JOB_CONTROL && PGRP_PIPE */ + + close (fildes[1]); + + istring = read_comsub (fildes[0], quoted); + + close (fildes[0]); + + current_command_subst_pid = pid; + last_command_exit_value = wait_for (pid); + last_command_subst_pid = pid; + last_made_pid = old_pid; + +#if defined (JOB_CONTROL) + /* If last_command_exit_value > 128, then the substituted command + was terminated by a signal. If that signal was SIGINT, then send + SIGINT to ourselves. This will break out of loops, for instance. */ + if (last_command_exit_value == (128 + SIGINT) && last_command_exit_signal == SIGINT) + kill (getpid (), SIGINT); + + /* wait_for gives the terminal back to shell_pgrp. If some other + process group should have it, give it away to that group here. + pipeline_pgrp is non-zero only while we are constructing a + pipline, so what we are concerned about is whether or not that + pipeline was started in the background. A pipeline started in + the background should never get the tty back here. */ +#if 0 + if (interactive && pipeline_pgrp != (pid_t)0 && pipeline_pgrp != last_asynchronous_pid) +#else + if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0) +#endif + give_terminal_to (pipeline_pgrp, 0); +#endif /* JOB_CONTROL */ + + return (istring); + } +} + +/******************************************************** + * * + * Utility functions for parameter expansion * + * * + ********************************************************/ + +#if defined (ARRAY_VARS) + +static arrayind_t +array_length_reference (s) + char *s; +{ + int len; + arrayind_t ind; + char *t, c; + ARRAY *array; + SHELL_VAR *var; + + var = array_variable_part (s, &t, &len); + + /* If unbound variables should generate an error, report one and return + failure. */ + if ((var == 0 || array_p (var) == 0) && unbound_vars_is_error) + { + c = *--t; + *t = '\0'; + err_unboundvar (s); + *t = c; + return (-1); + } + else if (var == 0) + return 0; + + /* We support a couple of expansions for variables that are not arrays. + We'll return the length of the value for v[0], and 1 for v[@] or + v[*]. Return 0 for everything else. */ + + array = array_p (var) ? array_cell (var) : (ARRAY *)NULL; + + if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') + return (array_p (var) ? array_num_elements (array) : 1); + + ind = array_expand_index (t, len); + if (ind < 0) + { + err_badarraysub (t); + return (-1); + } + + if (array_p (var)) + t = array_reference (array, ind); + else + t = (ind == 0) ? value_cell (var) : (char *)NULL; + + len = STRLEN (t); + return (len); +} +#endif /* ARRAY_VARS */ + +static int +valid_brace_expansion_word (name, var_is_special) + char *name; + int var_is_special; +{ + if (DIGIT (*name) && all_digits (name)) + return 1; + else if (var_is_special) + return 1; +#if defined (ARRAY_VARS) + else if (valid_array_reference (name)) + return 1; +#endif /* ARRAY_VARS */ + else if (legal_identifier (name)) + return 1; + else + return 0; +} + +static int +chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at) + char *name; + int quoted; + int *quoted_dollar_atp, *contains_dollar_at; +{ + char *temp1; + + if (name == 0) + { + if (quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + return 0; + } + + /* check for $@ and $* */ + if (name[0] == '@' && name[1] == 0) + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + else if (name[0] == '*' && name[1] == '\0' && quoted == 0) + { + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + + /* Now check for ${array[@]} and ${array[*]} */ +#if defined (ARRAY_VARS) + else if (valid_array_reference (name)) + { + temp1 = xstrchr (name, '['); + if (temp1 && temp1[1] == '@' && temp1[2] == ']') + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } /* [ */ + /* ${array[*]}, when unquoted, should be treated like ${array[@]}, + which should result in separate words even when IFS is unset. */ + if (temp1 && temp1[1] == '*' && temp1[2] == ']' && quoted == 0) + { + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + } +#endif + return 0; +} + +/* Parameter expand NAME, and return a new string which is the expansion, + or NULL if there was no expansion. + VAR_IS_SPECIAL is non-zero if NAME is one of the special variables in + the shell, e.g., "@", "$", "*", etc. QUOTED, if non-zero, means that + NAME was found inside of a double-quoted expression. */ +static char * +parameter_brace_expand_word (name, var_is_special, quoted) + char *name; + int var_is_special, quoted; +{ + char *temp, *tt; + intmax_t arg_index; + SHELL_VAR *var; + int atype; + + /* Handle multiple digit arguments, as in ${11}. */ + + if (legal_number (name, &arg_index)) + { + tt = get_dollar_var_value (arg_index); + if (tt) + temp = (*tt && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (tt) + : quote_escapes (tt); + else + temp = (char *)NULL; + FREE (tt); + } + else if (var_is_special) /* ${@} */ + { + int sindex; + tt = (char *)xmalloc (2 + strlen (name)); + tt[sindex = 0] = '$'; + strcpy (tt + 1, name); + + temp = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL, + (int *)NULL, (int *)NULL, 0); + free (tt); + } +#if defined (ARRAY_VARS) + else if (valid_array_reference (name)) + { + temp = array_value (name, quoted, &atype); + if (atype == 0 && temp) + temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (temp) + : quote_escapes (temp); + } +#endif + else if (var = find_variable (name)) + { + if (var_isset (var) && invisible_p (var) == 0) + { +#if defined (ARRAY_VARS) + temp = array_p (var) ? array_reference (array_cell (var), 0) : value_cell (var); +#else + temp = value_cell (var); +#endif + + if (temp) + temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (temp) + : quote_escapes (temp); + } + else + temp = (char *)NULL; + } + else + temp = (char *)NULL; + + return (temp); +} + +/* Expand an indirect reference to a variable: ${!NAME} expands to the + value of the variable whose name is the value of NAME. */ +static char * +parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at) + char *name; + int var_is_special, quoted; + int *quoted_dollar_atp, *contains_dollar_at; +{ + char *temp, *t; + + t = parameter_brace_expand_word (name, var_is_special, quoted); + /* Have to dequote here if necessary */ + if (t) + { + temp = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + ? dequote_string (t) + : dequote_escapes (t); + free (t); + t = temp; + } + chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at); + if (t == 0) + return (t); + temp = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted); + free (t); + return temp; +} + +/* Expand the right side of a parameter expansion of the form ${NAMEcVALUE}, + depending on the value of C, the separating character. C can be one of + "-", "+", or "=". QUOTED is true if the entire brace expression occurs + between double quotes. */ +static char * +parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat) + char *name, *value; + int c, quoted, *qdollaratp, *hasdollarat; +{ + WORD_LIST *l; + char *t, *t1, *temp; + int hasdol; + + /* XXX - Should we tilde expand in an assignment context if C is `='? */ + if (*value == '~') + temp = bash_tilde_expand (value, 0); + else if (xstrchr (value, '~') && unquoted_substring ("=~", value)) + temp = bash_tilde_expand (value, 1); + else + temp = savestring (value); + + /* If the entire expression is between double quotes, we want to treat + the value as a double-quoted string, with the exception that we strip + embedded unescaped double quotes. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *temp) + { + hasdol = 0; + t = string_extract_double_quoted (temp, &hasdol, 1); + free (temp); + temp = t; + } + + hasdol = 0; + /* XXX was 0 not quoted */ + l = *temp ? expand_string_for_rhs (temp, quoted, &hasdol, (int *)NULL) + : (WORD_LIST *)0; + if (hasdollarat) + *hasdollarat = hasdol || (l && l->next); + free (temp); + if (l) + { + /* The expansion of TEMP returned something. We need to treat things + slightly differently if HASDOL is non-zero. If we have "$@", the + individual words have already been quoted. We need to turn them + into a string with the words separated by the first character of + $IFS without any additional quoting, so string_list_dollar_at won't + do the right thing. We use string_list_dollar_star instead. */ + temp = (hasdol || l->next) ? string_list_dollar_star (l) : string_list (l); + + /* If l->next is not null, we know that TEMP contained "$@", since that + is the only expansion that creates more than one word. */ + if (qdollaratp && ((hasdol && quoted) || l->next)) + *qdollaratp = 1; + dispose_words (l); + } + else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && hasdol) + { + /* The brace expansion occurred between double quotes and there was + a $@ in TEMP. It does not matter if the $@ is quoted, as long as + it does not expand to anything. In this case, we want to return + a quoted empty string. */ + temp = (char *)xmalloc (2); + temp[0] = CTLNUL; + temp[1] = '\0'; + } + else + temp = (char *)NULL; + + if (c == '-' || c == '+') + return (temp); + + /* c == '=' */ + t = temp ? savestring (temp) : savestring (""); + t1 = dequote_string (t); + free (t); +#if defined (ARRAY_VARS) + if (valid_array_reference (name)) + assign_array_element (name, t1); + else +#endif /* ARRAY_VARS */ + bind_variable (name, t1); + free (t1); + return (temp); +} + +/* Deal with the right hand side of a ${name:?value} expansion in the case + that NAME is null or not set. If VALUE is non-null it is expanded and + used as the error message to print, otherwise a standard message is + printed. */ +static void +parameter_brace_expand_error (name, value) + char *name, *value; +{ + WORD_LIST *l; + char *temp; + + if (value && *value) + { + if (*value == '~') + temp = bash_tilde_expand (value, 0); + else if (xstrchr (value, '~') && unquoted_substring ("=~", value)) + temp = bash_tilde_expand (value, 1); + else + temp = savestring (value); + + l = expand_string (temp, 0); + FREE (temp); + temp = string_list (l); + report_error ("%s: %s", name, temp ? temp : ""); /* XXX was value not "" */ + FREE (temp); + dispose_words (l); + } + else + report_error (_("%s: parameter null or not set"), name); + + /* Free the data we have allocated during this expansion, since we + are about to longjmp out. */ + free (name); + FREE (value); +} + +/* Return 1 if NAME is something for which parameter_brace_expand_length is + OK to do. */ +static int +valid_length_expression (name) + char *name; +{ + return (name[1] == '\0' || /* ${#} */ + ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') || /* special param */ + (DIGIT (name[1]) && all_digits (name + 1)) || /* ${#11} */ +#if defined (ARRAY_VARS) + valid_array_reference (name + 1) || /* ${#a[7]} */ +#endif + legal_identifier (name + 1)); /* ${#PS1} */ +} + +#if defined (HANDLE_MULTIBYTE) +size_t +mbstrlen (s) + const char *s; +{ + size_t clen, nc; + mbstate_t mbs; + + nc = 0; + memset (&mbs, 0, sizeof (mbs)); + while ((clen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0 && (MB_INVALIDCH(clen) == 0)) + { + s += clen; + nc++; + } + return nc; +} +#endif + + +/* Handle the parameter brace expansion that requires us to return the + length of a parameter. */ +static intmax_t +parameter_brace_expand_length (name) + char *name; +{ + char *t, *newname; + intmax_t number, arg_index; + WORD_LIST *list; +#if defined (ARRAY_VARS) + SHELL_VAR *var; +#endif + + if (name[1] == '\0') /* ${#} */ + number = number_of_args (); + else if ((name[1] == '@' || name[1] == '*') && name[2] == '\0') /* ${#@}, ${#*} */ + number = number_of_args (); + else if ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') + { + /* Take the lengths of some of the shell's special parameters. */ + switch (name[1]) + { + case '-': + t = which_set_flags (); + break; + case '?': + t = itos (last_command_exit_value); + break; + case '$': + t = itos (dollar_dollar_pid); + break; + case '!': + if (last_asynchronous_pid == NO_PID) + t = (char *)NULL; + else + t = itos (last_asynchronous_pid); + break; + case '#': + t = itos (number_of_args ()); + break; + } + number = STRLEN (t); + FREE (t); + } +#if defined (ARRAY_VARS) + else if (valid_array_reference (name + 1)) + number = array_length_reference (name + 1); +#endif /* ARRAY_VARS */ + else + { + number = 0; + + if (legal_number (name + 1, &arg_index)) /* ${#1} */ + { + t = get_dollar_var_value (arg_index); + number = MB_STRLEN (t); + FREE (t); + } +#if defined (ARRAY_VARS) + else if ((var = find_variable (name + 1)) && array_p (var)) + { + t = array_reference (array_cell (var), 0); + number = MB_STRLEN (t); + } +#endif + else /* ${#PS1} */ + { + newname = savestring (name); + newname[0] = '$'; + list = expand_string (newname, Q_DOUBLE_QUOTES); + t = list ? string_list (list) : (char *)NULL; + free (newname); + if (list) + dispose_words (list); + + number = MB_STRLEN (t); + FREE (t); + } + } + + return (number); +} + +/* Skip characters in SUBSTR until DELIM. SUBSTR is an arithmetic expression, + so we do some ad-hoc parsing of an arithmetic expression to find + the first DELIM, instead of using strchr(3). Two rules: + 1. If the substring contains a `(', read until closing `)'. + 2. If the substring contains a `?', read past one `:' for each `?'. +*/ + +static char * +skiparith (substr, delim) + char *substr; + int delim; +{ + size_t sublen; + int skipcol, pcount, i; + DECLARE_MBSTATE; + + sublen = strlen (substr); + i = skipcol = pcount = 0; + while (substr[i]) + { + /* Balance parens */ + if (substr[i] == LPAREN) + { + pcount++; + i++; + continue; + } + if (substr[i] == RPAREN && pcount) + { + pcount--; + i++; + continue; + } + if (pcount) + { + ADVANCE_CHAR (substr, sublen, i); + continue; + } + + /* Skip one `:' for each `?' */ + if (substr[i] == ':' && skipcol) + { + skipcol--; + i++; + continue; + } + if (substr[i] == delim) + break; + if (substr[i] == '?') + { + skipcol++; + i++; + continue; + } + ADVANCE_CHAR (substr, sublen, i); + } + + return (substr + i); +} + +/* Verify and limit the start and end of the desired substring. If + VTYPE == 0, a regular shell variable is being used; if it is 1, + then the positional parameters are being used; if it is 2, then + VALUE is really a pointer to an array variable that should be used. + Return value is 1 if both values were OK, 0 if there was a problem + with an invalid expression, or -1 if the values were out of range. */ +static int +verify_substring_values (value, substr, vtype, e1p, e2p) + char *value, *substr; + int vtype; + intmax_t *e1p, *e2p; +{ + char *t, *temp1, *temp2; + arrayind_t len; + int expok; +#if defined (ARRAY_VARS) + ARRAY *a; +#endif + + /* duplicate behavior of strchr(3) */ + t = skiparith (substr, ':'); + if (*t && *t == ':') + *t = '\0'; + else + t = (char *)0; + + temp1 = expand_string_if_necessary (substr, Q_DOUBLE_QUOTES, expand_string); + *e1p = evalexp (temp1, &expok); + free (temp1); + if (expok == 0) + return (0); + + len = -1; /* paranoia */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + len = strlen (value); + break; + case VT_POSPARMS: + len = number_of_args () + 1; + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + a = (ARRAY *)value; + /* For arrays, the first value deals with array indices. */ + len = array_max_index (a); /* arrays index from 0 to n - 1 */ + break; +#endif + } + + if (len == -1) /* paranoia */ + return -1; + + if (*e1p < 0) /* negative offsets count from end */ + *e1p += len; + + if (*e1p >= len || *e1p < 0) + return (-1); + +#if defined (ARRAY_VARS) + /* For arrays, the second offset deals with the number of elements. */ + if (vtype == VT_ARRAYVAR) + len = array_num_elements (a); +#endif + + if (t) + { + t++; + temp2 = savestring (t); + temp1 = expand_string_if_necessary (temp2, Q_DOUBLE_QUOTES, expand_string); + free (temp2); + t[-1] = ':'; + *e2p = evalexp (temp1, &expok); + free (temp1); + if (expok == 0) + return (0); + if (*e2p < 0) + { + internal_error (_("%s: substring expression < 0"), t); + return (0); + } +#if defined (ARRAY_VARS) + /* In order to deal with sparse arrays, push the intelligence about how + to deal with the number of elements desired down to the array- + specific functions. */ + if (vtype != VT_ARRAYVAR) +#endif + { + *e2p += *e1p; /* want E2 chars starting at E1 */ + if (*e2p > len) + *e2p = len; + } + } + else + *e2p = len; + + return (1); +} + +/* Return the type of variable specified by VARNAME (simple variable, + positional param, or array variable). Also return the value specified + by VARNAME (value of a variable or a reference to an array element). + If this returns VT_VARIABLE, the caller assumes that CTLESC and CTLNUL + characters in the value are quoted with CTLESC and takes appropriate + steps. For convenience, *VALP is set to the dequoted VALUE. */ +static int +get_var_and_type (varname, value, quoted, varp, valp) + char *varname, *value; + int quoted; + SHELL_VAR **varp; + char **valp; +{ + int vtype; + char *temp; +#if defined (ARRAY_VARS) + SHELL_VAR *v; +#endif + + /* This sets vtype to VT_VARIABLE or VT_POSPARMS */ + vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0'; + if (vtype == VT_POSPARMS && varname[0] == '*') + vtype |= VT_STARSUB; + *varp = (SHELL_VAR *)NULL; + +#if defined (ARRAY_VARS) + if (valid_array_reference (varname)) + { + v = array_variable_part (varname, &temp, (int *)0); + if (v && array_p (v)) + { /* [ */ + if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']') + { + vtype = VT_ARRAYVAR; + if (temp[0] == '*') + vtype |= VT_STARSUB; + *valp = (char *)array_cell (v); + } + else + { + vtype = VT_ARRAYMEMBER; + *valp = array_value (varname, 1, (int *)NULL); + } + *varp = v; + } + else + return -1; + } + else if ((v = find_variable (varname)) && array_p (v)) + { + vtype = VT_ARRAYMEMBER; + *varp = v; + *valp = array_reference (array_cell (v), 0); + } + else +#endif +#if 1 + { + if (value && vtype == VT_VARIABLE) + { + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + *valp = dequote_string (value); + else + *valp = dequote_escapes (value); + } + else + *valp = value; + } +#else + *valp = (value && vtype == VT_VARIABLE) ? dequote_escapes (value) : value; +#endif + + return vtype; +} + +/******************************************************/ +/* */ +/* Functions to extract substrings of variable values */ +/* */ +/******************************************************/ + +#if defined (HANDLE_MULTIBYTE) +/* Character-oriented rather than strictly byte-oriented substrings. S and + E, rather being strict indices into STRING, indicate character (possibly + multibyte character) positions that require calculation. + Used by the ${param:offset[:length]} expansion. */ +static char * +mb_substring (string, s, e) + char *string; + int s, e; +{ + char *tt; + int start, stop, i, slen; + DECLARE_MBSTATE; + + start = 0; + slen = STRLEN (string); + + i = s; + while (string[start] && i--) + ADVANCE_CHAR (string, slen, start); + stop = start; + i = e - s; + while (string[stop] && i--) + ADVANCE_CHAR (string, slen, stop); + tt = substring (string, start, stop); + return tt; +} +#endif + +/* Process a variable substring expansion: ${name:e1[:e2]}. If VARNAME + is `@', use the positional parameters; otherwise, use the value of + VARNAME. If VARNAME is an array variable, use the array elements. */ + +static char * +parameter_brace_substring (varname, value, substr, quoted) + char *varname, *value, *substr; + int quoted; +{ + intmax_t e1, e2; + int vtype, r, starsub; + char *temp, *val, *tt; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + this_command_name = varname; + + vtype = get_var_and_type (varname, value, quoted, &v, &val); + if (vtype == -1) + return ((char *)NULL); + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + r = verify_substring_values (val, substr, vtype, &e1, &e2); + if (r <= 0) + return ((r == 0) ? &expand_param_error : (char *)NULL); + + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + tt = mb_substring (val, e1, e2); + else +#endif + tt = substring (val, e1, e2); + + if (vtype == VT_VARIABLE) + FREE (val); + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + temp = quote_string (tt); + else + temp = tt ? quote_escapes (tt) : (char *)NULL; + FREE (tt); + break; + case VT_POSPARMS: + tt = pos_params (varname, e1, e2, quoted); + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) + { + temp = tt ? quote_escapes (tt) : (char *)NULL; + FREE (tt); + } + else + temp = tt; + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + /* We want E2 to be the number of elements desired (arrays can be sparse, + so verify_substring_values just returns the numbers specified and we + rely on array_subrange to understand how to deal with them). */ + tt = array_subrange (array_cell (v), e1, e2, starsub, quoted); + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) + { + temp = tt ? quote_escapes (tt) : (char *)NULL; + FREE (tt); + } + else + temp = tt; + break; +#endif + default: + temp = (char *)NULL; + } + + return temp; +} + +/****************************************************************/ +/* */ +/* Functions to perform pattern substitution on variable values */ +/* */ +/****************************************************************/ + +char * +pat_subst (string, pat, rep, mflags) + char *string, *pat, *rep; + int mflags; +{ + char *ret, *s, *e, *str; + int rsize, rptr, l, replen, mtype; + + mtype = mflags & MATCH_TYPEMASK; + + /* Special cases: + * 1. A null pattern with mtype == MATCH_BEG means to prefix STRING + * with REP and return the result. + * 2. A null pattern with mtype == MATCH_END means to append REP to + * STRING and return the result. + */ + if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END)) + { + replen = STRLEN (rep); + l = strlen (string); + ret = (char *)xmalloc (replen + l + 2); + if (replen == 0) + strcpy (ret, string); + else if (mtype == MATCH_BEG) + { + strcpy (ret, rep); + strcpy (ret + replen, string); + } + else + { + strcpy (ret, string); + strcpy (ret + l, rep); + } + return (ret); + } + + ret = (char *)xmalloc (rsize = 64); + ret[0] = '\0'; + + for (replen = STRLEN (rep), rptr = 0, str = string;;) + { + if (match_pattern (str, pat, mtype, &s, &e) == 0) + break; + l = s - str; + RESIZE_MALLOCED_BUFFER (ret, rptr, (l + replen), rsize, 64); + + /* OK, now copy the leading unmatched portion of the string (from + str to s) to ret starting at rptr (the current offset). Then copy + the replacement string at ret + rptr + (s - str). Increment + rptr (if necessary) and str and go on. */ + if (l) + { + strncpy (ret + rptr, str, l); + rptr += l; + } + if (replen) + { + strncpy (ret + rptr, rep, replen); + rptr += replen; + } + str = e; /* e == end of match */ + + if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY) + break; + + if (s == e) + e++, str++; /* avoid infinite recursion on zero-length match */ + } + + /* Now copy the unmatched portion of the input string */ + if (*str) + { + RESIZE_MALLOCED_BUFFER (ret, rptr, STRLEN(str) + 1, rsize, 64); + strcpy (ret + rptr, str); + } + else + ret[rptr] = '\0'; + + return ret; +} + +/* Do pattern match and replacement on the positional parameters. */ +static char * +pos_params_pat_subst (string, pat, rep, mflags) + char *string, *pat, *rep; + int mflags; +{ + WORD_LIST *save, *params; + WORD_DESC *w; + char *ret, *tt; + + save = params = list_rest_of_args (); + if (save == 0) + return ((char *)NULL); + + for ( ; params; params = params->next) + { + ret = pat_subst (params->word->word, pat, rep, mflags); + w = make_bare_word (ret); + dispose_word (params->word); + params->word = w; + FREE (ret); + } + + if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB)) + ret = string_list_dollar_star (quote_list (save)); + else + ret = string_list ((mflags & MATCH_QUOTED) ? quote_list (save) : save); + dispose_words (save); + + return (ret); +} + +/* Perform pattern substitution on VALUE, which is the expansion of + VARNAME. PATSUB is an expression supplying the pattern to match + and the string to substitute. QUOTED is a flags word containing + the type of quoting currently in effect. */ +static char * +parameter_brace_patsub (varname, value, patsub, quoted) + char *varname, *value, *patsub; + int quoted; +{ + int vtype, mflags, starsub; + char *val, *temp, *pat, *rep, *p, *lpatsub, *tt; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + this_command_name = varname; + + vtype = get_var_and_type (varname, value, quoted, &v, &val); + if (vtype == -1) + return ((char *)NULL); + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + mflags = 0; + if (*patsub == '/') + { + mflags |= MATCH_GLOBREP; + patsub++; + } + + /* Malloc this because expand_string_if_necessary or one of the expansion + functions in its call chain may free it on a substitution error. */ + lpatsub = savestring (patsub); + + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + mflags |= MATCH_QUOTED; + + if (starsub) + mflags |= MATCH_STARSUB; + + if (rep = quoted_strchr (lpatsub, '/', ST_BACKSL)) + *rep++ = '\0'; + else + rep = (char *)NULL; + + if (rep && *rep == '\0') + rep = (char *)NULL; + +#if 0 + /* Expand PAT and REP for command, variable and parameter, arithmetic, + and process substitution. Also perform quote removal. Do not + perform word splitting or filename generation. */ + pat = expand_string_if_necessary (lpatsub, (quoted & ~Q_DOUBLE_QUOTES), expand_string_unsplit); +#else + /* Perform the same expansions on the pattern as performed by the + pattern removal expansions. */ + pat = getpattern (lpatsub, quoted, 1); +#endif + + if (rep) + { + if ((mflags & MATCH_QUOTED) == 0) + rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit); + else + rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit); + } + + p = pat; + if (pat && pat[0] == '#') + { + mflags |= MATCH_BEG; + p++; + } + else if (pat && pat[0] == '%') + { + mflags |= MATCH_END; + p++; + } + else + mflags |= MATCH_ANY; + + /* OK, we now want to substitute REP for PAT in VAL. If + flags & MATCH_GLOBREP is non-zero, the substitution is done + everywhere, otherwise only the first occurrence of PAT is + replaced. The pattern matching code doesn't understand + CTLESC quoting CTLESC and CTLNUL so we use the dequoted variable + values passed in (VT_VARIABLE) so the pattern substitution + code works right. We need to requote special chars after + we're done for VT_VARIABLE and VT_ARRAYMEMBER, and for the + other cases if QUOTED == 0, since the posparams and arrays + indexed by * or @ do special things when QUOTED != 0. */ + + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp = pat_subst (val, p, rep, mflags); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp) + { + tt = quote_escapes (temp); + free (temp); + temp = tt; + } + break; + case VT_POSPARMS: + temp = pos_params_pat_subst (val, p, rep, mflags); + if (temp && (mflags & MATCH_QUOTED) == 0) + { + tt = quote_escapes (temp); + free (temp); + temp = tt; + } + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + temp = array_patsub (array_cell (v), p, rep, mflags); + if (temp && (mflags & MATCH_QUOTED) == 0) + { + tt = quote_escapes (temp); + free (temp); + temp = tt; + } + break; +#endif + } + + FREE (pat); + FREE (rep); + free (lpatsub); + + return temp; +} + +/****************************************************************/ +/* */ +/* Functions to perform parameter expansion on a string */ +/* */ +/****************************************************************/ + +/* ${[#][!]name[[:]#[#]%[%]-=?+[word][:e1[:e2]]]} */ +static char * +parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_dollar_at) + char *string; + int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at; +{ + int check_nullness, var_is_set, var_is_null, var_is_special; + int want_substring, want_indir, want_patsub; + char *name, *value, *temp, *temp1; + int t_index, sindex, c; + intmax_t number; + + value = (char *)NULL; + var_is_set = var_is_null = var_is_special = check_nullness = 0; + want_substring = want_indir = want_patsub = 0; + + sindex = *indexp; + t_index = ++sindex; + name = string_extract (string, &t_index, "#%:-=?+/}", EX_VARNAME); + + /* If the name really consists of a special variable, then make sure + that we have the entire name. We don't allow indirect references + to special variables except `#', `?', `@' and `*'. */ + if ((sindex == t_index && + (string[t_index] == '-' || + string[t_index] == '?' || + string[t_index] == '#')) || + (sindex == t_index - 1 && string[sindex] == '!' && + (string[t_index] == '#' || + string[t_index] == '?' || + string[t_index] == '@' || + string[t_index] == '*'))) + { + t_index++; + free (name); + temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0); + name = (char *)xmalloc (3 + (strlen (temp1))); + *name = string[sindex]; + if (string[sindex] == '!') + { + /* indirect reference of $#, $?, $@, or $* */ + name[1] = string[sindex + 1]; + strcpy (name + 2, temp1); + } + else + strcpy (name + 1, temp1); + free (temp1); + } + sindex = t_index; + + /* Find out what character ended the variable name. Then + do the appropriate thing. */ + if (c = string[sindex]) + sindex++; + + /* If c is followed by one of the valid parameter expansion + characters, move past it as normal. If not, assume that + a substring specification is being given, and do not move + past it. */ + if (c == ':' && VALID_PARAM_EXPAND_CHAR (string[sindex])) + { + check_nullness++; + if (c = string[sindex]) + sindex++; + } + else if (c == ':' && string[sindex] != RBRACE) + want_substring = 1; + else if (c == '/' && string[sindex] != RBRACE) + want_patsub = 1; + + /* Catch the valid and invalid brace expressions that made it through the + tests above. */ + /* ${#-} is a valid expansion and means to take the length of $-. + Similarly for ${#?} and ${##}... */ + if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && + VALID_SPECIAL_LENGTH_PARAM (c) && string[sindex] == RBRACE) + { + name = (char *)xrealloc (name, 3); + name[1] = c; + name[2] = '\0'; + c = string[sindex++]; + } + + /* ...but ${#%}, ${#:}, ${#=}, ${#+}, and ${#/} are errors. */ + if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && + member (c, "%:=+/") && string[sindex] == RBRACE) + { + temp = (char *)NULL; + goto bad_substitution; + } + + /* Indirect expansion begins with a `!'. A valid indirect expansion is + either a variable name, one of the positional parameters or a special + variable that expands to one of the positional parameters. */ + want_indir = *name == '!' && + (legal_variable_starter ((unsigned char)name[1]) || DIGIT (name[1]) + || VALID_INDIR_PARAM (name[1])); + + /* Determine the value of this variable. */ + + /* Check for special variables, directly referenced. */ + if (SPECIAL_VAR (name, want_indir)) + var_is_special++; + + /* Check for special expansion things, like the length of a parameter */ + if (*name == '#' && name[1]) + { + /* If we are not pointing at the character just after the + closing brace, then we haven't gotten all of the name. + Since it begins with a special character, this is a bad + substitution. Also check NAME for validity before trying + to go on. */ + if (string[sindex - 1] != RBRACE || (valid_length_expression (name) == 0)) + { + temp = (char *)NULL; + goto bad_substitution; + } + + number = parameter_brace_expand_length (name); + free (name); + + *indexp = sindex; + return ((number < 0) ? &expand_param_error : itos (number)); + } + + /* ${@} is identical to $@. */ + if (name[0] == '@' && name[1] == '\0') + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + + if (contains_dollar_at) + *contains_dollar_at = 1; + } + + /* Process ${!PREFIX*} expansion. */ + if (want_indir && string[sindex - 1] == RBRACE && + (string[sindex - 2] == '*' || string[sindex - 2] == '@') && + legal_variable_starter ((unsigned char) name[1])) + { + char **x; + WORD_LIST *xlist; + + temp1 = savestring (name + 1); + number = strlen (temp1); + temp1[number - 1] = '\0'; + x = all_variables_matching_prefix (temp1); + xlist = strvec_to_word_list (x, 0, 0); + if (string[sindex - 2] == '*') + temp = string_list_dollar_star (xlist); + else + { + temp = string_list_dollar_at (xlist, quoted); + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + } + free (x); + free (xlist); + free (temp1); + *indexp = sindex; + return (temp); + } + +#if defined (ARRAY_VARS) + /* Process ${!ARRAY[@]} and ${!ARRAY[*]} expansion. */ /* [ */ + if (want_indir && string[sindex - 1] == RBRACE && + string[sindex - 2] == ']' && valid_array_reference (name+1)) + { + char *x, *x1; + + temp1 = savestring (name + 1); + x = array_variable_name (temp1, &x1, (int *)0); /* [ */ + FREE (x); + if (ALL_ELEMENT_SUB (x1[0]) && x1[1] == ']') + { + temp = array_keys (temp1, quoted); + if (x1[0] == '@') + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + } + + free (temp1); + *indexp = sindex; + return (temp); + } + + free (temp1); + } +#endif /* ARRAY_VARS */ + + /* Make sure that NAME is valid before trying to go on. */ + if (valid_brace_expansion_word (want_indir ? name + 1 : name, + var_is_special) == 0) + { + temp = (char *)NULL; + goto bad_substitution; + } + + if (want_indir) + temp = parameter_brace_expand_indir (name + 1, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at); + else + temp = parameter_brace_expand_word (name, var_is_special, quoted); + +#if defined (ARRAY_VARS) + if (valid_array_reference (name)) + chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at); +#endif + + var_is_set = temp != (char *)0; + var_is_null = check_nullness && (var_is_set == 0 || *temp == 0); + + /* Get the rest of the stuff inside the braces. */ + if (c && c != RBRACE) + { + /* Extract the contents of the ${ ... } expansion + according to the Posix.2 rules. */ + value = extract_dollar_brace_string (string, &sindex, quoted, 0); + if (string[sindex] == RBRACE) + sindex++; + else + goto bad_substitution; + } + else + value = (char *)NULL; + + *indexp = sindex; + + /* If this is a substring spec, process it and add the result. */ + if (want_substring) + { + temp1 = parameter_brace_substring (name, temp, value, quoted); + FREE (name); + FREE (value); + FREE (temp); + return (temp1); + } + else if (want_patsub) + { + temp1 = parameter_brace_patsub (name, temp, value, quoted); + FREE (name); + FREE (value); + FREE (temp); + return (temp1); + } + + /* Do the right thing based on which character ended the variable name. */ + switch (c) + { + default: + case '\0': + bad_substitution: + report_error (_("%s: bad substitution"), string ? string : "??"); + FREE (value); + FREE (temp); + free (name); + return &expand_param_error; + + case RBRACE: + if (var_is_set == 0 && unbound_vars_is_error) + { + err_unboundvar (name); + FREE (value); + FREE (temp); + free (name); + last_command_exit_value = EXECUTION_FAILURE; + return (interactive_shell ? &expand_param_error : &expand_param_fatal); + } + break; + + case '#': /* ${param#[#]pattern} */ + case '%': /* ${param%[%]pattern} */ + if (value == 0 || *value == '\0' || temp == 0 || *temp == '\0') + { + FREE (value); + break; + } + temp1 = parameter_brace_remove_pattern (name, temp, value, c, quoted); + free (temp); + free (value); + temp = temp1; + break; + + case '-': + case '=': + case '?': + case '+': + if (var_is_set && var_is_null == 0) + { + /* If the operator is `+', we don't want the value of the named + variable for anything, just the value of the right hand side. */ + + if (c == '+') + { + /* XXX -- if we're double-quoted and the named variable is "$@", + we want to turn off any special handling of "$@" -- + we're not using it, so whatever is on the rhs applies. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + + FREE (temp); + if (value) + { + temp = parameter_brace_expand_rhs (name, value, c, + quoted, + quoted_dollar_atp, + contains_dollar_at); + free (value); + } + else + temp = (char *)NULL; + } + else + { + FREE (value); + } + /* Otherwise do nothing; just use the value in TEMP. */ + } + else /* VAR not set or VAR is NULL. */ + { + FREE (temp); + temp = (char *)NULL; + if (c == '=' && var_is_special) + { + report_error (_("$%s: cannot assign in this way"), name); + free (name); + free (value); + return &expand_param_error; + } + else if (c == '?') + { + parameter_brace_expand_error (name, value); + return (interactive_shell ? &expand_param_error : &expand_param_fatal); + } + else if (c != '+') + { + /* XXX -- if we're double-quoted and the named variable is "$@", + we want to turn off any special handling of "$@" -- + we're not using it, so whatever is on the rhs applies. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + + temp = parameter_brace_expand_rhs (name, value, c, quoted, + quoted_dollar_atp, + contains_dollar_at); + } + free (value); + } + + break; + } + free (name); + return (temp); +} + +/* Expand a single ${xxx} expansion. The braces are optional. When + the braces are used, parameter_brace_expand() does the work, + possibly calling param_expand recursively. */ +static char * +param_expand (string, sindex, quoted, expanded_something, + contains_dollar_at, quoted_dollar_at_p, had_quoted_null_p, + pflags) + char *string; + int *sindex, quoted, *expanded_something, *contains_dollar_at; + int *quoted_dollar_at_p, *had_quoted_null_p, pflags; +{ + char *temp, *temp1, uerror[3]; + int zindex, t_index, expok; + unsigned char c; + intmax_t number; + SHELL_VAR *var; + WORD_LIST *list; + + zindex = *sindex; + c = string[++zindex]; + + temp = (char *)NULL; + + /* Do simple cases first. Switch on what follows '$'. */ + switch (c) + { + /* $0 .. $9? */ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + temp1 = dollar_vars[TODIGIT (c)]; + if (unbound_vars_is_error && temp1 == (char *)NULL) + { + uerror[0] = '$'; + uerror[1] = c; + uerror[2] = '\0'; + err_unboundvar (uerror); + last_command_exit_value = EXECUTION_FAILURE; + return (interactive_shell ? &expand_param_error : &expand_param_fatal); + } +#if 1 + if (temp1) + temp = (*temp1 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp1) + : quote_escapes (temp1); + else + temp = (char *)NULL; +#else + temp = temp1 ? quote_escapes (temp1) : (char *)NULL; +#endif + break; + + /* $$ -- pid of the invoking shell. */ + case '$': + temp = itos (dollar_dollar_pid); + break; + + /* $# -- number of positional parameters. */ + case '#': + temp = itos (number_of_args ()); + break; + + /* $? -- return value of the last synchronous command. */ + case '?': + temp = itos (last_command_exit_value); + break; + + /* $- -- flags supplied to the shell on invocation or by `set'. */ + case '-': + temp = which_set_flags (); + break; + + /* $! -- Pid of the last asynchronous command. */ + case '!': + /* If no asynchronous pids have been created, expand to nothing. + If `set -u' has been executed, and no async processes have + been created, this is an expansion error. */ + if (last_asynchronous_pid == NO_PID) + { + if (expanded_something) + *expanded_something = 0; + temp = (char *)NULL; + if (unbound_vars_is_error) + { + uerror[0] = '$'; + uerror[1] = c; + uerror[2] = '\0'; + err_unboundvar (uerror); + last_command_exit_value = EXECUTION_FAILURE; + return (interactive_shell ? &expand_param_error : &expand_param_fatal); + } + } + else + temp = itos (last_asynchronous_pid); + break; + + /* The only difference between this and $@ is when the arg is quoted. */ + case '*': /* `$*' */ + list = list_rest_of_args (); + + /* If there are no command-line arguments, this should just + disappear if there are other characters in the expansion, + even if it's quoted. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && list == 0) + temp = (char *)NULL; + else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + { + /* If we have "$*" we want to make a string of the positional + parameters, separated by the first character of $IFS, and + quote the whole string, including the separators. If IFS + is unset, the parameters are separated by ' '; if $IFS is + null, the parameters are concatenated. */ +#if 0 + temp = string_list_dollar_star (list); +#else + temp = (quoted & Q_DOUBLE_QUOTES) ? string_list_dollar_star (list) : string_list (list); +#endif + temp1 = quote_string (temp); + free (temp); + temp = temp1; + } + else + { + /* If the $* is not quoted it is identical to $@ */ + temp = string_list_dollar_at (list, quoted); + if (expand_no_split_dollar_star == 0 && contains_dollar_at) + *contains_dollar_at = 1; + } + + dispose_words (list); + break; + + /* When we have "$@" what we want is "$1" "$2" "$3" ... This + means that we have to turn quoting off after we split into + the individually quoted arguments so that the final split + on the first character of $IFS is still done. */ + case '@': /* `$@' */ + list = list_rest_of_args (); + + /* We want to flag the fact that we saw this. We can't turn + off quoting entirely, because other characters in the + string might need it (consider "\"$@\""), but we need some + way to signal that the final split on the first character + of $IFS should be done, even though QUOTED is 1. */ + if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + *quoted_dollar_at_p = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + + /* We want to separate the positional parameters with the first + character of $IFS in case $IFS is something other than a space. + We also want to make sure that splitting is done no matter what -- + according to POSIX.2, this expands to a list of the positional + parameters no matter what IFS is set to. */ + temp = string_list_dollar_at (list, quoted); + + dispose_words (list); + break; + + case LBRACE: + temp = parameter_brace_expand (string, &zindex, quoted, + quoted_dollar_at_p, + contains_dollar_at); + if (temp == &expand_param_error || temp == &expand_param_fatal) + return (temp); + + /* XXX */ + /* Quoted nulls should be removed if there is anything else + in the string. */ + /* Note that we saw the quoted null so we can add one back at + the end of this function if there are no other characters + in the string, discard TEMP, and go on. The exception to + this is when we have "${@}" and $1 is '', since $@ needs + special handling. */ + if (temp && QUOTED_NULL (temp)) + { + if (had_quoted_null_p) + *had_quoted_null_p = 1; + if (*quoted_dollar_at_p == 0) + { + free (temp); + temp = (char *)NULL; + } + + } + + goto return0; + + /* Do command or arithmetic substitution. */ + case LPAREN: + /* We have to extract the contents of this paren substitution. */ + t_index = zindex + 1; + temp = extract_command_subst (string, &t_index); + zindex = t_index; + + /* For Posix.2-style `$(( ))' arithmetic substitution, + extract the expression and pass it to the evaluator. */ + if (temp && *temp == LPAREN) + { + char *temp2; + temp1 = temp + 1; + temp2 = savestring (temp1); + t_index = strlen (temp2) - 1; + + if (temp2[t_index] != RPAREN) + { + free (temp2); + goto comsub; + } + + /* Cut off ending `)' */ + temp2[t_index] = '\0'; + + /* Expand variables found inside the expression. */ + temp1 = expand_string_if_necessary (temp2, Q_DOUBLE_QUOTES, expand_string); + free (temp2); + +arithsub: + /* No error messages. */ + this_command_name = (char *)NULL; + number = evalexp (temp1, &expok); + free (temp); + free (temp1); + if (expok == 0) + { + if (interactive_shell == 0 && posixly_correct) + { + last_command_exit_value = EXECUTION_FAILURE; + return (&expand_param_fatal); + } + else + return (&expand_param_error); + } + temp = itos (number); + break; + } + +comsub: + if (pflags & PF_NOCOMSUB) + /* we need zindex+1 because string[zindex] == RPAREN */ + temp1 = substring (string, *sindex, zindex+1); + else + temp1 = command_substitute (temp, quoted); + FREE (temp); + temp = temp1; + break; + + /* Do POSIX.2d9-style arithmetic substitution. This will probably go + away in a future bash release. */ + case '[': + /* Extract the contents of this arithmetic substitution. */ + t_index = zindex + 1; + temp = extract_arithmetic_subst (string, &t_index); + zindex = t_index; + + /* Do initial variable expansion. */ + temp1 = expand_string_if_necessary (temp, Q_DOUBLE_QUOTES, expand_string); + + goto arithsub; + + default: + /* Find the variable in VARIABLE_LIST. */ + temp = (char *)NULL; + + for (t_index = zindex; (c = string[zindex]) && legal_variable_char (c); zindex++) + ; + temp1 = (zindex > t_index) ? substring (string, t_index, zindex) : (char *)NULL; + + /* If this isn't a variable name, then just output the `$'. */ + if (temp1 == 0 || *temp1 == '\0') + { + FREE (temp1); + temp = (char *)xmalloc (2); + temp[0] = '$'; + temp[1] = '\0'; + if (expanded_something) + *expanded_something = 0; + goto return0; + } + + /* If the variable exists, return its value cell. */ + var = find_variable (temp1); + + if (var && invisible_p (var) == 0 && var_isset (var)) + { +#if defined (ARRAY_VARS) + if (array_p (var)) + { + temp = array_reference (array_cell (var), 0); + if (temp) + temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp) + : quote_escapes (temp); + else if (unbound_vars_is_error) + goto unbound_variable; + } + else +#endif + { + temp = value_cell (var); + + temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp) + : quote_escapes (temp); + } + + free (temp1); + + goto return0; + } + + temp = (char *)NULL; + +unbound_variable: + if (unbound_vars_is_error) + err_unboundvar (temp1); + else + { + free (temp1); + goto return0; + } + + free (temp1); + last_command_exit_value = EXECUTION_FAILURE; + return ((unbound_vars_is_error && interactive_shell == 0) + ? &expand_param_fatal + : &expand_param_error); + } + + if (string[zindex]) + zindex++; + +return0: + *sindex = zindex; + return (temp); +} + +/* Make a word list which is the result of parameter and variable + expansion, command substitution, arithmetic substitution, and + quote removal of WORD. Return a pointer to a WORD_LIST which is + the result of the expansion. If WORD contains a null word, the + word list returned is also null. + + QUOTED contains flag values defined in shell.h. + + ISEXP is used to tell expand_word_internal that the word should be + treated as the result of an expansion. This has implications for + how IFS characters in the word are treated. + + CONTAINS_DOLLAR_AT and EXPANDED_SOMETHING are return values; when non-null + they point to an integer value which receives information about expansion. + CONTAINS_DOLLAR_AT gets non-zero if WORD contained "$@", else zero. + EXPANDED_SOMETHING get non-zero if WORD contained any parameter expansions, + else zero. + + This only does word splitting in the case of $@ expansion. In that + case, we split on ' '. */ + +/* Values for the local variable quoted_state. */ +#define UNQUOTED 0 +#define PARTIALLY_QUOTED 1 +#define WHOLLY_QUOTED 2 + +static WORD_LIST * +expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_something) + WORD_DESC *word; + int quoted, isexp; + int *contains_dollar_at; + int *expanded_something; +{ + WORD_LIST *list; + WORD_DESC *tword; + + /* The intermediate string that we build while expanding. */ + char *istring; + + /* The current size of the above object. */ + int istring_size; + + /* Index into ISTRING. */ + int istring_index; + + /* Temporary string storage. */ + char *temp, *temp1; + + /* The text of WORD. */ + register char *string; + + /* The size of STRING. */ + size_t string_size; + + /* The index into STRING. */ + int sindex; + + /* This gets 1 if we see a $@ while quoted. */ + int quoted_dollar_at; + + /* One of UNQUOTED, PARTIALLY_QUOTED, or WHOLLY_QUOTED, depending on + whether WORD contains no quoting characters, a partially quoted + string (e.g., "xx"ab), or is fully quoted (e.g., "xxab"). */ + int quoted_state; + + int had_quoted_null; + int has_dollar_at; + int tflag; + + register unsigned char c; /* Current character. */ + int t_index; /* For calls to string_extract_xxx. */ + + char twochars[2]; + + DECLARE_MBSTATE; + + istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE); + istring[istring_index = 0] = '\0'; + quoted_dollar_at = had_quoted_null = has_dollar_at = 0; + quoted_state = UNQUOTED; + + string = word->word; + if (string == 0) + goto finished_with_string; + string_size = strlen (string); + + if (contains_dollar_at) + *contains_dollar_at = 0; + + /* Begin the expansion. */ + + for (sindex = 0; ;) + { + c = string[sindex]; + + /* Case on toplevel character. */ + switch (c) + { + case '\0': + goto finished_with_string; + + case CTLESC: + sindex++; +#if HANDLE_MULTIBYTE + if (MB_CUR_MAX > 1 && string[sindex]) + { + SADD_MBQCHAR_BODY(temp, string, sindex, string_size); + } + else +#endif + { + temp = (char *)xmalloc (3); + temp[0] = CTLESC; + temp[1] = c = string[sindex]; + temp[2] = '\0'; + } + +dollar_add_string: + if (string[sindex]) + sindex++; + +add_string: + if (temp) + { + istring = sub_append_string (temp, istring, &istring_index, &istring_size); + temp = (char *)0; + } + + break; + +#if defined (PROCESS_SUBSTITUTION) + /* Process substitution. */ + case '<': + case '>': + { + if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || posixly_correct) + { + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + t_index = sindex + 1; /* skip past both '<' and LPAREN */ + + temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index); /*))*/ + sindex = t_index; + + /* If the process substitution specification is `<()', we want to + open the pipe for writing in the child and produce output; if + it is `>()', we want to open the pipe for reading in the child + and consume input. */ + temp = temp1 ? process_substitute (temp1, (c == '>')) : (char *)0; + + FREE (temp1); + + goto dollar_add_string; + } +#endif /* PROCESS_SUBSTITUTION */ + + case '$': + if (expanded_something) + *expanded_something = 1; + + has_dollar_at = 0; + temp = param_expand (string, &sindex, quoted, expanded_something, + &has_dollar_at, "ed_dollar_at, + &had_quoted_null, + (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0); + + if (temp == &expand_param_error || temp == &expand_param_fatal) + { + free (string); + free (istring); + return ((temp == &expand_param_error) ? &expand_word_error + : &expand_word_fatal); + } + if (contains_dollar_at && has_dollar_at) + *contains_dollar_at = 1; + goto add_string; + break; + + case '`': /* Backquoted command substitution. */ + { + t_index = sindex++; + + if (expanded_something) + *expanded_something = 1; + + temp = string_extract (string, &sindex, "`", 0); + if (word->flags & W_NOCOMSUB) + /* sindex + 1 because string[sindex] == '`' */ + temp1 = substring (string, t_index, sindex + 1); + else + { + de_backslash (temp); + temp1 = command_substitute (temp, quoted); + } + FREE (temp); + temp = temp1; + goto dollar_add_string; + } + + case '\\': + if (string[sindex + 1] == '\n') + { + sindex += 2; + continue; + } + + c = string[++sindex]; + + if (quoted & Q_HERE_DOCUMENT) + tflag = CBSHDOC; + else if (quoted & Q_DOUBLE_QUOTES) + tflag = CBSDQUOTE; + else + tflag = 0; + + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0)) + { + SCOPY_CHAR_I (twochars, '\\', c, string, sindex, string_size); + } + else if (c == 0) + { + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + { + SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size); + } + + sindex++; +add_twochars: + /* BEFORE jumping here, we need to increment sindex if appropriate */ + RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, + DEFAULT_ARRAY_SIZE); + istring[istring_index++] = twochars[0]; + istring[istring_index++] = twochars[1]; + istring[istring_index] = '\0'; + + break; + + case '"': +#if 0 + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT|Q_PATQUOTE)) +#else + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) +#endif + goto add_character; + + t_index = ++sindex; + temp = string_extract_double_quoted (string, &sindex, 0); + + /* If the quotes surrounded the entire string, then the + whole word was quoted. */ + quoted_state = (t_index == 1 && string[sindex] == '\0') + ? WHOLLY_QUOTED + : PARTIALLY_QUOTED; + + if (temp && *temp) + { + tword = make_word (temp); /* XXX */ + free (temp); + temp = (char *)NULL; + + has_dollar_at = 0; + list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &has_dollar_at, (int *)NULL); + + if (list == &expand_word_error || list == &expand_word_fatal) + { + free (istring); + free (string); + /* expand_word_internal has already freed temp_word->word + for us because of the way it prints error messages. */ + tword->word = (char *)NULL; + dispose_word (tword); + return list; + } + + dispose_word (tword); + + /* "$@" (a double-quoted dollar-at) expands into nothing, + not even a NULL word, when there are no positional + parameters. */ + if (list == 0 && has_dollar_at) + { + quoted_dollar_at++; + break; + } + + /* If we get "$@", we know we have expanded something, so we + need to remember it for the final split on $IFS. This is + a special case; it's the only case where a quoted string + can expand into more than one word. It's going to come back + from the above call to expand_word_internal as a list with + a single word, in which all characters are quoted and + separated by blanks. What we want to do is to turn it back + into a list for the next piece of code. */ + if (list) + dequote_list (list); + + if (has_dollar_at) + { + quoted_dollar_at++; + if (contains_dollar_at) + *contains_dollar_at = 1; + if (expanded_something) + *expanded_something = 1; + } + } + else + { + /* What we have is "". This is a minor optimization. */ + FREE (temp); + list = (WORD_LIST *)NULL; + } + + /* The code above *might* return a list (consider the case of "$@", + where it returns "$1", "$2", etc.). We can't throw away the + rest of the list, and we have to make sure each word gets added + as quoted. We test on tresult->next: if it is non-NULL, we + quote the whole list, save it to a string with string_list, and + add that string. We don't need to quote the results of this + (and it would be wrong, since that would quote the separators + as well), so we go directly to add_string. */ + if (list) + { + if (list->next) + { + /* Testing quoted_dollar_at makes sure that "$@" is + split correctly when $IFS does not contain a space. */ + temp = quoted_dollar_at + ? string_list_dollar_at (list, Q_DOUBLE_QUOTES) + : string_list (quote_list (list)); + dispose_words (list); + goto add_string; + } + else + { + temp = savestring (list->word->word); + dispose_words (list); +#if 1 + /* If the string is not a quoted null string, we want + to remove any embedded unquoted CTLNUL characters. + We do not want to turn quoted null strings back into + the empty string, though. We do this because we + want to remove any quoted nulls from expansions that + contain other characters. For example, if we have + x"$*"y or "x$*y" and there are no positional parameters, + the $* should expand into nothing. */ + /* HOWEVER, this fails if the string contains a literal + CTLNUL or CTLNUL is contained in the (non-null) expansion + of some variable. I'm not sure what to do about this + yet. There has to be some way to indicate the difference + between the two. An auxiliary data structure might be + necessary. */ + if (QUOTED_NULL (temp) == 0) + remove_quoted_nulls (temp); /* XXX */ +#endif + } + } + else + temp = (char *)NULL; + + /* We do not want to add quoted nulls to strings that are only + partially quoted; we can throw them away. */ + if (temp == 0 && quoted_state == PARTIALLY_QUOTED) + continue; + + add_quoted_string: + + if (temp) + { + temp1 = temp; + temp = quote_string (temp); + free (temp1); + goto add_string; + } + else + { + /* Add NULL arg. */ + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + + /* break; */ + + case '\'': +#if 0 + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT|Q_PATQUOTE)) +#else + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) +#endif + goto add_character; + + t_index = ++sindex; + temp = string_extract_single_quoted (string, &sindex); + + /* If the entire STRING was surrounded by single quotes, + then the string is wholly quoted. */ + quoted_state = (t_index == 1 && string[sindex] == '\0') + ? WHOLLY_QUOTED + : PARTIALLY_QUOTED; + + /* If all we had was '', it is a null expansion. */ + if (*temp == '\0') + { + free (temp); + temp = (char *)NULL; + } + else + remove_quoted_escapes (temp); /* ??? */ + + /* We do not want to add quoted nulls to strings that are only + partially quoted; such nulls are discarded. */ + if (temp == 0 && (quoted_state == PARTIALLY_QUOTED)) + continue; + + /* If we have a quoted null expansion, add a quoted NULL to istring. */ + if (temp == 0) + { + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + goto add_quoted_string; + + /* break; */ + + default: + /* This is the fix for " $@ " */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c))) + { + if (string[sindex]) /* from old goto dollar_add_string */ + sindex++; + if (c == 0) + { + c = CTLNUL; + goto add_character; + } + else + { +#if HANDLE_MULTIBYTE + if (MB_CUR_MAX > 1) + sindex--; + + if (MB_CUR_MAX > 1) + { + SADD_MBQCHAR_BODY(temp, string, sindex, string_size); + } + else +#endif + { + twochars[0] = CTLESC; + twochars[1] = c; + goto add_twochars; + } + } + } + + SADD_MBCHAR (temp, string, sindex, string_size); + + add_character: + RESIZE_MALLOCED_BUFFER (istring, istring_index, 1, istring_size, + DEFAULT_ARRAY_SIZE); + istring[istring_index++] = c; + istring[istring_index] = '\0'; + + /* Next character. */ + sindex++; + } + } + +finished_with_string: + /* OK, we're ready to return. If we have a quoted string, and + quoted_dollar_at is not set, we do no splitting at all; otherwise + we split on ' '. The routines that call this will handle what to + do if nothing has been expanded. */ + + /* Partially and wholly quoted strings which expand to the empty + string are retained as an empty arguments. Unquoted strings + which expand to the empty string are discarded. The single + exception is the case of expanding "$@" when there are no + positional parameters. In that case, we discard the expansion. */ + + /* Because of how the code that handles "" and '' in partially + quoted strings works, we need to make ISTRING into a QUOTED_NULL + if we saw quoting characters, but the expansion was empty. + "" and '' are tossed away before we get to this point when + processing partially quoted strings. This makes "" and $xxx"" + equivalent when xxx is unset. We also look to see whether we + saw a quoted null from a ${} expansion and add one back if we + need to. */ + + /* If we expand to nothing and there were no single or double quotes + in the word, we throw it away. Otherwise, we return a NULL word. + The single exception is for $@ surrounded by double quotes when + there are no positional parameters. In that case, we also throw + the word away. */ + + if (*istring == '\0') + { + if (quoted_dollar_at == 0 && (had_quoted_null || quoted_state == PARTIALLY_QUOTED)) + { + istring[0] = CTLNUL; + istring[1] = '\0'; + tword = make_bare_word (istring); + list = make_word_list (tword, (WORD_LIST *)NULL); + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + tword->flags |= W_QUOTED; + } + /* According to sh, ksh, and Posix.2, if a word expands into nothing + and a double-quoted "$@" appears anywhere in it, then the entire + word is removed. */ + else if (quoted_state == UNQUOTED || quoted_dollar_at) + list = (WORD_LIST *)NULL; +#if 0 + else + { + tword = make_bare_word (istring); + list = make_word_list (tword, (WORD_LIST *)NULL); + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + tword->flags |= W_QUOTED; + } +#else + else + list = (WORD_LIST *)NULL; +#endif + } + else if (word->flags & W_NOSPLIT) + { + tword = make_bare_word (istring); + list = make_word_list (tword, (WORD_LIST *)NULL); + if (word->flags & W_ASSIGNMENT) + tword->flags |= W_ASSIGNMENT; /* XXX */ + if (word->flags & W_NOGLOB) + tword->flags |= W_NOGLOB; /* XXX */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + tword->flags |= W_QUOTED; + } + else + { + char *ifs_chars; + + ifs_chars = (quoted_dollar_at || has_dollar_at) ? ifs_value : (char *)NULL; + + /* If we have $@, we need to split the results no matter what. If + IFS is unset or NULL, string_list_dollar_at has separated the + positional parameters with a space, so we split on space (we have + set ifs_chars to " \t\n" above if ifs is unset). If IFS is set, + string_list_dollar_at has separated the positional parameters + with the first character of $IFS, so we split on $IFS. */ + if (has_dollar_at && ifs_chars) + list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1); + else + { + tword = make_bare_word (istring); + list = make_word_list (tword, (WORD_LIST *)NULL); + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED)) + tword->flags |= W_QUOTED; + if (word->flags & W_ASSIGNMENT) + tword->flags |= W_ASSIGNMENT; + if (word->flags & W_NOGLOB) + tword->flags |= W_NOGLOB; + } + } + + free (istring); + return (list); +} + +/* **************************************************************** */ +/* */ +/* Functions for Quote Removal */ +/* */ +/* **************************************************************** */ + +/* Perform quote removal on STRING. If QUOTED > 0, assume we are obeying the + backslash quoting rules for within double quotes or a here document. */ +char * +string_quote_removal (string, quoted) + char *string; + int quoted; +{ + size_t slen; + char *r, *result_string, *temp, *send; + int sindex, tindex, dquote; + unsigned char c; + DECLARE_MBSTATE; + + /* The result can be no longer than the original string. */ + slen = strlen (string); + send = string + slen; + + r = result_string = (char *)xmalloc (slen + 1); + + for (dquote = sindex = 0; c = string[sindex];) + { + switch (c) + { + case '\\': + c = string[++sindex]; + if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0) + *r++ = '\\'; + /* FALLTHROUGH */ + + default: + SCOPY_CHAR_M (r, string, send, sindex); + break; + + case '\'': + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) + { + *r++ = c; + sindex++; + break; + } + tindex = sindex + 1; + temp = string_extract_single_quoted (string, &tindex); + if (temp) + { + strcpy (r, temp); + r += strlen (r); + free (temp); + } + sindex = tindex; + break; + + case '"': + dquote = 1 - dquote; + sindex++; + break; + } + } + *r = '\0'; + return (result_string); +} + +#if 0 +/* UNUSED */ +/* Perform quote removal on word WORD. This allocates and returns a new + WORD_DESC *. */ +WORD_DESC * +word_quote_removal (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_DESC *w; + char *t; + + t = string_quote_removal (word->word, quoted); + w = make_bare_word (t); + free (t); + return (w); +} + +/* Perform quote removal on all words in LIST. If QUOTED is non-zero, + the members of the list are treated as if they are surrounded by + double quotes. Return a new list, or NULL if LIST is NULL. */ +WORD_LIST * +word_list_quote_removal (list, quoted) + WORD_LIST *list; + int quoted; +{ + WORD_LIST *result, *t, *tresult; + + for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) + { + tresult = make_word_list (word_quote_removal (t->word, quoted), (WORD_LIST *)NULL); + result = (WORD_LIST *) list_append (result, tresult); + } + return (result); +} +#endif + +/******************************************* + * * + * Functions to perform word splitting * + * * + *******************************************/ + +void +setifs (v) + SHELL_VAR *v; +{ + char *t; + unsigned char uc; + + ifs_var = v; + ifs_value = v ? value_cell (v) : " \t\n"; + + /* Should really merge ifs_cmap with sh_syntaxtab. */ + memset (ifs_cmap, '\0', sizeof (ifs_cmap)); + for (t = ifs_value ; t && *t; t++) + { + uc = *t; + ifs_cmap[uc] = 1; + } + + ifs_firstc = ifs_value ? *ifs_value : 0; +} + +char * +getifs () +{ + return ifs_value; +} + +/* This splits a single word into a WORD LIST on $IFS, but only if the word + is not quoted. list_string () performs quote removal for us, even if we + don't do any splitting. */ +WORD_LIST * +word_split (w, ifs_chars) + WORD_DESC *w; + char *ifs_chars; +{ + WORD_LIST *result; + + if (w) + { + char *xifs; + + xifs = ((w->flags & W_QUOTED) || ifs_chars == 0) ? "" : ifs_chars; + result = list_string (w->word, xifs, w->flags & W_QUOTED); + } + else + result = (WORD_LIST *)NULL; + + return (result); +} + +/* Perform word splitting on LIST and return the RESULT. It is possible + to return (WORD_LIST *)NULL. */ +static WORD_LIST * +word_list_split (list) + WORD_LIST *list; +{ + WORD_LIST *result, *t, *tresult; + + for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) + { + tresult = word_split (t->word, ifs_value); + result = (WORD_LIST *) list_append (result, tresult); + } + return (result); +} + +/************************************************** + * * + * Functions to expand an entire WORD_LIST * + * * + **************************************************/ + +/* Do any word-expansion-specific cleanup and jump to top_level */ +static void +exp_jump_to_top_level (v) + int v; +{ + /* Cleanup code goes here. */ + expand_no_split_dollar_star = 0; /* XXX */ + expanding_redir = 0; + + jump_to_top_level (v); +} + +/* Put NLIST (which is a WORD_LIST * of only one element) at the front of + ELIST, and set ELIST to the new list. */ +#define PREPEND_LIST(nlist, elist) \ + do { nlist->next = elist; elist = nlist; } while (0) + +/* Separate out any initial variable assignments from TLIST. If set -k has + been executed, remove all assignment statements from TLIST. Initial + variable assignments and other environment assignments are placed + on SUBST_ASSIGN_VARLIST. */ +static WORD_LIST * +separate_out_assignments (tlist) + WORD_LIST *tlist; +{ + register WORD_LIST *vp, *lp; + + if (!tlist) + return ((WORD_LIST *)NULL); + + if (subst_assign_varlist) + dispose_words (subst_assign_varlist); /* Clean up after previous error */ + + subst_assign_varlist = (WORD_LIST *)NULL; + vp = lp = tlist; + + /* Separate out variable assignments at the start of the command. + Loop invariant: vp->next == lp + Loop postcondition: + lp = list of words left after assignment statements skipped + tlist = original list of words + */ + while (lp && (lp->word->flags & W_ASSIGNMENT)) + { + vp = lp; + lp = lp->next; + } + + /* If lp != tlist, we have some initial assignment statements. + We make SUBST_ASSIGN_VARLIST point to the list of assignment + words and TLIST point to the remaining words. */ + if (lp != tlist) + { + subst_assign_varlist = tlist; + /* ASSERT(vp->next == lp); */ + vp->next = (WORD_LIST *)NULL; /* terminate variable list */ + tlist = lp; /* remainder of word list */ + } + + /* vp == end of variable list */ + /* tlist == remainder of original word list without variable assignments */ + if (!tlist) + /* All the words in tlist were assignment statements */ + return ((WORD_LIST *)NULL); + + /* ASSERT(tlist != NULL); */ + /* ASSERT((tlist->word->flags & W_ASSIGNMENT) == 0); */ + + /* If the -k option is in effect, we need to go through the remaining + words, separate out the assignment words, and place them on + SUBST_ASSIGN_VARLIST. */ + if (place_keywords_in_env) + { + WORD_LIST *tp; /* tp == running pointer into tlist */ + + tp = tlist; + lp = tlist->next; + + /* Loop Invariant: tp->next == lp */ + /* Loop postcondition: tlist == word list without assignment statements */ + while (lp) + { + if (lp->word->flags & W_ASSIGNMENT) + { + /* Found an assignment statement, add this word to end of + subst_assign_varlist (vp). */ + if (!subst_assign_varlist) + subst_assign_varlist = vp = lp; + else + { + vp->next = lp; + vp = lp; + } + + /* Remove the word pointed to by LP from TLIST. */ + tp->next = lp->next; + /* ASSERT(vp == lp); */ + lp->next = (WORD_LIST *)NULL; + lp = tp->next; + } + else + { + tp = lp; + lp = lp->next; + } + } + } + return (tlist); +} + +#define WEXP_VARASSIGN 0x001 +#define WEXP_BRACEEXP 0x002 +#define WEXP_TILDEEXP 0x004 +#define WEXP_PARAMEXP 0x008 +#define WEXP_PATHEXP 0x010 + +/* All of the expansions, including variable assignments at the start of + the list. */ +#define WEXP_ALL (WEXP_VARASSIGN|WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) + +/* All of the expansions except variable assignments at the start of + the list. */ +#define WEXP_NOVARS (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) + +/* All of the `shell expansions': brace expansion, tilde expansion, parameter + expansion, command substitution, arithmetic expansion, word splitting, and + quote removal. */ +#define WEXP_SHELLEXP (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP) + +/* Take the list of words in LIST and do the various substitutions. Return + a new list of words which is the expanded list, and without things like + variable assignments. */ + +WORD_LIST * +expand_words (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_ALL)); +} + +/* Same as expand_words (), but doesn't hack variable or environment + variables. */ +WORD_LIST * +expand_words_no_vars (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_NOVARS)); +} + +WORD_LIST * +expand_words_shellexp (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_SHELLEXP)); +} + +static WORD_LIST * +glob_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + char **glob_array, *temp_string; + register int glob_index; + WORD_LIST *glob_list, *output_list, *disposables, *next; + WORD_DESC *tword; + + output_list = disposables = (WORD_LIST *)NULL; + glob_array = (char **)NULL; + while (tlist) + { + /* For each word, either globbing is attempted or the word is + added to orig_list. If globbing succeeds, the results are + added to orig_list and the word (tlist) is added to the list + of disposable words. If globbing fails and failed glob + expansions are left unchanged (the shell default), the + original word is added to orig_list. If globbing fails and + failed glob expansions are removed, the original word is + added to the list of disposable words. orig_list ends up + in reverse order and requires a call to REVERSE_LIST to + be set right. After all words are examined, the disposable + words are freed. */ + next = tlist->next; + + /* If the word isn't an assignment and contains an unquoted + pattern matching character, then glob it. */ + if ((tlist->word->flags & W_NOGLOB) == 0 && + unquoted_glob_pattern_p (tlist->word->word)) + { + glob_array = shell_glob_filename (tlist->word->word); + + /* Handle error cases. + I don't think we should report errors like "No such file + or directory". However, I would like to report errors + like "Read failed". */ + + if (glob_array == 0 || GLOB_FAILED (glob_array)) + { + glob_array = (char **)xmalloc (sizeof (char *)); + glob_array[0] = (char *)NULL; + } + + /* Dequote the current word in case we have to use it. */ + if (glob_array[0] == NULL) + { + temp_string = dequote_string (tlist->word->word); + free (tlist->word->word); + tlist->word->word = temp_string; + } + + /* Make the array into a word list. */ + glob_list = (WORD_LIST *)NULL; + for (glob_index = 0; glob_array[glob_index]; glob_index++) + { + tword = make_bare_word (glob_array[glob_index]); + tword->flags |= W_GLOBEXP; /* XXX */ + glob_list = make_word_list (tword, glob_list); + } + + if (glob_list) + { + output_list = (WORD_LIST *)list_append (glob_list, output_list); + PREPEND_LIST (tlist, disposables); + } + else if (fail_glob_expansion != 0) + { + report_error (_("no match: %s"), tlist->word->word); + jump_to_top_level (DISCARD); + } + else if (allow_null_glob_expansion == 0) + { + /* Failed glob expressions are left unchanged. */ + PREPEND_LIST (tlist, output_list); + } + else + { + /* Failed glob expressions are removed. */ + PREPEND_LIST (tlist, disposables); + } + } + else + { + /* Dequote the string. */ + temp_string = dequote_string (tlist->word->word); + free (tlist->word->word); + tlist->word->word = temp_string; + PREPEND_LIST (tlist, output_list); + } + + strvec_dispose (glob_array); + glob_array = (char **)NULL; + + tlist = next; + } + + if (disposables) + dispose_words (disposables); + + if (output_list) + output_list = REVERSE_LIST (output_list, WORD_LIST *); + + return (output_list); +} + +#if defined (BRACE_EXPANSION) +static WORD_LIST * +brace_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + register char **expansions; + char *temp_string; + WORD_LIST *disposables, *output_list, *next; + WORD_DESC *w; + int eindex; + + for (disposables = output_list = (WORD_LIST *)NULL; tlist; tlist = next) + { + next = tlist->next; + + /* Only do brace expansion if the word has a brace character. If + not, just add the word list element to BRACES and continue. In + the common case, at least when running shell scripts, this will + degenerate to a bunch of calls to `xstrchr', and then what is + basically a reversal of TLIST into BRACES, which is corrected + by a call to REVERSE_LIST () on BRACES when the end of TLIST + is reached. */ + if (xstrchr (tlist->word->word, LBRACE)) + { + expansions = brace_expand (tlist->word->word); + + for (eindex = 0; temp_string = expansions[eindex]; eindex++) + { + w = make_word (temp_string); + /* If brace expansion didn't change the word, preserve + the flags. We may want to preserve the flags + unconditionally someday -- XXX */ + if (STREQ (temp_string, tlist->word->word)) + w->flags = tlist->word->flags; + output_list = make_word_list (w, output_list); + free (expansions[eindex]); + } + free (expansions); + + /* Add TLIST to the list of words to be freed after brace + expansion has been performed. */ + PREPEND_LIST (tlist, disposables); + } + else + PREPEND_LIST (tlist, output_list); + } + + if (disposables) + dispose_words (disposables); + + if (output_list) + output_list = REVERSE_LIST (output_list, WORD_LIST *); + + return (output_list); +} +#endif + +static WORD_LIST * +shell_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list; + int expanded_something, has_dollar_at; + char *temp_string; + + /* We do tilde expansion all the time. This is what 1003.2 says. */ + new_list = (WORD_LIST *)NULL; + for (orig_list = tlist; tlist; tlist = next) + { + temp_string = tlist->word->word; + + next = tlist->next; + + /* Posix.2 section 3.6.1 says that tildes following `=' in words + which are not assignment statements are not expanded. If the + shell isn't in posix mode, though, we perform tilde expansion + on `likely candidate' unquoted assignment statements (flags + include W_ASSIGNMENT but not W_QUOTED). A likely candidate + contains an unquoted :~ or =~. Something to think about: we + now have a flag that says to perform tilde expansion on arguments + to `assignment builtins' like declare and export that look like + assignment statements. We now do tilde expansion on such words + even in POSIX mode. */ + if (((tlist->word->flags & (W_ASSIGNMENT|W_QUOTED)) == W_ASSIGNMENT) && + (posixly_correct == 0 || (tlist->word->flags & W_TILDEEXP)) && + (unquoted_substring ("=~", temp_string) || unquoted_substring (":~", temp_string))) + { + tlist->word->word = bash_tilde_expand (temp_string, 1); + free (temp_string); + } + else if (temp_string[0] == '~') + { + tlist->word->word = bash_tilde_expand (temp_string, 0); + free (temp_string); + } + + expanded_something = 0; + expanded = expand_word_internal + (tlist->word, 0, 0, &has_dollar_at, &expanded_something); + + if (expanded == &expand_word_error || expanded == &expand_word_fatal) + { + /* By convention, each time this error is returned, + tlist->word->word has already been freed. */ + tlist->word->word = (char *)NULL; + + /* Dispose our copy of the original list. */ + dispose_words (orig_list); + /* Dispose the new list we're building. */ + dispose_words (new_list); + + last_command_exit_value = EXECUTION_FAILURE; + if (expanded == &expand_word_error) + exp_jump_to_top_level (DISCARD); + else + exp_jump_to_top_level (FORCE_EOF); + } + + /* Don't split words marked W_NOSPLIT. */ + if (expanded_something && (tlist->word->flags & W_NOSPLIT) == 0) + { + temp_list = word_list_split (expanded); + dispose_words (expanded); + } + else + { + /* If no parameter expansion, command substitution, process + substitution, or arithmetic substitution took place, then + do not do word splitting. We still have to remove quoted + null characters from the result. */ + word_list_remove_quoted_nulls (expanded); + temp_list = expanded; + } + + expanded = REVERSE_LIST (temp_list, WORD_LIST *); + new_list = (WORD_LIST *)list_append (expanded, new_list); + } + + if (orig_list) + dispose_words (orig_list); + + if (new_list) + new_list = REVERSE_LIST (new_list, WORD_LIST *); + + return (new_list); +} + +/* The workhorse for expand_words () and expand_words_no_vars (). + First arg is LIST, a WORD_LIST of words. + Second arg EFLAGS is a flags word controlling which expansions are + performed. + + This does all of the substitutions: brace expansion, tilde expansion, + parameter expansion, command substitution, arithmetic expansion, + process substitution, word splitting, and pathname expansion, according + to the bits set in EFLAGS. Words with the W_QUOTED or W_NOSPLIT bits + set, or for which no expansion is done, do not undergo word splitting. + Words with the W_NOGLOB bit set do not undergo pathname expansion. */ +static WORD_LIST * +expand_word_list_internal (list, eflags) + WORD_LIST *list; + int eflags; +{ + WORD_LIST *new_list, *temp_list; + int tint; + + if (list == 0) + return ((WORD_LIST *)NULL); + + garglist = new_list = copy_word_list (list); + if (eflags & WEXP_VARASSIGN) + { + garglist = new_list = separate_out_assignments (new_list); + if (new_list == 0) + { + if (subst_assign_varlist) + { + /* All the words were variable assignments, so they are placed + into the shell's environment. */ + for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next) + { + this_command_name = (char *)NULL; /* no arithmetic errors */ + tint = do_assignment (temp_list->word->word); + /* Variable assignment errors in non-interactive shells + running in Posix.2 mode cause the shell to exit. */ + if (tint == 0) + { + last_command_exit_value = EXECUTION_FAILURE; + if (interactive_shell == 0 && posixly_correct) + exp_jump_to_top_level (FORCE_EOF); + else + exp_jump_to_top_level (DISCARD); + } + } + dispose_words (subst_assign_varlist); + subst_assign_varlist = (WORD_LIST *)NULL; + } + return ((WORD_LIST *)NULL); + } + } + + /* Begin expanding the words that remain. The expansions take place on + things that aren't really variable assignments. */ + +#if defined (BRACE_EXPANSION) + /* Do brace expansion on this word if there are any brace characters + in the string. */ + if ((eflags & WEXP_BRACEEXP) && brace_expansion && new_list) + new_list = brace_expand_word_list (new_list, eflags); +#endif /* BRACE_EXPANSION */ + + /* Perform the `normal' shell expansions: tilde expansion, parameter and + variable substitution, command substitution, arithmetic expansion, + and word splitting. */ + new_list = shell_expand_word_list (new_list, eflags); + + /* Okay, we're almost done. Now let's just do some filename + globbing. */ + if (new_list) + { + if ((eflags & WEXP_PATHEXP) && disallow_filename_globbing == 0) + /* Glob expand the word list unless globbing has been disabled. */ + new_list = glob_expand_word_list (new_list, eflags); + else + /* Dequote the words, because we're not performing globbing. */ + new_list = dequote_list (new_list); + } + + if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist) + { + sh_assign_func_t *assign_func; + + /* If the remainder of the words expand to nothing, Posix.2 requires + that the variable and environment assignments affect the shell's + environment. */ + assign_func = new_list ? assign_in_env : do_assignment; + tempenv_assign_error = 0; + + for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next) + { + this_command_name = (char *)NULL; + tint = (*assign_func) (temp_list->word->word); + /* Variable assignment errors in non-interactive shells running + in Posix.2 mode cause the shell to exit. */ + if (tint == 0) + { + if (assign_func == do_assignment) + { + last_command_exit_value = EXECUTION_FAILURE; + if (interactive_shell == 0 && posixly_correct) + exp_jump_to_top_level (FORCE_EOF); + else + exp_jump_to_top_level (DISCARD); + } + else + tempenv_assign_error++; + } + } + + dispose_words (subst_assign_varlist); + subst_assign_varlist = (WORD_LIST *)NULL; + } + +#if 0 + tint = list_length (new_list) + 1; + RESIZE_MALLOCED_BUFFER (glob_argv_flags, 0, tint, glob_argv_flags_size, 16); + for (tint = 0, temp_list = new_list; temp_list; temp_list = temp_list->next) + glob_argv_flags[tint++] = (temp_list->word->flags & W_GLOBEXP) ? '1' : '0'; + glob_argv_flags[tint] = '\0'; +#endif + + return (new_list); +} diff --git a/tests/RUN-ONE-TEST b/tests/RUN-ONE-TEST index 3efcf32d6..72ec06a2c 100755 --- a/tests/RUN-ONE-TEST +++ b/tests/RUN-ONE-TEST @@ -1,4 +1,4 @@ -BUILD_DIR=/usr/local/build/chet/bash/bash-current +BUILD_DIR=/usr/local/build/bash/bash-current THIS_SH=$BUILD_DIR/bash PATH=$PATH:$BUILD_DIR diff --git a/tests/RUN-ONE-TEST~ b/tests/RUN-ONE-TEST~ new file mode 100755 index 000000000..3efcf32d6 --- /dev/null +++ b/tests/RUN-ONE-TEST~ @@ -0,0 +1,9 @@ +BUILD_DIR=/usr/local/build/chet/bash/bash-current +THIS_SH=$BUILD_DIR/bash +PATH=$PATH:$BUILD_DIR + +export THIS_SH PATH + +rm -f /tmp/xx + +/bin/sh "$@" diff --git a/tests/histexp.right b/tests/histexp.right index 193e9d4f8..f1c9e9d9a 100644 --- a/tests/histexp.right +++ b/tests/histexp.right @@ -1,5 +1,5 @@ echo $BASH_VERSION -./histexp.tests: line 22: history: !!:z: history expansion failed +./histexp.tests: line 24: history: !!:z: history expansion failed 1 for i in one two three; do echo $i; done 2 /bin/sh -c 'echo this is $0' 3 ls diff --git a/tests/histexp.tests b/tests/histexp.tests index 11596ddd5..721208cb2 100644 --- a/tests/histexp.tests +++ b/tests/histexp.tests @@ -5,6 +5,8 @@ trap 'rm /tmp/newhistory' 0 file=bax histchars='!^#' # make sure history comment char is set correctly +unset HISTFILESIZE + history -c HISTFILE=history.list diff --git a/tests/history.right b/tests/history.right index 619d34ca3..556a3129b 100644 --- a/tests/history.right +++ b/tests/history.right @@ -97,7 +97,7 @@ line 2 for history 6 HISTFILE=/tmp/newhistory 7 echo displaying \$HISTFILE after history -a 8 cat $HISTFILE -./history.tests: line 73: fc: history specification out of range +./history.tests: line 75: fc: history specification out of range 14 set -H 15 echo line 2 for history 16 unset HISTSIZE @@ -107,5 +107,5 @@ echo xx xb xc xx xb xc echo 44 48 4c 44 48 4c -./history.tests: line 88: fc: no command found +./history.tests: line 90: fc: no command found 1 diff --git a/tests/history.tests b/tests/history.tests index 76ea56190..833cf3a99 100644 --- a/tests/history.tests +++ b/tests/history.tests @@ -8,6 +8,8 @@ history -r -w /dev/null # bad option fc -v +unset HISTFILESIZE + # all of these should result in an empty history list history -c history -r /dev/null diff --git a/variables.c b/variables.c index dc876de6a..0a806707e 100644 --- a/variables.c +++ b/variables.c @@ -3646,6 +3646,7 @@ static struct name_and_function special_vars[] = { { "LC_CTYPE", sv_locale }, { "LC_MESSAGES", sv_locale }, { "LC_NUMERIC", sv_locale }, + { "LC_TIME", sv_locale }, { "MAIL", sv_mail }, { "MAILCHECK", sv_mail }, diff --git a/variables.c~ b/variables.c~ new file mode 100644 index 000000000..dc876de6a --- /dev/null +++ b/variables.c~ @@ -0,0 +1,4090 @@ +/* variables.c -- Functions for hacking shell variables. */ + +/* Copyright (C) 1987-2004 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Bash is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with Bash; see the file COPYING. If not, write to the Free + Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" +#include "posixtime.h" + +#if defined (qnx) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include "chartypes.h" +#include +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "flags.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "mailcheck.h" +#include "input.h" +#include "hashcmd.h" +#include "pathexp.h" + +#include "builtins/getopt.h" +#include "builtins/common.h" + +#if defined (READLINE) +# include "bashline.h" +# include +#else +# include +#endif + +#if defined (HISTORY) +# include "bashhist.h" +# include +#endif /* HISTORY */ + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +#define TEMPENV_HASH_BUCKETS 4 /* must be power of two */ + +#define ifsname(s) ((s)[0] == 'I' && (s)[1] == 'F' && (s)[2] == 'S' && (s)[3] == '\0') + +/* Variables used here and defined in other files. */ +extern int posixly_correct; +extern int line_number; +extern int subshell_environment, indirection_level, subshell_level; +extern int build_version, patch_level; +extern int expanding_redir; +extern char *dist_version, *release_status; +extern char *shell_name; +extern char *primary_prompt, *secondary_prompt; +extern char *current_host_name; +extern sh_builtin_func_t *this_shell_builtin; +extern SHELL_VAR *this_shell_function; +extern char *the_printed_command_except_trap; +extern char *this_command_name; +extern char *command_execution_string; +extern time_t shell_start_time; + +#if defined (READLINE) +extern int perform_hostname_completion; +#endif + +/* The list of shell variables that the user has created at the global + scope, or that came from the environment. */ +VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL; + +/* The current list of shell variables, including function scopes */ +VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL; + +/* The list of shell functions that the user has created, or that came from + the environment. */ +HASH_TABLE *shell_functions = (HASH_TABLE *)NULL; + +#if defined (DEBUGGER) +/* The table of shell function definitions that the user defined or that + came from the environment. */ +HASH_TABLE *shell_function_defs = (HASH_TABLE *)NULL; +#endif + +/* The current variable context. This is really a count of how deep into + executing functions we are. */ +int variable_context = 0; + +/* The set of shell assignments which are made only in the environment + for a single command. */ +HASH_TABLE *temporary_env = (HASH_TABLE *)NULL; + +/* Set to non-zero if an assignment error occurs while putting variables + into the temporary environment. */ +int tempenv_assign_error; + +/* Some funky variables which are known about specially. Here is where + "$*", "$1", and all the cruft is kept. */ +char *dollar_vars[10]; +WORD_LIST *rest_of_args = (WORD_LIST *)NULL; + +/* The value of $$. */ +pid_t dollar_dollar_pid; + +/* An array which is passed to commands as their environment. It is + manufactured from the union of the initial environment and the + shell variables that are marked for export. */ +char **export_env = (char **)NULL; +static int export_env_index; +static int export_env_size; + +/* Non-zero means that we have to remake EXPORT_ENV. */ +int array_needs_making = 1; + +/* The number of times BASH has been executed. This is set + by initialize_variables (). */ +int shell_level = 0; + +/* Some forward declarations. */ +static void set_machine_vars __P((void)); +static void set_home_var __P((void)); +static void set_shell_var __P((void)); +static char *get_bash_name __P((void)); +static void initialize_shell_level __P((void)); +static void uidset __P((void)); +#if defined (ARRAY_VARS) +static void make_vers_array __P((void)); +#endif + +static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t)); +#if defined (ARRAY_VARS) +static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t)); +#endif +static SHELL_VAR *get_self __P((SHELL_VAR *)); + +#if defined (ARRAY_VARS) +static SHELL_VAR *init_dynamic_array_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int)); +#endif + +static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t)); +static SHELL_VAR *get_seconds __P((SHELL_VAR *)); +static SHELL_VAR *init_seconds_var __P((void)); + +static int brand __P((void)); +static void sbrand __P((unsigned long)); /* set bash random number generator. */ +static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t)); +static SHELL_VAR *get_random __P((SHELL_VAR *)); + +static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t)); +static SHELL_VAR *get_lineno __P((SHELL_VAR *)); + +static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t)); +static SHELL_VAR *get_subshell __P((SHELL_VAR *)); + +#if defined (HISTORY) +static SHELL_VAR *get_histcmd __P((SHELL_VAR *)); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t)); +static SHELL_VAR *get_dirstack __P((SHELL_VAR *)); +#endif + +#if defined (ARRAY_VARS) +static SHELL_VAR *get_groupset __P((SHELL_VAR *)); +#endif + +static SHELL_VAR *get_funcname __P((SHELL_VAR *)); +static SHELL_VAR *init_funcname_var __P((void)); + +static void initialize_dynamic_variables __P((void)); + +static SHELL_VAR *hash_lookup __P((const char *, HASH_TABLE *)); +static SHELL_VAR *new_shell_variable __P((const char *)); +static SHELL_VAR *make_new_variable __P((const char *, HASH_TABLE *)); +static SHELL_VAR *bind_variable_internal __P((const char *, char *, HASH_TABLE *, int)); + +static void free_variable_hash_data __P((PTR_T)); + +static VARLIST *vlist_alloc __P((int)); +static VARLIST *vlist_realloc __P((VARLIST *, int)); +static void vlist_add __P((VARLIST *, SHELL_VAR *, int)); + +static void flatten __P((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int)); + +static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **)); + +static SHELL_VAR **vapply __P((sh_var_map_func_t *)); +static SHELL_VAR **fapply __P((sh_var_map_func_t *)); + +static int visible_var __P((SHELL_VAR *)); +static int visible_and_exported __P((SHELL_VAR *)); +static int local_and_exported __P((SHELL_VAR *)); +static int variable_in_context __P((SHELL_VAR *)); +#if defined (ARRAY_VARS) +static int visible_array_vars __P((SHELL_VAR *)); +#endif + +static SHELL_VAR *bind_tempenv_variable __P((const char *, char *)); +static void push_temp_var __P((PTR_T)); +static void propagate_temp_var __P((PTR_T)); +static void dispose_temporary_env __P((sh_free_func_t *)); + +static inline char *mk_env_string __P((const char *, const char *)); +static char **make_env_array_from_var_list __P((SHELL_VAR **)); +static char **make_var_export_array __P((VAR_CONTEXT *)); +static char **make_func_export_array __P((void)); +static void add_temp_array_to_env __P((char **, int, int)); + +static int n_shell_variables __P((void)); +static int set_context __P((SHELL_VAR *)); + +static void push_func_var __P((PTR_T)); +static void push_exported_var __P((PTR_T)); + +static inline int find_special_var __P((const char *)); + +/* Initialize the shell variables from the current environment. + If PRIVMODE is nonzero, don't import functions from ENV or + parse $SHELLOPTS. */ +void +initialize_shell_variables (env, privmode) + char **env; + int privmode; +{ + char *name, *string, *temp_string; + int c, char_index, string_index, string_length; + SHELL_VAR *temp_var; + + if (shell_variables == 0) + { + shell_variables = global_variables = new_var_context ((char *)NULL, 0); + shell_variables->scope = 0; + shell_variables->table = hash_create (0); + } + + if (shell_functions == 0) + shell_functions = hash_create (0); + +#if defined (DEBUGGER) + if (shell_function_defs == 0) + shell_function_defs = hash_create (0); +#endif + + for (string_index = 0; string = env[string_index++]; ) + { + char_index = 0; + name = string; + while ((c = *string++) && c != '=') + ; + if (string[-1] == '=') + char_index = string - name - 1; + + /* If there are weird things in the environment, like `=xxx' or a + string without an `=', just skip them. */ + if (char_index == 0) + continue; + + /* ASSERT(name[char_index] == '=') */ + name[char_index] = '\0'; + /* Now, name = env variable name, string = env variable value, and + char_index == strlen (name) */ + + /* If exported function, define it now. Don't import functions from + the environment in privileged mode. */ + if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4)) + { + string_length = strlen (string); + temp_string = (char *)xmalloc (3 + string_length + char_index); + + strcpy (temp_string, name); + temp_string[char_index] = ' '; + strcpy (temp_string + char_index + 1, string); + + parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST); + + /* Ancient backwards compatibility. Old versions of bash exported + functions like name()=() {...} */ + if (name[char_index - 1] == ')' && name[char_index - 2] == '(') + name[char_index - 2] = '\0'; + + if (temp_var = find_function (name)) + { + VSETATTR (temp_var, (att_exported|att_imported)); + array_needs_making = 1; + } + else + report_error (_("error importing function definition for `%s'"), name); + + /* ( */ + if (name[char_index - 1] == ')' && name[char_index - 2] == '\0') + name[char_index - 2] = '('; /* ) */ + } +#if defined (ARRAY_VARS) +# if 0 + /* Array variables may not yet be exported. */ + else if (*string == '(' && string[1] == '[' && xstrchr (string, ')')) + { + string_length = 1; + temp_string = extract_array_assignment_list (string, &string_length); + temp_var = assign_array_from_string (name, temp_string); + FREE (temp_string); + VSETATTR (temp_var, (att_exported | att_imported)); + array_needs_making = 1; + } +# endif +#endif + else + { + temp_var = bind_variable (name, string); + VSETATTR (temp_var, (att_exported | att_imported)); + array_needs_making = 1; + } + + name[char_index] = '='; + /* temp_var can be NULL if it was an exported function with a syntax + error (a different bug, but it still shouldn't dump core). */ + if (temp_var && function_p (temp_var) == 0) /* XXX not yet */ + { + CACHE_IMPORTSTR (temp_var, name); + } + } + + set_pwd (); + + /* Set up initial value of $_ */ + temp_var = bind_variable ("_", dollar_vars[0]); + + /* Remember this pid. */ + dollar_dollar_pid = getpid (); + + /* Now make our own defaults in case the vars that we think are + important are missing. */ + temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE); +#if 0 + set_auto_export (temp_var); /* XXX */ +#endif + + temp_var = set_if_not ("TERM", "dumb"); +#if 0 + set_auto_export (temp_var); /* XXX */ +#endif + +#if defined (qnx) + /* set node id -- don't import it from the environment */ + { + char node_name[22]; + qnx_nidtostr (getnid (), node_name, sizeof (node_name)); + temp_var = bind_variable ("NODE", node_name); + set_auto_export (temp_var); + } +#endif + + /* set up the prompts. */ + if (interactive_shell) + { +#if defined (PROMPT_STRING_DECODE) + set_if_not ("PS1", primary_prompt); +#else + if (current_user.uid == -1) + get_current_user_info (); + set_if_not ("PS1", current_user.euid == 0 ? "# " : primary_prompt); +#endif + set_if_not ("PS2", secondary_prompt); + } + set_if_not ("PS4", "+ "); + + /* Don't allow IFS to be imported from the environment. */ + temp_var = bind_variable ("IFS", " \t\n"); + setifs (temp_var); + + /* Magic machine types. Pretty convenient. */ + set_machine_vars (); + + /* Default MAILCHECK for interactive shells. Defer the creation of a + default MAILPATH until the startup files are read, because MAIL + names a mail file if MAILPATH is not set, and we should provide a + default only if neither is set. */ + if (interactive_shell) + set_if_not ("MAILCHECK", posixly_correct ? "600" : "60"); + + /* Do some things with shell level. */ + initialize_shell_level (); + + set_ppid (); + + /* Initialize the `getopts' stuff. */ + bind_variable ("OPTIND", "1"); + getopts_reset (0); + bind_variable ("OPTERR", "1"); + sh_opterr = 1; + + if (login_shell == 1) + set_home_var (); + + /* Get the full pathname to THIS shell, and set the BASH variable + to it. */ + name = get_bash_name (); + temp_var = bind_variable ("BASH", name); + free (name); + + /* Make the exported environment variable SHELL be the user's login + shell. Note that the `tset' command looks at this variable + to determine what style of commands to output; if it ends in "csh", + then C-shell commands are output, else Bourne shell commands. */ + set_shell_var (); + + /* Make a variable called BASH_VERSION which contains the version info. */ + bind_variable ("BASH_VERSION", shell_version_string ()); +#if defined (ARRAY_VARS) + make_vers_array (); +#endif + + if (command_execution_string) + bind_variable ("BASH_EXECUTION_STRING", command_execution_string); + + /* Find out if we're supposed to be in Posix.2 mode via an + environment variable. */ + temp_var = find_variable ("POSIXLY_CORRECT"); + if (!temp_var) + temp_var = find_variable ("POSIX_PEDANTIC"); + if (temp_var && imported_p (temp_var)) + sv_strict_posix (temp_var->name); + +#if defined (HISTORY) + /* Set history variables to defaults, and then do whatever we would + do if the variable had just been set. Do this only in the case + that we are remembering commands on the history list. */ + if (remember_on_history) + { + name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history", 0); + + set_if_not ("HISTFILE", name); + free (name); + + set_if_not ("HISTSIZE", "500"); + sv_histsize ("HISTSIZE"); + } +#endif /* HISTORY */ + + /* Seed the random number generator. */ + sbrand (dollar_dollar_pid + shell_start_time); + + /* Handle some "special" variables that we may have inherited from a + parent shell. */ + if (interactive_shell) + { + temp_var = find_variable ("IGNOREEOF"); + if (!temp_var) + temp_var = find_variable ("ignoreeof"); + if (temp_var && imported_p (temp_var)) + sv_ignoreeof (temp_var->name); + } + +#if defined (HISTORY) + if (interactive_shell && remember_on_history) + { + sv_history_control ("HISTCONTROL"); + sv_histignore ("HISTIGNORE"); + } +#endif /* HISTORY */ + + /* + * 24 October 2001 + * + * I'm tired of the arguing and bug reports. Bash now leaves SSH_CLIENT + * and SSH2_CLIENT alone. I'm going to rely on the shell_level check in + * isnetconn() to avoid running the startup files more often than wanted. + * That will, of course, only work if the user's login shell is bash, so + * I've made that behavior conditional on SSH_SOURCE_BASHRC being defined + * in config-top.h. + */ +#if 0 + temp_var = find_variable ("SSH_CLIENT"); + if (temp_var && imported_p (temp_var)) + { + VUNSETATTR (temp_var, att_exported); + array_needs_making = 1; + } + temp_var = find_variable ("SSH2_CLIENT"); + if (temp_var && imported_p (temp_var)) + { + VUNSETATTR (temp_var, att_exported); + array_needs_making = 1; + } +#endif + + /* Get the user's real and effective user ids. */ + uidset (); + + /* Initialize the dynamic variables, and seed their values. */ + initialize_dynamic_variables (); +} + +/* **************************************************************** */ +/* */ +/* Setting values for special shell variables */ +/* */ +/* **************************************************************** */ + +static void +set_machine_vars () +{ + SHELL_VAR *temp_var; + + temp_var = set_if_not ("HOSTTYPE", HOSTTYPE); + temp_var = set_if_not ("OSTYPE", OSTYPE); + temp_var = set_if_not ("MACHTYPE", MACHTYPE); + + temp_var = set_if_not ("HOSTNAME", current_host_name); +} + +/* Set $HOME to the information in the password file if we didn't get + it from the environment. */ + +/* This function is not static so the tilde and readline libraries can + use it. */ +char * +sh_get_home_dir () +{ + if (current_user.home_dir == 0) + get_current_user_info (); + return current_user.home_dir; +} + +static void +set_home_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("HOME"); + if (temp_var == 0) + temp_var = bind_variable ("HOME", sh_get_home_dir ()); +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +/* Set $SHELL to the user's login shell if it is not already set. Call + get_current_user_info if we haven't already fetched the shell. */ +static void +set_shell_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("SHELL"); + if (temp_var == 0) + { + if (current_user.shell == 0) + get_current_user_info (); + temp_var = bind_variable ("SHELL", current_user.shell); + } +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +static char * +get_bash_name () +{ + char *name; + + if ((login_shell == 1) && RELPATH(shell_name)) + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + else if (ABSPATH(shell_name)) + name = savestring (shell_name); + else if (shell_name[0] == '.' && shell_name[1] == '/') + { + /* Fast path for common case. */ + char *cdir; + int len; + + cdir = get_string_value ("PWD"); + if (cdir) + { + len = strlen (cdir); + name = (char *)xmalloc (len + strlen (shell_name) + 1); + strcpy (name, cdir); + strcpy (name + len, shell_name + 1); + } + else + name = savestring (shell_name); + } + else + { + char *tname; + int s; + + tname = find_user_command (shell_name); + + if (tname == 0) + { + /* Try the current directory. If there is not an executable + there, just punt and use the login shell. */ + s = file_status (shell_name); + if (s & FS_EXECABLE) + { + tname = make_absolute (shell_name, get_string_value ("PWD")); + if (*shell_name == '.') + { + name = sh_canonpath (tname, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + if (name == 0) + name = tname; + else + free (tname); + } + else + name = tname; + } + else + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + } + else + { + name = full_pathname (tname); + free (tname); + } + } + + return (name); +} + +void +adjust_shell_level (change) + int change; +{ + char new_level[5], *old_SHLVL; + intmax_t old_level; + SHELL_VAR *temp_var; + + old_SHLVL = get_string_value ("SHLVL"); + if (old_SHLVL == 0 || *old_SHLVL == '\0' || legal_number (old_SHLVL, &old_level) == 0) + old_level = 0; + + shell_level = old_level + change; + if (shell_level < 0) + shell_level = 0; + else if (shell_level > 1000) + { + internal_warning (_("shell level (%d) too high, resetting to 1"), shell_level); + shell_level = 1; + } + + /* We don't need the full generality of itos here. */ + if (shell_level < 10) + { + new_level[0] = shell_level + '0'; + new_level[1] = '\0'; + } + else if (shell_level < 100) + { + new_level[0] = (shell_level / 10) + '0'; + new_level[1] = (shell_level % 10) + '0'; + new_level[2] = '\0'; + } + else if (shell_level < 1000) + { + new_level[0] = (shell_level / 100) + '0'; + old_level = shell_level % 100; + new_level[1] = (old_level / 10) + '0'; + new_level[2] = (old_level % 10) + '0'; + new_level[3] = '\0'; + } + + temp_var = bind_variable ("SHLVL", new_level); + set_auto_export (temp_var); +} + +static void +initialize_shell_level () +{ + adjust_shell_level (1); +} + +/* If we got PWD from the environment, update our idea of the current + working directory. In any case, make sure that PWD exists before + checking it. It is possible for getcwd () to fail on shell startup, + and in that case, PWD would be undefined. If this is an interactive + login shell, see if $HOME is the current working directory, and if + that's not the same string as $PWD, set PWD=$HOME. */ + +void +set_pwd () +{ + SHELL_VAR *temp_var, *home_var; + char *temp_string, *home_string; + + home_var = find_variable ("HOME"); + home_string = home_var ? value_cell (home_var) : (char *)NULL; + + temp_var = find_variable ("PWD"); + if (temp_var && imported_p (temp_var) && + (temp_string = value_cell (temp_var)) && + same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + set_working_directory (temp_string); + else if (home_string && interactive_shell && login_shell && + same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + { + set_working_directory (home_string); + temp_var = bind_variable ("PWD", home_string); + set_auto_export (temp_var); + } + else + { + temp_string = get_working_directory ("shell-init"); + if (temp_string) + { + temp_var = bind_variable ("PWD", temp_string); + set_auto_export (temp_var); + free (temp_string); + } + } + + /* According to the Single Unix Specification, v2, $OLDPWD is an + `environment variable' and therefore should be auto-exported. + Make a dummy invisible variable for OLDPWD, and mark it as exported. */ + temp_var = bind_variable ("OLDPWD", (char *)NULL); + VSETATTR (temp_var, (att_exported | att_invisible)); +} + +/* Make a variable $PPID, which holds the pid of the shell's parent. */ +void +set_ppid () +{ + char namebuf[INT_STRLEN_BOUND(pid_t) + 1], *name; + SHELL_VAR *temp_var; + + name = inttostr (getppid (), namebuf, sizeof(namebuf)); + temp_var = find_variable ("PPID"); + if (temp_var) + VUNSETATTR (temp_var, (att_readonly | att_exported)); + temp_var = bind_variable ("PPID", name); + VSETATTR (temp_var, (att_readonly | att_integer)); +} + +static void +uidset () +{ + char buff[INT_STRLEN_BOUND(uid_t) + 1], *b; + register SHELL_VAR *v; + + b = inttostr (current_user.uid, buff, sizeof (buff)); + v = find_variable ("UID"); + if (v == 0) + { + v = bind_variable ("UID", b); + VSETATTR (v, (att_readonly | att_integer)); + } + + if (current_user.euid != current_user.uid) + b = inttostr (current_user.euid, buff, sizeof (buff)); + + v = find_variable ("EUID"); + if (v == 0) + { + v = bind_variable ("EUID", b); + VSETATTR (v, (att_readonly | att_integer)); + } +} + +#if defined (ARRAY_VARS) +static void +make_vers_array () +{ + SHELL_VAR *vv; + ARRAY *av; + char *s, d[32], b[INT_STRLEN_BOUND(int) + 1]; + + unbind_variable ("BASH_VERSINFO"); + + vv = make_new_array_variable ("BASH_VERSINFO"); + av = array_cell (vv); + strcpy (d, dist_version); + s = xstrchr (d, '.'); + if (s) + *s++ = '\0'; + array_insert (av, 0, d); + array_insert (av, 1, s); + s = inttostr (patch_level, b, sizeof (b)); + array_insert (av, 2, s); + s = inttostr (build_version, b, sizeof (b)); + array_insert (av, 3, s); + array_insert (av, 4, release_status); + array_insert (av, 5, MACHTYPE); + + VSETATTR (vv, att_readonly); +} +#endif /* ARRAY_VARS */ + +/* Set the environment variables $LINES and $COLUMNS in response to + a window size change. */ +void +sh_set_lines_and_columns (lines, cols) + int lines, cols; +{ + char val[INT_STRLEN_BOUND(int) + 1], *v; + + v = inttostr (lines, val, sizeof (val)); + bind_variable ("LINES", v); + + v = inttostr (cols, val, sizeof (val)); + bind_variable ("COLUMNS", v); +} + +/* **************************************************************** */ +/* */ +/* Printing variables and values */ +/* */ +/* **************************************************************** */ + +/* Print LIST (a list of shell variables) to stdout in such a way that + they can be read back in. */ +void +print_var_list (list) + register SHELL_VAR **list; +{ + register int i; + register SHELL_VAR *var; + + for (i = 0; list && (var = list[i]); i++) + if (invisible_p (var) == 0) + print_assignment (var); +} + +/* Print LIST (a list of shell functions) to stdout in such a way that + they can be read back in. */ +void +print_func_list (list) + register SHELL_VAR **list; +{ + register int i; + register SHELL_VAR *var; + + for (i = 0; list && (var = list[i]); i++) + { + printf ("%s ", var->name); + print_var_function (var); + printf ("\n"); + } +} + +/* Print the value of a single SHELL_VAR. No newline is + output, but the variable is printed in such a way that + it can be read back in. */ +void +print_assignment (var) + SHELL_VAR *var; +{ + if (var_isset (var) == 0) + return; + + if (function_p (var)) + { + printf ("%s", var->name); + print_var_function (var); + printf ("\n"); + } +#if defined (ARRAY_VARS) + else if (array_p (var)) + print_array_assignment (var, 0); +#endif /* ARRAY_VARS */ + else + { + printf ("%s=", var->name); + print_var_value (var, 1); + printf ("\n"); + } +} + +/* Print the value cell of VAR, a shell variable. Do not print + the name, nor leading/trailing newline. If QUOTE is non-zero, + and the value contains shell metacharacters, quote the value + in such a way that it can be read back in. */ +void +print_var_value (var, quote) + SHELL_VAR *var; + int quote; +{ + char *t; + + if (var_isset (var) == 0) + return; + + if (quote && posixly_correct == 0 && ansic_shouldquote (value_cell (var))) + { + t = ansic_quote (value_cell (var), 0, (int *)0); + printf ("%s", t); + free (t); + } + else if (quote && sh_contains_shell_metas (value_cell (var))) + { + t = sh_single_quote (value_cell (var)); + printf ("%s", t); + free (t); + } + else + printf ("%s", value_cell (var)); +} + +/* Print the function cell of VAR, a shell variable. Do not + print the name, nor leading/trailing newline. */ +void +print_var_function (var) + SHELL_VAR *var; +{ + if (function_p (var) && var_isset (var)) + printf ("%s", named_function_string ((char *)NULL, function_cell(var), 1)); +} + +/* **************************************************************** */ +/* */ +/* Dynamic Variables */ +/* */ +/* **************************************************************** */ + +/* DYNAMIC VARIABLES + + These are variables whose values are generated anew each time they are + referenced. These are implemented using a pair of function pointers + in the struct variable: assign_func, which is called from bind_variable + and, if arrays are compiled into the shell, some of the functions in + arrayfunc.c, and dynamic_value, which is called from find_variable. + + assign_func is called from bind_variable_internal, if + bind_variable_internal discovers that the variable being assigned to + has such a function. The function is called as + SHELL_VAR *temp = (*(entry->assign_func)) (entry, value, ind) + and the (SHELL_VAR *)temp is returned as the value of bind_variable. It + is usually ENTRY (self). IND is an index for an array variable, and + unused otherwise. + + dynamic_value is called from find_variable_internal to return a `new' + value for the specified dynamic varible. If this function is NULL, + the variable is treated as a `normal' shell variable. If it is not, + however, then this function is called like this: + tempvar = (*(var->dynamic_value)) (var); + + Sometimes `tempvar' will replace the value of `var'. Other times, the + shell will simply use the string value. Pretty object-oriented, huh? + + Be warned, though: if you `unset' a special variable, it loses its + special meaning, even if you subsequently set it. + + The special assignment code would probably have been better put in + subst.c: do_assignment_internal, in the same style as + stupidly_hack_special_variables, but I wanted the changes as + localized as possible. */ + +#define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \ + do \ + { \ + v = bind_variable (var, (val)); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +#define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \ + do \ + { \ + v = make_new_array_variable (var); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +static SHELL_VAR * +null_assign (self, value, unused) + SHELL_VAR *self; + char *value; + arrayind_t unused; +{ + return (self); +} + +#if defined (ARRAY_VARS) +static SHELL_VAR * +null_array_assign (self, value, ind) + SHELL_VAR *self; + char *value; + arrayind_t ind; +{ + return (self); +} +#endif + +/* Degenerate `dynamic_value' function; just returns what's passed without + manipulation. */ +static SHELL_VAR * +get_self (self) + SHELL_VAR *self; +{ + return (self); +} + +#if defined (ARRAY_VARS) +/* A generic dynamic array variable initializer. Intialize array variable + NAME with dynamic value function GETFUNC and assignment function SETFUNC. */ +static SHELL_VAR * +init_dynamic_array_var (name, getfunc, setfunc, attrs) + char *name; + sh_var_value_func_t *getfunc; + sh_var_assign_func_t *setfunc; + int attrs; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v) + return (v); + INIT_DYNAMIC_ARRAY_VAR (name, getfunc, setfunc); + if (attrs) + VSETATTR (v, attrs); + return v; +} +#endif + + +/* The value of $SECONDS. This is the number of seconds since shell + invocation, or, the number of seconds since the last assignment + the + value of the last assignment. */ +static intmax_t seconds_value_assigned; + +static SHELL_VAR * +assign_seconds (self, value, unused) + SHELL_VAR *self; + char *value; + arrayind_t unused; +{ + if (legal_number (value, &seconds_value_assigned) == 0) + seconds_value_assigned = 0; + shell_start_time = NOW; + return (self); +} + +static SHELL_VAR * +get_seconds (var) + SHELL_VAR *var; +{ + time_t time_since_start; + char *p; + + time_since_start = NOW - shell_start_time; + p = itos(seconds_value_assigned + time_since_start); + + FREE (value_cell (var)); + + VSETATTR (var, att_integer); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +init_seconds_var () +{ + SHELL_VAR *v; + + v = find_variable ("SECONDS"); + if (v) + { + if (legal_number (value_cell(v), &seconds_value_assigned) == 0) + seconds_value_assigned = 0; + } + INIT_DYNAMIC_VAR ("SECONDS", (v ? value_cell (v) : (char *)NULL), get_seconds, assign_seconds); + return v; +} + +/* The random number seed. You can change this by setting RANDOM. */ +static unsigned long rseed = 1; +static int last_random_value; + +/* A linear congruential random number generator based on the example + one in the ANSI C standard. This one isn't very good, but a more + complicated one is overkill. */ + +/* Returns a pseudo-random number between 0 and 32767. */ +static int +brand () +{ + rseed = rseed * 1103515245 + 12345; + return ((unsigned int)((rseed >> 16) & 32767)); /* was % 32768 */ +} + +/* Set the random number generator seed to SEED. */ +static void +sbrand (seed) + unsigned long seed; +{ + rseed = seed; + last_random_value = 0; +} + +static SHELL_VAR * +assign_random (self, value, unused) + SHELL_VAR *self; + char *value; + arrayind_t unused; +{ + sbrand (strtoul (value, (char **)NULL, 10)); + return (self); +} + +int +get_random_number () +{ + int rv; + + /* Reset for command and process substitution. */ + if (subshell_environment) + sbrand (rseed + getpid() + NOW); + + do + rv = brand (); + while (rv == last_random_value); + return rv; +} + +static SHELL_VAR * +get_random (var) + SHELL_VAR *var; +{ + int rv; + char *p; + + rv = get_random_number (); + last_random_value = rv; + p = itos (rv); + + FREE (value_cell (var)); + + VSETATTR (var, att_integer); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +assign_lineno (var, value, unused) + SHELL_VAR *var; + char *value; + arrayind_t unused; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + line_number = new_value; + return var; +} + +/* Function which returns the current line number. */ +static SHELL_VAR * +get_lineno (var) + SHELL_VAR *var; +{ + char *p; + int ln; + + ln = executing_line_number (); + p = itos (ln); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +assign_subshell (var, value, unused) + SHELL_VAR *var; + char *value; + arrayind_t unused; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + subshell_level = new_value; + return var; +} + +static SHELL_VAR * +get_subshell (var) + SHELL_VAR *var; +{ + char *p; + + p = itos (subshell_level); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +get_bash_command (var) + SHELL_VAR *var; +{ + char *p; + + p = savestring (the_printed_command_except_trap); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +#if defined (HISTORY) +static SHELL_VAR * +get_histcmd (var) + SHELL_VAR *var; +{ + char *p; + + p = itos (history_number ()); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} +#endif + +#if defined (READLINE) +/* When this function returns, VAR->value points to malloced memory. */ +static SHELL_VAR * +get_comp_wordbreaks (var) + SHELL_VAR *var; +{ + char *p; + + /* If we don't have anything yet, assign a default value. */ + if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0) + enable_hostname_completion (perform_hostname_completion); + +#if 0 + FREE (value_cell (var)); + p = savestring (rl_completer_word_break_characters); + + var_setvalue (var, p); +#else + var_setvalue (var, rl_completer_word_break_characters); +#endif + + return (var); +} + +/* When this function returns, rl_completer_word_break_characters points to + malloced memory. */ +static SHELL_VAR * +assign_comp_wordbreaks (self, value, unused) + SHELL_VAR *self; + char *value; + arrayind_t unused; +{ + if (rl_completer_word_break_characters && + rl_completer_word_break_characters != rl_basic_word_break_characters) + free (rl_completer_word_break_characters); + + rl_completer_word_break_characters = savestring (value); + return self; +} +#endif /* READLINE */ + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR * +assign_dirstack (self, value, ind) + SHELL_VAR *self; + char *value; + arrayind_t ind; +{ + set_dirstack_element (ind, 1, value); + return self; +} + +static SHELL_VAR * +get_dirstack (self) + SHELL_VAR *self; +{ + ARRAY *a; + WORD_LIST *l; + + l = get_directory_stack (); + a = array_from_word_list (l); + array_dispose (array_cell (self)); + dispose_words (l); + var_setarray (self, a); + return self; +} +#endif /* PUSHD AND POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) +/* We don't want to initialize the group set with a call to getgroups() + unless we're asked to, but we only want to do it once. */ +static SHELL_VAR * +get_groupset (self) + SHELL_VAR *self; +{ + register int i; + int ng; + ARRAY *a; + static char **group_set = (char **)NULL; + + if (group_set == 0) + { + group_set = get_group_list (&ng); + a = array_cell (self); + for (i = 0; i < ng; i++) + array_insert (a, i, group_set[i]); + } + return (self); +} +#endif /* ARRAY_VARS */ + +/* If ARRAY_VARS is not defined, this just returns the name of any + currently-executing function. If we have arrays, it's a call stack. */ +static SHELL_VAR * +get_funcname (self) + SHELL_VAR *self; +{ +#if ! defined (ARRAY_VARS) + char *t; + if (variable_context && this_shell_function) + { + FREE (value_cell (self)); + t = savestring (this_shell_function->name); + var_setvalue (self, t); + } +#endif + return (self); +} + +void +make_funcname_visible (on_or_off) + int on_or_off; +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v == 0 || v->dynamic_value == 0) + return; + + if (on_or_off) + VUNSETATTR (v, att_invisible); + else + VSETATTR (v, att_invisible); +} + +static SHELL_VAR * +init_funcname_var () +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v) + return v; +#if defined (ARRAY_VARS) + INIT_DYNAMIC_ARRAY_VAR ("FUNCNAME", get_funcname, null_array_assign); +#else + INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign); +#endif + VSETATTR (v, att_invisible|att_noassign); + return v; +} + +static void +initialize_dynamic_variables () +{ + SHELL_VAR *v; + + v = init_seconds_var (); + + INIT_DYNAMIC_VAR ("BASH_COMMAND", (char *)NULL, get_bash_command, (sh_var_assign_func_t *)NULL); + INIT_DYNAMIC_VAR ("BASH_SUBSHELL", (char *)NULL, get_subshell, assign_subshell); + + INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random); + INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno); + +#if defined (HISTORY) + INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL); +#endif + +#if defined (READLINE) + INIT_DYNAMIC_VAR ("COMP_WORDBREAKS", (char *)NULL, get_comp_wordbreaks, assign_comp_wordbreaks); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) + v = init_dynamic_array_var ("DIRSTACK", get_dirstack, assign_dirstack, 0); +#endif /* PUSHD_AND_POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) + v = init_dynamic_array_var ("GROUPS", get_groupset, null_array_assign, att_noassign); + +# if defined (DEBUGGER) + v = init_dynamic_array_var ("BASH_ARGC", get_self, null_array_assign, (att_invisible|att_noassign)); + v = init_dynamic_array_var ("BASH_ARGV", get_self, null_array_assign, (att_invisible|att_noassign)); +# endif /* DEBUGGER */ + v = init_dynamic_array_var ("BASH_SOURCE", get_self, null_array_assign, (att_invisible|att_noassign)); + v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, (att_invisible|att_noassign)); +#endif + + v = init_funcname_var (); +} + +/* **************************************************************** */ +/* */ +/* Retrieving variables and values */ +/* */ +/* **************************************************************** */ + +/* How to get a pointer to the shell variable or function named NAME. + HASHED_VARS is a pointer to the hash table containing the list + of interest (either variables or functions). */ + +static SHELL_VAR * +hash_lookup (name, hashed_vars) + const char *name; + HASH_TABLE *hashed_vars; +{ + BUCKET_CONTENTS *bucket; + + bucket = hash_search (name, hashed_vars, 0); + return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL); +} + +SHELL_VAR * +var_lookup (name, vcontext) + const char *name; + VAR_CONTEXT *vcontext; +{ + VAR_CONTEXT *vc; + SHELL_VAR *v; + + v = (SHELL_VAR *)NULL; + for (vc = vcontext; vc; vc = vc->down) + if (v = hash_lookup (name, vc->table)) + break; + + return v; +} + +/* Look up the variable entry named NAME. If SEARCH_TEMPENV is non-zero, + then also search the temporarily built list of exported variables. + The lookup order is: + temporary_env + shell_variables list +*/ + +SHELL_VAR * +find_variable_internal (name, force_tempenv) + const char *name; + int force_tempenv; +{ + SHELL_VAR *var; + int search_tempenv; + + var = (SHELL_VAR *)NULL; + + /* If explicitly requested, first look in the temporary environment for + the variable. This allows constructs such as "foo=x eval 'echo $foo'" + to get the `exported' value of $foo. This happens if we are executing + a function or builtin, or if we are looking up a variable in a + "subshell environment". */ + search_tempenv = force_tempenv || (expanding_redir == 0 && subshell_environment); + + if (search_tempenv && temporary_env) + var = hash_lookup (name, temporary_env); + + if (var == 0) + var = var_lookup (name, shell_variables); + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +/* Look up the variable entry named NAME. Returns the entry or NULL. */ +SHELL_VAR * +find_variable (name) + const char *name; +{ + return (find_variable_internal (name, (expanding_redir == 0 && this_shell_builtin != 0))); +} + +/* Look up the function entry whose name matches STRING. + Returns the entry or NULL. */ +SHELL_VAR * +find_function (name) + const char *name; +{ + return (hash_lookup (name, shell_functions)); +} + +/* Find the function definition for the shell function named NAME. Returns + the entry or NULL. */ +FUNCTION_DEF * +find_function_def (name) + const char *name; +{ + return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs)); +} + +/* Return the value of VAR. VAR is assumed to have been the result of a + lookup without any subscript, if arrays are compiled into the shell. */ +char * +get_variable_value (var) + SHELL_VAR *var; +{ + if (var == 0) + return ((char *)NULL); +#if defined (ARRAY_VARS) + else if (array_p (var)) + return (array_reference (array_cell (var), 0)); +#endif + else + return (value_cell (var)); +} + +/* Return the string value of a variable. Return NULL if the variable + doesn't exist. Don't cons a new string. This is a potential memory + leak if the variable is found in the temporary environment. Since + functions and variables have separate name spaces, returns NULL if + var_name is a shell function only. */ +char * +get_string_value (var_name) + const char *var_name; +{ + SHELL_VAR *var; + + var = find_variable (var_name); + return ((var) ? get_variable_value (var) : (char *)NULL); +} + +/* This is present for use by the tilde and readline libraries. */ +char * +sh_get_env_value (v) + const char *v; +{ + return get_string_value (v); +} + +/* **************************************************************** */ +/* */ +/* Creating and setting variables */ +/* */ +/* **************************************************************** */ + +/* Set NAME to VALUE if NAME has no value. */ +SHELL_VAR * +set_if_not (name, value) + char *name, *value; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v == 0) + v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH); + return (v); +} + +/* Create a local variable referenced by NAME. */ +SHELL_VAR * +make_local_variable (name) + const char *name; +{ + SHELL_VAR *new_var, *old_var; + VAR_CONTEXT *vc; + int was_tmpvar; + char *tmp_value; + + /* local foo; local foo; is a no-op. */ + old_var = find_variable (name); + if (old_var && local_p (old_var) && old_var->context == variable_context) + return (old_var); + + was_tmpvar = old_var && tempvar_p (old_var); + if (was_tmpvar) + tmp_value = value_cell (old_var); + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("make_local_variable: no function context at current scope")); + return ((SHELL_VAR *)NULL); + } + else if (vc->table == 0) + vc->table = hash_create (TEMPENV_HASH_BUCKETS); + + /* Since this is called only from the local/declare/typeset code, we can + call builtin_error here without worry (of course, it will also work + for anything that sets this_command_name). Variables with the `noassign' + attribute may not be made local. The test against old_var's context + level is to disallow local copies of readonly global variables (since I + believe that this could be a security hole). Readonly copies of calling + function local variables are OK. */ + if (old_var && (noassign_p (old_var) || + (readonly_p (old_var) && old_var->context == 0))) + { + if (readonly_p (old_var)) + sh_readonly (name); + return ((SHELL_VAR *)NULL); + } + + if (old_var == 0) + new_var = bind_variable_internal (name, "", vc->table, HASH_NOSRCH); + else + { + new_var = make_new_variable (name, vc->table); + + /* If we found this variable in one of the temporary environments, + inherit its value. Watch to see if this causes problems with + things like `x=4 local x'. */ + if (was_tmpvar) + var_setvalue (new_var, savestring (tmp_value)); + + new_var->attributes = exported_p (old_var) ? att_exported : 0; + } + + vc->flags |= VC_HASLOCAL; + + new_var->context = variable_context; + VSETATTR (new_var, att_local); + + if (ifsname (name)) + setifs (new_var); + + return (new_var); +} + +#if defined (ARRAY_VARS) +SHELL_VAR * +make_local_array_variable (name) + char *name; +{ + SHELL_VAR *var; + ARRAY *array; + + var = make_local_variable (name); + if (var == 0 || array_p (var)) + return var; + + array = array_create (); + + FREE (value_cell(var)); + var_setarray (var, array); + VSETATTR (var, att_array); + return var; +} +#endif /* ARRAY_VARS */ + +/* Create a new shell variable with name NAME. */ +static SHELL_VAR * +new_shell_variable (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + + entry->name = savestring (name); + var_setvalue (entry, (char *)NULL); + CLEAR_EXPORTSTR (entry); + + entry->dynamic_value = (sh_var_value_func_t *)NULL; + entry->assign_func = (sh_var_assign_func_t *)NULL; + + entry->attributes = 0; + + /* Always assume variables are to be made at toplevel! + make_local_variable has the responsibilty of changing the + variable context. */ + entry->context = 0; + + return (entry); +} + +/* Create a new shell variable with name NAME and add it to the hash table + TABLE. */ +static SHELL_VAR * +make_new_variable (name, table) + const char *name; + HASH_TABLE *table; +{ + SHELL_VAR *entry; + BUCKET_CONTENTS *elt; + + entry = new_shell_variable (name); + + /* Make sure we have a shell_variables hash table to add to. */ + if (shell_variables == 0) + { + shell_variables = global_variables = new_var_context ((char *)NULL, 0); + shell_variables->scope = 0; + shell_variables->table = hash_create (0); + } + + elt = hash_insert (savestring (name), table, HASH_NOSRCH); + elt->data = (PTR_T)entry; + + return entry; +} + +#if defined (ARRAY_VARS) +SHELL_VAR * +make_new_array_variable (name) + char *name; +{ + SHELL_VAR *entry; + ARRAY *array; + + entry = make_new_variable (name, global_variables->table); + array = array_create (); + var_setarray (entry, array); + VSETATTR (entry, att_array); + return entry; +} +#endif + +char * +make_variable_value (var, value) + SHELL_VAR *var; + char *value; +{ + char *retval; + intmax_t lval; + int expok; + + /* If this variable has had its type set to integer (via `declare -i'), + then do expression evaluation on it and store the result. The + functions in expr.c (evalexp()) and bind_int_variable() are responsible + for turning off the integer flag if they don't want further + evaluation done. */ + if (integer_p (var)) + { + lval = evalexp (value, &expok); + if (expok == 0) + jump_to_top_level (DISCARD); + retval = itos (lval); + } + else if (value) + { + if (*value) + retval = savestring (value); + else + { + retval = (char *)xmalloc (1); + retval[0] = '\0'; + } + } + else + retval = (char *)NULL; + + return retval; +} + +/* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the + temporary environment (but usually is not). */ +static SHELL_VAR * +bind_variable_internal (name, value, table, hflags) + const char *name; + char *value; + HASH_TABLE *table; + int hflags; +{ + char *newval; + SHELL_VAR *entry; + + entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table); + + if (entry == 0) + { + entry = make_new_variable (name, table); + var_setvalue (entry, make_variable_value (entry, value)); + } + else if (entry->assign_func) /* array vars have assign functions now */ + { + INVALIDATE_EXPORTSTR (entry); + return ((*(entry->assign_func)) (entry, value, -1)); + } + else + { + if (readonly_p (entry) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name); + return (entry); + } + + /* Variables which are bound are visible. */ + VUNSETATTR (entry, att_invisible); + + newval = make_variable_value (entry, value); + + /* Invalidate any cached export string */ + INVALIDATE_EXPORTSTR (entry); + +#if defined (ARRAY_VARS) + /* XXX -- this bears looking at again -- XXX */ + /* If an existing array variable x is being assigned to with x=b or + `read x' or something of that nature, silently convert it to + x[0]=b or `read x[0]'. */ + if (array_p (entry)) + { + array_insert (array_cell (entry), 0, newval); + free (newval); + } + else +#endif + { + FREE (value_cell (entry)); + var_setvalue (entry, newval); + } + } + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + if (exported_p (entry)) + array_needs_making = 1; + + return (entry); +} + +/* Bind a variable NAME to VALUE. This conses up the name + and value strings. If we have a temporary environment, we bind there + first, then we bind into shell_variables. */ + +SHELL_VAR * +bind_variable (name, value) + const char *name; + char *value; +{ + SHELL_VAR *v; + VAR_CONTEXT *vc; + + if (shell_variables == 0) + { + shell_variables = global_variables = new_var_context ((char *)NULL, 0); + shell_variables->scope = 0; + shell_variables->table = hash_create (0); + } + + /* If we have a temporary environment, look there first for the variable, + and, if found, modify the value there before modifying it in the + shell_variables table. This allows sourced scripts to modify values + given to them in a temporary environment while modifying the variable + value that the caller sees. */ + if (temporary_env) + bind_tempenv_variable (name, value); + + /* XXX -- handle local variables here. */ + for (vc = shell_variables; vc; vc = vc->down) + { + if (vc_isfuncenv (vc) || vc_isbltnenv (vc)) + { + v = hash_lookup (name, vc->table); + if (v) + return (bind_variable_internal (name, value, vc->table, 0)); + } + } + return (bind_variable_internal (name, value, global_variables->table, 0)); +} + +/* Make VAR, a simple shell variable, have value VALUE. Once assigned a + value, variables are no longer invisible. This is a duplicate of part + of the internals of bind_variable. If the variable is exported, or + all modified variables should be exported, mark the variable for export + and note that the export environment needs to be recreated. */ +SHELL_VAR * +bind_variable_value (var, value) + SHELL_VAR *var; + char *value; +{ + char *t; + + VUNSETATTR (var, att_invisible); + + t = make_variable_value (var, value); + FREE (value_cell (var)); + var_setvalue (var, t); + + INVALIDATE_EXPORTSTR (var); + + if (mark_modified_vars) + VSETATTR (var, att_exported); + + if (exported_p (var)) + array_needs_making = 1; + + return (var); +} + +/* Bind/create a shell variable with the name LHS to the RHS. + This creates or modifies a variable such that it is an integer. + + This used to be in expr.c, but it is here so that all of the + variable binding stuff is localized. Since we don't want any + recursive evaluation from bind_variable() (possible without this code, + since bind_variable() calls the evaluator for variables with the integer + attribute set), we temporarily turn off the integer attribute for each + variable we set here, then turn it back on after binding as necessary. */ + +SHELL_VAR * +bind_int_variable (lhs, rhs) + char *lhs, *rhs; +{ + register SHELL_VAR *v; + char *t; + int isint, isarr; + + isint = isarr = 0; +#if defined (ARRAY_VARS) +# if 0 + if (t = xstrchr (lhs, '[')) /*]*/ +# else + if (valid_array_reference (lhs)) +# endif + { + isarr = 1; + v = array_variable_part (lhs, (char **)0, (int *)0); + } + else +#endif + v = find_variable (lhs); + + if (v) + { + isint = integer_p (v); + VUNSETATTR (v, att_integer); + } + +#if defined (ARRAY_VARS) + if (isarr) + v = assign_array_element (lhs, rhs); + else +#endif + v = bind_variable (lhs, rhs); + + if (isint) + VSETATTR (v, att_integer); + + return (v); +} + +SHELL_VAR * +bind_var_to_int (var, val) + char *var; + intmax_t val; +{ + char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p; + + p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0); + return (bind_int_variable (var, p)); +} + +/* Do a function binding to a variable. You pass the name and + the command to bind to. This conses the name and command. */ +SHELL_VAR * +bind_function (name, value) + const char *name; + COMMAND *value; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry == 0) + { + BUCKET_CONTENTS *elt; + + elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH); + entry = new_shell_variable (name); + elt->data = (PTR_T)entry; + } + else + INVALIDATE_EXPORTSTR (entry); + + if (var_isset (entry)) + dispose_command (function_cell (entry)); + + if (value) + var_setfunc (entry, copy_command (value)); + else + var_setfunc (entry, 0); + + VSETATTR (entry, att_function); + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + VUNSETATTR (entry, att_invisible); /* Just to be sure */ + + if (exported_p (entry)) + array_needs_making = 1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + return (entry); +} + +/* Bind a function definition, which includes source file and line number + information in addition to the command, into the FUNCTION_DEF hash table.*/ +void +bind_function_def (name, value) + const char *name; + FUNCTION_DEF *value; +{ + FUNCTION_DEF *entry; + BUCKET_CONTENTS *elt; + COMMAND *cmd; + + entry = find_function_def (name); + if (entry) + { + dispose_function_def_contents (entry); + entry = copy_function_def_contents (value, entry); + } + else + { + cmd = value->command; + value->command = 0; + entry = copy_function_def (value); + value->command = cmd; + + elt = hash_insert (savestring (name), shell_function_defs, HASH_NOSRCH); + elt->data = (PTR_T *)entry; + } +} + +/* Add STRING, which is of the form foo=bar, to the temporary environment + HASH_TABLE (temporary_env). The functions in execute_cmd.c are + responsible for moving the main temporary env to one of the other + temporary environments. The expansion code in subst.c calls this. */ +int +assign_in_env (string) + const char *string; +{ + int offset; + char *name, *temp, *value; + SHELL_VAR *var; + + offset = assignment (string, 0); + name = savestring (string); + value = (char *)NULL; + + if (name[offset] == '=') + { + name[offset] = 0; + + var = find_variable (name); + if (var && (readonly_p (var) || noassign_p (var))) + { + if (readonly_p (var)) + err_readonly (name); + free (name); + return (0); + } + + temp = name + offset + 1; + temp = (xstrchr (temp, '~') != 0) ? bash_tilde_expand (temp, 1) : savestring (temp); + + value = expand_string_unsplit_to_string (temp, 0); + free (temp); + } + + if (temporary_env == 0) + temporary_env = hash_create (TEMPENV_HASH_BUCKETS); + + var = hash_lookup (name, temporary_env); + if (var == 0) + var = make_new_variable (name, temporary_env); + else + FREE (value_cell (var)); + + if (value == 0) + { + value = (char *)xmalloc (1); /* like do_assignment_internal */ + value[0] = '\0'; + } + + var_setvalue (var, value); + var->attributes |= (att_exported|att_tempvar); + var->context = variable_context; /* XXX */ + + INVALIDATE_EXPORTSTR (var); + var->exportstr = mk_env_string (name, value); + + array_needs_making = 1; + + if (ifsname (name)) + setifs (var); + + if (echo_command_at_execute) + /* The Korn shell prints the `+ ' in front of assignment statements, + so we do too. */ + xtrace_print_assignment (name, value, 0, 1); + + free (name); + return 1; +} + +/* **************************************************************** */ +/* */ +/* Copying variables */ +/* */ +/* **************************************************************** */ + +#ifdef INCLUDE_UNUSED +/* Copy VAR to a new data structure and return that structure. */ +SHELL_VAR * +copy_variable (var) + SHELL_VAR *var; +{ + SHELL_VAR *copy = (SHELL_VAR *)NULL; + + if (var) + { + copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + + copy->attributes = var->attributes; + copy->name = savestring (var->name); + + if (function_p (var)) + var_setfunc (copy, copy_command (function_cell (var))); +#if defined (ARRAY_VARS) + else if (array_p (var)) + var_setarray (copy, dup_array (array_cell (var))); +#endif + else if (value_cell (var)) + var_setvalue (copy, savestring (value_cell (var))); + else + var_setvalue (copy, (char *)NULL); + + copy->dynamic_value = var->dynamic_value; + copy->assign_func = var->assign_func; + + copy->exportstr = COPY_EXPORTSTR (var); + + copy->context = var->context; + } + return (copy); +} +#endif + +/* **************************************************************** */ +/* */ +/* Deleting and unsetting variables */ +/* */ +/* **************************************************************** */ + +/* Dispose of the information attached to VAR. */ +void +dispose_variable (var) + SHELL_VAR *var; +{ + if (var == 0) + return; + + if (function_p (var)) + dispose_command (function_cell (var)); +#if defined (ARRAY_VARS) + else if (array_p (var)) + array_dispose (array_cell (var)); +#endif + else + FREE (value_cell (var)); + + FREE_EXPORTSTR (var); + + free (var->name); + + if (exported_p (var)) + array_needs_making = 1; + + free (var); +} + +/* Unset the shell variable referenced by NAME. */ +int +unbind_variable (name) + const char *name; +{ + return makunbound (name, shell_variables); +} + +/* Unset the shell function named NAME. */ +int +unbind_func (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + SHELL_VAR *func; + + elt = hash_remove (name, shell_functions, 0); + + if (elt == 0) + return -1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + func = (SHELL_VAR *)elt->data; + if (func) + { + if (exported_p (func)) + array_needs_making++; + dispose_variable (func); + } + + free (elt->key); + free (elt); + + return 0; +} + +int +unbind_function_def (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + FUNCTION_DEF *funcdef; + + elt = hash_remove (name, shell_function_defs, 0); + + if (elt == 0) + return -1; + + funcdef = (FUNCTION_DEF *)elt->data; + if (funcdef) + dispose_function_def (funcdef); + + free (elt->key); + free (elt); + + return 0; +} + +/* Make the variable associated with NAME go away. HASH_LIST is the + hash table from which this variable should be deleted (either + shell_variables or shell_functions). + Returns non-zero if the variable couldn't be found. */ +int +makunbound (name, vc) + const char *name; + VAR_CONTEXT *vc; +{ + BUCKET_CONTENTS *elt, *new_elt; + SHELL_VAR *old_var; + VAR_CONTEXT *v; + char *t; + + for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down) + if (elt = hash_remove (name, v->table, 0)) + break; + + if (elt == 0) + return (-1); + + old_var = (SHELL_VAR *)elt->data; + + if (old_var && exported_p (old_var)) + array_needs_making++; + + /* If we're unsetting a local variable and we're still executing inside + the function, just mark the variable as invisible. The function + eventually called by pop_var_context() will clean it up later. This + must be done so that if the variable is subsequently assigned a new + value inside the function, the `local' attribute is still present. + We also need to add it back into the correct hash table. */ + if (old_var && local_p (old_var) && variable_context == old_var->context) + { + /* Reset the attributes. Preserve the export attribute if the variable + came from a temporary environment. Make sure it stays local, and + make it invisible. */ + old_var->attributes = (exported_p (old_var) && tempvar_p (old_var)) ? att_exported : 0; + VSETATTR (old_var, att_local); + VSETATTR (old_var, att_invisible); + FREE (value_cell (old_var)); + var_setvalue (old_var, (char *)NULL); + INVALIDATE_EXPORTSTR (old_var); + + new_elt = hash_insert (savestring (old_var->name), v->table, 0); + new_elt->data = (PTR_T)old_var; + stupidly_hack_special_variables (old_var->name); + + free (elt->key); + free (elt); + return (0); + } + + /* Have to save a copy of name here, because it might refer to + old_var->name. If so, stupidly_hack_special_variables will + reference freed memory. */ + t = savestring (name); + + free (elt->key); + free (elt); + + dispose_variable (old_var); + stupidly_hack_special_variables (t); + free (t); + + return (0); +} + +/* Get rid of all of the variables in the current context. */ +void +kill_all_local_variables () +{ + VAR_CONTEXT *vc; + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + if (vc == 0) + return; /* XXX */ + + if (vc->table && vc_haslocals (vc)) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + vc->table = (HASH_TABLE *)NULL; +} + +static void +free_variable_hash_data (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + dispose_variable (var); +} + +/* Delete the entire contents of the hash table. */ +void +delete_all_variables (hashed_vars) + HASH_TABLE *hashed_vars; +{ + hash_flush (hashed_vars, free_variable_hash_data); +} + +/* **************************************************************** */ +/* */ +/* Setting variable attributes */ +/* */ +/* **************************************************************** */ + +#define FIND_OR_MAKE_VARIABLE(name, entry) \ + do \ + { \ + entry = find_variable (name); \ + if (!entry) \ + { \ + entry = bind_variable (name, ""); \ + if (!no_invisible_vars) entry->attributes |= att_invisible; \ + } \ + } \ + while (0) + +/* Make the variable associated with NAME be readonly. + If NAME does not exist yet, create it. */ +void +set_var_read_only (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + VSETATTR (entry, att_readonly); +} + +#ifdef INCLUDE_UNUSED +/* Make the function associated with NAME be readonly. + If NAME does not exist, we just punt, like auto_export code below. */ +void +set_func_read_only (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + VSETATTR (entry, att_readonly); +} + +/* Make the variable associated with NAME be auto-exported. + If NAME does not exist yet, create it. */ +void +set_var_auto_export (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + set_auto_export (entry); +} + +/* Make the function associated with NAME be auto-exported. */ +void +set_func_auto_export (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + set_auto_export (entry); +} +#endif + +/* **************************************************************** */ +/* */ +/* Creating lists of variables */ +/* */ +/* **************************************************************** */ + +static VARLIST * +vlist_alloc (nentries) + int nentries; +{ + VARLIST *vlist; + + vlist = (VARLIST *)xmalloc (sizeof (VARLIST)); + vlist->list = (SHELL_VAR **)xmalloc ((nentries + 1) * sizeof (SHELL_VAR *)); + vlist->list_size = nentries; + vlist->list_len = 0; + vlist->list[0] = (SHELL_VAR *)NULL; + + return vlist; +} + +static VARLIST * +vlist_realloc (vlist, n) + VARLIST *vlist; + int n; +{ + if (vlist == 0) + return (vlist = vlist_alloc (n)); + if (n > vlist->list_size) + { + vlist->list_size = n; + vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *)); + } + return vlist; +} + +static void +vlist_add (vlist, var, flags) + VARLIST *vlist; + SHELL_VAR *var; + int flags; +{ + register int i; + + for (i = 0; i < vlist->list_len; i++) + if (STREQ (var->name, vlist->list[i]->name)) + break; + if (i < vlist->list_len) + return; + + if (i >= vlist->list_size) + vlist = vlist_realloc (vlist, vlist->list_size + 16); + + vlist->list[vlist->list_len++] = var; + vlist->list[vlist->list_len] = (SHELL_VAR *)NULL; +} + +/* Map FUNCTION over the variables in VAR_HASH_TABLE. Return an array of the + variables for which FUNCTION returns a non-zero value. A NULL value + for FUNCTION means to use all variables. */ +SHELL_VAR ** +map_over (function, vc) + sh_var_map_func_t *function; + VAR_CONTEXT *vc; +{ + VAR_CONTEXT *v; + VARLIST *vlist; + SHELL_VAR **ret; + int nentries; + + for (nentries = 0, v = vc; v; v = v->down) + nentries += HASH_ENTRIES (v->table); + + if (nentries == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (nentries); + + for (v = vc; v; v = v->down) + flatten (v->table, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +SHELL_VAR ** +map_over_funcs (function) + sh_var_map_func_t *function; +{ + VARLIST *vlist; + SHELL_VAR **ret; + + if (shell_functions == 0 || HASH_ENTRIES (shell_functions) == 0) + return ((SHELL_VAR **)NULL); + + vlist = vlist_alloc (HASH_ENTRIES (shell_functions)); + + flatten (shell_functions, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +/* Flatten VAR_HASH_TABLE, applying FUNC to each member and adding those + elements for which FUNC succeeds to VLIST->list. FLAGS is reserved + for future use. Only unique names are added to VLIST. If FUNC is + NULL, each variable in VAR_HASH_TABLE is added to VLIST. If VLIST is + NULL, FUNC is applied to each SHELL_VAR in VAR_HASH_TABLE. If VLIST + and FUNC are both NULL, nothing happens. */ +static void +flatten (var_hash_table, func, vlist, flags) + HASH_TABLE *var_hash_table; + sh_var_map_func_t *func; + VARLIST *vlist; + int flags; +{ + register int i; + register BUCKET_CONTENTS *tlist; + int r; + SHELL_VAR *var; + + if (var_hash_table == 0 || (HASH_ENTRIES (var_hash_table) == 0) || (vlist == 0 && func == 0)) + return; + + for (i = 0; i < var_hash_table->nbuckets; i++) + { + for (tlist = hash_items (i, var_hash_table); tlist; tlist = tlist->next) + { + var = (SHELL_VAR *)tlist->data; + + r = func ? (*func) (var) : 1; + if (r && vlist) + vlist_add (vlist, var, flags); + } + } +} + +void +sort_variables (array) + SHELL_VAR **array; +{ + qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp); +} + +static int +qsort_var_comp (var1, var2) + SHELL_VAR **var1, **var2; +{ + int result; + + if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0) + result = strcmp ((*var1)->name, (*var2)->name); + + return (result); +} + +/* Apply FUNC to each variable in SHELL_VARIABLES, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +vapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over (func, shell_variables); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Apply FUNC to each variable in SHELL_FUNCTIONS, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +fapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over_funcs (func); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Create a NULL terminated array of all the shell variables. */ +SHELL_VAR ** +all_shell_variables () +{ + return (vapply ((sh_var_map_func_t *)NULL)); +} + +/* Create a NULL terminated array of all the shell functions. */ +SHELL_VAR ** +all_shell_functions () +{ + return (fapply ((sh_var_map_func_t *)NULL)); +} + +static int +visible_var (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0); +} + +SHELL_VAR ** +all_visible_functions () +{ + return (fapply (visible_var)); +} + +SHELL_VAR ** +all_visible_variables () +{ + return (vapply (visible_var)); +} + +/* Return non-zero if the variable VAR is visible and exported. Array + variables cannot be exported. */ +static int +visible_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && exported_p (var)); +} + +/* Return non-zero if VAR is a local variable in the current context and + is exported. */ +static int +local_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context && exported_p (var)); +} + +SHELL_VAR ** +all_exported_variables () +{ + return (vapply (visible_and_exported)); +} + +SHELL_VAR ** +local_exported_variables () +{ + return (vapply (local_and_exported)); +} + +static int +variable_in_context (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context); +} + +SHELL_VAR ** +all_local_variables () +{ + VARLIST *vlist; + SHELL_VAR **ret; + VAR_CONTEXT *vc; + + vc = shell_variables; + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("all_local_variables: no function context at current scope")); + return (SHELL_VAR **)NULL; + } + if (vc->table == 0 || HASH_ENTRIES (vc->table) == 0 || vc_haslocals (vc) == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (HASH_ENTRIES (vc->table)); + + flatten (vc->table, variable_in_context, vlist, 0); + + ret = vlist->list; + free (vlist); + if (ret) + sort_variables (ret); + return ret; +} + +#if defined (ARRAY_VARS) +/* Return non-zero if the variable VAR is visible and an array. */ +static int +visible_array_vars (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && array_p (var)); +} + +SHELL_VAR ** +all_array_variables () +{ + return (vapply (visible_array_vars)); +} +#endif /* ARRAY_VARS */ + +char ** +all_variables_matching_prefix (prefix) + const char *prefix; +{ + SHELL_VAR **varlist; + char **rlist; + int vind, rind, plen; + + plen = STRLEN (prefix); + varlist = all_visible_variables (); + for (vind = 0; varlist && varlist[vind]; vind++) + ; + if (varlist == 0 || vind == 0) + return ((char **)NULL); + rlist = strvec_create (vind + 1); + for (vind = rind = 0; varlist[vind]; vind++) + { + if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen)) + rlist[rind++] = savestring (varlist[vind]->name); + } + rlist[rind] = (char *)0; + free (varlist); + + return rlist; +} + +/* **************************************************************** */ +/* */ +/* Managing temporary variable scopes */ +/* */ +/* **************************************************************** */ + +/* Make variable NAME have VALUE in the temporary environment. */ +static SHELL_VAR * +bind_tempenv_variable (name, value) + const char *name; + char *value; +{ + SHELL_VAR *var; + + var = temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL; + + if (var) + { + FREE (value_cell (var)); + var_setvalue (var, savestring (value)); + INVALIDATE_EXPORTSTR (var); + } + + return (var); +} + +/* Find a variable in the temporary environment that is named NAME. + Return the SHELL_VAR *, or NULL if not found. */ +SHELL_VAR * +find_tempenv_variable (name) + const char *name; +{ + return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL); +} + +/* Push the variable described by (SHELL_VAR *)DATA down to the next + variable context from the temporary environment. */ +static void +push_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + HASH_TABLE *binding_table; + + var = (SHELL_VAR *)data; + + binding_table = shell_variables->table; + if (binding_table == 0) + { + if (shell_variables == global_variables) + /* shouldn't happen */ + binding_table = shell_variables->table = global_variables->table = hash_create (0); + else + binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS); + } + + v = bind_variable_internal (var->name, value_cell (var), binding_table, 0); + + /* XXX - should we set the context here? It shouldn't matter because of how + assign_in_env works, but might want to check. */ + if (binding_table == global_variables->table) /* XXX */ + var->attributes &= ~(att_tempvar|att_propagate); + else + { + var->attributes |= att_propagate; + if (binding_table == shell_variables->table) + shell_variables->flags |= VC_HASTMPVAR; + } + v->attributes |= var->attributes; + + dispose_variable (var); +} + +static void +propagate_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + if (tempvar_p (var) && (var->attributes & att_propagate)) + push_temp_var (data); + else + dispose_variable (var); +} + +/* Free the storage used in the hash table for temporary + environment variables. PUSHF is a function to be called + to free each hash table entry. It takes care of pushing variables + to previous scopes if appropriate. */ +static void +dispose_temporary_env (pushf) + sh_free_func_t *pushf; +{ + hash_flush (temporary_env, pushf); + hash_dispose (temporary_env); + temporary_env = (HASH_TABLE *)NULL; + + array_needs_making = 1; + + sv_ifs ("IFS"); /* XXX here for now */ +} + +void +dispose_used_env_vars () +{ + if (temporary_env) + dispose_temporary_env (propagate_temp_var); +} + +/* Take all of the shell variables in the temporary environment HASH_TABLE + and make shell variables from them at the current variable context. */ +void +merge_temporary_env () +{ + if (temporary_env) + dispose_temporary_env (push_temp_var); +} + +/* **************************************************************** */ +/* */ +/* Creating and manipulating the environment */ +/* */ +/* **************************************************************** */ + +static inline char * +mk_env_string (name, value) + const char *name, *value; +{ + int name_len, value_len; + char *p; + + name_len = strlen (name); + value_len = STRLEN (value); + p = (char *)xmalloc (2 + name_len + value_len); + strcpy (p, name); + p[name_len] = '='; + if (value && *value) + strcpy (p + name_len + 1, value); + else + p[name_len + 1] = '\0'; + return (p); +} + +#ifdef DEBUG +/* Debugging */ +static int +valid_exportstr (v) + SHELL_VAR *v; +{ + char *s; + + s = v->exportstr; + if (legal_variable_starter ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + for (s = v->exportstr + 1; s && *s; s++) + { + if (*s == '=') + break; + if (legal_variable_char ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + } + if (*s != '=') + { + internal_error (_("no `=' in exportstr for %s"), v->name); + return (0); + } + return (1); +} +#endif + +static char ** +make_env_array_from_var_list (vars) + SHELL_VAR **vars; +{ + register int i, list_index; + register SHELL_VAR *var; + char **list, *value; + + list = strvec_create ((1 + strvec_len ((char **)vars))); + +#define USE_EXPORTSTR (value == var->exportstr) + + for (i = 0, list_index = 0; var = vars[i]; i++) + { +#if defined (__CYGWIN__) + /* We don't use the exportstr stuff on Cygwin at all. */ + INVALIDATE_EXPORTSTR (var); +#endif + if (var->exportstr) + value = var->exportstr; + else if (function_p (var)) + value = named_function_string ((char *)NULL, function_cell (var), 0); +#if defined (ARRAY_VARS) + else if (array_p (var)) +# if 0 + value = array_to_assignment_string (array_cell (var)); +# else + continue; /* XXX array vars cannot yet be exported */ +# endif +#endif + else + value = value_cell (var); + + if (value) + { + /* Gee, I'd like to get away with not using savestring() if we're + using the cached exportstr... */ + list[list_index] = USE_EXPORTSTR ? savestring (value) + : mk_env_string (var->name, value); + + if (USE_EXPORTSTR == 0) + SAVE_EXPORTSTR (var, list[list_index]); + + list_index++; +#undef USE_EXPORTSTR + +#if 0 /* not yet */ +#if defined (ARRAY_VARS) + if (array_p (var)) + free (value); +#endif +#endif + } + } + + list[list_index] = (char *)NULL; + return (list); +} + +/* Make an array of assignment statements from the hash table + HASHED_VARS which contains SHELL_VARs. Only visible, exported + variables are eligible. */ +static char ** +make_var_export_array (vcxt) + VAR_CONTEXT *vcxt; +{ + char **list; + SHELL_VAR **vars; + + vars = map_over (visible_and_exported, vcxt); + + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +static char ** +make_func_export_array () +{ + char **list; + SHELL_VAR **vars; + + vars = map_over_funcs (visible_and_exported); + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +/* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */ +#define add_to_export_env(envstr,do_alloc) \ +do \ + { \ + if (export_env_index >= (export_env_size - 1)) \ + { \ + export_env_size += 16; \ + export_env = strvec_resize (export_env, export_env_size); \ + } \ + export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \ + export_env[export_env_index] = (char *)NULL; \ + } while (0) + +/* Add ASSIGN to EXPORT_ENV, or supercede a previous assignment in the + array with the same left-hand side. Return the new EXPORT_ENV. */ +char ** +add_or_supercede_exported_var (assign, do_alloc) + char *assign; + int do_alloc; +{ + register int i; + int equal_offset; + + equal_offset = assignment (assign, 0); + if (equal_offset == 0) + return (export_env); + + /* If this is a function, then only supersede the function definition. + We do this by including the `=() {' in the comparison, like + initialize_shell_variables does. */ + if (assign[equal_offset + 1] == '(' && + strncmp (assign + equal_offset + 2, ") {", 3) == 0) /* } */ + equal_offset += 4; + + for (i = 0; i < export_env_index; i++) + { + if (STREQN (assign, export_env[i], equal_offset + 1)) + { + free (export_env[i]); + export_env[i] = do_alloc ? savestring (assign) : assign; + return (export_env); + } + } + add_to_export_env (assign, do_alloc); + return (export_env); +} + +static void +add_temp_array_to_env (temp_array, do_alloc, do_supercede) + char **temp_array; + int do_alloc, do_supercede; +{ + register int i; + + if (temp_array == 0) + return; + + for (i = 0; temp_array[i]; i++) + { + if (do_supercede) + export_env = add_or_supercede_exported_var (temp_array[i], do_alloc); + else + add_to_export_env (temp_array[i], do_alloc); + } + + free (temp_array); +} + +/* Make the environment array for the command about to be executed, if the + array needs making. Otherwise, do nothing. If a shell action could + change the array that commands receive for their environment, then the + code should `array_needs_making++'. + + The order to add to the array is: + temporary_env + list of var contexts whose head is shell_variables + shell_functions + + This is the shell variable lookup order. We add only new variable + names at each step, which allows local variables and variables in + the temporary environments to shadow variables in the global (or + any previous) scope. +*/ + +static int +n_shell_variables () +{ + VAR_CONTEXT *vc; + int n; + + for (n = 0, vc = shell_variables; vc; vc = vc->down) + n += HASH_ENTRIES (vc->table); + return n; +} + +void +maybe_make_export_env () +{ + register char **temp_array; + int new_size; + VAR_CONTEXT *tcxt; + + if (array_needs_making) + { + if (export_env) + strvec_flush (export_env); + + /* Make a guess based on how many shell variables and functions we + have. Since there will always be array variables, and array + variables are not (yet) exported, this will always be big enough + for the exported variables and functions. */ + new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 + + HASH_ENTRIES (temporary_env); + if (new_size > export_env_size) + { + export_env_size = new_size; + export_env = strvec_resize (export_env, export_env_size); + } + export_env[export_env_index = 0] = (char *)NULL; + + /* Make a dummy variable context from the temporary_env, stick it on + the front of shell_variables, call make_var_export_array on the + whole thing to flatten it, and convert the list of SHELL_VAR *s + to the form needed by the environment. */ + if (temporary_env) + { + tcxt = new_var_context ((char *)NULL, 0); + tcxt->table = temporary_env; + tcxt->down = shell_variables; + } + else + tcxt = shell_variables; + + temp_array = make_var_export_array (tcxt); + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + if (tcxt != shell_variables) + free (tcxt); + +#if defined (RESTRICTED_SHELL) + /* Restricted shells may not export shell functions. */ + temp_array = restricted ? (char **)0 : make_func_export_array (); +#else + temp_array = make_func_export_array (); +#endif + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + array_needs_making = 0; + } +} + +/* This is an efficiency hack. PWD and OLDPWD are auto-exported, so + we will need to remake the exported environment every time we + change directories. `_' is always put into the environment for + every external command, so without special treatment it will always + cause the environment to be remade. + + If there is no other reason to make the exported environment, we can + just update the variables in place and mark the exported environment + as no longer needing a remake. */ +void +update_export_env_inplace (env_prefix, preflen, value) + char *env_prefix; + int preflen; + char *value; +{ + char *evar; + + evar = (char *)xmalloc (STRLEN (value) + preflen + 1); + strcpy (evar, env_prefix); + if (value) + strcpy (evar + preflen, value); + export_env = add_or_supercede_exported_var (evar, 0); +} + +/* We always put _ in the environment as the name of this command. */ +void +put_command_name_into_env (command_name) + char *command_name; +{ + update_export_env_inplace ("_=", 2, command_name); +} + +#if 0 /* UNUSED -- it caused too many problems */ +void +put_gnu_argv_flags_into_env (pid, flags_string) + intmax_t pid; + char *flags_string; +{ + char *dummy, *pbuf; + int l, fl; + + pbuf = itos (pid); + l = strlen (pbuf); + + fl = strlen (flags_string); + + dummy = (char *)xmalloc (l + fl + 30); + dummy[0] = '_'; + strcpy (dummy + 1, pbuf); + strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_"); + dummy[l + 27] = '='; + strcpy (dummy + l + 28, flags_string); + + free (pbuf); + + export_env = add_or_supercede_exported_var (dummy, 0); +} +#endif + +/* **************************************************************** */ +/* */ +/* Managing variable contexts */ +/* */ +/* **************************************************************** */ + +/* Allocate and return a new variable context with NAME and FLAGS. + NAME can be NULL. */ + +VAR_CONTEXT * +new_var_context (name, flags) + char *name; + int flags; +{ + VAR_CONTEXT *vc; + + vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT)); + vc->name = name ? savestring (name) : (char *)NULL; + vc->scope = variable_context; + vc->flags = flags; + + vc->up = vc->down = (VAR_CONTEXT *)NULL; + vc->table = (HASH_TABLE *)NULL; + + return vc; +} + +/* Free a variable context and its data, including the hash table. Dispose + all of the variables. */ +void +dispose_var_context (vc) + VAR_CONTEXT *vc; +{ + FREE (vc->name); + + if (vc->table) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + + free (vc); +} + +/* Set VAR's scope level to the current variable context. */ +static int +set_context (var) + SHELL_VAR *var; +{ + return (var->context = variable_context); +} + +/* Make a new variable context with NAME and FLAGS and a HASH_TABLE of + temporary variables, and push it onto shell_variables. This is + for shell functions. */ +VAR_CONTEXT * +push_var_context (name, flags, tempvars) + char *name; + int flags; + HASH_TABLE *tempvars; +{ + VAR_CONTEXT *vc; + + vc = new_var_context (name, flags); + vc->table = tempvars; + if (tempvars) + { + /* Have to do this because the temp environment was created before + variable_context was incremented. */ + flatten (tempvars, set_context, (VARLIST *)NULL, 0); + vc->flags |= VC_HASTMPVAR; + } + vc->down = shell_variables; + shell_variables->up = vc; + + return (shell_variables = vc); +} + +static void +push_func_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + + var = (SHELL_VAR *)data; + + if (tempvar_p (var) && (posixly_correct || (var->attributes & att_propagate))) + { + /* XXX - should we set v->context here? */ + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0); + if (shell_variables == global_variables) + var->attributes &= ~(att_tempvar|att_propagate); + else + shell_variables->flags |= VC_HASTMPVAR; + v->attributes |= var->attributes; + } + + dispose_variable (var); +} + +/* Pop the top context off of VCXT and dispose of it, returning the rest of + the stack. */ +void +pop_var_context () +{ + VAR_CONTEXT *ret, *vcxt; + + vcxt = shell_variables; + if (vc_isfuncenv (vcxt) == 0) + { + internal_error (_("pop_var_context: head of shell_variables not a function context")); + return; + } + + if (ret = vcxt->down) + { + ret->up = (VAR_CONTEXT *)NULL; + shell_variables = ret; + if (vcxt->table) + hash_flush (vcxt->table, push_func_var); + dispose_var_context (vcxt); + } + else + internal_error (_("pop_var_context: no global_variables context")); +} + +/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and + all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */ +void +delete_all_contexts (vcxt) + VAR_CONTEXT *vcxt; +{ + VAR_CONTEXT *v, *t; + + for (v = vcxt; v != global_variables; v = t) + { + t = v->down; + dispose_var_context (v); + } + + delete_all_variables (global_variables->table); + shell_variables = global_variables; +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping temporary variable scopes */ +/* */ +/* **************************************************************** */ + +VAR_CONTEXT * +push_scope (flags, tmpvars) + int flags; + HASH_TABLE *tmpvars; +{ + return (push_var_context ((char *)NULL, flags, tmpvars)); +} + +static void +push_exported_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + + var = (SHELL_VAR *)data; + + /* If a temp var had its export attribute set, or it's marked to be + propagated, bind it in the previous scope before disposing it. */ + if (exported_p (var) || (var->attributes & att_propagate)) + { + var->attributes &= ~att_tempvar; /* XXX */ + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0); + if (shell_variables == global_variables) + var->attributes &= ~att_propagate; + v->attributes |= var->attributes; + } + + dispose_variable (var); +} + +void +pop_scope (is_special) + int is_special; +{ + VAR_CONTEXT *vcxt, *ret; + + vcxt = shell_variables; + if (vc_istempscope (vcxt) == 0) + { + internal_error (_("pop_scope: head of shell_variables not a temporary environment scope")); + return; + } + + ret = vcxt->down; + if (ret) + ret->up = (VAR_CONTEXT *)NULL; + + shell_variables = ret; + + /* Now we can take care of merging variables in VCXT into set of scopes + whose head is RET (shell_variables). */ + FREE (vcxt->name); + if (vcxt->table) + { + if (is_special) + hash_flush (vcxt->table, push_func_var); + else + hash_flush (vcxt->table, push_exported_var); + hash_dispose (vcxt->table); + } + free (vcxt); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping function contexts */ +/* */ +/* **************************************************************** */ + +static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL; +static int dollar_arg_stack_slots; +static int dollar_arg_stack_index; + +/* XXX - we might want to consider pushing and popping the `getopts' state + when we modify the positional parameters. */ +void +push_context (name, is_subshell, tempvars) + char *name; /* function name */ + int is_subshell; + HASH_TABLE *tempvars; +{ + if (is_subshell == 0) + push_dollar_vars (); + variable_context++; + push_var_context (name, VC_FUNCENV, tempvars); +} + +/* Only called when subshell == 0, so we don't need to check, and can + unconditionally pop the dollar vars off the stack. */ +void +pop_context () +{ + pop_dollar_vars (); + variable_context--; + pop_var_context (); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* Save the existing positional parameters on a stack. */ +void +push_dollar_vars () +{ + if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots) + { + dollar_arg_stack = (WORD_LIST **) + xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10) + * sizeof (WORD_LIST **)); + } + dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args (); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; +} + +/* Restore the positional parameters from our stack. */ +void +pop_dollar_vars () +{ + if (!dollar_arg_stack || dollar_arg_stack_index == 0) + return; + + remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1); + dispose_words (dollar_arg_stack[dollar_arg_stack_index]); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; + set_dollar_vars_unchanged (); +} + +void +dispose_saved_dollar_vars () +{ + if (!dollar_arg_stack || dollar_arg_stack_index == 0) + return; + + dispose_words (dollar_arg_stack[dollar_arg_stack_index]); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; +} + +/* Manipulate the special BASH_ARGV and BASH_ARGC variables. */ + +void +push_args (list) + WORD_LIST *list; +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + WORD_LIST *l; + arrayind_t i; + char *t; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + for (l = list, i = 0; l; l = l->next, i++) + array_push (bash_argv_a, l->word->word); + + t = itos (i); + array_push (bash_argc_a, t); + free (t); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/* Remove arguments from BASH_ARGV array. Pop top element off BASH_ARGC + array and use that value as the count of elements to remove from + BASH_ARGV. */ +void +pop_args () +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + ARRAY_ELEMENT *ce; + intmax_t i; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + ce = array_shift (bash_argc_a, 1, 0); + if (ce == 0 || legal_number (element_value (ce), &i) == 0) + i = 0; + + for ( ; i > 0; i--) + array_pop (bash_argv_a); + array_dispose_element (ce); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/************************************************* + * * + * Functions to manage special variables * + * * + *************************************************/ + +/* Extern declarations for variables this code has to manage. */ +extern int eof_encountered, eof_encountered_limit, ignoreeof; + +#if defined (READLINE) +extern int no_line_editing; +extern int hostname_list_initialized; +#endif + +/* An alist of name.function for each special variable. Most of the + functions don't do much, and in fact, this would be faster with a + switch statement, but by the end of this file, I am sick of switch + statements. */ + +#define SET_INT_VAR(name, intvar) intvar = find_variable (name) != 0 + +/* This table will be sorted with qsort() the first time it's accessed. */ +struct name_and_function { + char *name; + sh_sv_func_t *function; +}; + +static struct name_and_function special_vars[] = { + { "GLOBIGNORE", sv_globignore }, + +#if defined (HISTORY) + { "HISTCONTROL", sv_history_control }, + { "HISTFILESIZE", sv_histsize }, + { "HISTIGNORE", sv_histignore }, + { "HISTSIZE", sv_histsize }, + { "HISTTIMEFORMAT", sv_histtimefmt }, +#endif + +#if defined (READLINE) + { "HOSTFILE", sv_hostfile }, +#endif + + { "IFS", sv_ifs }, + { "IGNOREEOF", sv_ignoreeof }, + + { "LANG", sv_locale }, + { "LC_ALL", sv_locale }, + { "LC_COLLATE", sv_locale }, + { "LC_CTYPE", sv_locale }, + { "LC_MESSAGES", sv_locale }, + { "LC_NUMERIC", sv_locale }, + + { "MAIL", sv_mail }, + { "MAILCHECK", sv_mail }, + { "MAILPATH", sv_mail }, + + { "OPTERR", sv_opterr }, + { "OPTIND", sv_optind }, + + { "PATH", sv_path }, + { "POSIXLY_CORRECT", sv_strict_posix }, + +#if defined (READLINE) + { "TERM", sv_terminal }, + { "TERMCAP", sv_terminal }, + { "TERMINFO", sv_terminal }, +#endif /* READLINE */ + + { "TEXTDOMAIN", sv_locale }, + { "TEXTDOMAINDIR", sv_locale }, + +#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE) + { "TZ", sv_tz }, +#endif + +#if defined (HISTORY) && defined (BANG_HISTORY) + { "histchars", sv_histchars }, +#endif /* HISTORY && BANG_HISTORY */ + + { "ignoreeof", sv_ignoreeof }, + + { (char *)0, (sh_sv_func_t *)0 } +}; + +#define N_SPECIAL_VARS (sizeof (special_vars) / sizeof (special_vars[0]) - 1) + +static int +sv_compare (sv1, sv2) + struct name_and_function *sv1, *sv2; +{ + int r; + + if ((r = sv1->name[0] - sv2->name[0]) == 0) + r = strcmp (sv1->name, sv2->name); + return r; +} + +static inline int +find_special_var (name) + const char *name; +{ + register int i, r; + + for (i = 0; special_vars[i].name; i++) + { + r = special_vars[i].name[0] - name[0]; + if (r == 0) + r = strcmp (special_vars[i].name, name); + if (r == 0) + return i; + else if (r > 0) + /* Can't match any of rest of elements in sorted list. Take this out + if it causes problems in certain environments. */ + break; + } + return -1; +} + +/* The variable in NAME has just had its state changed. Check to see if it + is one of the special ones where something special happens. */ +void +stupidly_hack_special_variables (name) + char *name; +{ + static int sv_sorted = 0; + int i; + + if (sv_sorted == 0) /* shouldn't need, but it's fairly cheap. */ + { + qsort (special_vars, N_SPECIAL_VARS, sizeof (special_vars[0]), + (QSFUNC *)sv_compare); + sv_sorted = 1; + } + + i = find_special_var (name); + if (i != -1) + (*(special_vars[i].function)) (name); +} + +void +sv_ifs (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable ("IFS"); + setifs (v); +} + +/* What to do just after the PATH variable has changed. */ +void +sv_path (name) + char *name; +{ + /* hash -r */ + phash_flush (); +} + +/* What to do just after one of the MAILxxxx variables has changed. NAME + is the name of the variable. This is called with NAME set to one of + MAIL, MAILCHECK, or MAILPATH. */ +void +sv_mail (name) + char *name; +{ + /* If the time interval for checking the files has changed, then + reset the mail timer. Otherwise, one of the pathname vars + to the users mailbox has changed, so rebuild the array of + filenames. */ + if (name[4] == 'C') /* if (strcmp (name, "MAILCHECK") == 0) */ + reset_mail_timer (); + else + { + free_mail_files (); + remember_mail_dates (); + } +} + +/* What to do when GLOBIGNORE changes. */ +void +sv_globignore (name) + char *name; +{ + setup_glob_ignore (name); +} + +#if defined (READLINE) +/* What to do just after one of the TERMxxx variables has changed. + If we are an interactive shell, then try to reset the terminal + information in readline. */ +void +sv_terminal (name) + char *name; +{ + if (interactive_shell && no_line_editing == 0) + rl_reset_terminal (get_string_value ("TERM")); +} + +void +sv_hostfile (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v == 0) + clear_hostname_list (); + else + hostname_list_initialized = 0; +} +#endif /* READLINE */ + +#if defined (HISTORY) +/* What to do after the HISTSIZE or HISTFILESIZE variables change. + If there is a value for this HISTSIZE (and it is numeric), then stifle + the history. Otherwise, if there is NO value for this variable, + unstifle the history. If name is HISTFILESIZE, and its value is + numeric, truncate the history file to hold no more than that many + lines. */ +void +sv_histsize (name) + char *name; +{ + char *temp; + intmax_t num; + + temp = get_string_value (name); + + if (temp && *temp) + { + if (legal_number (temp, &num)) + { + if (name[4] == 'S') + { + stifle_history (num); + num = where_history (); + if (history_lines_this_session > num) + history_lines_this_session = num; + } + else + { + history_truncate_file (get_string_value ("HISTFILE"), (int)num); + if (num <= history_lines_in_file) + history_lines_in_file = num; + } + } + } + else if (name[4] == 'S') + unstifle_history (); +} + +/* What to do after the HISTIGNORE variable changes. */ +void +sv_histignore (name) + char *name; +{ + setup_history_ignore (name); +} + +/* What to do after the HISTCONTROL variable changes. */ +void +sv_history_control (name) + char *name; +{ + char *temp; + char *val; + int tptr; + + history_control = 0; + temp = get_string_value (name); + + if (temp == 0 || *temp == 0) + return; + + tptr = 0; + while (val = extract_colon_unit (temp, &tptr)) + { + if (STREQ (val, "ignorespace")) + history_control |= HC_IGNSPACE; + else if (STREQ (val, "ignoredups")) + history_control |= HC_IGNDUPS; + else if (STREQ (val, "ignoreboth")) + history_control |= HC_IGNBOTH; + else if (STREQ (val, "erasedups")) + history_control |= HC_ERASEDUPS; + + free (val); + } +} + +#if defined (BANG_HISTORY) +/* Setting/unsetting of the history expansion character. */ +void +sv_histchars (name) + char *name; +{ + char *temp; + + temp = get_string_value (name); + if (temp) + { + history_expansion_char = *temp; + if (temp[0] && temp[1]) + { + history_subst_char = temp[1]; + if (temp[2]) + history_comment_char = temp[2]; + } + } + else + { + history_expansion_char = '!'; + history_subst_char = '^'; + history_comment_char = '#'; + } +} +#endif /* BANG_HISTORY */ + +void +sv_histtimefmt (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + history_write_timestamps = (v != 0); +} +#endif /* HISTORY */ + +#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE) +void +sv_tz (name) + char *name; +{ + tzset (); +} +#endif + +/* If the variable exists, then the value of it can be the number + of times we actually ignore the EOF. The default is small, + (smaller than csh, anyway). */ +void +sv_ignoreeof (name) + char *name; +{ + SHELL_VAR *tmp_var; + char *temp; + + eof_encountered = 0; + + tmp_var = find_variable (name); + ignoreeof = tmp_var != 0; + temp = tmp_var ? value_cell (tmp_var) : (char *)NULL; + if (temp) + eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10; + set_shellopts (); /* make sure `ignoreeof' is/is not in $SHELLOPTS */ +} + +void +sv_optind (name) + char *name; +{ + char *tt; + int s; + + tt = get_string_value ("OPTIND"); + if (tt && *tt) + { + s = atoi (tt); + + /* According to POSIX, setting OPTIND=1 resets the internal state + of getopt (). */ + if (s < 0 || s == 1) + s = 0; + } + else + s = 0; + getopts_reset (s); +} + +void +sv_opterr (name) + char *name; +{ + char *tt; + + tt = get_string_value ("OPTERR"); + sh_opterr = (tt && *tt) ? atoi (tt) : 1; +} + +void +sv_strict_posix (name) + char *name; +{ + SET_INT_VAR (name, posixly_correct); + posix_initialize (posixly_correct); +#if defined (READLINE) + if (interactive_shell) + posix_readline_initialize (posixly_correct); +#endif /* READLINE */ + set_shellopts (); /* make sure `posix' is/is not in $SHELLOPTS */ +} + +void +sv_locale (name) + char *name; +{ + char *v; + + v = get_string_value (name); + if (name[0] == 'L' && name[1] == 'A') /* LANG */ + set_lang (name, v); + else + set_locale_var (name, v); /* LC_*, TEXTDOMAIN* */ +} + +#if defined (ARRAY_VARS) +void +set_pipestatus_array (ps, nproc) + int *ps; + int nproc; +{ + SHELL_VAR *v; + ARRAY *a; + ARRAY_ELEMENT *ae; + register int i; + char *t, tbuf[INT_STRLEN_BOUND(int) + 1]; + + v = find_variable ("PIPESTATUS"); + if (v == 0) + v = make_new_array_variable ("PIPESTATUS"); + if (array_p (v) == 0) + return; /* Do nothing if not an array variable. */ + a = array_cell (v); + + if (a == 0 || array_num_elements (a) == 0) + { + for (i = 0; i < nproc; i++) /* was ps[i] != -1, not i < nproc */ + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + return; + } + + /* Fast case */ + if (array_num_elements (a) == nproc && nproc == 1) + { + ae = element_forw (a->head); + free (element_value (ae)); + ae->value = itos (ps[0]); + } + else if (array_num_elements (a) <= nproc) + { + /* modify in array_num_elements members in place, then add */ + ae = a->head; + for (i = 0; i < array_num_elements (a); i++) + { + ae = element_forw (ae); + free (element_value (ae)); + ae->value = itos (ps[i]); + } + /* add any more */ + for ( ; i < nproc; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + } + else + { + /* deleting elements. it's faster to rebuild the array. */ + array_flush (a); + for (i = 0; ps[i] != -1; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + } +} +#endif + +void +set_pipestatus_from_exit (s) + int s; +{ +#if defined (ARRAY_VARS) + static int v[2] = { 0, -1 }; + + v[0] = s; + set_pipestatus_array (v, 1); +#endif +} -- 2.47.3