diff --git a/ANNOUNCE b/ANNOUNCE index 0183dec73..4b0f190b3 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -56,6 +56,9 @@ New features in built-in commands: Note that to use these options the operating system must support the corresponding resource limit. +- 'typeset' has a new '-g' flag that forces variables to be created or + modified at the global scope regardless of context, as on bash 4.2+. + New command line editor features: - Various keys on extended PC keyboards are now handled as expected in the diff --git a/NEWS b/NEWS index 5cec75821..1d807a759 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,14 @@ For full details, see the git log at: https://github.com/ksh93/ksh/tree/1.0 Any uppercase BUG_* names are modernish shell bug IDs. +2022-06-01: + +- New 'typeset' feature inspired by bash (and zsh and mksh): the -g flag + causes the specified variable(s) to be created globally, ignoring the + local scope of ksh functions or name spaces. For example, this allows + creating a global array or numeric variable from a ksh function. + Thanks to @ormaaj for the feature suggestion and to @hyenias for testing. + 2022-05-29: - Fixed a bug causing an inconsistent state after a special built-in command diff --git a/src/cmd/ksh93/bltins/typeset.c b/src/cmd/ksh93/bltins/typeset.c index e11319905..f85a72eb2 100644 --- a/src/cmd/ksh93/bltins/typeset.c +++ b/src/cmd/ksh93/bltins/typeset.c @@ -424,6 +424,9 @@ int b_typeset(int argc,register char *argv[],Shbltin_t *context) flag &= ~NV_VARNAME; flag |= (NV_EXPORT|NV_IDENT); break; + case 'g': + flag |= NV_GLOBAL; + break; case ':': errormsg(SH_DICT,2, "%s", opt_info.arg); break; @@ -458,9 +461,9 @@ endargs: errormsg(SH_DICT,2,e_optincompat1,"-m"); error_info.errors++; } - if((flag&NV_REF) && (flag&~(NV_REF|NV_IDENT|NV_ASSIGN))) + if((flag&NV_REF) && (flag&~(NV_REF|NV_IDENT|NV_ASSIGN|NV_GLOBAL))) { - errormsg(SH_DICT,2,e_optincompat1,"-n"); + errormsg(SH_DICT,2,e_optincompat2,"-n","other options except -g"); error_info.errors++; } if((flag&NV_TYPE) && (flag&~(NV_TYPE|NV_VARNAME|NV_ASSIGN))) @@ -489,6 +492,11 @@ endargs: errormsg(SH_DICT,ERROR_exit(2),"option argument cannot be greater than %d",SHRT_MAX); UNREACHABLE(); } + if((flag&NV_GLOBAL) && sh.mktype) + { + errormsg(SH_DICT,ERROR_exit(2),"-g: type members cannot be global"); + UNREACHABLE(); + } if(isfloat) flag |= NV_DOUBLE; if(sflag) @@ -634,6 +642,17 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp char *last = 0; int nvflags=(flag&(NV_ARRAY|NV_NOARRAY|NV_VARNAME|NV_IDENT|NV_ASSIGN|NV_STATIC|NV_MOVE)); int r=0, ref=0, comvar=(flag&NV_COMVAR),iarray=(flag&NV_IARRAY); + Dt_t *save_vartree; + Namval_t *save_namespace; + if(flag&NV_GLOBAL) + { + save_vartree = sh.var_tree; + troot = sh.var_tree = sh.var_base; +#if SHOPT_NAMESPACE + save_namespace = sh.namespace; + sh.namespace = NIL(Namval_t*); +#endif + } if(!sh.prefix) { if(!tp->pflag) @@ -1005,6 +1024,13 @@ static int setall(char **argv,register int flag,Dt_t *troot,struct tdata *tp if(r==0) r = 1; /* ensure the exit status is at least 1 */ } + if(flag&NV_GLOBAL) + { + sh.var_tree = save_vartree; +#if SHOPT_NAMESPACE + sh.namespace = save_namespace; +#endif + } return(r); } diff --git a/src/cmd/ksh93/data/builtins.c b/src/cmd/ksh93/data/builtins.c index bd49d9a9a..183a1b263 100644 --- a/src/cmd/ksh93/data/builtins.c +++ b/src/cmd/ksh93/data/builtins.c @@ -1776,7 +1776,7 @@ const char sh_opttrap[] = ; const char sh_opttypeset[] = -"+[-1c?\n@(#)$Id: typeset (ksh 93u+m) 2021-12-17 $\n]" +"+[-1c?\n@(#)$Id: typeset (ksh 93u+m) 2022-06-01 $\n]" "[--catalog?" SH_DICT "]" "[+NAME?typeset - declare or display variables with attributes]" "[+DESCRIPTION?Without the \b-f\b option, \btypeset\b sets, unsets, " @@ -1833,6 +1833,7 @@ const char sh_opttypeset[] = "encoding of the data. This option can be used with \b-Z\b to " "specify fixed-size fields.]" "[f?Each of the options and \aname\as refers to a function.]" +"[g?Forces variables to be created or modified at the global scope.]" "[i]#?[base:=10?An integer. \abase\a represents the arithmetic base " "from 2 to 64.]" "[l?Without \b-i\b, sets character mapping to \btolower\b. When used " @@ -1896,7 +1897,7 @@ const char sh_opttypeset[] = "\n[name[=value]...]\n" " -f [name...]\n" " -m [name=name...]\n" -" -n [name=name...]\n" +" -n [-g] [name=name...]\n" " -T [tname[=(type definition)]...]\n" "\n" "[+EXIT STATUS?]{" diff --git a/src/cmd/ksh93/include/nval.h b/src/cmd/ksh93/include/nval.h index d372980e2..e2238259d 100644 --- a/src/cmd/ksh93/include/nval.h +++ b/src/cmd/ksh93/include/nval.h @@ -184,6 +184,7 @@ struct Namval #define NV_NOSCOPE 0x80000 /* look only in current scope */ #define NV_NOFAIL 0x100000 /* return 0 on failure, no msg */ #define NV_NODISC NV_IDENT /* ignore disciplines */ +#define NV_GLOBAL 0x20000000 /* create global variable, ignoring local scope */ #define NV_FUNCT NV_IDENT /* option for nv_create */ #define NV_BLTINOPT NV_ZFILL /* mark builtins in libcmd */ diff --git a/src/cmd/ksh93/include/version.h b/src/cmd/ksh93/include/version.h index bd0267e09..7d597fae2 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 "2022-05-29" /* must be in this format for $((.sh.version)) */ +#define SH_RELEASE_DATE "2022-06-01" /* must be in this format for $((.sh.version)) */ #define SH_RELEASE_CPYR "(c) 2020-2022 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 469dbca1d..e7fa955a7 100644 --- a/src/cmd/ksh93/sh.1 +++ b/src/cmd/ksh93/sh.1 @@ -8323,6 +8323,19 @@ format can be used to output the actual data in this buffer instead of the base64 encoding of the data. .TP +.B \-g +Forces variables to be created or modified at the global scope, +even when +.B typeset +is executed in a function defined by the +.B function +.I name +syntax (see +.I Functions\^ +above) or in a name space (see +.I Name Spaces\^ +above). +.TP .B \-h Used within type definitions to add information when generating information about the subvariable on the man page. @@ -8359,7 +8372,7 @@ Equivalent to .B "\-M tolower" . .TP .B \-m -moves or renames the variable. +Moves or renames the variable. The value is the name of a variable whose value will be moved to .IR vname\^ . The original variable will be unset. @@ -8373,7 +8386,8 @@ defined by the value of variable .IR vname . This is usually used to reference a variable inside a function whose name has been passed as an argument. -Cannot be used with any other options. +Cannot be used with other options except +.BR \-g . .TP .B \-p The name, attributes and values for the given diff --git a/src/cmd/ksh93/sh/name.c b/src/cmd/ksh93/sh/name.c index 0f418aa97..d2285830e 100644 --- a/src/cmd/ksh93/sh/name.c +++ b/src/cmd/ksh93/sh/name.c @@ -273,6 +273,19 @@ void nv_setlist(register struct argnod *arg,register int flags, Namval_t *typ) struct Namref nr; int maketype = flags&NV_TYPE; /* make a 'typeset -T' type definition command */ struct sh_type shtp; + Dt_t *save_vartree; +#if SHOPT_NAMESPACE + Namval_t *save_namespace; +#endif + if(flags&NV_GLOBAL) + { + save_vartree = sh.var_tree; + sh.var_tree = sh.var_base; +#if SHOPT_NAMESPACE + save_namespace = sh.namespace; + sh.namespace = NIL(Namval_t*); +#endif + } if(maketype) { shtp.previous = sh.mktype; @@ -646,6 +659,13 @@ void nv_setlist(register struct argnod *arg,register int flags, Namval_t *typ) } /* continue loop */ } + if(flags&NV_GLOBAL) + { + sh.var_tree = save_vartree; +#if SHOPT_NAMESPACE + sh.namespace = save_namespace; +#endif + } } /* diff --git a/src/cmd/ksh93/sh/xec.c b/src/cmd/ksh93/sh/xec.c index 6f18874af..805a4aee0 100644 --- a/src/cmd/ksh93/sh/xec.c +++ b/src/cmd/ksh93/sh/xec.c @@ -1068,6 +1068,8 @@ int sh_exec(register const Shnode_t *t, int flags) flgs |= NV_STATIC; if(checkopt(com,'m')) flgs |= NV_MOVE; + if(checkopt(com,'g')) + flgs |= NV_GLOBAL; if(np==SYSNAMEREF || checkopt(com,'n')) flgs |= NV_NOREF; else if(argn>=3 && checkopt(com,'T')) diff --git a/src/cmd/ksh93/tests/functions.sh b/src/cmd/ksh93/tests/functions.sh index b488b4986..b6c5c6391 100755 --- a/src/cmd/ksh93/tests/functions.sh +++ b/src/cmd/ksh93/tests/functions.sh @@ -1341,5 +1341,44 @@ got=$( { "$SHELL" "$tmp/crash_rhbz1117404.ksh"; } 2>&1) ((!(e = $?))) || err_exit 'crash while handling function-local trap' \ "(got status $e$( ((e>128)) && print -n /SIG && kill -l "$e"), $(printf %q "$got"))" +# ====== +# 'typeset -g' should cause the active function scope to be ignored. +# https://github.com/ksh93/ksh/issues/479#issuecomment-1140523291 +if ! command typeset -g x 2>/dev/null +then + warning "shell does not have 'typeset -g'; skipping those tests" +else + unset x arr a b c + # must specify the length for -X as the default is -X 24 on 32-bit systems and -X 32 on 64-bit systems + typeset -A exp=( + ['x=123; function f { typeset -g x=456; }; function g { typeset x=789; f; }; g; typeset -p x']='x=456' + ['x=123; function f { typeset -g -i x; }; f; typeset -p x']='typeset -i x=123' + ['x=123; function f { typeset -g -F x; }; f; typeset -p x']='typeset -F x=123.0000000000' + ['x=123; function f { typeset -g -E x; }; f; typeset -p x']='typeset -E x=123' + ['x=123; function f { typeset -g -X24 x; }; f; typeset -p x']='typeset -X 24 x=0x1.ec0000000000000000000000p+6' + ['x=aBc; function f { typeset -g -u x; }; f; typeset -p x']='typeset -u x=ABC' + ['x=aBc; function f { typeset -g -l x; }; f; typeset -p x']='typeset -l x=abc' + ['x=aBc; function f { typeset -g -L x; }; f; typeset -p x']='typeset -L 3 x=aBc' + ['x=aBc; function f { typeset -g -R x; }; f; typeset -p x']='typeset -R 3 x=aBc' + ['x=aBc; function f { typeset -g -Z x; }; f; typeset -p x']='typeset -Z 3 -R 3 x=aBc' + ['x=aBc; function f { typeset -g -L2 x; }; f; typeset -p x']='typeset -L 2 x=aB' + ['x=aBc; function f { typeset -g -R2 x; }; f; typeset -p x']='typeset -R 2 x=Bc' + ['x=aBc; function f { typeset -g -Z2 x; }; f; typeset -p x']='typeset -Z 2 -R 2 x=Bc' + ['x=8; function f { typeset -g -i2 x; }; f; typeset -p x']='typeset -i 2 x=2#1000' + ['x=8; function f { typeset -g -i8 x; }; f; typeset -p x']='typeset -i 8 x=8#10' + ['arr=(a b c); function f { typeset -g -i arr[1]=(1 2 3); }; f; typeset -p arr']='typeset -a -i arr=(0 (1 2 3) 0)' + ['arr=(a b c); function f { typeset -g -F arr[1]=(1 2 3); }; f; typeset -p arr']='typeset -a -F arr=(0.0000000000 (1.0000000000 2.0000000000 3.0000000000) 0.0000000000)' + ['arr=(a b c); function f { typeset -g -E arr[1]=(1 2 3); }; f; typeset -p arr']='typeset -a -E arr=(0 (1 2 3) 0)' + ['arr=(a b c); function f { typeset -g -X24 arr[1]=(1 2 3); }; f; typeset -p arr']='typeset -a -X 24 arr=(0x0.000000000000000000000000p+0 (0x1.000000000000000000000000p+0 0x1.000000000000000000000000p+1 0x1.800000000000000000000000p+1) 0x0.000000000000000000000000p+0)' + ) + for cmd in "${!exp[@]}" + do + got=$(set +x; eval "$cmd" 2>&1) + [[ $got == "${exp[$cmd]}" ]] || err_exit "typeset -g in $(printf %q "$cmd") failed to activate global" \ + "scope from ksh function (expected $(printf %q "${exp[$cmd]}"), got $(printf %q "$got"))" + done + unset exp +fi + # ====== exit $((Errors<125?Errors:125)) diff --git a/src/cmd/ksh93/tests/namespace.sh b/src/cmd/ksh93/tests/namespace.sh index e9adc88ab..5c5978345 100755 --- a/src/cmd/ksh93/tests/namespace.sh +++ b/src/cmd/ksh93/tests/namespace.sh @@ -138,9 +138,54 @@ set +o allexport # is not executed -- the zillionth bug with virtual subshells >:-/ exp=$'123 456\n789 456' got=$(ulimit -t unlimited 2>/dev/null # workaround: force fork - trap 'echo $x ${.test.x}; x=789; echo $x ${.test.x}' EXIT; x=123; namespace test { x=456; set -Q 2>/dev/null; }) + trap 'echo $x ${.test.x}; x=789; echo $x ${.test.x}' EXIT + x=123 + namespace test + { + x=456 + set --bad_option 2>/dev/null + } +) [[ $got == "$exp" ]] || err_exit 'Parent scope not restored after special builtin throws error in namespace' \ "(expected $(printf %q "$exp"), got $(printf %q "$got"))" +# ====== +# 'typeset -g' should cause the active name space to be ignored. +# https://github.com/ksh93/ksh/issues/479#issuecomment-1140523291 +if ! command typeset -g x 2>/dev/null +then + warning "shell does not have 'typeset -g'; skipping those tests" +else + unset x arr a b c + # must specify the length for -X as the default is -X 24 on 32-bit systems and -X 32 on 64-bit systems + typeset -A exp=( + ['x=123; namespace test { typeset -g -i x; }; typeset -p x']='typeset -i x=123' + ['x=123; namespace test { typeset -g -F x; }; typeset -p x']='typeset -F x=123.0000000000' + ['x=123; namespace test { typeset -g -E x; }; typeset -p x']='typeset -E x=123' + ['x=123; namespace test { typeset -g -X24 x; }; typeset -p x']='typeset -X 24 x=0x1.ec0000000000000000000000p+6' + ['x=aBc; namespace test { typeset -g -u x; }; typeset -p x']='typeset -u x=ABC' + ['x=aBc; namespace test { typeset -g -l x; }; typeset -p x']='typeset -l x=abc' + ['x=aBc; namespace test { typeset -g -L x; }; typeset -p x']='typeset -L 3 x=aBc' + ['x=aBc; namespace test { typeset -g -R x; }; typeset -p x']='typeset -R 3 x=aBc' + ['x=aBc; namespace test { typeset -g -Z x; }; typeset -p x']='typeset -Z 3 -R 3 x=aBc' + ['x=aBc; namespace test { typeset -g -L2 x; }; typeset -p x']='typeset -L 2 x=aB' + ['x=aBc; namespace test { typeset -g -R2 x; }; typeset -p x']='typeset -R 2 x=Bc' + ['x=aBc; namespace test { typeset -g -Z2 x; }; typeset -p x']='typeset -Z 2 -R 2 x=Bc' + ['x=8; namespace test { typeset -g -i2 x; }; typeset -p x']='typeset -i 2 x=2#1000' + ['x=8; namespace test { typeset -g -i8 x; }; typeset -p x']='typeset -i 8 x=8#10' + ['arr=(a b c); namespace test { typeset -g -i arr[1]=(1 2 3); }; typeset -p arr']='typeset -a -i arr=(0 (1 2 3) 0)' + ['arr=(a b c); namespace test { typeset -g -F arr[1]=(1 2 3); }; typeset -p arr']='typeset -a -F arr=(0.0000000000 (1.0000000000 2.0000000000 3.0000000000) 0.0000000000)' + ['arr=(a b c); namespace test { typeset -g -E arr[1]=(1 2 3); }; typeset -p arr']='typeset -a -E arr=(0 (1 2 3) 0)' + ['arr=(a b c); namespace test { typeset -g -X24 arr[1]=(1 2 3); }; typeset -p arr']='typeset -a -X 24 arr=(0x0.000000000000000000000000p+0 (0x1.000000000000000000000000p+0 0x1.000000000000000000000000p+1 0x1.800000000000000000000000p+1) 0x0.000000000000000000000000p+0)' + ) + for cmd in "${!exp[@]}" + do + got=$(set +x; eval "$cmd" 2>&1) + [[ $got == "${exp[$cmd]}" ]] || err_exit "typeset -g in $(printf %q "$cmd") failed to activate global" \ + "scope from name space (expected $(printf %q "${exp[$cmd]}"), got $(printf %q "$got"))" + done + unset exp +fi + # ====== exit $((Errors<125?Errors:125))