Mark Needham

Thoughts on Software Development

Finding ways to use bash command line history shortcuts

with 2 comments

A couple of months ago I wrote about a bunch of command line history shortcuts that Phil had taught me and after recently coming across Peteris Krumins’ bash history cheat sheet I thought it’d be interesting to find some real ways to use them.

A few weeks ago I wrote about a UTF-8 byte order mark (BOM) that I wanted to remove from a file I was working on and I realised this evening that there were some other files with the same problem.

The initial command read like this:

awk '{if(NR==1)sub(/^\xef\xbb\xbf/,"");print}' data/Taxonomy/Products.csv  > data/Taxonomy/Products.csv.bak

The version of the file without the BOM is data/Taxonomy/Products.csv.bak but I wanted it to be data/Taxonomy/Products.csv so I needed to mv it to that location.

By making use of history expansion we can write this as follows:

mv !$ !!:2

!$ represents the last argument which is data/Taxonomy/Products.csv.bak and !!:2 gets the 2nd argument passed to the last command which in this case is data/Taxonomy/Products.csv.

As you’re typing it will expand to the following:

mv data/Taxonomy/Products.csv.bak data/Taxonomy/Products.csv

One of the things that we do quite frequently is look at the nginx configurations and logs of our different applications which involved doing the following:

$ tail -f /var/log/nginx/site-1-access.log
$ tail -f /var/log/nginx/site-2-access.log

or

$ vi /etc/nginx/sites-enabled/site-1-really-long-name-cause-we-can
$ vi /etc/nginx/sites-enabled/site-2-really-long-name-cause-we-can

Everything except for the file name is the same but typing the up arrow to get the previous command and then manually deleting the file name can end up taking longer than just writing out the whole command again if the site name is long.

Ctrl-w deletes the whole path so that doesn’t help us either.

An alternative is the use the ‘h’ modifier which “Removes a trailing pathname component, leaving the head.”

In this case we could do the following:

$ vi /etc/nginx/sites-enabled/site-1-really-long-name-cause-we-can
$ vi !$:h/site-2-really-long-name-cause-we-can

We still have to type out the whole file name and we don’t get any auto complete help which is a bit annoying.

I realised that on my zsh if I type a space after a history expansion command it expands what I’ve typed to the full paths of everything, which is due to the following key binding:

.oh-my-zsh $ grep -rn "magic-space" *
lib/key-bindings.zsh:20:bindkey ' ' magic-space    # also do history expansion on space

We can do the same thing in bash by running the following command:

bind Space:magic-space

Then if I wanted to open that second nginx file I could do the following:

$ vi !$:h # then type a space which will expand it to:
$ vi /etc/nginx/sites-enabled/ # I can then type backspace, then type 'site-2' and tab and open the file

It’s not completely smooth because of the backspace but I think it’s marginally quicker than the other options.

Another one which I mentioned in the first post is the ^original^replacement which will run the previous command but replace the first instance of ‘original’ with ‘replacement’.

With this one it often seems faster to type the up arrow and change what you want manually or retype the command but when doing a grep of a specific folder I think this is faster.

e.g.

$ grep -rn "magic-space" ~/.oh-my-zsh/lib
/Users/mneedham/.oh-my-zsh/lib/key-bindings.zsh:20:bindkey ' ' magic-space    # also do history expansion on space

Let’s say I was intrigued about bindkey and wanted to find all the instances of that.

One way to do that would be to type up and then manually go back along the line using Meta-B until I get to ‘bind-key’ when I can delete that with a few Ctrl-W‘s but in this case the search/replace approach is quicker:

$ ^magic-space^bindkey
grep -rn "bindkey" ~/.oh-my-zsh/lib
/Users/mneedham/.oh-my-zsh/lib/completion.zsh:24:bindkey -M menuselect '^o' accept-and-infer-next-history
/Users/mneedham/.oh-my-zsh/lib/completion.zsh:71:  bindkey "^I" expand-or-complete-with-dots

I’m still looking for other ways to re-use bash history more effectively so let me know any other cool tricks in the comments.

Be Sociable, Share!

Written by Mark Needham

September 19th, 2012 at 7:00 am

Posted in Shell Scripting

Tagged with

  • http://twitter.com/rjhunter Rob Hunter

    Some of Bash’s default bindings might be of interest to you:
      history-expand-line (M-^) expands any !history parts on your current input
      shell-expand-line (M-C-e) expands !history, shell wildcards, variables, aliases and whatever else

    I tend to use the interactive kill-buffer to deal with long-but similar pathnames like the ones you describe — a bit like the modern cut and paste, I guess, but still relatively quick. C-k to kill to end-of-line, C-y to yank it back.

    backward-word-kill (M-Rubout) is handy when unix-word-rubout (C-w) eats too much (like when you just want to get back to a path).

    It’s worth reading over the READLINE section of `man 1 bash` to see the default bindings.

    In case you’re not familiar with the notation, here’s how to press some of those keys on a modern machine:

        M-^ … Alt-Shift-6
        M-Rubout … Alt-Backspace
        M-C-e … Alt-Ctrl-E

    (make sure your Mac terminal is configured to use Option as Meta, otherwise you gotta hit Escape instead)

  • http://www.markhneedham.com/blog Mark Needham

    @twitter-13695272:disqus thanks for the tip about reading the ‘man 1 bash’ page – lots of neat shortcuts going on there that I hadn’t heard about before! Now to remember them when I’m at the shell…