## Recursive completion — call container's own bash completion The idea: instead of reimplementing completion, **delegate to bash inside the container** and get back its own completion results. --- ## The approach ```bash _incus_exec_delegate() { local cur="${COMP_WORDS[COMP_CWORD]}" local container="${COMP_WORDS[2]}" # find '--' position local dashdash_pos=0 local i for (( i=0; i < ${#COMP_WORDS[@]}; i++ )); do [[ "${COMP_WORDS[$i]}" == "--" ]] && dashdash_pos=$i done # complete container names if (( COMP_CWORD == 2 )); then COMPREPLY=($(compgen -W \ "$(incus list --format csv --columns n 2>/dev/null)" \ -- "$cur")) return fi # before '--' if (( dashdash_pos == 0 )); then COMPREPLY=($(compgen -W "-- --env --user --cwd" -- "$cur")) return fi # after '--' — delegate full completion to container's bash local inner_words=("${COMP_WORDS[@]:$((dashdash_pos + 1))}") local inner_cword=$(( COMP_CWORD - dashdash_pos - 1 )) local inner_cmd="${inner_words[0]}" local inner_line="${inner_words[*]}" # ask container's bash to run completion COMPREPLY=($(incus exec "$container" -- bash -c " # load bash completion source /usr/share/bash-completion/bash_completion 2>/dev/null source /etc/bash_completion 2>/dev/null # rebuild COMP_ variables inside container COMP_WORDS=(${inner_words[*]@Q}) COMP_CWORD=$inner_cword COMP_LINE=${inner_line@Q} COMP_POINT=\${#COMP_LINE} # find and call the completion function for the command local compfunc compfunc=\$(complete -p ${inner_cmd@Q} 2>/dev/null \ | grep -oP '(?<=-F )\S+') if [[ -n \"\$compfunc\" ]]; then \$compfunc ${inner_cmd@Q} ${cur@Q} ${inner_words[$((inner_cword - 1))]@Q} printf '%s\n' \"\${COMPREPLY[@]}\" else # fallback to file completion compgen -f -- ${cur@Q} fi " 2>/dev/null)) } complete -F _incus_exec_delegate incus ``` --- ## How it works ``` host$ incus exec mycontainer -- git che ↑ inner_cmd="git" ↓ incus exec mycontainer -- bash -c ' source /usr/share/bash-completion/bash_completion COMP_WORDS=(git che) COMP_CWORD=1 compfunc=$(complete -p git | grep -oP "(?<=-F )\S+") $compfunc ... # calls __git_wrap__git_main printf "%s\n" "${COMPREPLY[@]}" ' ↓ COMPREPLY=(checkout cherry cherry-pick) ``` --- ## Caveats ```bash # completion inside container requires bash-completion installed incus exec mycontainer -- apt install -y bash-completion # verify completion available for a command incus exec mycontainer -- bash -c "complete -p git" ``` --- ## Fallback chain ``` container has completion function for cmd? ↓ yes → delegate to it ↓ no → compgen -c (commands) ↓ compgen -f (files) ``` --- ## Quick reference | Step | What happens | |------|-------------| | rebuild `COMP_WORDS` | strip `incus exec CTR --` prefix | | `complete -p CMD` | find completion function in container | | `$compfunc` | call it with correct args | | fallback | `compgen -f` for file completion | The key insight is **rebuilding `COMP_WORDS` and `COMP_CWORD`** with the `incus exec CTR --` prefix stripped — the container's bash then sees the command line exactly as if it were running locally.