Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
[PATCH] zparseopts: support empty optspec
- X-seq: zsh-workers 54362
- From: dana <dana@xxxxxxx>
- To: zsh-workers@xxxxxxx
- Subject: [PATCH] zparseopts: support empty optspec
- Date: Wed, 15 Apr 2026 23:05:00 -0500
- Archived-at: <https://zsh.org/workers/54362>
- Feedback-id: i9be146f9:Fastmail
- List-id: <zsh-workers.zsh.org>
both getopt(1) and getopts(1) support an empty optspec, which is useful
if you explicitly want *no* options to be recognised, perhaps to reserve
them for the future. it also handles -- for you
there are currently two equivalent methods with zparseopts, and i think
both of them are accidental:
zparseopts -D -F -
this one works because we seem to just assume the presence of the -/--
terminator means a spec will follow, bypassing the normal check for one.
i've been using this for testing but i don't think we should support it
officially
zparseopts -a junk -D -F - -
this one works because, although it does handle the - as a spec, it's
not possible to actually parse an option from it (since -- is always a
terminator). there's really no other way this could work so i think it's
reasonable to handle it specially so that the junk array isn't needed
in addition to that i would like to support a single empty optspec
(which is currently erroneous):
zparseopts -D -F ''
this matches getopt/s and i think it's easier to understand what it
means
the first option with one -/-- will actually be illegal with a change
i'd like to make later (probably too late for 5.10). the warning i added
to the documentation references this potential change, and it's good
practice anyway even if it doesn't happen
dana
diff --git a/Doc/Zsh/mod_zutil.yo b/Doc/Zsh/mod_zutil.yo
index adbdc92c6..d74334d12 100644
--- a/Doc/Zsh/mod_zutil.yo
+++ b/Doc/Zsh/mod_zutil.yo
@@ -239,7 +239,11 @@ into the var(array) specified with the tt(-a) option; if the optional
`tt(=)var(array)' is given, it is instead copied into that array, which
should be declared as a normal array and never as an associative array.
-Note that it is an error to give any var(spec) without an
+At least one var(spec) must be given. If a single spec is given which
+is either an empty string or a single hyphen (as in
+tt(zparseopts -D -F '')), it signifies that no options are recognised.
+(This is useful for future-proofing functions that might gain options
+later.) Otherwise, it is an error to give any var(spec) without an
`tt(=)var(array)' unless one of the tt(-a) or tt(-A) options is used.
Unless the tt(-E) option is given, parsing stops at the first string
@@ -300,8 +304,14 @@ var(spec) wins. (This is not an issue with GNU-style parsing.)
The options of tt(zparseopts) itself cannot be stacked because, for
example, the stack `tt(-DEK)' is indistinguishable from a var(spec) for
-the GNU-style long option `tt(-)tt(-DEK)'. The options of tt(zparseopts)
-itself are:
+the GNU-style long option `tt(-)tt(-DEK)'.
+
+Nevertheless, it is strongly recommended to distinguish GNU-style long
+option var(spec)s from tt(zparseopts)'s own options by preceding them by
+a `tt(-)', `tt(--)', or short option tt(spec), as in
+tt(zparseopts -a opts - -foo). In the future this may become mandatory.
+
+The options of tt(zparseopts) itself are:
startitem()
item(tt(-a) var(array))(
diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c
index de070a53d..f13ac95ac 100644
--- a/Src/Modules/zutil.c
+++ b/Src/Modules/zutil.c
@@ -1882,10 +1882,15 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
break;
}
}
- if (!o) {
+
+ /* allow a single '' or - spec to signify no options recognised */
+ if (o && *args && !args[1] && (!**args || !strcmp(*args, "-"))) {
+ args++;
+ } else if (!o) {
zwarnnam(nam, "missing option descriptions");
return 1;
}
+
while ((o = dupstring(*args++))) {
int f = 0;
if (!*o) {
diff --git a/Test/V12zparseopts.ztst b/Test/V12zparseopts.ztst
index 907134230..6a880b49c 100644
--- a/Test/V12zparseopts.ztst
+++ b/Test/V12zparseopts.ztst
@@ -17,12 +17,29 @@
%test
+ () { zparseopts -D -F ''; print -r - ${(q+)@} } - foo
+ () { zparseopts -D -F ''; print -r - ${(q+)@} } -x
+ () { zparseopts -D -F - ''; print -r - ${(q+)@} } - foo
+ () { zparseopts -D -F - ''; print -r - ${(q+)@} } -x
+ () { zparseopts -D -F - -; print -r - ${(q+)@} } - foo
+ () { zparseopts -D -F - -; print -r - ${(q+)@} } -x
+0:zparseopts with empty option spec (no options recognised)
+>foo
+?(anon): bad option: -x
+>-x
+>foo
+?(anon): bad option: -x
+>-x
+>foo
+?(anon): bad option: -x
+>-x
+
() { zparseopts -F } -x
() { zparseopts -F - } -x
() { zparseopts -F -- } -x
() { zparseopts -F - a } -x
() { zparseopts -F a } -x
-1:zparseopts without option specs, without array
+1:zparseopts without option specs, without array (may change in future)
?(anon):zparseopts: missing option descriptions
?(anon): bad option: -x
?(anon): bad option: -x
Messages sorted by:
Reverse Date,
Date,
Thread,
Author