17 Commits

Author SHA1 Message Date
3b7a11c185 chore: removed unused files 2026-02-14 17:55:37 +01:00
5dc7de73b3 update: updated readme 2026-02-14 17:53:44 +01:00
640c22d366 update: updated readme detailed info 2026-02-14 17:37:38 +01:00
856bc8243f update: added more internal variables 2026-02-14 17:37:22 +01:00
fbd2349060 chore: added minishell version macro 2026-02-14 17:37:09 +01:00
marcnava-42cursus
77a704a09a norma arreglada 2026-02-14 15:12:25 +01:00
marcnava-42cursus
39e5719183 terminado 2026-02-14 15:04:25 +01:00
marcnava-42cursus
c1e622947d fixed heredoc expansion 2026-02-14 14:37:56 +01:00
marcnava-42cursus
001709139b Merge branch 'fix/variable_expansion' into solo 2026-02-14 14:27:52 +01:00
marcnava-42cursus
73ed56aa16 heredoc and builtins fix 2026-02-14 14:26:58 +01:00
5852e8618f multiple fixes 2026-02-14 13:48:15 +01:00
marcnava-42cursus
ba40670ace Fixed variable expansion errors 2026-02-14 13:33:44 +01:00
marcnava-42cursus
dd6101edec A POR TODAAS 2026-02-14 06:14:59 +01:00
e2b734cf0c save commit 2026-02-14 05:48:18 +01:00
32b3bd72b5 fix: prompt was not being requested to hashmap 2026-02-14 02:05:49 +01:00
c95703b42b update: replaced set_env by set_var in export builtin 2026-02-14 02:02:52 +01:00
f4cfae1107 feat: added internal variables 2026-02-14 02:01:10 +01:00
61 changed files with 2503 additions and 1443 deletions

2
.gitignore vendored
View File

@@ -69,5 +69,3 @@ dkms.conf
# debug information files
*.dwo
minishell-codex/

View File

@@ -1,72 +0,0 @@
# Repository Guidelines
## Project Structure & Module Organization
- `src/` holds the shell implementation, with submodules for `builtins/`, `executor/`, `parser/`, and `variables/`.
- `src/builtins/` currently includes: `cd`, `echo`, `env`, `exit`, `export`, `pwd`, `unset`.
- `src/executor/` includes pipeline/process orchestration plus file redirections in `redirections.c`.
- `include/` contains public headers used across modules.
- `lib/` is populated at build time with third-party 42 libraries (libft, get_next_line, ft_printf, ft_args).
- `docs/` stores project references and manual test notes (see `docs/tests.md`).
- `build/` is the object output directory generated by `make`.
## Build, Test, and Development Commands
- `make` or `make all`: build `minishell` (auto-clones required libs into `lib/`).
- `make clean`: remove objects in `build/`.
- `make fclean`: remove objects and the `minishell` binary (also cleans libs).
- `make re`: full rebuild.
- `./minishell`: run locally after a build.
- `make DEBUG=lldb` or `make DEBUG=valgrind` or `make DEBUG=address`: rebuild with debug/ASan-friendly flags.
## Coding Style & Naming Conventions
- The codebase follows 42 **Norminette v4** rules. Run `norminette *.c *.h` (or on specific files) before submitting changes.
- Keep file names lowercase with underscores (e.g., `src/builtins/echo/echo.c`).
- Keep headers in `include/` and expose only what modules need.
- Before adding or changing code, check `allowed.txt` to know which functions are permitted.
- Any function not listed in `allowed.txt` is not allowed in this project.
## Parser & Lexer Functionality (Current `src/parser`)
- Runtime entrypoint is `parse(line, minishell)` from `src/minishell.c` (`readline -> parse -> execute`).
- `parse` calls `lex(line)` and then converts token lists to `t_command` nodes with `parse_tokens`.
- `command_new` builds one command from tokens up to `TOKEN_PIPE`.
- `words_add` stores consecutive `TOKEN_WORD` tokens in `command->argv` and increments `argc`.
- `expand_envs` is currently a TODO (no `$VAR` expansion is applied in parser stage).
- Redirection tokens are converted into `t_redirection` and stored in `t_command.redirections`; heredocs are stored in `t_command.heredocs`.
- Path resolution is handled in executor (`executor_resolve_command_path`) before `execve`.
- `src/parser/lexer.c` provides a separate lexer (`lex`) that tokenizes into `TOKEN_WORD`, `TOKEN_PIPE`, `TOKEN_REDIRECT_IN`, `TOKEN_REDIRECT_OUT`, `TOKEN_APPEND`, and `TOKEN_HEREDOC`.
- The lexer tracks single/double quote context so metacharacters inside quotes remain part of words.
- Meta runs are read as contiguous chunks in `read_token` (for example, repeated `|`/`<`/`>` are captured as one token value).
- Current parser flow consumes lexer output directly.
## Executor Redirections (Current `src/executor`)
- File redirections are applied in `src/executor/redirections.c` via `open` + `dup2` before command execution.
- Supported file redirections: input `<`, output truncate `>`, output append `>>`.
- Redirections are applied for both forked commands (child path) and single builtins executed in parent.
- Parent-builtin redirections save/restore `STDIN_FILENO` and `STDOUT_FILENO` after builtin execution.
## Parser & Lexer Known Gaps
- Heredoc tokens are parsed and stored, but runtime heredoc execution/input feeding is still pending in executor.
- No explicit unmatched-quote syntax error handling is implemented in parser/lexer path.
## Testing Guidelines
- There is no automated test runner. Use manual checks in `docs/tests.md` and basic shell behavior checks (pipes, redirects, builtins).
- A local builtin edge-case script exists at `tests/builtins_edge_cases.sh` (expects a compiled `./minishell`).
- When debugging memory issues, run under valgrind and use the suppression file in `valgrind/readline.supp`.
## Builtins Status
- `cd`: handles `HOME` fallback, `cd -` via `OLDPWD`, updates `PWD`/`OLDPWD`, and returns failure on invalid usage (`too many arguments`, missing `HOME`/`OLDPWD`).
- `echo`: supports repeated `-n` flags (`-n`, `-nnn`) and prints remaining args preserving spaces between tokens.
- `env`: prints current environment as `KEY=VALUE`; returns failure when called with extra arguments.
- `exit`: validates numeric argument (with overflow checks), returns `1` on `too many arguments` without exiting, and exits with `2` on non-numeric argument.
- `export`: supports `NAME=VALUE` and `NAME` (stored as empty value), validates identifiers, and returns failure when any identifier is invalid.
- `pwd`: prints working directory using dynamic `getcwd` and returns failure if `getcwd` fails.
- `unset`: validates identifiers and removes matching variables from the environment map.
## Commit & Pull Request Guidelines
- Commit messages in this repo use a simple `type: summary` format (examples: `update: ...`, `fix: ...`). Keep summaries short and specific.
- For PRs, include:
1. What changed and why.
2. How to test (commands or manual steps).
3. Notes on any parser/executor/builtin behavior changes.
## Configuration Tips
- The project depends on `readline`; ensure your system has `libreadline-dev` or equivalent before building.

View File

@@ -1,3 +1,4 @@
<!-- *This project has been made by sede-san and padan-pe* -->
<div align="center">
<!-- Project badge -->
@@ -30,7 +31,21 @@
> The purpose of this project is to create a simple bash-like shell.
DETAILED INFO
**minishell** is a minimal bash-like shell implementation written in C. It provides essential shell functionality including command execution, piping, input/output redirection, and built-in commands.
### Key Features
- **Command Execution**: Execute external programs with argument parsing and path resolution.
- **Pipelines**: Connect multiple commands using the pipe operator (`|`).
- **Redirections**: Support for input (`<`), output (`>`), and append (`>>`) file redirections.
- **Built-in Commands**: `cd`, `echo`, `env`, `exit`, `export`, `pwd`, `unset`.
- **Environment Variables**: Access and manage shell environment variables.
- **Interactive Shell**: Read-Evaluate-Print Loop (REPL) powered by GNU Readline.
- **Quote Handling**: Proper handling of single and double quotes for literal strings.
### Development
The codebase adheres to **Norminette v4** standards and uses a modular architecture separating parsing, execution, and built-in command logic. Build artifacts are automatically generated; refer to `AGENTS.md` for detailed development guidelines.
For detailed info, refer to this project [subject](docs/en.subject.pdf).
@@ -88,7 +103,20 @@ For detailed info, refer to this project [subject](docs/en.subject.pdf).
### Basic Usage
INSTRUCTIONS
1. **Execute the shell**
```bash
./minishell
```
2. **Interact with it like if it was bash**
```bash
echo "Hello, World!"
...
cat groceries.txt | grep tomatoes
...
echo "Greetings $USER!" > greetings.txt
```
## 📏 Norminette

View File

@@ -1,177 +0,0 @@
[minishell_allowed]
readline
rl_clear_history
rl_on_new_line
rl_replace_line
rl_redisplay
add_history
printf
malloc
free
write
access
open
read
close
fork
wait
waitpid
wait3
wait4
signal
sigaction
sigemptyset
sigaddset
kill
exit
getcwd
chdir
stat
lstat
fstat
unlink
execve
dup
dup2
pipe
opendir
readdir
closedir
strerror
perror
isatty
ttyname
ttyslot
ioctl
getenv
tcsetattr
tcgetattr
tgetent
tgetflag
tgetnum
tgetstr
tgoto
tputs
[libft]
ft_atoi
ft_atoi_base
ft_atol
ft_bzero
ft_calloc
ft_cdlstadd_back
ft_cdlstadd_front
ft_cdlstclear
ft_cdlstdelone
ft_cdlstiter
ft_cdlstlast
ft_cdlstmap
ft_cdlstnew
ft_cdlstsize
ft_clstadd_back
ft_clstadd_front
ft_clstclear
ft_clstdelone
ft_clstiter
ft_clstlast
ft_clstmap
ft_clstnew
ft_clstsize
ft_dlstadd_back
ft_dlstadd_front
ft_dlstclear
ft_dlstdelone
ft_dlstiter
ft_dlstlast
ft_dlstmap
ft_dlstnew
ft_dlstsize
ft_eputchar
ft_eputendl
ft_eputnbr
ft_eputstr
ft_free
ft_free_split
ft_hashmap_clear
ft_hashmap_clear_keys
ft_hashmap_contains_key
ft_hashmap_entries
ft_hashmap_get
ft_hashmap_hashstr
ft_hashmap_new
ft_hashmap_put
ft_hashmap_remove
ft_hashmap_strcmp
ft_hashstr
ft_iabs
ft_imin
ft_isalnum
ft_isalpha
ft_isascii
ft_iscntrl
ft_isdigit
ft_islower
ft_isprint
ft_isspace
ft_isupper
ft_itoa
ft_itoa_base
ft_lstadd_back
ft_lstadd_front
ft_lstclear
ft_lstclear_nodes
ft_lstdelone
ft_lstiter
ft_lstlast
ft_lstmap
ft_lstnew
ft_lstsize
ft_ltoa
ft_memchr
ft_memcmp
ft_memcpy
ft_memmove
ft_memset
ft_nsplit
ft_pow
ft_putchar
ft_putchar_fd
ft_putendl
ft_putendl_fd
ft_putnbr
ft_putnbr_fd
ft_putstr
ft_putstr_fd
ft_realloc
ft_split
ft_strchr
ft_strcmp
ft_strdup
ft_strisnum
ft_striteri
ft_strjoin
ft_strlcat
ft_strlcpy
ft_strlen
ft_strmapi
ft_strncmp
ft_strncpy
ft_strnjoin
ft_strnstr
ft_strrchr
ft_strtrim
ft_substr
ft_swap
ft_tolower
ft_toupper
ft_uitoa
ft_uitoa_base
ft_ultoa_base
[get_next_line]
get_next_line
[ft_printf]
ft_eprintf
ft_fprintf
ft_printf

View File

@@ -1,151 +0,0 @@
# Correcciones en Builtins
Este documento resume los fallos detectados en los builtins actuales y la
solucion aplicada en codigo.
## 1) Infraestructura de builtins (`include/builtins.h`, `src/builtins/builtins.c`)
### Fallo
- Uso inconsistente de tipos (`u_int8_t`/`unsigned char` frente a `uint8_t`).
- `set_builtins` no comprobaba fallos de registro por builtin (duplicado de
clave o insercion en hashmap), pudiendo dejar estado parcial.
- `is_builtin` podia dereferenciar punteros nulos.
### Por que fallaba
- `u_int8_t` no es el tipo estandar C99 y depende de plataforma/headers.
- Si falla una insercion, la tabla quedaba inicializada parcialmente sin
rollback.
- En errores de inicializacion, consultar `is_builtin` podia romper.
### Solucion
- Unificacion de firmas a `uint8_t`.
- Nuevo helper `register_builtin()` con validacion tras `ft_hashmap_put`.
- Si falla cualquier alta: limpieza de `minishell->builtins` y retorno de
error.
- Guardas nulas en `is_builtin`.
## 2) `cd` (`src/builtins/cd/cd.c`)
### Fallo
- `cd` con demasiados argumentos devolvia `2` (bash devuelve `1`).
- `cd` sin `HOME` acababa llamando a `chdir(NULL)`.
- El manejo de error usaba comprobaciones invertidas (`access`) y codigos
incorrectos.
- No se actualizaban `PWD` y `OLDPWD` tras `chdir` exitoso.
- `cd -` (usar `OLDPWD`) no estaba soportado.
### Por que fallaba
- Codigos de salida incompatibles con el comportamiento esperado del shell.
- `HOME` no definido no se controlaba antes del `chdir`.
- La logica de `access` estaba al reves y mezclaba condiciones.
- Variables de entorno del directorio quedaban desincronizadas.
- Faltaba resolver el caso especial de `-` hacia `OLDPWD`.
### Solucion
- Refactor en `resolve_cd_path()` para validar argumentos y `HOME`.
- Retorno `EXIT_FAILURE` en `too many arguments` y `HOME not set`.
- Error de `chdir` simplificado a `perror("minishell: cd")` + retorno `1`.
- Actualizacion de `OLDPWD` y `PWD` mediante `getcwd(NULL, 0)` + `set_env()`.
- Soporte de `cd -`: usa `OLDPWD`, valida `OLDPWD not set` e imprime el nuevo
directorio tras el cambio.
## 3) `exit` (`src/builtins/exit/exit.c`)
### Fallo
- Habia un `printf` de debug en ejecucion real.
- `exit <no_numerico>` devolvia `2` pero no cerraba el shell.
- `exit n m` devolvia `2`; en bash es `1` y no sale del shell.
- Validacion numerica basada en `ft_strisnum` sin control de overflow.
- Se mostraba `exit` incluso en contexto no interactivo.
### Por que fallaba
- Debug residual contamina salida.
- Semantica de `exit` incompleta respecto a bash.
- Valores fuera de rango podian tratarse como validos por conversion directa.
- Mensaje `exit` debe mostrarse solo en shell interactivo.
### Solucion
- Eliminado debug print.
- Nuevo flujo `resolve_exit_status()`:
- Sin argumentos: usa `msh->exit_status`.
- Argumento no numerico o fuera de `long`: mensaje
`numeric argument required`, `msh->exit = true`, estado `2`.
- Demasiados argumentos: mensaje de error y estado `1`, sin salir.
- Parser numerico propio (`get_uint8_from_num` + `has_overflow`) con soporte
de signo y control de overflow.
- `ft_eputendl("exit")` solo si `isatty(STDIN_FILENO)`.
## 4) `pwd` (`src/builtins/pwd/pwd.c`)
### Fallo
- Si `getcwd` fallaba, el builtin devolvia `EXIT_SUCCESS`.
- Uso de buffer fijo (`PATH_MAX`) menos robusto para rutas largas.
### Por que fallaba
- El shell reportaba exito aunque no pudiera obtener el directorio.
- Un buffer fijo puede truncar o fallar en escenarios de rutas profundas.
### Solucion
- Cambio a `getcwd(NULL, 0)` con memoria dinamica.
- Si falla, `perror("minishell: pwd")` y retorno `EXIT_FAILURE`.
- `free()` del buffer dinamico tras imprimir.
## 5) `echo` (`src/builtins/echo/echo.c`, `src/builtins/echo/echo_def.c`)
### Cambio aplicado
- Ajuste de tipos de retorno auxiliares a `uint8_t` para mantener consistencia
con `builtins.h`.
### Nota
- No se detectaron fallos funcionales criticos adicionales en la logica actual
de `echo` durante esta revision.
## 6) Builtins faltantes: `env`, `export`, `unset`
### Fallo
- Los builtins `env`, `export` y `unset` no estaban implementados ni
registrados en `set_builtins`.
### Por que fallaba
- Comandos basicos de shell no existian en la tabla de builtins.
- Cualquier prueba/flujo que dependiera de gestion de variables exportadas
fallaba (listar, crear y eliminar variables de entorno).
### Solucion
- Nuevos builtins implementados:
- `src/builtins/env/env.c`
- `src/builtins/export/export.c`
- `src/builtins/unset/unset.c`
- Registro en `src/builtins/builtins.c` (tabla ampliada de 4 a 7 entradas).
- Nuevos prototipos en `include/builtins.h`.
- Soporte de borrado real de entorno mediante `unset_env`:
- Declaracion en `include/core.h`
- Implementacion en `src/variables/environment_unset.c`
## 7) Comportamiento aplicado en los nuevos builtins
### `env`
- Si recibe argumentos, devuelve error (`minishell: env: too many arguments`)
y estado `1`.
- Sin argumentos, lista `KEY=VALUE` de las variables de entorno actuales.
### `export`
- `export NAME=VALUE`: crea/actualiza la variable.
- `export NAME`: crea/actualiza con valor vacio (`NAME=`) para que aparezca
en `env`.
- Identificadores invalidos (`1A=2`, etc.) devuelven error
`not a valid identifier` y estado `1`.
- Sin argumentos, reutiliza el listado de `env`.
### `unset`
- Elimina variables validas del entorno en memoria.
- Identificadores invalidos devuelven error
`not a valid identifier` y estado `1`.
- Si el identificador es valido y no existe, no falla (comportamiento shell).
## Validacion realizada
- `norminette` ejecutado sobre los archivos modificados: `OK`.
- Build completa no ejecutable en este entorno por falta de acceso de red al
clonado de librerias (`github.com`), por lo que no se pudo validar runtime
del binario en esta sesion.

View File

@@ -1,77 +0,0 @@
# Minishell - Resumen para defensa
Este documento es un guion breve para explicar el proyecto con claridad.
## 1. Que es minishell
- Un shell interactivo basico: lee comandos, los parsea y los ejecuta.
- Objetivo didactico: procesos, pipes, fds, señales y parsing.
## 2. Flujo general (en 20 segundos)
1. Prompt con readline.
2. Lexer divide la linea en tokens validos (respetando comillas).
3. Parser crea comandos, argv y redirecciones.
4. Expansion de variables ($VAR, $?).
5. Resolver PATH.
6. Ejecutar con fork/execve y pipes.
7. Actualizar exit status.
## 3. Puntos clave que suelen preguntar
### Comillas
- Comilla simple: todo literal, no expansion.
- Comilla doble: expansion de $VAR, pero no metacaracteres.
- Si una comilla no se cierra: error y no se ejecuta nada.
### Redirecciones
- <, >, >>, <<.
- Se resuelven antes de ejecutar el comando.
- Heredoc: lectura hasta delimitador, sin historial.
### Pipes
- Conecto stdout del comando i con stdin del comando i+1.
- Uso pipe() y dup2() en el child.
- El padre cierra fds que no usa.
### Builtins
- echo -n, cd, pwd, export, unset, env, exit.
- Si no hay pipeline, se ejecutan en el padre para afectar el estado.
- En pipeline, se ejecutan en el child.
### Variables
- $VAR: entorno.
- $?: ultimo exit status.
### Senales
- Una sola variable global para la senal.
- ctrl-C: nueva linea y prompt.
- ctrl-D: salir del shell.
- ctrl-\: no hace nada.
## 4. Ejemplos rapidos para demostrar
1. Pipes
- `ls | wc -l`
2. Redirecciones
- `echo hola > out.txt`
- `cat < out.txt`
3. Heredoc
- `cat << EOF`
- escribir lineas
- `EOF`
4. Expansion
- `echo $HOME`
- `echo $?`
## 5. Errores y robustez
- No ejecuto si hay errores de parseo.
- Mensajes con perror cuando fallan syscalls.
- exit_status siempre actualizado.
## 6. Mensajes finales recomendados
- "He separado parsing y ejecucion para que el codigo sea mantenible y
la defensa mas clara".
- "Sigo el comportamiento de bash como referencia".

View File

@@ -1,56 +0,0 @@
# Errores detectados: parser, expansión y ejecutor
## Críticos
1. Segmentation fault al mezclar palabras y redirecciones en un mismo comando.
- Síntoma: `echo a > /tmp/x b` puede crashear.
- Referencias: `src/parser/parser.c:365`, `src/parser/parser.c:371`, `src/parser/parser.c:187`.
2. Bucle infinito al recibir EOF en modo no interactivo.
- Síntoma: con `printf 'echo ok\n' | ./minishell` entra en loop imprimiendo prompt.
- Referencias: `src/minishell.c:40`, `src/minishell.c:42`.
3. Parseo de pipes inválidos permite ejecución parcial o comandos inválidos.
- Síntoma: casos como `| echo x` o `echo hi |` no se bloquean correctamente.
- Referencias: `src/parser/parser.c:83`, `src/parser/parser.c:101`, `src/parser/parser.c:221`.
## Altos
4. Expansión de variables incompleta e incorrecta en casos especiales.
- Síntoma: `$FOO_BAR` y `$_X` fallan; `$?` y `$$` no se expanden correctamente.
- Referencias: `src/parser/parser.c:122`, `src/parser/parser.c:156`.
5. No se eliminan comillas tras parseo/expansión.
- Síntoma: `echo "$HOME"` imprime comillas literales.
- Referencias: `src/parser/lexer.c:161`, `src/parser/parser.c:142`.
6. No hay expansión en targets de redirección.
- Síntoma: `echo hi > $HOME_test` crea archivo literal `$HOME_test`.
- Referencias: `src/parser/parser.c:187`, `src/parser/parser.c:263`.
7. Heredoc parseado pero no ejecutado en runtime.
- Síntoma: se almacena en `heredocs` pero no se aplica en ejecución.
- Referencias: `src/parser/parser.c:397`, `src/executor/redirections.c:68`, `src/executor/process_helpers.c:110`.
8. Lexer acepta operadores inválidos (`||`, `>>>`, `><`) sin validación formal.
- Síntoma: tokenización por “runs” de metacaracteres produce entradas inválidas no rechazadas de forma robusta.
- Referencias: `src/parser/lexer.c:140`, `src/parser/lexer.c:77`.
9. Errores de sintaxis no siempre fijan `exit_status` correcto (debería ser `2`).
- Síntoma: algunos casos acaban con salida `0`.
- Referencias: `src/executor/executor.c:20`, `src/parser/parser.c:101`.
## Medios
10. Resolución de rutas explícitas reporta mal algunos errores de permisos/ejecución.
- Síntoma: ruta existente no ejecutable puede terminar como `command not found`.
- Referencias: `src/executor/path_resolver.c:57`, `src/executor/path_resolver.c:59`, `src/executor/command_exec.c:81`.
11. Check de puntero nulo defectuoso en el loop principal.
- Síntoma: `if (minishell == NULL)` desreferencia igualmente el puntero.
- Referencia: `src/minishell.c:35`.
12. Falta manejo explícito de comillas no cerradas.
- Síntoma: entrada con comillas abiertas no genera error sintáctico dedicado.
- Referencia: `src/parser/lexer.c:159`.

View File

@@ -1,292 +0,0 @@
# Minishell - Guia de estructura y pasos (parte obligatoria)
Objetivo de este documento
- Proponer una estructura de archivos y datos.
- Describir el proceso paso a paso como tutorial.
- Servir de guion para defensa (explicaciones claras y ordenadas).
Este documento no implementa nada. Solo define el plan y el por que.
---
## 1. Vision general del flujo
El shell se puede explicar como una tuberia de fases:
1. Lectura interactiva (prompt + historial).
2. Tokenizacion (lexing) con comillas y metacaracteres.
3. Parseo (construccion de comandos, redirecciones, pipes).
4. Expansion de variables ($VAR y $?).
5. Preparacion de ejecucion (resolucion de rutas, heredoc).
6. Ejecucion (builtins o execve) con pipes y redirecciones.
7. Gestion de exit status y señales.
Esta separacion permite explicar en defensa cada pieza por separado y justificar
las decisiones tecnicas.
---
## 2. Propuesta de estructuras de datos
Estas estructuras son solo guia. Adapta nombres a tu estilo y Norminette.
### 2.1 Token (lexer)
Representa una unidad del input (palabra, pipe, redireccion, etc.).
- enum e_tokentype
- TOK_WORD
- TOK_PIPE
- TOK_REDIR_IN (<)
- TOK_REDIR_OUT (>)
- TOK_REDIR_APPEND (>>)
- TOK_HEREDOC (<<)
- struct s_token
- char *text
- t_tokentype type
- struct s_token *next
Uso en defensa: el lexer separa el input en unidades, respetando comillas.
### 2.2 Redireccion
Guarda los datos de redireccion por comando.
- enum e_redirtype
- REDIR_IN
- REDIR_OUT
- REDIR_APPEND
- REDIR_HEREDOC
- struct s_redir
- t_redirtype type
- char *target
- int fd
- struct s_redir *next
Notas:
- target es el filename o delimitador de heredoc.
- fd se resuelve en fase de preparacion (open o pipe temporal).
### 2.3 Comando
- struct s_command
- char **argv
- int argc
- char *path
- t_redir *redirs
### 2.4 Pipeline
Una lista de comandos en orden, unidos por pipes.
- struct s_pipeline
- t_command **cmds
- size_t count
---
## 3. Lexer: reglas y pasos
Reglas clave del subject:
- No interpretar comillas sin cerrar.
- Comilla simple: no se expanden variables ni metacaracteres.
- Comilla doble: se expanden variables, pero se respetan caracteres normales.
- Metacaracteres: |, <, >, <<, >> separan tokens.
Pasos recomendados:
1. Recorrer la linea caracter a caracter.
2. Mantener estado: in_single, in_double.
3. Cuando no estas en comillas, detectar metacaracteres y cortar tokens.
4. Construir TOK_WORD con el texto exacto (sin eliminar comillas aun).
5. Si llegas a fin de linea con in_single o in_double activo, error de parseo.
Explicacion para defensa:
- El lexer no sabe de ejecucion, solo separa en tokens validos.
- El manejo de comillas se hace aqui para respetar la sintaxis del shell.
---
## 4. Parser: construccion de comandos
Objetivo: transformar tokens en una estructura ejecutable.
Pasos:
1. Recorrer lista de tokens.
2. Cada TOK_PIPE cierra un comando actual y abre el siguiente.
3. TOK_WORD se agrega a argv.
4. TOK_REDIR_* consume el siguiente token (debe ser TOK_WORD) como target.
5. Construir lista de redirecciones para cada comando.
6. Validar errores: pipe inicial/final, redireccion sin target, etc.
Explicacion para defensa:
- El parser aplica reglas de orden y construye una estructura clara.
- Separar argv y redirecciones evita mezclar logica en executor.
---
## 5. Expansion de variables
Reglas:
- $VAR se sustituye por getenv/tabla interna.
- $? se sustituye por el exit_status anterior.
- En comilla simple no se expande.
- En comilla doble si se expande.
Proceso recomendado:
1. Durante tokenizacion, guardar el texto con sus comillas o bien
marcar segmentos con estado de comillas.
2. En expansion, recorrer cada palabra y reemplazar $...
3. Si variable no existe, reemplazar por string vacio.
4. Eliminar comillas despues de la expansion.
Explicacion para defensa:
- La expansion es una fase separada para no complicar el parser.
- $?, variable especial, refleja el estado de la ultima ejecucion.
---
## 6. Redirecciones y heredoc
Redirecciones basicas:
- <: open(file, O_RDONLY)
- >: open(file, O_WRONLY | O_CREAT | O_TRUNC)
- >>: open(file, O_WRONLY | O_CREAT | O_APPEND)
Heredoc (<<):
1. Leer lineas hasta delimitador exacto.
2. Guardar el contenido en un pipe o fichero temporal.
3. Usar el extremo de lectura como STDIN del comando.
4. No guardar el contenido en historial.
Explicacion para defensa:
- Las redirecciones se resuelven antes de ejecutar el proceso.
- Heredoc es una fuente especial de entrada.
---
## 7. Resolucion de comandos y PATH
Reglas:
- Si argv[0] es una ruta absoluta o relativa (/, ./, ../), usarla tal cual.
- Si no, buscar en PATH separando por ':'.
- Si es builtin, no se necesita path real.
Proceso:
1. Detectar builtin.
2. Si no builtin y no es ruta, recorrer PATH y usar access().
3. Guardar path en t_command->path.
---
## 8. Ejecucion
Caso 1: comando unico builtin
- Ejecutar en el proceso padre para que pueda modificar estado del shell
(ej: cd, export, unset, exit).
Caso 2: pipeline o comando externo
- Usar fork + execve.
- Crear pipes entre comandos.
- Aplicar redirecciones antes de ejecutar.
Proceso para pipeline:
1. Para cada comando, crear pipe si hay siguiente.
2. fork.
3. En child: dup2 para redirecciones y pipes, luego ejecutar.
4. En parent: cerrar FDs innecesarios y seguir.
5. Esperar procesos, guardar exit status del ultimo comando.
Explicacion para defensa:
- Las pipes conectan stdout del comando i con stdin del comando i+1.
- Los builtins dentro de pipeline se ejecutan en child.
---
## 9. Builtins obligatorios
- echo con -n
- cd (ruta relativa o absoluta)
- pwd
- export (sin opciones)
- unset (sin opciones)
- env (sin opciones o argumentos)
- exit
Notas de defensa:
- export/unset trabajan sobre la tabla de variables del shell.
- env imprime variables de entorno.
- exit debe actualizar exit_status y terminar el loop principal.
---
## 10. Senales
Requisitos interactivos:
- ctrl-C: imprime nueva linea y muestra prompt.
- ctrl-D: termina el shell.
- ctrl-\: no hace nada.
Regla del subject:
- Solo una variable global para indicar la senal recibida.
Proceso:
1. Definir una variable global int g_signal.
2. Configurar handlers con sigaction.
3. En handler: actualizar g_signal y escribir un '\n' si procede.
4. En el loop principal: si g_signal indica SIGINT, resetear lineas de readline.
---
## 11. Manejo de errores y salida
- Mostrar errores con perror o mensajes consistentes.
- Si parseo falla, no ejecutar nada.
- Mantener exit_status actualizado.
Explicacion en defensa:
- Un shell robusto evita ejecutar comandos parcialmente parseados.
- exit_status es clave para $?.
---
## 12. Checklist para defensa (guion rapido)
1. Explico el flujo completo: lectura -> lexer -> parser -> expansion -> exec.
2. Explico como manejo comillas y metacaracteres.
3. Explico como construyo argv y redirecciones.
4. Explico expansion de $VAR y $?.
5. Explico pipes y redirecciones con dup2.
6. Explico por que los builtins se ejecutan en parent o child.
7. Explico manejo de senales y la variable global unica.
8. Explico exit_status y comportamiento de $?.
---
## 13. Sugerencia de estructura de archivos
- include/
- minishell.h
- core.h (estructuras globales y estado)
- parser.h (tokens, parser)
- executor.h
- builtins.h
- src/
- core/ (init, signals, util)
- parser/ (lexer.c, parser.c, expand.c)
- executor/ (executor.c, redirs.c)
- builtins/ (echo, cd, pwd, exit, env, export, unset)
- variables/ (environment.c)
- minishell.c (loop principal)
- main.c
Esto es solo una guia; no es obligatorio seguirla al pie de la letra.
---
## 14. Consejos para la defensa
- Usa bash como referencia de comportamiento.
- Demuestra un par de ejemplos: pipe, redireccion y expansion.
- Si algo falla, explica que el parser previene ejecucion parcial.
- Recalca el manejo correcto de ctrl-C y ctrl-\.

View File

@@ -1,17 +0,0 @@
### Multiple redirections
```
cmd > file1 > file2
```
or
```
cmd >> file1 >> file2
```
Should create both `file1` and `file2` but store the output in `file2`
### Environment variables
#### SHLVL
Must be increased by 1 if executed from any other shell or 1 if executed

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/29 22:09:51 by sede-san #+# #+# */
/* Updated: 2026/02/08 19:42:50 by sede-san ### ########.fr */
/* Updated: 2026/02/14 01:15:34 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -16,6 +16,7 @@
# include "ft_args.h"
# include "minishell.h"
# include "core.h"
# include "variables.h"
typedef uint8_t (*t_builtin_func)(t_command cmd, t_minishell *minishell);

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/22 19:10:13 by sede-san #+# #+# */
/* Updated: 2026/02/13 20:06:08 by sede-san ### ########.fr */
/* Updated: 2026/02/14 01:25:43 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -48,7 +48,10 @@ typedef enum e_redirection_type
typedef struct s_redirection
{
t_token_type type;
int io_number;
char *target;
bool heredoc_expand;
bool heredoc_ready;
} t_redirection;
/**
@@ -63,15 +66,9 @@ typedef struct s_redirection
typedef struct s_variables
{
t_hashmap *environment;
// char **internal;
t_hashmap *internal;
} t_variables;
typedef struct s_prompt
{
const char *ps1; // primary prompt string
const char *ps2; // secondary prompt string for multiline input
} t_prompt;
/**
* @brief Main minishell structure containing global state information
*
@@ -84,7 +81,6 @@ typedef struct s_minishell
{
t_variables variables;
t_hashmap *builtins;
t_prompt prompt;
uint8_t exit_status;
bool exit;
} t_minishell;
@@ -136,6 +132,8 @@ extern void minishell_set_execution_signals(void);
extern void minishell_set_child_signals(void);
extern void minishell_set_heredoc_signals(void);
extern bool minishell_consume_sigint(void);
/* environment.c */
@@ -149,8 +147,7 @@ extern char **get_envp(t_minishell *msh);
extern void free_envp(char **envp);
extern char *get_env(const char *env_name, t_minishell *msh);
extern void unset_env(const char *env_name, t_minishell *msh);
extern void handle_sigint_status(t_minishell *minishell);
extern bool handle_eof(char *line, t_minishell *minishell);
#endif /* CORE_H */

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/10 22:28:01 by sede-san #+# #+# */
/* Updated: 2026/02/10 23:24:16 by sede-san ### ########.fr */
/* Updated: 2026/02/14 01:11:38 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -19,5 +19,6 @@
extern void syntax_error_unexpected_token(t_token *token);
extern void malloc_error(void);
extern void command_not_found_error(const char *command);
#endif /* ERRORS_H */

View File

@@ -53,8 +53,12 @@ extern void executor_parent_cleanup(t_list *node, t_pipeline *pl);
extern uint8_t executor_wait_for_children(pid_t last_child_pid);
extern void executor_cmdfree(t_command *command);
extern bool executor_apply_redirections(const t_command *command,
int *saved_stdin, int *saved_stdout);
int *saved_stdin, int *saved_stdout, int *saved_stderr);
extern void executor_restore_redirections(int saved_stdin,
int saved_stdout);
int saved_stdout, int saved_stderr);
extern bool executor_prepare_heredocs(t_list *command_list,
t_minishell *minishell, uint8_t *exit_status);
extern char *executor_expand_heredoc_line(const char *line,
t_minishell *minishell);
#endif /* EXECUTOR_H */

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/20 16:35:10 by sede-san #+# #+# */
/* Updated: 2026/02/13 20:12:25 by sede-san ### ########.fr */
/* Updated: 2026/02/14 15:17:38 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -42,7 +42,6 @@
# include <term.h> // tgetent(3), tgetflag(3), tgetnum(3),
// tgetstr(3), tgoto(3), tputs(3)
void handle_sigint_status(t_minishell *minishell);
bool handle_eof(char *line, t_minishell *minishell);
# define MINISHELL_VERSION "1.0.0"
#endif /* MINISHELL_H */

View File

@@ -41,8 +41,10 @@ extern void command_clear(t_command *command);
extern void command_add_tokens(t_command **command, t_list **tokens);
extern bool is_pipe(t_token *token);
extern bool is_redirection(t_token *token);
void redirection_add(t_list **tokens, t_token *token,
t_command **command);
bool parser_token_is_fd_prefix(t_list *token_node,
int *io_number);
void redirection_add_with_fd(t_list **tokens, t_token *token,
t_command **command, int io_number);
void words_add(t_list **tokens, t_command **command);
void expand(t_list **commands, t_minishell *minishell);

43
include/variables.h Normal file
View File

@@ -0,0 +1,43 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* variables.h :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:45:35 by sede-san #+# #+# */
/* Updated: 2026/02/14 01:24:49 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef VARIABLES_H
# define VARIABLES_H
# include "minishell.h"
# include "core.h"
// variables.c
extern char *get_var(const char *name, t_minishell *minishell);
extern void set_var(const char *name, char *value, t_minishell *minishell);
extern void unset_var(const char *name, t_minishell *minishell);
// environment.c
extern char *get_env(const char *name, t_minishell *minishell);
extern void set_env(const char *name, char *value, t_minishell *minishell);
extern void unset_env(const char *name, t_minishell *minishell);
extern void set_envp(char **envp, t_minishell *minishell);
extern char **get_envp(t_minishell *minishell);
extern void free_envp(char **envp);
// internal.c
extern char *get_int(const char *name, t_minishell *minishell);
extern void set_int(const char *name, char *value, t_minishell *minishell);
extern void unset_int(const char *name, t_minishell *minishell);
extern void set_intp(t_minishell *minishell);
#endif /* VARIABLES_H */

View File

@@ -22,8 +22,9 @@ uint8_t builtin_env(
{
if (cmd.argc > 1)
{
ft_eputendl("minishell: env: too many arguments");
return (EXIT_FAILURE);
ft_eprintf("minishell: env: %s: No such file or directory\n",
cmd.argv[1]);
return (127);
}
print_env(msh);
return (EXIT_SUCCESS);

View File

@@ -16,6 +16,8 @@ static bool exit_arg_is_invalid(const char *arg)
{
if (arg == NULL)
return (true);
if (arg[0] == '\0')
return (true);
if ((*arg == '+' || *arg == '-') && arg[1] == '\0')
return (true);
if (!ft_strisnum(arg))

View File

@@ -6,14 +6,24 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/09 22:05:00 by codex #+# #+# */
/* Updated: 2026/02/09 22:05:00 by codex ### ########.fr */
/* Updated: 2026/02/14 02:02:14 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "builtins.h"
static uint8_t is_valid_identifier(const char *arg, size_t name_len);
static uint8_t export_one(char *arg, t_minishell *msh);
static uint8_t print_exported(t_minishell *msh);
static void **entries_to_array(t_list *lst, size_t *count,
bool add_oldpwd, t_map_entry *oldpwd_entry);
static bool print_sorted_entries(t_map_entry **entries, size_t count);
bool export_is_valid_identifier(const char *arg, size_t name_len);
void export_parse_assignment(char *arg, char **eq_pos,
size_t *name_len, bool *append);
uint8_t export_set_assigned_value(const char *name, char *eq_pos,
bool append, t_minishell *msh);
bool export_print_declaration(const char *name, const char *value);
void export_sort_entries(t_map_entry **entries, size_t count);
uint8_t builtin_export(
t_command cmd,
@@ -21,36 +31,24 @@ uint8_t builtin_export(
)
{
uint8_t status;
uint8_t result;
size_t i;
if (cmd.argc == 1)
return (builtin_env(cmd, msh));
return (print_exported(msh));
status = EXIT_SUCCESS;
i = 0;
while (cmd.argv[++i] != NULL)
if (export_one(cmd.argv[i], msh) != EXIT_SUCCESS)
{
result = export_one(cmd.argv[i], msh);
if (result == 2)
return (2);
if (result != EXIT_SUCCESS)
status = EXIT_FAILURE;
}
return (status);
}
static uint8_t is_valid_identifier(
const char *arg,
size_t name_len
)
{
size_t i;
if (name_len == 0 || arg == NULL)
return (0);
if (!ft_isalpha(arg[0]) && arg[0] != '_')
return (0);
i = 0;
while (++i < name_len)
if (!ft_isalnum(arg[i]) && arg[i] != '_')
return (0);
return (1);
}
static uint8_t export_one(
char *arg,
t_minishell *msh
@@ -59,21 +57,95 @@ static uint8_t export_one(
char *eq_pos;
char *name;
size_t name_len;
bool append;
eq_pos = ft_strchr(arg, '=');
name_len = ft_strlen(arg);
if (eq_pos != NULL)
name_len = (size_t)(eq_pos - arg);
if (!is_valid_identifier(arg, name_len))
return (ft_eprintf("minishell: export: `%s': not a valid identifier\n",
arg), EXIT_FAILURE);
if (arg[0] == '-' && arg[1] != '\0')
return (ft_eprintf("minishell: export: %s: invalid option\n", arg),
ft_eputendl("export: usage: export [name[=value] ...]"), 2);
export_parse_assignment(arg, &eq_pos, &name_len, &append);
if (!export_is_valid_identifier(arg, name_len))
return (ft_eprintf(
"minishell: export: `%s': not a valid identifier\n", arg),
EXIT_FAILURE);
name = ft_substr(arg, 0, name_len);
if (name == NULL)
return (EXIT_FAILURE);
if (eq_pos != NULL)
set_env(name, eq_pos + 1, msh);
else
set_env(name, "", msh);
if (export_set_assigned_value(name, eq_pos, append, msh) != EXIT_SUCCESS)
return (free(name), EXIT_FAILURE);
free(name);
return (EXIT_SUCCESS);
}
static uint8_t print_exported(
t_minishell *msh
)
{
t_list *entries;
t_map_entry **sorted_entries;
t_map_entry oldpwd_entry;
size_t count;
entries = ft_hashmap_entries(msh->variables.environment);
if (entries == NULL)
return (EXIT_SUCCESS);
oldpwd_entry = (t_map_entry){"OLDPWD", NULL};
sorted_entries = (t_map_entry **)entries_to_array(entries, &count,
!ft_hashmap_contains_key(msh->variables.environment, "OLDPWD"),
&oldpwd_entry);
ft_lstclear_nodes(&entries);
if (sorted_entries == NULL)
return (EXIT_FAILURE);
export_sort_entries(sorted_entries, count);
if (!print_sorted_entries(sorted_entries, count))
return (free(sorted_entries), EXIT_FAILURE);
return (free(sorted_entries), EXIT_SUCCESS);
}
static void **entries_to_array(
t_list *entries,
size_t *count,
bool add_oldpwd,
t_map_entry *oldpwd_entry
)
{
void **array;
t_list *node;
t_map_entry *entry;
size_t i;
*count = (size_t)ft_lstsize(entries) + (size_t)add_oldpwd;
array = (void **)malloc(sizeof(void *) * (*count));
if (array == NULL)
return (NULL);
i = 0;
node = entries;
while (node != NULL)
{
entry = (t_map_entry *)node->content;
if (entry != NULL && ft_strcmp((char *)entry->key, "_") != 0)
array[i++] = entry;
node = node->next;
}
if (add_oldpwd)
array[i++] = oldpwd_entry;
*count = i;
return (array);
}
static bool print_sorted_entries(
t_map_entry **entries,
size_t count
)
{
size_t i;
i = 0;
while (i < count)
{
if (!export_print_declaration((char *)entries[i]->key,
(char *)entries[i]->value))
return (false);
i++;
}
return (true);
}

View File

@@ -0,0 +1,40 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* export_sort.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 23:55:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 23:55:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "builtins.h"
void export_sort_entries(
t_map_entry **entries,
size_t count
)
{
size_t i;
size_t j;
t_map_entry *tmp;
i = 0;
while (i < count)
{
j = i + 1;
while (j < count)
{
if (ft_strcmp((char *)entries[i]->key, (char *)entries[j]->key) > 0)
{
tmp = entries[i];
entries[i] = entries[j];
entries[j] = tmp;
}
j++;
}
i++;
}
}

View File

@@ -0,0 +1,129 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* export_utils.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 15:20:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 15:20:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "builtins.h"
bool export_is_valid_identifier(
const char *arg,
size_t name_len
)
{
size_t i;
if (name_len == 0 || arg == NULL)
return (false);
if (!ft_isalpha(arg[0]) && arg[0] != '_')
return (false);
i = 0;
while (++i < name_len)
if (!ft_isalnum(arg[i]) && arg[i] != '_')
return (false);
return (true);
}
void export_parse_assignment(
char *arg,
char **eq_pos,
size_t *name_len,
bool *append
)
{
char *plus_eq;
*append = false;
*eq_pos = ft_strchr(arg, '=');
*name_len = ft_strlen(arg);
plus_eq = ft_strnstr(arg, "+=", *name_len);
if (plus_eq != NULL)
{
*append = true;
*name_len = (size_t)(plus_eq - arg);
*eq_pos = plus_eq + 1;
}
else if (*eq_pos != NULL)
*name_len = (size_t)(*eq_pos - arg);
}
uint8_t export_set_assigned_value(
const char *name,
char *eq_pos,
bool append,
t_minishell *msh
)
{
char *current;
char *joined;
if (append)
{
current = get_var((char *)name, msh);
if (current == NULL)
current = "";
joined = ft_strnjoin(2, current, eq_pos + 1);
if (joined == NULL)
return (EXIT_FAILURE);
set_var(name, joined, msh);
free(joined);
return (EXIT_SUCCESS);
}
if (eq_pos != NULL)
set_var(name, eq_pos + 1, msh);
else
set_var(name, "", msh);
return (EXIT_SUCCESS);
}
char *export_escape_value(
const char *value
)
{
char *escaped;
size_t i;
size_t j;
escaped = (char *)malloc((ft_strlen(value) * 2) + 1);
if (escaped == NULL)
return (NULL);
i = 0;
j = 0;
while (value[i] != '\0')
{
if (value[i] == '\\' || value[i] == '\"' || value[i] == '$')
escaped[j++] = '\\';
escaped[j++] = value[i++];
}
escaped[j] = '\0';
return (escaped);
}
bool export_print_declaration(
const char *name,
const char *value
)
{
char *escaped;
ft_putstr("declare -x ");
ft_putstr((char *)name);
if (value != NULL)
{
escaped = export_escape_value(value);
if (escaped == NULL)
return (false);
ft_putstr("=\"");
ft_putstr(escaped);
ft_putstr("\"");
free(escaped);
}
ft_putchar('\n');
return (true);
}

View File

@@ -14,6 +14,7 @@
static uint8_t is_valid_identifier(const char *arg);
static uint8_t unset_one(char *arg, t_minishell *msh);
static bool is_option(const char *arg);
uint8_t builtin_unset(
t_command cmd,
@@ -21,13 +22,19 @@ uint8_t builtin_unset(
)
{
uint8_t status;
uint8_t result;
size_t i;
status = EXIT_SUCCESS;
i = 0;
while (cmd.argv[++i] != NULL)
if (unset_one(cmd.argv[i], msh) != EXIT_SUCCESS)
{
result = unset_one(cmd.argv[i], msh);
if (result == 2)
return (2);
if (result != EXIT_SUCCESS)
status = EXIT_FAILURE;
}
return (status);
}
@@ -53,9 +60,21 @@ static uint8_t unset_one(
t_minishell *msh
)
{
if (is_option(arg))
{
ft_eprintf("minishell: unset: %s: invalid option\n", arg);
ft_eputendl("unset: usage: unset [name ...]");
return (2);
}
if (!is_valid_identifier(arg))
return (ft_eprintf("minishell: unset: `%s': not a valid identifier\n",
arg), EXIT_FAILURE);
return (EXIT_SUCCESS);
unset_env(arg, msh);
return (EXIT_SUCCESS);
}
static bool is_option(
const char *arg
)
{
return (arg != NULL && arg[0] == '-' && arg[1] != '\0');
}

View File

@@ -11,10 +11,11 @@
/* ************************************************************************** */
#include "core.h"
#include "signals_internal.h"
static int g_signal = 0;
int g_signal = 0;
static void sigint_handler(int signal)
static void sigint_handler_interactive(int signal)
{
g_signal = signal;
write(STDOUT_FILENO, "\n", 1);
@@ -28,7 +29,7 @@ void minishell_set_interactive_signals(void)
struct sigaction action;
ft_bzero(&action, sizeof(action));
action.sa_handler = sigint_handler;
action.sa_handler = sigint_handler_interactive;
sigemptyset(&action.sa_mask);
sigaction(SIGINT, &action, NULL);
action.sa_handler = SIG_IGN;
@@ -46,17 +47,6 @@ void minishell_set_execution_signals(void)
sigaction(SIGQUIT, &action, NULL);
}
void minishell_set_child_signals(void)
{
struct sigaction action;
ft_bzero(&action, sizeof(action));
action.sa_handler = SIG_DFL;
sigemptyset(&action.sa_mask);
sigaction(SIGINT, &action, NULL);
sigaction(SIGQUIT, &action, NULL);
}
bool minishell_consume_sigint(void)
{
if (g_signal != SIGINT)

24
src/core/signals_child.c Normal file
View File

@@ -0,0 +1,24 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* signals_child.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 18:20:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 18:20:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "core.h"
void minishell_set_child_signals(void)
{
struct sigaction action;
ft_bzero(&action, sizeof(action));
action.sa_handler = SIG_DFL;
sigemptyset(&action.sa_mask);
sigaction(SIGINT, &action, NULL);
sigaction(SIGQUIT, &action, NULL);
}

View File

@@ -0,0 +1,32 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* signals_heredoc.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 23:40:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 23:40:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "core.h"
#include "signals_internal.h"
static void sigint_handler_heredoc(int signal)
{
g_signal = signal;
write(STDOUT_FILENO, "\n", 1);
}
void minishell_set_heredoc_signals(void)
{
struct sigaction action;
ft_bzero(&action, sizeof(action));
action.sa_handler = sigint_handler_heredoc;
sigemptyset(&action.sa_mask);
sigaction(SIGINT, &action, NULL);
action.sa_handler = SIG_IGN;
sigaction(SIGQUIT, &action, NULL);
}

View File

@@ -0,0 +1,18 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* signals_internal.h :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 23:40:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 23:40:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef SIGNALS_INTERNAL_H
# define SIGNALS_INTERNAL_H
extern int g_signal;
#endif

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/10 22:22:06 by sede-san #+# #+# */
/* Updated: 2026/02/12 02:55:36 by sede-san ### ########.fr */
/* Updated: 2026/02/14 06:58:05 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -29,3 +29,10 @@ void malloc_error(void)
{
ft_eprintf("minishell: %s\n", strerror(ENOMEM));
}
void command_not_found_error(
const char *command
)
{
ft_eprintf("minishell: %s: command not found\n", command);
}

View File

@@ -6,12 +6,13 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
/* Updated: 2026/02/14 13:12:58 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "builtins.h"
#include "errors.h"
#include <errno.h>
static uint8_t resolve_execve_status(void)
@@ -40,9 +41,13 @@ static void handle_execve_error(
)
{
uint8_t exit_status;
size_t i;
exit_status = resolve_execve_status();
free_envp(envp);
i = 0;
while (envp[i] != NULL)
free(envp[i++]);
free(envp);
perror(command->path);
exit(exit_status);
}
@@ -56,10 +61,7 @@ static void execute_external_command(
envp = get_envp(minishell);
if (envp == NULL)
{
perror("get_envp");
exit(EXIT_FAILURE);
}
return (malloc_error(), exit(EXIT_FAILURE));
execve(command->path, command->argv, envp);
handle_execve_error(command, envp);
}
@@ -77,10 +79,7 @@ uint8_t executor_execute_command(
free(command->path);
command->path = executor_resolve_command_path(command, minishell);
if (command->path == NULL)
{
ft_eprintf("minishell: %s: command not found\n", command->argv[0]);
return (127);
}
return (command_not_found_error(command->argv[0]), 127);
execute_external_command(command, minishell);
return (EXIT_FAILURE);
}

View File

@@ -35,6 +35,9 @@ static void cmdfree_redirection(
{
if (redirection == NULL)
return ;
if (redirection->type == TOKEN_HEREDOC && redirection->heredoc_ready
&& redirection->target != NULL)
unlink(redirection->target);
free(redirection->target);
free(redirection);
}

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/08 19:10:47 by sede-san #+# #+# */
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
/* Updated: 2026/02/14 13:10:43 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -48,13 +48,15 @@ static void run_command_in_parent(
t_command *command;
int saved_stdin;
int saved_stdout;
int saved_stderr;
command = state->current_command->content;
if (!executor_apply_redirections(command, &saved_stdin, &saved_stdout))
if (!executor_apply_redirections(command, &saved_stdin, &saved_stdout,
&saved_stderr))
state->exit_status = EXIT_FAILURE;
else
state->exit_status = executor_execute_command(command, minishell);
executor_restore_redirections(saved_stdin, saved_stdout);
executor_restore_redirections(saved_stdin, saved_stdout, saved_stderr);
}
static bool run_current_command(
@@ -89,6 +91,12 @@ uint8_t execute(
t_exec_state state;
init_exec_state(&state, command_list);
if (!executor_prepare_heredocs(command_list, minishell, &state.exit_status))
{
minishell->exit_status = state.exit_status;
ft_lstclear(&command_list, (void (*)(void *))executor_cmdfree);
return (state.exit_status);
}
minishell_set_execution_signals();
while (state.current_command)
{

65
src/executor/heredoc.c Normal file
View File

@@ -0,0 +1,65 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 17:50:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 17:50:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "heredoc_internal.h"
#include "errors.h"
static bool prepare_heredoc_redirection(t_redirection *rd,
t_minishell *minishell, uint8_t *exit_status)
{
char *path;
int fd;
path = NULL;
fd = executor_heredoc_open_tmp(&path);
if (fd == -1)
return (perror("open"), *exit_status = EXIT_FAILURE, false);
if (!executor_heredoc_fill_tmp(fd, rd, minishell, exit_status))
return (executor_heredoc_discard_tmp(fd, path), false);
return (executor_heredoc_finalize_tmp(rd, fd, path, exit_status));
}
static bool process_command_heredocs(t_command *command, t_minishell *minishell,
uint8_t *exit_status)
{
t_list *redir_node;
t_redirection *rd;
redir_node = command->redirections;
while (redir_node != NULL)
{
rd = (t_redirection *)redir_node->content;
if (rd->type == TOKEN_HEREDOC
&& !prepare_heredoc_redirection(rd, minishell, exit_status))
return (false);
redir_node = redir_node->next;
}
return (true);
}
bool executor_prepare_heredocs(t_list *command_list, t_minishell *minishell,
uint8_t *exit_status)
{
t_command *command;
*exit_status = EXIT_SUCCESS;
minishell_set_heredoc_signals();
while (command_list != NULL)
{
command = (t_command *)command_list->content;
if (!process_command_heredocs(command, minishell, exit_status))
return (minishell_set_interactive_signals(), false);
command_list = command_list->next;
}
minishell_set_interactive_signals();
return (true);
}

View File

@@ -0,0 +1,107 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc_expand.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 17:30:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 17:30:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "variables.h"
#include "errors.h"
static bool is_var_char(char c)
{
return (ft_isalnum(c) || c == '_');
}
static bool append_text(char **result, const char *value)
{
char *joined;
joined = ft_strnjoin(2, *result, (char *)value);
if (joined == NULL)
{
free(*result);
*result = NULL;
return (false);
}
free(*result);
*result = joined;
return (true);
}
static char *expand_variable(const char *line, size_t *i, t_minishell *msh)
{
char *name;
char *value;
size_t start;
(*i)++;
if (line[*i] == '?')
return ((*i)++, ft_itoa(msh->exit_status));
if (line[*i] == '\0' || !is_var_char(line[*i]))
return (ft_strdup("$"));
start = *i;
while (line[*i] != '\0' && is_var_char(line[*i]))
(*i)++;
name = ft_substr(line, start, *i - start);
if (name == NULL)
return (NULL);
value = get_var(name, msh);
free(name);
if (value == NULL)
value = "";
return (ft_strdup(value));
}
static char *expand_literal_char(const char *line, size_t *i,
bool *in_single, bool *in_double)
{
char value[2];
if (line[*i] == '\'' && !*in_double)
*in_single = !*in_single;
else if (line[*i] == '\"' && !*in_single)
*in_double = !*in_double;
value[0] = line[*i];
value[1] = '\0';
(*i)++;
return (ft_strdup(value));
}
char *executor_expand_heredoc_line(
const char *line,
t_minishell *minishell
)
{
char *result;
char *expanded;
size_t i;
bool in_single;
bool in_double;
result = ft_strdup("");
if (result == NULL)
return (malloc_error(), NULL);
i = 0;
in_single = false;
in_double = false;
while (line[i] != '\0')
{
if (line[i] == '$' && !in_single)
expanded = expand_variable(line, &i, minishell);
else
expanded = expand_literal_char(line, &i, &in_single, &in_double);
if (expanded == NULL)
return (free(result), malloc_error(), NULL);
if (!append_text(&result, expanded))
return (free(expanded), malloc_error(), NULL);
free(expanded);
}
return (result);
}

View File

@@ -0,0 +1,77 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc_file.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 17:50:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 17:50:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "heredoc_internal.h"
#include <errno.h>
#include "variables.h"
static char *build_tmp_path(unsigned int id)
{
char *suffix;
char *path;
suffix = ft_uitoa(id);
if (suffix == NULL)
return (errno = ENOMEM, NULL);
path = ft_strnjoin(2, "/tmp/minishell_heredoc_", suffix);
free(suffix);
if (path == NULL)
return (errno = ENOMEM, NULL);
return (path);
}
int executor_heredoc_open_tmp(char **path_out)
{
static unsigned int counter;
char *path;
unsigned int attempts;
int fd;
attempts = 0;
while (attempts++ < 10000)
{
path = build_tmp_path(counter++);
if (path == NULL)
return (-1);
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);
if (fd != -1)
return (*path_out = path, fd);
free(path);
if (errno != EEXIST)
return (-1);
}
errno = EEXIST;
return (-1);
}
void executor_heredoc_discard_tmp(int fd, char *path)
{
if (fd >= 0)
close(fd);
if (path != NULL)
{
unlink(path);
free(path);
}
}
bool executor_heredoc_finalize_tmp(t_redirection *rd, int fd, char *path,
uint8_t *exit_status)
{
if (close(fd) == -1)
return (executor_heredoc_discard_tmp(-1, path), perror("close"),
*exit_status = EXIT_FAILURE, false);
free(rd->target);
rd->target = path;
rd->heredoc_ready = true;
return (true);
}

View File

@@ -0,0 +1,80 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc_input.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 17:50:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 17:50:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "heredoc_internal.h"
#include "errors.h"
static bool write_all(int fd, const char *buffer, size_t len)
{
size_t total;
ssize_t written;
total = 0;
while (total < len)
{
written = write(fd, buffer + total, len - total);
if (written <= 0)
return (false);
total += written;
}
return (true);
}
static bool write_heredoc_line(int fd, const char *line)
{
if (!write_all(fd, line, ft_strlen(line)))
return (false);
return (write_all(fd, "\n", 1));
}
static bool handle_heredoc_line(int fd, char *line, t_redirection *rd,
t_minishell *minishell)
{
char *expanded;
expanded = line;
if (rd->heredoc_expand)
{
expanded = executor_expand_heredoc_line(line, minishell);
free(line);
if (expanded == NULL)
return (false);
}
if (!write_heredoc_line(fd, expanded))
return (free(expanded), perror("write"), false);
free(expanded);
return (true);
}
bool executor_heredoc_fill_tmp(int fd, t_redirection *rd,
t_minishell *minishell, uint8_t *exit_status)
{
char *line;
while (1)
{
line = executor_heredoc_read_line(minishell);
if (minishell_consume_sigint())
return (free(line), *exit_status = 130, false);
if (line == NULL)
{
if (!isatty(STDIN_FILENO))
minishell->exit = true;
return (ft_eprintf("minishell: warning: here-document delimited by "
"end-of-file (wanted `%s')\n", rd->target), true);
}
if (ft_strcmp(line, rd->target) == 0)
return (free(line), true);
if (!handle_heredoc_line(fd, line, rd, minishell))
return (*exit_status = EXIT_FAILURE, false);
}
}

View File

@@ -0,0 +1,26 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc_internal.h :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 17:50:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 17:50:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef HEREDOC_INTERNAL_H
# define HEREDOC_INTERNAL_H
# include "executor.h"
char *executor_heredoc_read_line(t_minishell *minishell);
int executor_heredoc_open_tmp(char **path_out);
void executor_heredoc_discard_tmp(int fd, char *path);
bool executor_heredoc_finalize_tmp(t_redirection *rd, int fd, char *path,
uint8_t *exit_status);
bool executor_heredoc_fill_tmp(int fd, t_redirection *rd,
t_minishell *minishell, uint8_t *exit_status);
#endif

View File

@@ -0,0 +1,89 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* heredoc_readline.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/15 00:20:00 by sede-san #+# #+# */
/* Updated: 2026/02/15 00:20:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "heredoc_internal.h"
#include "variables.h"
#include <errno.h>
static bool append_char(char **line, size_t *len, char c)
{
char *new_line;
size_t i;
new_line = (char *)malloc(*len + 2);
if (new_line == NULL)
return (free(*line), *line = NULL, false);
i = 0;
while (i < *len)
{
new_line[i] = (*line)[i];
i++;
}
new_line[*len] = c;
new_line[*len + 1] = '\0';
free(*line);
*line = new_line;
(*len)++;
return (true);
}
static char *read_line_stdin(void)
{
char *line;
char c;
size_t len;
ssize_t nread;
line = ft_strdup("");
if (line == NULL)
return (NULL);
len = 0;
errno = 0;
nread = read(STDIN_FILENO, &c, 1);
while (nread > 0)
{
if (c == '\n')
return (line);
if (!append_char(&line, &len, c))
return (NULL);
nread = read(STDIN_FILENO, &c, 1);
}
if (nread == -1 && errno == EINTR)
return (line);
if (len > 0)
return (line);
return (free(line), NULL);
}
char *executor_heredoc_read_line(t_minishell *minishell)
{
char *prompt;
char *line;
size_t len;
if (!isatty(STDIN_FILENO))
{
line = get_next_line(STDIN_FILENO);
if (line == NULL)
return (NULL);
len = ft_strlen(line);
if (len > 0 && line[len - 1] == '\n')
line[len - 1] = '\0';
return (line);
}
prompt = get_var("PS2", minishell);
if (prompt == NULL)
prompt = DEFAULT_PS2;
if (write(STDOUT_FILENO, prompt, ft_strlen(prompt)) == -1)
return (NULL);
return (read_line_stdin());
}

View File

@@ -6,11 +6,12 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/11 00:00:00 by sede-san ### ########.fr */
/* Updated: 2026/02/14 13:16:15 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "executor.h"
#include "variables.h"
static bool is_path_explicit(
const char *command_name
@@ -37,7 +38,7 @@ static char *resolve_path_from_env(
return (NULL);
command_path = NULL;
i = -1;
while (!command_path && path_env[++i] != NULL)
while (command_name[0] != 0 && !command_path && path_env[++i] != NULL)
{
command_path = ft_strnjoin(3, path_env[i], "/", command_name);
if (command_path != NULL && access(command_path, X_OK) != EXIT_SUCCESS)

View File

@@ -53,7 +53,7 @@ void executor_child_process(
minishell_set_child_signals();
executor_setup_child_input(pipeline);
executor_setup_child_output(current_command, pipeline);
if (!executor_apply_redirections(command, NULL, NULL))
if (!executor_apply_redirections(command, NULL, NULL, NULL))
exit(EXIT_FAILURE);
exit_status = executor_execute_command(command, minishell);
exit(exit_status);

View File

@@ -19,6 +19,8 @@ static int open_redirection_target(
{
if (redirection->type == TOKEN_REDIRECT_IN)
return (open(redirection->target, O_RDONLY));
if (redirection->type == TOKEN_HEREDOC)
return (open(redirection->target, O_RDONLY));
if (redirection->type == TOKEN_REDIRECT_OUT)
return (open(redirection->target, O_WRONLY | O_CREAT | O_TRUNC, 0644));
if (redirection->type == TOKEN_APPEND)
@@ -27,73 +29,74 @@ static int open_redirection_target(
return (-1);
}
static bool backup_stream_if_needed(
const t_redirection *redirection,
int *saved_stdin,
int *saved_stdout
static bool backup_fd_if_needed(
int io_number,
int *saved_fd
)
{
if (redirection->type == TOKEN_REDIRECT_IN && saved_stdin != NULL
&& *saved_stdin == -1)
{
*saved_stdin = dup(STDIN_FILENO);
if (*saved_stdin == -1)
return (perror("dup"), false);
}
if ((redirection->type == TOKEN_REDIRECT_OUT
|| redirection->type == TOKEN_APPEND) && saved_stdout != NULL
&& *saved_stdout == -1)
{
*saved_stdout = dup(STDOUT_FILENO);
if (*saved_stdout == -1)
return (perror("dup"), false);
}
if (saved_fd == NULL || *saved_fd != -1)
return (true);
*saved_fd = dup(io_number);
if (*saved_fd == -1)
return (perror("dup"), false);
return (true);
}
static bool apply_redirection(
const t_redirection *redirection,
int *saved_stdin,
int *saved_stdout,
int *saved_stderr
)
{
int fd;
if (redirection == NULL || redirection->target == NULL)
return (false);
if ((redirection->io_number == STDIN_FILENO
&& !backup_fd_if_needed(STDIN_FILENO, saved_stdin))
|| (redirection->io_number == STDOUT_FILENO
&& !backup_fd_if_needed(STDOUT_FILENO, saved_stdout))
|| (redirection->io_number == STDERR_FILENO
&& !backup_fd_if_needed(STDERR_FILENO, saved_stderr)))
return (false);
fd = open_redirection_target(redirection);
if (fd == -1)
return (perror(redirection->target), false);
if (dup2(fd, redirection->io_number) == -1)
return (close(fd), perror("dup2"), false);
return (close(fd), true);
}
bool executor_apply_redirections(
const t_command *command,
int *saved_stdin,
int *saved_stdout
int *saved_stdout,
int *saved_stderr
)
{
t_list *node;
t_redirection *redirection;
int fd;
if (saved_stdin != NULL)
*saved_stdin = -1;
if (saved_stdout != NULL)
*saved_stdout = -1;
if (command == NULL || command->redirections == NULL)
if (saved_stderr != NULL)
*saved_stderr = -1;
if (command == NULL)
return (true);
node = command->redirections;
while (node != NULL)
{
redirection = (t_redirection *)node->content;
if (redirection == NULL || redirection->target == NULL)
return (false);
if (!backup_stream_if_needed(redirection, saved_stdin, saved_stdout))
return (false);
fd = open_redirection_target(redirection);
if (fd == -1)
return (perror(redirection->target), false);
if (redirection->type == TOKEN_REDIRECT_IN
&& dup2(fd, STDIN_FILENO) == -1)
return (close(fd), perror("dup2"), false);
if ((redirection->type == TOKEN_REDIRECT_OUT
|| redirection->type == TOKEN_APPEND)
&& dup2(fd, STDOUT_FILENO) == -1)
return (close(fd), perror("dup2"), false);
close(fd);
while (node != NULL
&& apply_redirection((t_redirection *)node->content,
saved_stdin, saved_stdout, saved_stderr))
node = node->next;
}
return (true);
return (node == NULL);
}
void executor_restore_redirections(
int saved_stdin,
int saved_stdout
int saved_stdout,
int saved_stderr
)
{
if (saved_stdin != -1)
@@ -108,4 +111,10 @@ void executor_restore_redirections(
perror("dup2");
close(saved_stdout);
}
if (saved_stderr != -1)
{
if (dup2(saved_stderr, STDERR_FILENO) == -1)
perror("dup2");
close(saved_stderr);
}
}

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/10/20 20:51:33 by sede-san #+# #+# */
/* Updated: 2026/02/13 20:12:07 by sede-san ### ########.fr */
/* Updated: 2026/02/14 12:57:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -15,29 +15,22 @@
#include "parser.h"
#include "executor.h"
static void set_prompts(
t_minishell *minishell
)
{
minishell->prompt.ps1 = DEFAULT_PS1;
minishell->prompt.ps2 = DEFAULT_PS2;
}
void minishell_init(
t_minishell *minishell,
char **envp
){
ft_bzero(minishell, sizeof(t_minishell));
set_intp(minishell);
set_envp(envp, minishell);
set_builtins(minishell);
set_prompts(minishell);
if (minishell->variables.environment == NULL || minishell->builtins == NULL)
minishell_clear(minishell);
}
void minishell_run(
t_minishell *minishell
){
)
{
char *line;
t_list *commands;
@@ -47,7 +40,7 @@ void minishell_run(
while (!minishell->exit)
{
if (isatty(STDIN_FILENO))
line = readline(minishell->prompt.ps1);
line = readline(get_var("PS1", minishell));
else
line = get_next_line(STDIN_FILENO);
handle_sigint_status(minishell);
@@ -67,6 +60,8 @@ void minishell_clear(
t_minishell *minishell
){
rl_clear_history();
if (minishell->variables.internal != NULL)
ft_hashmap_clear(&minishell->variables.internal, free);
if (minishell->variables.environment != NULL)
ft_hashmap_clear(&minishell->variables.environment, free);
if (minishell->builtins != NULL)

View File

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:09:23 by sede-san #+# #+# */
/* Updated: 2026/02/13 21:09:23 by sede-san ### ########.fr */
/* Updated: 2026/02/14 13:03:15 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -31,6 +31,7 @@ bool handle_eof(
return (false);
if (isatty(STDIN_FILENO))
ft_putendl("exit");
free(line);
minishell->exit = true;
return (true);
}

View File

@@ -14,6 +14,8 @@
#include "errors.h"
static t_list *parse_tokens(t_list *tokens);
static bool parse_add_command(t_list **commands, t_list **current_token);
static bool parse_advance_token(t_list **commands, t_list **current_token);
/**
* @brief Converts a command line string into a list of commands.
@@ -52,9 +54,7 @@ static t_list *parse_tokens(
)
{
t_list *commands;
t_command *command;
t_list *current_token;
t_list *new_command;
if (tokens == NULL)
return (NULL);
@@ -63,32 +63,49 @@ static t_list *parse_tokens(
if (((t_token *)current_token->content)->type == TOKEN_PIPE)
return (syntax_error_unexpected_token(
(t_token *)current_token->content), NULL);
while (current_token != NULL)
{
command = command_new(&current_token);
if (command == NULL)
{
ft_lstclear(&commands, (void (*)(void *))command_clear);
while (current_token != NULL && parse_add_command(&commands, &current_token)
&& parse_advance_token(&commands, &current_token))
continue ;
if (current_token != NULL)
return (NULL);
}
return (commands);
}
static bool parse_add_command(
t_list **commands,
t_list **current_token
)
{
t_command *command;
t_list *new_command;
command = command_new(current_token);
if (command == NULL)
return (ft_lstclear(commands, (void (*)(void *))command_clear), false);
new_command = ft_lstnew(command);
if (new_command == NULL)
{
command_clear(command);
ft_lstclear(&commands, (void (*)(void *))command_clear);
return (malloc_error(), NULL);
return (ft_lstclear(commands, (void (*)(void *))command_clear),
malloc_error(), false);
}
ft_lstadd_back(&commands, new_command);
if (current_token != NULL)
{
if (current_token->next == NULL)
{
ft_lstclear(&commands, (void (*)(void *))command_clear);
return (syntax_error_unexpected_token(
(t_token *)current_token->content), NULL);
}
current_token = current_token->next;
}
}
return (commands);
ft_lstadd_back(commands, new_command);
return (true);
}
static bool parse_advance_token(
t_list **commands,
t_list **current_token
)
{
if (*current_token == NULL)
return (true);
if ((*current_token)->next == NULL)
{
ft_lstclear(commands, (void (*)(void *))command_clear);
return (syntax_error_unexpected_token(
(t_token *)(*current_token)->content), false);
}
*current_token = (*current_token)->next;
return (true);
}

46
src/parser/parser_clear.c Normal file
View File

@@ -0,0 +1,46 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_clear.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 16:10:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 16:10:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
static void command_clear_argv(
t_command *command
)
{
int i;
if (command->argv != NULL)
{
i = 0;
while (i < command->argc)
{
free(command->argv[i]);
i++;
}
free(command->argv);
command->argv = NULL;
}
}
void command_clear(
t_command *command
)
{
if (command != NULL)
{
command_clear_argv(command);
ft_lstclear(&command->redirections,
(void (*)(void *))redirection_clear);
ft_lstclear(&command->heredocs, (void (*)(void *))redirection_clear);
free(command);
}
}

View File

@@ -58,10 +58,18 @@ void command_add_tokens(
)
{
t_token *token;
int io_number;
token = (t_token *)(*tokens)->content;
if (parser_token_is_fd_prefix(*tokens, &io_number))
{
*tokens = (*tokens)->next;
token = (t_token *)(*tokens)->content;
redirection_add_with_fd(tokens, token, command, io_number);
return ;
}
if (is_redirection(token))
redirection_add(tokens, token, command);
redirection_add_with_fd(tokens, token, command, -1);
else
words_add(tokens, command);
}

View File

@@ -0,0 +1,54 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_command_fd.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 16:40:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 16:40:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
#include <limits.h>
static bool parse_io_number(const char *value, int *io_number)
{
long number;
size_t i;
if (value == NULL || value[0] == '\0')
return (false);
number = 0;
i = 0;
while (value[i] != '\0')
{
if (!ft_isdigit(value[i]))
return (false);
if (number > (INT_MAX - (value[i] - '0')) / 10)
return (false);
number = (number * 10) + (value[i] - '0');
i++;
}
*io_number = (int)number;
return (true);
}
bool parser_token_is_fd_prefix(
t_list *token_node,
int *io_number
)
{
t_token *token;
t_token *next_token;
if (token_node == NULL || token_node->next == NULL)
return (false);
token = (t_token *)token_node->content;
next_token = (t_token *)token_node->next->content;
if (token == NULL || next_token == NULL || token->type != TOKEN_WORD
|| !is_redirection(next_token))
return (false);
return (parse_io_number(token->value, io_number));
}

View File

@@ -6,92 +6,109 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:24:45 by sede-san #+# #+# */
/* Updated: 2026/02/13 21:24:45 by sede-san ### ########.fr */
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
#include "errors.h"
#include "parser_expand_internal.h"
static char *replace_value(char *original, char *value, int start, int end);
static void expand_variable(char **argument, int *i, t_minishell *minishell);
static void expand_argument(char **argument, t_minishell *minishell);
static char *replace_value(
char *original,
char *value,
int start,
int end
static void command_clear_argv_expand(
t_command *command
)
{
const char *before = ft_substr(original, 0, start);
const char *after = ft_substr(original, end, ft_strlen(original) - end);
const char *expanded = ft_strnjoin(3, before, value, after);
free((char *)before);
free((char *)after);
return ((char *)expanded);
}
static void expand_variable(
char **argument,
int *i,
t_minishell *minishell
)
{
char *expanded;
char *variable_name;
char *variable_value;
const int start = *i + 1;
int end;
end = start;
while (ft_isalnum((*argument)[end]) || (*argument)[end] == '_')
end++;
variable_name = ft_substr(*argument, start, end - start);
if (variable_name == NULL)
return (minishell->exit = true, malloc_error());
variable_value = get_env(variable_name, minishell);
free(variable_name);
if (variable_value == NULL)
variable_value = "";
expanded = replace_value(*argument, variable_value, start - 1, end);
if (expanded == NULL)
return (minishell->exit = true, malloc_error());
*i += ft_strlen(variable_value);
free(*argument);
*argument = expanded;
}
static void expand_argument(
char **argument,
t_minishell *minishell
)
{
bool in_single_quote;
bool in_double_quote;
int i;
in_single_quote = false;
in_double_quote = false;
i = 0;
while ((*argument)[i] != '\0')
while (i < command->argc)
{
if ((*argument)[i] == '$' && !in_single_quote)
{
expand_variable(argument, &i, minishell);
if (*argument == NULL)
return (minishell->exit = true, malloc_error());
}
else
{
if ((*argument)[i] == '\'' && !in_double_quote)
in_single_quote = !in_single_quote;
else if ((*argument)[i] == '"' && !in_single_quote)
in_double_quote = !in_double_quote;
free(command->argv[i]);
i++;
}
free(command->argv);
}
static bool command_set_expanded_argv(
t_command *command,
t_list *expanded_args,
t_minishell *minishell
)
{
char **argv;
t_list *current;
int argc;
int i;
argc = ft_lstsize(expanded_args);
argv = (char **)malloc(sizeof(char *) * (argc + 1));
if (argv == NULL)
return (ft_lstclear(&expanded_args, free),
parser_expand_malloc_error(minishell), false);
i = 0;
current = expanded_args;
while (current != NULL)
{
argv[i++] = (char *)current->content;
current = current->next;
}
argv[i] = NULL;
ft_lstclear_nodes(&expanded_args);
command_clear_argv_expand(command);
command->argc = argc;
command->argv = argv;
return (true);
}
static bool expand_argv(
t_command *command,
t_minishell *minishell
)
{
int i;
t_list *expanded_args;
t_list *fields;
t_list *last;
i = 0;
expanded_args = NULL;
while (i < command->argc)
{
fields = NULL;
if (!parser_expand_word_fields(command->argv[i], minishell,
true, &fields))
return (ft_lstclear(&expanded_args, free), false);
if (expanded_args == NULL)
expanded_args = fields;
else
{
last = ft_lstlast(expanded_args);
if (last != NULL)
last->next = fields;
}
i++;
}
return (command_set_expanded_argv(command, expanded_args, minishell));
}
static bool expand_redirections(
t_list *redirections,
t_minishell *minishell
)
{
t_redirection *redirection;
char *expanded;
while (redirections != NULL)
{
redirection = (t_redirection *)redirections->content;
expanded = parser_expand_word(redirection->target, minishell,
true);
if (expanded == NULL)
return (false);
free(redirection->target);
redirection->target = expanded;
redirections = redirections->next;
}
return (true);
}
void expand(
@@ -99,24 +116,23 @@ void expand(
t_minishell *minishell
)
{
t_list *current_command;
t_list *current;
t_command *command;
int i;
current_command = *commands;
while (current_command != NULL)
if (commands == NULL || *commands == NULL)
return ;
current = *commands;
while (current != NULL)
{
command = (t_command *)current_command->content;
i = 0;
while (i < command->argc)
command = (t_command *)current->content;
if (!expand_argv(command, minishell)
|| !expand_redirections(command->redirections, minishell)
|| !expand_redirections(command->heredocs, minishell))
{
expand_argument(&command->argv[i], minishell);
if (command->argv[i] == NULL)
ft_lstclear(commands, (void (*)(void *))command_clear);
i++;
*commands = NULL;
return ;
}
if (command == NULL)
return (ft_lstclear(commands, (void (*)(void *))command_clear));
current_command = current_command->next;
current = current->next;
}
}

View File

@@ -0,0 +1,106 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_fields.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 13:15:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 13:15:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
static t_fields_ctx init_fields_ctx(
t_list **fields,
char **current,
t_field_state *state
)
{
t_fields_ctx ctx;
ctx.fields = fields;
ctx.current = current;
ctx.touched = &state->touched;
ctx.in_single_quote = &state->in_single_quote;
ctx.in_double_quote = &state->in_double_quote;
return (ctx);
}
static bool finish_word_fields(
t_fields_ctx ctx
)
{
if (*ctx.in_single_quote || *ctx.in_double_quote)
{
ft_lstclear(ctx.fields, free);
free(*ctx.current);
syntax_error_unexpected_token(NULL);
ctx.minishell->exit_status = 2;
return (false);
}
if ((*ctx.touched || (*ctx.current)[0] != '\0')
&& !parser_fields_push_field(ctx))
return (false);
free(*ctx.current);
return (true);
}
static bool process_word_fields(
const char *word,
t_fields_ctx ctx
)
{
size_t i;
i = 0;
while (word[i] != '\0')
{
if (!parser_fields_step(word, &i, ctx))
return (false);
}
return (true);
}
static bool set_empty_word_field(
t_list **fields,
t_minishell *minishell
)
{
t_list *node;
node = ft_lstnew(ft_strdup(""));
if (node == NULL || node->content == NULL)
return (free(node), parser_expand_malloc_error(minishell), false);
*fields = node;
return (true);
}
bool parser_expand_word_fields(
const char *word,
t_minishell *minishell,
bool expand_vars,
t_list **fields
)
{
char *current;
t_field_state state;
t_fields_ctx ctx;
*fields = NULL;
if (word[0] == '\0')
return (set_empty_word_field(fields, minishell));
current = ft_strdup("");
if (current == NULL)
return (parser_expand_malloc_error(minishell), false);
state.touched = false;
state.in_single_quote = false;
state.in_double_quote = false;
ctx = init_fields_ctx(fields, &current, &state);
ctx.minishell = minishell;
ctx.expand_vars = expand_vars;
if (!process_word_fields(word, ctx))
return (ft_lstclear(fields, free), free(current), false);
return (finish_word_fields(ctx));
}

View File

@@ -0,0 +1,85 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_fields_escape.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 16:40:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 16:40:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
static bool is_backslash_escaped_in_double_quote(
char c
)
{
return (c == '$' || c == '\"' || c == '\\');
}
static char get_escaped_char(
const char *word,
size_t *i
)
{
char value;
if (word[*i + 1] == '\0')
value = word[(*i)++];
else
{
value = word[*i + 1];
*i += 2;
}
return (value);
}
bool parser_fields_handle_backslash(
const char *word,
size_t *i,
t_fields_ctx ctx,
bool *handled
)
{
char value[2];
*handled = false;
if (word[*i] != '\\' || *ctx.in_single_quote)
return (true);
if (*ctx.in_double_quote
&& !is_backslash_escaped_in_double_quote(word[*i + 1]))
return (true);
*handled = true;
value[0] = get_escaped_char(word, i);
value[1] = '\0';
if (!parser_fields_append_text(ctx.current, value, ctx.minishell))
return (false);
*ctx.touched = true;
return (true);
}
bool parser_fields_expand_tilde(
const char *word,
size_t *i,
t_fields_ctx ctx,
bool *handled
)
{
char *home;
*handled = false;
if (word[*i] != '~' || *ctx.in_single_quote || *ctx.in_double_quote
|| *i != 0 || (word[*i + 1] != '\0' && word[*i + 1] != '/'))
return (true);
home = get_var("HOME", ctx.minishell);
if (home == NULL)
return (true);
*handled = true;
(*i)++;
if (!parser_fields_append_text(ctx.current, home, ctx.minishell))
return (false);
*ctx.touched = true;
return (true);
}

View File

@@ -0,0 +1,113 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_fields_step.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 14:12:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 14:12:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
static bool handle_quote_char(
const char *word,
size_t *i,
t_fields_ctx ctx
)
{
if (word[*i] == '\'' && !*ctx.in_double_quote)
{
*ctx.in_single_quote = !*ctx.in_single_quote;
*ctx.touched = true;
(*i)++;
return (true);
}
if (word[*i] == '\"' && !*ctx.in_single_quote)
{
*ctx.in_double_quote = !*ctx.in_double_quote;
*ctx.touched = true;
(*i)++;
return (true);
}
return (false);
}
static bool skip_dollar_quote_prefix(
const char *word,
size_t *i,
t_fields_ctx ctx
)
{
if (word[*i] == '$' && !*ctx.in_single_quote && !*ctx.in_double_quote
&& word[*i + 1] != '\0'
&& (word[*i + 1] == '\'' || word[*i + 1] == '\"'))
{
(*i)++;
return (true);
}
return (false);
}
static bool expand_dollar_token(
const char *word,
size_t *i,
t_fields_ctx ctx,
bool *handled
)
{
char *expanded;
*handled = false;
if (word[*i] != '$' || *ctx.in_single_quote || !ctx.expand_vars)
return (true);
*handled = true;
expanded = parser_expand_variable(word, i, ctx.minishell);
if (expanded == NULL)
return (false);
if (!*ctx.in_double_quote
&& !parser_fields_expand_unquoted_value(ctx, expanded))
return (free(expanded), false);
if (*ctx.in_double_quote && !parser_fields_append_text(ctx.current,
expanded, ctx.minishell))
return (free(expanded), false);
if (*ctx.in_double_quote && expanded[0] != '\0')
*ctx.touched = true;
free(expanded);
return (true);
}
bool parser_fields_step(
const char *word,
size_t *i,
t_fields_ctx ctx
)
{
char value[2];
bool handled;
if (handle_quote_char(word, i, ctx))
return (true);
if (!parser_fields_handle_backslash(word, i, ctx, &handled))
return (false);
if (handled)
return (true);
if (!parser_fields_expand_tilde(word, i, ctx, &handled))
return (false);
if (handled)
return (true);
if (skip_dollar_quote_prefix(word, i, ctx))
return (true);
if (!expand_dollar_token(word, i, ctx, &handled))
return (false);
if (handled)
return (true);
value[0] = word[(*i)++];
value[1] = '\0';
if (!parser_fields_append_text(ctx.current, value, ctx.minishell))
return (false);
*ctx.touched = true;
return (true);
}

View File

@@ -0,0 +1,100 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_fields_utils.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 14:12:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 14:12:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
bool parser_fields_append_text(
char **current,
const char *value,
t_minishell *minishell
)
{
char *joined;
joined = ft_strnjoin(2, *current, (char *)value);
if (joined == NULL)
{
free(*current);
*current = NULL;
return (parser_expand_malloc_error(minishell), false);
}
free(*current);
*current = joined;
return (true);
}
static bool is_ifs_char(
char c
)
{
return (c == ' ' || c == '\t' || c == '\n');
}
bool parser_fields_push_field(
t_fields_ctx ctx
)
{
t_list *node;
node = ft_lstnew(*ctx.current);
if (node == NULL)
{
free(*ctx.current);
*ctx.current = NULL;
return (parser_expand_malloc_error(ctx.minishell), false);
}
ft_lstadd_back(ctx.fields, node);
*ctx.current = ft_strdup("");
if (*ctx.current == NULL)
return (ft_lstclear(ctx.fields, free),
parser_expand_malloc_error(ctx.minishell), false);
*ctx.touched = false;
return (true);
}
static void skip_ifs(
const char *expanded,
size_t *i
)
{
while (expanded[*i] != '\0' && is_ifs_char(expanded[*i]))
(*i)++;
}
bool parser_fields_expand_unquoted_value(
t_fields_ctx ctx,
const char *expanded
)
{
size_t i;
char value[2];
i = 0;
while (expanded[i] != '\0')
{
if (is_ifs_char(expanded[i]))
{
if ((*ctx.touched || (*ctx.current)[0] != '\0')
&& !parser_fields_push_field(ctx))
return (false);
skip_ifs(expanded, &i);
continue ;
}
value[0] = expanded[i];
value[1] = '\0';
if (!parser_fields_append_text(ctx.current, value, ctx.minishell))
return (false);
*ctx.touched = true;
i++;
}
return (true);
}

View File

@@ -0,0 +1,67 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_internal.h :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 06:56:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef PARSER_EXPAND_INTERNAL_H
# define PARSER_EXPAND_INTERNAL_H
# include "parser.h"
# include "errors.h"
# include "variables.h"
typedef struct s_fields_ctx t_fields_ctx;
void parser_expand_malloc_error(t_minishell *minishell);
char *parser_expand_variable(const char *word, size_t *i,
t_minishell *minishell);
char *parser_expand_word(const char *word, t_minishell *minishell,
bool expand_vars);
bool parser_expand_word_fields(const char *word, t_minishell *minishell,
bool expand_vars, t_list **fields);
bool parser_fields_append_text(char **current, const char *value,
t_minishell *minishell);
bool parser_fields_push_field(t_fields_ctx ctx);
bool parser_fields_expand_unquoted_value(t_fields_ctx ctx,
const char *expanded);
bool parser_fields_step(const char *word, size_t *i, t_fields_ctx ctx);
bool parser_fields_handle_backslash(const char *word, size_t *i,
t_fields_ctx ctx, bool *handled);
bool parser_fields_expand_tilde(const char *word, size_t *i,
t_fields_ctx ctx, bool *handled);
typedef struct s_word_ctx
{
char **result;
bool *in_single_quote;
bool *in_double_quote;
t_minishell *minishell;
bool expand_vars;
} t_word_ctx;
typedef struct s_fields_ctx
{
t_list **fields;
char **current;
bool *touched;
bool *in_single_quote;
bool *in_double_quote;
t_minishell *minishell;
bool expand_vars;
} t_fields_ctx;
typedef struct s_field_state
{
bool touched;
bool in_single_quote;
bool in_double_quote;
} t_field_state;
#endif

View File

@@ -0,0 +1,82 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_variable.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 06:56:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
void parser_expand_malloc_error(
t_minishell *minishell
)
{
minishell->exit = true;
malloc_error();
}
static bool is_var_char(
char c
)
{
return (ft_isalnum(c) || c == '_');
}
static char *dup_or_error(
const char *value,
t_minishell *minishell
)
{
char *dup;
dup = ft_strdup(value);
if (dup == NULL)
return (parser_expand_malloc_error(minishell), NULL);
return (dup);
}
static char *expand_named_variable(
const char *word,
size_t *i,
t_minishell *minishell
)
{
char *name;
char *value;
size_t start;
start = *i;
while (word[*i] != '\0' && is_var_char(word[*i]))
(*i)++;
name = ft_substr(word, start, *i - start);
if (name == NULL)
return (parser_expand_malloc_error(minishell), NULL);
value = get_var(name, minishell);
free(name);
if (value == NULL)
value = "";
return (dup_or_error(value, minishell));
}
char *parser_expand_variable(const char *word, size_t *i,
t_minishell *minishell)
{
char *expanded;
(*i)++;
if (word[*i] == '?')
{
expanded = ft_itoa(minishell->exit_status);
if (expanded == NULL)
return (parser_expand_malloc_error(minishell), NULL);
return ((*i)++, expanded);
}
if (word[*i] == '\0' || !is_var_char(word[*i]))
return (dup_or_error("$", minishell));
return (expand_named_variable(word, i, minishell));
}

View File

@@ -0,0 +1,104 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* parser_expand_word.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 06:56:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 06:56:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser_expand_internal.h"
static bool word_toggle_quotes(
char c,
bool *in_single_quote,
bool *in_double_quote
)
{
if (c == '\'' && !*in_double_quote)
return (*in_single_quote = !*in_single_quote, true);
if (c == '\"' && !*in_single_quote)
return (*in_double_quote = !*in_double_quote, true);
return (false);
}
static bool word_append(
char **result,
const char *value,
t_minishell *minishell
)
{
char *joined;
joined = ft_strnjoin(2, *result, (char *)value);
if (joined == NULL)
{
free(*result);
*result = NULL;
return (parser_expand_malloc_error(minishell), false);
}
free(*result);
*result = joined;
return (true);
}
static bool word_step(
const char *word,
size_t *i,
t_word_ctx ctx
)
{
char *expanded;
char value[2];
if (word_toggle_quotes(word[*i], ctx.in_single_quote, ctx.in_double_quote))
return ((*i)++, true);
if (word[*i] == '$' && word[*i + 1] != '\0'
&& !*ctx.in_single_quote && !*ctx.in_double_quote
&& (word[*i + 1] == '\'' || word[*i + 1] == '\"'))
return ((*i)++, true);
if (word[*i] == '$' && !*ctx.in_single_quote && ctx.expand_vars)
{
expanded = parser_expand_variable(word, i, ctx.minishell);
if (expanded == NULL || !word_append(ctx.result, expanded,
ctx.minishell))
return (free(expanded), false);
return (free(expanded), true);
}
value[0] = word[*i];
value[1] = '\0';
(*i)++;
return (word_append(ctx.result, value, ctx.minishell));
}
char *parser_expand_word(
const char *word,
t_minishell *minishell,
bool expand_vars
)
{
char *result;
size_t i;
bool in_single_quote;
bool in_double_quote;
result = ft_strdup("");
if (result == NULL)
return (parser_expand_malloc_error(minishell), NULL);
i = 0;
in_single_quote = false;
in_double_quote = false;
while (word[i] != '\0')
{
if (!word_step(word, &i, (t_word_ctx){&result, &in_single_quote,
&in_double_quote, minishell, expand_vars}))
return (NULL);
}
if (in_single_quote || in_double_quote)
return (free(result), syntax_error_unexpected_token(NULL),
minishell->exit_status = 2, NULL);
return (result);
}

View File

@@ -13,7 +13,9 @@
#include "parser.h"
#include "errors.h"
static t_redirection *redirection_new(t_list **tokens);
static t_redirection *redirection_new(t_list **tokens, int io_number);
static bool redirection_read_target(t_redirection *rd, t_list **tk);
static bool has_single_quote(const char *value);
/**
* @brief Creates a new redirection from a list of tokens.
@@ -23,7 +25,8 @@ static t_redirection *redirection_new(t_list **tokens);
* @return A new redirection or `NULL` on error.
*/
static t_redirection *redirection_new(
t_list **tokens
t_list **tokens,
int io_number
)
{
t_redirection *redirection;
@@ -34,28 +37,62 @@ static t_redirection *redirection_new(
return (malloc_error(), NULL);
token = (t_token *)(*tokens)->content;
redirection->type = token->type;
*tokens = (*tokens)->next;
if (*tokens == NULL)
{
free(redirection);
return (syntax_error_unexpected_token(NULL), NULL);
}
token = (t_token *)(*tokens)->content;
redirection->heredoc_expand = true;
redirection->heredoc_ready = false;
if (io_number < 0)
redirection->io_number = STDOUT_FILENO;
else
redirection->io_number = io_number;
if (io_number < 0 && (token->type == TOKEN_REDIRECT_IN
|| token->type == TOKEN_HEREDOC))
redirection->io_number = STDIN_FILENO;
if (!redirection_read_target(redirection, tokens))
return (free(redirection), NULL);
return (redirection);
}
static bool redirection_read_target(
t_redirection *rd,
t_list **tk
)
{
t_token *token;
*tk = (*tk)->next;
if (*tk == NULL)
return (syntax_error_unexpected_token(NULL), false);
token = (t_token *)(*tk)->content;
if (token->type != TOKEN_WORD)
{
free(redirection);
while (*tokens != NULL)
*tokens = (*tokens)->next;
return (syntax_error_unexpected_token(token), NULL);
while (*tk != NULL)
*tk = (*tk)->next;
return (syntax_error_unexpected_token(token), false);
}
redirection->target = ft_strdup(token->value);
if (redirection->target == NULL)
if (rd->type == TOKEN_HEREDOC && has_single_quote(token->value))
rd->heredoc_expand = false;
rd->target = ft_strdup(token->value);
if (rd->target == NULL)
return (malloc_error(), false);
*tk = (*tk)->next;
return (true);
}
static bool has_single_quote(
const char *value
)
{
size_t i;
if (value == NULL)
return (false);
i = 0;
while (value[i] != '\0')
{
free(redirection);
return (malloc_error(), NULL);
if (value[i] == '\'')
return (true);
i++;
}
*tokens = (*tokens)->next;
return (redirection);
return (false);
}
void redirection_clear(
@@ -69,16 +106,17 @@ void redirection_clear(
}
}
void redirection_add(
void redirection_add_with_fd(
t_list **tokens,
t_token *token,
t_command **command
t_command **command,
int io_number
)
{
t_redirection *redirection;
t_list *redirection_tokens;
redirection = redirection_new(tokens);
redirection = redirection_new(tokens, io_number);
if (redirection == NULL)
{
command_clear(*command);
@@ -91,8 +129,6 @@ void redirection_add(
free(redirection);
return (malloc_error());
}
if (token->type == TOKEN_HEREDOC)
ft_lstadd_back(&(*command)->heredocs, redirection_tokens);
else
(void)token;
ft_lstadd_back(&(*command)->redirections, redirection_tokens);
}

View File

@@ -6,48 +6,86 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:29:44 by sede-san #+# #+# */
/* Updated: 2026/02/13 21:29:44 by sede-san ### ########.fr */
/* Updated: 2026/02/14 03:34:38 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "parser.h"
char **args_to_array(t_list **args, size_t argc);
void command_clear_argv(t_command *command);
/**
* @brief Converts a list of arguments into an array.
*
* @param args The list of arguments to convert.
* @param argc The number of arguments in the list.
*
* @return An array of arguments or `NULL` on error.
*
* @note The `args` list is cleared after the conversion and set to NULL.
*/
char **args_to_array(
static bool args_add_word(
t_list **args,
size_t argc
const char *value
)
{
t_list *node;
char *dup;
dup = ft_strdup(value);
if (dup == NULL)
return (false);
node = ft_lstnew(dup);
if (node == NULL)
return (free(dup), false);
ft_lstadd_back(args, node);
return (true);
}
static bool words_collect(
t_list *arg,
t_list **args,
int *new_argc,
t_list **end
)
{
t_token *token;
int io_number;
while (arg != NULL)
{
token = (t_token *)arg->content;
if (token->type != TOKEN_WORD || parser_token_is_fd_prefix(arg,
&io_number))
break ;
if (!args_add_word(args, token->value))
return (false);
(*new_argc)++;
arg = arg->next;
}
*end = arg;
return (true);
}
static bool command_append_words(
t_command *command,
t_list **args,
int new_argc
)
{
char **argv;
t_list *arg;
size_t i;
t_list *current;
int i;
argv = (char **)malloc(sizeof(char *) * (argc + 1));
argv = (char **)malloc(sizeof(char *) * (command->argc + new_argc + 1));
if (argv == NULL)
return (NULL);
return (false);
i = 0;
arg = *args;
while (arg != NULL)
while (i < command->argc)
{
argv[i] = (char *)arg->content;
arg = arg->next;
argv[i] = command->argv[i];
i++;
}
current = *args;
while (current != NULL)
{
argv[i++] = (char *)current->content;
current = current->next;
}
argv[i] = NULL;
free(command->argv);
ft_lstclear_nodes(args);
return (argv);
command->argv = argv;
command->argc += new_argc;
return (true);
}
/**
@@ -63,59 +101,12 @@ void words_add(
)
{
t_list *args;
t_list *arg;
t_token *token;
int new_argc;
args = NULL;
arg = *tokens;
token = (t_token *)arg->content;
while (arg != NULL && token->type == TOKEN_WORD)
{
ft_lstadd_back(&args, ft_lstnew(ft_strdup(token->value)));
(*command)->argc++;
arg = arg->next;
if (arg != NULL)
token = (t_token *)arg->content;
}
*tokens = arg;
(*command)->argv = args_to_array(&args, (*command)->argc);
ft_lstclear_nodes(&args);
}
void command_clear_argv(
t_command *command
)
{
int i;
if (command->argv != NULL)
{
i = 0;
while (i < command->argc)
{
free(command->argv[i]);
i++;
}
free(command->argv);
command->argv = NULL;
}
}
/**
* @brief Clears a command, freeing all associated memory.
*
* @param command The command to clear.
*/
void command_clear(
t_command *command
)
{
if (command != NULL)
{
command_clear_argv(command);
ft_lstclear(&command->redirections,
(void (*)(void *))redirection_clear);
ft_lstclear(&command->heredocs, (void (*)(void *))redirection_clear);
free(command);
}
new_argc = 0;
if (!words_collect(*tokens, &args, &new_argc, tokens)
|| !command_append_words(*command, &args, new_argc))
return (ft_lstclear(&args, free), command_clear(*command),
*command = NULL, (void)0);
}

View File

@@ -1,192 +0,0 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* environment.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/12/01 09:12:39 by sede-san #+# #+# */
/* Updated: 2026/02/08 19:44:15 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
/**
* @brief Parses and stores environment variables from envp array into a hashmap
*
* This function iterates through the environment variables array (envp) and
* splits each variable string on the '=' delimiter to separate the variable
* name from its value. Each name-value pair is then stored in the minishell's
* environment hashmap for later retrieval.
*
* @param envp Array of environment variable strings in "NAME=value" format
* @param msh Pointer to the minishell structure containing the environment
* hashmap
*
* @note The function assumes envp strings are in the standard format
* "NAME=value"
*/
void set_envp(
char **envp,
t_minishell *msh
) {
char *equal_sign;
char *key;
char *value;
if (msh == NULL || envp == NULL)
return ;
msh->variables.environment
= ft_hashmap_new(32, ft_hashmap_hashstr, ft_hashmap_strcmp);
if (msh->variables.environment == NULL)
return ;
while (*envp != NULL)
{
equal_sign = ft_strchr(*envp, '=');
if (equal_sign == NULL)
{
key = ft_strdup(*envp);
value = ft_strdup("");
}
else
{
key = ft_substr(*envp, 0, equal_sign - *envp);
value = ft_strdup(equal_sign + 1);
}
if (key == NULL || value == NULL)
{
free(key);
free(value);
ft_hashmap_clear(&msh->variables.environment, free);
return ;
}
ft_hashmap_put(msh->variables.environment, key, value);
envp++;
}
}
/**
* @brief Sets an environment variable in the minishell's environment hashmap.
*
* This function adds a new environment variable or updates an existing one
* in the minishell's environment hashmap. If the variable already exists,
* the old value is freed to prevent memory leaks. If the variable is new,
* a duplicate of the key name is created for storage.
*
* @param env_name The name of the environment variable to set
* @param env_value The value to assign to the environment variable
* @param msh Pointer to the minishell structure containing the
* environment hashmap
*/
void set_env(
const char *env_name,
char *env_value,
t_minishell *msh
) {
t_hashmap *environment;
const char *key;
char *old_value;
environment = msh->variables.environment;
key = env_name;
if (!ft_hashmap_contains_key(environment, key))
key = ft_strdup(env_name);
old_value = ft_hashmap_put(environment, key, ft_strdup(env_value));
if (old_value != NULL)
free(old_value);
}
/**
* @brief Converts the environment variables hashmap to an envp array format.
*
* This function extracts all environment variables from the minishell's
* environment hashmap and converts them into a NULL-terminated array of
* strings in the format "KEY=VALUE".
*
* @param msh Pointer to the minishell structure containing the environment
* variables hashmap.
*
* @return A dynamically allocated array of strings representing environment
* variables in "KEY=VALUE" format, terminated by NULL. Returns NULL
* if memory allocation fails. The caller is responsible for freeing
* the returned array and its individual string elements using
* the `free_envp()` function.
*
* @note The function allocates memory for both the array and individual
* strings using malloc and ft_strnjoin respectively.
* @note The returned array size is environment->size + 1 to accommodate
* the NULL terminator.
*/
char **get_envp(
t_minishell *msh
) {
char **envp;
t_list *env_list;
t_list *env;
t_map_entry *entry;
size_t i;
env_list = ft_hashmap_entries(msh->variables.environment);
envp = (char **)malloc(
(msh->variables.environment->size + 1) * sizeof(char *)
);
if (envp != NULL)
{
i = 0;
env = env_list;
while (env != NULL)
{
entry = env->content;
envp[i++] = ft_strnjoin(3, entry->key, "=", entry->value);
env = env->next;
}
envp[i] = NULL;
}
ft_lstclear_nodes(&env_list);
return (envp);
}
/**
* @brief Frees a dynamically allocated environment variables array
*
* This function deallocates memory for an array of strings that was previously
* allocated by `get_envp()`. It iterates through each string in the array,
* frees the memory for individual strings, and then frees the array itself.
*
* @param envp Pointer to the array of environment variable strings to be freed.
* Each string in the array should be dynamically allocated.
* The array must be NULL-terminated.
*/
void free_envp(
char **envp
) {
size_t i;
i = -1;
while (envp[++i] != NULL)
free(envp[i]);
free(envp);
}
/**
* @brief Retrieves the value of an environment variable from the shell's
* environment hashmap.
*
* This function searches for the specified environment variable name in the
* minishell's environment variable hashmap and returns its associated value.
*
* @param env_name The name of the environment variable to retrieve (e.g.,
* "PATH", "HOME")
* @param msh Pointer to the minishell structure containing the environment
* variables hashmap
*
* @return The value of the environment variable if found, NULL if not found
*/
char *get_env(
const char *env_name,
t_minishell *msh
) {
return (ft_hashmap_get(msh->variables.environment, env_name));
}

View File

@@ -0,0 +1,149 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* environment.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/12/01 09:12:39 by sede-san #+# #+# */
/* Updated: 2026/02/14 13:30:34 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
#include "variables.h"
#include "errors.h"
static char *resolve_key(
const char *name,
t_hashmap *environment,
t_minishell *minishell,
bool *owns_key
);
static char *resolve_value(
char *value,
t_minishell *minishell
);
/**
* @brief Retrieves the value of an environment variable from the shell's
* environment hashmap.
*
* This function searches for the specified environment variable name in the
* minishell's environment variable hashmap and returns its associated value.
*
* @param name The name of the environment variable to retrieve.
* @param minishell Pointer to the minishell object.
*
* @return The value of the environment variable if found, NULL if not found
*/
char *get_env(
const char *name,
t_minishell *minishell
)
{
return (ft_hashmap_get(minishell->variables.environment, name));
}
/**
* @brief Sets an environment variable in the minishell's environment hashmap.
*
* This function adds a new environment variable or updates an existing one
* in the minishell's environment hashmap. If the variable already exists,
* the old value is freed to prevent memory leaks. If the variable is new,
* a duplicate of the key name is created for storage.
*
* @param name The name of the environment variable to set
* @param value The value to assign to the environment variable
* @param minishell Pointer to the minishell object.
*/
void set_env(
const char *name,
char *value,
t_minishell *minishell
)
{
t_hashmap *environment;
char *key;
char *val;
char *old_value;
bool owns_key;
environment = minishell->variables.environment;
key = resolve_key(name, environment, minishell, &owns_key);
if (key == NULL)
return ;
val = resolve_value(value, minishell);
if (val == NULL)
{
if (owns_key)
free(key);
return ;
}
old_value = ft_hashmap_put(environment, key, val);
if (old_value != NULL)
free(old_value);
}
/**
* @brief Removes an environment variable by name.
*
* @param name The name of the environment variable to remove.
* @param minishell Pointer to the minishell structure.
*
* @note If the environment variable exists, it will be removed from the hashmap
* and its associated value will be freed.
*/
void unset_env(
const char *name,
t_minishell *minishell
)
{
t_hashmap *environment;
char *val;
environment = minishell->variables.environment;
val = ft_hashmap_remove(environment, (void *)name);
if (val != NULL)
free(val);
}
static char *resolve_key(
const char *name,
t_hashmap *environment,
t_minishell *minishell,
bool *owns_key
)
{
char *key;
key = (char *)name;
*owns_key = false;
if (name == NULL)
return (NULL);
if (!ft_hashmap_contains_key(environment, name))
{
key = ft_strdup(name);
if (key == NULL)
return (minishell->exit = true, malloc_error(), NULL);
*owns_key = true;
}
return (key);
}
static char *resolve_value(
char *value,
t_minishell *minishell
)
{
char *val;
if (value == NULL)
val = ft_strdup("");
else
val = ft_strdup(value);
if (val == NULL)
return (minishell->exit = true, malloc_error(), NULL);
return (val);
}

View File

@@ -0,0 +1,121 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* environment_envp.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/14 16:10:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 16:10:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
#include "variables.h"
#include "errors.h"
static void fill_envp(
char **envp,
t_list *env_list
)
{
t_list *env;
t_map_entry *entry;
size_t i;
i = 0;
env = env_list;
while (env != NULL)
{
entry = env->content;
envp[i++] = ft_strnjoin(3, entry->key, "=", entry->value);
env = env->next;
}
envp[i] = NULL;
}
char **get_envp(
t_minishell *minishell
)
{
char **envp;
t_list *env_list;
env_list = ft_hashmap_entries(minishell->variables.environment);
envp = (char **)malloc(
(minishell->variables.environment->size + 1) * sizeof(char *)
);
if (envp != NULL)
fill_envp(envp, env_list);
ft_lstclear_nodes(&env_list);
return (envp);
}
static bool import_env_entry(
char *entry_text,
t_minishell *minishell
)
{
char *equal;
char *key;
char *value;
size_t key_len;
equal = ft_strchr(entry_text, '=');
if (equal == NULL)
return (true);
key_len = (size_t)(equal - entry_text);
key = ft_substr(entry_text, 0, key_len);
value = ft_strdup(equal + 1);
if (key == NULL || value == NULL)
return (free(key), free(value), minishell->exit = true,
malloc_error(), false);
set_env(key, value, minishell);
free(key);
free(value);
return (!minishell->exit);
}
static void update_shlvl(
t_minishell *minishell
)
{
char *value;
char *new_shlvl;
int shlvl;
value = get_env("SHLVL", minishell);
shlvl = 0;
if (value != NULL)
shlvl = ft_atoi(value);
if (shlvl < 0)
shlvl = 0;
new_shlvl = ft_itoa(shlvl + 1);
if (new_shlvl == NULL)
return ((void)(minishell->exit = true), malloc_error());
set_env("SHLVL", new_shlvl, minishell);
free(new_shlvl);
}
void set_envp(
char **envp,
t_minishell *minishell
)
{
t_hashmap *environment;
if (minishell == NULL || envp == NULL)
return ;
environment = ft_hashmap_new(32, ft_hashmap_hashstr, ft_hashmap_strcmp);
if (environment == NULL)
return ;
minishell->variables.environment = environment;
while (*envp != NULL)
{
if (!import_env_entry(*envp, minishell))
return ;
envp++;
}
update_shlvl(minishell);
}

View File

@@ -1,41 +0,0 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* environment_unset.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/09 22:16:00 by codex #+# #+# */
/* Updated: 2026/02/09 22:16:00 by codex ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
void unset_env(
const char *env_name,
t_minishell *msh
){
t_hashmap *new_env;
t_list *entries;
t_list *current;
t_map_entry *entry;
new_env = ft_hashmap_new(32, ft_hashmap_hashstr, ft_hashmap_strcmp);
if (new_env == NULL)
return ;
entries = ft_hashmap_entries(msh->variables.environment);
current = entries;
while (current != NULL)
{
entry = current->content;
if (ft_strcmp(entry->key, env_name) != 0)
ft_hashmap_put(new_env,
ft_strdup(entry->key), ft_strdup(entry->value));
current = current->next;
}
ft_lstclear_nodes(&entries);
ft_hashmap_clear(&msh->variables.environment, free);
msh->variables.environment = new_env;
}

View File

@@ -0,0 +1,123 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* internal.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:29:43 by sede-san #+# #+# */
/* Updated: 2026/02/14 15:17:17 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "minishell.h"
#include "core.h"
#include "errors.h"
/**
* @brief Retrieves the value of an internal variable by name.
*
* @param name The name of the internal variable to retrieve.
* @param minishell Pointer to the minishell structure.
*
* @return The value of the internal variable if found, or NULL if not found.
*/
char *get_int(
const char *name,
t_minishell *minishell
)
{
return (ft_hashmap_get(minishell->variables.internal, name));
}
/**
* @brief Stores a variable as internal.
*
* @param name The name of the internal variable to set.
* @param value The value to associate with the internal variable.
* @param minishell Pointer to the minishell structure.
*
* @note If the internal variable already exists, its value will be updated and
* the old value will be freed. If it does not exist, a new internal
* variable will be created.
*/
void set_int(
const char *name,
char *value,
t_minishell *minishell
)
{
t_hashmap *internal;
char *key;
char *val;
char *old_val;
internal = minishell->variables.internal;
key = (char *)name;
if (key != NULL && !ft_hashmap_contains_key(internal, key))
{
key = ft_strdup(name);
if (key == NULL)
return (minishell->exit = true, malloc_error());
}
val = value;
if (val != NULL)
val = ft_strdup(value);
if (val == NULL)
{
if (key != name)
free(key);
return (minishell->exit = true, malloc_error());
}
old_val = ft_hashmap_put(internal, key, val);
if (old_val != NULL)
free(old_val);
}
/**
* @brief Removes an internal variable by name.
*
* @param name The name of the internal variable to remove.
* @param minishell Pointer to the minishell structure.
*
* @note If the internal variable exists, it will be removed from the hashmap
* and its associated value will be freed.
*/
void unset_int(
const char *name,
t_minishell *minishell
)
{
char *value;
value = ft_hashmap_remove(minishell->variables.internal, (void *)name);
if (value != NULL)
free(value);
}
/**
* @brief Initializes the internal variables hashmap with default values.
*
* @param minishell Pointer to the minishell structure to initialize.
*
* @warning This function must be called only once during the initialization of
* the minishell.
*/
void set_intp(
t_minishell *minishell
)
{
if (minishell == NULL)
return ;
minishell->variables.internal
= ft_hashmap_new(16, ft_hashmap_hashstr, ft_hashmap_strcmp);
if (minishell->variables.internal == NULL)
return ;
set_int("?", "0", minishell);
set_int("_", "minishell", minishell);
set_int("PS0", "", minishell);
set_int("PS1", DEFAULT_PS1, minishell);
set_int("PS2", DEFAULT_PS2, minishell);
set_int("MINISHELL", "minishell", minishell);
set_int("MINISHELL_VERSION", MINISHELL_VERSION, minishell);
}

50
src/variables/variables.c Normal file
View File

@@ -0,0 +1,50 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* variables.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:41:44 by sede-san #+# #+# */
/* Updated: 2026/02/14 00:52:12 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
#include "variables.h"
/**
* @brief Retrieves the value of a variable by name.
*
* @param name The name of the variable to retrieve.
* @param minishell Pointer to the minishell structure.
*
* @return The value of the variable if found, or NULL if not found.
*/
char *get_var(
const char *name,
t_minishell *minishell
)
{
char *value;
value = get_int(name, minishell);
if (value == NULL)
value = get_env(name, minishell);
return (value);
}
void set_var(const char *name, char *value, t_minishell *minishell)
{
if (ft_hashmap_contains_key(minishell->variables.internal, name))
set_int(name, value, minishell);
set_env(name, value, minishell);
}
void unset_var(
const char *name,
t_minishell *minishell
)
{
unset_int(name, minishell);
unset_env(name, minishell);
}