Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write a bash completion script for all executables on path?

I've had this use case come up for a couple of different scripts I've written or modified. Essentially, I want bash completion for option '-x' to complete executables on the PATH. This is sort of two questions wrapped in one.

So far I've had troubles because bash doesn't easily distinguish between aliases, builtins, functions, etc and executable files on the PATH. The _commands wrapper function in /usr/share/bash-completion/bash_completion completes on all of the above but I have no use for working with aliases, builtins, functions, etc and only want to complete on the commands that happen to be executables on the PATH.

So for example... If I enter scriptname -x bas[TAB], it should complete with base64, bash, basename, bashbug.

This is what my completion script looks like now:

_have pygsparkle && {

_pygsparkle(){
    local cur prev

    COMPREPLY=()
    cur=${COMP_WORDS[COMP_CWORD]}
    prev=${COMP_WORDS[COMP_CWORD-1]}

    case $prev in
    -x|--executable)
        # _command
        executables=$({ compgen -c; compgen -abkA function; } | sort | uniq -u)
        COMPREPLY=( $( compgen -W "$executables" -- "$cur" ) )
        return 0
        ;;
    esac

    if [[ $cur = -* ]]; then
        COMPREPLY=( $( compgen -W '--executable -h --help -x' -- "$cur" ) )
    fi

}

complete -F _pygsparkle pygsparkle

}

It seems to work as expected but { compgen -c; compgen -abkA function; } | sort | uniq -u is a pretty dirty hack. In zsh you can get a sorted list of executables on PATH running print -rl -- ${(ko)commands}. So it appears I'm missing at least 60+ execs, likely because uniq -u is dumping execs with that same name as aliases or functions.

Is there a better way to do this? Either a better command for getting all executables on PATH or a pre-existing completion function that will serve the same ends?

Update: Ok so the following function executes in under 1/6 sec and looks like the best option. Unless there are any other suggestions I'll probably just close the question.

_executables(){
    while read -d $'\0' path; do
        echo "${path##*/}"
    done < <(echo -n "$PATH" | xargs -d: -n1 -I% -- find -L '%' -maxdepth 1 -mindepth 1 -type f -executable -print0 2>/dev/null) | sort -u
}
like image 433
Six Avatar asked Oct 17 '25 14:10

Six


2 Answers

I have a run-in-background script. To get autocomplete for it I use bash's builtin complete:

> complete -c <script-name>
> <script-name> ech[TAB]
> <script-name> echo 

From the docs:

command
   Command names. May also be specified as -c.

To deal with non-executables, my first thought was to filter with which or type -P but that's terribly slow. A better way is to only check the unknowns. Use @Six's solution or some other to find items in compgen -c but not compgen -abkA function. For everything in both lists, check with type -P. For example:

function _executables {
    local exclude=$(compgen -abkA function | sort)
    local executables=$(
        comm -23 <(compgen -c) <(echo $exclude)
        type -tP $( comm -12 <(compgen -c) <(echo $exclude) )
    )
    COMPREPLY=( $(compgen -W "$executables" -- ${COMP_WORDS[COMP_CWORD]}) )
}
complete -F _executables <script-name>

I've tried a few times but introducing -F to complete just seems to be a slow way to do thing. Not to mention now having to handle absolute/relative path completion to executables, the horribly fiddly complete-the-directory-but-don't-add-a-space bit and correctly handling spaces in paths.

like image 154
jozxyqk Avatar answered Oct 20 '25 05:10

jozxyqk


Looks like you are looking for compgen -c

like image 35
hek2mgl Avatar answered Oct 20 '25 06:10

hek2mgl



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!