My Byte of Code Blog. Tips and observations on creating software with Objective-C, C/C++, Python, Cocoa and Boost on Mac.

Friday, February 05, 2010

Use vim to generate and execute bash commands

I have been using vim almost exclusively for file editing on any operating system that I use, whether Mac OS X, Linux or Windows. Here is an example how to use vim to build and execute bash commands using vim's regular expressions.

Often I manipulate log and data files. I need to copy a group of files. Each file in the group have date stamp and I need to make copies with different date stamps.

Here is how you can create bash commands to copy group of files from vim.

My directory contains four files and a directory called abc:

> ls
abc
abc.4.0.20100204.body
abc.4.0.20100204.header
abc.4.0.20100204.seqnums
abc.4.0.20100204.session

I need to create a copy of the whole group of files with timestamp of 20100201, bash command looks like this:

mv abc.4.0.20100204.body abc.4.0.20100201.body
...

Load output of external command to vim

In the directory open vim and load up list of files:

:r!ls *.*

The vim window will have the list of files displayed:

abc.4.0.20100204.body
abc.4.0.20100204.header
abc.4.0.20100204.seqnums
abc.4.0.20100204.session
~
~

This is read command :r followed by exclamation mark and command. It executes the external command in shell and inserts its standard input into vim. In this case I want to get a list of files but not the directory abc so I use:

ls *.*

Use regular expressions to prepare shell command

Now I want to generate copy commands for shell. In vim execute substitute command:

:%s/\(\(.*\)204\(.*\)\)$/cp \1 \2201\3/gc

You get asked if you want to execute each substitution, just press y, this is because of the c - confirm flag. This is a substitute command:

<range>s/pattern/replace/options

To summarize it:

  • Execute on the whole file - range %
  • Substitute s
  • Brackets are for remembering matched contents, they get numbered from left to right later referenced in replace
  • Match anything up to 204 (part of time stamp in file name), I want to update the timestamp
  • Match everything after the timestamp
  • Replace the matched line with: cp
  • Followed by the whole original line - \1 references the first bracket that matches everything up to end of the line in the original line.
  • Followed by the first part of filename up to 204 - stored by second group of brackets and reference by \2 in replace part
  • Followed by the string 201 that replaces the original 204 string
  • Followed by the rest of the matched line - \3
  • Command flags:
    • g - global, execute the substitute command repeatedly on the line - not important here because I'm matching the whole line
    • c - confirm before replacement

The vim window now looks like this:

cp abc.4.0.20100204.body abc.4.0.20100201.body
cp abc.4.0.20100204.header abc.4.0.20100201.header
cp abc.4.0.20100204.seqnums abc.4.0.20100201.seqnums
cp abc.4.0.20100204.session abc.4.0.20100201.session
~
~

Execute bash commands from vim

To execute bash commands in the current directory, first highlight the lines with Ctrl-v and down/up to highlight all commands. Now enter : to start typing the rest of the command. This will insert the range '<,'> signifying the highlighted lines. Type the rest of the command:

:'<,'>!bash

This executes commands in highlighted lines in bash. Quit vim and check the directory. You have copied files with 20100204 date stamp to the ones with 20100401:

> ls
abc
abc.4.0.20100201.header
abc.4.0.20100201.session
abc.4.0.20100204.header
abc.4.0.20100204.session
abc.4.0.20100201.body
abc.4.0.20100201.seqnums
abc.4.0.20100204.body
abc.4.0.20100204.seqnums

I have loaded output of external bash command into vim, used vim's regular expressions to generate commands, then executed the commands in bash directly from vim.

No comments:

Post a Comment