summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordana <dana@dana.is>2026-04-19 12:35:02 -0500
committerdana <dana@dana.is>2026-04-19 12:36:10 -0500
commit90fa3d2816ee3c770966dd42f548d770d266d2bd (patch)
tree043524d84eaa407ebd31bf51dfa4819b6c978bc3
parent54181, 54331: remove support for restricted shell (diff)
downloadzsh-90fa3d2816ee3c770966dd42f548d770d266d2bd.tar
zsh-90fa3d2816ee3c770966dd42f548d770d266d2bd.tar.gz
zsh-90fa3d2816ee3c770966dd42f548d770d266d2bd.tar.bz2
zsh-90fa3d2816ee3c770966dd42f548d770d266d2bd.tar.lz
zsh-90fa3d2816ee3c770966dd42f548d770d266d2bd.tar.xz
zsh-90fa3d2816ee3c770966dd42f548d770d266d2bd.tar.zst
zsh-90fa3d2816ee3c770966dd42f548d770d266d2bd.zip
54361 (tweaked): zparseopts: print friendlier usage errors, add -n option
tweaked to adjust order of options in documentation
-rw-r--r--ChangeLog7
-rw-r--r--Completion/Zsh/Command/_zparseopts1
-rw-r--r--Doc/Zsh/mod_zutil.yo8
-rw-r--r--Src/Modules/zutil.c23
-rw-r--r--Test/V12zparseopts.ztst59
5 files changed, 78 insertions, 20 deletions
diff --git a/ChangeLog b/ChangeLog
index 1eefe2c02..bbf2b0eac 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2026-04-19 dana <dana@dana.is>
+
+ * 54361 (tweaked): Completion/Zsh/Command/_zparseopts,
+ Doc/Zsh/mod_zutil.yo, Src/Modules/zutil.c,
+ Test/V12zparseopts.ztst: zparseopts: print friendlier usage
+ errors, add -n option
+
2026-04-14 Oliver Kiddle <opk@zsh.org>
* 54181, 54331: Completion/Zsh/Command/_set,
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..47daec3cb 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(-a) var(array) ] [ tt(-A) var(assoc) ] [ tt(-n) var(name) ] [ 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