The HP-UX Admin Man: If At First, Then vi vi Again

On a whim, Fred waxes on the virtues of vi once again. So, take control with command filtering.

It’s always a shock to run into an old friend and find that some prominent personality quirk has disappeared. So, I was surprised when it happened to me. I met an old friend last week. And after a short conversation, I was told that I had changed. The answer to my question as "to how?" went this way: "you haven’t raved about the virtues of vi yet, and we’ve been talking for 10 minutes already."

Right. Ok.

Has it has been that long? Just for that, here are more vi tidbits…

Where do I start? How about some command filtering?

Typing the date into a file is a waste of time. If you are using vi to edit the file, you can easily insert the output of the date command with a command filter. Give it a try.

Open up vi, on a file, then move the cursor to an empty line, and while in command mode, type:

!!date

You should see the line the cursor is on change to contain the current date and time.

If that didn’t happen, you were probably watching the bottom of the window and hit three exclamation marks, instead of two. The first exclamation mark does not appear, but the second one does. So if you hit three, you see only two. You should see only one.

The only problem with this technique is that the command output replaces the current line of text. That is why I said to go to an empty line. We will get around that later.

If you want to insert the output of a command into the middle of a line, there is no simple solution. The easiest method is probably to insert a couple of carriage returns in the middle of the line, making it into three lines. Then perform the !! with the cursor on the middle (empty) line and use the J command to join the lines afterwards.

The reason the current line is replaced is that the !! command actually sends the current line to be filtered by the command. Actually, the vi command is just !, and it accepts a movement command as an argument. As with most commands in vi, if they repeated, it acts on the current line. Therefore, !! sends the current line to the command specified.

For example, d2w deletes two words, but dd deletes the current line. Similarly, !10w would send the next 10 words through a filter command, and !! sends the current line. In the !!date example above, the current line disappeared because the date command does not accept piped input data.

Let’s try a command that does accept input to do true command filtering: Suppose we have a section of a file that looks like this:

 

1

2

3

4

If we put the cursor in vi on the 1, in command mode (hit <esc>), then issue the following key strokes:

!4!pr -t -2

The 4 lines (!4! or even !4j) would be cut from the file and piped to the pr command, which we told to format into 2 columns (-2), without trailers (-t), so we would end up with:

1 3

2 4

There are many uses for command filtering, which basically allows you to perform a command against selected lines of a file, rather than the entire file. For example, if we now wanted to take just these two lines of the file, and swap the order of the columns, just place the cursor on the first of the two lines, and issue:

!2jawk ’{print $2,$1}’

If you mess up a command, the lines you sent to the command are deleted and an error message often replaces them. Don’t panic! To get the lines back, issue the two key strokes :u for an ex undo command (vi is built on top of ex). The vi undo command (u) will not do it, only the ex undo (:u).

For another example, suppose you had a column of data in a spreadsheet-looking file that you wanted to remove. The UNIX command cut can remove either fields or character columns from a file. Because you can use ! to send any number of lines from a file to any UNIX command, we have an obvious solution. Let’s suppose we wanted to remove columns 20 through 28 from the entire file.

First place the cursor on the top line (1G is the vi command to move the cursor to the top line). Next, we need to issue the ! command and address the entire file. The vi command G will move the cursor to the last line of the file, thus: !G means send the entire file to a command. The cut command: cut -c1-19,29- will remove columns 20-28, so the whole key sequence would be:

1G!Gcut -c1-19,29-

Because any movement commands can be used following the !, you can use things like: !) to send one sentence; or !} to send from here to the end of this paragraph to a UNIX command. Note that you must send at least one line or the command will fail.

CROSSING BOUNDARIES

This means you must be careful when trying to send a number of words to a command. You must select enough words to cross a line boundary. Typically it is easier to work only with lines of text.

As we have mentioned, with vi there are both vi and ex commands available. ! is a vi command, but when you precede the ! with a colon, you are issuing an ex command (:!).

The :! command is similar to !, but instead of sending text to the command and inserting the output, it just runs the command and shows you the output. For example, the key strokes :!date makes the date appear, and instructs you to hit the return key to get back to the editor, leaving the file untouched.

The ex command :r accepts a file argument and inserts that file below the current line. The interesting part is that the file argument can be preceded by a !, which means that it’s a command instead of a file to be read.

When you do that, the output of the command is inserted in the file below the current line. This means that you do not have to issue the command from an empty line, like you would with the vi! command. For example:

That is one reason to use :r! instead of !.

A COMMAND PERFORMANCE

Another way to perform command filtering is to use the ex command :!, but to put a line address before the !. If you precede the ! with an address, the lines addressed are removed from the file, and sent through the command. For example:

:10!col -b

Would remove line 10 from the file, send it to the col -b command (which would remove any backspace characters) and the output is placed back into line 10 of the file.

You can use any line addresses that ex understands, such as ranges:

12,38!sort -n

The above key sequence would send lines 12 through 38 to the sort command for numerical ordering and place the result back into the file. We can also use line addressing symbols:

.,$!pr -i

This would send from the current line (.) to the end of the file ($) to the pr command to have multiple spaces replaced with tab characters. You can also use searches as addresses:

12,/END/!grep -v ’^\#’

Sends line 12 through the next occurrence of the string END (/END/) to the grep command, returning only those lines that do not (-v) begin with a # character (^\#). Note that since # has special meaning to the ex editor (alternate filename), we had to escape it (’^\#’) in the command.

I suppose we could have used the ex command :g for this task, but we’ll save that for next time.

Right now, I feel a need to spout on about the virtues of vi.