Partage
  • Partager sur Facebook
  • Partager sur Twitter

Programmer un mini shell

    21 janvier 2021 à 12:36:25

    Hello tout le monde,

    Un ami développeur ma conseillé d'essayer de programmer un mini shell. Ca tombe bien puisque je suis en train de lire en ce moment un livre sur la programmation système. a vrai dire là je n'ai aucune idée de comment je vais implémenter le mini shell. Je pense que la première étape est de réfléchir comment afficher un terminal, commennt intérargir avec et ensuite identifier les saisies utilisateurs et si ca match avec un mot clé (une commande) alors lancé la fonction. Je pense commencer simple déjà et par la suite et selon mon niveu de galère, pousser plus loin. Je suis ouvert à tous conseils. J'essaye de poster un peu plus tart les avancées sur mon code.

    • Partager sur Facebook
    • Partager sur Twitter
      21 janvier 2021 à 12:44:10

      Shémo a écrit:

      Hello tout le monde,

      Un ami développeur ma conseillé d'essayer de programmer un mini shell. Ca tombe bien puisque je suis en train de lire en ce moment un livre sur la programmation système. a vrai dire là je n'ai aucune idée de comment je vais implémenter le mini shell. Je pense que la première étape est de réfléchir comment afficher un terminal, commennt intérargir avec et ensuite identifier les saisies utilisateurs et si ca match avec un mot clé (une commande) alors lancé la fonction. Je pense commencer simple déjà et par la suite et selon mon niveu de galère, pousser plus loin. Je suis ouvert à tous conseils. J'essaye de poster un peu plus tart les avancées sur mon code.


      Un shell c'est pas un terminal. Un shell c'est un interpréteur de commande qui suit au minimum la norme posix sh → https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html 

      Un shell peut s'exécuter dans un terminal.

      Un terminal c'est en gros ce qui va afficher des caractères. Ça n'existe plus vraiment. Maintenant on a des émulateurs de terminaux.

      Mais bon si tu tiens vraiment à t'exercer en C avec un soupçon d'algo essaye plutôt les sites avec des coding challenge.

      Genre → http://www.france-ioi.org/ , https://www.codechef.com/  , …

      • Partager sur Facebook
      • Partager sur Twitter
        21 janvier 2021 à 12:51:00

        Ok alors juste coder un Shell. 

        je vais regarder les sites que tu m’as envoyé et m’exercer aussi sur ça.  

        • Partager sur Facebook
        • Partager sur Twitter
          23 janvier 2021 à 17:33:00

          tu veux faire une fenêtre dans laquelle l’utilisateur entre une commande et à laquelle tu répond, c'est bien ça ?

          Si oui, je pense que tu peux tout simplement faire s'exécuter ton programme dans un terminal comme cela :

          malo_montill-ubuntu@Montillaud-PC:~/Documents/fichiers_cpp$ 
          malo_montill-ubuntu@Montillaud-PC:~/Documents/fichiers_cpp$ ./simu_shell
          entrez une commande >> commande
          la commande s'effecue...
          c'est fait !
          
          entrez une commande >> autre_commande -argument
          la commande s'effecue...
          c'est fait !
          
          entrez une commande >> ...
          la commande s'effecue...
          c'est fait !
          
          entrez une commande >> 
          


          où ton programme effectue la commande au lieu d'écrire simplement

          la commande s'effecue...
          c'est fait !

          Tu peux tout simplement dresser une inventaire de commande correspondant à une fonction, puis appeler la fonction correspondant à la commande.

          Je te laisse réfléchir là dessus,

          Bonne chance !

          -
          Edité par Durza42 23 janvier 2021 à 17:33:34

          • Partager sur Facebook
          • Partager sur Twitter

          La gentillesse est le langage qu'un sourd peut entendre et qu'un aveugle peut voir.

          Il n'y a qu'un seul monde et il est faux, cruel, contradictoire, séduisant et dépourvu de sens. Un monde ainsi constitué est le monde réel. Nous avons besoin de mensonges pour conquérir cette réalité, cette "vérité".

            23 janvier 2021 à 17:51:53

            Salut Durza,

            En fait la je me pose la question si simplement je reproduit un SHELL ou l'utilisateur rentre une commande et j'utilise la fonction system qui va taper ce qu'il a rentré dans le shell et voir ce qui se passe. C'est la version simple. L'autre version est de recoder les commandes du shell en utilisant les appels systèmes. Je me tape à vrai dire.

            • Partager sur Facebook
            • Partager sur Twitter
              23 janvier 2021 à 18:06:10

              Shémo a écrit:

              Salut Durza,

              En fait la je me pose la question si simplement je reproduit un SHELL ou l'utilisateur rentre une commande et j'utilise la fonction system qui va taper ce qu'il a rentré dans le shell et voir ce qui se passe. C'est la version simple. L'autre version est de recoder les commandes du shell en utilisant les appels systèmes. Je me tape à vrai dire.


              Non … pas system (ça n'apporte rien à tes  compétences), fork et exec* !
              • Partager sur Facebook
              • Partager sur Twitter
                23 janvier 2021 à 18:08:26

                White Crow a écrit:


                Non … pas system (ça n'apporte rien à tes  compétences), fork et exec* !


                Ainsi que

                • les pipes
                • les redirections.
                Une première approche, pour commencer simpl(ist)e :

                - une boucle qui lit une ligne, et la traite.
                - on décompose en mots.  Si l'utilisateur a tapé  "  ls    toto titi", ça retourne le tableau { "ls", "toto", titi", NULL }
                - on regarde le premier mot. Si c'est une "commande interne" - par exemple "exit" ou "cd', on fait ce qu'il faut.
                - si c'est une commande externe, on fait un fork+exec pour la faire exécuter.
                pour la commande interne "cd" par exemple, on appelle "chdir"
                Recommandation : utiliser une table  nom de commande interne -> pointeur vers la fonction qui s'en occupe.
                Après, quand on a bien grandi parce qu'on a bien mangé sa soupe et fait ses exercices, on fait des choses plus compliquées, comme décomposer la ligne en un "pipeline" de commandes simples, qu'on lance en les raccordant par des pipes.

                -
                Edité par michelbillaud 23 janvier 2021 à 18:18:07

                • Partager sur Facebook
                • Partager sur Twitter
                  23 janvier 2021 à 18:46:41

                  Michel, voici un petit programme que j'avais fais y a un mois en bootcamp. Ca ressemble un peu à ton idée : j'ai une structure qui accueil un string (la rentrée utilisateur), et deux pointeurs de fonctions (une qui compare et l'autre qui éxécute une commande). C'est biensur simpliste. Ma question est : est il intéressant d'améliorer cette implémentation en gardant la srtucture ou mieux de partir sur autre chose ?

                  #include <stdio.h> /* printf */
                  #include <stdlib.h> /* exit_success */
                  #include <string.h> /* strcmp*/
                  
                  #define MAX 200
                  
                  enum end_process
                  {
                  	SUCCESS = 0, FAILED = 1, EXIT_PROG = 2
                  };
                  
                  static int Cmp_Remove(char *input, char *string_compare);
                  static int Cmp_Exit(char *input, char *string_compare);
                  static int Cmp_Count(char *input, char *string_compare);
                  static int Cmp_Char(char *input, char *string_compare);
                  static int Cmp_Default(char *input, char *string_compare);
                  static enum end_process Move_To_Top(char *filename, FILE *p, char *input);
                  static enum end_process Count_Lines(char *filename, FILE *p, char *input);
                  static enum end_process Remove_File(char *filename, FILE *p, char *input);
                  static enum end_process Exit_Prog(char *filename, FILE *p, char *input);
                  static enum end_process Print_In_File(char *filename, FILE *p, char *input);
                  
                  typedef struct commands commands_t;
                  struct commands
                  {
                      char *string;
                      int (*Pnt_compare)(char *input, char *string_compare);
                      enum end_process (*Pnt_operation)(char *filename, FILE *p, char *input);
                  };
                  
                  int main(int argc, char *argv[])
                  {	
                  	char string[MAX] = "0";
                  	int i = 0;	
                  	enum end_process flag = SUCCESS;
                  	
                  	FILE *file = NULL;
                  	
                  	commands_t array[5] = 
                  	{{"-remove\n", Cmp_Remove, Remove_File}, 
                  	{"-exit\n", Cmp_Exit, Exit_Prog}, 
                  	{"-count\n", Cmp_Count, Count_Lines}, 
                  	{"<", Cmp_Char, Move_To_Top}, 
                  	{"test", Cmp_Default, Print_In_File}};	
                  	
                  	if (argc < 2)
                  	{
                  		printf("Error with the argument argc");
                  		return FAILED;
                  	}
                  			
                  	
                  	while (flag != EXIT_PROG)
                      {		
                      
                      	printf("Please write a sentence [MAX 200 CHARACTERS] : \n");
                     		fgets(string, MAX, stdin);
                     		
                  		for (i = 0; i < 5; ++i)
                  		{	   
                  			if (array[i].Pnt_compare(string, array[i].string) == SUCCESS)   
                      		{
                      			flag = array[i].Pnt_operation(argv[1], file, string);  	
                      	  		break;
                      		}    	    	    		
                      	}
                      }
                  				
                  	return (EXIT_SUCCESS);
                  }
                  
                  enum end_process Print_In_File(char *filename, FILE *p, char *input)
                  {
                  	p = fopen(filename, "a+");
                  	
                  	if (p == NULL)
                  	{
                  		printf("Problem with the opening of the file!");
                  		return 2;
                  	}
                  	
                  	fputs(input, p);		
                  	fclose(p);
                  	
                  	return SUCCESS;
                  }
                  
                  int Cmp_Remove(char *input, char *string_compare)
                  {
                  	
                  	if (strcmp(input, string_compare) == 0)
                  	{
                  		return 0;
                  	}
                  	else
                  	{
                  		return 1;
                  	}
                  
                  }
                  
                  int Cmp_Exit(char *input, char *string_compare)
                  {
                  	
                  	if (strcmp(input, string_compare) == 0)
                  	{	
                  		return 0;
                  	}
                  	else
                  	{
                  		return 1;
                  	}
                  
                  }
                  
                  enum end_process Remove_File(char *filename, FILE *p, char *input)
                  {
                      remove(filename);	
                      printf("File deleted\n");
                      (void)(p);
                      (void)(input);
                      
                      return 2;
                          
                  }
                  
                  enum end_process Exit_Prog(char *filename, FILE *p, char *input)
                  {	
                  	
                  	(void)filename;
                  	(void)p;
                  	(void)input;
                  	
                  	return EXIT_PROG;
                  
                  }
                  
                  int Cmp_Count(char *input, char *string_compare)
                  {
                  	if (strcmp(input, string_compare) == 0)
                  	{
                  		return 0;
                  	}
                  	else
                  	{
                  		return 1;
                  	}
                  
                  }
                  
                  enum end_process Count_Lines(char *filename, FILE *p, char *input)
                  {
                  	int lines_counter = 0;
                  	char c; 
                  	p = fopen(filename, "a+");
                  	
                  	if (p == NULL)
                  	{
                  		printf("Problem with the opening of the file!");
                  		exit(1);
                  	}
                  	
                  	while (c != EOF)
                  	{
                  		c = fgetc(p);
                  		if (c == '\n')
                  		{
                  			++lines_counter;
                  		}
                  	} 
                  	
                  	fclose(p);
                  	printf("The number of lines is : %d\n\n", lines_counter);
                  	
                  	(void)input;
                  	(void)filename;
                  
                  	return SUCCESS;
                  } 
                  
                  int Cmp_Char(char *input, char *string_compare)
                  {
                  	
                  	if (strncmp(input, string_compare, 1) == 0)
                  	{	
                  		return 0;
                  	}
                  	else
                  	{
                  		return 1;
                  	}
                  
                  }
                  
                  enum end_process Move_To_Top(char *filename, FILE *p, char *input)
                  {
                  	FILE *temp_file;
                      char ch;
                        
                      p = fopen(filename, "r");
                      temp_file = fopen("file2.txt", "a+");
                      
                      if (p == NULL || temp_file == NULL)
                  	{
                  		printf("Problem with the opening of the file!");
                  		return 2;
                  	}
                      
                      ++input;
                     	fputs(input, temp_file);   	
                     	
                     	rewind(p);
                     	
                      while((ch = getc(p)) != EOF)
                      {
                          putc(ch, temp_file);
                    	}
                    	
                    	fclose(p);
                    	fclose(temp_file);
                    	
                    	p = fopen(filename, "w+");
                      temp_file = fopen("file2.txt", "r");
                      
                       if (p == NULL || temp_file == NULL)
                  	{
                  		printf("Problem with the opening of the file!");
                  		return 2;
                  	}
                      
                      rewind(temp_file);
                    	
                    	while ((ch = getc(temp_file)) != EOF)
                    	{
                    		putc(ch, p);
                    	}
                    	
                      fclose(p);
                      fclose(temp_file);
                      remove("file2.txt");
                      
                      return SUCCESS;
                      
                  }
                  
                  int Cmp_Default(char *input, char *string_compare)
                  {
                  	return 0;
                  	
                  	(void)input;
                  	(void)string_compare;
                  }
                  
                  



                  -
                  Edité par Moshé Frydmann 23 janvier 2021 à 18:47:04

                  • Partager sur Facebook
                  • Partager sur Twitter
                    23 janvier 2021 à 19:52:39

                    Shémo a écrit:

                    Michel, voici un petit programme que j'avais fais y a un mois en bootcamp. Ca ressemble un peu à ton idée.

                    Qui est loin d'être originale.

                    Tes fonctions de comparaison se ressemblent toutes, elles sont donc inutiles.


                    Pour les arguments inutilisés, c'est plus clair de définir une macro

                    #define UNUSED(x) (void)(x)
                    
                    int go_to_hell(char *foo, int bar)
                    {
                        UNUSED(foo);
                        UNUSED(bar);
                    
                        return 0;
                    }

                    en attendant mieux dans le standard (attributs...)

                    et d'utiliser la macro avant le return, sous peine de se manger des avertissements "code inaccessible après return", ce qui est un comble pour une déclaration qu'on met pour éviter d'autres messages concernant les paramètres non utilisés.


                    Au début du corps de la fonction c'est mieux. Ca dit de suite au lecteur : vous embêtez pas avec foo et bar, on s'en sert pas.

                    Avec des attributs ça serait mieux non seulement on déclarerait que ça ne sert pas, mais en plus le compilateur vérifierait qu'on raconte pas de conneries.

                    -
                    Edité par michelbillaud 23 janvier 2021 à 19:54:30

                    • Partager sur Facebook
                    • Partager sur Twitter
                      23 janvier 2021 à 21:55:34

                      Oui tu as raison je peux ne faire qu’une seule fonction compare (ou 2 Max) et pas 4. Pour les paramètres non utilisés yes tu as aussi raison, j’utilisais quelque fois une macro beaucoup plus propre que ça. faut que je mette à jour le code. 

                      mais dans l’idée, faire un Shell basé sur struct + tableau est il une bonne idée ? 

                      • Partager sur Facebook
                      • Partager sur Twitter
                        23 janvier 2021 à 21:57:46

                        Ce n'est qu'un tout petit aspect. Comparer avec les alternatives.

                        -
                        Edité par michelbillaud 23 janvier 2021 à 21:58:51

                        • Partager sur Facebook
                        • Partager sur Twitter
                          24 janvier 2021 à 8:20:20

                          Shémo a écrit:

                          mais dans l’idée, faire un Shell basé sur struct + tableau est il une bonne idée ? 


                          Si par là tu entends, faire un tableau de 
                          typedef struct commands commands_t;
                          struct commands
                          {
                              char *string;
                              int (*Pnt_compare)(char *input, char *string_compare);
                              enum end_process (*Pnt_operation)(char *filename, FILE *p, char *input);
                          };

                          La réponse est définitivement non, ou alors il faut l'adapter.

                          Pnt_compare ne sert pas à grand chose. Tous les Pnt_operation ont la même interface avec une sorte de liste de tous les paramètres possibles, du coup pour les implémentations tu y vas à coup de macro pour faire taire des warnings de compilo → pas cool.

                          Comme te l'a indiqué Michel un peu plus haut, quand tu implémentes une interface REPL (Read, Eval, Print, Loop) minimaliste, tu peux commencer par

                          • lire une ligne entière en supposant que toute la commande tient dessus ;
                          • tokeniser sommairement cette ligne = séparer la commande en bloc de caractères non blancs séparés par des blancs ;
                          • regrouper les tokens dans un tableau de chaînes à indicateur terminal (NULL, qu'on appelle aussi une sentinelle) ;
                          • décider ce que c'est (commande interne/exécutable externe sur le premier élément) et avoir un prototype simple pour les fonctions : une fonction qui renvoie un état et accepte comme paramètre au moins un tableau de chaînes.

                          Ce n'est pas hyper compliqué de faire ça, surtout simplifié ainsi.

                          Si tu veux augmenter le challenge au niveau algo au lieu de prendre un simple tableau qui va regrouper toutes les commandes avec un pointeur de fonction (en gros hein) tu pourrais :

                          • implémenter une table de hashage pour retrouver la commande interne …
                          • implémenter un arbre préfixe pour avoir une complétion sur les commandes internes, et éventuellement l'enrichir au fur et à mesure des commandes externes trouvées …
                          • éventuellement t'intéresser à comment on définit un langage …

                          Si tu veux t'enfoncer dans le cambouis d'un shell tu as le choix entre toutes les possibilités que t'offre une ligne de commande (exécution en tâche de fond, enfilade de commande via les opérateurs logiques, les pipes, les redirections, les définitions de fonctions/alias/variables d'env …)

                          • Partager sur Facebook
                          • Partager sur Twitter
                            24 janvier 2021 à 10:43:13

                            Merci pour ces précisions. Oui effectivement il y a le choix en terme de cassage de tête lol
                            • Partager sur Facebook
                            • Partager sur Twitter
                              24 janvier 2021 à 11:39:11

                              Quand je fais ce genre de chose, la table est du genre

                              struct descr_commande_interne commandes[] = {
                                   { "exit",  do_exit },
                                   { "help",  do_help },
                                   { "?",  do_help },        // synonyme
                                   ....
                                   { NULL, NULL}
                              };
                              

                              Après avoir décomposé la ligne, on regarde si le premier mot correspond à quelque chose dans la table. Si oui on lance la fonction correspondante, sinon (on est tombé sur NULL), il s'agit d'une "commande externe".


                              Dans la structure il y a une chaine (évidemment) et un pointeur sur une fonction qui prend en paramètres

                              • la ligne décomposée et le nombre de mots (dans le style argc/argv
                              • un pointeur sur un objet qui représente le contexte de l'interpréteur
                              par exemple pour do_exit, ça va faire basculer un indicateur qui dit qu'il faut continuer/arreter la boucle REPL
                              void do_exit(int argc, char *argv[], struct context *c) 
                              {
                                  UNUSED(argc);
                                  UNUSED(argv);
                                  c -> looping = false;
                              }

                               pour help, on pourra regarder ce qu'il y a dans argv[1] pour savoir à quel sujet.

                              PS: les langages de commande style shell ont une syntaxe merdique au possible, c'est une galère à analyser.

                              -
                              Edité par michelbillaud 24 janvier 2021 à 11:52:09

                              • Partager sur Facebook
                              • Partager sur Twitter
                                24 janvier 2021 à 12:30:56

                                On peut faire une LUT avec un tableau d'un type structure ? Ca fonctionne de la même façon? L'indice du tableau est en réalité le string que l'on recherche ? Donc ca en fait une compléxité O(1) et non pas O(n) donc plus rapide
                                • Partager sur Facebook
                                • Partager sur Twitter
                                  24 janvier 2021 à 13:10:46

                                  Shémo a écrit:

                                  On peut faire une LUT avec un tableau d'un type structure ? Ca fonctionne de la même façon? L'indice du tableau est en réalité le string que l'on recherche ? Donc ca en fait une compléxité O(1) et non pas O(n) donc plus rapide


                                  Non, avec une sdd type tableau le mieux que tu puisses espérer obtenir avec une recherche sur une clé qui n'est pas l'indice est O(log n) avec n le nombre d'éléments dans le tableau.

                                  Si tu veux en temps amorti constant il va falloir regarder du côté des hash tables.

                                  Tu peux presque faire mieux en O(k) où k est la longueur maximum d'une clé avec un arbre préfixe.

                                  Toutes les complexités données sont en temps.

                                  michelbillaud a écrit:

                                  [...]

                                  PS: les langages de commande style shell ont une syntaxe merdique au possible, c'est une galère à analyser.

                                  -
                                  Edité par michelbillaud il y a environ 1 heure

                                  Tu trouves ? https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_10 



                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    24 janvier 2021 à 13:46:35

                                    Rappel, la complexité, ça décrit un comportement asymptotique. Et ici on a un petit nombre de noms de commandes internes, donc faudrait voir. Il est probable que ça ne fasse aucune différence en pratique (a part le temps perdu à coder une amélioration qui n'en n'est pas une).

                                     Sinon, les chaînes sont connues au départ, donc voir le "perfect hashing", si on y tient.

                                    Pour la syntaxe, il y avait des langages de commandes mieux structurés, comme celui de Multics, et de GCOS (je retrouve pas les docs) qui empruntaient des traits de lisp.. Je suis un dinosaure.

                                    -
                                    Edité par michelbillaud 24 janvier 2021 à 13:50:52

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      24 janvier 2021 à 16:26:37

                                      michelbillaud a écrit:

                                      Rappel, la complexité, ça décrit un comportement asymptotique. Et ici on a un petit nombre de noms de commandes internes, donc faudrait voir. Il est probable que ça ne fasse aucune différence en pratique (a part le temps perdu à coder une amélioration qui n'en n'est pas une).

                                      [...]
                                      -

                                      Edité par michelbillaud il y a environ 1 heure

                                      Je faisais la remarque par rapport à 

                                      Shémo a écrit:

                                      [...]
                                      Donc ca en fait une compléxité O(1) et non pas O(n) donc plus rapide

                                      qui indique une méconnaissance des complexités des sdd classiques (surtout qu'ici c'est une simple recherche dans un tableau …, donc de base de base la sdd et l'algo).

                                      Ce n'était pas un conseil pour gagner en efficacité, tout au plus un conseil pour le faire au moins une fois et savoir ce que c'est comment ça fonctionne, se documenter, etc …
                                      C'est important (àmha) de savoir ce qu'est une hash table.

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        24 janvier 2021 à 16:31:18

                                        Une hash table est une Lookup table, non ? En gros la valeur est l'indice de ton tableau ce qui permet de faire une recherche rapide et direct sans devoir ittérrer tout le tableau.
                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                          24 janvier 2021 à 17:13:35

                                          Shémo a écrit:

                                          Une hash table est une Lookup table, non ? En gros la valeur est l'indice de ton tableau ce qui permet de faire une recherche rapide et direct sans devoir ittérrer tout le tableau.

                                          En C, nativement, tu n'as que des «tableaux dont les indices sont entiers» = le seul accès aléatoire est fait selon un indiçage entier. Tu n'as pas d'équivalents natifs de dictionnaire comme tu pourrais en trouver dans python par exemple. Il faut implémenter ça toi-même.
                                          Tu peux implémenter ça de différentes manières, la plus simple étant d'utiliser une fonction qui transforme une chaîne en indice puis un tableau de listes qui vont contenir les données. Mais bon, tu as beaucoup de littérature un peu partout sur le net.

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            24 janvier 2021 à 18:29:37

                                            Il faudra bien qu'il le voie un jour, mais c'est pas trop bon de se disperser.

                                            Pour l'instant il y pas mal de clarification/simplification de code à apprendre. Par exemple apprendre à se servir des booléens.

                                            -
                                            Edité par michelbillaud 24 janvier 2021 à 18:32:36

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              24 janvier 2021 à 20:06:56

                                              Oui effectivement j’avais vu que en C tu ne peux qu’utiliser des entiers pour ça. Bon je vais relire tous vos riches commzntaires afin de comprendre vers quoi me tourner. Merci encore :)
                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                                24 janvier 2021 à 23:16:22

                                                Non il y a un type booléen natif _Bool, qui s'appelle bool si on inclue stdbool.h,  avec des constantes true et false.
                                                • Partager sur Facebook
                                                • Partager sur Twitter

                                                Programmer un mini shell

                                                × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                                × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                                • Editeur
                                                • Markdown