10 Commits

Author SHA1 Message Date
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
46 changed files with 1560 additions and 390 deletions

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

@@ -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;
/**
@@ -129,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 */
@@ -142,8 +147,7 @@ extern char **get_envp(t_minishell *msh);
extern void free_envp(char **envp);
void handle_sigint_status(t_minishell *minishell);
bool handle_eof(char *line, t_minishell *minishell);
extern void handle_sigint_status(t_minishell *minishell);
extern bool handle_eof(char *line, t_minishell *minishell);
#endif /* CORE_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/14 01:13:42 by sede-san ### ########.fr */
/* Updated: 2026/02/14 15:17:38 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -42,4 +42,6 @@
# include <term.h> // tgetent(3), tgetflag(3), tgetnum(3),
// tgetstr(3), tgoto(3), tputs(3)
# 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);

View File

@@ -13,31 +13,31 @@
#ifndef VARIABLES_H
# define VARIABLES_H
# include "minishell.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);
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 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);
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 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);
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

@@ -12,8 +12,18 @@
#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_var(name, eq_pos + 1, msh);
else
set_var(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/14 01:34:37 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)
@@ -60,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);
}
@@ -81,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,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/11 00:00:00 by sede-san #+# #+# */
/* Updated: 2026/02/14 01:17:01 by sede-san ### ########.fr */
/* Updated: 2026/02/14 13:16:15 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -38,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;
t_list *node;
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/14 02:23:17 by sede-san ### ########.fr */
/* Updated: 2026/02/14 12:57:00 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */

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.
@@ -51,10 +53,8 @@ static t_list *parse_tokens(
t_list *tokens
)
{
t_list *commands;
t_command *command;
t_list *current_token;
t_list *new_command;
t_list *commands;
t_list *current_token;
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);
return (NULL);
}
new_command = ft_lstnew(command);
if (new_command == NULL)
{
command_clear(command);
ft_lstclear(&commands, (void (*)(void *))command_clear);
return (malloc_error(), NULL);
}
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;
}
}
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);
return (ft_lstclear(commands, (void (*)(void *))command_clear),
malloc_error(), false);
}
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

@@ -91,8 +91,7 @@ static bool expand_argv(
static bool expand_redirections(
t_list *redirections,
t_minishell *minishell,
bool expand_vars
t_minishell *minishell
)
{
t_redirection *redirection;
@@ -102,7 +101,7 @@ static bool expand_redirections(
{
redirection = (t_redirection *)redirections->content;
expanded = parser_expand_word(redirection->target, minishell,
expand_vars);
true);
if (expanded == NULL)
return (false);
free(redirection->target);
@@ -127,8 +126,8 @@ void expand(
{
command = (t_command *)current->content;
if (!expand_argv(command, minishell)
|| !expand_redirections(command->redirections, minishell, true)
|| !expand_redirections(command->heredocs, minishell, false))
|| !expand_redirections(command->redirections, minishell)
|| !expand_redirections(command->heredocs, minishell))
{
ft_lstclear(commands, (void (*)(void *))command_clear);
*commands = NULL;

View File

@@ -63,6 +63,20 @@ static bool process_word_fields(
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,
@@ -75,6 +89,8 @@ bool parser_expand_word_fields(
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);

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

@@ -90,6 +90,14 @@ bool parser_fields_step(
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))

View File

@@ -32,6 +32,10 @@ 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
{

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
ft_lstadd_back(&(*command)->redirections, redirection_tokens);
(void)token;
ft_lstadd_back(&(*command)->redirections, redirection_tokens);
}

View File

@@ -12,42 +12,80 @@
#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

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2025/12/01 09:12:39 by sede-san #+# #+# */
/* Updated: 2026/02/14 01:32:42 by sede-san ### ########.fr */
/* Updated: 2026/02/14 13:30:34 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -15,6 +15,17 @@
#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.
@@ -53,34 +64,21 @@ void set_env(
t_minishell *minishell
)
{
t_hashmap *environment;
char *key;
char *val;
char *old_value;
t_hashmap *environment;
char *key;
char *val;
char *old_value;
bool owns_key;
environment = minishell->variables.environment;
key = (char *)name;
if (key != NULL && !ft_hashmap_contains_key(environment, key))
{
key = ft_strdup(name);
if (key == NULL)
{
minishell->exit = true;
malloc_error();
return ;
}
}
val = value;
if (val == NULL)
val = ft_strdup("");
else
val = ft_strdup(value);
key = resolve_key(name, environment, minishell, &owns_key);
if (key == NULL)
return ;
val = resolve_value(value, minishell);
if (val == NULL)
{
if (key != name)
if (owns_key)
free(key);
minishell->exit = true;
malloc_error();
return ;
}
old_value = ft_hashmap_put(environment, key, val);
@@ -104,98 +102,48 @@ void unset_env(
{
t_hashmap *environment;
char *val;
environment = minishell->variables.environment;
val = ft_hashmap_remove(environment, (void *)name);
if (val != NULL)
free(val);
}
/**
* @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 minishell 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(
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 **envp;
t_list *env_list;
t_list *env;
t_map_entry *entry;
size_t i;
char *val;
env_list = ft_hashmap_entries(minishell->variables.environment);
envp = (char **)malloc(
(minishell->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 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 minishell 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 *minishell
)
{
t_hashmap **environment;
char **key_value;
if (minishell == NULL || envp == NULL)
return ;
environment = &minishell->variables.environment;
*environment = ft_hashmap_new(32, ft_hashmap_hashstr, ft_hashmap_strcmp);
if (*environment == NULL)
return ;
while (*envp != NULL)
{
key_value = ft_split(*envp, '=');
set_env(key_value[0], key_value[1], minishell);
ft_free_split(key_value);
envp++;
}
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

@@ -6,7 +6,7 @@
/* By: sede-san <sede-san@student.42madrid.com +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2026/02/13 21:29:43 by sede-san #+# #+# */
/* Updated: 2026/02/14 01:39:36 by sede-san ### ########.fr */
/* Updated: 2026/02/14 15:17:17 by sede-san ### ########.fr */
/* */
/* ************************************************************************** */
@@ -89,7 +89,7 @@ void unset_int(
)
{
char *value;
value = ft_hashmap_remove(minishell->variables.internal, (void *)name);
if (value != NULL)
free(value);
@@ -115,6 +115,9 @@ void set_intp(
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);
}

View File

@@ -26,7 +26,7 @@ char *get_var(
)
{
char *value;
value = get_int(name, minishell);
if (value == NULL)
value = get_env(name, minishell);
@@ -37,7 +37,7 @@ 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);
set_env(name, value, minishell);
}
void unset_var(