Modifier un fichier C à l'aide d'un script

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour tout le monde,

J'aimerais tester une bibliothèque de fonctions que j'ai écrite en C. Il y a un fichier par fonction. J'ai écrit un fichier MAIN.c qui contient les tests pour toutes ces fonctions.

Ce dont j'ai besoin, c'est d'un script qui ajoute un appel aux tests dans MAIN.c pour chaque fichier trouvé dans le dossier courant. Autrement dit, si j'ai fct1.c et fct2.c, je veux ajouter test_fct1() et test_fct2() dans le main de MAIN.c

Je n'arrive pas à écrire ce script en Bash. La partie qui me pose problème c'est vider le contenu du main et le remplacer par l'appel aux fonctions. Je suis aussi entrain de regarder du côté de Python (que je n'ai encore jamais vu).

Si vous aviez des pistes à me donner, ça serait cool. Également, comment vous organisez votre code pour pouvoir facilement lancer les tests ?

Merci d'avance.

Édité par matthieuj

+0 -0
Auteur du sujet

Le main fait plus de 1000 lignes donc j'ai réduit un peu, mais l'essentiel doit être là.

somme.c

1
2
3
4
5
6
#include "libbiblio.h"

int somme(int a, int b)
{
    return a + b;
}

main.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include "libbiblio.h"

#define OK(test) { printf("%s: \033[32mSUCCESS\033[0m\n", #test); return ; }
#define KO(test) { printf("%s: \033[31mFAIL: file %s, line %i\033[0m\n", \
    #test, __FILE__, __LINE__); return ; }

void    test_somme(void)
{
    if (somme(1, 2) != 3)
        KO(somme)
    else
        OK(somme)
}

int main(void)
{
    test_somme();  /* mettre cette ligne en commentaire si il n'y a pas de fichier somme.c */
                   /* dans le dossier courant                                              */
    return 0; 
}

Édité par matthieuj

+0 -0

Tu as deux méthodes, soit tu compiles tes .c en .o. Puis ensuite, tu les charges depuis ton main de manière dynamique.

Soit tu fais un vieux hack comme ça :

1
gcc main.c -DFUNCTION="`ls | grep "\.c" | sed 's/\.c/();/'`"
1
2
3
4
5
6
7
8
9
#include<stdio.h>


int main(void) {
  #ifdef FUNCTION
    FUNCTION
  #endif
  return 0;
}

Faudra penser à rajouter un grev -v main.c pour éviter d'appeler la fonction main en boucle …

Édité par ache

ache.one                                                                                   🦊

+0 -0

Pour rester sur du bash pur, de quoi démarrer (c'est loin d'être parfait, je te laisse découvrir tous les défauts de la méthode :p ) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
    is_test=0
    for f in *.c; do
        # On enlève l'extension du fichier
        name="${f%.*}"
        # On matche innocemment tout ce qui ressemble à un appel de test_$name
        regex="^\s*test_$name\s*(\s*)\s*"
        if [[ "$line" =~ $regex ]]; then
            is_test=1
        fi
    done
    # On commente tout ce qu'on a considéré comme test...
    if [ $is_test -eq 1 ]; then
        echo "//$line"
    else
        echo "$line"
    fi
done < main.c

Ou sinon sous gcc, on peut appeler les fonctions avant le main, automatiquement :

1
2
3
__attribute__((constructor)) void test_somme (void) {
... 
} 

Et même spécifier un ordre de priorité (Cf la doc gcc).

Utiliser un framework de test serait quand même sûrement moins sale…

Édité par naegi

+0 -0
Staff

Cette réponse a aidé l'auteur du sujet

Sinon, il y a le bon vieux ed(1).

1
2
3
4
5
6
7
8
9
$ ed main.c
P
*r !ls -1 *.c
*g/^main.c$/d
*g/.c$/m/main(/+1
*g/.c$/s/^/test_/
*g/.c$/s//()/
*w
*q

Pour l'explication :

  1. La commande P demande à ed(1) d'afficher un invite de commande (par défaut *) quand il attends une commande ;
  2. La commande r !ls -1 *.c insère à la fin du fichier le résultat de la commande ls -1 *.c (qui liste donc tous les fichiers sources C du répertoire courant sur une seule colonne) ;
  3. La commande g/^main.c$/d supprime du fichier toutes les lignes comportant exclusivement main.c ;
  4. La commande g/.c$/m/main(/+1 déplace chaque ligne se terminant par les caractères .c une ligne après la première comportant les caractères main( ;
  5. La commande g/.c$/s/^/test_/ ajoute les caractères test_ au début des lignes se terminant par .c ;
  6. La commande g/.c$/s//()/ substitue la suite .c par () dans toutes les lignes se terminant par .c ;
  7. La commande w sauvegarde les modifications et la commande q quitte l'éditeur.

Édit : ajout de l'insertion des caractères test_.

Édité par Taurre

+0 -0
Auteur du sujet

Chacun sa solution :) Merci à vous tous en tout cas.

J'ai toujours regretté de ne pas maîtriser ed et sed ou awk, que je trouve assez difficiles à apprendre. Par contre j'ai essayé de faire echo 'script' | ed main.c mais ça ne fonctionne pas.

Je vais regarder du côté des frameworks de tests, apparemment il y a du choix.

Édité par matthieuj

+0 -0

@Taurre: :lol: Tu m'as tué … :')

@matthieuj: La "maîtrise" de ces outils s'apprend au fils du temps. J'ai jamais vraiment appris à utiliser sed mais comme j'utilise vim tous les jours et que ça ressemble un peu … Pour ed, c'est un peu la même chose …

ache.one                                                                                   🦊

+0 -0
Staff

J'ai toujours regretté de ne pas maîtriser ed et sed ou awk, que je trouve assez difficiles à apprendre.

matthieuj

ed(1) reste foncièrement assez simple, c'est juste qu'il est quelque peu austère et, surtout, qu'il n'affiche pas le texte couramment édité ce qui rebute assez logiquement de nos jours.

Par contre j'ai essayé de faire echo 'script' | ed main.c mais ça ne fonctionne pas.

matthieuj

ed(1) edite le fichier fourni en argument. Si tu lui fourni une entrée, celle-ci est considérée comme une suite de commandes à appliquer. ;)

@Taurre: :lol: Tu m'as tué … :')

ache

Ne jamais oublier le bon vieux ed. :p

Édité par Taurre

+0 -0
Auteur du sujet

Par contre j'ai essayé de faire echo 'script' | ed main.c mais ça ne fonctionne pas.

matthieuj

ed(1) edite le fichier fourni en argument. Si tu lui fourni une entrée, celle-ci est considérée comme une suite de commandes à appliquer. ;)

Je ne sais pas pourquoi je me suis obstiné à faire un echo :P

Édité par matthieuj

+0 -0
Staff

Note, si tu utilises ed de manière non interactive, oublie la commande P (qui pour le coup est inutile) et remplace là par H qui t'affichera les messages d'erreur en cas de problèmes. ;)

Édité par Taurre

+0 -0
Auteur du sujet

Tout fonctionne correctement en utilisant un script ed et un script sh.

Par contre le script ed supprime toutes les lignes commençant par test_, je voudrais supprimer les lignes suivant le motif test_name(); Pour l'instant ça me donne: g/^ *test_.*\(\);$/d, mais ça ne fonctionne pas. Ma conclusion: je ne sais pas écrire une regex.

Édité par matthieuj

+0 -0
Staff

Vu que les lignes que tu souhaites supprimer ne comporte que les appels, ceci devrait fonctionner : g/test_.*);$/d ou, s'il est nécessaire d'être plus strict par rapport au début de la ligne : g/^ *test_.*);$/d.

Édité par Taurre

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte