Even though this is an old question, it seems to me it's a perennial question, and a more general, clearer solution is available than has been suggested so far. Credit where credit is due: I'm not sure I would have come up with it without considering Stéphane Chazelas's mention of the <>
update operator.
Opening a file for update in a Bourne shell is of limited utility. The shell gives you no way to seek on a file, and no way to set its new length (if shorter than the old one). But that's easily remedied, so easily I'm surprised it's not among the standard utilities in /usr/bin
.
This works:
$ grep -n foo T
8:foo
$ (exec 4<>T; grep foo T >&4 && ftruncate 4) && nl T;
1 foo
As does this (hat tip to Stéphane):
$ { grep foo T && ftruncate; } 1<>T && nl T;
1 foo
(I'm using GNU grep. Perhaps something's changed since he wrote his answer.)
Except, you have no /usr/bin/ftruncate. For a couple dozen lines of C, you can, see below. This ftruncate utility truncates an arbitrary file descriptor to an arbitrary length, defaulting to standard output and the current position.
The above command (1st example)
- opens file descriptor 4 on
T
for update. Just as with open(2), opening the file this way positions the current offset at 0.
- grep then processes
T
normally, and the shell redirects its output to T
via descriptor 4.
- ftruncate calls ftruncate(2) on descriptor 4, setting the length to the value of the current offset (exactly where grep left it).
The subshell then exits, closing descriptor 4. Here is ftruncate:
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main( int argc, char *argv[] ) {
off_t i, fd=1, len=0;
off_t *addrs[2] = { &fd, &len };
for( i=0; i < argc-1; i++ ) {
if( sscanf(argv[i+1], "%lu", addrs[i]) < 1 ) {
err(EXIT_FAILURE, "could not parse %s as number", argv[i+1]);
}
}
if( argc < 3 && (len = lseek(fd, 0, SEEK_CUR)) == -1 ) {
err(EXIT_FAILURE, "could not ftell fd %d as number", (int)fd);
}
if( 0 != ftruncate((int)fd, len) ) {
err(EXIT_FAILURE, argc > 1? argv[1] : "stdout");
}
return EXIT_SUCCESS;
}
N.B., ftruncate(2) is nonportable when used in this way. For absolute generality, read the last written byte, reopen the file O_WRONLY, seek, write the byte, and close.
Given that the question is 5 years old, I'm going to say this solution is nonobvious. It takes advantage of exec to open a new descriptor, and the <>
operator, both of which are arcane. I can't think of a standard utility that manipulates an inode by file descriptor. (The syntax could be ftruncate >&4
, but I'm not sure that an improvement.) It's considerably shorter than camh's competent, exploratory answer. It's just a little clearer than Stéphane's, IMO, unless you like Perl more than I do. I hope someone finds it useful.
A different way to do the same thing would be an executable version of lseek(2) that reports the current offset; the output could be used for /usr/bin/truncate, which some Linuxi provide.