Less Typing

Since the beginning of time, Man has had an uneasy relationship with his keyboard. Sure, keyboards make it possible to express our precise wishes to the computer, but in our fat-fingered excitement to get stuff done, we often suffer from typos and digital fatigue.

In this adventure, we will travel down the carpal tunnel to the land of less typing. We covered some of this in TLCL, but here we will look a little deeper.

Aliases and Shell Functions

The first thing we can do to reduce the number of characters we type is to make full use of aliases and shell functions. Aliases were created for this very purpose and are often a very effective solution. Shell functions perform in many ways like aliases but allow a full range of shell script-like capabilities such as programmatic logic, and option and argument processing.

Most Linux distributions provide some set of default alias definitions and it's easy to add more. To see the aliases we already have, we enter the alias command without arguments:

me@linuxbox: ~ $ alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias ls='ls --color=auto    

On this example system, we see alias is used to activate color output for some commonly used commands. It is also common to create aliases for various forms of the ls command:

alias ll='ls -l'
alias la='ls -A'
alias l='ls -CF'
alias l.='ls -d .*'
alias lm='ls -l | less'

Aliases are good for lots of things, for example, here's one that's useful for Debian-style systems:

alias update='sudo apt-get update && sudo apt-get upgrade'

Aliases are easy to create. It's usually just a matter of appending them to our .bashrc file. Before creating a new alias, it's a good idea to first test the proposed name of the alias with the type command to check if the name is already being used by another program or alias.

While being easy, aliases are somewhat limited. In particular, aliases are not good at handling complex logic or accepting positional parameters. For that we need to use shell functions.

As we recall from TLCL, shell functions are miniature shell scripts that we can add to our .bashrc file to perform anything that we may otherwise do with a shell script. Here is an example function that displays a quick snapshot of a system's health:

    status() {
      { echo -e "\nuptime:"
        uptime
        echo -e "\ndisk space:"
        df -h 2> /dev/null
        echo -e "\ninodes:"
        df -i 2> /dev/null
        echo -e "\nblock devices:"
        blkid
        echo -e "\nmemory:"
        free -m
        if [[ -r /var/log/syslog ]]; then
          echo -e "\nsyslog:"
          tail /var/log/syslog
        fi
        if [[ -r /var/log/messages ]]; then
          echo -e "\nmessages:"
          tail /var/log/messages
        fi
      } | less
    }

Unlike aliases, shell functions can accept positional parameters:

    params() {
        local argc=0
        while [[ -n $1 ]]; do
            argc=$((++argc))
            echo "Argument $argc = $1"
            shift
        done
    }

Command Line Editing

Aliases and shell functions are all well and good, provided we know in advance the operations we wish to perform, but what about the rest of the time? Most command line operations we perform are on-the-fly, so other techniques are needed.

As we saw in Chapter 8 of TLCL, the bash shell includes a library called readline to handle keyboard input during interactive shell sessions. This includes text typed at the shell prompt and keyboard input using the read builtin when the -e option is specified. The readline library supports a large number of commands that can be used to edit what we type at the command line. Many of the commands are taken from the emacs text editor (the subject of a future Adventure).

Control Commands

Before we get to the actual editing commands, let's look at some commands that are used to control the editing process.

Command Description
Enter Pressing the enter key causes the current command line to be accepted. Note that the cursor location within the line does not matter (i.e., it doesn't have to be at the end). If the line is not empty, it is added to the command history.
Esc Meta-prefix. If the Alt key is unavailable, the Esc key can be used in its place. For example, if a command calls for Alt-r but another program intercepts that command, press and release the Esc key followed by the r key.
Ctrl-g Abort the current editing command.
Ctrl-_ Incrementally undo changes to the line.
Alt-r Revert all changes to the line (i.e., complete undo).
Ctrl-l Clear the screen.
Alt-num Where num is a number. Some commands accept a numeric argument. For those commands that accept it, type this first followed by the command.

Moving Around

Here are some commands to move the cursor around the current command line. In the readline documentation, the current cursor location is referred to as the point.

Command Description
Right Move forward one character.
Left Move backward one character.
Alt-f Move forward one word.
Alt-b Move backward one word.
Ctrl-a Move to the beginning of the line.
Ctrl-e Move to the end of the line.

Using Command History

In order to save typing, we frequently reuse previously typed commands stored in the command history. We can move up and down the history list and the history list can be searched.

Command Description
Up Move to previous history list entry.
Down Move to next history list entry.
Alt-< Move to the beginning of the history list.
Alt-> Move to the end of the history list.
Ctrl-r Perform an incremental history search starting at the current position and moving up the history list. After a command is typed, a prompt appears and with each succeeding character typed, the position within the list moves to the next matching line. This is probably the most useful of the history search commands.
Ctrl-s Like Ctrl-r except the search is performed moving down the history list.
Alt-p Perform a non-incremental search moving up the history list.
Alt-n Perform a non-incremental search moving down the history list.
Alt-Ctrl-y Insert the first argument from the previous history entry. This command can take a numeric argument. When a numeric argument is given, the nth argument from the previous history entry is inserted.
Alt-. Insert the last argument from the previous history entry. When a numeric argument is given, behavior is the same as Alt-Ctrl-y above.

Changing Text

Command Description
Ctrl-d Delete the character at the point.
Ctrl-t Transpose characters. Exchange the character at the point with the character preceding it.
Alt-t Transpose words. Exchange the word at the point with the word preceding it.
Alt-u Change the current word to uppercase.
Alt-l Change the current word to lowercase.
Alt-c Capitalize the current word.

Cutting and Pasting

As with vim, cutting and pasting in readline are referred to as "killing" and "yanking." The clipboard is called the kill-ring and is implemented as a circular buffer. This means that it contains multiple entries (i.e., each kill adds a new entry). The latest entry is referred to as the "top" entry. It is possible to "rotate" the kill-ring to bring the previous entry to the top and delete the latest entry. However, this feature is rarely used.

Mostly, the kill commands are used to simply delete text rather than save it for later yanking.

Command Description
Alt-d Kill from the point to the end of the current word. If the point is located in whitespace, kill to the end of the next word.
Alt-Backspace Kill the word before the point.
Ctrl-k Kill from the point to end of line.
Ctrl-u Kill from the point to the beginning of the line.
Ctrl-y Yank the "top" entry from the kill-ring.
Alt-y Rotate the kill-ring and yank the new "top" entry.

Editing in Action

In Chapter 4 of TLCL, we considered the danger of using a wildcard with the rm command. It was suggested that we first test the wildcard with the ls command to see the result of the expansion. We then recall the command from the history and edit the line to replace the "ls" with "rm". So, how do we perform this simple edit?

First, the beginner's way: We recall the command with the up arrow, use the left arrow repeatedly to move the cursor to the space between the "ls" and the wildcard, backspace twice, then type "rm" and Enter.

That's a lot of keystrokes.

Next, the tough-guy's way: We recall the command with the up arrow, type Ctrl-a to jump to the beginning of the line, type Alt-d to kill the current word (the "ls"), type "rm" and Enter.

That's better.

Finally, the super-tough-guy's way: Type "rm " then Alt-. to recall the last argument (the wildcard) from the previous command, then Enter.

Wow.

Completion

Another trick that readline can perform is called completion. This is where readline will attempt to automatically complete something we type.

For example, let's imagine that our current working directory contains a single file named "foo.txt" and we want to view it with less. So we begin to type the command "less foo.txt" but instead of typing it all out, we just type "less f" and then press the Tab key. Pressing Tab tells readline to attempt completion on the file name and remainder of the command is completed automatically.

This will work as long as the "clue" given to readline is not ambiguous. If we had two files in our imaginary directory named "foo.txt" and "foo1.txt", a successful completion would not take place since "less f" could refer to either file. What happens instead is readline makes the next best guess by completing as far as "less foo" since both possible answers contain those characters. To make a full completion, we need to type either "less foo." for foo.txt or "less foo1" for foo1.txt.

If we have typed an ambiguous clue, we can view a list of all possible completions to get guidance as what to type next. In the case of our imaginary directory, pressing Tab a second time will display all of the file names beginning with "foo" so that we can see what more needs to be typed to remove the ambiguity.

Besides file name completion, readline can complete command names, environment variable names, user home directory names, and network host names:

Completion Description
Command names Completion on the first word of a line will complete the name of an available command. For example, typing "lsu" followed by Tab will complete as lsusb.
Variables If completion is attempted on a word beginning with "$", environment variable names will be used. For example, typing "echo $TE" will complete as echo $TERM.
User names To complete the name of a user's home directory, precede the user's name with a "~" and press 'Tab'. For example: ls ~ro followed by Tab will complete to ls ~root/. It is also possible to force completion of a user name without the leading ~ by typing Alt-~. For example "who ro" followed by Alt-~ will complete to who root.
Host names Completion on a word starting with "@" causes host name completion, however this feature rarely works on modern systems since they tend to use DHCP rather than listing host names in the /etc/hosts file.
File names In all other cases, completion is attempted on file and path names.

Programmable Completion

Bash includes some builtin commands that permit the completion facility to be programmed on a command-by-command basis. This means it's possible to set up a custom completion scheme for individual commands; however, doing this is beyond the scope of this adventure. We will instead talk about an optional package that uses these builtins to greatly extend the native completion facility. Called bash-completion, this package is installed automatically for some distributions (for example, Ubuntu) and is generally available for others. To check for the package, examine the /etc/bash-completion.d directory. If it exists, the package is installed.

The bash-completion package adds support for many command line programs, allowing us to perform completion on both command options and arguments. The ls command is a good example. If we type "ls --" then the Tab key a couple of times, we will see a list of possible options to the command:

me@linuxbox: ~ $ ls --
--all                                      --ignore=
--almost-all                               --ignore-backups
--author                                   --indicator-style=
--block-size=                              --inode
--classify                                 --literal
--color                                    --no-group
--color=                                   --numeric-uid-gid
--context                                  --quote-name
--dereference                              --quoting-style=
--dereference-command-line                 --recursive
--dereference-command-line-symlink-to-dir  --reverse
--directory                                --show-control-chars
--dired                                    --si
--escape                                   --size
--file-type                                --sort
--format=                                  --sort=
--group-directories-first                  --tabsize=
--help                                     --time=
--hide=                                    --time-style=
--hide-control-chars                       --version
--human-readable                           --width=

An option can be completed by typing a partial option followed by Tab. For example, typing "ls --ver" then Tab will complete to "ls --version".

The bash-completion system is interesting in its own right as it is implemented by a series of shell scripts that make use of the complete and compgen bash builtins. The main body of the work is done by the /etc/bash_completion (or /usr/share/bash-completion/bash_completion in newer versions) script along with additional scripts for individual programs in either the /etc/bash-completion.d directory or the /usr/share/bash-completion/completions directory. These scripts are good examples of advanced scripting technique and are worthy of study.

Summing Up

This adventure is a lot to take in and it might not seem all that useful at first, but as we continue to gain experience and practice with the command line, learning these labor-saving tricks will save us a lot of time and effort.

Further Reading


© 2000-2014, William E. Shotts, Jr. Verbatim copying and distribution of this entire article is permitted in any medium, provided this copyright notice is preserved.

Linux® is a registered trademark of Linus Torvalds.