Getting Started
Thank you for your interest in contributing to Minishell! This guide will help you understand the development workflow, coding standards, and best practices.
Fork and Clone
Fork the repository and clone your fork:git clone <your-fork-url>
cd minishell
Build the Project
Ensure everything builds correctly: Create a Branch
Create a feature branch for your changes:git checkout -b feature/your-feature-name
Make Your Changes
Implement your feature or fix following the guidelines below.
Test Thoroughly
Test your changes with various inputs and edge cases.
Submit a Pull Request
Push your branch and create a pull request with a clear description.
Code Style Guidelines
Minishell follows the 42 School Norm, a strict coding standard. Key requirements:
General Rules
- Line Length: Maximum 80 characters per line
- Function Length: Maximum 25 lines per function
- Parameters: Maximum 4 parameters per function
- Variables: Maximum 5 variables per function
- Indentation: Use tabs (not spaces)
- Braces: K&R style (opening brace on same line)
Naming Conventions
// Functions: snake_case with ft_ prefix
int ft_strlen(char *str);
void ft_execute_commands(t_mini *data);
// Structures: s_ prefix, typedef with t_ prefix
typedef struct s_node
{
char **full_cmd;
int infile;
} t_node;
// Variables: snake_case
char **bin_path;
int nbr_nodes;
// Constants: UPPER_SNAKE_CASE
#define TEMP_FILE ".\vtemp\th"
#define GREEN "\033[32m"
All source files must include the 42 header:
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* filename.c :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: author <[email protected]> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: YYYY/MM/DD HH:MM:SS by author #+# #+# */
/* Updated: YYYY/MM/DD HH:MM:SS by author ### ########.fr */
/* */
/* ************************************************************************** */
Example of proper function formatting:
void echo(char **av, int flag)
{
int i;
i = 1;
if (flag == 2)
i = 2;
while (av[i] && ft_strncmp(av[i], "-n", 2) == 0 && !av[i][2])
i++;
while (av[i])
{
printf("%s", av[i]);
i++;
if (av[i])
printf(" ");
}
if (flag == 1)
printf("\n");
g_status = 0;
}
Use tabs for indentation, not spaces. The 42 norm is strict about this.
Project Architecture
Where to Add Code
Depending on your contribution, add code to the appropriate module:
| Feature | Files to Modify |
|---|
| New built-in command | builtins.c, builtins_utils.c, init_shell.c, minishell.h |
| Parsing logic | parse_imput.c, more_parsing.c |
| Execution flow | ft_execute_commands.c, ft_execute_one_command.c |
| Environment variables | env_parsed.c, env_parsed2.c |
| Signal handling | signals.c |
| Utility functions | utils.c, utils2.c, utils3.c |
| New structure/typedef | minishell.h |
Main Data Structures
Understand the core structures before contributing:
// Main shell state
typedef struct s_mini
{
char **commands; // Parsed command array
char *full_path; // Path to executable
int splits; // Argument count
int ft_count_pipes; // Pipe count
char **execute_envp; // Environment array
t_node **nodes; // Execution nodes
int nbr_nodes; // Node count
char **bin_path; // PATH directories
t_prompt *env; // Environment list
} t_mini;
// Execution node
typedef struct s_node
{
char **full_cmd; // Command + args
char *full_path; // Full executable path
int infile; // Input FD
int outfile; // Output FD
int is_set; // Validity flag
pid_t n_pid; // Process ID
} t_node;
// Environment variable
typedef struct s_prompt
{
char *envp; // "KEY=value"
struct s_prompt *next; // Next variable
} t_prompt;
Adding a New Built-in Command
Follow these steps to add a new built-in command (e.g., export):
Implement the Command Function
Add your function to builtins.c:void my_builtin(char **args, t_mini **data)
{
int i;
i = 0;
while (args[i])
{
// Your implementation
i++;
}
g_status = 0; // Set exit status
}
Add Function Prototype
Declare the function in minishell.h://builtins.c
void echo(char **av, int flag);
void pwd(int argc);
void cd(int argc, char *av);
void unset(char **argv, t_prompt **data);
void my_builtin(char **args, t_mini **data); // Add this
Add Built-in Detection
Update is_builtin() in set_infile_outfile.c (or wherever it’s located):int is_builtin(char *str)
{
if (ft_strncmp(str, "echo", 4) == 0 && !str[4])
return (1);
if (ft_strncmp(str, "cd", 2) == 0 && !str[2])
return (1);
if (ft_strncmp(str, "pwd", 3) == 0 && !str[3])
return (1);
if (ft_strncmp(str, "export", 6) == 0 && !str[6])
return (1);
if (ft_strncmp(str, "unset", 5) == 0 && !str[5])
return (1);
if (ft_strncmp(str, "env", 3) == 0 && !str[3])
return (1);
if (ft_strncmp(str, "exit", 4) == 0 && !str[4])
return (1);
// Add your built-in check
if (ft_strncmp(str, "mybuiltin", 9) == 0 && !str[9])
return (1);
return (0);
}
Route Command in execute_builtin
Add routing logic in init_shell.c:int other_actions(int argc, char **argv, t_mini **data)
{
// ... existing code ...
else if (ft_strncmp(argv[0], "mybuiltin", 9) == 0 && !argv[0][9])
my_builtin(argv, data);
else
flag = 1;
return (flag);
}
Update Makefile (if new file)
If you created a new file, add it to CFILES:CFILES = main.c split.c utils.c ... my_builtin.c
Test the Built-in
make re
./minishell
minishell> mybuiltin arg1 arg2
Built-in commands should NOT fork. They run in the parent shell process to affect the shell’s state (like cd changing the working directory).
Testing Your Changes
Manual Testing
Test various scenarios:
# Basic functionality
minishell> echo hello world
minishell> pwd
minishell> cd /tmp
minishell> export TEST=value
minishell> echo $TEST
# Pipes
minishell> ls -l | grep txt | wc -l
# Redirections
minishell> echo test > output.txt
minishell> cat < output.txt
minishell> ls >> output.txt
# Here-documents
minishell> cat << EOF
> line 1
> line 2
> EOF
# Quotes
minishell> echo "hello world"
minishell> echo 'single quotes'
minishell> echo "$USER is $HOME"
# Exit status
minishell> ls /nonexistent
minishell> echo $?
# Signal handling
minishell> sleep 10
^C
minishell> cat
^C
Edge Cases to Test
- Empty input
- Only spaces/tabs
- Unclosed quotes:
echo "hello
- Invalid pipes:
| ls, ls |, ls | | grep
- Invalid redirections:
>, < >, >> <<
- Very long input (> 1000 characters)
- Multiple consecutive spaces
- Special characters in quotes
- Environment variable expansion:
$VAR, $?, $$
- Commands not in PATH
- Permission denied errors
- Directory vs file errors
Comparison Testing
Compare behavior with bash:
# Test in bash
bash$ echo hello | grep h | wc -l
1
# Test in minishell
minishell> echo hello | grep h | wc -l
1
Memory Leak Checking
Minishell must not have memory leaks. Use Valgrind:
Basic Valgrind Check
valgrind --leak-check=full --show-leak-kinds=all ./minishell
Suppressing Readline Leaks
Readline library may show “still reachable” memory. Create a suppression file:
# readline.supp
{
readline_still_reachable
Memcheck:Leak
match-leak-kinds: reachable
...
obj:*/libreadline.so.*
}
Run with suppressions:
valgrind --leak-check=full --suppressions=readline.supp ./minishell
Common Memory Issues
- Not freeing split arrays: Use
free_split() or ft_free()
- Not freeing readline input:
free(line) after use (main.c:42)
- Leaked environment list: Use
ft_free_stack() (utils3.c)
- Leaked nodes: Use
ft_free_nodes() (main.c:39)
Every malloc() must have a corresponding free(). Use tools like Valgrind to verify.
Debugging Tips
Using Printf Debugging
// Add debug prints
void enterdata(char *line, t_mini *data, int i)
{
printf("DEBUG: line = '%s'\n", line); // Debug
add_history(line);
line = prepare_line(line, -1, 0);
printf("DEBUG: prepared = '%s'\n", line); // Debug
// ... rest of function
}
Using GDB
# Compile with debug symbols (already enabled with -g)
make
# Run with GDB
gdb ./minishell
# Set breakpoints
(gdb) break main
(gdb) break enterdata
(gdb) break ft_execute_commands
# Run and debug
(gdb) run
(gdb) next # Step over
(gdb) step # Step into
(gdb) print data # Print variable
(gdb) print *data->nodes[0] # Print structure
Debug Environment Variables
void print_env(t_prompt *env)
{
while (env)
{
printf("ENV: %s\n", env->envp);
env = env->next;
}
}
Debug Command Nodes
void print_node(t_node *node, int index)
{
int i = 0;
printf("Node[%d]:\n", index);
printf(" full_path: %s\n", node->full_path);
printf(" full_cmd: ");
while (node->full_cmd && node->full_cmd[i])
{
printf("'%s' ", node->full_cmd[i]);
i++;
}
printf("\n infile: %d, outfile: %d\n", node->infile, node->outfile);
printf(" is_set: %d\n", node->is_set);
}
Common Pitfalls
Pitfall #1: Forgetting to free memoryEvery allocation must be freed. Use Valgrind to verify.
Pitfall #2: Modifying argv directly// BAD - modifies original
char **args = argv;
args[0] = "modified";
// GOOD - create a copy if needed
char **args = ft_strdup2(argv, len, 0);
Pitfall #3: Not closing file descriptors// Always close opened file descriptors
if (fd != STDIN_FILENO && fd != STDOUT_FILENO)
close(fd);
Pitfall #4: Infinite loops in parsingEnsure loop counters advance:while (str[i])
{
// Must have i++ somewhere
i++;
}
Pitfall #5: Buffer overflowsUse safe string functions:// BAD
strcpy(dest, src);
// GOOD
ft_strlcpy(dest, src, dest_size);
Code Review Checklist
Before submitting your pull request:
Getting Help
If you need assistance:
- Check the documentation: Review the building and structure guides
- Read the code: The codebase is well-organized and documented
- Open an issue: Describe your problem with code examples
- Ask in discussions: For general questions about contributing
Next Steps
Happy coding! We appreciate your contributions to Minishell.