Can C's fgets be coaxed to work with a string *not* from a file?
Asked Answered
D

7

9

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!

Dealer answered 15/1, 2010 at 2:0 Comment(2)
Are you OK with your string being modified?Moreno
I guess so. I just need a way to make the first code sample I linked to accept input from either a file or a string.Dealer
S
10

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 );
}
Shinto answered 15/1, 2010 at 3:15 Comment(9)
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.Dealer
OK, for C++ compilation happiness I made the following "const": "char *sgets", "char **input", "char *next", "char *str".Dealer
That sounds correct. And you are right; I did compile it as a C test app so did not notice those warnings.Shinto
I'd suggest using a second pointer to the original string. Otherwise, you lose the reference since str is modified by sgets.Cavill
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?Dealer
The problem I mentioned above is easier to see if you change "char buf[5];" to "char buf[32];".Dealer
Haha looks like someone had your idea previously: bugs.debian.org/cgi-bin/bugreport.cgi?bug=358701 They 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: pastie.org/779971Dealer
Thanks Monte. Sorry about that. I believe I fixed this version of it.Shinto
@MonteHurd what do you mean by "I made the following ... char *str "? Shouldn't the method signature be char *sgets(char *str, int num, const char **input) for C++?Ainu
U
5

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.

Uropygium answered 15/1, 2010 at 2:6 Comment(1)
sfio rocks! Joe Bob says check it out.Alti
M
3

sscanf should do it. Ofcourse the semantics are different.

Maryellen answered 15/1, 2010 at 2:7 Comment(1)
Won't sscanf stop on any whitespace instead of just newlines?Cavill
J
3

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;
}
Jennijennica answered 15/1, 2010 at 3:32 Comment(4)
No error checking, and using ambiguous variable names, bad, bad, very bad! (-1)Jennijennica
What happens when My\nlong\nstring\nexceeds\nthe\npipe\nbuffer?Disfigure
@jhfrontz: Your\nprogram\nwill\nprobably\nblock\nwhen\ncalling\n fputs.Jennijennica
Yielding "Found a line: The sound of one hand clapping."Disfigure
S
2

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.

Sage answered 15/1, 2010 at 2:45 Comment(0)
C
1

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;
}
Cavill answered 15/1, 2010 at 3:1 Comment(0)
S
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 
Stank answered 4/1, 2014 at 11:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.