Computers & Electronics
173,134 views
25 min · 3 min read
7 steps
Advanced

How to create reusable shell scripts to automate frequent command‑line tasks on macOS/Linux

Creating reusable shell scripts saves time and reduces repetitive typing for common tasks like backups, builds, or deployments. This guide walks you through practical steps to write, test, and maintain small scripts that run on macOS and Linux terminals. Follow these steps to build reliable scripts you can reuse for weeks or years.

Verified by pleasexplain editors
  1. Step 1: Choose the right shell

    Decide between bash, zsh, or sh based on your environment and portability needs. Use bash for GNU utilities on Linux, zsh for recent macOS defaults, or /bin/sh for widest compatibility; explicitly declare the shebang like #!/usr/bin/env bash to ensure consistent execution.

    [Illustration: Terminal window showing shebang line and shell version output]

  2. Step 2: Create a clear file layout

    Put scripts in a dedicated folder such as ~/bin or ~/.local/bin and add that folder to PATH for quick access. Give files short hyphenated names and set executable permissions with chmod 755 so you can run them as commands from any directory.

    [Illustration: Home folder tree with a bin directory and script files listed]

  3. Step 3: Start with a template

    Begin each script with a 6–12 line template: shebang, brief comment describing purpose, set -euo pipefail (or set -e for sh), and a usage/help function. This reduces bugs and provides immediate help when you run the script with --help or -h.

    [Illustration: Text editor showing a short reusable script template including usage function]

  4. Step 4: Add argument parsing

    Accept positional arguments and flags; use getopts for simple options (e.g., -f filename -v) and shift for remaining arguments. Validate inputs early and show a helpful error message and exit code 2 when required values are missing to avoid unintended operations.

    [Illustration: Command-line usage example with flags and positional arguments displayed]

  5. Step 5: Write idempotent actions

    Make scripts safe to run multiple times by checking state before changing it: use mkdir -p, cp -u or rsync --dry-run and --archive, and test files exist before deleting. Idempotency reduces accidental data loss and makes automation reliable for cron or systemd timers.

    [Illustration: Shell output showing rsync dry-run followed by real run with progress]

  6. Step 6: Implement logging and dry-run

    Print clear logs with timestamps and levels (INFO, WARN, ERROR) and support a --dry-run flag that shows planned commands without executing them. This gives confidence when running scripts on important data and helps troubleshooting with reproducible logs.

    [Illustration: Terminal showing colored log lines with timestamps and a dry-run summary]

  7. Step 7: Test, version, and document

    Write small unit or smoke tests using bats or simple shell assertions, store scripts in a git repository with tags, and maintain a README with examples and required dependencies. Regular testing and version history let you safely update scripts and roll back in 1–2 minutes if needed.

    [Illustration: Git commit history and a README open beside test output]


  • Keep scripts under 200 lines; factor long logic into helper scripts to improve readability.
  • Prefer explicit full paths for external commands in cron or systemd contexts, e.g., /usr/bin/rsync and /usr/bin/env.
  • Use set -u to catch undefined variables and set -o pipefail to detect pipeline failures in 90% of common errors.
  • Include a --help output limited to 6–10 lines showing required flags, examples, and exit codes.
  • Use rsync -av --delete for backups (test with --dry-run first) and limit network operations to off-peak hours like 02:00–04:00.
  • Schedule frequent scripts with cron or launchd: keep jobs under 5 minutes or use locking to prevent overlapping runs.

  • Never hard-code passwords or API keys in scripts; use environment variables, keychain, or secure files with 600 permissions.
  • Avoid destructive defaults: do not run rm -rf without explicit confirmation or a --force flag; require users to pass --yes for irreversible actions.
  • Be cautious when assuming GNU vs BSD tool behavior across macOS and Linux; test commands like sed, date, and stat on each target system.
  • Do not run scripts from untrusted sources; review code before execution and run initial tests in a safe directory to prevent accidental system changes.

Was this guide helpful?