Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
[PATCH] _call_program: add -qQ options, etc
- X-seq: zsh-workers 54912
- From: dana <dana@xxxxxxx>
- To: zsh-workers@xxxxxxx
- Subject: [PATCH] _call_program: add -qQ options, etc
- Date: Sat, 04 Jul 2026 22:44:45 -0500
- Archived-at: <https://zsh.org/workers/54912>
- Feedback-id: i9be146f9:Fastmail
- List-id: <zsh-workers.zsh.org>
some improvements to _call_program:
- add -qQ options to quote the command arguments before passing to eval.
these are sort of similar to zformat -qQ
- use zparseopts for arg parsing
- automatically execute the evaluated command in a sub-shell if
necessary. this prevents the function from wrecking the user's locale
if called in ${ ... }. ty mikael for the idea
dana
diff --git a/Completion/Base/Utility/_call_program b/Completion/Base/Utility/_call_program
index 55712b04b..38bcede0b 100644
--- a/Completion/Base/Utility/_call_program
+++ b/Completion/Base/Utility/_call_program
@@ -1,19 +1,30 @@
#autoload +X
local -xi COLUMNS=999
-local curcontext="${curcontext}" tmp err_fd=-1 clocale='_comp_locale;'
-local -a prefix
-
-if [[ "$1" = -p ]]; then
- shift
- if (( $#_comp_priv_prefix )); then
- curcontext="${curcontext%:*}/${${(@M)_comp_priv_prefix:#^*[^\\]=*}[1]}:"
- zstyle -t ":completion:${curcontext}:${1}" gain-privileges &&
- prefix=( $_comp_priv_prefix )
- fi
-elif [[ "$1" = -l ]]; then
- shift
- clocale=''
+local curcontext="${curcontext}" tmp ssb sse err_fd=-1 clocale='_comp_locale;'
+local -a prefix args
+local -A opth
+
+zparseopts -A opth -D -F - l p q Q || return
+
+(( $+opth[-l] )) && clocale=
+
+# function authors might be tempted to run this in ${ ... }. but doing so will
+# wreck the user's locale. force a sub-shell if necessary to avoid this
+(( $+opth[-l] || ZSH_SUBSHELL )) || ssb='(' sse=')'
+
+(( $+opth[-p] && $#_comp_priv_prefix )) && {
+ curcontext="${curcontext%:*}/${${(@M)_comp_priv_prefix:#^*[^\\]=*}[1]}:"
+ zstyle -t ":completion:${curcontext}:${1}" gain-privileges &&
+ prefix=( $_comp_priv_prefix )
+}
+
+if (( $+opth[-q] )); then
+ args=( "${(@q+)argv[2,-1]}" )
+elif (( $+opth[-Q] )); then
+ args=( "$2" "${(@q+)argv[3,-1]}" )
+else
+ args=( "${(@)argv[2,-1]}" )
fi
if (( ${debug_fd:--1} > 2 )) || [[ ! -t 2 ]]
@@ -25,12 +36,12 @@ fi
if zstyle -s ":completion:${curcontext}:${1}" command tmp; then
if [[ "$tmp" = -* ]]; then
- eval $clocale "$tmp[2,-1]" "$argv[2,-1]"
+ eval $ssb $clocale "$tmp[2,-1]" "${(@)args}" $sse
else
- eval $clocale $prefix "$tmp"
+ eval $ssb $clocale $prefix "$tmp" $sse
fi
else
- eval $clocale $prefix "$argv[2,-1]"
+ eval $ssb $clocale $prefix "${(@)args}" $sse
fi 2>&$err_fd
} always {
diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo
index 563833f7b..cc53fb110 100644
--- a/Doc/Zsh/compsys.yo
+++ b/Doc/Zsh/compsys.yo
@@ -4292,7 +4292,7 @@ The return status of tt(_call_function) itself is zero if the function
var(name) exists and was called and non-zero otherwise.
)
findex(_call_program)
-item(tt(_call_program) [ tt(-l) ] [ tt(-p) ] var(tag) var(string) ...)(
+item(tt(_call_program) [ tt(-lpqQ) ] var(tag) var(string) ...)(
This function provides a mechanism for the user to override the use of an
external command. It looks up the tt(command) style with the supplied
var(tag). If the style is set, its value is used as the command to
@@ -4314,6 +4314,15 @@ match the permissions to whatever the final command is likely to run
under. When looking up the tt(gain-privileges) and tt(command) styles,
the command component of the zstyle context will end with a slash
(`tt(/)') followed by the command that would be used to gain privileges.
+
+By default, the var(string)s are passed directly to tt(eval), which may
+require double-quoting in some cases. The options tt(-q) and tt(-Q) can
+help with this: with tt(-q), all var(string)s are quoted, whereas with
+tt(-Q) all var(string)s except the first one (usually the name of the
+command to run) are quoted. tt(-Q) is useful when the first var(string)
+is tt($words[1]), which may already be quoted since it is taken directly
+from the typed command line. These options do not affect the quoting of
+arguments derived from the tt(command) style.
)
findex(_combination)
item(tt(_combination) [ tt(-s) var(pattern) ] var(tag) var(style) var(spec) ... var(field) var(opts) ...)(
diff --git a/Test/Y07call_program.ztst b/Test/Y07call_program.ztst
new file mode 100644
index 000000000..19eca8f9f
--- /dev/null
+++ b/Test/Y07call_program.ztst
@@ -0,0 +1,107 @@
+# tests for _call_program
+
+%prep
+
+ if ( zmodload -s zsh/zpty ); then
+ source $ZTST_srcdir/comptest
+ mkdir comp.tmp
+ cd comp.tmp
+ comptestinit -z $ZTST_testdir/../Src/zsh && {
+ comptesteval '
+ pp() { printf "<%s>" "$@"; print }
+ compdef _tst tst
+ '
+ tst_call_program() {
+ comptesteval "_tst() {
+ [[ ${(q+)1} == -[lpqQ]#p[lpqQ]# ]] &&
+ local -a _comp_priv_prefix=( print -r priv )
+ local ret=\${ _call_program ${${(@q+)@}} }
+ _message - \$ret
+ }"
+ comptest $'tst \t'
+ }
+ }
+ else
+ ZTST_unimplemented='the zsh/zpty module is not available'
+ fi
+
+%test
+
+ tst_call_program tag pp a b c
+ tst_call_program tag pp 'a b c'
+ tst_call_program tag pp '"a b c"'
+0:basic command arguments
+>line: {tst }{}
+>MESSAGE:{<a><b><c>}
+>line: {tst }{}
+>MESSAGE:{<a><b><c>}
+>line: {tst }{}
+>MESSAGE:{<a b c>}
+
+ for 1 in -q -Q; do
+ tst_call_program $1 tag pp $1 a b c
+ tst_call_program $1 tag pp $1 'a b c'
+ tst_call_program $1 tag pp $1 '"a b c"'
+ done
+0:-qQ: argument quoting
+>line: {tst }{}
+>MESSAGE:{<-q><a><b><c>}
+>line: {tst }{}
+>MESSAGE:{<-q><a b c>}
+>line: {tst }{}
+>MESSAGE:{<-q><"a b c">}
+>line: {tst }{}
+>MESSAGE:{<-Q><a><b><c>}
+>line: {tst }{}
+>MESSAGE:{<-Q><a b c>}
+>line: {tst }{}
+>MESSAGE:{<-Q><"a b c">}
+
+ tst_call_program tag '"pp"' a b c
+ tst_call_program -q tag '"pp"' a b c
+ tst_call_program -Q tag '"pp"' a b c
+0:-qQ: command-name quoting
+>line: {tst }{}
+>MESSAGE:{<a><b><c>}
+>line: {tst }{}
+>MESSAGE:{}
+>line: {tst }{}
+>MESSAGE:{<a><b><c>}
+
+ if [[ -z ${lc::=${ ZTST_find_UTF8 }} ]]; then
+ ZTST_skip='no utf8 locale'
+ else
+ comptesteval "old_LC_ALL=\$LC_ALL; LC_ALL=${(q+)lc}"
+ for 1 in '' -l; do
+ tst_call_program $1 tag '
+ local -a arr=( z e é )
+ print -r - ${(o)arr}
+ '
+ done
+ comptesteval 'LC_ALL=$old_LC_ALL'
+ fi
+0:-l
+>line: {tst }{}
+>MESSAGE:{e z é}
+>line: {tst }{}
+>MESSAGE:{e é z}
+
+ tst_call_program tag print a b c
+ tst_call_program -p tag print a b c
+ comptesteval 'zstyle ":completion:*" gain-privileges yes'
+ tst_call_program tag print a b c
+ tst_call_program -p tag print a b c
+ comptesteval 'zstyle -d ":completion:*" gain-privileges'
+0:-p
+>line: {tst }{}
+>MESSAGE:{a b c}
+>line: {tst }{}
+>MESSAGE:{a b c}
+>line: {tst }{}
+>MESSAGE:{a b c}
+>line: {tst }{}
+>MESSAGE:{priv print a b c}
+
+%clean
+
+ zmodload -ui zsh/zpty
Messages sorted by:
Reverse Date,
Date,
Thread,
Author