diff --git a/ANNOUNCE b/ANNOUNCE index 45d55a820..4d53fed17 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -35,6 +35,18 @@ To get involved in development, read the brief policy information in README.md and then jump right in with a pull request or email a patch. See the TODO file in the top-level directory for a to-do list. +### MAIN CHANGES between 1.0.0-beta.2 and 1.0.0-(unreleased) ### + +New features in built-in commands: + +- Two bash-like flags for 'whence' were backported from ksh 93v-: + - 'whence -P/type -P' is an alias to the existing -p flag. + - 'whence -t/type -t' will print only the type of a command in a simple + format that is designed to be easy to use for scripts. Example: + $ type -t typeset; whence -t sh + builtin + file + ### MAIN CHANGES between 1.0.0-beta.1 and 1.0.0-beta.2 ### New features in built-in commands: diff --git a/NEWS b/NEWS index 06f6592f6..223bbf37f 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,14 @@ Any uppercase BUG_* names are modernish shell bug IDs. 2021-12-27: +- Two bash-like flags for 'whence' were backported from ksh 93v-: + - 'whence -P/type -P' is an alias to the existing -p flag. + - 'whence -t/type -t' will print only the type of a command in a simple + format that is designed to be easy to use for scripts. Example: + $ type -t typeset; whence -t sh + builtin + file + - Fixed a crash or freeze that would occur on Linux when using Ctrl+C to interrupt a command substitution containing a pipe in an interactive shell. diff --git a/src/cmd/ksh93/bltins/whence.c b/src/cmd/ksh93/bltins/whence.c index 625704f04..4c06ac2b7 100644 --- a/src/cmd/ksh93/bltins/whence.c +++ b/src/cmd/ksh93/bltins/whence.c @@ -20,7 +20,8 @@ ***********************************************************************/ /* * command [-pvVx] name [arg...] - * whence [-afpqv] name... + * type [-afpPqt] name... + * whence [-afpPqtv] name... * * David Korn * AT&T Labs @@ -35,12 +36,13 @@ #include "shlex.h" #include "builtins.h" -#define P_FLAG 1 -#define V_FLAG 2 -#define A_FLAG 4 -#define F_FLAG 010 -#define X_FLAG 020 -#define Q_FLAG 040 +#define P_FLAG (1 << 0) +#define V_FLAG (1 << 1) +#define A_FLAG (1 << 2) +#define F_FLAG (1 << 3) +#define X_FLAG (1 << 4) +#define Q_FLAG (1 << 5) +#define T_FLAG (1 << 6) static int whence(Shell_t *,char**, int); @@ -121,12 +123,15 @@ int b_whence(int argc,char *argv[],Shbltin_t *context) case 'v': flags |= V_FLAG; break; + case 't': + flags |= T_FLAG; + break; case 'f': flags |= F_FLAG; break; + case 'P': case 'p': flags |= P_FLAG; - flags &= ~V_FLAG; break; case 'q': flags |= Q_FLAG; @@ -138,6 +143,8 @@ int b_whence(int argc,char *argv[],Shbltin_t *context) errormsg(SH_DICT,ERROR_usage(2), "%s", opt_info.arg); UNREACHABLE(); } + if(flags&(P_FLAG|T_FLAG)) + flags &= ~V_FLAG; argv += opt_info.index; if(error_info.errors || !*argv) { @@ -171,7 +178,10 @@ static int whence(Shell_t *shp,char **argv, register int flags) /* reserved words first */ if(sh_lookup(name,shtab_reserved)) { - sfprintf(sfstdout,"%s%s\n",name,(flags&V_FLAG)?sh_translate(is_reserved):""); + if(flags&T_FLAG) + sfprintf(sfstdout,"keyword\n"); + else + sfprintf(sfstdout,"%s%s\n",name,(flags&V_FLAG)?sh_translate(is_reserved):""); if(!aflag) continue; aflag++; @@ -186,7 +196,10 @@ static int whence(Shell_t *shp,char **argv, register int flags) msg = sh_translate(is_alias); sfprintf(sfstdout,msg,name); } - sfputr(sfstdout,sh_fmtq(cp),'\n'); + if(flags&T_FLAG) + sfputr(sfstdout,"alias",'\n'); + else + sfputr(sfstdout,sh_fmtq(cp),'\n'); if(!aflag) continue; cp = 0; @@ -198,7 +211,8 @@ static int whence(Shell_t *shp,char **argv, register int flags) { if(flags&Q_FLAG) continue; - sfputr(sfstdout,name,-1); + if(!(flags&T_FLAG)) + sfputr(sfstdout,name,-1); if(flags&V_FLAG) { if(nv_isnull(np)) @@ -213,6 +227,8 @@ static int whence(Shell_t *shp,char **argv, register int flags) else sfprintf(sfstdout,sh_translate(is_function)); } + else if(flags&T_FLAG) + sfprintf(sfstdout,"function"); sfputc(sfstdout,'\n'); if(!aflag) continue; @@ -230,7 +246,10 @@ static int whence(Shell_t *shp,char **argv, register int flags) cp = ""; if(flags&Q_FLAG) continue; - sfprintf(sfstdout,"%s%s\n",name,cp); + if(flags&T_FLAG) + sfprintf(sfstdout,"builtin\n"); + else + sfprintf(sfstdout,"%s%s\n",name,cp); if(!aflag) continue; aflag++; @@ -273,7 +292,9 @@ static int whence(Shell_t *shp,char **argv, register int flags) { /* Undefined/autoloadable function on FPATH */ sfputr(sfstdout,sh_fmtq(cp),-1); - if(flags&V_FLAG) + if(flags&T_FLAG) + sfprintf(sfstdout,"function"); + else if(flags&V_FLAG) { sfprintf(sfstdout,sh_translate(is_ufunction)); sfprintf(sfstdout,sh_translate(e_autoloadfrom),sh_fmtq(stakptr(PATH_OFFSET))); @@ -283,13 +304,20 @@ static int whence(Shell_t *shp,char **argv, register int flags) } else if(cp) { + int is_pathbound_builtin = 0; cp = path_fullname(shp,cp); /* resolve '.' & '..' */ - if(flags&V_FLAG) + if(flags&(V_FLAG|T_FLAG)) { - sfputr(sfstdout,sh_fmtq(name),' '); + if(!(flags&T_FLAG)) + sfputr(sfstdout,sh_fmtq(name),' '); /* built-in version of program */ if(nv_search(cp,shp->bltin_tree,0)) - msg = sh_translate(is_builtver); + { + if(flags&T_FLAG) + is_pathbound_builtin = 1; + else + msg = sh_translate(is_builtver); + } /* tracked aliases next */ else if(!sh_isstate(SH_DEFPATH) && (np = nv_search(name,shp->track_tree,0)) @@ -298,9 +326,13 @@ static int whence(Shell_t *shp,char **argv, register int flags) msg = sh_translate(is_talias); else msg = sh_translate("is"); - sfputr(sfstdout,msg,' '); + if(!(flags&T_FLAG)) + sfputr(sfstdout,msg,' '); } - sfputr(sfstdout,sh_fmtq(cp),'\n'); + if(flags&T_FLAG) + sfputr(sfstdout,is_pathbound_builtin ? "builtin" : "file",'\n'); + else + sfputr(sfstdout,sh_fmtq(cp),'\n'); free((char*)cp); } else if(aflag<=1) diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index 2af216700..9d4b77601 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -2051,7 +2051,7 @@ _JOB_ ; const char sh_optwhence[] = -"[-1c?\n@(#)$Id: whence (ksh 93u+m) 2020-09-25 $\n]" +"[-1c?\n@(#)$Id: whence (ksh 93u+m) 2021-12-27 $\n]" "[--catalog?" SH_DICT "]" "[+NAME?whence, type - locate a command and describe its type]" "[+DESCRIPTION?Without \b-v\b, \bwhence\b writes on standard output an " @@ -2064,8 +2064,11 @@ const char sh_optwhence[] = "[+?The \btype\b command is equivalent to \bwhence -v\b.]" "[a?Like \b-v\b but displays all uses for each \aname\a rather than the first.]" "[f?Do not check for functions.]" -"[p?Do not check to see if \aname\a is a reserved word, a built-in, " +"[p|P?Do not check to see if \aname\a is a reserved word, a built-in, " "an alias, or a function. This turns off the \b-v\b option.]" +"[t?Output only a single-word type indicator for each \aname\a found: " + "\bkeyword\b, \balias\b, \bbuiltin\b, \bfunction\b or \bfile\b. " + "This turns off the \b-v\b option.]" "[q?Quiet mode. Returns 0 if all arguments are built-ins, functions, or are " "programs found on the path.]" "[v?For each name you specify, the shell displays a line that indicates " diff --git a/src/cmd/ksh93/data/testops.c b/src/cmd/ksh93/data/testops.c index bff987246..354c4ca08 100644 --- a/src/cmd/ksh93/data/testops.c +++ b/src/cmd/ksh93/data/testops.c @@ -103,11 +103,7 @@ const char sh_opttest[] = "[+-e \afile\a?\afile\a exists and is not a broken symlink.]" "[+-f \afile\a?\afile\a is a regular file.]" "[+-g \afile\a?\afile\a has its set-group-id bit set.]" - "[+-h \afile\a?Same as \b-L\b.]" "[+-k \afile\a *?\afile\a has its sticky bit on.]" -#if SHOPT_TEST_L - "[+-l \afile\a *?Same as \b-L\b.]" -#endif "[+-n \astring\a?Length of \astring\a is non-zero.]" "[+-o \aoption\a *?Shell option \aoption\a is enabled.]" "[+-p \afile\a?\afile\a is a FIFO.]" @@ -122,7 +118,11 @@ const char sh_opttest[] = "[+-z \astring\a?\astring\a is a zero-length string.]" "[+-G \afile\a *?Group of \afile\a is the effective " "group ID of the current process.]" - "[+-L \afile\a?\afile\a is a symbolic link.]" +#if SHOPT_TEST_L + "[+-L|l|h \afile\a?\afile\a is a symbolic link.]" +#else + "[+-L|h \afile\a?\afile\a is a symbolic link.]" +#endif "[+-N \afile\a *?\afile\a has been modified since it was last read.]" "[+-O \afile\a *?\afile\a exists and owner is the effective " "user ID of the current process.]" diff --git a/src/cmd/ksh93/sh.1 b/src/cmd/ksh93/sh.1 index a6ecbeb1f..80a9773e4 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -7672,7 +7672,7 @@ Does nothing, and exits 0. Used with .B while for infinite loops. .TP -\f3type\fP \*(OK \f3\-afpq\fP \*(CK \f2name\^\fP .\|.\|. +\f3type\fP \*(OK \f3\-afpPqt\fP \*(CK \f2name\^\fP .\|.\|. The same as .BR whence\ \-v . .TP @@ -8292,7 +8292,7 @@ See for a description of the format of .IR job . .TP -\f3whence\fP \*(OK \f3\-afpqv\fP \*(CK \f2name\^\fP .\|.\|. +\f3whence\fP \*(OK \f3\-afpPqtv\fP \*(CK \f2name\^\fP .\|.\|. For each .IR name , indicate how it @@ -8307,13 +8307,13 @@ The option skips the search for functions. The .B \-p -option -does a path search for +and +.B \-P +options +do a path search for .I name\^ even if name is an alias, a function, or a reserved word. -The -.B \-p -option turns off the +Both of these options turn off the .B \-v option. The @@ -8325,6 +8325,17 @@ to enter quiet mode. will return zero if all arguments are built-ins, functions, or are programs found on the path. The +.B \-t +option only outputs the type of the given command. +Like +.B \-p +and +.BR \-P , +.B \-t +will turn off the +.B \-v +option. +The .B \-a option is similar to the diff --git a/src/cmd/ksh93/tests/builtins.sh b/src/cmd/ksh93/tests/builtins.sh index 783a893d9..d009d5406 100755 --- a/src/cmd/ksh93/tests/builtins.sh +++ b/src/cmd/ksh93/tests/builtins.sh @@ -838,6 +838,89 @@ whence -q cat nonexist && err_exit "'whence -q' has the wrong exit status" whence -q nonexist && err_exit "'whence -q' has the wrong exit status" PATH=$save_PATH +# ====== +# These are the regression tests for the whence builtin's '-t' flag +for w in 'whence -t' 'type -t' 'whence -t -v'; do + exp=file + got=$($w $SHELL) + [[ $exp == "$got" ]] || err_exit "'$w' has the wrong output for external commands" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + got=$(builtin -d chmod; hash chmod; $w chmod) + [[ $exp == "$got" ]] || err_exit "'$w' has the wrong output for tracked aliases" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + exp=keyword + got=$($w time) + [[ $exp == "$got" ]] || err_exit "'$w' has the wrong output for keywords" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + exp=builtin + got=$($w sleep) + [[ $exp == "$got" ]] || err_exit "'$w' has the wrong output for regular builtins" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + got=$($w export) + [[ $exp == "$got" ]] || err_exit "'$w' has the wrong output for special builtins" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + if [[ $(PATH=/opt/ast/bin type cat) != "cat is a shell builtin version of /opt/ast/bin/cat" ]] + then warning "/opt/ast/bin/cat isn't a builtin; skipping path-bound builtin tests" + else + got=$(PATH=/opt/ast/bin $w cat) + [[ $exp == "$got" ]] || err_exit "Test A: '$w' has the wrong output for path-bound builtins" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + got=$($w /opt/ast/bin/cat) + [[ $exp == "$got" ]] || err_exit "Test B: '$w' has the wrong output for path-bound builtins" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + got=$(PATH=/opt/ast/bin:$PATH; $w cat) + [[ $exp == "$got" ]] || err_exit "Test C: '$w' has the wrong output for path-bound builtins" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + got=$(builtin cat; $w cat) + [[ $exp == "$got" ]] || err_exit "Test D: '$w' has the wrong output for path-bound builtins" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + fi + exp=alias + got=$(alias foo=bar; $w foo) + [[ $exp == "$got" ]] || err_exit "'$w' has the wrong output for aliases" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + exp=function + got=$(foo() { true; }; $w foo) + [[ $exp == "$got" ]] || err_exit "'$w' has the wrong output for POSIX functions" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + got=$(function foo { true; }; $w foo) + [[ $exp == "$got" ]] || err_exit "'$w' has the wrong output for KornShell functions" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + got=$(autoload FooBar; $w FooBar) + [[ $exp == "$got" ]] || err_exit "'$w' has the wrong output for undefined autoloaded functions" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + echo 'FooBar() { true; }' > FooBar + got=$(FPATH=. autoload FooBar; $w FooBar) + [[ $exp == "$got" ]] || err_exit "'$w' has the wrong output for autoloaded POSIX functions" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + echo 'function FooBar { true; }' > FooBar + got=$(FPATH=. autoload FooBar; $w FooBar) + [[ $exp == "$got" ]] || err_exit "'$w' has the wrong output for autoloaded KornShell functions" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + + # The final few tests are for '-t -a' + exp="alias +function +builtin +$($w -pa cat)" + got=$(alias cat=false + autoload cat + PATH=/opt/ast/bin:$PATH $w -a cat) + [[ $exp == "$got" ]] || err_exit "'$w -a' output is incorrect (cat command)" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" + if [[ -n $($w -pa time) ]] + then exp="keyword +alias +$($w -pa time)" + else + exp=$'keyword\nalias' + fi + got=$(alias time=nottime + $w -a time) + [[ $exp == "$got" ]] || err_exit "'$w -a' output is incorrect (time command)" \ + "(expected $(printf %q "$exp"); got $(printf %q "$got"))" +done + # ====== # 'cd ../.foo' should not exclude the '.' in '.foo' # https://bugzilla.redhat.com/889748