Skip to main content

Bash Interview Questions

Comprehensive collection of 35+ frequently asked Bash scripting interview questions with detailed answers. Perfect for preparation and skill assessment.

Fundamentals

Q1: What is the difference between sh and bash?

Answer:

sh (Bourne Shell) is the POSIX standard shell with minimal features. bash (Bourne Again Shell) is an extended version with additional features.

Key differences:

Featureshbash
ArraysNoYes
Associative arraysNoYes
FunctionsBasicFull support
ArithmeticLimitedFull $(( ))
Job controlNoYes
HistoryNoYes
AliasesNoYes

Example:

# This works in bash but not sh
declare -A person=(["name"]="Alice" ["age"]="30")

Q2: What is the shebang and why is it important?

Answer:

The shebang (#!) is the first line of a script telling the system which interpreter to use:

#!/bin/bash              # Use bash
#!/bin/sh # Use sh (POSIX)
#!/usr/bin/env python3 # Use python3

Importance:

  • Specifies interpreter explicitly
  • Makes script portable (uses env to find interpreter)
  • Allows ./script.sh execution without calling bash directly
  • Enables proper script permissions handling

Best practice:

#!/usr/bin/env bash      # More portable than #!/bin/bash

Q3: How do you declare and use variables in Bash?

Answer:

Bash variables are dynamically typed and don't require declaration:

# Simple assignment
name="Alice"
age=30
path="/home/user"

# Access with $ or ${}
echo $name # Alice
echo ${name} # Alice (explicit braces)

# Only ${} handles word boundaries correctly
echo ${name}_project # Alice_project
echo $name_project # Searches for $name_project (undefined)

Variable scope:

global_var="global"

function test_scope() {
local local_var="local" # Only visible in function
global_var="modified" # Modifies global
}

test_scope
echo $global_var # modified
echo $local_var # empty

Q4: What are the different types of quotes and their differences?

Answer:

Double quotes ("...") — Allows expansion:

greeting="Hello, $USER"
echo "$greeting" # Expands $USER
result=$(date)
echo "$result" # Expands command substitution

Single quotes ('...') — No expansion:

path='$HOME/documents'
echo "$path" # Outputs: $HOME/documents
command='echo "hello"'
echo "$command" # Outputs: echo "hello"

No quotes — Subject to word splitting and globbing:

files=*.txt              # Expands *.txt to filenames

Difference summary:

TypeExpansionWord SplittingUse Case
" "YesNoVariables, command substitution
' 'NoNoLiteral strings, regex patterns
NoneYesYesFile globbing, word splitting

Control Structures

Q5: Explain the difference between [ ] and [[ ]] conditional expressions.

Answer:

[ ] (test command) is POSIX-compliant but limited. [[ ]] (compound command) is bash-specific and more powerful.

Key differences:

# Word splitting with [ ]
var="hello world"
[ $var = "hello" ] # Error: too many arguments
[ "$var" = "hello" ] # OK (requires quotes)

# No word splitting with [[ ]]
[[ $var = hello* ]] # OK (pattern matching)
[[ $var == "hello" ]] # OK (without quotes)

# Regex matching (only in [[ ]])
[[ $var =~ ^h[a-z]+ ]] # OK
[ $var =~ pattern ] # Error

# Logical operators
[[ $x -gt 5 && $y -lt 10 ]] # OK (bash syntax)
[ $x -gt 5 -a $y -lt 10 ] # OK (POSIX syntax)

When to use:

  • [ ] — For POSIX compatibility across shells
  • [[ ]] — For bash-specific scripts (safer, more readable)

Q6: What is parameter expansion and how is it used?

Answer:

Parameter expansion modifies variable values during expansion:

# Default values
echo ${name:-"default"} # Use default if empty
echo ${name:="default"} # Set AND use default if empty

# Remove patterns
filename="document.pdf"
echo ${filename%.pdf} # Remove suffix: document
echo ${filename#*o} # Remove prefix up to 'o': cument.pdf

# Substring
str="Hello, World!"
echo ${str:0:5} # First 5 chars: Hello
echo ${str:7} # From position 7: World!

# Case conversion (bash 4+)
echo ${str,,} # Lowercase
echo ${str^^} # UPPERCASE

# Replace
text="foo bar foo"
echo ${text/foo/FOO} # First: FOO bar foo
echo ${text//foo/FOO} # All: FOO bar FOO

# Length
echo ${#str} # 13 (character count)

# Error on unset
echo ${required?Error message} # Fails if required unset

Q7: What's the difference between return and exit codes?

Answer:

return exits a function, exit exits the script. Both set exit code (0 = success, non-zero = failure).

check_positive() {
if [ $1 -gt 0 ]; then
return 0 # Success in function
else
return 1 # Failure in function
fi
}

if check_positive 5; then
echo "Positive"
fi

# Exit code stored in $?
check_positive 5
echo $? # 0

# Exit from script
if [ ! -f "$required_file" ]; then
echo "Error: file not found"
exit 1 # Exit script with code 1
fi

# Exit code in main context
echo "Done"
exit 0 # Explicit success

Common exit codes:

CodeMeaning
0Success
1General error
2Misuse of shell builtin
126Command not executable
127Command not found
128+NSignal N (e.g., 128+9=137 for SIGKILL)
130Script terminated by Ctrl+C (SIGINT)

Advanced Concepts

Q8: What is command substitution and how does it work?

Answer:

Command substitution captures command output into a variable using $() or backticks:

# Modern syntax (preferred)
current_user=$(whoami)
today=$(date +%Y-%m-%d)

# Backtick syntax (legacy, avoid)
user=`whoami`

# Nesting capability
files=$(ls -l $(find . -name "*.txt"))

# In compound commands
if [ $(whoami) = "root" ]; then
echo "Running as root"
fi

# Loop over lines
for line in $(cat file.txt); do
process "$line"
done

# Performance consideration: command substitution waits for completion
start=$(date +%s)
sleep 2
end=$(date +%s)
echo "Elapsed: $((end - start))"

Note: Use quotes to preserve whitespace:

output=$(ls -la)
echo "$output" # Preserves formatting
echo $output # Loses whitespace

Q9: Explain process substitution and provide examples.

Answer:

Process substitution treats command output as a file using <() or >():

# Compare sorted outputs
diff <(sort file1.txt) <(sort file2.txt)

# Paste output from two commands
paste <(seq 1 5) <(seq 10 14)

# Read multiple inputs into command
while IFS=: read user password uid rest; do
echo "$user: $uid"
done < <(grep admin /etc/passwd)

# Output to multiple places
tee >(cat > file1.txt) >(cat > file2.txt) <<< "content"

vs. Command substitution:

# Command substitution (waits for completion)
result=$(long_running_command)

# Process substitution (runs in background)
while read line; do
process "$line"
done < <(long_running_command)

Q10: What is a here document and when would you use it?

Answer:

A here document (<<) provides multi-line input to a command:

# Prevent expansion with quoted delimiter
cat << 'EOF'
Variables like $PATH are not expanded
Backticks `date` are literal
EOF

# Allow expansion with unquoted delimiter
cat << EOF
User: $USER
Home: $HOME
Date: $(date)
EOF

# Common use case: send email
mail -s "Report" admin@example.com << 'EOF'
Daily backup completed successfully.
All systems operational.
EOF

# SQL queries
mysql -u user -p password database << 'SQL'
SELECT * FROM users WHERE id > 100;
UPDATE users SET status='active' WHERE id = 200;
SQL

# Configuration files
cat > /etc/config.conf << 'CONFIG'
setting1=value1
setting2=value2
CONFIG

# Redirect to variable
config=$(cat << 'CONF'
# Configuration
debug=true
loglevel=info
CONF
)

Q11: Explain arrays in Bash and their uses.

Answer:

Indexed arrays (0-based):

# Declaration methods
colors=("red" "green" "blue")
colors[0]="red"
colors[1]="green"

# Access elements
echo ${colors[0]} # red
echo ${colors[@]} # All elements
echo ${colors[*]} # All elements (as single word)
echo ${#colors[@]} # Array length (3)

# Iteration
for color in "${colors[@]}"; do
echo "Color: $color"
done

# Add element
colors[3]="yellow"
colors+=("purple") # Append

# Delete element
unset colors[1]

Associative arrays (key-value, bash 4+):

declare -A person
person[name]="Alice"
person[age]="30"
person[city]="NYC"

# Access
echo ${person[name]} # Alice

# Iterate keys
for key in "${!person[@]}"; do
echo "$key: ${person[$key]}"
done

# Iterate values
for value in "${person[@]}"; do
echo "Value: $value"
done

# Check key exists
if [[ -v person[name] ]]; then
echo "Name is set"
fi

# Delete
unset person[city]

Common operations:

# Find element
arr=("a" "b" "c" "b")
search="b"
for i in "${!arr[@]}"; do
if [ "${arr[i]}" = "$search" ]; then
echo "Found at index $i"
fi
done

# Get array length
length=${#arr[@]}

# Get element count
count=0
for _ in "${arr[@]}"; do
((count++))
done

Q12: What is the purpose of trap and how do you use it?

Answer:

trap catches signals and system events for cleanup and error handling:

# Basic trap for cleanup
cleanup() {
rm -f /tmp/tempfile_$$
echo "Cleaned up"
}
trap cleanup EXIT

# Create temporary file
temp="/tmp/tempfile_$$"
touch "$temp"

# Trap multiple signals
shutdown() {
echo "Received signal, shutting down gracefully"
exit 0
}
trap shutdown INT TERM

# Error handling
error_handler() {
echo "Error occurred on line $1"
exit 1
}
trap 'error_handler $LINENO' ERR

# Ignore signal
trap '' INT # Ignore Ctrl+C

# Reset trap
trap - EXIT # Remove EXIT trap

# Debug trap
trap 'echo "Line $LINENO"' DEBUG

Signal reference:

SignalNumberMeaning
INT2Ctrl+C
TERM15Termination request
EXIT0Script exit
ERR-Command error
DEBUG-Before each command
KILL9Force kill (cannot trap)

Q13: Explain getopts and how to parse command-line arguments.

Answer:

getopts parses command-line options safely and portably:

#!/bin/bash

verbose=false
output_file=""
input_file=""

# Parse options
while getopts "vo:i:" opt; do
case $opt in
v)
verbose=true
;;
o)
output_file="$OPTARG"
;;
i)
input_file="$OPTARG"
;;
*)
echo "Usage: $0 [-v] [-o FILE] [-i FILE] [args...]"
exit 1
;;
esac
done

# Remove parsed options from arguments
shift $((OPTIND - 1))

# Remaining arguments
remaining_args="$@"

# Validation
if [[ -z "$input_file" ]]; then
echo "Error: -i is required"
exit 1
fi

echo "Verbose: $verbose"
echo "Output: $output_file"
echo "Input: $input_file"
echo "Remaining: $remaining_args"

Usage examples:

./script.sh -v -o output.txt -i input.txt file1 file2
./script.sh -vi input.txt -o output.txt
./script.sh -i input.txt

Benefits over manual parsing:

  • POSIX-compliant
  • Handles option combinations (-vi equals -v -i)
  • Tracks parsed options automatically
  • Error handling built-in

Functions and Scope

Q14: How do variable scope and local variables work?

Answer:

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

global_var="I'm global"

function test_scope() {
local local_var="I'm local"
global_var="Modified" # Modifies global
echo "Inside: $global_var, $local_var"
}

echo "Before: $global_var, $local_var" # I'm global, (empty)
test_scope
echo "After: $global_var, $local_var" # Modified, (empty)

Practical example:

# Without local (bugs possible)
counter=0
increment_bad() {
counter=$((counter + 1))
local temp=$((counter * 2))
return $temp # Can't return temp>255
}

# With proper scoping
counter=0
increment_good() {
local local_counter=$((++counter)) # Doesn't affect global
return $local_counter
}

# Better: return value via stdout
get_sum() {
local a=$1 b=$2
echo $((a + b))
}
result=$(get_sum 5 3)
echo "Sum: $result"

Q15: What are the differences between functions and sourced scripts?

Answer:

Functions are defined in memory and share the current shell's variables:

my_function() {
local var="local to function"
global_var="I modify globals"
echo "In function"
}

# Call function
my_function
echo $global_var # "I modify globals"

Sourced scripts (. file or source file) execute in the current shell:

# In helper.sh
helper_var="set by sourced script"
helper_function() {
echo "Helper function"
}

# In main script
source ./helper.sh # Or . ./helper.sh
echo $helper_var # "set by sourced script"
helper_function # Works

External scripts (run as ./script.sh) execute in a subshell:

# Subshell doesn't share variables
export_var="exported"
./script.sh $export_var # Passes via argument, can't modify original

Key differences:

AspectFunctionSourcedSubprocess
ScopeSame shellSame shellNew shell
Modify parent varsYesYesNo
PerformanceFastFastSlower
Use caseCode reuseConfig filesExternal tools

Pattern Matching and Regex

Q16: How do you use regular expressions in Bash?

Answer:

Extended regex with [[ ]]:

# Basic pattern matching
if [[ $string =~ ^hello ]]; then
echo "Starts with hello"
fi

# Capture groups
if [[ "user@example.com" =~ ^([a-z]+)@([a-z.]+)$ ]]; then
echo "User: ${BASH_REMATCH[1]}"
echo "Domain: ${BASH_REMATCH[2]}"
fi

# Character classes
[[ $var =~ [0-9]+ ]] # Contains digits
[[ $var =~ ^[a-zA-Z_] ]] # Starts with letter/underscore
[[ $var =~ [[:space:]] ]] # Contains whitespace
[[ $var =~ [[:digit:]] ]] # Contains digit

With grep:

# Basic regex
grep "pattern" file.txt

# Extended regex (ERE)
grep -E "pattern1|pattern2" file.txt

# Perl regex (if available)
grep -P "(?=.*digit)(?=.*letter)" file.txt

# Invert match
grep -v "excluded" file.txt

With sed and awk:

# sed regex
sed 's/\([0-9]\+\)-\([0-9]\+\)/[\1,\2]/' file.txt

# awk regex
awk '/^2024/ {print}' log.txt
awk '{gsub(/foo/, "bar"); print}' file.txt

Validation examples:

# Email validation (simplified)
is_email() {
[[ $1 =~ ^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-z]{2,}$ ]]
}

# IP address validation
is_ip() {
[[ $1 =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]
}

# URL validation
is_url() {
[[ $1 =~ ^https?:// ]]
}

Q17: How do you use sed for text transformation?

Answer:

sed is a stream editor for filtering and transforming text:

# Substitution (s command)
sed 's/old/new/' file.txt # Replace first occurrence per line
sed 's/old/new/g' file.txt # Replace all occurrences
sed 's/old/new/2' file.txt # Replace second occurrence
sed 's/old/new/i' file.txt # Case-insensitive

# Delete (d command)
sed '5d' file.txt # Delete line 5
sed '1,5d' file.txt # Delete lines 1-5
sed '/pattern/d' file.txt # Delete matching lines
sed '$d' file.txt # Delete last line

# Print (p command) - use with -n
sed -n '10p' file.txt # Print line 10
sed -n '/pattern/p' file.txt # Print matching lines
sed -n '1,5p' file.txt # Print lines 1-5

# In-place editing
sed -i 's/old/new/g' file.txt # Modify file directly
sed -i.bak 's/old/new/g' file.txt # Backup original

# Address ranges
sed '/start/,/end/d' file.txt # Delete from start to end
sed '/start/,/end/s/old/new/' file.txt # Replace in range

# Multiple commands
sed -e 's/foo/bar/' -e 's/hello/hi/' file.txt

Advanced examples:

# Add prefix to each line
sed 's/^/PREFIX: /' file.txt

# Add suffix to each line
sed 's/$/ :SUFFIX/' file.txt

# Swap two fields (CSV)
sed 's/\([^,]*\),\([^,]*\)/\2,\1/' file.csv

# Remove leading whitespace
sed 's/^[ \t]*//' file.txt

# Number lines
sed = file.txt | sed 'N;s/\n/: /'

Q18: Explain awk and common use cases.

Answer:

awk is a powerful text processing language for structured data:

# Print columns
awk '{print $1, $3}' file.txt # Columns 1 and 3
awk -F: '{print $1}' /etc/passwd # With separator

# Filtering
awk '$2 > 100' file.txt # Rows where column 2 > 100
awk '/pattern/ {print}' file.txt # Print matching lines
awk 'NR > 5 && NR < 10' file.txt # Lines 6-9

# Calculations
awk '{sum += $1} END {print sum}' file.txt # Sum column
awk '{print $1 * 2}' file.txt # Double column
awk '{sum += $1; count++} END {print sum/count}' file.txt # Average

# String functions
awk '{print toupper($1)}' file.txt # Uppercase
awk '{print length($0)}' file.txt # Line length
awk '{gsub(/old/, "new"); print}' file.txt # Replace all

# Built-in variables
awk '{print NR, NF, $0}' file.txt # Line #, field count, line
awk 'BEGIN {print FILENAME} {print}' file.txt # Filename

Practical examples:

# Sum column 3, grouped by column 1
awk '{sum[$1] += $3} END {for (key in sum) print key, sum[key]}' file.txt

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

# Extract and format
awk -F: '{printf "%s is user %d\n", $1, $3}' /etc/passwd

# Conditional processing
awk '{
if ($1 == "ERROR") {
print "Found error on line", NR
}
}' log.txt

# Multiple conditions
awk '$2 > 50 && $3 < 100 {print $0}' file.txt

I/O and Redirection

Q19: Explain input/output redirection in Bash.

Answer:

Bash provides flexible redirection of standard streams:

# Standard output (1) redirection
command > file.txt # Overwrite file
command >> file.txt # Append to file
command 1> file.txt # Explicit stdout (1)

# Standard error (2) redirection
command 2> errors.txt # Redirect errors
command 2>> errors.txt # Append errors

# Redirect both
command > out.txt 2> err.txt # Separate files
command > all.txt 2>&1 # Combine to one file
command &> all.txt # Shorthand for 2>&1

# Input redirection
command < input.txt # Read from file
command 0< input.txt # Explicit stdin (0)

# Here strings (<<<)
grep "pattern" <<< "text" # Pass string as input

# Here documents (<<)
cat << 'EOF'
Multi-line
input
EOF

# Null redirection
command >/dev/null 2>&1 # Suppress all output

# Duplicate file descriptors
command 2>&1 | tee output.txt # Copy to file and stdout
command > >(tee output.txt) # Process substitution

File descriptor reference:

FDNameDefault
0stdinkeyboard
1stdoutterminal
2stderrterminal

Q20: How do you read input in Bash scripts?

Answer:

Using read builtin:

# Simple read
read name
echo "Hello, $name"

# With prompt
read -p "Enter name: " name

# Multiple variables
read first last email
echo "$first $last <$email>"

# Default value
read -p "Name [default]: " name
name="${name:-default}"

# Limit input length
read -n 5 -p "5 chars: " input

# Timeout (10 seconds)
read -t 10 -p "Answer (10s): " answer

# Array input
read -a array
echo ${array[@]}

# Suppress echo (password)
read -sp "Password: " pass

Reading files line-by-line:

# Standard approach
while IFS= read -r line; do
echo "Line: $line"
done < file.txt

# With field separator
while IFS=: read -r user password uid gid rest; do
echo "User: $user, UID: $uid"
done < /etc/passwd

# Process substitution (for subshells)
while read line; do
process "$line"
done < <(some_command)

# Command output
some_command | while read line; do
process "$line"
done

Edge cases:

# Preserve whitespace
IFS= read -r line # Trim internal spaces: IFS=""

# Read binary data safely
read -r -d '' -n $size data < file.bin

# Check read success
if read -t 5 var; then
echo "Got input: $var"
else
echo "Timeout or error"
fi

Error Handling and Debugging

Q21: How do you implement error handling in Bash?

Answer:

Set error flags:

#!/bin/bash
set -e # Exit on error
set -u # Error on undefined variables
set -o pipefail # Pipe errors propagate
set -E # Trap inheritance in functions

Trap errors:

error_handler() {
echo "Error on line $1"
exit 1
}
trap 'error_handler $LINENO' ERR

# Function that fails
might_fail() {
return 1
}

might_fail # Triggers error handler

Check exit codes:

if ! command; then
echo "Command failed with code $?"
exit 1
fi

# Or use ||
command || {
echo "Error: command failed"
exit 1
}

# Use && for success
command && echo "Success" || echo "Failed"

Validate inputs:

check_args() {
if [ $# -ne 2 ]; then
echo "Usage: $0 <arg1> <arg2>"
exit 1
fi

if [ -z "$1" ]; then
echo "Error: arg1 cannot be empty"
exit 1
fi

if [ ! -f "$2" ]; then
echo "Error: file '$2' not found"
exit 1
fi
}
check_args "$@"

Optional strict mode:

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

Q22: How do you debug Bash scripts?

Answer:

Debug modes:

# Run with -x (trace mode)
bash -x script.sh

# Enable inside script
set -x
commands...
set +x

# Verbose mode (-v shows before execution)
bash -v script.sh
set -v

# Check syntax without running
bash -n script.sh

Customize PS4 for better tracing:

PS4='+ [${BASH_SOURCE}:${LINENO}] ${FUNCNAME[0]}(): '
set -x

Debug specific sections:

debug() {
[[ "$DEBUG" == "true" ]] && echo "DEBUG: $@" >&2
}

DEBUG=true ./script.sh # Enable debugging
debug "This is a debug message"

Breakpoints and stepping:

# Pause and allow interaction (not true breakpoints)
set -i # Interactive
PS1='\u@\h:\w\$ '

# Or use bash debugger (bashdb)
bashdb script.sh

Print variable state:

print_vars() {
echo "=== Variables ==="
declare -p
}

print_functions() {
echo "=== Functions ==="
declare -F
}

Performance and Best Practices

Q23: How do you optimize Bash script performance?

Answer:

Avoid unnecessary subshells:

# Bad: spawns subshell
result=$(cat file.txt)

# Better: read into variable
IFS= read -r result < file.txt

# Bad: subshell for each iteration
for file in *; do
echo "$(cat "$file")"
done

# Better: use pipe (single process)
while IFS= read -r file; do
process "$file"
done < <(find . -type f)

Use builtins over external commands:

# Bad: calls external wc command
count=$(wc -l < file.txt)

# Better: use parameter expansion
lines="${line%%$'\n'*}" # Gets first line

# Bad: multiple external commands
user=$(echo "$line" | awk -F: '{print $1}')

# Better: bash parameter expansion
IFS=: read user _ < <(echo "$line")

Avoid pipes when possible:

# Bad: multiple processes
cat file.txt | grep pattern | awk '{print $1}'

# Better: single awk process
awk '/pattern/ {print $1}' file.txt

Cache results:

# Bad: recalculates each iteration
for i in {1..1000}; do
echo "$(($(date +%s) + i))"
done

# Better: calculate once
timestamp=$(date +%s)
for i in {1..1000}; do
echo "$((timestamp + i))"
done

Use appropriate data structures:

# Bad: grep in loop
for item in "${items[@]}"; do
if grep -q "$item" whitelist.txt; then
process "$item"
fi
done

# Better: load into array first
declare -A whitelist
while read item; do
whitelist["$item"]=1
done < whitelist.txt

for item in "${items[@]}"; do
if [[ -v whitelist["$item"] ]]; then
process "$item"
fi
done

Q24: What are Bash best practices?

Answer:

Code style:

#!/usr/bin/env bash        # Portable shebang
set -euo pipefail # Error handling
IFS=$'\n\t' # Safer IFS

# Meaningful variable names
declare username # Or local in functions
declare -i count # Type hints

# Consistent formatting
if [[ condition ]]; then
statement
fi

Quoting:

# Always quote variables
echo "$var" # Good
echo $var # Bad: word splitting

# Use [[ ]] for tests
[[ $var == pattern ]] # Good
[ $var = pattern ] # Old style

# Array iteration
for item in "${array[@]}"; do # Good
process "$item"
done

Functions:

# Use function keyword (optional but explicit)
function check_file() {
local file="$1"
[[ -f "$file" ]] || return 1
}

# Validate arguments
main() {
if [ $# -ne 1 ]; then
echo "Usage: $0 <arg>" >&2
return 1
fi
process "$1"
}

Comments:

#!/bin/bash
# Script purpose: brief description
# Usage: script.sh [-v] <file>

# Check if file exists and is readable
if [[ -r "$file" ]]; then
process_file "$file"
fi

Error handling:

# Use trap for cleanup
cleanup() {
[[ -n "${temp_dir:-}" ]] && rm -rf "$temp_dir"
}
trap cleanup EXIT

# Informative error messages
if [[ ! -f "$file" ]]; then
echo "ERROR: File not found: $file" >&2
return 1
fi

System Administration

Q25: How do you write a system monitoring script?

Answer:

#!/usr/bin/env bash

# Monitor CPU, memory, and disk
check_system() {
local threshold_cpu=80
local threshold_mem=85
local threshold_disk=90

# CPU usage (varies by system)
cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed 's/.*, \([0-9.]*\)%* id.*/\1/' | awk '{print 100 - $1}')
if (( $(echo "$cpu_usage > $threshold_cpu" | bc -l) )); then
echo "WARNING: CPU at ${cpu_usage}%"
fi

# Memory usage
mem_usage=$(free | awk 'NR==2{printf("%.0f", $3/$2 * 100)}')
if [ "$mem_usage" -gt "$threshold_mem" ]; then
echo "WARNING: Memory at ${mem_usage}%"
fi

# Disk usage
disk_usage=$(df / | awk 'NR==2{print $5}' | sed 's/%//')
if [ "$disk_usage" -gt "$threshold_disk" ]; then
echo "WARNING: Disk at ${disk_usage}%"
fi

# Load average
load=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | xargs)
cpu_count=$(nproc)
if (( $(echo "$load > $cpu_count" | bc -l) )); then
echo "WARNING: Load average $load exceeds CPU count"
fi
}

# Log output
main() {
local logfile="/var/log/system_monitor.log"
{
echo "[$(date)] System Check"
check_system
} | tee -a "$logfile"
}

main "$@"

Additional Questions

Q26: What's the difference between source and exec?

source executes in current shell (same PID), exec replaces current shell (same PID, different process).

Q27: How do you handle spaces in filenames?

Quote variables: "$file", use arrays: for f in "${array[@]}", use find -print0 with read -d ''

Q28: What is IFS and why is it important?

IFS (Internal Field Separator) controls how Bash splits words. Default is space/tab/newline. Critical for reading fields correctly.

Q29: How do you create multiline strings?

Use $'...' syntax, heredocs, or concatenation: var=$'line1\nline2'

Q30: How do you implement a simple retry logic?

retry() {
local n=1 max=5 delay=1
while true; do
"$@" && break || {
if [ $n -lt $max ]; then
echo "Attempt $n failed, retrying..."
sleep $((delay))
n=$((n + 1))
else
return 1
fi
}
done
}

Q31: How do you validate input types?

is_integer() { [[ $1 =~ ^-?[0-9]+$ ]]; }
is_float() { [[ $1 =~ ^-?[0-9]+\.[0-9]+$ ]]; }
is_email() { [[ $1 =~ ^[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-z]+$ ]]; }

Q32: How do you organize large Bash projects?

Use modular structure with sourced files, separate concerns (functions, config, main), document with comments, use consistent naming.

Q33: What are the pitfalls to avoid in Bash?

Don't rely on word splitting, quote variables, use [[ ]] over [ ], avoid eval, check exit codes, use meaningful names, avoid global variables.

Q34: How do you test Bash scripts?

Use bash -n for syntax, shellcheck for linting, write test functions, mock external commands, test edge cases.

Q35: How do you ensure script portability?

Use POSIX features, check sh compatibility, avoid bash-isms, test on multiple shells/systems, document requirements.


Key Takeaways

  • Fundamentals: Understand variables, quoting, and control structures
  • Advanced: Master process substitution, traps, and text processing
  • Error handling: Always validate inputs and check exit codes
  • Performance: Use builtins, avoid unnecessary subshells and pipes
  • Best practices: Quote variables, use [[ ]], add comments, handle errors gracefully

See Also