1. Overview

We interact with the Linux OS through different commands over the command prompt. There are tons of commands and various options, so it can be difficult to remember all of them.

Fortunately, in Linux, we’ve got the Bash completion feature. This is one of the features we learn when we get introduced to Linux. It actually helps us compose commands quickly. And it’s done by completing files and directory names or command options as we’re preparing the command to execute.

In this tutorial, we’ll see how tab completion in Linux works and how we can roll one out ourselves.

2. The complete Command

The complete command is a shell built-in command. It’s the core command which makes the Bash completion happen. This sets the completion specification for a command. It accepts different options to generate completion words of different kinds.

Sometimes, for a command when Bash completion is invoked, we need to fulfill it with a list of file names or directory names. Other times, it could be a custom word list, jobs, or service names in the machine. We can specify how we need to prepare the word list during a Bash completion using the complete command.

Let’s look at its usage:

complete: usage: complete [-abcdefgjksuv] [-pr] [-DE] [-o option] [-A action] [-G globpat] [-W wordlist]  [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [name ...]

Now, let’s check how we can set a completion specification for a command:

$ complete -W "bar car" foo
$ foo [Tab][Tab]
bar car
$ foo b[Tab]
$ foo bar

Here we’re trying to set tab completion for an imaginary command foo using the complete command. For that, we’ve used the -W option to set the words list to be used when we invoke tab completion for the command foo.

After setting the words list, we tried invoking the tab completion for the foo command. As expected, it has listed the words we added for the command foo.

This demonstrates the basic setup to enable Bash completion for any command. We can combine different options to expand the completion list.

Suppose we wanted to list the files and directories during completion. Then, we can use the -f and -d options:

$ touch a.txt
$ mkdir test
$ complete -f -d -W "bar car" foo
$ foo [Tab][Tab]
a.txt  bar    car    text/  

In the above commands, first, we created a file a.txt and a directory test. Then we ran the complete command with the -f and -d flags. After that, we tried tab completion for the foo command. As a result, it listed the file and directory along with the word list.

2.1. Using Custom Functions

We can make Bash invoke a function during the completion process. This function can then return the word list for completion. For that, we use the -F option in the complete command. This is the option we normally use in the most practical applications:

$ complete -F _mac_addresses foo
$ foo [Tab][Tab]
09:00:22:eb:1e:29  52:54:00:04:29:35  

The above command sets the function _mac_addresses to be invoked during tab completion. The _mac_addresses function is defined as part of the Bash completion feature. It lists the MAC addresses of network interface cards present in our machine. Hence, upon pressing the tab key it listed the MAC address.

For the purpose of demonstration, we’ve used an already-defined function, but we can also define and use our own function to be invoked during tab completion.

3. Implementing a Function

As we’ve seen, we can make Bash call a function during a tab completion.

The function should have these capabilities:

  • contain logic to prepare a list of words for completion
  • fetch the command line arguments
  • based on command line arguments, compute the completion list
  • pass the completion list back to the shell

To facilitate this, we have a few predefined variables in Bash:

  • COMP_WORDS: array holding command line arguments
  • COMP_CWORD: index into the above array to point to the current argument
  • COMPREPLY: array holding the prepared words list, which is the result of our function, that’s finally used for completion

With these, we can prepare the completion list and send it back to the shell for printing in stdout.

3.1. Location of Function

We have to put our functions in a designated location so that the Bash completion mechanism can load them.

The /etc/bash_completion script holds the initialization functionalities for Bash completion. It gets invoked as part of the profile load when we log in to the shell. From the bash_completion script, it also loads all the scripts added in the /etc/bash_completion.d directory.

Therefore, we can define our custom function in a file and place the file in the /etc/bash_completion.d directory. Thus, our custom function will be sourced and ready to be called during a tab completion as we log in.

Inside the custom function, we need a pattern-matching logic to identify the current command arguments and filter the words for completion. Luckily, we’ve got a command to do just that. Let’s take a look at it.

3.2. The compgen Command

The compgen command simplifies the pattern matching during tab completion. It supports most of the different options the complete command supports.

Here’s its syntax:

compgen: usage: compgen [-abcdefgjksuv] [-o option] [-A action] [-G globpat] [-W wordlist]  [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [word]

Let’s look at an example usage:

$ compgen -W "bar car" -- ""
bar
car
$ compgen -W "bar car" -- "b"
bar

We’ve used the -W option here. With this option, we can specify a word list. After that, we gave the word to match after the .

In the first command, we gave an empty string. Then in the output, we could see it has listed all the options in the given words.

In the second one, we passed “b”. Then it printed only the matching options.

Thus, this command comes in handy while preparing a list of options given the portion of the string to match during a tab completion.

3.3. Sample Function

We’ll finish by looking at a function that completes the arguments for our imaginary command foo.

Let’s assume, the foo command has two options bar and car. And for bar, we have further options drink and eat. Likewise, for car, we have drive and park.

Now, let’s look at the function:

function _foo()
{
  latest="${COMP_WORDS[$COMP_CWORD]}"
  prev="${COMP_WORDS[$COMP_CWORD - 1]}"
  words=""
  case "${prev}" in
    foo)
      words="bar car"
      ;;
    bar)
      words="eat drink"
      ;;
    car)
      words="drive park"
      ;;
    *)
      ;;
  esac
  COMPREPLY=($(compgen -W "$words" -- $latest))
  return 0
}

complete -F _foo foo

Firstly, we’ve created a file at location /etc/bash_completion.d/foo. Secondly, inside the file, we defined a function _foo. Thirdly, we called the complete command for foo.

The function itself is easy enough to understand. First, we collect the latest and previous words entered. This is extracted from the COMP_WORDS array using the COMP_CWORD index. Then we check whether the previous word is foo or bar or car inside the case. In any of those cases, we set the word list to the words variable. Finally, we set the result in COMPREPLY array. This is done by executing the compgen command with the values in the words variable.

This is a simple function for demonstration purposes. We haven’t defined any completion spec for the sub-commands. But this gives an idea of how to implement a function.

Finally, let’s source the file to test the command and see that the tab completion is working for the command:

$ source /etc/bash_completion.d/foo
$ foo [Tab][Tab]
bar car
$ foo bar[Tab][Tab]
drink eat

4. Conclusion

In this article, we learned how tab completion works under the hood. We saw how the complete and compgen commands work. These commands support different options that we can choose depending on our needs. Finally, we discussed how to implement a function that makes the whole process of tab completion more flexible.