Bash completion makes bash start slowly

Starting a bash on my ubuntu system takes about 2 seconds. If I remove loading /etc/bash_completition in .bashrc it starts without delay. Of course I don't want to give up completition and I don't think loading that file is legit reason for a 2 seconds delay.

Any ideas how i can find out what the problem is or how i can speed things up.


Solution 1:

Update in 2013: Most of bash-completion was rewritten to autoload completions only when needed. The core script is much ligher now.


The completion script can sometimes be huge in shell script standards. On one servers I have access to, it's almost 1700 lines (57 KB) and that's just the core script. In /etc/bash_completion.d there are ~200 additional scripts for various other commands (openssl, mutt, mount...) totalling 25537 lines or 1.2 MB. Each script, when sourced, checks if a command is actually available before defining completion handlers; ~330 times in this case, each of which involves checking $PATH for an executable file with a given name. (Although I would expect /usr/bin to be cached in memory...)

Admittedly, even that only takes half a second to load, not two full seconds. But it might be at least part of the problem. Run du -hs /etc/bash_completion* or wc -l /etc/bash_completion{,.d/*} | grep total if you want to check.


You can try manually sourcing the script, in "trace" mode:

set -x
. /etc/bash_completion

You'll see each line as it is executed. If there's a particular command that takes a long time, you should notice it.

(set +x disables the trace mode.)

Solution 2:

I found a bit hackish solution that seems to work fairly enough.

Solution

At the bottom of ~/.bashrc add:

trap 'source /etc/bash_completion ; trap USR1' USR1
{ sleep 0.1 ; builtin kill -USR1 $$ ; } & disown

Explanation

trap 'source /etc/bash_completion ; trap USR1' USR1

Setup an handler to be run when the shell receives the signal SIGUSR1; the handler will load the completions and hence it will deactivate itself.

{ sleep 0.1 ; builtin kill -USR1 $$ ; } & disown

Asynchronously wait a bit then send the signal to the current shell. disown is needed to suppress bash process control feedback. sleep is needed to work asynchronously.

Problems

For some reason the first command issued to this shell will not be recorded in the history.

Solution 3:

You should use the lastest version (2.0) of bash_completion. If you're using Debian, it's in wheezy, but it has no dependencies on any other wheezy package, so you can install it on a squeeze with no issue.

The lastest version loads completion dynamically on the fly, so it divided the prompt load time for me by at least 10x.

Solution 4:

You can use a placeholder while the completions are loading; it should be enough to trick your eyes. This obviously work only if the time needed by source /etc/bash_completion is less than the time needed by you to type and issue the first shell command, otherwise it will be delayed as well.

The idea is to echo a fake PS1, source the completions and finally erease the terminal.

Suppose that your PS1 is \u@\h:\w\$, they you might write something like:

echo -ne "$USER@$HOSTNAME:${PWD/$HOME/\~}\$ "
source /etc/bash_completion
echo -ne '\e[2J\e[H'

Where:

  • 2J erases the terminal;
  • H move the cursor to the upper right corner.

Note: You may want to check if the user is root and use # instead of $ for consistency:

echo -ne "...$([ $UID = 0 ] && echo '#' || echo '$') "

Note: Removing \e[2J avoids the flickering but it will leave junk characters if the placeholder is longer than the actual prompt.