Toolmingo
Guides23 min read

50 Bash Interview Questions (With Answers)

Top Bash scripting interview questions with clear answers and examples — covering variables, arrays, functions, loops, string manipulation, file I/O, process control, and real-world scripting patterns.

Bash interviews test your shell scripting fluency, Linux command knowledge, process management, text processing, and ability to automate real-world tasks. This guide covers the 50 most common Bash interview questions with concise answers and working code examples.

Quick reference

Topic Most asked questions
Basics shebang, variables, quoting, special variables
Control flow if/elif/else, case, for, while, until
Functions definition, arguments, return values, local scope
Arrays indexed, associative, iteration, manipulation
Strings length, substring, search/replace, split
File I/O read, redirect, pipes, heredoc, process substitution
Process control background jobs, wait, trap, exit codes
Text processing grep, sed, awk, cut, sort, uniq
Scripting patterns argument parsing, logging, error handling, locking
Advanced eval, source, subshells, parameter expansion

Basics

1. What is a shebang line and why does it matter?

The first line of a script that tells the OS which interpreter to use:

#!/usr/bin/env bash

Using /usr/bin/env bash instead of /bin/bash finds bash in $PATH, making the script portable across different systems (macOS, Linux distros) where bash may live in different locations.

Without a shebang the OS uses the current shell to run the script, which may not be bash — causing unexpected behaviour if you rely on bash-specific features.

2. What is the difference between $* and $@?

Both expand to all positional parameters, but quoting changes the behaviour:

function demo() {
  echo "Using \"\$*\":"
  for arg in "$*"; do echo "  [$arg]"; done   # one string

  echo "Using \"\$@\":"
  for arg in "$@"; do echo "  [$arg]"; done   # individual args
}

demo "hello world" "foo bar"
# "$*" → [hello world foo bar]   (joined by IFS, one item)
# "$@" → [hello world] [foo bar] (two separate items)

Rule: Always use "$@" when forwarding arguments to preserve word splitting.

3. What is the difference between single quotes and double quotes in Bash?

Quote Variable expansion Command substitution Escape sequences
'text' No No No
"text" Yes ($VAR) Yes ($(cmd)) Partial (\n, \\, \")
No quotes Yes Yes Yes (+ globbing, word splitting)
VAR="world"
echo 'Hello $VAR'   # → Hello $VAR
echo "Hello $VAR"   # → Hello world
echo Hello $VAR     # → Hello world (+ risk of globbing/splitting)

4. What are special variables $?, $$, $!, $0?

Variable Meaning
$? Exit code of last command (0 = success)
$$ PID of current shell
$! PID of last background process
$0 Name/path of the script
$# Number of positional parameters
$_ Last argument of previous command
ls /nonexistent 2>/dev/null
echo "Exit: $?"   # → Exit: 2

sleep 5 &
echo "BG PID: $!"

5. What is the difference between = and == in Bash?

Inside [[ ]] both work for string comparison:

[[ "$a" = "$b" ]]    # POSIX string equality
[[ "$a" == "$b" ]]   # bash string equality (same result)
[[ "$a" == foo* ]]   # glob pattern match (right side unquoted)
[[ "$a" =~ ^foo.* ]] # extended regex match

Inside (( )) use == for numeric equality:

(( a == 5 ))   # numeric
(( a = 5 ))    # assignment — do not use for comparison!

6. What is the difference between [ ], [[ ]], and (( ))?

Construct Purpose Notes
[ ] POSIX test External command, limited operators
[[ ]] Bash extended test Built-in, supports &&, ||, =~, glob
(( )) Arithmetic evaluation C-style operators, no $ needed for vars
a=5
[ "$a" -gt 3 ] && echo "yes"      # POSIX
[[ $a -gt 3 ]] && echo "yes"      # bash (no quotes needed for integers)
(( a > 3 )) && echo "yes"         # arithmetic

7. Explain variable scoping in Bash.

By default all variables are global in Bash. Use local inside functions to limit scope:

GLOBAL="global"

my_func() {
  local LOCAL="local"
  GLOBAL="modified"
  echo "$LOCAL"    # visible
}

my_func
echo "$GLOBAL"    # modified
echo "$LOCAL"     # empty — out of scope

8. What does set -euo pipefail do?

#!/usr/bin/env bash
set -euo pipefail
Option Meaning
-e Exit immediately if any command fails (non-zero exit)
-u Treat unset variables as errors
-o pipefail Pipe fails if any command in pipeline fails (not just last)

Without pipefail: false | true exits 0. With pipefail: exits 1.

This is the recommended header for production scripts.


Control Flow

9. Write an if/elif/else with multiple conditions.

#!/usr/bin/env bash
score=75

if (( score >= 90 )); then
  echo "Grade: A"
elif (( score >= 80 )); then
  echo "Grade: B"
elif (( score >= 70 )); then
  echo "Grade: C"
else
  echo "Grade: F"
fi

# Compound conditions
if [[ -f "$file" && -r "$file" ]]; then
  echo "File exists and is readable"
fi

10. What is a case statement? Give an example.

read -rp "Enter OS: " os

case "$os" in
  ubuntu|debian)
    echo "apt-based"
    ;;
  centos|rhel|fedora)
    echo "dnf/yum-based"
    ;;
  arch*)
    echo "pacman-based"
    ;;
  *)
    echo "Unknown OS"
    ;;
esac

case is cleaner than long if-elif chains when matching a single value against multiple patterns.

11. What is the difference between for, while, and until?

# for — iterate over list or range
for i in {1..5}; do echo "$i"; done
for file in *.log; do rm "$file"; done

# C-style for
for (( i=0; i<5; i++ )); do echo "$i"; done

# while — loop while condition is true
count=0
while (( count < 5 )); do
  echo "$count"
  (( count++ ))
done

# while read — process file line by line
while IFS= read -r line; do
  echo "Line: $line"
done < /etc/passwd

# until — loop until condition becomes true
until ping -c1 google.com &>/dev/null; do
  echo "Waiting for network..."
  sleep 2
done

12. How do break and continue work in loops?

for i in {1..10}; do
  (( i % 2 == 0 )) && continue   # skip even
  (( i > 7 )) && break           # stop after 7
  echo "$i"
done
# Output: 1 3 5 7

break N exits N levels of nested loops. continue N continues the Nth enclosing loop.


Functions

13. How do you define and call a function in Bash?

# Two valid syntaxes:
greet() {
  local name="${1:-World}"
  echo "Hello, $name!"
}

function greet2 {
  echo "Hello, ${1:-World}!"
}

greet "Alice"    # → Hello, Alice!
greet            # → Hello, World!

14. How do functions return values in Bash?

Functions can only return an exit code (0–255). To return a string, use command substitution:

get_timestamp() {
  date +"%Y-%m-%d %H:%M:%S"
}

ts=$(get_timestamp)
echo "Time: $ts"

# Return codes for error handling:
divide() {
  if (( $2 == 0 )); then
    echo "Error: division by zero" >&2
    return 1
  fi
  echo $(( $1 / $2 ))
}

result=$(divide 10 2) && echo "Result: $result"
divide 5 0 || echo "Division failed"

15. What is the difference between return and exit in a function?

Command Scope Use
return [N] Function only Return exit status from function
exit [N] Entire script Terminate the script with exit code N

Calling exit inside a function terminates the whole script — often a bug. Use return inside functions.


Arrays

16. How do you create and use arrays in Bash?

# Indexed array
fruits=("apple" "banana" "cherry")
fruits+=(  "date")             # append
echo "${fruits[0]}"            # → apple
echo "${fruits[@]}"            # → all elements
echo "${#fruits[@]}"           # → 4 (length)
echo "${fruits[@]:1:2}"        # → banana cherry (slice)
unset fruits[1]                # remove element

# Iterate
for fruit in "${fruits[@]}"; do
  echo "$fruit"
done

# Array from command output
files=( $(ls *.sh 2>/dev/null) )   # word splitting — ok for filenames without spaces
mapfile -t lines < /etc/passwd      # safer: read file into array

17. What is an associative array and how do you use one?

declare -A config          # must declare explicitly

config[host]="localhost"
config[port]="5432"
config[db]="mydb"

echo "${config[host]}"     # → localhost
echo "${!config[@]}"       # → keys: host port db
echo "${config[@]}"        # → values

# Iterate key-value
for key in "${!config[@]}"; do
  echo "$key = ${config[$key]}"
done

# Check key exists
[[ -v config[host] ]] && echo "host key exists"

Associative arrays require Bash 4.0+.


String Manipulation

18. How do you get the length of a string?

str="hello world"
echo "${#str}"           # → 11

# Also works for array length
arr=(a b c)
echo "${#arr[@]}"        # → 3

19. How do you extract a substring?

str="Hello, World!"
echo "${str:7}"          # → World!    (from index 7)
echo "${str:7:5}"        # → World     (5 chars from index 7)
echo "${str: -6}"        # → orld!     (from end, note space before -)

20. How do you replace text in a string using parameter expansion?

path="/home/user/documents/file.txt"

# Replace first occurrence
echo "${path/user/alice}"           # → /home/alice/documents/file.txt

# Replace all occurrences
text="foo foo foo"
echo "${text//foo/bar}"             # → bar bar bar

# Remove prefix
echo "${path#/home/}"               # → user/documents/file.txt (shortest match)
echo "${path##*/}"                  # → file.txt (longest prefix match — basename)

# Remove suffix
echo "${path%.*}"                   # → /home/user/documents/file (remove extension)
echo "${path%%/*}"                  # → empty (longest suffix match)

# Case conversion (Bash 4+)
lower="hello world"
echo "${lower^^}"                   # → HELLO WORLD
echo "${lower^}"                    # → Hello world (first char only)
upper="HELLO"
echo "${upper,,}"                   # → hello

21. How do you split a string by a delimiter?

csv="one,two,three,four"

# Method 1: IFS splitting
IFS=',' read -ra parts <<< "$csv"
echo "${parts[0]}"       # → one
echo "${parts[@]}"       # → one two three four

# Method 2: parameter expansion (remove prefix/suffix)
# Method 3: tr + read
echo "$csv" | tr ',' '\n'

22. Useful string comparison operators

[[ -z "$str" ]]     # true if empty string
[[ -n "$str" ]]     # true if non-empty
[[ "$a" == "$b" ]]  # string equality
[[ "$a" != "$b" ]]  # string inequality
[[ "$a" < "$b" ]]   # lexicographic less-than
[[ "$a" > "$b" ]]   # lexicographic greater-than
[[ "$str" == *"sub"* ]]  # contains substring
[[ "$str" =~ ^[0-9]+$ ]] # regex match (integer check)

File I/O and Redirections

23. Explain the different types of I/O redirection.

cmd > file        # stdout to file (overwrite)
cmd >> file       # stdout to file (append)
cmd < file        # stdin from file
cmd 2> file       # stderr to file
cmd 2>&1          # stderr to stdout
cmd &> file       # stdout and stderr to file (bash shorthand)
cmd 2>/dev/null   # discard stderr
cmd | other       # pipe stdout to stdin of other

24. What is a heredoc and process substitution?

# Heredoc — multi-line string as stdin
cat << 'EOF'
This $VAR is not expanded (single-quoted delimiter)
EOF

DEST="/tmp/config.yaml"
cat > "$DEST" << EOF
host: ${HOST:-localhost}
port: ${PORT:-8080}
EOF

# Process substitution — treat command output as file
diff <(sort file1.txt) <(sort file2.txt)
while IFS= read -r line; do
  echo ">> $line"
done < <(find . -name "*.sh")

25. How do you read a file line by line safely?

while IFS= read -r line; do
  echo "Line: $line"
done < /etc/passwd

# IFS=    — preserves leading/trailing whitespace
# -r      — disables backslash interpretation
# Handle last line without newline:
while IFS= read -r line || [[ -n "$line" ]]; do
  process "$line"
done < file.txt

26. How do you check if a file or directory exists?

[[ -f "$path" ]]    # regular file exists
[[ -d "$path" ]]    # directory exists
[[ -e "$path" ]]    # file or dir exists (any type)
[[ -s "$path" ]]    # file exists and is non-empty
[[ -r "$path" ]]    # readable
[[ -w "$path" ]]    # writable
[[ -x "$path" ]]    # executable
[[ -L "$path" ]]    # symbolic link

if [[ ! -f "$config" ]]; then
  echo "Config not found: $config" >&2
  exit 1
fi

Process Control

27. How do you run processes in background and wait for them?

# Background with &
sleep 10 &
pid=$!
echo "Started PID $pid"

# Wait for specific PID
wait "$pid"
echo "Done, exit: $?"

# Parallel execution with wait
for host in server1 server2 server3; do
  ssh "$host" "df -h" > "/tmp/${host}.txt" &
done
wait   # wait for all background jobs

# jobs — list background jobs in current shell
jobs
fg 1   # bring job 1 to foreground
bg 1   # resume stopped job 1 in background

28. What is trap and how do you use it?

trap runs commands when the script receives a signal or exits:

#!/usr/bin/env bash
TMPFILE=$(mktemp)

cleanup() {
  echo "Cleaning up..."
  rm -f "$TMPFILE"
}

trap cleanup EXIT          # always runs on exit
trap 'cleanup; exit 1' INT TERM   # on Ctrl+C or kill

# Trap ERR — runs on any error (use with set -e)
trap 'echo "Error on line $LINENO"' ERR

# Ignore SIGHUP (keep running after terminal closes)
trap '' HUP

29. What are exit codes and how do you use them?

# Convention:
# 0  = success
# 1  = general error
# 2  = misuse of shell built-in
# 126 = command found but not executable
# 127 = command not found
# 130 = script terminated by Ctrl+C (128 + SIGINT 2)

command_that_might_fail || {
  echo "Failed with exit code: $?" >&2
  exit 1
}

# Check multiple exit codes
grep "pattern" file.txt
case $? in
  0) echo "Found" ;;
  1) echo "Not found" ;;
  2) echo "Error" ;;
esac

30. What is the difference between source (.) and executing a script?

Method New subshell Variables visible after Use case
./script.sh or bash script.sh Yes No Run isolated script
source script.sh or . script.sh No Yes Load functions/vars into current shell
# config.sh
DB_HOST="localhost"
DB_PORT=5432

# In main script:
source ./config.sh
echo "$DB_HOST"    # → localhost

Text Processing

31. How do you use grep in scripts?

# Check if pattern exists (silent, use exit code)
if grep -q "error" logfile.txt; then
  echo "Errors found"
fi

# Extract matching lines
grep -E "^ERROR|^WARN" app.log

# Count occurrences
grep -c "timeout" app.log

# Show context (3 lines before/after)
grep -B3 -A3 "FATAL" app.log

# Recursive, show filename and line number
grep -rn "TODO" ./src/

# Invert match (non-matching lines)
grep -v "^#" config.txt   # remove comment lines

32. How do you use sed for text transformation?

# Substitute (s command)
sed 's/old/new/' file.txt          # first occurrence per line
sed 's/old/new/g' file.txt         # all occurrences
sed 's/old/new/gi' file.txt        # case-insensitive, all

# In-place edit (backup to .bak)
sed -i.bak 's/localhost/db.prod/g' config.txt

# Delete lines matching pattern
sed '/^#/d' config.txt             # delete comment lines
sed '/^$/d' config.txt             # delete empty lines

# Print specific lines
sed -n '5,10p' file.txt            # lines 5-10
sed -n '/START/,/END/p' file.txt   # between patterns

# Insert/append
sed '3i\inserted line' file.txt    # insert before line 3
sed '3a\appended line' file.txt    # append after line 3

33. How do you use awk for data extraction?

# Print specific columns
awk '{print $1, $3}' data.txt               # columns 1 and 3
awk -F: '{print $1}' /etc/passwd            # colon-separated, print username

# Conditional printing
awk '$3 > 100 {print $0}' data.txt          # lines where col 3 > 100
awk 'NR==1 || $5=="ERROR" {print}' log.txt  # header + error lines

# Sum a column
awk '{sum += $2} END {print "Total:", sum}' data.txt

# Count occurrences
awk '{count[$1]++} END {for (k in count) print k, count[k]}' file.txt

# Multi-file with filename
awk 'FNR==1 {print "==> " FILENAME}' *.txt

34. How do you sort and deduplicate output?

# Sort alphabetically
sort file.txt

# Sort numerically (-n), reverse (-r), unique (-u)
sort -rn numbers.txt
sort -u file.txt                     # unique lines (sorted)

# Sort by field
sort -t: -k3 -n /etc/passwd          # sort by UID (field 3, : delimiter)

# Deduplicate (must be sorted first)
sort file.txt | uniq
sort file.txt | uniq -c | sort -rn   # frequency count, most common first
sort file.txt | uniq -d              # only duplicate lines
sort file.txt | uniq -u              # only unique lines

# Remove duplicates without sorting (preserve order)
awk '!seen[$0]++' file.txt

Scripting Patterns

35. How do you parse command-line arguments?

#!/usr/bin/env bash

usage() {
  cat << EOF
Usage: $(basename "$0") [OPTIONS] <input>
Options:
  -o, --output FILE   Output file (default: output.txt)
  -v, --verbose       Enable verbose mode
  -h, --help          Show this help
EOF
  exit "${1:-0}"
}

OUTPUT="output.txt"
VERBOSE=false

# Manual parsing
while [[ $# -gt 0 ]]; do
  case "$1" in
    -o|--output)   OUTPUT="$2"; shift 2 ;;
    -v|--verbose)  VERBOSE=true; shift ;;
    -h|--help)     usage 0 ;;
    --)            shift; break ;;
    -*)            echo "Unknown option: $1" >&2; usage 1 ;;
    *)             INPUT="$1"; shift ;;
  esac
done

[[ -z "${INPUT:-}" ]] && { echo "Error: input required" >&2; usage 1; }

$VERBOSE && echo "Input: $INPUT, Output: $OUTPUT"

36. How do you implement logging in a Bash script?

#!/usr/bin/env bash
LOGFILE="/var/log/myscript.log"

log() {
  local level="$1"; shift
  printf "[%s] [%-5s] %s\n" "$(date +"%Y-%m-%d %H:%M:%S")" "$level" "$*" | tee -a "$LOGFILE"
}

log_info()  { log "INFO"  "$@"; }
log_warn()  { log "WARN"  "$@" >&2; }
log_error() { log "ERROR" "$@" >&2; }

log_info "Script started"
log_warn "Config file missing, using defaults"
log_error "Cannot connect to database"

37. How do you create a lockfile to prevent concurrent execution?

LOCKFILE="/tmp/myscript.lock"

# Method 1: mkdir (atomic on most filesystems)
if ! mkdir "$LOCKFILE" 2>/dev/null; then
  echo "Already running (lock: $LOCKFILE)" >&2
  exit 1
fi
trap 'rmdir "$LOCKFILE"' EXIT

# Method 2: flock (preferred — handles stale locks)
exec 9>"$LOCKFILE"
if ! flock -n 9; then
  echo "Already running" >&2
  exit 1
fi
# Lock released automatically on script exit

38. How do you handle errors robustly in a script?

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'   # safer IFS (no word splitting on spaces)

die() {
  echo "[ERROR] $*" >&2
  exit 1
}

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
}

require_cmd jq
require_cmd curl

# Check required env vars
: "${DB_HOST:?DB_HOST is required}"
: "${API_KEY:?API_KEY is required}"

# Retry logic
retry() {
  local max=$1; shift
  local delay=$1; shift
  local attempt=1
  until "$@"; do
    (( attempt++ > max )) && return 1
    echo "Attempt $attempt/$max failed, retrying in ${delay}s..." >&2
    sleep "$delay"
  done
}

retry 3 5 curl -sf "https://api.example.com/health"

39. How do you write a script that sends an email or Slack notification?

# Email via sendmail/mail
send_alert() {
  local subject="$1"
  local body="$2"
  echo "$body" | mail -s "$subject" ops@example.com
}

# Slack webhook
slack_notify() {
  local message="$1"
  local webhook="${SLACK_WEBHOOK:?SLACK_WEBHOOK not set}"
  curl -sf -X POST "$webhook" \
    -H "Content-Type: application/json" \
    -d "{\"text\": \"$message\"}" >/dev/null
}

# Usage
slack_notify ":white_check_mark: Backup completed successfully"
slack_notify ":x: Deployment failed on $(hostname)"

Advanced Bash

40. What is parameter expansion and what are useful forms?

VAR="hello world"

# Default values
echo "${VAR:-default}"      # use default if VAR unset or empty
echo "${VAR:=default}"      # assign default if VAR unset or empty
echo "${VAR:?error msg}"    # error and exit if unset or empty
echo "${VAR:+alternate}"    # use alternate if VAR is set

# String length
echo "${#VAR}"              # → 11

# Substring
echo "${VAR:6}"             # → world
echo "${VAR:6:3}"           # → wor

# Pattern removal
FILE="/home/user/file.tar.gz"
echo "${FILE##*/}"          # → file.tar.gz  (basename)
echo "${FILE%/*}"           # → /home/user   (dirname)
echo "${FILE##*.}"          # → gz           (extension)
echo "${FILE%.*}"           # → /home/user/file.tar  (remove last ext)
echo "${FILE%%.*}"          # → /home/user/file      (remove all ext)

# Replacement
echo "${VAR/world/bash}"    # → hello bash
echo "${VAR// /_}"          # → hello_world (all spaces to underscore)

41. What is the difference between a subshell and a child process?

# Subshell ( ) — runs in subshell, variable changes don't propagate
(
  cd /tmp
  VAR="local"
  echo "$VAR"      # → local
)
echo "$VAR"        # → (original value — subshell change lost)

# Curly brace group { } — runs in SAME shell
{
  VAR="changed"
  echo "$VAR"
}
echo "$VAR"        # → changed

# Pipe creates subshell — common gotcha
count=0
cat file.txt | while read -r line; do
  (( count++ ))   # count change lost — runs in subshell!
done
echo "$count"     # → 0 (!) — use process substitution instead

# Fix with process substitution:
while read -r line; do
  (( count++ ))
done < <(cat file.txt)
echo "$count"     # → correct

42. How does eval work and when should you avoid it?

# eval re-evaluates a string as a command
VAR="echo hello"
eval "$VAR"    # → hello

# Useful for dynamic variable names (prefer nameref in bash 4.3+)
PREFIX="DB"
eval "${PREFIX}_HOST=localhost"
echo "$DB_HOST"    # → localhost

# Safer alternative: declare -n (nameref)
declare -n ref="${PREFIX}_HOST"
ref="localhost"
echo "$DB_HOST"    # → localhost

Avoid eval with untrusted input — it executes arbitrary code. Prefer declare -n or associative arrays.

43. How do you write a function that modifies a caller variable (by reference)?

# Bash 4.3+: declare -n nameref
trim() {
  declare -n _result="$1"
  local value="${2#"${2%%[![:space:]]*}"}"  # strip leading
  _result="${value%"${value##*[![:space:]]}"}"  # strip trailing
}

trim result "  hello world  "
echo "[$result]"    # → [hello world]

44. What is IFS and why does it matter?

IFS (Internal Field Separator) controls word splitting. Default is space, tab, newline.

# Default IFS: splits on whitespace
line="one two three"
read -ra parts <<< "$line"
echo "${parts[1]}"    # → two

# Custom IFS
IFS=: read -ra fields <<< "root:x:0:0:root:/root:/bin/bash"
echo "${fields[0]}"   # → root
echo "${fields[6]}"   # → /bin/bash

# Dangerous: unquoted "$@" with modified IFS
IFS=,
arr=($*)    # BUG: splits on comma now

Best practice: declare IFS locally or restore it after use.

45. How do you use xargs efficiently?

# Run command for each item
find . -name "*.log" | xargs rm

# With spaces in filenames — use -print0 and -0
find . -name "*.log" -print0 | xargs -0 rm

# Parallel execution — run 4 at a time
cat urls.txt | xargs -P4 -I{} curl -sf {} -o /dev/null

# Pass multiple args at once (-n)
cat list.txt | xargs -n2 echo   # pass 2 items at a time

# Replace {} placeholder
find . -name "*.sh" | xargs -I{} bash -n {}   # syntax check all scripts

Real-World Scripts

46. Write a script to backup a directory with timestamped archives.

#!/usr/bin/env bash
set -euo pipefail

SOURCE="${1:?Usage: $0 <source-dir> <backup-dir>}"
BACKUP_DIR="${2:?Usage: $0 <source-dir> <backup-dir>}"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
ARCHIVE="${BACKUP_DIR}/backup_${TIMESTAMP}.tar.gz"
KEEP_DAYS="${KEEP_DAYS:-30}"

[[ -d "$SOURCE" ]] || { echo "Source not found: $SOURCE" >&2; exit 1; }
mkdir -p "$BACKUP_DIR"

echo "Backing up $SOURCE..."
tar -czf "$ARCHIVE" -C "$(dirname "$SOURCE")" "$(basename "$SOURCE")"
echo "Backup created: $ARCHIVE ($(du -sh "$ARCHIVE" | cut -f1))"

# Remove backups older than KEEP_DAYS
find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime "+${KEEP_DAYS}" -delete
echo "Cleaned up backups older than ${KEEP_DAYS} days"

47. Write a script to monitor disk usage and alert when threshold exceeded.

#!/usr/bin/env bash
set -euo pipefail

THRESHOLD="${THRESHOLD:-80}"
ALERT_EMAIL="${ALERT_EMAIL:-}"

df -h --output=pcent,target | tail -n +2 | while read -r usage mountpoint; do
  pct="${usage%%%}"   # strip % sign
  if (( pct >= THRESHOLD )); then
    msg="ALERT: $mountpoint is ${pct}% full (threshold: ${THRESHOLD}%)"
    echo "$msg"
    if [[ -n "$ALERT_EMAIL" ]]; then
      echo "$msg" | mail -s "Disk Alert: $(hostname)" "$ALERT_EMAIL"
    fi
  fi
done

48. Write a script to process CSV data.

#!/usr/bin/env bash
# Input: CSV with header: name,department,salary
# Output: average salary per department

FILE="${1:?Usage: $0 <csv-file>}"

awk -F',' '
NR==1 { next }  # skip header
{
  dept=$2
  sal=$3
  sum[dept] += sal
  count[dept]++
}
END {
  printf "%-20s %10s %10s\n", "Department", "Count", "Avg Salary"
  printf "%-20s %10s %10s\n", "----------", "-----", "----------"
  for (d in sum)
    printf "%-20s %10d %10.2f\n", d, count[d], sum[d]/count[d]
}
' "$FILE" | sort -t$'\t' -k3 -rn

49. How do you use Bash for API calls and JSON parsing?

#!/usr/bin/env bash
set -euo pipefail

API_URL="https://api.github.com"
REPO="${1:?Usage: $0 <owner/repo>}"

# Fetch and parse with jq
response=$(curl -sf \
  -H "Accept: application/vnd.github.v3+json" \
  -H "Authorization: token ${GITHUB_TOKEN:-}" \
  "${API_URL}/repos/${REPO}")

name=$(jq -r '.full_name' <<< "$response")
stars=$(jq -r '.stargazers_count' <<< "$response")
forks=$(jq -r '.forks_count' <<< "$response")
lang=$(jq -r '.language // "N/A"' <<< "$response")

printf "Repo:     %s\nStars:    %d\nForks:    %d\nLanguage: %s\n" \
  "$name" "$stars" "$forks" "$lang"

# Paginated API call
page=1
while true; do
  data=$(curl -sf "${API_URL}/repos/${REPO}/issues?page=${page}&per_page=100")
  count=$(jq 'length' <<< "$data")
  (( count == 0 )) && break
  jq -r '.[] | [.number, .title] | @tsv' <<< "$data"
  (( page++ ))
done

50. What are common Bash script anti-patterns to avoid?

Anti-pattern Problem Fix
ls | grep pattern Parses ls output (breaks with special filenames) find . -name "pattern" or glob
cat file | grep Useless use of cat grep pattern file
for line in $(cat file) Word splitting, glob expansion while IFS= read -r line
if [ $? -eq 0 ] Loses context, verbose if command; then directly
Unquoted $VAR Word splitting and glob expansion Always quote: "$VAR"
rm -rf $DIR If DIR is empty → rm -rf / rm -rf "${DIR:?DIR not set}"
eval "$(user_input)" Arbitrary code execution Use arrays, read, or nameref
Ignoring set -e pipeline Silent errors propagate set -euo pipefail at top

Bash vs other scripting languages

Dimension Bash Python Ruby PowerShell
Best for System glue, CLI tools, DevOps Complex logic, data, APIs Scripting, automation Windows/Azure admin
Startup overhead Minimal ~30–100ms ~50–100ms ~500ms+
Data structures Strings, arrays, assoc arrays Full (dict, list, class) Full (Hash, Array, class) Full (.NET objects)
Error handling Explicit (set -e, ` , trap`) Exceptions
Cross-platform Linux/macOS (limited Windows) Excellent Good Windows-first
Readability Low (terse) High High Medium
Available everywhere Yes (Linux/Unix) Almost No Windows only
String manipulation Painful Excellent Excellent Good

Rule of thumb: If your script grows beyond 100 lines and needs complex logic, data structures, or HTTP calls → switch to Python.


Common mistakes

Mistake Why it fails Correct approach
if [ $VAR == "foo" ] Fails if VAR empty or has spaces if [[ "$VAR" == "foo" ]]
while read line Strips leading/trailing whitespace while IFS= read -r line
count++ Not arithmetic syntax in bash (( count++ )) or count=$(( count + 1 ))
[ -z $VAR ] Unquoted fails if VAR has spaces [ -z "$VAR" ]
2>&1 > file Order matters: stderr still goes to terminal > file 2>&1
Forgetting local Function variables pollute global scope Always local inside functions
for i in $(seq 1 100) Forks subshell, slow for (( i=1; i<=100; i++ ))
Not using mktemp Hardcoded temp files collide TMP=$(mktemp); trap 'rm -f "$TMP"' EXIT

Bash vs related terms

Term What it is
Bash Bourne Again SHell — most common Linux shell, scripting language
sh POSIX shell — minimal, no arrays or [[]]; Bash runs as sh with #!/bin/sh
zsh Z shell — Bash-compatible + features (used by macOS default since Catalina)
dash Debian Almquist Shell — fast POSIX sh, Ubuntu's /bin/sh; no Bash extensions
fish Friendly Interactive Shell — user-friendly but not POSIX compatible
Shell Generic term for any command interpreter (bash/zsh/sh/fish/etc.)
Terminal Application that hosts a shell (iTerm2, GNOME Terminal, Windows Terminal)
CLI Command-Line Interface — what shell scripts automate

FAQ

Q: Should I use #!/bin/bash or #!/usr/bin/env bash? Use #!/usr/bin/env bash for portability — on macOS, Homebrew bash may be in /usr/local/bin or /opt/homebrew/bin, not /bin/bash.

Q: When should I use Bash vs Python for scripting? Bash for: shell glue, file operations, running other commands, simple text processing under 100 lines. Python for: complex logic, JSON/API handling, data structures, anything that grows large.

Q: What is the safest way to handle filenames with spaces? Always quote variables ("$file"), use find -print0 | xargs -0, or use mapfile -t / while IFS= read -r to populate arrays from file lists.

Q: How do I debug a Bash script? bash -n script.sh — syntax check (no execution). bash -x script.sh — trace execution (print each command). set -x / set +x inside script to trace specific sections. shellcheck script.sh — static analysis tool (install separately).

Q: How do I make a Bash script exit on first error? Add set -euo pipefail after the shebang. -e exits on error, -u errors on unset vars, -o pipefail catches pipe failures. Add trap 'echo "Error on line $LINENO"' ERR for line number reporting.

Q: What is ShellCheck? ShellCheck (shellcheck.net) is a static analysis tool that catches common Bash bugs — unquoted variables, [ ] vs [[ ]], deprecated syntax, portability issues. Run it as part of CI on all shell scripts.

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools