Writing Your First Script

4 min read

Core idea

A shell script is the simplest kind of program on Linux: a plain text file containing the same commands you would type at the prompt, read top to bottom by an interpreter. The script is the source code, the script is the binary — there's no compilation step. What turns a text file into something the system will execute as a program is three small ingredients. (1) A shebang as the first line — #!/bin/bash — tells the kernel which interpreter should read the file. (2) An executable permission bitchmod 755 script — tells the kernel this file is allowed to run. (3) A discoverable location — placing the script in a directory listed in $PATH — lets you invoke it by name instead of full path. Skip any one of the three and the script either won't run or will require an awkward incantation every time.

Shotts's argument: The barrier to writing your first useful program on Linux is much lower than people expect. Three lines of bash, three permission and location decisions, and you have a tool that runs with myscript instead of bash myscript.sh — indistinguishable from ls or grep to the user.

Why it matters

Composition beats memorization

Every pipeline you have learned (sort, uniq, cut, sed, find, etc.) becomes a reusable verb the moment you put it in a script. A six-line script that wraps a long pipeline is faster to remember than the pipeline itself and lives forever in ~/bin. The script becomes part of your personal toolkit, on the same footing as the system tools you call every day.

Scripts are where productivity compounds

Anything you do twice on the command line is a candidate for scripting. The third time you reach for the same find … -exec invocation, it should be a script. The compounding is huge: every script saves you the recall cost of its contents, and small scripts call each other, building up a personal library.

Scripts demystify "real" programs

There's nothing magical about /usr/bin/ls. From the kernel's perspective, it's a file with the executable bit set and a known interpreter (in ls's case, the ELF binary loader). A bash script with #!/bin/bash is the same shape — different interpreter, same lifecycle. Writing scripts is how you internalize that all programs on Unix are files plus metadata, not a separate, mysterious category.

Key takeaways

Mental model

What happens when you type myscript

The decision tree the shell walks every time you press Enter is short, but it explains every "command not found" you will ever see. The shell scans $PATH, finds the file, checks the executable bit, reads the shebang, and launches the named interpreter with the script as its argument.

What happens when you type myscript

Three knobs make a script "official"

Three knobs make a script "official"

Practical application

Three habits separate scripts you regret from scripts you reuse. First, always include the shebang, even for two-line scripts — it makes the language explicit and prevents sh from being silently substituted on systems where /bin/sh is dash instead of bash. Second, use long option names in scripts even if you use short ones interactively: find . --type f --name '*.log' is far more readable a month later than find . -type f -name '*.log'. Third, include comments at decision points — not narration of every line, but a # why whenever a future reader might wonder why a flag was chosen.

For organization, the standard layout is ~/bin for personal scripts (most Debian-family distributions add it to $PATH automatically when it exists), /usr/local/bin for scripts you share with all users on the machine, and /usr/local/sbin for admin scripts that only root should run. Save scripts without a .sh extension when they live in a bin directory — like every other command on $PATH, they should be invoked by name, not by file type.

Example

Suppose you find yourself running the same disk-usage snapshot every Monday morning to send to your team. The command line is long enough that you mistype it half the time. Promote it to a script.

# 1. Create ~/bin if it doesn't exist; most distributions auto-add it to PATH
mkdir -p ~/bin

# 2. Write the script
cat > ~/bin/disksnap << 'EOF'
#!/bin/bash
# disksnap — print top-10 disk consumers under /home with a dated header
# Usage: disksnap [directory]   (default: /home)

target="${1:-/home}"

echo "Disk snapshot — ${target} — $(date +%F)"
echo "----------------------------------------"
du -sh "${target}"/* 2>/dev/null \
  | sort -hr \
  | head -10
EOF

# 3. Make it executable
chmod 755 ~/bin/disksnap

# 4. Confirm PATH picks it up (if not, add export PATH="$HOME/bin:$PATH" to ~/.bashrc)
which disksnap

# 5. Run it without a leading ./
disksnap                 # uses /home
disksnap /srv            # overrides the default

What this tiny script teaches:

  • The shebang #!/bin/bash makes the language explicit. Even if the user's interactive shell is zsh, the script runs under bash.
  • Comments at the top form a built-in help page. A teammate reading it learns the purpose and usage without external documentation.
  • ${1:-/home} shows a useful bash idiom: "first argument, or /home if not given." That single line turns a fixed script into a parameterized one.
  • The script reuses three text-processing tools (du, sort, head) — exactly the pipeline composition the previous topics built up to.
  • chmod 755 grants execute permission to everyone on the system; for a private script use 700 so other users on a shared machine cannot see or run it.

Now disksnap | lpr -P PDF prints the same report to a PDF, disksnap | mail -s "Monday disk" team@example.com emails it, and crontab -e adds a line that runs it every Monday at 8am. The script has become a building block — exactly like the system commands you've spent the rest of the book learning.

Continue exploring

Tags