
Questions regarding this column should be sent to the author
at ray@cse.ucsc.edu.
VInsight, Update 2000, Permissions on Open Files
By Ray Swartz
I've Something In My 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.
Steve Palmer / Norristown, Pennsylvania
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. For instance, to delete five lines
into buffer ``a'' enter: "a5dd.
If you use a search pattern--which begins and ends with either
the slash (/) or the question mark (?)
character--to delete, add the characters +0 at the
end of the pattern. For example,
"ad/Chapter 2/+0
to delete through the next line that has the string ``Chapter 2''
in it. 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. As with most visual mode addresses, the
deletion will include the character the cursor is on if it is at
the start of the string to be deleted, but stop just short of it
if at is at the end of that string. The same is true of the
character in the column you've designated by number.
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/\139
followed 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, again use
``ex'' mode substitutions. To change ``yes'' to ``no'' only in
the third column, (when your table uses spaces to separate
columns) type:
:s/^\( *[^ ][^ ]* *[^ ][^ ]* *\)yes/\1 no
followed by a return. This only works if there are no space
characters within any column. (The space before the ``no'' keeps
the change from moving the subsequent columns' 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
followed by a return will change each single space to a plus
sign (+). Be sure to choose a substituted character
that does not appear as itself anywhere in the text you're
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
V to be the command to delete five lines, enter from
visual-command mode (followed by a return, of course):
:map V 5dd
From that time forward, whenever you type a capital ``V'' 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 ``V'' 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.
Dates and Confused
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 Netherlands
The More Things Change, The More They Stay The Same
Question: 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?
P.Ramadurai / Bangalore, India
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 */
}
|