views:

7735

answers:

11

What is the simplest way to read a full line in a C console program The text entered might have a variable length and we can't make any assumption about its content.

A: 

Are you asking about the argv/argc for the command line for your process, or are you asking about console input while the program is running? Your title is inconsistent (to me) with the content of your question.


EDIT

if so use if so use int main(int argc, char *argv[]); argc and argv argument

Tim
I meant a function like ReadLine() in a console program, I hava edited my post to reflect that.
pbreault
+13  A: 

You need dynamic memory management, and use the fgets function to read your line. However, there seems to be no way to see how many characters it read. So you use fgetc:

char * getline(void) {
    char * line = malloc(100), * linep = line;
    size_t lenmax = 100, len = lenmax;
    int c;

    if(line == NULL)
        return NULL;

    for(;;) {
        c = fgetc(stdin);
        if(c == EOF)
            break;

        if(--len == 0) {
            char * linen = realloc(linep, lenmax *= 2);
            len = lenmax;

            if(linen == NULL) {
                free(linep);
                return NULL;
            }
            line = linen + (line - linep);
            linep = linen;
        }

        if((*line++ = c) == '\n')
            break;
    }
    *line = '\0';
    return linep;
}

Note: Never use gets ! It does not do bounds checking and can overflow your buffer

Johannes Schaub - litb
sorry for the many edits to that code. as you see there are many pitfalls :)
Johannes Schaub - litb
Caveat - need to check result of realloc there. But if that fails, then there are worse problems most likely.
Tim
right. it's not "exception safe" :)
Johannes Schaub - litb
You could probably improve the efficiency a bit by doing fgets with buffer, and checking if you have the newline character at the end. If you don't, realloc your accumulation buffer, copy into it, and fgets again.
Paul Tomblin
Paul, what guarantees me that there is a newline or '\0' at the end if fgets doesn't need more space? it could have read less than the buffer size. A wait. you mean using strlen to find the \0 and looking before it? Well wasn't sure what is faster. So i did go with this char-by-char way.
Johannes Schaub - litb
@litb: this is C, not C++. There are no exceptions (in the C++ sense) and no concept of exception-safety.
Adam Rosenfield
dammit. randomly looking at it again, i found there are silly mistakes in teh code. i hope it's fixed now.
Johannes Schaub - litb
looks pretty nice for just reading a line from console :)
badbadboy
Don't forget to make the "100" into a constant or a #DEFINE in actual use.
Paul Fisher
J.F. Sebastian
Sebastian, are you aware that is not a Standard C function? It's a GNU extension.
Johannes Schaub - litb
and, before i copy some random function from the internetz, i better write a function that works and whose caveats i know about. :)
Johannes Schaub - litb
This function needs a correction: the line "len = lenmax;" after the realloc should either precede the realloc or should be "len = lenmax >> 1;" -- or some other equivalent that accounts for the fact that half the length is already used.
Matt Gallagher
A: 

So, if you were looking for command arguments, take a look at Tim's answer. If you just want to read a line from console:

#include <stdio.h>

int main()
{
  char string [256];
  printf ("Insert your full address: ");
  gets (string);
  printf ("Your address is: %s\n",string);
  return 0;
}

Yes, it is not secure, you can do buffer overrun, it does not check for end of file, it does not support encodings and a lot of other stuff. Actually I didn't even think whether it did ANY of this stuff. I agree I kinda screwed up :) But...when I see a question like "How to read a line from the console in C?", I assume a person needs something simple, like gets() and not 100 lines of code like above. Actually, I think, if you try to write those 100 lines of code in reality, you would do many more mistakes, than you would have done had you chosen gets ;)

badbadboy
THis doesn't allow for long strings... - which I think is the crux of his question.
Tim
-1, gets() should not be used since it doesn't do bounds checking.
unwind
gets is an in secure function
Baget
You should probably delete this answer or edit it before it kills your rep if you are concerned about that...
Tim
On the other hand if you are writing a program for yourself and just need to read an input this is perfectly fine. How much security a program needs is par tof the spec - you don't HAVe to put it as a priority everytime.
Martin Beckett
@Tim - I want to keep all history :)
badbadboy
+1  A: 

You might need to use a character by character (getc()) loop to ensure you have no buffer overflows and don't truncate the input.

Tim
how in the world could anyone call this offensive or downvote this answer? Wow. the accepted answer uses getc(). Someone must be annoyed with me from another question I bet.
Tim
I agree that a -1 is a bit over the top, adding +1 to get 0 (which is the right value in my opinion).
hlovdal
A: 

This function should do what you want:

char* readLine( FILE* file )
 {
 char buffer[1024];
 char* result = 0;
 int length = 0;

 while( !feof(file) )
  {
  fgets( buffer, sizeof(buffer)-1, file );
  int len = strlen(buffer);
  buffer[len] = 0;

  length += len;
  char* tmp = (char*)malloc(length+1);
  tmp[0] = 0;

  if( result )
   {
   strcpy( tmp, result );
   free( result );
   result = tmp;
   }

  strcat( result, buffer );

  if( strstr( buffer, "\n" ) break;
  }

 return result;
 }

char* line = readLine( stdin );
/* Use it */
free( line );

I hope this helps.

David Allan Finch
+1  A: 

I would change @litb's answer to

char *getline(void)
{
   char* accumulator = malloc(100);
   char readBuf[100];
   int accumulatorSize = 100;
   *accumulator = '\0';

   while (!feof(stdin))
   {
     fgets(readBuf, 99, stdin);
     strcat(accumulator, readBuf);
     /* possible fencepost error here */
     if (readBuf[strlen(readBuf)] != '\n')
     {
       accumulatorSize += 100;
       accumulator = realloc(accumulator, accumulatorSize);
       /* should probably check for realloc returning null */
     }
     else
       break;
   }
   return accumulator;
}

Keep in mind this is more bare C code than I've written in 11 years.

Paul Tomblin
This leaks the readBuf variable
Adam Tegen
@Adam - made readBuf an automatic array. That should stop the leak.
Paul Tomblin
Paul, this code has other problems :) for example you compare a char to a string literal :) (fencepost check) also fgets does return redBuf (char*), not some integer that you could compare to EOF :)
Johannes Schaub - litb
@litb - better? Every now and then I look at C and wonder how I used to do this professionally.
Paul Tomblin
+2  A: 

If you are using the GNU C library, you can use getline() and pass stdin to it

dmityugov
A: 

I came across the same problem some time ago, this was my solutuion, hope it helps.

/*
 * Initial size of the read buffer
 */
#define DEFAULT_BUFFER 1024

/*
 * Standard boolean type definition
 */
typedef enum{ false = 0, true = 1 }bool;

/*
 * Flags errors in pointer returning functions
 */
bool has_err = false;

/*
 * Reads the next line of text from file and returns it.
 * The line must be free()d afterwards.
 *
 * This function will segfault on binary data.
 */
char *readLine(FILE *file){
    char *buffer   = NULL;
    char *tmp_buf  = NULL;
    bool line_read = false;
    int  iteration = 0;
    int  offset    = 0;

    if(file == NULL){
        fprintf(stderr, "readLine: NULL file pointer passed!\n");
        has_err = true;

        return NULL;
    }

    while(!line_read){
        if((tmp_buf = malloc(DEFAULT_BUFFER)) == NULL){
            fprintf(stderr, "readLine: Unable to allocate temporary buffer!\n");
            if(buffer != NULL)
                free(buffer);
            has_err = true;

            return NULL;
        }

        if(fgets(tmp_buf, DEFAULT_BUFFER, file) == NULL){
            free(tmp_buf);

            break;
        }

        if(tmp_buf[strlen(tmp_buf) - 1] == '\n') /* we have an end of line */
            line_read = true;

        offset = DEFAULT_BUFFER * (iteration + 1);

        if((buffer = realloc(buffer, offset)) == NULL){
            fprintf(stderr, "readLine: Unable to reallocate buffer!\n");
            free(tmp_buf);
            has_err = true;

            return NULL;
        }

        offset = DEFAULT_BUFFER * iteration - iteration;

        if(memcpy(buffer + offset, tmp_buf, DEFAULT_BUFFER) == NULL){
            fprintf(stderr, "readLine: Cannot copy to buffer\n");
            free(tmp_buf);
            if(buffer != NULL)
                free(buffer);
            has_err = true;

            return NULL;
        }

        free(tmp_buf);
        iteration++;
    }

    return buffer;
}
dsm
+3  A: 

GNU Readline

plan9assembler
readline does much more then the OP asked for, and it is under the GPL, which makes it unusable for every app that is not under the GPL.
quinmars
A: 

A very simple implementation to read line for static allocation.

char line[1024];

scanf("%[^\n]", line);

A: 

As suggested, you can use getchar() to read from the console until an end-of-line or an EOF is returned, building your own buffer. Growing buffer dynamically can occur if you are unable to set a reasonable maximum line size.

You can use also use fgets as a safe way to obtain a line as a C null-terminated string:

#include <stdio.h>

char line[1024];  /* Generously large value for most situations */

char *eof;

line[0] = '\0'; /* Ensure empty line if no input delivered */
line[sizeof(line)-1] = ~'\0';  /* Ensure no false-null at end of buffer */

eof = fgets(line, sizeof(line), stdin);

If you have exhausted the console input or if the operation failed for some reason, eof == NULL is returned and the line buffer might be unchanged (which is why setting the first char to '\0' is handy).

fgets will not overfill line[] and it will ensure that there is a null after the last-accepted character on a successful return.

If end-of-line was reached, the character preceding the terminating '\0' will be a '\n'.

If there is no terminating '\n' before the ending '\0' it may be that there is more data or that the next request will report end-of-file. You'll have to do another fgets to determine which is which. (In this regard, looping with getchar() is easier.)

In the (updated) example code above, if line[sizeof(line)-1] == '\0' after successful fgets, you know that the buffer was filled completely. If that position is proceeded by a '\n' you know you were lucky. Otherwise, there is either more data or an end-of-file up ahead in stdin. (When the buffer is not filled completely, you could still be at an end-of-file and there also might not be a '\n' at the end of the current line. Since you have to scan the string to find and/or eliminate any '\n' before the end of the string (the first '\0' in the buffer), I am inclined to prefer using getchar() in the first place.)

Do what you need to do to deal with there still being more line than the amount you read as the first chunk. The examples of dynamically-growing a buffer can be made to work with either getchar or fgets. There are some tricky edge cases to watch out for (like remembering to have the next input start storing at the position of the '\0' that ended the previous input before the buffer was extended).

orcmid