Questions regarding this column should be sent to the author at ray@cse.ucsc.edu.
vi
Question: I switched from a mainframe to a Unix
system four years ago and have gradually come to feel comfortable
in my new environment. However, there are two mainframe editor
features that I have not yet learned how to perform with
vi
. Can they be done?
The first feature is excluding lines. With the mainframe editor, I could look at and operate on separate portions of a file without splitting the screen and without bothering with line numbers simply by excluding a block of lines from the display.
The second feature was columnwise operation. I'd like to be
able to operate on a set of characters only if they occur in
certain columns of the text. I know I can do this with
awk
or sed
, but I'd like to be able to
do so with vi
.
Answer: While I have been using vi
for
at least 10 years now, there are still many things I don't know
(nor care to know :-) about it. Thus, to get a definitive answer
to your question, I contacted Walter Zintz, UnixWorld Online's
Book Review Editor and frequent contributor. Walter knows just
about all there is to know about vi
. He provided
invaluable help in preparing this answer:
The way to exclude lines from an edit session is to temporarily delete them from the file. Then, when you are done with your edits, return them to their original location.
The temporary deletes are done by ``deleting into a buffer.''
The vi
editor provides buffers identified by single
letters where you can store text during an editing session. To
delete a block of lines into a named buffer, precede your
deletion command with a double quote then the letter designation,
as in "a
, finally suffix +0
. For
instance, to delete five lines into buffer ``a'' enter:
"a5dd
. The +0
suffix grabs the entire
line at the start and end of the block.
Once you've deleted the text into a buffer, mark the line just
above the deletion with the same letter--by typing ``m'' followed
by the letter--so you will remember where the deleted text should
reappear. Then, when you've finished your edits, just return to
the marked line--by typing a single quote then the letter--and
restore the deleted text block by typing "ap
.
Incidentally, the deleted text block is safe inside a letter-named buffer, even switching to edit another file won't lose it. Of course, you'll lose the buffer contents if you exit the editor.
Columnwise editing can be done in several ways. To move to, say, the 27th character position type the digits ``27'' followed by a vertical bar while in visual-command mode. This notation will also work as an address for editing commands; that is, typing ``d27|'' will delete the characters from the cursor position to that 27th character position, whether the direction be forward or backward.
To replace ``25'' with ``39'' if and only if the digits ``25'' are in the 7th and 8th character positions on the line, type:
s/^\(......\)25/\139followed by a return. The six dots are metacharacters matching any six characters at the beginning of the line, and the
\1
in the replacement pattern tells vi
to insert whatever was found inside the first \( \)
bracketed regular expression sequence, that is, the actual
characters found, not the six dots.
If you know exactly how many characters are in the line, you can save the trouble and hazard of typing long strings of dots when you are working on columns near the right-hand edge. To make the above substitution in columns 55 and 56 of a 60 column line, type the following then a return:
s/25\(....\)$/39\1
To edit columns of figures or text found in tables, then use ``ex'' mode ``depends.'' To change ``yes'' to ``no'' only in the third column, (using spaces to separate columns) type:
s/^\( *[^ ][^ ]* *[^ ][^ ]* *\)yes/\1 no
This only works if there are no space characters within any column. (The space before the ``no'' keeps the change from moving the subsequent column's entries one space to the left.)
If there may be one or more single spaces within a column (for instance, the column contains phrases with spaces between the words), and there are always at least two space characters separating one column from another horizontally, then it's only necessary to change the single spaces (within column entries) to some other character before editing. A command such as:
s/\([^ ]\) \([^ ]\)/\1+\2/g/will do this. Be sure to choose a substituted character that does not appear as itself anywhere in the text your editing. Next, do your editing as in my previous comments, then change back all the space-substitute characters to spaces.
If the edits you are performing are repetitive, you may wish
to use macros instead of inventing and typing a new fancy command
for each set of changes. Macros can be defined with the
editor's map
command. For instance,
to define a macro named Q
to be the command to
delete five lines, enter from visual-command mode:
:map Q 5dd
From that time forward, whenever you type a capital ``Q'' in
visual-command mode you delete five lines beginning with the line where
the cursor is positioned. Of course, you can still enter a
capital ``Q'' while entering text because that's performed in
text-entry mode, not visual-command mode. To make a macro
definition permanent across subsequent invocations of the editor,
you'll need to place its definition in the vi
initialization file, .exrc
.
Another convenient way to work with text is to mark it with
the m
command. Type ``m'' followed by a letter to
specify a mark. So typing ``ma'' marks the current line as
``a.'' There are two ways to return to the ``a'' line while in
visual-command mode: use the 'a
command to return to
the first non-whitespace character, but use `a
to
bring the cursor to the same character the cursor was positioned
when the line was marked, anywhere on the line.
Feedback:Your remarks on the year 2000 in a previous column (May, 1994 issue of Open Computing) inspired me to check the behavior of some programs with the clock set to 23:59 hours--a minute prior to midnight--on February 28, 2000.
There is quite a lot of disagreement in the literature that the year 2000 is in fact a leap year. Some sources stick to the 4/100/400 rule, others add the ``divisible by 2000'' as yet another exception to the list of exceptions to the rule.
Of course, the software also disagrees on this issue (it was written by humans after all). An interesting inconsistency can be found among SCO Unix software modules.
On SCO, the cal
utility thinks 2000 is a leap
year (as you showed in your column). On the other hand,
date
doesn't think so. I set the clock to Feb 28,
2000 23:59 hours, waited a minute and typed ``date'' a second
time: date
reported that it's now March 1!
I'm definitely taking the day after Feb 28, 2000 off!
Kees Hendrikse / Enschede, The NetherlandsQuestion: When I change the permissions of my terminal
(chmod 000 `tty`
) I had thought that I would not be
able to write to my screen because I no longer have write
permission. Not so! Even though the ls -l
command
output shows no access permission, I can type (read the keyboard)
and the characters are echoed (written) to the screen. So why
can I type characters on the screen even though I don't have
permission?
Answer: It appears we have a paradox here. How can you write to a device after you remove its write permission? Yet, everything makes sense, once you understand how open files are divorced from their disk-based counterpart.
File permissions are checked only when a process tries to open the disk-based file. Read permission is checked when you open for input, write permission when you open for output or append. These permissions work the same for your terminal because it's accessed through a disk-based (device) file.
After a file has been opened in the requested mode, the
connection between the process and the file is noted by the
process-table entry in the kernel. In fact, the file descriptor
returned by the open()
system call is an index into
an array of open-file descriptors in the process-table entry for
the process that opened the file. Most important for our
discussion: the permissions of the disk-based file aren't
consulted again.
The terminal file was opened by the program that monitors the
terminal port for a log-in request, generally by
getty
, ttymon
, or possibly some other
terminal-monitoring program. This open file is inherited by your
log-in shell. Any changes to the access permissions on your
already-opened terminal will have no effect on your ability to
read from or write to it.
Your question reminds me of a related phenomenon: creating an
``invisible file'' that no one else can list--with
ls
--much less access. You open a file then erase it
from the file system. You can still access the file because the
program that opened the file refers to it through the file
descriptor, not the file name on disk. This approach is
frequently used for temporary files that you don't want accessed
or overwritten by other processes. I've written a demonstration
program to illustrate:
#include <stdio.h> main() { FILE *out; /* pointer to file */ char str[100]; /* buffer */ out = fopen("hidden", "w+"); /* open for appending */ printf("Before file 'hidden' removed:\n\n"); system("ls -C"); /* check that file is there */ system("rm hidden"); /* remove file from directory */ printf("\nAfter file 'hidden' removed:\n\n"); system("ls -C"); /* verify file is gone */ /* write into file */ fprintf(out, "This is a line written to an invisible file\n"); fseek(out, 0L, 0); /* move to beginning of file */ fgets(str, 100, out); /* read line just written to file */ printf("\n%s", str); /* print out line just read */ exit(0); /* file deallocated upon exit */ }