Science and technology

How to create a Bash completion script

I just lately labored on making a Bash completion script for a venture, and I loved it very a lot. In this publish, I’ll attempt to familiarize you with the method of making a Bash completion script.

What is Bash completion?

Bash completion is a performance by way of which Bash helps customers kind their instructions extra rapidly and simply. It does this by presenting attainable choices when customers press the Tab key whereas typing a command.

$ git<tab><tab>
git                 git-receive-pack    git-upload-archive  
gitk                git-shell           git-upload-pack    
$ git-s<tab>
$ git-shell

How it really works

The completion script is code that makes use of the builtin Bash command full to outline which completion ideas might be displayed for a given executable. The nature of the completion choices range, from easy static to extremely refined.

Why hassle?

This performance helps customers by:

  • saving them from typing textual content when it may be auto-completed
  • serving to them know the accessible continuations to their instructions
  • stopping errors and enhancing their expertise by hiding or displaying choices primarily based on what they’ve already typed

Hands-on

Here’s what we are going to do on this tutorial:

We will first create a dummy executable script referred to as dothis. All it does is execute the command that resides on the quantity that was handed as an argument within the person’s historical past. For instance, the next command will merely execute the ls -a command, on condition that it exists in historical past with quantity 235:

dothis 235

Then we are going to create a Bash completion script that may show instructions together with their quantity from the person’s historical past, and we are going to “bind” it to the dothis executable.

$ dothis <tab><tab>
215 ls
216 ls -la
217 cd ~
218 man historical past
219 git standing
220 historical past | minimize -c eight-

You can see a gif demonstrating the performance at this tutorial’s code repository on GitHub.

Let the present start.

Creating the executable script

Create a file named dothis in your working listing and add the next code:

if [ -z "$1" ]; then
  echo "No command number passed"
  exit 2
fi

exists=$(fc -l -1000 | grep ^$1 -- 2>/dev/null)

if [ -n "$exists" ]; then
  fc -s -- "$1"
else
  echo "Command with number $1 was not found in recent history"
  exit 2
fi

Notes:

  • We first verify if the script was referred to as with an argument
  • We then verify if the precise quantity is included within the final 1000 instructions
    • if it exists, we execute the command utilizing the fc performance
    • in any other case, we show an error message

Make the script executable with:

chmod +x ./dothis

We will execute this script many occasions on this tutorial, so I recommend you place it in a folder that’s included in your path in order that we are able to entry it from wherever by typing dothis.

I put in it in my house bin folder utilizing:

set up ./dothis ~/bin/dothis

You can do the identical given that you’ve got a ~/bin folder and it’s included in your PATH variable.

Check to see if it’s working:

dothis

You ought to see this:

$ dothis
No command quantity handed

Done.

Creating the completion script

Create a file named dothis-completion.bash. From now on, we are going to consult with this file with the time period completion script.

Once we add some code to it, we are going to supply it to permit the completion to take impact. We should supply this file each single time we alter one thing in it.

Later on this tutorial, we are going to focus on our choices for registering this script each time a Bash shell opens.

Static completion

Suppose that the dothis program supported an inventory of instructions, for instance:

Let’s use the full command to register this checklist for completion. To use the correct terminology, we are saying we use the full command to outline a completion specification (compspec) for our program.

Add this to the completion script.

#/usr/bin/env bash
full -W "now tomorrow never" dothis

Here’s what we specified with the full command above:

  • we used the -W (wordlist) choice to offer an inventory of phrases for completion.
  • we outlined to which “program” these completion phrases shall be used (the dothis parameter)

Source the file:

supply ./dothis-completion.bash

Now strive urgent Tab twice within the command line, as proven under:

$ dothis <tab><tab>
by no means     now       tomorrow

Try once more after typing the n:

$ dothis n<tab><tab>
by no means now

Magic! The completion choices are robotically filtered to match solely these beginning with n.

Note: The choices aren’t displayed within the order that we outlined them within the glossary; they’re robotically sorted.

There are many different choices for use as a substitute of the -W that we used on this part. Most produce completions in a set method, which means that we don’t intervene dynamically to filter their output.

For instance, if we need to have listing names as completion phrases for the dothis program, we might change the whole command to the next:

full -A listing dothis

Pressing Tab after the dothis program would get us an inventory of the directories within the present listing from which we execute the script:

$ dothis <tab><tab>
dir1/ dir2/ dir3/

Find the whole checklist of the accessible flags in the Bash Reference Manual.

Dynamic completion

We shall be producing the completions of the dothis executable with the next logic:

  • If the person presses the Tab key proper after the command, we are going to present the final 50 executed instructions together with their numbers in historical past
  • If the person presses the Tab key after typing a quantity that matches multiple command from historical past, we are going to present solely these instructions together with their numbers in historical past
  • If the person presses the Tab key after a quantity that matches precisely one command in historical past, we auto-complete the quantity with out appending the command’s literal (if that is complicated, no worries—you’ll perceive later)

Let’s begin by defining a perform that may execute every time the person requests completion on a dothis command. Change the completion script to this:

#/usr/bin/env bash
_dothis_completions()

full -F _dothis_completions dothis

Note the next:

  • we used the -F flag within the full command defining that the _dothis_completions is the perform that may present the completions of the dothis executable
  • COMPREPLY is an array variable used to retailer the completions—the completion mechanism makes use of this variable to show its contents as completions

Now supply the script and go for completion:

$ dothis <tab><tab>
by no means now tomorrow

Perfect. We produce the identical completions as within the earlier part with the glossary. Or not? Try this:

$ dothis nev<tab><tab>
by no means     now       tomorrow

As you’ll be able to see, although we kind nev after which request for completion, the accessible choices are all the time the identical and nothing will get accomplished robotically. Why is that this taking place?

  • The contents of the COMPREPLY variable are all the time displayed. The perform is now answerable for including/eradicating entries from there.
  • If the COMPREPLY variable had just one component, then that phrase can be robotically accomplished within the command. Since the present implementation all the time returns the identical three phrases, this is not going to occur.

Enter compgen: a builtin command that generates completions supporting many of the choices of the fullcommand (ex. -W for glossary, -d for directories) and filtering them primarily based on what the person has already typed.

Don’t fear in the event you really feel confused; all the pieces will turn out to be clear later.

Type the next within the console to higher perceive what compgen does:

$ compgen -W "now tomorrow never"
now
tomorrow
by no means
$ compgen -W "now tomorrow never" n
now
by no means
$ compgen -W "now tomorrow never" t
tomorrow

So now we are able to use it, however we have to discover a strategy to know what has been typed after the dothis command. We have already got the best way: The Bash completion services present Bash variables associated to the completion happening. Here are the extra necessary ones:

  • COMP_WORDS: an array of all of the phrases typed after the identify of this system the compspec belongs to
  • COMP_CWORD: an index of the COMP_WORDS array pointing to the phrase the present cursor is at—in different phrases, the index of the phrase the cursor was when the tab key was pressed
  • COMP_LINE: the present command line

To entry the phrase simply after the dothis phrase, we are able to use the worth of COMP_WORDS[1]

Change the completion script once more:

#/usr/bin/env bash
_dothis_completions()

  COMPREPLY=($(compgen -W "now tomorrow never" "$"))

full -F _dothis_completions dothis

Source, and there you might be:

 $ dothis
by no means     now       tomorrow  
$ dothis n
by no means  now

Now, as a substitute of the phrases now, tomorrow, by no means, we wish to see precise numbers from the command historical past.

The fc -l command adopted by a unfavourable quantity -n shows the final n instructions. So we are going to use:

fc -l -50

which lists the final 50 executed instructions together with their numbers. The solely manipulation we have to do is substitute tabs with areas to show them correctly from the completion mechanism. sed to the rescue.

Change the completion script as follows:

#/usr/bin/env bash
_dothis_completions()
sed 's/t//')" -- "$"))

full -F _dothis_completions dothis

Source and check within the console:

$ dothis <tab><tab>
632 supply dothis-completion.bash   649 supply dothis-completion.bash   666 cat ~/.bash_profile
633 clear                           650 clear                           667 cat ~/.bashrc
634 supply dothis-completion.bash   651 supply dothis-completion.bash   668 clear
635 supply dothis-completion.bash   652 supply dothis-completion.bash   669 set up ./dothis ~/bin/dothis
636 clear                           653 supply dothis-completion.bash   670 dothis
637 supply dothis-completion.bash   654 clear                           671 dothis 6546545646
638 clear                           655 dothis 654                      672 clear
639 supply dothis-completion.bash   656 dothis 631                      673 dothis
640 supply dothis-completion.bash   657 dothis 150                      674 dothis 651
641 supply dothis-completion.bash   658 dothis                          675 supply dothis-completion.bash
642 clear                           659 clear                           676 dothis 651
643 dothis 623  ls -la              660 dothis                          677 dothis 659
644 clear                           661 set up ./dothis ~/bin/dothis   678 clear
645 supply dothis-completion.bash   662 dothis                          679 dothis 665
646 clear                           663 set up ./dothis ~/bin/dothis   680 clear
647 supply dothis-completion.bash   664 dothis                          681 clear
648 clear                           665 cat ~/.bashrc

Not unhealthy.

We do have an issue, although. Try typing a quantity as you see it in your completion checklist after which press the important thing once more.

$ dothis 623<tab>
$ dothis 623  ls 623  ls -la
...
$ dothis 623  ls 623  ls 623  ls 623  ls 623  ls -la

This is occurring as a result of in our completion script, we used the $ to all the time verify the primary typed phrase after the dothis command (the quantity 623 within the above snippet). Hence the completion continues to recommend the identical completion repeatedly when the Tab secret’s pressed.

To repair this, we is not going to permit any sort of completion to happen if a minimum of one argument has already been typed. We will add a situation in our perform that checks the dimensions of the aforementioned COMP_WORDS array.

#/usr/bin/env bash
_dothis_completions()
sed 's/t//')" -- "$"))

full -F _dothis_completions dothis

Source and retry.

$ dothis 623<tab>
$ dothis 623 ls -la<tab> # SUCCESS: nothing occurs right here

There is one other factor we don’t like, although. We do need to show the numbers together with the corresponding instructions to assist customers resolve which one is desired, however when there is just one completion suggestion and it will get robotically picked by the completion mechanism, we shouldn’t append the command literal too.

In different phrases, our dothis executable accepts solely a quantity, and we haven’t added any performance to verify or count on different arguments. When our completion perform provides just one consequence, we must always trim the command literal and reply solely with the command quantity.

To accomplish this, we are going to maintain the response of the compgen command in an array variable, and if its dimension is 1, we are going to trim the one and solely component to maintain simply the quantity. Otherwise, we’ll let the array as is.

Change the completion script to this:

#/usr/bin/env bash
_dothis_completions()
sed 's/t/ /')" -- "$"))

  if [ "$#ideas[@]" == "1" ]; then
    # if there's just one match, we take away the command literal
    # to proceed with the automated completion of the quantity
    native quantity=$(echo $ideas[0]/% */)
    COMPREPLY=("$quantity")
  else
    # multiple ideas resolved,
    # reply with the ideas intact
    COMPREPLY=("$ideas[@]")
  fi

full -F _dothis_completions dothis

Registering the completion script

If you need to allow the completion only for you in your machine, all it’s a must to do is add a line in your .bashrc file sourcing the script:

supply <path-to-your-script>/dothis-completion.bash

If you need to allow the completion for all customers, you’ll be able to simply copy the script below /and so on/bash_completion.d/ and it’ll robotically be loaded by Bash.

Fine-tuning the completion script

Here are some additional steps for higher outcomes:

Displaying every entry in a brand new line

In the Bash completion script I used to be engaged on, I too needed to current ideas consisting of two elements. I needed to show the primary half within the default shade and the second half in grey to tell apart it as assist textual content. In this tutorial’s instance, it will be good to current the numbers within the default shade and the command literal in a much less fancy one.

Unfortunately, this isn’t attainable, a minimum of for now, as a result of the completions are displayed as plain textual content and shade directives aren’t processed (for instance: e[34mBlue).

What we are able to do to enhance the person expertise (or not) is to show every entry in a brand new line. This answer isn’t that apparent since we are able to’t simply append a brand new line character in every COMPREPLY entry. We will comply with a reasonably hackish technique and pad suggestion literals to a width that fills the terminal.

Enter printf. If you need to show every suggestion on every personal line, change the completion script to the next:

#/usr/bin/env bash
_dothis_completions()
sed 's/t//')" -- "$"))

  if [ "$#ideas[@]" == "1" ]; then
    native quantity="$ideas[0]/% */"
    COMPREPLY=("$quantity")
  else
    for i in "$"; do
      ideas[$i]="$(printf '%*s' "-$COLUMNS"  "$")"
    completed

    COMPREPLY=("$ideas[@]")
  fi

full -F _dothis_completions dothis

Source and check:

dothis <tab><tab>
...
499 supply dothis-completion.bash                  
500 clear
...      
503 dothis 500

Customizable habits

In our case, we hard-coded to show the final 50 instructions for completion. This isn’t an excellent apply. We ought to first respect what every person may want. If he/she hasn’t made any choice, we must always default to 50.

To accomplish that, we are going to verify if an atmosphere variable DOTHIS_COMPLETION_COMMANDS_NUMBER has been set.

Change the completion script one final time:

#/usr/bin/env bash
_dothis_completions()
sed 's/t//')" -- "$"))

  if [ "$#ideas[@]" == "1" ]; then
    native quantity="$ideas[0]/% */"
    COMPREPLY=("$quantity")
  else
    for i in "$"; do
      ideas[$i]="$(printf '%*s' "-$COLUMNS"  "$")"
    completed

    COMPREPLY=("$ideas[@]")
  fi

full -F _dothis_completions dothis

Source and check:

export DOTHIS_COMPLETION_COMMANDS_NUMBER=5
$ dothis <tab><tab>
505 clear
506 supply ./dothis-completion.bash
507 dothis clear
508 clear
509 export DOTHIS_COMPLETION_COMMANDS_NUMBER=5

Useful hyperlinks

Code and feedback

You can discover the code of this tutorial on GitHub.

For suggestions, feedback, typos, and so on., please open an issue within the repository.

Long publish, cat picture

Let me introduce you to my debugger:

That’s all, people!

This publish was initially posted at Iridakos.com. Reposted with permission.

Most Popular

To Top