Articles
Bash Scripting Interview Questions: Quoting, Exit Codes, Pipes, and Safe Automation
Prepare for Bash scripting interviews with quoting examples, pipeline exit codes, cleanup traps, filename-safe processing, and practical failure tests.
Bash scripting interview questions are really questions about boundaries: where one argument ends, which command failed, who owns a temporary file, and what happens when automation stops halfway through. A strong answer explains those boundaries before reaching for another command.
This guide uses Bash, not generic POSIX sh, and GNU utilities where explicitly noted. The exercises are original teaching examples, not claims about a particular employer's interview. You will trace arguments, diagnose a misleading successful pipeline, and design a script that refuses to call partial work a success.
What should I explain before writing a shell script?
Start with the contract. Suppose the interviewer asks you to process every report under a directory and write a summary. Ask whether traversal is recursive, whether symbolic links count, whether unreadable files should fail the run, and whether another process can change files while you work.
These questions change the implementation. A best-effort dashboard may accept a partial count with a warning. A billing job should probably reject partial input. The same command can be appropriate for one and wrong for the other.
State the environment too: Bash version, available utilities, working directory, locale, and permissions. A script that runs interactively may fail under a scheduler because PATH differs or because no terminal is attached. Prefer explicit inputs and deliberate output locations over assumptions inherited from your laptop.
A useful interview answer has four parts: the input contract, a small implementation, one adversarial example, and an observable success condition. "It printed something" is not a success condition. "It processed every permitted input and returned zero only after the output was committed" is.
Why does quoting change the number of arguments?
In an ordinary command, an unquoted expansion can undergo word splitting and filename expansion. Double quotes preserve a variable's value as one argument while still allowing parameter expansion. Single quotes preserve literal characters instead. GNU Bash documents the exact quoting rules; the important practical habit is to reason about the resulting argument list.
Consider this original exercise in a directory with no matching wildcard files:
name='quarter one.csv'
printf '<%s>\n' "$name"
printf '<%s>\n' $name
The first call prints one bracketed value. The second prints <quarter> and <one.csv> on separate lines. The program receiving the arguments cannot reconstruct the author's intention. That information was lost before it started.
Now set name='*.csv'. Quoted, the argument is the literal wildcard text. Unquoted, Bash may replace it with matching paths. An empty value creates another revealing test: "$name" remains one empty argument, while an ordinary unquoted empty expansion disappears.
Do not explain this as "quotes make strings." Shell syntax is not Python syntax. Quotes control expansion and token boundaries. The most useful debugging question is: what exact arguments does the command receive?
How do I forward arguments without breaking them?
Use "$@" to forward the positional arguments separately. Inside double quotes, "$*" joins them into a single argument, using the first character of IFS as a separator. That difference matters in wrappers around compilers, deployment tools, or test runners.
run_check() {
printf 'Checking %s argument(s)\n' "$#"
command printf '<%s>\n' "$@"
}
run_check 'quarter one.csv' '' '*.csv'
The wrapper receives three arguments, including an empty one. A good test checks all three, not merely the visible filename. Build command arguments with an array when options are conditional:
args=(--verbose --output "$destination")
some_tool "${args[@]}" "$input"
Here some_tool is a placeholder for a utility whose option contract you have checked. Do not put a whole command into a string and then use eval. An array preserves boundaries; eval reparses code and can turn untrusted input into shell syntax.
Also distinguish quoting from option protection. A filename beginning with a dash can still be interpreted as an option even when quoted. Use the utility's supported end-of-options marker, usually --, or an appropriate path prefix. Quoting and option parsing solve different problems.
Why can a failed pipeline return zero?
By default, a Bash pipeline reports the status of its last command. With pipefail, it reports the rightmost nonzero status, or zero if every stage succeeded. This is defined behavior, not an intermittent shell bug.
false | true
printf 'default status: %s\n' "$?"
set -o pipefail
false | true
printf 'pipefail status: %s\n' "$?"
Expected output is zero, then one. The second stage can succeed at handling the input it received even though the first stage failed to produce the intended input.
| Stage statuses, left to right | Default result | With pipefail | Interpretation |
|---|---|---|---|
| 0, 0, 0 | 0 | 0 | Every stage reports success |
| 2, 0, 0 | 0 | 2 | An upstream error was hidden |
| 2, 3, 0 | 0 | 3 | Rightmost failure is returned |
| 0, 0, 1 | 1 | 1 | Last stage fails either way |
For per-stage evidence, copy PIPESTATUS immediately after the pipeline. Another command, including a diagnostic printf, replaces the status information. If errexit is active, a failing pipeline may terminate the script before the next line, so choose a controlled test context rather than pasting this into production blindly.
There is an important counterexample: a consumer such as head may intentionally stop early, and its producer may receive SIGPIPE. pipefail reveals that status; it does not decide whether early termination was acceptable. Your script's contract still owns that decision.
Is set -euo pipefail enough for safe automation?
No. These options are useful controls, not a correctness proof. -u detects some unset-variable use. pipefail changes pipeline status. -e has documented exceptions around conditions, logical lists, and other contexts, so "any error always exits" is an inaccurate explanation.
Write an explicit branch when failure is meaningful business logic:
if grep -q 'READY' "$input"; then
printf 'ready\n'
else
status=$?
if (( status == 1 )); then
printf 'not ready\n'
else
printf 'could not inspect input\n' >&2
exit "$status"
fi
fi
For GNU grep, one means no match; a larger status indicates an error. Treating both as "not ready" hides missing files and permission failures. Notice that the status is captured before another command can overwrite it.
Avoid if ! command; then status=$? when you need the original failing status: the logical negation has already changed the status you observe. Test the command normally and capture its status in the else branch.
An interview follow-up may ask whether an expected no-match should fail a deployment. The answer depends on what the pattern represents. A missing optional warning is different from a missing mandatory readiness marker. Make that policy visible in the code.
How do I process filenames containing spaces or newlines?
Newlines are legal in Unix filenames, so a line-oriented path list is not a general filename format. NUL-delimited output is a common solution because a path cannot contain a NUL byte. Do not parse the display output of ls.
For a counting exercise with GNU find, emit one fixed byte per regular file instead of printing the path:
set -o pipefail
find "$root" -type f -printf x | wc -c
This counts regular-file directory entries recursively without following symbolic links by default. It does not deduplicate hard-linked inodes. GNU -printf is not available in every find, so state the dependency.
If find encounters an unreadable directory, the pipeline can still print a partial number. With pipefail, its final status signals failure. A production caller must reject that number or label it incomplete; simply printing a warning somewhere is not enough if another program consumes stdout as authoritative data.
For actual path processing, use find -print0 with a NUL-aware consumer. A Bash while IFS= read -r -d '' path loop can read this format, but ordinary pipeline loops commonly run in a subshell, so variable updates may not survive outside. Process substitution avoids that particular scope issue but does not automatically propagate the producer's exit status. Explain both hazards instead of presenting either syntax as magic.
What should cleanup traps guarantee?
A cleanup trap should release resources the script actually owns and preserve the meaningful failure status. Register cleanup soon after acquisition. If acquisition fails, do not pretend a resource exists. Use a securely created temporary directory rather than a predictable shared filename.
Sketch the lifecycle before writing the trap: acquire temporary storage, register cleanup, build output, validate it, then publish the result. On any earlier failure, the destination should remain unchanged when the contract requires all-or-nothing behavior.
EXIT traps handle ordinary shell exit paths. INT and TERM need a deliberate interruption policy, often exiting with conventional signal-related statuses so the exit cleanup runs. No trap can handle SIGKILL, and power loss is outside shell cleanup guarantees. A retry must therefore tolerate leftover temporary work.
Do not casually demonstrate recursive deletion against a variable in an interview. State that cleanup verifies an owned temporary path before removing it. If cleanup itself fails, report that failure without disguising the original operation's error. A failed cleanup and a failed business operation are two facts worth preserving.
How would you design a reliable report-writing script?
Use this original scenario: input reports live under a supplied directory, any unreadable report invalidates the result, and readers must never see a half-written summary. The script may be interrupted, and the same job may be retried.
First, validate arguments and establish the destination policy. Then write to a temporary file in the destination directory, so the eventual rename stays on the same filesystem. Validate the completed file before renaming it into place. An atomic rename helps readers observe an old or new file, but it is not by itself a power-loss durability guarantee.
Next, define concurrency. Two individually correct jobs can overwrite each other's output. Use a suitable locking or unique-output strategy if overlapping runs are possible. Do not assume that a scheduler prevents overlaps unless that property is explicitly configured.
Finally, separate stdout data from stderr diagnostics. Log which input failed, return a nonzero status, and avoid printing secrets in tracing output. set -x is useful for a controlled reproduction but may expose tokens expanded into command arguments. Redaction and restricted logging matter more than collecting every possible line.
The strongest answer includes a failure injection: stop the producer halfway through, make one input unreadable, or send TERM during output construction. Then verify that the public destination is unchanged and the exit status communicates failure.
Which tests expose weak Bash interview answers?
Build a small fixture set in a disposable directory and predict the result before running it. Include a filename with spaces, an empty argument, a wildcard character, a leading dash, a newline, an unreadable subtree, and a destination that cannot be written.
Also test zero inputs, duplicate invocations, and interruption after resource acquisition. Permission tests should use an ordinary user; running as an administrator can make an unreadable-file test meaningless. Compare actual status and output separately.
For a ten-minute practice round, spend two minutes stating the contract, four implementing, and four trying to invalidate your own success claim. Explain one trade-off aloud: portability versus a GNU-specific shortcut, streaming versus temporary storage, or strict failure versus best-effort results.
Your final answer should fit one sentence: I preserve argument boundaries, distinguish expected outcomes from errors, and publish output only after the whole operation succeeds.
Sources and Further Reading
Technical references checked September 23, 2026. GNU-specific behavior is identified in the text; examples are original practice scenarios.