aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2025-09-06 14:52:34 +0100
committerRon Yorston <rmy@pobox.com>2025-09-06 14:52:34 +0100
commit01fbdbf4b48cedf0869e010ef5ccb817d6677e42 (patch)
treec7a7819b76621b206d3d3aaf0b7ceb08d02ca853 /shell
parentd8086da8bfbf76b9910d04e3e7f646ebc7f4b593 (diff)
parent53b3854e8141f4fc5fad10f180fc4fac2feee954 (diff)
downloadbusybox-w32-01fbdbf4b48cedf0869e010ef5ccb817d6677e42.tar.gz
busybox-w32-01fbdbf4b48cedf0869e010ef5ccb817d6677e42.tar.bz2
busybox-w32-01fbdbf4b48cedf0869e010ef5ccb817d6677e42.zip
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
-rw-r--r--shell/ash.c165
-rw-r--r--shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.right1
-rwxr-xr-xshell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.tests4
-rw-r--r--shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.right1
-rwxr-xr-xshell/ash_test/ash-heredoc/heredoc_bkslash_newline3.tests4
-rw-r--r--shell/ash_test/ash-heredoc/heredoc_bkslash_newline3a.right1
-rwxr-xr-xshell/ash_test/ash-heredoc/heredoc_bkslash_newline3a.tests4
-rw-r--r--shell/ash_test/ash-vars/var_backslash1.right26
-rwxr-xr-xshell/ash_test/ash-vars/var_backslash1.tests38
-rw-r--r--shell/hush.c42
-rw-r--r--shell/hush_test/hush-bugs/var_backslash1.right26
-rwxr-xr-xshell/hush_test/hush-bugs/var_backslash1.tests38
-rw-r--r--shell/hush_test/hush-heredoc/heredoc_bkslash_newline2a.right1
-rwxr-xr-xshell/hush_test/hush-heredoc/heredoc_bkslash_newline2a.tests4
-rw-r--r--shell/hush_test/hush-heredoc/heredoc_bkslash_newline3.right1
-rwxr-xr-xshell/hush_test/hush-heredoc/heredoc_bkslash_newline3.tests4
-rw-r--r--shell/hush_test/hush-heredoc/heredoc_bkslash_newline3a.right1
-rwxr-xr-xshell/hush_test/hush-heredoc/heredoc_bkslash_newline3a.tests4
18 files changed, 290 insertions, 75 deletions
diff --git a/shell/ash.c b/shell/ash.c
index 605215e41..656f3d73b 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -1078,28 +1078,47 @@ out2str(const char *p)
1078# define CTL_LAST CTLFROMPROC 1078# define CTL_LAST CTLFROMPROC
1079#endif 1079#endif
1080 1080
1081/* variable substitution byte (follows CTLVAR) */ 1081/* ${VAR[ops]} encoding is CTLVAR,<type_byte>,"VARNAME=",<ops_encoded(details?)>,CTLENDVAR */
1082#define VSTYPE 0x0f /* type of variable substitution */ 1082/* variable type byte (follows CTLVAR) */
1083#define VSNUL 0x10 /* colon--treat the empty string as unset */ 1083#define VSTYPE 0x0f /* type of variable substitution */
1084 1084#define VSNUL 0x10 /* colon: the op is one of :- :+ :? := */
1085/* values of VSTYPE field */ 1085/* values of VSTYPE field. The first 5 must be in this order, "}-+?=" string is used elsewhere to index into them */
1086#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ 1086#define VSNORMAL 0x1 /* $var or ${var} */
1087#define VSMINUS 0x2 /* ${var-text} */ 1087#define VSMINUS 0x2 /* ${var[:]-text} */
1088#define VSPLUS 0x3 /* ${var+text} */ 1088#define VSPLUS 0x3 /* ${var[:]+text} */
1089#define VSQUESTION 0x4 /* ${var?message} */ 1089#define VSQUESTION 0x4 /* ${var[:]?message} */
1090#define VSASSIGN 0x5 /* ${var=text} */ 1090#define VSASSIGN 0x5 /* ${var[:]=text} */
1091#define VSTRIMRIGHT 0x6 /* ${var%pattern} */ 1091#define VSTRIMRIGHT 0x6 /* ${var%pattern} */
1092#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ 1092#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */
1093#define VSTRIMLEFT 0x8 /* ${var#pattern} */ 1093#define VSTRIMLEFT 0x8 /* ${var#pattern} */
1094#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ 1094#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */
1095#define VSLENGTH 0xa /* ${#var} */ 1095#define VSLENGTH 0xa /* ${#var} */
1096#if BASH_SUBSTR 1096#if BASH_SUBSTR
1097#define VSSUBSTR 0xc /* ${var:position:length} */ 1097#define VSSUBSTR 0xb /* ${var:position:length} */
1098#endif 1098#endif
1099#if BASH_PATTERN_SUBST 1099#if BASH_PATTERN_SUBST
1100#define VSREPLACE 0xd /* ${var/pattern/replacement} */ 1100#define VSREPLACE 0xc /* ${var/pattern/replacement} */
1101#define VSREPLACEALL 0xe /* ${var//pattern/replacement} */ 1101#define VSREPLACEALL 0xd /* ${var//pattern/replacement} */
1102#endif
1103static const char vstype_suffix[][3] ALIGN1 = {
1104 [VSNORMAL - VSNORMAL] = "}", // $var or ${var}
1105 [VSMINUS - VSNORMAL] = "-", // ${var-text}
1106 [VSPLUS - VSNORMAL] = "+", // ${var+text}
1107 [VSQUESTION - VSNORMAL] = "?", // ${var?message}
1108 [VSASSIGN - VSNORMAL] = "=", // ${var=text}
1109 [VSTRIMRIGHT - VSNORMAL] = "%", // ${var%pattern}
1110 [VSTRIMRIGHTMAX - VSNORMAL] = "%%",// ${var%%pattern}
1111 [VSTRIMLEFT - VSNORMAL] = "#", // ${var#pattern}
1112 [VSTRIMLEFTMAX - VSNORMAL] = "##",// ${var##pattern}
1113 [VSLENGTH - VSNORMAL] = "", // ${#var}
1114#if BASH_SUBSTR
1115 [VSSUBSTR - VSNORMAL] = ":", // ${var:position:length}
1116#endif
1117#if BASH_PATTERN_SUBST
1118 [VSREPLACE - VSNORMAL] = "/", // ${var/pattern/replacement}
1119 [VSREPLACEALL - VSNORMAL] = "//",// ${var//pattern/replacement}
1102#endif 1120#endif
1121};
1103 1122
1104static const char dolatstr[] ALIGN1 = { 1123static const char dolatstr[] ALIGN1 = {
1105 CTLQUOTEMARK, CTLVAR, VSNORMAL, '@', '=', CTLQUOTEMARK, '\0' 1124 CTLQUOTEMARK, CTLVAR, VSNORMAL, '@', '=', CTLQUOTEMARK, '\0'
@@ -1473,7 +1492,9 @@ sharg(union node *arg, FILE *fp)
1473 1492
1474 if (subtype & VSNUL) 1493 if (subtype & VSNUL)
1475 putc(':', fp); 1494 putc(':', fp);
1476 1495#if 1
1496 fputs(vstype_suffix[(subtype & VSTYPE) - VSNORMAL], fp);
1497#else
1477 switch (subtype & VSTYPE) { 1498 switch (subtype & VSTYPE) {
1478 case VSNORMAL: 1499 case VSNORMAL:
1479 putc('}', fp); 1500 putc('}', fp);
@@ -1509,6 +1530,7 @@ sharg(union node *arg, FILE *fp)
1509 default: 1530 default:
1510 out1fmt("<subtype %d>", subtype); 1531 out1fmt("<subtype %d>", subtype);
1511 } 1532 }
1533#endif
1512 break; 1534 break;
1513 case CTLENDVAR: 1535 case CTLENDVAR:
1514 putc('}', fp); 1536 putc('}', fp);
@@ -1649,13 +1671,14 @@ showtree(union node *n)
1649static void 1671static void
1650ash_vmsg(const char *msg, va_list ap) 1672ash_vmsg(const char *msg, va_list ap)
1651{ 1673{
1674//In dash, the order/format is different:
1675// arg0: LINENO: [commandname:] MSG
1676//If you fix it, change testsuite to match
1652 fprintf(stderr, "%s: ", arg0); 1677 fprintf(stderr, "%s: ", arg0);
1653 if (commandname) { 1678 if (commandname && strcmp(arg0, commandname) != 0)
1654 if (strcmp(arg0, commandname)) 1679 fprintf(stderr, "%s: ", commandname);
1655 fprintf(stderr, "%s: ", commandname); 1680 if (!iflag || g_parsefile->pf_fd > 0)
1656 if (!iflag || g_parsefile->pf_fd > 0) 1681 fprintf(stderr, "line %d: ", errlinno);
1657 fprintf(stderr, "line %d: ", errlinno);
1658 }
1659 vfprintf(stderr, msg, ap); 1682 vfprintf(stderr, msg, ap);
1660 newline_and_flush(stderr); 1683 newline_and_flush(stderr);
1661} 1684}
@@ -4224,7 +4247,7 @@ struct job {
4224 struct job *prev_job; /* previous job */ 4247 struct job *prev_job; /* previous job */
4225}; 4248};
4226 4249
4227static struct job *makejob(/*union node *,*/ int); 4250static struct job *makejob(int);
4228#if !ENABLE_PLATFORM_MINGW32 4251#if !ENABLE_PLATFORM_MINGW32
4229static int forkshell(struct job *, union node *, int); 4252static int forkshell(struct job *, union node *, int);
4230#endif 4253#endif
@@ -5475,7 +5498,7 @@ growjobtab(void)
5475 * Called with interrupts off. 5498 * Called with interrupts off.
5476 */ 5499 */
5477static struct job * 5500static struct job *
5478makejob(/*union node *node,*/ int nprocs) 5501makejob(int nprocs)
5479{ 5502{
5480 int i; 5503 int i;
5481 struct job *jp; 5504 struct job *jp;
@@ -5525,13 +5548,6 @@ static char *cmdnextc;
5525static void 5548static void
5526cmdputs(const char *s) 5549cmdputs(const char *s)
5527{ 5550{
5528 static const char vstype[VSTYPE + 1][3] ALIGN1 = {
5529 "", "}", "-", "+", "?", "=",
5530 "%", "%%", "#", "##"
5531 IF_BASH_SUBSTR(, ":")
5532 IF_BASH_PATTERN_SUBST(, "/", "//")
5533 };
5534
5535 const char *p, *str; 5551 const char *p, *str;
5536 char cc[2]; 5552 char cc[2];
5537 char *nextc; 5553 char *nextc;
@@ -5594,32 +5610,34 @@ cmdputs(const char *s)
5594 case '=': 5610 case '=':
5595 if (subtype == 0) 5611 if (subtype == 0)
5596 break; 5612 break;
5613 /* We are in variable name */
5597 if ((subtype & VSTYPE) != VSNORMAL) 5614 if ((subtype & VSTYPE) != VSNORMAL)
5598 quoted <<= 1; 5615 quoted <<= 1;
5599 str = vstype[subtype & VSTYPE]; 5616 str = vstype_suffix[(subtype & VSTYPE) - VSNORMAL];
5600 if (subtype & VSNUL) 5617 if (!(subtype & VSNUL))
5601 c = ':'; 5618 goto dostr;
5602 else 5619 c = ':';
5603 goto checkstr;
5604 break; 5620 break;
5605 case '\'': 5621 case '$':
5622 /* Can happen inside quotes, or in variable name $$ */
5623 if (subtype != 0)
5624 // Testcase:
5625 // $ true $$ &
5626 // $ <cr>
5627 // [1]+ Done true ${$} // shows ${\$} without "if (subtype)" check
5628 break;
5629 /* Not in variable name - show as \$ */
5630 case '\'': /* These can only happen inside quotes */
5606 case '\\': 5631 case '\\':
5607 case '"': 5632 case '"':
5608 case '$':
5609 /* These can only happen inside quotes */
5610 cc[0] = c; 5633 cc[0] = c;
5611 str = cc; 5634 str = cc;
5612//FIXME:
5613// $ true $$ &
5614// $ <cr>
5615// [1]+ Done true ${\$} <<=== BUG: ${\$} is not a valid way to write $$ (${$} would be ok)
5616 c = '\\'; 5635 c = '\\';
5617 break; 5636 break;
5618 default: 5637 default:
5619 break; 5638 break;
5620 } 5639 }
5621 USTPUTC(c, nextc); 5640 USTPUTC(c, nextc);
5622 checkstr:
5623 if (!str) 5641 if (!str)
5624 continue; 5642 continue;
5625 dostr: 5643 dostr:
@@ -5631,7 +5649,7 @@ cmdputs(const char *s)
5631 if (quoted & 1) { 5649 if (quoted & 1) {
5632 USTPUTC('"', nextc); 5650 USTPUTC('"', nextc);
5633 } 5651 }
5634 *nextc = 0; 5652 *nextc = '\0';
5635 cmdnextc = nextc; 5653 cmdnextc = nextc;
5636} 5654}
5637 5655
@@ -7392,7 +7410,7 @@ evalbackcmd(union node *n, struct backcmd *result
7392 if (pipe(pip) < 0) 7410 if (pipe(pip) < 0)
7393 ash_msg_and_raise_perror("can't create pipe"); 7411 ash_msg_and_raise_perror("can't create pipe");
7394 /* process substitution uses NULL job, like openhere() */ 7412 /* process substitution uses NULL job, like openhere() */
7395 jp = (ctl == CTLBACKQ) ? makejob(/*n,*/ 1) : NULL; 7413 jp = (ctl == CTLBACKQ) ? makejob(1) : NULL;
7396#if ENABLE_PLATFORM_MINGW32 7414#if ENABLE_PLATFORM_MINGW32
7397 memset(&fs, 0, sizeof(fs)); 7415 memset(&fs, 0, sizeof(fs));
7398 fs.fpid = FS_EVALBACKCMD; 7416 fs.fpid = FS_EVALBACKCMD;
@@ -10462,7 +10480,7 @@ evaltree(union node *n, int flags)
10462#endif 10480#endif
10463 case NNOT: 10481 case NNOT:
10464 status = !evaltree(n->nnot.com, EV_TESTED); 10482 status = !evaltree(n->nnot.com, EV_TESTED);
10465 goto setstatus; 10483 break;
10466 case NREDIR: 10484 case NREDIR:
10467 errlinno = lineno = n->nredir.linno; 10485 errlinno = lineno = n->nredir.linno;
10468 expredir(n->nredir.redirect); 10486 expredir(n->nredir.redirect);
@@ -10473,7 +10491,7 @@ evaltree(union node *n, int flags)
10473 } 10491 }
10474 if (n->nredir.redirect) 10492 if (n->nredir.redirect)
10475 popredir(/*drop:*/ 0); 10493 popredir(/*drop:*/ 0);
10476 goto setstatus; 10494 break;
10477 case NCMD: 10495 case NCMD:
10478 evalfn = evalcommand; 10496 evalfn = evalcommand;
10479 checkexit: 10497 checkexit:
@@ -10517,7 +10535,7 @@ evaltree(union node *n, int flags)
10517 evalfn = evaltree; 10535 evalfn = evaltree;
10518 calleval: 10536 calleval:
10519 status = evalfn(n, flags); 10537 status = evalfn(n, flags);
10520 goto setstatus; 10538 break;
10521 } 10539 }
10522 case NIF: 10540 case NIF:
10523 status = evaltree(n->nif.test, EV_TESTED); 10541 status = evaltree(n->nif.test, EV_TESTED);
@@ -10531,17 +10549,18 @@ evaltree(union node *n, int flags)
10531 goto evaln; 10549 goto evaln;
10532 } 10550 }
10533 status = 0; 10551 status = 0;
10534 goto setstatus; 10552 break;
10535 case NDEFUN: 10553 case NDEFUN:
10536 defun(n); 10554 defun(n);
10537 /* Not necessary. To test it: 10555 /* Not necessary. To test it:
10538 * "false; f() { qwerty; }; echo $?" should print 0. 10556 * "false; f() { qwerty; }; echo $?" should print 0.
10539 */ 10557 */
10540 /* status = 0; */ 10558 /* status = 0; */
10541 setstatus:
10542 exitstatus = status;
10543 break; 10559 break;
10544 } 10560 }
10561
10562 exitstatus = status;
10563
10545 out: 10564 out:
10546 /* Order of checks below is important: 10565 /* Order of checks below is important:
10547 * signal handlers trigger before exit caused by "set -e". 10566 * signal handlers trigger before exit caused by "set -e".
@@ -10723,7 +10742,7 @@ evalsubshell(union node *n, int flags)
10723 INT_OFF; 10742 INT_OFF;
10724 if (backgnd == FORK_FG) 10743 if (backgnd == FORK_FG)
10725 get_tty_state(); 10744 get_tty_state();
10726 jp = makejob(/*n,*/ 1); 10745 jp = makejob(1);
10727#if ENABLE_PLATFORM_MINGW32 10746#if ENABLE_PLATFORM_MINGW32
10728 memset(&fs, 0, sizeof(fs)); 10747 memset(&fs, 0, sizeof(fs));
10729 fs.fpid = FS_EVALSUBSHELL; 10748 fs.fpid = FS_EVALSUBSHELL;
@@ -10839,7 +10858,7 @@ evalpipe(union node *n, int flags)
10839 INT_OFF; 10858 INT_OFF;
10840 if (n->npipe.pipe_backgnd == 0) 10859 if (n->npipe.pipe_backgnd == 0)
10841 get_tty_state(); 10860 get_tty_state();
10842 jp = makejob(/*n,*/ pipelen); 10861 jp = makejob(pipelen);
10843 prevfd = -1; 10862 prevfd = -1;
10844 for (lp = n->npipe.cmdlist; lp; lp = lp->next) { 10863 for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
10845 prehash(lp->n); 10864 prehash(lp->n);
@@ -11822,7 +11841,7 @@ evalcommand(union node *cmd, int flags)
11822 /* No, forking off a child is necessary */ 11841 /* No, forking off a child is necessary */
11823 INT_OFF; 11842 INT_OFF;
11824 get_tty_state(); 11843 get_tty_state();
11825 jp = makejob(/*cmd,*/ 1); 11844 jp = makejob(1);
11826 if (forkshell(jp, cmd, FORK_FG) != 0) { 11845 if (forkshell(jp, cmd, FORK_FG) != 0) {
11827 /* parent */ 11846 /* parent */
11828 break; 11847 break;
@@ -13779,12 +13798,12 @@ decode_dollar_squote(void)
13779#endif 13798#endif
13780 13799
13781/* Used by expandstr to get here-doc like behaviour. */ 13800/* Used by expandstr to get here-doc like behaviour. */
13782#define FAKEEOFMARK ((char*)(uintptr_t)1) 13801#define FAKEEOFMARK ((struct heredoc*)(uintptr_t)1)
13783 13802
13784static ALWAYS_INLINE int 13803static ALWAYS_INLINE int
13785realeofmark(const char *eofmark) 13804realeofmark(struct heredoc *here)
13786{ 13805{
13787 return eofmark && eofmark != FAKEEOFMARK; 13806 return here && here != FAKEEOFMARK;
13788} 13807}
13789 13808
13790/* 13809/*
@@ -13806,7 +13825,7 @@ realeofmark(const char *eofmark)
13806#define PARSEPROCSUB() {style = PSUB; goto parsebackq; parsebackq_psreturn:;} 13825#define PARSEPROCSUB() {style = PSUB; goto parsebackq; parsebackq_psreturn:;}
13807#define PARSEARITH() {goto parsearith; parsearith_return:;} 13826#define PARSEARITH() {goto parsearith; parsearith_return:;}
13808static int 13827static int
13809readtoken1(int c, int syntax, char *eofmark, int striptabs) 13828readtoken1(int c, int syntax, struct heredoc *eofmark)
13810{ 13829{
13811 /* NB: syntax parameter fits into smallint */ 13830 /* NB: syntax parameter fits into smallint */
13812 /* c parameter is an unsigned char or PEOF */ 13831 /* c parameter is an unsigned char or PEOF */
@@ -14058,23 +14077,30 @@ checkend: {
14058 int markloc; 14077 int markloc;
14059 char *p; 14078 char *p;
14060 14079
14061 if (striptabs) { 14080 if (eofmark->striptabs) {
14062 while (c == '\t') 14081 while (c == '\t')
14063 c = pgetc(); 14082 if (eofmark->here->type == NHERE)
14083 c = pgetc(); /* dash always does pgetc() */
14084 else /* NXHERE */
14085 c = pgetc_eatbnl();
14086 /* (see heredoc_bkslash_newline3a.tests) */
14064 } 14087 }
14065 14088
14066 markloc = out - (char *)stackblock(); 14089 markloc = out - (char *)stackblock();
14067 for (p = eofmark; STPUTC(c, out), *p; p++) { 14090 for (p = eofmark->eofmark; STPUTC(c, out), *p; p++) {
14068 if (c != *p) 14091 if (c != *p)
14069 goto more_heredoc; 14092 goto more_heredoc;
14070 /* FIXME: fails for backslash-newlined terminator: 14093 /* dash still has this not fixed (as of 2025-08)
14071 * cat <<EOF 14094 * cat <<EOF
14072 * ... 14095 * ...
14073 * EO\ 14096 * EO\
14074 * F 14097 * F
14075 * (see heredoc_bkslash_newline2.tests) 14098 * (see heredoc_bkslash_newline2.tests)
14076 */ 14099 */
14077 c = pgetc(); 14100 if (eofmark->here->type == NHERE)
14101 c = pgetc(); /* dash always does pgetc() */
14102 else /* NXHERE */
14103 c = pgetc_eatbnl();
14078 } 14104 }
14079 14105
14080 if (c == '\n' || c == PEOF) { 14106 if (c == '\n' || c == PEOF) {
@@ -14084,7 +14110,6 @@ checkend: {
14084 needprompt = doprompt; 14110 needprompt = doprompt;
14085 } else { 14111 } else {
14086 int len_here; 14112 int len_here;
14087
14088 more_heredoc: 14113 more_heredoc:
14089 p = (char *)stackblock() + markloc + 1; 14114 p = (char *)stackblock() + markloc + 1;
14090 len_here = out - p; 14115 len_here = out - p;
@@ -14593,7 +14618,7 @@ xxreadtoken(void)
14593 } 14618 }
14594 } /* for (;;) */ 14619 } /* for (;;) */
14595 14620
14596 return readtoken1(c, BASESYNTAX, (char *) NULL, 0); 14621 return readtoken1(c, BASESYNTAX, NULL);
14597} 14622}
14598#else /* old xxreadtoken */ 14623#else /* old xxreadtoken */
14599#define RETURN(token) return lasttoken = token 14624#define RETURN(token) return lasttoken = token
@@ -14644,7 +14669,7 @@ xxreadtoken(void)
14644 } 14669 }
14645 break; 14670 break;
14646 } 14671 }
14647 return readtoken1(c, BASESYNTAX, (char *)NULL, 0); 14672 return readtoken1(c, BASESYNTAX, NULL);
14648#undef RETURN 14673#undef RETURN
14649} 14674}
14650#endif /* old xxreadtoken */ 14675#endif /* old xxreadtoken */
@@ -14750,9 +14775,9 @@ parseheredoc(void)
14750 tokpushback = 0; 14775 tokpushback = 0;
14751 setprompt_if(needprompt, 2); 14776 setprompt_if(needprompt, 2);
14752 if (here->here->type == NHERE) 14777 if (here->here->type == NHERE)
14753 readtoken1(pgetc(), SQSYNTAX, here->eofmark, here->striptabs); 14778 readtoken1(pgetc(), SQSYNTAX, here);
14754 else 14779 else
14755 readtoken1(pgetc_eatbnl(), DQSYNTAX, here->eofmark, here->striptabs); 14780 readtoken1(pgetc_eatbnl(), DQSYNTAX, here);
14756 n = stzalloc(sizeof(struct narg)); 14781 n = stzalloc(sizeof(struct narg));
14757 n->narg.type = NARG; 14782 n->narg.type = NARG;
14758 /*n->narg.next = NULL; - stzalloc did it */ 14783 /*n->narg.next = NULL; - stzalloc did it */
@@ -14795,8 +14820,7 @@ expandstr(const char *ps, int syntax_type)
14795 * PS1='$(date "+%H:%M:%S) > ' 14820 * PS1='$(date "+%H:%M:%S) > '
14796 */ 14821 */
14797 exception_handler = &jmploc; 14822 exception_handler = &jmploc;
14798 readtoken1(pgetc_eatbnl(), syntax_type, FAKEEOFMARK, 0); 14823 readtoken1(pgetc_eatbnl(), syntax_type, FAKEEOFMARK);
14799
14800 n.narg.type = NARG; 14824 n.narg.type = NARG;
14801 n.narg.next = NULL; 14825 n.narg.next = NULL;
14802 n.narg.text = wordtext; 14826 n.narg.text = wordtext;
@@ -16179,7 +16203,7 @@ procargs(char **argv)
16179#if ENABLE_PLATFORM_MINGW32 16203#if ENABLE_PLATFORM_MINGW32
16180 login_sh = applet_name[0] == 'l'; 16204 login_sh = applet_name[0] == 'l';
16181#else 16205#else
16182 login_sh = xargv[0] && xargv[0][0] == '-'; 16206 login_sh = /*xargv[0] &&*/ xargv[0][0] == '-';
16183#endif 16207#endif
16184#if NUM_SCRIPTS > 0 16208#if NUM_SCRIPTS > 0
16185 if (minusc) 16209 if (minusc)
@@ -16231,7 +16255,6 @@ procargs(char **argv)
16231#endif 16255#endif
16232 setarg0: 16256 setarg0:
16233 arg0 = *xargv++; 16257 arg0 = *xargv++;
16234 commandname = arg0;
16235 } 16258 }
16236 16259
16237 shellparam.p = xargv; 16260 shellparam.p = xargv;
diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.right b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.right
new file mode 100644
index 000000000..3d79316d7
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.right
@@ -0,0 +1 @@
Ok1
diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.tests b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.tests
new file mode 100755
index 000000000..eb2223031
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline2a.tests
@@ -0,0 +1,4 @@
1cat <<-EOF
2 Ok1
3 EO\
4F
diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.right b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.right
new file mode 100644
index 000000000..3d79316d7
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.right
@@ -0,0 +1 @@
Ok1
diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.tests b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.tests
new file mode 100755
index 000000000..de21132d1
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3.tests
@@ -0,0 +1,4 @@
1cat <<EOF
2Ok1
3\
4EOF
diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3a.right b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3a.right
new file mode 100644
index 000000000..3d79316d7
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3a.right
@@ -0,0 +1 @@
Ok1
diff --git a/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3a.tests b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3a.tests
new file mode 100755
index 000000000..da3860804
--- /dev/null
+++ b/shell/ash_test/ash-heredoc/heredoc_bkslash_newline3a.tests
@@ -0,0 +1,4 @@
1cat <<-EOF
2Ok1
3 \
4 EOF
diff --git a/shell/ash_test/ash-vars/var_backslash1.right b/shell/ash_test/ash-vars/var_backslash1.right
new file mode 100644
index 000000000..f384da1f5
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_backslash1.right
@@ -0,0 +1,26 @@
1a is '\*bc'
2b is '\'
3c is '*'
4${a##?*} removes everything: || - matches one char, then all
5${a##?"*"} removes \*: |bc| - matches one char, then *
6${a##\*} removes nothing: |\*bc| - first char is not *
7${a##\\*} removes everything: || - matches \, then all
8${a##\\\*} removes \*: |bc| - matches \, then *
9${a##?$c} removes everything: || - matches one char, then all
10${a##?"$c"} removes \*: |bc| - matches one char, then *
11${a##\\$c} removes everything: || - matches \, then all
12${a##\\\$c} removes nothing: |\*bc| - matches \, but then second char is not $
13${a##\\"$c"} removes \*: |bc| - matches \, then *
14${a##$b} removes \: |*bc| - matches \
15${a##"$b"} removes \: |*bc| - matches \
16
17${a##"$b"?} removes \*: |bc| - matches \, then one char
18${a##"$b"*} removes everything: || - matches \, then all
19${a##"$b""?"} removes nothing: |\*bc| - second char is not ?
20${a##"$b""*"} removes \*: |bc| - matches \, then *
21${a##"$b"\*} removes \*: |bc| - matches \, then *
22${a##"$b"$c} removes everything:|| - matches \, then all
23${a##"$b""$c"} removes \*: |bc| - matches \, then *
24${a##"$b?"} removes nothing: |\*bc| - second char is not ?
25${a##"$b*"} removes \*: |bc| - matches \, then *
26${a##"$b$c"} removes \*: |bc| - matches \, then *
diff --git a/shell/ash_test/ash-vars/var_backslash1.tests b/shell/ash_test/ash-vars/var_backslash1.tests
new file mode 100755
index 000000000..7eea3cc19
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_backslash1.tests
@@ -0,0 +1,38 @@
1a='\*bc'
2b='\'
3c='*'
4echo "a is '$a'"
5echo "b is '$b'"
6echo "c is '$c'"
7echo '${a##?*} removes everything: '"|${a##?*}|"' - matches one char, then all'
8echo '${a##?"*"} removes \*: '"|${a##?"*"}|"' - matches one char, then *'
9echo '${a##\*} removes nothing: '"|${a##\*}|"' - first char is not *'
10echo '${a##\\*} removes everything: '"|${a##\\*}|"' - matches \, then all'
11echo '${a##\\\*} removes \*: '"|${a##\\\*}|"' - matches \, then *'
12echo '${a##?$c} removes everything: '"|${a##?$c}|"' - matches one char, then all'
13echo '${a##?"$c"} removes \*: '"|${a##?"$c"}|"' - matches one char, then *'
14echo '${a##\\$c} removes everything: '"|${a##\\$c}|"' - matches \, then all'
15echo '${a##\\\$c} removes nothing: '"|${a##\\\$c}|"' - matches \, but then second char is not $'
16echo '${a##\\"$c"} removes \*: '"|${a##\\"$c"}|"' - matches \, then *'
17echo '${a##$b} removes \: '"|${a##$b}|"' - matches \'
18echo '${a##"$b"} removes \: '"|${a##"$b"}|"' - matches \'
19echo
20## In bash, this isn't working as expected
21#echo '${a##$b?} removes \*: '"|${a##$b?}|"' - matches \, then one char' # bash prints |\*bc|
22#echo '${a##$b*} removes everything: '"|${a##$b*}|"' - matches \, then all' # bash prints |\*bc|
23#echo '${a##$b$c} removes everything: '"|${a##$b$c}|"' - matches \, then all' # bash prints |\*bc|
24#echo '${a##$b"$c"} removes \*: '"|${a##$b"$c"}|"' - matches \, then *' # bash prints |\*bc|
25## the cause seems to be that $b emits backslash that "glues" onto next character if there is one:
26## a='\*bc'; b='\'; c='*'; echo "|${a##?$b*}|" # bash prints |bc| - the $b* works as \* (matches literal *)
27## a='\*bc'; b='\'; c='*'; echo "|${a##\\$b*}|" # bash prints |bc|
28#echo
29echo '${a##"$b"?} removes \*: '"|${a##"$b"?}|"' - matches \, then one char'
30echo '${a##"$b"*} removes everything: '"|${a##"$b"*}|"' - matches \, then all'
31echo '${a##"$b""?"} removes nothing: '"|${a##"$b""?"}|"' - second char is not ?' # bash prints |bc|
32echo '${a##"$b""*"} removes \*: '"|${a##"$b""*"}|"' - matches \, then *'
33echo '${a##"$b"\*} removes \*: '"|${a##"$b"\*}|"' - matches \, then *'
34echo '${a##"$b"$c} removes everything:'"|${a##"$b"$c}|"' - matches \, then all'
35echo '${a##"$b""$c"} removes \*: '"|${a##"$b""$c"}|"' - matches \, then *'
36echo '${a##"$b?"} removes nothing: '"|${a##"$b?"}|"' - second char is not ?' # bash prints |bc|
37echo '${a##"$b*"} removes \*: '"|${a##"$b*"}|"' - matches \, then *' # bash prints ||
38echo '${a##"$b$c"} removes \*: '"|${a##"$b$c"}|"' - matches \, then *'
diff --git a/shell/hush.c b/shell/hush.c
index 09ab6ebc0..2cceef25a 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -115,6 +115,11 @@
115//config:# It's only needed to get "nice" menuconfig indenting. 115//config:# It's only needed to get "nice" menuconfig indenting.
116//config:if SHELL_HUSH || HUSH || SH_IS_HUSH || BASH_IS_HUSH 116//config:if SHELL_HUSH || HUSH || SH_IS_HUSH || BASH_IS_HUSH
117//config: 117//config:
118//config:config HUSH_NEED_FOR_SPEED
119//config: bool "Faster, but larger code"
120//config: default y
121//config: depends on SHELL_HUSH
122//config:
118//config:config HUSH_BASH_COMPAT 123//config:config HUSH_BASH_COMPAT
119//config: bool "bash-compatible extensions" 124//config: bool "bash-compatible extensions"
120//config: default y 125//config: default y
@@ -3388,7 +3393,19 @@ static int glob_needed(const char *s)
3388 s += 2; 3393 s += 2;
3389 continue; 3394 continue;
3390 } 3395 }
3391 if (*s == '*' || *s == '[' || *s == '?' || *s == '{') 3396 if (*s == '*' || *s == '?')
3397 return 1;
3398 /* Only force glob if "..[..].." detected.
3399 * Not merely "[", "[[", "][" etc.
3400 * Optimization to avoid glob()
3401 * on "[ COND ]" and "[[ COND ]]":
3402 * strace hush -c 'i=0; while [ $((++i)) != 50000 ]; do :; done'
3403 * shouldn't be doing 50000 stat("[").
3404 * (Can do it for "{" too, but it's not a common case).
3405 */
3406 if (*s == '[' && strchr(s+1, ']'))
3407 return 1;
3408 if (*s == '{' /* && strchr(s+1, '}')*/)
3392 return 1; 3409 return 1;
3393 s++; 3410 s++;
3394 } 3411 }
@@ -3579,7 +3596,16 @@ static int glob_needed(const char *s)
3579 s += 2; 3596 s += 2;
3580 continue; 3597 continue;
3581 } 3598 }
3582 if (*s == '*' || *s == '[' || *s == '?') 3599 if (*s == '*' || *s == '?')
3600 return 1;
3601 /* Only force glob if "..[..].." detected.
3602 * Not merely "[", "[[", "][" etc.
3603 * Optimization to avoid glob()
3604 * on "[ COND ]" and "[[ COND ]]":
3605 * strace hush -c 'i=0; while [ $((++i)) != 50000 ]; do :; done'
3606 * shouldn't be doing 50000 stat("[").
3607 */
3608 if (*s == '[' && strchr(s+1, ']'))
3583 return 1; 3609 return 1;
3584 s++; 3610 s++;
3585 } 3611 }
@@ -4599,7 +4625,15 @@ static char *fetch_till_str(o_string *as_string,
4599 past_EOL = heredoc.length; 4625 past_EOL = heredoc.length;
4600 /* Get 1st char of next line, possibly skipping leading tabs */ 4626 /* Get 1st char of next line, possibly skipping leading tabs */
4601 do { 4627 do {
4602 ch = i_getch(input); 4628 if (heredoc_flags & HEREDOC_QUOTED)
4629 ch = i_getch(input);
4630 else { /* see heredoc_bkslash_newline3a.tests:
4631 * cat <<-EOF
4632 * <tab>\
4633 * <tab>EOF
4634 */
4635 ch = i_getch_and_eat_bkslash_nl(input);
4636 }
4603 if (ch != EOF) 4637 if (ch != EOF)
4604 nommu_addchr(as_string, ch); 4638 nommu_addchr(as_string, ch);
4605 } while ((heredoc_flags & HEREDOC_SKIPTABS) && ch == '\t'); 4639 } while ((heredoc_flags & HEREDOC_SKIPTABS) && ch == '\t');
@@ -4625,7 +4659,7 @@ static char *fetch_till_str(o_string *as_string,
4625 prev = 0; /* not '\' */ 4659 prev = 0; /* not '\' */
4626 continue; 4660 continue;
4627 } 4661 }
4628 } 4662 } /* if (\n or EOF) */
4629 if (ch == EOF) { 4663 if (ch == EOF) {
4630 o_free(&heredoc); 4664 o_free(&heredoc);
4631 return NULL; /* error */ 4665 return NULL; /* error */
diff --git a/shell/hush_test/hush-bugs/var_backslash1.right b/shell/hush_test/hush-bugs/var_backslash1.right
new file mode 100644
index 000000000..f384da1f5
--- /dev/null
+++ b/shell/hush_test/hush-bugs/var_backslash1.right
@@ -0,0 +1,26 @@
1a is '\*bc'
2b is '\'
3c is '*'
4${a##?*} removes everything: || - matches one char, then all
5${a##?"*"} removes \*: |bc| - matches one char, then *
6${a##\*} removes nothing: |\*bc| - first char is not *
7${a##\\*} removes everything: || - matches \, then all
8${a##\\\*} removes \*: |bc| - matches \, then *
9${a##?$c} removes everything: || - matches one char, then all
10${a##?"$c"} removes \*: |bc| - matches one char, then *
11${a##\\$c} removes everything: || - matches \, then all
12${a##\\\$c} removes nothing: |\*bc| - matches \, but then second char is not $
13${a##\\"$c"} removes \*: |bc| - matches \, then *
14${a##$b} removes \: |*bc| - matches \
15${a##"$b"} removes \: |*bc| - matches \
16
17${a##"$b"?} removes \*: |bc| - matches \, then one char
18${a##"$b"*} removes everything: || - matches \, then all
19${a##"$b""?"} removes nothing: |\*bc| - second char is not ?
20${a##"$b""*"} removes \*: |bc| - matches \, then *
21${a##"$b"\*} removes \*: |bc| - matches \, then *
22${a##"$b"$c} removes everything:|| - matches \, then all
23${a##"$b""$c"} removes \*: |bc| - matches \, then *
24${a##"$b?"} removes nothing: |\*bc| - second char is not ?
25${a##"$b*"} removes \*: |bc| - matches \, then *
26${a##"$b$c"} removes \*: |bc| - matches \, then *
diff --git a/shell/hush_test/hush-bugs/var_backslash1.tests b/shell/hush_test/hush-bugs/var_backslash1.tests
new file mode 100755
index 000000000..7eea3cc19
--- /dev/null
+++ b/shell/hush_test/hush-bugs/var_backslash1.tests
@@ -0,0 +1,38 @@
1a='\*bc'
2b='\'
3c='*'
4echo "a is '$a'"
5echo "b is '$b'"
6echo "c is '$c'"
7echo '${a##?*} removes everything: '"|${a##?*}|"' - matches one char, then all'
8echo '${a##?"*"} removes \*: '"|${a##?"*"}|"' - matches one char, then *'
9echo '${a##\*} removes nothing: '"|${a##\*}|"' - first char is not *'
10echo '${a##\\*} removes everything: '"|${a##\\*}|"' - matches \, then all'
11echo '${a##\\\*} removes \*: '"|${a##\\\*}|"' - matches \, then *'
12echo '${a##?$c} removes everything: '"|${a##?$c}|"' - matches one char, then all'
13echo '${a##?"$c"} removes \*: '"|${a##?"$c"}|"' - matches one char, then *'
14echo '${a##\\$c} removes everything: '"|${a##\\$c}|"' - matches \, then all'
15echo '${a##\\\$c} removes nothing: '"|${a##\\\$c}|"' - matches \, but then second char is not $'
16echo '${a##\\"$c"} removes \*: '"|${a##\\"$c"}|"' - matches \, then *'
17echo '${a##$b} removes \: '"|${a##$b}|"' - matches \'
18echo '${a##"$b"} removes \: '"|${a##"$b"}|"' - matches \'
19echo
20## In bash, this isn't working as expected
21#echo '${a##$b?} removes \*: '"|${a##$b?}|"' - matches \, then one char' # bash prints |\*bc|
22#echo '${a##$b*} removes everything: '"|${a##$b*}|"' - matches \, then all' # bash prints |\*bc|
23#echo '${a##$b$c} removes everything: '"|${a##$b$c}|"' - matches \, then all' # bash prints |\*bc|
24#echo '${a##$b"$c"} removes \*: '"|${a##$b"$c"}|"' - matches \, then *' # bash prints |\*bc|
25## the cause seems to be that $b emits backslash that "glues" onto next character if there is one:
26## a='\*bc'; b='\'; c='*'; echo "|${a##?$b*}|" # bash prints |bc| - the $b* works as \* (matches literal *)
27## a='\*bc'; b='\'; c='*'; echo "|${a##\\$b*}|" # bash prints |bc|
28#echo
29echo '${a##"$b"?} removes \*: '"|${a##"$b"?}|"' - matches \, then one char'
30echo '${a##"$b"*} removes everything: '"|${a##"$b"*}|"' - matches \, then all'
31echo '${a##"$b""?"} removes nothing: '"|${a##"$b""?"}|"' - second char is not ?' # bash prints |bc|
32echo '${a##"$b""*"} removes \*: '"|${a##"$b""*"}|"' - matches \, then *'
33echo '${a##"$b"\*} removes \*: '"|${a##"$b"\*}|"' - matches \, then *'
34echo '${a##"$b"$c} removes everything:'"|${a##"$b"$c}|"' - matches \, then all'
35echo '${a##"$b""$c"} removes \*: '"|${a##"$b""$c"}|"' - matches \, then *'
36echo '${a##"$b?"} removes nothing: '"|${a##"$b?"}|"' - second char is not ?' # bash prints |bc|
37echo '${a##"$b*"} removes \*: '"|${a##"$b*"}|"' - matches \, then *' # bash prints ||
38echo '${a##"$b$c"} removes \*: '"|${a##"$b$c"}|"' - matches \, then *'
diff --git a/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2a.right b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2a.right
new file mode 100644
index 000000000..3d79316d7
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2a.right
@@ -0,0 +1 @@
Ok1
diff --git a/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2a.tests b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2a.tests
new file mode 100755
index 000000000..eb2223031
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline2a.tests
@@ -0,0 +1,4 @@
1cat <<-EOF
2 Ok1
3 EO\
4F
diff --git a/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3.right b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3.right
new file mode 100644
index 000000000..3d79316d7
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3.right
@@ -0,0 +1 @@
Ok1
diff --git a/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3.tests b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3.tests
new file mode 100755
index 000000000..de21132d1
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3.tests
@@ -0,0 +1,4 @@
1cat <<EOF
2Ok1
3\
4EOF
diff --git a/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3a.right b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3a.right
new file mode 100644
index 000000000..3d79316d7
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3a.right
@@ -0,0 +1 @@
Ok1
diff --git a/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3a.tests b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3a.tests
new file mode 100755
index 000000000..da3860804
--- /dev/null
+++ b/shell/hush_test/hush-heredoc/heredoc_bkslash_newline3a.tests
@@ -0,0 +1,4 @@
1cat <<-EOF
2Ok1
3 \
4 EOF