mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-06-11 19:18:01 +02:00
52864: Change ${|var|...} to ${{var} ...}, limit local REPLY to ${|...}
This commit is contained in:
parent
5ba43e58c2
commit
76019f7174
5 changed files with 150 additions and 56 deletions
|
@ -1,5 +1,9 @@
|
||||||
2024-04-01 Bart Schaefer <schaefer@zsh.org>
|
2024-04-01 Bart Schaefer <schaefer@zsh.org>
|
||||||
|
|
||||||
|
* 52864: Src/lex.c, Src/subst.c, Test/D10nofork.ztst,
|
||||||
|
Test/V10private.ztst: Change ${|var|...} to ${{var} ...},
|
||||||
|
limit local REPLY behavior to ${|...}, update tests.
|
||||||
|
|
||||||
* 52781 (and typo fix): Src/hashtable.c: HIST IGNORE_DUPS treats
|
* 52781 (and typo fix): Src/hashtable.c: HIST IGNORE_DUPS treats
|
||||||
whitespace as significant when HIST_REDUCE_BLANKS is also set.
|
whitespace as significant when HIST_REDUCE_BLANKS is also set.
|
||||||
|
|
||||||
|
|
|
@ -1423,7 +1423,7 @@ gettokstr(int c, int sub)
|
||||||
if (lexstop)
|
if (lexstop)
|
||||||
break;
|
break;
|
||||||
if (!cmdsubst && in_brace_param && act == LX2_STRING &&
|
if (!cmdsubst && in_brace_param && act == LX2_STRING &&
|
||||||
(c == '|' || c == Bar || inblank(c))) {
|
(c == '|' || c == Bar || c == '{' || c == Inbrace || inblank(c))) {
|
||||||
cmdsubst = in_brace_param;
|
cmdsubst = in_brace_param;
|
||||||
cmdpush(CS_CURSH);
|
cmdpush(CS_CURSH);
|
||||||
} else if (in_pattern == 2 && c != '/')
|
} else if (in_pattern == 2 && c != '/')
|
||||||
|
|
140
Src/subst.c
140
Src/subst.c
|
@ -1866,10 +1866,11 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
|
||||||
* joining the array into a string (for compatibility with ksh/bash).
|
* joining the array into a string (for compatibility with ksh/bash).
|
||||||
*/
|
*/
|
||||||
int quoted_array_with_offset = 0;
|
int quoted_array_with_offset = 0;
|
||||||
/* Indicates ${|...;} */
|
/*
|
||||||
char *rplyvar = NULL;
|
* Nofork substitution controls
|
||||||
/* Indicates ${ ... ;} */
|
*/
|
||||||
char *rplytmp = NULL;
|
char *rplyvar = NULL; /* Indicates ${|...;} or ${{var} ...;} */
|
||||||
|
char *rplytmp = NULL; /* Indicates ${ ... ;} */
|
||||||
|
|
||||||
*s++ = '\0';
|
*s++ = '\0';
|
||||||
/*
|
/*
|
||||||
|
@ -1897,14 +1898,16 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
|
||||||
* flags in parentheses, but also one ksh hack.
|
* flags in parentheses, but also one ksh hack.
|
||||||
*/
|
*/
|
||||||
if (c == Inbrace) {
|
if (c == Inbrace) {
|
||||||
/* The command string to be run by ${|...;} */
|
/* For processing nofork command substitution string */
|
||||||
char *cmdarg = NULL;
|
char *cmdarg = NULL, *endvar = NULL, inchar = *++s;
|
||||||
|
char *outbracep = s, sav = *s;
|
||||||
|
Param rplypm = NULL;
|
||||||
size_t slen = 0;
|
size_t slen = 0;
|
||||||
int trim = (!EMULATION(EMULATE_ZSH)) ? 2 : !qt;
|
int trim = (!EMULATION(EMULATE_ZSH)) ? 2 : !qt;
|
||||||
inbrace = 1;
|
|
||||||
s++;
|
|
||||||
|
|
||||||
/* Short-path for the nofork command substitution ${|cmd;}
|
inbrace = 1; /* Outer scope boolean, see above */
|
||||||
|
|
||||||
|
/* Handling for nofork command substitution e.g. ${|cmd;}
|
||||||
* See other comments about kludges for why this is here.
|
* See other comments about kludges for why this is here.
|
||||||
*
|
*
|
||||||
* The command string is extracted and executed, and the
|
* The command string is extracted and executed, and the
|
||||||
|
@ -1913,48 +1916,77 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
|
||||||
* should not be part of command substitution in any case.
|
* should not be part of command substitution in any case.
|
||||||
* Use ${(U)${|cmd;}} as you would for ${(U)$(cmd;)}.
|
* Use ${(U)${|cmd;}} as you would for ${(U)$(cmd;)}.
|
||||||
*/
|
*/
|
||||||
if (*s == '|' || *s == Bar || inblank(*s)) {
|
if (inchar == '|' || inchar == Bar || inblank(inchar)) {
|
||||||
char *outbracep = s;
|
|
||||||
char sav = *s;
|
|
||||||
*s = Inbrace;
|
*s = Inbrace;
|
||||||
if (skipparens(Inbrace, Outbrace, &outbracep) == 0) {
|
if (skipparens(Inbrace, Outbrace, &outbracep) == 0)
|
||||||
slen = outbracep - s - 1;
|
slen = outbracep - s - 1;
|
||||||
if ((*s = sav) != Bar) {
|
*s = sav;
|
||||||
sav = *outbracep;
|
if (inchar == '|')
|
||||||
*outbracep = '\0';
|
inchar = Bar; /* Simplify later compares */
|
||||||
tokenize(s);
|
} else if (inchar == '{' || inchar == Inbrace) {
|
||||||
*outbracep = sav;
|
*s = Inbrace;
|
||||||
|
if ((outbracep = itype_end(s+1, INAMESPC, 0))) {
|
||||||
|
if (*outbracep == Inbrack &&
|
||||||
|
(outbracep = parse_subscript(++outbracep, 1, ']')))
|
||||||
|
++outbracep;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we reached the first close brace, find the last */
|
||||||
|
if (outbracep && *outbracep == Outbrace) {
|
||||||
|
char outchar = inchar == Inbrace ? Outbrace : '}';
|
||||||
|
endvar = outbracep++;
|
||||||
|
|
||||||
|
/* Require space to avoid ${{var}} typo for ${${var}} */
|
||||||
|
if (!inblank(*outbracep)) {
|
||||||
|
zerr("bad substitution");
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*endvar = '|'; /* Almost anything but braces/brackets */
|
||||||
|
outbracep = s;
|
||||||
|
if (skipparens(Inbrace, outchar, &outbracep) == 0)
|
||||||
|
*endvar = Outbrace;
|
||||||
|
else { /* Never happens? */
|
||||||
|
*endvar = outchar;
|
||||||
|
outbracep = endvar + 1;
|
||||||
|
}
|
||||||
|
slen = outbracep - s - 1;
|
||||||
|
if (inchar != Inbrace)
|
||||||
|
outbracep[-1] = Outbrace;
|
||||||
|
*s = sav;
|
||||||
|
inchar = Inbrace; /* Simplify later compares */
|
||||||
|
} else {
|
||||||
|
zerr("bad substitution");
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (slen > 1) {
|
if (slen > 1) {
|
||||||
char *outbracep = s + slen;
|
char *outbracep = s + slen;
|
||||||
|
if (!itok(*s) || inblank(inchar)) {
|
||||||
|
/* This tokenize() is important */
|
||||||
|
char sav = *outbracep;
|
||||||
|
*outbracep = '\0';
|
||||||
|
tokenize(s);
|
||||||
|
*outbracep = sav;
|
||||||
|
}
|
||||||
if (*outbracep == Outbrace) {
|
if (*outbracep == Outbrace) {
|
||||||
if ((rplyvar = itype_end(s+1, INAMESPC, 0))) {
|
if (endvar == s+1) {
|
||||||
if (*rplyvar == Inbrack &&
|
/* For consistency with ${} we allow ${{}...} */
|
||||||
(rplyvar = parse_subscript(++rplyvar, 1, ']')))
|
|
||||||
++rplyvar;
|
|
||||||
}
|
|
||||||
if (rplyvar == s+1 && *rplyvar == Bar) {
|
|
||||||
/* Is ${||...} a subtitution error or a syntax error?
|
|
||||||
zerr("bad substitution");
|
|
||||||
return NULL;
|
|
||||||
*/
|
|
||||||
rplyvar = NULL;
|
rplyvar = NULL;
|
||||||
}
|
}
|
||||||
if (rplyvar && *rplyvar == Bar) {
|
if (endvar && *endvar == Outbrace) {
|
||||||
cmdarg = dupstrpfx(rplyvar+1, outbracep-rplyvar-1);
|
cmdarg = dupstrpfx(endvar+1, outbracep-endvar-1);
|
||||||
rplyvar = dupstrpfx(s+1,rplyvar-s-1);
|
rplyvar = dupstrpfx(s+1,endvar-s-1);
|
||||||
} else {
|
} else {
|
||||||
cmdarg = dupstrpfx(s+1, outbracep-s-1);
|
cmdarg = dupstrpfx(s+1, outbracep-s-1);
|
||||||
rplyvar = "REPLY";
|
rplyvar = "REPLY";
|
||||||
}
|
}
|
||||||
if (inblank(*s)) {
|
if (inblank(inchar)) {
|
||||||
/*
|
/*
|
||||||
* Admittedly a hack. Take advantage of the enforced
|
* Admittedly a hack. Take advantage of the added
|
||||||
* locality of REPLY and the semantics of $(<file) to
|
* parameter scope and the semantics of $(<file) to
|
||||||
* construct a command to write/read a temporary file.
|
* construct a command to write/read a temporary file.
|
||||||
* Then fall through to the regular handling of $REPLY
|
* Then fall through to the regular parameter handling
|
||||||
* to manage word splitting, expansion flags, etc.
|
* to manage word splitting, expansion flags, etc.
|
||||||
*/
|
*/
|
||||||
char *outfmt = ">| %s {\n%s\n;}"; /* 13 */
|
char *outfmt = ">| %s {\n%s\n;}"; /* 13 */
|
||||||
|
@ -1977,22 +2009,39 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rplyvar) {
|
if (rplyvar) {
|
||||||
Param pm;
|
/* char *rplyval = getsparam("REPLY"); cf. Future? below */
|
||||||
/* char *rplyval = getsparam("REPLY"); */
|
|
||||||
startparamscope(); /* "local" behaves as if in a function */
|
startparamscope(); /* "local" behaves as if in a function */
|
||||||
pm = createparam("REPLY", PM_LOCAL|PM_UNSET);
|
if (inchar == Bar) {
|
||||||
if (pm) /* Shouldn't createparam() do this? */
|
/* rplyvar should be REPLY at this point, but create
|
||||||
pm->level = locallevel;
|
* hardwired name anyway to expose any bugs elsewhere
|
||||||
/* if (rplyval) setsparam("REPLY", ztrdup(rplyval)); */
|
*/
|
||||||
|
rplypm = createparam("REPLY", PM_LOCAL|PM_UNSET|PM_HIDE);
|
||||||
|
if (rplypm) /* Shouldn't createparam() do this? */
|
||||||
|
rplypm->level = locallevel;
|
||||||
|
/* Future? Expose global value of $REPLY if any? */
|
||||||
|
/* if (rplyval) setsparam("REPLY", ztrdup(rplyval)); */
|
||||||
|
} else if (inblank(inchar)) {
|
||||||
|
rplypm = createparam(".zsh.cmdsubst",
|
||||||
|
PM_LOCAL|PM_UNSET|PM_HIDE|
|
||||||
|
PM_READONLY_SPECIAL);
|
||||||
|
if (rplypm)
|
||||||
|
rplypm->level = locallevel;
|
||||||
|
}
|
||||||
|
if (inchar != Inbrace && !rplypm) {
|
||||||
|
zerr("failed to create scope for command substitution");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rplyvar && cmdarg && *cmdarg) {
|
if (rplyvar && cmdarg && *cmdarg) {
|
||||||
int obreaks = breaks;
|
int obreaks = breaks;
|
||||||
Eprog cmdprog;
|
Eprog cmdprog;
|
||||||
/* Execute the shell command */
|
/* Execute the shell command */
|
||||||
|
queue_signals();
|
||||||
untokenize(cmdarg);
|
untokenize(cmdarg);
|
||||||
cmdprog = parse_string(cmdarg, 0);
|
cmdprog = parse_string(cmdarg, 0);
|
||||||
if (cmdprog) {
|
if (cmdprog) {
|
||||||
|
/* exec.c handles dont_queue_signals() */
|
||||||
execode(cmdprog, 1, 0, "cmdsubst");
|
execode(cmdprog, 1, 0, "cmdsubst");
|
||||||
cmdoutval = lastval;
|
cmdoutval = lastval;
|
||||||
/* "return" behaves as if in a function */
|
/* "return" behaves as if in a function */
|
||||||
|
@ -2002,6 +2051,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
|
||||||
}
|
}
|
||||||
} else /* parse error */
|
} else /* parse error */
|
||||||
errflag |= ERRFLAG_ERROR;
|
errflag |= ERRFLAG_ERROR;
|
||||||
|
if (rplypm)
|
||||||
|
rplypm->node.flags &= ~PM_READONLY_SPECIAL;
|
||||||
if (rplytmp && !errflag) {
|
if (rplytmp && !errflag) {
|
||||||
int onoerrs = noerrs, rplylen;
|
int onoerrs = noerrs, rplylen;
|
||||||
noerrs = 2;
|
noerrs = 2;
|
||||||
|
@ -2017,15 +2068,16 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
|
||||||
}
|
}
|
||||||
noerrs = onoerrs;
|
noerrs = onoerrs;
|
||||||
if (rplylen >= 0)
|
if (rplylen >= 0)
|
||||||
setsparam("REPLY", metafy(cmdarg, rplylen, META_REALLOC));
|
setsparam(rplyvar, metafy(cmdarg, rplylen, META_REALLOC));
|
||||||
}
|
}
|
||||||
|
unqueue_signals();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rplytmp)
|
if (rplytmp)
|
||||||
unlink(rplytmp);
|
unlink(rplytmp);
|
||||||
if (rplyvar) {
|
if (rplyvar) {
|
||||||
if (strcmp(rplyvar, "REPLY") == 0) {
|
if (inchar != Inbrace) {
|
||||||
if ((val = dupstring(getsparam("REPLY"))))
|
if ((val = dupstring(getsparam(rplyvar))))
|
||||||
vunset = 0;
|
vunset = 0;
|
||||||
else {
|
else {
|
||||||
vunset = 1;
|
vunset = 1;
|
||||||
|
|
|
@ -14,6 +14,28 @@
|
||||||
0:Basic substitution and REPLY scoping
|
0:Basic substitution and REPLY scoping
|
||||||
>INNER OUTER
|
>INNER OUTER
|
||||||
|
|
||||||
|
reply=(x OUTER x)
|
||||||
|
purl ${{reply} reply=(\{ INNER \})} $reply
|
||||||
|
0:Basic substitution, brace quoting, and array result
|
||||||
|
>{
|
||||||
|
>INNER
|
||||||
|
>}
|
||||||
|
>{
|
||||||
|
>INNER
|
||||||
|
>}
|
||||||
|
|
||||||
|
() {
|
||||||
|
setopt localoptions ignorebraces
|
||||||
|
purl ${{reply} reply=({ INNER })} $reply
|
||||||
|
}
|
||||||
|
0:Basic substitution, ignorebraces, and array result
|
||||||
|
>{
|
||||||
|
>INNER
|
||||||
|
>}
|
||||||
|
>{
|
||||||
|
>INNER
|
||||||
|
>}
|
||||||
|
|
||||||
purr ${| REPLY=first}:${| REPLY=second}:$REPLY
|
purr ${| REPLY=first}:${| REPLY=second}:$REPLY
|
||||||
0:re-scoping of REPLY in one statement
|
0:re-scoping of REPLY in one statement
|
||||||
>first:second:OUTER
|
>first:second:OUTER
|
||||||
|
@ -229,7 +251,7 @@ F:Why not use this error in the previous case as well?
|
||||||
>26
|
>26
|
||||||
|
|
||||||
unset reply
|
unset reply
|
||||||
purl ${|reply| reply=(1 2 ${| REPLY=3 } 4) }
|
purl ${{reply} reply=(1 2 ${| REPLY=3 } 4) }
|
||||||
typeset -p reply
|
typeset -p reply
|
||||||
0:array behavior with global assignment
|
0:array behavior with global assignment
|
||||||
>1
|
>1
|
||||||
|
@ -315,7 +337,7 @@ F:status of "print" should hide return
|
||||||
|
|
||||||
unset zz
|
unset zz
|
||||||
outer=GLOBAL
|
outer=GLOBAL
|
||||||
purr "${|zz|
|
purr "${{zz}
|
||||||
local outer=LOCAL
|
local outer=LOCAL
|
||||||
zz=NONLOCAL
|
zz=NONLOCAL
|
||||||
} $outer $?"
|
} $outer $?"
|
||||||
|
@ -440,7 +462,8 @@ F:must do this before evaluating the next test block
|
||||||
0:ignored braces, part 1
|
0:ignored braces, part 1
|
||||||
>buried}
|
>buried}
|
||||||
|
|
||||||
purr "${ purr ${REPLY:-buried}}}"
|
# Global $REPLY still set from earlier test
|
||||||
|
purr "${ purr ${REPLY:+buried}}}"
|
||||||
0:ignored braces, part 2
|
0:ignored braces, part 2
|
||||||
>buried
|
>buried
|
||||||
>}
|
>}
|
||||||
|
@ -453,6 +476,7 @@ F:must do this before evaluating the next test block
|
||||||
1:ignored braces, part 4
|
1:ignored braces, part 4
|
||||||
?(eval):3: parse error near `}'
|
?(eval):3: parse error near `}'
|
||||||
|
|
||||||
|
unsetopt ignorebraces
|
||||||
# "break" blocks function calls in outer loop
|
# "break" blocks function calls in outer loop
|
||||||
# Could use print, but that might get fixed
|
# Could use print, but that might get fixed
|
||||||
repeat 3 do purr ${
|
repeat 3 do purr ${
|
||||||
|
@ -467,11 +491,25 @@ F:must do this before evaluating the next test block
|
||||||
?1
|
?1
|
||||||
?2
|
?2
|
||||||
|
|
||||||
print -u $ZTST_fd ${ZTST_testname}: TEST COMPLETE
|
# Cannot "purr": break skips pending function calls
|
||||||
0:make sure we got to the end
|
# Use "repeat" to avoid infinite loop on failure
|
||||||
F:some tests might silently break the test harness
|
repeat 3 do; echo ${|REPLY=x; break }; done
|
||||||
|
repeat 3 do; echo ${{x} x=y; break }; done
|
||||||
|
repeat 3 do; echo ${ echo z; break }; done
|
||||||
|
0:break after assignment completes the assignment
|
||||||
|
>x
|
||||||
|
>y
|
||||||
|
>z
|
||||||
|
|
||||||
|
# Subshell because error exits
|
||||||
|
( purr ${ echo ${unset?oops} } )
|
||||||
|
1:error handling (without crashing)
|
||||||
|
*?*unset: oops
|
||||||
|
|
||||||
|
purr ${ .zsh.cmdsubst=error }
|
||||||
|
1:reserved parameter name (without crashing)
|
||||||
|
*?*.zsh.cmdsubst: can't modify read-only parameter
|
||||||
|
|
||||||
%clean
|
%clean
|
||||||
|
|
||||||
unfunction purr purl
|
unfunction purr purl
|
||||||
unsetopt ignorebraces
|
|
||||||
|
|
|
@ -497,7 +497,7 @@ F:Better if caught in checkclobberparam() but exec.c doesn't know scope
|
||||||
() {
|
() {
|
||||||
private z=outer
|
private z=outer
|
||||||
print ${(t)z} $z
|
print ${(t)z} $z
|
||||||
print ${| REPLY=${|z| z=nofork} }
|
print ${| REPLY=${{z} z=nofork} }
|
||||||
print ${(t)z} $z
|
print ${(t)z} $z
|
||||||
}
|
}
|
||||||
0:nofork may write to private in calling function
|
0:nofork may write to private in calling function
|
||||||
|
@ -518,9 +518,9 @@ F:Better if caught in checkclobberparam() but exec.c doesn't know scope
|
||||||
() {
|
() {
|
||||||
private z=outer
|
private z=outer
|
||||||
print ${(t)z} $z
|
print ${(t)z} $z
|
||||||
print ${|z|
|
print ${{z}
|
||||||
private q
|
private q
|
||||||
z=${|q| q=nofork}
|
z=${{q} q=nofork}
|
||||||
}
|
}
|
||||||
print ${(t)z} $z
|
print ${(t)z} $z
|
||||||
}
|
}
|
||||||
|
@ -533,7 +533,7 @@ F:Better if caught in checkclobberparam() but exec.c doesn't know scope
|
||||||
print ${|
|
print ${|
|
||||||
() { REPLY="{$q}" }
|
() { REPLY="{$q}" }
|
||||||
}
|
}
|
||||||
print ${|q|
|
print ${{q}
|
||||||
() { q=nofork }
|
() { q=nofork }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue