summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheo Buehler <tb@cvs.openbsd.org>2020-09-13 15:39:10 +0000
committerTheo Buehler <tb@cvs.openbsd.org>2020-09-13 15:39:10 +0000
commit89bbe9e45ad8deb54d499460a2b47496292969b0 (patch)
tree2480869f6ea83179e589d23506a960372e407237
parent4b1e401314d2ed1b7a892705f5a410aed727d963 (diff)
Fix "$@" splitting with empty IFS
One uncommon but useful way of writing shell scripts is to start off by disabling field/word splitting (IFS='') and pathname expansion/globbing (set -f), re-enabling either or both only for the commands that need them, e.g. within a subshell. This helps avoid a lot of snags with field splitting and globbing if you forget to quote a variable somewhere, adding to the general robustness of a script. (In fact it eliminates much of the need to quote variable/parameter expansions, with empty removal remaining as the only issue.) Unfortunately OpenBSD ksh (like all pdksh variants except mksh) has a POSIX compliance bug that is a show stopper for this approach: "$@" does not generate words (arguments) if IFS is empty. As a result, the separate command arguments represented by "$@" become a single argument. So passing on an intact set of positional parameters to a command or function is impossible with field splitting disabled. Of course this is illogical: the quoted special parameter "$@" generates zero or more words, it doesn't split any words, so the contents of IFS (or lack thereof) should be neither here nor there. It's old ksh88 behaviour copied by the original pdksh, but it violates POSIX and it has been fixed many years ago in ksh93 and all other POSIX shells. From Martijn Dekker (who also wrote the above paragraphs) back in 2016. Thanks to Avi Halachmi for reminding us of the issue. ok czarkoff deraadt kn
-rw-r--r--bin/ksh/eval.c65
1 files changed, 48 insertions, 17 deletions
diff --git a/bin/ksh/eval.c b/bin/ksh/eval.c
index ba4b73f404e..2f22c2ee2eb 100644
--- a/bin/ksh/eval.c
+++ b/bin/ksh/eval.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: eval.c,v 1.65 2019/06/28 13:34:59 deraadt Exp $ */
+/* $OpenBSD: eval.c,v 1.66 2020/09/13 15:39:09 tb Exp $ */
/*
* Expansion - quoting, separation, substitution, globbing
@@ -47,6 +47,8 @@ typedef struct Expand {
#define IFS_WORD 0 /* word has chars (or quotes) */
#define IFS_WS 1 /* have seen IFS white-space */
#define IFS_NWS 2 /* have seen IFS non-white-space */
+#define IFS_IWS 3 /* beginning of word, ignore IFS white-space */
+#define IFS_QUOTE 4 /* beg.w/quote, becomes IFS_WORD unless "$@" */
static int varsub(Expand *, char *, char *, int *, int *);
static int comsub(Expand *, char *);
@@ -217,7 +219,17 @@ expand(char *cp, /* input word */
c = *sp++;
break;
case OQUOTE:
- word = IFS_WORD;
+ switch (word) {
+ case IFS_QUOTE:
+ /* """something */
+ word = IFS_WORD;
+ break;
+ case IFS_WORD:
+ break;
+ default:
+ word = IFS_QUOTE;
+ break;
+ }
tilde_ok = 0;
quote = 1;
continue;
@@ -297,6 +309,8 @@ expand(char *cp, /* input word */
if (f&DOBLANK)
doblank++;
tilde_ok = 0;
+ if (word == IFS_QUOTE && type != XNULLSUB)
+ word = IFS_WORD;
if (type == XBASE) { /* expand? */
if (!st->next) {
SubType *newst;
@@ -358,6 +372,11 @@ expand(char *cp, /* input word */
f |= DOTEMP_;
/* FALLTHROUGH */
default:
+ /* '-' '+' '?' */
+ if (quote)
+ word = IFS_WORD;
+ else if (dp == Xstring(ds, dp))
+ word = IFS_IWS;
/* Enable tilde expansion */
tilde_ok = 1;
f |= DOTILDE;
@@ -387,10 +406,17 @@ expand(char *cp, /* input word */
*/
x.str = trimsub(str_val(st->var),
dp, st->stype);
- if (x.str[0] != '\0' || st->quote)
+ if (x.str[0] != '\0') {
+ word = IFS_IWS;
type = XSUB;
- else
+ } else if (quote) {
+ word = IFS_WORD;
+ type = XSUB;
+ } else {
+ if (dp == Xstring(ds, dp))
+ word = IFS_IWS;
type = XNULLSUB;
+ }
if (f&DOBLANK)
doblank++;
st = st->prev;
@@ -422,6 +448,10 @@ expand(char *cp, /* input word */
if (f&DOBLANK)
doblank++;
st = st->prev;
+ if (quote || !*x.str)
+ word = IFS_WORD;
+ else
+ word = IFS_IWS;
continue;
case '?':
{
@@ -463,12 +493,8 @@ expand(char *cp, /* input word */
type = XBASE;
if (f&DOBLANK) {
doblank--;
- /* not really correct: x=; "$x$@" should
- * generate a null argument and
- * set A; "${@:+}" shouldn't.
- */
- if (dp == Xstring(ds, dp))
- word = IFS_WS;
+ if (dp == Xstring(ds, dp) && word != IFS_WORD)
+ word = IFS_IWS;
}
continue;
@@ -503,7 +529,12 @@ expand(char *cp, /* input word */
if (c == 0) {
if (quote && !x.split)
continue;
+ if (!quote && word == IFS_WS)
+ continue;
+ /* this is so we don't terminate */
c = ' ';
+ /* now force-emit a word */
+ goto emit_word;
}
if (quote && x.split) {
/* terminate word for "$@" */
@@ -554,15 +585,15 @@ expand(char *cp, /* input word */
* -----------------------------------
* IFS_WORD w/WS w/NWS w
* IFS_WS -/WS w/NWS -
- * IFS_NWS -/NWS w/NWS w
+ * IFS_NWS -/NWS w/NWS -
+ * IFS_IWS -/WS w/NWS -
* (w means generate a word)
- * Note that IFS_NWS/0 generates a word (at&t ksh
- * doesn't do this, but POSIX does).
*/
- if (word == IFS_WORD ||
- (!ctype(c, C_IFSWS) && c && word == IFS_NWS)) {
- char *p;
-
+ if ((word == IFS_WORD) || (word == IFS_QUOTE) || (c &&
+ (word == IFS_IWS || word == IFS_NWS) &&
+ !ctype(c, C_IFSWS))) {
+ char *p;
+ emit_word:
*dp++ = '\0';
p = Xclose(ds, dp);
if (fdo & DOBRACE_)