Skip to content

Commit b23649e

Browse files
committed
Fix field separator for $* in here-document
Reproducer: IFS=/ set a b c cat <<EOF $* EOF Actual output: a b c Expected output: a/b/c In any context in which field splitting is not possible, POSIX specifies that $* uses the first character of $IFS as the output field separator.[*] Here-documents are such a context. Analysis: In macro.c, there are three places where IFS is initialised - in sh_mactrim, sh_macexpand and in sh_machere. The latter function deals with here-documents. IFS is initialised by pointing ifsp to the value of IFSNOD and setting ifs to the first byte of this value. However, in sh_machere, ifs is hardcoded to a space. This is the bug. src/cmd/ksh93/sh/macro.c: - Add setup_ifs function to centralise this logic. - Call setup_ifs in the three places mentioned, replacing the duplicative old code (including the buggy version). [*] https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_05_02
1 parent 409a886 commit b23649e

File tree

3 files changed

+43
-10
lines changed

3 files changed

+43
-10
lines changed

NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ This documents significant changes in the dev branch of ksh 93u+m.
22
For full details, see the git log at: https://github.com/ksh93/ksh
33
Uppercase BUG_* IDs are shell bug IDs as used by the Modernish shell library.
44

5+
2025-04-24:
6+
7+
- Fixed a bug in the expansion of $* within a here-document: the positional
8+
parameters were separated by a hardcoded space instead of the first
9+
character of $IFS (which defaults to a space but can be changed).
10+
511
2025-04-23:
612

713
- [v1.1] Ksh now supports the bash-like case modification expansions

src/cmd/ksh93/sh/macro.c

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,19 @@ char *sh_mactry(char *string)
145145
return "";
146146
}
147147

148+
/*
149+
* Read $IFS and initialize:
150+
* - ifsp: pointer to value of IFS (NULL if IFS is unset)
151+
* - ifs: first byte of value of IFS (0 if empty, space if IFS is unset)
152+
*/
153+
static void setup_ifs(Mac_t *mp)
154+
{
155+
if(mp->ifsp = nv_getval(sh_scoped(IFSNOD)))
156+
mp->ifs = *mp->ifsp;
157+
else
158+
mp->ifs = ' ';
159+
}
160+
148161
/*
149162
* Perform parameter expansion, command substitution, and arithmetic
150163
* expansion on <str>.
@@ -167,10 +180,7 @@ char *sh_mactrim(char *str, int mode)
167180
mp->assign = -mode;
168181
mp->quoted = mp->lit = mp->split = mp->quote = 0;
169182
mp->sp = 0;
170-
if(mp->ifsp=nv_getval(sh_scoped(IFSNOD)))
171-
mp->ifs = *mp->ifsp;
172-
else
173-
mp->ifs = ' ';
183+
setup_ifs(mp);
174184
stkseek(stkp,0);
175185
fcsopen(str);
176186
copyto(mp,0,mp->arith);
@@ -204,10 +214,7 @@ int sh_macexpand(struct argnod *argp, struct argnod **arghead,int flag)
204214
Mac_t savemac = *mp;
205215
Stk_t *stkp = sh.stk;
206216
mp->sp = 0;
207-
if(mp->ifsp=nv_getval(sh_scoped(IFSNOD)))
208-
mp->ifs = *mp->ifsp;
209-
else
210-
mp->ifs = ' ';
217+
setup_ifs(mp);
211218
if((flag&ARG_OPTIMIZE) && !sh.indebug && !(flags&ARG_MESSAGE))
212219
nv_setoptimize((char**)&argp->argchn.ap);
213220
else
@@ -275,8 +282,7 @@ void sh_machere(Sfio_t *infile, Sfio_t *outfile, char *string)
275282
mp->sp = outfile;
276283
mp->split = mp->assign = mp->pattern = mp->patfound = mp->lit = mp->arith = 0;
277284
mp->quote = 1;
278-
mp->ifsp = nv_getval(sh_scoped(IFSNOD));
279-
mp->ifs = ' ';
285+
setup_ifs(mp);
280286
fcsave(&save);
281287
if(infile)
282288
fcfopen(infile);

src/cmd/ksh93/tests/heredoc.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,5 +580,26 @@ exp=$'\t\tTest line 1\n\tTest line 2'
580580
[[ $got == "$exp" ]] || err_exit "processing a here-document from a command substitution in a here-document" \
581581
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
582582
583+
# ======
584+
# $@ and $* in here-document
585+
# Note: POSIX sys that $@ has unspecified behaviour in this context because
586+
# field generation is not posible, but ksh's traditional behaviour is to pull
587+
# a space out of a hat and use it as the output field separator. Only $* makes
588+
# sense in a here-document. In any scalar context (in which field splittig is
589+
# not possible), POSIX specifies that $* uses the first character of $IFS as
590+
# the output field separator.
591+
got=$(
592+
IFS=/
593+
set ONE TWO THREE
594+
cat <<-EOF
595+
start$@end
596+
start$*end
597+
EOF
598+
)
599+
exp=$'startONE TWO THREEend\nstartONE/TWO/THREEend'
600+
[[ $got == "$exp" ]] || err_exit '$@ and $* in here-document' \
601+
"(expected $(printf %q "$exp"), got $(printf %q "$got"))"
602+
603+
583604
# ======
584605
exit $((Errors<125?Errors:125))

0 commit comments

Comments
 (0)