By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
424,972 Members | 1,115 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 424,972 IT Pros & Developers. It's quick & easy.

Insert characters into a text stream.

P: n/a
Hi all,

Is there a standard library function to insert an array of characters
at a particular point in a text stream without overwriting the existing
content, such that the following data in appropriately moved further
down? From a cursory search of the libc documentation I can't find such
a function. Will I have to write it myself?

Thanks.

Aug 24 '06 #1
Share this Question
Share on Google+
9 Replies


P: n/a
"anachronic_individual" <an******************@gmail.comwrote:
Is there a standard library function to insert an array of characters
at a particular point in a text stream without overwriting the existing
content, such that the following data in appropriately moved further
down?
<http://c-faq.com/osdep/insdelrec.html>.

Richard
Aug 24 '06 #2

P: n/a
Richard Bos wrote:
"anachronic_individual" <an******************@gmail.comwrote:
Is there a standard library function to insert an array of characters
at a particular point in a text stream without overwriting the existing
content, such that the following data in appropriately moved further
down?

<http://c-faq.com/osdep/insdelrec.html>.
Thanks for the pointer. Next time I check the FAQ before I post.

Nevertheless I went ahead and wrote a function to insert arbitrary text
into a text stream. It compiles with gcc -W -Wall -std=c99 -pedantic
with one warning about comparision between a signed and unsigned
object. It also appears to work as intended.

I'll be greatful if the group can review the code for possible errors
or non-portable assumptions or anything else. I admit the code is not
elegant, but this is actually my third try, and I was getting a little
desperate. The code follows:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>

#define BUFFER_SIZE 512
int s_insert(FILE *s, char *is, size_t isl, char *buf, size_t bufl);

int main(void) {
FILE *fp;
char *buffer, *ins;
char insstr[] = { 'I', 'n', 's', 'e', 'r', 't', '!' };
char *base_text = "This is the\nbase text in\nthe file.\n";
char *testfile = "s_insert_t.txt";
char *openmode = "w+";
size_t isl, bufl;
int rv, ct;

/* initialise variables */
ins = insstr;
isl = sizeof(insstr);
bufl = BUFFER_SIZE;
printf("isl = %u\tbufl = %u\n", isl, bufl);

/* allocate buffer */
buffer = malloc(bufl);
if(buffer == NULL) { puts("malloc() failed."); return EXIT_FAILURE; }

/* open test file and write the base text to it */
fp = fopen(testfile, openmode);
if(fp == NULL) { puts("fopen() failed."); return EXIT_FAILURE; }
else {
if(fputs(base_text, fp) == EOF) {
puts("fputs() failed.");
exit(EXIT_FAILURE);
}
else {
if(fflush(fp) == EOF) {
puts("fflush() failed.");
exit(EXIT_FAILURE);
}
if(fseek(fp, 0, SEEK_SET) != 0) {
puts("fseek() failed.");
exit(EXIT_FAILURE);
}
}
}

/* display the file contents & string to be inserted */
puts("File before insert:");
while((rv = fgetc(fp)) != EOF)
putc(rv, stdout);
putc('\n', stdout);
if(fseek(fp, 0, SEEK_SET) != 0) {
puts("fseek() failed.");
exit(EXIT_FAILURE);
}
puts("\nText to be inserted:");
for(rv = 0; rv < isl; rv++)
putc(ins[rv], stdout);
putc('\n', stdout);

/* insert the text and display the new file contents */
for(ct = 0; ct < 10; ct++) {
rv = s_insert(fp, ins, isl, buffer, bufl);
if(rv == 0) {
puts("s_insert() returned zero.");
exit(EXIT_FAILURE);
}
else {
if(fflush(fp) == EOF) {
puts("fflush failed.");
exit(EXIT_FAILURE);
}
if(fseek(fp, 0, SEEK_SET) != 0) {
puts("fseek() failed.");
exit(EXIT_FAILURE);
}
puts("File after insert:");
while((rv = fgetc(fp)) != EOF)
putc(rv, stdout);
putc('\n', stdout);
if(fseek(fp, 0, SEEK_SET) != 0) {
puts("fseek() failed.");
exit(EXIT_FAILURE);
}
}
}
if(fclose(fp) == EOF) {
puts("fclose() failed.");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}

int s_insert(FILE *s, char *is, size_t isl, char *buf, size_t bufl) {
char *sbuf;
int cc, ss, bufi, sbufi;
size_t bufc, sbufc, halfb, cnt;
fpos_t pos0, pos1, *crpos, *cwpos;

/* initialise variables */
ss = 1;
sbufi = 1;
sbufc = isl;
halfb = bufl / 2;
sbuf = buf + halfb;
bufi = 0;
bufc = 0;
crpos = &pos0;
cwpos = &pos1;

/* get current file position */
if(fgetpos(s, crpos) != 0 || fgetpos(s, cwpos) != 0)
return 0;

/* copy argument to be inserted into secondary buffer */
memcpy(sbuf, is, sbufc);

/* main insert loop */
for( ;; ) {
/* if primary buffer is empty and secondary buffer is
* not, copy secondary buffer to primary buffer...
*/
if(bufi == 0 && sbufi == 1) {
memcpy(buf, sbuf, sbufc);
bufi = 1;
bufc = sbufc;
sbufi = 0;
sbufc = 0;
}
else
break;

/* try to fill secondary buffer, if empty, from the
* current reading position of stream, and update the
* former...
*/
if(sbufi == 0 && ss == 1) {
if(fsetpos(s, crpos) != 0)
return 0;
for(sbufc = 0; sbufc < halfb; sbufc++) {
cc = getc(s);
if(cc == EOF) {
if(ferror(s))
return 0;
else {
ss = 0;
if(sbufc == 0)
sbufi = 0;
else
sbufi = 1;
break;
}
}
else {
sbuf[sbufc] = cc;
sbufi = 1;
}
}
/* update stream reading pos. if not at EOF */
if(ss != 0) {
if(fgetpos(s, crpos) != 0)
return 0;
}
}

/* if primary buffer is not empty, write it to stream
* at latter's current writing offset and update it...
*/
if(bufi == 1) {
if(fsetpos(s, cwpos) != 0)
return 0;
for(cnt = 0; cnt < bufc; cnt++) {
cc = putc(buf[cnt], s);
if(cc == EOF)
return 0;
}

if(fgetpos(s, cwpos) != 0)
return 0;
else {
bufi = 0;
bufc = 0;
}
}
}

return 1;
}

Aug 24 '06 #3

P: n/a
anachronic_individual wrote:
>
Is there a standard library function to insert an array of
characters at a particular point in a text stream without
overwriting the existing content, such that the following data
in appropriately moved further down? From a cursory search of
the libc documentation I can't find such a function. Will I
have to write it myself?
There is no 'existing content' in a text stream, it is whatever you
write to it (for output). For input it is whatever it is.

All you can do is copy it, during which you can insert or delete
whatever you wish.

--
Chuck F (cb********@yahoo.com) (cb********@maineline.net)
Available for consulting/temporary embedded and systems.
<http://cbfalconer.home.att.netUSE maineline address!

Aug 24 '06 #4

P: n/a
Is there a standard library function to insert an array of characters
at a particular point in a text stream without overwriting the existing
content, such that the following data in appropriately moved further
down?

<http://c-faq.com/osdep/insdelrec.html>.

Thanks for the pointer. Next time I check the FAQ before I post.

Nevertheless I went ahead and wrote a function to insert arbitrary text
into a text stream. It compiles with gcc -W -Wall -std=c99 -pedantic
with one warning about comparision between a signed and unsigned
object. It also appears to work as intended.
I expect the performance will suck horribly when it is used repeatedly
to insert stuff in the first half of terabyte files. There just
isn't a good way to do this. Unless you've got unusual OS support
for this (e.g. a file structure of a list of VARIABLE-LENGTH records,
which makes seeking potentially expensive), you're stuck with at
least re-writing the file from the point of modification to the end
of the file.

If you want to be safe from manual interruption not screwing up the
file, you pretty much have to copy the whole file (making
insertions/changes as you go), which is why it's generally a good
idea to do all the edits in ONE pass. Then you rename it back to
the original name.
Aug 24 '06 #5

P: n/a
"anachronic_individual" <an******************@gmail.comwrote:
# Hi all,
#
# Is there a standard library function to insert an array of characters

No. The system I/O on some systems provide this capability, but
others do not. Standard C being a kind of greatest common divisor
does not include it.

--
SM Ryan http://www.rawbw.com/~wyrmwif/
This is one wacky game show.
Aug 24 '06 #6

P: n/a
anachronic_individual wrote:
Is there a standard library function to insert an array of characters
at a particular point in a text stream without overwriting the existing
content, such that the following data in appropriately moved further
down? From a cursory search of the libc documentation I can't find such
a function. Will I have to write it myself?
The C language standard library does not support the concept of
"abstract streams". The most logical way of doing this is to take a
stream object and layer another stream object on top of it, which
performs the insert at the right point. In this way you could perform
an arbitrary number of inserts, with just some allocation and callback
overhead per insert, and then only pay the writeback penalty on the
write *after* all the inserts.

But alas, C standard just doesn't provide support for this sort of
abstraction. The Better String Library does:

http://bstring.sf.net/

See the struct bStream functions. You can look through the source for
bsUuDecode for how to layer struct bStreams. If you just want to do
single instance inserts (i.e., that are not somehow written back to
some file) you can just do bsunread() calls, which will do direct
inserts into the stream results at the current point you are reading.

--
Paul Hsieh
http://www.pobox.com/~qed/
http://bstring.sf.net/

Aug 24 '06 #7

P: n/a
we******@gmail.com wrote:
anachronic_individual wrote:
>Is there a standard library function to insert an array of characters
at a particular point in a text stream without overwriting the existing
content, such that the following data in appropriately moved further
down? From a cursory search of the libc documentation I can't find such
a function. Will I have to write it myself?

The C language standard library does not support the concept of
"abstract streams". The most logical way of doing this is to take a
stream object and layer another stream object on top of it, which
performs the insert at the right point. In this way you could perform
an arbitrary number of inserts, with just some allocation and callback
overhead per insert, and then only pay the writeback penalty on the
write *after* all the inserts.

But alas, C standard just doesn't provide support for this sort of
abstraction. The Better String Library does:

http://bstring.sf.net/

See the struct bStream functions. You can look through the source for
bsUuDecode for how to layer struct bStreams. If you just want to do
single instance inserts (i.e., that are not somehow written back to
some file) you can just do bsunread() calls, which will do direct
inserts into the stream results at the current point you are reading.
I am impressed that you can insert characters into the stdin
stream. I assume you can also handle a stream originating in a
modem, or other serial input device without ever triggering overrun
errors. I also assume you have the capability of rewinding these
streams.

I fail to see the connection with any string library, although
streams may be used to initialize strings.

--
Chuck F (cb********@yahoo.com) (cb********@maineline.net)
Available for consulting/temporary embedded and systems.
<http://cbfalconer.home.att.netUSE maineline address!
Aug 24 '06 #8

P: n/a
CBFalconer wrote:
we******@gmail.com wrote:
anachronic_individual wrote:
Is there a standard library function to insert an array of characters
at a particular point in a text stream without overwriting the existing
content, such that the following data in appropriately moved further
down? From a cursory search of the libc documentation I can't find such
a function. Will I have to write it myself?
The C language standard library does not support the concept of
"abstract streams". The most logical way of doing this is to take a
stream object and layer another stream object on top of it, which
performs the insert at the right point. In this way you could perform
an arbitrary number of inserts, with just some allocation and callback
overhead per insert, and then only pay the writeback penalty on the
write *after* all the inserts.

But alas, C standard just doesn't provide support for this sort of
abstraction. The Better String Library does:

http://bstring.sf.net/

See the struct bStream functions. You can look through the source for
bsUuDecode for how to layer struct bStreams. If you just want to do
single instance inserts (i.e., that are not somehow written back to
some file) you can just do bsunread() calls, which will do direct
inserts into the stream results at the current point you are reading.

I am impressed that you can insert characters into the stdin
stream. I assume you can also handle a stream originating in a
modem, or other serial input device without ever triggering overrun
errors. I also assume you have the capability of rewinding these
streams.
Correct. It can do all of those things. (I dropped any seek/tell-like
behavior in order to keep things sensible.)

The way it does that is by building streams on top of streams. So it
doesn't *literally* modify any streams. It is really just has
facilities for modifying what is read out of a stream -- and it can do
so arbitrarily.
I fail to see the connection with any string library, although
streams may be used to initialize strings.
You fail to see the utility of fgets and fprintf? There is an
intersection between IO and string management, in that you can clearly
read and write strings to IO. There are serious semantic
considerations for doing such things (as the question of "How do I
input a string" being a constantly recurring question here is evidence
of.)

As such any complete string library must include some amount of IO code
(just as the std lib does). In Bstrlib, I avoid direct IO, but instead
create a kind of fully reprogrammable pseudo-IO -- it basically wraps
any fread-like function. So it allows for useful things like using
bstrings as if they were (read-only) files.

Its not a complete IO-library (its not meant to be), but it does
implement particular kinds of IO semantics which are useful and aligned
well to string managment.

--
Paul Hsieh
http://www.pobox.com/~qed/
http://bstring.sf.net/

Aug 25 '06 #9

P: n/a
On 24 Aug 2006 03:22:28 -0700, "anachronic_individual"
<an******************@gmail.comwrote:
Nevertheless I went ahead and wrote a function to insert arbitrary text
into a text stream. It compiles with gcc -W -Wall -std=c99 -pedantic
with one warning about comparision between a signed and unsigned
object. It also appears to work as intended.
Well, actually into a text file, not into the stream, which is the
thing (i.e. the FILE) that C uses to access the file. It should work
with some limitations (see below) on any system where text files are
just bytes, which are all the popular OSen today. There have been and
I believe still are some systems with record-oriented filesystems
where this type of approach probably can't work, or not well; OTOH
those systems may have other, platform-specific, but (much?) more
efficient ways.
I'll be greatful if the group can review the code for possible errors
or non-portable assumptions or anything else. I admit the code is not
elegant, but this is actually my third try, and I was getting a little
desperate. The code follows:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>

#define BUFFER_SIZE 512
That's quite small for most systems today that have any kind of
randomly accessible filesystem (roughly speaking, disk). You could
probably do several hundred K at least, and usually tens of Ms.
(Of course you'd need to expand your test case(s) dramatically.)
int s_insert(FILE *s, char *is, size_t isl, char *buf, size_t bufl);

int main(void) {
FILE *fp;
char *buffer, *ins;
char insstr[] = { 'I', 'n', 's', 'e', 'r', 't', '!' };
char *base_text = "This is the\nbase text in\nthe file.\n";
char *testfile = "s_insert_t.txt";
char *openmode = "w+";
I would definitely make the pointers to literals 'const char *' and
probably the insert-data const char [] since you never modify it and
don't want to (be able to); see below. Note that this small base_text
doesn't exercise your loop logic below (for multiple iterations,
positioning back and forth), which you should be testing.
size_t isl, bufl;
int rv, ct;

/* initialise variables */
ins = insstr;
isl = sizeof(insstr);
bufl = BUFFER_SIZE;
printf("isl = %u\tbufl = %u\n", isl, bufl);
There's no real need for these to be separate variables (but no harm).
%u is not portably the right or even fully safe way to display size_t
values. In C99 use %zu; or just cast to unsigned long and use %lu,
even if this wraps and gives (pretty obvious) wrong output it's safe.
/* allocate buffer */
buffer = malloc(bufl);
if(buffer == NULL) { puts("malloc() failed."); return EXIT_FAILURE; }

/* open test file and write the base text to it */
fp = fopen(testfile, openmode);
if(fp == NULL) { puts("fopen() failed."); return EXIT_FAILURE; }
else {
if(fputs(base_text, fp) == EOF) {
puts("fputs() failed.");
exit(EXIT_FAILURE);
}
else {
There's no need to use else after exit(), which will never return.
<OT=waylike Boston Charlie <G</Especially since you do so
inconsistently, which is confusing for no benefit; and if you do use
it consistently, you end up with all your 'good' normal-case code
indented to about column 400. I know some SESE zealots will disagree,
but I consider early-exit (or early-return) for these kind of cases
fine. If you really must be 'structured', a reasonable compromise is:
int ok = TRUE; /* or C99 or your own bool */
if( ok ){ // redundant but for consistency
try first thing
if( failed ){
error handling
ok = FALSE;
} }
if( ok ){
try second thing
if( failed ){
error handling
ok = FALSE;
} }
etc.
or a variant on this, if you have your functions return error codes
(or error strings), which I often do and find useful:
int err = 0; // no error // or const char * err = NULL;
if( !err ){
try thing
if( failed ){
error handling
err = whatever_nonzero; // or err = "whatever";
} }
etc.
if(fflush(fp) == EOF) {
puts("fflush() failed.");
exit(EXIT_FAILURE);
}
if(fseek(fp, 0, SEEK_SET) != 0) {
puts("fseek() failed.");
exit(EXIT_FAILURE);
}
}
}

/* display the file contents & string to be inserted */
puts("File before insert:");
while((rv = fgetc(fp)) != EOF)
putc(rv, stdout);
putc('\n', stdout);
Probably more efficient to do this in chunks with fread, or (mostly)
lines with fgets; or failing that with getc rather than fgetc.
if(fseek(fp, 0, SEEK_SET) != 0) {
puts("fseek() failed.");
exit(EXIT_FAILURE);
}
puts("\nText to be inserted:");
for(rv = 0; rv < isl; rv++)
putc(ins[rv], stdout);
Could just do fwrite (ins, 1, isl, stdout) or fputs (ins, stdout) ...
putc('\n', stdout);
or since you want a newline just puts (ins) .
/* insert the text and display the new file contents */
for(ct = 0; ct < 10; ct++) {
rv = s_insert(fp, ins, isl, buffer, bufl);
if(rv == 0) {
puts("s_insert() returned zero.");
exit(EXIT_FAILURE);
}
else {
if(fflush(fp) == EOF) {
puts("fflush failed.");
exit(EXIT_FAILURE);
}
I think I would put the fflush() in (at the end of) s_insert instead
of in the caller; to me it makes logical sense as part of the insert.
Either way (used consistently) is legal, of course.
if(fseek(fp, 0, SEEK_SET) != 0) {
puts("fseek() failed.");
exit(EXIT_FAILURE);
}
puts("File after insert:");
Might want to make this printf ("... after insert #%d:", ct);
while((rv = fgetc(fp)) != EOF)
putc(rv, stdout);
putc('\n', stdout);
As above.
if(fseek(fp, 0, SEEK_SET) != 0) {
puts("fseek() failed.");
exit(EXIT_FAILURE);
}
}
}
if(fclose(fp) == EOF) {
puts("fclose() failed.");
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS;
}

int s_insert(FILE *s, char *is, size_t isl, char *buf, size_t bufl) {
'is' could be const char * since you don't (want to) change it, if
(all) the callers are adjusted accordingly. If it's (always) a proper
string you could compute the length instead of passing it; this makes
life a little easier for the caller(s), and eliminates one opportunity
for a mistake. It's a small one, but small things accumulate.
char *sbuf;
int cc, ss, bufi, sbufi;
size_t bufc, sbufc, halfb, cnt;
fpos_t pos0, pos1, *crpos, *cwpos;

/* initialise variables */
ss = 1;
sbufi = 1;
sbufc = isl;
halfb = bufl / 2;
sbuf = buf + halfb;
bufi = 0;
bufc = 0;
crpos = &pos0;
cwpos = &pos1;

/* get current file position */
if(fgetpos(s, crpos) != 0 || fgetpos(s, cwpos) != 0)
return 0;
There's no reason to have variables crpos and cwpos to point to pos0
and pos1, just use fgetpos (s, &pos0). And fpos_t is required to be a
non-array object type so pos1 = pos0 will work. (Or posr, posw.)
/* copy argument to be inserted into secondary buffer */
memcpy(sbuf, is, sbufc);
This assumes, without checking, that the (caller-supplied) insert data
fits within half of the (caller-supplied) buffer, or it will mess up
royally. You should at least document this is as a limitation. I would
check it and either return an error or bomb out.
/* main insert loop */
for( ;; ) {
/* if primary buffer is empty and secondary buffer is
* not, copy secondary buffer to primary buffer...
*/
if(bufi == 0 && sbufi == 1) {
memcpy(buf, sbuf, sbufc);
bufi = 1;
bufc = sbufc;
sbufi = 0;
sbufc = 0;
}
Instead of having a flag AND separately a count for each buffer, can
use a count of nonzero to mean there is something in the buffer; this
makes the code shorter to write and easier to check.
else
break;

/* try to fill secondary buffer, if empty, from the
* current reading position of stream, and update the
* former...
*/
if(sbufi == 0 && ss == 1) {
if(fsetpos(s, crpos) != 0)
return 0;
for(sbufc = 0; sbufc < halfb; sbufc++) {
cc = getc(s);
if(cc == EOF) {
Much easier to just use fread().
if(ferror(s))
return 0;
else {
ss = 0;
if(sbufc == 0)
sbufi = 0;
else
sbufi = 1;
break;
}
}
else {
sbuf[sbufc] = cc;
sbufi = 1;
}
}
/* update stream reading pos. if not at EOF */
if(ss != 0) {
if(fgetpos(s, crpos) != 0)
return 0;
}
}

/* if primary buffer is not empty, write it to stream
* at latter's current writing offset and update it...
*/
if(bufi == 1) {
if(fsetpos(s, cwpos) != 0)
return 0;
for(cnt = 0; cnt < bufc; cnt++) {
cc = putc(buf[cnt], s);
And somewhat easier to use fwrite().
if(cc == EOF)
return 0;
}

if(fgetpos(s, cwpos) != 0)
return 0;
else {
bufi = 0;
bufc = 0;
}
}
}

return 1;
}

- David.Thompson1 at worldnet.att.net
Sep 10 '06 #10

This discussion thread is closed

Replies have been disabled for this discussion.