hush: add support for ${var/pattern/repl}, conditional on bash compat

function                                             old     new   delta
expand_vars_to_list                                 2386    2833    +447
expand_string_to_string                               69     110     +41
parse_dollar                                         681     721     +40
hush_main                                            963     945     -18
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 3/1 up/down: 528/-18)           Total: 510 bytes

Signed-off-by: Denys Vlasenko <dvlasenk@redhat.com>
This commit is contained in:
Denys Vlasenko
2010-09-05 14:45:38 +02:00
parent 701e127f7d
commit 36f774a0cd
12 changed files with 362 additions and 36 deletions

View File

@@ -50,7 +50,6 @@
*
* Bash compat TODO:
* redirection of stdout+stderr: &> and >&
* subst operator: ${var/[/]expr/expr}
* brace expansion: one/{two,three,four}
* reserved words: function select
* advanced test: [[ ]]
@@ -330,6 +329,17 @@
#define _SPECIAL_VARS_STR "_*@$!?#"
#define SPECIAL_VARS_STR ("_*@$!?#" + 1)
#define NUMERIC_SPECVARS_STR ("_*@$!?#" + 3)
#if ENABLE_HUSH_BASH_COMPAT
/* Support / and // replace ops */
/* Note that // is stored as \ in "encoded" string representation */
# define VAR_ENCODED_SUBST_OPS "\\/%#:-=+?"
# define VAR_SUBST_OPS ("\\/%#:-=+?" + 1)
# define MINUS_PLUS_EQUAL_QUESTION ("\\/%#:-=+?" + 5)
#else
# define VAR_ENCODED_SUBST_OPS "%#:-=+?"
# define VAR_SUBST_OPS "%#:-=+?"
# define MINUS_PLUS_EQUAL_QUESTION ("%#:-=+?" + 3)
#endif
#define SPECIAL_VAR_SYMBOL 3
@@ -2600,6 +2610,60 @@ static arith_t expand_and_evaluate_arith(const char *arg, int *errcode_p)
}
#endif
#if ENABLE_HUSH_BASH_COMPAT
/* ${var/[/]pattern[/repl]} helpers */
static char *strstr_pattern(char *val, const char *pattern, int *size)
{
while (1) {
char *end = scan_and_match(val, pattern, SCAN_MOVE_FROM_RIGHT + SCAN_MATCH_LEFT_HALF);
debug_printf_varexp("val:'%s' pattern:'%s' end:'%s'\n", val, pattern, end);
if (end) {
*size = end - val;
return val;
}
if (*val == '\0')
return NULL;
/* Optimization: if "*pat" did not match the start of "string",
* we know that "tring", "ring" etc will not match too:
*/
if (pattern[0] == '*')
return NULL;
val++;
}
}
static char *replace_pattern(char *val, const char *pattern, const char *repl, char exp_op)
{
char *result = NULL;
unsigned res_len = 0;
unsigned repl_len = strlen(repl);
while (1) {
int size;
char *s = strstr_pattern(val, pattern, &size);
if (!s)
break;
result = xrealloc(result, res_len + (s - val) + repl_len + 1);
memcpy(result + res_len, val, s - val);
res_len += s - val;
strcpy(result + res_len, repl);
res_len += repl_len;
debug_printf_varexp("val:'%s' s:'%s' result:'%s'\n", val, s, result);
val = s + size;
if (exp_op == '/')
break;
}
if (val[0] && result) {
result = xrealloc(result, res_len + strlen(val) + 1);
strcpy(result + res_len, val);
debug_printf_varexp("val:'%s' result:'%s'\n", val, result);
}
debug_printf_varexp("result:'%s'\n", result);
return result;
}
#endif
/* 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
* to be filled). This routine is extremely tricky: has to deal with
@@ -2750,7 +2814,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
var = arg;
*p = '\0';
exp_saveptr = arg[1] ? strchr("%#:-=+?", arg[1]) : NULL;
exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL;
first_char = arg[0] = first_ch & 0x7f;
exp_op = 0;
@@ -2767,7 +2831,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
exp_saveptr = var + 1;
} else {
/* ${?}, ${var}, ${var:0}, ${var[:]%0} etc */
exp_saveptr = var+1 + strcspn(var+1, "%#:-=+?");
exp_saveptr = var+1 + strcspn(var+1, VAR_ENCODED_SUBST_OPS);
}
exp_op = exp_save = *exp_saveptr;
if (exp_op) {
@@ -2775,7 +2839,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
if (exp_op == ':') {
exp_op = *exp_word++;
if (ENABLE_HUSH_BASH_COMPAT
&& (exp_op == '\0' || !strchr("%#:-=+?"+3, exp_op))
&& (exp_op == '\0' || !strchr(MINUS_PLUS_EQUAL_QUESTION, exp_op))
) {
/* oops... it's ${var:N[:M]}, not ${var:?xxx} or some such */
exp_op = ':';
@@ -2799,7 +2863,7 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
val = utoa(G.root_pid);
break;
case '!': /* bg pid */
val = G.last_bg_pid ? utoa(G.last_bg_pid) : (char*)"";
val = G.last_bg_pid ? utoa(G.last_bg_pid) : "";
break;
case '?': /* exitcode */
val = utoa(G.last_exitcode);
@@ -2843,13 +2907,47 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg, char
// exp_op, to_be_freed, exp_word, loc);
free(exp_exp_word);
if (loc) { /* match was found */
if (scan_flags & SCAN_MATCH_LEFT_HALF) /* # or ## */
if (scan_flags & SCAN_MATCH_LEFT_HALF) /* #[#] */
val = loc;
else /* % or %% */
else /* %[%] */
*loc = '\0';
}
}
} else if (exp_op == ':') {
}
#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:
@@ -3084,6 +3182,16 @@ static char *expand_string_to_string(const char *str)
{
char *argv[2], **list;
/* This is generally an optimization, but it also
* handles "", which otherwise trips over !list[0] check below.
* (is this ever happens that we actually get str="" here?)
*/
if (!strchr(str, SPECIAL_VAR_SYMBOL) && !strchr(str, '\\')) {
//TODO: Can use on strings with \ too, just unbackslash() them?
debug_printf_expand("string_to_string(fast)='%s'\n", str);
return xstrdup(str);
}
argv[0] = (char*)str;
argv[1] = NULL;
list = expand_variables(argv, EXPVAR_FLAG_ESCAPE_VARS | EXPVAR_FLAG_SINGLEWORD);
@@ -3271,7 +3379,7 @@ static void re_execute_shell(char ***to_free, const char *s,
*pp++ = (char *) G.argv0_for_re_execing;
*pp++ = param_buf;
for (cur = G.top_var; cur; cur = cur->next) {
if (cur->varstr == hush_version_str)
if (strcmp(cur->varstr, hush_version_str) == 0)
continue;
if (cur->flg_read_only) {
*pp++ = (char *) "-R";
@@ -6170,8 +6278,8 @@ static void add_till_backquote(o_string *dest, struct in_str *input)
*
* Also adapted to eat ${var%...} and $((...)) constructs, since ... part
* can contain arbitrary constructs, just like $(cmd).
* In bash compat mode, it needs to also be able to stop on '}' or ':'
* for ${var:N[:M]} parsing.
* In bash compat mode, it needs to also be able to stop on ':' or '/'
* for ${var:N[:M]} and ${var/P[/R]} parsing.
*/
#define DOUBLE_CLOSE_CHAR_FLAG 0x80
static int add_till_closing_bracket(o_string *dest, struct in_str *input, unsigned end_ch)
@@ -6323,19 +6431,30 @@ static int parse_dollar(o_string *as_string,
/* handle parameter expansions
* http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
*/
if (!strchr("%#:-=+?", ch)) /* ${var<bad_char>... */
if (!strchr(VAR_SUBST_OPS, ch)) /* ${var<bad_char>... */
goto bad_dollar_syntax;
o_addchr(dest, ch);
/* Eat everything until closing '}' (or ':') */
end_ch = '}';
if (ENABLE_HUSH_BASH_COMPAT
&& ch == ':'
&& !strchr("%#:-=+?"+3, i_peek(input))
&& !strchr(MINUS_PLUS_EQUAL_QUESTION, i_peek(input))
) {
/* It's ${var:N[:M]} thing */
end_ch = '}' * 0x100 + ':';
}
if (ENABLE_HUSH_BASH_COMPAT
&& ch == '/'
) {
/* It's ${var/[/]pattern[/repl]} thing */
if (i_peek(input) == '/') { /* ${var//pattern[/repl]}? */
i_getch(input);
nommu_addchr(as_string, '/');
ch = '\\';
}
end_ch = '}' * 0x100 + '/';
}
o_addchr(dest, ch);
again:
if (!BB_MMU)
pos = dest->length;
@@ -6352,14 +6471,18 @@ static int parse_dollar(o_string *as_string,
if (ENABLE_HUSH_BASH_COMPAT && (end_ch & 0xff00)) {
/* close the first block: */
o_addchr(dest, SPECIAL_VAR_SYMBOL);
/* while parsing N from ${var:N[:M]}... */
/* while parsing N from ${var:N[:M]}
* or pattern from ${var/[/]pattern[/repl]} */
if ((end_ch & 0xff) == last_ch) {
/* ...got ':' - parse the rest */
/* got ':' or '/'- parse the rest */
end_ch = '}';
goto again;
}
/* ...got '}', not ':' - it's ${var:N}! emulate :999999999 */
o_addstr(dest, "999999999");
/* got '}' */
if (end_ch == '}' * 0x100 + ':') {
/* it's ${var:N} - emulate :999999999 */
o_addstr(dest, "999999999");
} /* else: it's ${var/[/]pattern} */
}
break;
}
@@ -7186,13 +7309,6 @@ static int set_mode(const char cstate, const char mode)
int hush_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int hush_main(int argc, char **argv)
{
static const struct variable const_shell_ver = {
.next = NULL,
.varstr = (char*)hush_version_str,
.max_len = 1, /* 0 can provoke free(name) */
.flg_export = 1,
.flg_read_only = 1,
};
int opt;
unsigned builtin_argc;
char **e;
@@ -7205,10 +7321,18 @@ int hush_main(int argc, char **argv)
G.argv0_for_re_execing = argv[0];
#endif
/* Deal with HUSH_VERSION */
G.shell_ver = const_shell_ver; /* copying struct here */
G.shell_ver.flg_export = 1;
G.shell_ver.flg_read_only = 1;
/* Code which handles ${var/P/R} needs writable values for all variables,
* therefore we xstrdup: */
G.shell_ver.varstr = xstrdup(hush_version_str),
G.top_var = &G.shell_ver;
debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
/* reinstate HUSH_VERSION in environment */
debug_printf_env("putenv '%s'\n", G.shell_ver.varstr);
putenv(G.shell_ver.varstr);
/* Initialize our shell local variables with the values
* currently living in the environment */
cur_var = G.top_var;
@@ -7224,9 +7348,6 @@ int hush_main(int argc, char **argv)
}
e++;
}
/* reinstate HUSH_VERSION */
debug_printf_env("putenv '%s'\n", hush_version_str);
putenv((char *)hush_version_str);
/* Export PWD */
set_pwd_var(/*exp:*/ 1);