Shell Scripting Interview Questions and Answers

Below are 15+ shell scripting interview questions and practical exercises covering Bash automation, file tests, process handling, text processing, debugging, locking, and production-safe scripting. For operating system fundamentals (processes, memory, scheduling), see operating system interview questions. For C/C++ debugging context on Linux, see C and C++ interview questions.

NOTE

Scripting answers vary by shell and environment. The examples below assume bash on Linux unless noted otherwise.

Prep tip: Answer each question aloud or write the script yourself first, then read What interviewers are testing: to understand the hidden evaluation criterion. Use the explanation to learn the mechanism and compare your response with A strong answer is: For coding questions, test quoting, argument validation, exit status, and failure behavior—not only the happy path.


Interview context and how to prepare

What do shell scripting interviews test?

Practical automation under constraints:

  • File tests, loops, functions, exit codes
  • Text processing (awk, sed, grep patterns)
  • Debugging (bash -x, logging with logger)
  • Safe scripting habits for production cron/systemd jobs

A strong answer is:

Shell interviews test practical automation—file tests, loops, exit codes, awk/sed/grep, bash -x debugging, and safe habits for cron and systemd jobs.


Interactive automation and expect

How can you create a script that will wait for specific output and hence will act according to it? - for instance, wait for “username: ” before sending the username.

What interviewers are testing: whether you know expect matches prompts before sending input—the classic pattern for interactive CLI automation.

Use expect when a command is genuinely interactive:

tcl
#!/usr/bin/expect -f

spawn mycommand
expect "username: "
send "alice\r"

expect "password: "
send "$env(MY_PASSWORD)\r"

expect eof

expect waits until the child process emits text matching a pattern; send writes the appropriate response. Prefer environment variables or a secure credential mechanism over hard-coded passwords. Where possible, use a non-interactive CLI or API instead of automating prompts.

A strong answer is:

"I use expect for genuinely interactive commands: spawn the program, expect a prompt, then send the response. Where possible, I prefer a non-interactive CLI/API instead of automating prompts."


Logging, debugging, and safe execution

You want to add logger to your script so how can you send logging messages to the /var/log/messages for your script “*MyCoolScript*”?

What interviewers are testing: whether you know logger -t tags syslog/journald output—and that the destination file depends on distro config, not logger itself.

logger sends a message to the system logging service; it does not guarantee a specific file path.

bash
logger -t MyCoolScript 'Starting application'

Whether the message appears in /var/log/messages, /var/log/syslog, journald only, or another destination depends on distro and logging configuration. On systemd systems, inspect it with:

bash
journalctl -t MyCoolScript

A strong answer is:

"I send the message with logger -t MyCoolScript 'Starting application'. logger submits to the system logging service; whether it lands in /var/log/messages, /var/log/syslog, or only journald depends on the host's logging configuration."

You have a bash script that does not produce the expected result so how can you debug it?

What interviewers are testing: whether you debug with bash -x or set -x and trace execution—not guess at silent failures.

Run with tracing:

bash
bash -x ./script.sh

Or inside the script:

bash
set -x
# commands to trace
set +x

Check syntax without executing:

bash
bash -n ./script.sh

A useful diagnostic sequence:

  1. bash -n script.sh — syntax only
  2. bash -x script.sh — execution trace
  3. Check $? / command exit status
  4. Add targeted logging
  5. Run ShellCheck

A strong answer is:

"I start with bash -n for syntax and bash -x or set -x for execution tracing, then inspect exit statuses and run ShellCheck. I avoid adding random echo statements before understanding where execution diverges."

You need to create a backup script called backupMyFiles which will run every hour. How do you ensure the script is not already running and exit with a clear message if a previous run is still active?

What interviewers are testing: whether you prevent overlapping cron runs with flock or a lock file—not race-prone ps | wc counting.

bash
#!/usr/bin/env bash

exec 9>/run/lock/backupMyFiles.lock

if ! flock -n 9; then
    echo "Previous backupMyFiles is still running."
    exit 1
fi

# backup work here

If /run/lock is not writable for the account, use an appropriate per-user runtime directory.

A strong answer is:

"I use flock to acquire a non-blocking lock before starting. If another process already owns it, the script exits immediately with a clear message."


File permissions and validation scripts

Write a shell script that checks if a file (as an argument) has write permissions and accordingly if it is available print “*write access approved*” else print “*no write access*”.

What interviewers are testing: Whether you know Bash file-test operators, quote paths correctly, validate positional arguments, and distinguish effective write access from inspecting permission bits manually.

bash
#!/usr/bin/env bash

if [[ $# -ne 1 ]]; then
    echo "Usage: $0 <file>" >&2
    exit 2
fi

filename=$1

if [[ -w "$filename" ]]; then
    echo "write access approved"
else
    echo "no write access"
fi

A strong answer is:

"I validate that one path was supplied, quote the variable, and use Bash's -w file test to check whether the current process has write access."

Write a script that receives one parameters (file name) and checks if the file exists or not - If it does, print “*Roger that!*” else, print “*Huston we’ve got a problem!*”

What interviewers are testing: Whether you validate input and choose the correct file test (-f versus -e) while safely handling filenames containing spaces or glob characters.

bash
#!/usr/bin/env bash

if [[ $# -ne 1 ]]; then
    echo "Usage: $0 <file>" >&2
    exit 2
fi

if [[ -f "$1" ]]; then
    echo "Roger that!"
else
    echo "Houston, we've got a problem!"
fi

-f means the path exists and is a regular file. If the requirement is merely “path exists,” use -e.

A strong answer is:

"I quote the argument and use -f when I specifically require a regular file; I use -e when any existing filesystem object should count."

Write a script that checks if a file, given as an argument, has more than 10 lines or not, if it does - print “*Over 10*”, else print “*Less than 10*”

What interviewers are testing: Whether you can combine command substitution, redirected input, numeric comparison, and defensive argument handling without unnecessary pipelines.

bash
#!/usr/bin/env bash

if [[ $# -ne 1 || ! -f "$1" ]]; then
    echo "Usage: $0 <file>" >&2
    exit 2
fi

count=$(wc -l < "$1")

if (( count > 10 )); then
    echo "Over 10"
else
    echo "10 or fewer"
fi

$(...) is preferable to backticks; wc -l < "$1" avoids a useless cat; quote paths. Exactly 10 lines is not “less than 10.”

A strong answer is:

"I count with wc -l < "$1" and use arithmetic comparison. I also handle exactly 10 correctly instead of calling it 'less than 10.'"


Users, processes, and system introspection

You have a regular user access to a server, with no root permissions, but you need to create a script that requires root permissions to run - how can you manipulate the system to think that you have root permissions, without a real superuser access?

What interviewers are testing: whether you know fakeroot fakes metadata for packaging—not real kernel root; privileged work needs sudo, polkit, or capabilities.

You cannot make the kernel treat an unprivileged process as real root without authorization or a privilege mechanism.

fakeroot does not grant kernel privileges. It primarily simulates ownership/permission metadata for packaging and build workflows such as dpkg or rpm builds.

For genuine privileged actions, use approved sudo, polkit, capabilities, a privileged service, or redesign the script so privileged work is done by an authorized mechanism.

A strong answer is:

"You cannot make an ordinary process become real root without an authorized privilege mechanism. fakeroot only simulates ownership metadata for build/package workflows; privileged operations require something like sudo, capabilities, polkit, or a privileged service."

Write a script that goes over all users on the system and prints each user last login date, or No data for that user when last login is unknown.

What interviewers are testing: Whether you can enumerate system accounts and safely combine command output with loops without word-splitting, repeated subprocesses, or fragile cat | awk patterns.

bash
#!/usr/bin/env bash

while IFS=: read -r user _; do
    last_login=$(last -n 1 -- "$user" 2>/dev/null | head -n 1)

    if [[ -n $last_login && $last_login != wtmp* ]]; then
        printf '%s: %s\n' "$user" "$last_login"
    else
        printf '%s: No data\n' "$user"
    fi
done < <(getent passwd)

Login-history tooling differs between distributions and depends on retention configuration. This is an interview scripting exercise, not a reliable historical-accounting system.

A strong answer is:

"I enumerate accounts with getent passwd, query each account once, quote the username, and handle missing login history explicitly rather than parsing /etc/passwd with cat | awk and rerunning last."

How can you check what are the most common commands that you have used in the Linux shell?

What interviewers are testing: whether you parse history with awk/sort to rank command frequency—not manual scrolling.

bash
history |
awk '{$1=""; sub(/^ +/, ""); split($0, a, /[[:space:]]+/); count[a[1]]++}
     END {for (cmd in count) print count[cmd], cmd}' |
sort -nr |
head

Shell history is user-specific and its display format can be customized, which affects parsing.

A strong answer is:

"I strip the history number, extract the command name, count with awk, then sort numerically. I remember that shell history is user-specific and its display format can be customized."

Create a script called *KillUserProcs* that will get a username as an input and will kill all his processes.

What interviewers are testing: whether you validate the user, try TERM, then KILL—not blind kill -9 on the first signal.

bash
#!/usr/bin/env bash

if [[ $# -ne 1 ]]; then
    echo "Usage: $0 <username>" >&2
    exit 2
fi

username=$1

if ! id "$username" >/dev/null 2>&1; then
    echo "Unknown user: $username" >&2
    exit 1
fi

pkill -TERM -u "$username" || true
sleep 2
pkill -KILL -u "$username" || true

The caller needs permission to signal those processes.

A strong answer is:

"I validate the account, send TERM first, wait for graceful shutdown, and only use KILL for processes that remain. The script still needs sufficient permission to signal that user's processes."


Text processing and one-liners

Using perl, write a command that will print all the IPs, Bcasts and Masks configured on the server line by line.

What interviewers are testing: Whether you can parse structured-enough command output with Perl while recognizing that modern ip uses CIDR prefixes rather than legacy ifconfig netmask formatting.

See also: print all the IPs with ip command.

Prefer modern ip output:

bash
ip -o -4 addr show

Perl parsing for address, prefix, and broadcast:

bash
ip -o -4 addr show |
perl -ne '
    if (/\sinet\s+([0-9.]+)\/(\d+)(?:\s+brd\s+([0-9.]+))?/) {
        print "IP: $1\n";
        print "Prefix: /$2\n";
        print "Broadcast: ", ($3 // "N/A"), "\n";
    }
'

Modern ip reports the network mask as prefix length (/24). Convert to dotted-decimal only if the interviewer explicitly requires that legacy representation.

A strong answer is:

"I parse ip -o -4 addr show, extracting the IPv4 address, CIDR prefix, and broadcast address. I prefer modern ip output over parsing legacy ifconfig."

Create a Fibonacci function (Fn=Fn-1+Fn-2) using awk (until F20).

What interviewers are testing: whether you can iterate a Fibonacci sequence in awk with loop variables—not just recite the formula.

bash
awk 'BEGIN {
    a = 1
    b = 1

    for (i = 1; i <= 20; i++) {
        print a
        next_value = a + b
        a = b
        b = next_value
    }
}'

This prints the first 20 Fibonacci terms starting 1, 1.

A strong answer is:

"I keep the previous two values in variables, print the current term, then advance both values in a loop for 20 iterations."


Network and utility scripts

Write a script that goes to the Whatismyip website and then prints Your IP is with the result from the site.

What interviewers are testing: Whether you handle network-command failure, capture stdout safely, and distinguish public-IP discovery from inspecting local interface addresses.

Use a simple HTTPS endpoint and handle failure:

bash
#!/usr/bin/env bash

if ! ip=$(curl -fsS https://api.ipify.org); then
    echo "Could not determine public IP" >&2
    exit 1
fi

printf 'Your IP is: %s\n' "$ip"

A strong answer is:

"I call a simple HTTPS endpoint with curl -fsS, check the command's exit status, and only print the result if the request succeeds."

Create a small calculator in bash script which will have an internal function “*dosomething*” that will receive a math function as an input - mycalc 4+4\*4.

What interviewers are testing: Whether you write a reusable Bash function, preserve a mathematical expression through quoting, and avoid eval for arbitrary user input.

bash
#!/usr/bin/env bash

dosomething() {
    printf '%s\n' "$1" | bc -l
}

if [[ $# -ne 1 ]]; then
    echo "Usage: $0 '<expression>'" >&2
    exit 2
fi

dosomething "$1"

Run as:

bash
./mycalc '4+4*4'

Output: 20

For production-facing input, validate permitted expression syntax rather than treating arbitrary input as trusted.

A strong answer is:

"I pass the quoted expression to a function and feed it to bc -l. The caller quotes expressions containing shell metacharacters such as *."


Modern bash practices

Why do teams use set -euo pipefail in bash scripts?

What interviewers are testing: Whether you understand what each strict-mode option actually changes and know that errexit has exceptions requiring explicit error handling.

Common safety flags:

  • -e — asks Bash to exit for many unhandled non-zero statuses, but it has context-dependent exceptions in if/while tests, &&/||, pipelines, and negation; it is not a substitute for deliberate error handling
  • -u — treat unset variables as errors
  • -o pipefail — pipeline fails if any stage fails

Example header:

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

Combine with explicit trap handlers for cleanup in long-running jobs.

A strong answer is:

"set -euo pipefail asks Bash to stop on many unhandled command failures, reject unset variables, and propagate failures from pipeline stages. I still use explicit error handling where failure is expected because set -e has context-dependent exceptions."

How does ShellCheck help in interviews and on the job?

What interviewers are testing: Whether you use static analysis to catch quoting, portability, and data-flow mistakes before a shell script reaches cron, CI, or production.

ShellCheck statically analyzes bash/sh scripts for:

  • Quoting bugs and word-splitting traps
  • Undefined variables (especially after refactors)
  • Deprecated or non-portable constructs

ShellCheck is shell-aware and its recommendations depend on the target shell, which is another reason Bash shebangs should be consistent.

A strong answer is:

"ShellCheck statically catches common shell bugs such as unsafe word splitting, quoting mistakes, suspicious tests, undefined variables, and portability problems. I run it locally and in CI, but still test runtime behavior because static analysis cannot validate the environment."


On-site shell scripting interview prep


References

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)