Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Bash Complete Three-Part Pattern

I have a command line tool which takes arguments in an three-part form:

$ t first_second_third

I have a set of valid values for first, a set of valid values for second, and a set of valid values for third. I want to use Bash complete functionality to complete each part of the option value, as in this example:

$ t [tab][tab]           # shows options for first part
walk run skip bike
$ t w[tab]               # completes first part and appends delimiter
$ t walk_[tab][tab]      # shows options for second part
home work park
$ t walk_h[tab]          # completes second part and appends delimiter
$ t walk_home_[tab][tab] # shows options for second part
morning afternoon evening
$ t walk_home_a[tab]     # completes second part and appends space
$ t walk_home_afternoon

I have this code:

_tool () {
  local cur="${COMP_WORDS[COMP_CWORD]}"

  local first="walk run skip bike"
  local second="home work park"
  local third="morning afternoon evening"

  case "${cur}" in
    *_*_*)
      COMPREPLY=( $(compgen -W "${third}" -- "") ); return 0;;
    *_*)
      COMPREPLY=( $(compgen -W "${second}" -S "_" -- ${cur}) ); compopt -o nospace; return 0;;
    *)
      COMPREPLY=( $(compgen -W "${first}" -S "_" -- ${cur}) ); compopt -o nospace; return 0;;
  esac
}

complete -F _tool t

The first clause works great; nothing special there. But the second clause gives me no options or completions.

$ t [tab][tab]
bike_  run_   skip_  walk_
$ t b[tab]
$ t bike_[tab][tab] # nothing but bells

I replaced the second clause with the following (i.e. I replaced ${cur} with an empty string in the COMPREPLY call):

COMPREPLY=( $(compgen -W "${second}" -S "_" -- "") ); compopt -o nospace; return 0;;

I get a list of all options on the command line, but no completions.

$ t bike_[tab][tab]
home_  park_  work_
$ t bike_h[tab][tab]
home_  park_  work_
$ t bike_ho[tab][tab]
home_  park_  work_

I thought there might be some bad interaction with ${cur} and the COMPREPLY word list, so I changed the second clause again, adding a prefix which matches the first part of the current word:

local prefix=( $(expr "${cur}" : '\(.*_\)') )
COMPREPLY=( $(compgen -W "${second}" -P "${prefix}" -S "_" -- "") ); compopt -o nospace; return 0;;

This did not help much. With ${cur} still in the COMPREPLY command, I got no options or completions again. With an empty string instead, I got the full option (not just the curent part). But, pressing tab would erase what is typed.

$ t bike_[tab][tab]
bike_home_  bike_park_  bike_work_
$ t bike_ho[tab]
$ t bike_ # "ho" was erased

Of course, I have the same problem with the third part, too.

  1. How can I get Bash to complete the current word, part by part?
  2. How can I get Bash to not append the delimiter until an option has been selected?

This is similar to another Bash complete question, but I do not want to list every possible permutation of the values and they are not formatted as filenames (not sure if the filename pattern makes a difference).

Thanks for the help!
--ap

like image 936
Alexander Avatar asked Apr 28 '13 00:04

Alexander


People also ask

How do you autocomplete in bash?

Bash completion is a bash function that allows you to auto complete commands or arguments by typing partially commands or arguments, then pressing the [Tab] key. This will help you when writing the bash command in terminal.

How does tab completion work bash?

The programmable completion feature in Bash permits typing a partial command, then pressing the [Tab] key to auto-complete the command sequence. [1] If multiple completions are possible, then [Tab] lists them all.

How do you complete a command in Linux terminal?

Linux commands are executed on Terminal by pressing Enter at the end of the line. You can run commands to perform various tasks, from package installation to user management and file manipulation.


1 Answers

I was able to create what you want. See my code below:

_tool () {
  local cur="${COMP_WORDS[COMP_CWORD]}"

  local first="walk run skip bike"

  case "${cur}" in
    *_*_*)
      local firstsecond=`echo ${cur} | awk -F '_' '{print $1"_"$2}'`
      local third="${firstsecond}_morning ${firstsecond}_afternoon ${firstsecond}_evening"
      COMPREPLY=( $(compgen -W "${third}" -- ${cur}) ); return 0;;
    *_*)
      local firstcomp=`echo ${cur} | awk -F '_' '{print $1}'`
      local second="${firstcomp}_home ${firstcomp}_work ${firstcomp}_park"
      COMPREPLY=( $(compgen -W "${second}" -S "_" -- ${cur}) ); compopt -o nospace; return 0;;
    *)
      COMPREPLY=( $(compgen -W "${first}" -S "_" -- ${cur}) ); compopt -o nospace; return 0;;
  esac
}

complete -F _tool t

I am not much familiar with autocomplete, and it was the first time I used compgen or complete, so I'm not sure I can answer your questions about the theory of the thing. I can tell you what I did, in case it helps you understand how this badly-documented functions work. First I tried to understand what you had. I reproduced your problems successfully. Then I had this idea that, even though you didn't want to list every permutation yourself in the code, I could use the local variables to mimic as if it had. The first thing I did was to get the "first" element after we had the first underscore, and add the second element to it, and feed that to compgen. To my surprise, it worked fine. Then I tried to do the same to the 3rd level... and then problems started. I found out that the second part was not working as fine as I had imagined. after completing the first word, I would get the suggestions for the second; after chosing one word to be the second and hit [TAB], the first underscore would disappear.

$ t [tab][tab]           # shows options for first part
bike_ run_ skip_ walk_
$ t w[tab]               # completes first part and appends delimiter
$ t walk_[tab][tab]      # shows options for second part
walk_home_ walk_park_ walk_work_
$ t walk_h[tab]          # completes second part and appends delimiter
$ t walk_home_[tab][tab] # screws up with it
$ t walkhome_            

I tried moving the underscore into awk, to no success:

      local first=`echo ${cur} | awk -F '_' '{print $1"_"}'`
      local second="${first}home ${first}work ${first}park"

For some reason I had to change the variable name to something different from "first", and it started working. Then, on the 3rd element I had the same problem that you described last, where whatever I typed in the 3rd element would disappear, but [tab][tab] would give me the options:

$ t walk_home_[tab][tab] # shows options for second part
morning afternoon evening
$ t walk_home_aft[tab]     # erases what I typed
$ t walk_home_

To which I added ${cur} to compgen in the 3rd word and it worked. This is just a summary of what I did... but I got a working version, so I'm happy with it. :-)

like image 89
msb Avatar answered Nov 07 '22 13:11

msb