Guide pratique de la programmation de fichiers sous Linux et de la création de bibliothèques

Opérations sur les répertoires et fichiers sous Linux

Arborescence standard de Linux

Windows utilise des commandes DOS, tandis que Linux utilise des commandes shell.

Commandes Linux courantes

  • cd dossier : accéder à un dossier
  • ls : lister les fichiers du répertoire courant
  • touch fichier : créer un fichier
  • rm fichier : supprimer un fichier
  • mkdir repertoire : créer un répertoire
  • rm -rf repertoire : supprimer un répertoire (récursif et forcé)
  • TAB : autocomplétion
  • sudo commande : exécuter avec les droits administrateur
  • pwd : afficher le chemin absolu du répertoire courant

Éditeur vi

vi est un éditeur de texte présent par défaut sur la plupart des systèmes Linux. vim est une version améliorée, mais les systèmes embarqués ne possèdent souvent que vi. Sous Ubuntu, on peut installer vim avec :

sudo apt-get install vim

Pour ouvrir ou créer un fichier : vi nomfichier

Deux modes principaux de vi

Mode commande (accessible via Echap) :

  • : + numéro_ligne : aller à une ligne spécifique
  • G : aller à la fin du fichier
  • yy : copier la ligne courante
  • yx (x = nombre) : copier x lignes à partir de la position courante
  • p : coller le contenu copié
  • :wq : enregistrer et quitter
  • :q : quitter sans enregistrer (si aucune modification)
  • :q! : quitter de force
  • :set nu : afficher les numéros de ligne
  • :dd : supprimer une ligne
  • ESC: gg=G : indenter automatiquement tout le code

Mode insertion (accessible via i) : permet de taper du texte (souris inutilisable).

Pour compiler un programme C avec GCC :

sudo apt-get install gcc
gcc test.c                # produit a.out par défaut
gcc test.c -o hello       # produit un exécutable nommé hello

Les fichiers sous Linux

Les pages de manuel Linux s'utilisent ainsi : man 1 pour les commandes shell, man 2 pour les appels système (open, write, etc.).

Permissions des fichiers

Les permissions sont représentées par trois chiffres octaux (ex: 0755, 0644).

  • 7 = rwx (lecture, écriture, exécution)
  • 6 = rw- (lecture, écriture)
  • 5 = r-x (lecture, exécution)
  • 4 = r-- (lecture seule)
  • 3 = -wx (écriture, exécution)
  • 2 = -w- (écriture seule)
  • 1 = --x (exécution seule)
  • 0 = --- (aucun droit)

Exemple avec ls -l :

-rw-rw-r-- 1 cc cc   41 mai  2 06:53 1q
-rw-rw-r-- 1 cc cc   13 mai  2 05:53 a.c
-rwxrwxr-x 1 cc cc 8656 mai  2 02:04 demo

Pour le fichier a.c : l'utilisateur et le groupe ont les droits rw-, les autres ont r--.

Fonctions d'entrée/sortie de bas niveau (file I/O)

1. open()

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

Retour : un descripteur de fichier (entier) en cas de succès, -1 en cas d'échec.

Paramètres :

  • pathname : chemin du fichier (absolu ou relatif).
  • flags : mode d'ouverture (O_RDONLY, O_WRONLY, O_RDWR) combiné avec O_CREAT, O_TRUNC, O_APPEND, O_EXCL, etc.
  • mode : permissions si création (ex : 0666).

Exemple :

int fd = open("/home/user/fichier.txt", O_RDWR | O_CREAT, 0644);
if (fd == -1) { /* erreur */ }

2. close()

#include <unistd.h>
int close(int fd);

3. write()

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

Écrit count octets depuis buf dans le fichier référencé par fd. Retourne le nombre d'octets écrits, ou -1 en cas d'erreur.

4. read()

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

Lit au plus count octets depuis fd et les stocke dans buf. Retourne le nombre d'octets lus (0 si fin de fichier), -1 en cas d'erreur.

Paramètres de main()

int main(int argc, char *argv[])
{
    return 0;
}

argc = nombre d'arguments (le nom du programme compte), argv = tableau de chaînes (argv[0] = nom du programme, argv[1] = premier arguement, etc.).

Exemple : implémentation simplifiée de la commande cp

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    if (argc != 3) {
        printf("Usage: %s source dest\n", argv[0]);
        return -1;
    }

    int fd_src = open(argv[1], O_RDONLY);
    if (fd_src == -1) { perror("source"); return -2; }

    int fd_dest = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd_dest == -1) { perror("dest"); close(fd_src); return -3; }

    char buffer[128];
    ssize_t bytes;
    while ((bytes = read(fd_src, buffer, sizeof(buffer))) > 0) {
        write(fd_dest, buffer, bytes);
    }

    close(fd_src);
    close(fd_dest);
    return 0;
}

5. lseek()

#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

whence : SEEK_SET (début), SEEK_CUR (position actuelle), SEEK_END (fin). Retourne la nouvelle position (en octets depuis le début) ou -1 en cas d'erreur.

E/S fichier (file I/O) vs E/S standard (stdio)

Caractéristique File I/O (appels système) Standard I/O (bibliothèque C)
Interface open, read, write, close, lseek fopen, fread, fwrite, fclose, fseek
En-tête <unistd.h>, <fcntl.h> <stdio.h>
Bufferisation Pas de buffer utilisateur (buffer noyau) Buffer utilisateur (ligne, bloc, ou aucun)
Portabilité Dépend du système Standard C, portable
Performances Moins d'overhead mais plus d'appels système Moins d'appels système grâce au buffer

Types de bufferisaiton (stdio)

  • Sans buffer : les données sont écrites immédiatement (ex : stderr).
  • Buffer de ligne : les données sont écrites quand un '\n' apparaît ou quand le buffer est plein (ex : stdin/stdout avec un terminal).
  • Buffer complet : les données sont écrites quand le buffer est plein (ex : fichier disque).

Exemple illustrant le buffer (stdio)

#include <stdio.h>

int main() {
    printf("Hello Linux");
    while(1); // boucle infinie, le texte n'apparaît pas car pas de '\n' ni fflush
    return 0;
}

Fonctions stdio

6. fopen()

#include <stdio.h>
FILE *fopen(const char *pathname, const char *mode);

Mode : "r", "r+", "w", "w+", "a", "a+", "rb", "wb", etc. Retourne un pointeur FILE* ou NULL en cas d'rereur.

7. fread()

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

Lit nmemb éléments de size octets chacun depuis stream et les place dans ptr. Retourne le nombre d'éléments lus.

8. fwrite()

#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

Écrit nmemb éléments de size octets depuis ptr vers stream. Retourne le nombre d'éléments écrits.

9. fseek()

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);

Retourne 0 en cas de succès, une valeur non nulle en cas d'erreur. whence : SEEK_SET, SEEK_CUR, SEEK_END.

10. fclose()

#include <stdio.h>
int fclose(FILE *stream);

11. fgets() et fputs()

char *fgets(char *s, int size, FILE *stream);
int fputs(const char *s, FILE *stream);

fgets lit au plus size-1 caractères (inclut le '\n' si présent). fputs écrit la chaîne (sans '\n' ajouté). Contrastes avec gets/puts : gets ne limite pas la taille, puts ajoute un '\n'.

12. fflush()

int fflush(FILE *stream);

Vide le buffer utilisateur vers le noyau.

13. Autres fonctions utiles

  • rewind(FILE *stream) : remet le pointeur au début (équivalent à fseek(fp, 0, SEEK_SET)).
  • ftell(FILE *stream) : retourne la position courante (en octets depuis le début) ou -1L en cas d'erreur.
  • fprintf(FILE *stream, const char *format, ...) : écrit formaté dans un fichier.
  • sprintf(char *str, const char *format, ...) : écrit dans une chaîne.
  • fgetc(FILE *stream) : lit un caractère (retourne int, EOF si fin ou erreur).
  • fputc(int c, FILE *stream) : écrit un caractère (retourne le caractère écrit ou EOF).
  • feof(FILE *stream) : teste si la fin de fichier a été atteinte (non nul si oui).
  • ferror(FILE *stream) : teste s'il y a eu une erreur de lecture/écriture.
  • clearerr(FILE *stream) : efface les indicateurs de fin de fichier et d'erreur.

Exemple : implémentation de la commande cat avec stdio

#include <stdio.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s fichier\n", argv[0]);
        return 1;
    }
    FILE *fp = fopen(argv[1], "r");
    if (!fp) {
        perror("fopen");
        return 2;
    }
    int ch;
    while ((ch = fgetc(fp)) != EOF) {
        fputc(ch, stdout);
    }
    fclose(fp);
    return 0;
}

Bibliothèques statiques et dynamiques

Les bibliothèques se trouvent généralement dans /lib et /usr/lib.

Bibliothèque statique (libxxx.a)

Liée lors de la compilation. Avantage : exécutable autonome et rapide. Inconvénient : taille plus importante.

Création :

gcc -c mylib.c -o mylib.o
ar rcs libmylib.a mylib.o

Utilisation :

gcc main.c -o monprog -L. -lmylib
./monprog

-L. indique de chercher dans le répertoire courant, -lmylib référence libmylib.a (sans le préfixe lib ni l'extension).

Bibliothèque dynamique (libxxx.so)

Liée lors de l'exécution. Avantage : exécutable plus petit. Inconvénient : nécessite que la bibliothèque soit présente sur le système.

Création :

gcc -c mylib.c -o mylib.o
gcc -shared -fpic -o libmylib.so mylib.o

Utilisation :

gcc main.c -o monprog -L. -lmylib

Avant d'exécuter, il faut que le système trouve la bibliothèque.

Trois méthodes pour rendre la bibliothèque accessible

  1. Copie dans un répertoire standard :

    sudo cp libmylib.so /usr/lib/
    sudo ldconfig
    
  2. Variable d'environnement (temporaire) :

    export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/chemin/vers/la/bibliothèque"
    
  3. Modification de /etc/ld.so.conf :

    sudo nano /etc/ld.so.conf
    # Ajouter la ligne : /chemin/vers/la/bibliothèque
    sudo ldconfig
    

Étiquettes: Linux programmation C appels système stdio bibliothèques statiques

Publié le 8 juin à 21h58