Top 60 Shell Scripting Interview Questions (2026)

The 60 shell scripting questions interviewers actually ask, with direct answers, runnable Bash, and what the interviewer is listening for. Grouped for freshers, intermediate, and experienced rounds.

60 questions with answers

What Is Shell Scripting?

Key Takeaways

  • A shell script is a plain-text file of shell commands the interpreter runs top to bottom, used to automate repetitive command-line work.
  • Bash is the default shell on most Linux systems, so interview answers assume Bash unless the question says otherwise.
  • Interviews test how you handle quoting, exit codes, variables, and text processing with grep, sed, and awk, not just syntax recall.
  • This page is a question bank: work your tier first, read one tier up, and say answers out loud because delivery is evaluated too.

A shell script is a file that lists commands for a command-line interpreter to run in sequence, which turns a set of manual terminal steps into one repeatable program. The shell reads each line, expands variables and globs, and runs the resulting commands, so scripting is really the same shell you type into, saved to a file and made executable. Bash (the Bourne Again Shell) is the default interactive shell on most Linux distributions, and its behavior is defined in the GNU Bash Reference Manual, the canonical source these answers follow. In interviews, shell questions probe the parts that bite in real scripts: quoting and word splitting, exit codes and error handling, variable expansion, conditionals and loops, and the text-processing trio of grep, sed, and awk. This page collects the 60 questions that come up most, each with a direct answer and runnable code. If you're building fundamentals, the Bash manual is the reference to keep open; increasingly the first round is a live AI coding interview, so pair this bank with our AI interview preparation guides for the format side.

60Questions with answers on this page
3Difficulty tiers: freshers, intermediate, experienced
BashDefault shell these answers assume
30-45 minTypical length of a shell scripting round

Watch: Bash Scripting Tutorial for Beginners

Video: Bash Scripting Tutorial for Beginners (freeCodeCamp.org, YouTube)

Test yourself and earn a certificate

10 quick questions. Score 70%+ to download your Shell Scripting certificate.

Jump to quiz

All Questions on This Page

60 questions
Shell Scripting Interview Questions for Freshers
  1. 1. What is a shell script and why do teams use them?
  2. 2. What is the shebang line and why does it matter?
  3. 3. What is the difference between single and double quotes?
  4. 4. How do you declare and use variables in a shell script?
  5. 5. What are positional parameters like $1, $2, and $@?
  6. 6. What is an exit status and how do you read it?
  7. 7. How do conditionals work in shell scripts?
  8. 8. What loop types does the shell support?
  9. 9. How do you read user input in a script?
  10. 10. What is command substitution?
  11. 11. What is the difference between echo and printf?
  12. 12. How do you write comments in a shell script?
  13. 13. How do you make a script executable and run it?
  14. 14. How do you compare strings and numbers in tests?
  15. 15. What are file test operators?
  16. 16. How do you do arithmetic in the shell?
  17. 17. What are pipes and redirection?
  18. 18. What are stdout, stderr, and stdin?
  19. 19. What does grep do and how do you use it?
  20. 20. What is globbing and how does it differ from regex?
  21. 21. What are environment variables and how do you set them?
  22. 22. What do the special variables $$, $!, and $- mean?
  23. 23. How do you give a variable a default value if it is unset?
  24. 24. How do you define and call a function?
Shell Scripting Intermediate Interview Questions
  1. 25. What is sed and what is it good for?
  2. 26. What is awk and when do you reach for it?
  3. 27. When do you use grep vs sed vs awk?
  4. 28. What does set -euo pipefail do and why use it?
  5. 29. How do arrays work in Bash?
  6. 30. What is parameter expansion and what can it do?
  7. 31. What is a here-document (heredoc)?
  8. 32. What does trap do?
  9. 33. What is the difference between running a script and sourcing it?
  10. 34. What do cut, tr, sort, and uniq do?
  11. 35. What does xargs do?
  12. 36. How do you use find effectively?
  13. 37. When do you use a case statement?
  14. 38. How do you handle errors beyond set -e?
  15. 39. What is the difference between [ ], [[ ]], and (( ))?
  16. 40. What is process substitution?
  17. 41. How do you schedule a shell script to run automatically?
  18. 42. Why declare variables local inside functions?
  19. 43. How do you debug a shell script?
  20. 44. What is the correct way to read a file line by line?
Shell Scripting Interview Questions for Experienced Engineers
  1. 45. How do you write a portable script that runs on any POSIX shell?
  2. 46. Explain word splitting and how quoting prevents bugs.
  3. 47. What is shellcheck and how does it fit your workflow?
  4. 48. How do you handle signals for graceful shutdown in a long-running script?
  5. 49. How do you create temporary files safely?
  6. 50. What is a subshell and how does it affect variables and performance?
  7. 51. How do you handle floating-point math in shell scripts?
  8. 52. How do you parse command-line options in a script?
  9. 53. What makes a script idempotent and why does it matter?
  10. 54. What are the conventions for exit codes?
  11. 55. How do you process a very large file efficiently in shell?
  12. 56. How do you prevent two copies of a script from running at once?
  13. 57. What are the main security pitfalls in shell scripts?
  14. 58. When should you stop using shell and switch to a real programming language?
  15. 59. How can you use trap for debugging and error reporting?
  16. 60. What does eval do and why is it dangerous?

Shell Scripting Interview Questions for Freshers

Freshers24 questions

The fundamentals every entry-level round checks. If any answer here surprises you, that's your study list.

Q1. What is a shell script and why do teams use them?

A shell script is a text file of shell commands that the interpreter runs in order. It turns a set of manual terminal steps into one repeatable program you can run, schedule, and version.

Teams use them for automation that lives close to the system: backups, log rotation, deployment steps, batch file operations, and glue between other command-line tools. The shell is already on every Linux box, so there's nothing extra to install.

Key point: A one-line definition plus one concrete automation example beats a memorized feature list. Interviewers open with this to hear how you organize an answer.

Watch a deeper explanation

Video: Shell Scripting Crash Course: Beginner Level (Traversy Media, YouTube)

Q2. What is the shebang line and why does it matter?

The shebang is the first line, #!/bin/bash, that tells the kernel which interpreter should run the file. Without it, the script runs under whatever shell the caller happens to use, which can change behavior.

It's only honored when you execute the file directly (./script.sh). Running it as bash script.sh ignores the shebang because you've already named the interpreter.

bash
#!/bin/bash
# using /usr/bin/env finds bash on PATH, which is more portable
#!/usr/bin/env bash

echo "Running under: $BASH_VERSION"

Key point: #!/usr/bin/env bash for portability matters.

Q3. What is the difference between single and double quotes?

Double quotes allow variable expansion and command substitution while preventing word splitting and globbing. Single quotes are fully literal: nothing inside expands, so $HOME stays the text $HOME.

The practical rule: reach for double quotes around variables so values with spaces stay intact, and use single quotes when you want the text exactly as written.

bash
name="Asha"
echo "Hello $name"    # Hello Asha
echo 'Hello $name'    # Hello $name
echo "Today is $(date +%A)"   # command substitution works in double quotes
Single quotesDouble quotes
Variable expansionNoYes
Command substitutionNoYes
Word splittingPreventedPrevented
Typical useLiteral text, regexValues that hold spaces

Key point: The follow-up is usually 'why quote variables at all?'. Have the filename-with-spaces example ready.

Q4. How do you declare and use variables in a shell script?

Assign with name=value and no spaces around the equals sign, then read it with $name or ${name}. Everything is a string by default; there are no separate number or boolean types.

Spaces around = are the classic beginner error: x = 5 tries to run a command called x. Use the braces form ${name} when the name touches other text.

bash
count=3
greeting="hello"
echo "${greeting}, ${count} times"

# common mistake: this is NOT assignment
# count = 3   -> tries to run the command "count"

Key point: The no-spaces rule around = comes up constantly. State it before you're asked.

Q5. What are positional parameters like $1, $2, and $@?

Positional parameters hold the arguments passed to a script or function. $1 is the first argument, $2 the second, $0 is the script name, $# is the argument count, and $@ is all arguments.

Quote "$@" when passing arguments along, because it preserves each argument as a separate word even when values contain spaces. Unquoted $* joins everything into one string.

bash
#!/bin/bash
echo "Script: $0"
echo "First arg: $1"
echo "Count: $#"
for arg in "$@"; do
  echo "arg: $arg"
done

Key point: The distinction between "$@" and "$*" is a favorite. $@ keeps arguments separate, $* joins them.

Q6. What is an exit status and how do you read it?

Every command returns an exit status: 0 for success, any non-zero value for a specific failure. The special variable $? holds the status of the last command that ran.

Scripts use exit status to make decisions: run the next step only if the previous one succeeded. Your own scripts should exit non-zero on failure so callers can detect it.

bash
grep -q "error" app.log
if [ $? -eq 0 ]; then
  echo "found an error line"
fi

# cleaner: test the command directly
if grep -q "error" app.log; then
  echo "found an error line"
fi

Key point: Reading $? works, but testing the command directly in the if is the idiom interviewers prefer.

Q7. How do conditionals work in shell scripts?

if runs a command and branches on its exit status; the [ ] test command (or the Bash [[ ]] keyword) evaluates a condition and returns 0 or non-zero. elif and else add branches, and fi closes the block.

Numeric comparisons use -eq, -lt, -gt; string comparisons use = and !=. Spaces inside the brackets are required, another common early bug.

bash
score=72
if [ "$score" -ge 90 ]; then
  echo "A"
elif [ "$score" -ge 70 ]; then
  echo "B"
else
  echo "needs work"
fi

Q8. What loop types does the shell support?

for iterates over a list of words or a glob; while runs while a command succeeds; until runs while a command fails. C-style for ((i=0; i<5; i++)) also works in Bash.

The most common pattern is looping over files with a glob. Guard against no matches with nullglob or a test, because an unmatched glob stays literal by default.

bash
for file in *.log; do
  echo "processing $file"
done

i=0
while [ "$i" -lt 3 ]; do
  echo "count $i"
  i=$((i + 1))
done

Q9. How do you read user input in a script?

The read builtin captures a line from standard input into one or more variables. read -p shows a prompt, read -s hides typing (for passwords), and read -r stops backslashes from being interpreted.

Almost always use read -r; the plain form mangles backslashes, which surprises people processing file paths.

bash
read -rp "Enter your name: " name
echo "Hi, $name"

read -rsp "Password: " pass
echo
echo "captured ${#pass} characters"

Q10. What is command substitution?

Command substitution runs a command and replaces the expression with its standard output. The modern form is $(command); the older backtick form works but nests poorly and is harder to read.

It's how you capture output into a variable or feed one command's result into another line, like storing today's date or a file count.

bash
today=$(date +%Y-%m-%d)
file_count=$(ls -1 | wc -l)
echo "On $today there are $file_count files"

Key point: Preferring $() over backticks, and knowing backticks don't nest cleanly, is a small signal of real fluency.

Q11. What is the difference between echo and printf?

echo prints arguments with a trailing newline and is fine for simple messages, but its handling of flags and escape sequences varies between shells and systems. printf gives you a format string with predictable escapes and field formatting.

For anything beyond a plain line, especially formatted numbers or reliable escapes across systems, printf is the safer choice.

bash
echo "simple line"
printf "%-10s %5.2f\n" "price" 9.5
printf "%s\n" "one" "two" "three"   # each on its own line

Q12. How do you write comments in a shell script?

Anything after a # to the end of the line is a comment, except inside quotes and except the shebang on line one. There's no block-comment syntax.

A common trick for a multi-line block is a heredoc sent to the no-op colon command, but most scripts just prefix each line with #.

bash
# this whole line is a comment
echo "run" # trailing comment after a command

: <<'NOTE'
This block is ignored.
Useful for temporarily disabling a section.
NOTE

Q13. How do you make a script executable and run it?

Give the file execute permission with chmod +x script.sh, then run it as ./script.sh. Without execute permission you can still run it explicitly with bash script.sh.

The ./ prefix matters because the current directory usually isn't on PATH, which is a deliberate safety default so you don't run a stray script by name.

bash
chmod +x deploy.sh
./deploy.sh          # runs via the shebang
bash deploy.sh       # runs regardless of the execute bit

Q14. How do you compare strings and numbers in tests?

Strings use = and != (and < , > inside [[ ]] for lexical order). Numbers use -eq, -ne, -lt, -le, -gt, -ge. Mixing them up, like using -eq on strings, is a frequent bug.

Always quote string variables in a test so an empty value doesn't break the syntax. Inside [[ ]] the quoting is more forgiving, which is one reason it's preferred in Bash.

bash
name="admin"
if [ "$name" = "admin" ]; then echo "match"; fi

n=5
if [ "$n" -gt 3 ]; then echo "big enough"; fi
CompareString operatorNumber operator
Equal=-eq
Not equal!=-ne
Greater than> (in [[ ]])-gt
Less than< (in [[ ]])-lt

Q15. What are file test operators?

File test operators check attributes of a path: -f (regular file exists), -d (directory), -e (exists at all), -r / -w / -x (readable, writable, executable), and -s (exists and is non-empty).

They're how scripts decide whether to read a config, create a directory, or bail out early. Combine them with && or nest them in an if.

bash
config="/etc/app/config.yml"
if [ -f "$config" ]; then
  echo "loading $config"
else
  echo "no config, using defaults"
fi

[ -d "./logs" ] || mkdir logs

Q16. How do you do arithmetic in the shell?

Wrap integer math in $(( )): result=$((3 + 4)). Inside the double parentheses you don't need $ on variable names, and you get the usual operators plus ++ and +=.

The shell only does integer math natively. For decimals, pipe through bc or use awk, since $(( )) truncates.

bash
a=7
b=2
echo $((a + b))        # 9
echo $((a / b))        # 3  (integer division)
echo "scale=2; $a/$b" | bc   # 3.50  (decimals via bc)

Key point: $(( )) is integer-only, and reaching for bc or awk for decimals,.

Q17. What are pipes and redirection?

A pipe (|) sends one command's standard output into the next command's standard input, chaining tools into a stream. Redirection sends a stream to or from a file: > overwrites, >> appends, < reads input, and 2> captures errors.

This composition is the heart of shell work: small tools connected by pipes solve problems no single tool does alone.

bash
cat access.log | grep "404" | wc -l        # count 404s
sort names.txt | uniq > unique.txt          # dedupe to a file
command 2> errors.log                        # capture only errors

Watch a deeper explanation

Video: Beginner's Guide to the Bash Terminal (Joe Collins (EzeeLinux), YouTube)

Q18. What are stdout, stderr, and stdin?

Every process has three standard streams: stdin (0) for input, stdout (1) for normal output, and stderr (2) for error messages. Keeping errors on a separate stream lets you log or filter them independently of results.

That's why 2>&1 exists: it merges stderr into stdout so both go to the same place, useful when redirecting all output of a command to one log.

bash
ls /missing 2> err.txt              # errors to a file, results to screen
command > out.log 2>&1              # both streams to one file
echo "oops" >&2                     # write to stderr from your script

Q19. What does grep do and how do you use it?

grep searches text for lines matching a pattern and prints them. Common flags: -i (ignore case), -v (invert, show non-matches), -r (recurse directories), -n (show line numbers), and -c (count).

grep -E turns on extended regex for alternation and grouping; grep -F treats the pattern as a fixed string, which is faster and avoids regex surprises when searching for literal text.

bash
grep -in "error" app.log            # case-insensitive, with line numbers
grep -rc "TODO" ./src               # count matches per file, recursively
grep -Ev "^#|^$" config.conf        # skip comments and blank lines

Watch a deeper explanation

Video: Bash Tutorial: How to Use the Command Line in Linux, Windows, and Mac Terminal (freeCodeCamp.org, YouTube)

Q20. What is globbing and how does it differ from regex?

Globbing is the shell's filename expansion: * matches any run of characters, ? matches one character, and [abc] matches a character set. The shell expands globs before the command runs.

Globs are not regular expressions. * in a glob means 'any characters', but in regex it means 'zero or more of the previous item'. Confusing the two is a frequent source of bugs when moving between file matching and grep patterns.

bash
ls *.txt            # all files ending in .txt
ls report-?.csv     # report-1.csv, report-a.csv, one char
ls img[0-9].png     # img0.png through img9.png

Key point: The 'glob star vs regex star' distinction is a small trap that catches people who only know one context.

Q21. What are environment variables and how do you set them?

Environment variables are name-value pairs passed to child processes. A plain assignment stays local to the current shell; export makes it visible to programs the shell launches. PATH, HOME, and USER are familiar examples.

Setting VAR=value command runs one command with that variable set, without changing your session, which is handy for one-off overrides.

bash
export API_ENV="staging"      # visible to child processes
app_run                        # sees API_ENV

DEBUG=1 app_run                # set for just this one command

Q22. What do the special variables $$, $!, and $- mean?

$$ is the process id of the current shell, handy for unique temp names or logging. $! is the process id of the last command started in the background, which you use to wait on or signal it. $- lists the current shell option flags.

These sit alongside the argument variables ($0, $#, $@) and the exit status ($?). Knowing $! is what lets a script launch a background job and manage it later.

bash
echo "shell pid: $$"

sleep 30 &
bg_pid=$!
echo "started background job $bg_pid"
kill "$bg_pid"      # stop it using $!

Q23. How do you give a variable a default value if it is unset?

Use ${var:-default}, which yields default when var is unset or empty but doesn't change var. ${var:=default} also assigns the default back to var. The colon makes empty count as unset; drop it to treat only truly-unset as missing.

This is the clean way to handle optional arguments and environment overrides without a separate if block.

bash
env="${APP_ENV:-development}"     # use APP_ENV, else development
echo "running in $env"

: "${PORT:=8080}"                 # assign default back to PORT
echo "port is $PORT"

Key point: Knowing the difference between :- (use default) and := (assign default) is the follow-up. Have both ready.

Q24. How do you define and call a function?

Define with name() { ... } and call it by name, passing arguments that appear inside as $1, $2, and so on. Functions share the script's variables unless you mark them local.

A function returns an exit status with return (0 to 255), not a value. To hand back data, echo it and capture with command substitution.

bash
greet() {
  local who="$1"
  echo "Hello, $who"
}

greet "Asha"

message=$(greet "Ben")   # capture the output
echo "$message"

Key point: Knowing a function returns a status, not a value, and that you echo results, separates people who've written real scripts from those who haven't.

Back to question list

Shell Scripting Intermediate Interview Questions

Intermediate20 questions

For candidates with working experience: text processing, safer scripting, and the mechanics that separate users from understanders.

Q25. What is sed and what is it good for?

sed is a stream editor: it reads text line by line and applies edits, most often substitution with s/pattern/replacement/. It's the go-to for search-and-replace across a stream or file without opening an editor.

The g flag replaces every match on a line, not just the first; -i edits files in place; and & in the replacement re-inserts the matched text. sed works on whole streams, so it fits naturally in pipes.

bash
echo "hello world" | sed 's/world/there/'   # hello there
sed -i 's/localhost/127.0.0.1/g' config.conf   # replace in place, every match
sed -n '10,20p' big.log                        # print only lines 10 to 20

Key point: The g flag and -i are the two things interviewers check you know. Volunteer both.

Watch a deeper explanation

Video: Shell Scripting Tutorial (Derek Banas, YouTube)

Q26. What is awk and when do you reach for it?

awk is a text-processing language built around fields and records. It splits each line into fields ($1, $2, ...) and runs pattern-action rules, which makes column extraction, filtering, and quick aggregation one-liners.

Reach for awk when the data is columnar and you need arithmetic or grouping. It handles sums, averages, and per-key totals that grep and sed can't.

bash
awk '{ print $1, $3 }' data.txt              # print columns 1 and 3
awk -F',' '$2 > 100 { print $1 }' sales.csv   # comma-separated, filter on col 2
awk '{ total += $1 } END { print total }' nums.txt   # sum a column

Key point: The END block for totals is the awk feature that most impresses. Show it.

Watch a deeper explanation

Video: Bash Scripting Full Course 3 Hours (linuxhint, YouTube)

Q27. When do you use grep vs sed vs awk?

Use grep to find and print lines that match. Use sed to transform text, mostly substitution and line edits. Use awk when data is columnar and you need fields, arithmetic, or grouping.

They overlap, so the honest answer is about the simplest tool that fits: don't reach for awk to do a plain search, and don't chain five greps when one awk rule is clearer.

TaskToolWhy
Find matching linesgrepFast, purpose-built for search
Search and replacesedStream substitution with s///
Column extractionawkBuilt-in field splitting
Sums and per-key totalsawkArithmetic and associative arrays

Key point: Interviewers love the 'simplest tool that fits' framing. It shows judgment, not just recall.

Q28. What does set -euo pipefail do and why use it?

set -e exits on the first unhandled command failure, set -u treats unset variables as errors, and set -o pipefail makes a pipeline fail if any command in it fails, not just the last. Together they turn silent failures into loud, early exits.

It's the common safety header for production scripts. The caveat worth naming: set -e has surprising exceptions (commands in conditionals, for example), so it's a strong default, not a substitute for explicit error handling.

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

# now a typo in a variable name aborts instead of expanding to empty
# and a failing curl in a pipe stops the script
curl -fsS "$URL" | tar -xz

Key point: Knowing that set -e has exceptions (it won't fire inside an if condition) is the senior-sounding detail.

Q29. How do arrays work in Bash?

Bash indexed arrays are declared with arr=(a b c) and accessed by index ${arr[0]}. Expand all elements safely with "${arr[@]}" and get the count with ${#arr[@]}.

Associative arrays (declare -A) map string keys to values, which is Bash's dictionary. Both are Bash-only; plain POSIX sh has no arrays, worth flagging if portability matters.

bash
fruits=("apple" "ripe banana" "cherry")
echo "${fruits[1]}"          # ripe banana
echo "count: ${#fruits[@]}"   # count: 3
for f in "${fruits[@]}"; do echo "$f"; done

declare -A ages=([asha]=31 [ben]=26)
echo "${ages[asha]}"          # 31

Q30. What is parameter expansion and what can it do?

Parameter expansion is the ${...} syntax that transforms a variable without calling external tools. It provides defaults, substring extraction, prefix and suffix removal, and search-and-replace, all inside the shell.

Using it instead of piping to sed or cut is faster and avoids a subprocess. The pattern operators (#, ##, %, %%) trim from the front or back, greedy or not.

bash
path="/var/log/app.log"
echo "${path##*/}"      # app.log      (strip longest leading */)
echo "${path%.log}"     # /var/log/app (strip .log suffix)

name="${1:-default}"    # use $1, or "default" if unset/empty
upper="${name^^}"       # uppercase (Bash 4+)

Key point: Trimming a filename with ${var##*/} instead of basename shows you know the shell can do more than glue tools.

Q31. What is a here-document (heredoc)?

A heredoc feeds a block of literal text to a command's standard input, ending at a chosen marker. It's how you embed multi-line content: a config file, an SQL query, an email body, without escaping each line.

Quote the opening marker ('EOF') to stop variable expansion inside the block; leave it unquoted to expand variables. The <<- variant strips leading tabs so you can indent the block.

bash
cat > config.ini <<EOF
[app]
env=$APP_ENV
port=8080
EOF

# quoted marker keeps text literal
cat <<'EOF'
This $variable is not expanded.
EOF

Q32. What does trap do?

trap runs a command when the script receives a signal or hits a pseudo-event. The common use is trap 'cleanup' EXIT, which guarantees a cleanup function runs whether the script finishes normally, errors out, or is interrupted.

It's how you remove temp files, release locks, or restore state reliably. Catching INT and TERM lets a long-running script shut down gracefully on Ctrl+C or a kill.

bash
tmp=$(mktemp)
cleanup() { rm -f "$tmp"; }
trap cleanup EXIT

# use $tmp freely; it is removed on any exit path
echo "work" > "$tmp"

Key point: trap ... EXIT for cleanup is the answer that indicates production experience. Bare temp files without it read as junior.

Q33. What is the difference between running a script and sourcing it?

Running (./script.sh or bash script.sh) starts a new child shell, so any variables or directory changes vanish when it ends. Sourcing (source script.sh or . script.sh) runs the lines in your current shell, so its changes persist.

That's why you source environment files and activation scripts but execute normal programs. Sourcing a script that calls exit will close your current shell, a surprise worth knowing.

bash
# env.sh sets DEPLOY_KEY
source env.sh          # DEPLOY_KEY now set in your shell
. env.sh               # same thing, POSIX form

./env.sh               # runs in a child; DEPLOY_KEY does NOT persist

Key point: The 'changes persist vs vanish' framing plus the exit gotcha is the complete answer.

Q34. What do cut, tr, sort, and uniq do?

cut extracts columns by delimiter or character position; tr translates or deletes characters; sort orders lines; uniq collapses adjacent duplicate lines. Chained together they cover a lot of quick data shaping.

The key gotcha: uniq only removes adjacent duplicates, so you almost always sort first. sort -u does both in one step.

bash
cut -d',' -f1,3 data.csv          # columns 1 and 3
tr 'a-z' 'A-Z' < file.txt          # uppercase everything
sort access.log | uniq -c | sort -rn   # frequency count, most common first

Key point: Explaining that uniq needs sorted input is the detail that catches people who copied the one-liner without understanding it.

Q35. What does xargs do?

xargs reads items from standard input and builds them into arguments for another command. It bridges tools that produce a list (like find) with tools that take arguments (like rm or grep).

Use find ... -print0 | xargs -0 to handle filenames with spaces or newlines safely. -n limits arguments per call and -P runs calls in parallel.

bash
find . -name '*.tmp' -print0 | xargs -0 rm     # safe delete, handles spaces
echo "1 2 3 4" | xargs -n2 echo                 # echo 1 2 / echo 3 4
find . -name '*.log' | xargs -P4 gzip          # gzip 4 files at a time

Q36. How do you use find effectively?

find walks a directory tree and matches by name, type, size, time, or permission, then acts on the matches. -name globs the filename, -type f or d filters kind, -mtime filters by age, and -exec or a pipe to xargs runs a command per match.

-exec cmd {} + batches matches into fewer command calls, which is faster than the one-per-file + \; form for large trees.

bash
find /var/log -name '*.log' -mtime +7 -delete   # logs older than 7 days
find . -type f -size +100M                       # files over 100 MB
find . -name '*.py' -exec grep -l 'TODO' {} +    # files containing TODO

Q37. When do you use a case statement?

case matches a value against patterns, which reads far cleaner than a long if/elif chain when branching on one variable. Each branch is a glob-style pattern ending in ;;, and *) is the catch-all default.

It's the standard shape for parsing a command argument or a menu choice, and patterns can use |, wildcards, and character classes.

bash
case "$1" in
  start)   echo "starting" ;;
  stop)    echo "stopping" ;;
  restart) echo "restarting" ;;
  *)       echo "usage: $0 {start|stop|restart}" ;;
esac

Q38. How do you handle errors beyond set -e?

Check exit status explicitly where you need to react: cmd || { echo 'failed' >&2; exit 1; }. Group cleanup in a trap on EXIT. Validate inputs early and exit with a clear message rather than letting a later command fail cryptically.

For a pipeline, remember only the last command's status is $? unless pipefail is set. Writing errors to stderr (>&2) keeps them out of captured output.

bash
deploy() {
  cp "$1" /opt/app/ || { echo "copy failed" >&2; return 1; }
  systemctl restart app || { echo "restart failed" >&2; return 1; }
}

deploy "build.tar" || exit 1

A reliable error-handling pattern

1Set the safety header
set -euo pipefail at the top
2Validate inputs early
check arguments and files before doing work
3Check each risky command
use || to react to failures with a clear message
4Clean up on exit
trap a cleanup function on EXIT so it always runs

The technical decision depends on whether failures are loud and cleanup is guaranteed, not whether you memorized every flag.

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

[ ] is the POSIX test command, portable but prone to word-splitting problems with unquoted variables. [[ ]] is a Bash keyword with safer parsing, pattern matching, regex via =~, and && / || inside. (( )) is for integer arithmetic and numeric comparisons.

In Bash scripts, use [[ ]] for string and file tests and (( )) for math. Fall back to [ ] only when the script must run under plain POSIX sh.

bash
name="admin user"
[[ "$name" == admin* ]] && echo "starts with admin"   # pattern match
[[ "$email" =~ ^[^@]+@[^@]+$ ]] && echo "looks like an email"

((count = 3 + 4))
((count > 5)) && echo "big"
ConstructUse forPortable to POSIX sh
[ ]Basic string and file testsYes
[[ ]]Safe tests, patterns, regexNo (Bash/Zsh)
(( ))Integer arithmetic and comparisonNo (Bash/Zsh)

Q40. What is process substitution?

Process substitution, <(command), makes a command's output look like a file, so you can feed the output of one command where a filename is expected. The classic use is comparing two commands' output with diff without temp files.

It's a Bash feature (not POSIX sh) implemented with named pipes. It's the clean way to diff, join, or paste live command output.

bash
diff <(sort file1.txt) <(sort file2.txt)     # compare sorted output, no temp files

while read -r line; do
  echo "got: $line"
done < <(grep "error" app.log)

Key point: Diffing two sorted streams with <( ) is the example interviewers recognize instantly.

Q41. How do you schedule a shell script to run automatically?

cron is the classic scheduler: a crontab line has five time fields (minute, hour, day of month, month, day of week) followed by the command. crontab -e edits your jobs, and each line runs the command on that schedule.

Two production tips worth naming: cron runs with a minimal environment and PATH, so use absolute paths, and redirect output to a log or you'll miss errors. systemd timers are the modern alternative on many systems.

bash
# run backup.sh every day at 2:30 AM, log output
30 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

# every 15 minutes
*/15 * * * * /usr/local/bin/healthcheck.sh

Key point: The minimal-environment gotcha (absolute paths, no interactive PATH) is what separates people who've been burned by cron from those who haven't.

Q42. Why declare variables local inside functions?

By default every variable in a shell script is global, so a variable set inside a function leaks out and can clobber one with the same name elsewhere. Declaring it with local scopes it to the function.

In any script with functions, local is the difference between reusable functions and mysterious action-at-a-distance bugs. It's Bash-specific but widely available.

bash
count=100

bump() {
  local count=0     # without local, this would overwrite the outer count
  count=$((count + 1))
  echo "$count"
}

bump          # 1
echo "$count" # 100, unchanged

Q43. How do you debug a shell script?

Run it with bash -x script.sh (or add set -x inside) to trace every command with its expanded values, which shows exactly what the shell saw after expansion. set -v prints lines before expansion. bash -n does a syntax check without running.

For targeted tracing, wrap a section between set -x and set +x. Combined with set -u to catch unset variables, this finds most bugs quickly.

bash
bash -x ./deploy.sh          # trace the whole run

# trace just one section
set -x
risky_step "$arg"
set +x

bash -n ./deploy.sh          # syntax check only

Key point: Naming set -x for tracing and bash -n for a dry syntax check covers the two questions that follow this one.

Q44. What is the correct way to read a file line by line?

Use while IFS= read -r line; do ...; done < file. IFS= keeps leading and trailing whitespace, -r stops backslash mangling, and reading from a redirect avoids loading the whole file into memory.

Avoid for line in $(cat file): it splits on all whitespace, not newlines, and breaks on spaces and globs. The while-read idiom is the answer the question expects.

bash
while IFS= read -r line; do
  echo "processing: $line"
done < input.txt

# the anti-pattern that splits on spaces and expands globs:
# for line in $(cat input.txt); do ...; done

Key point: Rejecting for line in $(cat ...) and giving the while IFS= read -r idiom is one of the most reliable signals of shell competence.

Back to question list

Shell Scripting Interview Questions for Experienced Engineers

Experienced16 questions

advanced rounds probe resilience, portability, and production scars. Expect every answer here to draw a follow-up.

Q45. How do you write a portable script that runs on any POSIX shell?

Target the POSIX shell feature set: use #!/bin/sh, avoid Bash-only constructs (arrays, [[ ]], process substitution, ${var^^}), and test with a strict shell like dash. Stick to [ ], printf over echo -e, and parameter expansion that POSIX defines.

The honest trade-off to name: portability costs convenience. Many teams standardize on Bash and #!/usr/bin/env bash instead, because the machines they run on all have it. Portability matters most for install scripts and containers with minimal shells.

Bash-onlyPOSIX-portable alternative
arr=(a b c)Space-separated string or positional params
[[ x == y* ]]case statement or [ ] with grep
<(command)Temp file with trap cleanup
${var^^}tr '[:lower:]' '[:upper:]'

Key point: Naming dash as the strict test shell, and that many shops just standardize on Bash, is the balanced production-ready answer.

Q46. Explain word splitting and how quoting prevents bugs.

After expansion, the shell splits unquoted results on the characters in IFS (space, tab, newline by default) and then expands globs. So an unquoted $file holding 'my report.txt' becomes two arguments, and a value containing * expands to matching filenames.

Quoting expansions ("$file", "$@", "${arr[@]}") turns off splitting and globbing, keeping each value intact. This one habit prevents the majority of real-world shell bugs, which is exactly why linters like shellcheck flag every unquoted expansion.

bash
file="my report.txt"
rm $file      # tries to remove "my" and "report.txt" (two args)
rm "$file"    # removes the one file, correct

# IFS controls splitting; change it deliberately, restore it after
OLD_IFS=$IFS; IFS=","
read -ra parts <<< "a,b,c"
IFS=$OLD_IFS

Key point: Connecting word splitting to shellcheck's quote-everything rule shows you've adopted the tooling, not just the theory.

Q47. What is shellcheck and how does it fit your workflow?

shellcheck is a static analyzer for shell scripts. It catches unquoted expansions, useless use of cat, wrong test operators, portability issues, and dozens of other bug classes before the script ever runs.

In a real workflow it runs in CI and in the editor, so problems surface at write time. Treating its warnings as errors is the fastest way to raise a team's shell quality, and it matters.

bash
shellcheck deploy.sh          # lint a single script

# disable one check on one line, with a reason
# shellcheck disable=SC2086  # word splitting is intentional here
rm $files

Key point: Bringing up shellcheck in practice is a strong signal. Most under-prepared candidates never mention tooling.

Q48. How do you handle signals for graceful shutdown in a long-running script?

Trap the signals you care about: INT (Ctrl+C), TERM (default kill and container stop), and HUP. In the handler, stop accepting new work, finish or abort in-flight work, release resources, and exit with a status that reflects the signal.

For a script that manages child processes, forward the signal to the children and wait for them, otherwise orphaned processes linger. In containers, handling TERM is what lets your process shut down cleanly before the runtime sends KILL.

bash
running=true
shutdown() { echo "draining..."; running=false; }
trap shutdown INT TERM

while $running; do
  process_one_item
  sleep 1
done
echo "clean exit"

Key point: The container angle (TERM before KILL) is what makes this The production-ready answer rather than a textbook one.

Q49. How do you create temporary files safely?

Use mktemp, which creates a file (or directory with -d) with a random, unpredictable name and safe permissions, then returns its path. Never build a temp name from $$ or a fixed string, because that invites race conditions and predictable-name attacks.

Pair mktemp with a trap on EXIT to remove it on every exit path. For directories, mktemp -d gives a private workspace you clean up in one rm -rf.

bash
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT

curl -fsS "$URL" -o "$tmpdir/download.tar.gz"
tar -xzf "$tmpdir/download.tar.gz" -C "$tmpdir"
# everything is cleaned up automatically on exit

Key point: The security angle (predictable names are a real vulnerability class) is why this question exists, not just tidiness.

Q50. What is a subshell and how does it affect variables and performance?

A subshell is a child copy of the current shell, created by parentheses ( ... ), each side of a pipe, and command substitution. Changes to variables inside a subshell don't propagate back to the parent, which is a frequent surprise.

The classic bug: piping into a while loop runs the loop in a subshell, so counters set inside it are lost afterward. Fixes are process substitution or lastpipe. Spawning many subshells in a hot loop also costs measurable time versus a single tool call.

bash
count=0
grep "x" file | while read -r _; do
  count=$((count + 1))   # runs in a subshell
done
echo "$count"            # 0, the increments were lost

# fix: process substitution keeps the loop in the current shell
while read -r _; do count=$((count + 1)); done < <(grep "x" file)
echo "$count"            # correct

Key point: The pipe-into-while-loop counter bug is a favorite trap. Recognizing it instantly marks real experience.

Q51. How do you handle floating-point math in shell scripts?

The shell does integer arithmetic only; $(( )) truncates. For decimals, pipe an expression to bc (with a scale for precision) or use awk, which has full floating-point built in and is usually the cleaner choice inside a pipeline.

For anything beyond a couple of calculations, that's often the sign to move the logic to a language with real numeric types rather than fighting bc syntax.

bash
echo "scale=4; 22/7" | bc            # 3.1428

average=$(awk "BEGIN { print (12.5 + 7.3) / 2 }")
echo "$average"                       # 9.9

# comparing decimals also needs awk or bc, not (( ))
awk "BEGIN { exit !(3.2 > 3.1) }" && echo "greater"

Q52. How do you parse command-line options in a script?

getopts is the built-in for short options: it loops through flags, sets a variable to each option letter, and puts an argument in OPTARG when the option string marks it with a colon. It handles bundling and stops at the first non-option.

For long options (--verbose), getopts doesn't help; teams either use a manual while/case loop or an external getopt. Naming that limitation shows you've built a real CLI, not just a demo.

bash
verbose=0
output=""
while getopts ":vo:" opt; do
  case "$opt" in
    v) verbose=1 ;;
    o) output="$OPTARG" ;;
    \?) echo "unknown option: -$OPTARG" >&2; exit 1 ;;
  esac
done
shift $((OPTIND - 1))   # drop parsed options, leave the rest

Key point: Knowing getopts is short-options-only, and how you'd handle long options, is the follow-up. Have it ready.

Q53. What makes a script idempotent and why does it matter?

An idempotent script produces the same end state whether it runs once or many times: it checks before it acts (mkdir -p, test before create, append only if missing) instead of assuming a clean slate. Running it twice doesn't duplicate or break anything.

It matters for automation and configuration management, where scripts rerun on retries, cron, or partial failures. A non-idempotent deploy that appends a line every run is a classic production bug.

bash
mkdir -p /opt/app/data          # safe to run repeatedly

# add a line only if it is not already there
grep -qxF "export PATH=/opt/bin:\$PATH" ~/.bashrc \
  || echo "export PATH=/opt/bin:\$PATH" >> ~/.bashrc

Key point: The 'append every run' anti-pattern is the concrete example the question needs. The it.

Q54. What are the conventions for exit codes?

0 means success, 1 to 125 are application-defined failures, 126 means the command was found but not executable, 127 means command not found, and 128+n means the process was killed by signal n (so 130 is Ctrl+C, SIGINT). Codes above 255 wrap around.

Well-behaved scripts return distinct non-zero codes for distinct failure modes, so callers and monitoring can react differently. Reserving a small set of meaningful codes beats returning 1 for everything.

CodeMeaning
0Success
1-125Application-defined error
126Found but not executable
127Command not found
128+nKilled by signal n (130 = Ctrl+C)

Q55. How do you process a very large file efficiently in shell?

Stream it, never load it. Pipe through line-oriented tools (awk, sed, grep) that read one record at a time so memory stays flat regardless of file size. Prefer a single awk pass over chaining many tools, since each stage is another process reading the whole stream.

For CPU-bound work, split the file and process chunks in parallel with xargs -P or GNU parallel. And know when to stop: past a certain complexity, a streaming program in Python or Go is more maintainable than a heroic awk script.

bash
# one streaming pass: count and sum in a single awk, no full load
awk '{ n++; total += $2 } END { print n, total }' huge.csv

# parallel processing of many files
find logs/ -name '*.gz' | xargs -P8 -I{} sh -c 'zcat {} | grep ERROR'

Q56. How do you prevent two copies of a script from running at once?

Use a lock. flock ties an advisory lock to a file descriptor and releases it automatically when the script exits, which avoids stale locks from a plain if-file-exists check that a crash would leave behind. Wrapping the script in flock -n on a lock file is the clean pattern.

The manual mkdir-as-lock trick works because directory creation is atomic, but flock is safer because it self-releases. This comes up for cron jobs that might overlap when one run runs long.

bash
#!/usr/bin/env bash
# re-exec under flock; -n fails fast if another copy holds the lock
[ "${FLOCKED:-}" ] || exec env FLOCKED=1 flock -n /tmp/job.lock "$0" "$@"

echo "only one instance runs this section"
sleep 30

Key point: Preferring flock over a hand-rolled lockfile, because it self-releases on crash, is the detail that lands here.

Q57. What are the main security pitfalls in shell scripts?

The big ones: unquoted expansions that allow injection or unexpected globbing, passing untrusted input to eval or a shell, predictable temp file names, secrets in the script or in ps output, and a permissive PATH that lets an attacker shadow a command.

Defenses: quote everything, avoid eval on data you don't control, use mktemp, read secrets from the environment or a secret store rather than arguments (which are visible in ps), and set an explicit PATH at the top of privileged scripts.

  • Quote every expansion; unquoted input is the root of most injection and glob bugs.
  • Never eval untrusted input, and avoid building commands from user-supplied strings.
  • Read secrets from the environment or a secret manager, not command-line arguments visible in ps.
  • Set PATH explicitly in privileged scripts so a malicious directory can't shadow a real command.

Key point: Mentioning that command-line arguments are visible in ps, so secrets don't belong there, is the pitfall most people miss.

Q58. When should you stop using shell and switch to a real programming language?

Switch when logic outgrows the shell's strengths: complex data structures, non-trivial parsing, math beyond integers, code that needs unit tests, error handling that must be precise, or a script pushing past a few hundred lines. Those are the signs the shell is fighting you.

The shell stays the right tool for gluing commands, file operations, and short automation. The senior instinct is recognizing the crossover early, before a 400-line Bash script becomes the thing nobody wants to touch.

Key point: the key point is you reach for the right tool, not defend Bash to the death.

Q59. How can you use trap for debugging and error reporting?

Beyond signals, trap can fire on the ERR pseudo-signal (when a command fails under set -e) to print context, and on DEBUG before every command. A trap on ERR that reports the failing line and command turns a silent set -e exit into an actionable message.

Combining trap ERR with $LINENO and BASH_COMMAND gives you a lightweight backtrace, which is the kind of self-diagnosing script that saves hours in production.

bash
#!/usr/bin/env bash
set -euo pipefail
trap 'echo "error on line $LINENO: $BASH_COMMAND" >&2' ERR

risky_command_that_might_fail
echo "reached only if the command above succeeded"

Key point: A trap on ERR reporting $LINENO and $BASH_COMMAND is the answer that separates people who ship maintainable scripts.

Q60. What does eval do and why is it dangerous?

eval takes a string, expands it a second time, and runs the result as a command. That extra expansion is exactly the danger: any untrusted content in the string can inject arbitrary commands, so eval on data you don't fully control is a security hole.

Legitimate uses are rare and narrow, like reifying dynamically named variables. Most cases people reach for eval are better solved with arrays, indirect expansion (${!name}), or restructuring the code so no second evaluation is needed.

bash
user_input='x"; rm -rf /tmp/data"'
# eval "echo $user_input"   # DANGER: runs the injected rm

# safer alternative for dynamic variable names: indirect expansion
key="HOME"
echo "${!key}"     # prints the value of $HOME, no eval needed

Key point: Offering indirect expansion ${!name} as the safe alternative to eval is what turns 'eval is bad' into The production-ready answer.

Back to question list

Shell Scripting vs Python, Perl, and Compiled Programs

Shell scripting wins for gluing command-line tools together: file operations, running other programs, piping text, and short automation that lives close to the system. It loses the moment logic gets complex, data structures matter, or you need portability and tests, which is where Python fits better. Compiled programs are for performance-sensitive or long-lived services. Knowing where the shell stops being the right tool is itself an interview signal, because reaching for Bash on a 500-line data-processing job is a common mistake reviewers watch for.

ToolBest atData handlingWatch out for
Shell (Bash)Gluing CLI tools, file ops, automationText streams, argumentsQuoting bugs, no real data types
PythonLogic-heavy scripts, data processingRich types, librariesHeavier startup, more to install
PerlText munging, legacy sysadmin scriptsStrong regex, text focusDeclining ecosystem, read-only in many shops
Compiled (Go, C)Long-running services, speedFull type systemsBuild step, slower to iterate

How to Prepare for a Shell Scripting Interview

Prepare in layers and practice at a real terminal. Most shell rounds move from concept questions to writing a small script live to debugging or a one-liner challenge, so rehearse each stage rather than only reading answers.

  • Master your tier's concepts until you can explain them without notes, then read one tier up for the stretch questions.
  • Type and run every snippet on a real shell; watching quoting and exit codes behave cements them far faster than reading.
  • a short script out loud with a timer, because your process, not just the final answer is the technical point is the implementation path.
  • Take the quiz on this page to find weak spots, then revisit those questions before the real round.

The typical shell scripting interview flow

1Recruiter or phone screen
background, tooling you use, a few concept checks
2Technical concepts
quoting, exit codes, variables, conditionals, grep/sed/awk
3Live scripting
write and explain a script or one-liner under observation
4Debugging or design
read a broken script, harden it, discuss trade-offs

Earlier rounds increasingly run as AI coding interviews. The concepts on this page are what gets probed at every stage.

Test Yourself: Shell Scripting Quiz

Ready to test your Shell Scripting knowledge?

10 questions, about 6 minutes. Score 70% or higher to earn a shareable certificate.

10 questions Instant feedback Free certificate on 70%+

Frequently  Asked  Questions

Which Shell Scripting topics should I prioritize before a technical round?

Prioritize the areas that change real decisions: fundamentals, practical tasks, debugging, trade-offs, and evidence. The highest-value questions are tied to a project example, a validation check, or a failure mode.

Are these questions enough to pass a shell scripting interview?

They cover the question-answer portion well, but most shell rounds also include live scripting: writing a small script or one-liner while explaining your thinking. solving problems out loud at a terminal with a timer is the practice path. Our AI coding interview prep guide covers that format, including how automated evaluation reads your process.

Which shell do these answers assume?

Bash, throughout. It's the default on most Linux systems and what interviewers usually mean by shell scripting. Where behavior differs in POSIX sh or Zsh, we say so. When in doubt in an interview, state that you're answering for Bash and note anything that changes under a stricter POSIX shell.

How long does it take to prepare for a shell scripting interview?

If you write shell scripts at work, one to two weeks of an hour a day covers this bank with practice runs. Starting colder, plan three to four weeks and write scripts daily; reading answers without running them at a terminal is how preparation quietly fails.

Do interviewers ask exactly these questions?

The concepts repeat far more reliably than the exact wording. Interviewers rephrase, chain follow-ups, and push toward whatever you claim confidence in. Prepare the underlying ideas, quoting, exit codes, redirection, grep, sed, and awk, and the phrasing takes care of itself.

Is there a way to test my shell scripting knowledge?

Yes. The quiz on this page scores you as you go and explains every answer, so you find your weak spots fast. Pass the threshold and you can download a shareable certificate with your name and score, free and with no sign-up. It is the quickest way to check yourself before the real round.

From the team that builds coding interviews

Hyring builds the AI Coding Interviewer that runs live technical rounds in 20+ languages for 5,000+ hiring teams. These Shell Scripting questions and scoring notes reflect what actually gets asked and evaluated inside the interviews we host.

See how the AI Coding Interviewer works

Sources

Adithyan RKWritten by Adithyan RK
Surya N
Fact-checked by Surya N
Published on: 8 May 2026Last updated: 11 Jul 2026
Share: