Skip to content

mr-body/Minishell

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

273 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Minishell (42 Cursus)

Objetivo: implementar um interpretador de comandos (shell) minimalista em C, reproduzindo comportamentos essenciais de um terminal Unix/Linux.

Este repositório contém uma implementação do projeto minishell do 42 Cursus. O foco é aprender (na prática) como um shell funciona por dentro: leitura de linha, parsing, expansão de variáveis, criação de processos, pipes, redirecionamentos, sinais, e execução de binários.


Índice


Visão geral

O minishell é um “mini bash”. Ele deve aceitar comandos digitados pelo usuário, interpretá-los e executá-los, incluindo:

  • Comandos simples (ls, cat, grep, etc.)
  • Pipes (|) e redirecionamentos (>, >>, <)
  • Expansão de variáveis de ambiente ($HOME, $USER, $?)
  • Builtins (ex.: cd, echo, pwd, export, unset, env, exit)
  • Controle básico de sinais (Ctrl-C, Ctrl-, Ctrl-D)

O que é um shell?

Um shell é um programa que:

  1. Mostra um prompt
  2. Lê uma linha do usuário
  3. Interpreta (tokeniza/parseia) a linha de acordo com regras (aspas, espaços, pipes, redirects)
  4. Expande variáveis e resolve caminhos
  5. Executa:
    • builtins (executados no processo do shell quando necessário)
    • programas externos (via fork() + execve())
  6. Espera os processos terminarem e guarda o status de saída

O minishell é um ótimo laboratório para entender como o Linux executa programas e como processos se comunicam.


Requisitos do projeto (42)

Em linhas gerais, o projeto pede:

  • Um prompt funcionando
  • Leitura de linha usando readline (ou equivalente permitido)
  • Histórico funcional
  • Execução de comandos baseados em PATH e execve
  • Pipes e redirecionamentos
  • Aspas simples e duplas
  • Variáveis de ambiente e $?
  • Builtins obrigatórios
  • Tratamento de sinais como no bash
  • Não interpretar \ e ; (no comportamento clássico do minishell)

Observação: alguns detalhes variam conforme o enunciado/versão. A ideia do README é documentar o que este projeto faz e como.


Funcionalidades implementadas

Prompt e leitura de linha

  • O programa exibe um prompt (ex.: minishell$ )
  • Lê input do usuário
  • Lida com Ctrl-D (EOF): geralmente encerra o shell de forma limpa

Por que isso é difícil?

  • O prompt precisa se comportar bem com sinais (Ctrl-C não deve “matar” o shell, só limpar a linha)
  • readline tem comportamento específico e precisa ser integrado com sinais e com o estado do terminal

Histórico

  • Armazena comandos digitados (via funcionalidades do readline)
  • Permite navegar com as setas (dependendo do terminal)

Lexer / Parser

Um shell precisa transformar texto em uma estrutura executável.

  • Lexer: divide a linha em tokens (palavras, |, >, >>, <, <<, etc.) respeitando aspas.
  • Parser: monta uma estrutura (ex.: lista de comandos conectados por pipes) e associa redirecionamentos a cada comando.

Pontos críticos:

  • Espaços dentro de aspas não quebram tokens: echo "ola mundo"
  • Operadores precisam ser reconhecidos: >> não é > duas vezes
  • Mensagens de erro para sintaxe inválida: | cmd, cmd | | cmd, cmd > etc.

Expansões

Implementadas as expansões típicas do minishell:

  • Variáveis de ambiente: $HOME, $PATH, $USER...
  • Exit status: $?

Regras importantes:

  • Em aspas simples ('...') não deve haver expansão.
  • Em aspas duplas ("...") deve haver expansão de $VAR.

Pontos críticos:

  • Concatenar texto com expansão: echo "user=$USER"
  • Variáveis inexistentes viram string vazia

Pipes

  • Suporte a cmd1 | cmd2 | cmd3
  • Criação de pipes com pipe() e processos com fork()
  • Redirecionamento de stdin/stdout usando dup2()

Pontos críticos:

  • Fechar FDs corretamente para não travar (deadlocks)
  • Esperar pelos processos (waitpid) e decidir qual exit status refletir no final

Redirecionamentos

Suporta:

  • > redireciona stdout (sobrescreve)
  • >> redireciona stdout (append)
  • < redireciona stdin

Pontos críticos:

  • Ordem e múltiplos redirecionamentos: cmd > a > b (o último normalmente “vence”)
  • Mensagens de erro quando arquivo não abre

Here-document (heredoc)

  • Suporte a << LIMITER
  • Lê linhas até encontrar o LIMITER
  • Normalmente implementado com pipe temporário ou arquivo temporário

Pontos críticos:

  • Sinais durante o heredoc (Ctrl-C)
  • Regras de expansão dentro do heredoc variam com aspas no limiter (dependendo do enunciado)

Builtins

Builtins clássicos do minishell:

  • echo (com -n)
  • cd (com caminhos relativos/absolutos)
  • pwd
  • export
  • unset
  • env
  • exit

Por que builtins são especiais?

  • Alguns precisam rodar no processo do shell para ter efeito (ex.: cd, export, unset, exit).
  • Em pipelines, muitas implementações rodam builtins em processos filhos para manter a semântica do pipe.

Execução de comandos externos

  • Busca binários em PATH quando o comando não contém /
  • Usa execve() para executar
  • Herda e/ou prepara o ambiente (envp) a partir das variáveis mantidas pelo minishell

Pontos críticos:

  • Erros clássicos: “command not found”, “permission denied”, diretório como comando
  • Respeitar códigos de saída usados no bash (127, 126, etc.)

Sinais

Comportamento típico esperado (inspirado no bash):

  • Ctrl-C (SIGINT):
    • no prompt: limpa linha e mostra prompt novamente
    • durante execução: interrompe o processo em execução
  • Ctrl-\ (SIGQUIT):
    • geralmente ignorado no prompt
    • pode gerar “Quit (core dumped)” em alguns casos (depende da configuração)

Pontos críticos:

  • Separar o comportamento do processo pai (shell) e filhos (comandos)
  • Configurar handlers com signal()/sigaction()
  • Restaurar comportamento padrão nos filhos antes do execve()

Exit status

  • O minishell mantém uma variável global (ou parte do estado) com o último status
  • $? deve refletir isso

Regra importante:

  • Em pipeline, normalmente o status final é do último comando do pipe (comportamento do bash).

Como compilar e executar

Ajuste os comandos conforme o Makefile do projeto.

1) Dependências

  • gcc / clang
  • make
  • readline (geralmente libreadline-dev no Linux)

2) Compilar

make

3) Executar

./minishell

Exemplos de uso

Comandos simples:

minishell$ pwd
minishell$ ls -la
minishell$ echo "HOME=$HOME"

Pipes:

minishell$ cat file.txt | grep hello | wc -l

Redirecionamentos:

minishell$ echo "ola" > out.txt
minishell$ cat < out.txt
minishell$ echo "mais" >> out.txt

Heredoc:

minishell$ cat << EOF
linha 1
linha 2
EOF

Arquitetura (alto nível)

Mesmo sem ver tua estrutura interna, um minishell normalmente é organizado assim:

  1. Loop principal
    • mostra prompt
    • lê linha
    • valida / ignora linha vazia
  2. Tokenização (lexer)
    • gera uma lista de tokens (com tipo e valor)
  3. Parsing
    • transforma tokens em comandos e operadores (pipes/redirects)
    • produz uma estrutura (ex.: lista de comandos com argumentos e redirs)
  4. Expansão
    • resolve $VAR, $?
    • remove aspas quando necessário
  5. Execução
    • se comando é builtin e não está em pipeline: executa no pai
    • caso contrário: cria processos, configura pipes, aplica redirs, execve
  6. Wait/Status
    • coleta status
    • atualiza $?

Dificuldades comuns e como resolvi

1) Aspas e tokens

Problema: lidar com ' e " sem quebrar tokens no meio.

Estratégia:

  • Fazer um lexer por estado (fora de aspas / dentro de aspas simples / dentro de aspas duplas)
  • Só finalizar token quando encontrar separador válido fora de aspas

2) Expansão vs aspas

Problema: expandir $VAR dentro de "" mas não dentro de ''.

Estratégia:

  • Durante o parsing ou etapa de expansão, manter informação sobre o contexto da string
  • Aplicar expansão apenas quando permitido

3) Pipes e FDs

Problema: processos travando por FDs abertos demais.

Estratégia:

  • Fechar sempre as pontas do pipe que não são usadas em cada processo
  • No pai, fechar FDs assim que não forem mais necessários

4) Builtins em pipeline

Problema: export dentro de pipeline não deve alterar o ambiente do pai.

Estratégia:

  • Se existir pipe, executar builtin no filho (efeito local)
  • Se não existir pipe, executar no processo pai (efeito persistente)

5) Sinais (principal “pegadinha”)

Problema: Ctrl-C deve agir diferente no prompt vs durante execução.

Estratégia:

  • No pai: handler custom para SIGINT
  • Nos filhos: restaurar sinais default antes do execve()

Nível de aprendizado (42)

Este projeto é considerado um dos mais importantes do cursus porque força você a consolidar conceitos centrais de sistemas operacionais:

  • Processos: fork, execve, waitpid
  • Comunicação: pipe, encadeamento e redirecionamento de fluxo
  • Arquivos e FDs: open, dup2, close
  • Sinais: SIGINT, SIGQUIT, comportamento pai/filho
  • Parsing: tokenização, gramática simples, tratamento de erros
  • Memória: evitar leaks, gerenciar strings e arrays de forma segura

Se você chegou até aqui e fez tudo funcionando bem (principalmente sem leaks e com sinais corretos), você saiu do projeto com um nível “42” muito mais sólido em:

  • leitura e escrita de C
  • raciocínio de baixo nível
  • depuração com gdb/valgrind
  • desenho de arquitetura

Testes e depuração

Sugestões:

  • Compare com o bash:
    • rode o mesmo comando no bash e no minishell e compare saída e status
  • Ferramentas:
    • valgrind --leak-check=full ./minishell
    • gdb ./minishell

Casos de teste úteis:

  • Erros de sintaxe:
    • | ls
    • echo hi >
  • Aspas:
    • echo "a b"
    • echo '$HOME'
  • Expansões:
    • `echo $?
    • echo "$USER"
  • Pipes + redirs:
    • cat < infile | grep x > out
  • Heredoc:
    • cat << EOF | wc -l

Limitações / não implementado

Dependendo do escopo do teu projeto, alguns itens normalmente não são exigidos:

  • && e ||
  • ;
  • wildcard * (globbing)
  • subshell ( ... )

Se você implementou algum desses extras, documente aqui.


Referências

  • man bash, man readline, man execve, man fork, man pipe, man dup2, man waitpid, man signal
  • Materiais do 42 sobre minishell

Autor

  • GitHub: @mr-body

About

O minishell é uma aplicação em C que simula o comportamento básico de um terminal Linux.

Topics

Resources

Stars

Watchers

Forks

Contributors