📜 Part of Pranav Kulkarni's technical portfolio Visit pranavkulkarni.org →
Lesson 1 · Shell Scripting

Bash Scripting Fundamentals

Learn to write powerful automation scripts with bash.

Your First Script

Every bash script starts with a shebang line that tells the system which interpreter to use:

#!/bin/bash
# My first script

echo "Hello, World!"

Save this as hello.sh, make it executable, and run it:

$ chmod +x hello.sh
$ ./hello.sh
Hello, World!

Variables

#!/bin/bash

# Assign variables (no spaces around =)
name="Pranav"
age=25

# Use variables with $
echo "Name: $name"
echo "Age: ${age} years" # Curly braces for clarity

# Command substitution
current_date=$(date +%Y-%m-%d)
echo "Today is: $current_date"

Conditionals

#!/bin/bash

if [ -f "/etc/passwd" ]; then
echo "File exists"
elif [ -d "/etc" ]; then
echo "Directory exists"
else
echo "Not found"
fi

Common Test Operators

Operator Description
-f fileFile exists and is regular file
-d dirDirectory exists
-z stringString is empty
-n stringString is not empty
$a -eq $bNumbers are equal
$a -gt $ba is greater than b

Loops

# For loop
for file in *.txt; do
echo "Processing: $file"
done

# While loop
count=1
while [ $count -le 5 ]; do
echo "Count: $count"
((count++))
done

Functions

#!/bin/bash

greet() {
local name="$1" # First argument
echo "Hello, $name!"
}

greet "World"
greet "Pranav"

Quoting (the #1 source of bugs)

Bash splits words on spaces and expands globs (*) unless you quote variables. If you remember one rule: quote your variables.

# Bad: breaks on spaces
file="My File.txt"
rm $file

# Good: always quote
file="My File.txt"
rm -- "$file"

Exit Codes and Error Handling

In Linux, “success” is exit code 0 and “failure” is anything non-zero. You can check the last exit code with $?.

curl -fsSL https://example.com
echo "Exit code: $?"

# Use || to handle failures
command || echo "Command failed"

Arguments and Flags (getopts)

Scripts become useful when you can pass inputs. Use positional parameters ($1, $2, …) and getopts for flags.

#!/bin/bash
set -e

while getopts ":f:v" opt; do
case "$opt" in
f) file="$OPTARG" ;;
v) verbose=1 ;;
*) echo "Usage: $0 -f file [-v]" ; exit 2 ;;
esac
done
shift $((OPTIND-1))

echo "File: $file"

A Safer Script Template

For production scripts, use a safer default mode and add a cleanup trap.

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

cleanup() {
rm -f "$tmp_file"
}
trap cleanup EXIT

✅ Practice (20 minutes)

  • Write a script that takes -f (file) and prints the number of lines via wc -l.
  • Break it by passing a filename with spaces, then fix it using proper quoting.
  • Add set -euo pipefail and intentionally trigger an error to see how it fails fast.

💡 Best Practice

For production scripts, prefer set -euo pipefail (fail fast, catch undefined vars, and detect pipeline failures) and always quote variables. This prevents cascading and hard-to-debug errors.