gitGood.dev
Back to Blog

Top 50 Bash and Shell Scripting Interview Questions 2026

P
Pat
12 min read

Top 50 Bash and Shell Scripting Interview Questions 2026

Bash is the language nobody puts on their resume but everybody is expected to know. If you are interviewing for SRE, DevOps, platform, or any backend role that involves a runtime, expect to be dropped into a terminal. The interviewer is not testing whether you can write a 500-line bash framework. They are testing whether you can read someone else's grungy script and reason about what will go wrong.

Here are the 50 questions that come up most often, in the order an interviewer might escalate from "do you know the basics" up to "can you debug this live."


Part 1: Fundamentals (1-12)

1. What is the difference between sh, bash, zsh, and dash?

sh is the POSIX shell standard. On most Linux distros, /bin/sh is a symlink to dash (Debian/Ubuntu) or bash (in POSIX mode). bash is the GNU Bourne-Again Shell with many extensions. zsh is interactive-friendly with extra features. Scripts targeting portability should use #!/bin/sh with POSIX-only features; scripts that need bashisms should explicitly #!/bin/bash.

2. What does the shebang line do?

#!/bin/bash tells the kernel which interpreter to use when executing the file. Without it, the script runs in whatever shell invoked it. #!/usr/bin/env bash is more portable because env finds bash on PATH (useful when bash lives in /usr/local/bin/ on macOS).

3. What is the difference between ', ", and backticks in bash?

  • Single quotes '...' - literal, no expansion of any kind.
  • Double quotes "..." - variable expansion, command substitution, but no glob expansion.
  • Backticks `...` - command substitution. Use $(...) instead because it nests cleanly.

4. What is $@ vs $*?

Both refer to all positional parameters. The difference shows up when quoted:

  • "$@" expands to "$1" "$2" "$3" (each argument quoted separately).
  • "$*" expands to "$1 $2 $3" (one string).

Always prefer "$@". Forgetting this is the cause of many "spaces in filenames break my script" bugs.

5. What does set -e do? When can it bite you?

set -e causes the script to exit on any command failure. Bites you because:

  • Pipelines: only the last command's exit matters unless you also set -o pipefail.
  • Commands inside if, &&, ||, or ! do not trigger exit.
  • Functions called inside && do not respect set -e in some bash versions.

The standard incantation for "strict mode" scripts: set -euo pipefail.

6. What does set -u do?

Treats undefined variables as errors. Forces you to be deliberate about variables. Use ${VAR:-default} when you genuinely want a fallback.

7. What is parameter expansion ${VAR:-default}?

If VAR is unset or empty, use default. Variants:

  • ${VAR:-default} - use default if unset/empty (does not assign).
  • ${VAR:=default} - assign default if unset/empty.
  • ${VAR:?error} - exit with error if unset/empty.
  • ${VAR:+alt} - use alt if VAR is set.

8. How do you check if a variable is empty?

if [[ -z "$VAR" ]]; then
  echo "empty"
fi

Or [[ -n "$VAR" ]] for non-empty. Use [[ ]] over [ ] in bash - it has saner quoting and supports pattern matching.

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

  • [ ... ] - POSIX test, also known as test. Works in any shell, but quoting is treacherous.
  • [[ ... ]] - bash conditional, smarter parsing, supports &&, ||, =~. Bash-only.
  • (( ... )) - arithmetic context. (( x > 5 )) evaluates as a number.

10. How do you read user input?

read -r -p "Enter name: " name

-r prevents backslash from being interpreted as escape. Always use -r unless you have a specific reason not to.

11. What is command substitution?

files=$(ls *.txt)

The output of the inner command becomes the value. Always quote: "$(ls *.txt)" if you want to preserve newlines and spaces.

12. What does command || true do?

Forces a command to "succeed" so set -e does not exit the script. Useful when a command might fail for non-fatal reasons (e.g., grep returning nothing).


Part 2: I/O and Redirection (13-22)

13. Explain the three standard streams.

  • stdin (file descriptor 0) - input.
  • stdout (file descriptor 1) - normal output.
  • stderr (file descriptor 2) - errors and diagnostics.

14. How do you redirect stderr?

command 2> error.log         # stderr to file
command 2>&1                  # stderr merged into stdout
command > all.log 2>&1        # both to a file (order matters)
command &> all.log            # bash shorthand for both

15. What is tee and why is it useful?

Reads stdin and writes to both stdout and one or more files.

command | tee output.log
command | tee -a output.log    # append
command | sudo tee /etc/file   # the standard "write to root file" trick

16. What is a pipe vs a redirect?

A pipe | connects one process's stdout to another process's stdin in memory. A redirect > connects to a file. > truncates by default; >> appends.

17. What is << (heredoc) and <<< (herestring)?

Heredoc passes a multi-line string as stdin:

cat <<EOF
Hello $USER
EOF

Use <<'EOF' (quoted delimiter) to disable variable expansion. Herestring passes a single line:

grep root <<< "$content"

18. What is /dev/null?

A bit-bucket. Reading returns EOF. Writing discards. command > /dev/null 2>&1 silences a command entirely.

19. What is process substitution <(...)?

Treats the output of a command as a temporary file:

diff <(sort file1) <(sort file2)

bash-only feature, very useful for diff and comparison workflows.

20. What is the difference between $(command) and $(< file)?

$(command) runs a command and captures stdout. $(< file) reads a file's contents into a variable - faster than $(cat file) because no fork.

21. How do you redirect into and out of a specific FD?

exec 3< input.txt        # open FD 3 for reading
read -r line <&3
exec 3<&-                # close FD 3

Useful for advanced scripts that need to read from multiple files concurrently.

22. What does command 1>&2 mean?

Send stdout (FD 1) to where stderr (FD 2) currently points. Often used in echo "Error: ..." 1>&2 to write error messages to stderr.


Part 3: Control Flow and Functions (23-32)

23. How do you write a function?

my_function() {
  local arg="$1"
  echo "Got: $arg"
}

my_function "hello"

Always declare locals with local to avoid leaking variables into the enclosing scope.

24. How do you return a value from a function?

return only sets an exit status (0-255). To return data, echo it and capture with command substitution:

get_user() {
  echo "alice"
}

USER=$(get_user)

25. What is local and why does it matter?

Declares a variable scoped to the current function. Without it, variables leak out and cause cross-function bugs that are nightmarish to debug. Always use local.

26. How do you iterate over arguments?

for arg in "$@"; do
  echo "Arg: $arg"
done

Quoted "$@" preserves arguments with spaces.

27. How do you read a file line-by-line?

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

IFS= prevents leading/trailing whitespace stripping. -r prevents backslash escaping. Both matter for correctness.

28. What does IFS control?

Internal Field Separator. Determines how bash splits unquoted command output and the read command. Default is space-tab-newline. Set IFS=$'\n' to split only on newlines, or IFS=',' for CSV.

29. What is the difference between &&, ||, and ;?

  • cmd1 && cmd2 - run cmd2 only if cmd1 succeeded.
  • cmd1 || cmd2 - run cmd2 only if cmd1 failed.
  • cmd1 ; cmd2 - run cmd2 regardless.

Common idiom: mkdir -p /path && cd /path - only cd if mkdir succeeded.

30. What is a case statement?

Pattern-matching alternative to long if-else chains:

case "$1" in
  start) start_service ;;
  stop) stop_service ;;
  *) echo "Unknown: $1" ;;
esac

31. What does getopts do?

Parses command-line options:

while getopts "f:vh" opt; do
  case $opt in
    f) FILE="$OPTARG" ;;
    v) VERBOSE=1 ;;
    h) usage; exit 0 ;;
  esac
done

For long options (--file), use getopt (not getopts) or write a manual parser.

32. How do you write an idempotent script?

Check before acting. Examples:

  • mkdir -p instead of mkdir.
  • [[ -f file ]] || touch file.
  • grep -q pattern file || echo pattern >> file.
  • Use proper config management (Ansible, etc.) for anything beyond trivial.

Part 4: Signals, Traps, and Background (33-40)

33. What is a trap and how do you use it?

Runs a command when a signal is received (or on exit):

cleanup() {
  rm -f /tmp/myfile
}
trap cleanup EXIT

The EXIT pseudo-signal fires regardless of how the script exits.

34. How do you handle Ctrl-C cleanly in a script?

trap 'echo "Interrupted"; exit 130' INT

130 is the conventional exit code for SIGINT (128 + signal number).

35. What is nohup?

Runs a command immune to hangup (SIGHUP), so it survives terminal close. Output redirected to nohup.out by default. Modern alternative: systemd-run --user --scope, or just run it in tmux/screen.

36. What is the difference between & and disown?

& runs in the background but the process is still attached to the shell - it dies with the shell. disown removes the job from the shell's job table so it survives. nohup ... & is the classic combination.

37. How do you wait for background jobs to complete?

job1 &
job2 &
wait

wait blocks until all background jobs in the current shell finish. wait $! waits for the most recently launched. wait -n waits for any one to finish.

38. How do you kill a process by name?

pkill -f "my_script.sh"
# or:
ps -ef | grep my_script | grep -v grep | awk '{print $2}' | xargs kill

Be careful with pkill -f - it matches against the full command line and you can clobber unintended processes.

39. How do you check if a previous command succeeded?

$? is the exit code of the last command. Zero is success.

some_command
if [[ $? -eq 0 ]]; then
  echo "ok"
fi

Or just use if some_command; then ....

40. What are PIPESTATUS and BASH_REMATCH?

PIPESTATUS is an array of exit codes from each command in the last pipeline. Useful when set -o pipefail is not enough. BASH_REMATCH holds capture groups from [[ $str =~ regex ]].


Part 5: Live Debugging and Performance (41-50)

41. Walk me through how you would debug a script that "stops working" silently.

Add set -x (trace) at the top, or run bash -x script.sh. Add PS4='+ ${BASH_SOURCE}:${LINENO}: ' to show file and line in trace output. Check return codes after every meaningful command. If the script is huge, instrument with echo "DEBUG: ..." 1>&2 at decision points.

42. A bash script is taking 30 seconds to run a simple loop. How do you investigate?

Look for:

  • Subshells inside loops ($(command) 10,000 times = 10,000 forks).
  • External tool calls inside loops (grep, sed, awk per iteration - batch them instead).
  • Network or file system calls without caching.

Profile with time and by adding date +%s.%N checkpoints.

43. How do you check for syntax errors without running the script?

bash -n script.sh

Pairs well with shellcheck script.sh for static analysis.

44. What is shellcheck and why should I use it?

A static analyzer that catches common bash bugs: missing quotes, unused variables, deprecated syntax, subtle issues with read, for, etc. Run it on every script before reviewing. Most editors have a plugin.

45. How do you handle large data in bash without crashing?

Use streams, not arrays. Pipe through awk, sort, cut instead of reading whole files into bash arrays. Bash is slow at iterating large arrays, but excellent at gluing fast tools together.

46. What is the difference between awk and sed for an interview question?

  • sed - stream editor for line-by-line transformations (substitute, delete, insert).
  • awk - field-aware scripting language for tabular data, with state.

Rule of thumb: if the task fits in one regex substitution, use sed. If it involves columns, conditions, or aggregation, use awk.

47. Write a one-liner to find the largest 10 files in a directory tree.

find . -type f -exec du -h {} + | sort -rh | head -10

48. Write a one-liner to count occurrences of each unique line in a file.

sort file | uniq -c | sort -rn

49. Write a one-liner that fails loudly if a command produces unexpected output.

output=$(command)
[[ "$output" == "expected" ]] || { echo "Unexpected: $output" >&2; exit 1; }

50. When should you stop using bash and switch to Python (or Go)?

Roughly:

  • 100 lines is your soft limit. Past that, the readability cost compounds.
  • Anything with structured data (JSON, complex argument parsing, async) - bash will hurt.
  • Anything that needs unit tests - bash testing exists (bats) but Python is dramatically better.
  • Anything that needs to be cross-platform.

The right answer in interview: "I default to bash for glue and orchestration, Python for anything with logic, Go for anything that needs to be a real binary."


How to Drill These

The same advice applies as with Linux questions: do not memorize. Do.

  • Pick five questions a day.
  • Type them into a real terminal, even when you "already know" the answer.
  • Build one real automation script per week. CI scripts, deploy scripts, log-rotation scripts.
  • Run shellcheck on everything you write and read the suggestions seriously.

The shell is muscle memory. You build it by doing.

If you want to drill these under interview pressure, gitGood has Linux and DevOps question banks covering shell scripting, plus a chat-based AI mock interview where an AI interviewer probes your reasoning with follow-up questions.

#bash #shell #scripting #interviews #devops #sre #career