Back to homepage

La saisie textuelle sous la console en C

Cet article est en cours de ré-écritre, complétion.
La version affichée ci-dessous est un brouillon incomplet et un document de travail.

1. Introduction
    a. Définition
    b. La fonction fflush
2. La lecture de caractères
    a. Le lecture du caractère
    b. Le filtrage
3. La lecture de chaînes
4. Les autres types de données
5. Saisie interactive
    a. Readline
    b. CLIF
6. L'affichage des erreurs
7. Conclusion

1. Introduction

a. Définition

En C, un flux est un canal destiné à transmettre ou à recevoir des données.
Il peut s'agir de fichier ou de périphériques.
On peut y accéder via des fonction d'accès directe ou via mise en cache.
La librairie stdio.h en définit trois pour les périphériques de communications de base la console :

stdinEntrée standard (clavier)
stdoutSortie standard (écran)
stderrSortie erreur (écran également)

Ansi, pour afficher une erreur, on comprendra qu'il faille utiliser la fonction fprintf et non la fonction printf :

/* printf("message") correspond à fprintf(stdout, "message"); */
printf("Ceci est un texte d'information.\n");

fprintf(stderr, "Ceci est une erreur.\n");

On verra un peu plus tard qu'il existe des fonctions spécifiques pour cela.

b. La fonction fflush

La fonction fflush permet de vider le cache du flux passé en paramètre.

La légende par laquelle fflush(stdin) pourrait servir à vider le tampon du flux d'entrée avant une saisie est rigoureusement fausse.
En effet, fflush est décrit (cf.
man) comme étant destinée aux flux ouverts en écriture, ce qui n'est pas vraiment le cas d'un flux d'entrée !

2. La lecture de caractères

a. Le lecture du caractère

Lire un caractère en entrée est opération qui peut souvent nous réserver des surprises.
Un retour chariot ou un espace d'une saisie précédente est considéré par le système comme une réponse correcte ;
c'est donc au programmeur de bien gérer le filtrage des caractères autorisés.

Les quatres fonctions suivantes qui suivent sont quasi-identiques :

Le retour-chariot est laissé dans le tampon du flux après une saisie qui aura été validé par la touche Entrée.
Ce caractère sera interprété comme une valeur lue lors d'un prochain appel à l'une de ces fonctions.
Le plus propre est de vidanger le flux après un appel à l'une de ces fonctions.

/* lecture d'un caractère */
char c;
c = getchar();

/* vidange du flux */
while(getchar() != '\n');

b. Le filtrage

Le filtrage des informations d'entrée est primordiale, l'utilisateur est le seul (ou presque) élément imprévisible dans l'éxécution d'un programme.
Un filtrage pour une saisie de caractère est facilement implémentable via la boucle do/while ; voici quelques morceaux de code :

/* saisie d'un caractère 'visible' */
char c;
while((c = getchar()) <= 0x20);

/* vidange du flux */
while(getchar() != '\n');
/* saisie d'un chiffre */
char c;
do
	c = getchar();
while(c < '0' || c > '9');

/* vidange du flux */
while(getchar() != '\n');
/* saisie d'une confirmation */
char c;
printf(" [y/n] ?");

/* le ou avec la valeur 0x20 permet
   de forcer la casse en minuscule */
do
	c = getchar() | 0x20;
while (c != 'y' && c != 'n');

/* vidange du flux */
while(getchar() != '\n');

3. La lecture de chaînes

Au niveau de la lecture de chaines clavier, on peut isoler trois fonctions qui fonctionnent différemment :

La fonction scanf laisse dans le tampon du flux les caractères d'espacements tel que l'espace, la tabulation ou le retour chariot.
En outre, elle ne permet que la saisie d'un mot continu et non d'une phrase à cause des espaces qui servent à délimiter les différents champs.

gets lit une ligne entière au clavier et retire le retour chariot final du tampon du flux.
Il n'y a pas de contrôle de la longueur des informations entrées, il y a donc un risque de débordement.
Pour cette raison, cette fonction est très fortement déconseillée.

fgets, comme gets permet de lire des lignes entières avec des espaces.
Le retour chariot final est également récupéré du flux mais il faut savoir qu'il sera placé dans le tampon qui retourne les données.
Le programmeur a donc à sa charge le fait de le retirer :

/* lecture d'une ligne de texte */
fgets(buffer, sizeof(buffer), stream);

/* on retire le '\n' */
buffer[strlen(buffer)-1] = '\0';

b. Filtrage

La manière généralement la plus élégante et la plus puissance est l'utilisation des expressions rationnelles.

static bool compiled;
static regex_t preg;
const char *email_regex = "[a-zA-Z0-9+._-]+@[a-zA-Z0-9+._-]+\\.[a-zA-Z0-9+._-]{1,3}";
bool is_valid_email(char *email_address)
{
    if (!compiled)
    {
        if (!regcomp(&preg, str_regex, 0))
        {
            fprintf(stderr, "Internal error: unable to compile regex /%s/\n", email_regex);
            return true; /* fail to check , by default accept */
        }
        compiled = true;
    }
    return (regexec(&preg, email_address, 0, NULL, 0) == 0)
}

Des fonctions systèmes permettent de valider des formats spécifiques, comme une adresse IP :

bool is_valid_ip(const char *address)
{
        struct sockaddr_in sa;
        return (inet_pton(AF_INET, address, &(sa.sin_addr)) == 1);
}

Certaines vérifications supplémentaires peuvent être adjointes, comme vérifier qu'une adresse IP est un masque de sous-réseau valide :

bool is_valid_mask(const char *mask)
{
        struct sockaddr_in sa;
        uint32_t mask_raw;

        if (inet_pton(AF_INET, mask, &(sa.sin_addr)) == 1)
        {
                mask_raw = ntohl(*((uint32_t *)&sa.sin_addr));
                while ((mask_raw & 0x80000000) > 0)
                {
                        mask_raw <<= 1;
                }
                return (mask_raw == 0);
        }
        return false;
}

Ou bien si un fichier existe :

bool is_valid_file(const char *filename)
{
        struct file_stat;

        return (stat(filename, &file_stat) == 0);
}

4. Les autres types de données

La fonction scanf permet la lecture d'information de type entier, flottant ou personnalisé.

Le retour chariot ainsi que les données qui n’intéressent pas la fonction sont laissée dans le flux.
On doit par conséquent vidanger le flux manuellement après une lecture avec scanf.

/* lecture d'un entier */
int i;
scanf("%d", &i);

/* vidange du flux */
while(getchar() != '\n');

Mot de passe :

#include 

#define KB_RETURN	13
#define KB_DEL		8

int get_password(char *dest, size_t size_dest)
{
        char c;
        int i;

        if (size_dest < 1)
                return;

        i = 0;
        while (i < size_dest-1)
        {
                c = getchar();
                switch(c)
                {
                case KB_RETURN:
                        dest[i] = '\0';
                        i++;
                        return i;
                case KB_DEL:
                        if (i >  0)
                                i--;
                        break;
                default:
                        dest[i] = c;
                        i++;
                }

        }
        dest[i] = '\0';
        i++;
        return i;
}

5. Saisie interactive

5a. Readline

...

5b. CLIF

...

6. L'affichage des erreurs

Quelques fonctions d'affichages d'erreurs :

void error(int status, int errnum, const char *format, ...);
void error_at_line(int status, int errnum, const char *filename,
                   unsigned int linenum, const char *format, ...);
perror
strerror
err

To display current file and line where error occurs:

#include 
#include 

error_at_line(0, 0, __FILE__, __LINE__, "Error");

To quit program after displaying error:

#include 
#include 

error_at_line(0, errno, __FILE__, __LINE__, "Error");

For errno function:

#include 
#include 
#include 

error_at_line(EXIT_FAILURE, errno, __FILE__, __LINE__, "Fatal error");

7. Conclusion

La saisie caractère par caractère laissera forcément le retour chariot dans le tampon de flux.
Il sera donc plus efficace d'utiliser la fonction la plus simple : getchar

Pour les saisies de chaines, la fonction qui permet de gérer proprement une ligne entière est fgets.
Il sera nécessaire de retirer le retour chariot qui sera retournée dans le buffer, à la suite des données utiles (voir plus haut).

La fonction scanf sera egalement utile pour les saisies personnalisées (décimal, hexadécimal, mots simples, etc.).

Pour les fonctions getchar et scanf, il conviendra de retirer les données superflues ainsi que le retour chariot pour les fonctions opérant par la suite sur le flux (voir plus haut).

contact/mail protection