'Bash completion scripting - getting a "transparent proxy"-like behaviour

I am trying to write a simple Bash completion script for a program that runs its arguments as a command. A good example of this is kind of program is the prime-run script provided by the nvidia-prime package:

#!/bin/bash
__NV_PRIME_RENDER_OFFLOAD=1 __VK_LAYER_NV_optimus=NVIDIA_only __GLX_VENDOR_LIBRARY_NAME=nvidia "$@"

This script sets a few environment variables, which instructs the prime driver to use the Nvidia dGPU on a hybrid system. The first argument is treated as the command, and all trailing arguments are passed through. So for example you can run prime-run code . and VSCode will start in the current directory using the dGPU.

Therefore from a completion-script POV, what we want is to basically try to complete as if the prime-run token isn't there (hence "transparent proxy"-like behaviour). To give a rather contrived example:

> prime-run journalc<TAB>
(completes journalctl)
> prime-run journalctl --us<TAB>
(completes --user)

However I am finding this surprisingly difficult in Bash (not that I know how in other shells). So the question is simple: is it possible and if so how?

Ideas I've (hopelessly) had

  • The simple complete -A command prime-run: the first argument gets completed as a command as expected (let's call it foo), but the following arguments are also completed as commands rather than as arguments to foo
  • Use some combination of compgen and complete -p to invoke the completion function of foo, but AFAIK the completion function for all foo is locally defined and thus uncallable


Solution 1:[1]

TL;DR

bash-completion provides a function named _command_offset (permalink), which is exactly what I need.

# A meta-command completion function for commands like sudo(8), which need to
# first complete on a command, then complete according to that command's own
# completion definition.

Keep reading if you are interested in how I got here.


So I was daydreaming the other day, when it hit me - doesn't sudo basically have the exact same behaviour I want? So the task became simple - reverse engineer the completion script for sudo. Source available here: permalink.

Turns out, most of the code has to do with completing the various options, so it's safe to simply throw most of it out:

  • L 8-11, 50-52: Related to sudo's edit mode. Safe to ditch.
  • L 19-24, 27-39, 43-49: These complete sudo's options. Safe to ditch.

So we're left with this:

_sudo()
{
    local cur prev words cword split
    _init_completion -s || return

    for ((i = 1; i <= cword; i++)); do
        if [[ ${words[i]} != -* ]]; then
            local PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin
            local root_command=${words[i]}
            _command_offset $i
            return
        fi
    done

    $split && return
} &&
    complete -F _sudo sudo sudoedit
  • The for and if block are there to deal with sudo's options that precede the "guest command". Safe to ditch (after replacing all $i with 1).
  • The variable $split is only referenced in _init_completion (permalink), and it seems to be used for handling different argument styles (--foo=bar v.s. --foo bar). Same with the -s flag. Irrelevant.
  • Appending to $PATH and setting $root_command have to do with privilege escalation. Only relevant to sudo.

So after the dust has cleared, by process of elimination, I ended up with this simple chunk of code:

_my-script()
{
    local cur prev words cword
    _init_completion || return

    _command_offset 1

} && complete -F _my-script my-script

Declaring these four local variables and calling _init_completion is standard for all completion scipts, so really it's as simple as one command. Of course someone had to write the massively-complex _command_offset function so lucky me I guess?

Anyways, thank you for reading the story of me messing around and hopefully this will be helpful to some other person in the future.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 cyqsimon