views:

252

answers:

6

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!

+4  A: 

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.

R Samuel Klatchko
sfio rocks! Joe Bob says check it out.
Norman Ramsey
+3  A: 

sscanf should do it. Ofcourse the semantics are different.

Hassan Syed
Won't sscanf stop on any whitespace instead of just newlines?
Judge Maygarden
+2  A: 

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.

jamesdlin
+1  A: 

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;
}
Judge Maygarden
+3  A: 

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 );
}
Mark Wilkins
Getting a weird compiler warning on the string literal in main from your example: "warning: deprecated conversion from string constant to ‘char*’". Google is my friend, but I haven't quite solved it yet.Edit: It would seem I compiled this as C++, hence the warning. But, still, for curiosity's sake, I'm interested in how this would be addressed.
Monte
OK, for C++ compilation happiness I made the following "const": "char *sgets", "char **input", "char *next", "char *str".
Monte
That sounds correct. And you are right; I did compile it as a C test app so did not notice those warnings.
Mark Wilkins
I'd suggest using a second pointer to the original string. Otherwise, you lose the reference since str is modified by sgets.
Judge Maygarden
I found one small compatibility problem that I haven't been able to resolve thus far today - According to fgets documentation "A newline character makes fgets stop reading, but it is considered a valid character and therefore it is included in the string copied to str." The problem is this code includes the *leading* newline char, whereas fgets includes the *trailing* newline char. My tweaking efforts have been in vain - any idea?
Monte
The problem I mentioned above is easier to see if you change "char buf[5];" to "char buf[32];".
Monte
Haha looks like someone had your idea previously:http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=358701They have an "sgets" function too! I had to make its return const and its "char **source" parameter const too but it seems to work and deal with the newline issue nicely. Thanks again for all the help!!!Edit: here's my cleaned up version: http://pastie.org/779971
Monte
Thanks Monte. Sorry about that. I believe I fixed this version of it.
Mark Wilkins
Awesome! I like your version better - much more readable.
Monte
+1  A: 

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;
}
dreamlax
No error checking, and using ambiguous variable names, bad, bad, very bad! (-1)
dreamlax