Bash scripting-এর গোড়া: চমকে না দেওয়া নিরাপদ script লিখুন

Bash server-এর কাজ স্বয়ংক্রিয় করে, কিন্তু অসতর্ক script ভুল জিনিস মুছে ফেলে। পরীক্ষিত backup script বানিয়ে নিরাপদ অভ্যাস শিখুন।

নতুনতম archive রেখে Bash backup script চালানো একটি terminal

Bash হলো server-এর ঝামেলার কাজের ভাষা: backup, deployment, log পরিষ্কার। শুরু করা সহজ, আবার বিপজ্জনক ভুল করাও সহজ, কারণ ছোট একটি typo ভুল folder মুছে দিতে পারে। এই লেখা একটি কাজের script বানিয়ে গোড়া শেখায়, তারপর নিরাপদ অভ্যাস ব্যাখ্যা করে। নিচের script ও আউটপুট প্রকাশের আগে Bash 5 সহ Linux-এ চালানো হয়েছে।

Script

backup.sh নামে সংরক্ষণ করে chmod +x backup.sh চালান। এটি folder-এর তারিখ-দেওয়া archive বানায় এবং শুধু নতুনতম কয়েকটি রাখে:

#!/usr/bin/env bash
# backup.sh SOURCE_DIR DEST_DIR [KEEP]  - make a dated .tar.gz backup and keep only the newest KEEP files
set -euo pipefail

usage() {
  echo "Usage: $0 SOURCE_DIR DEST_DIR [KEEP]" >&2
  exit 2
}

[[ $# -ge 2 ]] || usage
source_dir=$1
dest_dir=$2
keep=${3:-3}

[[ -d "$source_dir" ]] || { echo "Error: $source_dir is not a directory" >&2; exit 1; }
[[ "$keep" =~ ^[0-9]+$ ]] || { echo "Error: KEEP must be a number" >&2; exit 1; }
mkdir -p "$dest_dir"

tmp=$(mktemp "$dest_dir/.partial.XXXXXX")
trap 'rm -f "$tmp"' EXIT          # clean up if anything fails

name="$(basename "$source_dir")-$(date +%Y%m%d-%H%M%S).tar.gz"
tar -czf "$tmp" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"
mv "$tmp" "$dest_dir/$name"
echo "Created $dest_dir/$name ($(du -h "$dest_dir/$name" | cut -f1))"

# delete old backups, newest first, keep $keep
count=0
for file in $(ls -1t "$dest_dir"/"$(basename "$source_dir")"-*.tar.gz); do
  count=$((count + 1))
  if (( count > keep )); then
    rm -- "$file"
    echo "Removed old backup $(basename "$file")"
  fi
done

চালান

$ ./backup.sh site backups 2
Created backups/site-20261028-030946.tar.gz (4.0K)
$ ./backup.sh site backups 2
Created backups/site-20261028-030948.tar.gz (4.0K)
$ ./backup.sh site backups 2
Created backups/site-20261028-030949.tar.gz (4.0K)
Removed old backup site-20261028-030946.tar.gz
$ ls backups
site-20261028-030948.tar.gz  site-20261028-030949.tar.gz

$ ./backup.sh
Usage: ./backup.sh SOURCE_DIR DEST_DIR [KEEP]        (exit code 2)
$ ./backup.sh nope backups
Error: nope is not a directory                       (exit code 1)
$ ./backup.sh site backups x
Error: KEEP must be a number                         (exit code 1)

(Backup একটি ছোট পরীক্ষার folder-এর, তাই মাত্র কয়েক কিলোবাইট।)

লাইন ধরে গোড়া

Shebang #!/usr/bin/env bash সিস্টেমকে জানায় কোন interpreter ফাইল চালাবে। executable করার পর ./backup.sh দিয়ে চালান।

Variable ও argument। $1, $2, $3 হলো argument; $# কয়টি। keep=${3:-3} মানে "তৃতীয় argument নিন, না থাকলে ৩"। =-এর দুপাশে ফাঁকা ছাড়া লিখুন: name=value।

Condition। [[ -d "$dir" ]] পরীক্ষা করে directory আছে কি না। আরও দরকারি পরীক্ষা: -f (ফাইল), -z (খালি string), -e (আছে)। [[ ... ]] Bash-এর রূপ। (( count > keep )) সংখ্যা তুলনার জন্য।

Command substitution। $(date +%Y%m%d-%H%M%S) একটি কমান্ড চালিয়ে তার আউটপুট বসায়। এভাবেই ফাইলের নামে timestamp আসে।

Function। usage() { ...; } বারবার ব্যবহারের কমান্ড গুছায়।

Loop। for file in ...; do ... done প্রতিটি item-এর জন্য পুনরাবৃত্তি করে।

Exit code। প্রতিটি কমান্ড একটি সংখ্যা ফেরত দেয়: ০ মানে সফল, অন্য যেকোনো কিছু মানে ব্যর্থ। exit 2 ও exit 1 কলারকে (cron, CI, অন্য script) জানায় কী হয়েছে। echo "$?" শেষ exit code দেখায়। error বার্তা >&2 দিয়ে stderr-এ পাঠান।

নিরাপত্তার লাইন: set -euo pipefail

এই এক লাইন অনেক বিপর্যয় ঠেকায়:

  • -e: কমান্ড ব্যর্থ হলে চালিয়ে না গিয়ে script থামান।
  • -u: unset variable-কে error ধরুন। এটি ছাড়া DIR খালি থাকলে rm -rf "$DIR/" হয়ে যায় rm -rf /।
  • -o pipefail: pipeline-এর যেকোনো অংশ ব্যর্থ হলে পুরোটা ব্যর্থ, শুধু শেষটি নয়।

এটি জাদু নয়, কিছু ব্যতিক্রমও আছে, তবে নতুন script-এর জন্য খুব ভালো ডিফল্ট।

সবসময় variable quote করুন

rm -- "$file"        # ভালো: নামে ফাঁকা থাকলেও টেকে
rm $file             # খারাপ: "my notes.txt" দুটি argument হয়ে যায়

প্রায় সব জায়গায় "$var" ডবল quote দিয়ে লিখুন। quote ছাড়া মানের ফাঁকা ও wildcard কমান্ডের অর্থ বদলে দেয়। -- option-এর শেষ বোঝায়, তাই -rf নামের ফাইল flag হিসেবে পড়া হয় না।

trap দিয়ে পরিষ্কার

Script আগে একটি সাময়িক ফাইলে লেখে, আর trap 'rm -f "$tmp"' EXIT script যেভাবেই শেষ হোক (সফল, error বা Ctrl+C) সেটি মুছে দেয়। tar সফল হলেই কেবল mv দিয়ে চূড়ান্ত নামে সরানো হয়। ফলে অর্ধেক লেখা backup কখনো আসল backup-এর মতো দেখায় না। সাময়িক নামে লিখে তারপর rename করা, এই প্যাটার্ন নকল করার মতো।

চালানোর আগে কাজ যাচাই করুন

  • bash -n script.sh না চালিয়ে syntax দেখে।
  • shellcheck script.sh একটি বিনামূল্যের linter, যা quoting ভুল ও অন্য ফাঁদ ধরে। এটি বহুল সুপারিশকৃত; আপনার package manager থেকে install করুন।
  • আগে নিরীহ পরীক্ষার folder-এ চালান। বিপজ্জনক কমান্ডের আগে echo বসান (echo rm -- "$file") যাতে দেখা যায় কী চলত।
  • bash -x script.sh debug-এর জন্য প্রতিটি কমান্ড চলার সময় ছাপে।

এই script-এর একটি জানা দুর্বলতা

পরিষ্কারের loop ls থেকে ফাইলের নাম পড়ে। এখানে ঠিক, কারণ নামগুলো আমরা নিজে বানাই (ফাঁকা বা অদ্ভুত অক্ষর নেই), কিন্তু সাধারণভাবে ls পার্স করা ভঙ্গুর। যেকোনো নামের ফাইলের জন্য find ও -print0 বা for loop-এ glob ব্যবহার করুন। নিজের script কোথায় ভঙ্গুর জানাও নিরাপদ script লেখার অংশ।

নির্ধারিত সময়ে চালান

Backup নিয়মিত চললেই কাজে আসে। cron-এ 0 2 * * * /opt/scripts/backup.sh /var/www/site /backups 7 প্রতিদিন ০২:০০-এ চালায়। cron-এ পূর্ণ path ব্যবহার করুন, কারণ এর environment ন্যূনতম। systemd timer একটি আধুনিক বিকল্প। আর আমাদের backup লেখার নিয়ম মনে রাখুন: যে backup কখনো restore করে দেখেননি তা আশা, backup নয়। একটি কপি server-এর বাইরেও রাখুন।

কখন Bash ব্যবহার করবেন না

কমান্ড জোড়া লাগাতে Bash দারুণ। Script-এ data structure, JSON, HTTP call বা অনেক যুক্তি লাগলে Python-এর মতো ভাষা সাধারণত পরিষ্কার ও নিরাপদ। মোটামুটি নিয়ম: Bash script প্রায় ১০০ লাইন ছাড়ালে বা array-র array লাগলে নতুন করে লেখার কথা ভাবুন।

অনুশীলনের ধারণা

১. কী মোছা হতো তা শুধু ছাপে এমন --dry-run option যোগ করুন। ২. /-এর disk ব্যবহার ৮০%-এর ওপরে গেলে সতর্ক করে এমন script লিখুন। ৩. একটি URL 200 দেয় কি না দেখে, না দিলে exit 1 করে এমন script বানান। ৪. আকার অনুযায়ী log ফাইল rotate করুন।

মূল কথা: প্রতিটি script শুরু করুন set -euo pipefail দিয়ে, variable quote করুন, argument যাচাই করুন, trap দিয়ে পরিষ্কার করুন, নিরীহ data-য় test করুন এবং shellcheck চালান। এই অভ্যাস Bash-কে ঝুঁকিপূর্ণ থেকে নির্ভরযোগ্য করে।


Linux ও DevOps সিরিজ, পর্ব ৩ (প্রতিদিনের পরিচালনা): 3 নম্বর (মোট 5)। আগের লেখা: Linux troubleshooting: server গোলমাল করলে যে কমান্ড চালাবেন.

আরও পড়ুন

Python দিয়ে এলোমেলো CSV পরিষ্কার: নাম, তারিখ, ফোন ও ডুপ্লিকেট

আসল data এলোমেলো। standard library দিয়ে contacts পরিষ্কার করা, প্রতিটি বাতিল row ব্যাখ্যা করা ও test-সহ একটি script বানান।

প্রজেক্ট•৫ মিনিটে পড়া