'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 itfoo
), but the following arguments are also completed as commands rather than as arguments tofoo
- Use some combination of
compgen
andcomplete -p
to invoke the completion function offoo
, but AFAIK the completion function for allfoo
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
andif
block are there to deal withsudo
's options that precede the "guest command". Safe to ditch (after replacing all$i
with1
). - 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 tosudo
.
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 |