Cum se citește o linie din consolă în C? (Programare, C, Io, Consolă, Stdin)

pbreault a intrebat.

Care este cel mai simplu mod de a citi o linie completă într-un program de consolă în CTextul introdus poate avea o lungime variabilă și nu putem face nicio presupunere cu privire la conținutul său.

14 răspunsuri
Johannes Schaub – litb

Aveți nevoie de o gestionare dinamică a memoriei și utilizați fgets pentru a citi linia. Cu toate acestea, se pare că nu există nicio modalitate de a vedea câte caractere a citit. Așa că folosiți 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) == '
')
            break;
    }
    *line = '';
    return linep;
}

Notă: Nu folosiți niciodată gets ! Nu face verificarea limitelor și poate depăși bufferul dvs.

Comentarii

  • Atenție – trebuie să verificați rezultatul realocării. Dar dacă nu reușește, atunci cel mai probabil există probleme mai grave. –  > Por Tim.
  • Probabil că ați putea îmbunătăți puțin eficiența dacă ați face fgets cu bufferul și ați verifica dacă aveți caracterul newline la sfârșit. Dacă nu aveți, realocați bufferul de acumulare, copiați în el și faceți fgets din nou. –  > Por Paul Tomblin.
  • Această funcție are nevoie de o corecție: linia „len = lenmax;” de după realocare ar trebui fie să preceadă realocarea, fie să fie „len = lenmax >> 1;” – sau un alt echivalent care să țină cont de faptul că jumătate din lungime este deja folosită. –  > Por Matt Gallagher.
  • @Johannes, ca răspuns la întrebarea ta, abordarea lui @Paul POATE fi mai rapidă în majoritatea implementărilor libc (adică reintrantă), deoarece abordarea ta blochează implicit stdin pentru fiecare caracter, în timp ce abordarea lui îl blochează o dată pentru fiecare buffer. Ați putea utiliza metoda mai puțin portabilă fgetc_unlocked dacă siguranța firelor de execuție nu este o preocupare, dar performanța este. –  > Por vladr.
  • Rețineți că această getline() este diferită de standardul POSIX getline() de POSIX. –  > Por Jonathan Leffler.
dmityugov

Dacă utilizați biblioteca GNU C sau o altă bibliotecă compatibilă cu POSIX, puteți utiliza funcția getline() și să treceți stdin către aceasta pentru fluxul de fișiere.

Anish Jhaveri

O implementare foarte simplă, dar nesigură, pentru a citi linia pentru alocarea statică:

char line[1024];

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

O implementare mai sigură, fără posibilitatea de depășire a bufferului, dar cu posibilitatea de a nu citi întreaga linie, este:

char line[1024];

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

Nu „diferența de unu” dintre lungimea specificată la declararea variabilei și lungimea specificată în șirul de format. Acesta este un artefact istoric.

Paul Kapustin

Deci, dacă ați căutat argumente de comandă, aruncați o privire la răspunsul lui Tim. dacă doriți doar să citiți o linie din consolă:

#include <stdio.h>

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

Da, nu este sigur, puteți face depășiri ale bufferului, nu verifică sfârșitul fișierului, nu suportă codificări și multe alte lucruri.De fapt, nici măcar nu m-am gândit dacă face vreunul dintre aceste lucruri. Sunt de acord că am cam dat-o în bară :)Dar…când văd o întrebare de genul „Cum să citesc o linie din consolă în C?”, presupun că o persoană are nevoie de ceva simplu, cum ar fi gets() și nu de 100 de linii de cod ca mai sus.De fapt, cred că, dacă ai încerca să scrii acele 100 de linii de cod în realitate, ai face mult mai multe greșeli, decât ai fi făcut dacă ai fi ales gets 😉

Comentarii

  • Acest lucru nu permite șiruri lungi… – ceea ce cred că este esența întrebării sale. –  > Por Tim.
  • -1, gets() nu ar trebui să fie folosit deoarece nu face verificarea limitelor. –  > Por unwind.
  • Pe de altă parte, dacă scrieți un program pentru dumneavoastră și trebuie doar să citiți o intrare, acest lucru este perfect în regulă. Cât de multă securitate are nevoie un program face parte din specificații – nu TREBUIE să o puneți ca prioritate de fiecare dată. –  > Por Martin Beckett.
  • @Tim – Vreau să păstrez toată istoria 🙂 –  > Por Paul Kapustin.
  • Votat în jos. gets nu mai există, deci nu funcționează în C11. –  > Por Antti Haapala.
Tim

S-ar putea să fie nevoie să folosiți o buclă caracter cu caracter (getc()) pentru a vă asigura că nu aveți depășiri de memorie tampon și că nu trunchiați intrarea.

Ciro Santilli新疆棉花TRUMP BAN BAD

getline Exemplu executabil

getline a fost menționat pe acest răspuns, dar iată un exemplu.

Acesta este POSIX 7, , ne alocă memorie și reutilizează frumos bufferul alocat pe o buclă.

Noii începători în domeniul pointerilor, citiți acest lucru: De ce este primul argument al getline un pointer la pointerul „char**” în loc de „char*”?

main.c

#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 (1) {
        puts("enter a line");
        read = getline(&line, &len, stdin);
        if (read == -1)
            break;
        printf("line = %s", line);
        printf("line length = %zu
", read);
        puts("");
    }
    free(line);
    return 0;
}

Compilați și rulați:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out

Rezultat: acest lucru se afișează pe therminal:

enter a line

Apoi, dacă tastați:

asdf

și apăsați enter, apare următorul lucru:

line = asdf
line length = 5

urmat de altul:

enter a line

Sau de la o țeavă la stdin:

printf 'asdf
qwer
' | ./main.out

dă:

enter a line
line = asdf
line length = 5

enter a line
line = qwer
line length = 5

enter a line

Testat pe Ubuntu 20.04.

implementarea glibc

Nu există POSIX? Poate doriți să vă uitați la implementarea glibc 2.23.

Aceasta rezolvă la getdelim, , care este un simplu superset POSIX al lui getline cu un terminator de linie arbitrar.

Aceasta dublează memoria alocată ori de câte ori este necesară o creștere și pare a fi sigură pentru fire de execuție.

Necesită o oarecare expansiune de macro, dar este puțin probabil să faceți ceva mai bun.

Comentarii

  • Care este scopul lui len aici, în condițiile în care read furnizează și lungimea –  > Por Abdul.
  • @Abdul vezi man getline. len este lungimea tamponului existent, 0 este magic și îi spune să aloce. Read este numărul de caractere citite. Dimensiunea bufferului poate fi mai mare decât read. –  > Por Ciro Santilli新疆棉花TRUMP BAN BAD.
orcmid

După cum s-a sugerat, puteți utiliza getchar() pentru a citi din consolă până când se returnează un sfârșit de linie sau un EOF, construindu-vă propriul buffer. Creșterea dinamică a bufferului poate apărea dacă nu puteți stabili o dimensiune maximă rezonabilă a liniei.

Puteți utiliza, de asemenea, fgets ca o modalitate sigură de a obține o linie sub forma unui șir C cu terminație nulă:

#include <stdio.h>

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

char *eof;

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

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

Dacă ați epuizat intrarea în consolă sau dacă operațiunea a eșuat dintr-un motiv oarecare, se returnează eof == NULL, iar bufferul liniei poate rămâne neschimbat (de aceea este utilă setarea primului caracter la „”).

fgets nu va umple excesiv linia[] și se va asigura că există un null după ultimul caracter acceptat la o întoarcere reușită.

În cazul în care s-a ajuns la sfârșitul liniei, caracterul care precede caracterul final „” va fi un „
„.

În cazul în care nu există un „
” de încheiere înainte de „” de sfârșit, este posibil să existe mai multe date sau ca următoarea cerere să raporteze un sfârșit de fișier. Va trebui să mai efectuați un alt fgets pentru a determina care dintre acestea este cazul. (În această privință, bucla cu getchar() este mai ușoară).

În exemplul de cod (actualizat) de mai sus, dacă line[sizeof(line)-1] == ‘’ după un fgets reușit, știți că bufferul a fost umplut complet. Dacă poziția respectivă este urmată de un „
„, știți că ați avut noroc. În caz contrar, în stdin există fie mai multe date, fie un end-of-file. (În cazul în care bufferul nu este umplut complet, este posibil să vă aflați încă la un capăt de fișier și, de asemenea, este posibil să nu existe un „
” la sfârșitul liniei curente. Deoarece trebuie să scanați șirul pentru a găsi și/sau elimina orice „
” înainte de sfârșitul șirului (primul „” din buffer), înclin să prefer să folosesc getchar() în primul rând).

Faceți ceea ce trebuie să faceți pentru a face față faptului că mai există încă mai multe rânduri decât cantitatea pe care ați citit-o ca fiind primul chunk. Exemplele de creștere dinamică a unui buffer pot fi făcute să funcționeze fie cu getchar, fie cu fgets. Există câteva cazuri dificile de care trebuie să aveți grijă (cum ar fi să nu uitați să faceți ca următoarea intrare să înceapă să stocheze la poziția „” care a încheiat intrarea anterioară înainte ca bufferul să fie extins).

Cherubim

Cum se citește o linie din consolă în C?

  • Construirea unei funcții proprii, este una dintre modalitățile care v-ar ajuta să realizați citirea unei linii din consolă

  • Eu folosesc alocarea dinamică a memoriei pentru a aloca cantitatea necesară de memorie necesară

  • Când suntem pe cale să epuizăm memoria alocată, încercăm să dublăm dimensiunea memoriei

  • Și aici folosesc o buclă pentru a scana fiecare caracter al șirului, unul câte unul, folosind getchar() până când utilizatorul introduce '
    '
    sau EOF caracter

  • în cele din urmă eliminăm orice memorie alocată suplimentar înainte de a returna linia

//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()) != '
') && (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`
        length++;
    }
    line[length + 1] = ''; //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;
}
  • Acum puteți citi o linie completă în acest mod :

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

Iată o linie exemplu de program care utilizează scan_line() funcția :

#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
",a); //printing the scanned line

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

eșantion de intrare :

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

eșantion ieșire :

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

dsm

M-am confruntat cu aceeași problemă cu ceva timp în urmă, aceasta a fost soluția mea, sper că vă ajută.

/*
 * 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!
");
        has_err = true;

        return NULL;
    }

    while(!line_read){
        if((tmp_buf = malloc(DEFAULT_BUFFER)) == NULL){
            fprintf(stderr, "readLine: Unable to allocate temporary buffer!
");
            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] == '
') /* 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!
");
            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
");
            free(tmp_buf);
            if(buffer != NULL)
                free(buffer);
            has_err = true;

            return NULL;
        }

        free(tmp_buf);
        iteration++;
    }

    return buffer;
}

Comentarii

  • Codul dvs. ar deveni MULT mai simplu dacă ați folosi goto pentru a gestiona cazul de eroare. Cu toate acestea, nu credeți că ați putea reutiliza tmp_buf, , în loc de mallocde a o introduce cu aceeași dimensiune în buclă, iar și iar? –  > Por Shahbaz.
  • Utilizarea unei singure variabile globale has_err pentru raportarea erorilor face ca această funcție să fie nesigură pentru fire și mai puțin confortabilă de utilizat. Nu o faceți în acest mod. Deja indicați o eroare prin returnarea lui NULL. Există, de asemenea, posibilitatea de a crede că mesajele de eroare tipărite nu sunt o idee bună într-o funcție de bibliotecă de uz general. –  > Por Jonathan Leffler.
minune.mice

Pe sistemele BSD și Android puteți utiliza și fgetln:

#include <stdio.h>

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

Astfel:

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

The line nu are terminație nulă și conține
(sau orice altceva folosește platforma dvs.) la sfârșit. Devine invalid după următoarea operațiune de intrare/ieșire pe flux.

Comentarii

  • Da, funcția există. Avertismentul că nu oferă un șir de caractere cu terminație nulă este suficient de mare și problematic încât probabil că este mai bine să nu o folosiți – este periculoasă. –  > Por Jonathan Leffler.
user2074102

Ceva de genul acesta:

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()) != '
' && 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] = '';
    *pStrBfr = strbfr; //successfully returns pointer to NULL terminated buffer
    return i + 1; 
    error:
    *pStrBfr = strbfr;
    return i + 1;
}

Aaron nevalinz

Cel mai bun și mai simplu mod de a citi o linie dintr-o consolă este folosind funcția getchar(), prin care veți stoca câte un caracter într-un 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] != '
' ){
    message[++i] = getchar(); /* gets the next character */
}

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

return ( 0 );

}

Antonin GAVREL

Iată o implementare minimă pentru a face acest lucru, partea bună este că nu va păstra „
„, însă trebuie să îi dai o dimensiune de citit pentru securitate:

#include <stdio.h>
#include <errno.h>

int sc_gets(char *buf, int n)
{
    int count = 0;
    char c;

    if (__glibc_unlikely(n <= 0))
        return -1;

    while (--n && (c = fgetc(stdin)) != '
')
        buf[count++] = c;
    buf[count] = '';

    return (count != 0 || errno != EAGAIN) ? count : -1;
}

Testați cu:

#define BUFF_SIZE 10

int main (void) {
    char buff[BUFF_SIZE];

    sc_gets(buff, sizeof(buff));
    printf ("%s
", buff);

    return 0;
}

NB: Sunteți limitat la INT_MAX pentru a găsi întoarcerea de linie, ceea ce este mai mult decât suficient.

David Allan Finch

Această funcție ar trebui să facă ceea ce doriți:

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, "
" ) break;
  }

 return result;
 }

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

Sper că vă ajută.

Comentarii

  • Ar trebui să faceți fgets( buffer, sizeof(buffer), file ); not sizeof(buffer)-1. fgets lasă spațiu pentru terminarea lui null. –  > Por user102008.
  • Rețineți că while (!feof(file)) este întotdeauna greșit și acesta este doar un alt exemplu de utilizare eronată. –  > Por Jonathan Leffler.