👑 Premium Sign in Sign up

Language lab · Beginner → Ops

🐚 Bash

Concept → trace → write code → run tests → explain why → improve → mini-project. Start in the browser if you like; set up your own computer when you are ready.

💻 Runs on your computerOfficial download & docs ↗

📖 Topics with real code (14)

Every verified output below was produced by running the code with GNU bash, version 5.2.37(1)-release (x86_64-pc-linux-gnu) on 5 Nov 2026. Open a topic to edit the code and run it on your own computer with the real compiler, through the free GayaGnan agent.

🌱 Beginner (5)

Variables and types

Bash variables hold text. No spaces around =. Use $var or ${var} to read; always quote "$var". $(( )) does whole-number maths; declare -i makes an integer variable.

#!/usr/bin/env bash
name="Asha"
age=15
declare -i next=age+1
greeting="Namaste, ${name}!"
echo "$greeting age $age next year $next"
echo "length of name: ${#name}, upper: ${name^^}"
echo "maths: $(( 17 / 5 )) remainder $(( 17 % 5 )) power $(( 2 ** 10 ))"
echo "decimals need bc or awk: $(awk 'BEGIN { printf "%.2f", 7/2 }')"
readonly PI=3.14
echo "readonly PI=$PI"

✅ Verified output

Namaste, Asha! age 15 next year 16
length of name: 4, upper: ASHA
maths: 3 remainder 2 power 1024
decimals need bc or awk: 3.50
readonly PI=3.14
Tests and boolean logic

[[ ]] tests strings and files; (( )) tests numbers. && runs the next command only on success, || only on failure. Every command returns an exit code: 0 means success.

#!/usr/bin/env bash
a=17; b=5
(( a > b )) && echo "$a is greater than $b"
[[ "abc" == a* ]] && echo "pattern match works"
[[ -z "" ]] && echo "empty string is -z"
[[ -d /tmp ]] && echo "/tmp is a directory"
false || echo "false failed, so || ran"
true; echo "exit code of true: $?"
grep -q root /etc/passwd && echo "root user exists"

✅ Verified output

17 is greater than 5
pattern match works
empty string is -z
/tmp is a directory
false failed, so || ran
exit code of true: 0
root user exists
Conditionals: if, elif, case

if/elif/else on tests; case matches patterns, like switch, and ends each branch with ;;.

#!/usr/bin/env bash
marks=78
if (( marks >= 90 )); then grade="A+"
elif (( marks >= 75 )); then grade="A"
elif (( marks >= 35 )); then grade="Pass"
else grade="Fail"
fi
echo "Grade: $grade"
for file in report.pdf photo.JPG notes.txt; do
  case "$file" in
    *.pdf) echo "$file -> document" ;;
    *.jpg|*.JPG|*.png) echo "$file -> image" ;;
    *) echo "$file -> other" ;;
  esac
done

✅ Verified output

Grade: A
report.pdf -> document
photo.JPG -> image
notes.txt -> other
Loops: for, while, until

for walks a list or a C-style range; while and until repeat on a condition. break and continue work as usual.

#!/usr/bin/env bash
for i in {1..5}; do
  (( i == 3 )) && continue
  echo "for $i"
done
for (( i = 10; i > 0; i -= 3 )); do echo "c-style $i"; done
n=0
until (( n >= 3 )); do echo "until $n"; (( n++ )); done
printf "line one\nline two\n" | while read -r line; do echo "read: $line"; done

✅ Verified output

for 1
for 2
for 4
for 5
c-style 10
c-style 7
c-style 4
c-style 1
until 0
until 1
until 2
read: line one
read: line two
Functions, arguments and return codes

Functions read arguments as $1, $2, $@. Use local for local variables. return sets an exit code (0-255); print text to "return" a value.

#!/usr/bin/env bash
area() {
  local length=$1 width=${2:-1}
  echo $(( length * width ))
}
is_even() { (( $1 % 2 == 0 )); }
sum_all() {
  local total=0
  for n in "$@"; do (( total += n )); done
  echo "$total"
}
echo "area $(area 5 3) and $(area 7)"
is_even 10 && echo "10 is even"
is_even 7 || echo "7 is odd"
echo "sum $(sum_all 4 9 2 7) of $# script args"

✅ Verified output

area 15 and 7
10 is even
7 is odd
sum 22 of 0 script args

🌿 Intermediate (6)

Arrays

Indexed arrays: ${arr[@]} is every item, ${#arr[@]} the count, ${arr[@]:1:2} a slice. Quote "${arr[@]}" so items with spaces stay whole.

#!/usr/bin/env bash
marks=(72 95 64 88)
marks+=(91)
echo "all: ${marks[*]} count: ${#marks[@]} first: ${marks[0]} last: ${marks[-1]}"
sorted=($(printf '%s\n' "${marks[@]}" | sort -n))
echo "sorted: ${sorted[*]} slice: ${sorted[@]:1:2}"
files=("my report.pdf" "notes.txt")
for f in "${files[@]}"; do echo "file: $f"; done
total=0; for m in "${marks[@]}"; do (( total += m )); done
echo "average: $(( total / ${#marks[@]} ))"

✅ Verified output

all: 72 95 64 88 91 count: 5 first: 72 last: 91
sorted: 64 72 88 91 95 slice: 72 88
file: my report.pdf
file: notes.txt
average: 82
String manipulation

Parameter expansion edits text without extra programs: ${s#prefix}, ${s%suffix}, ${s/old/new}, ${s:offset:length}, ${s,,} lower case.

#!/usr/bin/env bash
path="/var/log/nginx/access.log.gz"
echo "file: ${path##*/}"
echo "dir: ${path%/*}"
echo "no .gz: ${path%.gz}"
echo "extension: ${path##*.}"
s="GayaGnan learning"
echo "${s/learning/labs} | ${s:0:4} | ${s,,} | ${#s} chars"
IFS=',' read -r name marks <<< "Asha,88"
echo "name=$name marks=$marks"
printf "%-8s|%5.1f\n" "Ravi" 95

✅ Verified output

file: access.log.gz
dir: /var/log/nginx
no .gz: /var/log/nginx/access.log
extension: gz
GayaGnan labs | Gaya | gayagnan learning | 17 chars
name=Asha marks=88
Ravi    | 95.0
Associative arrays

declare -A makes a dictionary (Bash 4+). Iterate keys with ${!map[@]}. Good for counting.

#!/usr/bin/env bash
declare -A stock=([pen]=40 [book]=12)
stock[eraser]=25
(( stock[pen] -= 5 ))
for item in $(printf '%s\n' "${!stock[@]}" | sort); do echo "$item ${stock[$item]}"; done
[[ -v stock[ruler] ]] || echo "no ruler"
declare -A freq
for w in the cat and the hat and the bat; do (( freq[$w]++ )); done
echo "the=${freq[the]} and=${freq[and]} cat=${freq[cat]}"

✅ Verified output

book 12
eraser 25
pen 35
no ruler
the=3 and=2 cat=1
Pipes, redirection and text tools

| sends one command's output to the next. > writes a file, >> appends, 2> redirects errors. grep, sort, uniq, awk and cut build reports.

#!/usr/bin/env bash
log=$(mktemp)
cat > "$log" <<'EOF'
2026-11-05 10:01 GET /index 200
2026-11-05 10:02 GET /login 500
2026-11-05 10:03 POST /login 200
2026-11-05 10:04 GET /index 200
2026-11-05 10:05 GET /exam 404
EOF
echo "requests: $(wc -l < "$log")"
echo "errors:"; awk '$5 >= 400 { print "  " $4, $5 }' "$log"
echo "top pages:"; awk '{ print $4 }' "$log" | sort | uniq -c | sort -rn
ls /no/such/dir 2> /dev/null || echo "error hidden with 2>/dev/null"
rm -f "$log"

✅ Verified output

requests: 5
errors:
  /login 500
  /exam 404
top pages:
      2 /login
      2 /index
      1 /exam
error hidden with 2>/dev/null
Safe scripts: set -euo pipefail and trap

set -e stops on the first error, -u on unset variables, pipefail fails a pipe if any part fails. trap runs cleanup on exit.

#!/usr/bin/env bash
set -euo pipefail
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"; echo "cleaned up $tmp"' EXIT
echo "working in $tmp"
echo "data" > "$tmp/file.txt"
if ! grep -q "missing" "$tmp/file.txt"; then echo "grep found nothing (handled)"; fi
result=$(ls "$tmp" | wc -l)
echo "files: $result"
echo "unset check: ${MAYBE_UNSET:-default value}"

✅ Verified output

working in /tmp/tmp.vRpXjcnSwf
grep found nothing (handled)
files: 1
unset check: default value
cleaned up /tmp/tmp.vRpXjcnSwf
Files: create, read, loop over lines

Here-docs write multi-line text; while read loops line by line; IFS splits CSV fields. Temp files come from mktemp.

#!/usr/bin/env bash
f=$(mktemp)
cat > "$f" <<'EOF'
name,marks
Asha,88
Ravi,95
EOF
total=0; count=0
while IFS=, read -r name marks; do
  [[ $name == name ]] && continue
  echo "$name -> $marks"
  (( total += marks, count++ ))
done < "$f"
echo "average $(( total / count ))"
echo "size $(stat -c %s "$f") bytes, lines $(wc -l < "$f")"
rm -f "$f"

✅ Verified output

Asha -> 88
Ravi -> 95
average 91
size 27 bytes, lines 3

🌳 Advanced (3)

Recursion and memoized functions

Bash functions can call themselves. An associative array acts as a cache. Whole-number maths only, and deep recursion is slow: use another language for heavy work.

#!/usr/bin/env bash
declare -A memo
fib() {
  local n=$1
  if (( n < 2 )); then echo "$n"; return; fi
  if [[ -n ${memo[$n]:-} ]]; then echo "${memo[$n]}"; return; fi
  local a b
  a=$(fib $(( n - 1 )))
  b=$(fib $(( n - 2 )))
  memo[$n]=$(( a + b ))
  echo "${memo[$n]}"
}
for i in {0..9}; do printf "%s " "$(fib "$i")"; done; echo
fact() { (( $1 <= 1 )) && echo 1 || echo $(( $1 * $(fact $(( $1 - 1 ))) )); }
echo "10! = $(fact 10)"

✅ Verified output

0 1 1 2 3 5 8 13 21 34 
10! = 3628800
Parallel jobs with & and wait

& runs a command in the background; wait waits for them. Three 1-second jobs finish in about 1 second instead of 3.

#!/usr/bin/env bash
start=$SECONDS
out=$(mktemp -d)
for i in 1 2 3; do
  ( sleep 1; echo $(( i * i )) > "$out/$i" ) &
done
wait
echo "squares: $(cat "$out"/1 "$out"/2 "$out"/3 | tr '\n' ' ')"
echo "took about $(( SECONDS - start )) second(s)"
seq 1 6 | xargs -P 3 -I{} sh -c 'echo $(( {} * 10 ))' | sort -n | tr '\n' ' '; echo
rm -rf "$out"

✅ Verified output

squares: 1 4 9 
took about 1 second(s)
10 20 30 40 50 60
Processes, signals and exit codes

$$ is this script's process id, $! the last background job. kill sends signals; exit codes tell a caller what happened (0 ok, others error).

#!/usr/bin/env bash
sleep 30 &
pid=$!
echo "started background sleep, pid is a number: $([[ $pid =~ ^[0-9]+$ ]] && echo yes)"
kill -TERM "$pid"
wait "$pid" 2>/dev/null
echo "exit status after SIGTERM: $? (128 + 15)"
check() { [[ $1 -ge 35 ]] && return 0 || return 3; }
check 20; echo "check 20 returned $?"
( exit 7 ); echo "subshell exit code $?"
echo "this shell: bash ${BASH_VERSINFO[0]}"

✅ Verified output

started background sleep, pid is a number: yes
exit status after SIGTERM: 143 (128 + 15)
check 20 returned 3
subshell exit code 7
this shell: bash 5

💻 Terminal on my computer

Like a cloud shell, but on your own laptop: install compilers and run real commands from this page through the free GayaGnan agent. It asks you before connecting, installing or running anything.

🛠️ Set up and verify

  1. Install
    wsl --install   (Ubuntu on Windows; restart, then open Ubuntu)
    Bash/zsh are built in: open Terminal
    Bash is built in: open a terminal
  2. Editor: Visual Studio Code with ShellCheck, Bash IDE.
  3. Create hello.sh
    #!/usr/bin/env bash
    set -euo pipefail
    echo "Hello, GayaGnan!"
  4. Run it and check the version
    bash hello.sh
    bash --version
  5. Health check: paste what your terminal printed.

Install one language at a time. These commands only install or print versions; nothing here deletes files. Versions change: check the official page and note the version you installed.

🧭 The 14-lab path

  • 00 · Setup and verify — install the runtime and editor, run hello world, print the version, fix PATH
  • 01 · Syntax and trace — follow each line and the value of every variable
  • 02 · Logic — conditionals and decision trees
  • 03 · Loops — trace tables and loop challenges
  • 04 · Functions — break problems down and test each piece
  • 05 · Data structures — lists, maps, stacks, queues and trees you can see
  • 06 · Debugging — bug cases with a hint ladder
  • 07 · Algorithms — search, sort and Big-O comparison
  • 08 · Files and data — read and write fixture files 🧪 Bash & PowerShell Automation
  • 09 · API or database — talk to a mock API or a sandbox database
  • 10 · Testing — write tests and read failing assertions
  • 11 · Security — secure coding and dependency hygiene for this language
  • 12 · Project — one portfolio mini-project
  • 13 · Assessment — quiz, coding test, rubric and an explain-your-answer prompt

📚 Modules

  1. shell basics
  2. variables
  3. quoting
  4. conditions
  5. loops
  6. functions
  7. pipes
  8. grep, sed and awk basics
  9. exit codes
  10. files
  11. permissions
  12. processes
  13. script tests

🏗️ Mini-projects

  • Mock log report
  • Directory inventory
  • Pipeline validation script

Projects use synthetic data only.