| grep pattern file | Search text for lines matching a pattern/regex |
| awk '{print $1}' file | Parse and process structured text by field |
| sed 's/old/new/g' file | Stream-edit text — substitute, delete, filter lines |
| find . -name "*.log" | Locate files by name, size, type, or modified time |
| ps aux / top | List running processes / live resource monitor |
| ss -tlnp | Show listening ports and owning process |
| df -h / du -sh | Disk space by filesystem / by directory |
| chmod 755 / chown user:grp | Change file permission bits / ownership |
Linux & Shell Scripting Interview Questions & Answers
Q1. What does the grep command do, and what is its general syntax?
A: grep (Global Regular Expression Print) scans input line by line and prints lines matching a given pattern, literal text or regex. The syntax is grep [options] pattern [file...]; when no file is given it reads from stdin, which is why it composes naturally in pipelines. Common flags include -i (case-insensitive), -r (recursive directory search), and -n (show line numbers).
grep -rn "ERROR" /var/log/app/
Q2. How do you search recursively, case-insensitively, and with line numbers using grep?
A: Combine -r (recurse into subdirectories), -i (ignore case), and -n (prefix each match with its line number) into a single flag group. This is the standard first command reached for when hunting an error string across a directory of rotated log files without knowing the exact casing or file it landed in.
grep -rin "outofmemory" /var/log/myapp/
Q3. What is the difference between grep, grep -E, and grep -P?
A: Plain grep uses basic regular expressions (BRE), where metacharacters like +, ?, and | must be backslash-escaped to be treated as special. -E (equivalent to egrep) enables extended regular expressions (ERE), so those metacharacters work unescaped, allowing alternation like a|b directly. -P enables Perl-compatible regular expressions (PCRE) on builds that support it, adding features like lookahead/lookbehind that neither BRE nor ERE support.
grep -E "ERROR|WARN" app.log
Q4. How do you invert a grep match to show lines that do NOT match a pattern?
A: The -v flag inverts the match, printing every line that does not contain the pattern instead of every line that does. This is commonly combined with other filters, for example stripping noisy DEBUG lines out of a log before searching it for something else.
grep -v "DEBUG" app.log
Q5. How do you count matching lines with grep without printing the lines themselves?
A: The -c flag makes grep print only the count of matching lines per file instead of the lines themselves, which is faster than piping to wc -l and avoids dumping potentially huge output to the terminal when you only need a number.
grep -c "Exception" app.log
Q6. How do you show the lines of context surrounding a grep match?
A: -A n shows n lines after each match, -B n shows n lines before, and -C n shows n lines on both sides. This is essential for reading a full stack trace, since the exception message line alone rarely tells the whole story without the surrounding "Caused by" frames.
grep -C 3 "NullPointerException" app.log
Q7. What does awk do, and how is its program structure organized?
A: awk is a pattern-scanning and text-processing language that treats input as records (by default, lines) split into fields (by default, whitespace-delimited, referenced as $1, $2, ...). A program is a series of pattern { action } rules; for every input record, if the pattern matches (or is omitted, matching every record), the action block executes. The special BEGIN and END blocks run once before the first record and once after the last, respectively.
awk '{print $1, $NF}' access.log
Q8. How do you change awk's field separator and sum a numeric column?
A: The -F flag sets the input field separator (here, a comma for CSV data). Inside the action, accumulating into a variable across records and printing it in the END block is the standard idiom for column aggregation, since awk automatically initializes numeric variables to 0.
awk -F',' '{sum+=$3} END{print sum}' data.csv
Q9. How do you use awk to print only lines matching a condition, such as HTTP 5xx responses in an access log?
A: awk's pattern portion can be a regex match against a specific field using the ~ operator, restricting the action to only records where that field matches, unlike grep which matches against the whole line. This lets you filter on a structured column (like an HTTP status code field) precisely, without accidentally matching "500" appearing elsewhere in the line.
awk '$9 ~ /^5[0-9][0-9]$/ {print $0}' access.log
Q10. What is the difference between $0, $1, and NF in awk?
A: $0 refers to the entire current record (the whole line as read); $1 through $N refer to individual fields split by the field separator. NF is a built-in variable holding the total number of fields in the current record, so $NF always references the last field dynamically regardless of how many fields a given line has, which is useful when field count varies row to row.
Q11. How does sed perform stream editing, and what is the basic substitution syntax?
A: sed (stream editor) reads input line by line, applies a sequence of editing commands, and writes the transformed result to stdout, leaving the original file untouched unless -i is used. The classic substitution command is s/pattern/replacement/flags, where the g flag replaces every occurrence on the line instead of just the first.
sed 's/foo/bar/g' file.txt
Q12. How do you edit a file in place with sed while keeping a backup of the original?
A: The -i flag edits the file in place; supplying a suffix immediately after it (no space, e.g. -i.bak) tells sed to first save a copy of the original file with that suffix appended before overwriting it, giving you a rollback path if the substitution was wrong.
sed -i.bak 's/DEBUG/INFO/g' app.log
Q13. How do you delete lines matching a pattern using sed?
A: The d command deletes any line matched by the preceding address (a line number, range, or regex). This example strips every comment line (starting with #) from a config file, which is a common preprocessing step before parsing configuration in a script.
sed '/^#/d' config.conf
Q14. How do you print only a specific range of lines from a file using sed?
A: The -n flag suppresses sed's default behavior of auto-printing every line, and the p command explicitly prints only the addressed lines — here, lines 10 through 20. This is a lighter-weight alternative to piping through head/tail when you need an arbitrary middle slice of a file.
sed -n '10,20p' app.log
Q15. What does the find command search for, and how do you locate files by name, size, and modification time?
A: find walks a directory tree and reports files/directories matching the given tests, which can be combined. This example finds files under /var/log named *.log, modified within the last day (-mtime -1), and larger than 10 megabytes (-size +10M) — a typical way to hunt for a runaway log file filling up disk.
find /var/log -name "*.log" -mtime -1 -size +10M
Q16. How do you use find to execute a command on each matched file?
A: The -exec action runs the given command for each matched file, with {} substituted for the file's path and \; terminating the command. Using -exec ... + instead batches multiple matched files into fewer command invocations, similar to how xargs batches arguments.
find . -name "*.tmp" -exec rm {} \;
Q17. What is the difference between find -exec and piping find's output into xargs?
A: Plain -exec cmd {} \; launches one new process per matched file, which is fine for a handful of files but slow for thousands. Piping to xargs collects the matched paths and batches as many as fit within the system's argument-length limit into each invocation of the target command, dramatically reducing the number of process spawns for large result sets.
find . -name "*.log" | xargs rm -f
Q18. Why is find ... -print0 | xargs -0 safer than a plain find | xargs pipeline?
A: A plain pipeline delimits filenames with newlines, which breaks if any filename itself contains a space, newline, or other special character — a surprisingly common occurrence. -print0 delimits each filename with a null byte instead, and xargs -0 splits its input on null bytes; since a null byte can never legally appear inside a filename, this combination is guaranteed to handle any filename correctly.
find . -name "*.log" -print0 | xargs -0 grep -l "ERROR"
Q19. How do you use xargs to run a command with each input item substituted at a specific argument position?
A: The -I{} flag defines a placeholder token (here, {}) that xargs replaces with each input item in turn, letting you position the substituted value anywhere in the command rather than only at the end — necessary whenever the target command's argument order isn't "value last."
cat hosts.txt | xargs -I{} ssh {} "uptime"
Q20. How do you combine grep, awk, sort, and uniq to count occurrences of a value across matching log lines?
A: Each tool does one job and passes its output to the next: grep filters to relevant lines, awk extracts the field of interest, sort groups identical values adjacently (a prerequisite for uniq, which only collapses adjacent duplicates), and uniq -c counts them, with a final sort ranking the most frequent value first. This "Unix pipeline" style is a very common way to answer ad-hoc log-analysis questions without writing a script.
grep "ERROR" app.log | awk '{print $5}' | sort | uniq -c | sort -rn
Q21. How do you search for a pattern across multiple files and show only the names of files that contain a match?
A: The -l flag makes grep print only the filename of each file containing at least one match, instead of the matching lines themselves, and it stops scanning each file as soon as one match is found for efficiency. This is useful for quickly narrowing down which log file among many rotated files contains a given error before opening one directly.
grep -l "ConnectionTimeout" /var/log/app/*.log
Q22. How do you use awk to print specific columns from ps output for lightweight monitoring?
A: Since ps aux output is whitespace-delimited, piping it through awk and selecting specific field numbers extracts exactly the columns you care about (here, PID, %CPU, %MEM, and command) without the noise of the full listing — a quick alternative to writing a full monitoring script for a one-off check.
ps aux | awk '{print $2, $3, $4, $11}'
Q23. How does the Linux file permission model work — owner, group, other, and the read/write/execute bits?
A: Every file has an owning user and an owning group, and permission bits are defined separately for three classes: the owner, the group, and everyone else (other). Each class gets a read (r), write (w), and execute (x) bit, so rwxr-xr-- means the owner has full access, the group can read and execute but not write, and everyone else can only read. For a directory, execute means the ability to traverse (cd) into it, and write means the ability to create or delete entries inside it.
Q24. How do you view file permissions in long-listing format, and how do you interpret the output?
A: ls -l shows one line per file with a leading type/permission string like -rwxr-xr--, followed by link count, owner, group, size, modification date, and name. The very first character indicates file type (- regular file, d directory, l symbolic link), and the remaining nine characters are three rwx triplets for owner, group, and other in that order.
ls -l script.sh
# -rwxr-xr-- 1 alice devs 4096 Aug 9 10:00 script.sh
Q25. What is the difference between chmod's symbolic mode and numeric (octal) mode?
A: Symbolic mode adjusts permissions relative to the current state using u/g/o/a combined with +/-/=, for example adding execute for the owner without touching anything else. Numeric (octal) mode sets the entire permission set at once by summing r=4, w=2, x=1 per class — 755 means rwx for owner and r-x for group and other — which is precise and easy to script, but replaces the whole mode rather than adjusting one bit.
chmod u+x script.sh
chmod 755 script.sh
Q26. What do the setuid, setgid, and sticky bits do?
A: setuid on an executable makes it run with the file owner's privileges instead of the invoking user's — the classic example is /usr/bin/passwd, owned by root, so any user can update the shadow password file through it. setgid on an executable runs it with the owning group's privileges; on a directory, it makes new files created inside inherit that directory's group. The sticky bit on a world-writable directory (like /tmp) restricts deleting or renaming a file within it to that file's owner, the directory's owner, or root, even though others have write access to the directory itself.
chmod 4755 /usr/bin/mytool # setuid
chmod 1777 /tmp # sticky bit
Q27. How do you change a file's owner and group using chown and chgrp?
A: chown user:group file changes both owner and group in one call; chgrp group file changes only the group. Both typically require root privileges (or CAP_CHOWN) unless you are only changing the group to one you already belong to, and both accept -R to apply recursively across a directory tree.
chown appuser:appgroup /opt/app/app.jar
chgrp deploy /opt/app/logs
Q28. What is umask, and how does it affect the default permissions of newly created files?
A: umask is a bitmask subtracted from the maximum default permissions a process grants when creating a new file or directory — 666 (rw-rw-rw-) for files and 777 (rwxrwxrwx) for directories, since files aren't made executable by default at creation. A umask of 022 clears the write bit for group and other, so new files come out as 644 and new directories as 755, without any explicit chmod call.
umask 022
Q29. How do you recursively change ownership of an entire directory tree?
A: The -R flag applies chown recursively, walking every file and subdirectory under the given path — the standard step after deploying an application as root, to hand ownership over to the dedicated service account it should run as.
chown -R appuser:appgroup /opt/app
Q30. What is the difference between a hard link and a symbolic link?
A: A hard link is an additional directory entry pointing to the exact same inode as the original file; both names are equally valid, the underlying data persists until every hard link to it is removed, and hard links cannot cross filesystem boundaries or point at directories. A symbolic link is a small separate file that simply stores a path to its target; it can cross filesystems and point at directories, but becomes a "dangling link" if the target is moved or deleted.
ln original.txt hardlink.txt
ln -s /opt/app/releases/v3 /opt/app/current
Q31. Why might a Java process fail to write to a log directory even though the log file shows 777 permissions?
A: Creating or appending within a directory also depends on the containing directory's own permissions and ownership, not solely the target file's bits — if the process's user/group lacks write and execute on the parent directory, writes still fail regardless of the file's mode. Mandatory access control frameworks like SELinux or AppArmor, filesystem mount options such as read-only or noexec, and disk quota limits can also silently block writes that plain Unix permissions would otherwise allow.
Q32. How do you audit a system for files owned by a specific user or with world-writable permissions?
A: find can search the whole filesystem by ownership (-user) or by permission bits (-perm), which is a common security review task to catch accidental over-permissioning. Redirecting stderr to /dev/null suppresses the "Permission denied" noise generated while traversing directories the current user can't read.
find / -user appuser -type f 2>/dev/null
find / -perm -0002 -type f 2>/dev/null
Q33. What information does ps aux show, and how does it differ from ps -ef?
A: ps aux (BSD-style syntax) lists every process for every user with columns like USER, PID, %CPU, %MEM, VSZ, RSS, STAT, START, and COMMAND. ps -ef (UNIX System V-style syntax) shows a similarly complete listing but explicitly includes PPID (parent PID) and the full command line, which is handy for tracing which process launched which. Both are widely available and largely interchangeable for everyday troubleshooting.
ps aux | grep java
ps -ef | grep java
Q34. How do you monitor CPU and memory usage per process in real time, and how does htop differ from top?
A: top provides an interactive, auto-refreshing view of overall system load and per-process CPU%/MEM%, sortable interactively (for example Shift+M for memory, Shift+P for CPU). htop is a more user-friendly alternative with color, mouse support, and a visual process tree, but must usually be installed separately, whereas top ships by default on nearly every distribution.
top -o %CPU
Q35. What do the process states R, S, D, Z, and T mean in the ps STAT column?
A: R is running or runnable (on the CPU or waiting for a turn); S is interruptible sleep, where most idle processes sit while waiting for an event; D is uninterruptible sleep, typically blocked on I/O and notably unkillable by ordinary signals until the I/O completes; Z is a zombie, a process that has exited but whose parent hasn't yet collected its exit status; T is stopped, for example suspended by SIGSTOP or Ctrl+Z.
Q36. How do you find the PID of a running Java process by name?
A: The JDK's jps tool lists running JVM processes with their main class or JAR name, which is more reliable than grepping ps output since it's aware of Java processes specifically. pgrep -f matches against the full command line and works for any process type, Java or otherwise.
jps -l
pgrep -f MyApplication
Q37. What is the difference between SIGTERM and SIGKILL, and why does it matter for gracefully shutting down a Java app?
A: SIGTERM (signal 15, the default signal sent by plain kill) asks a process to terminate gracefully; the JVM's registered shutdown hooks run, letting in-flight requests drain and resources close cleanly. SIGKILL (signal 9) terminates the process immediately at the kernel level and cannot be caught, blocked, or handled in any way, so shutdown hooks never execute and in-progress work or on-disk state can be left inconsistent. Always send SIGTERM first and reserve SIGKILL for processes that don't exit after a reasonable grace period.
kill -15 12345
kill -9 12345
Q38. How do you send a signal to all processes matching a name pattern instead of a single PID?
A: pkill matches against process names or, with -f, full command lines, and sends the specified signal (SIGTERM by default) to every matching process. This avoids the two-step dance of grepping ps output for a PID and then calling kill on it manually.
pkill -f "com.acme.OrderService"
Q39. What does kill -3 (SIGQUIT) do to a running JVM, and why is it useful for troubleshooting?
A: Sending SIGQUIT to a JVM process makes it print a full thread dump — every live thread's state and stack trace — to its standard output/log, without terminating the process. It's one of the fastest, lowest-overhead ways to snapshot exactly what every thread is doing at a given instant, invaluable for diagnosing a hung request, deadlock, or exhausted thread pool in production.
kill -3 $(pgrep -f MyApp)
Q40. What is the difference between nice and renice?
A: nice launches a new process with an initial scheduling priority (a niceness value from -20, highest priority, to 19, lowest; the default is 0), and only root can request negative, higher-priority values. renice changes the niceness of an already-running process by PID, so you can deprioritize (or, as root, reprioritize) a process without restarting it.
nice -n 10 ./batch-job.sh
renice -n 5 -p 12345
Q41. How do you list a process's open file descriptors and active network connections?
A: lsof -p pid lists every open file descriptor for that process — regular files, sockets, pipes — while lsof -i :port filters specifically to whatever process is bound to a given network port. Both are essential for diagnosing "too many open files" errors or finding what's currently using a port.
lsof -p 12345
lsof -i :8080
Q42. How can you find which process is holding a port that a Java service is failing to bind to?
A: Either lsof -i :port or ss -tlnp | grep port reveals the PID and process name currently bound to that port, letting you decide whether to stop the conflicting process, reconfigure it, or simply run your own service on a different port. This is one of the most common causes of a Spring Boot or Tomcat application failing at startup with "Address already in use."
lsof -i :8080
ss -tlnp | grep 8080
Q43. What is a zombie process, and how do you deal with one that persists?
A: A zombie is a terminated child process whose exit status hasn't yet been collected by its parent via wait(); it still occupies a slot in the process table but consumes no other resources. A single lingering zombie is harmless and self-cleans once the parent reaps it. If zombies keep accumulating, the real bug is in the parent process never calling wait()/waitpid() — you can't kill a zombie directly since it's already dead, so you typically need to fix or restart the parent process.
Q44. How do you check overall system load, and how do you interpret the load average numbers?
A: The three numbers reported by uptime are the average number of processes either running or in uninterruptible I/O wait, averaged over the last 1, 5, and 15 minutes respectively. A load average at or below the number of CPU cores generally means the system is keeping up; sustained values well above the core count indicate CPU or I/O saturation, with requests queuing for resources.
uptime
Q45. How do you view real-time system resource usage broken down by CPU, memory, and I/O?
A: vmstat interval count prints repeated samples showing columns like r (runnable processes), b (blocked in uninterruptible sleep), free (free memory), si/so (swap in/out), and us/sy/id/wa (user, system, idle, and I/O-wait CPU time). This quickly tells you whether a slowdown is CPU-bound, memory/swap-bound, or I/O-bound without needing a heavier monitoring tool.
vmstat 2 5
Q46. How do you view the full process tree to trace parent-child relationships?
A: pstree renders the process hierarchy visually, showing which process spawned which; adding -p includes PIDs. ps -ef --forest gives an equivalent tree view using indentation within the standard ps table format, useful when you also need the other ps columns (user, start time) alongside the hierarchy.
pstree -p 12345
ps -ef --forest
Q47. What is a pipe in the shell, and how does it connect commands?
A: A pipe (|) connects the standard output of one command directly to the standard input of the next, without writing an intermediate file to disk. Each command in the pipeline runs as its own process, and the shell typically starts them all concurrently, letting data stream through the kernel's pipe buffer as it's produced rather than waiting for the first command to fully finish.
cat access.log | grep "500" | wc -l
Q48. What is the difference between > and >> for output redirection?
A: > redirects stdout to a file, truncating (overwriting) it if it already exists, or creating it if not. >> appends to the end of the file instead of overwriting it, creating the file if needed — the correct choice whenever you're writing to a log file you don't want destroyed on every run.
echo "start" > run.log
echo "next line" >> run.log
Q49. What do file descriptors 0, 1, and 2 mean, and what does 2>&1 do?
A: File descriptor 0 is stdin, 1 is stdout, and 2 is stderr. 2>&1 redirects stderr to wherever stdout is currently pointing, and is commonly used as command > out.log 2>&1 to merge both streams into a single log file — the order matters, since stdout must already be redirected to the file before stderr is told to follow it.
java -jar app.jar > app.log 2>&1
Q50. How do you redirect stdout and stderr to two separate files?
A: Using two separate redirect operators — one for file descriptor 1 (stdout) and one for file descriptor 2 (stderr) — sends normal output and error output to different files, useful when you want to monitor errors independently without them interleaving with regular application logging.
java -jar app.jar 1>out.log 2>err.log
Q51. What does /dev/null do, and when would you redirect output there?
A: /dev/null is a special device file that silently discards anything written to it and returns end-of-file immediately when read from — a "black hole" for data. It's used to suppress unwanted output, such as noisy cron job messages or a command's verbose stdout/stderr you don't need, without affecting the command's actual exit status.
curl -s https://example.com > /dev/null 2>&1
Q52. What is a here-document (heredoc), and when is it useful in shell scripts?
A: A heredoc feeds a multi-line block of literal text as stdin to a command directly inside a script, using <<DELIMITER ... DELIMITER syntax, instead of requiring a separate external file. It's commonly used to embed inline SQL, generate a small config file, or send a multi-line message from within a script without extra file management.
cat <<EOF > config.yaml
server:
port: 8080
EOF
Q53. What does the tee command do, and how is it different from a plain redirect?
A: tee reads from stdin and writes it both to stdout — so it can continue flowing down the rest of a pipeline — and simultaneously to one or more named files, whereas a plain > redirect sends output only to the file, with nothing left flowing to a subsequent command. It's the standard tool when you want to both watch a command's live output on screen and save it for later.
java -jar app.jar | tee app.log
Q54. How do you chain commands so the next one runs only on success, only on failure, or unconditionally?
A: && runs the next command only if the previous one exited with status 0 (success); || runs the next command only if the previous one failed (non-zero exit); a plain ; runs the next command regardless of the previous command's exit status. These operators are the basis of most simple deployment and health-check one-liners.
mvn clean install && java -jar target/app.jar
systemctl start app.service || echo "start failed"
Q55. What is an environment variable, and how do you set one for the current shell session?
A: An environment variable is a named value held in a process's environment, inherited by any child process it spawns, commonly used to configure paths, credentials, and runtime options without hardcoding them into a program. export marks a shell variable so it's included in the environment passed to subsequently launched programs.
export JAVA_HOME=/usr/lib/jvm/java-21
echo $JAVA_HOME
Q56. What is the difference between export and a plain shell variable assignment?
A: VAR=value without export creates a variable local to the current shell process only — it is not passed to child processes spawned from that shell, such as a script or program you subsequently run. export VAR=value marks the variable for inclusion in the environment of every child process launched afterward, which is essential for variables like JAVA_HOME or PATH that any program you invoke needs to be able to see.
Q57. What is PATH, and how do you add a directory to it?
A: PATH is a colon-separated list of directories the shell searches, in order, to resolve a bare command name into an executable's location. Appending to it preserves the existing entries while adding a new search location at the end; prepending instead (PATH=/new/dir:$PATH) makes that directory take priority over existing entries containing a same-named executable.
export PATH=$PATH:/opt/app/bin
Q58. What is the difference between ~/.bashrc, ~/.bash_profile, and /etc/environment?
A: ~/.bashrc runs for every new interactive non-login shell (like opening a new terminal tab) and typically holds aliases, functions, and prompt customization. ~/.bash_profile (or ~/.profile) runs once at login shell startup, such as an SSH session, and traditionally sources .bashrc plus sets session-wide variables. /etc/environment is a system-wide, non-shell-script key=value file read at login by PAM, intended for variables that should apply to every user and every shell type, not just interactive bash sessions.
Q59. How do you view all currently set environment variables, and how do you unset one?
A: printenv (or env) lists every variable currently exported into the environment. unset removes a variable entirely from the current shell (and hence from the environment of any subsequently launched child process), which is different from setting it to an empty string.
printenv
unset JAVA_OPTS
Q60. How do you pass environment variables to a Java process, and how does that differ from JVM system properties?
A: OS-level environment variables, set before launching the JVM as shown, are read inside Java via System.getenv("VAR_NAME"). JVM system properties, by contrast, are set with -Dkey=value directly on the java command line and read via System.getProperty("key") — the two mechanisms are distinct even though both configure runtime behavior, and -D properties are the more common way to pass application-specific configuration directly into a Java process.
JAVA_OPTS="-Xmx2g" java -jar app.jar
Q61. How do you declare and use a variable in a bash script, and why can't there be spaces around =?
A: Bash only parses VAR=value as an assignment when there is no whitespace around the =; writing NAME = value with spaces instead gets parsed as running a command called NAME with two arguments, = and value, producing a "command not found" error. Variables are referenced with a $ prefix, optionally wrapped in braces (${name}) to disambiguate the variable name from surrounding text.
name="deploy"
echo "Running $name"
Q62. What is the shebang line, and why does every bash script need one?
A: The shebang (#!) on the very first line tells the kernel which interpreter to invoke on the rest of the file when it's executed directly (for example, ./script.sh with the execute bit set). Without it, the OS has no reliable way to determine how to run the file's contents; running the script by explicitly typing bash script.sh works around a missing or wrong shebang, but loses the ability to execute the file directly and any portability guarantees the shebang provides.
#!/bin/bash
Q63. How do you write an if/elif/else conditional in bash?
A: The if [ condition ]; then ... elif [ condition ]; then ... else ... fi structure evaluates conditions in order and runs the block of the first one that's true, falling through to else if none match. Numeric comparisons inside single-bracket tests use operators like -eq, -ne, -lt, and -gt rather than ==, which is reserved there for string comparison.
if [ "$STATUS" -eq 0 ]; then
echo "Success"
elif [ "$STATUS" -eq 1 ]; then
echo "Warning"
else
echo "Failure"
fi
Q64. What is the difference between [ ], [[ ]], and (( )) in bash conditionals?
A: [ ] is the POSIX test command, portable across shells but with quirky quoting rules and no native support for pattern matching or unescaped &&/||. [[ ]] is a bash (and zsh/ksh) keyword extension that supports pattern and regex matching (==, =~), safer word-splitting behavior around unquoted variables, and logical operators directly inside it. (( )) is for arithmetic evaluation, letting you write natural comparisons like ((x > 10)), returning true/false based on whether the arithmetic result is nonzero.
Q65. How do you write a for loop to iterate over a list of files and over a numeric range in bash?
A: for var in list; do ... done iterates once per item in the list, which can be a glob pattern, a set of literal words, or command substitution output. Brace expansion ({1..5}) generates a numeric range inline without needing an external seq command.
for file in *.log; do
echo "Processing $file"
done
for i in {1..5}; do
echo "Iteration $i"
done
Q66. How do you write a while loop, and how is it commonly used to read a file line by line?
A: while condition; do ... done repeats the block as long as the condition remains true. Redirecting a file into a while read loop is the standard safe idiom for processing a file line by line: IFS= prevents leading/trailing whitespace from being stripped, and -r prevents backslashes in the line from being interpreted as escape sequences, so each line is read exactly as written.
while IFS= read -r line; do
echo "Line: $line"
done < input.txt
Q67. How do you define and call a function in bash, and how does it "return" a value?
A: Functions are defined with name() { ... } and invoked just like any other command, with arguments accessible inside the function as $1, $2, etc. — independent of the script's own positional parameters. Bash functions don't return arbitrary data types like other languages: return sets a numeric exit status (0-255) checked via $?, while actual data is typically "returned" by writing to stdout and having the caller capture it with command substitution, such as result=$(greet "World").
greet() {
local name=$1
echo "Hello, $name"
}
greet "World"
Q68. What do $1, $@, $#, and $0 represent in a bash script?
A: $0 is the script's own name/path as it was invoked; $1, $2, and so on are the individual positional arguments passed to the script; $# is the count of arguments supplied; $@ expands to all positional arguments as separate quoted words — the idiomatic choice when looping with for arg in "$@" — while $* instead joins them into one single string using the first character of IFS.
Q69. What is the difference between a local and a global variable inside a bash function?
A: By default, any variable assigned inside a function is global — visible and modifiable outside that function as well, which can cause subtle bugs in larger scripts where a helper function accidentally clobbers a variable the caller relies on. Declaring it with local scopes the variable to that function invocation only; it ceases to exist once the function returns, and is the recommended default for any variable that shouldn't leak into the caller's scope.
Q70. How do you declare and use an array in bash?
A: Arrays are declared with parentheses, individual elements accessed by index with ${arr[i]}, all elements expanded with ${arr[@]}, and the element count read with ${#arr[@]}. Iterating with "${arr[@]}" (quoted, with @) correctly preserves elements containing spaces as single items, unlike unquoted expansion which would word-split them.
hosts=("web1" "web2" "web3")
echo "${hosts[0]}"
echo "${#hosts[@]}"
for h in "${hosts[@]}"; do
ssh "$h" uptime
done
Q71. What does set -euo pipefail do at the top of a bash script, and why is it recommended?
A: -e exits the script immediately if any command returns a non-zero status, instead of silently continuing past a failure. -u treats a reference to an undefined variable as an error rather than silently expanding to an empty string, catching typos in variable names early. -o pipefail makes a pipeline's overall exit status equal to the first non-zero status of any command within it, rather than only the last command's status, so a failing grep buried in the middle of a pipe isn't masked by a later, successful command. Together they make scripts fail fast and loud instead of limping along after an unnoticed error.
#!/bin/bash
set -euo pipefail
Q72. How do you check the exit status of the last command in bash, and what does 0 vs. non-zero mean?
A: $? holds the exit status of the most recently completed foreground command. By universal Unix convention, 0 means success and any non-zero value from 1 to 255 indicates failure, with individual tools assigning their own specific meanings to particular non-zero codes. Checking $? must happen immediately after the command of interest, since running any other command in between overwrites it.
java -jar app.jar
if [ $? -eq 0 ]; then
echo "Started OK"
fi
Q73. How do you use a case statement in bash for multi-branch matching, such as a service control script?
A: case value in pattern) commands ;; esac matches the given value against a series of patterns in order, running the block for the first match, with * serving as a catch-all default. This is the idiomatic way to implement a start/stop/restart-style dispatch script driven by a single command-line argument, cleaner than a long if/elif chain of string comparisons.
case "$1" in
start)
systemctl start app.service
;;
stop)
systemctl stop app.service
;;
restart)
systemctl restart app.service
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
Q74. What is command substitution in bash, and why is $(...) preferred over backticks?
A: Command substitution runs a command and replaces the $(...) expression with its captured stdout, letting you assign a command's output straight into a variable. $(...) is preferred over the legacy backtick syntax because it nests cleanly — backticks require awkward escaping when nested inside each other — and is visually unambiguous about exactly where the substitution starts and ends.
today=$(date +%Y-%m-%d)
count=$(grep -c "ERROR" app.log)
Q75. How do you check whether a file or directory exists in a bash script before acting on it?
A: -f tests for a regular file, -d for a directory, -e for existence of any type, -x for executable permission, -w for writable, and -r for readable. These test operators are the standard guard used before attempting a file operation in a script, avoiding a cryptic failure deep inside the script when a path turns out not to exist.
if [ -f "/opt/app/app.jar" ]; then
echo "Jar found"
fi
if [ -d "/var/log/app" ]; then
echo "Log dir exists"
fi
Q76. How do you provide a default value for an unset variable or a missing script argument?
A: The ${VAR:-default} parameter expansion evaluates to $VAR if it is set and non-empty, otherwise to default, without modifying VAR itself. A related form, ${VAR:=default}, does the same but also assigns the default back into VAR if it was unset — a convenient way to provide sane defaults for optional script arguments or config values without an explicit if-check.
PORT=${1:-8080}
Q77. How do you trap signals inside a bash script to guarantee cleanup on exit?
A: trap registers a function or command to run when the script receives a specified signal, or the pseudo-signal EXIT, which fires whenever the script exits for any reason — normal completion, an error, or an actual received signal. This is the standard way to guarantee cleanup work, such as removing temp files, releasing a lock file, or stopping a background process, happens even if the script is interrupted with Ctrl+C or killed early.
cleanup() {
echo "Cleaning up temp files..."
rm -f /tmp/app.$$.tmp
}
trap cleanup EXIT SIGINT SIGTERM
Q78. How do you run a long process in the background from a script and later wait for it to finish?
A: Appending & after a command runs it asynchronously in the background, and $! immediately captures its PID. wait pid pauses the script until that specific background process (or, called without arguments, all currently running background jobs) completes — essential when a script kicks off multiple parallel tasks and needs to know when they've all finished before moving on.
java -jar worker.jar &
WORKER_PID=$!
echo "Started worker with PID $WORKER_PID"
wait $WORKER_PID
Q79. What is cron, and how do you edit a user's crontab?
A: cron is a time-based job scheduler daemon that runs commands at times and intervals defined in a crontab (cron table) file. crontab -e opens the current user's crontab in an editor for modification, crontab -l lists its current contents, and system-wide jobs can additionally live in /etc/crontab or drop-in files under /etc/cron.d/.
crontab -e
crontab -l
Q80. What do the five fields in a cron schedule expression represent?
A: In order, the fields are minute (0-59), hour (0-23), day of month (1-31), month (1-12), and day of week (0-6, where Sunday is 0). The example below runs backup.sh at 02:00 every day regardless of date or weekday, since * in a field means "every value."
# minute hour day-of-month month day-of-week command
0 2 * * * /opt/app/scripts/backup.sh
Q81. How do you schedule a cron job to run every 15 minutes, and what does the */N syntax mean?
A: */N in a cron field means "every Nth value starting from the field's minimum," so */15 in the minute field fires at :00, :15, :30, and :45 of every hour. This step syntax also works on explicit ranges, for example 0-30/10 meaning every 10 units between 0 and 30 rather than across the whole field.
*/15 * * * * /opt/app/scripts/healthcheck.sh
Q82. Why do cron jobs often fail even though the same command works fine when run manually from a terminal?
A: cron jobs run with a minimal environment — PATH is typically just /usr/bin:/bin, none of an interactive shell's exported variables like JAVA_HOME or custom PATH additions are inherited, and the working directory defaults to the crontab owner's home rather than wherever you happened to be when testing manually. The fix is to use absolute paths for every binary and file referenced in the job, and to explicitly export any required environment variables at the top of the script (or within the crontab entry itself) rather than relying on them already being set.
Q83. How do you redirect a cron job's output to a log file for troubleshooting?
A: Appending redirection operators directly to the crontab command line, exactly as you would in an interactive shell, captures both stdout and stderr from the scheduled job into a file for later review, instead of them being silently emailed to the crontab owner (cron's traditional default behavior) or simply lost.
0 2 * * * /opt/app/scripts/backup.sh >> /var/log/backup.log 2>&1
Q84. What does the @reboot shortcut and other cron nicknames mean?
A: @reboot runs the associated command once at system startup instead of on a recurring schedule — useful for starting a monitoring script or re-establishing state right after a reboot. Other shortcuts include @daily (equivalent to "0 0 * * *"), @hourly ("0 * * * *"), @weekly, and @monthly, which are simply more readable stand-ins for their equivalent five-field expressions.
Q85. How do you check cron's own execution logs to confirm whether a job actually fired?
A: Depending on the distribution, cron's activity — a log entry each time a job is invoked, though not the job's own output — is written to /var/log/syslog or /var/log/cron, or accessible via journalctl on systemd-based systems. Checking this is the first step when a scheduled job silently "didn't run," since it tells you whether cron attempted the job at all versus the job running but failing internally.
grep CRON /var/log/syslog
journalctl -u cron
Q86. How would you use cron to periodically capture a JVM heap dump for a memory leak investigation without constantly babysitting the process?
A: Scheduling periodic dumps (for example, every 6 hours) captures the heap's growth trend over time for comparison in a tool like Eclipse Memory Analyzer, rather than relying on catching the process right before it runs out of memory. Note that a literal percent sign in date's format string must be escaped as \% inside a crontab, since cron otherwise treats an unescaped % as a newline; also be mindful that jmap with the live option forces a full garbage collection and can pause the application briefly, so schedule it for low-traffic windows.
0 */6 * * * /usr/bin/jmap -dump:live,format=b,file=/tmp/heap-$(date +\%Y\%m\%d\%H).hprof $(pgrep -f MyApp) >> /var/log/heapdump.log 2>&1
Q87. What is systemd, and what does a .service unit file define?
A: systemd is the init system and service manager used by most modern Linux distributions, running as PID 1 and responsible for booting the system and supervising long-running services. A .service unit file, typically under /etc/systemd/system/, declares how to start, stop, and restart a service: the executable to run, its working directory, environment variables, restart policy, and dependencies on other units.
[Unit]
Description=My Java Application
After=network.target
[Service]
ExecStart=/usr/bin/java -jar /opt/app/app.jar
Restart=on-failure
User=appuser
[Install]
WantedBy=multi-user.target
Q88. What are the basic systemctl commands to start, stop, enable, and check a service?
A: start/stop/restart control a service's current running state immediately; enable/disable control whether it starts automatically at boot, by creating or removing a symlink into the appropriate target's .wants directory; status shows the current state, recent log lines, and the main PID in one view.
systemctl start myapp
systemctl enable myapp
systemctl status myapp
Q89. What is the difference between systemctl restart and systemctl reload for a running service?
A: restart fully stops the running process and starts a brand-new one, dropping all existing connections and in-memory state — necessary for changes like deploying a new JAR. reload, when the unit defines an ExecReload, sends a signal (often SIGHUP) asking the running process to re-read its configuration in place, preserving open connections — a pattern common for servers like nginx, but less commonly implemented by typical Java applications unless explicitly coded to handle it.
Q90. How do you view the logs of a systemd-managed service?
A: journalctl -u <unit> filters the systemd journal down to that service's log output, including both anything it wrote to stdout/stderr and systemd's own lifecycle messages about it. Adding -f follows new entries in real time like tail -f, and --since filters by a time range — both essential during live troubleshooting of a production incident.
journalctl -u myapp -f
journalctl -u myapp --since "1 hour ago"
Q91. What does Restart=on-failure mean in a systemd unit, and how does it differ from Restart=always?
A: on-failure restarts the service only if it exits with a non-zero code, is killed by certain signals, or times out — a clean, intentional exit with status 0 is left stopped. always restarts the service after any exit whatsoever, including a clean shutdown, which is rarely what you actually want for an app you might deliberately stop for maintenance — though note that systemctl stop still works even with Restart=always, since that's treated as an explicit stop request rather than a crash.
Q92. How do you set environment variables for a systemd-managed Java service?
A: Environment= sets individual key-value pairs directly in the unit file, while EnvironmentFile= points to an external file of KEY=value lines, which is more convenient when there are many variables or they need to change without editing the unit itself. Both are available to the process launched by ExecStart, including for interpolation into the command line as shown.
[Service]
Environment="JAVA_OPTS=-Xmx2g -Xms2g"
EnvironmentFile=/etc/myapp/env
ExecStart=/usr/bin/java $JAVA_OPTS -jar /opt/app/app.jar
Q93. What does systemctl daemon-reload do, and when must you run it?
A: daemon-reload tells systemd to re-scan and re-parse unit files on disk, picking up any changes made since it last loaded them — a new unit file, an edited ExecStart line, a changed dependency. It must be run after editing a unit file and before starting or restarting the service for the change to actually take effect; forgetting this step is one of the most common systemd troubleshooting gotchas, where a service keeps starting with stale, previously-loaded configuration.
systemctl daemon-reload
systemctl restart myapp
Q94. How do you limit a systemd service's memory and CPU usage using resource control directives?
A: systemd integrates with the kernel's cgroups to enforce resource limits per unit. MemoryMax caps the total memory the service's cgroup can use, with the kernel OOM-killing processes within that cgroup if it's exceeded; CPUQuota caps CPU time as a percentage of a single core, where 150% allows roughly 1.5 cores' worth of processing. This offers a lightweight way to bound a service's resource footprint without running it inside a full container.
[Service]
MemoryMax=2G
CPUQuota=150%
Q95. What does netstat show, and why is ss now generally preferred over it?
A: netstat historically showed active network connections, listening ports, and routing tables, but it's deprecated on most modern distributions and reads connection information by parsing files under /proc, which is comparatively slow. ss (socket statistics) queries the kernel directly via netlink, is significantly faster on systems with many open connections, and is the tool actively maintained and shipped by default going forward, with largely equivalent flags — -t for TCP, -l for listening sockets, -n for numeric ports, -p to show the owning process.
ss -tlnp
Q96. How do you check which ports are currently listening on a server?
A: Combining -t (TCP), -l (listening only), -n (numeric addresses/ports, skipping slow DNS lookups), and -p (owning process) with either ss or netstat gives a complete listing of every service currently accepting connections and which process owns each port — the first thing to check when deciding whether a port is free to use or a service failed to bind.
ss -tlnp
netstat -tlnp
Q97. How do you test connectivity to a specific host and port from the command line, useful for verifying a Java service can reach a database?
A: Both nc -zv and telnet attempt a TCP connection to the given host and port without sending application-level data, confirming whether the network path and listening service are reachable. nc -zv (zero-I/O mode, verbose) reports success or failure immediately and exits cleanly, while telnet opens an interactive session you must manually exit — nc is generally the more scriptable, modern choice for this check.
nc -zv db-host.internal 5432
Q98. How do you check disk space usage at the filesystem level versus for a specific directory?
A: df -h reports overall disk usage per mounted filesystem in human-readable units — total, used, available, and percent used — the first command to check when a service fails with "No space left on device." du -sh path summarizes the total size of a specific directory tree recursively into a single line, useful for pinpointing which directory (often accumulated log files) is actually consuming the space df reported as full.
df -h
du -sh /var/log/app
Q99. How do you find the largest directories or files under a path when disk usage is unexpectedly high?
A: Listing every entry's size with du -ah, sorting numerically in reverse with sort -rh (human-readable-aware sort), and limiting to the top results with head quickly surfaces the biggest space consumers under a directory, which is the fastest way to hunt down what's filling a disk before deciding what to clean up or archive.
du -ah /var/log | sort -rh | head -20
Q100. How do you check current network interface configuration and IP addresses?
A: ip addr show (or its shorthand ip a) lists every network interface along with its assigned IPv4/IPv6 addresses, state (up/down), and MAC address. This is the modern replacement for the older ifconfig command, which is deprecated and absent by default on many current distributions.
ip addr show
Q101. How do you look up and troubleshoot DNS resolution from the command line?
A: Both nslookup and dig query DNS and display the resolved IP address(es) for a hostname; dig gives more detailed output — query time, the authoritative server that answered, and full record data — and is generally preferred for troubleshooting DNS issues in depth, while nslookup gives a quicker, simpler answer for a basic lookup.
dig db-host.internal
Q102. How do you create and extract a gzip-compressed tar archive?
A: -c creates an archive and -x extracts one; -z pipes the archive through gzip compression or decompression; -v is verbose, listing each file as it's processed; -f specifies the archive filename and must immediately precede that filename argument. -C changes into the given directory before extracting, so files land in the intended location rather than the current working directory.
tar -czvf app-backup.tar.gz /opt/app
tar -xzvf app-backup.tar.gz -C /opt/restore
Q103. How do you list the contents of a tar archive without extracting it?
A: Combining -t (list contents) with the same -z and -v flags used for extraction previews exactly what's inside an archive — file paths, permissions, sizes — without actually writing anything to disk, useful for confirming an archive contains what you expect before committing to extracting it.
tar -tzvf app-backup.tar.gz
Q104. What is the difference between tar's gzip, bzip2, and xz compression options in terms of ratio versus speed?
A: gzip (-z) is fast to compress and decompress with a moderate compression ratio, making it a good general-purpose default. bzip2 (-j) typically compresses smaller than gzip but is noticeably slower, especially when compressing. xz (-J) generally achieves the best compression ratio of the three but is the slowest and most CPU-intensive, a reasonable trade-off for archival backups where storage cost matters more than speed and CPU isn't contended.
Q105. How do you extract just a single file from a large tar archive without extracting everything?
A: Appending the specific path (matching how it's stored inside the archive) after the archive filename in the extract command tells tar to extract only that one member instead of the whole archive, saving time and disk space when you only need one file out of a large backup.
tar -xzvf app-backup.tar.gz opt/app/logs/app.log
Q106. What is a thread dump, and what are the common ways to capture one for a running Java process?
A: A thread dump is a snapshot of every live thread in the JVM at a point in time, including each thread's state (RUNNABLE, BLOCKED, WAITING) and full stack trace — essential for diagnosing hangs, deadlocks, or thread-pool exhaustion. It can be captured with jstack pid, by sending SIGQUIT (kill -3 pid, which writes it to the process's own stdout/log), or via JMX-based tooling like VisualVM or Java Flight Recorder, all without needing to restart the process.
jstack -l 12345 > threaddump-$(date +%s).txt
Q107. How do you spot a deadlock in a thread dump?
A: jstack automatically detects certain classic lock-ordering deadlocks and prints a "Found one Java-level deadlock" section explicitly listing the involved threads and the locks each is waiting for versus holding. Even without that explicit section, you can trace one manually: look for multiple threads in BLOCKED state, each waiting to acquire a lock another blocked thread currently holds, forming a cycle where thread A waits on a lock held by thread B, which in turn waits on a lock held by thread A.
Q108. What is a heap dump, and how do you capture one from a running JVM with jmap?
A: A heap dump is a binary snapshot of every object on the JVM heap at capture time, used to find memory leaks, oversized caches, or unexpected object retention by opening it in a tool like Eclipse Memory Analyzer (MAT) or VisualVM. The live qualifier forces a full garbage collection before dumping so only reachable, still-live objects are included, which shrinks the dump size and clarifies true retention — but the forced GC can cause a noticeable pause on a large production heap, so it's best triggered during a maintenance window or low-traffic period.
jmap -dump:live,format=b,file=heap.hprof 12345
Q109. How do you configure a JVM to automatically write a heap dump on OutOfMemoryError, instead of trying to catch the process in the act?
A: This pair of JVM flags tells the runtime to automatically write a full heap dump to the specified path the moment an OutOfMemoryError is thrown, capturing the exact state that caused the failure rather than requiring you to reproduce it or get lucky with a manually-timed jmap. It's considered a production best practice to always enable this flag on Java services, since OutOfMemoryError incidents are rarely reproducible on demand after the fact.
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/dumps/ -jar app.jar
Q110. How do you grep a large application log for exceptions and view the surrounding stack trace context?
A: Combining -A n (show n lines after each match) with a pattern like "Exception" surfaces both the exception message line and enough of the following stack trace to be useful, and piping through less lets you page through potentially long output interactively rather than having it all scroll past in the terminal.
grep -A 20 "Exception" app.log | less
Q111. How do you use awk to extract and tally HTTP response codes from an access log to investigate elevated error rates?
A: Extracting the status-code field with awk, then piping through sort | uniq -c | sort -rn, produces a ranked frequency table of every response code seen in the log — an immediate way to see whether errors are concentrated in 500s, 502s, or elsewhere, without writing a dedicated log-parsing script for what is often a one-off investigation.
awk '{print $9}' access.log | sort | uniq -c | sort -rn | head
Q112. How do you tail a live log file while simultaneously filtering it for a specific error pattern in real time?
A: tail -f streams new lines as they're appended to the file, and piping through grep filters them live as they arrive. --line-buffered forces grep to flush its output after every matching line instead of buffering output in larger blocks (grep's default behavior when its output isn't directly connected to a terminal), which would otherwise noticeably delay matches from appearing in a piped context like this.
tail -f app.log | grep --line-buffered "OutOfMemoryError"
Q113. How do you monitor JVM garbage collection activity in real time without attaching a full profiler?
A: jstat -gcutil pid interval samples GC statistics at the given interval (in milliseconds) for the target PID, printing percentages of each generation's capacity currently used — survivor spaces, eden, old generation, metaspace — plus cumulative counts and time spent in young and full GCs. It's a lightweight way to spot GC pressure or a steadily growing old-generation trend without the overhead of enabling full GC logging or attaching an APM agent.
jstat -gcutil 12345 1000
Q114. How do you enable detailed GC logging on a JVM for later offline analysis?
A: Unified JVM logging (Java 9+) can record every GC event with timestamps to a rotating set of log files, letting you analyze pause frequency, pause duration, and heap-occupancy trends after the fact using a tool like GCViewer or GCEasy, without needing to reproduce the issue live in front of a monitoring dashboard. Rotating by file count and file size, as shown, prevents the log from growing unbounded on a long-running production service.
java -Xlog:gc*:file=/var/log/app/gc.log:time,uptime:filecount=5,filesize=50M -jar app.jar
Post a Comment
Add