diff --git a/NEWS b/NEWS index c98e94a86..34df220ff 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,30 @@ For full details, see the git log at: https://github.com/ksh93/ksh Any uppercase BUG_* names are modernish shell bug IDs. +2021-12-26: + +- Listing aliases or tracked aliases in a script no longer corrupts + shcomp's generated bytecode. + +- Listing specific aliases with 'alias -p' and specific tracked aliases + with 'alias -pt' now works as documented. This means that the following + string of commands now works as you would expect: + $ hash -r; unalias -a + $ alias foo=bar; hash cat + $ alias -p foo; alias -pt cat + alias foo=bar + alias -t cat + +- As a result of the above fix, listing all tracked aliases with 'alias -pt' + now prints commands that can be reused to recreate the tracked aliases. + +- Attempting to list a non-existent alias or tracked alias with the -p option + now causes an error and sets the exit status to the number of non-existent + aliases passed. + +- Attempting to list 256 non-existent aliases now errors out with the exit + status set to one. + 2021-12-22: - Process substitutions run in a profile script no longer print their diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index 3836d8023..93adc5f7d 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -132,7 +132,7 @@ int b_alias(int argc,register char *argv[],Shbltin_t *context) { register unsigned flag = NV_NOARRAY|NV_NOSCOPE|NV_ASSIGN; register Dt_t *troot; - register int rflag=0, n; + register int rflag=0, xflag=0, n; struct tdata tdata; NOT_USED(argc); memset((void*)&tdata,0,sizeof(tdata)); @@ -151,20 +151,26 @@ int b_alias(int argc,register char *argv[],Shbltin_t *context) { case 'p': tdata.prefix = argv[0]; + tdata.pflag = 1; break; case 't': flag |= NV_TAGGED; break; case 'x': /* obsolete, ignored */ + xflag = 1; break; case 'r': rflag=1; break; case ':': + if(sh.shcomp) + return(2); /* don't print usage info while shcomp is compiling */ errormsg(SH_DICT,2, "%s", opt_info.arg); break; case '?': + if(sh.shcomp) + return(2); errormsg(SH_DICT,ERROR_usage(0), "%s", opt_info.arg); return(2); } @@ -179,12 +185,17 @@ int b_alias(int argc,register char *argv[],Shbltin_t *context) if(flag&NV_TAGGED) { troot = sh_subtracktree(1); /* use hash table */ - tdata.aflag = '-'; /* make setall() treat 'hash' like 'alias -t' */ + if(tdata.pflag) + tdata.aflag = '+'; /* for 'alias -pt', don't add anything to the hash table */ + else + tdata.aflag = '-'; /* make setall() treat 'hash' like 'alias -t' */ if(rflag) /* hash -r: clear hash table */ nv_scan(troot,nv_rehash,(void*)0,NV_TAGGED,NV_TAGGED); } else if(argv[1] && tdata.sh->subshell && !tdata.sh->subshare) sh_subfork(); /* avoid affecting the parent shell's alias table */ + if(xflag && (flag&NV_TAGGED)) + return(0); /* do nothing for 'alias -tx' */ return(setall(argv,flag,troot,&tdata)); } @@ -597,6 +608,8 @@ static void print_value(Sfio_t *iop, Namval_t *np, struct tdata *tp) sfwrite(iop,"}\n",2); return; } + if(tp->prefix && *tp->prefix=='a' && !nv_isattr(np,NV_TAGGED)) + sfprintf(iop,"%s ", tp->prefix); table = tp->sh->last_table; sfputr(iop,nv_name(np),aflag=='+'?'\n':'='); tp->sh->last_table = table; @@ -736,8 +749,16 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp continue; } np = nv_open(name,troot,nvflags|((nvflags&NV_ASSIGN)?0:NV_ARRAY)|((iarray|(nvflags&(NV_REF|NV_NOADD)==NV_REF))?NV_FARRAY:0)); - if(!np) + if(!np || (troot==sh.track_tree && nv_isattr(np,NV_NOALIAS))) + { + if(troot==sh.alias_tree || troot==sh.track_tree) + { + if(!sh.shcomp) + sfprintf(sfstderr,sh_translate(troot==sh.alias_tree ? e_noalias: e_notrackedalias),name); + r++; + } continue; + } if(np->nvflag&NV_RDONLY && !tp->pflag && (flag & ~(NV_ASSIGN|NV_RDONLY|NV_EXPORT))) /* allow readonly/export on readonly vars */ { @@ -775,7 +796,8 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp { if(troot!=shp->var_tree && (nv_isnull(np) || !print_namval(sfstdout,np,0,tp))) { - sfprintf(sfstderr,sh_translate(e_noalias),name); + if(!sh.shcomp) + sfprintf(sfstderr,sh_translate(e_noalias),name); r++; } if(!comvar && !iarray) @@ -979,6 +1001,14 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp print_all(sfstdout,troot,tp); sfsync(sfstdout); } + /* This is to handle cases where more than 255 non-existent + aliases were passed to the alias command. */ + if(r>255) + { + r &= SH_EXITMASK; + if(r==0) + r = 1; /* ensure the exit status is at least 1 */ + } return(r); } @@ -1404,6 +1434,8 @@ static int print_namval(Sfio_t *file,register Namval_t *np,register int flag, st flag = '\n'; if(tp->noref && nv_isref(np)) return(0); + if(sh.shcomp) + return(1); /* print nothing while shcomp is compiling */ if(nv_isattr(np,NV_NOPRINT|NV_INTEGER)==NV_NOPRINT) { if(is_abuiltin(np)) @@ -1418,7 +1450,9 @@ static int print_namval(Sfio_t *file,register Namval_t *np,register int flag, st isfun = is_afunction(np); if(tp->prefix) { - outname = (*tp->prefix=='t' && (!nv_isnull(np) || nv_isattr(np,NV_FLOAT|NV_RDONLY|NV_BINARY|NV_RJUST|NV_NOPRINT))); + outname = (*tp->prefix=='t' && (!nv_isnull(np) || nv_isattr(np,NV_FLOAT|NV_RDONLY|NV_BINARY|NV_RJUST|NV_NOPRINT))); + if(tp->scanroot==sh.track_tree && *tp->prefix=='a') + tp->prefix = "alias -t"; if(indent && (isfun || outname || *tp->prefix!='t')) { sfnputc(file,'\t',indent); @@ -1427,7 +1461,7 @@ static int print_namval(Sfio_t *file,register Namval_t *np,register int flag, st if(!isfun) { if(*tp->prefix=='t') - nv_attribute(np,tp->outfile,tp->prefix,tp->aflag); + nv_attribute(np,tp->outfile,tp->prefix,tp->aflag); else sfputr(file,tp->prefix,' '); } diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index 64606e6ab..2af216700 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -336,7 +336,7 @@ const char sh_optcont[] = const char sh_optalarm[] = "r [varname seconds]"; const char sh_optalias[] = -"[-1c?\n@(#)$Id: alias (ksh 93u+m) 2020-06-10 $\n]" +"[-1c?\n@(#)$Id: alias (ksh 93u+m) 2021-12-26 $\n]" "[--catalog?" SH_DICT "]" "[+NAME?alias - define or display aliases]" "[+DESCRIPTION?\balias\b creates or redefines alias definitions " @@ -363,7 +363,9 @@ const char sh_optalias[] = "[t?Each \aname\a is looked up as a command in \b$PATH\b and its path is " "added to the hash table as a 'tracked alias'. If no \aname\a is " "given, this prints the hash table. See \bhash(1)\b.]" -"[x?Ignored, this option is obsolete.]" +"[x?This option is obsolete. In most contexts the \b-x\b option is ignored, " + "although when it's combined with \b-t\b it will make \balias\b do " + "nothing.]" "\n" "\n[name[=value]...]\n" "\n" diff --git a/src/cmd/ksh93/data/msg.c b/src/cmd/ksh93/data/msg.c index bbdbd814d..8e4d75e99 100644 --- a/src/cmd/ksh93/data/msg.c +++ b/src/cmd/ksh93/data/msg.c @@ -115,6 +115,7 @@ const char e_nounattr[] = "cannot unset attribute C or A or a"; const char e_selfref[] = "%s: invalid self reference"; const char e_globalref[] = "%s: global reference cannot refer to local variable"; const char e_noalias[] = "%s: alias not found\n"; +const char e_notrackedalias[] = "%s: tracked alias not found\n"; const char e_format[] = "%s: bad format"; const char e_redef[] = "%s: type cannot be redefined"; const char e_required[] = "%s: is a required element of %s"; diff --git a/src/cmd/ksh93/include/name.h b/src/cmd/ksh93/include/name.h index f238e249b..c24b8ea15 100644 --- a/src/cmd/ksh93/include/name.h +++ b/src/cmd/ksh93/include/name.h @@ -242,6 +242,7 @@ extern const char e_restricted[]; extern const char e_ident[]; extern const char e_varname[]; extern const char e_noalias[]; +extern const char e_notrackedalias[]; extern const char e_noarray[]; extern const char e_notenum[]; extern const char e_nounattr[]; diff --git a/src/cmd/ksh93/include/version.h b/src/cmd/ksh93/include/version.h index 2cb8a93f4..c6a91870b 100644 --- a/src/cmd/ksh93/include/version.h +++ b/src/cmd/ksh93/include/version.h @@ -21,7 +21,7 @@ #define SH_RELEASE_FORK "93u+m" /* only change if you develop a new ksh93 fork */ #define SH_RELEASE_SVER "1.0.0-beta.2" /* semantic version number: https://semver.org */ -#define SH_RELEASE_DATE "2021-12-22" /* must be in this format for $((.sh.version)) */ +#define SH_RELEASE_DATE "2021-12-26" /* must be in this format for $((.sh.version)) */ #define SH_RELEASE_CPYR "(c) 2020-2021 Contributors to ksh " SH_RELEASE_FORK /* Scripts sometimes field-split ${.sh.version}, so don't change amount of whitespace. */ diff --git a/src/cmd/ksh93/sh.1 b/src/cmd/ksh93/sh.1 index f0304cf3a..a6ecbeb1f 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -5712,7 +5712,9 @@ is given, the name and value of the alias is printed. The obsolete .B \-x -option has no effect. +option has no effect in most contexts, although if it's used with +.B \-t +it will suppress all output. The exit status is non-zero if a .I name\^ is given, but no value, and no alias has been defined for the diff --git a/src/cmd/ksh93/tests/alias.sh b/src/cmd/ksh93/tests/alias.sh index 8f568e7db..490664e09 100755 --- a/src/cmd/ksh93/tests/alias.sh +++ b/src/cmd/ksh93/tests/alias.sh @@ -104,5 +104,176 @@ unalias foo && err_exit 'unalias should return non-zero when a previously set al err=$(set +x; { "$SHELL" -i -c 'unalias history; unalias r'; } 2>&1) && [[ -z $err ]] \ || err_exit "the 'history' and 'r' aliases can't be removed (got $(printf %q "$err"))" +# ====== +# Listing aliases in a script shouldn't break shcomp bytecode +exp="alias foo='bar(' +alias four=4 +alias three=3 +alias two=2 +foo='bar(' +foo='bar(' +four=4 +three=3 +two=2 +foo='bar(' +four=4 +three=3 +two=2 +noalias: alias not found +ls=$(whence -p ls) +alias -t ls" +alias_script=$tmp/alias_script.sh +cat > "$alias_script" << EOF +unalias -a +alias foo='bar(' +alias two=2 +alias three=3 +alias four=4 +alias -p +alias foo +alias -x +alias +alias noalias +alias -t ls +alias -t +alias -pt +EOF +got=$(set +x; redirect 2>&1; $SHELL <($SHCOMP "$alias_script")) +[[ $exp == $got ]] || err_exit "Listing aliases corrupts shcomp bytecode" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Specifying an alias with 'alias -p' should print that alias in a reusable form +exp='alias foo=BAR' +got=$( + alias foo=BAR bar=FOO + alias -p foo +) +[[ $exp == $got ]] || err_exit "Specifying an alias with 'alias -p' doesn't work" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +got=$( + hash -r cat + alias -tx + alias -ptx + alias -ptx cat + alias -ptx nosuchcommand +) +[[ -z $got ]] || err_exit "The -x option should prevent output when combined with -t" \ + "(got $(printf %q "$got"))" + +# Listing members of the hash table with 'alias -pt' should work +exp='alias -t cat +vi: tracked alias not found +alias -t cat +alias -t chmod' +got=$( + set +x + redirect 2>&1 + hash -r cat chmod + alias -pt cat vi # vi shouldn't be added to the hash table + alias -pt +) +[[ $exp == $got ]] || err_exit "Listing members of the hash table with 'alias -pt' doesn't work" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" + +# Attempting to list a non-existent alias or tracked alias should fail +# with an error message. Note that the exit status should match the +# number of aliases passed that don't exist. +exp='foo: alias not found +bar: alias not found +nosuchalias: alias not found' +got=$( + set +x + redirect 2>&1 + unalias -a + alias foo bar nosuchalias +) +ret=$? +[[ $exp == $got ]] || err_exit "Listing non-existent aliases with 'alias' should fail with an error message" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +((ret == 3)) || err_exit "Listing non-existent aliases with 'alias' has wrong exit status" \ + "(expected 3, got $ret)" + +# Same as above, but tests alias -p with a different number +# of non-existent aliases. +exp='foo: alias not found +bar: alias not found +nosuchalias: alias not found +stillnoalias: alias not found' +got=$( + set +x + redirect 2>&1 + unalias -a + alias -p foo bar nosuchalias stillnoalias +) +ret=$? +[[ $exp == $got ]] || err_exit "Listing non-existent aliases with 'alias -p' should fail with an error message" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +((ret == 4)) || err_exit "Listing non-existent aliases with 'alias -p' has wrong exit status" \ + "(expected 4, got $ret)" + +# Tracked aliases next. +exp='rm: tracked alias not found +ls: tracked alias not found' +got=$( + set +x + redirect 2>&1 + hash -r + alias -pt rm ls +) +ret=$? +[[ $exp == $got ]] || err_exit "Listing non-existent tracked aliases with 'alias -pt' should fail with an error message" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +((ret == 2)) || err_exit "Listing non-existent tracked aliases with 'alias -pt' has wrong exit status" \ + "(expected 2, got $ret)" + +# Detect zombie aliases with 'alias'. +exp='vi: alias not found +chmod: alias not found' +got=$($SHELL -c ' + hash vi chmod + hash -r + alias vi chmod +' 2>&1) +ret=$? +[[ $exp == $got ]] || err_exit "Listing removed tracked aliases with 'alias' should fail with an error message" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +((ret == 2)) || err_exit "Listing removed tracked aliases with 'alias' has wrong exit status" \ + "(expected 2, got $ret)" + +# Detect zombie aliases with 'alias -p'. +exp='vi: alias not found +chmod: alias not found' +got=$($SHELL -c ' + hash vi chmod + hash -r + alias -p vi chmod +' 2>&1) +ret=$? +[[ $exp == $got ]] || err_exit "Listing removed tracked aliases with 'alias -p' should fail with an error message" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +((ret == 2)) || err_exit "Listing removed tracked aliases with 'alias -p' has wrong exit status" \ + "(expected 2, got $ret)" + +# Detect zombie tracked aliases. +exp='vi: tracked alias not found +chmod: tracked alias not found' +got=$($SHELL -c ' + hash vi chmod + hash -r + alias -pt vi chmod +' 2>&1) +ret=$? +[[ $exp == $got ]] || err_exit "Listing removed tracked aliases with 'alias -pt' should fail with an error message" \ + "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +((ret == 2)) || err_exit "Listing removed tracked aliases with 'alias -pt' has wrong exit status" \ + "(expected 2, got $ret)" + +# The exit status on error must be >0, including when handling +# 256 non-existent aliases. +(unalias -a; alias $(integer -s i; for((i=0;i<256;i++)) do print -n "x "; done) 2> /dev/null) +got=$? +((got > 0)) || err_exit "Exit status is zero when alias is passed 256 non-existent aliases" + # ====== exit $((Errors<125?Errors:125))