Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
[PATCH] zparseopts: print friendlier usage errors, add -n option
- X-seq: zsh-workers 54361
- From: dana <dana@xxxxxxx>
- To: zsh-workers@xxxxxxx
- Subject: [PATCH] zparseopts: print friendlier usage errors, add -n option
- Date: Wed, 15 Apr 2026 18:39:22 -0500
- Archived-at: <https://zsh.org/workers/54361>
- Feedback-id: i9be146f9:Fastmail
- List-id: <zsh-workers.zsh.org>
currently all error messages produced by zparseopts look like this:
caller:zparseopts:5: bad option: -x
this is unattractive and possibly unhelpful in usage errors meant for
end-users (as opposed to errors in the usage of zparseopts itself). the
user probably only cares about the caller name, and even that is
sometimes not meaningful
this patch changes end-user error messages to include only the caller
name, and adds an -n option to override it:
% () { zparseopts -F - } -x
(anon): bad option: -x
% () { zparseopts -n foo -F - } -x
foo: bad option: -x
compare to util-linux getopt(1):
% getopt -n foo '' -x > /dev/null
foo: invalid option -- x
dana
diff --git a/Completion/Zsh/Command/_zparseopts b/Completion/Zsh/Command/_zparseopts
index ae81937c1..cc5e28a9a 100644
--- a/Completion/Zsh/Command/_zparseopts
+++ b/Completion/Zsh/Command/_zparseopts
@@ -13,6 +13,7 @@ _arguments -A '-*' : \
'-G[enable GNU-style parsing]' \
'-K[preserve contents of arrays/associations when specs are not matched]' \
'-M[enable mapping among equivalent options with opt1=opt2 spec form]' \
+ '-n+[specify program name for usage errors]:program name' \
'-v+[specify array from which to parse options]:array:_parameters -g "*array*"' \
'(-)-[end zparseopts options; specs follow]' \
'*: :->spec' \
diff --git a/Doc/Zsh/mod_zutil.yo b/Doc/Zsh/mod_zutil.yo
index 06b953f69..adbdc92c6 100644
--- a/Doc/Zsh/mod_zutil.yo
+++ b/Doc/Zsh/mod_zutil.yo
@@ -230,7 +230,7 @@ item(tt(zregexparse))(
This implements some internals of the tt(_regex_arguments) function.
)
findex(zparseopts)
-item(tt(zparseopts) [ tt(-D) tt(-E) tt(-F) tt(-G) tt(-K) tt(-M) ] [ tt(-a) var(array) ] [ tt(-A) var(assoc) ] [ tt(-v) var(argv) ] [ tt(-) ] var(spec) ...)(
+item(tt(zparseopts) [ tt(-D) tt(-E) tt(-F) tt(-G) tt(-K) tt(-M) ] [ tt(-n) var(name) ] [ tt(-a) var(array) ] [ tt(-A) var(assoc) ] [ tt(-v) var(argv) ] [ tt(-) ] var(spec) ...)(
This builtin simplifies the parsing of options in positional parameters,
i.e. the set of arguments given by tt($*). Each var(spec) describes one
option and must be of the form `var(opt)[tt(=)var(array)]'. If an option
@@ -391,6 +391,12 @@ is found, the values are stored as usual. This changes only the way the
values are stored, not the way tt($*) is parsed, so results may be
unpredictable if the `var(name)tt(+)' specifier is used inconsistently.
)
+item(tt(-n) var(name))(
+If this option is given, var(name) is used when printing usage errors
+intended for the end user. Otherwise, the name of the calling function
+or script is used. Error messages arising from misusage of tt(zparseopts)
+itself are not affected by this option.
+)
item(tt(-v) var(argv))(
With this option, the elements of the named array var(argv) are used as the
positional parameters to parse, rather than those given by tt($*). The
diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c
index 9e1eabcf1..de070a53d 100644
--- a/Src/Modules/zutil.c
+++ b/Src/Modules/zutil.c
@@ -1739,6 +1739,7 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
{
char *o, *p, *n, **pp, **aval, **ap, *assoc = NULL, **cp, **np;
char *paramsname = NULL, **params;
+ char *progname = scriptname ? scriptname : (argzero ? argzero : nam);
int del = 0, flags = 0, extract = 0, fail = 0, gnu = 0, keep = 0;
Zoptdesc sopts[256], d;
Zoptarr a, defarr = NULL;
@@ -1808,6 +1809,16 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
}
flags |= ZOF_MAP;
break;
+ case 'n':
+ if (o[2])
+ progname = o + 2;
+ else if (*args)
+ progname = *args++;
+ else {
+ zwarnnam(nam, "missing program name");
+ return 1;
+ }
+ break;
case 'a':
if (defarr) {
zwarnnam(nam, "default array given more than once");
@@ -1981,9 +1992,9 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
if (!(d = sopts[(unsigned char) *o])) {
if (fail) {
if (*o != '-' || o > *pp + 1)
- zwarnnam(nam, "bad option: -%c", *o);
+ fprintf(stderr, "%s: bad option: -%c\n", progname, *o);
else
- zwarnnam(nam, "bad option: -%s", o);
+ fprintf(stderr, "%s: bad option: -%s\n", progname, o);
return 1;
}
o = NULL;
@@ -2002,8 +2013,8 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
(!(d->flags & (ZOF_GNUL | ZOF_GNUS)) &&
pp[1] && pp[1][0] != '-')) {
if (!pp[1]) {
- zwarnnam(nam, "missing argument for option: -%s",
- d->name);
+ fprintf(stderr, "%s: missing argument for option: -%s\n",
+ progname, d->name);
return 1;
}
add_opt_val(d, *++pp);
@@ -2045,8 +2056,8 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
(!(d->flags & (ZOF_GNUL | ZOF_GNUS)) &&
pp[1] && pp[1][0] != '-')) {
if (!pp[1]) {
- zwarnnam(nam, "missing argument for option: -%s",
- d->name);
+ fprintf(stderr, "%s: missing argument for option: -%s\n",
+ progname, d->name);
return 1;
}
add_opt_val(d, *++pp);
diff --git a/Test/V12zparseopts.ztst b/Test/V12zparseopts.ztst
index 5736dd3a1..907134230 100644
--- a/Test/V12zparseopts.ztst
+++ b/Test/V12zparseopts.ztst
@@ -17,6 +17,18 @@
%test
+ () { zparseopts -F } -x
+ () { zparseopts -F - } -x
+ () { zparseopts -F -- } -x
+ () { zparseopts -F - a } -x
+ () { zparseopts -F a } -x
+1:zparseopts without option specs, without array
+?(anon):zparseopts: missing option descriptions
+?(anon): bad option: -x
+?(anon): bad option: -x
+?(anon):zparseopts: no default array defined: a
+?(anon):zparseopts: no default array defined: a
+
() {
local -a optv
zparseopts -a optv - a b: c:- z
@@ -65,15 +77,15 @@
} $=1
done
0:zparseopts -F
-?(anon):zparseopts:2: bad option: -x
+?(anon): bad option: -x
>ret: 1, optv: , argv: -a -x -z
-?(anon):zparseopts:2: bad option: -x
+?(anon): bad option: -x
>ret: 1, optv: , argv: -ax -z
-?(anon):zparseopts:2: bad option: --x
+?(anon): bad option: --x
>ret: 1, optv: , argv: -a --x -z
-?(anon):zparseopts:2: bad option: -x
+?(anon): bad option: -x
>ret: 1, optv: , argv: -axy
-?(anon):zparseopts:2: bad option: --
+?(anon): bad option: --
>ret: 1, optv: , argv: -a-xy
for 1 in '-a 1 2 3' '1 2 3'; do
@@ -218,7 +230,7 @@
print -r - ret: $?, optv: $optv, argv: $argv
} -ab1 -c
0:missing optarg
-?(anon):zparseopts:2: missing argument for option: -c
+?(anon): missing argument for option: -c
>ret: 1, optv: , argv: -ab1 -c
for spec in -foo: -foo:- -foo::; do
@@ -253,11 +265,11 @@
} --foobar 1 2 3
done
0:zparseopts -G, single parameter, without =
-?(anon):zparseopts:2: bad option: --foobar
+?(anon): bad option: --foobar
>ret: 1, optv: , argv: --foobar 1 2 3
-?(anon):zparseopts:2: bad option: --foobar
+?(anon): bad option: --foobar
>ret: 1, optv: , argv: --foobar 1 2 3
-?(anon):zparseopts:2: bad option: --foobar
+?(anon): bad option: --foobar
>ret: 1, optv: , argv: --foobar 1 2 3
for spec in -foo: -foo:- -foo::; do
@@ -352,15 +364,15 @@
>ret: 0, gopt: , optv: -foobar, argv: 1 2 3
>ret: 0, gopt: , optv: -foo=bar, argv: 1 2 3
>ret: 0, gopt: , optv: -foobar, argv: 1 2 3
-?(anon):zparseopts:2: bad option: -f
+?(anon): bad option: -f
>ret: 1, gopt: -G, optv: , argv: -foobar 1 2 3
>ret: 0, gopt: -G, optv: -foo bar, argv: 1 2 3
>ret: 0, gopt: -G, optv: -foo bar, argv: 1 2 3
-?(anon):zparseopts:2: bad option: -f
+?(anon): bad option: -f
>ret: 1, gopt: -G, optv: , argv: -foobar 1 2 3
>ret: 0, gopt: -G, optv: -foo=bar, argv: 1 2 3
>ret: 0, gopt: -G, optv: -foo=bar, argv: 1 2 3
-?(anon):zparseopts:2: bad option: -f
+?(anon): bad option: -f
>ret: 1, gopt: -G, optv: , argv: -foobar 1 2 3
>ret: 0, gopt: -G, optv: -foo=bar, argv: 1 2 3
>ret: 0, gopt: -G, optv: -foo=, argv: bar 1 2 3
@@ -382,10 +394,31 @@
done
done
0:only -- acts as explicit parsing terminator with -G
-?(anon):zparseopts:2: bad option: --baz
+?(anon): bad option: --baz
>ret: 1, term: -, optv: , argv: --foo x --bar - --baz
>ret: 0, term: -, gopt: , optv: --foo, argv: --bar
>ret: 0, term: -, gopt: -G, optv: --foo, argv: - --bar
>ret: 0, term: --, optv: --foo --bar, argv: x -- --baz
>ret: 0, term: --, gopt: , optv: --foo, argv: --bar
>ret: 0, term: --, gopt: -G, optv: --foo, argv: --bar
+
+ fn1() {
+ zparseopts -F - a
+ }
+ fn2() {
+ local -a optv
+ zparseopts -a optv -F - a
+ }
+ fn3() {
+ local -a optv
+ zparseopts -a optv -nprog -F - a
+ zparseopts -a optv -n prog -F - a
+ }
+ fn1 -x
+ fn2 -x
+ fn3 -x
+1:zparseopts -n, internal vs external usage errors
+?fn1:zparseopts:1: no default array defined: a
+?fn2: bad option: -x
+?prog: bad option: -x
+?prog: bad option: -x
Messages sorted by:
Reverse Date,
Date,
Thread,
Author