Merge remote-tracking branch 'mksh/master'

This commit is contained in:
KO Myung-Hun
2016-06-29 08:17:29 +09:00
10 changed files with 361 additions and 182 deletions

View File

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.697 2016/03/04 18:28:39 tg Exp $' srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.698 2016/06/25 23:49:12 tg Exp $'
#- #-
# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
# 2011, 2012, 2013, 2014, 2015, 2016 # 2011, 2012, 2013, 2014, 2015, 2016
@ -1648,9 +1648,12 @@ ac_ifcpp 'ifdef MKSH_NOPROSPECTOFWORK' isset_MKSH_NOPROSPECTOFWORK '' \
check_categories="$check_categories arge nojsig" check_categories="$check_categories arge nojsig"
ac_ifcpp 'ifdef MKSH_ASSUME_UTF8' isset_MKSH_ASSUME_UTF8 '' \ ac_ifcpp 'ifdef MKSH_ASSUME_UTF8' isset_MKSH_ASSUME_UTF8 '' \
'if the default UTF-8 mode is specified' && : "${HAVE_SETLOCALE_CTYPE=0}" 'if the default UTF-8 mode is specified' && : "${HAVE_SETLOCALE_CTYPE=0}"
ac_ifcpp 'ifdef MKSH_CONSERVATIVE_FDS' isset_MKSH_CONSERVATIVE_FDS '' \ if ac_ifcpp 'ifdef MKSH_CONSERVATIVE_FDS' isset_MKSH_CONSERVATIVE_FDS '' \
'if traditional/conservative fd use is requested' && \ 'if traditional/conservative fd use is requested'; then
check_categories="$check_categories convfds" check_categories="$check_categories convfds"
else
echo >&2 "WARNING: not building with -DMKSH_CONSERVATIVE_FDS is deprecated"
fi
#ac_ifcpp 'ifdef MKSH_DISABLE_DEPRECATED' isset_MKSH_DISABLE_DEPRECATED '' \ #ac_ifcpp 'ifdef MKSH_DISABLE_DEPRECATED' isset_MKSH_DISABLE_DEPRECATED '' \
# "if deprecated features are to be omitted" && \ # "if deprecated features are to be omitted" && \
# check_categories="$check_categories nodeprecated" # check_categories="$check_categories nodeprecated"
@ -2655,7 +2658,7 @@ MKSH_BINSHPOSIX if */sh or */-sh, enable set -o posix
MKSH_BINSHREDUCED if */sh or */-sh, enable set -o sh MKSH_BINSHREDUCED if */sh or */-sh, enable set -o sh
MKSH_CLRTOEOL_STRING "\033[K" MKSH_CLRTOEOL_STRING "\033[K"
MKSH_CLS_STRING "\033[;H\033[J" MKSH_CLS_STRING "\033[;H\033[J"
MKSH_CONSERVATIVE_FDS fd 0-9 for scripts, shell only up to 31 MKSH_CONSERVATIVE_FDS fd 0-9 for scripts, shell only up to 31 (soon default)
MKSH_DEFAULT_EXECSHELL "/bin/sh" (do not change) MKSH_DEFAULT_EXECSHELL "/bin/sh" (do not change)
MKSH_DEFAULT_PROFILEDIR "/etc" (do not change) MKSH_DEFAULT_PROFILEDIR "/etc" (do not change)
MKSH_DEFAULT_TMPDIR "/tmp" (do not change) MKSH_DEFAULT_TMPDIR "/tmp" (do not change)

160
check.t
View File

@ -1,4 +1,4 @@
# $MirOS: src/bin/mksh/check.t,v 1.731 2016/05/05 22:58:19 tg Exp $ # $MirOS: src/bin/mksh/check.t,v 1.739 2016/06/26 00:44:55 tg Exp $
# -*- mode: sh -*- # -*- mode: sh -*-
#- #-
# Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, # Copyright © 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
@ -27,10 +27,10 @@
# http://svnweb.freebsd.org/base/head/bin/test/tests/legacy_test.sh?view=co&content-type=text%2Fplain # http://svnweb.freebsd.org/base/head/bin/test/tests/legacy_test.sh?view=co&content-type=text%2Fplain
# #
# Integrated testsuites from: # Integrated testsuites from:
# (2013/12/02 20:39:44) http://openbsd.cs.toronto.edu/cgi-bin/cvsweb/src/regress/bin/ksh/?sortby=date # (2013/12/02 20:39:44) http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/regress/bin/ksh/?sortby=date
expected-stdout: expected-stdout:
@(#)MIRBSD KSH R52 2016/05/05 @(#)MIRBSD KSH R52 2016/06/25
description: description:
Check version of shell. Check version of shell.
stdin: stdin:
@ -39,7 +39,7 @@ name: KSH_VERSION
category: shell:legacy-no category: shell:legacy-no
--- ---
expected-stdout: expected-stdout:
@(#)LEGACY KSH R52 2016/05/05 @(#)LEGACY KSH R52 2016/06/25
description: description:
Check version of legacy shell. Check version of legacy shell.
stdin: stdin:
@ -199,7 +199,7 @@ description:
stdin: stdin:
alias X='case ' alias X='case '
alias Y=Z alias Y=Z
X Y in 'Y') echo is y ;; Z) echo is z ; esac X Y in 'Y') echo is y ;; Z) echo is z ;; esac
expected-stdout: expected-stdout:
is z is z
--- ---
@ -1311,6 +1311,7 @@ stdin:
(echo 38 ${IFS+x'a'y} / "${IFS+x'a'y}" .) 2>/dev/null || echo failed in 38 (echo 38 ${IFS+x'a'y} / "${IFS+x'a'y}" .) 2>/dev/null || echo failed in 38
foo="x'a'y"; (echo 39 ${foo%*'a'*} / "${foo%*'a'*}" .) 2>/dev/null || echo failed in 39 foo="x'a'y"; (echo 39 ${foo%*'a'*} / "${foo%*'a'*}" .) 2>/dev/null || echo failed in 39
foo="a b c"; (echo -n '40 '; ./pfs "${foo#a}"; echo .) 2>/dev/null || echo failed in 40 foo="a b c"; (echo -n '40 '; ./pfs "${foo#a}"; echo .) 2>/dev/null || echo failed in 40
(foo() { return 100; }; foo; echo 41 ${#+${#:+${#?}}\ \}\}\}}) 2>/dev/null || echo failed in 41
expected-stdout: expected-stdout:
1 }z 1 }z
2 ''z} 2 ''z}
@ -1352,6 +1353,7 @@ expected-stdout:
38 xay / x'a'y . 38 xay / x'a'y .
39 x' / x' . 39 x' / x' .
40 < b c> . 40 < b c> .
41 3 }}}
--- ---
name: expand-unglob-dblq name: expand-unglob-dblq
description: description:
@ -1400,6 +1402,7 @@ stdin:
(echo "$1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz") 2>/dev/null || \ (echo "$1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz") 2>/dev/null || \
echo "$1 QSTN brac -> error" echo "$1 QSTN brac -> error"
} }
: '}}}' '}}}' '}}}' '}}}' '}}}' '}}}' '}}}' '}}}'
tl_norm 1 - tl_norm 1 -
tl_norm 2 '' tl_norm 2 ''
tl_norm 3 x tl_norm 3 x
@ -1530,6 +1533,7 @@ stdin:
(echo $1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz) 2>/dev/null || \ (echo $1 QSTN brac foo ${v:?a$u{{{\}b} c ${v:?d{}} baz) 2>/dev/null || \
echo "$1 QSTN brac -> error" echo "$1 QSTN brac -> error"
} }
: '}}}' '}}}' '}}}' '}}}' '}}}' '}}}' '}}}' '}}}'
tl_norm 1 - tl_norm 1 -
tl_norm 2 '' tl_norm 2 ''
tl_norm 3 x tl_norm 3 x
@ -1637,7 +1641,7 @@ expected-exit: 1
--- ---
name: expand-weird-1 name: expand-weird-1
description: description:
Check corner case of trim expansion vs. $# vs. ${#var} Check corner cases of trim expansion vs. $# vs. ${#var} vs. ${var?}
stdin: stdin:
set 1 2 3 4 5 6 7 8 9 10 11 set 1 2 3 4 5 6 7 8 9 10 11
echo ${#} # value of $# echo ${#} # value of $#
@ -1645,24 +1649,60 @@ stdin:
echo ${##1} # $# trimmed 1 echo ${##1} # $# trimmed 1
set 1 2 3 4 5 6 7 8 9 10 11 12 set 1 2 3 4 5 6 7 8 9 10 11 12
echo ${##1} echo ${##1}
expected-stdout:
11
2
1
2
---
name: expand-weird-2
description:
Check corner case of ${var?} vs. ${#var}
stdin:
(exit 0) (exit 0)
echo $? = ${#?} . echo $? = ${#?} .
(exit 111) (exit 111)
echo $? = ${#?} . echo $? = ${#?} .
expected-stdout: expected-stdout:
11
2
1
2
0 = 1 . 0 = 1 .
111 = 3 . 111 = 3 .
--- ---
name: expand-weird-2
description:
Check more substitution and extension corner cases
stdin:
:& set -C; pid=$$; sub=$!; flg=$-; set -- i; exec 3>x.tmp
#echo "D: !=$! #=$# \$=$$ -=$- ?=$?"
echo >&3 3 = s^${!-word} , ${#-word} , p^${$-word} , f^${--word} , ${?-word} .
echo >&3 4 = ${!+word} , ${#+word} , ${$+word} , ${-+word} , ${?+word} .
echo >&3 5 = s^${!=word} , ${#=word} , p^${$=word} , f^${-=word} , ${?=word} .
echo >&3 6 = s^${!?word} , ${#?word} , p^${$?word} , f^${-?word} , ${??word} .
echo >&3 7 = sl^${#!} , ${##} , pl^${#$} , fl^${#-} , ${#?} .
echo >&3 8 = sw^${%!} , ${%#} , pw^${%$} , fw^${%-} , ${%?} .
echo >&3 9 = ${!!} , s^${!#} , ${!$} , s^${!-} , s^${!?} .
echo >&3 10 = s^${!#pattern} , ${##pattern} , p^${$#pattern} , f^${-#pattern} , ${?#pattern} .
echo >&3 11 = s^${!%pattern} , ${#%pattern} , p^${$%pattern} , f^${-%pattern} , ${?%pattern} .
echo >&3 12 = $# : ${##} , ${##1} .
set --
echo >&3 14 = $# : ${##} , ${##1} .
set -- 1 2 3 4 5
echo >&3 16 = $# : ${##} , ${##1} .
set -- 1 2 3 4 5 6 7 8 9 a b c d e
echo >&3 18 = $# : ${##} , ${##1} .
exec 3>&-
<x.tmp sed \
-e "s/ pl^${#pid} / PID /g" -e "s/ sl^${#sub} / SUB /g" -e "s/ fl^${#flg} / FLG /g" \
-e "s/ pw^${%pid} / PID /g" -e "s/ sw^${%sub} / SUB /g" -e "s/ fw^${%flg} / FLG /g" \
-e "s/ p^$pid / PID /g" -e "s/ s^$sub / SUB /g" -e "s/ f^$flg / FLG /g"
expected-stdout:
3 = SUB , 1 , PID , FLG , 0 .
4 = word , word , word , word , word .
5 = SUB , 1 , PID , FLG , 0 .
6 = SUB , 1 , PID , FLG , 0 .
7 = SUB , 1 , PID , FLG , 1 .
8 = SUB , 1 , PID , FLG , 1 .
9 = ! , SUB , $ , SUB , SUB .
10 = SUB , 1 , PID , FLG , 0 .
11 = SUB , 1 , PID , FLG , 0 .
12 = 1 : 1 , .
14 = 0 : 1 , 0 .
16 = 5 : 1 , 5 .
18 = 14 : 2 , 4 .
---
name: expand-weird-3 name: expand-weird-3
description: description:
Check that trimming works with positional parameters (Debian #48453) Check that trimming works with positional parameters (Debian #48453)
@ -2573,6 +2613,10 @@ stdin:
eval "$fnd" eval "$fnd"
foo foo
print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} |" print -r -- "| va={$va} vb={$vb} vc={$vc} vd={$vd} |"
x=y
foo
typeset -f foo
print -r -- "| vc={$vc} vd={$vd} |"
# check append # check append
v=<<- v=<<-
vapp1 vapp1
@ -2598,6 +2642,19 @@ expected-stdout:
} vc={=c u \x40= } vc={=c u \x40=
} vd={=d $x \x40= } vd={=d $x \x40=
} | } |
function foo {
vc=<<-
=c $x \x40=
<<
vd=<<-""
=d $x \x40=
}
| vc={=c y \x40=
} vd={=d $x \x40=
} |
| vapp1^vapp2^ | | vapp1^vapp2^ |
--- ---
name: heredoc-12 name: heredoc-12
@ -2755,6 +2812,28 @@ expected-stdout:
C:echo line 5 C:echo line 5
x-en x-en
--- ---
name: heredoc-comsub-6
description:
Check here documents and here strings can be used
without a specific command, like $(<…) (extension)
stdin:
foo=bar
x=$(<<<EO${foo}F)
echo "3<$x>"
y=$(<<-EOF
hi!
$foo) is not a problem
EOF)
echo "7<$y>"
expected-stdout:
3<EObarF>
7<hi!
bar) is not a problem>
---
name: heredoc-subshell-1 name: heredoc-subshell-1
description: description:
Tests for here documents in subshells, taken from Austin ML Tests for here documents in subshells, taken from Austin ML
@ -8909,17 +8988,45 @@ name: print-funny-chars
description: description:
Check print builtin's capability to output designated characters Check print builtin's capability to output designated characters
stdin: stdin:
{
print '<\0144\0344\xDB\u00DB\u20AC\uDB\x40>' print '<\0144\0344\xDB\u00DB\u20AC\uDB\x40>'
print '<\x00>' print '<\x00>'
print '<\x01>' print '<\x01>'
print '<\u0000>' print '<\u0000>'
print '<\u0001>' print '<\u0001>'
} | {
# integer-base-one-3Ar
typeset -Uui16 -Z11 pos=0
typeset -Uui16 -Z5 hv=2147483647
dasc=
if read -arN -1 line; then
typeset -i1 line
i=0
while (( i < ${#line[*]} )); do
hv=${line[i++]}
if (( (pos & 15) == 0 )); then
(( pos )) && print "$dasc|"
print -n "${pos#16#} "
dasc=' |'
fi
print -n "${hv#16#} "
if (( (hv < 32) || (hv > 126) )); then
dasc=$dasc.
else
dasc=$dasc${line[i-1]#1#}
fi
(( (pos++ & 15) == 7 )) && print -n -- '- '
done
fi
while (( pos & 15 )); do
print -n ' '
(( (pos++ & 15) == 7 )) && print -n -- '- '
done
(( hv == 2147483647 )) || print "$dasc|"
}
expected-stdout: expected-stdout:
<d<><64>Û€Û@> 00000000 3C 64 E4 DB C3 9B E2 82 - AC C3 9B 40 3E 0A 3C 00 |<d.........@>.<.|
<> 00000010 3E 0A 3C 01 3E 0A 3C 00 - 3E 0A 3C 01 3E 0A |>.<.>.<.>.<.>.|
<>
<>
<>
--- ---
name: print-bksl-c name: print-bksl-c
description: description:
@ -11973,6 +12080,17 @@ expected-stdout:
while getopts a: optc; do while getopts a: optc; do
echo "OPTARG=$OPTARG, OPTIND=$OPTIND, optc=$optc." echo "OPTARG=$OPTARG, OPTIND=$OPTIND, optc=$optc."
done done
echo done
expected-stdout:
OPTARG=Mary, OPTIND=3, optc=a.
OPTARG=, OPTIND=4, optc=?.
done
expected-stderr-pattern: /.*-x.*option/
---
name: utilities-getopts-3
description:
Check unsetting OPTARG
stdin:
set -- -x arg -y set -- -x arg -y
getopts x:y opt && echo "${OPTARG-unset}" getopts x:y opt && echo "${OPTARG-unset}"
getopts x:y opt && echo "${OPTARG-unset}" getopts x:y opt && echo "${OPTARG-unset}"

76
eval.c
View File

@ -23,7 +23,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.187 2016/05/05 22:45:57 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/eval.c,v 1.189 2016/06/26 00:44:57 tg Exp $");
/* /*
* string expansion * string expansion
@ -1061,7 +1061,7 @@ varsub(Expand *xp, const char *sp, const char *word,
int c; int c;
int state; /* next state: XBASE, XARG, XSUB, XNULLSUB */ int state; /* next state: XBASE, XARG, XSUB, XNULLSUB */
int stype; /* substitution type */ int stype; /* substitution type */
int slen; int slen = 0;
const char *p; const char *p;
struct tbl *vp; struct tbl *vp;
bool zero_ok = false; bool zero_ok = false;
@ -1140,10 +1140,24 @@ varsub(Expand *xp, const char *sp, const char *word,
xp->str = shf_smprintf("%d", c); xp->str = shf_smprintf("%d", c);
return (XSUB); return (XSUB);
} }
if (stype == '!' && c != '\0' && *word == CSUBST) {
sp++;
if ((p = cstrchr(sp, '[')) && (p[1] == '*' || p[1] == '@') &&
p[2] == ']') {
c = '!';
stype = 0;
goto arraynames;
}
xp->var = global(sp);
xp->str = p ? shf_smprintf("%s[%lu]",
xp->var->name, arrayindex(xp->var)) : xp->var->name;
*stypep = 0;
return (XSUB);
}
/* Check for qualifiers in word part */ /* Check for qualifiers in word part */
stype = 0; stype = 0;
c = word[slen = 0] == CHAR ? word[1] : 0; c = word[slen + 0] == CHAR ? word[slen + 1] : 0;
if (c == ':') { if (c == ':') {
slen += 2; slen += 2;
stype = 0x80; stype = 0x80;
@ -1182,8 +1196,6 @@ varsub(Expand *xp, const char *sp, const char *word,
return (-1); return (-1);
if (!stype && *word != CSUBST) if (!stype && *word != CSUBST)
return (-1); return (-1);
*stypep = stype;
*slenp = slen;
c = sp[0]; c = sp[0];
if (c == '*' || c == '@') { if (c == '*' || c == '@') {
@ -1230,9 +1242,9 @@ varsub(Expand *xp, const char *sp, const char *word,
case 0x100 | 'Q': case 0x100 | 'Q':
return (-1); return (-1);
} }
c = 0;
arraynames:
XPinit(wv, 32); XPinit(wv, 32);
if ((c = sp[0]) == '!')
++sp;
vp = global(arrayname(sp)); vp = global(arrayname(sp));
for (; vp; vp = vp->u.array) { for (; vp; vp = vp->u.array) {
if (!(vp->flag&ISSET)) if (!(vp->flag&ISSET))
@ -1253,24 +1265,13 @@ varsub(Expand *xp, const char *sp, const char *word,
xp->split = tobool(p[1] == '@'); xp->split = tobool(p[1] == '@');
state = XARG; state = XARG;
} }
} else {
/* Can't assign things like $! or $1 */
if ((stype & 0x17F) == '=' &&
ctype(*sp, C_VAR1 | C_DIGIT))
return (-1);
if (*sp == '!' && sp[1] && !ctype(sp[1], C_VAR1)) {
++sp;
xp->var = global(sp);
if (vstrchr(sp, '['))
xp->str = shf_smprintf("%s[%lu]",
xp->var->name,
arrayindex(xp->var));
else
xp->str = xp->var->name;
} else { } else {
xp->var = global(sp); xp->var = global(sp);
xp->str = str_val(xp->var); xp->str = str_val(xp->var);
} /* can't assign things like $! or $1 */
if ((stype & 0x17F) == '=' && !*xp->str &&
ctype(*sp, C_VAR1 | C_DIGIT))
return (-1);
state = XSUB; state = XSUB;
} }
@ -1288,6 +1289,8 @@ varsub(Expand *xp, const char *sp, const char *word,
if (Flag(FNOUNSET) && xp->str == null && !zero_ok && if (Flag(FNOUNSET) && xp->str == null && !zero_ok &&
(ctype(c, C_SUBOP2) || (state != XBASE && c != '+'))) (ctype(c, C_SUBOP2) || (state != XBASE && c != '+')))
errorf("%s: parameter not set", sp); errorf("%s: parameter not set", sp);
*stypep = stype;
*slenp = slen;
return (state); return (state);
} }
@ -1323,14 +1326,31 @@ comsub(Expand *xp, const char *cp, int fn MKSH_A_UNUSED)
struct ioword *io = *t->ioact; struct ioword *io = *t->ioact;
char *name; char *name;
if ((io->ioflag & IOTYPE) != IOREAD) switch (io->ioflag & IOTYPE) {
case IOREAD:
shf = shf_open(name = evalstr(io->ioname, DOTILDE),
O_RDONLY, 0, SHF_MAPHI | SHF_CLEXEC);
if (shf == NULL)
warningf(!Flag(FTALKING), "%s: %s %s: %s",
name, "can't open", "$(<...) input",
cstrerror(errno));
break;
case IOHERE:
if (!herein(io, &name)) {
xp->str = name;
/* as $(…) requires, trim trailing newlines */
name += strlen(name);
while (name > xp->str && name[-1] == '\n')
--name;
*name = '\0';
return (XSUB);
}
shf = NULL;
break;
default:
errorf("%s: %s", T_funny_command, errorf("%s: %s", T_funny_command,
snptreef(NULL, 32, "%R", io)); snptreef(NULL, 32, "%R", io));
shf = shf_open(name = evalstr(io->ioname, DOTILDE), O_RDONLY, }
0, SHF_MAPHI | SHF_CLEXEC);
if (shf == NULL)
warningf(!Flag(FTALKING), "%s: %s %s: %s", name,
"can't open", "$(<...) input", cstrerror(errno));
} else if (fn == FUNSUB) { } else if (fn == FUNSUB) {
int ofd1; int ofd1;
struct temp *tf = NULL; struct temp *tf = NULL;

59
exec.c
View File

@ -23,7 +23,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/exec.c,v 1.173 2016/04/09 16:41:07 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/exec.c,v 1.175 2016/06/26 00:44:58 tg Exp $");
#ifndef MKSH_DEFAULT_EXECSHELL #ifndef MKSH_DEFAULT_EXECSHELL
#define MKSH_DEFAULT_EXECSHELL MKSH_UNIXROOT "/bin/sh" #define MKSH_DEFAULT_EXECSHELL MKSH_UNIXROOT "/bin/sh"
@ -34,7 +34,6 @@ static int comexec(struct op *, struct tbl * volatile, const char **,
static void scriptexec(struct op *, const char **) MKSH_A_NORETURN; static void scriptexec(struct op *, const char **) MKSH_A_NORETURN;
static int call_builtin(struct tbl *, const char **, const char *, bool); static int call_builtin(struct tbl *, const char **, const char *, bool);
static int iosetup(struct ioword *, struct tbl *); static int iosetup(struct ioword *, struct tbl *);
static int herein(struct ioword *, char **);
static const char *do_selectargs(const char **, bool); static const char *do_selectargs(const char **, bool);
static Test_op dbteste_isa(Test_env *, Test_meta); static Test_op dbteste_isa(Test_env *, Test_meta);
static const char *dbteste_getopnd(Test_env *, Test_op, bool); static const char *dbteste_getopnd(Test_env *, Test_op, bool);
@ -60,7 +59,6 @@ execute(struct op * volatile t,
const char *s, *ccp; const char *s, *ccp;
struct ioword **iowp; struct ioword **iowp;
struct tbl *tp = NULL; struct tbl *tp = NULL;
char *cp;
if (t == NULL) if (t == NULL)
return (0); return (0);
@ -79,11 +77,18 @@ execute(struct op * volatile t,
/* we want to run an executable, do some variance checks */ /* we want to run an executable, do some variance checks */
if (t->type == TCOM) { if (t->type == TCOM) {
/*
* Clear subst_exstat before argument expansion. Used by
* null commands (see comexec() and c_eval()) and by c_set().
*/
subst_exstat = 0;
/* for $LINENO */
current_lineno = t->lineno;
/* check if this is 'var=<<EOF' */ /* check if this is 'var=<<EOF' */
/*XXX this is broken, dont use! */
/*XXX https://bugs.launchpad.net/mksh/+bug/1380389 */
if ( if (
/* we have zero arguments, i.e. no programme to run */ /* we have zero arguments, i.e. no program to run */
t->args[0] == NULL && t->args[0] == NULL &&
/* we have exactly one variable assignment */ /* we have exactly one variable assignment */
t->vars[0] != NULL && t->vars[1] == NULL && t->vars[0] != NULL && t->vars[1] == NULL &&
@ -97,42 +102,20 @@ execute(struct op * volatile t,
/* and has no right-hand side (i.e. "varname=") */ /* and has no right-hand side (i.e. "varname=") */
ccp[0] == CHAR && ((ccp[1] == '=' && ccp[2] == EOS) || ccp[0] == CHAR && ((ccp[1] == '=' && ccp[2] == EOS) ||
/* or "varname+=" */ (ccp[1] == '+' && ccp[2] == CHAR && /* or "varname+=" */ (ccp[1] == '+' && ccp[2] == CHAR &&
ccp[3] == '=' && ccp[4] == EOS)) && ccp[3] == '=' && ccp[4] == EOS))) {
/* plus we can have a here document content */ char *cp, *dp;
herein(t->ioact[0], &cp) == 0 && cp && *cp) {
char *sp = cp, *dp;
size_t n = ccp - t->vars[0] + (ccp[1] == '+' ? 4 : 2);
size_t z;
/* drop redirection (will be garbage collected) */ if ((rv = herein(t->ioact[0], &cp) /*? 1 : 0*/))
t->ioact = NULL; cp = NULL;
dp = shf_smprintf("%s%s", evalstr(t->vars[0],
/* set variable to its expanded value */ DOASNTILDE | DOSCALAR), rv ? null : cp);
z = strlen(cp); typeset(dp, Flag(FEXPORT) ? EXPORT : 0, 0, 0, 0);
if (notoktomul(z, 2) || notoktoadd(z * 2, n + 1))
internal_errorf(Toomem, (size_t)-1);
dp = alloc(z * 2 + n + 1, APERM);
memcpy(dp, t->vars[0], n);
t->vars[0] = dp;
dp += n;
while (*sp) {
*dp++ = QCHAR;
*dp++ = *sp++;
}
*dp = EOS;
/* free the expanded value */ /* free the expanded value */
afree(cp, APERM); afree(cp, APERM);
afree(dp, ATEMP);
goto Break;
} }
/*
* Clear subst_exstat before argument expansion. Used by
* null commands (see comexec() and c_eval()) and by c_set().
*/
subst_exstat = 0;
/* for $LINENO */
current_lineno = t->lineno;
/* /*
* POSIX says expand command words first, then redirections, * POSIX says expand command words first, then redirections,
* and assignments last.. * and assignments last..
@ -1615,7 +1598,7 @@ hereinval(struct ioword *iop, int sub, char **resbuf, struct shf *shf)
return (0); return (0);
} }
static int int
herein(struct ioword *iop, char **resbuf) herein(struct ioword *iop, char **resbuf)
{ {
int fd = -1; int fd = -1;

37
funcs.c
View File

@ -38,7 +38,7 @@
#endif #endif
#endif #endif
__RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.295 2016/02/26 20:56:43 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/funcs.c,v 1.297 2016/06/26 00:44:25 tg Exp $");
#if HAVE_KILLPG #if HAVE_KILLPG
/* /*
@ -2004,21 +2004,21 @@ c_read(const char **wp)
} }
#endif #endif
bytesread = blocking_read(fd, xp, bytesleft); if ((bytesread = blocking_read(fd, xp, bytesleft)) == (size_t)-1) {
if (bytesread == (size_t)-1) { if (errno == EINTR) {
/* interrupted */ /* check whether the signal would normally kill */
if (errno == EINTR && fatal_trap_check()) { if (!fatal_trap_check()) {
/* /* no, just ignore the signal */
* Was the offending signal one that would goto c_read_readloop;
* normally kill a process? If so, pretend }
* the read was killed. /* pretend the read was killed */
*/ } else {
/* unexpected error */
bi_errorf("%s", cstrerror(errno));
}
rv = 2; rv = 2;
goto c_read_out; goto c_read_out;
} }
/* just ignore the signal */
goto c_read_readloop;
}
c_read_didread: c_read_didread:
switch (readmode) { switch (readmode) {
@ -2855,6 +2855,7 @@ c_test(const char **wp)
int argc, rv, invert = 0; int argc, rv, invert = 0;
Test_env te; Test_env te;
Test_op op; Test_op op;
Test_meta tm;
const char *lhs, **swp; const char *lhs, **swp;
te.flags = 0; te.flags = 0;
@ -2915,6 +2916,16 @@ c_test(const char **wp)
rv = test_eval(&te, op, lhs, *te.pos.wp++, true); rv = test_eval(&te, op, lhs, *te.pos.wp++, true);
goto ptest_out; goto ptest_out;
} }
if (ptest_isa(&te, tm = TM_AND) || ptest_isa(&te, tm = TM_OR)) {
/* XSI */
argc = test_eval(&te, TO_STNZE, lhs, NULL, true);
rv = test_eval(&te, TO_STNZE, *te.pos.wp++, NULL, true);
if (tm == TM_AND)
rv = argc && rv;
else
rv = argc || rv;
goto ptest_out;
}
/* back up to lhs */ /* back up to lhs */
te.pos.wp = swp; te.pos.wp = swp;
if (ptest_isa(&te, TM_NOT)) { if (ptest_isa(&te, TM_NOT)) {

43
lex.c
View File

@ -23,7 +23,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.224 2016/05/05 22:45:58 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/lex.c,v 1.225 2016/06/25 23:55:00 tg Exp $");
/* /*
* states while lexing word * states while lexing word
@ -1574,12 +1574,27 @@ get_brace_var(XString *wsp, char *wp)
c = getsc(); c = getsc();
/* State machine to figure out where the variable part ends. */ /* State machine to figure out where the variable part ends. */
switch (state) { switch (state) {
case PS_SAW_BANG: case PS_SAW_HASH:
if (ctype(c, C_VAR1)) if (ctype(c, C_VAR1)) {
goto out; char c2;
if (0) c2 = getsc();
/* FALLTHROUGH */ ungetsc(c2);
if (c2 != /*{*/ '}') {
ungetsc(c);
goto out;
}
}
goto ps_common;
case PS_SAW_BANG:
switch (c) {
case '@':
case '#':
case '-':
case '?':
goto out;
}
goto ps_common;
case PS_INITIAL: case PS_INITIAL:
switch (c) { switch (c) {
case '%': case '%':
@ -1594,24 +1609,12 @@ get_brace_var(XString *wsp, char *wp)
} }
/* FALLTHROUGH */ /* FALLTHROUGH */
case PS_SAW_PERCENT: case PS_SAW_PERCENT:
case PS_SAW_HASH: ps_common:
if (ksh_isalphx(c)) if (ksh_isalphx(c))
state = PS_IDENT; state = PS_IDENT;
else if (ksh_isdigit(c)) else if (ksh_isdigit(c))
state = PS_NUMBER; state = PS_NUMBER;
else if (c == '#') { else if (ctype(c, C_VAR1))
if (state == PS_SAW_HASH) {
char c2;
c2 = getsc();
ungetsc(c2);
if (c2 != /*{*/ '}') {
ungetsc(c);
goto out;
}
}
state = PS_VAR1;
} else if (ctype(c, C_VAR1))
state = PS_VAR1; state = PS_VAR1;
else else
goto out; goto out;

73
mksh.1
View File

@ -1,4 +1,4 @@
.\" $MirOS: src/bin/mksh/mksh.1,v 1.395 2016/05/05 22:45:58 tg Exp $ .\" $MirOS: src/bin/mksh/mksh.1,v 1.399 2016/06/25 23:52:06 tg Exp $
.\" $OpenBSD: ksh.1,v 1.160 2015/07/04 13:27:04 feinerer Exp $ .\" $OpenBSD: ksh.1,v 1.160 2015/07/04 13:27:04 feinerer Exp $
.\"- .\"-
.\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
@ -76,7 +76,7 @@
.\" with -mandoc, it might implement .Mx itself, but we want to .\" with -mandoc, it might implement .Mx itself, but we want to
.\" use our own definition. And .Dd must come *first*, always. .\" use our own definition. And .Dd must come *first*, always.
.\" .\"
.Dd $Mdocdate: May 5 2016 $ .Dd $Mdocdate: June 25 2016 $
.\" .\"
.\" Check which macro package we use, and do other -mdoc setup. .\" Check which macro package we use, and do other -mdoc setup.
.\" .\"
@ -2456,16 +2456,19 @@ This is called a here string.
Standard input is duplicated from file descriptor Standard input is duplicated from file descriptor
.Ar fd . .Ar fd .
.Ar fd .Ar fd
can be a number, indicating the number of an existing file descriptor; can be a single digit, indicating the number of an existing file descriptor;
the letter the letter
.Ql p , .Ql p ,
indicating the file descriptor associated with the output of the current indicating the file descriptor associated with the output of the current
co-process; or the character co-process; or the character
.Ql \- , .Ql \- ,
indicating standard input is to be closed. indicating standard input is to be closed.
Note that .Pp
.Ar fd Note that the current version of
is limited to a single digit in most shell implementations. .Nm
supports some two-digit fd numbers in some environments;
this feature is deprecated and will be removed from a subsequent release
in favour of a ksh93-/perl-style "named file descriptors" feature.
.It \*(Gt& Ns Ar fd .It \*(Gt& Ns Ar fd
Same as Same as
.Ic \*(Lt& , .Ic \*(Lt& ,
@ -2477,7 +2480,7 @@ This is a deprecated (legacy) GNU
.Nm bash .Nm bash
extension supported by extension supported by
.Nm .Nm
which also supports the preceding explicit fd number, for example, which also supports the preceding explicit fd digit, for example,
.Ic 3&\*(Gt Ns Ar file .Ic 3&\*(Gt Ns Ar file
is the same as is the same as
.Ic 3\*(Gt Ns Ar file 2\*(Gt&3 .Ic 3\*(Gt Ns Ar file 2\*(Gt&3
@ -2506,7 +2509,7 @@ extensions.
In any of the above redirections, the file descriptor that is redirected In any of the above redirections, the file descriptor that is redirected
(i.e. standard input or standard output) (i.e. standard input or standard output)
can be explicitly given by preceding the can be explicitly given by preceding the
redirection with a number (portably, only a single digit). redirection with a single digit.
Parameter, command, and arithmetic Parameter, command, and arithmetic
substitutions, tilde substitutions, and (if the shell is interactive) substitutions, tilde substitutions, and (if the shell is interactive)
file name generation are all performed on the file name generation are all performed on the
@ -2723,9 +2726,15 @@ Rotate left (right); the result is similar to shift (see
.Ic \*(Lt\*(Lt ) .Ic \*(Lt\*(Lt )
except that the bits shifted out at one end are shifted in except that the bits shifted out at one end are shifted in
at the other end, instead of zero or sign bits. at the other end, instead of zero or sign bits.
.Pp
.Em Note :
These operators are deprecated; in a subsequent mksh release,
.Ic \*(ha\*(Lt No and Ic \*(ha\*(Gt
.No will replace them, and Ic \*(Gt\*(Gt\*(Gt
will be an arithmetic right shift.
.It \*(Lt\*(Lt \*(Gt\*(Gt .It \*(Lt\*(Lt \*(Gt\*(Gt
Shift left (right); the result is the left argument with its bits shifted left Shift left (right); the result is the left argument with its bits logically
(right) by the amount given in the right argument. shifted left (right) by the amount given in the right argument.
.It + \- * / .It + \- * /
Addition, subtraction, multiplication, and division. Addition, subtraction, multiplication, and division.
.It % .It %
@ -5445,16 +5454,20 @@ Note that editing command names are used only with the
command. command.
Furthermore, many editing commands are useful only on terminals with Furthermore, many editing commands are useful only on terminals with
a visible cursor. a visible cursor.
The default bindings were chosen to resemble corresponding
Emacs key bindings.
The user's The user's
.Xr tty 4 .Xr tty 4
characters (e.g.\& characters (e.g.\&
.Dv ERASE ) .Dv ERASE )
are bound to are bound to
reasonable substitutes and override the default bindings. reasonable substitutes and override the default bindings;
their customary values are shown in parentheses below.
The default bindings were chosen to resemble corresponding
Emacs key bindings:
.Bl -tag -width Ds .Bl -tag -width Ds
.It abort: \*(haC, \*(haG .It Xo abort:
.No INTR Pq \*(haC ,
.No \*(haG
.Xc
Abort the current command, empty the line buffer and Abort the current command, empty the line buffer and
set the exit state to interrupted. set the exit state to interrupted.
.It auto\-insert: Op Ar n .It auto\-insert: Op Ar n
@ -5528,7 +5541,8 @@ command above.
Note that \*(haI is usually generated by the TAB (tabulator) key. Note that \*(haI is usually generated by the TAB (tabulator) key.
.It Xo delete\-char\-backward: .It Xo delete\-char\-backward:
.Op Ar n .Op Ar n
.No ERASE , \*(ha? , \*(haH .No ERASE Pq \*(haH ,
.No \*(ha? , \*(haH
.Xc .Xc
Deletes Deletes
.Ar n .Ar n
@ -5542,7 +5556,9 @@ Deletes
characters after the cursor. characters after the cursor.
.It Xo delete\-word\-backward: .It Xo delete\-word\-backward:
.Op Ar n .Op Ar n
.No WERASE , \*(ha[\*(ha? , \*(ha[\*(haH , \*(ha[h .No Pfx1+ERASE Pq \*(ha[\*(haH ,
.No WERASE Pq \*(haW ,
.No \*(ha[\*(ha? , \*(ha[\*(haH , \*(ha[h
.Xc .Xc
Deletes Deletes
.Ar n .Ar n
@ -5592,14 +5608,14 @@ Moves to the end of the history.
Moves the cursor to the end of the input line. Moves the cursor to the end of the input line.
.It eot: \*(ha_ .It eot: \*(ha_
Acts as an end-of-file; this is useful because edit-mode input disables Acts as an end-of-file; this is useful because edit-mode input disables
normal terminal input canonicalization. normal terminal input canonicalisation.
.It Xo eot\-or\-delete: .It Xo eot\-or\-delete:
.Op Ar n .Op Ar n
.No \*(haD .No EOF Pq \*(haD
.Xc .Xc
Acts as If alone on a line, same as
.Ic eot .Ic eot ,
if alone on a line; otherwise acts as otherwise,
.Ic delete\-char\-forward . .Ic delete\-char\-forward .
.It error: (not bound) .It error: (not bound)
Error (ring the bell). Error (ring the bell).
@ -5631,8 +5647,13 @@ word.
.Xc .Xc
Goes to history number Goes to history number
.Ar n . .Ar n .
.It kill\-line: KILL .It Xo kill\-line:
.No KILL Pq \*(haU
.Xc
Deletes the entire input line. Deletes the entire input line.
If Ctrl-U should only delete the line up to the cursor, use:
.Pp
.D1 $ bind \-m \*(haU='\*(ha[0\*(haK'
.It kill\-region: \*(haW .It kill\-region: \*(haW
Deletes the input between the cursor and the mark. Deletes the input between the cursor and the mark.
.It Xo kill\-to\-eol: .It Xo kill\-to\-eol:
@ -5669,12 +5690,14 @@ This is only useful after an
.Ic search\-history .Ic search\-history
or or
.Ic search\-history\-up . .Ic search\-history\-up .
.It no\-op: QUIT .It Xo no\-op:
.No QUIT Pq \*(ha\e
.Xc
This does nothing. This does nothing.
.It prefix\-1: \*(ha[ .It prefix\-1: \*(ha[
Introduces a 2-character command sequence. Introduces a 2-character command sequence.
.It prefix\-2: \*(haX , \*(ha[[ , \*(ha[O .It prefix\-2: \*(haX , \*(ha[[ , \*(ha[O
Introduces a 2-character command sequence. Introduces a multi-character command sequence.
.It Xo prev\-hist\-word: .It Xo prev\-hist\-word:
.Op Ar n .Op Ar n
.No \*(ha[. , \*(ha[_ .No \*(ha[. , \*(ha[_
@ -6589,7 +6612,7 @@ for the in-memory portion of the history is slow, should use
.Xr memmove 3 . .Xr memmove 3 .
.Pp .Pp
This document attempts to describe This document attempts to describe
.Nm mksh\ R52c+CVS .Nm mksh\ R52d
and up, and up,
.\" with vendor patches from insert-your-name-here, .\" with vendor patches from insert-your-name-here,
compiled without any options impacting functionality, such as compiled without any options impacting functionality, such as

9
sh.h
View File

@ -175,9 +175,9 @@
#endif #endif
#ifdef EXTERN #ifdef EXTERN
__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.771 2016/05/05 22:56:14 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/sh.h,v 1.774 2016/06/26 00:44:59 tg Exp $");
#endif #endif
#define MKSH_VERSION "R52 2016/05/05" #define MKSH_VERSION "R52 2016/06/25"
/* arithmetic types: C implementation */ /* arithmetic types: C implementation */
#if !HAVE_CAN_INTTYPES #if !HAVE_CAN_INTTYPES
@ -1082,7 +1082,11 @@ EXTERN bool builtin_spec;
EXTERN char *current_wd; EXTERN char *current_wd;
/* input line size */ /* input line size */
#ifdef MKSH_SMALL
#define LINE (4096 - ALLOC_OVERHEAD) #define LINE (4096 - ALLOC_OVERHEAD)
#else
#define LINE (16384 - ALLOC_OVERHEAD)
#endif
/* /*
* Minimum required space to work with on a line - if the prompt leaves * Minimum required space to work with on a line - if the prompt leaves
* less space than this on a line, the prompt is truncated. * less space than this on a line, the prompt is truncated.
@ -1748,6 +1752,7 @@ int search_access(const char *, int);
const char *search_path(const char *, const char *, int, int *); const char *search_path(const char *, const char *, int, int *);
void pr_menu(const char * const *); void pr_menu(const char * const *);
void pr_list(char * const *); void pr_list(char * const *);
int herein(struct ioword *, char **);
/* expr.c */ /* expr.c */
int evaluate(const char *, mksh_ari_t *, int, bool); int evaluate(const char *, mksh_ari_t *, int, bool);
int v_evaluate(struct tbl *, const char *, volatile int, bool); int v_evaluate(struct tbl *, const char *, volatile int, bool);

7
shf.c
View File

@ -25,7 +25,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/shf.c,v 1.73 2016/05/05 22:56:15 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/shf.c,v 1.74 2016/05/17 15:36:35 tg Exp $");
/* flags to shf_emptybuf() */ /* flags to shf_emptybuf() */
#define EB_READSW 0x01 /* about to switch to reading */ #define EB_READSW 0x01 /* about to switch to reading */
@ -774,7 +774,7 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
size_t field, precision, len; size_t field, precision, len;
unsigned long lnum; unsigned long lnum;
/* %#o produces the longest output */ /* %#o produces the longest output */
char numbuf[(8 * sizeof(long) + 2) / 3 + 1]; char numbuf[(8 * sizeof(long) + 2) / 3 + 1 + /* NUL */ 1];
/* this stuff for dealing with the buffer */ /* this stuff for dealing with the buffer */
ssize_t nwritten = 0; ssize_t nwritten = 0;
@ -914,6 +914,7 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
integral: integral:
flags |= FL_NUMBER; flags |= FL_NUMBER;
cp = numbuf + sizeof(numbuf); cp = numbuf + sizeof(numbuf);
*--cp = '\0';
switch (c) { switch (c) {
case 'd': case 'd':
@ -964,7 +965,7 @@ shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
} }
} }
} }
len = numbuf + sizeof(numbuf) - (s = cp); len = numbuf + sizeof(numbuf) - 1 - (s = cp);
if (flags & FL_DOT) { if (flags & FL_DOT) {
if (precision > len) { if (precision > len) {
field = precision; field = precision;

50
tree.c
View File

@ -23,7 +23,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.83 2016/03/04 14:26:16 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/tree.c,v 1.85 2016/06/26 00:09:35 tg Exp $");
#define INDENT 8 #define INDENT 8
@ -49,6 +49,7 @@ ptree(struct op *t, int indent, struct shf *shf)
struct ioword **ioact; struct ioword **ioact;
struct op *t1; struct op *t1;
int i; int i;
const char *ccp;
Chain: Chain:
if (t == NULL) if (t == NULL)
@ -56,12 +57,9 @@ ptree(struct op *t, int indent, struct shf *shf)
switch (t->type) { switch (t->type) {
case TCOM: case TCOM:
prevent_semicolon = false; prevent_semicolon = false;
/* /* special-case 'var=<<EOF' (cf. exec.c:execute) */
* special-case 'var=<<EOF' (rough; see
* exec.c:execute() for full code)
*/
if ( if (
/* we have zero arguments, i.e. no programme to run */ /* we have zero arguments, i.e. no program to run */
t->args[0] == NULL && t->args[0] == NULL &&
/* we have exactly one variable assignment */ /* we have exactly one variable assignment */
t->vars[0] != NULL && t->vars[1] == NULL && t->vars[0] != NULL && t->vars[1] == NULL &&
@ -69,7 +67,13 @@ ptree(struct op *t, int indent, struct shf *shf)
t->ioact != NULL && t->ioact[0] != NULL && t->ioact != NULL && t->ioact[0] != NULL &&
t->ioact[1] == NULL && t->ioact[1] == NULL &&
/* of type "here document" (or "here string") */ /* of type "here document" (or "here string") */
(t->ioact[0]->ioflag & IOTYPE) == IOHERE) { (t->ioact[0]->ioflag & IOTYPE) == IOHERE &&
/* the variable assignment begins with a valid varname */
(ccp = skip_wdvarname(t->vars[0], true)) != t->vars[0] &&
/* and has no right-hand side (i.e. "varname=") */
ccp[0] == CHAR && ((ccp[1] == '=' && ccp[2] == EOS) ||
/* or "varname+=" */ (ccp[1] == '+' && ccp[2] == CHAR &&
ccp[3] == '=' && ccp[4] == EOS))) {
fptreef(shf, indent, "%S", t->vars[0]); fptreef(shf, indent, "%S", t->vars[0]);
break; break;
} }
@ -322,26 +326,34 @@ wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
++wp; ++wp;
goto wdvarput_csubst; goto wdvarput_csubst;
} }
/* FALLTHROUGH */
case CHAR: case CHAR:
c = *wp++; c = *wp++;
shf_putc(c, shf); shf_putc(c, shf);
break; break;
case QCHAR: { case QCHAR:
bool doq;
c = *wp++; c = *wp++;
doq = (c == '"' || c == '`' || c == '$' || c == '\\'); if (opmode & WDS_TPUTS)
if (opmode & WDS_TPUTS) { switch (c) {
if (quotelevel == 0) case '\n':
doq = true; if (quotelevel == 0) {
} else { c = '\'';
doq = false;
}
if (doq)
shf_putc('\\', shf);
shf_putc(c, shf); shf_putc(c, shf);
shf_putc('\n', shf);
}
break;
default:
if (quotelevel == 0)
/* FALLTHROUGH */
case '"':
case '`':
case '$':
case '\\':
shf_putc('\\', shf);
break; break;
} }
shf_putc(c, shf);
break;
case COMSUB: case COMSUB:
shf_puts("$(", shf); shf_puts("$(", shf);
cs = ")"; cs = ")";