You are not logged in.

#1 2020-08-29 18:26:00

CarbonChauvinist
Member
Registered: 2012-06-16
Posts: 261

custom bash completion - remove selected completion from list

So I'm trying to wrap my head around writing a bash completion function for my personal use and have struggled through to get something that kinda almost works.

This should be fairly simple, I have a bash function that I use to remove package(s) from a local aur utils repo.

$ type rr
rr is a function
rr () 
{ 
    repo-remove /home/ghost/.aur/aur.db.tar "$@"
}

What I'd like to do is be able to auto-complete so that it parses all packages in the local repo and returns it as a list to complete through. I then thought, that I'd prefer it only return a list of packages that are not currently installed, and finally I decided why not default to non-installed packages but allow a flag to show all.

Okay, so here's the script:

_rr(){
    COMPREPLY=()
    local cur prev

    cur=${COMP_WORDS[COMP_CWORD]}
    prev=${COMP_WORDS[COMP_CWORD-1]}
    
    mapfile -t FILTERED_PKGS < <(pacman -Sl aur | awk '!/installed/ {print $2}')
    mapfile -t ALL_PKGS < <(pacman -Slq aur)

    #echo ""
    #echo "COMP_WORDS : ${COMP_WORDS} is array"
    #echo "COMP_WORDS WHOLE ARRAY : ${COMP_WORDS[@]} are the array elements"
    #echo "COMP_CWORD : ${COMP_CWORD} is the current word index in the array"
    #echo "COMP_WORDS[COMP_CWORD] : ${COMP_WORDS[COMP_CWORD]} is the current word"
    #echo "COMP_LINE : ${COMP_LINE} is the entire array accessed differently"
    #echo "COMP_POINT : ${COMP_POINT} is the current character index of array"
    #echo "COMP_KEY : ${COMP_KEY}"
    #echo "COMP_TYPE : ${COMP_TYPE}"
    #echo "args : $@"
    #echo "reply : ${COMPREPLY}"
    #echo "cur is : $cur"
    #echo "prev is : $prev"
    #echo ""

    if [ "$prev" = "-a" ] ; then
    	COMPREPLY=($(compgen -W "${ALL_PKGS[*]}" "$cur"))
    else
   	COMPREPLY=($(compgen -W "${FILTERED_PKGS[*]}" "$cur"))
    fi

    return 0

}&&
complete -F _rr rr

This works okay, but here's what I'm missing:
1. Once an item from the complete list selected, how do I then remove the option from the next list of completion words that is presented? Lets say I have three completion options "foo1 foo2 foo3" and I auto-complete to select foo1 .."rr foo1 [tab]" should only return "foo2 foo3", right now it will return all three again allowing multiple selections of the same item.
2. Assuming that the option "-a" should only be the first option passed, how can I check the "-a" as always the first argument to the "rr" command?


"the wind-blown way, wanna win? don't play"

Offline

#2 2020-08-30 06:19:43

chaseleif
Member
From: Texas
Registered: 2020-08-01
Posts: 10

Re: custom bash completion - remove selected completion from list

You could just check for equality and set another variable to determine whether the first argument is -a.
This does that:

#!/bin/bash
DOALL=0
if [[ "$1" == "-a" ]]
then
  echo "\$1 == -a"
  DOALL=1
else
  echo "\$1 == -a returned false"
fi
if [[ "$DOALL" == 1 ]]
then
  echo "DOALL was set to 1"
else
  echo "DOALL is zero"
fi

To get the list options to be dynamic you can write the output of options to a file, then parse the file and print the options from there. Then you could remove the line from the file so next time that option is gone.
Here's a script I've used to read arguments from a file line-by-line loop, where I used each line as an argument in running another program:

#!/bin/bash
INPUT=./combos
HFUN=$1
IFS=\n
[ ! -f $INPUT ] && { echo "$INPUT file not found"; exit 0; }
echo "start - heuristic number $HFUN: $(date +%Y-%m-%d-%H:%M:%S)" >> testtime$HFUN
while read line
  do
    ./astar h$HFUN "$line" testing
done < $INPUT | sort -n -r -k 1,1 >> testtime$HFUN
echo "end - heuristic number $HFUN: $(date +%Y-%m-%d-%H:%M:%S)" >> testtime$HFUN

I'm no pro at bash scripts, I just found this page which looks like a good resource: https://tldp.org/LDP/abs/html/

Looks like you can also read the options into an array, unset indices as they are used, and when you gather a list for printing you can loop for each index in the array https://linuxconfig.org/how-to-use-arra … ash-script

Last edited by chaseleif (2020-08-30 07:01:25)

Offline

#3 2020-08-30 18:43:27

CarbonChauvinist
Member
Registered: 2012-06-16
Posts: 261

Re: custom bash completion - remove selected completion from list

@chaseleif thanks!

I was trying to use the $1 $2 $3 etc variables within the custom complete function, but it appears that those have a special/different meaning than in regular bash shell? AFAICT anyway. It seems that instead of referring to static list of arguments passed to a command, they reference specific words in the completion chain, some which are dynamic and change based on what's being completed and has been completed.

(btw this blog post quoted below is a really good resource on custom completions as well).

$1 : the first argument is the name of the command whose arguments are being completed
$2 : the second argument is the word being completed
$3 : the word preceding the word being completed on the current command line

So in this case $1 is always 'rr' and $2 is basically $cur and $3 is basically $prev? (i.e. constantly updates based on what's being completed or has been completed). I'm not sure of the customary way to handle this so I just went for using the $COMP_WORDS[1] index instead - still using your suggested method.

_rr(){
    COMPREPLY=()
    local cur prev DOALL

    cur=${COMP_WORDS[COMP_CWORD]}
    prev=${COMP_WORDS[COMP_CWORD-1]}
    
    mapfile -t FILTERED_PKGS < <(pacman -Sl aur | awk '!/installed/ {print $2}')
    mapfile -t ALL_PKGS < <(pacman -Slq aur)

    if [[ ${COMP_WORDS[1]} && ${COMP_WORDS[1]} = "-a" ]]; then
	    COMPREPLY=($(compgen -W "${ALL_PKGS[*]}" "$cur"))
    else
	    COMPREPLY=($(compgen -W "${FILTERED_PKGS[*]}" "$cur"))
    fi
    
    return 0

}&&
complete -F _rr rr

I've played around with your suggestions for removing already chosen completions. I think I'd like to try and use an associative array to do this rather than an external file. Something like the following I think? (Is there a better way to map this out than two for loops?)

...
    mapfile -t FILTERED_PKGS < <(pacman -Sl aur | awk '!/installed/ {print $2}')                                                                                                                
    mapfile -t ALL_PKGS < <(pacman -Slq aur)                                                                                                                                                    
                                                                                                                                                                                                
    declare -A ALL_HOLDING FILTERED_HOLDING                                                                                                                                                     
                                                                                                                                                                                                
    for ((i = 0; i < "${#FILTERED_PKGS[@]}"; i++)); do                                                                                                                                          
            FILTERED_HOLDING["${FILTERED_PKGS[$i]}"]=${FILTERED_PKGS[$i]}
    done

    for ((i = 0; i < "${#ALL_PKGS[@]}"; i++)); do
            ALL_HOLDING["${ALL_PKGS[$i]}"]="${ALL_PKGS[$i]}"
    done
...

but now I'm not sure of the best way to remove the completion from either array.

?I guess I should only remove the previous completion (ie. $3) from either holding array as long as it is not a blank?... I'll have to think/google about this some more.

In the meantime though I have satisfactory auto-completion working. Thanks for your insight.

Last edited by CarbonChauvinist (2020-08-31 00:34:38)


"the wind-blown way, wanna win? don't play"

Offline

#4 2020-08-31 17:25:24

chaseleif
Member
From: Texas
Registered: 2020-08-01
Posts: 10

Re: custom bash completion - remove selected completion from list

Cool stuff.
So, looks like $1 to $9 are ordered arguments, from "How to Use Command Line Parameters in Scripts" down on this page: https://www.howtogeek.com/442332/how-to … s-in-bash/
or this page looks like it may have even more stuff: https://www.baeldung.com/linux/bash-special-variables

That $cur/$prev looks like just their parsing scheme, I think

The for loops can have different limits and performance isn't really a concern, I wouldn't try to combine them.

The last link about arrays I posted last time shows how to delete specific elements and iterate over valid elements:
https://linuxconfig.org/how-to-use-arra … -the-array
(This bookmark brings you toward the bottom to the unset part of the page)
For deleting an element, right above that you see a for loop for [element] in array, where it skips an index that has been deleted.

Offline

Board footer

Powered by FluxBB