Shell Scripting
Shell Scripting
A comprehensive question and answer guide for Shell Scripting — from the very basics to advanced topics. Use it to study, review, or prepare for interviews.
Beginner
Q1: What is a shell?
A shell is a command-line interpreter that provides an interface between the user and the operating system kernel.
It reads commands typed by the user and executes them.
Common shells include bash, sh, zsh, fish, and dash.
Q2: What is a shell script?
A shell script is a plain text file containing a sequence of shell commands that are executed in order. Shell scripts are used to automate repetitive tasks, manage files, and configure systems.
Q3: What is a shebang and why is it important?
The shebang (#!) is the first line of a shell script that tells the operating system which interpreter to use to execute the script.
#!/bin/bash echo "Hello, World!"
Without the shebang the script may be run with the wrong shell or fail to execute directly.
Q4: How do you make a shell script executable?
chmod +x script.sh ./script.sh
chmod +x adds execute permission, and ./ runs it from the current directory.
Q5: How do you print output to the terminal?
echo "Hello, World!" printf "Name: %s\n" "Alice"
echo is simple and appends a newline automatically.
printf gives more control over formatting (similar to C's printf).
Q6: What are the differences between echo and printf?
# echo with escape sequences (requires -e flag) echo -e "Line1\nLine2" # prints two lines echo -n "No newline" # suppress trailing newline # printf — always interprets escape sequences, no flag needed printf "Line1\nLine2\n" # prints two lines printf "%-10s %5d\n" "age" 25 # formatted columns printf "%s\n" "$@" # safely print each argument on its own line
Prefer printf in scripts — it behaves consistently across shells and does not interpret
leading dashes or escape sequences by default.
Q7: How do you define and use a variable?
name="Alice" echo "Hello, ${name}"
- No spaces around
=when assigning. - Prefix the variable name with
$to read its value.
Q8: What are environment variables and how do you manage them?
Environment variables are variables inherited by child processes.
# Set and export a variable to child processes export MY_VAR="hello" # View all environment variables env printenv # View a single variable printenv HOME echo "$PATH" # Unset a variable unset MY_VAR # Set a variable only for one command (inline) MY_VAR="world" ./script.sh
Variables set without export are local to the current shell and not visible in child processes.
Q9: How does redirection work in shell?
# Redirect stdout to a file (overwrite) echo "hello" > output.txt # Redirect stdout to a file (append) echo "world" >> output.txt # Redirect stdin from a file sort < names.txt # Redirect stderr to a file ls /bad 2> errors.txt # Redirect both stdout and stderr to a file ./script.sh > all.log 2>&1 ./script.sh &> all.log # bash shorthand # Discard output entirely ./noisy-script.sh > /dev/null 2>&1
The order of redirections matters: 2>&1 must come after >.
Q10: What is the difference between single quotes and double quotes?
name="World" echo 'Hello $name' # prints: Hello $name (no expansion) echo "Hello $name" # prints: Hello World (variable expanded)
Single quotes treat everything literally. Double quotes allow variable and command substitution.
Q11: How do pipes and command chaining work?
# Pipe — pass stdout of one command as stdin to the next ls -l | grep ".sh" | wc -l # && — run next command only if previous succeeded (exit 0) mkdir /tmp/mydir && cd /tmp/mydir # || — run next command only if previous failed (non-zero exit) cd /missing || echo "Directory not found" # ; — run commands sequentially regardless of exit code echo "start" ; sleep 2 ; echo "done"
Q12: How do you read input from the user?
read -p "Enter your name: " name echo "Hello, $name" # Silent input (e.g. passwords) read -s -p "Password: " pass echo "" # Read with timeout read -t 10 -p "Answer within 10s: " answer
read stores the input in a variable.
-p displays a prompt, -s hides input, -t sets a timeout in seconds.
Q13: How do you write comments in a shell script?
# This is a single-line comment echo "Running script..." # inline comment
Only single-line comments (#) exist in shell.
There is no native multi-line comment syntax, but a heredoc trick is sometimes used.
Q14: How do you perform arithmetic in shell?
a=5 b=3 sum=$((a + b)) echo "Sum: $sum" # prints: Sum: 8 # Alternative with expr result=$(expr $a \* $b) echo "Product: $result" # prints: Product: 15
$((...)) is the preferred modern syntax for integer arithmetic.
Q15: What are common comparison operators for numbers?
| Operator | Meaning |
|---|---|
-eq |
Equal |
-ne |
Not equal |
-lt |
Less than |
-le |
Less than or equal |
-gt |
Greater than |
-ge |
Greater than or equal |
if [ $a -gt $b ]; then echo "$a is greater than $b" fi
Q16: How do you write an if-else statement?
age=18 if [ $age -ge 18 ]; then echo "Adult" elif [ $age -ge 13 ]; then echo "Teenager" else echo "Child" fi
Q17: What are the basic loop types in shell?
# for loop for i in 1 2 3 4 5; do echo "Number: $i" done # C-style for loop for ((i=0; i<5; i++)); do echo "i=$i" done # while loop count=1 while [ $count -le 5 ]; do echo "Count: $count" ((count++)) done # until loop (runs until condition becomes true) until [ $count -gt 5 ]; do echo "Count: $count" ((count++)) done
Q18: What is $0, $1, $2 … $@ and $#?
#!/bin/bash echo "Script name : $0" echo "First arg : $1" echo "Second arg : $2" echo "All args : $@" echo "Arg count : $#"
| Variable | Meaning |
|---|---|
$0 |
Name of the script |
$1 to $9 |
Positional parameters (arguments) |
$@ |
All arguments as separate strings |
$* |
All arguments as a single string |
$# |
Number of arguments |
Q19: What does $? represent?
$? holds the exit status (return code) of the last executed command.
0 means success; any non-zero value means failure.
ls /nonexistent echo "Exit code: $?" # prints a non-zero value (e.g. 2)
Intermediate
Q20: What is the difference between [ ] and [[ ]]?
[ ] is the POSIX-compliant test command available in all shells.
[[ ]] is a bash/ksh/zsh extension that is more powerful and less error-prone.
# [[ ]] supports regex matching and logical operators without quoting issues name="Alice" if [[ $name == A* ]]; then echo "Name starts with A" fi # Regex matching with =~ if [[ "$name" =~ ^[A-Z][a-z]+$ ]]; then echo "Name looks like a proper noun" fi
Prefer [[ ]] in bash scripts for safety and additional features.
Q21: How do you use regular expressions in shell?
# =~ operator inside [[ ]] for regex matching email="user@example.com" if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then echo "Valid email" fi # grep with regex echo "foo123" | grep -E '^[a-z]+[0-9]+$' # Case-insensitive regex match grep -iE 'error|warn' logfile.txt
Captured groups from =~ are stored in the BASH_REMATCH array:
${BASH_REMATCH[0]} is the full match, ${BASH_REMATCH[1]} is the first group.
Q22: How do you define and call a function?
greet() { local name="$1" echo "Hello, $name!" } greet "Alice" greet "Bob"
Use local to limit a variable's scope to the function.
Functions return an exit code (0–255), not a value — use echo to return data.
Q23: How do you capture the output of a command?
# Command substitution today=$(date +%Y-%m-%d) echo "Today is: $today" # Backtick style (older, avoid) files=`ls`
Prefer $(...) over backticks — it is more readable and supports nesting.
Q24: What is a heredoc and when do you use it?
A heredoc lets you pass a multi-line string to a command without creating a temporary file.
cat <<EOF Line 1 Line 2 Today is $(date) EOF # Indented heredoc (bash 4.4+ with <<-) cat <<-EOF Indented line 1 Indented line 2 EOF
Use <<'EOF' (single-quoted delimiter) to disable variable expansion inside the heredoc.
Q25: What is a here-string?
A here-string (<<<) passes a single string as stdin to a command — a compact alternative to echo+pipe.
# Instead of: echo "hello world" | read -r a b read -r a b <<< "hello world" echo "$a" # hello echo "$b" # world # Useful with grep, tr, etc. grep "foo" <<< "foo bar baz" # Pass a variable as stdin tr '[:lower:]' '[:upper:]' <<< "$str"
Q26: How do arrays work in bash?
# Declare an array fruits=("apple" "banana" "cherry") echo "${fruits[0]}" # apple echo "${fruits[@]}" # all elements echo "${#fruits[@]}" # number of elements # Append an element fruits+=("date") # Loop over array for fruit in "${fruits[@]}"; do echo "$fruit" done
Q27: How do you read a file into an array with mapfile / readarray?
# Read all lines of a file into an array mapfile -t lines < /etc/hosts # or equivalently: readarray -t lines < /etc/hosts echo "${lines[0]}" # first line echo "${#lines[@]}" # total number of lines # Loop over lines for line in "${lines[@]}"; do echo "$line" done
The -t flag strips the trailing newline from each line.
Both mapfile and readarray are synonyms and require bash 4.0+.
Q28: What is the difference between $@ and $* in arrays and functions?
When double-quoted:
"$@"expands each element as a separate quoted word — safe for filenames with spaces."$*"expands all elements as a single word joined by the first character ofIFS.
Always prefer "$@" when iterating over arguments or array elements.
Q29: What are special variables $$ and $!?
| Variable | Meaning |
|---|---|
$$ |
PID of the current shell/script |
$! |
PID of the last background command |
$- |
Current shell option flags |
$_ |
Last argument of the previous command |
Q30: How do you handle errors and exit codes?
#!/bin/bash set -e # exit immediately on error set -u # treat unset variables as errors set -o pipefail # catch errors in pipelines cp source.txt dest.txt || { echo "Copy failed"; exit 1; }
Using set -euo pipefail at the top of scripts is considered best practice.
Q31: What is process substitution?
Process substitution allows you to treat the output of a command as a file.
# Compare output of two commands diff <(ls dir1) <(ls dir2) # Read from a process as if it were a file while IFS= read -r line; do echo "Line: $line" done < <(grep "ERROR" logfile.txt)
Q32: What is the difference between source (.) and executing a script?
source script.sh # or: . script.sh ./script.sh
source/.runs the script in the current shell — variable changes persist../script.shruns the script in a subshell — changes do not affect the parent shell.
Q33: How do you work with files and directories?
# Test conditions [ -f file.txt ] && echo "Is a regular file" [ -d /tmp ] && echo "Is a directory" [ -r file.txt ] && echo "Is readable" [ -w file.txt ] && echo "Is writable" [ -x script.sh ] && echo "Is executable" [ -s file.txt ] && echo "Is non-empty" [ -e path ] && echo "Exists" [ -L link ] && echo "Is a symbolic link" [ -nt newer.txt ] && echo "Is newer than" [ -ot older.txt ] && echo "Is older than"
Q34: How do you use grep, sed, and awk?
# grep — search for patterns in text grep "error" logfile.txt # lines containing "error" grep -i "error" logfile.txt # case-insensitive grep -r "TODO" ./src/ # recursive search grep -v "debug" logfile.txt # invert match (exclude lines) grep -c "error" logfile.txt # count matching lines grep -n "error" logfile.txt # show line numbers # sed — stream editor (find & replace, delete, insert) sed 's/foo/bar/' file.txt # replace first occurrence per line sed 's/foo/bar/g' file.txt # replace all occurrences sed -i 's/foo/bar/g' file.txt # edit file in place sed '/^#/d' file.txt # delete comment lines sed -n '5,10p' file.txt # print lines 5 to 10 # awk — pattern scanning and processing awk '{print $1}' file.txt # print first field (column) awk -F: '{print $1}' /etc/passwd # use : as delimiter awk '$3 > 100' data.txt # print rows where column 3 > 100 awk '{sum += $1} END {print sum}' numbers.txt # sum a column awk 'NR==5' file.txt # print line 5
Q35: How do you use cut, tr, sort, uniq, and wc?
# cut — extract fields or characters cut -d',' -f1,3 data.csv # fields 1 and 3, comma-delimited cut -c1-10 file.txt # first 10 characters of each line # tr — translate or delete characters echo "Hello World" | tr 'a-z' 'A-Z' # uppercase echo "hello world" | tr -s ' ' # squeeze multiple spaces echo "hello" | tr -d 'l' # delete character # sort — sort lines sort file.txt # alphabetical sort -n numbers.txt # numeric sort sort -r file.txt # reverse sort sort -u file.txt # sort and remove duplicates sort -t',' -k2 data.csv # sort CSV by second field # uniq — filter duplicate adjacent lines (use after sort) sort file.txt | uniq # remove duplicates sort file.txt | uniq -c # count occurrences sort file.txt | uniq -d # show only duplicates # wc — word/line/character count wc -l file.txt # line count wc -w file.txt # word count wc -c file.txt # byte count
Q36: How do you use the find command?
# Find files by name find /home -name "*.log" # Find files by type (f=file, d=dir, l=symlink) find /tmp -type f find /var -type d -name "cache" # Find by size find . -size +10M # larger than 10 MB find . -size -1k # smaller than 1 KB # Find by modification time find . -mtime -7 # modified in the last 7 days find . -mtime +30 # modified more than 30 days ago # Execute a command on found files find . -name "*.tmp" -delete find . -name "*.sh" -exec chmod +x {} \; # Find and print with null delimiter (safe for filenames with spaces) find . -name "*.txt" -print0 | xargs -0 grep "TODO"
Q37: How does xargs work?
xargs builds and executes commands from standard input — useful for processing lists of files or items.
# Basic usage — pass stdin as arguments echo "file1 file2 file3" | xargs rm # One argument per line (-L 1) cat filelist.txt | xargs -L 1 echo "Processing:" # Use null delimiter to handle filenames with spaces find . -name "*.log" -print0 | xargs -0 rm -f # Run in parallel (-P) cat urls.txt | xargs -P 4 -I{} curl -O {} # -I{} — replace {} with each input item ls *.txt | xargs -I{} cp {} /backup/
Q38: How do you use the select statement for interactive menus?
select generates a numbered menu from a list and loops until explicitly broken.
#!/bin/bash PS3="Choose an option: " # the select prompt variable select choice in "Start" "Stop" "Restart" "Quit"; do case $choice in Start) echo "Starting..." ;; Stop) echo "Stopping..." ;; Restart) echo "Restarting..." ;; Quit) break ;; *) echo "Invalid option $REPLY" ;; esac done
REPLY holds the raw number the user typed.
PS3 sets the prompt shown below the menu.
Q39: How does string manipulation work in bash?
str="Hello, World!" echo "${#str}" # Length: 13 echo "${str:7:5}" # Substring: World echo "${str,,}" # Lowercase: hello, world! echo "${str^^}" # Uppercase: HELLO, WORLD! echo "${str/World/Bash}" # Replace first: Hello, Bash! echo "${str//l/L}" # Replace all: HeLLo, WorLd! echo "${str#Hello, }" # Remove prefix: World! echo "${str%!}" # Remove suffix: Hello, World # Default value if variable is unset or empty echo "${name:-"stranger"}" # Assign default if unset : "${config_file:=/etc/app.conf}"
Q40: How do you use case statements?
read -p "Enter a fruit: " fruit case "$fruit" in apple) echo "You chose apple." ;; banana | mango) echo "You chose a tropical fruit." ;; [0-9]*) echo "That looks like a number, not a fruit." ;; *) echo "Unknown fruit." ;; esac
Q41: What is IFS and how does it affect word splitting?
IFS (Internal Field Separator) controls how bash splits words.
Default value is space, tab, and newline.
IFS=',' read -ra parts <<< "one,two,three" for part in "${parts[@]}"; do echo "$part" done # Save and restore IFS OLD_IFS="$IFS" IFS=':' read -ra paths <<< "$PATH" IFS="$OLD_IFS"
Always restore IFS after changing it to avoid side effects.
Advanced
Q42: What is the difference between soft links and hard links?
ln -s target.txt symlink.txt # soft (symbolic) link ln target.txt hardlink.txt # hard link
- A soft link is a pointer to a path — it breaks if the target is deleted or moved.
- A hard link is another directory entry pointing to the same inode — it survives deletion of the original filename.
Q43: How do you write robust scripts using traps?
trap lets you catch signals and errors to perform cleanup before the script exits.
#!/bin/bash tmpfile=$(mktemp) cleanup() { echo "Cleaning up..." rm -f "$tmpfile" } trap cleanup EXIT # runs on any exit trap 'echo "Interrupted"' INT # runs on Ctrl+C (SIGINT) trap 'echo "Terminated"' TERM # runs on kill (SIGTERM) trap 'echo "Line $LINENO"' ERR # runs on any error (with set -e) echo "Working..." > "$tmpfile" # ... rest of script ...
Q44: What are Linux signals and how do you send them?
Signals are software interrupts sent to processes to notify them of events.
| Signal | Number | Default action | Common use |
|---|---|---|---|
SIGHUP |
1 | Terminate | Reload config (daemons) |
SIGINT |
2 | Terminate | Ctrl+C — interrupt |
SIGQUIT |
3 | Core dump | Ctrl+\ |
SIGKILL |
9 | Terminate (forceful) | Cannot be caught or ignored |
SIGTERM |
15 | Terminate (graceful) | Default signal sent by kill |
SIGSTOP |
19 | Stop process | Cannot be caught or ignored |
SIGTSTP |
20 | Stop process | Ctrl+Z — suspend to background |
SIGUSR1 |
10 | Terminate | User-defined signal |
SIGUSR2 |
12 | Terminate | User-defined signal |
kill -15 $PID # send SIGTERM (graceful shutdown) kill -9 $PID # send SIGKILL (force kill) kill -HUP $PID # send SIGHUP (reload) kill -l # list all signals pkill -f "myapp" # kill by process name pattern
Q45: How do job control (fg, bg, jobs, disown) work?
# Run a command in the background sleep 100 & # List background jobs jobs # [1]+ Running sleep 100 & # Bring job 1 to foreground fg %1 # Suspend foreground job (Ctrl+Z), then send to background bg %1 # Disown a job — detach it from the shell (survives shell exit) disown %1 # Run a command immune to hangup signal (survives logout) nohup ./long-running.sh & # Check if a specific PID is running kill -0 $PID && echo "Running" || echo "Not running"
Q46: What is ulimit and how do you use it?
ulimit controls the resource limits available to the shell and its child processes.
ulimit -a # show all current limits ulimit -n 4096 # max open file descriptors ulimit -u 100 # max number of user processes ulimit -v $((512*1024)) # max virtual memory (KB) ulimit -s 8192 # max stack size (KB) ulimit -t 60 # max CPU time (seconds) # Soft vs hard limits ulimit -Sn # show soft limit for open files ulimit -Hn # show hard limit for open files
Hard limits can only be raised by root. Soft limits can be raised up to the hard limit by the user.
Q47: How do named pipes (FIFOs) work?
mkfifo mypipe echo "Hello from producer" > mypipe & # writer (runs in background) cat mypipe # reader (blocks until writer writes) rm mypipe
Named pipes allow inter-process communication between unrelated processes.
Q48: What are /dev/fd, /dev/tcp, and /dev/udp?
These are special virtual files provided by the kernel and bash.
# /dev/fd — file descriptor access ls /dev/fd # 0=stdin, 1=stdout, 2=stderr exec 3> output.txt # open fd 3 for writing echo "hello" >&3 exec 3>&- # close fd 3 # /dev/tcp — open a TCP socket (bash built-in, no netcat needed) exec 3<>/dev/tcp/example.com/80 echo -e "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n" >&3 cat <&3 exec 3>&- # /dev/udp — open a UDP socket echo "hello" > /dev/udp/127.0.0.1/9999
/dev/tcp and /dev/udp are bash extensions — not available in plain sh.
Q49: How do you implement a mutex/lock in shell scripts?
LOCKFILE="/tmp/myscript.lock" acquire_lock() { if ! mkdir "$LOCKFILE" 2>/dev/null; then echo "Script is already running. Exiting." exit 1 fi } release_lock() { rmdir "$LOCKFILE" } trap release_lock EXIT acquire_lock echo "Critical section running..."
Using mkdir for locking is atomic on most file systems, making it safer than touch.
Q50: How do you run commands in parallel and wait for them?
#!/bin/bash pids=() for i in 1 2 3 4; do sleep "$i" & pids+=($!) done for pid in "${pids[@]}"; do wait "$pid" && echo "PID $pid finished OK" || echo "PID $pid failed" done
& runs a command in the background, wait waits for it, $! captures its PID.
Q51: What is a subshell and how does it affect variable scope?
A subshell is a child process created by the current shell. Variables set in a subshell are not visible in the parent.
x=10 ( x=20 echo "Inside subshell: x=$x" # 20 ) echo "Outside subshell: x=$x" # 10 — unchanged
Subshells are created by (...), pipelines, command substitution, and background jobs.
Q52: How do you handle long options and flags with getopts / getopt?
#!/bin/bash usage() { echo "Usage: $0 [-n name] [-v]" >&2; exit 1; } verbose=0 name="" while getopts ":n:v" opt; do case $opt in n) name="$OPTARG" ;; v) verbose=1 ;; :) echo "Option -$OPTARG requires an argument." >&2; usage ;; \?) echo "Invalid option: -$OPTARG" >&2; usage ;; esac done shift $((OPTIND - 1)) echo "Name: $name, Verbose: $verbose, Remaining args: $*"
getopts is POSIX-compliant and built-in.
Use getopt (external) for long options (--name).
Q53: How does coproc work in bash?
coproc creates a coprocess — a background process with bidirectional pipes to the current shell.
coproc my_proc { cat; } echo "Hello" >&"${my_proc[1]}" # write to coprocess stdin read line <&"${my_proc[0]}" # read from coprocess stdout echo "Got: $line"
Useful when you need a persistent two-way communication channel with a child process.
Q54: How do you use associative arrays (hash maps)?
Associative arrays require bash 4.0+.
declare -A capitals capitals["France"]="Paris" capitals["Germany"]="Berlin" capitals["Japan"]="Tokyo" echo "${capitals[France]}" # Paris echo "${!capitals[@]}" # all keys echo "${capitals[@]}" # all values echo "${#capitals[@]}" # number of entries # Delete an entry unset 'capitals[Germany]' # Iterate over key-value pairs for country in "${!capitals[@]}"; do echo "$country -> ${capitals[$country]}" done
Q55: How do you profile and debug a shell script?
#!/bin/bash set -x # print each command and its expanded form before execution set -v # print shell input lines as they are read # Measure execution time of a section time { for i in $(seq 1 1000); do : done }
You can also run a script with bash -x script.sh without modifying it.
Use PS4='+(${BASH_SOURCE}:${LINENO}): ' to include file and line number in trace output.
Q56: What are the differences between exec, fork, and source in shell?
| Mechanism | Description |
|---|---|
fork |
Shell creates a child process; parent waits for it to finish |
exec |
Replaces the current process image — no child is created |
source/. |
Runs commands in the current shell process; no new process created |
exec /bin/bash # replaces current shell — the original process is gone
Q57: What are bash vs sh compatibility gotchas?
#!/bin/sh # strict POSIX sh — many bash features are unavailable # These are bash-only — will FAIL under /bin/sh: # [[ ]] — use [ ] instead # arrays — not supported # (( )) — use expr or [ ] for arithmetic # local — not POSIX (works in most sh implementations but not guaranteed) # $'...' — ANSI-C quoting # &> — use > file 2>&1 # {a..z} — brace expansion # mapfile — bash 4+ only # source — use . instead # echo -e — not portable; use printf
If portability is required, use #!/bin/sh and validate with checkbashisms or shellcheck.
Q58: How do you write a script logging pattern?
#!/bin/bash # Log levels LOG_FILE="/var/log/myscript.log" log() { local level="$1" shift local message="$*" local timestamp timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE" } log_info() { log "INFO " "$@"; } log_warn() { log "WARN " "$@" >&2; } log_error() { log "ERROR" "$@" >&2; } # Usage log_info "Script started" log_warn "Configuration file missing — using defaults" log_error "Failed to connect to database"
Q59: How do you integrate a shell script with cron?
# Edit the crontab for the current user crontab -e # Crontab syntax: # ┌──────── minute (0–59) # │ ┌────── hour (0–23) # │ │ ┌──── day of month (1–31) # │ │ │ ┌── month (1–12) # │ │ │ │ ┌ day of week (0–7, 0 and 7 = Sunday) # │ │ │ │ │ # * * * * * command # Examples 0 * * * * /home/user/backup.sh # every hour at :00 30 2 * * * /scripts/cleanup.sh # every day at 02:30 0 0 * * 0 /scripts/weekly-report.sh # every Sunday at midnight */5 * * * * /scripts/check-health.sh # every 5 minutes # Cron environment is minimal — always use absolute paths # and explicitly set needed variables at the top of your script: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin HOME=/home/user
Cron does not load .bashrc or .profile — always use absolute paths and export PATH explicitly.
Q60: What are eval risks and safer alternatives?
# DANGEROUS — eval executes arbitrary code user_input="rm -rf /" eval "$user_input" # catastrophic! # Safer alternative: use variable indirection var_name="greeting" declare "$var_name=hello" echo "${!var_name}" # hello (indirect expansion) # Safer alternative: use arrays instead of dynamic variable names declare -A data data["key"]="value" echo "${data[key]}" # If eval is truly needed, sanitize input strictly safe_name="${input//[^a-zA-Z0-9_]/}" eval "echo \"\$$safe_name\""
Q61: How do you write a self-contained script that detects its own location?
#!/bin/bash SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" echo "Script is located in: $SCRIPT_DIR"
This works correctly even when the script is sourced, symlinked, or called from a different directory.
Q62: What are some security best practices for shell scripting?
- Always quote variables —
"$var"prevents word splitting and globbing issues. - Validate input — never trust user-supplied data.
- Avoid
eval— it executes arbitrary code and is a common injection vector. - Use absolute paths for critical commands in scripts run as root.
- Restrict permissions — scripts that contain secrets should not be world-readable.
- Use
set -euo pipefail— catch errors early. - Sanitize filenames — use
--to separate options from filenames (e.g.,rm -- "$file"). - Prefer mktemp for temporary files instead of predictable names.
- Use ShellCheck — a static analysis tool that catches common bugs and pitfalls.
- Avoid storing secrets in scripts — use environment variables or a secrets manager.
# Unsafe tmpfile="/tmp/myapp-$$" # Safe tmpfile=$(mktemp /tmp/myapp-XXXXXX) trap 'rm -f "$tmpfile"' EXIT