Je ne comprends pas "Dans un fork()". Ou bien la fonction est appelée avant le fork() et le process fils aura le même contexte donc aura changé de répertoire. Ou bien c'est après le fork() et là bien évidemment les process étant séparés, tout changement de répertoire du père ou du fils n'est pas fait dans l'autre.
Et en supposant que ft_strcmp() fait la même chose que strcmp(), ta fonction cd() ne sait aller que sur le répertoire home. Il faut plutôt écrire:
if ( !path || ft_strcmp(path, "~") == 0 ) // si le path es NULL ou bien le path est "~"
Au fait, si je le lance dans un fork c'est parce que j execute des commandes avec execve mais peut etre que je dois pas fork cd pour cd ? Il y a aucune commande qui bash qui peut faire en sorte que j'ai a fork ?
Et effectivement je me suis trompe sur le retour de ft_strcmp, merci.
Et aussi autre question, const char * et char const * et const char * const, on est d'accord que les trois c'est la meme chose il y a aucune difference en C?
Et aussi, j'aimerai implementer les here doc, j'ai une solution qui consiste a creer un fichier mais j'aime pas trop cette solution car si quelqu'un s'amuse a chmod le fichier ou le creer juste avant moi je ne pourrai pas ecrire dedans temporairement, j'ai penser a un coup de marteau, boucle tant que nom_du_fichier + i existe mais la c'est vraiment pas top top, j'ai vu qu'il y avait un flag O_TEMP mais j'arrive pas a y acceder. Il y a aucun moyen de faire autrement ?
J'ai aussi tenter de reproduire la fonction exit sans option mais disons que c'est encore bancal, pouvez vous m'aider la dessus svp ?
#define LLMAX 9223372036854775807LL
#define LLMIN -9223372036854775807LL
void is_exit(char **argument)
{
const char *tmp;
long long res;
int index;
if (!argument || !*argument)
{
ft_putendl("exit");
return ;
}
index = 0;
while (argument[++index])
{
tmp = argument[index];
res = ft_atoull(tmp); // long long int
if (*tmp == '+' || *tmp == '-')
tmp ++;
while (ft_isdigit(*tmp))
tmp++;
if (index > 1)
{
ft_putendl("exit");
ft_printf("bash: exit: too many arguments\n", tmp);
exit(1);
}
if (*tmp != '\0' || res > LLMAX || res < LLMIN)
{
ft_printf("bash: exit: %s: numeric argument required\n", tmp);
exit(2);
}
}
ft_putendl("exit");
if (argument[1])
exit(ft_atoull(argument[1]));
exit(EXIT_SUCCESS);
}
Un shell permet de lancer l'exécution de programmes, ce qu'on appelle commandes externes
Il traite aussi des commandes internes, comme exit, if, function, while case, etc
Sous unix, le lancement des commandes externes se fait par une combinaison de fork,exec,wait,dup2 etc.
La commande cd fait partie des commandes internes.
En gros le shell c'est une boucle qui
Lit une commande
L'execute
Pour exécuter on commence par regarder si c'est une des commandes internes. Si c'est exit, ça fait sortir de la boucle. Si c'est cd, ça appelle chdir. Et si c'est une commande externe, ça l'appelle dans un processus fils à part.
(Il y a aussi le cas des structures de contrôle, passons, on ne peut pas tout expliquer d'un coup)
(L'autre jour j'ai commencé à écrire un petit document pédagogique là dessus, faut que je le finisse. To be continued)
Ps : tu devrais séparer l'analyse syntaxique de la ligne (Une commande simple est une suite de mots) de l'exécution. Faire une fonction qui découpe la ligne.
- Edité par michelbillaud 17 janvier 2023 à 8:51:07
D'accord merci donc je dois pas fork en executant une commande interne.
michelbillaud a écrit:
Ps : tu devrais séparer l'analyse syntaxique de la ligne (Une commande simple est une suite de mots) de l'exécution. Faire une fonction qui découpe la ligne.
Si vous parlez du parametre de la fonction is_exit, je la stock deja separement. j'ai un double tableau pour ca qui me stock le nom de la command et les params/opt de la commande comme ca je peux l'envoyer a execve directement
que la fonction reçoive aussi le nombre d'arguments
comme ça il suffirait de tester qu'il y en a 0 ou 1 (ce qui servirait aussi pour cd)
et que si il y en a un, c'est une chaîne numérique (avec une fonction bool is_a_number(char *) )
PS: les conventions habituelles veulent quand le nom d'une fonction commence par is, ou has, elle retourne un boolean. Un nom comme execute_exit_command(), ça serait plus clair que is_exit()
- Edité par michelbillaud 17 janvier 2023 à 12:39:40
Et aussi autre question, const char * et char const * et const char * const, on est d'accord que les trois c'est la meme chose il y a aucune difference en C?
Le mot const peut être mis avant ou après le type, donc les 2 premiers sont bien la même chose. Pas le troisième, les 2 mots const indiquent que 2 choses sont constantes, il s'agit d'un pointeur constant sur des caractères constants. Pour les pour 2 premiers la variable n'est pas constante, elle pointe sur des caractères qui ne seront pas modifiés, mais peut être réaffectée pour pointer ailleurs.
const int I = 5; // I vaudra toujours 5 (I=... interdit)
char* const Ptr = ""; // Ptr ne changera jamais (Ptr=... interdit)
const char* ptr; // pointe sur du constant (*ptr=... interdit)
const char* const P2 = ""; // (ni P2=... ni *P2=...)
char const**const PP = &ptr;
PP = &ptr; // interdit PP est constant (à cause du dernier const)
*PP = ptr; // oui *pp peut être modifié
**PP = 'a'; // interdit **PP est constant (à cause du 1er const)
Bon, passons à plus rigolo, les "doubles pointeurs". Si on déclare
int **p;
c'est pour avoir une variable qui contiendra l'adresse d'un pointeur vers un entier. Si on met const devant
const int **p;
qu'est-ce qui est const ?
Si on part de l'idée que p est de type "pointeur vers un pointeur d'entier", ça conduirait à la conclusion que p est en lecture seule. Et bien, perdu :
const int **p;
p = NULL; // ça passe
La vraie signification de la déclaration (que vos Profs de C Ne Veulent Pas que Vous Sachiez) : **p est de type "entier en lecture seule". C'est pas pareil. Ce que ça empêche de faire, c'est ça
// **p = 12; NON
On peut aussi intervertir le type (int) et const, avec le même effet : int const ** p;
---
Si on veut empêcher de modifier le double pointeur, il faut mettre const avant le nom
int ** const q = NULL;
// q = NULL; NON
*q = NULL;
**q = 12;
Si on le met au milieu
int * const *r;
ça dit que r est un pointeur (modifiable) vers un pointeur (non modifiable) vers un entier (modifiable).
int * const * r = NULL;
r = NULL; // r est modifiable
// *r = NULL; // NON : *r en lecture seule
**r = 12; /// **r est modifiable
- Edité par michelbillaud 18 janvier 2023 à 11:38:56
Merci pour vos precisions mais quelque chose me chagrine
michelbillaud a écrit:
Pour exécuter on commence par regarder si c'est une des commandes internes. Si c'est exit, ça fait sortir de la boucle. Si c'est cd, ça appelle chdir. Et si c'est une commande externe, ça l'appelle dans un processus fils à part.
Prenons le cas de: cd .. | cd ..
en executant avant valgrind bash, on s apercoit qu'un proc a bien ete lance, devrais-je crer un proc enfant s'il y a plus d'une commande et si c'est une commande interne ?
Aussi, j'ai une derniere question, j'execute des forks a deux endroits, un pour les heredoc et l autre pour les procs, pour recuperer le bon status code, vous pensez qu'une variable globale fait l'affaire ou alors je me balade ma struct/variable un peu partout ?
la page de manuel de bash dit que chaque commande d'un pipeline s'exécute dans un processus séparé. Si on se sert du manuel de bash comme spécification de ce qu'on veut faire, ben voilà. (À un moment il faut bien spécifier ce qu'on veut faire, avant de coder)
Heredocs, forks, Status, variable globale : desolé je ne vois pas de quoi il sagit. Je dirais que l'exécution d'une commande peut modifier l'état du shell (penser aux affectations message="coucou" par exemple, mais aussi "retcode" de la derniere commande executee) et qu'il va donc falloir transmettre ce contexte aux fonctions qui exécutent les commandes. Ça répond peut etre pas à la question.
- Edité par michelbillaud 19 janvier 2023 à 15:15:44
en executant avant valgrind bash, on s apercoit qu'un proc a bien ete lance, devrais-je crer un proc enfant s'il y a plus d'une commande et si c'est une commande interne ?
Mais l'explication la plus courte, c'est que si le shell fork la commande, alors ce que fait la commande n'a pas d'effet sur le shell. Le chdir() n'aura d'effet que dans le processus de la commande cd, mais pas dans le shell (qui est le processus parent).
La fonction chdir() permet de changer le répertoire courant pour le processus en cours d'exécution. Lorsque vous utilisez cette fonction dans un processus enfant créé à l'aide de la fonction fork(), le changement de répertoire ne s'applique qu'au processus enfant et non au processus parent.
Lorsque vous utilisez la fonction fork(), un nouveau processus est créé à partir de celui qui l'appelle. Le processus enfant hérite de la plupart des ressources du processus parent, y compris le répertoire courant. C'est pourquoi, lorsque vous utilisez chdir() dans un processus enfant, le répertoire courant ne change pas pour le processus parent.
Pour résoudre ce problème, vous pouvez utiliser la fonction chdir() dans le processus parent après avoir créé le processus enfant. Vous pouvez également utiliser une variable d'environnement pour stocker le répertoire courant et la partager entre les processus parent et enfant.
Il est important de noter que le changement de répertoire ne modifie pas les variables d'environnements. Il est donc important de récupérer la valeur de PWD pour la stocker dans une variable d'environnement pour pouvoir la réutiliser dans les processus enfants.
Voici un exemple de code qui utilise une variable d'environnement pour stocker le répertoire courant et le partager entre les processus parent et enfant :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
void change_directory(const char *path)
{
if (chdir(path) == -1)
perror(path);
}
void cd(const char *path)
{
char cwd[1024];
if (getcwd(cwd, sizeof(cwd)) != NULL)
{
setenv("OLDPWD", cwd, 1);
}
if (!path || ft_strcmp(path, "~") != 0)
change_directory(getenv("HOME"));
else
change_directory(path);
if (getcwd(cwd, sizeof(cwd)) != NULL)
{
setenv("PWD", cwd, 1);
}
}
int main()
{
pid_t pid;
pid = fork();
if (pid == 0)
{
cd("/usr/local");
printf("Child Process: Current directory is %s\n", getenv("PWD"));
}
else
{
printf("Parent Process: Current directory is %s\n", getenv("PWD"));
}
return 0;
}
Voici un exemple de code qui utilise une variable d'environnement pour stocker le répertoire courant et le partager entre les processus parent et enfant :
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/types.h>voidchange_directory(constchar *path) { if (chdir(path) == -1) perror(path); } voidcd(constchar *path) { char cwd[1024]; if (getcwd(cwd, sizeof(cwd)) != NULL) { setenv("OLDPWD", cwd, 1); } if (!path || ft_strcmp(path, "~") != 0) change_directory(getenv("HOME")); else change_directory(path); if (getcwd(cwd, sizeof(cwd)) != NULL) { setenv("PWD", cwd, 1); } } intmain() { pid_t pid; pid = fork(); if (pid == 0) { cd("/usr/local"); printf("Child Process: Current directory is %s\n", getenv("PWD")); } else { printf("Parent Process: Current directory is %s\n", getenv("PWD")); } return0; }
Dans cet exemple, nous utilisons la fonction getcwd() pour récupérer le répertoire courant avant de changer de répertoire, et la fonction setenv() pour stocker cette valeur dans les variables d'environnement "OLDPWD" et "PWD". Les processus parent et enfant peuvent ensuite utiliser ces variables pour accéder au répertoire
La fonction chdir() permet de changer le répertoire courant pour le processus en cours d'exécution. Lorsque vous utilisez cette fonction dans un processus enfant créé à l'aide de la fonction fork(), le changement de répertoire ne s'applique qu'au processus enfant et non au processus parent.
Lorsque vous utilisez la fonction fork(), un nouveau processus est créé à partir de celui qui l'appelle. Le processus enfant hérite de la plupart des ressources du processus parent, y compris le répertoire courant. C'est pourquoi, lorsque vous utilisez chdir() dans un processus enfant, le répertoire courant ne change pas pour le processus parent.
Pour résoudre ce problème, vous pouvez utiliser la fonction chdir() dans le processus parent après avoir créé le processus enfant. Vous pouvez également utiliser une variable d'environnement pour stocker le répertoire courant et la partager entre les processus parent et enfant.
Il est important de noter que le changement de répertoire ne modifie pas les variables d'environnements. Il est donc important de récupérer la valeur de PWD pour la stocker dans une variable d'environnement pour pouvoir la réutiliser dans les processus enfants.
Voici un exemple de code qui utilise une variable d'environnement pour stocker le répertoire courant et le partager entre les processus parent et enfant :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
void change_directory(const char *path)
{
if (chdir(path) == -1)
perror(path);
}
void cd(const char *path)
{
char cwd[1024];
if (getcwd(cwd, sizeof(cwd)) != NULL)
{
setenv("OLDPWD", cwd, 1);
}
if (!path || ft_strcmp(path, "~") != 0)
change_directory(getenv("HOME"));
else
change_directory(path);
if (getcwd(cwd, sizeof(cwd)) != NULL)
{
setenv("PWD", cwd, 1);
}
}
int main()
{
pid_t pid;
pid = fork();
if (pid == 0)
{
cd("/usr/local");
printf("Child Process: Current directory is %s\n", getenv("PWD"));
}
else
{
printf("Parent Process: Current directory is %s\n", getenv("PWD"));
}
return 0;
}
Voici un exemple de code qui utilise une variable d'environnement pour stocker le répertoire courant et le partager entre les processus parent et enfant :
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/types.h>voidchange_directory(constchar *path) { if (chdir(path) == -1) perror(path); } voidcd(constchar *path) { char cwd[1024]; if (getcwd(cwd, sizeof(cwd)) != NULL) { setenv("OLDPWD", cwd, 1); } if (!path || ft_strcmp(path, "~") != 0) change_directory(getenv("HOME")); else change_directory(path); if (getcwd(cwd, sizeof(cwd)) != NULL) { setenv("PWD", cwd, 1); } } intmain() { pid_t pid; pid = fork(); if (pid == 0) { cd("/usr/local"); printf("Child Process: Current directory is %s\n", getenv("PWD")); } else { printf("Parent Process: Current directory is %s\n", getenv("PWD")); } return0; }
Dans cet exemple, nous utilisons la fonction getcwd() pour récupérer le répertoire courant avant de changer de répertoire, et la fonction setenv() pour stocker cette valeur dans les variables d'environnement "OLDPWD" et "PWD". Les processus parent et enfant peuvent ensuite utiliser ces variables pour accéder au répertoire
Réponse ChatGPT, à ce qu'il paraît sur stackoverflow c'est une invasion... et les utilisateurs détectés sont sanctionnés, car souvent les réponses ne sont pas correctes.
Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard) La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)
montre bien que les deux "cd" (qui ramènent au répertoire d'accueil) sont effectués dans des processus à part (*)
Pareil pour les affectations aux variables du shell
$ a=avant
$ echo $a
avant
$ a=apres | echo $a
avant
$ echo $a
avant
(*) ou du moins, en toute rigueur, que ça ne change pas le répertoire courant du shell. Ce qui laisse fortement induire que ce sont des processus différents.
- Edité par michelbillaud 22 janvier 2023 à 9:33:04
Merci Michel pour ce contenu tres interessant, compte-t-il y avoir une maj pour les redirections, les &&, ||, wildcards et subshell ?
Merci bien!
Bonjour,
Over my dead body, parce que
c'est trop fatigant, ne serait-ce qu'en raison de la syntaxe épouvantable des langages de commande dérivés de sh
ce qui m'intéresse c'est d'expliquer des petits bouts techniques, le pipeline est là https://www.mbillaud.fr/notes/pipeline.html en insistant sur le raisonnement pour y arriver. (Mon job, c'était d'essayer d'enseigner des choses à des débutants, en décomposant les difficultés, pas de produire du code)
développer un truc complet serait fournir une solution aux fainéants qui ne veulent pas faire leur projet de programmation, ce qui ne leur rendra pas service
si on veut un exemple de shell assez complet, il suffit de chercher un peu, avec tout ce qui est dispo en open-source, ou dans les archives des unix des années 70-80 (sans parler des projets perso présentés fièrement)
la vie est trop courte
Si j'étais motivé pour développer un interpréteur de commandes
j'essaierai de définir une syntaxe propre, quitte à ce qu'elle soit très éloignée de sh. La base commune c'est qu'une suite de commandes, une par ligne, soit un script correct
je ne ferais surtout pas ça en C, qui n'a pas de conteneurs, où le traitement des exceptions est une corvée, etc.
le refactorer (choix des identificateurs, découpage en fonctions etc) sans dire de gros mots
en déduire les fonctionnalités qu'il implémente.
- Edité par michelbillaud 23 février 2023 à 9:41:37
Chdir et fork
× 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.
En recherche d'emploi.
En recherche d'emploi.
Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)