📜 ⬆️ ⬇️

Using bash completion on the command line, your own scripts and applications. Part 2

About bash completion on Habré, I already wrote here , and even the end promised to tell you about setting up auto-completion for your own scripts.

However, a year and a half has passed, and personally I haven’t gotten to continue the hand. But this honth duty was taken by habrayuser infthi , publish on his behalf.


')
I will consider a model problem close to the one on which I learned the basics of auto-completion. The bottom line is:



There is a script that has three subcommands. One of these commands, work, is not considered in the example, the rest have the following subcommands. history makes import or export, each of these commands must be passed the name of the project and a pair of values ​​under the flags. help can tell you about work, history and help.

Now about how auto completion works. For a bash, a function is written to which the already entered arguments are passed, and based on them, it generates possible additions. This function (let's call it _my_command) is registered for a specific command (in this case, we execute a script called script, so registration is for script) with the magic magic command complete:

complete -F _my_command script

Now the most interesting thing is writing this function, processing the arguments, and listing the available parameters.
To begin with, from man bash you can learn about the existence of special variables for working with autocompletion. We will use the following:

COMPREPLY
This is an array from which bash gets possible additions.

COMP_WORDS
This is an array containing the arguments already entered. Analyzing them, we can understand what options add-ons should be offered.

COMP_CWORD
This is the index in the previous array, which shows the position of the currently edited argument in it.

Now let's try to analyze the input based on these variables, and if the first argument is entered - try to add it
  _my_command(){ # ,      COMPREPLY=() #    ,   ,    . cur="${COMP_WORDS[COMP_CWORD]}" #    subcommands_1="work history help" #    - .     . if [[ ${COMP_CWORD} == 1 ]] ; then #   ,     COMPREPLY=( $(compgen -W "${subcommands_1}" -- ${cur}) ) #some magic return 0 #COMPREPLY ,   fi } 


if now we write this function together with the above call to complete in some script, for example ./complete.sh, execute it in the current console (better, of course, to run a new bash for experiments, and then kill it). ./complete sh, and, having entered “script”, press Tab 2 times, bash will offer us add-on options:
 $ script help history work 

Accordingly, if you start to enter some subcommand, for example wo and press Tab, then auto-completion will occur.

However, I have not yet explained how exactly the magic used in the script works, namely

  COMPREPLY=( $(compgen -W "${subcommands_1}" -- ${cur}) ) #some magic 


Here we fill the list of returned options using the built-in bash compgen utility.

This utility takes as input a list of all possible values ​​of the argument, as well as the current entered part of the argument, and selects those values ​​to which the entered part can be supplemented. The input part of the argument is passed after -, and the list of possible values ​​is more interesting. In the above case, the possible values ​​are taken (as indicated by the -W flag) and given to the script of the word list (that is, in the example above, from subcommands_1 = “work history help”). However, there you can specify other flags - for example -d - and then compgen will supplement based on the existing directories on the machine, or -f - then it will complement to the files.

You can see what he gives:
 $ compgen -W "qwerty qweasd asdfgh" -- qwe qwerty qweasd 


Accordingly, you can generate different lists of candidates for auto-completion. For example, for the problem being solved, we (for importing and exporting history) need a list of possible projects. In my case, each project has a directory in "$ {HOME} / projects", respectively, candidates can be selected as
COMPREPLY=($(compgen -W "`ls ${HOME}/projects`" -- ${cur}))

Thus, autocompletion works simply: we look at the arguments entered before the current one, and based on the syntax of calling the necessary command, we generate a list of possible values ​​of the current argument. After that, the list of candidates is filtered by the already entered part of the argument, and passed to the bash. If the challenger is one, then the bash submits it. It's simple.

In conclusion, my clumsy implementation of autocompletion for the model specified at the beginning:

 _my_command() { COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" subcommands_1="work history help" #    subcommands_history="import export" #   history if [[ ${COMP_CWORD} == 1 ]] ; then #         COMPREPLY=( $(compgen -W "${subcommands_1}" -- ${cur}) ) return 0 fi subcmd_1="${COMP_WORDS[1]}" #       ,        case "${subcmd_1}" in # ,      work) COMPREPLY=() #     return 0 ;; history) if [[ ${COMP_CWORD} == 2 ]] ; then # script history;   import  export COMPREPLY=( $(compgen -W "${subcommands_history}" -- ${cur}) ) return 0 fi #     ,  :    subcmd_2="${COMP_WORDS[2]}" if [[ ${COMP_CWORD} == 3 ]] ; then #        . COMPREPLY=($(compgen -W "`ls ${HOME}/projects`" -- ${cur})) return 0 fi case "${subcmd_2}" in #        .     ,      -    ,  -     . import) case "${COMP_WORDS[COMP_CWORD-1]}" in -src) COMPREPLY=($(compgen -d -- ${cur})) #      return 0 ;; -file) COMPREPLY=($(compgen -f -- ${cur})) #     return 0 ;; *) COMPREPLY=($(compgen -W "-src -file" -- ${cur})) #   return 0 ;; esac ;; export) #     -o,    -      ,  ,  -     if [[ ${COMP_WORDS[COMP_CWORD-1]} == "-o" ]] ; then COMPREPLY=($(compgen -f -- ${cur})) return 0 fi COMPREPLY=($(compgen -W "-o" -- ${cur})) return 0 ;; *) ;; esac ;; help) #    help      ,   . COMPREPLY=( $(compgen -W "${subcommands_1}" -- ${cur})) return 0 ;; esac return 0 } complete -F _my_command script 


And most importantly - the connection of our script to the bash on an ongoing basis. This is done either (clumsily) by writing a call to our script in .bashrc, or (by default, if you have such a file:) via / etc / bash_completion. In the second case, we have to put our script in /etc/bash_completion.d, all scripts from where are picked up from / etc / bash_completion.

PS: Let me remind you that a positive feedback should be left in the user's karma infthi

Source: https://habr.com/ru/post/115886/


All Articles