-
Notifications
You must be signed in to change notification settings - Fork 0
Minishell project 42
🐚 Minishell – panoramica
“Minishell è una shell semplificata che replica il comportamento base di bash: legge un input, lo tokenizza, lo analizza, costruisce i comandi e li esegue gestendo builtin, segnali, redirections e pipeline.”
🧩 Token & Parser
“Il lexer divide l’input in token, che rappresentano le unità minime come parole e operatori. Il parser usa questi token per costruire una lista di comandi (t_cmd), ognuno con argomenti e redirections.”
🔗 Builder
“Il builder fa da ponte tra parser ed execution: converte le strutture del parser in strutture più semplici e lineari, pronte per essere eseguite, come t_exec_cmd e t_pipeline.”
🧠 Builtin
“Le builtin sono comandi interni alla shell. Quelle che modificano lo stato della shell, come cd, export, unset ed exit, devono essere eseguite nel processo parent. Se invece una builtin è in una pipeline, viene eseguita in un child, come in bash.”
🔁 Parent / Child
“Il parent è il processo shell che mantiene lo stato globale, mentre i child sono processi temporanei creati con fork per eseguire i comandi. Le modifiche fatte dai child non tornano al parent.”
🔀 Execution
“La fase di execution gestisce fork, execve, pipe, redirections ed heredoc. Se c’è una pipeline, ogni comando viene eseguito in un processo separato e collegato tramite pipe. L’exit code finale è quello dell’ultimo comando, come in bash.”
📄 Redirections & enum
“Le redirections sono rappresentate con un enum (R_IN, R_OUT, R_APP, R_HEREDOC) per distinguere chiaramente i diversi tipi di input e output e gestirli con switch-case.”
🧠 Heredoc
“Gli heredoc vengono preparati prima dell’esecuzione: il contenuto viene letto, scritto su un file temporaneo e poi trattato come un normale input file.”
🚦 Signals
“La gestione dei segnali cambia in base al contesto: nel prompt SIGINT non chiude la shell, durante l’esecuzione i segnali colpiscono solo i child, garantendo un comportamento coerente con bash.”
📦 BUILDER – collegamento parser → execution
Builder fa da ponte tra il parser e l’esecuzione. Il parser produce una struttura comoda per analizzare la sintassi, ma non ideale per l’esecuzione. Il builder converte questi dati in strutture più semplici e lineari, pronte per fork, pipe e redirections.
In pratica cosa fa il builder:
Trasforma t_cmd (output del parser) in:
t_exec_cmd → argv + redirections
t_pipeline → array di comandi + numero totale
Duplica in modo sicuro:
argv
liste di redirections
Mantiene l’ordine delle redirections (fondamentale: l’ultima vince)
Fornisce funzioni di free centralizzate, per evitare memory leak
Punto tecnico che puoi sottolineare
“Il builder separa completamente parsing ed execution: execution non conosce token o sintassi, ma solo comandi già pronti.”
🧩 BUILTIN – comandi interni alla shell
Builtin, cioè quei comandi che non possono essere eseguiti con execve perché devono modificare lo stato della shell.
Builtin principali
cd → cambia directory del processo shell
export / unset → modificano l’ambiente
exit → termina la shell
echo, pwd, env → output standard
“Le builtin vengono eseguite nel parent solo quando non sono in pipeline, perché altrimenti le modifiche andrebbero perse nel child.”
In pipeline → builtin nel child (comportamento bash)
Fuori pipeline → builtin nel parent, ma:
salvo stdin/stdout
applico redirections
ripristino i fd dopo l’esecuzione
Altro punto tecnico forte
Gestione exit code coerente con bash
Validazione identificatori (export, unset)
Gestione errori (exit con argomenti non numerici, troppi argomenti, ecc.)
🚦 SIGNALS – comportamento interattivo corretto
Gestione dei segnali, fondamentale per rendere la shell utilizzabile come bash.
Comportamenti implementati
Prompt interattivo
Ctrl-C:
non chiude la shell
pulisce la riga corrente
ristampa il prompt
Durante l’esecuzione
il parent ignora SIGINT/SIGQUIT
i child ripristinano il comportamento di default
Heredoc
Ctrl-C interrompe correttamente l’input heredoc
Frase molto efficace
“La gestione dei segnali cambia in base al contesto: prompt, child, parent in esecuzione. Questo evita che la shell muoia quando si interrompe un comando.”
Mi sono occupata dei moduli che collegano parsing, esecuzione e comportamento interattivo: il builder prepara i comandi, le builtin gestiscono lo stato della shell, i segnali garantiscono un comportamento coerente con bash, e l’execution coordina fork, pipe, redirections e heredoc.
1 Builder → ponte parser/execution
2 Builtin → comandi che modificano la shell, parent vs child
3 Signals → comportamento corretto di Ctrl-C / Ctrl-\
4 Execution → fork, pipe, redirections, heredoc, exit code
****BUILTIN: ****
🔹 cd – change directory Cosa fa
Cambia la directory di lavoro del processo shell.
Senza argomenti → va in $HOME
Con un path → prova a spostarsi lì
Aggiorna le variabili:
PWD
OLDPWD
Perché è una builtin
Perché chdir() cambia la directory solo del processo che la chiama.
Se cd fosse eseguito con execve in un child:
il child cambierebbe directory
il parent (la shell) resterebbe nella directory precedente ➡️ comportamento sbagliato
“cd è una builtin perché deve modificare lo stato del processo shell; se fosse eseguita in un child, il cambio di directory andrebbe perso.”
🔹 export – aggiunge/modifica variabili d’ambiente
Senza argomenti → stampa le variabili d’ambiente in ordine alfabetico
Con argomenti:
export VAR=value → crea o modifica la variabile
export VAR → la esporta senza valore
Controlli importanti
Il nome della variabile deve:
iniziare con lettera o _
contenere solo alfanumerici o _
Se non valido → errore, ma la shell non termina
Perché è una builtin
Perché modifica l’ambiente della shell.
Un execve può solo leggere l’ambiente, non modificarlo per il parent.
“export gestisce l’ambiente della shell, quindi deve essere eseguita nel parent per rendere le variabili disponibili ai comandi successivi.”
🔹 unset – rimuove variabili d’ambiente
Rimuove una o più variabili dall’ambiente:
unset VAR
Regole
Stesse regole di validazione di export
Se la variabile non esiste → nessun errore
Perché è una builtin
Perché modifica direttamente l’ambiente della shell.
“unset elimina variabili dall’ambiente della shell e quindi deve essere eseguita nel processo principale.”
🔹 exit – termina la shell
Senza argomenti → termina la shell con exit code corrente
Con argomento numerico → termina con quel valore (mod 256)
Con argomento non numerico:
stampa errore
exit code 2
Con troppi argomenti:
stampa errore
non termina la shell
Perché è una builtin
Perché deve terminare la shell stessa, non un processo figlio.
Se fosse in un child → la shell continuerebbe a girare.
“exit è una builtin perché deve interrompere il processo shell e gestire correttamente i codici di uscita come bash.”
🔹 echo – stampa su standard output
Stampa gli argomenti
Supporta l’opzione:
-n → non stampa il newline finale
Esempio:
echo -n ciao
Perché è una builtin
In bash è una builtin per efficienza e compatibilità
In minishell:
non modifica lo stato della shell
può essere eseguita anche in pipeline
echo è una builtin semplice: gestisce solo output, ma deve rispettare il comportamento di bash, come l’opzione -n.
🔹 pwd – print working directory
Stampa la directory corrente
Implementazione
Usa getcwd() oppure il valore di PWD
Perché è una builtin
In bash è builtin
In minishell è semplice e veloce
Frase breve
“pwd stampa la directory corrente della shell.”
🔹 env – stampa l’ambiente
Stampa tutte le variabili d’ambiente nel formato:
VAR=value
Solo quelle con valore assegnato
Nota importante
Non stampa variabili senza =
Non modifica nulla
Perché è una builtin
Accesso diretto all’ambiente della shell
“env stampa l’ambiente corrente, ed è utile per verificare le variabili esportate.”
Builtin + pipeline e discorso parent / child Cos’è una pipeline
Una pipeline è una sequenza di comandi collegati con |:
ls | grep txt | wc -l
Ogni comando:
legge da stdin
scrive su stdout
lo stdout di uno diventa lo stdin del successivo
👉 Per funzionare, ogni comando della pipeline deve stare in un processo diverso.
Builtin in pipeline
Esempio:
export VAR=42 | cat
In bash:
export viene eseguito in un child
la variabile non rimane nella shell
Regola importante
Se una builtin è dentro una pipeline, viene eseguita in un child.
Questo vale anche per:
cd
export
unset
exit
Builtin NON in pipeline export VAR=42 cd src
Qui:
la builtin viene eseguita nel parent
modifica lo stato della shell
“Le builtin vengono eseguite nel parent solo se non fanno parte di una pipeline; in pipeline devono stare in un child per rispettare il modello dei processi.”
🧠 Parent e Child: concetto base Parent
È il processo minishell
Mostra il prompt
Mantiene:
directory corrente
ambiente
exit status
Child
Creato con fork()
Esegue:
comandi esterni
builtin in pipeline
Muore dopo l’esecuzione
👉 Tutto ciò che il child modifica non torna al parent.
🏠 Cos’è $HOME
$HOME è una variabile d’ambiente.
Contiene:
/home/nome_utente
A cosa serve
Indica la directory “personale” dell’utente
Usata da:
cd (senza argomenti)
programmi che salvano config
“$HOME è una variabile d’ambiente che indica la directory personale dell’utente.”
🐚 Cos’è bash
Bash è una shell, cioè un programma che:
legge comandi da tastiera
li interpreta
li esegue
Esempi di shell
bash
zsh
fish
minishell (la vostra!)
Perché si cita bash
È lo standard di riferimento
Minishell deve comportarsi “come bash”:
exit code
pipeline
segnali
builtin
“Bash è la shell di riferimento: minishell cerca di riprodurne il comportamento di base.”
🚦 SIGINT e SIGQUIT
Sono segnali UNIX, usati per comunicare con i processi.
🔹 SIGINT (Interrupt) Come si genera
Ctrl-C
Comportamento standard
Termina il processo
In minishell
Nel prompt:
non chiude la shell
pulisce la riga
ristampa il prompt
Durante l’esecuzione:
colpisce il child
il parent resta vivo
“SIGINT interrompe il comando in esecuzione, ma non deve chiudere la shell.”
🔹 SIGQUIT (Quit) Come si genera
Ctrl-\
Comportamento standard
Termina il processo
produce un core dump
In minishell
ignorato nel prompt
ripristinato nei child
“SIGQUIT viene ignorato dalla shell ma lasciato attivo nei processi figli.”
token Token = unità minima del comando
Esempio:
echo "ciao" > file
Token:
echo → WORD
"ciao" → WORD
→ REDIR_OUT
file → WORD
A cosa servono
Il lexer divide l’input in token
Il parser usa i token per costruire i comandi
“I token sono i mattoni di base dell’input, prodotti dal lexer e usati dal parser.”
Enum redirections enum e_redir_type { R_IN, // < R_OUT, // > R_APP, // >> R_HEREDOC // << };
Cosa rappresentano
R_IN → input da file
R_OUT → output su file (truncate)
R_APP → output in append
R_HEREDOC → input multilinea da stdin
Perché usare un enum
codice più leggibile
evita stringhe o numeri magici
facile switch-case
“Le redirections sono rappresentate con un enum per distinguere chiaramente i diversi tipi di input e output.” Minishell replica il comportamento di bash: tokenizza l’input, costruisce i comandi, gestisce builtin, segnali, redirections e pipeline usando fork e pipe, distinguendo tra parent e child per preservare lo stato della shell.