Shell Scripting

Shell Scripting Q&A (Beginner to Advanced)

A comprehensive question and answer guide for Shell Scripting — from the very basics to advanced topics. Use it to study, review, or prepare for interviews.


Beginner

Q: What is a shell?

A shell is a command-line interpreter that provides an interface between the user and the operating system kernel. It reads commands typed by the user and executes them. Common shells include bash, sh, zsh, fish, and dash.

Q: What is a shell script?

A shell script is a plain text file containing a sequence of shell commands that are executed in order. Shell scripts are used to automate repetitive tasks, manage files, and configure systems.

Q: What is a shebang and why is it important?

The shebang (#!) is the first line of a shell script that tells the operating system which interpreter to use to execute the script.

#!/bin/bash
echo "Hello, World!"

Without the shebang the script may be run with the wrong shell or fail to execute directly.

Q: How do you make a shell script executable?

chmod +x script.sh
./script.sh

chmod +x adds execute permission, and ./ runs it from the current directory.

Q: How do you print output to the terminal?

echo "Hello, World!"
printf "Name: %s\n" "Alice"

echo is simple and appends a newline automatically. printf gives more control over formatting (similar to C's printf).

Q: How do you define and use a variable?

name="Alice"
echo "Hello, ${name}"
  • No spaces around = when assigning.
  • Prefix the variable name with $ to read its value.

Q: What is the difference between single quotes and double quotes?

name="World"
echo 'Hello $name'   # prints: Hello $name  (no expansion)
echo "Hello $name"   # prints: Hello World  (variable expanded)

Single quotes treat everything literally. Double quotes allow variable and command substitution.

Q: How do you read input from the user?

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

read stores the input in a variable. -p displays a prompt before waiting for input.

Q: How do you write comments in a shell script?

# This is a single-line comment
echo "Running script..."  # inline comment

Only single-line comments (#) exist in shell. There is no native multi-line comment syntax, but a heredoc trick is sometimes used.

Q: How do you perform arithmetic in shell?

a=5
b=3
sum=$((a + b))
echo "Sum: $sum"        # prints: Sum: 8

# Alternative with expr
result=$(expr $a \* $b)
echo "Product: $result" # prints: Product: 15

$((...)) is the preferred modern syntax for integer arithmetic.

Q: What are common comparison operators for numbers?

Operator Meaning
-eq Equal
-ne Not equal
-lt Less than
-le Less than or equal
-gt Greater than
-ge Greater than or equal
if [ $a -gt $b ]; then
  echo "$a is greater than $b"
fi

Q: How do you write an if-else statement?

age=18
if [ $age -ge 18 ]; then
  echo "Adult"
elif [ $age -ge 13 ]; then
  echo "Teenager"
else
  echo "Child"
fi

Q: What are the basic loop types in shell?

# for loop
for i in 1 2 3 4 5; do
  echo "Number: $i"
done

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

# until loop (runs until condition becomes true)
until [ $count -gt 5 ]; do
  echo "Count: $count"
  ((count++))
done

Q: What is $0, $1, $2 … $@ and $#?

#!/bin/bash
echo "Script name : $0"
echo "First arg   : $1"
echo "Second arg  : $2"
echo "All args    : $@"
echo "Arg count   : $#"
Variable Meaning
$0 Name of the script
$1 to $9 Positional parameters (arguments)
$@ All arguments as separate strings
$* All arguments as a single string
$# Number of arguments

Q: What does $? represent?

$? holds the exit status (return code) of the last executed command. 0 means success; any non-zero value means failure.

ls /nonexistent
echo "Exit code: $?"  # prints a non-zero value (e.g. 2)

Intermediate

Q: What is the difference between [ ] and [[ ]]?

[ ] is the POSIX-compliant test command available in all shells. [[ ]] is a bash/ksh/zsh extension that is more powerful and less error-prone.

# [[ ]] supports regex matching and logical operators without quoting issues
name="Alice"
if [[ $name == A* ]]; then
  echo "Name starts with A"
fi

Prefer [[ ]] in bash scripts for safety and additional features.

Q: How do you define and call a function?

greet() {
  local name="$1"
  echo "Hello, $name!"
}

greet "Alice"
greet "Bob"

Use local to limit a variable's scope to the function. Functions return an exit code (0–255), not a value — use echo to return data.

Q: How do you capture the output of a command?

# Command substitution
today=$(date +%Y-%m-%d)
echo "Today is: $today"

# Backtick style (older, avoid)
files=`ls`

Prefer $(...) over backticks — it is more readable and supports nesting.

Q: What is a heredoc and when do you use it?

A heredoc lets you pass a multi-line string to a command without creating a temporary file.

cat <<EOF
Line 1
Line 2
Today is $(date)
EOF

Use <<'EOF' (single-quoted delimiter) to disable variable expansion inside the heredoc.

Q: How do arrays work in bash?

# Declare an array
fruits=("apple" "banana" "cherry")

echo "${fruits[0]}"     # apple
echo "${fruits[@]}"     # all elements
echo "${#fruits[@]}"    # number of elements

# Append an element
fruits+=("date")

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

Q: What is the difference between $@ and $* in arrays and functions?

When double-quoted:

  • "$@" expands each element as a separate quoted word — safe for filenames with spaces.
  • "$*" expands all elements as a single word joined by the first character of IFS.

Always prefer "$@" when iterating over arguments or array elements.

Q: What are special variables $$ and $!?

Variable Meaning
$$ PID of the current shell/script
$! PID of the last background command
$- Current shell option flags
$_ Last argument of the previous command

Q: How do you handle errors and exit codes?

#!/bin/bash
set -e          # exit immediately on error
set -u          # treat unset variables as errors
set -o pipefail # catch errors in pipelines

cp source.txt dest.txt || { echo "Copy failed"; exit 1; }

Using set -euo pipefail at the top of scripts is considered best practice.

Q: What is process substitution?

Process substitution allows you to treat the output of a command as a file.

# Compare output of two commands
diff <(ls dir1) <(ls dir2)

# Read from a process as if it were a file
while IFS= read -r line; do
  echo "Line: $line"
done < <(grep "ERROR" logfile.txt)

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

source script.sh  # or: . script.sh
./script.sh
  • source / . runs the script in the current shell — variable changes persist.
  • ./script.sh runs the script in a subshell — changes do not affect the parent shell.

Q: How do you work with files and directories?

# Test conditions
[ -f file.txt ]   && echo "Is a regular file"
[ -d /tmp ]       && echo "Is a directory"
[ -r file.txt ]   && echo "Is readable"
[ -w file.txt ]   && echo "Is writable"
[ -x script.sh ]  && echo "Is executable"
[ -s file.txt ]   && echo "Is non-empty"
[ -e path ]       && echo "Exists"

Q: How does string manipulation work in bash?

str="Hello, World!"

echo "${#str}"           # Length: 13
echo "${str:7:5}"        # Substring: World
echo "${str,,}"          # Lowercase: hello, world!
echo "${str^^}"          # Uppercase: HELLO, WORLD!
echo "${str/World/Bash}" # Replace first: Hello, Bash!
echo "${str//l/L}"       # Replace all:   HeLLo, WorLd!
echo "${str#Hello, }"    # Remove prefix: World!
echo "${str%!}"          # Remove suffix: Hello, World

Q: How do you use case statements?

read -p "Enter a fruit: " fruit
case "$fruit" in
  apple)
    echo "You chose apple."
    ;;
  banana | mango)
    echo "You chose a tropical fruit."
    ;;
  *)
    echo "Unknown fruit."
    ;;
esac

Q: What is IFS and how does it affect word splitting?

IFS (Internal Field Separator) controls how bash splits words. Default value is space, tab, and newline.

IFS=',' read -ra parts <<< "one,two,three"
for part in "${parts[@]}"; do
  echo "$part"
done

Always restore IFS after changing it to avoid side effects.


Advanced

Q: What is the difference between soft links and hard links?

ln -s target.txt symlink.txt  # soft (symbolic) link
ln   target.txt hardlink.txt  # hard link
  • A soft link is a pointer to a path — it breaks if the target is deleted or moved.
  • A hard link is another directory entry pointing to the same inode — it survives deletion of the original filename.

Q: How do you write robust scripts using traps?

trap lets you catch signals and errors to perform cleanup before the script exits.

#!/bin/bash
tmpfile=$(mktemp)

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

trap cleanup EXIT        # runs on any exit
trap 'echo "Interrupted"' INT  # runs on Ctrl+C (SIGINT)

echo "Working..." > "$tmpfile"
# ... rest of script ...

Q: How do named pipes (FIFOs) work?

mkfifo mypipe
echo "Hello from producer" > mypipe &   # writer (runs in background)
cat mypipe                               # reader (blocks until writer writes)
rm mypipe

Named pipes allow inter-process communication between unrelated processes.

Q: How do you implement a mutex/lock in shell scripts?

LOCKFILE="/tmp/myscript.lock"

acquire_lock() {
  if ! mkdir "$LOCKFILE" 2>/dev/null; then
    echo "Script is already running. Exiting."
    exit 1
  fi
}

release_lock() {
  rmdir "$LOCKFILE"
}

trap release_lock EXIT
acquire_lock

echo "Critical section running..."

Using mkdir for locking is atomic on most file systems, making it safer than touch.

Q: How do you run commands in parallel and wait for them?

#!/bin/bash
pids=()

for i in 1 2 3 4; do
  sleep "$i" &
  pids+=($!)
done

for pid in "${pids[@]}"; do
  wait "$pid" && echo "PID $pid finished OK" || echo "PID $pid failed"
done

& runs a command in the background, wait waits for it, $! captures its PID.

Q: What is a subshell and how does it affect variable scope?

A subshell is a child process created by the current shell. Variables set in a subshell are not visible in the parent.

x=10
(
  x=20
  echo "Inside subshell: x=$x"   # 20
)
echo "Outside subshell: x=$x"    # 10 — unchanged

Subshells are created by (...), pipelines, command substitution, and background jobs.

Q: How do you handle long options and flags with getopts / getopt?

#!/bin/bash
usage() { echo "Usage: $0 [-n name] [-v]" >&2; exit 1; }

verbose=0
name=""

while getopts ":n:v" opt; do
  case $opt in
    n) name="$OPTARG" ;;
    v) verbose=1 ;;
    :) echo "Option -$OPTARG requires an argument." >&2; usage ;;
   \?) echo "Invalid option: -$OPTARG" >&2; usage ;;
  esac
done

shift $((OPTIND - 1))
echo "Name: $name, Verbose: $verbose, Remaining args: $*"

getopts is POSIX-compliant and built-in. Use getopt (external) for long options (--name).

Q: How does coproc work in bash?

coproc creates a coprocess — a background process with bidirectional pipes to the current shell.

coproc my_proc { cat; }

echo "Hello" >&"${my_proc[1]}"   # write to coprocess stdin
read line <&"${my_proc[0]}"      # read from coprocess stdout
echo "Got: $line"

Useful when you need a persistent two-way communication channel with a child process.

Q: How do you use associative arrays (hash maps)?

Associative arrays require bash 4.0+.

declare -A capitals

capitals["France"]="Paris"
capitals["Germany"]="Berlin"
capitals["Japan"]="Tokyo"

echo "${capitals["France"]}"       # Paris

# Iterate over key-value pairs
for country in "${!capitals[@]}"; do
  echo "$country -> ${capitals[$country]}"
done

Q: How do you profile and debug a shell script?

#!/bin/bash
set -x    # print each command and its expanded form before execution
set -v    # print shell input lines as they are read

# Measure execution time of a section
time {
  for i in $(seq 1 1000); do
    :
  done
}

You can also run a script with bash -x script.sh without modifying it. Use PS4='+(${BASH_SOURCE}:${LINENO}): ' to include file and line number in trace output.

Q: What are the differences between exec, fork, and source in shell?

Mechanism Description
fork Shell creates a child process; parent waits for it to finish
exec Replaces the current process image — no child is created
source/. Runs commands in the current shell process; no new process created
exec /bin/bash    # replaces current shell — the original process is gone

Q: How do you write a self-contained script that detects its own location?

#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "Script is located in: $SCRIPT_DIR"

This works correctly even when the script is sourced, symlinked, or called from a different directory.

Q: What are some security best practices for shell scripting?

  1. Always quote variables"$var" prevents word splitting and globbing issues.
  2. Validate input — never trust user-supplied data.
  3. Avoid eval — it executes arbitrary code and is a common injection vector.
  4. Use absolute paths for critical commands in scripts run as root.
  5. Restrict permissions — scripts that contain secrets should not be world-readable.
  6. Use set -euo pipefail — catch errors early.
  7. Sanitize filenames — use -- to separate options from filenames (e.g., rm -- "$file").
  8. Prefer mktemp for temporary files instead of predictable names.
# Unsafe
tmpfile="/tmp/myapp-$$"

# Safe
tmpfile=$(mktemp /tmp/myapp-XXXXXX)