Question

Specifically, the code sample here works great, but only when the string is stored in a file.

Sometimes I need it to process a generated string (stored in a string variable), but I'm having trouble convincing fgets's third parameter to work with string variables because it's a pointer to a FILE structure.

Or perhaps there's a functional equivalent to fgets that may be used on strings?

Any suggestions? Thanks!

Was it helpful?

Solution

In the spirit of hacking together quick answers, here is "sgets" that I just wrote. It attempts to emulate fgets but with string input.

Edit Fixed a bug that Monte pointed out (thanks). Madly typing out a utility while believing that at least 15 other people with the exact same idea are frantically doing the same thing does not lead to well-tested code. Bad me. The original version was including the newline character on the succeeding call.

char *sgets( char * str, int num, char **input )
{
    char *next = *input;
    int  numread = 0;

    while ( numread + 1 < num && *next ) {
        int isnewline = ( *next == '\n' );
        *str++ = *next++;
        numread++;
        // newline terminates the line but is included
        if ( isnewline )
            break;
    }

    if ( numread == 0 )
        return NULL;  // "eof"

    // must have hit the null terminator or end of line
    *str = '\0';  // null terminate this tring
    // set up input for next call
    *input = next;
    return str;
}


int main( int argc, char* argv[] )
{
    // quick and dirty test
    char *str = "abc\ndefghitjklksd\na\n12345\n12345\n123456\nabc\n\n";
    char buf[5];

    while ( sgets( buf, sizeof( buf ), &str ))
        printf( "'%s'\n", buf );
}

OTHER TIPS

The standard C library does not provide that functionality.

But AT&T's safe/fast I/O library does enable memory streams and also provides wrapper code to use the FILE API with their extensions. The last update is from Feb 2005 so either they finally worked out all the bugs or they can no longer afford to maintain it now that Luke Wilson is on the payroll :-(

The package can be downloaded here.

sscanf should do it. Ofcourse the semantics are different.

If the string is already in memory, you could tokenize on newlines (either with strtok if you're okay with mutating the string and if don't need to worry about re-entrancy, or by manually using strchr and copying to a separate buffer yourself).

You wouldn't get platform-dependent newline conversion that the stdio functions would normally give you, however, so some extra care would be needed if your strings in memory use, say, CRLF line terminators.

Use a pipe, and then open the pipe with fdopen to obtain a FILE *, then read from that.


#include <stdio.h>

int main (int argc, char *argv[])
{
    int pipes[2];
    FILE *write;
    FILE *read;
    char buffer[1000];

    pipe (pipes);

    read = fdopen (pipes[0], "r");
    write = fdopen (pipes[1], "w");
    fputs ("My\nlong\nstring\nin\nmany\nlines\n", write);
    fclose (write);

    while (fgets (buffer, sizeof(buffer), read) != NULL)
    {
        printf ("Found a line: %s", buffer);
    }

    fclose (read);

    return 0;
}

All you need to do is perform a linear search for line endings in the string. Here is a small program to get you started writing your own string streaming class.

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

typedef struct StringStream StringStream;

struct StringStream {
    const char *data;
    const char *position;
};

StringStream *
stringstream_new(const char *data)
{
    StringStream *self = malloc(sizeof (StringStream));

    self->data = self->position = data;

    return self;
}

void
stringstream_delete(StringStream *self)
{
    free(self);
}

char *
stringstream_gets(char *s, int n, StringStream *self)
{
    const char * eol;
    int i, len;

    if (NULL == self->position || '\0' == *self->position)
        return NULL;

    eol = strchr(self->position, '\n');

    if (eol) {
        len = eol - self->position + 1;
        len = len <= n ? len : n - 1;

        for (i = 0; i < len; ++i)
            s[i] = *self->position++;

    } else {
        for (i = 0; *self->position && i < n; ++i)
            s[i] = *self->position++;
            if ('\0' == *self->position)
                self->position = NULL;
            else
                ++self->position;
    }

    s[i] = '\0';

    return s;
}

int
main(int argc, char * argv[])
{
    static const int LEN = 100;
    static const char TEST_STRING[] =
        "line 0\n"
        "line 1\n"
        "line 2\n"
        "line 3\n"
        "line 4\n"
        "line 5\n"
        "line 6\n"
        "line 7\n"
        "line 8\n"
        "line 9\n";

    StringStream *stream;
    char buf[LEN];

    stream = stringstream_new(TEST_STRING);

    while (stringstream_gets(buf, LEN, stream))
        printf("gets: %s\n", buf);

    stringstream_delete(stream);

    return 0;
}

i modified fgets function's source code:

size_t  my_fgets( inBuf , n , outBuf )
unsigned char *inBuf;
size_t n;
unsigned char *outBuf;
{
    size_t len = 0;
    unsigned char *s;
    unsigned char *p, *t;

    if (n <= 0)             /* sanity check */
            return (-1);

    p =  inBuf;
    s = outBuf;

    n--;                    /* leave space for NUL */

    while (n != 0) {

        len = n;
        t = memchr((void *)p, '\n', strlen(p));

        //printf ("'p' found at position %d.\n", t -p + 1);

        if (t != NULL) {
            len = ++t -p;
            (void)memcpy((void *)s, (void *)p, len);
            s[len] = 0;
            return len;
        }

        (void)memcpy((void *)s, (void *)p, len);
        s += len;
        n -= len;

    }

    *s = 0;

    return len;

}

and main function:

int main(void)
{
    char *inBuf = "this \n"
                  "is \n"
                  "test \n";

    char outBuf[1024];

    my_fgets(inBuf,strlen(inBuf),outBuf);
    printf("outBuf:%s \n",outBuf);

    return 0;
}

and output:

outBuf:this 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top