Como ler uma linha do console em C?

108

Qual é a maneira mais simples de ler uma linha inteira em um programa de console C? O texto inserido pode ter um comprimento variável e não podemos fazer nenhuma suposição sobre seu conteúdo.

pbreault
fonte
você poderia esclarecer, por favor? como @Tim disse abaixo, é confuso o que você está pedindo :)
warren

Respostas:

81

Você precisa de gerenciamento de memória dinâmico e usar a fgetsfunção para ler sua linha. No entanto, parece não haver maneira de ver quantos caracteres ele lê. Então você usa 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! Ele não faz verificação de limites e pode estourar o buffer

Johannes Schaub - litb
fonte
Advertência - é necessário verificar o resultado do realocamento lá. Mas, se isso falhar, provavelmente haverá problemas piores.
Tim
4
Você provavelmente poderia melhorar um pouco a eficiência fazendo fgets com buffer e verificando se há o caractere de nova linha no final. Do contrário, realoque seu buffer de acumulação, copie-o e fgets novamente.
Paul Tomblin
3
Esta função precisa de uma correção: a linha "len = lenmax;" após o realloc deve preceder o realloc ou deve ser "len = lenmax >> 1;" - ou algum outro equivalente que explique o fato de que metade do comprimento já foi usado.
Matt Gallagher
1
@Johannes, em resposta à sua pergunta, a abordagem de @Paul PODE ser mais rápida na maioria das implementações libc (isto é, reentrantes), uma vez que sua abordagem bloqueia implicitamente stdin para cada personagem enquanto a dele bloqueia uma vez por buffer. Você poderia usar o menos portátil fgetc_unlockedse a segurança do thread não fosse uma preocupação, mas o desempenho, sim.
vladr
3
Observe que este getline() é diferente da getline()função padrão POSIX .
Jonathan Leffler
28

Se estiver usando a biblioteca GNU C ou outra biblioteca compatível com POSIX, você pode usar getline()e passar stdinpara ela para o fluxo de arquivos.

dmityugov
fonte
16

Uma implementação muito simples, mas insegura para ler a linha para alocação estática:

char line[1024];

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

Uma implementação mais segura, sem a possibilidade de estouro de buffer, mas com a possibilidade de não ler a linha inteira, é:

char line[1024];

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

Não a 'diferença por um' entre o comprimento especificado declarando a variável e o comprimento especificado na string de formato. É um artefato histórico.

Jonathan Leffler
fonte
14
Isso não é seguro de forma alguma. Ele sofre exatamente do mesmo problema por que getsfoi removido do padrão completamente
Antti Haapala
6
Nota do moderador: o comentário acima se refere a uma revisão anterior da resposta.
Robert Harvey
13

Portanto, se você estiver procurando por argumentos de comando, dê uma olhada na resposta de Tim. Se você quiser apenas ler uma linha do 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;
}

Sim, não é seguro, você pode fazer buffer overrun, não verifica o fim do arquivo, não suporta codificações e muitas outras coisas. Na verdade, eu nem pensei se ele fazia alguma dessas coisas. Concordo que estraguei tudo :) Mas ... quando vejo uma pergunta como "Como ler uma linha do console em C?", Presumo que uma pessoa precise de algo simples, como gets () e não 100 linhas de código como acima. Na verdade, eu acho, se você tentar escrever essas 100 linhas de código na realidade, você cometerá muito mais erros do que teria feito se tivesse escolhido get;)

Paul Kapustin
fonte
1
Isso não permite cordas longas ... - que eu acho que é o ponto crucial de sua pergunta.
Tim
2
-1, gets () não deve ser usado, pois não faz a verificação de limites.
relaxe
7
Por outro lado, se você está escrevendo um programa para si mesmo e só precisa ler uma entrada, isso está perfeitamente bem. A quantidade de segurança de que um programa precisa faz parte das especificações - você não precisa colocá-la como uma prioridade sempre.
Martin Beckett
4
@Tim - Eu quero manter toda a história :)
Paul Kapustin
4
Votos negativos. getsnão existe mais, portanto, isso não funciona em C11.
Antti Haapala
11

Pode ser necessário usar um loop caractere por caractere (getc ()) para garantir que não haja estouros de buffer e não trunque a entrada.

Tim
fonte
9

getline exemplo executável

Mencionado nesta resposta mas aqui está um exemplo.

É POSIX 7 , aloca memória para nós e reutiliza o buffer alocado em um loop.

Ponteiro newbs, leia isto: Por que o primeiro argumento de getline é um ponteiro para o ponteiro "char **" em vez 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;
}

implementação glibc

Sem POSIX? Talvez você queira dar uma olhada na implementação da glibc 2.23 .

Resolve para getdelim, que é um superconjunto POSIX simples getlinecom um terminador de linha arbitrário.

Ele dobra a memória alocada sempre que um aumento é necessário e parece seguro para threads.

Requer alguma expansão macro, mas é improvável que você faça muito melhor.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fonte
Qual é o propósito de lenaqui, quando a leitura fornece o comprimento também
Abdul
@Abdul see man getline. lené o comprimento do buffer existente, 0é mágico e diz a ele para alocar. Ler é o número de caracteres lidos. O tamanho do buffer pode ser maior que read.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
6

Muitas pessoas, como eu, chegam a este post com o título correspondendo ao que é pesquisado, embora a descrição diga sobre comprimento variável. Na maioria dos casos, sabemos o comprimento de antemão.

Se você sabe o comprimento de antemão, tente abaixo:

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

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

Manohar Reddy Poreddy
fonte
5

Conforme sugerido, você pode usar getchar () para ler do console até que um fim de linha ou um EOF seja retornado, construindo seu próprio buffer. O crescimento do buffer pode ocorrer dinamicamente se você não puder definir um tamanho de linha máximo razoável.

Você também pode usar o uso de fgets como uma maneira segura de obter uma linha como uma string C terminada em 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);

Se você esgotou a entrada do console ou se a operação falhou por algum motivo, eof == NULL é retornado e o buffer de linha pode estar inalterado (é por isso que definir o primeiro caractere como '\ 0' é útil).

fgets não irá sobrecarregar a linha [] e garantirá que haja um nulo após o último caractere aceito em um retorno bem-sucedido.

Se o fim da linha for atingido, o caractere precedendo o final '\ 0' será um '\ n'.

Se não houver término '\ n' antes do término '\ 0', pode ser que haja mais dados ou que a próxima solicitação relatará o fim do arquivo. Você terá que fazer outro fgets para determinar qual é qual. (Nesse sentido, fazer um loop com getchar () é mais fácil.)

No código de exemplo (atualizado) acima, se linha [sizeof (linha) -1] == '\ 0' após fgets bem-sucedidos, você sabe que o buffer foi preenchido completamente. Se essa posição for precedida por um '\ n', você sabe que teve sorte. Caso contrário, haverá mais dados ou um fim de arquivo adiante em stdin. (Quando o buffer não está completamente preenchido, você ainda pode estar no final do arquivo e também pode não haver um '\ n' no final da linha atual. Já que você tem que examinar a string para encontrar e / ou elimine qualquer '\ n' antes do final da string (o primeiro '\ 0' no buffer), estou inclinado a preferir usar getchar () em primeiro lugar.)

Faça o que for necessário para lidar com o fato de ainda haver mais linha do que a quantidade que você leu como o primeiro pedaço. Os exemplos de crescimento dinâmico de um buffer podem funcionar com getchar ou fgets. Existem alguns casos extremos complicados a serem observados (como lembrar que a próxima entrada começa a ser armazenada na posição '\ 0' que encerrou a entrada anterior antes que o buffer fosse estendido).

orcmid
fonte
2

Como ler uma linha do console em C?

  • Construir sua própria função é uma das maneiras que o ajudaria a conseguir ler uma linha do console

  • Estou usando a alocação de memória dinâmica para alocar a quantidade necessária de memória necessária

  • Quando estamos prestes a esgotar a memória alocada, tentamos dobrar o tamanho da memória

  • E aqui estou usando um loop para verificar cada caractere da string, um por um, usando a getchar()função até que o usuário insira '\n'ou EOFcaractere

  • finalmente removemos qualquer memória alocada adicional antes de retornar a linha

//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;
}
  • Agora você pode ler uma linha completa desta forma:

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

Aqui está um programa de exemplo usando a scan_line()função:

#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
}

amostra de entrada:

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

amostra de saída:

Twinkle Twinkle little star.... in the sky!
Querubim
fonte
0

Me deparei com o mesmo problema há algum tempo, essa foi minha solução, espero que ajude.

/*
 * 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
fonte
1
Seu código se tornaria MUITO mais simples se você o usasse gotopara lidar com o caso de erro. No entanto, você não acha que poderia reutilizar tmp_buf, em vez de malloccolocá-lo com o mesmo tamanho repetidamente no loop?
Shahbaz
Usar uma única variável global has_errpara relatar erros torna essa função thread-insegura e menos confortável de usar. Não faça assim. Você já indica um erro ao retornar NULL. Também há espaço para pensar que as mensagens de erro impressas não são uma boa ideia em uma função de biblioteca de uso geral.
Jonathan Leffler
0

Em sistemas BSD e Android, você também pode usar fgetln:

#include <stdio.h>

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

Igual a:

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

O linenão é terminado em null e contém \n(ou o que quer que sua plataforma esteja usando) no final. Torna-se inválido após a próxima operação de E / S no fluxo.

maravilha. ratos
fonte
Sim, a função existe. A ressalva de que ele não fornece uma string terminada em nulo é suficientemente grande e problemática que provavelmente é melhor não usá-la - é perigoso.
Jonathan Leffler
0

Algo assim:

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;
}

fonte
0

A melhor e mais simples maneira de ler uma linha de um console é usando a função getchar (), por meio da qual você armazenará um caractere por vez em um array.

{
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
fonte
-3

Esta função deve fazer o que você quiser:

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

Eu espero que isso ajude.

David Allan Finch
fonte
1
Você fgets( buffer, sizeof(buffer), file );não deveria sizeof(buffer)-1. fgetsdeixa espaço para o nulo final.
user102008
Observe que while (!feof(file))está sempre errado e este é apenas mais um exemplo de uso incorreto.
Jonathan Leffler