Skip to main content

Bash Scripting Fundamentals - Writing and Executing Scripts

β€’ 5 min read
bash shell scripting bash script script execution script structure

Quick Answer: Create a Bash Script in Three Steps

To create a Bash script: (1) Start with #!/bin/bash as the first line, (2) Add your commands, (3) Make it executable with chmod +x script.sh. Then run it with ./script.sh.

Quick Comparison: Script Execution Methods

MethodSyntaxProsCons
Direct execution./script.shFast, explicit intentNeeds execute permission
Bash interpreterbash script.shWorks without +xSlower, less clear
Source/dot. script.shRuns in current shellAffects current environment
Scheduled (cron)Via crontabAutomated, persistentNeeds proper setup

Bottom line: Use chmod +x script.sh and ./script.sh for normal scripts. Use bash script.sh for debugging or when you can’t modify permissions.


What is a Bash Script?

A Bash script is simply a text file containing Bash commands that the system runs as a sequence. Instead of typing commands interactively one at a time, scripts let you save a series of commands and run them all at once, repeatedly, on a schedule, or pass them to others. Scripts turn ad-hoc commands into reusable, automated workflows.

Script vs Interactive Shell

AspectInteractive ShellScript
ExecutionCommand by commandAll commands at once
StorageMemory (lost on exit)File (persistent)
ReusabilityOne session onlyUnlimited
AutomationManualAutomatic
Error handlingInteractive recoveryPredefined behavior

Common Script Uses

  • System administration: Backups, user management, updates
  • Automation: Scheduled tasks via cron, deployment pipelines
  • DevOps: Infrastructure provisioning, CI/CD integration
  • Development: Build tools, testing scripts, environment setup

Script Structure and Anatomy

Minimal Script

#!/bin/bash

echo "Hello, Script!"

This three-line script demonstrates:

  1. Shebang (line 1): Tells the system to use Bash interpreter
  2. Command (line 3): The actual work
  3. Blank line: Organization (optional but recommended)

Typical Script Structure

#!/bin/bash

# ============================================================
# Script: backup.sh
# Purpose: Backup user directories
# Author: Admin
# Date: 2026-02-21
# ============================================================

# Configuration
BACKUP_DIR="/home/backups"
SOURCE_DIR="/home/user"

# Functions
create_backup() {
  echo "Starting backup..."
  tar -czf "$BACKUP_DIR/backup.tar.gz" "$SOURCE_DIR"
}

# Main
main() {
  if [ ! -d "$BACKUP_DIR" ]; then
    mkdir -p "$BACKUP_DIR"
  fi

  create_backup
  echo "Backup complete"
}

# Run main function
main "$@"

The Shebang (#!)

The shebang (also called hashbang) tells the system which interpreter to use for the script.

Shebang Syntax

#!/bin/bash

Common Shebangs

ShebangInterpreterUse
#!/bin/bashBashMost common, full Bash features
#!/bin/shPOSIX shellMaximum portability
#!/usr/bin/env bashBash (via env)Cross-platform compatibility
#!/bin/zshZshIf you prefer Zsh

Why Use /usr/bin/env bash?

#!/usr/bin/env bash

This is more portable because:

  • Locates bash in your system’s PATH
  • Works even if bash is installed in non-standard location
  • Better for cross-platform scripts

Location Check

Find where Bash is installed on your system:

which bash
# Output: /bin/bash

type -a bash
# Output: bash is /bin/bash

File Permissions

Scripts need execute permission to run directly. Without it, you must call bash script.sh explicitly.

Check Permissions

ls -l script.sh
# Output: -rw-r--r-- 1 user group 245 Feb 21 10:00 script.sh

The first part -rw-r--r-- shows permissions:

  • First character - = regular file (not directory)
  • Next three characters rw- = owner can read/write (no execute)
  • Next three characters r-- = group can read only
  • Last three characters r-- = others can read only

Add Execute Permission

# Add execute for owner only
chmod u+x script.sh

# Add execute for owner, group, others
chmod +x script.sh

# Verify
ls -l script.sh
# Output: -rwxr-xr-x 1 user group 245 Feb 21 10:00 script.sh

The x now appears for the owner: rwx


Executing Scripts

There are several ways to run a Bash script.

Method 1: Direct Execution (Requires Shebang + Execute Permission)

./script.sh

The shebang tells the system to use Bash.

Method 2: Explicit Bash Interpreter

bash script.sh

This works even without a shebang or execute permission. The bash command is provided explicitly.

Method 3: Source the Script

source script.sh
# or
. script.sh

Runs the script in the current shell environment (not a subshell). Variables persist after execution.

Method 4: Using bash with -c Flag

bash -c 'echo "Hello from command string"'

Executes a bash command directly without a file.

Comparison

MethodShebangExecuteSubshellUse Case
./script.shRequiredRequiredYesStandard execution
bash script.shNot neededNot neededYesPortable, debugging
source script.shNot neededNot neededNoLoad functions/variables
bash -c '...'N/AN/AYesQuick commands

Script Organization

Directory Layout

~/scripts/
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ logging.sh        # Logging functions
β”‚   └── backup.sh         # Backup functions
β”œβ”€β”€ bin/
β”‚   β”œβ”€β”€ daily-backup.sh   # Main backup script
β”‚   └── cleanup.sh        # Cleanup script
└── config/
    └── backup.conf       # Configuration file

Sourcing Utilities

In main scripts, load utility functions:

#!/bin/bash

# Load utility functions
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/logging.sh"
source "$SCRIPT_DIR/lib/backup.sh"

# Use functions
log "Starting backup"
create_backup
log "Backup complete"

Comments and Documentation

Single-Line Comments

# This is a comment
echo "Hello"  # Inline comment

Multi-Line Comments

# Comment block
# explaining what
# this section does

# Or using here-document
: << 'EOF'
This is a multi-line comment.
It doesn't execute.
It can span multiple lines.
EOF

Header Documentation

#!/bin/bash

# ============================================================
# Script: backup.sh
# Description: Automated daily backup of user directories
# Author: John Admin
# Created: 2026-02-21
# Modified: 2026-02-21
#
# Dependencies: tar, gzip, rsync
# Usage: ./backup.sh [source] [destination]
#
# Examples:
#   ./backup.sh /home/user /backup
#   ./backup.sh  # Uses defaults from config
# ============================================================

Debugging Scripts

Enable Debug Output

# Method 1: Run with -x flag
bash -x script.sh

# Method 2: Enable inside script
set -x
echo "This will be traced"
set +x
echo "This won't be traced"

Output with -x:

+ echo 'Starting script'
Starting script
+ count=0
+ count=1
+ echo 'Count: 1'
Count: 1

Enable All Debugging Options

#!/bin/bash

# Exit on error
set -e

# Error on undefined variable
set -u

# Trace execution
set -x

# Pipe failures cause script failure
set -o pipefail

# Equivalent shorthand
set -euxo pipefail

Add Debug Output

#!/bin/bash

DEBUG=true

debug() {
  if [ "$DEBUG" = true ]; then
    echo "[DEBUG] $*" >&2
  fi
}

debug "Script started"
count=0
debug "Count initialized to: $count"

Run with debug:

./script.sh  # No debug output
DEBUG=true ./script.sh  # Shows debug messages

Best Practices

1. Always Quote Variables

# Good
name="$1"
echo "Hello, $name"

# Bad (breaks with spaces)
echo Hello, $1

2. Use Functions for Organization

# Good
log() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
}

backup() {
  tar -czf "$BACKUP_FILE" "$SOURCE_DIR"
}

main() {
  log "Starting backup"
  backup
  log "Backup complete"
}

main "$@"

3. Validate Input

#!/bin/bash

if [ $# -lt 2 ]; then
  echo "Usage: $0 <source> <destination>"
  exit 1
fi

source="$1"
destination="$2"

4. Use Set Options

#!/bin/bash
set -euo pipefail

# exit on error (e)
# error on undefined variables (u)
# pipe failures fail the script (o pipefail)

5. Handle Errors

#!/bin/bash

error() {
  echo "ERROR: $*" >&2
  exit 1
}

[ -f "$file" ] || error "File not found: $file"

Frequently Asked Questions

Q: What’s the difference between #!/bin/bash and #!/usr/bin/env bash?

A: #!/bin/bash assumes Bash is at /bin/bash. #!/usr/bin/env bash finds Bash in your PATH, making it more portable across systems where Bash might be in different locations.

Q: Can I run a script without execute permission?

A: Yes, if you call the Bash interpreter explicitly: bash script.sh. But for direct execution (./script.sh), you need execute permission and a shebang.

Q: What’s the difference between source and ./script.sh?

A: source runs the script in the current shell, so variables persist. ./script.sh runs in a subshell, so variables are isolated. Use source for loading functions, use ./script.sh for independent execution.

Q: How do I debug a script?

A: Use bash -x script.sh to trace execution, or add set -x inside the script. You can also add echo statements to print variable values.

Q: Should I use #!/bin/bash or #!/bin/sh?

A: Use #!/bin/bash for scripts you control. Use #!/bin/sh only if you need maximum portability to systems with minimal shells. Modern systems have Bash available.


Next Steps

Explore related topics:

Now that you understand script fundamentals, you’re ready to write production-quality Bash scripts!