@@ -148,6 +148,16 @@ _comp_readline_variable_on()
148148_comp_deprecate_func _rl_enabled _comp_readline_variable_on
149149
150150# This function shell-quotes the argument
151+ # @param $1 String to be quoted
152+ # @var[out] ret Resulting string
153+ _comp_quote ()
154+ {
155+ ret=\' ${1// \' / \'\\\'\' } \'
156+ }
157+
158+ # This function shell-quotes the argument
159+ # @deprecated Use `_comp_quote` instead. Note that `_comp_quote` stores
160+ # the results in the variable `ret` instead of writing them to stdout.
151161quote ()
152162{
153163 local quoted=${1// \' / \'\\\'\' }
@@ -162,10 +172,77 @@ quote_readline()
162172 printf %s " $ret "
163173} # quote_readline()
164174
175+ # shellcheck disable=SC1003
176+ _comp_dequote__initialize ()
177+ {
178+ unset -f " $FUNCNAME "
179+ local regex_param=' \$([_a-zA-Z][_a-zA-Z0-9]*|[-*@#?$!0-9_])|\$\{[!#]?([_a-zA-Z][_a-zA-Z0-9]*(\[([0-9]+|[*@])\])?|[-*@#?$!0-9_])\}'
180+ local regex_quoted=' \\.|' \' ' [^' \' ' ]*' \' ' |\$?"([^\"$`!]|' $regex_param ' |\\.)*"|\$' \' ' ([^\' \' ' ]|\\.)*' \' ' '
181+ _comp_dequote__regex_safe_word=' ^([^\' \' ' "$`;&|<>()!]|' $regex_quoted ' |' $regex_param ' )*$'
182+ }
183+ _comp_dequote__initialize
184+
185+ # This function expands a word using `eval` in a safe way. This function can
186+ # be typically used to get the expanded value of `${word[i]}` as
187+ # `_comp_dequote "${word[i]}"`. When the word contains unquoted shell special
188+ # characters, command substitutions, and other unsafe strings, the function
189+ # call fails before applying `eval`. Otherwise, `eval` is applied to the
190+ # string to generate the result.
191+ #
192+ # @param $1 String to be expanded. A safe word consists of the following
193+ # sequence of substrings:
194+ #
195+ # - Shell non-special characaters: [^\'"$`;&|<>()!].
196+ # - Parameter expansions of the forms $PARAM, ${!PARAM},
197+ # ${#PARAM}, ${NAME[INDEX]}, ${!NAME[INDEX]}, ${#NAME[INDEX]}
198+ # where INDEX is an integer, `*` or `@`, NAME is a valid
199+ # variable name [_a-zA-Z][_a-zA-Z0-9]*, and PARAM is NAME or a
200+ # parameter [-*@#?$!0-9_].
201+ # - Quotes \?, '...', "...", $'...', and $"...". In the double
202+ # quotations, parameter expansions are allowed.
203+ #
204+ # @var[out] ret Array that contains the expanded results. Multiple words or no
205+ # words may be generated through pathname expansions.
206+ #
207+ # Note: This function allows parameter expansions as safe strings, which might
208+ # cause unexpected results:
209+ #
210+ # * This allows execution of arbitrary commands through extra expansions of
211+ # array subscripts in name references. For example,
212+ #
213+ # declare -n v='dummy[$(echo xxx >/dev/tty)]'
214+ # echo "$v" # This line executes the command 'echo xxx'.
215+ # _comp_dequote '"$v"' # This line also executes it.
216+ #
217+ # * This may change the internal state of the variable that has side effects.
218+ # For example, the state of the random number generator of RANDOM can change:
219+ #
220+ # RANDOM=1234 # Set seed
221+ # echo "$RANDOM" # This produces 30658.
222+ # RANDOM=1234 # Reset seed
223+ # _comp_dequote '"$RANDOM"' # This line changes the internal state.
224+ # echo "$RANDOM" # This fails to reproduce 30658.
225+ #
226+ # We allow these parameter expansions as a part of safe strings assuming the
227+ # referential transparency of the simple parameter expansions and the sane
228+ # setup of the variables by the user or other frameworks that the user loads.
229+ _comp_dequote ()
230+ {
231+ ret=() # fallback value for unsafe word and failglob
232+ [[ $1 =~ $_comp_dequote__regex_safe_word ]] || return 1
233+ eval " ret=($1 )" 2> /dev/null # may produce failglob
234+ }
235+
165236# This function shell-dequotes the argument
237+ # @deprecated Use `_comp_dequote' instead. Note that `_comp_dequote` stores
238+ # the results in the array `ret` instead of writing them to stdout.
166239dequote ()
167240{
168- eval printf %s " $1 " 2> /dev/null
241+ local ret
242+ _comp_dequote " $1 "
243+ local rc=$?
244+ printf %s " $ret "
245+ return $rc
169246}
170247
171248# Unset the given variables across a scope boundary. Useful for unshadowing
@@ -1030,14 +1107,14 @@ _parse_help()
10301107 shopt -s lastpipe
10311108 set -o noglob
10321109
1033- eval local cmd="$(quote " $1 ")"
1110+ local cmd=$1
10341111 local line rc=1
1035- {
1112+ (
10361113 case $cmd in
1037- -) cat ;;
1038- *) LC_ALL=C "$(dequote " $cmd ") " ${2:- --help} 2 >& 1 ;;
1114+ -) exec cat ;;
1115+ *) _comp_dequote " $cmd " && LC_ALL=C "$ret " ${2:- --help} 2 >& 1 ;;
10391116 esac
1040- } |
1117+ ) |
10411118 while read -r line; do
10421119
10431120 [[ $line == *([[:blank:]])-* ]] || continue
@@ -1067,14 +1144,14 @@ _parse_usage()
10671144 shopt -s lastpipe
10681145 set -o noglob
10691146
1070- eval local cmd="$(quote " $1 ")"
1147+ local cmd=$1
10711148 local line match option i char rc=1
1072- {
1149+ (
10731150 case $cmd in
1074- -) cat ;;
1075- *) LC_ALL=C "$(dequote " $cmd ") " ${2:- --usage} 2 >& 1 ;;
1151+ -) exec cat ;;
1152+ *) _comp_dequote " $cmd " && LC_ALL=C "$ret " ${2:- --usage} 2 >& 1 ;;
10761153 esac
1077- } |
1154+ ) |
10781155 while read -r line; do
10791156
10801157 while [[ $line =~ \[[[:space:]]*(-[^]]+)[[:space:]]*\] ]]; do
0 commit comments