vi Buffers

vi Buffers

Remember when we first starting talking about searching, I mentioned that the expression you were looking for was held in a buffer. Also, whatever was matched by /[Bb][Oo][Aa][Tt] can be held in a buffer. We can then use that buffer as part of the replacement expression. For example, if we wanted to replace every occurrence of “UNIX” with “Linux,” we could do it like this:

:%s/UNIX/Linux/g

The scope of this command is defined by the %, the shortcut way of referring to the entire text. Or, you could first save “UNIX” into a buffer, then use it in the replacement expression. To enclose something in a buffer, we enclose it within a matching pair of back slashes \( and \) to define the extent of a buffer. You can even have multiple pairs that define the extent of multiple buffers. These are reference by \#, where # is the number of the buffer.

In this example

:%s/\(UNIX\)/Linux \1/g

the text “UNIX,” is placed into the first buffer. You then reference this buffer with \1 to tell vi to plug in the contents of the first buffer. Because the entire search pattern is the same as the pattern buffer, you could also have written it like this

:%s/\(UNIX\)/Linux &/g

in which the ampersand represents the entire search pattern.

This obviously doesn’t save much typing. In fact, in this example, it requires more typing to save “UNIX” into the buffer and then use it. However, if what you wanted to save was longer, you would save time. You also save time if you want to use the buffer twice. For example, assume you have a file with a list of other files, some of them C language source files. All of them end in .c. You now want to change just the names of the C files so that the ending is “old” instead of .c. To do this, insert mv at the beginning of each line as well as produce two copies of the file name: one with .c and one with .old. You could do it like this:

:%s/^\(.*\)\.c/mv \1.c \1.old/g

In English, this line says:

  • For every line (%)
  • substitute (s)
  • for the pattern starting at the beginning of the line (^), consisting of any number of characters ( \(.*\) ) (placing this pattern into buffer #1) followed by .c
  • and use the pattern mv, followed by the contents of buffer #1 (\1), followed by a .c, which is again followed by the contents of buffer #1, (\1) followed by .old
  • and do this for every line (g), (i.e., globally)

Now each line is of the form

mv file.c file.old

Note the slash preceeding the dot in the expression “\.c”. The slash “protects” the dot from being interpreted as the metacharacter for “any character”. Instead, you want to search for a literal dot, so you need to protect it.

We can now change the permissions to make this a shell script and execute it. We would then move all the files as described above.

Using numbers like this is useful if there is more that one search pattern that you want to process. For example, assume that we have a three-column table for which we want to change the order of the columns. For simplicity’s sake, lets also assume that each column is separated by a space so as not to make the search pattern too complicated.

Before we start, we need to introduce a new concept to vi, but one that you have seen before: [ ]. Like the shell, the square bracket pair ([ ]) of vi is used to limit sets of characters. Inside of the brackets, the caret (^) takes on a new meaning. Rather than indicating the beginning of a line, here it negates the character we are searching for. So we could type

%s/\([^ ]*\)\([^ ]*\)\([^ ]*\)/\3 \1 \2/g

Here we have three regular expressions, all referring to the same thing: \([^ ]*\). As we discussed above, the slash pair \( and \) delimits each of the buffers, so everything inside is the search pattern. Here, we are searching for [^ ]*, which is any number of matches to the set enclosed within the brackets. Because the brackets limit a set, the set is ^, followed by a space. Because the ^ indicates negation, we are placing any number of characters that is not a space into the buffer. In the replacement pattern, we are telling vi to print pattern3, a space, pattern1, another space, then pattern2.

In the first two instances, we followed the pattern with a space. As a result, those spaces were not saved into any of the buffers. We did this because we may have wanted to define our column separator differently. Here we just used another space.

I have often had occasion to want to use the pattern buffers more than once. Because they are not cleared after each use, you can use them as many times as you want. Using the example above, if we change it to

%s/\([^ ]*\)\([^ ]*\) >\([^ ]*\)/\3 \1 \2 \1/g

we would get pattern3, then pattern1, then pattern2, and at the end, pattern1 again.

Believe it or not, there are still more buffers. In fact, there are dozens that we haven’t touched on. The first set is the numbered buffers, which are numbered 1-9. These are used when we delete text and they behave like a stack. That is, the first time we delete something, say a word, it is placed in buffer number 1. We next delete a line that is placed in buffer 1 and the word that was in buffer 1 is placed in buffer 2. Once all the numbered buffers all full, any new deletions push the oldest ones out the bottom of the stack and are no longer available.

To access these buffers, we first tell vi that we want to use one of the buffers by pressing the double-quote (“). Next, we specify then the number of the buffer, say 6, then we type either p or P to put it, as in “6p. When you delete text and then do a put without specifying any buffer, it automatically comes from buffer 1.

There are some other buffers, in fact, 26 of them, that you can use by name. These are the named buffers. If you can’t figure out what their names are, think about how many of them there are (26). With these buffers, we can intentionally and specifically place something into a particular buffer. First, we say which buffer we want by preceding its name with a double-quote (“); for example, “f. This says that we want to place some text in the named buffer f. Then, we place the data in the buffer, for example, by deleting an entire line with dd or by deleting two words with d2w. We can later put the contents of that buffer with “fp. Until we place something new in that buffer, it will contain what we originally deleted.

If you want to put something into a buffer without having to delete it, you can. You do this by “yanking it.” To yank an entire line, you could done one of several things. First, there is yy. Next, Y. Then, you could use y, followed by a movement commands, as in y-4, which would yank the next four lines (including the current one), or y/expression, which would yank everything from your current position up to and including expression.

To place yanked data into a named buffer (rather than the default buffer, buffer number 1), it is the same procedure as when you delete. For example, to yank the next 12 lines into named buffer h, we would do “h12yy. Now those 12 lines are available to us. Keep in mind that we do not have to store full lines. Inputting “h12yw will put the next 12 words into buffer h.

Some of the more observant readers might have noticed that because there are 26 letters and each has both an upper- and lowercase, we could have 52 named buffers. Well, up to now, the uppercase letters did something different. If uppercase letters were used to designate different buffers, then the pattern would be compromised. Have no fear, it is.

Instead of being different buffers than their lowercase brethren, the uppercase letters are the same buffer. The difference is that yanking or deleting something into an uppercase buffer appends the contents rather that overwriting them.

You can also have vi keep track of up to 26 different places with the file you are editing. These functions are just like bookmarks in word processors. (Pop quiz: If there 26 of them, what are their names?)

To mark a spot, move to that place in the file, type m for mark (what else?), then a single back quote (`), followed by the letter you want to use for this bookmark. To go back to that spot, press the back quote (`), followed by the appropriate letter. So, to assign a bookmark q to a particular spot, you would enter `q. Keep in mind that reloading the current file or editing a new one makes you lose the bookmarks.

Note that with newer version of vi (particularly vim) you don’t press the backquote to set the mark, just m followed by the appropriate letter.