hush: move variable expansion into a separate function. No logic changes
function old new delta expand_one_var - 1551 +1551 expand_vars_to_list 2833 1175 -1658 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/1 up/down: 1551/-1658) Total: -107 bytes Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com>
This commit is contained in:
parent
36f774a0cd
commit
f2dc20c2d5
513
shell/hush.c
513
shell/hush.c
@ -2664,6 +2664,263 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Helper:
|
||||||
|
* Handles <SPECIAL_VAR_SYMBOL>varname...<SPECIAL_VAR_SYMBOL> construct.
|
||||||
|
*/
|
||||||
|
static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, char **pp, char first_ch)
|
||||||
|
{
|
||||||
|
const char *val = NULL;
|
||||||
|
char *to_be_freed = NULL;
|
||||||
|
char *p = *pp;
|
||||||
|
char *var;
|
||||||
|
char first_char;
|
||||||
|
char exp_op;
|
||||||
|
char exp_save = exp_save; /* for compiler */
|
||||||
|
char *exp_saveptr; /* points to expansion operator */
|
||||||
|
char *exp_word = exp_word; /* for compiler */
|
||||||
|
|
||||||
|
var = arg;
|
||||||
|
*p = '\0';
|
||||||
|
exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL;
|
||||||
|
first_char = arg[0] = first_ch & 0x7f;
|
||||||
|
exp_op = 0;
|
||||||
|
|
||||||
|
if (first_char == '#' && arg[1] && !exp_saveptr) {
|
||||||
|
/* handle length expansion ${#var} */
|
||||||
|
var++;
|
||||||
|
exp_op = 'L';
|
||||||
|
} else {
|
||||||
|
/* maybe handle parameter expansion */
|
||||||
|
if (exp_saveptr /* if 2nd char is one of expansion operators */
|
||||||
|
&& strchr(NUMERIC_SPECVARS_STR, first_char) /* 1st char is special variable */
|
||||||
|
) {
|
||||||
|
/* ${?:0}, ${#[:]%0} etc */
|
||||||
|
exp_saveptr = var + 1;
|
||||||
|
} else {
|
||||||
|
/* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */
|
||||||
|
exp_saveptr = var+1 + strcspn(var+1, VAR_ENCODED_SUBST_OPS);
|
||||||
|
}
|
||||||
|
exp_op = exp_save = *exp_saveptr;
|
||||||
|
if (exp_op) {
|
||||||
|
exp_word = exp_saveptr + 1;
|
||||||
|
if (exp_op == ':') {
|
||||||
|
exp_op = *exp_word++;
|
||||||
|
if (ENABLE_HUSH_BASH_COMPAT
|
||||||
|
&& (exp_op == '\0' || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op))
|
||||||
|
) {
|
||||||
|
/* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */
|
||||||
|
exp_op = ':';
|
||||||
|
exp_word--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*exp_saveptr = '\0';
|
||||||
|
} /* else: it's not an expansion op, but bare ${var} */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lookup the variable in question */
|
||||||
|
if (isdigit(var[0])) {
|
||||||
|
/* parse_dollar() should have vetted var for us */
|
||||||
|
int n = xatoi_positive(var);
|
||||||
|
if (n < G.global_argc)
|
||||||
|
val = G.global_argv[n];
|
||||||
|
/* else val remains NULL: $N with too big N */
|
||||||
|
} else {
|
||||||
|
switch (var[0]) {
|
||||||
|
case '$': /* pid */
|
||||||
|
val = utoa(G.root_pid);
|
||||||
|
break;
|
||||||
|
case '!': /* bg pid */
|
||||||
|
val = G.last_bg_pid ? utoa(G.last_bg_pid) : "";
|
||||||
|
break;
|
||||||
|
case '?': /* exitcode */
|
||||||
|
val = utoa(G.last_exitcode);
|
||||||
|
break;
|
||||||
|
case '#': /* argc */
|
||||||
|
val = utoa(G.global_argc ? G.global_argc-1 : 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
val = get_local_var_value(var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle any expansions */
|
||||||
|
if (exp_op == 'L') {
|
||||||
|
debug_printf_expand("expand: length(%s)=", val);
|
||||||
|
val = utoa(val ? strlen(val) : 0);
|
||||||
|
debug_printf_expand("%s\n", val);
|
||||||
|
} else if (exp_op) {
|
||||||
|
if (exp_op == '%' || exp_op == '#') {
|
||||||
|
/* Standard-mandated substring removal ops:
|
||||||
|
* ${parameter%word} - remove smallest suffix pattern
|
||||||
|
* ${parameter%%word} - remove largest suffix pattern
|
||||||
|
* ${parameter#word} - remove smallest prefix pattern
|
||||||
|
* ${parameter##word} - remove largest prefix pattern
|
||||||
|
*
|
||||||
|
* Word is expanded to produce a glob pattern.
|
||||||
|
* Then var's value is matched to it and matching part removed.
|
||||||
|
*/
|
||||||
|
if (val && val[0]) {
|
||||||
|
char *exp_exp_word;
|
||||||
|
char *loc;
|
||||||
|
unsigned scan_flags = pick_scan(exp_op, *exp_word);
|
||||||
|
if (exp_op == *exp_word) /* ## or %% */
|
||||||
|
exp_word++;
|
||||||
|
//TODO: avoid xstrdup unless needed
|
||||||
|
// (see HACK ALERT below)
|
||||||
|
val = to_be_freed = xstrdup(val);
|
||||||
|
exp_exp_word = expand_pseudo_dquoted(exp_word);
|
||||||
|
if (exp_exp_word)
|
||||||
|
exp_word = exp_exp_word;
|
||||||
|
loc = scan_and_match(to_be_freed, exp_word, scan_flags);
|
||||||
|
//bb_error_msg("op:%c str:'%s' pat:'%s' res:'%s'",
|
||||||
|
// exp_op, to_be_freed, exp_word, loc);
|
||||||
|
free(exp_exp_word);
|
||||||
|
if (loc) { /* match was found */
|
||||||
|
if (scan_flags & SCAN_MATCH_LEFT_HALF) /* #[#] */
|
||||||
|
val = loc;
|
||||||
|
else /* %[%] */
|
||||||
|
*loc = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if ENABLE_HUSH_BASH_COMPAT
|
||||||
|
else if (exp_op == '/' || exp_op == '\\') {
|
||||||
|
/* Empty variable always gives nothing: */
|
||||||
|
// "v=''; echo ${v/*/w}" prints ""
|
||||||
|
if (val && val[0]) {
|
||||||
|
/* It's ${var/[/]pattern[/repl]} thing */
|
||||||
|
char *pattern, *repl, *t;
|
||||||
|
pattern = expand_pseudo_dquoted(exp_word);
|
||||||
|
if (!pattern)
|
||||||
|
pattern = xstrdup(exp_word);
|
||||||
|
debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern);
|
||||||
|
*p++ = SPECIAL_VAR_SYMBOL;
|
||||||
|
exp_word = p;
|
||||||
|
p = strchr(p, SPECIAL_VAR_SYMBOL);
|
||||||
|
*p = '\0';
|
||||||
|
repl = expand_pseudo_dquoted(exp_word);
|
||||||
|
debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl);
|
||||||
|
/* HACK ALERT. We depend here on the fact that
|
||||||
|
* G.global_argv and results of utoa and get_local_var_value
|
||||||
|
* are actually in writable memory:
|
||||||
|
* replace_pattern momentarily stores NULs there. */
|
||||||
|
t = (char*)val;
|
||||||
|
to_be_freed = replace_pattern(t,
|
||||||
|
pattern,
|
||||||
|
(repl ? repl : exp_word),
|
||||||
|
exp_op);
|
||||||
|
if (to_be_freed) /* at least one replace happened */
|
||||||
|
val = to_be_freed;
|
||||||
|
free(pattern);
|
||||||
|
free(repl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else if (exp_op == ':') {
|
||||||
|
#if ENABLE_HUSH_BASH_COMPAT && ENABLE_SH_MATH_SUPPORT
|
||||||
|
/* It's ${var:N[:M]} bashism.
|
||||||
|
* Note that in encoded form it has TWO parts:
|
||||||
|
* var:N<SPECIAL_VAR_SYMBOL>M<SPECIAL_VAR_SYMBOL>
|
||||||
|
*/
|
||||||
|
arith_t beg, len;
|
||||||
|
int errcode = 0;
|
||||||
|
|
||||||
|
beg = expand_and_evaluate_arith(exp_word, &errcode);
|
||||||
|
debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg);
|
||||||
|
*p++ = SPECIAL_VAR_SYMBOL;
|
||||||
|
exp_word = p;
|
||||||
|
p = strchr(p, SPECIAL_VAR_SYMBOL);
|
||||||
|
*p = '\0';
|
||||||
|
len = expand_and_evaluate_arith(exp_word, &errcode);
|
||||||
|
debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
|
||||||
|
|
||||||
|
if (errcode >= 0 && len >= 0) { /* bash compat: len < 0 is illegal */
|
||||||
|
if (beg < 0) /* bash compat */
|
||||||
|
beg = 0;
|
||||||
|
debug_printf_varexp("from val:'%s'\n", val);
|
||||||
|
if (len == 0 || !val || beg >= strlen(val))
|
||||||
|
val = "";
|
||||||
|
else {
|
||||||
|
/* Paranoia. What if user entered 9999999999999
|
||||||
|
* which fits in arith_t but not int? */
|
||||||
|
if (len >= INT_MAX)
|
||||||
|
len = INT_MAX;
|
||||||
|
val = to_be_freed = xstrndup(val + beg, len);
|
||||||
|
}
|
||||||
|
debug_printf_varexp("val:'%s'\n", val);
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
die_if_script("malformed ${%s:...}", var);
|
||||||
|
val = "";
|
||||||
|
}
|
||||||
|
} else { /* one of "-=+?" */
|
||||||
|
/* Standard-mandated substitution ops:
|
||||||
|
* ${var?word} - indicate error if unset
|
||||||
|
* If var is unset, word (or a message indicating it is unset
|
||||||
|
* if word is null) is written to standard error
|
||||||
|
* and the shell exits with a non-zero exit status.
|
||||||
|
* Otherwise, the value of var is substituted.
|
||||||
|
* ${var-word} - use default value
|
||||||
|
* If var is unset, word is substituted.
|
||||||
|
* ${var=word} - assign and use default value
|
||||||
|
* If var is unset, word is assigned to var.
|
||||||
|
* In all cases, final value of var is substituted.
|
||||||
|
* ${var+word} - use alternative value
|
||||||
|
* If var is unset, null is substituted.
|
||||||
|
* Otherwise, word is substituted.
|
||||||
|
*
|
||||||
|
* Word is subjected to tilde expansion, parameter expansion,
|
||||||
|
* command substitution, and arithmetic expansion.
|
||||||
|
* If word is not needed, it is not expanded.
|
||||||
|
*
|
||||||
|
* Colon forms (${var:-word}, ${var:=word} etc) do the same,
|
||||||
|
* but also treat null var as if it is unset.
|
||||||
|
*/
|
||||||
|
int use_word = (!val || ((exp_save == ':') && !val[0]));
|
||||||
|
if (exp_op == '+')
|
||||||
|
use_word = !use_word;
|
||||||
|
debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
|
||||||
|
(exp_save == ':') ? "true" : "false", use_word);
|
||||||
|
if (use_word) {
|
||||||
|
to_be_freed = expand_pseudo_dquoted(exp_word);
|
||||||
|
if (to_be_freed)
|
||||||
|
exp_word = to_be_freed;
|
||||||
|
if (exp_op == '?') {
|
||||||
|
/* mimic bash message */
|
||||||
|
die_if_script("%s: %s",
|
||||||
|
var,
|
||||||
|
exp_word[0] ? exp_word : "parameter null or not set"
|
||||||
|
);
|
||||||
|
//TODO: how interactive bash aborts expansion mid-command?
|
||||||
|
} else {
|
||||||
|
val = exp_word;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exp_op == '=') {
|
||||||
|
/* ${var=[word]} or ${var:=[word]} */
|
||||||
|
if (isdigit(var[0]) || var[0] == '#') {
|
||||||
|
/* mimic bash message */
|
||||||
|
die_if_script("$%s: cannot assign in this way", var);
|
||||||
|
val = NULL;
|
||||||
|
} else {
|
||||||
|
char *new_var = xasprintf("%s=%s", var, val);
|
||||||
|
set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} /* one of "-=+?" */
|
||||||
|
|
||||||
|
*exp_saveptr = exp_save;
|
||||||
|
} /* if (exp_op) */
|
||||||
|
|
||||||
|
arg[0] = first_ch;
|
||||||
|
|
||||||
|
*pp = p;
|
||||||
|
*to_be_freed_pp = to_be_freed;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
/* Expand all variable references in given string, adding words to list[]
|
/* Expand all variable references in given string, adding words to list[]
|
||||||
* at n, n+1,... positions. Return updated n (so that list[n] is next one
|
* at n, n+1,... positions. Return updated n (so that list[n] is next one
|
||||||
* to be filled). This routine is extremely tricky: has to deal with
|
* to be filled). This routine is extremely tricky: has to deal with
|
||||||
@ -2803,255 +3060,12 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
default: { /* <SPECIAL_VAR_SYMBOL>varname<SPECIAL_VAR_SYMBOL> */
|
default:
|
||||||
//TODO: move to a subroutine?
|
val = expand_one_var(&to_be_freed, arg, &p, first_ch);
|
||||||
char *var;
|
IF_HUSH_TICK(store_val:)
|
||||||
char first_char;
|
|
||||||
char exp_op;
|
|
||||||
char exp_save = exp_save; /* for compiler */
|
|
||||||
char *exp_saveptr; /* points to expansion operator */
|
|
||||||
char *exp_word = exp_word; /* for compiler */
|
|
||||||
|
|
||||||
var = arg;
|
|
||||||
*p = '\0';
|
|
||||||
exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL;
|
|
||||||
first_char = arg[0] = first_ch & 0x7f;
|
|
||||||
exp_op = 0;
|
|
||||||
|
|
||||||
if (first_char == '#' && arg[1] && !exp_saveptr) {
|
|
||||||
/* handle length expansion ${#var} */
|
|
||||||
var++;
|
|
||||||
exp_op = 'L';
|
|
||||||
} else {
|
|
||||||
/* maybe handle parameter expansion */
|
|
||||||
if (exp_saveptr /* if 2nd char is one of expansion operators */
|
|
||||||
&& strchr(NUMERIC_SPECVARS_STR, first_char) /* 1st char is special variable */
|
|
||||||
) {
|
|
||||||
/* ${?:0}, ${#[:]%0} etc */
|
|
||||||
exp_saveptr = var + 1;
|
|
||||||
} else {
|
|
||||||
/* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */
|
|
||||||
exp_saveptr = var+1 + strcspn(var+1, VAR_ENCODED_SUBST_OPS);
|
|
||||||
}
|
|
||||||
exp_op = exp_save = *exp_saveptr;
|
|
||||||
if (exp_op) {
|
|
||||||
exp_word = exp_saveptr + 1;
|
|
||||||
if (exp_op == ':') {
|
|
||||||
exp_op = *exp_word++;
|
|
||||||
if (ENABLE_HUSH_BASH_COMPAT
|
|
||||||
&& (exp_op == '\0' || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op))
|
|
||||||
) {
|
|
||||||
/* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */
|
|
||||||
exp_op = ':';
|
|
||||||
exp_word--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*exp_saveptr = '\0';
|
|
||||||
} /* else: it's not an expansion op, but bare ${var} */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* lookup the variable in question */
|
|
||||||
if (isdigit(var[0])) {
|
|
||||||
/* parse_dollar() should have vetted var for us */
|
|
||||||
i = xatoi_positive(var);
|
|
||||||
if (i < G.global_argc)
|
|
||||||
val = G.global_argv[i];
|
|
||||||
/* else val remains NULL: $N with too big N */
|
|
||||||
} else {
|
|
||||||
switch (var[0]) {
|
|
||||||
case '$': /* pid */
|
|
||||||
val = utoa(G.root_pid);
|
|
||||||
break;
|
|
||||||
case '!': /* bg pid */
|
|
||||||
val = G.last_bg_pid ? utoa(G.last_bg_pid) : "";
|
|
||||||
break;
|
|
||||||
case '?': /* exitcode */
|
|
||||||
val = utoa(G.last_exitcode);
|
|
||||||
break;
|
|
||||||
case '#': /* argc */
|
|
||||||
val = utoa(G.global_argc ? G.global_argc-1 : 0);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
val = get_local_var_value(var);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* handle any expansions */
|
|
||||||
if (exp_op == 'L') {
|
|
||||||
debug_printf_expand("expand: length(%s)=", val);
|
|
||||||
val = utoa(val ? strlen(val) : 0);
|
|
||||||
debug_printf_expand("%s\n", val);
|
|
||||||
} else if (exp_op) {
|
|
||||||
if (exp_op == '%' || exp_op == '#') {
|
|
||||||
/* Standard-mandated substring removal ops:
|
|
||||||
* ${parameter%word} - remove smallest suffix pattern
|
|
||||||
* ${parameter%%word} - remove largest suffix pattern
|
|
||||||
* ${parameter#word} - remove smallest prefix pattern
|
|
||||||
* ${parameter##word} - remove largest prefix pattern
|
|
||||||
*
|
|
||||||
* Word is expanded to produce a glob pattern.
|
|
||||||
* Then var's value is matched to it and matching part removed.
|
|
||||||
*/
|
|
||||||
if (val) {
|
|
||||||
char *exp_exp_word;
|
|
||||||
char *loc;
|
|
||||||
unsigned scan_flags = pick_scan(exp_op, *exp_word);
|
|
||||||
if (exp_op == *exp_word) /* ## or %% */
|
|
||||||
exp_word++;
|
|
||||||
val = to_be_freed = xstrdup(val);
|
|
||||||
exp_exp_word = expand_pseudo_dquoted(exp_word);
|
|
||||||
if (exp_exp_word)
|
|
||||||
exp_word = exp_exp_word;
|
|
||||||
loc = scan_and_match(to_be_freed, exp_word, scan_flags);
|
|
||||||
//bb_error_msg("op:%c str:'%s' pat:'%s' res:'%s'",
|
|
||||||
// exp_op, to_be_freed, exp_word, loc);
|
|
||||||
free(exp_exp_word);
|
|
||||||
if (loc) { /* match was found */
|
|
||||||
if (scan_flags & SCAN_MATCH_LEFT_HALF) /* #[#] */
|
|
||||||
val = loc;
|
|
||||||
else /* %[%] */
|
|
||||||
*loc = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if ENABLE_HUSH_BASH_COMPAT
|
|
||||||
else if (exp_op == '/' || exp_op == '\\') {
|
|
||||||
/* Empty variable always gives nothing: */
|
|
||||||
// "v=''; echo ${v/*/w}" prints ""
|
|
||||||
if (val && val[0]) {
|
|
||||||
/* It's ${var/[/]pattern[/repl]} thing */
|
|
||||||
char *pattern, *repl, *t;
|
|
||||||
pattern = expand_pseudo_dquoted(exp_word);
|
|
||||||
if (!pattern)
|
|
||||||
pattern = xstrdup(exp_word);
|
|
||||||
debug_printf_varexp("pattern:'%s'->'%s'\n", exp_word, pattern);
|
|
||||||
*p++ = SPECIAL_VAR_SYMBOL;
|
|
||||||
exp_word = p;
|
|
||||||
p = strchr(p, SPECIAL_VAR_SYMBOL);
|
|
||||||
*p = '\0';
|
|
||||||
repl = expand_pseudo_dquoted(exp_word);
|
|
||||||
debug_printf_varexp("repl:'%s'->'%s'\n", exp_word, repl);
|
|
||||||
/* HACK ALERT. We depend here on the fact that
|
|
||||||
* G.global_argv and results of utoa and get_local_var_value
|
|
||||||
* are actually in writable memory:
|
|
||||||
* replace_pattern momentarily stores NULs there. */
|
|
||||||
t = (char*)val;
|
|
||||||
to_be_freed = replace_pattern(t,
|
|
||||||
pattern,
|
|
||||||
(repl ? repl : exp_word),
|
|
||||||
exp_op);
|
|
||||||
if (to_be_freed) /* at least one replace happened */
|
|
||||||
val = to_be_freed;
|
|
||||||
free(pattern);
|
|
||||||
free(repl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
else if (exp_op == ':') {
|
|
||||||
#if ENABLE_HUSH_BASH_COMPAT && ENABLE_SH_MATH_SUPPORT
|
|
||||||
/* It's ${var:N[:M]} bashism.
|
|
||||||
* Note that in encoded form it has TWO parts:
|
|
||||||
* var:N<SPECIAL_VAR_SYMBOL>M<SPECIAL_VAR_SYMBOL>
|
|
||||||
*/
|
|
||||||
arith_t beg, len;
|
|
||||||
int errcode = 0;
|
|
||||||
|
|
||||||
beg = expand_and_evaluate_arith(exp_word, &errcode);
|
|
||||||
debug_printf_varexp("beg:'%s'=%lld\n", exp_word, (long long)beg);
|
|
||||||
*p++ = SPECIAL_VAR_SYMBOL;
|
|
||||||
exp_word = p;
|
|
||||||
p = strchr(p, SPECIAL_VAR_SYMBOL);
|
|
||||||
*p = '\0';
|
|
||||||
len = expand_and_evaluate_arith(exp_word, &errcode);
|
|
||||||
debug_printf_varexp("len:'%s'=%lld\n", exp_word, (long long)len);
|
|
||||||
|
|
||||||
if (errcode >= 0 && len >= 0) { /* bash compat: len < 0 is illegal */
|
|
||||||
if (beg < 0) /* bash compat */
|
|
||||||
beg = 0;
|
|
||||||
debug_printf_varexp("from val:'%s'\n", val);
|
|
||||||
if (len == 0 || !val || beg >= strlen(val))
|
|
||||||
val = "";
|
|
||||||
else {
|
|
||||||
/* Paranoia. What if user entered 9999999999999
|
|
||||||
* which fits in arith_t but not int? */
|
|
||||||
if (len >= INT_MAX)
|
|
||||||
len = INT_MAX;
|
|
||||||
val = to_be_freed = xstrndup(val + beg, len);
|
|
||||||
}
|
|
||||||
debug_printf_varexp("val:'%s'\n", val);
|
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
die_if_script("malformed ${%s:...}", var);
|
|
||||||
val = "";
|
|
||||||
}
|
|
||||||
} else { /* one of "-=+?" */
|
|
||||||
/* Standard-mandated substitution ops:
|
|
||||||
* ${var?word} - indicate error if unset
|
|
||||||
* If var is unset, word (or a message indicating it is unset
|
|
||||||
* if word is null) is written to standard error
|
|
||||||
* and the shell exits with a non-zero exit status.
|
|
||||||
* Otherwise, the value of var is substituted.
|
|
||||||
* ${var-word} - use default value
|
|
||||||
* If var is unset, word is substituted.
|
|
||||||
* ${var=word} - assign and use default value
|
|
||||||
* If var is unset, word is assigned to var.
|
|
||||||
* In all cases, final value of var is substituted.
|
|
||||||
* ${var+word} - use alternative value
|
|
||||||
* If var is unset, null is substituted.
|
|
||||||
* Otherwise, word is substituted.
|
|
||||||
*
|
|
||||||
* Word is subjected to tilde expansion, parameter expansion,
|
|
||||||
* command substitution, and arithmetic expansion.
|
|
||||||
* If word is not needed, it is not expanded.
|
|
||||||
*
|
|
||||||
* Colon forms (${var:-word}, ${var:=word} etc) do the same,
|
|
||||||
* but also treat null var as if it is unset.
|
|
||||||
*/
|
|
||||||
int use_word = (!val || ((exp_save == ':') && !val[0]));
|
|
||||||
if (exp_op == '+')
|
|
||||||
use_word = !use_word;
|
|
||||||
debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
|
|
||||||
(exp_save == ':') ? "true" : "false", use_word);
|
|
||||||
if (use_word) {
|
|
||||||
to_be_freed = expand_pseudo_dquoted(exp_word);
|
|
||||||
if (to_be_freed)
|
|
||||||
exp_word = to_be_freed;
|
|
||||||
if (exp_op == '?') {
|
|
||||||
/* mimic bash message */
|
|
||||||
die_if_script("%s: %s",
|
|
||||||
var,
|
|
||||||
exp_word[0] ? exp_word : "parameter null or not set"
|
|
||||||
);
|
|
||||||
//TODO: how interactive bash aborts expansion mid-command?
|
|
||||||
} else {
|
|
||||||
val = exp_word;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exp_op == '=') {
|
|
||||||
/* ${var=[word]} or ${var:=[word]} */
|
|
||||||
if (isdigit(var[0]) || var[0] == '#') {
|
|
||||||
/* mimic bash message */
|
|
||||||
die_if_script("$%s: cannot assign in this way", var);
|
|
||||||
val = NULL;
|
|
||||||
} else {
|
|
||||||
char *new_var = xasprintf("%s=%s", var, val);
|
|
||||||
set_local_var(new_var, /*exp:*/ 0, /*lvl:*/ 0, /*ro:*/ 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} /* one of "-=+?" */
|
|
||||||
|
|
||||||
*exp_saveptr = exp_save;
|
|
||||||
} /* if (exp_op) */
|
|
||||||
|
|
||||||
arg[0] = first_ch;
|
|
||||||
#if ENABLE_HUSH_TICK
|
|
||||||
store_val:
|
|
||||||
#endif
|
|
||||||
if (!(first_ch & 0x80)) { /* unquoted $VAR */
|
if (!(first_ch & 0x80)) { /* unquoted $VAR */
|
||||||
debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, output->o_escape);
|
debug_printf_expand("unquoted '%s', output->o_escape:%d\n", val, output->o_escape);
|
||||||
if (val) {
|
if (val && val[0]) {
|
||||||
/* unquoted var's contents should be globbed, so don't escape */
|
/* unquoted var's contents should be globbed, so don't escape */
|
||||||
smallint sv = output->o_escape;
|
smallint sv = output->o_escape;
|
||||||
output->o_escape = 0;
|
output->o_escape = 0;
|
||||||
@ -3062,10 +3076,11 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
|
|||||||
} else { /* quoted $VAR, val will be appended below */
|
} else { /* quoted $VAR, val will be appended below */
|
||||||
debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, output->o_escape);
|
debug_printf_expand("quoted '%s', output->o_escape:%d\n", val, output->o_escape);
|
||||||
}
|
}
|
||||||
} /* default: */
|
break;
|
||||||
|
|
||||||
} /* switch (char after <SPECIAL_VAR_SYMBOL>) */
|
} /* switch (char after <SPECIAL_VAR_SYMBOL>) */
|
||||||
|
|
||||||
if (val) {
|
if (val && val[0]) {
|
||||||
o_addQstr(output, val, strlen(val));
|
o_addQstr(output, val, strlen(val));
|
||||||
}
|
}
|
||||||
free(to_be_freed);
|
free(to_be_freed);
|
||||||
|
Loading…
Reference in New Issue
Block a user