On Sun, Jul 08 2018, Anton Lindqvist <[email protected]> wrote:
> On Sat, Jul 07, 2018 at 10:06:41AM +0200, Andreas Kusalananda Kähäri wrote:
>> Hi,
>>
>> I'm not sure this is a bug or a misunderstanding by me, but it sure
>> surprised me:
>>
>> $ set -- file.txt txt
>>
>> $ printf 'Two values: %s %s\n' "$1" "$2"
>> Two values: file.txt txt
>>
>> $ printf 'First value with suffix removed: %s\n' "${1%.*}"
>> First value with suffix removed: file
>>
>> $ printf 'First value with second value removed from end: %s\n'
>> "${1%.$2}"
>> First value with second value removed from end: txt
>>
>> I would have expected the last two parameter expansion to generate the
>> same result ("file"). Also, removing the dot ("${1%$2}") generates an
>> empty result.
>>
>> Note that using variables, this works as expected:
>>
>> $ filename=file.txt
>> $ suffix=txt
>> $ printf 'First value with second value removed from end: %s\n'
>> "${filename%.$suffix}"
>> First value with second value removed from end: file
>>
>> This is on a current system using either /bin/sh or /bin/ksh and the
>> effect of trying to remove from the start of the string is similar.
>
> That's indeed a bug. Lookup of positional arguments ($1 and $2) is done
> using global() which in turn returns a pointer to static storage for
> read-only variables (positional arguments are read-only). So the second
> call to global() overwrites the previous one. Here's a first stab in
> which read-only variables are copied during expansion. I ended up adding
> a new function in order to avoid deep indenting. Also, zero st_head in
> order to simplify the free loop; otherwise st_head.var would contain
> stack garbage.
>
> Comments? OK?
s/has/have/ in the comment you're adding, I think, except for this
ok jca@ for the diff and regress parts
> Index: eval.c
> ===================================================================
> RCS file: /cvs/src/bin/ksh/eval.c,v
> retrieving revision 1.60
> diff -u -p -r1.60 eval.c
> --- eval.c 9 Apr 2018 17:53:36 -0000 1.60
> +++ eval.c 8 Jul 2018 08:17:05 -0000
> @@ -58,6 +58,8 @@ static char *tilde(char *);
> static char *homedir(char *);
> static void alt_expand(XPtrV *, char *, char *, char *, int);
>
> +static struct tbl *varcpy(struct tbl *);
> +
> /* compile and expand word */
> char *
> substitute(const char *cp, int f)
> @@ -190,7 +192,8 @@ expand(char *cp, /* input word */
> doblank = 0;
> make_magic = 0;
> word = (f&DOBLANK) ? IFS_WS : IFS_WORD;
> - st_head.next = NULL;
> +
> + memset(&st_head, 0, sizeof(st_head));
> st = &st_head;
>
> while (1) {
> @@ -305,7 +308,7 @@ expand(char *cp, /* input word */
> st->stype = stype;
> st->base = Xsavepos(ds, dp);
> st->f = f;
> - st->var = x.var;
> + st->var = varcpy(x.var);
> st->quote = quote;
> /* skip qualifier(s) */
> if (stype)
> @@ -577,7 +580,7 @@ expand(char *cp, /* input word */
> Xinit(ds, dp, 128, ATEMP);
> }
> if (c == 0)
> - return;
> + goto done;
> if (word != IFS_NWS)
> word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS;
> } else {
> @@ -682,6 +685,14 @@ expand(char *cp, /* input word */
> word = IFS_WORD;
> }
> }
> +
> +done:
> + for (st = &st_head; st != NULL; st = st->next) {
> + if (st->var == NULL || (st->var->flag & RDONLY) == 0)
> + continue;
> +
> + afree(st->var, ATEMP);
> + }
> }
>
> /*
> @@ -1285,4 +1296,24 @@ alt_expand(XPtrV *wp, char *start, char
> }
> }
> return;
> +}
> +
> +/*
> + * Copy the given variable if it's flagged as read-only.
> + * Such variables has static storage and only one can therefore be
> referenced at
> + * a time.
> + * This is necessary in order to allow variable expansion expressions to
> refer
> + * to multiple read-only variables.
> + */
> +static struct tbl *
> +varcpy(struct tbl *vp)
> +{
> + struct tbl *cpy;
> +
> + if ((vp->flag & RDONLY) == 0)
> + return vp;
> +
> + cpy = alloc(sizeof(struct tbl), ATEMP);
> + memcpy(cpy, vp, sizeof(struct tbl));
> + return cpy;
> }
>
--
jca | PGP : 0x1524E7EE / 5135 92C1 AD36 5293 2BDF DDCC 0DFA 74AE 1524 E7EE