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.
📖 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
- Install
wsl --install (Ubuntu on Windows; restart, then open Ubuntu)
Bash/zsh are built in: open Terminal
Bash is built in: open a terminal
- Editor: Visual Studio Code with ShellCheck, Bash IDE.
- Create
hello.sh#!/usr/bin/env bash set -euo pipefail echo "Hello, GayaGnan!"
- Run it and check the version
bash hello.sh bash --version
- 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
- shell basics
- variables
- quoting
- conditions
- loops
- functions
- pipes
- grep, sed and awk basics
- exit codes
- files
- permissions
- processes
- script tests
🏗️ Mini-projects
- Mock log report
- Directory inventory
- Pipeline validation script
Projects use synthetic data only.