Lazier Tab completion

Bash completion allows you to complete paths, commands, and filenames, and where implemented can even expand the syntax of commands like git and svn. It’s very convenient by default, but there are a couple of tweaks that can make it much faster and easier to use.

Single Tab press

With Bash completion enabled, the default is to complete the current path to the longest unique string it can on the first press of the Tab key, and then show a list of all the possible completions if the Tab key is then pressed a second time.

If you don’t like pressing Tab twice, you can fix this with a line in .inputrc so that the line is both completed to the longest unambiguous pattern, and then possible values are also printed:

set show-all-if-ambiguous on

Log out and then in again, or re-read your .inputrc file with Ctrl+X then Ctrl+R, and you should find that you now only need to press Tab once on completions to get both behaviours in one hit.

If you want Tab completion of filenames to work the same way in Vim’s command mode, you can include the below in your .vimrc. Note that the separator is a colon, rather than a comma, which means to perform both the command completion and suggestion at the first Tab press:

set wildmode=longest:list

Complete case-insensitively

If you commonly Tab your way through long paths that sometimes include mixed-case names, you can make Readline handle that for you by making the completion case-insensitive, with the following in your .inputrc:

set completion-ignore-case on

You can supplement this to make the completion apply similar insensitivity between hyphens and underscores:

set completion-map-case on

If you were tabbing your way through an ImageMagick directory, for example, and forgot the capital letters in typing imagem, Readline would quietly complete it to ImageMagick for you. Similarly, if you typed lib-undersc when the actual filename was lib_underscore, the shell would quietly fix that for you too.

You can get case-insensitivity for the filename completion in Vim with the following. I suggest wrapping it in a conditional as below, since it’s a reasonably new option:

if exists("&wildignorecase")
    set wildignorecase
endif

Unfortunately, there doesn’t seem to be a way that I can find to reproduce the underscore/hyphen mapping. If you know of one, please comment.

Don’t prompt for many possible completions

If you’re on a fast terminal and you’re not bothered by having the screeds of texts it can sometimes generate being spat at you, you can turn off the sometimes annoying prompt that checks with you whether you want to show more than a hundred results for possible completion, with the following in .inputrc:

set completion-query-items -1

If you do this, you should probably set Readline’s built-in pager to be off, otherwise it’ll attempt to walk you through the possible completions page-by-page:

set page-completions off

If you do happen to deal with directories with more than a thousand files in them now and then (which doesn’t tend to happen much in routine system administration), it might be a bit safer to set it to some number much higher than the default of 100:

set completion-query-items 1000

These three tips combined make tabbing your way through path names much more pleasant and intuitive; you won’t find yourself irritably tapping Tab over and over nearly as much. Even if you’re very much used to playing ball with the stricter idiosyncrasies of the usual form of tab completion, you may find making the shell do the work for you in this way starts to feel very natural very quickly.

Vi mode in Bash

Because the Bash shell uses GNU Readline, you’re able to set options in .bashrc and .inputrc to extensively customise how you enter your command lines, including specifying whether you want the default Emacs-like keybindings (e.g. Ctrl+W to erase a word), or bindings using a modal interface familiar to vi users.

To use vi mode in Bash and any other tool that uses GNU Readline, such as the MySQL command line, you need only put this into your .inputrc file, then log out and in again:

set editing-mode vi

If you only want to use this mode in Bash, an alternative is to use the following in your .bashrc, again with a login/logout or resourcing of the file:

set -o vi

You can check this has applied correctly with the following, which will spit out a list of the currently available bindings for a set of fixed possible actions for text:

$ bind -P

The other possible value for editing-mode is emacs. Both options simply change settings in the output of bind -P above.

This done, you should find that you open a command line in insert mode, but when you press Escape or Ctrl+[, you will enter an emulation of Vi’s normal mode. Enter will run the command in its present state while in either mode. The bindings of most use in this mode are the classic keys for moving to positions on a line:

  • ^ — Move to start of line
  • $ — Move to end of line
  • b — Move back a word
  • w — Move forward a word
  • e — Move to the end of the next word

Deleting, yanking, and pasting all work too. Also note that k and j in normal mode allow you to iterate through your history in the same way that Ctrl+P and Ctrl+N do in Emacs mode. Searching with ? and / doesn’t work by default, but the Ctrl+R and Ctrl+S bindings still seem to work.

If you are reasonably used to working with the modeless Emacs for most of your shell work but do occasionally find yourself in a situation where being able to edit a long command line in vi would be handy, you may prefer to press Ctrl+X Ctrl+E to bring up the command line in your $EDITOR instead, affording you the complete power of Vim to edit rather than the somewhat sparse vi emulation provided by Readline. If you want to edit a command you just submitted, the fc (fix command) Bash builtin works too.

The shell is a very different environment for editing text, being inherently interactive and fixed to one line rather than a static multi-line buffer, which leads some otherwise diehard vi users such as myself to prefer the Emacs mode for editing commands. For one thing, vi mode in Bash trips on the vi anti-pattern of putting you in insert mode by default, and at the start of every command line. But if the basic vi commands are etched indelibly into your memory and have become automatic, you may appreciate being able to edit your command line using these keys instead.¬†It’s certainly worth a try at any rate, and I do still occasionally see set -o vi in the .bashrc files of a few of the pros on GitHub.

Arabesque passed 100,000 visits today, having existed little over a month, with over 60,000 unique visitors. Thanks very much to everyone who reads and comments on the articles.

High-speed Bash

One of my favourite technical presentations I’ve read online has been Hal Pomeranz’s Unix Command-Line Kung Fu, a catalogue of shortcuts and efficient methods of doing very clever things with the Bash shell. None of these are grand arcane secrets, but they’re things that are often forgotten in the course of daily admin work, when you find yourself typing something you needn’t, or pressing up repeatedly to find something you wrote for which you could simply search your command history.

I highly recommend reading the whole thing, as I think even the most experienced shell users will find there are useful tidbits in there that would make their lives easier and their time with the shell more productive, beyond simpler things like tab completion. Here, I’ll recap two of the things I thought were the most simple and useful items in the presentation for general shell usage, and see if I can add a little value to them with reference to the Bash manual.

History with Ctrl+R

For many shell users, finding a command in history means either pressing the up arrow key repeatedly, or perhaps piping a history call through grep. It turns out there’s a much nicer way to do this, using Bash’s built-in history searching functionality; if you press Ctrl+R and start typing a search pattern, the most recent command matching that pattern will automatically be inserted on your current line, at which point you can adapt it as you need, or simply press Enter to run it again. You can keep pressing Ctrl+R to move further back in your history to the next-most recent match. On my shell, if I search through my history for git, I can pull up what I typed for a previous commit:

(reverse-i-search)`git': git commit -am "Pulled up-to-date colors."

This functionality isn’t actually exclusive to Bash; you can establish a history search function in quite a few tools that use GNU Readline, including the MySQL client command line.

You can search forward through history in the same way with Ctrl+S, but it’s likely you’ll have to fix up a couple of terminal annoyances first.

Additionally, if like me you’re a Vim user and you don’t really like having to reach for the arrow keys, or if you’re on a terminal where those keys are broken for whatever reason, you can browse back and forth within your command history with Ctrl+P (previous) and Ctrl+N (next). These are just a few of the Emacs-style shortcuts that GNU Readline provides; check here for a more complete list.

Repeating commands with !!

The last command you ran in Bash can be abbreviated on the next line with two exclamation marks:

$ echo "Testing."
Testing.
$ !!
Testing.

You can use this to simply repeat a command over and over again, although for that you really should be using watch, but more interestingly it turns out this is very handy for building complex pipes in stages. Suppose you were building a pipeline to digest some data generated from a program like netstat, perhaps to determine the top 10 IP addresses that are holding open the most connections to a server. You might be able to build a pipeline like this:

# netstat -ant
# !! | awk '{print $5}'
# !! | sort
# !! | uniq -c
# !! | sort -rn
# !! | sed 10q

Similarly, you can repeat the last argument from the previous command line using !$, which is useful if you’re doing a set of operations on one file, such as checking it out via RCS, editing it, and checking it back in:

$ co -l file.txt
$ vim !$
$ ci -u !$

Or if you happen to want to work on a set of arguments, you can repeat all of the arguments from the previous command using !*:

$ touch a.txt b.txt c.txt
$ rm !*

When you remember to user these three together, they can save you a lot of typing, and will really increase your accuracy because you won’t be at risk of mistyping any of the commands or arguments. Naturally, however, it pays to be careful what you’re running through rm!