1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-05-10 17:46:19 +02:00

52759: ${ ... } trims one trailing newline; "${ ... }" preserves that newline.

This commit is contained in:
Bart Schaefer 2024-03-17 14:28:28 -07:00
parent 45b0a838aa
commit 25182cc2e6
5 changed files with 61 additions and 17 deletions

View File

@ -1,5 +1,10 @@
2024-03-14 Bart Schaefer <schaefer@zsh.org> 2024-03-14 Bart Schaefer <schaefer@zsh.org>
* 52759: Doc/Zsh/expn.yo, Etc/FAQ.yo, Src/subst.c,
Test/D10nofork.ztst: change ${ ... } substitution to trim one
trailing newline; instead "${ ... }" (with quotes) preserves that
newline. All trailing newlines are still trimmed in emulations.
* unposted: Etc/BUGS: HIST_IGNORE_DUPS mishandles quoted whitespace. * unposted: Etc/BUGS: HIST_IGNORE_DUPS mishandles quoted whitespace.
* 52752: Src/params.c, Test/B02typeset.ztst: more typeset -p fixes * 52752: Src/params.c, Test/B02typeset.ztst: more typeset -p fixes

View File

@ -1950,6 +1950,9 @@ the braces by whitespace, like `tt(${ )...tt( })', is replaced by its
standard output. Like `tt(${|)...tt(})' and unlike standard output. Like `tt(${|)...tt(})' and unlike
`tt($LPAR())...tt(RPAR())', the command executes in the current shell `tt($LPAR())...tt(RPAR())', the command executes in the current shell
context with function local behaviors and does not create a subshell. context with function local behaviors and does not create a subshell.
Word splitting does not apply unless tt(SH_WORD_SPLIT) is set, but a
single trailing newline is stripped unless the substitution is enclosed
in double quotes.
Note that because the `tt(${|)...tt(})' and `tt(${ )...tt( })' forms Note that because the `tt(${|)...tt(})' and `tt(${ )...tt( })' forms
must be parsed at once as both string tokens and commands, all other must be parsed at once as both string tokens and commands, all other

View File

@ -1091,20 +1091,23 @@ sect(Comparisons of forking and non-forking command substitution)
mytt(set -- pos1 pos2 etc). Nothing that happens within mytt($(command)) mytt(set -- pos1 pos2 etc). Nothing that happens within mytt($(command))
affects the caller. affects the caller.
mytt($(command)) removes trailing newlines from the output of mytt(command)
when substituting, whereas mytt(${ command }) and its variants do not.
The latter is consistent with mytt(${|...}) from mksh but differs from
bash and ksh, so in emulation modes, newlines are stripped from command
output (not from tt(REPLY) assignments).
When not enclosed in double quotes, the expansion of mytt($(command)) is When not enclosed in double quotes, the expansion of mytt($(command)) is
split on tt(IFS) into an array of words. In contrast, and unlike both split on tt(IFS) into an array of words. In contrast, and unlike both
bash and ksh, unquoted non-forking substitutions behave like parameter bash and ksh, unquoted non-forking substitutions behave like parameter
expansions with respect to the tt(SH_WORD_SPLIT) option. expansions with respect to the tt(SH_WORD_SPLIT) option.
When mytt(command) is myem(not) a builtin, mytt(${ command }) does fork, and Both of the mytt(${|...}) formats retain any trailing newlines,
typically forks the same number of times as mytt($(command)), because in except as handled by the tt(SH_WORD_SPLIT) option, consistent with
the latter case zsh usually optimizes the final fork into an exec. mytt(${|...}) from mksh. mytt(${ command }) removes a single final
newline, but mytt("${ command }") retains it. This differs from
bash and ksh, so in emulation modes, newlines are stripped even from
quoted command output. In all cases, mytt($(command)) removes all
trailing newlines from the output of mytt(command).
When mytt(command) is myem(not) a builtin, mytt(${ command }) does
fork, and typically forks the same number of times as
mytt($(command)), because in the latter case zsh usually optimizes
the final fork into an exec.
Redirecting input from files has subtle differences: Redirecting input from files has subtle differences:
itemization( itemization(

View File

@ -1900,6 +1900,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
/* The command string to be run by ${|...;} */ /* The command string to be run by ${|...;} */
char *cmdarg = NULL; char *cmdarg = NULL;
size_t slen = 0; size_t slen = 0;
int trim = (!EMULATION(EMULATE_ZSH)) ? 2 : !qt;
inbrace = 1; inbrace = 1;
s++; s++;
@ -2005,10 +2006,13 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
int onoerrs = noerrs, rplylen; int onoerrs = noerrs, rplylen;
noerrs = 2; noerrs = 2;
rplylen = zstuff(&cmdarg, rplytmp); rplylen = zstuff(&cmdarg, rplytmp);
if (! EMULATION(EMULATE_ZSH)) { if (trim) {
/* bash and ksh strip trailing newlines here */ /* bash and ksh strip trailing newlines here */
while (rplylen > 0 && cmdarg[rplylen-1] == '\n') while (rplylen > 0 && cmdarg[rplylen-1] == '\n') {
rplylen--; rplylen--;
if (trim == 1)
break;
}
cmdarg[rplylen] = 0; cmdarg[rplylen] = 0;
} }
noerrs = onoerrs; noerrs = onoerrs;

View File

@ -86,9 +86,39 @@ F:setting option inside is too late for that substitution
?(eval):8: no matches found: f?* ?(eval):8: no matches found: f?*
purr ${| REPLY=$'trailing newlines remain\n\n' } purr ${| REPLY=$'trailing newlines remain\n\n' }
0:newline removal should not occur 0:newline removal should not occur, part 1
>trailing newlines remain >trailing newlines remain
> >
>
purr ${ echo $'one trailing newline\nremoved\n\n\n' }
0:newline removal in ${ ... }, zsh mode
>one trailing newline
>removed
>
>
>
() {
emulate -L ksh
purl ${ echo $'all trailing newlines\nremoved\n\n\n' }
purr "${ echo $'all trailing newlines\nremoved\n\n\n' }"
}
0:newline removal in ${ ... }, emulation mode, shwordsplit
>all
>trailing
>newlines
>removed
>all trailing newlines
>removed
purr "${ echo $'no trailing newlines\nremoved\n\n\n' }"
0:newline removal should not occur, part 2
>no trailing newlines
>removed
>
>
>
> >
() { () {
@ -159,7 +189,7 @@ F:Why not use this error in the previous case as well?
1:unbalanced braces, part 4+ 1:unbalanced braces, part 4+
?(eval):1: closing brace expected ?(eval):1: closing brace expected
purr ${ purr STDOUT } purr "${ purr STDOUT }"
0:capture stdout 0:capture stdout
>STDOUT >STDOUT
> >
@ -322,7 +352,7 @@ F:Fiddly here to get EOF past the test syntax
0:here-string behavior 0:here-string behavior
>in a here string >in a here string
<<<${ purr $'stdout as a here string' } <<<"${ purr $'stdout as a here string' }"
0:another capture stdout 0:another capture stdout
>stdout as a here string >stdout as a here string
> >
@ -331,7 +361,7 @@ F:Fiddly here to get EOF past the test syntax
wrap=${ purr "capture in environment assignment" } typeset -p wrap wrap=${ purr "capture in environment assignment" } typeset -p wrap
0:assignment context 0:assignment context
>typeset -g wrap='REPLY in environment assignment' >typeset -g wrap='REPLY in environment assignment'
>typeset -g wrap=$'capture in environment assignment\n' >typeset -g wrap='capture in environment assignment'
# Repeat return and exit tests with stdout capture # Repeat return and exit tests with stdout capture
@ -410,7 +440,7 @@ 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}}} purr "${ purr ${REPLY:-buried}}}"
0:ignored braces, part 2 0:ignored braces, part 2
>buried >buried
>} >}
@ -418,7 +448,6 @@ F:must do this before evaluating the next test block
purr ${ { echo nested ;} } purr ${ { echo nested ;} }
0:ignored braces, part 3 0:ignored braces, part 3
>nested >nested
>
purr ${ { echo nested } } DONE purr ${ { echo nested } } DONE
1:ignored braces, part 4 1:ignored braces, part 4