সরাসরি পর্বে যান
পর্ব ১০ · ৮১-এর মধ্যে · মডিউল ২

Bash automation — নিরাপদ script, cron আর systemd timer

Bash Automation — Safe Scripts, Cron and systemd Timers

Taught in Bangla, this lesson decides when Bash, Python or Ansible fits, then builds safe scripts with quoting, set -euo pipefail and its gaps, functions, exit codes, trap and mktemp. It makes scripts idempotent with dry-run, logging and retry with backoff, reads the five cron fields with a next-run simulator, stops overlap with flock, and compares cron with systemd timers.

এই পর্বে যা শিখবেন

  • কোন কাজ Bash-এ, কোনটি Python বা Ansible-এ করা ভালো, সেই সিদ্ধান্ত
  • Quoting, set -euo pipefail, function, exit code, trap আর mktemp ঠিকভাবে ব্যবহার করা
  • এমন script লেখা, যা বারবার চালালেও নিরাপদ, আর যার আচরণ log থেকে বোঝা যায়
  • Cron-এর পাঁচটি ঘর পড়া, পরের চলার সময় হিসাব করা আর তার ফাঁদ এড়ানো
  • Lock দিয়ে overlap বন্ধ করা, আর cron ও systemd timer-এর মধ্যে বেছে নেওয়া

১ · কখন script লিখবেন — Bash, Python না Ansible

একটি কাল্পনিক অনলাইন বাজার অ্যাপের কথা ভাবুন। প্রতি রাতে একজন প্রকৌশলী server-এ ঢুকে database-এর backup নেন, ফাইলটি অন্য জায়গায় পাঠান আর পুরোনো backup মোছেন। কাজটি ১৫ মিনিটের। পর্ব ০৭-এর ভাষায় এটি toil: হাতে করা, বারবারের, আর এতে স্থায়ী কোনো উন্নতি নেই। একদিন তিনি ছুটিতে গেলেন, আর সেদিন backup হলো না।

Shell script হলো রান্নাঘরের রেসিপি খাতা। মা মুখে মুখে রান্না শেখালে প্রতিবার একটু আলাদা হয়, আর মা না থাকলে রান্নাই আটকে যায়। খাতায় লেখা থাকলে যে কেউ একই রান্না করতে পারে। কিন্তু খাতায় “লবণ ১০ চামচ” ভুল লেখা থাকলে প্রতিবার সেই ভুলটিই হুবহু হবে। তাই script-কেও কোডের মতো যত্নে লিখতে হয়।

হাতের কাজ script-এ আনার আগে পর্ব ০৭-এর হিসাবটি করুন: কাজটি বছরে কত ঘণ্টা খায়, আর script বানাতে ও রক্ষণাবেক্ষণে কত ঘণ্টা লাগবে। তারপর ঠিক করুন, কোন হাতিয়ার। Bash হলো কমান্ড জোড়া লাগানোর আঠা। Data structure বা জটিল যুক্তি এর শক্তি নয়।

হাতিয়ারকখন ভালোকখন নয়
Bashএকটি server-এ কয়েকটি কমান্ড পরপর চালানো, ফাইল সরানো, ছোট glue কাজJSON ভাঙা, হিসাব, জটিল শাখা, দীর্ঘ যুক্তি
PythonAPI ডাকা, JSON, retry-র জটিল নিয়ম, unit test দরকার এমন কাজদুই লাইনের কাজ, বা যেখানে Python বসানো নেই
Ansibleঅনেক server-এ একই configuration বসানো, চাওয়া অবস্থা বর্ণনা করাএকবারের ছোট কাজ, ঘন ঘন সময়সূচির কাজ
Terraform ধরনের IaCCloud resource বানানো আর তার অবস্থা ধরে রাখাServer-এর ভেতরের দৈনন্দিন কাজ

একটি ছোট হিসাব। ওপরের কাজটি বছরে ৩৬৫ × ১৫ = ৫,৪৭৫ মিনিট, অর্থাৎ প্রায় ৯১ ঘণ্টা। ধরে নিই script লিখতে, পরীক্ষা করতে আর monitoring বসাতে ১২ ঘণ্টা লাগে, আর বছরে রক্ষণাবেক্ষণে যায় ৮ ঘণ্টা। তাহলে প্রথম বছরেই প্রায় ৭১ ঘণ্টা বাঁচে। উল্টো দিকও আছে: বছরে দুবারের ৫ মিনিটের নিরীহ কাজের জন্য script বানালে খরচই বেশি। তবে যে কাজ কম হয় কিন্তু ভুল হলে বড় ক্ষতি, যেমন database failover, সেখানে সময়ের হিসাব ছাড়াও script বা runbook দরকার, কারণ চাপের মুহূর্তে মানুষ ভুল করে।

একটি প্রচলিত নিয়ম: Google-এর Shell Style Guide বলে, script ১০০ লাইনের বেশি হলে বা জটিল নিয়ন্ত্রণ-প্রবাহ এলে আরও গোছানো ভাষায় নেওয়ার কথা ভাবতে হবে। সীমাটি আইন নয়, তবে সংকেত হিসেবে ভালো। Ansible নিয়ে বিস্তারিত পর্ব ৫৬-এ, আর Terraform পর্ব ৫২-তে। Python দিয়ে automation দেখতে পারেন Python-এর automation পর্বে।

২ · Shebang, variable আর quoting — ছোট চিহ্নের বড় দাম

Script-এর প্রথম লাইনটিকে বলে shebang. এটি kernel-কে জানায়, এই ফাইল কোন প্রোগ্রাম দিয়ে চলবে।

#!/usr/bin/env bash
# env finds bash through PATH. #!/bin/bash is also common.
name="Rahim"              # no spaces around =
echo "Hello, $name"

chmod +x hello.sh দিলে ./hello.sh লিখে চালানো যায় (অনুমতির কথা পর্ব ০৮-এ)। একটি সতর্কতা: Debian আর Ubuntu-তে /bin/sh সাধারণত dash, Bash নয়। তাই #!/bin/sh লিখে Bash-এর [[ ]] বা array ব্যবহার করলে ভুল হতে পারে। Bash-এর সুবিধা চাইলে shebang-এও Bash লিখুন।

Quoting: সবচেয়ে সস্তা নিরাপত্তা

Quote ছাড়া variable লিখলে shell দুটি বাড়তি কাজ করে। মানটিকে ফাঁকা জায়গায় ভাঙে (word splitting), আর * থাকলে তাকে ফাইলের নামে বদলায় (glob). Quoting এই দুটি বন্ধ করে।

পার্সেলের ঠিকানায় লেখা “নতুন বাজার”। নতুন কর্মী যদি ফাঁকা দেখলেই আলাদা জায়গা ভাবেন, তাহলে তিনি “নতুন” আর “বাজার” নামের দুটি জায়গায় পার্সেল খুঁজবেন, আর আসল বাড়ি বাদ পড়বে। Quote হলো ঠিকানার চারপাশে দেওয়া বাক্স: ভেতরের সবটা এক ঠিকানা।

#!/usr/bin/env bash
# Three files. The target has a space in its name.
T=$(mktemp -d)
trap 'rm -rf -- "$T"' EXIT
cd "$T"

fresh() { rm -f -- *; touch "old backup.tar" old backup.tar; }
left()  { printf 'left:'; printf ' [%s]' *; echo; }

target="old backup.tar"

echo "--- without quotes ---"
fresh
set -- $target
echo "shell sees $# arguments: [$1] [$2]"
rm -f $target
left

echo "--- with quotes ---"
fresh
rm -f -- "$target"
left

echo "--- empty variable ---"
DIR=""
printf 'the command would become: rm -rf %s*\n' "$DIR/"
if ( : "${DIR:?}" ) 2>/dev/null; then
  echo "guard passed"
else
  echo 'guard ${DIR:?} stopped the script before rm'
fi

আউটপুটের প্রথম অংশ বলে shell sees 2 arguments: [old] [backup.tar], তারপর left: [old backup.tar]. মানে যে ফাইল মোছার কথা, সেটিই রয়ে গেছে, আর নিরীহ দুটি ফাইল মুছে গেছে। Quote দিলে left: [backup.tar] [old], অর্থাৎ শুধু লক্ষ্যটি গেছে। শেষ অংশটি কিছু মোছে না, শুধু দেখায় the command would become: rm -rf /*, আর পাহারা ${DIR:?} তার আগেই থামিয়ে দেয়। -- বলে দেয় এরপরের কিছুই option নয়, ফলে -rf নামের ফাইলও বিপদ ঘটায় না।

সবচেয়ে দামি ফাঁদ: rm -rf "$DIR/"-এ DIR খালি হলে কমান্ডটি হয় rm -rf "/". GNU rm শুধু এই আক্ষরিক /-টি ঠেকায় (--preserve-root, এখনকার default). কিন্তু "$DIR/"* হলে shell আগেই সেটিকে /bin /etc /home ... তালিকায় বদলে দেয়, আর rm সুরক্ষার কিছু দেখতেই পায় না। তাই মোছার কমান্ডে লিখুন rm -rf -- "${DIR:?}/"*. এই ধরনের কমান্ড কখনো পরীক্ষার জন্যও বাস্তব মেশিনে চালাবেন না।

লেখাঅর্থ
"$x"মান এক টুকরো থাকে; প্রায় সবসময় এটিই চাই
"$@"সব argument, প্রতিটি আলাদা ও অক্ষত
${x:-7}x খালি বা অনুপস্থিত হলে 7 ব্যবহার
${x:?message}x খালি হলে message দেখিয়ে script থামানো

৩ · set -euo pipefail — ভুলে থামা, আর তার অবাক করা দিক

Bash-এর default আচরণ অবাক করার মতো: একটি কমান্ড ব্যর্থ হলেও পরের লাইন চলে। নিচের script-টি একটি বানান ভুলে কী করতে পারে, ধাপে ধাপে দেখুন। ধরুন cron এটিকে চালিয়েছে, তাই শুরুর ফোল্ডার ব্যবহারকারীর home.

1#!/usr/bin/env bash2set -euo pipefail   # ধাপ ৬ থেকে3cd /srv/app/tmpp    # বানান ভুল4rm -rf ./*5cp /srv/build/app.tar .6echo "deploy done"
এই মুহূর্তে
set -e
এখনকার ফোল্ডার
ফোল্ডারের ভেতরে
শেষ exit code
  1. শুরু। Script চলছে /home/deploy থেকে। সেখানে তিনটি দরকারি ফোল্ডার আছে। set -e এখন ধরুন বন্ধ, অর্থাৎ লাইন ২ নেই।
  2. cd ব্যর্থ। tmp-এর জায়গায় tmpp লেখা, তাই exit code 1. ফোল্ডার বদলায়নি, কিন্তু Bash থামে না।
  3. আসল ক্ষতি। rm -rf ./* ভেবেছিল সে /srv/app/tmp-এ আছে, অথচ সে আছে home-এ। তিনটি ফোল্ডারই গেল, exit code 0.
  4. ভুল জায়গায় ফাইল। app.tar home-এ কপি হলো। Exit code আবার 0. কেউ কিছু টের পাচ্ছে না।
  5. সবচেয়ে খারাপ অংশ। শেষ কমান্ড echo সফল, তাই পুরো script-এর exit code 0. Cron-এর কাছে কাজটি সফল।
  6. এবার set -euo pipefail চালু। একই script, ফোল্ডারগুলো অক্ষত, লাইন ২ এখন চলছে।
  7. প্রথম ভুলেই থামা। cd ব্যর্থ, script exit code 1 নিয়ে শেষ। rm, cp, echo কিছুই চলেনি, ফোল্ডার তিনটি নিরাপদ। ব্যর্থতাটিও এবার চোখে পড়বে।

তিনটি option আলাদা কাজ করে। একসাথে লেখা হয় বলে একটি মনে হয়, কিন্তু প্রতিটির আলাদা নাম আছে।

Optionকাজ
-e (errexit)কোনো কমান্ড ব্যর্থ হলে script থামে
-u (nounset)অজানা variable ব্যবহার করলে script থামে; বানান ভুল ধরা পড়ে
-o pipefailPipeline-এর যেকোনো ধাপ ব্যর্থ হলে পুরো pipeline ব্যর্থ

Pipeline-এর exit code default-এ শুধু শেষ ধাপের। ফলে মাঝের ব্যর্থতা ঢাকা পড়ে। একই script দুভাবে চালিয়ে দেখুন। ব্যর্থতা ইচ্ছাকৃত, তবে আলাদা process-এ চলছে, তাই বাইরের প্রোগ্রাম ঠিকভাবেই শেষ হয়।

#!/usr/bin/env bash
# The same pipeline, without and with pipefail. sales.log does not exist.
T=$(mktemp -d)
trap 'rm -rf -- "$T"' EXIT
cd "$T"

cat > report_a.sh <<'EOF'
set -e
cat sales.log 2>/dev/null | sort > report.txt
echo "report ready, sending $(wc -l < report.txt) lines"
EOF

cat > report_b.sh <<'EOF'
set -eo pipefail
cat sales.log 2>/dev/null | sort > report.txt
echo "report ready, sending $(wc -l < report.txt) lines"
EOF

echo "--- set -e only ---"
bash report_a.sh
echo "script exit code: $?"

echo "--- set -eo pipefail ---"
bash report_b.sh
echo "script exit code: $?"

echo "--- what each stage returned ---"
cat sales.log 2>/dev/null | sort | wc -l
echo "PIPESTATUS: ${PIPESTATUS[*]}"

শুধু set -e-এ cat ব্যর্থ হলেও sort সফল, তাই লেখা আসে report ready, sending 0 lines আর script exit code: 0. একটি খালি report চুপচাপ চলে যেত। pipefail দিলে কোনো report-এর লাইন আসে না, শুধু script exit code: 1. শেষ লাইন PIPESTATUS: 1 0 0 দেখায়, তিন ধাপের প্রথমটিই ব্যর্থ ছিল।

একটি বাড়তি অভ্যাস: set -e থাকলেও গুরুত্বপূর্ণ জায়গায় ব্যর্থতার বার্তা নিজে লিখুন, যেমন cd /srv/app || { echo "cannot enter /srv/app" >&2; exit 1; }. এতে রাতের log পড়ে সকালে বোঝা যায়, ঠিক কোন ধাপে কেন থেমেছিল।

set -e-এর জানা ফাঁদ

set -euo pipefail সিট-বেল্টের মতো: দরকারি, কিন্তু দুর্ঘটনা পুরো ঠেকায় না। নিচের প্রতিটি আচরণ Bash 5.2-তে চালিয়ে মেলানো।

লেখাকী ঘটেউপায়
if deploy; then বা deploy || echo nodeploy function-এর ভেতরে -e কাজই করে না, ব্যর্থ লাইনের পরেও চলেFunction-এর ভেতরে প্রতিটি ধাপে || return 1, অথবা আলাদা script
local x=$(cmd)Exit code local-এর, cmd-এর নয়; ব্যর্থতা হারায়দুই লাইনে: local x, তারপর x=$(cmd)
((i++)), i যখন 0ফলের মান 0, তাই exit code 1, script থেমে যায়i=$((i + 1))
grep কিছু না পেলেExit code 1, তাই সাধারণ “মিল নেই” ফলেও script থামেgrep ... || true, তবে ভেবে
yes | head -1pipefail-এ বাঁদিকের ধাপ SIGPIPE-এ মরে (141), pipeline ব্যর্থPipeline ছোট রাখা, বা অস্থায়ী ফাইল
x=$(false; echo ok)$( )-এর ভেতরে -e default-এ চলে নাshopt -s inherit_errexit (Bash 4.4+)

একটি অভ্যাস: ShellCheck নামের বিনা মূল্যের tool script না চালিয়েই পড়ে আর এই ধরনের ভুল ধরে। যেমন quote ছাড়া variable (SC2086), cd-এর ব্যর্থতা না দেখা (SC2164), আর "$DIR/" ধরনের ঝুঁকি (SC2115). এটি editor-এ আর CI-তে রাখার পরামর্শ রইল। সতর্কবার্তা বন্ধ করার আগে তার কারণ পড়ে নিন।

৪ · Function, argument আর exit code

লম্বা script-কে ছোট function-এ ভাগ করুন। Bash-এর function কিছুটা আলাদা: সে মান “ফেরত” দেয় না। সে দেয় exit code (0 থেকে 255), আর লেখা দেয় stdout-এ।

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

usage() { echo "usage: $0 PATH LIMIT_PERCENT" >&2; exit 2; }

# Prints the used percentage of a path, e.g. 73
used_percent() {
  local path=$1
  df --output=pcent "$path" | tail -n 1 | tr -dc '0-9'
}

[ "$#" -eq 2 ] || usage        # $# = number of arguments
path=$1
limit=$2

used=$(used_percent "$path")
if [ "$used" -ge "$limit" ]; then
  echo "ALERT: $path is ${used}% full" >&2
  exit 1
fi
echo "ok: $path is ${used}% full"

$1, $2 হলো argument, $# তাদের সংখ্যা, "$@" পুরো তালিকা, আর shift প্রথমটি বাদ দেয়। Function-এর ভেতরের variable-এ local দিন, নইলে সেটি পুরো script-এ ছড়ায়। Exit code-এর কিছু অর্থ প্রচলিত। Cron, systemd আর CI সবাই এই সংখ্যা দেখেই সফল বা ব্যর্থ ঠিক করে, তাই ব্যর্থ হলে script-কে অবশ্যই non-zero দিয়ে শেষ করতে হবে।

Codeপ্রচলিত অর্থ
0সফল
1সাধারণ ব্যর্থতা
2ভুল ব্যবহার, যেমন ভুল argument (প্রচলিত রীতি, বাধ্যতামূলক নয়)
126 / 127চালানোর অনুমতি নেই / কমান্ড পাওয়া যায়নি (cron-এ PATH ভুল হলে এই 127 দেখা যায়)
128 + nSignal n-এ মৃত্যু: 130 = SIGINT, 137 = SIGKILL, 143 = SIGTERM

একটি অভ্যাস: বার্তা আর ফলাফল আলাদা রাখুন। ফলাফল stdout-এ, error আর log stderr-এ (>&2). তাহলে পরের কমান্ড pipe দিয়ে শুধু ফলাফল পায়।

Script ভুল করলে কীভাবে খুঁজবেন

তিনটি সহজ হাতিয়ার মনে রাখুন। bash -n script চালায় না, শুধু syntax পড়ে। bash -x প্রতিটি কমান্ড চালানোর আগে + চিহ্ন দিয়ে ছাপে, আর variable-এর আসল মান বসিয়ে দেখায়। ফলে ধরা পড়ে $DIR আসলে কী ছিল। শুধু একটি অংশ দেখতে চাইলে সেখানে set -x লিখে শেষে set +x দিন।

bash -n deploy.sh          # syntax check only, runs nothing
bash -x deploy.sh          # print each command after expansion
shellcheck deploy.sh       # static analysis, if installed

-x সব মান খোলাখুলি ছাপে। Script-এ password বা token থাকলে সেটি log-এ চলে যাবে। তাই production-এ, বিশেষ করে CI-এর log-এ, -x ব্যবহারের আগে ভাবুন।

৫ · পরিষ্কার করে বের হওয়া — mktemp আর trap

বিয়েবাড়ির ক্যাটারিংয়ে নিয়ম থাকে: অনুষ্ঠান ঠিকভাবে শেষ হোক বা মাঝপথে ঝড় আসুক, বের হওয়ার আগে চুলার গ্যাস বন্ধ করতে হবে। trap ঠিক সেই নিয়ম: “যেভাবেই বের হই, শেষ কাজ এটি।”

অস্থায়ী ফাইলের নাম হাতে ঠিক করবেন না। /tmp/backup.tmp ধরনের নিশ্চিত নাম দুটি কাজের মধ্যে ধাক্কা লাগায়। আর /tmp সবার জন্য খোলা বলে অনুমান করা যায় এমন নাম নিরাপত্তার ঝুঁকিও। mktemp অনন্য নাম বানায়, আর শুধু মালিকের অনুমতি রাখে।

1#!/usr/bin/env bash2set -euo pipefail3WORK=$(mktemp -d)4trap 'rm -rf -- "$WORK"' EXIT5pg_dump shop > "$WORK/shop.sql"6gzip "$WORK/shop.sql"7upload_backup "$WORK/shop.sql.gz"8echo "backup ok"
অস্থায়ী ফোল্ডার (ধরে নেওয়া মাপ)
WORK
ভেতরে
জায়গা
trap
exit code
  1. mktemp -d. একটি অনন্য ফোল্ডার তৈরি হলো। নামটি নমুনা, প্রতিবার আলাদা।
  2. Trap আগে। ফোল্ডার বানানোর পরপরই, কাজ শুরুর আগে, trap বসানো হলো। যত আগে, তত কম ফাঁক।
  3. Dump. ধরে নিই database dump ২ GB. জায়গা এখন 2.0 GB.
  4. Compress. gzip ফাইলটিকে বদলে দিল। ধরে নিই ফল 0.4 GB, আসল ফাইলটি আর নেই।
  5. Upload ব্যর্থ। Network সমস্যায় লাইন ৭ exit code 1 দিল। set -e script থামাচ্ছে, লাইন ৮ চলবে না।
  6. Trap চলল। বের হওয়ার মুহূর্তে rm -rf -- "$WORK" চলল, জায়গা 0 GB. Exit code 1-ই রইল, ফলে ব্যর্থতার খবর হারাল না।
  7. Trap না থাকলে? প্রতি ব্যর্থ রাতে 0.4 GB পড়ে থাকে। ৭ রাতে 7 × 0.4 = 2.8 GB. ছোট server-এ এভাবেই একদিন disk ভরে আর সব service থেমে যায়।

EXIT trap স্বাভাবিক শেষ, set -e-এর থামা আর exit সব ক্ষেত্রেই চলে। Bash-এ Ctrl+C (SIGINT) আর SIGTERM-এ থামলেও চলে; এই আচরণ অন্য shell-এ একই নাও হতে পারে। কিন্তু SIGKILL বা বিদ্যুৎ চলে গেলে কিছুই চলে না। তাই পুরোনো অস্থায়ী ফাইল মোছার একটি আলাদা ব্যবস্থাও দরকার হতে পারে। Signal নিয়ে বিস্তারিত অপারেটিং সিস্টেমের signal পর্বে পাবেন।

৬ · বারবার চালালেও নিরাপদ — idempotent script, dry-run, log আর retry

Idempotency মানে কাজটি একবার করা আর দশবার করার ফল একই। লিফটের বোতাম ভাবুন: পাঁচবার চাপলেও লিফট একবারই আসে। Automation-এ এটি জরুরি, কারণ script মাঝপথে ভেঙে যায়, আর তখন “আবার চালান” হওয়া চাই সবচেয়ে নিরাপদ উত্তর।

দ্বিতীয়বারে ভাঙেIdempotent রূপ
mkdir logsmkdir -p logs
echo "x=1" >> app.conf (লাইন জমতে থাকে)grep -qx 'x=1' app.conf || echo "x=1" >> app.conf
ln -s new currentln -sfn new current
useradd appid app >/dev/null 2>&1 || useradd app

এবার backup rotation-সহ একটি ছোট কাজ দুবার চালানো যাক। ফাইলের নাম db-2026-03-01.tar.gz ধরনের। এই ISO তারিখের নাম বর্ণানুক্রমে সাজালে সময়ের ক্রমও পাওয়া যায়, তাই glob-এর সাজানো তালিকাই পুরোনো থেকে নতুনের ক্রম। ফাইলগুলো খালি, শুধু নাম দিয়ে কৌশলটি দেখানো।

#!/usr/bin/env bash
# Idempotent setup + backup rotation. The second run must change nothing.
set -euo pipefail
shopt -s nullglob
T=$(mktemp -d)
trap 'rm -rf -- "$T"' EXIT
cd "$T"
mkdir backups
for d in 01 02 03 04 05; do : > "backups/db-2026-03-$d.tar.gz"; done

job() {
  local today="2026-03-06" keep=3 i
  mkdir -p backups logs
  touch app.conf
  if grep -qx 'retention=3' app.conf; then echo "conf: already set"
  else echo 'retention=3' >> app.conf; echo "conf: added retention=3"; fi

  if [ -e "backups/db-$today.tar.gz" ]; then echo "backup: $today exists"
  else : > "backups/db-$today.tar.gz"; echo "backup: created $today"; fi

  local files=(backups/db-*.tar.gz)          # sorted: oldest first
  local extra=$(( ${#files[@]} - keep ))
  for (( i = 0; i < extra; i++ )); do
    rm -- "${files[i]}"
    echo "rotate: removed ${files[i]#backups/}"
  done
  [ "$extra" -gt 0 ] || echo "rotate: nothing to remove"
}

echo "=== first run ===";  job
echo "=== second run ==="; job
echo "=== final state ==="
printf '%s\n' backups/*
echo "conf lines: $(wc -l < app.conf)"

প্রথম চালানোয় conf: added retention=3, backup: created 2026-03-06, তারপর সবচেয়ে পুরোনো তিনটি (01, 02, 03) মোছা। ছয়টি থেকে তিনটি থাকল। দ্বিতীয় চালানোয় conf: already set, backup: 2026-03-06 exists আর rotate: nothing to remove: কিছুই বদলায়নি। শেষে রইল 04, 05, 06, আর conf lines: 1. nullglob না দিলে ফাইল না থাকার সময় db-* লেখাটিই একটি “ফাইল” হিসেবে তালিকায় ঢুকত।

Rotation-এর আগে ভাবুন: নতুন backup যদি ব্যর্থ বা খালি হয়, পুরোনো মোছা কি ঠিক? নিয়ম করুন, নতুন backup-এর সফলতা (মাপ, checksum) যাচাইয়ের পরেই মোছা। আর backup-এর আসল পরীক্ষা হলো restore; বিস্তারিত পর্ব ২৪-এ।

Dry-run, log আর retry

Dry-run: --dry-run flag দিলে script শুধু বলে কী করত, করে না। ঝুঁকির কমান্ডগুলো একটি ছোট run function-এর ভেতর দিয়ে চালালেই এটি হয়ে যায় (পরের অংশের পুরো script দেখুন)। অনেক tool-এও আছে, যেমন rsync --dry-run.

Log: রাত ৩টার script-এর একমাত্র সাক্ষী তার log. প্রতিটি লাইনে UTC সময়, আর কী ঘটল সেটি লিখুন। logger -t backup "done" কমান্ড system log-এ লেখে। Cron-এ দুটি ধারাই ফাইলে নিতে চাইলে লিখুন >>job.log 2>&1. ক্রমটি জরুরি: আগে >>job.log, পরে 2>&1; উল্টো লিখলে error ফাইলে যাবে না। Log ফাইলও বাড়তে থাকে, তাই Linux-এর logrotate দিয়ে তারও মেয়াদ ঠিক করুন। অনেক server-এর log একসাথে খোঁজার কথা পর্ব ৬৪-এ।

Retry: Network ক্ষণিকের জন্য ব্যর্থ হতেই পারে। পর্ব ০৬-এর exponential backoff আর jitter এখানেও খাটে: অপেক্ষা দ্বিগুণ হতে থাকে, আর একটু এলোমেলো মেশানো হয়, যাতে অনেক server একসাথে ঝাঁপিয়ে না পড়ে (thundering herd).

# retry MAX_TRIES command args...
retry() {
  local max=$1 n=1 delay=2
  shift
  until "$@"; do
    if [ "$n" -ge "$max" ]; then
      echo "giving up after $n tries: $*" >&2
      return 1
    fi
    sleep $(( delay + RANDOM % 3 ))     # jitter: 0 to 2 extra seconds
    delay=$(( delay * 2 ))              # 2, 4, 8, 16 ...
    n=$(( n + 1 ))
  done
}

# -f makes curl fail on HTTP errors; without it a 500 page counts as success
retry 5 curl -fsS -o report.csv https://example.com/report.csv

৫ বার চেষ্টায় অপেক্ষা ৪ বার, jitter ছাড়া মোট ২ + ৪ + ৮ + ১৬ = ৩০ সেকেন্ড। মনে রাখবেন, যে কাজ idempotent নয়, তাকে retry করলে কাজটি দুবার হতে পারে। তাই retry আর idempotency একসাথে ভাবতে হয়।

৭ · Cron — পাঁচটি ঘর আর তার ফাঁদ

Cron হলো রেলের সময়সূচি। ঘড়ি মিললেই গাড়ি ছাড়ে, আগের গাড়ি পৌঁছাল কি না, যাত্রী উঠল কি না, সে দেখে না। আর গাড়ি ছাড়ার পরে কী হলো, স্টেশনের ঘড়ি তা জানে না।

Crontab-এর প্রতিটি লাইনে পাঁচটি সময়ের ঘর, তারপর কমান্ড। crontab -e দিয়ে নিজের তালিকা সম্পাদনা করা যায়, আর /etc/cron.d/-এর ফাইলে কমান্ডের আগে ব্যবহারকারীর নামও লিখতে হয়।

Crontab-এর পাঁচটি ঘর

minute · hour · day of month · month · day of week · command

Crontab লাইনের পাঁচটি সময়ের ঘর আর কমান্ড 30 2 * * 0-4 মিনিট ঘণ্টা তারিখ মাস বার 0–59 0–23 1–31 1–12 0–7 0, 7 = রবি /usr/local/bin/backup.sh অর্থ: রবি থেকে বৃহস্পতি, রাত ০২:৩০ (সার্ভারের ঘড়িতে, ঢাকার ঘড়িতে নাও হতে পারে) * সব মান 1,15 তালিকা 1-5 পরিসর */15 প্রতি ১৫ ধাপে
ছবি ১০.১ — বাঁ থেকে ডানে পাঁচটি সময়ের ঘর, তারপর কমান্ড। পাঁচটি শর্ত মিললে সেই মিনিটে কাজটি চলে।

একটি অদ্ভুত নিয়ম: “তারিখ” আর “বার”, দুটিই নির্দিষ্ট করলে শর্ত হয় অথবা, এবং নয়। 0 0 2 * 5 মানে “মাসের ২ তারিখে, অথবা যেকোনো শুক্রবারে”। নিচের simulator একটি কাল্পনিক ঘড়ি মিনিটে মিনিটে এগিয়ে এই নিয়মগুলো মিলিয়ে দেখে। Server ধরে নেওয়া হয়েছে UTC-তে।

// Next-run calculator for 5-field cron on a simulated clock. Edit and Run again.
const START = Date.UTC(2026, 1, 27, 23, 50);  // Fri 27 Feb 2026, 23:50 UTC
const JOBS = ["*/15 * * * *", "30 2 * * *", "0 9 * * 0-4", "0 0 1 * *", "0 0 2 * 5", "0 3 31 2 *"];
function parse(field, lo, hi) {               // "1-5", "*/15", "0,30"
  const set = new Set();
  for (const part of field.split(",")) {
    const [range, step = "1"] = part.split("/");
    const [a, b] = range === "*" ? [lo, hi] : range.split("-").map(Number);
    for (let v = a; v <= (b ?? a); v += Number(step)) set.add(v);
  }
  return set;
}
function matcher(expr) {
  const [mi, hr, dom, mon, dow] = expr.split(" ");
  const [m, h, d, o, w] = [parse(mi, 0, 59), parse(hr, 0, 23),
    parse(dom, 1, 31), parse(mon, 1, 12), parse(dow, 0, 7)];
  if (w.has(7)) w.add(0);                     // 0 and 7 both mean Sunday
  const anyDay = dom[0] === "*" || dow[0] === "*";
  return t => {
    const a = d.has(t.getUTCDate()), b = w.has(t.getUTCDay());
    return m.has(t.getUTCMinutes()) && h.has(t.getUTCHours()) &&
      o.has(t.getUTCMonth() + 1) && (anyDay ? a && b : a || b);
  };
}
const show = t => t.toUTCString().slice(0, 3) + " " + t.toISOString().slice(0, 16).replace("T", " ");
const dhaka = t => new Date(t.getTime() + 6 * 3600e3).toISOString().slice(11, 16);
for (const expr of JOBS) {
  const hit = matcher(expr), runs = [];
  for (let i = 1; i <= 5 * 366 * 1440 && runs.length < 2; i++) {  // max 5 years
    const t = new Date(START + i * 60000);
    if (hit(t)) runs.push(show(t) + " UTC = " + dhaka(t) + " Dhaka");
  }
  console.log(expr.padEnd(14), runs.length ? "" : "never in 5 years");
  runs.forEach(r => console.log("   " + r));
}

ঘড়ি শুরু শুক্রবার ২৩:৫০ UTC থেকে। */15-এর পরের চলা 00:00, কারণ ১৫-এর গুণিতক গোনা হয় ঘণ্টার শুরু থেকে, কাজ চালু করার সময় থেকে নয়। 30 2 * * *-এর ফল Sat 2026-02-28 02:30 UTC = 08:30 Dhaka: “রাত আড়াইটার” কাজ আসলে ঢাকার সকালে চলছে। 0 0 2 * 5 চলে Mon 2026-03-02-এ (২ তারিখ, যদিও সোমবার), তারপর Fri 2026-03-06-এ (শুক্রবার, যদিও ৬ তারিখ); এটিই “অথবা” নিয়ম। শেষ লাইনটি সবচেয়ে শিক্ষণীয়: 0 3 31 2 * মানে ৩১ ফেব্রুয়ারি, তাই ফল never in 5 years. Cron এমন লাইনে কোনো error দেয় না, শুধু চুপচাপ কখনো চালায় না। এই simulator শুধু সংখ্যার ঘর বোঝে; MON, JAN ধরনের নাম বা @daily ধরনের সংক্ষেপ এতে নেই, যদিও আসল cron-এ সেগুলো চলে।

Cron-এর ফাঁদগুলো

ফাঁদকী ঘটেউপায়
ছোট environmentTerminal-এ চলে, cron-এ চলে না। Cron .bashrc পড়ে না, PATH সাধারণত শুধু /usr/bin:/bin, আর environment variable প্রায় নেই।কমান্ডের পুরো path, অথবা crontab-এর শুরুতে PATH=...; secret ফাইল থেকে পড়া (পর্ব ৩৩)
শুরুর ফোল্ডারকাজটি চলে home থেকে, script-এর ফোল্ডার থেকে নয়পুরো path, অথবা cd /srv/app || exit 1
% চিহ্নCrontab-এর লাইনে % মানে নতুন লাইন, তাই date +%F ভেঙে যায়\% লেখা, অথবা কমান্ডটি script-এ রাখা
Time zoneCron চলে server-এর ঘড়িতে। Cloud VM-এ অনেক সময় তা UTCUTC-তেই ভাবা, আর ঢাকার সময় থেকে ৬ ঘণ্টা বিয়োগ
Overlapআগের চলা শেষ না হলেও cron পরেরটি চালায়flock (পরের অংশে)
নীরব ব্যর্থতাআউটপুট mail হয়, mail ব্যবস্থা না থাকলে হারিয়ে যায়। কেউ জানেই না।Log ফাইল, আর dead man's switch
মেশিন বন্ধসেই মিনিটে মেশিন বন্ধ থাকলে কাজটি বাদ পড়েanacron, অথবা systemd-এর Persistent=true

একটি বাস্তবসম্মত crontab এমন দেখায়। প্রথম সারির variable-গুলো নিচের সব লাইনে খাটে।

PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=ops@example.com

# 20:30 UTC = 02:30 Dhaka. Output goes to a log, errors stay visible to MAILTO.
30 20 * * * /usr/local/bin/nightly-backup.sh /srv/backups >>/var/log/backup.log

Time zone-এর একটি গল্প। ধরুন দল ঠিক করল, ব্যবহারকারী যখন ঘুমিয়ে, তখন ভারী backup চলবে। কেউ লিখলেন 30 2 * * *. Server UTC-তে, তাই কাজটি চলল ঢাকার সকাল ০৮:৩০-এ, ঠিক অফিস শুরুর সময়। অ্যাপ ধীর হলো, আর কেউ বুঝলেন না কেন। সমাধান সহজ: timedatectl দিয়ে server-এর ঘড়ি দেখা, আর crontab-এ মন্তব্যে দুই সময়ই লেখা। বাংলাদেশে এখন daylight saving নেই। কিন্তু যেসব দেশে আছে, সেখানে ঘড়ি বদলের রাতে স্থানীয় সময়ের কাজ বাদ পড়তে বা দুবার চলতে পারে। Server UTC-তে রাখলে এই ঝামেলাও থাকে না।

কোন ব্যবহারকারী হিসেবে চালাবেন? root-এর crontab সবচেয়ে সহজ, কিন্তু সেখানে ভুল হলে ক্ষতির সীমা থাকে না। কাজটির জন্য আলাদা সীমিত ব্যবহারকারী বানান, যে শুধু দরকারি ফোল্ডার ছুঁতে পারে। Password crontab-এর লাইনে লিখবেন না; chmod 600 করা ফাইলে বা secret manager-এ রাখুন।

নীরব ব্যর্থতা ধরার সবচেয়ে ভালো উপায় হলো উল্টো করে ভাবা: কাজ ব্যর্থ হলে খবর দেওয়া নয়, কাজ সফল হলে খবর দেওয়া। Script-এর শেষে একটি monitoring ঠিকানায় সংকেত যায়। নির্দিষ্ট সময়ে সংকেত না এলেই alert বাজে। এতে script না চললেও ধরা পড়ে, যা script-এর ভেতরের কোনো error handling পারে না। Alert নিয়ে বিস্তারিত পর্ব ৬৬-তে।

৮ · Overlap আর lock — দুটি চলা একসাথে নয়

একটি কাল্পনিক SMS কাজ প্রতি ৫ মিনিটে চলে (*/5 * * * *). এটি last_id ফাইল পড়ে, নতুন বিলের SMS পাঠায়, শেষে last_id লেখে। সাধারণত ১ মিনিটে শেষ হয়। একদিন SMS gateway ধীর হলো, আর কাজটি ৭ মিনিট নিল। ফলে তৈরি হলো একটি race condition. ধরে নিই ৫০১–৫২০ নম্বর বিল বাকি, আর ০২:০৩-এ আরও ৩টি (৫২১–৫২৩) আসে।

ঘড়ির সাথে অবস্থা
পদ্ধতি
ঘড়ি
চলছে
lock-এর মালিক
last_id ফাইল
মোট SMS গেছে
দুবার পেয়েছেন
  1. ০২:০০, lock ছাড়া। Run A শুরু, last_id 500, বাকি ২০টি। Gateway ধীর।
  2. ০২:০৫, দ্বিতীয় চলা। Cron জানে না A এখনো চলছে। Run B শুরু। A এখনো ফাইল লেখেনি, তাই B-ও পড়ে 500, আর ধরে ৫০১–৫২৩, মোট ২৩টি।
  3. ০২:০৭, A শেষ। ২০টি SMS গেল, last_id = 520.
  4. ০২:০৮, B শেষ। Gateway তখন স্বাভাবিক। B ২৩টি পাঠাল, যার ২০টি আগেই গেছে। মোট ৪৩ SMS, ২০ জন গ্রাহক দুবার পেলেন, আর টাকাও দুবার গেল।
  5. ০২:০০, এবার flock -n. একই পরিস্থিতি। A lock পেল, তারপর কাজ শুরু।
  6. ০২:০৫, B আটকে গেল। Lock ব্যস্ত, তাই B অপেক্ষা না করে সঙ্গে সঙ্গে বেরিয়ে গেল। এটি ব্যর্থতা নয়, শুধু log-এ “skipped”.
  7. ০২:০৭, A শেষ। ২০টি SMS, last_id = 520. Process শেষ হতেই kernel lock ছেড়ে দিল।
  8. ০২:১০, C চলল। Lock পেল, last_id 520 থেকে বাকি ৩টি পাঠাল। মোট ২৩ SMS, কেউ দুবার পাননি।

গুদামঘরের একটিই চাবি। যিনি চাবি নিয়েছেন, তিনি ভেতরে; পরের জন দরজায় চাবি না পেলে ফিরে যান। আর flock-এর বিশেষ সুবিধা হলো, ভেতরের মানুষ হঠাৎ চলে গেলেও চাবি আপনা-আপনি দরজায় ফিরে আসে।

touch job.lock ধরনের পুরোনো lock file-এর সমস্যা হলো, script ক্র্যাশ করলে ফাইলটি থেকে যায়, আর পরের সব চলা চিরকাল আটকে থাকে। আবার “ফাইল আছে কি” দেখা আর বানানোর মাঝেও ফাঁক থাকে। flock kernel-এর lock নেয়, যা file descriptor বন্ধ হলেই ছাড়ে, তাই process মরে গেলেও আটকায় না। Crontab-এ এক লাইনেই লেখা যায়, আবার script-এর ভেতরেও বসানো যায়।

# In a crontab line: skip this run if the previous one holds the lock
*/5 * * * * flock -n /run/lock/sms.lock /usr/local/bin/send-sms.sh

# Inside a script: fd 9 stays open until the script ends
exec 9>/run/lock/sms.lock
flock -n 9 || { echo "previous run still active, skipping" >&2; exit 0; }

-n মানে অপেক্ষা নয়; -w 30 দিলে ৩০ সেকেন্ড অপেক্ষা করে। Crontab-এর এক লাইনের রূপে lock না পেলে exit code হয় 1; -E 0 দিলে 0 হয়। মনে রাখবেন, lock শুধু একটি মেশিনের ভেতরে কাজ করে। একই cron দুটি server-এ চললে এই lock কিছুই থামাবে না। সেই সমস্যা নিচের ভাবনার প্রশ্নে আছে।

সব একসাথে: একটি পূর্ণ script

নিচের উদাহরণে এই পর্বের সব অভ্যাস এক জায়গায় আছে। Argument যাচাই, lock, অস্থায়ী ফোল্ডার, dry-run, log আর rotation. shop একটি কাল্পনিক database.

#!/usr/bin/env bash
# nightly-backup.sh — usage: nightly-backup.sh [--dry-run] DEST_DIR
set -euo pipefail

readonly KEEP=7
DRY_RUN=0

log() { printf '%s %s\n' "$(date -u +%FT%TZ)" "$*" >&2; }
die() { log "ERROR: $*"; exit 1; }
run() { if (( DRY_RUN )); then log "DRY-RUN: $*"; else "$@"; fi; }

if [[ ${1:-} == --dry-run ]]; then DRY_RUN=1; shift; fi
DEST=${1:?usage: nightly-backup.sh [--dry-run] DEST_DIR}
[[ -d $DEST ]] || die "no such directory: $DEST"

exec 9>/run/lock/nightly-backup.lock
flock -n 9 || { log "previous run still active, skipping"; exit 0; }

WORK=$(mktemp -d)
trap 'rm -rf -- "$WORK"' EXIT

FILE="db-$(date -u +%F).sql.gz"
pg_dump shop | gzip > "$WORK/$FILE"
run mv -- "$WORK/$FILE" "$DEST/"

shopt -s nullglob
backups=("$DEST"/db-*.sql.gz)
for (( i = 0; i < ${#backups[@]} - KEEP; i++ )); do
  run rm -- "${backups[i]}"
done
log "finished: $FILE"

৯ · systemd timer বনাম cron

আধুনিক Linux-এ systemd সব service চালায়, আর তার systemd timer cron-এর একটি শক্তিশালী বিকল্প। এখানে দুটি ফাইল লাগে: .service বলে কী চালাতে হবে, .timer বলে কখন। Service unit-এর ভেতরের কথা পর্ব ১১-তে।

# /etc/systemd/system/backup.service
[Unit]
Description=Nightly database backup

[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/nightly-backup.sh /srv/backups
# /etc/systemd/system/backup.timer
[Unit]
Description=Run backup every night

[Timer]
OnCalendar=*-*-* 02:30:00 Asia/Dhaka
Persistent=true
RandomizedDelaySec=5min

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer        # next and last run
systemd-analyze calendar "*-*-* 02:30:00 Asia/Dhaka"   # check the expression
journalctl -u backup.service -n 50        # logs of the last runs
sudo systemctl start backup.service       # run it once now, by hand
বিষয়Cronsystemd timer
সহজতাএক লাইনদুটি ফাইল
Logনিজে ব্যবস্থা করতে হয়journal-এ আপনা-আপনি
বাদ পড়া কাজবাদই থাকে (anacron আলাদা)Persistent=true দিলে মেশিন চালু হলে চলে
Overlapনিজে flock দিতে হয়Service চলন্ত থাকলে timer নতুন করে শুরু করে না
Time zoneসাধারণত server-এর (কিছু রূপে CRON_TZ)Expression-এ লেখা যায়, যেমন Asia/Dhaka
Resource সীমানেইMemoryMax= ইত্যাদি service-এ
একসাথে শুরু ছড়ানোনেইRandomizedDelaySec=
কোথায় পাওয়া যায়প্রায় সব Unix-এশুধু systemd-চালিত Linux-এ; সাধারণ container-এ নয়

সিদ্ধান্তের সহজ নিয়ম: ছোট, অল্প গুরুত্বের কাজে cron যথেষ্ট। Backup-এর মতো গুরুত্বপূর্ণ কাজে timer ভালো। Cloud-এ তৃতীয় পথও আছে: provider-এর managed scheduler (যেমন Amazon EventBridge Scheduler, Azure Logic Apps-এর recurrence, Google Cloud Scheduler), যা কোনো একটি server-এর ওপর নির্ভর করে না। Kubernetes-এও CronJob আছে।

আরও গভীরে: set -e-এর ব্যতিক্রমগুলো খামখেয়াল নয়, POSIX shell মানে লেখা আছে: if-এর শর্ত, && বা ||-এর শেষ অংশ বাদে বাকি অংশ, আর !-এর পরের কমান্ডে ব্যর্থতা উপেক্ষিত। Vixie cron ঘরানায় “তারিখ বা বার” নিয়মটি নির্ভর করে ঘরটি * দিয়ে শুরু কি না তার ওপর, তাই */2-ও “যেকোনো” ধরা হয়। flock advisory lock: যে প্রোগ্রাম lock না দেখে ফাইল ছোঁয়, kernel তাকে আটকায় না, আর NFS-এ আচরণ version-ভেদে আলাদা। systemd timer দুই রকম: OnCalendar= ঘড়ি ধরে, আর OnBootSec= বা OnUnitActiveSec= ধরে শেষ ঘটনার পর থেকে সময়।

পরীক্ষা ও ইন্টারভিউতে সাধারণত যে ধরনের প্রশ্ন আসে:

  • set -euo pipefail কী করে? ব্যর্থ কমান্ডে থামে, অজানা variable-এ থামে, আর pipeline-এর মাঝের ব্যর্থতা ধরে।
  • Terminal-এ চলে, cron-এ চলে না কেন? সাধারণত ছোট PATH আর environment, অথবা শুরুর ফোল্ডার ভিন্ন।
  • Exit code 137 মানে? ১২৮ + ৯, অর্থাৎ SIGKILL, প্রায়ই memory ফুরিয়ে OOM killer-এর হাতে।
  • Idempotent script কী? বারবার চালালেও ফল একই; যেমন mkdir -p.
  • একই cron কাজ overlap হওয়া কীভাবে ঠেকাবেন? flock -n, অথবা systemd timer; একাধিক server হলে কেন্দ্রীয় scheduler বা distributed lock.

১০ · AI-এর যুগে — AI-এর লেখা script চালানোর আগে

AI কয়েক সেকেন্ডে script লিখে দেয়, আর সেটি দেখতে গোছানো। কিন্তু script-এর ভুল প্রায়ই খালি variable, ব্যর্থতা বা দ্বিতীয়বার চালানোর সময় ধরা পড়ে, যা সাধারণ পরীক্ষায় আসে না। তাই একে অপরিচিত কারও লেখা কোডের মতো পড়ুন, বিশেষ করে সেটি rm, sudo বা cloud CLI ছুঁলে।

AI-এর সাধারণ ভুলযাচাই
Quote ছাড়া variable, পাহারা ছাড়া rm -rf, কোনো সতর্কতা নেইShellCheck চালানো, ${DIR:?} বসানো, throwaway VM-এ পরীক্ষা
set -e ছাড়া, অথবা || true দিয়ে ব্যর্থতা ঢাকাইচ্ছা করে একটি ধাপ ব্যর্থ করে exit code দেখা
Script-এ বসানো password, key বা tokenSecret manager (পর্ব ৩৩); prompt-এও secret নয়
Cron লাইনে শুধু কমান্ডের নাম, time zone-এর কথা নেইServer-এর time zone দেখা, পুরো path দেওয়া, ছোট environment-এ পরীক্ষা
Lock, log বা ব্যর্থতার খবর নেইflock, log আর dead man's switch যোগ করা
নেই এমন flag; 0.0.0.0/0 খোলা, * অনুমতি, public storage, latest tagman আর অফিশিয়াল docs মেলানো; least privilege; নির্দিষ্ট version
terraform destroy, kubectl delete ধরনের কমান্ড, খরচের কথা ছাড়া--dry-run বা terraform plan, diff পড়া, budget alert

নিচের prompt AI-কে ব্যর্থতার পথ নিয়েও ভাবতে বাধ্য করে। পাঠানোর আগে আসল server-এর নাম, IP আর secret মুছে দিন।

তুমি একজন অভিজ্ঞ SRE. নিচের কাজের জন্য একটি Bash script লেখো, Ubuntu LTS-এর জন্য: [এখানে কাজটি লিখুন; কোনো secret, আসল hostname বা গ্রাহকের তথ্য দেবে না] শর্ত: ১. set -euo pipefail, সব variable quote করা, আর ShellCheck-এ পরিষ্কার থাকবে। ২. মোছার প্রতিটি কমান্ডে ${VAR:?} পাহারা, আর --dry-run flag. ৩. mktemp আর trap দিয়ে clean-up; flock দিয়ে overlap বন্ধ। ৪. দুবার চালালে ফল একই থাকবে (idempotent). ৫. প্রতিটি ব্যর্থতার পথ আলাদা করে তালিকা করো, আর কোন exit code আসবে বলো। ৬. Cron বা systemd timer-এর লাইন দাও, time zone উল্লেখ করে। ৭. কোনো flag নিয়ে নিশ্চিত না হলে "নিশ্চিত নই" লেখো, আর man page-এ কী দেখতে হবে বলো। ৮. কোনো মুছে ফেলার বা খরচ বাড়ানোর কমান্ড থাকলে আগে সতর্ক করো।

AI-এর script চালানোর আগে

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন। এগুলোর একক সঠিক উত্তর নেই; যুক্তিটিই আসল।

প্র ০১set -e এত ফাঁকফোকরে ভরা কেন? Bash কি এটি ঠিক করে দিতে পারত না?

এই আচরণের মূল শিকড় পুরোনো। Shell প্রথমে বানানো হয়েছিল মানুষের সরাসরি ব্যবহারের জন্য, আর Stephen Bourne-এর shell (১৯৭০-এর দশকের শেষ দিকের Unix) থেকেই আজকের ভাষার অনেক রীতি এসেছে। সেখানে কমান্ডের ব্যর্থতা কোনো “exception” নয়, একটি সাধারণ মান। grep-এর 1 মানে “মিল নেই”, যা অনেক সময় একেবারে ঠিক ফল। তাই ভাষাটির কাছে ব্যর্থতা আর “না” উত্তরের মধ্যে কোনো পার্থক্য নেই।

-e এই নকশার ওপর বসানো একটি চাদর। if grep ... লিখলে শর্তের ব্যর্থতায় script থামলে if অর্থহীন হয়ে যেত, তাই ব্যতিক্রম রাখতেই হলো। সমস্যা হলো, এই ব্যতিক্রম গভীরেও ছড়ায়: if-এর ভেতরে ডাকা function-এর সব লাইনেও -e নিষ্ক্রিয়। POSIX মানে এই আচরণ লিখিত হয়ে গেছে, আর পুরোনো অসংখ্য script এর ওপর নির্ভর করে। তাই Bash বদলাতে গেলে কোটি লাইনের কোড ভাঙার ঝুঁকি। Bash বরং নতুন option যোগ করেছে, যেমন inherit_errexit.

এ কারণে অভিজ্ঞ মহলেও মত আলাদা। Greg Wooledge-এর পরিচিত Bash wiki-র FAQ ১০৫ বলে, set -e-এর ওপর ভরসা না করে নিজে ব্যর্থতা দেখা ভালো। অনেক দল আবার একে জরুরি মনে করে, কারণ অসম্পূর্ণ জাল জাল না থাকার চেয়ে ভালো। বাস্তবসম্মত পথ হলো দুটিই: set -euo pipefail রাখা, আর গুরুত্বপূর্ণ কমান্ডে স্পষ্ট পরীক্ষা (|| die "...") লেখা।

একটি ছোট পরীক্ষা করে দেখতে পারেন। একটি function-এর ভেতরে ব্যর্থ কমান্ডের পরে একটি echo রাখুন। সরাসরি ডাকলে script থামবে, কিন্তু if-এর শর্তে ডাকলে echo-ও চলবে। একই কোড, ডাকার জায়গা বদলেই আচরণ বদলে গেল। এই কারণেই বড় script-এ set -e-এর ওপর চোখ বুজে ভরসা করা কঠিন।

মূল উপলব্ধি: ফাঁকগুলো ভুল নয়, পিছনের দিকের সামঞ্জস্যের দাম। যে ভাষায় error handling মূল নকশায় নেই, সেখানে চাদর দিয়ে পুরোটা ঢাকা যায় না। জটিলতা বাড়লে সেটিই Python-এ যাওয়ার সংকেত।

প্র ০২একটি খালি variable কীভাবে মানুষের পুরো ডেটা মুছে দিতে পারে? এখান থেকে ব্যবস্থার শিক্ষা কী?

সবচেয়ে পরিচিত প্রকাশ্য উদাহরণগুলোর একটি ২০১৫ সালের। Linux-এ Steam-এর একটি script-এ rm -rf "$STEAMROOT/"* ধরনের লাইন ছিল। প্রকাশিত bug report অনুযায়ী, Steam-এর ফোল্ডার সরানোর পরে variable-টি খালি হয়ে যেত, আর কমান্ডটি হয়ে যেত rm -rf "/"*. কিছু ব্যবহারকারী তাঁদের home-এর ফাইল হারান, এমনকি লেখার অনুমতি থাকা মাউন্ট করা ড্রাইভের ফাইলও। লেখক কোডের পাশে একটি মন্তব্যে আশঙ্কার কথাও লিখেছিলেন বলে আলোচনায় এসেছিল।

লক্ষ করুন, এখানে quote ঠিকই ছিল। অর্থাৎ শুধু “quote করুন” শিখলেই এই ভুল ঠেকানো যেত না। সমস্যা ছিল খালি মান সামলানো। GNU rm বহু বছর ধরে আক্ষরিক / মুছতে অস্বীকার করে, কিন্তু * shell-ই ভেঙে দেয়, তাই rm দেখে শুধু /bin, /home ধরনের সাধারণ তালিকা। কোনো একক স্তরের সুরক্ষা সব পথ বন্ধ করে না। বিশেষ করে যে script ব্যবহারকারীর কম্পিউটারে চলে, তার পরিবেশ লেখকের হাতে থাকে না, তাই “এটি তো খালি হতেই পারে না” ধরে নেওয়া সবচেয়ে বিপজ্জনক অনুমান।

ব্যবস্থার শিক্ষা কয়েকটি। প্রথমত, মুছে ফেলার মতো অপরিবর্তনীয় কাজে পাহারা দ্বিগুণ করুন: ${VAR:?}, পথটি প্রত্যাশিত উপসর্গ দিয়ে শুরু কি না তার পরীক্ষা, আর মোছার আগে log. দ্বিতীয়ত, blast radius ছোট রাখুন: script-কে আলাদা সীমিত ব্যবহারকারী হিসেবে চালান, root হিসেবে নয়, যাতে ভুল হলেও সে শুধু নিজের ফোল্ডার ছুঁতে পারে। systemd-এ User= আর ProtectHome= এই কাজে লাগে। তৃতীয়ত, ধরে নিন ভুল হবেই, তাই পরীক্ষিত backup রাখুন। চতুর্থত, এই ধরনের ঘটনায় লেখককে দোষ না দিয়ে পর্ব ০৭-এর blameless দৃষ্টিতে ব্যবস্থার ফাঁক খুঁজুন: review, ShellCheck, পরীক্ষার পরিবেশ।

মূল উপলব্ধি: নিরাপত্তা একটি চতুর লাইনে নয়, স্তরে স্তরে আসে। কম অনুমতি, পাহারা, dry-run আর backup একসাথে থাকলে একটি খালি variable বিপর্যয় না হয়ে একটি log-এর লাইন হয়ে যায়।

প্র ০৩Cron পঞ্চাশ বছরের পুরোনো। systemd timer এত সুবিধা দেওয়ার পরেও cron মরেনি কেন?

Cron-এর জন্ম ১৯৭০-এর দশকের Unix-এ, যখন একটি মেশিনে অনেক ব্যবহারকারী কাজ করতেন। ১৯৮৭ সালের দিকে Paul Vixie-র পুনর্লেখা সংস্করণ ব্যাপকভাবে ছড়ায়, আর আজকের Debian-এর cron ও Red Hat পরিবারের cronie মূলত সেই ঘরানার উত্তরাধিকার। পাঁচ ঘরের ভাষাটি এত দিনে একটি সর্বজনীন রীতি হয়ে গেছে। Kubernetes CronJob, CI system-এর schedule আর অনেক cloud scheduler একই ধরনের expression বোঝে, যদিও খুঁটিনাটিতে পার্থক্য থাকে (কিছু জায়গায় বছর বা সেকেন্ডের বাড়তি ঘর আছে)।

cron-এর শক্তি তার সরলতায়। এক লাইনে একটি কাজ, কোনো unit ফাইল নয়, daemon-reload নয়। প্রায় সব Unix-এ পাওয়া যায়। ছোট VPS-এ একটি লগ পরিষ্কার করার জন্য দুটি ফাইল লেখা অনেকের কাছে বাড়তি। আর পুরোনো server, script আর মানুষের অভ্যাসের বিশাল জড়তাও আছে।

অন্যদিকে systemd timer এসেছে সেই সমস্যাগুলো মেটাতে, যেগুলো cron-এর নকশায় ছিল না: log, নির্ভরতা, resource সীমা, মেশিন বন্ধ থাকলে বাদ পড়া কাজ। তবে এতে দাম আছে: systemd ছাড়া সিস্টেমে এটি নেই, আর সাধারণ container-এ systemd চলে না। Container-এর দুনিয়ায় সিদ্ধান্তটি প্রায়ই অন্য জায়গায় সরে যায়: orchestrator বা cloud scheduler-এ।

ইঞ্জিনিয়ারিংয়ে এটি একটি বড় নিয়মের উদাহরণ। “যথেষ্ট ভালো” আর সর্বত্র উপস্থিত প্রযুক্তি সহজে মরে না, কারণ বদলের খরচ তার ক্ষতির চেয়ে বেশি। তাই আসল প্রশ্নটি “কোনটি আধুনিক” নয়, “এই কাজ ব্যর্থ হলে কত ক্ষতি, আর সেই ব্যর্থতা কত দ্রুত জানব”। গ্রাহকের backup হলে timer আর monitoring; অস্থায়ী ফোল্ডার মোছার জন্য cron-ই যথেষ্ট।

মূল উপলব্ধি: হাতিয়ার বাছাই করুন কাজের ঝুঁকি দেখে, ফ্যাশন দেখে নয়। Cron আর timer প্রতিদ্বন্দ্বী নয়, দুটি আলাদা ঝুঁকির স্তরের উত্তর।

প্র ০৪GitLab-এর ২০১৭ সালের ঘটনা থেকে cron-এ চলা backup নিয়ে কী শেখার আছে?

২০১৭ সালের ৩১ জানুয়ারি GitLab.com-এর একটি বড় database incident হয়, আর প্রতিষ্ঠানটি খোলাখুলি তার postmortem প্রকাশ করে। প্রকাশিত বিবরণ অনুযায়ী, replication সমস্যা সামলানোর সময় একজন প্রকৌশলী ক্লান্ত অবস্থায় secondary server ভেবে primary server-এর data directory মুছে ফেলেন। ব্যক্তির ভুল এখানে শুরু মাত্র; শিক্ষাটি আসে এর পরে।

দল যখন backup খুঁজতে গেল, তখন দেখা গেল নিয়মিত pg_dump backup কাজ করছিল না। প্রকাশিত postmortem বলে, ব্যবহৃত pg_dump-এর version আর database-এর version মেলেনি, তাই ব্যর্থ হচ্ছিল। ব্যর্থতার খবর cron-এর email-এ আসার কথা ছিল, কিন্তু সেই email-গুলো গ্রহণকারী প্রান্তে DMARC-এর কারণে প্রত্যাখ্যাত হচ্ছিল, আর কেউ তা জানতেন না। অর্থাৎ script ব্যর্থ, আর ব্যর্থতার বার্তাও ব্যর্থ। শেষ পর্যন্ত একটি অন্য কাজের জন্য নেওয়া disk snapshot থেকে পুনরুদ্ধার হয়, আর প্রায় ছয় ঘণ্টার ডেটা হারায়।

এই পর্বের কয়েকটি বিষয় এখানে একসাথে দেখা যায়। নীরব ব্যর্থতা: “error হলে জানানো” নকশা নিজেই ব্যর্থ হতে পারে। তাই dead man's switch দরকার, যেখানে সফলতার সংকেত না এলে alert বাজে। Version-এর অমিল: script যে পরিবেশ ধরে নিয়েছে, তা বদলে গেছে, অথচ exit code আর log কেউ দেখছিল না। Backup-এর অর্থ: ফাইল থাকা মানেই backup নয়; নিয়মিত restore করে দেখা না হলে সেটি শুধু একটি আশা। আর ধ্বংসাত্মক কমান্ডে কোন মেশিনে আছি, তা স্পষ্ট দেখানো (যেমন prompt-এর রং) সহজ একটি সুরক্ষা। ঘটনার পরে সবচেয়ে প্রশংসিত দিকটি ছিল খোলাখুলি প্রকাশ: প্রকাশিত বিবরণে ব্যক্তিকে দোষ না দিয়ে ব্যবস্থার ফাঁকগুলো তালিকা করা হয়েছিল, যা পর্ব ০৭-এর blameless সংস্কৃতির একটি পরিচিত উদাহরণ।

মূল উপলব্ধি: একটি backup script তখনই সফল, যখন নিয়মিত restore প্রমাণ করে সে কাজ করছে, আর তার নীরবতা কেউ টের পায়। পর্ব ২৪-এ এই পরীক্ষার পদ্ধতি দেখবেন।

প্র ০৫একই cron কাজ ১০টি server-এ চললে কী হবে? flock কেন যথেষ্ট নয়, আর এর সমাধান কী?

পর্ব ১৭-এর autoscaling-এ একই image থেকে অনেক server তৈরি হয়। image-এর ভেতরে crontab থাকলে প্রতিটি server রাত ২টায় একই কাজ চালাবে। মাসিক বিল পাঠানোর কাজ হলে গ্রাহক ১০টি বিল পাবেন। flock এখানে অসহায়, কারণ তার lock থাকে একটি মেশিনের kernel-এ; অন্য মেশিন তার খবরই জানে না। আবার scale-in হলে যে server-এ কাজটি চলছিল, সেটিই মাঝপথে বন্ধ হয়ে যেতে পারে।

সমাধানের কয়েকটি পথ আছে, আর প্রতিটির দাম আছে। এক, একটি নির্দিষ্ট “scheduler server”: সহজ, কিন্তু সেটি নষ্ট হলে সব কাজ বন্ধ, অর্থাৎ single point of failure. দুই, কেন্দ্রীয় lock: database-এর advisory lock বা অন্য কোনো সমন্বয় ব্যবস্থায় lock নেওয়া। এতে lease-এর মেয়াদ ঠিক করা কঠিন: কাজ ধীর হলে মেয়াদ শেষ হয়ে দ্বিতীয়জন ঢুকে পড়তে পারে। তিন, managed scheduler বা Kubernetes CronJob (concurrencyPolicy: Forbid দিয়ে), যেখানে সময়ের দায়িত্ব server-এর বাইরে। চার, scheduler শুধু একটি বার্তা queue-তে দেয়, আর worker-রা তা তুলে নেয়।

কিন্তু একটি কঠিন সত্য বাকি থাকে। Distributed ব্যবস্থায় এই scheduler-গুলো সাধারণত “অন্তত একবার” চালানোর চেষ্টা করে, “ঠিক একবার” নিশ্চয়তা দেয় না। Network বিচ্ছিন্ন হলে দুজনই ভাবতে পারে কাজটি তার। তাই শেষ রক্ষা lock নয়, কাজটির নিজের idempotency: বিলের টেবিলে “এই মাসের বিল পাঠানো হয়েছে” চিহ্ন, unique key বা পর্ব ০৬-এর idempotency token. তাহলে কাজ দুবার চললেও ফল একবারের সমান।

এই চিন্তাটি পরে পর্ব ৭০-এর queue আর পর্ব ৭১-এর serverless নকশায় বারবার ফিরবে।

মূল উপলব্ধি: Lock চলার সংখ্যা কমায়, কিন্তু নিরাপত্তা দেয় idempotency. একাধিক মেশিনে সময়সূচি মানেই ধরে নেওয়া, কাজ দুবার চলতে পারে, আর সেভাবেই কাজটিকে নকশা করা।

অনুশীলন

এগুলোর জন্য কোনো cloud account লাগবে না। নিজের কম্পিউটারে চালাতে চাইলে শুধু ফাঁকা পরীক্ষার ফোল্ডারে চালান।

  1. Cron আর হিসাব: Server UTC-তে চলে। (ক) প্রতিদিন ঢাকার সময় রাত ০২:৩০-এ চলবে এমন লাইন লিখুন। (খ) রবি থেকে বৃহস্পতি ঢাকার সকাল ৯টায়। (গ) 0 */6 * * * দিনে কতবার চলে, আর কখন? (ঘ) ৩ GB-র দৈনিক backup-এর শেষ ১৪টি রাখা হয়, আর নতুনটি নেওয়ার পরে পুরোনো মোছা হয়। Disk-এ সর্বোচ্চ কত জায়গা লাগবে?

    (ক) ০২:৩০ − ৬ ঘণ্টা = আগের দিনের ২০:৩০ UTC, তাই 30 20 * * *. প্রতিদিনের কাজ বলে দিন বদলানো সমস্যা নয়; নির্দিষ্ট বারের কাজ হলে বারটিও এক দিন পিছিয়ে যেত।

    (খ) ০৯:০০ − ৬ = ০৩:০০ UTC, একই দিনে। তাই 0 3 * * 0-4.

    (গ) ৪ বার: ০০:০০, ০৬:০০, ১২:০০ আর ১৮:০০ (server-এর ঘড়িতে)।

    (ঘ) মোছার ঠিক আগে ১৫টি থাকে: ১৫ × ৩ = ৪৫ GB, ৪২ GB নয়। Compress বা dump-এর অস্থায়ী জায়গা হিসাবে ধরলে আরও বেশি।

  2. নকশা ও সমালোচনা: একজন সহকর্মীর crontab লাইন: 0 * * * * cd /opt/app; ./sync.sh; rm -rf $TMPDIR/*. Script-টি মাঝে মাঝে ৯০ মিনিট চলে। অন্তত পাঁচটি সমস্যা ধরুন, আর একটি ভালো রূপ প্রস্তাব করুন।

    ১. cd ব্যর্থ হলেও ;-এর কারণে পরের কমান্ড চলে; && দরকার। ২. $TMPDIR cron-এর environment-এ সম্ভবত নেই। তখন /* হয়ে যায়, যা সবচেয়ে বিপজ্জনক অংশ। ৩. Quote আর ${...:?} পাহারা নেই। ৪. প্রতি ঘণ্টায় চলে অথচ ৯০ মিনিট লাগতে পারে, তাই overlap হবে; lock নেই। ৫. Log নেই, ফলে ব্যর্থতা নীরব। ৬. ./sync.sh-এর সফলতা না দেখেই মোছা হচ্ছে।

    ভালো রূপ: 0 * * * * flock -n /run/lock/sync.lock /usr/local/bin/sync-job.sh >>/var/log/sync.log 2>&1. আর sync-job.sh-এর ভেতরে set -euo pipefail, mktemp -d, trap, আর শেষে সফলতার সংকেত। গুরুত্বপূর্ণ কাজ হলে systemd timer আরও ভালো।

  3. আউটপুট অনুমান করুন: নিচের script চালালে কী কী লাইন ছাপা হবে, আর exit code কত হবে? আগে নিজে ভাবুন, তারপর উত্তর দেখুন।
    #!/usr/bin/env bash
    set -euo pipefail
    count=0
    for f in a b; do
      count=$((count + 1))
    done
    out=$(echo hello | tr a-z A-Z)
    echo "$out $count"
    printf "x y\n" | grep -c z || echo "no match"
    false | true
    echo "pipe ok?"

    Bash 5.2-এ চালানো ফল:

    HELLO 2
    0
    no match

    Exit code 1, আর pipe ok? ছাপা হয় না। grep -c মিল না পেলে 0 ছাপে, আবার exit code 1 দেয়, তাই ||-এর ডান অংশও চলে। false | true-তে শেষ ধাপ সফল হলেও pipefail পুরো pipeline-কে ব্যর্থ ধরে, আর set -e script থামায়। pipefail না থাকলে শেষ লাইনটিও ছাপা হতো।

  4. নকশা: একটি কাল্পনিক ছোট VPS-এ প্রতি রাতে log folder compress করে ৩০ দিনের বেশি পুরোনো ফাইল মুছতে হবে। Cron না systemd timer বেছে নেবেন, আর এই কাজের জন্য কোন কোন সুরক্ষা বসাবেন? ফল কীভাবে যাচাই করবেন?

    কাজটি কম ঝুঁকির, তাই cron চলে। তবে VPS মাঝে মাঝে বন্ধ থাকলে বা log দরকার হলে timer-এ Persistent=true সুবিধাজনক। একটিমাত্র উত্তর নেই; যুক্তিটি লিখুন।

    সুরক্ষা: set -euo pipefail; ফোল্ডার readonly LOG_DIR=/var/log/myapp, যাতে খালি হওয়ার সুযোগ নেই; মোছার আগে find "$LOG_DIR" -name '*.gz' -mtime +30 -print দিয়ে dry-run; তারপর -delete; flock; সীমিত ব্যবহারকারী, root নয়। অনেক ক্ষেত্রে নিজের script-এর বদলে Linux-এর logrotate-ই ভালো: আগে দেখে নিন, tool আগে থেকেই আছে কি না।

    যাচাই: disk ব্যবহারের ধারা দেখা, script-এর শেষের log, আর সফলতার সংকেত। পরীক্ষার ফোল্ডারে touch -d দিয়ে পুরোনো তারিখের ফাইল বানিয়ে আগে চালিয়ে দেখা যায়।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি নিরাপদ Bash script-এর অংশগুলো ওপর থেকে নিচে সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Shebang লাইন
  2. set -euo pipefail
  3. Argument যাচাই
  4. flock দিয়ে lock নেওয়া
  5. mktemp -d আর trap ... EXIT
  6. মূল কাজ, তারপর সফলতার log আর সংকেত

Bash script-এ DIR="" থাকলে rm -rf "$DIR/"* কমান্ডটি কেন ভয়ংকর?

  • কারণ এটি syntax error দিয়ে script থামিয়ে দেয়
  • কারণ এটি শুধু বর্তমান ফোল্ডার মুছে দেয়
  • কারণ পথটি /* হয়ে যায়, আর --preserve-root এটি ঠেকাতে পারে না
  • কারণ rm তখন DIR নামের ফাইলটি মোছে

একটি script-এ শুধু set -e আছে। cat missing.log | sort > out.txt লাইনে missing.log নেই। কী ঘটবে?

  • Script সঙ্গে সঙ্গে থেমে যাবে
  • Script চলতে থাকবে, আর out.txt খালি থাকবে
  • sort চলবেই না, কিন্তু script এগোবে
  • Bash missing.log তৈরি করে আবার চেষ্টা করবে

প্রচলিত Vixie-ধরনের cron-এ 0 0 2 * 5 কখন চলে?

  • শুধু সেই ২ তারিখে, যেদিন শুক্রবার
  • প্রতি বছর মে মাসের ২ তারিখে
  • প্রতিদিন রাত ২টায়, শুধু শুক্রবার বাদে
  • মধ্যরাতে, প্রতি মাসের ২ তারিখে অথবা প্রতি শুক্রবারে

একটি server-এর ঘড়ি UTC. Crontab-এর 30 2 * * * লাইনটি বাংলাদেশের সময়ে (UTC+6) কখন চলবে?

  • সকাল ০৮:৩০
  • রাত ০২:৩০
  • আগের দিন রাত ০৮:৩০
  • দুপুর ০২:৩০

touch job.lock ধরনের lock-এর তুলনায় flock-এর প্রধান সুবিধা কোনটি?

  • এটি একাধিক server-এর মধ্যেও কাজ করে
  • Process মরে গেলে lock আপনা-আপনি ছেড়ে যায়
  • এটি কাজের সময় কমিয়ে দেয়
  • এটি অন্য যেকোনো প্রোগ্রামকে ফাইলটি পড়তে বাধা দেয়

একটি server রাত ০২:০০ থেকে ০৪:০০ পর্যন্ত বন্ধ ছিল। ০২:৩০-এর backup কাজটি মেশিন চালু হওয়ার পরে চালাতে systemd timer-এ কোন সেটিং লাগে?

  • RandomizedDelaySec=
  • MemoryMax=
  • Persistent=true
  • Type=oneshot

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Bash হলো আঠা: কয়েকটি কমান্ড জোড়া লাগাতে ভালো। জটিল যুক্তিতে Python, অনেক server-এ Ansible.
  2. সবসময় quote করুন, আর মোছার আগে ${DIR:?}. খালি variable আর * একসাথে হলে --preserve-root-ও বাঁচায় না।
  3. set -euo pipefail, trap আর mktemp প্রতিটি script-এর শুরুতে। তবে set -e-এর ফাঁকগুলো মনে রাখুন।
  4. Idempotent, dry-run, log, backoff আর flock: “আবার চালান” যেন সবসময় নিরাপদ উত্তর হয়।
  5. Cron-এর ঘড়ি server-এর, environment ছোট আর ব্যর্থতা নীরব। গুরুত্বপূর্ণ কাজে systemd timer আর সফলতার সংকেত ব্যবহার করুন।

পরের পর্বে: এই পর্বের timer-এর পিছনের .service ফাইলটি খুলে দেখব: process কীভাবে service হয়, systemd কীভাবে তাকে চালায় ও ফিরিয়ে আনে, journalctl দিয়ে log পড়া আর resource সীমা।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান