scop / bash-completion

Programmable completion functions for bash
GNU General Public License v2.0
2.89k stars 380 forks source link

new occasions of unbound variables #524

Open calestyo opened 3 years ago

calestyo commented 3 years ago

Describe the bug

There are some new occasions of unbound variables, which AFAIU, #44 are now considered bugs.

To reproduce

Invoke "set -u" before invoking bash-completion in .bashrc. Launching a new shell, gives:

bash: filenames: unbound variable
bash: filenames: unbound variable
bash: filenames: unbound variable

Versions (please complete the following information)

scop commented 3 years ago

Thanks for the report. Please follow instructions in the bug report template -- this does not happen for me nor in CI, load time debug trace is needed to diagnose further.

I'm guessing that the trace will reveal that the errors come from completions not included with bash-completion but some 3rd party ones, per the above, and because there is no variable named filenames in the bash-completion source tree that I can see.

calestyo commented 3 years ago

Indeed you're right.

I was just looking at the occurrence of filenames in /usr/share/bash-completion/bash_completion and once I've found it, didn't think about the possibility of 3rd party completions.

Reported the issues at their respective upstreams: [0] [1]

Sorry for the noise

Cheers, Chris.

calestyo commented 3 years ago

Hey again.

This time I might have found a real occasion ;-)

If I have set -u and a command like: find ../new/ -type f -printf '%f\n' | while IFS='' read -r FILENAME; do printf 'ln -s /dev/nu %q\n' "${FILENAME}" ; done | sh now when I go to the /dev/nu and complete it with tab to /dev/null I get: ../new/ -type f -printf '%f\n' | while IFS='' read -r FILENAME; do printf 'ln -s /dev/nubash: COMP_WORDS[i]: unbound variable

This seems to happen in:

+ offset=1
+ (( i = 1 ))
+ (( i <= COMP_CWORD ))
+ [[ printf != -* ]]
+ offset=1
+ break
+ _command_offset 1
+ local word_offset=1 i j
+ (( i = 0 ))
+ (( i < word_offset ))
+ (( j = 0 ))
+ (( j <= 55 ))
+ [[ do printf 'ln -s /dev/n %q\n' "${FILENAME}" ; done | sh == \d\o* ]]
+ break
+ COMP_LINE=' printf '\''ln -s /dev/n %q\n'\'' "${FILENAME}" ; done | sh'
+ (( COMP_POINT -= 2 ))
+ (( i++ ))
+ (( i < word_offset ))
+ (( i = 0 ))
+ (( i <= COMP_CWORD - word_offset ))
+ COMP_WORDS[i]=printf
+ (( i++ ))
+ (( i <= COMP_CWORD - word_offset ))
+ COMP_WORDS[i]=''\''ln -s /dev/n %q\n'\'''
+ (( i++ ))
+ (( i <= COMP_CWORD - word_offset ))
+ (( i ))
+ (( i <= COMP_CWORD ))
+ unset 'COMP_WORDS[i]'
+ (( i++ ))
+ (( i <= COMP_CWORD ))
+ (( COMP_CWORD -= word_offset ))
+ COMPREPLY=()
+ local cur
+ _get_comp_words_by_ref cur
+ local exclude flag i OPTIND=1
+ words=()
+ local cur cword words
+ upargs=()
+ upvars=()
+ local upargs upvars vcur vcword vprev vwords
+ getopts c:i:n:p:w: flag cur
+ [[ 1 -ge 1 ]]
+ case ${!OPTIND} in
+ vcur=cur
+ (( OPTIND += 1 ))
+ [[ 1 -ge 2 ]]
+ __get_cword_at_cursor_by_ref '' words cword cur
+ words=()
+ local cword words
+ __reassemble_comp_words_by_ref '' words cword
+ local exclude i j line ref
+ [[ -n '' ]]
+ printf -v cword %s 1
+ [[ -v exclude ]]
+ for i in "${!COMP_WORDS[@]}"
+ printf -v 'words[i]' %s printf
+ for i in "${!COMP_WORDS[@]}"
+ printf -v 'words[i]' %s ''\''ln -s /dev/n %q\n'\'''
+ for i in "${!COMP_WORDS[@]}"
+ printf -v 'words[i]' %s '"${FILENAME}"'
+ for i in "${!COMP_WORDS[@]}"
+ printf -v 'words[i]' %s ';'
+ for i in "${!COMP_WORDS[@]}"
+ printf -v 'words[i]' %s done
+ for i in "${!COMP_WORDS[@]}"
+ printf -v 'words[i]' %s '|'
+ for i in "${!COMP_WORDS[@]}"
+ printf -v 'words[i]' %s sh
+ local i cur= index=21 'lead= printf '\''ln -s /dev/n'
+ [[ 21 -gt 0 ]]
+ [[ -n  printf 'ln -s /dev/n ]]
+ [[ -n printf'ln-s/dev/n ]]
+ cur=' printf '\''ln -s /dev/n %q\n'\'' "${FILENAME}" ; done | sh'
+ (( i = 0 ))
+ (( i <= cword ))
+ [[ 53 -ge 6 ]]
+ [[  print != \p\r\i\n\t\f ]]
+ cur='printf '\''ln -s /dev/n %q\n'\'' "${FILENAME}" ; done | sh'
+ (( index > 0 ))
+ (( index-- ))
+ [[ 52 -ge 6 ]]
+ [[ printf != \p\r\i\n\t\f ]]
+ (( i < cword ))
+ local old_size=52
+ cur=' '\''ln -s /dev/n %q\n'\'' "${FILENAME}" ; done | sh'
+ local new_size=46
+ (( index -= old_size - new_size ))
+ (( ++i ))
+ (( i <= cword ))
+ [[ 46 -ge 19 ]]
+ [[  'ln -s /dev/n %q\n != \'\l\n\ \-\s\ \/\d\e\v\/\n\ \%\q\\\n\' ]]
+ cur=''\''ln -s /dev/n %q\n'\'' "${FILENAME}" ; done | sh'
+ (( index > 0 ))
+ (( index-- ))
+ [[ 45 -ge 19 ]]
+ [[ 'ln -s /dev/n %q\n' != \'\l\n\ \-\s\ \/\d\e\v\/\n\ \%\q\\\n\' ]]
+ (( i < cword ))
+ (( ++i ))
+ (( i <= cword ))
+ [[ -n 'ln -s /dev/n %q\n' "${FILENAME}" ; done | sh ]]
+ [[ ! -n 'ln-s/dev/n%q\n'"${FILENAME}";done|sh ]]
+ (( index < 0 ))
+ local words cword cur
+ _upvars -a7 words printf ''\''ln -s /dev/n %q\n'\''' '"${FILENAME}"' ';' done '|' sh -v cword 1 -v cur ''\''ln -s /dev/n'
+ (( 15 ))
+ (( 15 ))
+ case $1 in
+ [[ -n 7 ]]
+ printf %d 7
+ [[ -n words ]]
+ unset -v words
+ eval 'words=("${@:3:7}")'
words=("${@:3:7}")
++ words=("${@:3:7}")
+ shift 9
+ (( 6 ))
+ case $1 in
+ [[ -n cword ]]
+ unset -v cword
+ eval 'cword="$3"'
cword="$3"
++ cword=1
+ shift 3
+ (( 3 ))
+ case $1 in
+ [[ -n cur ]]
+ unset -v cur
+ eval 'cur="$3"'
cur="$3"
++ cur=''\''ln -s /dev/n'
+ shift 3
+ (( 0 ))
+ [[ -v vcur ]]
+ upvars+=("$vcur")
+ upargs+=(-v $vcur "$cur")
+ [[ -v vcword ]]
+ [[ -v vprev ]]
+ [[ -v vwords ]]
+ (( 1 ))
+ local cur
+ _upvars -v cur ''\''ln -s /dev/n'
+ (( 3 ))
+ (( 3 ))
+ case $1 in
+ [[ -n cur ]]
+ unset -v cur
+ eval 'cur="$3"'
cur="$3"
++ cur=''\''ln -s /dev/n'
+ shift 3
+ (( 0 ))
+ (( COMP_CWORD == 0 ))
+ local cmd=printf compcmd=printf
++ complete -p printf
+ local 'cspec=complete -F _minimal printf'
+ [[ ! -n complete -F _minimal printf ]]
+ [[ ! -n complete -F _minimal printf ]]
+ [[ -n complete -F _minimal printf ]]
+ [[ _minimal printf != \c\o\m\p\l\e\t\e\ \-\F\ \_\m\i\n\i\m\a\l\ \p\r\i\n\t\f ]]
+ local 'func=_minimal printf'
+ func=_minimal
+ (( 7 >= 2 ))
+ _minimal printf sh '|'
+ local cur prev words cword split
+ _init_completion -s
+ local exclude= flag outx errx inx OPTIND=1
+ getopts n:e:o:i:s flag -s
+ case $flag in
+ split=false
+ exclude+==
+ getopts n:e:o:i:s flag -s
+ COMPREPLY=()
+ local 'redir=@(?([0-9])<|?([0-9&])>?(>)|>&)'
+ _get_comp_words_by_ref -n '=<>&' cur prev words cword
+ local exclude flag i OPTIND=1
+ words=()
+ local cur cword words
+ upargs=()
+ upvars=()
+ local upargs upvars vcur vcword vprev vwords
+ getopts c:i:n:p:w: flag -n '=<>&' cur prev words cword
+ case $flag in
+ exclude='=<>&'
+ getopts c:i:n:p:w: flag -n '=<>&' cur prev words cword
+ [[ 6 -ge 3 ]]
+ case ${!OPTIND} in
+ vcur=cur
+ (( OPTIND += 1 ))
+ [[ 6 -ge 4 ]]
+ case ${!OPTIND} in
+ vprev=prev
+ (( OPTIND += 1 ))
+ [[ 6 -ge 5 ]]
+ case ${!OPTIND} in
+ vwords=words
+ (( OPTIND += 1 ))
+ [[ 6 -ge 6 ]]
+ case ${!OPTIND} in
+ vcword=cword
+ (( OPTIND += 1 ))
+ [[ 6 -ge 7 ]]
+ __get_cword_at_cursor_by_ref '=<>&' words cword cur
+ words=()
+ local cword words
+ __reassemble_comp_words_by_ref '=<>&' words cword
+ local exclude i j line ref
+ [[ -n =<>& ]]
+ exclude='[=<>&]'
+ printf -v cword %s 1
+ [[ -v exclude ]]
+ line=' printf '\''ln -s /dev/n %q\n'\'' "${FILENAME}" ; done | sh'
+ (( i = 0, j = 0 ))
+ (( i < 7 ))
+ [[ 0 -gt 0 ]]
+ ref='words[0]'
+ printf -v 'words[0]' %s printf
+ line=' '\''ln -s /dev/n %q\n'\'' "${FILENAME}" ; done | sh'
+ (( i == COMP_CWORD ))
+ (( i++, j++ ))
+ (( i < 7 ))
+ [[ 1 -gt 0 ]]
+ [[ 'ln -s /dev/n %q\n' == +([=<>&]) ]]
+ ref='words[1]'
+ printf -v 'words[1]' %s ''\''ln -s /dev/n %q\n'\'''
+ line=' "${FILENAME}" ; done | sh'
+ (( i == COMP_CWORD ))
+ printf -v cword %s 1
+ (( i++, j++ ))
+ (( i < 7 ))
+ [[ 2 -gt 0 ]]
bash: COMP_WORDS[i]: unbound variable

Thanks, Chris.

scop commented 3 years ago

Yup, reproduced, thanks.

calestyo commented 3 years ago

I think I might have found another case:

scp someKnownHostname:fo<TAB>
bash: $1: unbound variable

also fails for me, when I try to complete the remote pathnames.

scop commented 3 years ago

I can't seem to be able to reproduce that one. Please file a new issue about that and complete the info requested in it so we can debug that one further.