Shell scripting is an essential skill that all Linux administrators, DevOps professionals, and systems programmers should have. Interviews assess your theoretical understanding, syntax accuracy, and your ability to write automation scripts. Having both given and taken these kinds of interviews over the years, I've put together the questions that come up most often, along with the kind of answers that actually land well on both sides of the table. Most common shell scripting interview questions have been collected and grouped according to their difficulty level and provided with brief answers and sample code snippets.
Read Also: What is IDE: Integrated Development Environment
A shell script is a text file containing a sequence of shell commands that are executed automatically to perform repetitive or administrative tasks. It helps automate workflows, reduce manual effort and improve consistency.
The shebang (#!) is placed at the beginning of the script to specify which interpreter should execute it. For example:
| #!/bin/bash |
This tells the operating system to run the script using the Bash shell, regardless of the user's default shell.
First, I grant execute permission using the chmod command:
| chmod +x script.sh |
Then I can execute it using:
| ./script.sh |
Alternatively, I can run it directly with an interpreter:
| bash script.sh |
The first method requires execute permission, the second does not.
The terminal is the interface, while the shell is the program that processes commands. Here is their brief differentiation:
| Feature | Shell | Terminal |
| Definition | A shell is a command-line interpreter that processes and executes user commands. | A terminal is an application or interface that allows users to interact with the shell. |
| Purpose | Interprets commands, runs programs and manages scripts. | Provides a text-based interface for entering commands and viewing output. |
| Role | Acts as the bridge between the user and the operating system. | Acts as the window or environment in which the shell runs. |
| Function | Executes commands, scripts and system operations. | Displays input and output while hosting the shell session. |
| Examples | Bash, Zsh, Fish, Korn Shell (Ksh), C Shell (Csh). | GNOME Terminal, Konsole, Windows Terminal, Terminal (macOS), xterm. |
| Dependency | Can run inside different terminal applications. | Typically launches a shell when opened. |
| Analogy | The shell is the engine that understands and executes commands. | The terminal is the dashboard through which you interact with the engine. |
Also Read: Kubernetes Tutorial For Beginner
Single-line comments are written using the # symbol.
Example:
|
# This is a comment echo "Hello World" |
Shell scripting does not have an official multi-line comment syntax, but developers commonly use multiple # lines or an unused here-document when appropriate.
The simplest way is to add & to command.
| ./backup.sh & |
If I want the script to continue running even after logging out, I use:
| nohup ./backup.sh & |
To monitor background jobs:
| jobs |
To bring a job back to the foreground:
| fg |
$? stores the exit status of the last executed command.
0 indicates success.
Any non-zero value indicates an error or failure.
I typically use ls -l.
| ls -l filename |
If the output starts with l and points to another file using ->, it is a symbolic (soft) link.
Example:
| lrwxrwxrwx 1 user user 10 Jul 8 link -> file.txt |
To verify a hard link, I compare inode numbers using:
| ls -li file.txt hardlink.txt |
If both files have the same inode number, they are hard links.
Example:
|
12345 file.txt 12345 hardlink.txt |
Read Also: Conditional Statements in Python
Shell scripts help automate repetitive and administrative tasks, reducing manual effort and improving efficiency.
Some common use cases include:
Automating backups
Monitoring servers
Deploying applications
Managing files and directories
Scheduling recurring tasks with cron
Processing logs
Performing system health checks
Using shell scripts also improves consistency and minimizes human error.
Advantages:
Easy to learn and write.
Automates repetitive tasks.
Saves time and reduces manual effort.
Ideal for system administration.
Integrates well with Linux/Unix commands.
No compilation is required.
Disadvantages:
Slower than compiled languages.
Debugging large scripts can be difficult.
Limited support for complex data structures.
Syntax varies across different shells.
Not suitable for large-scale application development.
Error handling is less robust compared to modern programming languages.
Some commonly used Unix/Linux shells are:
Bash (Bourne Again Shell): Most widely used Linux shell.
Sh (Bourne Shell): Original Unix shell.
Zsh (Z Shell): Advanced shell with plugins, themes and auto-completion.
Ksh (Korn Shell): Combines features of Bourne Shell and C Shell.
Csh (C Shell): Uses C-like syntax.
Tcsh: Enhanced version of C Shell.
Fish (Friendly Interactive Shell): User-friendly shell with intelligent suggestions and syntax highlighting.
Among these, Bash remains the default shell on many Linux distributions and is the most commonly used for shell scripting.
Read Also: Seaborn: A Powerful Python Library for Statistical Graphics
Bash provides several built-in special variables that make scripting easier by giving access to script information, arguments and process details. Some commonly used ones include:
$0 – Name of the script.
$1, $2, ... – Positional parameters (command-line arguments).
$# – Total number of arguments passed.
$@ – All arguments as separate quoted strings.
$* – All arguments as a single string.
$$ – Process ID (PID) of the current script.
$? – Exit status of the last executed command.
$! – PID of the last background process.
These variables are essential for writing dynamic and reusable shell scripts.
Both $* and $@ represent all command-line arguments, but they behave differently when enclosed in double quotes.
"$*" treats all arguments as a single string.
"$@" treats each argument as a separately quoted string.
For example, if the script is run as:
| ./script.sh Hello "Good Morning" |
"$*" becomes:
| Hello Good Morning |
as one argument.
"$@" preserves them as:
|
Hello Good Morning |
as two separate arguments.
In most scripting scenarios, "$@" is preferred because it preserves spaces and individual arguments correctly.
Bash supports integer arithmetic using several methods.
The most common approaches are:
| result=$((10 + 5)) |
or
| ((count++)) |
You can also use commands like let:
| let "x = 20 * 2" |
Related Article: Bash Cheat Sheet: Linux Commands and Scripting Guide
Both are loop control statements but serve different purposes.
break immediately exits the loop.
continue skips the current iteration and proceeds with the next iteration.
|
for i in {1..5} do if [ $i -eq 3 ]; then continue fi echo $i done |
If break were used instead, the loop would stop entirely when i becomes 3.
The key difference is where the script executes.
source script.sh (or . script.sh) runs the script in the current shell, so any variables, aliases, or environment changes remain available after execution.
./script.sh starts a new shell process and any changes made inside the script are lost once it finishes.
I use source when I want to load environment variables or shell configurations and./script.sh for running standalone scripts.
Arguments are passed after the script name:
| ./script.sh John 25 |
Inside the script, they can be accessed using positional parameters:
|
echo "Name: $1" echo "Age: $2" echo "Total arguments: $#" |
To process all arguments, I usually use:
|
for arg in "$@" do echo "$arg" done |
This safely handles arguments containing spaces.
Functions help organize code into reusable blocks.
Example:
|
greet() { echo "Welcome, $1" } greet "Alice" |
Functions can accept parameters, return an exit status using return, or output values using echo that can be captured by the caller.
Using functions improves code readability, maintainability and avoids repetition.
Read Also: What is C# Programming Language?
Pipes and redirection are fundamental features for connecting commands and managing input/output.
| sends the output of one command as input to another.
> redirects output to a file and overwrites existing content.
>> appends output to a file.
< reads input from a file.
2> redirect standard error to a file.
| ls | grep ".txt" |
| echo "Hello" > file.txt |
| echo "World" >> file.txt |
| sort < data.txt |
| command 2> error.log |
These operators are widely used for automation, logging and processing command output.
The recommended approach is using a while loop with read:
|
while IFS= read -r line do echo "$line" done < file.txt |
IFS= prevents trimming leading or trailing whitespace.
-r prevents backslashes from being interpreted as escape characters.
This method is reliable and handles spaces and special characters correctly.
Conditional statements let a script make decisions based on the result of a test or command.
if / elif / else
|
#!/bin/bash num=10 if [ $num -gt 0 ]; then echo "Positive number" elif [ $num -lt 0 ]; then echo "Negative number" else echo "Zero" fi |
[ ] is the test command; spaces around the brackets are required.
Comparison operators for numbers: -eq, -ne, -gt, -lt, -ge, -le.
Comparison operators for strings: =, !=, -z (empty), -n (not empty).
case
case is useful when checking a variable against multiple possible values and is often cleaner than a long if/elif chain.
|
read -p "Enter a fruit: " fruit case $fruit in apple) echo "It's an apple" ;; banana|mango) echo "It's a banana or mango" ;; *) echo "Unknown fruit" ;; esac |
Each pattern ends with ;; and *) acts as the default/catch-all case, similar to default in other languages.
Read Also: Complete List of Command Prompt (CMD) Commands
In production, I use a structured approach to error handling instead of relying on default behavior. I validate inputs, check the exit status of critical commands, handle expected failures gracefully and log meaningful error messages. I also terminate the script when unrecoverable errors occur and perform cleanup tasks before exiting. This prevents partial execution and makes troubleshooting much easier.
The [ command is the traditional POSIX-compatible test command and works across most Unix shells. The [[ construct is an enhanced testing feature available in Bash and some other modern shells. It provides more flexible string comparisons, supports pattern matching and reduces issues related to word splitting and filename expansion. For Bash scripts, I generally prefer [[ because it is safer and more readable.
A subshell is a separate shell process that executes commands independently of the parent shell. Any variables or environment changes made inside a subshell do not affect the parent shell once it finishes. Subshells are commonly used to isolate operations, execute grouped commands, or prevent temporary changes from impacting the main script.
The trap command allows a script to respond to signals or events such as interruptions, termination requests, or script exit. It is mainly used for cleanup tasks, like deleting temporary files, closing resources, or restoring the environment before the script exits. Using trap makes production scripts more reliable and prevents resource leaks.
I minimize unnecessary loops and avoid repeatedly reading the same file. Whenever possible, I use efficient command-line utilities designed for text processing, process data in a streaming manner instead of loading everything into memory and reduce the number of external command calls. I also profile the script to identify bottlenecks and optimize only the parts that significantly impact performance.
I start by reviewing logs to identify where the failure occurred. If additional investigation is needed, I enable controlled debugging to trace script execution without exposing sensitive information. I also verify input data, environment variables, permissions and command exit statuses. Any debugging changes are temporary to avoid affecting production performance or security.
I validate all user inputs, properly quote variables to prevent unexpected behavior, avoid executing untrusted input and follow the principle of least privilege. Sensitive information such as passwords or API keys should never be hardcoded and should instead be stored securely using environment variables or secret management solutions. I also restrict file permissions and log only necessary information without exposing confidential data.
I maintain structured logs with timestamps, log levels and descriptive messages so issues can be traced easily. Errors and important events are recorded separately when appropriate and logs are integrated with centralized monitoring systems whenever possible. I also ensure that critical failures generate alerts so administrators can respond quickly.
An idempotent script can be executed multiple times without causing unintended side effects or inconsistent system states. I achieve this by checking the current system state before performing actions, avoiding duplicate operations and ensuring resources are only created or modified when necessary. Idempotency is especially important for automation, deployments and scheduled jobs because scripts may need to be rerun after failures.
I break the script into reusable functions, use meaningful variable names and organize related logic into separate modules where appropriate. I eliminate duplicate code, add clear documentation and follow consistent formatting standards. For performance, I reduce unnecessary process creation, choose efficient utilities and periodically review the script for bottlenecks. Regular testing and version control also help ensure the script remains reliable as it evolves.
Read Also: What is R Programming Language?
I would first identify which part of the script is consuming the most time by reviewing logs, measuring execution time and analyzing resource usage. Once I locate the bottleneck, I would optimize inefficient loops, minimize repeated file reads and reduce unnecessary external command calls. I would process the log file in a streaming manner instead of loading it into memory and use efficient text-processing utilities wherever possible. Finally, I would test the optimized version with production-sized data to verify the performance improvement.
I would first compare the execution environments because cron runs with a much more limited environment than an interactive shell. I would verify file paths, environment variables, permissions and the working directory. Next, I would review the cron logs and add detailed logging to the script to identify the exact point of failure. After fixing the missing dependencies or configuration issues, I would test the cron job again to ensure it runs consistently.
I would design the script to terminate immediately when a critical step fails instead of continuing execution. I would validate the success of each important operation, implement proper error handling and log the reason for any failure. If the deployment involves temporary files or partially completed tasks, I would perform cleanup before exiting. This approach ensures that the deployment either completes successfully or fails safely without leaving the system in an inconsistent state.
I would divide the deployment into clearly defined stages such as validation, deployment, verification and cleanup. Before deploying, I would check connectivity and system readiness on every server. During deployment, I would log every important action with timestamps and record any failures. If deployment fails on a server, I would capture the error, continue or stop based on the deployment strategy and generate a final summary report. This makes the deployment process reliable, traceable and easier to troubleshoot.
I would remove all hardcoded credentials from the script and retrieve them securely through environment variables or a centralized secret management solution. I would ensure that only authorized users and processes can access the credentials by applying the principle of least privilege. I would also avoid logging sensitive information and regularly rotate credentials according to security policies. This approach improves security while keeping the automation process fully functional.
Shell scripting remains one of the most practical skills a Linux administrator, DevOps engineer, or systems programmer can bring to an interview. The freshers' questions confirm you understand the fundamentals, the intermediate questions test your fluency with everyday scripting patterns, and the experienced and scenario-based questions reveal whether you can write scripts that are safe, efficient, and reliable enough for production use.
Rather than memorizing answers, practice writing and running these scripts yourself. Break things on purpose, read the error messages, and get comfortable with tools like set -x, trap, and shellcheck. Interviewers are usually less interested in a textbook definition than in seeing that you've actually debugged a failing cron job or optimized a slow log-processing script before. The more hands-on scripting you do, the more naturally these answers will come during the actual interview.
Yes. Despite the rise of higher-level automation tools, shell scripting is still the glue behind most Linux systems, CI/CD pipelines, and infrastructure automation. Most DevOps and SRE roles expect at least intermediate shell scripting proficiency.
Bash is the best starting point since it's the default shell on most Linux distributions and the one most interview questions and production scripts are written for. Zsh is worth learning afterward if you want additional interactive features.
For entry-level roles, interviewers typically expect familiarity with basic syntax, variables, loops, conditionals, permissions, and simple automation tasks like backups or file management, roughly the "Freshers" and "Intermediate" sections above.
Set up a Linux VM or use WSL/a cloud instance. Recreate common real-world tasks: parsing logs, automating backups, writing deployment scripts, and scheduling jobs with cron. Practicing on real problems builds the muscle memory that interview questions are designed to test.