From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 19939 invoked by alias); 25 Apr 2017 09:15:44 -0000 Mailing-List: contact zsh-workers-help@zsh.org; run by ezmlm Precedence: bulk X-No-Archive: yes List-Id: Zsh Workers List List-Post: List-Help: X-Seq: 41008 Received: (qmail 23771 invoked from network); 25 Apr 2017 09:15:44 -0000 X-Qmail-Scanner-Diagnostics: from mailout2.w1.samsung.com by f.primenet.com.au (envelope-from , uid 7791) with qmail-scanner-2.11 (clamdscan: 0.99.2/21882. spamassassin: 3.4.1. Clear:RC:0(210.118.77.12):SA:0(-5.0/5.0):. Processed in 1.034321 secs); 25 Apr 2017 09:15:44 -0000 X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on f.primenet.com.au X-Spam-Level: X-Spam-Status: No, score=-5.0 required=5.0 tests=RCVD_IN_DNSWL_HI, RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,RP_MATCHES_RCVD,SPF_HELO_PASS autolearn=unavailable autolearn_force=no version=3.4.1 X-Envelope-From: p.stephenson@samsung.com X-Qmail-Scanner-Mime-Attachments: | X-Qmail-Scanner-Zip-Files: | Received-SPF: none (ns1.primenet.com.au: domain at samsung.com does not designate permitted sender hosts) X-AuditID: cbfec7f5-f792f6d0000063e9-4b-58ff13b6b5a2 Date: Tue, 25 Apr 2017 10:15:20 +0100 From: Peter Stephenson To: Zsh Hackers' List Subject: PATCH: perform expansion for precommand modifiers Message-id: <20170425101520.23910ede@pwslap01u.europe.root.pri> Organization: Samsung Cambridge Solution Centre X-Mailer: Claws Mail 3.7.9 (GTK+ 2.22.0; i386-redhat-linux-gnu) MIME-version: 1.0 Content-type: text/plain; charset=US-ASCII Content-transfer-encoding: 7bit X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFnrFIsWRmVeSWpSXmKPExsWy7djPc7rbhP9HGEyexmFxsPkhkwOjx6qD H5gCGKO4bFJSczLLUov07RK4Mra/XcxSsDyu4seTBYwNjO9cuxg5OSQETCRuL/rAAmGLSVy4 t56ti5GLQ0hgKaPEn2lnWSGcXiaJY+87WWE6Pq5fzw6RWMYoMf/8LyhnGpNEw7qbzCBVQgJn GCX2HFWFSJxllLj/6SdYO4uAqsSLA4vYQGw2AUOJqZtmM3YxcnCICGhLtH8UAwkLC1hJ7Jw7 A2wOr4C9xKKtP8BsfgF9iat/PzFBXGEvMfPKGUaIGkGJH5Pvgf3ALKAjsW3bY3YIW15i85q3 zCA3SAi8Z5P4+fYCG8guCQFZiU0HmCFMF4mWiykQI4UlXh3fwg5hy0hcntwNDZZ+Rokn3b4Q Y2YwSpw+s4MNImEt0Xf7IiPELj6JSdumQ83klehoE4Io8ZB4dnsV1BxHiRUXDzGBlAgJxEqs /ZY3gVFhFpIHZiF5YBaSBxYwMq9iFEktLc5NTy021StOzC0uzUvXS87P3cQITAGn/x3/uoNx 6TGrQ4wCHIxKPLwGDP8ihFgTy4orcw8xSnAwK4nwegj8jxDiTUmsrEotyo8vKs1JLT7EKM3B oiTOy3XqWoSQQHpiSWp2ampBahFMlomDU6qBkd3C+ovHQruVy8U6XKMPuMt2mkWvNq+LZv0p e/FTh23qXvNf6yJXPGmsCTRdOCfooZjcUQZp67vRyyZMjT0w/9X6XFvx9wqSl4werGhPOt7p ctGjatJ+rxe8G3+7Jc5/f+/xhTL+deWPX8frSYopHDU/s+1Q46ScMMlHpz/xrlm4fd3K1Ivz PZVYijMSDbWYi4oTAbyqoT79AgAA X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFuphkeLIzCtJLcpLzFFi42I5/e/4Fd3zwv8jDK7/lLU42PyQyYHRY9XB D0wBjFFuNhmpiSmpRQqpecn5KZl56bZKoSFuuhZKCnmJuam2ShG6viFBSgpliTmlQJ6RARpw cA5wD1bSt0twy9j+djFLwfK4ih9PFjA2ML5z7WLk5JAQMJH4uH49O4QtJnHh3nq2LkYuDiGB JYwSW5r3sUM4M5gkbm0/BpU5xyixo2kTK0iLkMBZRon30yNAbBYBVYkXBxaxgdhsAoYSUzfN Zuxi5OAQEdCWaP8oBhIWFrCS2Dl3BjOIzStgL7Fo6w8wm19AX+Lq309MEFfYS8y8coYRokZQ 4sfkeywgNrOAlsTmbU2sELa8xOY1b5khTlCXuHF3N/sERsFZSFpmIWmZhaRlASPzKkaR1NLi 3PTcYiO94sTc4tK8dL3k/NxNjMCY2Hbs55YdjF3vgg8xCnAwKvHwGjD8ixBiTSwrrsw9xCjB wawkwush8D9CiDclsbIqtSg/vqg0J7X4EKMp0P8TmaVEk/OB8ZpXEm9oYmhuaWhkbGFhbmSk JM479cOVcCGB9MSS1OzU1ILUIpg+Jg5OqQbGpcryLx5EyF3Lirh+YZfT5/I9V+SEM/6fXMPF wnClJXveDZnEXUc+SNzdOm3uOjOH5P2nP4aIN3pva7t8z/Xj5P8LkqVebOfPfOEiVPTW5/Mm 95xLF3y8rk7KcDzp0HOsoHWy/s27GqvMle9eNf20+1/FrFkPDnve13z6XuR76r7St9d/HNBa lqvEUpyRaKjFXFScCACrApRInwIAAA== X-MTR: 20000000000000000@CPGS X-CMS-MailID: 20170425091533eucas1p26af70366557848059af3b7c8957f1d55 X-Msg-Generator: CA X-Sender-IP: 182.198.249.180 X-Local-Sender: =?UTF-8?B?UGV0ZXIgU3RlcGhlbnNvbhtTQ1NDLURhdGEgUGxhbmUb?= =?UTF-8?B?7IK87ISx7KCE7J6QG1ByaW5jaXBhbCBFbmdpbmVlciwgU29mdHdhcmU=?= X-Global-Sender: =?UTF-8?B?UGV0ZXIgU3RlcGhlbnNvbhtTQ1NDLURhdGEgUGxhbmUbU2Ft?= =?UTF-8?B?c3VuZyBFbGVjdHJvbmljcxtQcmluY2lwYWwgRW5naW5lZXIsIFNvZnR3YXJl?= X-Sender-Code: =?UTF-8?B?QzEwG0VIURtDMTBDRDA1Q0QwNTAwNTg=?= CMS-TYPE: 201P X-HopCount: 7 X-CMS-RootMailID: 20170425091533eucas1p26af70366557848059af3b7c8957f1d55 X-RootMTR: 20170425091533eucas1p26af70366557848059af3b7c8957f1d55 References: This is something I've been meaning to get around to for a while. "exec" and "command" have a for quite a long time now taken options, but the parsing hadn't caught up properly so that it wasn't possible to put the options, let alone the modifiers themselves, in a parameter. This fixes both --- see the new tests. It's not particularly elegant but I don't want to assume it's safe to re-expand parts of the command line that have already been expanded, and a bit of experimenation suggested passing in the ability to expand parts of a list down to prefork() caused too much complexity. The key reason we can't simply expand everything in one go is the need to decide if we want to do fudged assignment-style expansion, now not quite as crucial as it was but still useful. pws diff --git a/Src/exec.c b/Src/exec.c index f021a08..b273ee8 100644 --- a/Src/exec.c +++ b/Src/exec.c @@ -2642,6 +2642,27 @@ execcmd_analyse(Estate state, Execcmd_params eparams) } /* + * Transfer the first node of args to preargs, performing + * prefork expansion on the way if necessary. + */ +static void execcmd_getargs(LinkList preargs, LinkList args, int expand) +{ + if (!firstnode(args)) { + return; + } else if (expand) { + local_list0(svl); + init_list0(svl); + /* not init_list1, as we need real nodes */ + addlinknode(&svl, uremnode(args, firstnode(args))); + /* Analysing commands, so vanilla options to prefork */ + prefork(&svl, 0, NULL); + joinlists(preargs, &svl); + } else { + addlinknode(preargs, uremnode(args, firstnode(args))); + } +} + +/* * Execute a command at the lowest level of the hierarchy. */ @@ -2671,6 +2692,11 @@ execcmd_exec(Estate state, Execcmd_params eparams, LinkList redir = eparams->redir; Wordcode varspc = eparams->varspc; int type = eparams->type; + /* + * preargs comes from expanding the head of the args list + * in order to check for prefix commands. + */ + LinkList preargs; doneps4 = 0; @@ -2725,9 +2751,16 @@ execcmd_exec(Estate state, Execcmd_params eparams, * command if it contains some tokens (e.g. x=ex; ${x}port), so this * * only works in simple cases. has_token() is called to make sure * * this really is a simple case. */ - if (type == WC_SIMPLE || type == WC_TYPESET) { - while (args && nonempty(args)) { - char *cmdarg = (char *) peekfirst(args); + if ((type == WC_SIMPLE || type == WC_TYPESET) && args) { + /* + * preargs contains args that have been expanded by prefork. + * running excargs causes the expansion, if possible and + * if parsseargs is empty, otherwise does nothing. + */ + preargs = newlinklist(); + execcmd_getargs(preargs, args, eparams->htok); + while (nonempty(preargs)) { + char *cmdarg = (char *) peekfirst(preargs); checked = !has_token(cmdarg); if (!checked) break; @@ -2766,7 +2799,13 @@ execcmd_exec(Estate state, Execcmd_params eparams, break; } checked = 0; - if ((cflags & BINF_COMMAND) && nextnode(firstnode(args))) { + uremnode(preargs, firstnode(preargs)); + if (!firstnode(preargs)) { + execcmd_getargs(preargs, args, eparams->htok); + if (!firstnode(preargs)) + break; + } + if ((cflags & BINF_COMMAND)) { /* * Check for options to "command". * If just -p, this is handled here: use the default @@ -2776,10 +2815,11 @@ execcmd_exec(Estate state, Execcmd_params eparams, * Otherwise, just leave marked as BINF_COMMAND * modifier with no additional action. */ - LinkNode argnode = nextnode(firstnode(args)); - char *argdata = (char *) getdata(argnode); - char *cmdopt; + LinkNode argnode, oldnode; + char *argdata, *cmdopt; int has_p = 0, has_vV = 0, has_other = 0; + argnode = firstnode(preargs); + argdata = (char *) getdata(argnode); while (IS_DASH(*argdata)) { /* Just to be definite, stop on single "-", too, */ if (!argdata[1] || @@ -2812,25 +2852,30 @@ execcmd_exec(Estate state, Execcmd_params eparams, break; } + oldnode = argnode; argnode = nextnode(argnode); - if (!argnode) - break; + if (!argnode) { + execcmd_getargs(preargs, args, eparams->htok); + if (!(argnode = nextnode(oldnode))) + break; + } argdata = (char *) getdata(argnode); } if (has_vV) { - /* Leave everything alone, dispatch to whence */ + /* + * Leave everything alone, dispatch to whence. + * We need to put the name back in the list. + */ + pushnode(preargs, "command"); hn = &commandbn.node; is_builtin = 1; break; } else if (has_p) { - /* Use default path; absorb command and option. */ - uremnode(args, firstnode(args)); + /* Use default path */ use_defpath = 1; - if ((argnode = nextnode(firstnode(args)))) - argdata = (char *) getdata(argnode); } /* - * Else just absorb command and any trailing + * Else just any trailing * end-of-options marker. This can only occur * if we just had -p or something including more * than just -p, -v and -V, in which case we behave @@ -2838,16 +2883,16 @@ execcmd_exec(Estate state, Execcmd_params eparams, * isn't a good place for standard option handling. */ if (IS_DASH(argdata[0]) && IS_DASH(argdata[1]) && !argdata[2]) - uremnode(args, firstnode(args)); - } - if ((cflags & BINF_EXEC) && nextnode(firstnode(args))) { + uremnode(preargs, argnode); + } else if (cflags & BINF_EXEC) { /* * Check for compatibility options to exec builtin. * It would be nice to do these more generically, * but currently we don't have a mechanism for * precommand modifiers. */ - char *next = (char *) getdata(nextnode(firstnode(args))); + LinkNode argnode = firstnode(preargs), oldnode; + char *argdata = (char *) getdata(argnode); char *cmdopt, *exec_argv0 = NULL; /* * Careful here: we want to make sure a final dash @@ -2857,17 +2902,23 @@ execcmd_exec(Estate state, Execcmd_params eparams, * people aren't likely to mix the option style * with the zsh style. */ - while (next && IS_DASH(*next) && strlen(next) >= 2) { - if (!firstnode(args)) { + while (argdata && IS_DASH(*argdata) && strlen(argdata) >= 2) { + oldnode = argnode; + argnode = nextnode(oldnode); + if (!argnode) { + execcmd_getargs(preargs, args, eparams->htok); + argnode = nextnode(oldnode); + } + if (!argnode) { zerr("exec requires a command to execute"); lastval = 1; errflag |= ERRFLAG_ERROR; goto done; } - uremnode(args, firstnode(args)); - if (IS_DASH(next[0]) && IS_DASH(next[1]) && !next[2]) + uremnode(preargs, oldnode); + if (IS_DASH(argdata[0]) && IS_DASH(argdata[1]) && !argdata[2]) break; - for (cmdopt = &next[1]; *cmdopt; ++cmdopt) { + for (cmdopt = &argdata[1]; *cmdopt; ++cmdopt) { switch (*cmdopt) { case 'a': /* argument is ARGV0 string */ @@ -2876,21 +2927,25 @@ execcmd_exec(Estate state, Execcmd_params eparams, /* position on last non-NULL character */ cmdopt += strlen(cmdopt+1); } else { - if (!firstnode(args)) { + if (!argnode) { zerr("exec requires a command to execute"); lastval = 1; errflag |= ERRFLAG_ERROR; goto done; } - if (!nextnode(firstnode(args))) { + if (!nextnode(argnode)) + execcmd_getargs(preargs, args, + eparams->htok); + if (!nextnode(argnode)) { zerr("exec flag -a requires a parameter"); lastval = 1; errflag |= ERRFLAG_ERROR; goto done; } - exec_argv0 = (char *) - getdata(nextnode(firstnode(args))); - uremnode(args, firstnode(args)); + exec_argv0 = (char *) getdata(argnode); + oldnode = argnode; + argnode = nextnode(argnode); + uremnode(args, oldnode); } break; case 'c': @@ -2906,8 +2961,9 @@ execcmd_exec(Estate state, Execcmd_params eparams, return; } } - if (firstnode(args) && nextnode(firstnode(args))) - next = (char *) getdata(nextnode(firstnode(args))); + if (!argnode) + break; + argdata = (char *) getdata(argnode); } if (exec_argv0) { char *str, *s; @@ -2919,12 +2975,14 @@ execcmd_exec(Estate state, Execcmd_params eparams, zputenv(str); } } - uremnode(args, firstnode(args)); hn = NULL; if ((cflags & BINF_COMMAND) && unset(POSIXBUILTINS)) break; + if (!nonempty(preargs)) + execcmd_getargs(preargs, args, eparams->htok); } - } + } else + preargs = NULL; /* if we get this far, it is OK to pay attention to lastval again */ if (noerrexit == 2 && !is_shfunc) @@ -2946,8 +3004,12 @@ execcmd_exec(Estate state, Execcmd_params eparams, esprefork = (magic_assign || (isset(MAGICEQUALSUBST) && type != WC_TYPESET)) ? PREFORK_TYPESET : 0; - if (args && eparams->htok) - prefork(args, esprefork, NULL); + if (args) { + if (eparams->htok) + prefork(args, esprefork, NULL); + if (preargs) + args = joinlists(preargs, args); + } if (type == WC_SIMPLE || type == WC_TYPESET) { int unglobbed = 0; diff --git a/Src/linklist.c b/Src/linklist.c index 3aa8125..85d9bb3 100644 --- a/Src/linklist.c +++ b/Src/linklist.c @@ -348,6 +348,35 @@ newsizedlist(int size) } /* + * Join two linked lists. Neither may be null, though either + * may be empty. + * + * It is assumed the pieces come from the heap, but if not it is + * safe to free LinkList second. + */ + +/**/ +mod_export LinkList +joinlists(LinkList first, LinkList second) +{ + LinkNode moveme = firstnode(second); + if (moveme) { + if (firstnode(first)) { + LinkNode anchor = lastnode(first); + anchor->next = moveme; + moveme->prev = anchor; + } else { + first->list.first = moveme; + moveme->prev = &first->node; + } + first->list.last = second->list.last; + + second->list.first = second->list.last = NULL; + } + return first; +} + +/* * Return the node whose data is the pointer "dat", else NULL. * Can be used as a boolean test. */ diff --git a/Test/A01grammar.ztst b/Test/A01grammar.ztst index e4b6870..32caf5f 100644 --- a/Test/A01grammar.ztst +++ b/Test/A01grammar.ztst @@ -103,6 +103,13 @@ 0:`exec' with -a option, no space >/bin/SPLOOSH + ( + opts=(-a /bin/WHOOOSH) + exec $opts /bin/sh -c 'echo $0' + ) +0:`exec' with -a option from expansion +>/bin/WHOOOSH + (export FOO=bar; exec -c /bin/sh -c 'echo x${FOO}x') 0:`exec' with -c option >xx @@ -123,6 +130,21 @@ >cat is /*/cat >echo is a shell builtin + args=( + 'command -pv cat' + 'command -pv echo' + 'command -p -V cat' + 'command -p -V -- echo' + ) + for arg in $args; do + ${=arg} + done +0:command -p in combination, using expansion +*>*/cat +>echo +>cat is /*/cat +>echo is a shell builtin + cd() { echo Not cd at all; } builtin cd . && unfunction cd 0:`builtin' precommand modifier diff --git a/Test/E01options.ztst b/Test/E01options.ztst index 2bd4fdb..dac9430 100644 --- a/Test/E01options.ztst +++ b/Test/E01options.ztst @@ -804,6 +804,20 @@ >print is a shell builtin ?(eval):8: command not found: print + ( + setopt posixbuiltins + opts=() + command $opts print foo + opts=(-v) + command $opts print + opts=(-V) + command $opts print + ) +0:command with options from expansion +>foo +>print +>print is a shell builtin + # With non-special command: original value restored # With special builtin: new value kept # With special builtin preceeded by "command": original value restored.