The HP-UX Admin Man: Act Locally, vi Globally

Last month we looked at vi’s command filtering ability. This month, let’s take a look at the global feature of vi. Global is actually an ex command, but all UNIX gurus know that. For the rest of us, vi is the visual mode (screen editor) of ex, the line editor.

Because many vi commands can also act on more than a line of text, it might be more accurate to say that all editing that acts on less than a line are done with vi commands.

Anytime a command starts with a colon character, it is an ex command, the colon is not really part of the command. The colon just tells vi to accept up to a new line as an ex editing command.

:G IS FOR GLOBAL

The global command can be spelled out, but since no other commands start with a g, only the g is needed. Because it’s an ex command, we could say the command is :g. From here on, we will not bring up this distinction, but in examples will always put a colon before ex commands.

The global (vi) command accepts a pattern and command as arguments. The format for the command is:

vi/pattern/[commands]

Note that the [ ]’s mean that the commands are optional. Global will perform the command supplied (or print as a default if none is given) against all lines that contain a match for the supplied pattern.

For example, both of these commands do the same thing:

vi/^#/

vi/^#/p

The pattern /^#/ matches lines whose first character is a pound (#) symbol. Both commands will display the matching lines, and a prompt telling you to ‘Hit return to continue,’ meaning remove the display of matching lines and return to vi mode.

We have been using the term pattern, since that is what the documentation uses. In reality, the characters we supply as the pattern have the meanings of regular expressions, or RE for short.

THE USUAL SUSPECTS

Another way to write the syntax for this command is vi/RE/p which looks suspiciously like grep. And in fact, is where that command has its roots.

As an example of supplying a command argument to global, lets use the delete (d) command:

vi/^#/d

The command above reads something like "delete all lines that start with a pound symbol." If you are familiar with ex at all, you would know that almost all ex commands accept a line address or range of line addresses as a prefix. It might seem that the global command already means act on all lines that contain a pattern, thus would not need an address. In reality, this makes the command even more powerful.

You can use standard line addressing symbols to select a range of lines in a file, then use the global command to only act on lines within that range that contain a pattern. Sort of a hierarchical addressing. For example:

:12,/END/g/^#/d

The command above uses a line range (line 12 through the next line that has the string ‘END’ in it) to restrict where the global command will apply. In other words, the command above translates into English as ‘delete all lines from line 12 down to the next ‘END’ string that begin with a pound symbol.’ If you recall my previous column, we showed a way to do exactly the same thing with command filtering (:12,/END/!grep –v '^\#'). But I think that using global is easier, though it is limited to those commands that ex can do.

POUNDING SOME BEERS

As a more complex command argument example, let’s suppose you have some Perl code or a shell script that you are writing in vi. You have used the # symbol to start all comment lines. It’s getting late and after several beers you can’t seem to find the comment lines.

In a moment of inspiration, you decide that if you put a whole line of # signs above each comment line they would be easier to find.

To setup for this, edit in the first line of pound symbols. Lets suppose it was done on line 2 of the file. What we now want to do, is copy line 2 to every location above lines that start with a pound symbol. The ex copy command can do it once. Using the global command allows us to perform that copy to all lines matching the pattern:

:3,$g/^#/ 2 co –1

This reads as: On lines 3 through the end of the file (:3,$), perform a global command against all lines that begin with a pound symbol (g/^#/). The command to be performed on these lines is to copy line number 2 (2 co) to just above (-1) the line currently selected by global. Note that –1 is dash one, meaning one line above the current line, +3 would mean three lines below the current line. We could have written just above the current line this way: .-1, since period means current line.

THE POWER OF TWO

Next let’s try a global operation using two commands. Presume we want to delete all lines that match a pattern, but we also want to browse them in case there was something we needed. The command vi/^#/p would print the lines. Since we want to do two commands (delete and print), we must separate the two commands (confusingly) with a pipe symbol (don't think of a shell pipe, in this case the pipe just means the end of one command, there is nothing being "piped"):

vi/^#/p|d

This command will print all lines that begin with a pound symbol, and also delete them. If you notice by looking at the print output that some should not have been deleted, use :u to undo the delete.

Another example of performing multiple commands might be if we want to extract certain lines from a file into another, and delete those lines in the original file. Here is a way to do that with one command:

vi/Texas/.w >>texans|d

The command above will: for eachline that has "Texas" on it (vi/Texas/), write that line to a file named "texans" (.w>>texans), then delete each of those lines (|d). Note that the period before the write means current line, so .w means write the current line, just like 12w means write line 12. Instead of deleting the lines that were written to the "texans" file, we could have moved those lines to the end of the current file:

vi/Texas/.w >>texans|.mo$

Note that sometimes ex allows spaces, and sometimes gets confused by them. The example above works best without any space characters in it.

:V IS FOR VICTORY?

As a final note, there is another command supported by most versions of vi named :v. I don’t know what it stands for, but :v acts on lines that are NOT matched by the supplied pattern. This is similar in operation to the –v option of grep. This could also be accomplished by inverting pattern lines with the ! symbol immediately following the vi. For example:

vi/^#/l

The command above lists (that is an ell, not a one) all lines that start with a pound symbol (show end of line with a dollar symbol, and make tabs visible).

:v/^#/l

vi!/^#/l

Both of the above commands will list all lines that do not start with a pound symbol.

I never expected the global command to take up a whole column, but since it has, we will save the rest of vi for a future column. Unless I get plenty of e-mail.

Must Read Articles