Putting It All Together

Table of Contents

  1. What We're Building
  2. What a Shell Script Is
  3. Writing the Script
  4. Reading It Line by Line
  5. Running and Testing It
  6. Why the Script Can't cd for You
  7. Installing It as Your Own Command
  8. The Whole Series in One Page
  9. Where to Go Next

1. What We're Building

Every time you start a new project, you do the same few things: make a folder, add src, tests, and docs inside it, write a README, add a .gitignore, create an empty .env and lock it down. That's maybe ten commands — and exactly the kind of repetitive work the command line is meant to automate.

In this chapter you'll write a script called new-project that does all of it in one go:

new-project my-app
Creating project: my-app
Done! Files created:
my-app
my-app/.env
my-app/.gitignore
my-app/README.md
my-app/docs
my-app/src
my-app/src/index.js
my-app/tests

Almost every line of it uses something from an earlier chapter, and by the end, it'll be a real command you can run from anywhere — just like ls or git.

2. What a Shell Script Is

A shell script is a text file containing commands — the same commands you've been typing — that the shell runs from top to bottom. You made a tiny one, hello.sh, in chapter 10.

Anything you can type at the prompt, you can put in a script. A script just adds two things that are less useful when you're typing by hand:

  • Arguments: the script can receive values when you run it, like my-app above. Inside the script, the first argument is available as $1, the second as $2, and so on.
  • Decisions: the script can check something — "was a name given? does that folder already exist?" — and react to it.

Full shell programming (loops, functions, and the rest) is a big topic of its own, and this series deliberately stops short of it. One small if is all we need.

3. Writing the Script

Go to your practice folder and open a new file in nano:

cd ~/cli-practice
nano new-project.sh

Type or paste in the following, then save with Ctrl + O, Enter, and exit with Ctrl + X:

#!/bin/bash
# new-project.sh — create a starter folder for a new project.
# Usage: new-project.sh <project-name>

set -e

name="$1"

if [ -z "$name" ]; then
  echo "Usage: new-project.sh <project-name>" >&2
  exit 1
fi

if [ -e "$name" ]; then
  echo "Error: '$name' already exists." >&2
  exit 1
fi

echo "Creating project: $name"

mkdir -p "$name"/{src,tests,docs}

echo "# $name" > "$name/README.md"
echo "Created on $(date +%Y-%m-%d) by $USER." >> "$name/README.md"

printf '%s\n' "node_modules/" ".env" ".DS_Store" > "$name/.gitignore"

touch "$name/src/index.js" "$name/.env"
chmod 600 "$name/.env"

echo "Done! Files created:"
find "$name" | sort

4. Reading It Line by Line

Before running a script — even your own — read it. Here's what each part does, and where you learned it.

#!/bin/bash

The shebang: #! followed by the path of the program that should run this file. When you run ./new-project.sh, the operating system reads this first line and hands the file to /bin/bash. That's why the script works the same even if your terminal uses zsh. You'll also see #!/usr/bin/env bash, which looks bash up on your PATH (chapter 9) instead of using a fixed location.

# new-project.sh — create a starter folder for a new project.

Comments (chapter 2). Everything after # is ignored. A good script says at the top what it does and how to use it.

set -e

"Stop the script as soon as any command fails." Without it, if mkdir failed, the script would carry on and run every other command anyway. set -e uses exit codes (chapter 8): any command that exits with something other than 0 stops the script.

name="$1"

A variable (chapter 9) holding the first argument. When you run new-project.sh my-app, $1 is my-app. Giving it a readable name makes the rest of the script easier to follow.

if [ -z "$name" ]; then
  echo "Usage: new-project.sh <project-name>" >&2
  exit 1
fi

The one decision in this script. [ -z "$name" ] tests whether name is empty (-z means "zero length"). If it is — you forgot the argument — the script prints how to use it and stops. if ... then ... fi marks the start and end of the block (fi is if backwards).

Two details come from earlier chapters. >&2 sends the message to stderr (chapter 6), because it's an error, not normal output. And exit 1 ends the script with a non-zero exit code, so anything that runs this script can tell it failed.

if [ -e "$name" ]; then

The same idea: -e tests whether something with that name already exists. Without this check, running the script twice would quietly add files into a project you'd already started.

mkdir -p "$name"/{src,tests,docs}

mkdir -p and brace expansion (chapter 4). The quotes around "$name" matter: if someone runs new-project.sh "my app", the space is kept as part of one name instead of splitting it into two. Quoting variables is the most important habit in shell scripting.

echo "# $name" > "$name/README.md"
echo "Created on $(date +%Y-%m-%d) by $USER." >> "$name/README.md"

Redirection (chapter 6): > creates the README with a heading, and >> adds a second line. $USER is an environment variable (chapter 9). And $(...) is new: it's command substitution — the shell runs the command inside the brackets and drops its output into the line. Here date +%Y-%m-%d prints today's date, like 2026-10-01.

printf '%s\n' "node_modules/" ".env" ".DS_Store" > "$name/.gitignore"

The printf trick from chapter 6, writing three lines into .gitignore so Git won't track dependencies, secrets, or macOS clutter.

touch "$name/src/index.js" "$name/.env"
chmod 600 "$name/.env"

Empty files (chapter 4), and permissions (chapter 10): .env will hold secrets, so only you can read it.

find "$name" | sort

find (chapter 7) lists everything created, piped into sort (chapter 6) so the order is predictable.

5. Running and Testing It

The file isn't executable yet (chapter 10), so add x first:

chmod +x new-project.sh

Now test it properly — not just the happy path, but the mistakes too.

No argument:

./new-project.sh
echo $?
Usage: new-project.sh <project-name>
1

A real project:

./new-project.sh my-app

You should see the list of files from section 1. Check the results with the tools you know:

cat my-app/README.md
ls -la my-app
# my-app
Created on 2026-10-01 by dara.

In the ls -la output, notice the hidden .env and .gitignore (chapter 3), and that .env shows -rw-------.

The same name again:

./new-project.sh my-app
echo $?
Error: 'my-app' already exists.
1

Your script now handles the three cases that matter. Testing the failure cases, not just the success, is a habit worth taking into every piece of code you write.

6. Why the Script Can't cd for You

It would be nice if the script finished by moving you into the new folder. Try adding cd "$name" as its last line and running it on a new name — then run pwd. You're still in ~/cli-practice.

This isn't a bug. Remember chapter 8: running a script starts a child process — a separate copy of Bash. The cd happens inside that child, which changes its current directory, then exits. Your own shell, the parent, was never touched. A child process can't change its parent's current directory, variables, or anything else (chapter 9).

That's also why cd itself has to be built into the shell (chapter 7). The simple fix is to chain it yourself with && (chapter 8):

./new-project.sh my-api && cd my-api

The cd only runs if the script succeeded. (Remove the cd line from the script if you added it.)

7. Installing It as Your Own Command

Right now you have to type ./new-project.sh, and only from inside ~/cli-practice. Let's make it a real command that works from anywhere. This is exactly how command-line tools are installed — they're executable files in a folder on your PATH.

Make a bin folder in your home folder (bin is short for binaries, the traditional name for program folders) and move the script into it, dropping the .sh so the command looks like any other:

mkdir -p ~/bin
mv new-project.sh ~/bin/new-project

Then add that folder to your PATH, permanently, in your startup file (chapter 9):

nano ~/.zshrc     # or: nano ~/.bashrc

Add this line at the end, then save and exit:

export PATH="$HOME/bin:$PATH"

Load the change and check that the shell can find your command:

source ~/.zshrc   # or: source ~/.bashrc
which new-project
/Users/dara/bin/new-project

Now it works from anywhere:

cd ~/cli-practice/projects
new-project portfolio && cd portfolio

You've written a program, given it permission to run, put it on your PATH, and run it like any other command. Every command you've used in this series — ls, git, node — got onto your computer the same way.

You can also update the usage message inside the script to say new-project instead of new-project.sh, now that that's its name.

8. The Whole Series in One Page

ChapterThe one idea to remember
1. What Is the Command Line?The terminal is the window, the shell is the program that understands you. Read before you press Enter
2. Your First Commandscommand -options arguments, separated by spaces. Use Tab, ↑, Ctrl + C, --help, and man
3. Moving Aroundpwd, ls, cd. Paths are absolute (from /) or relative (from here). ~, ., ..
4. Working with Files and Foldersmkdir, touch, cp, mv, rm. Wildcards are expanded by the shell. rm has no undo
5. Reading and Editing Filescat for short files, less for long ones, tail -f for logs, nano to edit, :q! to escape vim
6. Pipes and Redirection> replaces, >> appends, 2> catches errors, | connects small tools into pipelines
7. Finding Thingsfind searches names, grep -rni searches contents, which and type find commands
8. Processes from the Terminalps, top, kill, &, jobs, fg. Exit code 0 means success. lsof -i :3000 finds who has a port
9. Environment Variables and PATHexport passes variables to child processes. PATH is where commands are found. Open a new terminal
10. Permissions and sudorwx for owner, group, others. Scripts need ./ and +x. Use sudo only when you know why
11. Putting It All TogetherA script is just commands in a file. Quote your variables, test the failures, put it on PATH

9. Where to Go Next

The terminal is no longer a mystery — it's a tool you can use. Here's how it connects to what comes next on the Start Here roadmap:

  • Git & GitHub for Beginners — the next step, and you'll drive it entirely from the terminal: git init, git add, git commit, and that vim window you now know how to escape.
  • How the Web Works and Networking for Developers — what's actually behind that port 3000, and tools like curl that talk to servers from the command line.
  • Docker CLI Tutorial — every Docker command builds on this series: docker ps and docker logs -f are ps and tail -f for containers, and you'll spend time in shells inside them.
  • Hardening a Linux Server — permissions, users, and sudo for real, on a server with no screen at all.

Keep ~/cli-practice around and come back to it whenever you want to try something safely. And the next time a tutorial says "just run this in your terminal", you'll know exactly what you're typing — and what it will do.

Buy Me a Coffee

Found this tutorial helpful? Consider buying me a coffee to support more free content!