Shell Scripting Bash Linux Basics May 2026

Shell Scripting Introduction to Shell Scripting

Understand what a shell is, how different shells compare, how to write your first bash script, make it executable, and run it — the foundation every script author needs.

A shell is a command-line interpreter that sits between you and the operating system kernel. When you type a command, the shell parses it, talks to the kernel, and returns output. A shell script is simply a text file containing a sequence of those commands — saved once, run many times.

Linux systems ship with several shells. Each has its own syntax quirks and feature set. bash is the most common for scripting because it is installed everywhere and its behaviour is well-documented.

  • sh — POSIX shell. Minimal, portable, available on every Unix. Limited features.
  • bash — Bourne Again Shell. Superset of sh. Arrays, [[ ]], process substitution, here-docs. Default on most Linux distros.
  • zsh — Extended bash-like shell. Popular for interactive use (macOS default since Catalina). More powerful glob patterns.
  • fish — Friendly interactive shell. Not POSIX-compatible — avoid for scripting.
  • dash — Lightweight POSIX shell. Ubuntu uses it as /bin/sh. Much faster than bash for simple scripts.
BASH
# Find out which shell is running right now
echo "$SHELL"          # Your login shell path
echo "$BASH_VERSION"   # Bash version (empty if not bash)
echo "$ZSH_VERSION"    # Zsh version (empty if not zsh)

# List all installed shells
cat /etc/shells

# Check current process shell
ps -p $$ -o comm=
bash — shell check
vriddh@prod-01:~$echo "$SHELL"
/bin/bash
vriddh@prod-01:~$echo "$BASH_VERSION"
5.2.15(1)-release
vriddh@prod-01:~$cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/zsh
/usr/bin/zsh
/bin/dash

The very first line of any script is the shebang (#!). It tells the kernel which interpreter to invoke when the script is executed. Without it, the OS uses your current shell — which is unpredictable on different machines.

BASH
#!/bin/bash          # Hardcoded path — fast, reliable on Linux
#!/usr/bin/env bash  # Searches PATH — more portable (macOS, NixOS)
#!/bin/sh            # POSIX only — maximum portability, fewer features
#!/usr/bin/env python3 # Works for Python scripts too
💡 Best practice — Use #!/usr/bin/env bash for portability across Linux and macOS. Use #!/bin/bash when you need a guaranteed specific path (e.g., inside Docker containers or system scripts).

Every script follows the same structure: shebang, then optional header comment, then commands. Keep it readable — future-you will thank present-you.

BASH
#!/usr/bin/env bash
# hello.sh — My first shell script
# Author : vriddh
# Created: 2026-05-01
# Usage  : ./hello.sh [name]

set -euo pipefail   # Exit on error, unset var, pipe failure

NAME="${1:-World}"  # Use first arg, default to "World"

echo "Hello, ${NAME}!"
echo "Today is: $(date '+%A, %d %B %Y')"
echo "Running as: $(whoami) on $(hostname)"

A script file is just text until you give it execute permission. Use chmod to set the permission, then run it directly or explicitly via bash.

BASH
# Create the file
nano hello.sh           # or vim, gedit, VS Code etc.

# Give execute permission
chmod +x hello.sh       # Add execute for owner (most common)
chmod 755 hello.sh      # rwxr-xr-x — owner full, others read+execute
chmod 700 hello.sh      # rwx------ — owner only (for sensitive scripts)

# Run it — three ways
./hello.sh              # Execute directly (needs shebang + execute bit)
./hello.sh Vriddh       # Pass an argument
bash hello.sh          # Explicit interpreter (ignores shebang, no execute needed)
bash -x hello.sh       # Debug mode — prints each command before running
bash -n hello.sh       # Syntax check only — does not execute
Key Success Error Info / dim
bash — hello.sh
vriddh@prod-01:~/scripts$chmod +x hello.sh
vriddh@prod-01:~/scripts$./hello.sh
Hello, World!
Today is: Friday, 01 May 2026
Running as: vriddh on prod-01
vriddh@prod-01:~/scripts$./hello.sh Vriddh
Hello, Vriddh!
Today is: Friday, 01 May 2026
Running as: vriddh on prod-01
vriddh@prod-01:~/scripts$bash -n hello.sh
(no output — syntax OK)
vriddh@prod-01:~/scripts$./missing.sh
bash: ./missing.sh: No such file or directory

Well-structured scripts are easier to debug, maintain, and hand off to teammates. Follow this template for every script you write.

BASH
#!/usr/bin/env bash
# =============================================================
# script_name.sh — One-line description
# Author  : Your Name
# Created : YYYY-MM-DD
# Updated : YYYY-MM-DD
# Usage   : ./script_name.sh [options]
# Depends : curl, jq, mysql-client
# =============================================================

set -euo pipefail
IFS=$'\n\t'           # Safer word splitting

# ── CONSTANTS ─────────────────────────────────────────────
readonly SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
readonly LOG_FILE="/var/log/$(basename "$0" .sh).log"
readonly TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

# ── FUNCTIONS ─────────────────────────────────────────────
log()   { echo "[${TIMESTAMP}] $*" | tee -a "${LOG_FILE}"; }
err()   { echo "[ERROR] $*" >&2; exit 1; }
usage() { echo "Usage: $(basename "$0") [--option value]"; exit 0; }

# ── ARGUMENT PARSING ──────────────────────────────────────
[[ "${1:-}" == "--help" ]] && usage

# ── MAIN ──────────────────────────────────────────────────
main() {
  log "Script started"
  # your logic here
  log "Script completed"
}

main "$@"
✔ Key habits — Always use set -euo pipefail, always quote variables, always have a usage() function, and always log to a file. These four habits prevent 90% of production script incidents.
⚠ Common mistake — Running scripts with source ./script.sh or . ./script.sh executes them in the current shell — any exit call will close your terminal. Only use source when you intentionally want to modify the current shell environment (e.g., loading environment variables).
BASH
# Method 1 — Direct execution (needs chmod +x and shebang)
./script.sh

# Method 2 — Explicit interpreter (no execute bit needed)
bash script.sh

# Method 3 — Source (runs in CURRENT shell — exports vars/functions)
source script.sh
. script.sh           # Equivalent shorthand

# Method 4 — Subshell (changes don't affect parent)
( ./script.sh )

# Method 5 — With full path (good for cron)
/home/vriddh/scripts/script.sh

# Check exit code of last command
./script.sh
echo "Exit code: $?"   # 0 = success, non-zero = failure
💡 Which method to use? — Use ./script.sh for daily use. Use bash script.sh when troubleshooting. Use source only for environment setup files like .bashrc or config.sh. Never use source on scripts that call exit.