Chapter 8: Shell Scripting¶
Source slides:
V06_shell_scripting.pdf. Exercise:E06_shell_scripting.pdf. Code:lecture_scripts/example01.sh..example18.sh,backup-data.sh,if_syntax,test01/. Exercise solutions:task1_solution.sh(prime),task2_solution.sh(extended backup-data with-s).
1. Chapter Overview¶
Shell scripts are executable text files that automate workflows: launch CFD jobs, sweep parameters, post-process logs, back up results, set up the environment. Bash, the default shell on Linux, has its own programming language with variables, control flow, arrays, functions, options parsing, and command substitution.
Why it matters in HPC/CFD: 90 % of the "glue code" between solvers, schedulers and post-processing is bash. A correct script saves hours; a buggy one wastes core-hours.
What the examiner asks (very high frequency):
- "Write a script that …"
- "Predict the output of this script."
- "Explain
${1},$#,$@,$?,$$." - "What is the difference between
[ ],[[ ]],(( ))?" - "Convert this
ifto acase(and vice versa)." - "Explain the
getoptsblock." - "Modify the backup script to add
-sfor sync."
What you must master for top grade:
- The script header (
#!/bin/bash), permission, and run methods. - All control structures:
if/elif/else,case,for,while,until,select. - Variables, quoting, command substitution
$(…), arithmetic(( … )). - Arrays.
- Functions, return values, exit codes.
getoptsfor options parsing.- Reading user input with
read. - The full
backup-data.shline-by-line.
2. Basics from Zero¶
A shell script is just a list of commands the shell would otherwise have run interactively, saved into a .sh file with a shebang on line 1:
To run it:
The shell already knows about variables, conditions, loops, functions, file tests, command substitution. You combine those with the existing tools (grep, awk, sed, find, chmod, rsync, …) to automate tasks.
বাংলায়: Shell script মানে এমন একটা টেক্সট ফাইল, যেখানে আপনি টার্মিনালে যা যা টাইপ করতেন সেগুলোই লাইন ধরে সাজিয়ে রাখা হয়েছে। প্রথম লাইনের shebang বলে দেয় কোন interpreter দিয়ে ফাইলটা চালাতে হবে, আর chmod +x দিয়ে চালানোর অনুমতি দিতে হয়। পরীক্ষায় প্রায়ই আসে script চালানোর দুটো উপায় — ./script.sh (executable bit লাগবে) আর bash script.sh (executable bit লাগবে না)।
Real-life analogy. A bash script is like a paper recipe. Each line is a step ("preheat oven", "grep ERROR run.log"). Variables = quantities. Functions = mini sub-recipes. Loops = "repeat for each carrot".
Real-life HPC use. The classic backup-data.sh from the lecture: it takes [SOURCE] and [DEST] directories, parses a -v (verbose) option, validates inputs, copies (or syncs in the extended version), and reports success.
What if you misunderstand? You write if [ $a==$b ] (no spaces) → always true. Or you forget to quote a variable that contains spaces → the test breaks with cryptic errors. The shell's behaviour is fragile in many specific ways; mastering it earns the grade.
বাংলায়: Bash-এর syntax খুবই খুঁতখুঁতে — স্পেস এদিক-ওদিক হলেই অর্থ বদলে যায়। যেমন
VAR=5ঠিক, কিন্তুVAR = 5লিখলে bash ভাবে VAR নামের একটা command চালাতে বলা হয়েছে। আবার[ $a==$b ]লিখলে test-টা সবসময় true হয়, কারণ পুরোটাকে একটাই non-empty string হিসেবে দেখা হয়। পরীক্ষায় এই ভুলগুলো খুঁজে বের করতে বলা প্রায় নিশ্চিত।
3. Hard English Made Easy¶
| Hard Term | Simple English | বাংলা | Example |
|---|---|---|---|
| Shebang | #! line specifying interpreter |
কোন ইন্টারপ্রিটার চালানো হবে | #!/bin/bash |
| Positional parameter | Argument passed to script | কমান্ডে দেওয়া আর্গুমেন্ট | ${1}, ${2} |
| Special variable | Predefined shell var | পূর্বনির্ধারিত ভেরিয়েবল | $#, $@, $? |
| Command substitution | Use a command's output as text | কমান্ডের আউটপুটকে টেক্সট হিসেবে | $(date) |
| Arithmetic expansion | Math inside (( )) |
শেলে গণনা | (( a+1 )) |
| Conditional expression | Test [ ] / [[ ]] |
শর্ত পরীক্ষা | [ -f f ] |
| Exit status | 0 success, ≠0 failure | প্রস্থান কোড | $? |
| Function | Reusable block | পুনঃব্যবহারযোগ্য ব্লক | myfn(){…} |
| Local variable | Lives in a function only | শুধু ফাংশনের ভেতরে | local x |
| Subshell | Child shell | পুত্র শেল | ( cd dir; cmd ) |
| Source / dot | Run in current shell | বর্তমান শেলে চালানো | source f |
| Pipe | Output → input chain | আউটপুট পরের কমান্ডে যাওয়া | a \| b |
| Stdin/Stdout/Stderr | 0/1/2 streams | তিনটি স্ট্যান্ডার্ড স্ট্রিম | 2>&1 |
| getopts | Builtin for short options | ছোট অপশন পার্সার | while getopts |
| Trap | Catch signals | সিগন্যাল ধরা | trap 'echo bye' EXIT |
| Quoting | Single vs double quotes | উদ্ধৃতি চিহ্ন ব্যবহার | "$x" vs '$x' |
| Glob | Filename pattern | ফাইল প্যাটার্ন | *.sh |
| Heredoc | Inline multi-line input | একাধিক লাইন ইনপুট | cat <<EOF |
| Process ID | Number of running process | প্রসেস আইডি | $$ |
4. Deep Theory Explanation¶
4.1 Script anatomy¶
#!/bin/bash # 1. shebang
# Description: … # 2. comments
set -e # 3. fail on error
SOURCE="${1:-./default}" # 4. defaulted positional
[[ -d $SOURCE ]] || { echo "missing"; exit 1; } # 5. guard
main() { # 6. function
for f in "$SOURCE"/*; do
process "$f"
done
}
process() {
echo "Doing $1"
}
main "$@" # 7. forward all args to main
exit 0
4.2 Variables and special variables¶
NAME="alice" # assignment, no spaces around =
echo "$NAME" # use → "alice"
echo '${NAME}' # single quotes don't expand → ${NAME}
echo "${NAME}" # double quotes do
echo "${NAME}File" # the braces protect the boundary
| Var | Meaning |
|---|---|
${1}…${9} |
Positional |
${10+} |
requires braces |
$0 |
Script name |
$# |
Number of arguments |
$@ |
All args (preserves quoting) |
$* |
All args as a single string |
$? |
Last exit status |
$$ |
PID of script |
$! |
PID of last background |
$_ |
Last arg of previous command |
$IFS |
Internal field separator |
বাংলায়: Special variable গুলো হলো shell-এর নিজে থেকে রাখা তথ্য:
$#মানে কয়টা argument এসেছে,$@মানে সবগুলো argument আলাদা আলাদা ভাবে,$?মানে আগের command সফল হয়েছিল কি না (0 মানে সফল), আর$$হলো script-এর নিজের process ID। পরীক্ষায় "Explain$#,$@,$?" প্রায় প্রতি বছরই আসে — এই চারটা মুখস্থ রাখুন।
4.3 Quoting rules¶
| Form | Behaviour |
|---|---|
'…' |
Literal — no expansion |
"…" |
Variable, command-substitution, arithmetic expand; globs do not |
\$x |
Escape $ |
\$IFS" |
Reset IFS |
Always quote variables that may contain spaces or be empty: if [ -z "${VAR}" ].
বাংলায়: Single quote মানে ভেতরের সবকিছু হুবহু literal —
$NAMEলেখা থাকলে$NAME-ই ছাপা হবে। Double quote-এর ভেতরে variable আর command substitution কাজ করে, কিন্তু শব্দ ভাগ (word splitting) হয় না — তাই স্পেসওয়ালা path নিরাপদ থাকে। নিয়ম একটাই: variable ব্যবহারের সময় সবসময় double quote দিন, যেমন "$VAR" — মান খালি বা স্পেসওয়ালা হলেও script ভাঙবে না।
4.4 Conditional expressions¶
Three test forms:
| Form | Type | Notes |
|---|---|---|
[ EXPR ] |
POSIX test |
needs spaces around [ and ]; quote vars |
[[ EXPR ]] |
Bash | safer; supports &&, ||, regex =~, glob == |
(( EXPR )) |
Arithmetic | C-like math |
File tests:
| Test | True when |
|---|---|
-f f |
regular file exists |
-d d |
directory exists |
-e p |
path exists |
-r f |
readable |
-w f |
writable |
-x f |
executable |
-s f |
non-empty |
-L f |
symlink |
f1 -nt f2 |
newer than |
f1 -ot f2 |
older |
String tests:
| Test | True when |
|---|---|
-z s |
empty |
-n s |
non-empty |
s1 = s2 |
equal |
s1 != s2 |
unequal |
s1 < s2 |
lex. less (in [[ ]]) |
s =~ regex |
regex match (in [[ ]]) |
Numeric tests (in [ ]):
-eq -ne -lt -le -gt -ge. In (( )) use C operators == != < <= > >=.
বাংলায়: তিন রকম test-এর কাজ আলাদা: [ ] হলো পুরনো POSIX test, [[ ]] হলো bash-এর নিরাপদ সংস্করণ (regex আর && সাপোর্ট করে), আর (( )) শুধু গণিতের জন্য। সবচেয়ে বড় ফাঁদ: সংখ্যা তুলনায় -eq, -lt ব্যবহার করতে হবে; = বা < দিলে string হিসেবে তুলনা হয়, ফলে "10" কে "9"-এর চেয়ে ছোট ধরা হয়। পরীক্ষায় এই তিন ফর্মের পার্থক্য জিজ্ঞেস করা প্রায় নিশ্চিত।
4.5 if / elif / else¶
if [ ${1} -lt 2006 ]; then
echo "above 18"
elif [ ${1} -eq 2006 ]; then
echo "exactly 18"
else
echo "below 18"
fi
(from example06.sh)
4.6 case¶
case "$choice" in
1) echo "Service started" ;;
2) echo "Service stopped" ;;
3) ls ;;
*) echo "invalid" ;;
esac
(from example08.sh)
Globs allowed: [Yy][Ee][Ss]), alternation start|begin).
4.7 Loops¶
while:
for (C-style):
for (list):
until:
(from example10–13.sh)
break / continue to skip iterations.
বাংলায়: কোন loop কখন ব্যবহার করবেন সেটাই আসল প্রশ্ন: নির্দিষ্ট সংখ্যকবার চালাতে for, শর্ত সত্য থাকা পর্যন্ত while, আর শর্ত সত্য হওয়া মাত্র থামতে until। until আসলে while-এর উল্টো — একই কাজ শর্তে ! বসিয়ে while দিয়েও করা যায়। break মানে loop থেকে পুরোপুরি বেরিয়ে যাওয়া, আর continue মানে শুধু এই iteration বাদ দিয়ে পরেরটায় যাওয়া — দুটো গুলিয়ে ফেললে output prediction প্রশ্নে নম্বর যায়।
4.8 Arrays¶
ARRAY=(23 45 56 76)
echo ${ARRAY[0]} # first
echo ${ARRAY[@]} # all
echo ${#ARRAY[@]} # length
ARRAY+=(99) # append
unset 'ARRAY[1]' # remove element
(example09.sh)
Associative array (Bash 4+): declare -A AA; AA[name]="alice".
বাংলায়: Array-র চারটা রূপ মুখস্থ রাখুন:
ARRAY[0]দিয়ে প্রথম element,"${ARRAY[@]}"দিয়ে সবগুলো আলাদা আলাদা শব্দ হিসেবে,${#ARRAY[@]}দিয়ে দৈর্ঘ্য, আর+=দিয়ে শেষে যোগ। Loop-এ সবসময়"${ARRAY[@]}"(double quote সহ) লিখুন — নাহলে স্পেসওয়ালা element ভেঙে টুকরো হয়ে যাবে। পরীক্ষায় প্রায়ই array-র output predict করতে দেয়।
4.9 Reading user input¶
read -p "Enter a number: " NUMBER
read -s PASSWORD # silent
read -t 10 ANS # timeout
read -a ARR <<< "a b c" # into array
4.10 Command substitution & arithmetic¶
(from example17.sh)
4.11 Functions¶
return only carries an exit status (0–255). To "return a value", echo and capture: r=$(myfn 4).
local var to keep variables function-scoped (highly recommended).
বাংলায়: Bash function-এর return কিন্তু C-এর মতো মান ফেরত দেয় না — শুধু 0 থেকে 255-এর একটা exit status দেয়, যেটা
$?দিয়ে পড়তে হয়। আসল মান ফেরত দিতে হলে function-এর ভেতরে echo করে বাইরে command substitution দিয়ে ধরতে হয়, যেমন r=$(myfn 4)। আর local না লিখলে function-এর ভেতরের variable পুরো script-এ ছড়িয়ে পড়ে — পরীক্ষায় এই দুটো পার্থক্যই বারবার আসে।
4.12 getopts (single-letter options)¶
OPTSTRING=":a:b:v"
while getopts "$OPTSTRING" opt; do
case "$opt" in
a) echo "-a $OPTARG" ;;
b) echo "-b $OPTARG" ;;
v) VERBOSE=1 ;;
:) echo "-$OPTARG needs an argument"; exit 1 ;;
?) echo "invalid -$OPTARG"; exit 1 ;;
esac
done
shift $((OPTIND-1))
":" at start of OPTSTRING enables silent error handling. Letter followed by : means it requires an argument.
(from example14.sh–example16.sh)
How the arguments flow through OPTIND/OPTARG for ./script.sh -v -s out.dat data1 with OPTSTRING=":vs:":
Command line: ./script.sh -v -s out.dat data1
│ │ │ │
$1 $2 $3 $4 OPTIND starts at 1
│ │ │
┌──────────────────────┘ │ └────────────┐
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────────┐
│ while getopts ":vs:" opt; do ... done │
│ │
│ pass 1: reads $1 = "-v" ─► opt='v', OPTARG empty, OPTIND ─► 2 │
│ pass 2: reads $2 = "-s" ─► opt='s', needs an argument: │
│ consumes $3 = "out.dat" ─► OPTARG="out.dat", OPTIND ─► 4 │
│ pass 3: $4 = "data1" has no leading "-" ─► getopts returns false │
│ loop ends, OPTIND stays 4 │
└──────────────────────────────────────────────────────────────────┘
│
▼
shift $((OPTIND-1)) # shift 3 ─► drops -v -s out.dat
│
▼
Now $1 = "data1" # the first non-option argument
বাংলায়: getopts প্রতিবার একটা করে option পড়ে: option-এর অক্ষরটা opt-এ যায়, আর সেই option-এর argument (যদি লাগে) OPTARG-এ যায়। OPTSTRING-এ অক্ষরের পরে : মানে ওই option argument নেয়, আর শুরুতে : মানে error নিজে handle করবেন। Loop শেষে
shift $((OPTIND-1))দিতেই হবে — না দিলে$1তখনও-v-ই থেকে যায়, আসল argument নয়। পরীক্ষায় এই shift ভুলে যাওয়াটাই সবচেয়ে common ভুল।
4.13 Backup script (backup-data.sh) – walkthrough¶
#!/bin/bash
OPTSTRING=":v"
VERBOSE=0
USAGE() {
echo "Error : ${1}"
echo "Usage: ./backup-data [OPTIONS] [SOURCE] [DEST]"
echo "-v Verbose flag"
return 0
}
while getopts ${OPTSTRING} opt; do
case ${opt} in
v) VERBOSE=1 ;;
?) USAGE "Invalid option"; exit 1 ;;
esac
done
shift $(( OPTIND - 1 ))
SOURCE=${1}
DEST=${2}
if [ -d "${SOURCE}" ] && [ -d "${DEST}" ]; then
if [ ${VERBOSE} -eq 1 ]; then
echo "Taking backup ..."
cp -vr ${SOURCE} ${DEST}
if [ ${?} -eq 1 ]; then
echo "Something went wrong"; exit 1
else
echo "Done"; exit 0
fi
else
cp -r ${SOURCE} ${DEST}
[ ${?} -ne 0 ] && exit 1 || exit 0
fi
else
echo "Invalid directories"; exit 1
fi
Steps:
- Header & vars.
USAGEfunction helps print the help line.getoptsparses-v.shiftconsumes parsed options so${1}is nowSOURCE.- Validate both dirs exist.
- Branch on verbose to
cp -vrorcp -r. - Check
$?exit status ofcp. - Return 0 on success.
বাংলায়: backup-data.sh হলো এই chapter-এর সবকিছুর সমন্বয়: getopts দিয়ে -v পড়া, shift দিয়ে option সরানো, -d test দিয়ে directory আছে কি না দেখা, তারপর cp চালিয়ে $? দিয়ে সফলতা যাচাই। পরীক্ষায় হয় পুরো script-টা line ধরে ব্যাখ্যা করতে বলে, নয়তো -s (rsync) option যোগ করতে বলে — দুটোই আগে থেকে হাতে লিখে প্র্যাকটিস করুন।
4.14 Lecture diagram explanations¶
if-then-elif-else-fiflowchart: condition diamond ↦ branch arrow ↦ block; describe in answers as "C-like control flow".getoptsloop: "while there are options, look up letter in case, set vars; after the loop,shiftaway the consumed options".- Stream redirection diagram: stdin/stdout/stderr arrows around the script box — same as Ch. 7.
Full execution flowchart for a script combining if/elif/else and a loop:
#!/bin/bash
N=${1}
if [ "${N}" -lt 0 ]; then
echo "negative"; exit 1
elif [ "${N}" -eq 0 ]; then
echo "zero"; exit 0
else
i=1
while [ ${i} -le ${N} ]; do
echo "i = ${i}"
(( i++ ))
done
fi
echo "done"
┌─────────────────────┐
│ START: N = $1 │
└──────────┬──────────┘
▼
┌─────────────────────┐ yes ┌──────────────────┐
│ N < 0 ? ├───────►│ echo "negative" │──► exit 1
└──────────┬──────────┘ └──────────────────┘
│ no
▼
┌─────────────────────┐ yes ┌──────────────────┐
│ N == 0 ? ├───────►│ echo "zero" │──► exit 0
└──────────┬──────────┘ └──────────────────┘
│ no (else branch)
▼
┌─────────────────────┐
│ i = 1 │
└──────────┬──────────┘
▼
┌─────────────────────┐ no ┌──────────────┐
┌────►│ i <= N ? ├────────►│ echo "done" │──► exit 0
│ └──────────┬──────────┘ └──────────────┘
│ │ yes
│ ▼
│ ┌─────────────────────┐
│ │ echo "i = $i" │
│ │ (( i++ )) │
│ └──────────┬──────────┘
└────────────────┘ back to the loop test
Subshell vs source — where do your variables go?
./script.sh (or: bash script.sh) source script.sh (or: . script.sh)
────────────────────────────────── ──────────────────────────────────
┌────────────────────────────┐ ┌────────────────────────────┐
│ parent shell (PID 4000) │ │ current shell (PID 4000) │
│ $X is unset │ │ │
│ │ fork + exec │ │ script lines run IN THIS │
│ ▼ │ │ SAME process, line by line │
│ ┌───────────────────────┐ │ │ │
│ │ child shell (PID 4123)│ │ │ X=5 ─► survives │
│ │ X=5 set HERE │ │ │ cd /tmp ─► pwd CHANGES │
│ │ cd /tmp only here │ │ │ │
│ │ exit ─► child dies, │ │ │ afterwards: │
│ │ X dies with it │ │ │ echo $X ─► 5 │
│ └───────────────────────┘ │ │ pwd ─► /tmp │
│ │ │ └────────────────────────────┘
│ ▼ │
│ afterwards: │ Use source for: module load,
│ echo $X ─► (empty) │ activating environments,
│ pwd ─► unchanged │ reading config files.
└────────────────────────────┘
4.15 Exit-status algebra — formal treatment¶
Every command finishes with an exit status \(e \in \{0, 1, \dots, 255\}\). Bash defines truth inversely to C: a command counts as "true" iff \(e = 0\).
The operators && and || short-circuit:
c1 && c2— runc2only if \(e(c_1) = 0\).c1 || c2— runc2only if \(e(c_1) \neq 0\).
The status $? of the whole chain is the status of the last command that actually ran.
Truth table (using true with \(e = 0\) and false with \(e = 1\)):
| \(e(c_1)\) | \(e(c_2)\) | c1 && c2 runs c2? |
$? after |
c1 \|\| c2 runs c2? |
$? after |
|---|---|---|---|---|---|
| 0 | 0 | yes | 0 | no | 0 |
| 0 | 1 | yes | 1 | no | 0 |
| 1 | 0 | no | 1 | yes | 0 |
| 1 | 1 | no | 1 | yes | 1 |
Worked example 1 — trace $? after every line:
cd /nonexistent && echo "entered" # cd fails: e=1 -> echo skipped, chain keeps 1
echo $? # prints 1
cd /tmp && echo "entered" # cd ok: e=0 -> echo runs, its e=0 wins
echo $? # prints 0
Worked example 2 — the A && B || C trap. The chain A && B || C is not an if/else:
Trace: true gives \(e=0\) → && runs false → chain status is now 1 → || sees 1 → runs echo. So C runs when A fails or when B fails. A genuine if/else must be written if A; then B; else C; fi.
Worked example 3 — guard pattern from real job scripts:
If run.log contains ERROR: grep -q returns 0 → first echo runs → final $? is 0. If not: grep returns 1 → first echo skipped (chain carries 1) → second echo runs → final $? is 0.
বাংলায়: Bash-এ সত্য-মিথ্যার হিসাব C-এর উল্টো: exit status 0 মানে সফল/সত্য, আর শূন্য ছাড়া যেকোনো মান মানে ব্যর্থ। তাই
&&মানে "আগেরটা সফল হলে চালাও",||মানে "আগেরটা ব্যর্থ হলে চালাও"। আরA && B || C-কে if/else ভাবা পরীক্ষার সবচেয়ে বড় ফাঁদ — B ব্যর্থ হলেও C চলে যায়!
4.16 Arithmetic & comparison — the formal rules¶
Bash integer arithmetic lives inside $(( )) / (( )). Operators follow C precedence: ** (highest), then * / %, then + -, then comparisons, then && ||.
Worked examples (integer-only!):
echo $(( 17/5 )) # 3 (integer division truncates toward zero)
echo $(( 17%5 )) # 2 (remainder: 17 = 3*5 + 2)
echo $(( 2**10 )) # 1024
echo $(( 3+4*2 )) # 11 (precedence: * before +)
echo $(( (3+4)*2 )) # 14
echo $(( 7/2*2 )) # 6 (left-to-right: 7/2=3, 3*2=6 — NOT 7)
There are no floats in bash arithmetic: echo $((1/3)) is 0. For real numbers delegate to bc or awk: awk 'BEGIN{printf "%.4f\n", 1/3}'.
The string-vs-numeric comparison trap.
| Intent | Numeric (correct) | String (trap!) |
|---|---|---|
| equal | [ "$a" -eq "$b" ] |
[ "$a" = "$b" ] |
| less than | [ "$a" -lt "$b" ] |
[[ "$a" < "$b" ]] (lexicographic!) |
Worked trap: [[ 10 < 9 ]] is true — string comparison goes character by character, and "1" < "9". Numerically [ 10 -lt 9 ] is false, as expected. Same disease as sort without -n.
[[ 10 < 9 ]] && echo "string: 10 < 9 is TRUE" # prints!
[ 10 -lt 9 ] || echo "numeric: 10 < 9 is FALSE" # prints
বাংলায়: Bash-এ
<দুই অর্থে চলে:[[ ]]-এর ভেতরে string তুলনা (অভিধান-ক্রম), আর-ltসংখ্যা তুলনা। "10 < 9 সত্য" — এই অদ্ভুত ফল দেখলেই বুঝবে string তুলনা হচ্ছে। সংখ্যার জন্য সবসময়-eq -lt -gtপরিবার, নাহলে(( a < b ))— arithmetic context-এ<সংখ্যাই বোঝে।
4.17 Control-flow & environment diagrams¶
(a) if/elif/else + loop flow of a typical job script:
┌─────────────┐
│ parse args │
└──────┬──────┘
▼
┌─────────────────┐ no ┌──────────────┐
│ $# >= 2 ? ├────────►│ usage; exit 1│
└────────┬────────┘ └──────────────┘
│ yes
▼
┌─────────────────┐ no
│ [ -d "$SRC" ] ? ├────────► error; exit 1
└────────┬────────┘
│ yes
▼
┌──────────────────────────┐
│ for case in case_* │◄────────┐
│ run solver > log 2>&1 │ │ next case
│ check $? → log status ├─────────┘
└────────────┬─────────────┘
▼ after loop
summary; exit 0
(b) ./script.sh (subshell) vs source script.sh (current shell):
./script.sh (or bash script.sh) source script.sh (or . script.sh)
┌─────────────────────────┐ ┌─────────────────────────┐
│ your shell (PID 4000) │ │ your shell (PID 4000) │
│ │ fork+exec │ │ runs commands HERE │
│ ▼ │ │ X=5 persists [yes] │
│ child bash (PID 4123) │ │ cd /tmp persists [yes] │
│ X=5 set here… │ └─────────────────────────┘
│ child exits → X gone │
└─────────────────────────┘
Rule: scripts that should MODIFY your session (module load, conda
activate, ~/.bashrc) must be sourced, not executed.
(c) getopts parsing flow for ./backup.sh -v -s src dst:
argv: -v -s src dst
│ │ │ │
┌──────▼────▼─────┼─────┼───────────────────────────┐
│ while getopts ":vs" opt │
│ pass 1: opt=v (OPTIND→2) │
│ pass 2: opt=s (OPTIND→3) │
│ pass 3: not an option → loop ends │
└───────────────────┬───────────────────────────────┘
▼
shift $((OPTIND-1)) # drops -v -s
▼
now $1=src $2=dst # positional args clean
বাংলায়: getopts-এর পুরো নাটক তিন অঙ্কে: (১) লুপ অপশনগুলো এক-একটা করে খায় আর OPTIND এগোয়, (২)
:দিয়ে শুরু OPTSTRING মানে error নিজে সামলাবে, (৩) লুপ শেষেshift $((OPTIND-1))দিলে বাকি থাকে শুধু আসল argument। এই shift ভুলে গেলে$1তখনও-v— পরীক্ষার প্রিয় bug।
5. Command / Syntax / Code Breakdown¶
#!/bin/bash¶
Always first line. Without it the script may run with sh (more limited).
set -euo pipefail¶
-e exit on error, -u undefined var = error, -o pipefail failure inside a pipe propagates. Strongly recommended in HPC scripts.
[ -d "${SOURCE}" ]¶
File test. Quote always to handle paths with spaces.
[[ "${NUM}" =~ ^[0-9]+$ ]]¶
Bash regex test (only inside [[ ]]).
(( a+b ))¶
Arithmetic. No $ needed for vars. Exit status 0 if result non-zero, 1 if zero.
$(cmd)¶
Modern command substitution; nestable.
Function definition¶
Always declare locals and quote params.
read -p "Q: " ANS¶
Prompt and read one line.
printf "%-10s %8.3f\n" "$name" "$num"¶
C-style formatted print. Better than echo for tables.
trap¶
Run cleanup on any exit.
6. Mandatory Practical Examples¶
Example 6.1 — Hello world (example02.sh)¶
Run: bash example02.sh → Hello World. Demonstrates shebang, echo, exit.
Example 6.2 — Variables and arguments (example03.sh)¶
#!/bin/bash
LOCALVAR="Hello World"
echo "LOCALVAR= ${LOCALVAR}"
echo "FIRST ARGUMENT = ${1}"
echo "SECOND ARGUMENT = ${2}"
exit 0
Run: bash example03.sh foo bar →
Example 6.3 — Quoting (example04.sh)¶
#!/bin/bash
LOCAL="Hello"
echo "${LOCAL}World" # → HelloWorld
echo "$LOCALWorld" # → (empty) - tries to expand $LOCALWorld
echo "$LOCAL World" # → Hello World
Lesson: use ${VAR} to delimit the variable name.
Example 6.4 — File test (example05.sh)¶
#!/bin/bash
if [ -f ${1} ]; then
echo "File ${1} exists !"
else
echo "File doesn't exist !"
fi
exit 0
bash example05.sh /etc/passwd → "File /etc/passwd exists !".
Example 6.5 — if-elif-else (example06.sh)¶
#!/bin/bash
if [ ${1} -lt 2006 ]; then
echo "The person is above 18 years old"
elif [ ${1} -eq 2006 ]; then
echo "The person 18 years old"
else
echo "The person is below 18 years old"
fi
Run: bash example06.sh 2000 → above 18.
Example 6.6 — Bash regex (example07.sh)¶
#!/bin/bash
PATTERN='^#[[:digit:]]{4}$'
if [[ ${1} =~ ${PATTERN} ]]; then
echo "matches"
else
echo "no match"
fi
bash example07.sh "#1234" → matches.
Example 6.7 — case (example08.sh)¶
See 4.6 above. Run interactively: enter 1/2/3.
Example 6.8 — Array (example09.sh)¶
See 4.8.
Example 6.9 — while/for/until (example10–13.sh)¶
See 4.7.
Example 6.10 — getopts (example14.sh)¶
#!/bin/bash
OPTSTRING=":ab"
while getopts ${OPTSTRING} opt; do
case ${opt} in
a) echo "Option -a was triggered." ;;
b) echo "Option -b was triggered." ;;
?) echo "Invalid option: -${OPTARG}."; exit 1 ;;
esac
done
Run: bash example14.sh -a -b → both messages. bash example14.sh -c → Invalid.
Example 6.11 — getopts with arguments (example15.sh)¶
OPTSTRING=":a:b:"
while getopts ${OPTSTRING} opt; do
case ${opt} in
a) echo "-a $OPTARG" ;;
b) echo "-b $OPTARG" ;;
:) echo "Option -${OPTARG} requires an argument."; exit 1 ;;
?) echo "Invalid option."; exit 1 ;;
esac
done
bash example15.sh -a hello -b world → -a hello / -b world.
Example 6.12 — Verbose adder (example16.sh)¶
Adds -v flag, two args, prints sum.
Example 6.13 — Command substitution (example17.sh)¶
Uses LINE=$(sed -n '2p' file) to grab line 2.
Example 6.14 — Function (example18.sh)¶
is_even returns 1 if even, 0 if odd. Captured via RES=$?.
Example 6.15 — Prime number (E06 Task 1 solution)¶
#!/bin/bash
PRIME=1
read -p "Enter a number : " NUMBER
if [[ "${NUMBER}" =~ ^[0-9]+$ ]]; then
if [ "${NUMBER}" -lt 2 ]; then
PRIME=0
else
for (( i=2 ; i<NUMBER ; i++ )); do
if (( NUMBER % i == 0 )); then PRIME=0; break; fi
done
fi
if [ "${PRIME}" -ne 0 ]; then
echo "${NUMBER} is a Prime"
else
echo "${NUMBER} is NOT a Prime"
fi
else
echo "Must be positive integer"; exit 1
fi
(The lecture's regex ^[0-9]*.$ is buggy — it allows trailing punctuation. The corrected regex is ^[0-9]+$. Mention this bug fix in the exam answer.)
বাংলায়: এই prime-checker-টা পরীক্ষার সম্পূর্ণ প্যাকেজ: input validation (regex), special case (<2), লুপে ভাগ-পরীক্ষা আর early break। lecture-এর regex-এর bug-টা ধরিয়ে দিলে extra নম্বর।
Example 6.16 — Extended backup script (E06 Task 2 solution)¶
Adds -s for sync (rsync) on top of -v:
OPTSTRING=":vs"
VERBOSE=0; SYNC=0
while getopts ${OPTSTRING} opt; do
case ${opt} in
v) VERBOSE=1 ;;
s) SYNC=1 ;;
?) USAGE "Invalid"; exit 1 ;;
esac
done
shift $((OPTIND-1))
SOURCE=${1}; DEST=${2}
if [ -d "$SOURCE" ] && [ -d "$DEST" ]; then
if [ $SYNC -eq 1 ]; then
if [ $VERBOSE -eq 1 ]; then rsync -aPv "$SOURCE" "$DEST"
else rsync -aP "$SOURCE" "$DEST"; fi
else
if [ $VERBOSE -eq 1 ]; then cp -vr "$SOURCE" "$DEST"
else cp -r "$SOURCE" "$DEST"; fi
fi
else
USAGE "Invalid directories"; exit 1
fi
Real-Life HPC/CFD Meaning. Identical pattern in production cluster scripts: copy results of long simulations from $WORK to $HOME or to a project share, with optional sync mode.
7. Real HPC/CFD Workflow¶
#!/bin/bash
set -euo pipefail
RES=()
for case in case_*; do
cd "$case"
echo "Running $case"
nohup ./solver > run.log 2>&1 &
RES+=($!)
cd ..
done
wait "${RES[@]}"
echo "All cases done"
# Aggregate residuals
for case in case_*; do
awk '/^Time =/ {print $3, $7}' "$case/run.log" > "$case/residuals.dat"
done
# Compress
tar czf $(date +%F)-results.tgz case_*/residuals.dat
বাংলায়: এটা একটা ছোট job-orchestrator: প্রতিটা case background-এ (
&) চালিয়ে PID গুলো array-তে রাখা হয় ($!), তারপরwaitসবগুলোর শেষ হওয়া পর্যন্ত দাঁড়ায়।set -euo pipefailথাকায় কোনো ধাপ ব্যর্থ হলেই script থেমে যায় — নীরবে ভুল ফল তৈরি হয় না।
8. Exercises and Solutions¶
Both Exercise 6 tasks are fully solved (6.15, 6.16).
Marking scheme — Task 1 (10 marks):
- 1: shebang.
- 1:
read -puser input. - 1: regex check positive integer.
- 1: special-case
<2. - 2: divisibility loop.
- 1: break early.
- 1: result message.
- 2: clean exit codes.
Marking scheme — Task 2 (10 marks):
- 1: extend OPTSTRING.
- 1: parse
-s. - 1: directory validation.
- 2: branch sync vs copy.
- 2: branch verbose.
- 1:
rsync -aPflag. - 1: error handling on
$?. - 1: USAGE refresh.
Common mistakes.
- Spaces around
=in assignment:VAR = 5is wrong →VAR=5. - Forgetting
;beforethen:if [ x ]; then. $1mixed with literal text:echo $1fileexpands$1file; use${1}file.cp -vrpaths with spaces unquoted.if [ $a==$b ](no spaces) → always true.- Using the
functionkeyword inconsistently; prefer POSIXfn() { … }.
Harder version of Task 1. Validate also negative numbers, accept a comma-separated list and report each:
read -p "Enter numbers comma-separated: " LINE
IFS=, read -ra NUMS <<< "$LINE"
for n in "${NUMS[@]}"; do
if ! [[ "$n" =~ ^[0-9]+$ ]]; then echo "skip $n"; continue; fi
PRIME=1
(( n<2 )) && PRIME=0
for ((i=2; i*i<=n; i++)); do (( n%i==0 )) && { PRIME=0; break; }; done
(( PRIME )) && echo "$n: PRIME" || echo "$n: not"
done
9. Written Exam Focus¶
9.1 Short Answers¶
Q. Difference between $@ and $*.
A. Both expand to all positional parameters. "$@" produces individual quoted args; "$*" produces one single string joined by IFS. Use "$@" when iterating to preserve word boundaries.
Q. What is $??
A. Exit status of the previous command (0 success, non-zero failure).
Q. Why use [[ ]] over [ ]?
A. [[ ]] is a Bash builtin: handles empty/unquoted variables safely, supports &&, ||, regex =~, glob match.
Q. What does shift do?
A. Drops $1 and shifts the rest down (so $2 becomes $1). With shift N, drops N. Used after getopts with OPTIND-1.
Q. Difference between > and | in scripts.
A. > redirects stdout to a file; | pipes stdout to another command's stdin.
9.2 Medium Answers¶
Q. (8 marks) Explain getopts with an example.
A. getopts "$OPTSTRING" var reads the next short option into var, advancing OPTIND. Inside OPTSTRING, a letter alone means flag; letter+: means it expects an argument (placed in OPTARG). A leading : enables silent error mode. After the loop, shift $((OPTIND-1)) consumes the parsed options. Example (example15.sh): OPTSTRING=":a:b:" parses -a value -b value.
Q. (5 marks) Compare for, while, and until loops.
A. for x in list iterates over a finite list (or C-style for ((;;))). while [ cond ] repeats while cond is true. until [ cond ] repeats until cond becomes true (negated while). Choose for for known iteration count, while for condition-driven repetition, until to loop until something succeeds.
9.3 Long Answer (12 marks)¶
Q. Walk through backup-data.sh and explain each step. (Use 4.13.)
9.4 Output Prediction¶
example03.sh foo bar baz →
example07.sh "#1234" → "matches".
example09.sh → 23 / 45 / 56 / "23 45 56 76" / 4.
example13.sh → Count = 1..4.
9.5 Comparison¶
while vs until — opposite condition. for (list) vs for ((;;)) — list-driven vs count-driven.
| Script | Binary | |
|---|---|---|
| Execution | Interpreter line by line | CPU-native |
| Edit-run cycle | Fast | Need recompile |
| Speed | Slow for math | Fast |
| Use | glue, automation | solvers, kernels |
| Form | Strengths |
|---|---|
[ ] |
POSIX, portable |
[[ ]] |
Bash, regex, safer |
(( )) |
Arithmetic, no $, C-like |
9.6 Templates¶
Script template:
#!/bin/bash
set -euo pipefail
usage() { echo "usage: $0 SRC DST" >&2; }
[[ $# -lt 2 ]] && { usage; exit 1; }
SOURCE="${1}"; DEST="${2}"
process() { :; }
process
getopts template: see 4.12. Loop template: for x in "${ARR[@]}"; do …; done.
9.7 Marking Scheme — "Write a script that backs up a folder" (10 marks)¶
- 1 shebang.
- 1 args check.
- 1 dir test.
- 2 cp/rsync execution.
- 2 exit code handling.
- 1 verbose option (getopts).
- 1 usage message.
- 1 quoting / safety.
10. Very Hard Questions¶
Beginner
- How to make a script executable? →
chmod +x s.sh. - Print first arg. →
echo $1. - Number of args. →
$#. - Exit success. →
exit 0. - Print array length. →
${#ARRAY[@]}.
Intermediate
- Test if file exists. →
[ -f f ]. - Loop over
*.cpp. →for f in *.cpp; do ...; done. - Read user input. →
read -p "?: " ans. - Run cmd in subshell. →
( cd dir; ls ). - Capture command output. →
x=$(cmd).
Hard
- Why does
cd dir; cmddiffer from(cd dir; cmd)? → Subshell scope; the parenthesised version doesn't change the current shell's pwd. - Rename all
.txtto.bakin current dir. →for f in *.txt; do mv -- "$f" "${f%.txt}.bak"; done. - getopts with required args & combined flags. → see 4.12.
- Predict the exit status of
(( 0 )). → 1 (zero is "false" in arithmetic context). - How to source vs run? →
. file.shorsource file.shruns in the current shell.
Very Hard
- Why is
for f in $(ls)worse thanfor f in *? → Word splitting breaks filenames with spaces; globs are safe. - Explain
set -epitfalls in pipes. → A failing command in the middle of a pipe doesn't trigger-eunlesspipefailis set. - Implement memoization in bash. → Use associative arrays (
declare -A cache).
Deep Integration
- Combine awk + bash to re-run only failed cases. →
awk '/FAIL/{print $1}' results | xargs -n1 ./rerun.sh. - Use trap to clean tmp files on Ctrl-C. →
trap 'rm -f "$tmp"' EXIT INT TERM.
Coding/Command
- Script that prints the total size of a directory in bytes. →
du -sb "$1" | cut -f1. - Loop over 1..10 printing squares. →
for i in {1..10}; do echo $((i*i)); done.
Debugging
if [ $x = "yes" ]fails when x is empty. → Use[ "$x" = "yes" ]or[[ ]].for f in *.cppruns with literal*.cppwhen no matches. →shopt -s nullglobor guard with[ -e "$f" ] || continue.
Long Written
- (250 words) Walk through the extended backup-data.sh from 6.16 explaining how getopts, case, dir tests, and rsync cooperate.
11. Debugging and Mistake Analysis¶
| Mistake | Why wrong | Correct | Explanation |
|---|---|---|---|
VAR = 5 |
Spaces → runs VAR as command |
VAR=5 |
Bash assignment |
[ $a==$b ] |
Always true (single string) | [ "$a" = "$b" ] |
spaces & = |
[ -f $f ] with empty $f |
"too many arguments" | [ -f "${f}" ] |
quote |
function fn() { … } |
non-portable mix | fn() { … } |
POSIX form |
cp $1 $2 with spaces |
breaks into extra args | cp "$1" "$2" |
quote |
return 100 for a value |
return sets exit code only | echo & capture $( ) |
scope |
No local in functions |
leaks vars to caller | local x |
hygiene |
set -e after the error |
doesn't undo | put at top | order |
Forgot chmod +x |
"Permission denied" | chmod +x |
run-bit |
Using $1 right after getopts |
still sees -v |
shift $((OPTIND-1)) |
options |
বাংলায়: এই টেবিল থেকে পরীক্ষায় সবচেয়ে বেশি আসে প্রথম তিনটা: assignment-এ স্পেস,
==-এর দুপাশে স্পেস না দেওয়া, আর খালি variable unquoted রাখা। তিনটারই ওষুধ এক: সবসময়"${VAR}"লেখো আর[ ]-এর ভেতরে প্রতিটা token-এর মাঝে স্পেস দাও।
12. Mini Project for Mastery¶
Goal: "Sweep" script that runs a CFD case for many Reynolds numbers in parallel.
#!/bin/bash
set -euo pipefail
TEMPLATE="${1:-case_template}"
RE_LIST=(500 1000 2000 5000 10000)
SLOTS=4
i=0
for RE in "${RE_LIST[@]}"; do
DIR="case_Re${RE}"
rm -rf "$DIR"
cp -r "$TEMPLATE" "$DIR"
sed -i -E "s/^(Re *= *).*/\1${RE}/" "$DIR/in.dat"
(
cd "$DIR"
./solver > run.log 2>&1
) &
((i++))
if (( i % SLOTS == 0 )); then wait; fi
done
wait
echo "All cases done."
Connection to exam: positional arg with default ${1:-…}, arrays, loops, sed, redirection, parallel & + wait, arithmetic — all of Chapter 8 in one script.
13. Final Chapter Cheat Sheet¶
| Item | Memorise |
|---|---|
| Shebang | #!/bin/bash |
| Strict mode | set -euo pipefail |
| Args | $1 $2 $# $@ $* $? $$ $! |
| Quote | always "${VAR}" |
| if | if [ ... ]; then ... fi |
| elif/else | elif, else |
| case | case x in pat) ... ;; esac |
| for | for i in {1..10}; do ...; done |
| while | while [ cond ]; do ...; done |
| until | until [ cond ]; do ...; done |
| arr | A=(a b c); "${A[@]}"; ${#A[@]} |
| function | fn(){ local x=$1; ...; } |
| read | read -p "?: " v |
| arith | (( x = y + 1 )); int-only; ** % precedence |
| exit-status algebra | && runs on 0, \|\| runs on non-0; A && B \|\| C ≠ if/else |
| substitution | x=$(cmd) |
| getopts | while getopts ":a:b:" o; do ...; done; shift $((OPTIND-1)) |
| file tests | -f -d -e -r -w -x -s -L |
| string tests | -z -n = != =~ |
| numeric tests | -eq -ne -lt -le -gt -ge |
| string-vs-num trap | [[ 10 < 9 ]] true (lexicographic); use -lt |
| trap | trap 'cleanup' EXIT |
| Trap | [ $a==$b ] always true |
| Top phrase | "Bash script = shell commands + control flow + variables + functions; saved with shebang and executable bit." |
14. Mock Exam — Four Levels¶
Level 1 — Basic (definitions & syntax)¶
Q1. Write the first line every bash script should have, and the command making run.sh executable.
Solution: #!/bin/bash and chmod +x run.sh.
Q2. What do $#, $0, and $$ expand to?
Solution: $# number of arguments, $0 script name, $$ PID of the current shell.
Q3. Write a for loop printing 1 to 5.
Solution: for i in {1..5}; do echo $i; done
Q4. How do you read a value into variable name with the prompt "Name: "?
Solution: read -p "Name: " name
Q5. What does exit 2 do inside a script?
Solution: Terminates the script immediately with exit status 2 (a failure code visible to the caller via $?).
Level 2 — Intuitive (predict the output / explain why)¶
Q1. Predict the output: x=5; ( x=99 ); echo $x
Solution: 5 — ( ) runs in a subshell; the assignment dies with it.
Q2. Predict: [ -z "$UNSET_VAR" ] && echo empty || echo full
Solution: empty — -z is true for a zero-length string; an unset variable expands to empty (quoted).
Q3. Why does if [ $n -gt 10 ] crash with "unary operator expected" when n is empty, but [[ $n -gt 10 ]] doesn't?
Solution: [ ] performs word-splitting first: empty $n vanishes, leaving [ -gt 10 ] — malformed. [[ ]] is parsed as syntax, not words, so the empty operand is handled.
Q4. Predict: echo $(( 10/4 )) $(( 10%4 )) $(( 10**2 ))
Solution: 2 2 100 — integer division truncates; modulo gives the remainder; ** is power.
Q5. false; echo $?; echo $? — what prints?
Solution: 1 then 0 — the first echo reports false's status; the second reports the (successful) first echo's status. $? always refers to the IMMEDIATELY previous command.
Level 3 — Hard (exam level)¶
Q1. (8 marks) Trace $? after each line:
Solution: line1: file missing → grep exits 2 → $?=2. line2: echo prints A, succeeds → $?=0. line3: /etc is a dir (status 0) → && runs false → $?=1. line4: echo prints B → $?=0.
বাংলা ইঙ্গিত: chain-এর $? হলো শেষ-চলা কমান্ডের status — &&-এর ডান দিক চললে সেটাই, না চললে বাঁ দিকেরটা।
Q2. (8 marks) Write a script check_args.sh that requires exactly 2 arguments (else usage+exit 1), requires arg1 to be an existing readable file, and arg2 to be a positive integer. Use [[ ]] and a regex.
Solution:
#!/bin/bash
set -euo pipefail
usage() { echo "usage: $0 FILE COUNT" >&2; exit 1; }
[[ $# -eq 2 ]] || usage
[[ -f "$1" && -r "$1" ]] || { echo "no readable file: $1" >&2; exit 1; }
[[ "$2" =~ ^[1-9][0-9]*$ ]] || { echo "COUNT must be positive int" >&2; exit 1; }
echo "ok"
^[1-9][0-9]*$ — ^[0-9]+$ দিলে 0-ও পাশ করে যেত; এই সূক্ষ্মতা-ই নম্বর বাঁচায়।
Q3. (10 marks) What is wrong with this loop, and write the fixed version?
Solution: Three sins: $(ls) word-splits filenames with spaces; $f unquoted (splits again); ls output parsing is fragile. Fix:
for f in $(ls ...) দেখলেই লাল পতাকা — glob নিজেই list দেয়, ls লাগেই না; আর সব expansion-এ quote।
Q4. (10 marks) Write a function retry that runs a given command up to N times until it succeeds, sleeping 2 s between attempts: retry 5 curl -s http://host/data.
Solution:
retry() {
local n=$1; shift
local i
for (( i=1; i<=n; i++ )); do
"$@" && return 0
echo "attempt $i failed" >&2
(( i < n )) && sleep 2
done
return 1
}
"$@" preserves the command + args exactly; the function's status is the command's last status.
বাংলা ইঙ্গিত: shift-এর পরে "$@"-ই পুরো কমান্ড — eval লাগবে না; আর সফল হলেই return 0, এটাই retry-প্যাটার্নের হৃৎপিণ্ড।
Q5. (10 marks) Predict the full output, then explain the two getopts subtleties:
./s.sh -v -n 3 file.dat # OPTSTRING=":vn:"
# inside: v) V=1 ;; n) N=$OPTARG ;; then shift $((OPTIND-1)); echo "$V $N $1"
Solution: Output: 1 3 file.dat. Subtleties: (a) n: means -n consumes the NEXT word as OPTARG (the 3); (b) OPTIND ends at 4, so shift 3 removes -v -n 3, making file.dat the new $1.
বাংলা ইঙ্গিত: OPTIND সবসময় "পরের unprocessed argument"-এর index — তাই shift হয় OPTIND−1 টা।
Level 4 — Beyond the lecture (transfer + coding)¶
Q1. Write a bash snippet that launches ./solver on every case_*/ directory, at most 4 concurrently, collecting nonzero exit codes, and finally exits 1 if ANY case failed. (No GNU parallel.)
Solution:
#!/bin/bash
set -uo pipefail
pids=(); dirs=()
fail=0
for d in case_*/; do
( cd "$d" && ./solver > run.log 2>&1 ) &
pids+=($!); dirs+=("$d")
(( ${#pids[@]} % 4 == 0 )) && wait -n || true
done
for i in "${!pids[@]}"; do
wait "${pids[$i]}" || { echo "FAILED: ${dirs[$i]}" >&2; fail=1; }
done
exit $fail
wait <pid> সেই process-এর exit status ফেরত দেয় — এটাই per-case ব্যর্থতা ধরার চাবি; set -e এখানে ইচ্ছে করেই নেই, নাহলে প্রথম fail-এই script মরে যেত।
Q2. Your sweep script must edit in.dat setting Re from an array, but a colleague's file uses either Re = 100 or Re=100 (spaces vary). Write the robust sed call, and explain why you must use double quotes around the sed program.
Solution:
[[:space:]]* absorbs any spacing; double quotes (not single) let ${RE} expand inside the sed program while \1 stays escaped.
বাংলা ইঙ্গিত: shell variable sed-এ ঢোকাতে হলে double quote — single quote-এ ${RE} আক্ষরিক থেকে যায়; এই এক লাইনেই দুই chapter-এর জ্ঞান মেশে।
Q3. A nightly cron job runs backup.sh, which works interactively but fails in cron with "command not found: rsync". Diagnose and give two fixes.
Solution: Cron runs with a minimal environment — no interactive ~/.bashrc, so PATH lacks /usr/local/bin (or module-provided paths). Fixes: (1) use absolute paths: /usr/bin/rsync; (2) set PATH at the top of the script (export PATH=/usr/bin:/bin:/usr/local/bin) or source the needed profile. (Bonus: module load must also be re-done inside the script on clusters.)
বাংলা ইঙ্গিত: "টার্মিনালে চলে কিন্তু cron/sbatch-এ চলে না" শুনলেই উত্তর: environment আলাদা — PATH/module/bashrc সেখানে নেই।
Q4. Combine with C++ (Ch 11/12): write build_and_test.sh that recompiles solver.cpp only when the binary is older than the source (Make-like), runs it on test.in, and diffs against expected.out, reporting PASS/FAIL.
Solution:
#!/bin/bash
set -euo pipefail
if [ ! -x solver ] || [ solver.cpp -nt solver ]; then
g++ -O2 -std=c++17 -Wall solver.cpp -o solver
echo "rebuilt"
fi
./solver < test.in > actual.out
if diff -q actual.out expected.out > /dev/null; then
echo PASS
else
echo FAIL >&2; exit 1
fi
-nt ("newer than") is bash's timestamp comparison — exactly Make's rebuild rule in one operator.
বাংলা ইঙ্গিত: [ src -nt bin ] = Make-এর timestamp নিয়ম bash-এ — দুই chapter জুড়ে দেওয়া প্রশ্ন পরীক্ষায় খুব "german university" স্টাইল।
End of Chapter 8.