hush: add support for local builtin
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
parent
98f74034cd
commit
295fef80bc
@ -232,6 +232,13 @@ config HUSH_FUNCTIONS
|
|||||||
help
|
help
|
||||||
Enable support for shell functions in hush. +800 bytes.
|
Enable support for shell functions in hush. +800 bytes.
|
||||||
|
|
||||||
|
config HUSH_LOCAL
|
||||||
|
bool "Support local builtin"
|
||||||
|
default n
|
||||||
|
depends on HUSH_FUNCTIONS
|
||||||
|
help
|
||||||
|
Enable support for local variables in functions.
|
||||||
|
|
||||||
config HUSH_EXPORT_N
|
config HUSH_EXPORT_N
|
||||||
bool "Support export '-n' option"
|
bool "Support export '-n' option"
|
||||||
default n
|
default n
|
||||||
|
246
shell/hush.c
246
shell/hush.c
@ -394,6 +394,9 @@ struct parse_context {
|
|||||||
struct variable {
|
struct variable {
|
||||||
struct variable *next;
|
struct variable *next;
|
||||||
char *varstr; /* points to "name=" portion */
|
char *varstr; /* points to "name=" portion */
|
||||||
|
#if ENABLE_HUSH_LOCAL
|
||||||
|
unsigned func_nest_level;
|
||||||
|
#endif
|
||||||
int max_len; /* if > 0, name is part of initial env; else name is malloced */
|
int max_len; /* if > 0, name is part of initial env; else name is malloced */
|
||||||
smallint flg_export; /* putenv should be done on this var */
|
smallint flg_export; /* putenv should be done on this var */
|
||||||
smallint flg_read_only;
|
smallint flg_read_only;
|
||||||
@ -488,6 +491,10 @@ struct globals {
|
|||||||
struct variable shell_ver;
|
struct variable shell_ver;
|
||||||
#if ENABLE_HUSH_FUNCTIONS
|
#if ENABLE_HUSH_FUNCTIONS
|
||||||
struct function *top_func;
|
struct function *top_func;
|
||||||
|
# if ENABLE_HUSH_LOCAL
|
||||||
|
struct variable **shadowed_vars_pp;
|
||||||
|
unsigned func_nest_level;
|
||||||
|
# endif
|
||||||
#endif
|
#endif
|
||||||
/* Signal and trap handling */
|
/* Signal and trap handling */
|
||||||
#if ENABLE_HUSH_FAST
|
#if ENABLE_HUSH_FAST
|
||||||
@ -529,6 +536,9 @@ static int builtin_jobs(char **argv);
|
|||||||
#if ENABLE_HUSH_HELP
|
#if ENABLE_HUSH_HELP
|
||||||
static int builtin_help(char **argv);
|
static int builtin_help(char **argv);
|
||||||
#endif
|
#endif
|
||||||
|
#if ENABLE_HUSH_LOCAL
|
||||||
|
static int builtin_local(char **argv);
|
||||||
|
#endif
|
||||||
#if HUSH_DEBUG
|
#if HUSH_DEBUG
|
||||||
static int builtin_memleak(char **argv);
|
static int builtin_memleak(char **argv);
|
||||||
#endif
|
#endif
|
||||||
@ -599,6 +609,9 @@ static const struct built_in_command bltins[] = {
|
|||||||
#if ENABLE_HUSH_JOB
|
#if ENABLE_HUSH_JOB
|
||||||
BLTIN("jobs" , builtin_jobs , "List active jobs"),
|
BLTIN("jobs" , builtin_jobs , "List active jobs"),
|
||||||
#endif
|
#endif
|
||||||
|
#if ENABLE_HUSH_LOCAL
|
||||||
|
BLTIN("local" , builtin_local , "Set local variable"),
|
||||||
|
#endif
|
||||||
#if HUSH_DEBUG
|
#if HUSH_DEBUG
|
||||||
BLTIN("memleak" , builtin_memleak , "Debug tool"),
|
BLTIN("memleak" , builtin_memleak , "Debug tool"),
|
||||||
#endif
|
#endif
|
||||||
@ -1261,12 +1274,21 @@ static const char *get_local_var_value(const char *name)
|
|||||||
* -1: clear export flag and unsetenv the variable
|
* -1: clear export flag and unsetenv the variable
|
||||||
* flg_read_only is set only when we handle -R var=val
|
* flg_read_only is set only when we handle -R var=val
|
||||||
*/
|
*/
|
||||||
#if BB_MMU
|
#if !BB_MMU && ENABLE_HUSH_LOCAL
|
||||||
#define set_local_var(str, flg_export, flg_read_only) \
|
/* all params are used */
|
||||||
|
#elif BB_MMU && ENABLE_HUSH_LOCAL
|
||||||
|
#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
|
||||||
|
set_local_var(str, flg_export, local_lvl)
|
||||||
|
#elif BB_MMU && !ENABLE_HUSH_LOCAL
|
||||||
|
#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
|
||||||
set_local_var(str, flg_export)
|
set_local_var(str, flg_export)
|
||||||
|
#elif !BB_MMU && !ENABLE_HUSH_LOCAL
|
||||||
|
#define set_local_var(str, flg_export, local_lvl, flg_read_only) \
|
||||||
|
set_local_var(str, flg_export, flg_read_only)
|
||||||
#endif
|
#endif
|
||||||
static int set_local_var(char *str, int flg_export, int flg_read_only)
|
static int set_local_var(char *str, int flg_export, int local_lvl, int flg_read_only)
|
||||||
{
|
{
|
||||||
|
struct variable **var_pp;
|
||||||
struct variable *cur;
|
struct variable *cur;
|
||||||
char *eq_sign;
|
char *eq_sign;
|
||||||
int name_len;
|
int name_len;
|
||||||
@ -1278,15 +1300,10 @@ static int set_local_var(char *str, int flg_export, int flg_read_only)
|
|||||||
}
|
}
|
||||||
|
|
||||||
name_len = eq_sign - str + 1; /* including '=' */
|
name_len = eq_sign - str + 1; /* including '=' */
|
||||||
cur = G.top_var; /* cannot be NULL (we have HUSH_VERSION and it's RO) */
|
var_pp = &G.top_var;
|
||||||
while (1) {
|
while ((cur = *var_pp) != NULL) {
|
||||||
if (strncmp(cur->varstr, str, name_len) != 0) {
|
if (strncmp(cur->varstr, str, name_len) != 0) {
|
||||||
if (!cur->next) {
|
var_pp = &cur->next;
|
||||||
/* Bail out. Note that now cur points
|
|
||||||
* to the last var in the linked list */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cur = cur->next;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/* We found an existing var with this name */
|
/* We found an existing var with this name */
|
||||||
@ -1298,33 +1315,61 @@ static int set_local_var(char *str, int flg_export, int flg_read_only)
|
|||||||
free(str);
|
free(str);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (flg_export == -1) {
|
if (flg_export == -1) { // "&& cur->flg_export" ?
|
||||||
debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
|
debug_printf_env("%s: unsetenv '%s'\n", __func__, str);
|
||||||
*eq_sign = '\0';
|
*eq_sign = '\0';
|
||||||
unsetenv(str);
|
unsetenv(str);
|
||||||
*eq_sign = '=';
|
*eq_sign = '=';
|
||||||
}
|
}
|
||||||
|
#if ENABLE_HUSH_LOCAL
|
||||||
|
if (cur->func_nest_level < local_lvl) {
|
||||||
|
/* New variable is declared as local,
|
||||||
|
* and existing one is global, or local
|
||||||
|
* from enclosing function.
|
||||||
|
* Remove and save old one: */
|
||||||
|
*var_pp = cur->next;
|
||||||
|
cur->next = *G.shadowed_vars_pp;
|
||||||
|
*G.shadowed_vars_pp = cur;
|
||||||
|
/* bash 3.2.33(1) and exported vars:
|
||||||
|
* # export z=z
|
||||||
|
* # f() { local z=a; env | grep ^z; }
|
||||||
|
* # f
|
||||||
|
* z=a
|
||||||
|
* # env | grep ^z
|
||||||
|
* z=z
|
||||||
|
*/
|
||||||
|
if (cur->flg_export)
|
||||||
|
flg_export = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
|
if (strcmp(cur->varstr + name_len, eq_sign + 1) == 0) {
|
||||||
free_and_exp:
|
free_and_exp:
|
||||||
free(str);
|
free(str);
|
||||||
goto exp;
|
goto exp;
|
||||||
}
|
}
|
||||||
if (cur->max_len >= strlen(str)) {
|
if (cur->max_len != 0) {
|
||||||
/* This one is from startup env, reuse space */
|
if (cur->max_len >= strlen(str)) {
|
||||||
strcpy(cur->varstr, str);
|
/* This one is from startup env, reuse space */
|
||||||
goto free_and_exp;
|
strcpy(cur->varstr, str);
|
||||||
}
|
goto free_and_exp;
|
||||||
/* max_len == 0 signifies "malloced" var, which we can
|
}
|
||||||
* (and has to) free */
|
} else {
|
||||||
if (!cur->max_len)
|
/* max_len == 0 signifies "malloced" var, which we can
|
||||||
|
* (and has to) free */
|
||||||
free(cur->varstr);
|
free(cur->varstr);
|
||||||
|
}
|
||||||
cur->max_len = 0;
|
cur->max_len = 0;
|
||||||
goto set_str_and_exp;
|
goto set_str_and_exp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Not found - create next variable struct */
|
/* Not found - create new variable struct */
|
||||||
cur->next = xzalloc(sizeof(*cur));
|
cur = xzalloc(sizeof(*cur));
|
||||||
cur = cur->next;
|
#if ENABLE_HUSH_LOCAL
|
||||||
|
cur->func_nest_level = local_lvl;
|
||||||
|
#endif
|
||||||
|
cur->next = *var_pp;
|
||||||
|
*var_pp = cur;
|
||||||
|
|
||||||
set_str_and_exp:
|
set_str_and_exp:
|
||||||
cur->varstr = str;
|
cur->varstr = str;
|
||||||
@ -1418,7 +1463,7 @@ static void arith_set_local_var(const char *name, const char *val, int flags)
|
|||||||
{
|
{
|
||||||
/* arith code doesnt malloc space, so do it for it */
|
/* arith code doesnt malloc space, so do it for it */
|
||||||
char *var = xasprintf("%s=%s", name, val);
|
char *var = xasprintf("%s=%s", name, val);
|
||||||
set_local_var(var, flags, 0);
|
set_local_var(var, flags, /*lvl:*/ 0, /*ro:*/ 0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -1438,7 +1483,7 @@ static void add_vars(struct variable *var)
|
|||||||
debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr);
|
debug_printf_env("%s: restoring exported '%s'\n", __func__, var->varstr);
|
||||||
putenv(var->varstr);
|
putenv(var->varstr);
|
||||||
} else {
|
} else {
|
||||||
debug_printf_env("%s: restoring local '%s'\n", __func__, var->varstr);
|
debug_printf_env("%s: restoring variable '%s'\n", __func__, var->varstr);
|
||||||
}
|
}
|
||||||
var = next;
|
var = next;
|
||||||
}
|
}
|
||||||
@ -1471,7 +1516,7 @@ static struct variable *set_vars_and_save_old(char **strings)
|
|||||||
var_p->next = old;
|
var_p->next = old;
|
||||||
old = var_p;
|
old = var_p;
|
||||||
}
|
}
|
||||||
set_local_var(*s, 1, 0);
|
set_local_var(*s, /*exp:*/ 1, /*lvl:*/ 0, /*ro:*/ 0);
|
||||||
}
|
}
|
||||||
s++;
|
s++;
|
||||||
}
|
}
|
||||||
@ -2302,7 +2347,7 @@ static int expand_vars_to_list(o_string *output, int n, char *arg, char or_mask)
|
|||||||
val = NULL;
|
val = NULL;
|
||||||
} else {
|
} else {
|
||||||
char *new_var = xasprintf("%s=%s", var, val);
|
char *new_var = xasprintf("%s=%s", var, val);
|
||||||
set_local_var(new_var, 0, 0);
|
set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3036,9 +3081,13 @@ static int run_function(const struct function *funcp, char **argv)
|
|||||||
smallint sv_flg;
|
smallint sv_flg;
|
||||||
|
|
||||||
save_and_replace_G_args(&sv, argv);
|
save_and_replace_G_args(&sv, argv);
|
||||||
|
|
||||||
/* "we are in function, ok to use return" */
|
/* "we are in function, ok to use return" */
|
||||||
sv_flg = G.flag_return_in_progress;
|
sv_flg = G.flag_return_in_progress;
|
||||||
G.flag_return_in_progress = -1;
|
G.flag_return_in_progress = -1;
|
||||||
|
#if ENABLE_HUSH_LOCAL
|
||||||
|
G.func_nest_level++;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* On MMU, funcp->body is always non-NULL */
|
/* On MMU, funcp->body is always non-NULL */
|
||||||
# if !BB_MMU
|
# if !BB_MMU
|
||||||
@ -3052,7 +3101,32 @@ static int run_function(const struct function *funcp, char **argv)
|
|||||||
rc = run_list(funcp->body);
|
rc = run_list(funcp->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ENABLE_HUSH_LOCAL
|
||||||
|
{
|
||||||
|
struct variable *var;
|
||||||
|
struct variable **var_pp;
|
||||||
|
|
||||||
|
var_pp = &G.top_var;
|
||||||
|
while ((var = *var_pp) != NULL) {
|
||||||
|
if (var->func_nest_level < G.func_nest_level) {
|
||||||
|
var_pp = &var->next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* Unexport */
|
||||||
|
if (var->flg_export)
|
||||||
|
bb_unsetenv(var->varstr);
|
||||||
|
/* Remove from global list */
|
||||||
|
*var_pp = var->next;
|
||||||
|
/* Free */
|
||||||
|
if (!var->max_len)
|
||||||
|
free(var->varstr);
|
||||||
|
free(var);
|
||||||
|
}
|
||||||
|
G.func_nest_level--;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
G.flag_return_in_progress = sv_flg;
|
G.flag_return_in_progress = sv_flg;
|
||||||
|
|
||||||
restore_G_args(&sv, argv);
|
restore_G_args(&sv, argv);
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
@ -3606,7 +3680,7 @@ static int run_pipe(struct pipe *pi)
|
|||||||
p = expand_string_to_string(*argv);
|
p = expand_string_to_string(*argv);
|
||||||
debug_printf_exec("set shell var:'%s'->'%s'\n",
|
debug_printf_exec("set shell var:'%s'->'%s'\n",
|
||||||
*argv, p);
|
*argv, p);
|
||||||
set_local_var(p, 0, 0);
|
set_local_var(p, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
|
||||||
argv++;
|
argv++;
|
||||||
}
|
}
|
||||||
/* Do we need to flag set_local_var() errors?
|
/* Do we need to flag set_local_var() errors?
|
||||||
@ -3651,9 +3725,17 @@ static int run_pipe(struct pipe *pi)
|
|||||||
}
|
}
|
||||||
#if ENABLE_HUSH_FUNCTIONS
|
#if ENABLE_HUSH_FUNCTIONS
|
||||||
else {
|
else {
|
||||||
|
# if ENABLE_HUSH_LOCAL
|
||||||
|
struct variable **sv;
|
||||||
|
sv = G.shadowed_vars_pp;
|
||||||
|
G.shadowed_vars_pp = &old_vars;
|
||||||
|
# endif
|
||||||
debug_printf_exec(": function '%s' '%s'...\n",
|
debug_printf_exec(": function '%s' '%s'...\n",
|
||||||
funcp->name, argv_expanded[1]);
|
funcp->name, argv_expanded[1]);
|
||||||
rcode = run_function(funcp, argv_expanded) & 0xff;
|
rcode = run_function(funcp, argv_expanded) & 0xff;
|
||||||
|
# if ENABLE_HUSH_LOCAL
|
||||||
|
G.shadowed_vars_pp = sv;
|
||||||
|
# endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@ -4050,7 +4132,7 @@ static int run_list(struct pipe *pi)
|
|||||||
}
|
}
|
||||||
/* Insert next value from for_lcur */
|
/* Insert next value from for_lcur */
|
||||||
/* note: *for_lcur already has quotes removed, $var expanded, etc */
|
/* note: *for_lcur already has quotes removed, $var expanded, etc */
|
||||||
set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), 0, 0);
|
set_local_var(xasprintf("%s=%s", pi->cmds[0].argv[0], *for_lcur++), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (rword == RES_IN) {
|
if (rword == RES_IN) {
|
||||||
@ -6250,7 +6332,7 @@ int hush_main(int argc, char **argv)
|
|||||||
break;
|
break;
|
||||||
case 'R':
|
case 'R':
|
||||||
case 'V':
|
case 'V':
|
||||||
set_local_var(xstrdup(optarg), 0, opt == 'R');
|
set_local_var(xstrdup(optarg), /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ opt == 'R');
|
||||||
break;
|
break;
|
||||||
# if ENABLE_HUSH_FUNCTIONS
|
# if ENABLE_HUSH_FUNCTIONS
|
||||||
case 'F': {
|
case 'F': {
|
||||||
@ -6583,6 +6665,55 @@ static void print_escaped(const char *s)
|
|||||||
} while (*s);
|
} while (*s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !ENABLE_HUSH_LOCAL
|
||||||
|
#define helper_export_local(argv, exp, lvl) \
|
||||||
|
helper_export_local(argv, exp)
|
||||||
|
#endif
|
||||||
|
static void helper_export_local(char **argv, int exp, int lvl)
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
char *name = *argv;
|
||||||
|
|
||||||
|
/* So far we do not check that name is valid (TODO?) */
|
||||||
|
|
||||||
|
if (strchr(name, '=') == NULL) {
|
||||||
|
struct variable *var;
|
||||||
|
|
||||||
|
var = get_local_var(name);
|
||||||
|
if (exp == -1) { /* unexporting? */
|
||||||
|
/* export -n NAME (without =VALUE) */
|
||||||
|
if (var) {
|
||||||
|
var->flg_export = 0;
|
||||||
|
debug_printf_env("%s: unsetenv '%s'\n", __func__, name);
|
||||||
|
unsetenv(name);
|
||||||
|
} /* else: export -n NOT_EXISTING_VAR: no-op */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (exp == 1) { /* exporting? */
|
||||||
|
/* export NAME (without =VALUE) */
|
||||||
|
if (var) {
|
||||||
|
var->flg_export = 1;
|
||||||
|
debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr);
|
||||||
|
putenv(var->varstr);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Exporting non-existing variable.
|
||||||
|
* bash does not put it in environment,
|
||||||
|
* but remembers that it is exported,
|
||||||
|
* and does put it in env when it is set later.
|
||||||
|
* We just set it to "" and export. */
|
||||||
|
/* Or, it's "local NAME" (without =VALUE).
|
||||||
|
* bash sets the value to "". */
|
||||||
|
name = xasprintf("%s=", name);
|
||||||
|
} else {
|
||||||
|
/* (Un)exporting/making local NAME=VALUE */
|
||||||
|
name = xstrdup(name);
|
||||||
|
}
|
||||||
|
set_local_var(name, /*exp:*/ exp, /*lvl:*/ lvl, /*ro:*/ 0);
|
||||||
|
} while (*++argv);
|
||||||
|
}
|
||||||
|
|
||||||
static int builtin_export(char **argv)
|
static int builtin_export(char **argv)
|
||||||
{
|
{
|
||||||
unsigned opt_unexport;
|
unsigned opt_unexport;
|
||||||
@ -6625,50 +6756,23 @@ static int builtin_export(char **argv)
|
|||||||
argv++;
|
argv++;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
do {
|
helper_export_local(argv, (opt_unexport ? -1 : 1), 0);
|
||||||
char *name = *argv;
|
|
||||||
|
|
||||||
/* So far we do not check that name is valid (TODO?) */
|
|
||||||
|
|
||||||
if (strchr(name, '=') == NULL) {
|
|
||||||
struct variable *var;
|
|
||||||
|
|
||||||
var = get_local_var(name);
|
|
||||||
if (opt_unexport) {
|
|
||||||
/* export -n NAME (without =VALUE) */
|
|
||||||
if (var) {
|
|
||||||
var->flg_export = 0;
|
|
||||||
debug_printf_env("%s: unsetenv '%s'\n", __func__, name);
|
|
||||||
unsetenv(name);
|
|
||||||
} /* else: export -n NOT_EXISTING_VAR: no-op */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
/* export NAME (without =VALUE) */
|
|
||||||
if (var) {
|
|
||||||
var->flg_export = 1;
|
|
||||||
debug_printf_env("%s: putenv '%s'\n", __func__, var->varstr);
|
|
||||||
putenv(var->varstr);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
/* Exporting non-existing variable.
|
|
||||||
* bash does not put it in environment,
|
|
||||||
* but remembers that it is exported,
|
|
||||||
* and does put it in env when it is set later.
|
|
||||||
* We just set it to "" and export. */
|
|
||||||
name = xasprintf("%s=", name);
|
|
||||||
} else {
|
|
||||||
/* (Un)exporting NAME=VALUE */
|
|
||||||
name = xstrdup(name);
|
|
||||||
}
|
|
||||||
set_local_var(name,
|
|
||||||
/*export:*/ (opt_unexport ? -1 : 1),
|
|
||||||
/*readonly:*/ 0
|
|
||||||
);
|
|
||||||
} while (*++argv);
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ENABLE_HUSH_LOCAL
|
||||||
|
static int builtin_local(char **argv)
|
||||||
|
{
|
||||||
|
if (G.func_nest_level == 0) {
|
||||||
|
bb_error_msg("%s: not in a function", argv[0]);
|
||||||
|
return EXIT_FAILURE; /* bash compat */
|
||||||
|
}
|
||||||
|
helper_export_local(argv, 0, G.func_nest_level);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static int builtin_trap(char **argv)
|
static int builtin_trap(char **argv)
|
||||||
{
|
{
|
||||||
int sig;
|
int sig;
|
||||||
@ -6944,7 +7048,7 @@ static int builtin_read(char **argv)
|
|||||||
//TODO: bash unbackslashes input, splits words and puts them in argv[i]
|
//TODO: bash unbackslashes input, splits words and puts them in argv[i]
|
||||||
|
|
||||||
string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL);
|
string = xmalloc_reads(STDIN_FILENO, xasprintf("%s=", name), NULL);
|
||||||
return set_local_var(string, 0, 0);
|
return set_local_var(string, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set
|
/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#set
|
||||||
|
3
shell/hush_test/hush-misc/func_local1.right
Normal file
3
shell/hush_test/hush-misc/func_local1.right
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
z=a
|
||||||
|
z=z
|
||||||
|
Done
|
5
shell/hush_test/hush-misc/func_local1.tests
Executable file
5
shell/hush_test/hush-misc/func_local1.tests
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
export z=z
|
||||||
|
f() { local z=a; env | grep ^z; }
|
||||||
|
f
|
||||||
|
env | grep ^z
|
||||||
|
echo Done
|
14
shell/hush_test/hush-misc/func_local2.right
Normal file
14
shell/hush_test/hush-misc/func_local2.right
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
1
|
||||||
|
2
|
||||||
|
1
|
||||||
|
2
|
||||||
|
1
|
||||||
|
1
|
||||||
|
2
|
||||||
|
2
|
||||||
|
3
|
||||||
|
2
|
||||||
|
2
|
||||||
|
3
|
||||||
|
1
|
||||||
|
Done
|
7
shell/hush_test/hush-misc/func_local2.tests
Executable file
7
shell/hush_test/hush-misc/func_local2.tests
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
x=1
|
||||||
|
f() { echo $x; local x=$((x+1)); echo $x; }
|
||||||
|
g() { f; echo $x; f; local x=$((x+1)); f; echo $x; f; }
|
||||||
|
f
|
||||||
|
g
|
||||||
|
echo $x
|
||||||
|
echo Done
|
Loading…
Reference in New Issue
Block a user