I’ve always preferred command-line interfaces (CLI) over GUIs. If I use GUIs at all then it’s mostly for browsing the web. Luckily, there is a plugin for my web browser that allows me to do most of my surfing using vi keystrokes. Yes, I try to avoid the mouse as much as I can.

I believe that most people who prefer GUIs either are bad at typing or haven’t taken the time to learn to use a CLI in an idiomatic way. With this post, I want to share some Bash CLI idioms that can significantly improve your efficiency. I don’t aim for a complete list — I rather present a compilation of the most frequently “not-known” techniques that I’ve encountered when I observed people working with Bash.

THE BASICS

First of all, make sure that you have enabled the command-line editing mode that suits you best. You are much more productive when your favorite shortcuts are available:

1

2

$ set -o emacs # Emacs-style editing (default)

$ set -o vi # vi-style editing

Often, we need to do something that we’ve already done before. You can easily re-execute the previous command with this shortcut:

1

$ !!

Courtesy of this operator, forgetting to put ‘sudo’ in front of a command is not a big deal:

1

2

3

$ visudo

visudo: /etc/sudoers: Permission denied

$ sudo !!

If you want to re-execute “one of the previous commands” instead of the last one, you could use the following abbreviations:

1

2

3

$ !:5 # select 5th command from history

$ !:-2 # select penultimate command

$ !cp # select last command that started with 'cp'

However, I don’t find these history expansions particularly useful. Often, going through the history by pressing ‘Arrow Up’ is equally fast.

The real game changer, however, is CTRL-R. Press CTRL-R and start typing any characters from one of your previous commands. This will start a backwards search through your command-line history and present you with the most recent command containing the characters you typed (or continue to type). Pressing CTRL-R again will find the next most recent command and so on. If you only partly need what you typed in the past, no problem — you can always edit the command-line that CTRL-R found before executing it.

MORE FUN WITH WORD DESIGNATORS

If you want to rename a file, please don’t do it like this:

1

$ mv some/long/path/to/oldfile some/long/path/to/newfile

Even with TAB completion, this requires too much typing. Why not reuse the path of the first argument?

1

$ mv some/long/path/to/oldfile !#:1:h/newfile

‘!#’ is a shortcut for “the command-line typed so far” ‘:1’ selects the first argument and ‘:h’ strips off the last component (ie. “oldfile”).

In some cases, you don’t want to rename the file entirely but only change the extension. This can be achieved in a similar fashion:

1

$ mv some/long/path/to/file.c !#:1:r/.cpp

You guessed it: ‘:r’ removes the file extension.

What if you did a mistake and wanted to undo this change? Again, that’s quite easy if you know the trick:

1

$ mv !!:2 !!:1

Which translates to “do another move but swap the arguments from the previous command”.

Sometimes, my fingers get ahead of me and I type ‘vm’ instead of ‘mv’:

1

2

$ vm some/path/to/file some/other/file /tmp

vm: command not found

Of course, you can always edit the last command be pressing ‘Arrow Up’ and change ‘vm’ to ‘mv’, but the following is much easier to type:

1

$ mv !*

‘!*’ is a placeholder for “all arguments of the previous command”.

The word designator that I use the most — by far — is ‘!$’; it expands to the last argument of the last command:

1

2

3

4

5

$ vi ~/bin/myscript.sh # edit a shell script

$ ls -l !$ # show attributes

-rw-rw-r-- 1 ralf ralf 21 Mar 25 10:21 myscript.sh

$ chmod +x !$ # make it executable

$ ./!$ # run it

Many times, people gratuitously reach for the mouse to copy the output of a previous command in order to use it as an argument for another command. Why not use ‘$()’ to capture command output?

If I was asked to name my favorite standard command-line tool, no doubt I would pick ‘xargs‘. Even though it is largely useless by itself, it’s the superglue that allowes you to build powerful command-lines. It takes the output of a command and uses it as arguments for another one.

Here’s an example that uses ‘xargs’ to build a tar archive containing all the C files that contain C++ comments:

In rare cases, when I have to do work that involves complicated selection/filtering, I reach out for TUI (usually ncurses-based) applications like ‘tig‘, ‘vifm‘, or ‘mc‘ that run in the shell and can be fully operated by the keyboard. Nevertheless, I first try to get by with the simpler ‘menucmd‘ tool. Here’s an example that builds a menu from all shell script files found in a directory. If the user selects an item, the specified action (‘cp -t /tmp’) is executed on it.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

$ menucmd.py cp -t /tmp -- $(find scripts/ -name "*.sh")

1 scripts/xz_wrap.sh

2 scripts/mkuboot.sh

3 scripts/depmod.sh

4 scripts/link-vmlinux.sh

5 scripts/gcc-x86_64-has-stack-protector.sh

: :

15 scripts/gcc-x86_32-has-stack-protector.sh

16 scripts/gen_initramfs_list.sh

17 scripts/gcc-goto.sh

18 scripts/headers_install.sh

19 scripts/checksyscalls.sh

cp -t /tmp █

There you go. Even if this bag of tricks is not complete I hope it will serve you well. As always, in order to use a tool efficiently, you have to invest in learning how to use it idiomatically*. But this investment pays nice dividends in the medium-to-long term.

*) What’s the idiomatic way for vi users to underline a headline? 1. Yank the headline (‘yy’). 2. Paste the yanked headline under the exiting headline (‘p’). 3. Select the second headline (‘V’). 4. Replace every character in selected line with an underscore (‘r-‘) — that’s only six keystrokes! Awesome!