1
votes

EDIT
The solution to the problem was understanding what Ctrl-D was actually doing.
On a new empty line, a single Ctrl-D will signal EOF.
But if there are characters already in the line, the first Ctrl-D causes the contents of the line to be echoed to the screen (but not written to STDOUT). With characters already in the buffer, a second Ctrl-D must be issued to signal EOF, thus writing the buffer to STDOUT.
This can be demonstrated by redirecting output to a file.
EDIT

I'm using fgetc() to read input from stdin. I loop until I receive an EOF. In the loop I build a string based on the characters typed before Ctrl-D was pressed. But I can't figure out a way of exiting the loop since the buffer ch = fgetc() reads from does not contain the EOF. (The EOF only triggers the fgetc() to return its first value.)

ungetc() does not allow pushing an EOF into the buffer, pushing any other char runs the risk of confusion with real data, I'm stuck!! I've read a LOT of answers but they don't address this issue or don't apply to the use-case I'm trying to implement.

I would like to be able to count, peek, etc on the stdin buffer.

I don't really want to read a whole line (or X chars at a time) because I'm processing each character as it arrives (edit) from fgetc().

Any suggestions on how to overcome this dilemma? (Without using NCurses)

I'm using Ubuntu. EOF = Ctrl-D Here is some code I'm working with:

This works, and does the same as Jonathan's simple example, but not what I want:

int main(int argc, char **argv) {

    int inputChr;

    do {
        inputChr = fgetc(stdin);
        if (inputChr != EOF) {
            fputc( inputChr, stdout);
        }
        if (feof(stdin)) {
            if (ferror(stdin)) {
                perror(NULL);
                return errno;
            }
        }
    } while (inputChr != EOF);
    return EXIT_SUCCESS;
}

HOWEVER, this is getting stuck but is trying to do what I want (edit) but requires Ctrl-D a second time:

char *buildLine (FILE *inputSource, char *currLine, int showTabs, int showNonPrint, int *haveLF) {

    int inputChr;
    char *thisLine = malloc(1);
    int inputSize;

    *haveLF = FALSE;
    while ( (inputChr = fgetc(inputSource)) != EOF ) {

        if (ferror(inputSource)) {
            perror(NULL);
        } else {
            if (inputChr == LF) {
                *haveLF = TRUE;
            } else {
                thisLine = strconcat(thisLine,(char *)&inputChr);
            }
        }
    }

    return thisLine;
}

Some more code that's been asked about:

char * strconcat ( char *str1, char * str2) {

    char *newStr = malloc(strlen(str1)+strlen(str2)+1);
    if (newStr == NULL) {
        return NULL;
    }
    strcpy(newStr,str1);
    strcat(newStr,str2);

    return newStr;
}

THIS VERSION BELOW processes the input character by character and works just like cat. But I decided I would process each character into a line first, before applying some extra transforms I need to implement. This simplified the state-machine design, but maybe trying to build lines wasn't good option (without using NCurses). :(

int echoInput( FILE *inputSource, FILE *outputDestination, int numbers, int showEnds) {

    int haveNewLine = TRUE;
    int lineNo = 1;
    int inputChr;

    do {
        inputChr = fgetc(inputSource);
        if (inputChr != EOF) {
            if (numbers && haveNewLine) {
                long lineNoSize = (long) log10(lineNo)+1;   // effectively floor(log10(lineNo)+1) = number digits
                char *lineNoStr =  (lineNoSize<6)?malloc(8):malloc(lineNoSize+2);   // If less than 6 digits, allow for minimum 6 plus tab.  Also +1 for terminator.
                if (lineNoStr == NULL) {
                    printf ("Error::Out of Memory");
                    return ENOMEM;
                }
            sprintf(lineNoStr,"%6d\t",lineNo);  // format lineNo string
                fputs(lineNoStr, outputDestination);    // send string to output
                lineNo++;
                haveNewLine = FALSE;
            }
            if (inputChr == LF) {
                if (showEnds) {
                    fputc('$', outputDestination);  // send char to output
                }
                haveNewLine = TRUE;
            }
            fputc( inputChr, outputDestination);
        }
        if (feof(inputSource)) {
            if (ferror(inputSource)) {
                perror(NULL);
                return errno;
            }
        }
        if (ferror(outputDestination)) {
            perror(NULL);
            return errno;
        }
    } while (inputChr != EOF);
    return EXIT_SUCCESS;
}
2
Which OS are you using?user3386109
Make sure ch is an int if you intend to check it for EOF.M.M
Typically in Linux, pressing Ctrl-D after typing some characters causes the input stream to be flushed (so your program can start reading it) but does not end the input. Pressing it again , or pressing it straight after a newline , causes the stream to end.M.M
@MattMcNabb: setvbuf is part of Standard C, and it will work perfectly if stdin is, for example, a FIFO. With Unix systems, at least, it won't work if stdin is a tty but that's neither required nor prohibited by the C standard; not because the stdin is line-buffered but because by default the tty device itself doesn't return anything to userland until an ENTER or certain other special character is typed.rici
OK; you need a *thisLine = '\0'; after the malloc() in the calling code; otherwise, you are reading out of bounds. You're also leaking like a sieve; you need to free(str1); in strconcat(). Also, if memory allocation fails, your code is going to crash and burn because of reading through a NULL pointer. You should consider using realloc() instead of malloc() each time; it will be more cost-effective over time.Jonathan Leffler

2 Answers

3
votes

There must be other variations of this question with good enough answers, but here's one more.

The value returned by fgetc() (and getc() and getchar()) is an int and not a char. It has to be an int because the set of values that can be returned includes every possible value of a char and one extra value, EOF, which is negative (whereas the characters are all positive). Although EOF is most commonly -1, you should never code to that assumption.

Two things can go wrong with:

char c;

while ((c = fgetc(stdin)) != EOF)

If the type char is signed, then some characters (usually 0xFF, often ÿ, y-umlaut, Unicode U+00FF, LATIN SMALL LETTER Y WITH DIAERESIS) will be misinterpreted as indicating EOF before the EOF is reached.

If the type char is unsigned, then you will never detect EOF because the value assigned to c will be 0xFF (positive), and that will never compare equal to EOF (a negative value).

You're correct that you can't push EOF back onto the input stream with ungetc().

Note that Control-D (or Control-Z on Windows) does not add a character to the input queue. Rather, it signals that there are no more characters available (slightly simplifying things), and that means the read() system call returns 0 bytes read, which means EOF.

A trivial program to copy standard input to standard output using getchar() and putchar() is:

int c;
while ((c = getchar()) != EOF)
    putchar(c);

You can adapt that to use fgetc() or getc() and fputc() or putc() if you wish to open files and read those. The key point is the use of an int to hold the value read.

0
votes

EOF is typically an integer (not a char) and it does not have the same value as any valid character.

Normal C style would be to terminate the string you are building up with a \0. It's theoretically possible to read a NUL character, of course, and if you want to deal with these possibility you'll need to record the number of characters read as well as the buffer they were read into.