Linux • Bash • Tutorial

Bash Shell Scripting Guide

This is a clean, blog-ready learning guide inspired by a Bash shell scripting tutorial page. It keeps the same full learning journey and topic coverage in an original rewritten format: what Bash is, how to write scripts, variables, arguments, input, arrays, conditionals, loops, functions, debugging, and practical scripting habits.

Readable white theme • Single-file HTML • Good for blog embedding or direct publishing

1. Introduction to Bash

Bash, short for Bourne Again Shell, is both a command interpreter and a scripting language. It was created as a free GNU replacement for the original Bourne shell and is commonly used on Linux, Unix-like systems, macOS, and Windows environments that provide a Unix-style shell layer.

What it isA shell plus a scripting language for running commands and automating tasks.
Why it mattersIt is ideal for file work, text processing, system administration, and repeatable automation.
Where it runsLinux, Unix, macOS, and Windows through compatible tools such as WSL.

What is a shell?

A shell is the layer between you and the operating system. You type instructions into the shell, and it interprets them, runs programs, and returns results. In practice, it is both an interactive command environment and a programmable automation tool.

Common uses of Bash

2. Getting Started: Your First Script

The shebang line

A Bash script should begin with a shebang so the system knows which interpreter should execute it.

#!/bin/bash

The #! marker introduces the interpreter path. Using a shebang makes script behavior more predictable across environments.

Create, edit, make executable, and run

touch hello.sh
chmod +x hello.sh
nano hello.sh
./hello.sh
What each step does
  • touch creates an empty file if it does not exist.
  • chmod +x adds execute permission.
  • An editor such as nano, vim, or VS Code is used to add the script content.
  • ./hello.sh runs the file from the current directory.

First example

#!/bin/bash

# A simple first script
echo "Hello World!"

Comments begin with # and are ignored by the interpreter. The echo command prints text to the terminal.

Running normal system commands inside a script

#!/bin/bash

echo "Hello World!"
whoami
id
ls -la
pwd
uname -a

Bash scripts can run normal terminal commands just like you would type them manually.

Comments

# A single-line comment
echo "This executes"  # Inline comment

: '
A common multi-line comment workaround.
Useful for temporarily disabling a block.
'
Quick check

Q: What symbol comments out a line? A: #

Q: What does echo "BishBashBosh" print? A: BishBashBosh

3. Variables

Variables act as named storage for values. In Bash, they are often used for strings, numbers, command results, file paths, and reusable settings across a script.

Naming rules

# Valid examples
name="John"
user_name="john_doe"
USER1="admin"
_temp="tmp"

# Invalid examples
# 1user="john"
# user-name="john"
# user name="john"

Assignment rule: no spaces around =

# Correct
name="Enes"
age=24
city="Istanbul"

# Incorrect
# name = "Enes"
# age= 24
# city ="Istanbul"

Using variables

name="Enes"
echo $name
echo "My name is $name"

Interpolation inside strings

name="Enes"
age=24
echo "$name is $age years old"

Use curly braces for clarity

WORD="script"
echo "${WORD}ing is fun"

Braces are helpful when text appears directly after the variable name or when an expression is more complex.

Command substitution

user=$(whoami)
current_dir=$(pwd)
today=$(date)
file_count=$(ls -1 | wc -l)

echo "Hello $user"
echo "Current directory: $current_dir"
echo "Today is: $today"
echo "Files here: $file_count"
Main variable ideas
  • You can reassign variables later in the script.
  • Variables are frequently used in conditions, loops, and functions.
  • Quoting variables usually prevents unwanted word splitting.

Q: What will echo "$name is $age years old" output if name="Jammy" and age="21"? A: Jammy is 21 years old

4. Parameters and Arguments

Script arguments are values passed when a script runs. They let one script behave differently depending on the input you provide.

./greet.sh John

Inside the script, the first argument is available as $1, the second as $2, and so on.

Special parameter variables

$0The script name itself.
$1 to $9Individual positional arguments.
$#Total number of arguments.
$@All arguments, individually preserved when quoted.
$*All arguments as one combined string.
$?Exit status of the last command.

Simple example

#!/bin/bash

echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Argument count: $#"
echo "All arguments: $@"

Validate required arguments

#!/bin/bash

if [[ $# -eq 0 ]]; then
  echo "Error: No name provided"
  echo "Usage: $0 <name>"
  exit 1
fi

name=$1
echo "Hello, $name!"

This style prevents a script from running with missing input and provides a clear usage message.

Q: How do you get the number of supplied arguments? A: $#

Q: How do you get the script filename? A: $0

Q: What does echo $1 $3 print if the script was run with ./script.sh hello hola aloha? A: hello aloha

5. User Input

Bash scripts can pause and collect interactive input from the user. This is commonly done with the read command.

Basic input

#!/bin/bash

read name
echo "Hello, $name"

Prompt while reading

read -p "Enter your name: " name
echo "Welcome, $name"

Read silently for passwords

read -s -p "Enter password: " password
echo
# Continue processing

Read multiple values

read -p "Enter first and last name: " first last
echo "First: $first"
echo "Last: $last"

Validation idea

Input should be validated before it is trusted. Examples include checking whether a response is empty, verifying file names, and making sure numeric values really are numbers.

read -p "Enter age: " age

if [[ ! $age =~ ^[0-9]+$ ]]; then
  echo "Please enter a valid number"
  exit 1
fi

Q: If a script asks for input, how do you store it in a variable named test? A: read test

6. Arrays

Arrays allow one variable name to hold many values. In standard indexed arrays, each item has a numeric position starting at 0.

transport=("car" "train" "bike" "bus")

Accessing items

echo "First item: ${transport[0]}"
echo "Second item: ${transport[1]}"
echo "All items: ${transport[@]}"
echo "Number of items: ${#transport[@]}"

Adding and changing values

transport+=("tram")
transport[2]="bicycle"
unset transport[1]

Loop through an array

for item in "${transport[@]}"; do
  echo "$item"
done

Slicing and copying

numbers=(1 2 3 4 5 6 7 8 9 10)
echo "${numbers[@]:2:4}"   # 3 4 5 6
echo "${numbers[@]:5}"     # 6 7 8 9 10

copy=("${numbers[@]}")

Combining arrays

fruits=("apple" "banana")
vegetables=("carrot" "broccoli")
food=("${fruits[@]}" "${vegetables[@]}")
echo "${food[@]}"

Associative arrays

Bash 4+ supports associative arrays, where values are stored under string keys instead of numeric indexes.

declare -A person
person[name]="John"
person[age]=30
person[city]="New York"

echo "${person[name]}"
echo "Keys: ${!person[@]}"
echo "Values: ${person[@]}"

7. Conditionals

Conditionals let your script decide what to do based on whether a test succeeds or fails.

Basic forms

if [[ condition ]]; then
  # true branch
fi

if [[ condition ]]; then
  # true branch
else
  # false branch
fi

if [[ condition1 ]]; then
  # branch 1
elif [[ condition2 ]]; then
  # branch 2
else
  # fallback
fi

String comparisons

if [[ "$a" == "$b" ]]; then
  echo "Strings are equal"
fi

if [[ -z "$name" ]]; then
  echo "Empty string"
fi

Numeric comparisons

if [[ $age -gt 17 ]]; then
  echo "Adult"
fi
Common numeric operators
  • -eq equal to
  • -ne not equal to
  • -gt greater than
  • -ge greater than or equal to
  • -lt less than
  • -le less than or equal to

File tests

if [[ -e "$file" ]]; then echo "Exists"; fi
if [[ -f "$file" ]]; then echo "Regular file"; fi
if [[ -d "$dir" ]]; then echo "Directory"; fi
if [[ -r "$file" ]]; then echo "Readable"; fi
if [[ -w "$file" ]]; then echo "Writable"; fi
if [[ -x "$file" ]]; then echo "Executable"; fi

Exit codes and command success

cp "$source" "$dest"
if [[ $? -eq 0 ]]; then
  echo "Copy successful"
else
  echo "Copy failed"
fi

Case statements

read -p "Enter a command (start/stop/restart): " cmd

case $cmd in
  start)
    echo "Starting service..."
    ;;
  stop)
    echo "Stopping service..."
    ;;
  restart)
    echo "Restarting service..."
    ;;
  *)
    echo "Unknown command"
    ;;
esac

Use case when you are checking many possible values of the same variable. It is often cleaner than a long if/elif chain.

8. Loops

Loops repeat an action. Bash commonly uses for, while, and until loops.

For loops

for name in Alice Bob Charlie; do
  echo "Hello, $name"
done

for i in {1..5}; do
  echo "Number: $i"
done

Loop through an array

items=("apple" "banana" "orange")
for item in "${items[@]}"; do
  echo "$item"
done

Nested loops

for i in {1..3}; do
  for j in {1..3}; do
    echo "$i x $j = $((i * j))"
  done
done

While loops

counter=1
while [[ $counter -le 5 ]]; do
  echo "Count: $counter"
  ((counter++))
done

Reading a file line by line

while read line; do
  echo "Line: $line"
done < input.txt

Infinite loops

while true; do
  echo "Press Ctrl+C to stop"
  sleep 1
done

Until loops

counter=1
until [[ $counter -gt 5 ]]; do
  echo "Count: $counter"
  ((counter++))
done

A while loop keeps going while the condition is true. An until loop keeps going until the condition becomes true.

9. Functions

Functions package reusable logic into named blocks. They improve organization, reduce repetition, and make scripts easier to maintain.

Basic function syntax

function greet {
  echo "Hello from a function"
}

greet

Functions with parameters

function greet_user {
  local name=$1
  echo "Hello, $name!"
}

greet_user "John"
greet_user "Alice"

Returning status codes

function is_even {
  local number=$1
  if (( number % 2 == 0 )); then
    return 0
  else
    return 1
  fi
}

if is_even 10; then
  echo "Even"
fi

Practical example: file check

function check_file {
  local path=$1
  if [[ -e "$path" ]]; then
    echo "Exists"
  else
    echo "Not found"
  fi
}

Practical example: calculator helpers

function add_numbers {
  echo $(($1 + $2))
}

function divide_numbers {
  if [[ $# -ne 2 ]]; then
    echo "Need exactly 2 arguments"
    return 1
  fi
  if [[ $2 -eq 0 ]]; then
    echo "Division by zero is not allowed"
    return 1
  fi
  echo $(($1 / $2))
}

Practical example: backup function

function backup_file {
  local source=$1

  if [[ ! -f "$source" ]]; then
    echo "Source file not found"
    return 1
  fi

  local timestamp=$(date +%Y%m%d_%H%M%S)
  local backup="${source}.${timestamp}.backup"

  cp "$source" "$backup"

  if [[ $? -eq 0 ]]; then
    echo "Backup created: $backup"
    return 0
  else
    echo "Backup failed"
    return 1
  fi
}

Practical example: string helpers

function to_uppercase {
  echo "$1" | tr '[:lower:]' '[:upper:]'
}

function to_lowercase {
  echo "$1" | tr '[:upper:]' '[:lower:]'
}

function string_length {
  echo ${#1}
}

Practical example: menu-driven script

function show_date { date; }
function show_users { who; }
function show_disk { df -h; }
function show_menu {
  echo "1. Show date"
  echo "2. Show users"
  echo "3. Show disk usage"
  echo "4. Exit"
}

while true; do
  show_menu
  read -p "Enter choice: " choice
  case $choice in
    1) show_date ;;
    2) show_users ;;
    3) show_disk ;;
    4) echo "Goodbye!"; exit 0 ;;
    *) echo "Invalid option" ;;
  esac
  read -p "Press Enter to continue..."
done

Function habits that help

10. Best Practices

Organize scripts clearly

#!/bin/bash

# Script metadata and purpose
# Global variables
# Functions
# Main logic

main() {
  echo "Script started"
  # Do work here
  echo "Script completed"
}

main "$@"

Use stronger error handling

set -e
set -u
set -o pipefail
trap 'echo "Error on line $LINENO"' ERR

These options help a script fail fast and report problems more clearly.

Validate inputs

if [[ $# -eq 0 ]]; then
  echo "Usage: $0 <filename>"
  exit 1
fi

filename=$1
if [[ ! -f "$filename" ]]; then
  echo "Error: File does not exist"
  exit 1
fi

Use meaningful names

# Better
user_count=10
backup_directory="/backups"
log_file_path="/var/log/app.log"

# Worse
uc=10
bd="/backups"
lfp="/var/log/app.log"

Quote variables

filename="my file.txt"

# Good
cat "$filename"

# Risky when spaces exist
# cat $filename

Put repeated logic into functions

function print_header {
  echo "================================"
  echo "$1"
  echo "================================"
}

Add usage/help output

function show_usage {
  cat << EOF
Usage: $0 [OPTIONS] <file>

OPTIONS:
  -h, --help      Show this help
  -v, --verbose   Verbose mode
  -o, --output    Output file
EOF
}

Debugging tips

set -x
# commands here
set +x

Security basics

read -p "Enter filename: " filename

if [[ "$filename" =~ [^a-zA-Z0-9._-] ]]; then
  echo "Invalid filename"
  exit 1
fi

/bin/ls /home/user
Strong Bash scripts are not only about making something work. They should also be readable, safe, predictable, and easy to maintain.

Key takeaways

Start with #!/bin/bash Quote variables Validate input Reuse functions Comment clearly Handle errors Test thoroughly

Good next steps