Info-Tech

Making a bash completion script (2018)

I currently worked on creating a bash completion script for registering list aliases and navigating to them with autocomplete in Bash and I learned the characteristic in actual fact sharp.


On this put up I will familiarize you with the strategy of adding bash completion to your scripts.

What’s bash completion?

Bash completion is a functionality thru which bash helps customers form their commands faster and more uncomplicated. It accomplishes that by presenting seemingly solutions when customers press the tab key while typing a sigh.

$ git
git                 git-pick up-pack    git-upload-archive  
gitk                git-shell           git-upload-pack     
$ git-s
$ git-shell

How it in actual fact works

The completion script is code that makes impart of the builtin bash sigh total to define which completion solutions may perchance perhaps moreover be displayed for a given executable. The character of the completion solutions fluctuate from straightforward static to extremely delicate.

Why bother

It is unbiased correct to scheme the kind of functionality to customers:

  • to set apart them from typing text when it will moreover be auto-done
  • to wait on them know what are the on hand continuations to their commands
  • to forestall errors and red meat up their trip by hiding or exhibiting solutions in step with what they have got already typed

Fingers on

Here’s what we are going to originate in this tutorial.

We’re going to first scheme a dummy executable script called dothis. All it does is function the sigh which resides on the number, that became as soon as passed as an argument, in consumer’s historical previous. Shall we embrace, the next sigh will simply function the ls -a sigh on condition that it exists in historical previous with number 235:

Then we are able to scheme a bash completion script that will show commands in conjunction with their number from consumer’s historical previous and we are able to “bind” it to the dothis executable.

$ dothis 
215 ls
216 ls -la
217 cd ~
218 man historical previous
219 git popularity
220 historical previous | reduce relieve -c 8-


That you just would have the ability to gaze a gif demonstrating the functionality at this tutorial’s code repository on GitHub.

Let the show launch.

Creating the executable script

Produce a file named dothis to your working list and add the next code:

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

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

if [ -n "$exists" ]; then
  fc -s -- "$1"
else
  echo "Say with number $1 became as soon as no longer dispute in fresh historical previous"
  exit 2
fi

Notes:

  • We first take a look at if the script became as soon as called with an argument
  • We then take a look at if the impart number is incorporated within the last 1000 commands
    • if it exists we offer out the sigh using the fc functionality
    • otherwise we show an error message

Produce the script executable with:

We’re going to function this script all every other time and all every other time in this tutorial so I like to recommend you jam it in a folder that is incorporated to your direction so as that we can procure admission to it from any place with correct typing dothis.

I attach in it in my dwelling bin folder using:

install ./dothis ~/bin/dothis

That you just would have the ability to originate the same on condition that which that you may perchance moreover have gotten a ~/bin folder and that it is incorporated to your PATH variable.

Are attempting to watch if it’s working:

That you just would have the ability to perchance moreover unruffled gaze this.

$ dothis
No sigh number passed

Performed.

Creating the completion script

Produce a file named dothis-completion.bash. From now on we are able to be referring to this file with the term completion script.

When we add some code to it, we are able to offer it to enable the completion to steal originate.
We need to offer this file each time we change one thing in it.

We’re going to focus on our solutions for registering this script every time a bash shell opens in a while.

Static completion

Train that the dothis program supported a list of commands, let’s impart:

  • now
  • the next day
  • never

Let’s impart the total sigh to register this list for completion. To impart the precise terminology, we impart we impart the total sigh to define a completion specification (compspec) for our program.

Add this to the completion script.

#/usr/bin/env bash
total -W "now the next day never" dothis

What did we specify with the total sigh above:

  • we used the -W (wordlist) risk to scheme a list of phrases for completion.
  • we outlined to which “program” these completion phrases will seemingly be used (the dothis parameter)

Source the file:

offer ./dothis-completion.bash

Now try urgent tab twice within the sigh line as shown beneath:

$ dothis 
never     now       the next day

Are attempting all every other time after typing the n:

$ dothis n
never now

Magic! The completion solutions are robotically filtered to compare handiest these starting with n.

Reveal:
The solutions are no longer displayed within the sigh that we outlined them within the thesaurus, they’re robotically sorted.

There are change other solutions to be used as a change of the -W that we used in this portion. Most of them scheme completions in a fixed system, which technique that we don’t intervene dynamically to filter their output.

Shall we embrace, if we wished to have list names as completion phrases for the dothis program, we would change your total sigh to the next:

total -A list dothis

Urgent tab after the dothis program, we would procure a list of the directories within the hot list from which we offer out the script:

$ dothis 
dir1/ dir2/ dir3/

Rating your total list of the on hand flags here.

Dynamic completion

We’re going to be producing the completions of the dothis executable with the next logic:

  • If consumer presses the tab key honest after the sigh, we are able to show the last 50 accomplished commands in conjunction with their numbers in historical previous
  • If consumer presses the tab key after typing a bunch that matches larger than one commands from historical previous, we are able to show handiest these commands in conjunction with their numbers in historical previous
  • If consumer presses the tab key after a bunch that matches precisely one sigh in historical previous, we auto-total the number with out appending the sigh’s literal (if it’s confusing, which that you may perchance better label in a while, no worries)

Let’s start by defining a characteristic that will function every time consumer requests completion on a dothis sigh. Change the completion script to this:

#/usr/bin/env bash
_dothis_completions()
{
  COMPREPLY+=("now")
  COMPREPLY+=("the next day")
  COMPREPLY+=("never")
}

total -F _dothis_completions dothis

Reveal the next:

  • we used the -F flag to your total sigh defining that the _dothis_completions is the characteristic that will present the completions of the dothis executable
  • COMPREPLY is an array variable used to retailer the completions – the completion mechanism makes impart of this variable to show its contents as completions

Now offer the script and chase for completion:

$ dothis 
never now the next day

Ultimate. We scheme the same completions as within the outdated portion with the thesaurus. Or no longer? Attach that:

$ dothis nev
never     now       the next day

As which that you may perchance moreover gaze, although we form nev after which we quiz for completion, the on hand solutions are steadily the same and nothing gets done robotically. Why is this occurring?

  • The contents of the COMPREPLY variable are steadily displayed. The characteristic is accountable to add/steal away entries from there now.
  • If the COMPREPLY variable had handiest one part then that notice may perchance perhaps be robotically done within the sigh. Since fresh implementation steadily return the same three phrases, this isn’t any longer going to happen.

Enter compgen: a builtin sigh that generates completions supporting many of the solutions of the total sigh (ex. -W for thesaurus, -d for directories) and filtering them in step with what already has been typed by the patron.

Don’t agonize whilst you feel at a loss for phrases, all the pieces will develop into sure in a while.

Form the next within the console to larger label what compgen does:

$ compgen -W "now the next day never"
now
the next day
never
$ compgen -W "now the next day never" n
now
never
$ compgen -W "now the next day never" t
the next day

So now we can impart it but we now need to catch a system to know what has been typed after the dothis sigh.
We already have the trend. The bash completion companies present bash variables connected to the completion taking jam. Here are the extra considerable ones:

  • COMP_WORDS: an array of all 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 notice the hot cursor is at – in other phrases, the index of the notice the cursor became as soon as when the tab key became as soon as pressed
  • COMP_LINE: the hot sigh line

To procure admission to the notice correct after the dothis notice, we can impart the payment of COMP_WORDS[1]

Change the completion script all every other time:

#/usr/bin/env bash
_dothis_completions()
{
  COMPREPLY=($(compgen -W "now the next day never" "${COMP_WORDS[1]}"))
}

total -F _dothis_completions dothis

Source and there which that you may perchance moreover be:

 $ dothis
never     now       the next day  
$ dothis n
never  now

Now, as a change of the now, the next day, never phrases, we would snatch to watch precise numbers from the sigh historical previous.

The fc -l sigh followed by a negative number -n shows the last n commands.
So, we are able to impart:

which lists the last 50 accomplished commands in conjunction with their numbers. The ideal manipulation we now need to originate is change tabs with spaces in sigh to be displayed correctly from the completion mechanism. sed to the rescue.

Change the completion script as follows:

#/usr/bin/env bash
_dothis_completions()
{
  COMPREPLY=($(compgen -W "$(fc -l -50 | sed 's/t//')" -- "${COMP_WORDS[1]}"))
}

total -F _dothis_completions dothis

Source and take a look at within the console:

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

Now not unhealthy.

We originate have a field although. Are attempting typing a bunch as you gaze it to your completion list and the press the key all every other time.

$ dothis 623
$ dothis 623  ls 623  ls -la
...
$ dothis 623  ls 623  ls 623  ls 623  ls 623  ls -la

Here is going on because in our completion script, we used the ${COMP_WORDS[1]} to steadily take a look at the main typed notice after the dothis sigh (the number 623 within the above snippet). Therefore the completion continues suggesting the same completion all every other time and all every other time while urgent the tab key.

To repair this, we isn’t any longer going to enable any roughly completion to steal jam if no longer decrease than one argument has already been typed. We’re going to add a situation in our characteristic checking the scale of the aforementioned COMP_WORDS array.

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

  COMPREPLY=($(compgen -W "$(fc -l -50 | sed 's/t//')" -- "${COMP_WORDS[1]}"))
}

total -F _dothis_completions dothis

Source and retry.

$ dothis 623
$ dothis 623 ls -la # SUCCESS: nothing occurs here

There may perchance be one other thing we don’t love although. We originate desire to show the numbers in conjunction with the corresponding commands to wait on customers make a option which one is the specified but when there is handiest one completion advice and gets robotically picked by the completion mechanism, we shouldn’t append the sigh literal too.

In other phrases, our dothis executable accepts handiest a bunch and we haven’t added any functionality checking or attempting ahead to other arguments. When our completion characteristic affords handiest one consequence, we may perchance perhaps moreover unruffled correctly-organized the sigh literal and respond handiest with the sigh number.

To make this, we are able to preserve the response of the compgen sigh in an array variable and if its size is 1 we are able to properly-organized the one and handiest part to preserve correct the number. In any other case, we’ll let the array as is.

Change the completion script to this:

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

  # preserve the solutions in a local variable
  native solutions=($(compgen -W "$(fc -l -50 | sed 's/t/ /')" -- "${COMP_WORDS[1]}"))

  if [ "${#suggestions[@]}" == "1" ]; then
    # if there's handiest one match, we steal away the sigh literal
    # to proceed with the computerized completion of the number
    native number=$(echo ${solutions[0]/% */})
    COMPREPLY=("$number")
  else
    # larger than one solutions resolved,
    # respond with the solutions intact
    COMPREPLY=("${solutions[@]}")
  fi
}

total -F _dothis_completions dothis

Registering the completion script

Whereas you would snatch to enable the completion correct so that you can your machine, all it’s well-known to originate is add a line to your .bashrc file sourcing the script:

offer /dothis-completion.bash

Whereas you would snatch to enable the completion for all customers, which that you may perchance moreover correct reproduction the script beneath /and tons others/bash_completion.d/ and it will robotically be loaded by Bash.

Glorious tuning the completion script

Some additional steps for better results :)

Exhibiting every entry in a brand unique line

Nicely, within the bash completion script I became as soon as working on, I too needed to dispute solutions consisting of two facets. I wished to show the main portion with the default color and the 2nd portion with a grey color to assign that it became as soon as correct wait on text. On this tutorial’s example, it can be nice to dispute the numbers within the default color but the sigh literal in a single other one, much less indulge in.

Sadly, here isn’t any longer seemingly no longer decrease than for the time being since the completions are displayed as unpleasant text and color directives are no longer processed (let’s impart: e[34mBlue).

What we can do though to improve user experience (or not :D) is to display each entry in a new line. This solution is not that obvious since again we can’t just append a new line character in each COMPREPLY entry. We will follow a rather hackish way and pad suggestion literals to such a width that fills the terminal’s width.

Enter printf. If you want to display each suggestion on each own line, change the completion script to the following:

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

  native IFS=$'n'
  native solutions=($(compgen -W "$(fc -l -50 | sed 's/t//')" -- "${COMP_WORDS[1]}"))

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

    COMPREPLY=("${solutions[@]}")
  fi
}

total -F _dothis_completions dothis

Source and take a look at:

dothis 
...
499 offer dothis-completion.bash                   
500 sure
...       
503 dothis 500

Customizable habits

In our case, we laborious-coded to show the last 50 commands for completion. Here isn’t any longer a legit apply. We may perchance perhaps moreover unruffled first appreciate what every consumer may perchance perhaps moreover snatch and if he/she hasn’t made any desire then we may perchance perhaps moreover unruffled default to 50.

To make that we are going to take a look at if an setting variable DOTHIS_COMPLETION_COMMANDS_NUMBER has been set apart of living.

Change the completion script one last time:

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

  native commands_number=${DOTHIS_COMPLETION_COMMANDS_NUMBER:-50}
  native IFS=$'n'
  native solutions=($(compgen -W "$(fc -l -$commands_number | sed 's/t//')" -- "${COMP_WORDS[1]}"))

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

    COMPREPLY=("${solutions[@]}")
  fi
}

total -F _dothis_completions dothis

Source and take a look at:

export DOTHIS_COMPLETION_COMMANDS_NUMBER=5
$ dothis 
505 sure
506 offer ./dothis-completion.bash
507 dothis sure
508 sure
509 export DOTHIS_COMPLETION_COMMANDS_NUMBER=5

That you just would have the ability to catch the code of this tutorial on GitHub.

For solutions, comments, typos and tons others. please start an draw back within the repository.

Long put up, cat picture

Let me introduce you to my debugger.

That’s all of us!

Content Protection by DMCA.com

Back to top button