mirror of
git://git.code.sf.net/p/cdesktopenv/code
synced 2025-03-09 15:50:02 +00:00
New feature: 'typeset -g' as in bash 4.2+
typeset -g allows directly manipulating the attributes of variables at the global level from any context. This feature already exists on bash 4.2 and later. mksh (R50+), yash and zsh have this flag as well, but it's slightly different: it ignores the current local scope, but a parent local scope from a calling function may still be used -- whereas on bash, '-g' always refers to the global scope. Since ksh93 uses static scoping (see III.Q28 at <http://kornshell.com/doc/faq.html>), only the bash behaviour makes sense here. Note that the implementation needs to be done both in nv_setlist() (name.c) and in b_typeset() (typeset.c) because assignments are executed before the typeset built-in itself. Hence also the pre-parsing of typeset options in sh_exec(). src/cmd/ksh93/include/nval.h: - Add new NV_GLOBAL bit flag, using a previously unused bit that still falls within the 32-bit integer range. src/cmd/ksh93/sh/xec.c: sh_exec(): - When pre-parsing typeset flags, make -g pass the NV_GLOBAL flag to the nv_setlist() call that processes shell assignments prior to running the command. src/cmd/ksh93/sh/name.c: nv_setlist(): - When the NV_GLOBAL bit flag is passed, save the current variable tree pointer (sh.var_tree) as well as the current namespace (sh.namespace) and temporarily set the former to the global variable tree (sh.var_base) and the latter to NULL. This makes assignments global and ignores namesapces. src/cmd/ksh93/bltins/typeset.c: - b_typeset(): - Use NV_GLOBAL bit flag for -g. - Allow combining -n with -g, permitting 'typeset -gn var' or 'nameref -g var' to create a global nameref from a function. - Do not allow a nonsensical use of -g when using nested typeset to define member variables of 'typeset -T' types. (Type method functions can still use it as normal.) - setall(): - If NV_GLOBAL is passed, use sh.var_base and deactivate sh.namespace as in nv_setlist(). This allows attributes to be set correctly for global variables. src/cmd/ksh93/tests/{functions,namespace}.sh: - Add regression tests based on reproducers for problems found by @hyenias in preliminary versions of this feature. Resolves: https://github.com/ksh93/ksh/issues/479
This commit is contained in:
parent
f73b8617dd
commit
ea300089a1
11 changed files with 167 additions and 8 deletions
3
ANNOUNCE
3
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
|
||||
|
|
8
NEWS
8
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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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?]{"
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue