Top 10 interactive shell anti-patterns

  • 9 October, 2007

In general, if you're doing a repetitive task by hitting lots of keys, you're doing it wrong. Not only are you wasting time, you're wearing out your keyboard and damaging your wrists and fingers.

Here are the ten most common anti-patterns (actually amelioration patterns) that I commonly see in other people's line editing:

  1. Repeating a command by retyping it

    Press Ctrl-p and Enter, or "!!". Zsh users get "r" as well. You can even get the command n times before, use "!-1". Using the arrow keys is obviously flawed - find out how to unmap them in your shell.

  2. Manually changing back to your previous directory - Give "cd -" a whirl.

  3. Typing a long command (or sequence of commands) excessively often - Obviously wrong, but I see it so often it's worth mentioning. Try using an alias and put it in your shell's startup file. If the command is parameterised, you could construct a simple function. If you are writing software, look into the build systems available for your language - the basics of GNU Make can be mastered within 20 minutes or so, and have many other benefits too.

  4. Opening another shell when the current process blocks - (e.g. You realise the command you started is going to take a lot longer than you thought) Pause the current process with Ctrl+z and then put the job in the background with bg. fg will bring it to the foreground again. Fewer shells mean you stay focused on what you were trying to achieve.

  5. Searching history by grep'ing ~/.bash_history, or Up Up Up ... etc. - Use CTRL+r (or "/" in vi-mode) for search-as-you type. You can immediately run the command by pressing Enter.

  6. Adding a prefix to a command with Up Home $command Space - (e.g. You ran a command that really needed sudo). Try: $prefix !!

  7. Changing a command with Home Right Right Right Right ... BkSpace BkSpace ... ``etc. - (e.g you accidentally ran ``chown instead of chmod) Try: $new_command !*

  8. Retyping significant parts of the previous line

    $ command arg1 /long/path/to/file
    $ command2 arg2 /long/path/to/file
    $ command3 arg1 arg3 /long/path/to/file

    Try using !$ to get the right-most argument on the previous line (you can also use !:2 to get the second argument, etc.):

    $ command arg1 /long/path/to/file
    $ command2 arg2 !$
    $ command3 arg1 arg3 !$
  9. Using Home/End to move to the beginning/end of the line - This is how you completely ruin your wrists. Use Ctrl+a and Ctrl+e.

  10. Hitting tens of keys to make a substitition* - (e.g. you made a trivial mistake in the middle of your previous command). Use ^foo^bar. If you forget the other nine, try this one - stop punishing yourself for making mistakes.

Comments (23)

Regarding #1, 6, 7, and 8:

I disabled shell history in bash using set +H because of the following behavior:

ethan@sundance:~$ echo "Hi!"
bash: !": event not found
ethan@sundance:~$ echo "Hi\!"

In other words, there's no way to get an unescaped exclamation mark in a double-quoted string (at least that I can see). This is a pain if you use svn commit -m "Message!". I can't figure out why this behavior exists; it seems that "\!" ought to give you an exclamation mark, but the bash reference manual says explicitly:


"If enabled, history expansion will be performed unless an `!' appearing in double quotes is escaped using a backslash. The backslash preceding the `!' is not removed."

I would love it if you could tell me why this is a feature and not a bug.

Anyhow, if you do set +H, you can still use readline to navigate your history. 6 is actually the same number of keystrokes without history: Up, Ctrl-A, command, space versus command, space, bang bang. 7 and 8 may be shorter with history expansion, but I would argue that the interactive versions are "easier"; you can see what command you're about to execute before you execute it. Not to say that I don't miss !$, because I do..

And #4 is much less useful if you're running a command which prints a progress bar or something like that to stdout. scp, wget..

However, I need to master #10, and remember to use it more often..


Oct. 9, 2007, 7:46 p.m. #

M-. (or Esc-.) in bash substitutes the last argument of the previous command as well. Hitting it multiple times bumps to the command before the previous, etc.

Oct. 9, 2007, 7:51 p.m. #

@Ethan: Ignoring a couple of trivial patches, I am not the author of any shell. You should it touch with the authors if you want justifications for particular features. (Failing that, contact your distribution's package maintainer.)

``echo "Hi\!" works in Zsh, FWIW``

Oct. 9, 2007, 8:09 p.m. #

For when you *do* need to navigate in a command, don't use the arrow keys unless you really need to move by characters. The following in ~/.inputrc makes Ctrl-left and Ctrl-right move by words:

<code>"\e[1~": beginning-of-line</code>
<code>"\e[4~": end-of-line</code>
<code>"\e[1;5C": forward-word</code>
<code>"\e[1;5D": backward-word</code>
<code>"\e[5C": forward-word</code>
<code>"\e[5D": backward-word</code>
<code>"\e\e[C": forward-word</code>
<code>"\e\e[D": backward-word</code>
<code>$if term=rxvt</code>
<code>"\e[8~": end-of-line</code>
<code>"\eOc": forward-word</code>
<code>"\eOd": backward-word</code>

Oct. 9, 2007, 8:49 p.m. #

Thanks to everyone for your comments, but can you restrict them to anti-patterns you've seen in the wild? I don't particularly want to be flooded by everyone's shell customisations, it's really not as helpful as you think. Thanks.

Oct. 9, 2007, 8:59 p.m. #

Wow, all these useful commands that I never knew, you should make them into a cheatsheet ;)

Oct. 9, 2007, 9 p.m. #

Cool tips. Thanks!

Oct. 9, 2007, 9:03 p.m. #

"Using the arrow keys is obviously flawed"

Why is tapping the up arrow once "obviously flawed" compared to typing "!!" or Ctrl-P?

I'm not being sarcastic. I've only ever used the arrow keys and was quite happy. If there's a better way I'd love to know it.

Oct. 9, 2007, 9:14 p.m. #

"Wow, all these useful commands that I never knew" and which I still don't get!

I love Linux and the shell, but sometimes the barriers to entry and understanding are oh so large for us non-programmers.

For example, number six makes little sense to me. So does number ten, "If you forget the other nine..." meant to me the other nine mistakes discussed in point ten -- just now do i realize that you meant the other nine tips. Anyways, number ten still doesn't make sense to me...

but thanks for those that do!

Oct. 9, 2007, 9:21 p.m. #
Simon Huggins

What's wrong about the arrow keys that they are "obviously flawed"?

Oct. 9, 2007, 11:07 p.m. #
Benoit Thiell

Learned some things today. Now, 'sudo !!' is my best friend. Thanks!

Oct. 10, 2007, 5:54 a.m. #

<blockquote cite="Ethan Glasser-Camp">there’s no way to get an unescaped exclamation mark in a double-quoted string (at least that I can see). This is a pain if you use svn commit -m “Message!”.</blockquote>

The need to shout subversion commit messages does not justify turning of shell history (in my opinion).

<blockquote cite="michael schurter">Why is tapping the up arrow once “obviously flawed” compared to typing “!!” or Ctrl-P?</blockquote>

Because it is all the way on the other side of your keyboard. Moving your hand that far will decrease your typing speed. The same argument applies to the home/end/delete keys.

Oct. 10, 2007, 7:20 a.m. #

IIRC it's zsh only, anyway Alt-q, when typing a long command and you need, for example, to consult the manual or change the working directory...

Oct. 11, 2007, 8:01 a.m. #

You say to use home to get to the front of the line in step 5, then say NOT to use it in step 10.

Also the reason people grep | history is to see the different ways they used a command like, "history | grep mount" will show you what you mounted recently.

Oct. 11, 2007, 8:28 p.m. #

@Mark: Um, wut? Where do I say to use the Home key. And my suggestion re. history does that too.

Oct. 11, 2007, 8:43 p.m. #

How would you repeat the same command with a different argument?
For instance, how would you type:
mkdir foo
mkdir bar

Oct. 12, 2007, 12:58 p.m. #

I'm sorry number 6 and number 9 not 5 and 10...

Oct. 12, 2007, 3:14 p.m. #

@Alexandre: !:0 bar

@Mark: That's the "don't do this" part of number 6.

Oct. 12, 2007, 3:24 p.m. #

"For example, number six makes little sense to me. So does number ten"

I agree that while I already knew of #6, it took me a moment to understand what the author was saying, but in essence it is that say we have the following scenario, you need to edit xorg.conf.
You type:
"nano /etc/X11/xorg.conf"
and hit enter, but instantly remember that you need root privileges to edit the file, so you quit, now the bad way of doing things is to type it all up again, and adding sudo before it (if you are working on a system which uses sudo ofc), the better way would be to type
"sudo !!"
which is the interpreted as sudo [the last executed command] or
"sudo nano /etc/X11/xorg.conf"

As for #10, try the following:
create a folder somewhere in your system (like the desktop) or whatever.
Create an empty file outside the folder.
(when I tested this I had a "sandbox" folder and the file "foo" on the desktop.)
then I tried
"mv foo sndbox"
which of course failed since I misspelled sandbox.
The next command I ran then was
Talk about kick-ass stuff :D

(Of course all of these commands should be without double quotes)

Hope it helped
cheers :)

Oct. 13, 2007, 3:22 p.m. #

The issue with subversion is fixed in an easier manner by using single quotes...

svn ci -m 'This works!'

Oct. 14, 2007, 6:28 p.m. #

Lamby, my bad... Nice article by the way.

Oct. 15, 2007, 4:13 p.m. #

Explaining: Everything the shell (BASH) doesn't substitute anything inside single quotes. Try: echo '$HOME' "$HOME"

Oct. 16, 2007, 2:01 a.m. #

The behavior of not removing the preceding escape character (\) when escaping an ! seems like a serious flaw to me. Definitely a bug-feature. Fortunately you can get around this by assigning a shell variable, eg E, to ! and take advantage of the substitution you're getting when using "" rather than '':

E='!' # no escape necessary for 's
cat /etc/hosts | awk "$E/localhost/{print}" # $E substitution

You could also keep E aliased to 'echo -n !' so that you can use `E` as an uninterpreted ! in the command line:

alias E='echo -n !' # no escape necessary for 's
cat /etc/hosts | awk "`E`/localhost/{print}" # `E` substitution

Both are somewhat ugly, but I like the first way better.


Jan. 3, 2009, 12:27 p.m. #