¿Cómo leer una línea de la consola en C?

108

¿Cuál es la forma más sencilla de leer una línea completa en un programa de consola C? El texto ingresado puede tener una longitud variable y no podemos hacer ninguna suposición sobre su contenido.

pbreault
fuente
¿Podrías aclararme, por favor? como @Tim dijo a continuación, es confuso lo que estás pidiendo :)
warren

Respuestas:

81

Necesita administración de memoria dinámica y usa la fgetsfunción para leer su línea. Sin embargo, parece que no hay forma de saber cuántos caracteres lee. Entonces usas 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) {
            len = lenmax;
            char * linen = realloc(linep, lenmax *= 2);

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

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

Nota : ¡Nunca use get! No comprueba los límites y puede desbordar su búfer

Johannes Schaub - litb
fuente
Advertencia: es necesario verificar el resultado de la reasignación allí. Pero si eso falla, lo más probable es que haya problemas peores.
Tim
4
Probablemente podría mejorar un poco la eficiencia haciendo fgets con búfer y verificando si tiene el carácter de nueva línea al final. Si no lo hace, reasigne su búfer de acumulación, cópielo en él y vuelva a buscar.
Paul Tomblin
3
Esta función necesita una corrección: la línea "len = lenmax;" después de la reasignación debe preceder a la reasignación o debe ser "len = lenmax >> 1;" - o algún otro equivalente que explique el hecho de que ya se utiliza la mitad de la longitud.
Matt Gallagher
1
@Johannes, en respuesta a su pregunta, el enfoque de @ Paul PODRÍA ser más rápido en la mayoría de las implementaciones de libc (es decir, reentrantes), ya que su enfoque bloquea implícitamente stdin para cada carácter, mientras que el suyo lo bloquea una vez por búfer. Puede usar el menos portátil fgetc_unlockedsi la seguridad de los subprocesos no es una preocupación, pero el rendimiento sí.
vladr
3
Tenga en cuenta que esto getline()es diferente de la getline()función estándar POSIX .
Jonathan Leffler
28

Si está utilizando la biblioteca GNU C u otra biblioteca compatible con POSIX, puede usarla getline()y pasarla stdinpara el flujo de archivos.

dmityugov
fuente
16

Una implementación muy simple pero insegura para leer la línea para la asignación estática:

char line[1024];

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

Una implementación más segura, sin posibilidad de desbordamiento del búfer, pero con la posibilidad de no leer toda la línea, es:

char line[1024];

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

No es la 'diferencia en uno' entre la longitud especificada que declara la variable y la longitud especificada en la cadena de formato. Es un artefacto histórico.

Jonathan Leffler
fuente
14
Esto no es seguro en absoluto. Sufre exactamente el mismo problema de por qué getsse eliminó por completo del estándar
Antti Haapala
6
Nota del moderador: el comentario anterior se refiere a una revisión previa de la respuesta.
Robert Harvey
13

Entonces, si estaba buscando argumentos de comando, eche un vistazo a la respuesta de Tim. Si solo desea leer una línea de la consola:

#include <stdio.h>

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

Sí, no es seguro, puede saturar el búfer, no verifica el final del archivo, no admite codificaciones y muchas otras cosas. En realidad, ni siquiera pensé si hacía ALGUNA de estas cosas. Estoy de acuerdo, lo arruiné un poco :) Pero ... cuando veo una pregunta como "¿Cómo leer una línea de la consola en C?", Asumo que una persona necesita algo simple, como gets () y no 100 líneas de código. como arriba. En realidad, creo que si intenta escribir esas 100 líneas de código en realidad, cometería muchos más errores de los que hubiera cometido si hubiera elegido get;)

Paul Kapustin
fuente
1
Esto no permite cadenas largas ... - que creo que es el quid de su pregunta.
Tim
2
-1, gets () no debe usarse ya que no verifica los límites.
relajarse el
7
Por otro lado, si está escribiendo un programa para usted mismo y solo necesita leer una entrada, está perfectamente bien. Cuánta seguridad necesita un programa es parte de la especificación; no TIENE que ponerlo como una prioridad siempre.
Martin Beckett
4
@Tim - Quiero mantener toda la historia :)
Paul Kapustin
4
Voto en contra. getsya no existe, por lo que esto no funciona en C11.
Antti Haapala
11

Es posible que deba utilizar un bucle carácter por carácter (getc ()) para asegurarse de que no haya desbordamientos de búfer y no trunque la entrada.

Tim
fuente
9

getline ejemplo ejecutable

Mencionado en esta respuesta pero aquí hay un ejemplo.

Es POSIX 7 , nos asigna memoria y reutiliza el búfer asignado en un bucle muy bien.

Pointer newbs, lea esto: ¿Por qué el primer argumento de getline es un puntero al puntero "char **" en lugar de "char *"?

#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char *line = NULL;
    size_t len = 0;
    ssize_t read = 0;
    while (read != -1) {
        puts("enter a line");
        read = getline(&line, &len, stdin);
        printf("line = %s", line);
        printf("line length = %zu\n", read);
        puts("");
    }
    free(line);
    return 0;
}

implementación glibc

¿Sin POSIX? Tal vez quiera ver la implementación glibc 2.23 .

Se resuelve en getdelim, que es un superconjunto POSIX simple getlinecon un terminador de línea arbitrario.

Duplica la memoria asignada cada vez que se necesita un aumento y parece seguro para subprocesos.

Requiere cierta expansión macro, pero es poco probable que lo haga mucho mejor.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
¿Cuál es el propósito de lenaquí, cuando se lee también proporciona la longitud
Abdul
@Abdul ver man getline. lenes la longitud del búfer existente, 0es mágico y le dice que asigne. Leer es el número de caracteres leídos. El tamaño del búfer podría ser mayor que read.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
6

Muchas personas, como yo, llegan a esta publicación con el título que coincide con lo que se busca, aunque la descripción dice sobre la longitud variable. En la mayoría de los casos, conocemos la longitud de antemano.

Si conoce la longitud de antemano, intente a continuación:

char str1[1001] = { 0 };
fgets(str1, 1001, stdin); // 1000 chars may be read

fuente: https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm

Manohar Reddy Poreddy
fuente
5

Como se sugirió, puede usar getchar () para leer desde la consola hasta que se devuelva un final de línea o un EOF, creando su propio búfer. Puede producirse un crecimiento de búfer dinámicamente si no puede establecer un tamaño de línea máximo razonable.

También puede usar fgets como una forma segura de obtener una línea como una cadena C terminada en nulo:

#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);

Si ha agotado la entrada de la consola o si la operación falló por alguna razón, se devuelve eof == NULL y es posible que el búfer de línea no se modifique (por lo que establecer el primer carácter en '\ 0' es útil).

fgets no sobrecargará la línea [] y se asegurará de que haya un nulo después del último carácter aceptado en una devolución exitosa.

Si se alcanzó el final de la línea, el carácter que precede al '\ 0' final será un '\ n'.

Si no hay terminación '\ n' antes de la terminación '\ 0', es posible que haya más datos o que la siguiente solicitud informe el final del archivo. Tendrá que hacer otros fgets para determinar cuál es cuál. (En este sentido, hacer un bucle con getchar () es más fácil).

En el código de ejemplo (actualizado) anterior, si la línea [sizeof (línea) -1] == '\ 0' después de fgets exitosos, sabrá que el búfer se llenó por completo. Si esa posición está precedida por un '\ n', sabrá que tuvo suerte. De lo contrario, hay más datos o un final de archivo más adelante en stdin. (Cuando el búfer no se llena por completo, es posible que aún esté al final del archivo y que tampoco haya un '\ n' al final de la línea actual. Dado que debe escanear la cadena para encontrar y / o eliminar cualquier '\ n' antes del final de la cadena (el primer '\ 0' en el búfer), me inclino a preferir usar getchar () en primer lugar).

Haga lo que tenga que hacer para lidiar con que todavía haya más línea que la cantidad que leyó como el primer fragmento. Los ejemplos de crecimiento dinámico de un búfer se pueden hacer para que funcionen con getchar o fgets. Hay algunos casos extremos complicados a tener en cuenta (como recordar que la siguiente entrada comience a almacenarse en la posición del '\ 0' que terminó la entrada anterior antes de que se extendiera el búfer).

orcmid
fuente
2

¿Cómo leer una línea de la consola en C?

  • Construir su propia función es una de las formas que le ayudaría a lograr leer una línea desde la consola

  • Estoy usando la asignación de memoria dinámica para asignar la cantidad requerida de memoria requerida

  • Cuando estamos a punto de agotar la memoria asignada, intentamos duplicar el tamaño de la memoria.

  • Y aquí estoy usando un bucle para escanear cada carácter de la cadena uno por uno usando la getchar()función hasta que el usuario ingrese '\n'o EOFcarácter

  • finalmente eliminamos cualquier memoria asignada adicionalmente antes de devolver la línea

//the function to read lines of variable length

char* scan_line(char *line)
{
    int ch;             // as getchar() returns `int`
    long capacity = 0;  // capacity of the buffer
    long length = 0;    // maintains the length of the string
    char *temp = NULL;  // use additional pointer to perform allocations in order to avoid memory leaks

    while ( ((ch = getchar()) != '\n') && (ch != EOF) )
    {
        if((length + 1) >= capacity)
        {
            // resetting capacity
            if (capacity == 0)
                capacity = 2; // some initial fixed length 
            else
                capacity *= 2; // double the size

            // try reallocating the memory
            if( (temp = realloc(line, capacity * sizeof(char))) == NULL ) //allocating memory
            {
                printf("ERROR: unsuccessful allocation");
                // return line; or you can exit
                exit(1);
            }

            line = temp;
        }

        line[length] = (char) ch; //type casting `int` to `char`
    }
    line[length + 1] = '\0'; //inserting null character at the end

    // remove additionally allocated memory
    if( (temp = realloc(line, (length + 1) * sizeof(char))) == NULL )
    {
        printf("ERROR: unsuccessful allocation");
        // return line; or you can exit
        exit(1);
    }

    line = temp;
    return line;
}
  • Ahora puedes leer una línea completa de esta manera:

    char *line = NULL;
    line = scan_line(line);

Aquí hay un programa de ejemplo que usa la scan_line()función:

#include <stdio.h>
#include <stdlib.h> //for dynamic allocation functions

char* scan_line(char *line)
{
    ..........
}

int main(void)
{
    char *a = NULL;

    a = scan_line(a); //function call to scan the line

    printf("%s\n",a); //printing the scanned line

    free(a); //don't forget to free the malloc'd pointer
}

entrada de muestra:

Twinkle Twinkle little star.... in the sky!

salida de muestra:

Twinkle Twinkle little star.... in the sky!
Querubines
fuente
0

Me encontré con el mismo problema hace algún tiempo, esta fue mi solución, espero que ayude.

/*
 * 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
fuente
1
Su código se volvería MUCHO más simple si lo usa gotopara manejar el caso de error. Sin embargo, ¿no cree que podría reutilizarlo tmp_buf, en lugar de mallochacerlo con el mismo tamaño una y otra vez en el ciclo?
Shahbaz
El uso de una única variable global has_errpara informar errores hace que esta función sea insegura y menos cómoda de usar. No lo hagas de esa manera. Ya indicaste un error al devolver NULL. También hay espacio para pensar que los mensajes de error impresos no son una buena idea en una función de biblioteca de propósito general.
Jonathan Leffler
0

En sistemas BSD y Android también puede usar fgetln:

#include <stdio.h>

char *
fgetln(FILE *stream, size_t *len);

Al igual que:

size_t line_len;
const char *line = fgetln(stdin, &line_len);

El lineno tiene terminación nula y contiene \n(o lo que sea que esté usando su plataforma) al final. Se vuelve inválido después de la siguiente operación de E / S en curso.

maravilla.mice
fuente
Sí, la función existe. La advertencia de que no proporciona una cadena terminada en nulo es lo suficientemente grande y problemática como para que probablemente sea mejor no usarla: es peligrosa.
Jonathan Leffler
0

Algo como esto:

unsigned int getConsoleInput(char **pStrBfr) //pass in pointer to char pointer, returns size of buffer
{
    char * strbfr;
    int c;
    unsigned int i;
    i = 0;
    strbfr = (char*)malloc(sizeof(char));
    if(strbfr==NULL) goto error;
    while( (c = getchar()) != '\n' && c != EOF )
    {
        strbfr[i] = (char)c;
        i++;
        strbfr = (void*)realloc((void*)strbfr,sizeof(char)*(i+1));
        //on realloc error, NULL is returned but original buffer is unchanged
        //NOTE: the buffer WILL NOT be NULL terminated since last
        //chracter came from console
        if(strbfr==NULL) goto error;
    }
    strbfr[i] = '\0';
    *pStrBfr = strbfr; //successfully returns pointer to NULL terminated buffer
    return i + 1; 
    error:
    *pStrBfr = strbfr;
    return i + 1;
}

fuente
0

La mejor y más sencilla forma de leer una línea desde una consola es usar la función getchar (), mediante la cual almacenará un carácter a la vez en una matriz.

{
char message[N];        /* character array for the message, you can always change the character length */
int i = 0;          /* loop counter */

printf( "Enter a message: " );
message[i] = getchar();    /* get the first character */
while( message[i] != '\n' ){
    message[++i] = getchar(); /* gets the next character */
}

printf( "Entered message is:" );
for( i = 0; i < N; i++ )
    printf( "%c", message[i] );

return ( 0 );

}

Aaron nevalinz
fuente
-3

Esta función debería hacer lo que quieras:

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

 while( !feof(file) )
  {
  fgets( buffer, sizeof(buffer), 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 );

Espero que esto ayude.

David Allan Finch
fuente
1
Usted debe hacer fgets( buffer, sizeof(buffer), file );no sizeof(buffer)-1. fgetsdeja espacio para el nulo de terminación.
user102008
Tenga en cuenta que while (!feof(file))siempre está mal y este es solo un ejemplo más de uso erróneo.
Jonathan Leffler