July 5th, 2006

Bash history substitution

Anyone who has had an introductory unix course should know about the bash shell’s “history” command, which gives you a numbered list of commands that you’ve run previously.

You can execute one of those commands again by doing:
$ ![number] (for a particular command you’ve seen on the history list)
or
$ !! (for the previous command/last command in the history list)

One thing that I didn’t learn in any class, but did find out about from a co-worker, several years ago, was history substitution. It’s easy to run the previous command with minor changes, by using a caret-delimited substitution expression. Here’s a very simple example:

view a file:
$ cat spugbrap.txt

then, edit that same file:
$ ^cat^vim^

that gets expanded to (and executed as):
$ vim spugbrap.txt

I’ve used this feature countless times since learning about it, but it always suffered from a limitation: If the pattern you’re trying to match occurs multiple times in the previous commandline, this subtitution method only replaces the first occurrance of it. So, I recently decided to find out how to substitute multiple occurances, since I was sure there had to be an easy way. Here’s one way I found:

watch a couple of tomcat log files continuously:
$ tail -F ~/tomcat/logs/stdout_20060704.log ~/tomcat/logs/stderr_20060704.log

The next day, the log file names are different, because they’re date-based. so I want to change 20060704 to 20060705, and it needs to happen twice because the date occurs twice in that commandline. No problem! Assuming the previously executed command was the “tail” commandline, above, simply enter this:
$ !!:gs/20060704/20060705/

that gets expanded to (and executed as):
$ tail -F ~/tomcat/logs/stdout_20060705.log ~/tomcat/logs/stderr_20060705.log

What if my previous command was long, and I need to make multiple substitutions with multiple strings?

previous command:
$ tail -F stdout_20060704.log stderr_20060704.log host-manager.2006-07-04.log catalina.2006-07-04.log admin.2006-07-04.log localhost.2006-07-04.log manager.2006-07-04.log jakarta_service_20060704.log

to change the dates, which occur in two formats:
$ !!:gs/20060704/20060705/:gs/2006-07-04/2006-07-05/

If the command I want to run (with substitutions) was not the previous command, but some other command that appears in the numbered list from running ‘history’, put the history line number between the exclamation point and the colon:
$ !123:gs/oldstr/replacementstr/

For more information about this, and other bash history manipulation capabilities, check out:
Bash Features - Using History Interactively

2 Responses to “Bash history substitution”

  1. ClintJCL Says:

    gah!

    I’m happy with up-arrow,enter.

    It seems that the thought required to do those is almost more work. I would just do “cat[uparrow][ctrl-left a cpl times][changes 4 to 5]”…

    That’s a neat trick though.

    g for global change (like perl), but what is the s for?

  2. spugbrap Says:

    I often do the type of previous command editing that you described, clint.. but in this particular case, there are actually about 10 date-based filenames on the commandline, and changing them all manually got tedious.

    The ’s’ is for substitution. :)
    “s/old/new/
    Substitute new for the first occurrence of old in the event line. […]” (from the bash reference page mentioned at the bottom of the original post).

    But yeah, the ‘g’ was crucial for doing what I needed to do.. otherwise, the method I already knew about (^old^new^) would have sufficed.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>