Search code examples
bashshellbash-completion

Custom bash completion obtaining from help text


For some reason bash completion doesn't work when I obtain from help text. I know this sounds cryptic but please read further to understand me.

This is my C++ file compiled and placed as dbcmd executable file in one of the PATHs:

$ cat dbcmd.cpp
#include <iostream>
#include <string>
#include <vector>

static void usage()
{
        std::cerr << "Usage: dbcmd [<command> [<command-args>]]\n";
        std::cerr << "Commands:\n";

        std::vector<std::string> commands;

        commands.push_back("backup");
        commands.push_back("db-backup");
        commands.push_back("db-purge");
        commands.push_back("load");
        commands.push_back("debug");
        commands.push_back("analyze");
        commands.push_back("cycle");
        commands.push_back("endday");
        commands.push_back("gendbset");
        commands.push_back("help");
        commands.push_back("perform");
        commands.push_back("print");
        commands.push_back("remove");
        commands.push_back("restart");
        commands.push_back("dump");
        commands.push_back("start");
        commands.push_back("status");
        commands.push_back("stop");
        commands.push_back("tables");
        commands.push_back("info");
        commands.push_back("update");
        for (auto command: commands)
                std::cerr << "  " << command << "\n";
}

int main()
{
        usage();
        return 0;
}

And this is my bash completion script (credit):

$ cat dbcmd_completion.bash
#/usr/bin/env bash
_dbcmd_completions()
{
        if [ "${#COMP_WORDS[@]}" != "2" ]; then
                return
        fi

        local IFS=$'\n'
        local list_var="$(dbcmd|awk -F '\t' '{printf("%s", $1)}'|sed 's/.*://'|sed 's/^\s.//'|sed 's/\s\+/ /g')"
        local suggestions=($(compgen -W "$list_var" -- "${COMP_WORDS[1]}"))

        if [ "${#suggestions[@]}" == "1" ]; then
                local number="${suggestions[0]/%\ */}"
                COMPREPLY=("$number")
        else
                for i in "${!suggestions[@]}"; do
                        suggestions[$i]="$(printf '%*s' "-$COLUMNS"  "${suggestions[$i]}")"
                done
                COMPREPLY=("${suggestions[@]}")
        fi
}

_dothis_completions()
{
        if [ "${#COMP_WORDS[@]}" != "2" ]; then
                return
        fi
        local list_var="backup db-backup db-purge load debug analyze cycle endday gendbset help perform print remove restart dump start status stop tables info update"
        local suggestions=($(compgen -W "$list_var" -- "${COMP_WORDS[1]}"))

        if [ "${#suggestions[@]}" == "1" ]; then
                local number="${suggestions[0]/%\ */}"
                COMPREPLY=("$number")
        else
                for i in "${!suggestions[@]}"; do
                        suggestions[$i]="$(printf '%*s' "-$COLUMNS"  "${suggestions[$i]}")"
                done
                COMPREPLY=("${suggestions[@]}")
        fi
}
#complete -F _dothis_completions dbcmd
complete -F _dbcmd_completions dbcmd

When I uncomment and use _dothis_completions it works perfectly, but _dbcmd_completions--which obtains completion list from help text of dbcmd--doesn't work. When TAB is pressed it produces weird behaviour. When you execute echo $list_var basically in both of the functions produce the same result. Not sure why bash completion doesn't like awk/sed produced result. This is on RH 9. Can anyone provide a solution to this?


Solution

  • Finally able to solve this. Things to note are; 1. Removed IFS 2.redirecting stderr output to stdout

    Working script is:

    $ cat dbcmd_completion.bash
    #/usr/bin/env bash
    _dbcmd_completions()
    {
            if [ "${#COMP_WORDS[@]}" != "2" ]; then
                    return
            fi
            local list_var="$(dbcmd 2>&1|awk -F '\t' '{printf("%s", $1)}'|sed 's/.*://'|sed 's/^\s.//'|sed 's/\s\+/ /g')"
            local suggestions=($(compgen -W "$list_var" -- "${COMP_WORDS[1]}"))
            if [ "${#suggestions[@]}" == "1" ]; then
                    local number="${suggestions[0]/%\ */}"
                    COMPREPLY=("$number")
            else
                    for i in "${!suggestions[@]}"; do
                            suggestions[$i]="$(printf '%*s' "-$COLUMNS"  "${suggestions[$i]}")"
                    done
                    COMPREPLY=("${suggestions[@]}")
            fi
    }
    
    complete -F _dbcmd_completions dbcmd