v1
This commit is contained in:
commit
ab6da28992
29
.vscode/launch.json
vendored
Normal file
29
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "gcc - Build and debug active file",
|
||||
"type": "cppdbg",
|
||||
"request": "launch",
|
||||
"program": "${fileDirname}/${fileBasenameNoExtension}",
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"cwd": "${workspaceFolder}",
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"setupCommands": [
|
||||
{
|
||||
"description": "Enable pretty-printing for gdb",
|
||||
"text": "-enable-pretty-printing",
|
||||
"ignoreFailures": true
|
||||
}
|
||||
],
|
||||
"preLaunchTask": "C/C++: gcc build active file",
|
||||
"miDebuggerPath": "/usr/bin/gdb"
|
||||
}
|
||||
]
|
||||
}
|
8
.vscode/settings.json
vendored
Normal file
8
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"files.associations": {
|
||||
"signal.h": "c",
|
||||
"readcmd.h": "c",
|
||||
"stdlib.h": "c",
|
||||
"wait.h": "c"
|
||||
}
|
||||
}
|
BIN
.vscode/tasks
vendored
Executable file
BIN
.vscode/tasks
vendored
Executable file
Binary file not shown.
30
.vscode/tasks.json
vendored
Normal file
30
.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"tasks": [
|
||||
{
|
||||
"type": "cppbuild",
|
||||
"label": "C/C++: gcc build active file",
|
||||
"command": "/usr/bin/gcc",
|
||||
"args": [
|
||||
"-Wall",
|
||||
"-g",
|
||||
"jobs.c",
|
||||
"readcmd.c",
|
||||
"minishell.c",
|
||||
"-o",
|
||||
"${fileDirname}/${fileBasenameNoExtension}"
|
||||
],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
"problemMatcher": [
|
||||
"$gcc"
|
||||
],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"detail": "Task generated by Debugger."
|
||||
}
|
||||
],
|
||||
"version": "2.0.0"
|
||||
}
|
14
Makefile
Normal file
14
Makefile
Normal file
|
@ -0,0 +1,14 @@
|
|||
CC = gcc
|
||||
LDFLAGS = -Wall
|
||||
|
||||
all: minishell
|
||||
|
||||
minishell: readcmd.c minishell.c
|
||||
$(CC) $(LDFLAGS) $^ -o $@
|
||||
|
||||
clean:
|
||||
rm *.o minishell
|
||||
|
||||
.PHONY: clean all
|
||||
|
||||
readcmd.o: readcmd.h
|
38
README.md
Executable file
38
README.md
Executable file
|
@ -0,0 +1,38 @@
|
|||
Introduction
|
||||
------------
|
||||
Le code fourni a pour but de vous décharger du travail d'analyse d'une ligne de commande,
|
||||
avant son interprétation par le shell. Il propose une fonction `readcmd()` qui fournit
|
||||
le résultat de cette analyse.
|
||||
|
||||
Le source est fourni non pas à titre d'exemple (il n'est pas d'une clarté éblouissante),
|
||||
mais à titre de documentation (et de spécification :)) ; il n'est ni
|
||||
nécessaire, ni conseillé de le modifier pour réaliser le travail demandé : l'analyse
|
||||
réalisée est suffisante pour la syntaxe des commandes que votre shell doit interpréter.
|
||||
|
||||
Ce code est a priori tout à fait fiable, mais n'hésitez cependant pas à nous signaler
|
||||
ce qui vous apparaîtrait comme des anomalies, ou des lacunes dans la documentation.
|
||||
|
||||
La structure `cmdline`
|
||||
----------------------
|
||||
L'appel à readcmd() retourne une structure cmdline, qui contient le résultat de l'analyse
|
||||
de la ligne de commande pour le shell.
|
||||
Cette structure contient notamment :
|
||||
|
||||
- l'indication du fait que la commande doit être lancée en tâche de fond ou non
|
||||
- les redirections éventuelles
|
||||
- la décomposition de la ligne de commande en commandes élémentaires, et la décomposition
|
||||
de chaque commande en mots. Le champ `seq` référence le résultat de cette décomposition,
|
||||
qui est vu comme un tableau à deux dimensions [commandes élémentaires, mots de la commande]
|
||||
|
||||
**Pour plus de détails, voir le fichier d'en-tête `readcmd.h`.**
|
||||
|
||||
**Exemples :**
|
||||
|
||||
- "ls -l" : seq[0][0] = "ls", seq[0][1] = "-l", seq[0][2] = NULL, seq[1] = NULL, backgrounded = NULL, in = NULL, out = NULL
|
||||
- "ls -l > toto" : seq[0][0] = "ls", seq[0][1] = "-l", seq[0][2] = NULL,
|
||||
seq[1] = NULL, backgrounded = NULL, in = NULL, out => "toto"
|
||||
- "ls | grep toto | wc -l" : seq[0][0] = "ls", seq[0][1] = NULL,
|
||||
seq[1][0] = "grep", seq[1][1] = "toto", seq[1][2] = NULL,
|
||||
seq[2][0] = "wc", seq[0][1] = "-l", seq[0][2] = NULL,
|
||||
seq[3] = NULL, backgrounded = NULL, in = NULL, out = NULL
|
||||
- "sleep 100 &" : seq[0][0] = "sleep", seq[0][1] = "20", backgrounded != NULL, in = NULL, out = NULL
|
85
jobs.c
Normal file
85
jobs.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "jobs.h"
|
||||
|
||||
void ajouter(list* l_ptr, int pid, int id) {
|
||||
|
||||
cell* new_cell = malloc(sizeof(*new_cell));
|
||||
|
||||
new_cell->pid = pid;
|
||||
new_cell->id = id;
|
||||
|
||||
if (*l_ptr == NULL) {
|
||||
new_cell->next = NULL;
|
||||
} else {
|
||||
new_cell->next = *l_ptr;
|
||||
}
|
||||
|
||||
*l_ptr = new_cell;
|
||||
|
||||
printf("[%d] %d\n", id, pid);
|
||||
|
||||
}
|
||||
|
||||
void supprimer(list* l_ptr, int pid) {
|
||||
|
||||
cell* cursor = *l_ptr;
|
||||
|
||||
if (cursor->pid == pid) {
|
||||
cell* cursor2free = cursor;
|
||||
*l_ptr = cursor->next;
|
||||
free(cursor2free);
|
||||
} else {
|
||||
while (cursor->next != NULL) {
|
||||
if (cursor->next->pid == pid) {
|
||||
break;
|
||||
} else {
|
||||
cursor = cursor->next;
|
||||
}
|
||||
}
|
||||
cell* cursor_next = cursor->next->next;
|
||||
free(cursor->next);
|
||||
cursor->next = cursor_next;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
void afficher(list* l_ptr) {
|
||||
|
||||
cell* cursor = *l_ptr;
|
||||
|
||||
while (cursor != NULL) {
|
||||
printf("[%d] %d\n", cursor->id, cursor->pid);
|
||||
cursor = cursor->next;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void initialiser(list* l_ptr) {
|
||||
*l_ptr = NULL;
|
||||
}
|
||||
|
||||
void liberer(list* l_ptr) {
|
||||
free(*l_ptr);
|
||||
*l_ptr = NULL;
|
||||
}
|
||||
|
||||
int est_vide(list l_ptr) {
|
||||
return l_ptr == NULL;
|
||||
}
|
||||
|
||||
int contiens(list* l_ptr, int pid) {
|
||||
|
||||
cell* cursor = *l_ptr;
|
||||
|
||||
while (cursor != NULL) {
|
||||
if (cursor->pid == pid) {
|
||||
return 1;
|
||||
}
|
||||
cursor = cursor->next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
20
jobs.h
Normal file
20
jobs.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#ifndef __JOBS_H
|
||||
#define __JOBS_H
|
||||
|
||||
typedef struct cell cell;
|
||||
struct cell {
|
||||
int pid;
|
||||
int id;
|
||||
cell* next;
|
||||
};
|
||||
|
||||
typedef cell* list;
|
||||
|
||||
void ajouter(cell** list, int pid, int id);
|
||||
void supprimer(cell** list, int pid);
|
||||
void afficher(cell** list);
|
||||
void initialiser(list* list);
|
||||
void liberer(list* list);
|
||||
int contiens(list* l_ptr, int pid);
|
||||
|
||||
#endif
|
102
minishell.c
Normal file
102
minishell.c
Normal file
|
@ -0,0 +1,102 @@
|
|||
#define _GNU_SOURCE
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/wait.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <setjmp.h>
|
||||
#include "readcmd.h"
|
||||
#include "jobs.h"
|
||||
|
||||
int prompting = 0;
|
||||
int job_id = 1;
|
||||
list jobs;
|
||||
|
||||
void handler_print(int signal_num, siginfo_t* info) {
|
||||
if (contiens(&jobs, info->si_pid)) {
|
||||
if (prompting) { printf("\n"); }
|
||||
printf("[%d] --> (%d) %s\n", info->si_pid, signal_num, strsignal(signal_num));
|
||||
supprimer(&jobs, info->si_pid);
|
||||
job_id--;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
extern int errno;
|
||||
|
||||
initialiser(&jobs);
|
||||
|
||||
struct sigaction action;
|
||||
action.sa_flags = SA_SIGINFO; //| SA_RESTART;
|
||||
action.sa_handler = handler_print;
|
||||
sigemptyset(&action.sa_mask);
|
||||
sigaction(SIGCHLD, &action, NULL);
|
||||
|
||||
|
||||
char initcd[256], currentcd[256];
|
||||
getcwd(initcd, 256);
|
||||
|
||||
while (1) {
|
||||
getcwd(currentcd, 256);
|
||||
printf("%s >>> ", currentcd);
|
||||
prompting = 1;
|
||||
struct cmdline* cmd = readcmd();
|
||||
prompting = 0;
|
||||
|
||||
if (cmd == NULL || cmd->seq[0] == NULL) { // ligne vide, on skip
|
||||
continue;
|
||||
} else if (!strcmp(cmd->seq[0][0], "exit")) { // on quitte le shell
|
||||
break;
|
||||
} else if (!strcmp(cmd->seq[0][0], "cd")) { // cd
|
||||
if (cmd->seq[0][1] == NULL) { // vide
|
||||
chdir(initcd);
|
||||
} else { // avec un path
|
||||
chdir(cmd->seq[0][1]);
|
||||
}
|
||||
continue;
|
||||
} else if (!strcmp(cmd->seq[0][0], "jobs")) {
|
||||
afficher(&jobs);
|
||||
continue;
|
||||
}
|
||||
|
||||
pid_t pidFils = fork();
|
||||
|
||||
if (pidFils == -1) {
|
||||
fprintf(stderr, "ERROR: forking failed, %s\n", strerror(errno));
|
||||
exit(errno);
|
||||
}
|
||||
|
||||
if (pidFils == 0) { // fils
|
||||
execvp(cmd->seq[0][0], cmd->seq[0]);
|
||||
exit(errno); // si execlp échoue on exit avec une erreur
|
||||
|
||||
} else { // père
|
||||
if (cmd->backgrounded) {
|
||||
ajouter(&jobs, pidFils, job_id++);
|
||||
} else {
|
||||
jmp_buf env;
|
||||
setjmp(env);
|
||||
int codeTerm;
|
||||
pid_t idFils = waitpid(pidFils, &codeTerm, 0); // on attend la fin de l'exec du fils
|
||||
|
||||
if (idFils == -1){
|
||||
|
||||
// todo : peut mieux faire ?
|
||||
if (errno == 4) {
|
||||
longjmp(env, 1);
|
||||
}
|
||||
|
||||
fprintf(stderr, "ERROR: waiting for %d failed, (%d) %s\n", codeTerm, errno, strerror(errno));
|
||||
exit(errno);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
BIN
minishell.pdf
Normal file
BIN
minishell.pdf
Normal file
Binary file not shown.
297
readcmd.c
Executable file
297
readcmd.c
Executable file
|
@ -0,0 +1,297 @@
|
|||
/*
|
||||
* Copyright (C) 2002, Simon Nieuviarts
|
||||
*/
|
||||
|
||||
/*
|
||||
* Backgrounding added. [PM] Ajout d'une rustine nécessaire : lignes 153 et 293 commentées
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include "readcmd.h"
|
||||
|
||||
|
||||
static void memory_error(void)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
perror(0);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
static void *xmalloc(size_t size)
|
||||
{
|
||||
void *p = malloc(size);
|
||||
if (!p) memory_error();
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
static void *xrealloc(void *ptr, size_t size)
|
||||
{
|
||||
void *p = realloc(ptr, size);
|
||||
if (!p) memory_error();
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
/* Read a line from standard input and put it in a char[] */
|
||||
static char *readline(void)
|
||||
{
|
||||
size_t buf_len = 16;
|
||||
char *buf = xmalloc(buf_len * sizeof(char));
|
||||
|
||||
if (fgets(buf, buf_len, stdin) == NULL) {
|
||||
free(buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
do {
|
||||
size_t l = strlen(buf);
|
||||
if ((l > 0) && (buf[l-1] == '\n')) {
|
||||
l--;
|
||||
buf[l] = 0;
|
||||
return buf;
|
||||
}
|
||||
if (buf_len >= (INT_MAX / 2)) memory_error();
|
||||
buf_len *= 2;
|
||||
buf = xrealloc(buf, buf_len * sizeof(char));
|
||||
if (fgets(buf + l, buf_len - l, stdin) == NULL) return buf;
|
||||
} while (1);
|
||||
}
|
||||
|
||||
|
||||
/* Split the string in words, according to the simple shell grammar. */
|
||||
static char **split_in_words(char *line)
|
||||
{
|
||||
char *cur = line;
|
||||
char **tab = 0;
|
||||
size_t l = 0;
|
||||
char c;
|
||||
|
||||
while ((c = *cur) != 0) {
|
||||
char *w = 0;
|
||||
char *start;
|
||||
switch (c) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
/* Ignore any whitespace */
|
||||
cur++;
|
||||
break;
|
||||
case '<':
|
||||
w = "<";
|
||||
cur++;
|
||||
break;
|
||||
case '>':
|
||||
w = ">";
|
||||
cur++;
|
||||
break;
|
||||
case '|':
|
||||
w = "|";
|
||||
cur++;
|
||||
break;
|
||||
case '&':
|
||||
w = "&";
|
||||
cur++;
|
||||
break;
|
||||
default:
|
||||
/* Another word */
|
||||
start = cur;
|
||||
while (c) {
|
||||
c = *++cur;
|
||||
switch (c) {
|
||||
case 0:
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '<':
|
||||
case '>':
|
||||
case '|':
|
||||
case '&':
|
||||
c = 0;
|
||||
break;
|
||||
default: ;
|
||||
}
|
||||
}
|
||||
w = xmalloc((cur - start + 1) * sizeof(char));
|
||||
strncpy(w, start, cur - start);
|
||||
w[cur - start] = 0;
|
||||
}
|
||||
if (w) {
|
||||
tab = xrealloc(tab, (l + 1) * sizeof(char *));
|
||||
tab[l++] = w;
|
||||
}
|
||||
}
|
||||
tab = xrealloc(tab, (l + 1) * sizeof(char *));
|
||||
tab[l++] = 0;
|
||||
return tab;
|
||||
}
|
||||
|
||||
|
||||
static void freeseq(char ***seq)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
for (i=0; seq[i]!=0; i++) {
|
||||
char **cmd = seq[i];
|
||||
|
||||
for (j=0; cmd[j]!=0; j++) free(cmd[j]);
|
||||
free(cmd);
|
||||
}
|
||||
free(seq);
|
||||
}
|
||||
|
||||
|
||||
/* Free the fields of the structure but not the structure itself */
|
||||
static void freecmd(struct cmdline *s)
|
||||
{
|
||||
if (s->in) free(s->in);
|
||||
if (s->out) free(s->out);
|
||||
// if (s->backgrounded) free(s->backgrounded);
|
||||
if (s->seq) freeseq(s->seq);
|
||||
}
|
||||
|
||||
|
||||
struct cmdline *readcmd(void)
|
||||
{
|
||||
static struct cmdline *static_cmdline = 0;
|
||||
struct cmdline *s = static_cmdline;
|
||||
char *line;
|
||||
char **words;
|
||||
int i;
|
||||
char *w;
|
||||
char **cmd;
|
||||
char ***seq;
|
||||
size_t cmd_len, seq_len;
|
||||
|
||||
line = readline();
|
||||
if (line == NULL) {
|
||||
if (s) {
|
||||
freecmd(s);
|
||||
free(s);
|
||||
}
|
||||
return static_cmdline = 0;
|
||||
}
|
||||
|
||||
cmd = xmalloc(sizeof(char *));
|
||||
cmd[0] = 0;
|
||||
cmd_len = 0;
|
||||
seq = xmalloc(sizeof(char **));
|
||||
seq[0] = 0;
|
||||
seq_len = 0;
|
||||
|
||||
words = split_in_words(line);
|
||||
free(line);
|
||||
|
||||
if (!s)
|
||||
static_cmdline = s = xmalloc(sizeof(struct cmdline));
|
||||
else
|
||||
freecmd(s);
|
||||
s->err = 0;
|
||||
s->in = 0;
|
||||
s->out = 0;
|
||||
s->backgrounded = 0;
|
||||
s->seq = 0;
|
||||
|
||||
i = 0;
|
||||
while ((w = words[i++]) != 0) {
|
||||
switch (w[0]) {
|
||||
case '&':
|
||||
if(s->backgrounded){
|
||||
s->err = "error on &";
|
||||
goto error;
|
||||
}
|
||||
s->backgrounded = &w[0];
|
||||
break;
|
||||
case '<':
|
||||
/* Tricky : the word can only be "<" */
|
||||
if (s->in) {
|
||||
s->err = "only one input file supported";
|
||||
goto error;
|
||||
}
|
||||
if (words[i] == 0) {
|
||||
s->err = "filename missing for input redirection";
|
||||
goto error;
|
||||
}
|
||||
s->in = words[i++];
|
||||
break;
|
||||
case '>':
|
||||
/* Tricky : the word can only be ">" */
|
||||
if (s->out) {
|
||||
s->err = "only one output file supported";
|
||||
goto error;
|
||||
}
|
||||
if (words[i] == 0) {
|
||||
s->err = "filename missing for output redirection";
|
||||
goto error;
|
||||
}
|
||||
s->out = words[i++];
|
||||
break;
|
||||
case '|':
|
||||
/* Tricky : the word can only be "|" */
|
||||
if (cmd_len == 0) {
|
||||
s->err = "misplaced pipe";
|
||||
goto error;
|
||||
}
|
||||
|
||||
seq = xrealloc(seq, (seq_len + 2) * sizeof(char **));
|
||||
seq[seq_len++] = cmd;
|
||||
seq[seq_len] = 0;
|
||||
|
||||
cmd = xmalloc(sizeof(char *));
|
||||
cmd[0] = 0;
|
||||
cmd_len = 0;
|
||||
break;
|
||||
default:
|
||||
cmd = xrealloc(cmd, (cmd_len + 2) * sizeof(char *));
|
||||
cmd[cmd_len++] = w;
|
||||
cmd[cmd_len] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd_len != 0) {
|
||||
seq = xrealloc(seq, (seq_len + 2) * sizeof(char **));
|
||||
seq[seq_len++] = cmd;
|
||||
seq[seq_len] = 0;
|
||||
} else if (seq_len != 0) {
|
||||
s->err = "misplaced pipe";
|
||||
i--;
|
||||
goto error;
|
||||
} else
|
||||
free(cmd);
|
||||
free(words);
|
||||
s->seq = seq;
|
||||
return s;
|
||||
error:
|
||||
while ((w = words[i++]) != 0) {
|
||||
switch (w[0]) {
|
||||
case '<':
|
||||
case '>':
|
||||
case '|':
|
||||
case '&':
|
||||
break;
|
||||
default:
|
||||
free(w);
|
||||
}
|
||||
}
|
||||
free(words);
|
||||
freeseq(seq);
|
||||
for (i=0; cmd[i]!=0; i++) free(cmd[i]);
|
||||
free(cmd);
|
||||
if (s->in) {
|
||||
free(s->in);
|
||||
s->in = 0;
|
||||
}
|
||||
if (s->out) {
|
||||
free(s->out);
|
||||
s->out = 0;
|
||||
}
|
||||
if (s->backgrounded) {
|
||||
// free(s->backgrounded);
|
||||
s->out = 0;
|
||||
}
|
||||
return s;
|
||||
}
|
40
readcmd.h
Executable file
40
readcmd.h
Executable file
|
@ -0,0 +1,40 @@
|
|||
#ifndef __READCMD_H
|
||||
#define __READCMD_H
|
||||
|
||||
/* Lit une ligne de commande depuis l'entrée standard.
|
||||
* Remarque :
|
||||
* Dans le cas où suffisamment de mémoire ne peut être allouée pour le résultat,
|
||||
* affiche un message d'erreur et appelle exit().
|
||||
*/
|
||||
|
||||
struct cmdline *readcmd(void);
|
||||
/* Structure retournée par readcmd()
|
||||
* Remarques utiles :
|
||||
* - readcmd() peut retourner null, dans le cas où le processus en attente sur readcmd reçoit un signal
|
||||
* - en cas d'appels successifs,
|
||||
* readcmd() réutilise la mémoire allouée à la structure cmdline qu'elle retourne
|
||||
*/
|
||||
struct cmdline {
|
||||
char *err; /* Si non null : message d'erreur à afficher.
|
||||
* Dans ce cas, les autres champs sont nuls. */
|
||||
char *in; /* Si non null : nom du fichier vers lequel l'entrée doit être redirigée. */
|
||||
char *out; /* Si non null : nom du fichier vers lequel la sortie doit être redirigée. */
|
||||
char *backgrounded; /* Si non null : commande en tâche de fond */
|
||||
char ***seq; /* Une ligne de commande est une suite de commandes liées par des tubes
|
||||
* Ainsi,
|
||||
* - une commande est un tableau de chaînes de caractères (char **).
|
||||
* Chaque élément de ce tableau est en effet une chaîne (char *)
|
||||
* correspondant à un argument de la commande.
|
||||
* Le dernier pointeur du tableau est suivi d'un pointeur null, qui
|
||||
* marque la fin du tableau.
|
||||
* - seq est un tableau de commandes (char ***), chaque commande de ce
|
||||
* tableau correspondant à une commande élémentaire du pipeline.
|
||||
* Le dernier élément (char **) du tableau est ici aussi suivi d'un pointeur null.
|
||||
* - Dans le cas particulier où une ligne vide est saisie, le pointeur référencé par
|
||||
* seq vaut directement NULL : puisque le tableau n'a aucun élément, le marqueur
|
||||
* de fin de tableau se trouve référencé par seq, ce que l'on peut écrire
|
||||
* (*seq == NULL)
|
||||
* (ou (seq[0] == NULL), mais cela peut prêter à confusion, seq[0] n'existant pas)
|
||||
*/
|
||||
};
|
||||
#endif
|
Loading…
Reference in a new issue