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!

Community
  • 1
  • 1
Monte Hurd
  • 4,349
  • 5
  • 30
  • 35

7 Answers7

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 );
}
Mark Wilkins
  • 40,729
  • 5
  • 57
  • 110
  • 1
    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 Hurd Jan 15 '10 at 12:25
  • OK, for C++ compilation happiness I made the following "const": "char *sgets", "char **input", "char *next", "char *str". – Monte Hurd Jan 15 '10 at 12:38
  • That sounds correct. And you are right; I did compile it as a C test app so did not notice those warnings. – Mark Wilkins Jan 15 '10 at 13:39
  • I'd suggest using a second pointer to the original string. Otherwise, you lose the reference since str is modified by sgets. – Judge Maygarden Jan 15 '10 at 14:11
  • 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 Hurd Jan 15 '10 at 19:06
  • The problem I mentioned above is easier to see if you change "char buf[5];" to "char buf[32];". – Monte Hurd Jan 15 '10 at 19:14
  • Haha looks like someone had your idea previously: http://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: http://pastie.org/779971 – Monte Hurd Jan 15 '10 at 19:31
  • Thanks Monte. Sorry about that. I believe I fixed this version of it. – Mark Wilkins Jan 15 '10 at 20:24
  • @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++? – Matthias Jan 25 '17 at 14:02
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.

R Samuel Klatchko
  • 74,869
  • 16
  • 134
  • 187
3

sscanf should do it. Ofcourse the semantics are different.

Hassan Syed
  • 20,075
  • 11
  • 87
  • 171
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;
}
dreamlax
  • 93,976
  • 29
  • 161
  • 209
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.

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
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;
}
Judge Maygarden
  • 26,961
  • 9
  • 82
  • 99
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 
omeraygor
  • 119
  • 1
  • 3