Skip to content

ChandlerL24/Unix-Shell-Project

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

msh — A Tiny Unix Shell

msh (short for "my shell") is a small command-line shell written in C. It works a lot like bash or zsh, just stripped down to the essentials. You type a command, it runs the program for you, and then it gives you the prompt back so you can type the next one.

Under the hood it does the same things a real shell does: it parses what you type, spins up child processes to run your commands, keeps track of background and foreground jobs, remembers your command history, and responds to keyboard signals like Ctrl-C and Ctrl-Z.


What it can do

  • Run programs — type the full path to a program plus its arguments and msh will run it.
  • Run several commands at once — separate them with ; (run one after another) or & (run in the background).
  • Background & foreground jobs — start something in the background with &, then move it around with bg and fg.
  • Job tracking — see everything that's currently running with jobs.
  • Send signals — use the built-in kill to send signals (like terminate or stop) to a process.
  • Command history — every command you type is remembered, even between sessions. Recall an old command by number with !N.
  • Keyboard signals — Ctrl-C cancels the current foreground program, and Ctrl-Z suspends it, without killing the shell itself.

Getting it running

Build it

There's a tiny build script in scripts/:

cd scripts
./build.sh

That just calls gcc to compile everything in src/ and drops the finished msh binary into bin/. Under the covers it runs:

gcc -I../include/ -o ../bin/msh ../src/*.c

Start the shell

./bin/msh

You'll get a prompt:

msh>

Type a command and hit Enter. To leave, just type exit.

Optional settings

You can tweak a few limits when you launch it:

Flag What it does Default
-s N How many history entries to keep 10
-j N How many jobs can run at once 16
-l N Max characters allowed per command line 1024

For example:

./bin/msh -s 50 -j 8 -l 2048

How to use it

Running a program

msh expects the full path to whatever you want to run, just like the kernel does:

msh> /bin/ls -l
msh> /bin/echo hello world

Multiple commands on one line

  • ; runs commands in the foreground, one after another:
    msh> /bin/echo first ; /bin/echo second
    
  • & runs a command in the background so you get your prompt right back:
    msh> /bin/sleep 10 &
    

Managing jobs

msh> jobs              # list everything that's running or stopped
msh> bg %1             # resume job #1 in the background
msh> fg %1             # bring job #1 to the foreground
msh> kill -9 12345     # send signal 9 (kill) to process 12345

Job control supports signals 2 (interrupt), 9 (kill), 18 (continue), and 19 (stop).

Command history

msh> history           # show your recent commands, numbered
msh> !3                # re-run command number 3

Your history is saved to data/.msh_history when you exit, so it's still there the next time you start the shell.


How it all works (the tour)

Here's the journey a command takes from the moment you press Enter:

  1. You type a line. msh.c holds the main loop. It prints the msh> prompt, reads your whole line with getline, and (unless you typed exit) hands it off to evaluate.

  2. The line gets split into separate commands. parse_tok (in shell.c) walks through the line and breaks it apart at every ; or &. Each chunk is one command, and it also notes whether that command should run in the foreground or background.

  3. Each command gets split into arguments. separate_args takes a single command and chops it into an argv-style array (the program name plus its arguments), just like the argv your own main function receives.

  4. Built-in commands are handled directly. Some commands (history, jobs, bg, fg, kill, and !N) are handled by the shell itself in builtin_cmd — there's no separate program to run, so msh just does the work and moves on.

  5. Everything else becomes a child process. For a normal program, msh calls fork to make a copy of itself, and the child calls execve to replace itself with the program you asked for. The parent (the shell) records the new job in its jobs table.

    • If it's a foreground job, the shell waits for it to finish before showing the prompt.
    • If it's a background job, the shell records it and immediately gives you the prompt back.
  6. Signals keep everything in sync. signal_handlers.c sets up handlers so the shell reacts to events:

    • SIGCHLD fires when a child finishes or changes state — the handler "reaps" finished children (so they don't become zombies) and updates job states.
    • SIGINT (Ctrl-C) is forwarded to whatever is running in the foreground.
    • SIGTSTP (Ctrl-Z) suspends the foreground job instead of the shell.
  7. On exit, things get cleaned up. When you type exit, the shell waits for any background jobs to finish, saves your command history to disk, frees all its memory, and shuts down.


What's in each file

Unix-Shell-Project/
├── src/
│   ├── msh.c               # main entry point + the read-eval loop and argument parsing
│   ├── shell.c             # the heart of it: parse the line, run built-ins, fork/exec programs
│   ├── job.c               # the jobs table — add, delete, and free background/foreground jobs
│   ├── history.c           # command history: load from file, add lines, recall, save on exit
│   └── signal_handlers.c   # handlers for Ctrl-C, Ctrl-Z, and child process events
│
├── include/                # matching header files for each source file
│   ├── shell.h
│   ├── job.h
│   ├── history.h
│   └── signal_handlers.h
│
├── scripts/
│   └── build.sh            # one-line gcc build script
│
├── bin/                    # where the compiled msh binary lands
├── data/
│   └── .msh_history        # your saved command history
└── tests/                  # unit tests + milestone test cases
    ├── test_parse_tok.c
    ├── test_separate_args.c
    └── milestone1/          # input/expected-output test pairs

A few things worth knowing

  • Use full paths. msh runs programs with execve and doesn't search your PATH, so use /bin/ls rather than just ls.
  • History skips a few things. Blank lines, exit, and !N recalls aren't saved to history — only the real commands you ran.
  • History rolls over. Once history is full, the oldest entry drops off to make room for the newest one.
  • Each job gets its own process group, which is what makes Ctrl-C and Ctrl-Z affect the right program instead of the shell.

Testing

The tests/ folder has small unit tests for the parsing functions (test_parse_tok and test_separate_args) plus a set of milestone test cases in tests/milestone1/. Each milestone case is a trio of files — an .in (what to type), .args (how to launch the shell), and .ans (the expected output) — that get compared automatically by test_msh.sh.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors