# The Scripts Every Project Has

Most software projects keep a few small scripts for the same four jobs: setup.sh gets a computer ready, dev.sh runs the project while you work, test.sh checks it and deploy.sh puts it live, and setup.sh checks its tools first with command -v.

Journeyman · Unix · Linux · Updated November 10, 2026

## After this lesson you can

- Give a project setup.sh, dev.sh, test.sh and deploy.sh, and choose the right one for a job
- Check that a script's tools are installed with command -v, and list every missing one before stopping

## Key takeaways

- A project's scripts turn the README's steps into commands: setup.sh, dev.sh, test.sh and deploy.sh, kept together in a scripts folder.
- command -v tool prints where a command is, or prints nothing and gives exit code 1 when it isn't installed.
- A setup script checks every tool and lists all the missing ones, so you can install them in one go.
- A test script answers with its exit code: 0 when everything passed, 1 when something didn't, so other scripts can act on it.
- cd "$(dirname "${BASH_SOURCE[0]}")/.." takes a script to the project's top folder, so it works from any folder.

You offer to help with a friend's bakery website, and its README says: install Python and rsync, start a web server with this long command, open this address, and before you put anything live, check every page by hand. Ten steps, and by step six you aren't sure you did step four. Or it's your own project, and after three months you can't remember how you started it. Most software projects solve this the same way: a few small scripts with names everyone recognizes. You run `./scripts/setup.sh` once, and `./scripts/dev.sh` every time you sit down to work. Web developers find them in almost every project they join. In a JavaScript project the same jobs are often `npm run dev` and `npm test`, and underneath, those run shell commands too.

## Four jobs, four scripts

Each **project script** does one job, and the names say which:

- **`setup.sh`** gets a computer ready to work on the project: it checks that the tools are installed and makes any folders or settings files. You run it once, after you download the project.
- **`dev.sh`** runs the project on your own computer while you work on it. For a website, that's a small **web server**, a program that hands pages to your browser, at `http://localhost:8000`.
- **`test.sh`** checks the project and answers yes or no.
- **`deploy.sh`** puts it live, like the one in *Running Commands on Another Computer*.

They live together in a `scripts` folder, and each one starts with the same line:

```bash
cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit
```

Read it as "go to the folder above the one this script is saved in", which is the project's top folder (*One Script, Several Files: source* taught `dirname` and `BASH_SOURCE`). The script then finds `site/` and its other files wherever you run it from.

## The bakery's project

These lines make the practice project: three pages, one of them missing its `<title>`, the line that names a page in the browser's tab. They're safe to run again.

```bash
mkdir -p ~/terminal-practice/bakery-site/site ~/terminal-practice/bakery-site/scripts && cd ~/terminal-practice/bakery-site
printf '<title>Bluebird Bakery</title>\n<h1>Bluebird Bakery</h1>\n<p>Fresh bread every morning.</p>\n' > site/index.html
printf '<title>Menu</title>\n<h1>Menu</h1>\n<p>Sourdough loaf: $6. Blueberry muffin: $3.</p>\n' > site/menu.html
printf '<h1>Order a cake</h1>\n<p>Call us two days ahead.</p>\n' > site/order.html
ls site
```

```text
index.html  menu.html  order.html
```

Make `scripts/dev.sh` with nano (*Your First Script*). Python comes with a small web server, so `dev.sh` starts it on the `site` folder:

```bash
#!/usr/bin/env bash
# dev.sh: shows the site at http://localhost:8000 until you press Ctrl+C.
cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit

echo "Open http://localhost:8000 in your browser. Ctrl+C stops it."
python3 -m http.server 8000 --directory site
```

Run `chmod +x scripts/dev.sh`, then `./scripts/dev.sh`, and open the address. When you press Ctrl+C, the server stops:

```text
Open http://localhost:8000 in your browser. Ctrl+C stops it.
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Keyboard interrupt received, exiting.
```

## setup.sh: check the tools first

`dev.sh` needs `python3`, and `deploy.sh` will need `rsync`. A script that finds out halfway through that a tool is missing leaves a job half done, so `setup.sh` asks first. `command -v` (read "command, dash v") is built into bash. Give it a name, and it prints where that command is, or prints nothing and gives exit code 1:

```bash
command -v python3
command -v shellcheck; echo "exit code: $?"
```

```text
/usr/bin/python3
exit code: 1
```

ShellCheck (*ShellCheck: A Second Pair of Eyes*) isn't installed on this computer. If it is on yours, you'll see its path instead. Here's `scripts/setup.sh`. It checks every tool before it stops, so one run lists everything you need:

```bash
#!/usr/bin/env bash
# setup.sh: checks that the tools this project needs are installed.
cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit

missing=0
for tool in python3 rsync shellcheck; do
  if command -v "$tool" > /dev/null; then
    echo "found:   $tool"
  else
    echo "missing: $tool"
    missing=1
  fi
done

if [[ $missing -eq 1 ]]; then
  echo "Install what's missing, then run setup.sh again."
  exit 1
fi
echo "Ready. Start the site with ./scripts/dev.sh"
```

`> /dev/null` throws away the path, because only the exit code matters here. Run it:

```bash
chmod +x scripts/setup.sh
./scripts/setup.sh; echo "exit code: $?"
```

```text
found:   python3
found:   rsync
missing: shellcheck
Install what's missing, then run setup.sh again.
exit code: 1
```

Why not `which`? It's a separate program that isn't on every system, while `command -v` is part of the shell, on Linux and on a Mac alike.

## test.sh: answer with an exit code

`test.sh` checks what you'd otherwise check by hand: here, that every page has a `<title>`. Its exit code is the answer, so other scripts, and Git in *Git Hooks: Scripts Git Runs for You*, can act on it:

```bash
#!/usr/bin/env bash
# test.sh: checks that every page has a <title>.
cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit

failed=0
for page in site/*.html; do
  if ! grep -q '<title>' "$page"; then
    echo "No title: $page"
    failed=1
  fi
done

if [[ $failed -eq 0 ]]; then
  echo "Every page has a title."
fi
exit "$failed"
```

Make it runnable, then run it from inside `site`, to see the first line at work:

```bash
chmod +x scripts/test.sh
cd site
../scripts/test.sh; echo "exit code: $?"
```

```text
No title: site/order.html
exit code: 1
```

Go back up with `cd ..`, give the order page a title, and run it again:

```bash
printf '<title>Order a cake</title>\n<h1>Order a cake</h1>\n<p>Call us two days ahead.</p>\n' > site/order.html
./scripts/test.sh; echo "exit code: $?"
```

```text
Every page has a title.
exit code: 0
```

## Check yourself

2 questions to try on the lesson page.

## So what

Four scripts with names everyone knows turn a page of instructions into four commands, and a project you can set up, run, check and ship the same way every time.

## Key terms

- [Project Script](https://www.ask-codey.com/glossary/project-script)
- [command -v](https://www.ask-codey.com/glossary/command-v)
- [Exit Code](https://www.ask-codey.com/glossary/exit-code)
- [Web server](https://www.ask-codey.com/glossary/web-server)

## Cheat sheets

- [The Scripts Every Project Has](https://www.ask-codey.com/cheat-sheets/bash-project-scripts-cheat-sheet)

## Related

- [Running Commands on Another Computer](https://www.ask-codey.com/lessons/bash-remote-commands)
- [One Script, Several Files: source](https://www.ask-codey.com/lessons/bash-script-libraries)
- [Did It Work? Exit Codes, && and ||](https://www.ask-codey.com/lessons/bash-exit-codes)
- [Exercise: A Project's Scripts](https://www.ask-codey.com/lessons/exercise-bash-a-projects-scripts)

Source: https://www.ask-codey.com/lessons/bash-project-scripts
