Comment lire une ligne de la console en C?


108

Quelle est la manière la plus simple de lire une ligne complète dans un programme de console C Le texte saisi peut avoir une longueur variable et nous ne pouvons faire aucune hypothèse sur son contenu.


pourriez-vous clarifier, s'il vous plaît? comme @Tim l'a dit ci-dessous, ce que vous demandez est déroutant :)
warren

Réponses:


81

Vous avez besoin d'une gestion dynamique de la mémoire et utilisez la fgetsfonction pour lire votre ligne. Cependant, il ne semble y avoir aucun moyen de voir combien de caractères il a lu. Donc, vous utilisez 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;
}

Remarque : n'utilisez jamais gets! Il ne vérifie pas les limites et peut déborder de votre tampon


Mise en garde - besoin de vérifier le résultat de la réallocation là-bas. Mais si cela échoue, il y aura probablement des problèmes pires.
Tim

4
Vous pourriez probablement améliorer un peu l'efficacité en faisant des fgets avec un tampon et en vérifiant si vous avez le caractère de nouvelle ligne à la fin. Si vous ne le faites pas, réallouez votre tampon d'accumulation, copiez-y et recommencez.
Paul Tomblin

3
Cette fonction nécessite une correction: la ligne "len = lenmax;" après la réallocation doit soit précéder la réallocation, soit être "len = lenmax >> 1;" - ou tout autre équivalent qui tient compte du fait que la moitié de la longueur est déjà utilisée.
Matt Gallagher

1
@Johannes, en réponse à votre question, l'approche de @ Paul POURRAIT être plus rapide sur la plupart des implémentations de libc (c'est-à-dire réentrantes), puisque votre approche verrouille implicitement stdin pour chaque caractère alors que son verrouillage le verrouille une fois par tampon. Vous pouvez utiliser le moins portable fgetc_unlockedsi la sécurité des threads n'est pas un problème mais les performances le sont.
vladr

3
Notez que ceci getline()est différent de la getline()fonction standard POSIX .
Jonathan Leffler

28

Si vous utilisez la bibliothèque GNU C ou une autre bibliothèque compatible POSIX, vous pouvez l'utiliser getline()et la transmettre stdinpour le flux de fichiers.


16

Une implémentation très simple mais non sécurisée pour lire la ligne pour l'allocation statique:

char line[1024];

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

Une implémentation plus sûre, sans possibilité de débordement de tampon, mais avec la possibilité de ne pas lire toute la ligne, est:

char line[1024];

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

Pas la «différence de un» entre la longueur spécifiée déclarant la variable et la longueur spécifiée dans la chaîne de format. C'est un artefact historique.


14
Ce n'est pas du tout sûr. Il souffre exactement du même problème pourquoi a getsété complètement supprimé de la norme
Antti Haapala

6
Note du modérateur: Le commentaire ci-dessus fait référence à une révision précédente de la réponse.
Robert Harvey

13

Donc, si vous cherchiez des arguments de commande, jetez un œil à la réponse de Tim. Si vous voulez juste lire une ligne depuis la 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;
}

Oui, ce n'est pas sécurisé, vous pouvez faire un dépassement de tampon, cela ne vérifie pas la fin du fichier, il ne prend pas en charge les encodages et bien d'autres choses. En fait, je ne me suis même pas demandé si ça faisait quoi que ce soit. Je suis d'accord, j'ai un peu foiré :) Mais ... quand je vois une question comme "Comment lire une ligne de la console en C?", Je suppose qu'une personne a besoin de quelque chose de simple, comme gets () et non 100 lignes de code comme ci-dessus. En fait, je pense que si vous essayez d'écrire ces 100 lignes de code en réalité, vous feriez beaucoup plus d'erreurs que vous n'auriez fait si vous aviez choisi get;)


1
Cela ne permet pas de longues chaînes ... - ce qui, je pense, est au cœur de sa question.
Tim du

2
-1, gets () ne doit pas être utilisé car il ne vérifie pas les limites.
détendez-vous

7
D'un autre côté, si vous écrivez un programme pour vous-même et que vous avez juste besoin de lire une entrée, c'est parfaitement bien. Le niveau de sécurité dont un programme a besoin dépend de la spécification - vous n'avez pas à le mettre en priorité à chaque fois.
Martin Beckett

4
@Tim - Je veux garder toute l'histoire :)
Paul Kapustin

4
Voté contre. getsn'existe plus, donc cela ne fonctionne pas en C11.
Antti Haapala le

11

Vous devrez peut-être utiliser une boucle caractère par caractère (getc ()) pour vous assurer qu'il n'y a pas de débordement de tampon et de ne pas tronquer l'entrée.


9

getline exemple exécutable

Mentionné sur cette réponse mais voici un exemple.

C'est POSIX 7 , nous alloue de la mémoire, et réutilise joliment le tampon alloué sur une boucle.

Pointer newbs, lisez ceci: Pourquoi le premier argument de getline est-il un pointeur vers le pointeur "char **" au lieu 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;
}

implémentation de la glibc

Pas de POSIX? Peut-être voulez-vous regarder l' implémentation de la glibc 2.23 .

Il résout en getdelim, qui est un simple sur-ensemble POSIX getlineavec un terminateur de ligne arbitraire.

Il double la mémoire allouée chaque fois qu'une augmentation est nécessaire et semble sûr pour les threads.

Cela nécessite une certaine expansion macro, mais il est peu probable que vous fassiez beaucoup mieux.


Quel est le but de lenici, lorsque lu fournit la longueur aussi
Abdul

@Abdul voir man getline. lenest la longueur du tampon existant, 0est magique et lui dit d'allouer. La lecture correspond au nombre de caractères lus. La taille du tampon peut être supérieure à read.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功


5

Comme suggéré, vous pouvez utiliser getchar () pour lire depuis la console jusqu'à ce qu'une fin de ligne ou un EOF soit renvoyé, en construisant votre propre tampon. Une croissance dynamique du tampon peut se produire si vous ne parvenez pas à définir une taille de ligne maximale raisonnable.

Vous pouvez également utiliser des fgets comme moyen sûr d'obtenir une ligne sous forme de chaîne C terminée par un null:

#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 vous avez épuisé l'entrée de la console ou si l'opération a échoué pour une raison quelconque, eof == NULL est renvoyé et le tampon de ligne peut rester inchangé (c'est pourquoi définir le premier caractère sur '\ 0' est pratique).

fgets ne remplira pas trop la ligne [] et s'assurera qu'il y a un nul après le dernier caractère accepté lors d'un retour réussi.

Si la fin de ligne a été atteinte, le caractère précédant le «\ 0» de fin sera un «\ n».

S'il n'y a pas de fin '\ n' avant la fin '\ 0', c'est peut-être qu'il y a plus de données ou que la prochaine requête signalera la fin de fichier. Vous devrez faire un autre fgets pour déterminer lequel est lequel. (À cet égard, faire une boucle avec getchar () est plus facile.)

Dans l'exemple de code (mis à jour) ci-dessus, si la ligne [sizeof (line) -1] == '\ 0' après des fgets réussis, vous savez que le tampon a été complètement rempli. Si cette position est précédée d'un '\ n', vous savez que vous avez eu de la chance. Sinon, il y a soit plus de données, soit une fin de fichier en avance dans stdin. (Lorsque le tampon n'est pas complètement rempli, vous pouvez toujours être à une fin de fichier et il se peut qu'il n'y ait pas non plus de «\ n» à la fin de la ligne actuelle. Puisque vous devez analyser la chaîne pour trouver et / ou éliminer tout '\ n' avant la fin de la chaîne (le premier '\ 0' du tampon), j'ai tendance à préférer utiliser getchar () en premier lieu.)

Faites ce que vous devez faire pour faire en sorte qu'il y ait encore plus de lignes que le montant que vous lisez comme premier morceau. Les exemples de croissance dynamique d'un tampon peuvent être conçus pour fonctionner avec getchar ou fgets. Il y a quelques cas de bord délicats à surveiller (comme se souvenir que l'entrée suivante commence à stocker à la position du '\ 0' qui a mis fin à l'entrée précédente avant l'extension du tampon).


2

Comment lire une ligne de la console en C?

  • Construire votre propre fonction est l'un des moyens qui vous aiderait à lire une ligne depuis la console

  • J'utilise l' allocation de mémoire dynamique pour allouer la quantité de mémoire requise

  • Lorsque nous sommes sur le point d'épuiser la mémoire allouée, nous essayons de doubler la taille de la mémoire

  • Et ici, j'utilise une boucle pour scanner chaque caractère de la chaîne un par un en utilisant la getchar()fonction jusqu'à ce que l'utilisateur entre '\n'ou EOFcaractère

  • enfin nous supprimons toute mémoire supplémentaire allouée avant de renvoyer la ligne

//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;
}
  • Vous pouvez maintenant lire une ligne complète de cette façon:

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

Voici un exemple de programme utilisant la scan_line()fonction:

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

exemple d'entrée:

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

exemple de sortie:

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

0

J'ai rencontré le même problème il y a quelque temps, c'était ma solution, j'espère que cela aide.

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

1
Votre code deviendrait BEAUCOUP plus simple si vous l'utilisiez gotopour gérer le cas d'erreur. Néanmoins, ne pensez-vous pas que vous pourriez le réutiliser tmp_buf, au lieu de mallocle réutiliser avec la même taille encore et encore dans la boucle?
Shahbaz

L'utilisation d'une seule variable globale has_errpour signaler les erreurs rend cette fonction peu sûre pour les threads et moins confortable à utiliser. Ne fais pas ça de cette façon. Vous indiquez déjà une erreur en renvoyant NULL. Il est également possible de penser que les messages d'erreur imprimés ne sont pas une bonne idée dans une fonction de bibliothèque à usage général.
Jonathan Leffler

0

Sur les systèmes BSD et Android, vous pouvez également utiliser fgetln:

#include <stdio.h>

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

Ainsi:

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

Le linen'est pas terminé par null et contient \n(ou ce que votre plate-forme utilise) à la fin. Il devient invalide après la prochaine opération d'E / S sur le flux.


Oui, la fonction existe. La mise en garde qu'elle ne fournit pas de chaîne terminée par un nul est suffisamment grande et problématique pour qu'il vaut probablement mieux ne pas l'utiliser - c'est dangereux.
Jonathan Leffler

0

Quelque chose comme ça:

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

0

Le moyen le meilleur et le plus simple de lire une ligne à partir d'une console est d'utiliser la fonction getchar (), dans laquelle vous stockerez un caractère à la fois dans un tableau.

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

}


-3

Cette fonction doit faire ce que vous voulez:

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

J'espère que ça aide.


1
Vous ne devriez fgets( buffer, sizeof(buffer), file );pas sizeof(buffer)-1. fgetslaisse de l'espace pour le null de fin.
user102008

Notez que while (!feof(file))c'est toujours faux et ce n'est qu'un autre exemple d'utilisation erronée.
Jonathan Leffler
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.