diff --git a/ANNOUNCE b/ANNOUNCE index 699398b14..3bb4719bf 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -82,6 +82,12 @@ New shell language features: useful; it now matches all hidden files (dotfiles) in the current directory, without the harmful inclusion of '.' and '..'. +- Tilde expansion can now be extended or modified by defining a + .sh.tilde.get or .sh.tilde.set discipline function. This replaces a + 2004 undocumented attempt to add this functionality via a .sh.tilde + command, which never worked and crashed the shell. See the manual for + details on the new method. + New features in built-in commands: - Usage error messages now show the --help/--man self-documentation options. diff --git a/NEWS b/NEWS index 07ae72fe6..349a90cf5 100644 --- a/NEWS +++ b/NEWS @@ -422,6 +422,11 @@ Any uppercase BUG_* names are modernish shell bug IDs. 2021-03-16: +- Tilde expansion can now be extended or modified by defining a .sh.tilde.get + or .sh.tilde.set discipline function. This replaces a 2004 undocumented + attempt to add this functionality via a .sh.tilde built-in, which never + worked and crashed the shell. See the manual for details on the new method. + - Fixed a bug in interactive shells: if a variable used by the shell called a discipline function (such as PS1.get() or COLUMNS.set()), the value of $? was set to the exit status of the discipline function instead of the last diff --git a/src/cmd/ksh93/data/variables.c b/src/cmd/ksh93/data/variables.c index 9d0f7b9e4..6f6f0a796 100644 --- a/src/cmd/ksh93/data/variables.c +++ b/src/cmd/ksh93/data/variables.c @@ -104,6 +104,7 @@ const struct shtable2 shtab_variables[] = ".sh.math", 0, (char*)0, ".sh.pool", 0, (char*)0, ".sh.pid", NV_INTEGER|NV_NOFREE, (char*)0, + ".sh.tilde", 0, (char*)0, "SHLVL", NV_INTEGER|NV_NOFREE|NV_EXPORT, (char*)0, #if SHOPT_MULTIBYTE "CSWIDTH", 0, (char*)0, diff --git a/src/cmd/ksh93/include/variables.h b/src/cmd/ksh93/include/variables.h index d663d9f37..d7219bb3a 100644 --- a/src/cmd/ksh93/include/variables.h +++ b/src/cmd/ksh93/include/variables.h @@ -104,6 +104,7 @@ extern void sh_save_rand_seed(struct rand *, int); #define SH_MATHNOD (shgd->bltin_nodes+62) #define SH_JOBPOOL (shgd->bltin_nodes+63) #define SH_PIDNOD (shgd->bltin_nodes+64) -#define SHLVL (shgd->bltin_nodes+65) +#define SH_TILDENOD (shgd->bltin_nodes+65) +#define SHLVL (shgd->bltin_nodes+66) #endif /* SH_VALNOD */ diff --git a/src/cmd/ksh93/sh.1 b/src/cmd/ksh93/sh.1 index caf0953e5..6dee5e68b 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -849,6 +849,37 @@ A .B : also terminates a user name following a .BR \(ap . +.PP +The tilde expansion mechanism may be extended or modified +by defining one of the discipline functions +.B .sh.tilde.set +or +.B .sh.tilde.get +(see +.I Functions +and +.I Discipline Functions +below). +If either exists, +then upon encountering a tilde word to expand, +that function is called with the tilde word assigned to either +.B .sh.value +(for the +.B .sh.tilde.set +function) or +.B .sh.tilde +(for the +.B .sh.tilde.get +function). +Performing tilde expansion within a discipline function will not recursively +call that function, but default tilde expansion remains active, +so literal tildes should still be quoted where required. +Either function may assign a replacement string to +.BR .sh.value . +If this value is non-empty and does not start with a +.BR \(ap , +it replaces the default tilde expansion when the function terminates. +Otherwise, the tilde expansion is left unchanged. .SS Command Substitution. The standard output from a command list enclosed in parentheses preceded by a dollar sign ( diff --git a/src/cmd/ksh93/sh/macro.c b/src/cmd/ksh93/sh/macro.c index cded43095..abfd45885 100644 --- a/src/cmd/ksh93/sh/macro.c +++ b/src/cmd/ksh93/sh/macro.c @@ -2631,7 +2631,23 @@ static void tilde_expand2(Shell_t *shp, register int offset) char *cp = NIL(char*); /* character pointer for tilde expansion result */ char *stakp = stakptr(0); /* current stack object (&stakp[offset] is tilde string) */ int curoff = staktell(); /* current offset of current stack object */ + static char block; /* for disallowing tilde expansion in .get/.set to change ${.sh.tilde} */ /* + * Allow overriding tilde expansion with a .sh.tilde.set or .get discipline function. + */ + if(!block && SH_TILDENOD->nvfun && SH_TILDENOD->nvfun->disc) + { + stakfreeze(1); /* terminate current stack object to avoid data corruption */ + block++; + nv_putval(SH_TILDENOD, &stakp[offset], 0); + cp = nv_getval(SH_TILDENOD); + block--; + if(cp[0]=='\0' || cp[0]=='~') + cp = NIL(char*); /* do not use empty or unexpanded result */ + stakset(stakp,curoff); /* restore stack to state on function entry */ + } + /* + * Perform default tilde expansion unless overridden. * Write the result to the stack, if any. */ stakputc(0); diff --git a/src/cmd/ksh93/tests/pty.sh b/src/cmd/ksh93/tests/pty.sh index 904067e5c..c156f1f35 100755 --- a/src/cmd/ksh93/tests/pty.sh +++ b/src/cmd/ksh93/tests/pty.sh @@ -751,6 +751,24 @@ r ^:test-2: echo Success\r\n$ r ^Success\r\n$ ! +# err_exit # +((SHOPT_VSH || SHOPT_ESH)) && tst $LINENO <<"!" +L value of $? after tilde expansion in tab completion + +# Make sure that a .sh.tilde.set discipline function +# cannot influence the exit status. + +w .sh.tilde.set() { true; } +w HOME=/tmp +w false ~\t +u false /tmp +w echo "Exit status is: $?" +u Exit status is: 1 +w (exit 42) +w echo $? ~\t +u 42 /tmp +! + # err_exit # ((SHOPT_MULTIBYTE && (SHOPT_VSH || SHOPT_ESH))) && [[ ${LC_ALL:-${LC_CTYPE:-${LANG:-}}} =~ [Uu][Tt][Ff]-?8 ]] && diff --git a/src/cmd/ksh93/tests/tilde.sh b/src/cmd/ksh93/tests/tilde.sh index 978786b92..1be00b968 100755 --- a/src/cmd/ksh93/tests/tilde.sh +++ b/src/cmd/ksh93/tests/tilde.sh @@ -107,6 +107,8 @@ got=~ HOME=$saveHOME # ====== +# Tilde expansion discipline function tests + # This nonfunctional mess was removed in ksh 93u+m ... if builtin .sh.tilde 2>/dev/null then got=$(.sh.tilde & wait "$!" 2>&1) @@ -114,5 +116,57 @@ then got=$(.sh.tilde & wait "$!" 2>&1) "(got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$got"))" fi +# ... and replaced by a proper use of discipline functions that allows customising tilde expansion. +((.sh.version >= 20210316)) && +for disc in get set +do ( + ulimit -t unlimited 2>/dev/null # fork subshell to cope with a possible crash + + eval ".sh.tilde.$disc() + { + case \${.sh.${ [[ $disc == get ]] && print tilde || print value; }} in + '~tmp') .sh.value=\$tmp ;; + '~INC') .sh.value=\$((++i)) ;; + '~spc') .sh.value=$'one\ttwo three\n\tfour' ;; + '~') .sh.value=~/addition ;; # this should not recurse + esac + }" + + got=~/foo + exp=$HOME/addition/foo + [[ $got == "$exp" ]] || err_exit "$disc discipline: bare tilde expansion:" \ + "expected $(printf %q "$exp"), got $(printf %q "$got")" + + .sh.tilde=oldvalue + got=$(print ~tmp/foo.$$; print "${.sh.tilde}") + exp=$tmp/foo.$$$'\n'$tmp + [[ $got == "$exp" ]] || err_exit "$disc discipline: result left in \${.sh.tilde}:" \ + "expected $(printf %q "$tmp"), got $(printf %q "${.sh.tilde}")" + [[ ${.sh.tilde} == oldvalue ]] || err_exit "$disc discipline: \${.sh.tilde} subshell leak" + + i=0 + set -- ~INC ~INC ~INC ~INC ~INC + got=$#,$1,$2,$3,$4,$5 + exp=5,1,2,3,4,5 + [[ $got == "$exp" ]] || err_exit "$disc discipline: counter:" \ + "expected $(printf %q "$exp"), got $(printf %q "$got")" + ((i==5)) || err_exit "$disc discipline: counter: $i != 5" + + set -- ~spc ~spc ~spc + got=$#,$1,$2,$3 + exp=$'3,one\ttwo three\n\tfour,one\ttwo three\n\tfour,one\ttwo three\n\tfour' + [[ $got == "$exp" ]] || err_exit "$disc discipline: quoting of whitespace:" \ + "expected $(printf %q "$exp"), got $(printf %q "$got")" + + print "$Errors" >$tmp/Errors + ) & + wait "$!" 2>crashmsg + if ((!(e = $?))) + then read Errors <$tmp/Errors + else err_exit ".sh.tilde.$disc discipline function crashes the shell" \ + "(got status $e$( ((e>128)) && print -n / && kill -l "$e"), $(printf %q "$(