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

Linux টার্মিনাল — shell, কমান্ড আর ফাইল

The Linux Terminal — Shell, Commands, Files, Pipes & Your First Script

Taught in Bangla, this lesson gives a hands-on start in the Linux terminal: terminal vs shell vs console, getting a Linux shell on any device, navigating with pwd, ls and cd, creating, copying, moving and deleting files safely, getting help with man and --help, redirection and pipes via file descriptors 0, 1 and 2, exit status with && and ||, environment variables, PATH and a first shell script.

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

  • Windows, Mac বা ফোন — যেটিই থাকুক, পুরো কোর্সের জন্য একটি কাজের Linux shell জোগাড় করা
  • ফোল্ডারে ঘোরাফেরা, ফাইল তৈরি-কপি-সরানো-মোছা — নিরাপদ অভ্যাসসহ
  • Redirection আর pipe দিয়ে ছোট ছোট টুল জুড়ে কাজের জিনিস বানানো
  • PATH, environment variable, exit status আর প্রথম shell script
  • প্রতিটি কমান্ডের পেছনে OS-এর কোন ধারণা কাজ করছে — process, file descriptor, ফাইল — আর কোন পর্বে সেগুলো গভীরে দেখবেন

১ · Terminal, shell আর console — তিনটি আলাদা জিনিস

আগের দুই পর্বে দেখেছেন, অপারেটিং সিস্টেম হলো কম্পিউটারের ব্যবস্থাপক। এই কোর্সের বাকি অংশে আমরা সেই ব্যবস্থাপককে কাজ করতে চোখে দেখব — কোন process চলছে, কোন ফাইল খোলা, মেমরি কতটা খরচ হলো। এগুলো দেখার সবচেয়ে সরাসরি জানালা হলো terminal. তাই এই পর্বটি একটু হাতে-কলমে: শেষে আপনার একটি কাজের shell থাকবে, আর থাকবে প্রতিদিনের ২০-২৫টি কমান্ডের অভ্যাস।

নতুনরা প্রায়ই তিনটি শব্দ গুলিয়ে ফেলেন — terminal, shell, console. এরা একসাথে কাজ করে, কিন্তু একই জিনিস নয়।

Terminal (emulator)

পর্দার সেই জানালা, যেখানে লেখা দেখায় আর কিবোর্ড থেকে অক্ষর নেয়। সে কমান্ডের মানে বোঝে না — শুধু অক্ষর আদান-প্রদান করে, রং আর ফন্ট সামলায়।

Windows Terminal, GNOME Terminal, macOS Terminal, iTerm2

Shell

জানালার ভেতরে চলা প্রোগ্রাম, যে আপনার লাইনটি পড়ে, ভেঙে বোঝে, তারপর দরকারি প্রোগ্রাম চালায়। আসল কারিগর এটিই।

bash, zsh, sh (dash), fish, PowerShell

Console

মূলত কম্পিউটারের সাথে সরাসরি যুক্ত প্রধান পর্দা-কিবোর্ড। সার্ভার রুমে মনিটর লাগিয়ে যা দেখেন, বা Linux-এ Ctrl+Alt+F3 চেপে যে কালো পর্দা আসে। কথ্য ভাষায় অনেকে terminal-কেও console বলেন।

virtual console, serial console

একটি রেস্তোরাঁর কথা ভাবুন। Terminal হলো অর্ডার নেওয়ার কাউন্টার আর টোকেনের পর্দা — অর্ডার লেখে, খাবার তৈরি হলে নম্বর দেখায়, কিন্তু রান্না জানে না। Shell হলো হেড-বাবুর্চি, যিনি অর্ডারটি পড়ে বোঝেন, কোন সহকারীকে কোন কাজ দেবেন ঠিক করেন। আর রান্নাঘরের চুলা, গ্যাস, ফ্রিজ — অর্থাৎ আসল সম্পদ — নিয়ন্ত্রণ করে মালিকপক্ষ, মানে kernel. হেড-বাবুর্চিও চুলা জ্বালাতে মালিকের নিয়ম মেনে অনুরোধ করেন।

শেষ বাক্যটিই এই কোর্সের জন্য সবচেয়ে গুরুত্বপূর্ণ। Shell kernel-এর অংশ নয়। সে একটি সাধারণ user প্রোগ্রাম, ঠিক আপনার লেখা C প্রোগ্রামের মতো। ফাইল খুলতে, নতুন প্রোগ্রাম চালাতে বা ফোল্ডার বদলাতে তাকেও kernel-কে অনুরোধ করতে হয় — এই অনুরোধের নাম system call, যা পর্ব ০৫-এ বিস্তারিত দেখবেন। এজন্যই একই Linux kernel-এর ওপর কেউ bash ব্যবহার করেন, কেউ zsh, কেউ fish — shell বদলালেও OS বদলায় না।

প্রথম দিকের কম্পিউটারে “terminal” ছিল আলাদা একটি যন্ত্র — টাইপরাইটারের মতো teletype, পরে পর্দাওয়ালা যন্ত্র, যেমন ১৯৭৮ সালের DEC VT100. Linux-এ আজও terminal-কে tty বলা হয় — teletype-এর সংক্ষেপ। Shell-এর ইতিহাসও লম্বা: ১৯৭১ সালে Unix-এর প্রথম সংস্করণে ছিল Ken Thompson-এর shell; ১৯৭৯ সালে Version 7 Unix-এ আসে Stephen Bourne-এর sh; ১৯৮৯ সালে GNU প্রজেক্টের জন্য Brian Fox তৈরি করেন bash (“Bourne-Again SHell”) — যা আজও বেশিরভাগ Linux distro-র ডিফল্ট interactive shell. ২০১৯ সালে macOS Catalina থেকে Apple ডিফল্ট shell হিসেবে zsh বেছে নেয়।

২ · আপনার জন্য একটি Linux shell — কোথায় পাবেন

এই কোর্সের বেশিরভাগ উদাহরণ Linux-এর জন্য, কারণ Linux-এর ভেতরটা খোলা — /proc ফোল্ডারে kernel নিজেই process আর মেমরির তথ্য সাজিয়ে রাখে, সোর্স কোডও পড়া যায়। আপনার যন্ত্র যা-ই হোক, নিচের কোনো একটি পথ বেছে নিন। একাধিক থাকলে আমাদের পরামর্শ: Windows-এ WSL, Mac-এ শুরুর জন্য Terminal আর পরে একটি Linux VM, আর কিছু ইনস্টল করতে না চাইলে এই সাইটের runnable bash ব্লক।

WSL (Windows Subsystem for Linux) Windows 10-এর নতুন সংস্করণ আর Windows 11-এ Microsoft-এর নিজস্ব ব্যবস্থা। WSL 2 একটি হালকা virtual machine-এ আসল Linux kernel চালায়, তাই এই কোর্সের প্রায় সব পরীক্ষা এখানে চলবে।

  1. Start মেনুতে “Terminal” বা “PowerShell” খুঁজে Run as administrator দিয়ে খুলুন।
  2. নিচের কমান্ডটি লিখে Enter চাপুন, তারপর কম্পিউটার restart দিন। ডিফল্টভাবে Ubuntu ইনস্টল হয়।
  3. প্রথমবার Ubuntu খুললে একটি Linux username আর password চাইবে — এটি Windows-এর password থেকে আলাদা হতে পারে। টাইপ করার সময় password-এর অক্ষর দেখা যায় না, এটাই স্বাভাবিক।
wsl --install
wsl -l -v          # কোন distro আছে, WSL 1 না 2 — দেখায়

WSL-এর ভেতর থেকে Windows-এর C ড্রাইভ পাবেন /mnt/c-তে। তবে কাজের ফাইল Linux-এর নিজের home ফোল্ডারে (~) রাখুন — WSL 2-তে দুই ফাইল সিস্টেমের সীমানা পেরিয়ে পড়া-লেখা বেশ ধীর। BIOS-এ virtualization (Intel VT-x বা AMD-V) বন্ধ থাকলে WSL 2 চালু হয় না; তখন সেটিংটি চালু করতে হয়।

Mac-এ Terminal অ্যাপ আগে থেকেই আছে (Applications → Utilities). macOS Unix-ধরনের OS, তাই ls, cd, pipe, redirection — এই পর্বের প্রায় সব কিছু একই রকম চলে। কিন্তু তিনটি পার্থক্য মনে রাখুন:

  • ডিফল্ট shell zsh, bash নয়। Prompt-এর শেষে $-এর বদলে % দেখবেন। Mac-এর সাথে আসা /bin/bash খুব পুরোনো (৩.২ সংস্করণ)।
  • টুলগুলো BSD ঘরানার, GNU নয়। Linux-এর sed -i 's/a/b/' f Mac-এ লিখতে হয় sed -i '' 's/a/b/' f; date, stat, readlink-এর অনেক option-ও আলাদা। ইন্টারনেট বা AI-এর কমান্ড Mac-এ না চললে প্রথমে এটাই সন্দেহ করুন।
  • /proc নেই। Process আর মেমরির পর্বগুলোর জন্য (পর্ব ০৮ থেকে) একটি Linux VM বা এই সাইটের runnable ব্লক লাগবে। Apple Silicon Mac-এ UTM বা Multipass দিয়ে ARM-এর Ubuntu চালানো যায়।

VirtualBox (বিনামূল্যে) বা VMware-এ Ubuntu-র ISO ফাইল দিয়ে একটি virtual machine বানান — অন্তত ২ GB RAM আর ২০ GB ডিস্ক দিলে স্বচ্ছন্দে চলে। VM-এর সুবিধা: পুরোপুরি আলাদা কম্পিউটার, ভুল হলেও আপনার আসল OS নিরাপদ, আর চাইলে snapshot নিয়ে আগের অবস্থায় ফেরা যায়। বুট প্রক্রিয়া বা kernel module-এর মতো পরীক্ষা (পর্ব ০৭, ৫৫) VM-এই সবচেয়ে নিরাপদ।

পুরোনো একটি ল্যাপটপ থাকলে তাতে সরাসরি Ubuntu বা Linux Mint বসানোও চমৎকার শেখার পথ। Linux kernel-এর সাথে টুল আর প্যাকেজ মিলিয়ে তৈরি এমন সম্পূর্ণ OS-কে বলে distro.

ব্রাউজার থেকেই একটি আসল Linux shell পাওয়া যায় — যেমন Google Cloud Shell, AWS CloudShell বা GitHub Codespaces. এগুলোর জন্য অ্যাকাউন্ট লাগে, কোনোটিতে card যাচাই চায়, আর বিনামূল্যের সীমা সময়ে সময়ে বদলায় — শুরুর আগে শর্তগুলো পড়ে নিন।

Android ফোনে Termux অ্যাপ একটি Linux-ধরনের পরিবেশ দেয়, যেখানে bash আর বেশিরভাগ মূল কমান্ড চলে। তবে এটি পূর্ণ distro নয় আর root ছাড়া চলে, তাই কিছু ফোল্ডার আর টুল ভিন্ন। ফোনে অভ্যাস গড়ার জন্য ভালো, গভীর পরীক্ষার জন্য নয়।

এই কোর্সের যেসব কোড ব্লকে Run বোতাম আছে, সেগুলো একটি দূরের সার্ভারে আসল Linux-এ চলে: bash 5.2, Linux 6.x kernel, একটি container-এর ভেতরে, user-এর নাম wandbox. কয়েকটি সীমা জানা থাকা দরকার:

  • root বা sudo নেই, নেটওয়ার্কও নেই।
  • প্রতিবার Run চাপলে নতুন, খালি পরিবেশ — আগের বার বানানো ফাইল থাকে না।
  • ব্লকটি একটি script হিসেবে চলে, interactive terminal হিসেবে নয়। তাই less বা Tab চাপার মতো জিনিস এখানে পরীক্ষা করা যায় না।

কিছু ইনস্টল না করেই এই পর্বের চারটি ব্লক চালিয়ে দেখতে পারবেন।

Prompt পড়তে শিখুন

Terminal খুললেই shell একটি লাইন দেখিয়ে অপেক্ষা করে — এর নাম prompt. প্রথম দেখায় হিজিবিজি লাগে, কিন্তু এর প্রতিটি অংশ কাজের তথ্য দেয়। নিচের ডায়াগ্রামে Ubuntu-র একটি সাধারণ prompt আর তার পরে টাইপ করা একটি কমান্ড ভেঙে দেখুন:

rahim @laptop :~/shop $ ls -l -a docs
  1. rahim — কে কাজ করছেন। বর্তমান user-এর নাম। Linux-এ প্রতিটি process কোনো না কোনো user-এর নামে চলে, আর সেই user-এর অনুমতিই ঠিক করে কোন ফাইল ছোঁয়া যাবে (পর্ব ৪৭)।
  2. @laptop — কোন কম্পিউটারে। Hostname. সার্ভারে SSH দিয়ে ঢুকলে এই অংশটিই মনে করিয়ে দেয় আপনি নিজের ল্যাপটপে নন — ভুল সার্ভারে কমান্ড চালানোর দুর্ঘটনা এখান থেকেই ঠেকানো যায়।
  3. :~/shop — কোথায় আছেন। বর্তমান ফোল্ডার, অর্থাৎ working directory. ~ মানে আপনার home ফোল্ডার, তাই পুরো ঠিকানা /home/rahim/shop.
  4. $ — কে হিসেবে। সাধারণ user হলে $, সর্বোচ্চ ক্ষমতার user (root) হলে #. Prompt-এ # দেখলে প্রতিটি কমান্ড দুবার ভেবে চালান। (macOS-এর zsh-এ $-এর জায়গায় % দেখা যায়।) Prompt-এর চেহারা PS1 নামের একটি variable ঠিক করে, তাই অন্য কম্পিউটারে একটু ভিন্ন দেখাতে পারে।
  5. ls — কী করতে হবে। লাইনের প্রথম শব্দটিই command — কোন প্রোগ্রাম বা shell-এর কোন নিজস্ব কাজ চলবে। এখানে ls (list) — ফোল্ডারের ভেতরে কী আছে, তা দেখানো।
  6. -l -a — কীভাবে করতে হবে। - দিয়ে শুরু শব্দগুলো option বা flag. -l মানে লম্বা বিবরণ (permission, মালিক, আকার, সময়), -a মানে লুকানো ফাইলও দেখানো। ছোট option জোড়া লাগানো যায়: -la. লম্বা রূপও আছে, যেমন --all.
  7. docs — কিসের ওপর। বাকি শব্দগুলো argument — কমান্ড কোন জিনিস নিয়ে কাজ করবে। Shell এই পুরো লাইনটি শব্দে ভেঙে ["ls", "-l", "-a", "docs"] তালিকা হিসেবে প্রোগ্রামটির হাতে দেয় — C-তে এটিই argv. মনে রাখার সূত্র: কমান্ড · option · argument, মাঝে স্পেস।

৩ · ঘোরাফেরা — pwd, ls, cd আর path

Linux-এ সব ফাইল একটিমাত্র উল্টো গাছে সাজানো। গাছের গোড়া / (root directory) — Windows-এর মতো আলাদা C:, D: ড্রাইভ নেই; অন্য ডিস্ক বা পেনড্রাইভও এই গাছেরই কোনো ডালে জুড়ে যায় (mounting, পর্ব ৪২)। কয়েকটি পরিচিত ডাল:

ফোল্ডারকী থাকেকোন পর্বে কাজে লাগবে
/home/rahimuser-এর নিজস্ব ফাইল (~)সব পর্বে
/usr/bin, /binls, grep-এর মতো প্রোগ্রামএই পর্বের PATH অংশ
/etcsystem-এর সেটিং ফাইল (যেমন /etc/passwd)পর্ব ৪৭
/prockernel-এর বানানো “ফাইল” — চলমান process-এর তথ্যপর্ব ০৮, ১১, ৩৮
/devdevice-গুলো ফাইলের রূপে (ডিস্ক, terminal, /dev/null)পর্ব ৩৯
/tmpঅস্থায়ী ফাইলপরীক্ষার জন্য

তিনটি কমান্ডেই ঘোরাফেরা চলে:

  • pwd (print working directory) — “আমি এখন কোথায়?” পূর্ণ ঠিকানা দেখায়।
  • ls — এখানে কী আছে? ls -l বিস্তারিত, ls -a লুকানো ফাইলসহ (নাম . দিয়ে শুরু হলেই Linux-এ ফাইলটি “লুকানো”), ls -lh আকার KB/MB-তে।
  • cd ঠিকানা (change directory) — অন্য ফোল্ডারে যান। শুধু cd লিখলে home-এ ফেরেন, cd - লিখলে ঠিক আগের ফোল্ডারে।

Absolute আর relative path

ঠিকানা লেখার দুটি উপায়। Absolute path শুরু হয় / দিয়ে, গাছের গোড়া থেকে পুরো পথ: /home/rahim/shop/docs. আপনি যেখানেই থাকুন, এর মানে একই। Relative path শুরু হয় / ছাড়া, আর হিসাব হয় বর্তমান ফোল্ডার থেকে: docs/readme.txt. সাথে তিনটি শর্টকাট:

চিহ্নমানেকে বোঝে
.এই ফোল্ডারটিইkernel — প্রতিটি ফোল্ডারে . নামে একটি সত্যিকারের entry থাকে
..এক ধাপ ওপরের (parent) ফোল্ডারkernel — প্রতিটি ফোল্ডারে .. entry থাকে
~আপনার home directoryশুধু shell — কমান্ড চালানোর আগে shell নিজেই /home/rahim বসিয়ে দেয়

ঢাকায় কাউকে ঠিকানা বলার দুটি উপায়: “বাংলাদেশ, ঢাকা, মিরপুর ১০, রোড ৫, বাড়ি ১২” — এটি absolute, দেশের যেকোনো জায়গা থেকে কাজ করে। আর “এই মোড় থেকে বাঁয়ে দ্বিতীয় গলি” — এটি relative, শুধু আপনি এখন কোথায় দাঁড়িয়ে আছেন তার ওপর নির্ভর করে। ভুল মোড়ে দাঁড়িয়ে relative ঠিকানা মানলে ভুল বাড়িতে পৌঁছাবেন — script-এ relative path-এর বাগও ঠিক এমনই।

এবার দেখা যাক cd ../docs লিখলে ঠিক কী ঘটে। ধরুন আপনি আছেন ~/shop/src-এ:

1/2└── home3    └── rahim            (~)4        └── shop5            ├── docs6            │   └── readme.txt7            └── src8                └── main.c
হিসাবের অবস্থা
বাকি ঠিকানা
এখন যেখানে
process-এর cwd
$PWD
  1. শুরু কোথা থেকে? ঠিকানা ../docs-এর প্রথম অক্ষর / নয়, তাই এটি relative. হিসাব শুরু হবে বর্তমান ফোল্ডার ~/shop/src থেকে (সবুজ দাগ)। / দিয়ে শুরু হলে শুরু হতো গাছের গোড়া থেকে।
  2. প্রথম টুকরো .. — এক ধাপ ওপরে। src-এর parent হলো shop. খেয়াল করুন, এখনো কিছুই বদলায়নি — process-এর cwd আর $PWD আগের মতোই ~/shop/src; শুধু হিসাব চলছে।
  3. দ্বিতীয় টুকরো docs — shop ফোল্ডারের entry-গুলোর মধ্যে docs নামটি খোঁজা হলো। পাওয়া গেছে, আর এটি একটি ফোল্ডার (ফাইল হলে আসত Not a directory, না পেলে No such file or directory).
  4. Kernel-এর কাছে অনুরোধ। cd একটি shell builtin; সে chdir() system call ডাকে (bash সাধারণত হিসাব-করা পূর্ণ ঠিকানা /home/rahim/shop/docs পাঠায়)। Kernel যাচাই করে ফোল্ডারটিতে ঢোকার (x) অনুমতি আছে কি না; থাকলে shell process-এর cwd বদলে ~/shop/docs হয়। (বাস্তবে kernel এই টুকরো-টুকরো খোঁজাটি নিজেই করে — পর্ব ৪২-এ দেখবেন।)
  5. Shell হিসাব হালনাগাদ করে। সফল হওয়ায় bash OLDPWD=~/shop/src আর PWD=~/shop/docs রাখে, আর prompt নতুন ঠিকানা দেখায়। এখন cd - লিখলে OLDPWD দেখে আগের জায়গায় ফিরবেন। বিফল হলে cwd একটুও বদলায় না।

এবার নিজে চালিয়ে দেখুন। নিচের ব্লকটি বর্তমান ফোল্ডারে একটি ছোট দোকানের ফোল্ডার-কাঠামো বানায়, ঘোরাফেরা করে, ফাইল কপি করে, সরায় আর মোছে:

echo "start: $(basename "$PWD")"
mkdir -p shop/docs shop/src      # -p: make parents, no error if they exist
cd shop
touch notes.txt src/main.c       # empty files
echo "Dhaka branch" > docs/readme.txt
ls                               # what is here?
ls -a docs                       # -a also shows . and ..
cd docs
echo "now in: $(basename "$PWD")"
cd ..                            # go up one level
cp docs/readme.txt docs/readme.bak
mv notes.txt docs/               # move (or rename)
ls docs
ls -l docs | awk 'NR>1 {print $1, $5, $NF}'
rm docs/readme.bak               # gone for good - no Recycle Bin
ls docs
cd ~ && echo "home: $HOME"

আসল রানে আউটপুট শুরু হলো start: wandbox দিয়ে — অর্থাৎ script চলা শুরু করে user-এর home ফোল্ডারে। প্রথম ls দেখাল docs, notes.txt, src; ls -a docs দেখাল ., .. আর readme.txt — সেই দুটি লুকানো entry, যা প্রতিটি ফোল্ডারে থাকে। cd docs-এর পর now in: docs. কপি আর সরানোর পর ls -l-এর ছাঁটা রূপ দেখাল -rw-r--r-- 0 notes.txt, -rw-r--r-- 13 readme.bak, -rw-r--r-- 13 readme.txt — touch-এর ফাইল ০ byte, আর “Dhaka branch” লেখাটি ১২টি অক্ষর আর শেষের newline মিলিয়ে ১৩ byte. rm-এর পর শুধু notes.txt আর readme.txt থাকল, শেষে home: /home/wandbox.

একটি লক্ষ করার মতো বিষয়: নিজের terminal-এ ls নামগুলো পাশাপাশি দেখায়, অথচ এখানে প্রতি লাইনে একটি। কারণ ls জিজ্ঞেস করে নেয় তার আউটপুট সত্যিকারের terminal-এ যাচ্ছে কি না; ফাইল বা pipe-এ গেলে সে প্রতি লাইনে একটি নাম লেখে, যাতে পরের প্রোগ্রাম সহজে পড়তে পারে। -rw-r--r-- কলামের মানে পর্ব ৪৭-এ বিস্তারিত দেখবেন।

৪ · ফাইল আর ফোল্ডার — তৈরি, কপি, সরানো, মোছা, দেখা

উপরের ব্লকে যে কমান্ডগুলো দেখলেন, সেগুলো একসাথে সাজিয়ে রাখি:

কমান্ডকাজমনে রাখার কথা
mkdir -p a/b/cফোল্ডার বানানো-p থাকলে মাঝের ফোল্ডারও বানায়, আগে থেকে থাকলে ত্রুটি দেয় না
touch f.txtখালি ফাইল বানানোফাইল আগে থেকে থাকলে শুধু সময় হালনাগাদ করে, লেখা মোছে না
cp a bকপিফোল্ডার কপিতে cp -r; b আগে থেকে থাকলে চুপচাপ ওপরে লিখে দেয়
mv a bসরানো বা নাম বদলানোএকই ডিস্কে হলে তথ্য কপি হয় না, শুধু ফোল্ডারের entry বদলায় — তাই বড় ফাইলেও তাৎক্ষণিক
rm f, rm -r dirমোছাRecycle Bin নেই; ফেরানোর সহজ উপায় নেই
rmdir dirখালি ফোল্ডার মোছাফোল্ডারে কিছু থাকলে মোছে না — নিরাপদ

rm নিয়ে নিরাপদ অভ্যাস। Linux-এর rm কিছু জিজ্ঞেস করে না, আর মোছা ফাইল সাধারণভাবে ফেরত আসে না। তাই:

  • wildcard দিয়ে মোছার আগে একই নকশা ls দিয়ে দেখে নিন — ls *.log ঠিক দেখালে তবেই rm *.log.
  • নতুন অবস্থায় rm -i ব্যবহার করুন — প্রতিটি ফাইলের আগে জিজ্ঞেস করে।
  • rm -r লেখার আগে pwd দেখে নিশ্চিত হোন আপনি কোন ফোল্ডারে আছেন।
  • script-এ variable দিয়ে path বানালে সবসময় উদ্ধৃতিচিহ্নে রাখুন ("$dir"), আর variable খালি হতে পারে কি না ভাবুন — খালি variable-এ "$dir/" হয়ে যায় শুধু /.
  • অচেনা কেউ, কোনো ফোরাম বা AI “এটা চালান, ঠিক হয়ে যাবে” বলে rm বা sudo-ওয়ালা কমান্ড দিলে আগে প্রতিটি অংশের মানে বুঝুন। গুরুত্বপূর্ণ ফাইলের backup সবসময় রাখুন।

ফাইলের ভেতরে দেখা — cat, less, head, tail

  • cat f — পুরো ফাইল একবারে পর্দায় ঢেলে দেয়। ছোট ফাইলের জন্য ভালো। (নামটি এসেছে concatenate থেকে: cat a b দুটি ফাইল জুড়ে দেখায়।)
  • less f — বড় ফাইল পাতায় পাতায় পড়া। Space পরের পাতা, / লিখে খোঁজা, q বেরোনো।
  • head -n 5 f — প্রথম ৫ লাইন; tail -n 5 f — শেষ ৫ লাইন।
  • tail -f app.log — ফাইলের শেষে নজর রাখে, নতুন লাইন লেখা হলেই দেখায়; সার্ভারের log দেখার প্রিয় কৌশল। থামাতে Ctrl+C.

ভুল করে ছবি বা প্রোগ্রামের মতো binary ফাইল cat করলে পর্দা অদ্ভুত অক্ষরে ভরে যেতে পারে; তখন reset লিখে Enter চাপুন। ফাইলটি কী ধরনের, আগে জানতে চাইলে file f.

Wildcard — নামের নকশা দিয়ে অনেক ফাইল একসাথে

*.txt মানে “.txt দিয়ে শেষ হওয়া সব নাম”। এমন নকশাকে বলে glob:

  • * — যেকোনো সংখ্যক যেকোনো অক্ষর (শূন্যটিও)। report* মেলে report, report1, report-final.pdf.
  • ? — ঠিক একটি অক্ষর। file?.c মেলে file1.c, কিন্তু file10.c নয়।
  • [abc], [0-9] — বন্ধনীর যেকোনো একটি অক্ষর।
  • {jan,feb}.csv — glob নয়, brace expansion: ফাইল থাকুক বা না থাকুক, সরাসরি দুটি শব্দ jan.csv feb.csv বানায়। mkdir -p {2025,2026}/{jan,feb} একবারে চারটি ফোল্ডার বানায়।
মূল ধারণা

Wildcard মেলায় shell, কমান্ড নয়। rm *.log লিখলে rm প্রোগ্রামটি কখনো * দেখে না — shell আগেই ফোল্ডার পড়ে নকশাটিকে a.log b.log c.log তালিকায় বদলে দেয়, তারপর rm-কে সেই তালিকা দেয়। তাই একই নিয়ম সব কমান্ডে খাটে। কোনো নাম না মিললে bash নকশাটি হুবহু রেখে দেয়, আর কমান্ড বলে '*.log': No such file. লুকানো (.-দিয়ে-শুরু) ফাইল *-এ মেলে না। আর নকশাটি উদ্ধৃতিচিহ্নে রাখলে ('*.log') shell ছোঁয় না — তখন কমান্ড নিজে সেটি পায়, যেমন find . -name '*.log'.

৫ · সাহায্য পাওয়া — man, --help, type আর which

কেউ সব কমান্ডের সব option মুখস্থ রাখেন না; অভিজ্ঞরা জানেন কোথায় খুঁজতে হয়। আর সবচেয়ে নির্ভরযোগ্য উত্তর আপনার নিজের কম্পিউটারেই আছে — কারণ সেটি ঠিক আপনার কাছে থাকা সংস্করণের জন্য লেখা।

  • man ls — অফিসিয়াল ম্যানুয়াল বা man page. less-এর মতোই পড়া যায় (/ খোঁজা, q বেরোনো)। ম্যানুয়াল কয়েকটি section-এ ভাগ: ১ — কমান্ড, ২ — system call, ৩ — C library function. তাই man 2 chdir দেখায় kernel-এর chdir system call, আর এই কোর্সে বারবার man 2 fork, man 7 pipe দেখতে বলব। (WSL-এর হালকা Ubuntu-তে man page কম থাকলে sudo apt install man-db manpages-dev)
  • ls --help — অনেক GNU কমান্ড নিজেই সংক্ষিপ্ত সাহায্য দেখায়; man-এর চেয়ে দ্রুত। (macOS-এর BSD টুলে --help সাধারণত কাজ করে না, সেখানে man)
  • type cd — shell এই নামটি কীভাবে চালাবে: alias, function, builtin, না আলাদা প্রোগ্রাম। type -a echo সব সম্ভাবনা দেখায়।
  • which ls বা command -v ls — PATH-এ কোন ফাইলটি চলবে তার ঠিকানা। which builtin চেনে না; তাই type বেশি নির্ভরযোগ্য।

type কেন জরুরি? কারণ কিছু কমান্ড আলাদা প্রোগ্রাম হতেই পারে না। cd-এর কথা ভাবুন। প্রতিটি process-এর নিজস্ব working directory থাকে। Shell যদি cd-কে আলাদা প্রোগ্রাম হিসেবে চালাত, তাহলে নতুন child process-টি নিজের ফোল্ডার বদলাত, তারপর শেষ হয়ে যেত — shell যেখানে ছিল সেখানেই থাকত! তাই cd, export, exit-এর মতো যেসব কাজ shell-এর নিজের অবস্থা বদলায়, সেগুলো shell-এর ভেতরেই লেখা থাকে। এক বাক্যে: process নিজের জগৎ নিজে বদলাতে পারে, অন্যের নয় — এই নীতি পর্ব ০৮ আর ১০-এ বারবার ফিরবে।

৬ · Redirection আর pipe — ০, ১, ২-এর খেলা

প্রতিটি process জন্ম থেকেই তিনটি খোলা “চ্যানেল” নিয়ে আসে। Kernel এগুলোকে ছোট সংখ্যা দিয়ে চেনে — এর নাম file descriptor (fd):

fdনামডিফল্টভাবে যুক্তবদলানোর চিহ্ন
0stdin — ইনপুটকিবোর্ড (terminal)< file, |-এর ডান পাশ
1stdout — সাধারণ আউটপুটপর্দা (terminal)> file, >> file, |-এর বাঁ পাশ
2stderr — ত্রুটির বার্তাপর্দা (terminal)2> file, 2>&1

প্রোগ্রাম জানেই না তার fd 1 পর্দায় যুক্ত, না কোনো ফাইলে। সে শুধু kernel-কে বলে “fd 1-এ এই লেখাটি পাঠাতে চাই”। Redirection মানে প্রোগ্রাম চালানোর ঠিক আগে shell এই সংযোগগুলো বদলে দেয়:

  • ls > list.txt — stdout ফাইলে। ফাইল না থাকলে তৈরি, থাকলে আগের লেখা মুছে নতুন করে।
  • echo hi >> log.txt — শেষে যোগ (append), আগেরটা থাকে।
  • wc -l < list.txt — stdin ফাইল থেকে।
  • cmd 2> err.txt — শুধু ত্রুটির বার্তা ফাইলে; cmd 2>/dev/null — ত্রুটির বার্তা ফেলে দেওয়া (/dev/null একটি বিশেষ device, যা সব লেখা গিলে ফেলে)।
  • cmd > all.txt 2>&1 — দুটোই একই ফাইলে। ক্রমটি জরুরি: shell বাঁ থেকে ডানে পড়ে; আগে fd 1 ফাইলে যায়, তারপর “fd 2 = fd 1 এখন যেখানে”। উল্টো লিখলে (2>&1 > all.txt) fd 2 থেকে যায় পর্দায়।

sort data.txt > data.txt লিখবেন না — shell কমান্ড চালানোর আগেই >-এর জন্য ফাইলটি খালি করে ফেলে, ফলে sort পড়ে একটি খালি ফাইল আর আপনার ডেটা হারায়। আলাদা নামে লিখে পরে mv করুন।

Pipe — এক প্রোগ্রামের আউটপুট, পরেরটির ইনপুট

a | b মানে: a-এর stdout সরাসরি b-এর stdin-এ। মাঝে কোনো ফাইল নেই — kernel-এর ভেতরে একটি ছোট বাফার, যার এক মুখে লেখা হয় আর অন্য মুখ থেকে পড়া হয়। একে বলে pipe. দুটি প্রোগ্রাম একসাথে চলে: a লিখতে থাকে, b পড়তে থাকে।

গার্মেন্টস কারখানার সেলাই লাইনের কথা ভাবুন। একজন কলার লাগান, পাশের জন হাতা, তার পাশের জন বোতাম। কেউ পুরো শার্ট বানান না, কিন্তু টেবিলের ওপর দিয়ে কাপড় গড়িয়ে যেতে যেতে শার্ট তৈরি হয়ে যায় — আর সবাই একসাথে কাজ করছেন। Pipe-ও তাই: প্রতিটি প্রোগ্রাম একটিমাত্র ছোট কাজ ভালোভাবে করে, আর লাইন জুড়ে বড় কাজ হয়।

এই লাইনের কারিগরদের বলে filter — stdin থেকে পড়ে, কিছু বদলে stdout-এ লেখে:

কমান্ডকাজউদাহরণ
wcলাইন (-l), শব্দ (-w), byte (-c) গোনাwc -l < f
sortসাজানো; -n সংখ্যা হিসেবে, -r উল্টোsort -rn
uniqপাশাপাশি একই লাইন একটিতে নামানো; -c গুনে দেখায় — তাই আগে sortsort | uniq -c
grepনকশা মেলে এমন লাইন রাখা; -c গোনা, -i বড়-ছোট অক্ষর সমান, -v উল্টোgrep -i error app.log
cutপ্রতি লাইন থেকে কলাম কাটা; -d বিভাজক, -f কোন কলামcut -d: -f1 /etc/passwd
trঅক্ষর বদলানোtr ' ' '\n' — স্পেসকে নতুন লাইন

একটি চায়ের দোকানের তিনটি অর্ডার নিয়ে দেখা যাক — কোন জিনিস কতবার চাওয়া হয়েছে, এক লাইনে:

printf '%s\n' "rice fish rice" "tea rice" "fish tea tea" > order.txt
cat order.txt
echo "--- lines, words:"
wc -l < order.txt
wc -w < order.txt
echo "--- each word on its own line, counted:"
tr ' ' '\n' < order.txt | sort | uniq -c | sort -rn
echo "--- lines containing tea:"
grep -c tea order.txt
echo "--- first word of each line:"
cut -d' ' -f1 order.txt
echo "extra tea" >> order.txt     # >> appends
wc -l < order.txt
ls missing.txt 2> err.txt         # 2> sends errors to a file
echo "ls exit status: $?"
cat err.txt

আসল আউটপুট: ফাইলের তিন লাইন, তারপর 3 লাইন আর 8 শব্দ। মূল pipeline-টি দিল 3 tea, 3 rice, 2 fish — tr প্রতিটি শব্দকে আলাদা লাইনে আনল, sort একই শব্দ পাশাপাশি আনল, uniq -c গুনল, শেষের sort -rn বেশি থেকে কমে সাজাল। (৩-৩ সমান হলে -r পুরো লাইনটিকে উল্টো ক্রমে তুলনা করে, তাই tea আগে) grep -c tea দিল 2 — খেয়াল করুন, এটি লাইন গোনে, শব্দ নয়; তৃতীয় লাইনে tea দুবার থাকলেও একবার গোনা হলো। cut দিল rice, tea, fish. >>-এর পর লাইন হলো 4.

শেষ অংশটি সবচেয়ে শিক্ষণীয়: ls missing.txt ব্যর্থ হলো, কিন্তু পর্দায় কোনো ত্রুটি আসেনি — কারণ fd 2 গিয়েছিল err.txt-এ। ls exit status: 2, আর cat err.txt দেখাল ls: cannot access 'missing.txt': No such file or directory. সাধারণ আউটপুট আর ত্রুটি আলাদা চ্যানেলে থাকে বলেই এভাবে আলাদা করা যায়।

এবার ভেতরে তাকানো যাক। ls -l | grep txt > out.txt লিখে Enter চাপলে shell ঠিক কী কী করে? নিচের ডায়াগ্রামে প্রতিটি ধাপে কোন process-এর কোন fd কোথায় যুক্ত, খেয়াল করুন:

  1. bashparentপড়ে, ভাঙে, চালায়
  2. ls -lchild ১fd 1 → pipe
  3. pipekernel বাফারলেখার মুখ → পড়ার মুখ
  4. grep txtchild ২fd 0 ← pipe
  5. out.txtফাইলgrep-এর fd 1
fd টেবিল — কে কোথায় যুক্ত
bash: fd 3, 4
ls: fd 1
ls: fd 2
grep: fd 0
grep: fd 1
$?
ধাপ ১shell যা পড়ল
ls -l | grep txt > out.txt⏎
ধাপ ২শব্দ আর operator
[ls] [-l]  |  [grep] [txt] > [out.txt]
কমান্ড ১: ls -l          (stdout → pipe)
কমান্ড ২: grep txt       (stdin ← pipe, stdout → out.txt)
ধাপ ৩expansion — বদলানোর কিছু আছে?
~ ?  $VAR ?  $(...) ?  * ? [ ] ?  উদ্ধৃতিচিহ্ন ?
→ কিছুই নেই; শব্দগুলো যেমন ছিল তেমনই থাকল
ধাপ ৪bash → kernel
pipe(fds)   // kernel: fds[0] = 3 (পড়া), fds[1] = 4 (লেখা)
ধাপ ৫child ১-এর ভেতরে
fork()                     // bash-এর কপি
dup2(4, 1)                 // stdout = pipe-এর লেখার মুখ
close(3); close(4)
execve("/usr/bin/ls", ["ls","-l"], env)
ধাপ ৬child ২-এর ভেতরে
fork()
dup2(3, 0)                 // stdin = pipe-এর পড়ার মুখ
close(3); close(4)
fd = open("out.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666)
dup2(fd, 1); close(fd)     // stdout = out.txt
execve("/usr/bin/grep", ["grep","txt"], env)
ধাপ ৭bash অপেক্ষা করে
close(3); close(4)
waitpid(ls)   waitpid(grep)
ls লেখে → pipe → grep পড়ে → out.txt
ls শেষ → pipe-এর লেখার মুখ বন্ধ → grep পায় EOF → grep শেষ
$? = grep-এর exit status;  prompt আবার দেখায়
  1. লাইন পড়া। আপনি Enter চাপলেন। Terminal পুরো লাইনটি shell-এর stdin-এ পাঠাল, আর bash সেটি পড়ল। এখনো কোনো প্রোগ্রাম চলেনি।
  2. শব্দ আর চিহ্নে ভাঙা। bash লাইনটিকে শব্দে (token) ভাঙল আর দুটি বিশেষ চিহ্ন চিনল: | লাইনটিকে দুটি কমান্ডে ভাগ করে, আর > out.txt শুধু দ্বিতীয় কমান্ডের stdout-এর নির্দেশ। খেয়াল করুন, > আর out.txt কখনো grep-এর argument হবে না — grep পাবে শুধু ["grep", "txt"].
  3. Expansion. bash দেখে কোথাও ~, $VAR, $(…), wildcard বা উদ্ধৃতিচিহ্ন আছে কি না, থাকলে বদলায় — wildcard থেকে ফাইলের তালিকা ঠিক এই ধাপেই আসে। এই লাইনে কিছুই নেই, তাই শব্দগুলো অপরিবর্তিত। কমান্ড দুটির পূর্ণ ঠিকানা (/usr/bin/ls, /usr/bin/grep) PATH থেকে খোঁজা হয় — কীভাবে, তা অংশ ৮-এ।
  4. Pipe তৈরি। bash kernel-কে pipe() system call দিয়ে একটি pipe বানাতে বলে। Kernel একটি বাফার বানায় আর bash-কে দুটি নতুন fd দেয় — সাধারণত সবচেয়ে ছোট খালি নম্বর, যেমন 3 (পড়ার মুখ) আর 4 (লেখার মুখ)। এখনো bash একাই, আর দুটি মুখই তার হাতে।
  5. বাঁ পাশ: ls. bash fork করে একটি child বানায়। Child-টি bash-এরই কপি, তাই 3 আর 4 তারও আছে। সে dup2(4, 1) দিয়ে নিজের stdout pipe-এর লেখার মুখে জোড়ে, বাড়তি দুটি মুখ বন্ধ করে, তারপর exec দিয়ে নিজের ভেতরে ls প্রোগ্রাম বসায়। fd টেবিল exec-এর পরও থেকে যায় — তাই ls না জেনেই pipe-এ লেখে। fd 2 বদলায়নি, তাই ls-এর ত্রুটি পর্দাতেই আসবে।
  6. ডান পাশ: grep আর out.txt. দ্বিতীয় child: stdin pipe-এর পড়ার মুখে জোড়ে, তারপর redirection-এর জন্য open() দিয়ে out.txt খোলে — ফাইল না থাকলে তৈরি, থাকলে খালি (O_TRUNC) — আর সেটিকে fd 1 বানায়। তারপর exec করে grep. Redirection তাই হয় child-এর ভেতরে, exec-এর ঠিক আগে; bash-এর নিজের stdout পর্দাতেই থাকে। দুটি child এখন একসাথে চলছে, তাই ls ফোল্ডার পড়ার আগে না পরে out.txt তৈরি হলো, তার ওপর নির্ভর করে out.txt নিজেই তালিকায় আসতে পারে।
  7. অপেক্ষা আর ফল। bash নিজের হাতের 3 আর 4 বন্ধ করে — না করলে pipe-এর লেখার মুখ খোলা থাকত আর grep কখনো “শেষ” (EOF) পেত না, চিরকাল অপেক্ষা করত। তারপর দুটি child-এর জন্য waitpid. ls লিখে শেষ হলে grep EOF পায়, বাকি লাইন লিখে শেষ হয়। Pipeline-এর $? হয় শেষ কমান্ড grep-এর exit status, আর prompt ফেরে। এই পুরো প্রক্রিয়া পর্ব ১০ আর ১২-এ নিজে C-তে লিখবেন, আর পর্ব ৫৯-এর ক্যাপস্টোনে নিজের shell-এ।

Pipe-এর ধারণা Bell Labs-এর Doug McIlroy-র। ১৯৬৪ সালের একটি অভ্যন্তরীণ নোটে তিনি লেখেন, প্রোগ্রামগুলোকে বাগানের হোসপাইপের মতো জোড়া লাগানো যাওয়া উচিত। ১৯৭৩ সালে Ken Thompson Unix-এ pipe() system call আর shell-এ | যোগ করেন, আর অল্প সময়ের মধ্যেই অনেক প্রোগ্রাম stdin থেকে পড়ে stdout-এ লেখার মতো করে নতুনভাবে লেখা হয়। McIlroy পরে Unix দর্শনকে সংক্ষেপে বলেন: প্রতিটি প্রোগ্রাম একটি কাজ ভালোভাবে করুক, প্রোগ্রামগুলো একসাথে কাজ করুক, আর লেখার stream-ই হোক সবার সাধারণ ভাষা।

৭ · Exit status আর &&, || — কমান্ড কি সফল হলো?

প্রতিটি process শেষ হওয়ার সময় তার parent-কে একটি ছোট সংখ্যা দিয়ে যায় — exit status. নিয়মটি অনেকের কাছে উল্টো লাগে: 0 মানে সফল, আর ১ থেকে ২৫৫ পর্যন্ত যেকোনো সংখ্যা মানে কোনো না কোনো সমস্যা। যুক্তিটি সহজ — সফল হওয়ার উপায় একটাই, কিন্তু ব্যর্থ হওয়ার কারণ অনেক, তাই ব্যর্থতার জন্য অনেক সংখ্যা রাখা হয়েছে। Shell শেষ কমান্ডের exit status রাখে $?-এ। কয়েকটি প্রচলিত মান: ১ সাধারণ ব্যর্থতা, ২ — অনেক টুলে গুরুতর সমস্যা বা ভুল ব্যবহার, ১২৬ ফাইল পাওয়া গেছে কিন্তু চালানোর অনুমতি নেই, ১২৭ কমান্ডই পাওয়া যায়নি, আর ১২৮-এর বেশি মানে process কোনো signal-এ মারা গেছে (১২৮ + signal নম্বর, যেমন Ctrl+C-এ ১৩০ — পর্ব ১৩)।

Exit status দেখে পরের কাজ ঠিক করা যায়:

  • a && b — a সফল (০) হলে তবেই b. যেমন mkdir build && cd build: ফোল্ডার না বানাতে পারলে ঢোকার চেষ্টাই করবে না।
  • a || b — a ব্যর্থ হলে তবেই b. যেমন cd /data || exit 1: ফোল্ডারে ঢুকতে না পারলে script এখানেই থামবে, ভুল জায়গায় কাজ চালাবে না।
  • a ; b — ফল যাই হোক, পরপর দুটিই।
true;  echo "true   -> $?"
false; echo "false  -> $?"
grep -q root /etc/passwd;    echo "grep found     -> $?"
grep -q zzzz /etc/passwd;    echo "grep not found -> $?"
ls /no/such/dir 2>/dev/null; echo "ls missing     -> $?"
nosuchcmd 2>/dev/null;       echo "unknown cmd    -> $?"
echo "--- && : next command only on success"
mkdir -p backup && echo "backup folder ready"
cd /no/such/dir 2>/dev/null && echo "never printed"
echo "--- || : next command only on failure"
cd /no/such/dir 2>/dev/null || echo "cd failed, still in $(basename "$PWD")"
false && echo "A" || echo "B"
echo "--- a pipeline reports its LAST command's status"
grep zzzz /etc/passwd | wc -l; echo "pipeline      -> $?"
set -o pipefail
grep zzzz /etc/passwd | wc -l; echo "with pipefail -> $?"

আসল রানের আউটপুট: true -> 0, false -> 1, grep found -> 0, grep not found -> 1, ls missing -> 2, unknown cmd -> 127. খেয়াল করুন, grep-এর ১ মানে “ত্রুটি” নয়, “মিল পাওয়া যায়নি” — প্রতিটি টুলের man page-এ লেখা থাকে কোন সংখ্যার কী মানে। তারপর backup folder ready ছাপা হলো, কিন্তু “never printed” কখনো আসেনি; ||-এর পর এল cd failed, still in wandbox — ব্যর্থ cd ফোল্ডার একটুও বদলায়নি। false && echo "A" || echo "B" ছাপাল শুধু B.

শেষ চারটি লাইন একটি গুরুত্বপূর্ণ ফাঁদ দেখায়। grep কিছু না পেয়ে ব্যর্থ হলো, wc -l ছাপাল 0 — কিন্তু pipeline -> 0! কারণ pipeline-এর exit status শুধু শেষ কমান্ডের। set -o pipefail চালুর পর একই লাইন দিল with pipefail -> 1 — এখন কোনো একটি অংশ ব্যর্থ হলেই পুরো pipeline ব্যর্থ। গুরুত্বপূর্ণ script-এ তাই অনেকে শুরুতে set -euo pipefail লেখেন; তবে এর নিজস্ব কিছু সূক্ষ্মতা আছে, তাই অন্ধভাবে নয়, বুঝে ব্যবহার করুন।

a && b || c দেখতে if-else-এর মতো, কিন্তু পুরোপুরি এক নয়: a সফল হয়ে b ব্যর্থ হলে c-ও চলবে। সত্যিকারের দুই পথের সিদ্ধান্তে if a; then b; else c; fi লিখুন। আর $? শুধু ঠিক আগের কমান্ডের ফল রাখে — মাঝে একটি echo চালালেই মানটি হারিয়ে যায়।

৮ · Environment variable, PATH আর প্রথম script

Shell-এ variable বানানো সহজ: city=Dhaka (সমান চিহ্নের দুপাশে স্পেস দেবেন না), আর ব্যবহার echo "$city". কিন্তু এই variable শুধু shell-এর নিজের — shell যে প্রোগ্রাম চালায়, সে এটি দেখে না। export city লিখলে variable-টি হয় environment variable: এরপর shell যতগুলো child process চালাবে, প্রত্যেকে জন্মের সময় এর একটি কপি পাবে। কপি — তাই child নিজের মান বদলালেও parent-এর মান বদলায় না।

  • env বা printenv — সব environment variable দেখায়; echo "$HOME" একটি।
  • পরিচিত কয়েকটি: HOME (home ফোল্ডার), USER, PWD, SHELL, LANG (ভাষা ও অক্ষর-সংকেত), আর সবচেয়ে গুরুত্বপূর্ণ PATH.
  • LANG=C sort f — শুধু এই একটি কমান্ডের জন্য variable বদলানো।

PATH — shell প্রোগ্রাম খোঁজে কোথায়?

আপনি ls লিখলেন, কিন্তু shell কীভাবে জানল যে আসলে চালাতে হবে /usr/bin/ls? উত্তর PATH — কোলন (:) দিয়ে আলাদা করা ফোল্ডারের একটি তালিকা। নামের মধ্যে / না থাকলে shell এই তালিকা বাঁ থেকে ডানে ঘুরে প্রথম যে ফোল্ডারে ওই নামের চালানো যায় এমন ফাইল পায়, সেটিই চালায়।

$ echo $PATHখোঁজার তালিকা, বাঁ থেকে ডানে
/home/rahim/bin:/usr/local/bin:/usr/bin:/bin
  1. ~/binls?নেই
  2. /usr/local/binls?নেই
  3. /usr/binlsপাওয়া গেছে
  4. fork + exec/usr/bin/lschild process
bash-এর হিসাব
যাচাই
ফল
hash টেবিল
  1. নামে / আছে? ./greet.sh বা /usr/bin/ls-এর মতো নামে / থাকলে shell কিছু খোঁজে না, সরাসরি সেই ঠিকানার ফাইল চালায়। ls-এ / নেই, তাই খুঁজতে হবে।
  2. আগে shell-এর নিজের ভান্ডার। bash ক্রমানুসারে দেখে: এই নামে কোনো alias (ছদ্মনাম) আছে? shell function? builtin (যেমন cd, echo)? আগে খুঁজে মনে রাখা hash টেবিলে আছে? কোথাও মিললে PATH-এ যায়ই না। (Ubuntu-র interactive shell-এ সাধারণত ls একটি alias — ls --color=auto; সেটি বদলে দেওয়ার পর আসল ls খোঁজা চলে।) এখানে কিছু নেই।
  3. PATH-এর প্রথম ফোল্ডার। /home/rahim/bin/ls — এই নামে কোনো ফাইল নেই। পরেরটিতে।
  4. দ্বিতীয় ফোল্ডার। /usr/local/bin/ls — নেই। (আপনি নিজে কোনো প্রোগ্রাম ইনস্টল করলে প্রায়ই এখানে যায়।)
  5. তৃতীয় ফোল্ডার — পাওয়া গেছে। /usr/bin/ls আছে আর তাতে চালানোর (x) অনুমতি আছে। খোঁজা এখানেই থামে — প্রথম মিলটিই জেতে, /bin আর দেখাই হয় না। bash ঠিকানাটি hash টেবিলে লিখে রাখে, যাতে পরেরবার পুরো তালিকা ঘুরতে না হয়।
  6. চালানো। এবার পরিচিত দুই ধাপ: fork করে child, child-এ execve("/usr/bin/ls", …). কোথাও না পেলে bash বলত ls: command not found, আর $? হতো ১২৭. ফাইল পেয়েও চালানোর অনুমতি না থাকলে Permission denied আর ১২৬.

PATH-এ নিজের ফোল্ডার যোগ করতে export PATH="$HOME/bin:$PATH" লিখে ~/.bashrc ফাইলে রেখে দেন অনেকে (bash প্রতিটি নতুন interactive shell খোলার সময় এটি পড়ে)। তবে নিরাপত্তার একটি নিয়ম মানুন: PATH-এ কখনো . (বর্তমান ফোল্ডার) রাখবেন না, বিশেষ করে শুরুতে। রাখলে কেউ যদি কোনো শেয়ার করা ফোল্ডারে ls নামে একটি ক্ষতিকর ফাইল রেখে দেয়, সেই ফোল্ডারে ঢুকে ls লিখলেই সেটি চলে যেতে পারে। এজন্যই বর্তমান ফোল্ডারের প্রোগ্রাম চালাতে Linux-এ ./ লিখতে হয় — এটি অসুবিধা নয়, ইচ্ছাকৃত সুরক্ষা।

প্রথম shell script

একই কমান্ডগুলো বারবার টাইপ না করে একটি ফাইলে লিখে রাখা যায় — এটিই shell script. প্রথম লাইনে থাকে shebang: #!/bin/bash. আপনি ./greet.sh চালালে kernel ফাইলের প্রথম দুটি byte দেখে — #! পেলে বোঝে এটি machine code নয়, script; তখন সে লাইনের বাকি অংশে লেখা প্রোগ্রামটি (/bin/bash) চালায় আর ফাইলের নামটি তার হাতে দেয়। তাই একই নিয়মে #!/usr/bin/env python3 লিখে Python script-ও চালানো যায়। নিচের ব্লকে একটি heredoc (<<'EOF' থেকে EOF পর্যন্ত লেখা যায় cat-এর stdin-এ) দিয়ে script ফাইলটি বানানো হয়েছে, তারপর চালানো হয়েছে:

cat > greet.sh <<'EOF'
#!/bin/bash
# greet.sh - a first script
name=${1:-friend}
echo "Hello, $name! I got $# argument(s)."
echo "CITY from parent: ${CITY:-unset}"
if [ "$PPID" -eq "$SHELL_PID" ]; then
  echo "I am a child process of the shell"
fi
exit 0
EOF
ls -l greet.sh | cut -c1-10
./greet.sh 2>/dev/null || echo "exit $?: found, but not executable yet"
chmod +x greet.sh
ls -l greet.sh | cut -c1-10
export SHELL_PID=$$
CITY=Dhaka
./greet.sh Rahim
export CITY
./greet.sh Rahim Karim
echo "script exit status: $?"
greet.sh 2>/dev/null || echo "exit $?: greet.sh is not in PATH"
type cd
type ls
echo "$PATH" | tr ':' '\n' | head -n 4

আসল রানে প্রথমে এল -rw-r--r-- — নতুন ফাইলে পড়া-লেখার অনুমতি আছে, চালানোর (x) নেই; তাই ./greet.sh ব্যর্থ হলো: exit 126: found, but not executable yet. chmod +x-এর পর হলো -rwxr-xr-x (file permission-এর পুরো গল্প পর্ব ৪৭-এ)। প্রথমবার চালালে: Hello, Rahim! I got 1 argument(s). আর CITY from parent: unset — কারণ CITY=Dhaka তখনো export হয়নি, শুধু shell-এর নিজের variable ছিল। export CITY-এর পর দ্বিতীয়বার: I got 2 argument(s). আর CITY from parent: Dhaka. দুবারই এল I am a child process of the shell — script-এর $PPID (parent-এর PID) মিলে গেছে shell-এর $$-এর সাথে, অর্থাৎ script সত্যিই shell-এর child process হিসেবে চলেছে (PID সংখ্যাগুলো প্রতিবার আলাদা হয়, তাই ছাপানো হয়নি, শুধু সম্পর্ক যাচাই করা হয়েছে)।

তারপর script exit status: 0 — script-এর শেষ লাইন exit 0. ./ ছাড়া greet.sh লিখলে: exit 127: greet.sh is not in PATH — বর্তমান ফোল্ডার PATH-এ নেই, ঠিক যেমনটি ওপরে বলা হয়েছে। type cd দিল cd is a shell builtin, type ls দিল ls is /usr/bin/ls, আর PATH-এর প্রথম চারটি ফোল্ডার: /usr/local/sbin, /usr/local/bin, /usr/sbin, /usr/bin. আপনার কম্পিউটারে PATH একটু ভিন্ন হবে।

“Permission denied” এলে সমাধান প্রায় সবসময় নিজের ফাইলে chmod +x বা chmod u+x — chmod 777 নয়, sudo-ও নয়। 777 মানে কম্পিউটারের যেকোনো user ফাইলটি বদলে ফেলতে পারবে; আর sudo দিয়ে script চালালে তার প্রতিটি ভুল root-এর ক্ষমতায় ঘটে। সমস্যাটি কোন অনুমতির, আগে বুঝুন — পর্ব ৪৭.

৯ · দ্রুত কাজের অভ্যাস, OS-এর মানচিত্র আর Windows-এর সাথে তুলনা

আঙুলের অভ্যাস — history আর Tab

  • Tab completion: নামের শুরু লিখে Tab — shell বাকিটা পূরণ করে। একাধিক মিল থাকলে দুবার Tab চাপলে তালিকা দেখায়। বানান ভুল এড়ানোর সবচেয়ে সহজ উপায়।
  • ↑ / ↓ — আগের কমান্ডগুলো; history — পুরো তালিকা (bash এটি সাধারণত ~/.bash_history ফাইলে রাখে)।
  • Ctrl+R — পুরোনো কমান্ড খোঁজা: কয়েকটি অক্ষর লিখলেই মিলে যাওয়া শেষ কমান্ডটি আসে।
  • Ctrl+C — চলমান প্রোগ্রাম থামানো (আসলে একটি signal পাঠানো, পর্ব ১৩); Ctrl+D — “ইনপুট শেষ” (EOF); Ctrl+L — পর্দা পরিষ্কার।
  • Terminal-এ কপি-পেস্ট: Linux terminal-এ সাধারণত Ctrl+Shift+C / V, কারণ শুধু Ctrl+C প্রোগ্রাম থামায়।

Password বা গোপন token কমান্ড লাইনে সরাসরি লিখবেন না (যেমন mysql -pMySecret) — সেটি history ফাইলে থেকে যায়, আর চলার সময় অন্য user-রাও process-এর তালিকায় argument দেখতে পারে। প্রোগ্রাম নিজে যখন password জিজ্ঞেস করে, তখন দিন।

এই পর্বের কমান্ড আর OS-এর ধারণা — মানচিত্র

আপনি যা করেনভেতরে OS-এর যে ধারণাগভীরে দেখবেন
কোনো বাইরের কমান্ড চালানোনতুন process: fork, exec, waitপর্ব ০৮, ১০
cd, pwdprocess-এর নিজস্ব cwd, chdir system callপর্ব ০৫, ০৮
>, <, 2>file descriptor, open, dup2পর্ব ৪১
|kernel-এর pipe বাফার, দুটি process একসাথেপর্ব ১২
$?, &&, ||exit status, parent-এর waitপর্ব ১০
Ctrl+C, job চালানোsignal, process group, job controlপর্ব ১১, ১৩
ls, mv, rm, pathdirectory entry, inode, path lookupপর্ব ৪২, ৪৩
chmod +x, $ বনাম #user, permission, rootপর্ব ৪৭
export, PATHenvironment exec-এর সময় child-এ কপি হয়পর্ব ১০

Windows PowerShell-এ একই কাজ

Windows-এর নিজস্ব আধুনিক shell হলো PowerShell. সুবিধার জন্য এতে ls, cd, cat, pwd-এর মতো নাম alias হিসেবে রাখা আছে, তাই প্রথম দেখায় Linux-এর মতো লাগে — কিন্তু ভেতরে চলে PowerShell-এর নিজস্ব cmdlet, option-ও আলাদা। সবচেয়ে বড় পার্থক্য: PowerShell-এর pipe লেখা নয়, object পাঠায় (ফাইলের নাম, আকার, সময় আলাদা আলাদা ঘর হিসেবে)।

কাজbash (Linux)PowerShell (Windows)
কোথায় আছিpwdGet-Location (alias pwd)
তালিকা, লুকানোসহls -laGet-ChildItem -Force (alias ls, dir)
ফোল্ডার বদলcd ../docsSet-Location ..\docs (alias cd)
ফোল্ডার / ফাইল তৈরিmkdir -p a/b, touch fNew-Item -ItemType Directory a\b -Force, New-Item f
কপি / সরানো / মোছাcp, mv, rm -rCopy-Item, Move-Item, Remove-Item -Recurse
ফাইল দেখাcat, head -n 5, tail -fGet-Content, -TotalCount 5, -Wait
লেখা খোঁজাgrep -i error fSelect-String -Pattern error f
গোনা, সাজানোwc -l, sort | uniqMeasure-Object -Line, Sort-Object -Unique
সাহায্য, কমান্ড কোথায়man ls, type lsGet-Help, Get-Command
Environment, PATH$HOME, $PATH (বিভাজক :)$env:USERPROFILE, $env:Path (বিভাজক ;)
শেষ কমান্ডের ফল$? (সংখ্যা)$? (True/False), বাইরের প্রোগ্রামের সংখ্যা $LASTEXITCODE
সফল হলে / ব্যর্থ হলে&&, ||PowerShell 7-এ আছে; পুরোনো Windows PowerShell 5.1-এ নেই
Get-ChildItem *.txt | Where-Object Length -gt 1KB | Sort-Object Length -Descending
# উদাহরণ: object পাঠানো হচ্ছে — Length আলাদা একটি ঘর, তাই cut দিয়ে কলাম কাটতে হয় না

আরও গভীরে: Linux-এ pipe-এর বাফার ডিফল্টভাবে ৬৪ KiB (kernel 2.6.11 থেকে; fcntl দিয়ে বদলানো যায়)। বাফার ভরে গেলে লেখক process-কে kernel ঘুম পাড়িয়ে রাখে, খালি হলে জাগায় — তাই দ্রুত লেখক ধীর পাঠককে ডুবিয়ে দিতে পারে না; এটিই প্রাকৃতিক backpressure. Pipeline-এর সব process bash একটি process group-এ রাখে, যাতে Ctrl+C সবাইকে একসাথে পৌঁছায় (পর্ব ১১)। আর cd ..-এ একটি সূক্ষ্মতা আছে: bash ডিফল্টভাবে $PWD লেখাটি থেকে শেষ অংশ কেটে “যুক্তিসঙ্গত” (logical) parent বের করে, অথচ kernel .. entry মেনে আসল (physical) parent-এ যায় — symlink-এর ভেতর দিয়ে এলে দুটি আলাদা হতে পারে; cd -P আর pwd -P আসলটা দেখায়।

পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:

  • Terminal আর shell-এর পার্থক্য কী? — terminal লেখা দেখানো আর নেওয়ার জানালা; shell সেই লেখা বুঝে প্রোগ্রাম চালানো একটি user প্রোগ্রাম (bash, zsh).
  • > আর >>-এর পার্থক্য? 2>&1 কী করে? — প্রথমটি ফাইল খালি করে লেখে, দ্বিতীয়টি শেষে যোগ করে; 2>&1 fd 2-কে fd 1 যেখানে যুক্ত, সেখানে জোড়ে।
  • Pipe (|) আর redirection (>)-এর পার্থক্য? — pipe দুটি process-কে kernel বাফার দিয়ে জোড়ে, দুটি একসাথে চলে; redirection একটি process-এর fd-কে ফাইলে জোড়ে।
  • cd কেন builtin? — working directory প্রতিটি process-এর নিজস্ব; আলাদা প্রোগ্রাম হলে শুধু child-এর ফোল্ডার বদলাত, shell-এর নয়।
  • Exit status ০ আর ১২৭-এর মানে? — ০ সফল; ১২৭ কমান্ড খুঁজে পাওয়া যায়নি (PATH-এ নেই বা বানান ভুল)।

১০ · AI-এর যুগে টার্মিনাল — কমান্ড কপি করার আগে

“আমার ফোল্ডারের সব ছবি এক জায়গায় আনার কমান্ড দিন” — AI মুহূর্তে একটি লাইন দেয়। বেশিরভাগ সময় ঠিকই থাকে। কিন্তু terminal-এ ভুলের দাম অন্য জায়গার চেয়ে বেশি: এখানে কোনো “Undo” বোতাম নেই, আর AI আপনার কম্পিউটার দেখতে পায় না — কোন OS, কোন distro, কোন ফোল্ডারে আছেন, কিছুই জানে না। তাই প্রশ্নে এগুলো বলে দেওয়া আর উত্তর যাচাই করা আপনার দায়িত্ব।

AI-এর ভুলযা ঘটেকীভাবে ধরবেন
ভুল OS বা distro-র কমান্ড (Mac-এ GNU-র sed -i, Fedora-য় apt, Windows-এ bash-এর লাইন)ত্রুটি, বা আরও খারাপ — চুপচাপ ভিন্ন আচরণপ্রশ্নে OS আর shell বলুন; man বা --help দিয়ে option মিলিয়ে নিন
অকারণে sudo বা chmod 777নিরাপত্তা দুর্বল হয়, ভুলের ক্ষতি বহুগুণ“sudo ছাড়া কীভাবে?” জিজ্ঞেস করুন; কোন অনুমতি লাগছে বুঝুন
উদ্ধৃতিচিহ্ন ছাড়া variable (rm -rf $DIR/*)স্পেসওয়ালা নামে ভুল ফাইল, খালি variable-এ ভয়ংকর ফলসব variable "$DIR"; আগে echo বা ls দিয়ে শুকনো পরীক্ষা
পুরোনো টুল (ifconfig, service) বা পুরোনো option“command not found” বা ভিন্ন আউটপুটআপনার সংস্করণের man page দেখুন
pipeline-এর ব্যর্থতা লুকানো, ls-এর আউটপুট parse করাscript “সফল” বলে, কিন্তু কাজ হয়নিpipefail, glob বা find ব্যবহার; ইচ্ছে করে ভুল ইনপুট দিয়ে পরীক্ষা
ইন্টারনেট থেকে script নামিয়ে সরাসরি sudo bash-এ পাঠানোঅদেখা কোড root হিসেবে চলেআগে ফাইলে নামিয়ে পড়ুন, উৎস যাচাই করুন

AI-কে terminal-এর কাজ দেওয়ার সময় পরিবেশ আর নিরাপত্তার শর্ত স্পষ্ট করুন:

আমি Windows 11-এ WSL 2-এর Ubuntu 24.04 ব্যবহার করি, shell হলো bash. ~/photos ফোল্ডার আর তার সব সাব-ফোল্ডার থেকে .jpg আর .JPG ফাইলগুলো ~/all-photos-এ কপি (মুভ নয়) করতে চাই; একই নামের ফাইল থাকলে ওপরে লেখা যাবে না। ১. sudo বা chmod ছাড়া একটি কমান্ড বা ছোট script দাও। ২. প্রতিটি অংশ (option, উদ্ধৃতিচিহ্ন, wildcard) সহজ বাংলায় ব্যাখ্যা করো। ৩. আসল কাজের আগে কোন কমান্ড দিয়ে শুধু দেখে নিতে পারি কোন ফাইলগুলো কপি হবে (dry run), সেটাও দাও। ৪. নামে স্পেস বা বাংলা অক্ষর থাকলে তোমার কমান্ড কেন ঠিক থাকবে, বলো। ৫. কোন অংশ macOS-এ আলাদা হতো, জানাও।

AI-এর দেওয়া terminal কমান্ড চালানোর আগে — চেকলিস্ট

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

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১cd, export আর exit shell-এর ভেতরে লেখা, অথচ ls, grep আলাদা প্রোগ্রাম। এই ভাগাভাগি কি শুধু সুবিধার জন্য, নাকি OS-এর গঠনই এটি বাধ্য করে?

বাধ্য করে — আর কারণটি OS-এর একটি মৌলিক নীতি: প্রতিটি process-এর নিজস্ব অবস্থা থাকে, আর এক process আরেকটির অবস্থা সরাসরি বদলাতে পারে না। Working directory, environment variable, খোলা file descriptor — এগুলো kernel প্রতিটি process-এর জন্য আলাদা করে রাখে (process-এর এই হিসাবের খাতাকে বলে PCB, পর্ব ০৮)।

Shell যখন কোনো বাইরের প্রোগ্রাম চালায়, সে fork করে নিজের একটি কপি বানায়, তারপর সেই কপিতে exec করে। Child যা-ই বদলাক — ফোল্ডার, variable — সবই child-এর নিজের কপিতে, আর child শেষ হলে সব হারিয়ে যায়। তাই কেউ যদি /usr/bin/cd নামে একটি প্রোগ্রাম লেখেন, সেটি ঠিকঠাক chdir() ডাকবে, সফলও হবে — কিন্তু shell একচুলও নড়বে না। (মজার ব্যাপার, POSIX মানদণ্ড মেনে কিছু সিস্টেমে সত্যিই /usr/bin/cd ফাইল থাকে; সেটি মূলত শুধু “এই ফোল্ডারে যাওয়া যায় কি না” যাচাইয়ে কাজে লাগে।)

একই যুক্তিতে export (shell-এর নিজের environment বদলায়), exit (shell নিজেকে শেষ করে), source বা . (একটি ফাইল নিজের ভেতরে চালায়, child-এ নয়) builtin হতে বাধ্য। এজন্যই ./setup.sh-এর ভেতরে cd বা export করলে script শেষে আপনার shell-এ তার কোনো প্রভাব থাকে না, কিন্তু source setup.sh করলে থাকে — Python-এর virtual environment চালু করতে source venv/bin/activate লিখতে হয় ঠিক এই কারণে।

অন্যদিকে echo বা printf-এর মতো কিছু কমান্ড builtin হয় শুধু গতির জন্য — একটি লুপে হাজারবার fork আর exec করার খরচ বাঁচাতে। এদের আলাদা প্রোগ্রাম সংস্করণও থাকে (type -a echo দুটিই দেখায়)। প্রথম Unix shell-এও chdir shell-এর ভেতরে ছিল, কারণ সমস্যাটি তখনই স্পষ্ট ছিল।

মূল উপলব্ধি: builtin আর বাইরের প্রোগ্রামের ভাগ আসলে process isolation-এর আয়না। “এই কাজটি কোন process-এর অবস্থা বদলাচ্ছে?” — এই প্রশ্নটি করতে শিখলে পর্ব ১০-এর fork/exec আর পর্ব ১২-এর IPC অনেক সহজ লাগবে।

প্র ০২Unix-এর pipe পাঠায় সাদামাটা লেখা, PowerShell-এর pipe পাঠায় object. কোনটি ভালো নকশা? দুটি দর্শনের trade-off কী?

১৯৭৩ সালে Unix-এ pipe আসার সময় সিদ্ধান্ত হয়েছিল, প্রোগ্রামগুলোর সাধারণ ভাষা হবে byte-এর stream, বাস্তবে লাইনে ভাগ করা লেখা। এর শক্তি বিশাল: যেকোনো ভাষায় লেখা, যেকোনো সময়ের প্রোগ্রাম একে অপরের সাথে জোড়া যায় — ১৯৭০-এর দশকের sort আজকের কোনো Rust টুলের আউটপুটও সাজাতে পারে। মানুষও আউটপুট পড়তে পারে, কোনো বিশেষ টুল লাগে না। Kernel-এর কাছেও pipe খুব সরল — শুধু একটি বাফার, কোনো ধরন বোঝার দায় নেই।

দুর্বলতাও স্পষ্ট। প্রতিটি প্রোগ্রামকে আগেরটির আউটপুট “পড়ে বুঝতে” হয়। ls -l-এর পঞ্চম কলাম আকার — কিন্তু ফাইলের নামে স্পেস থাকলে বা অন্য locale-এ তারিখের ধরন বদলালে কলাম গোনা ভেঙে যায়। এজন্যই অভিজ্ঞরা বলেন ls-এর আউটপুট parse করবেন না। লেখা-ভিত্তিক নকশায় এই ভঙ্গুরতা থেকেই যায়।

২০০২ সালে Microsoft-এর Jeffrey Snover “Monad Manifesto” নামে একটি নথিতে ভিন্ন পথ প্রস্তাব করেন, যা ২০০৬ সালে PowerShell হিসেবে আসে। Windows-এর জগৎ লেখা-ফাইলের চেয়ে বেশি চলত API আর structured ডেটায়, তাই সেখানে pipe দিয়ে .NET object পাঠানো যৌক্তিক ছিল: Get-ChildItem | Where-Object Length -gt 1KB-এ Length একটি আসল সংখ্যা, কলাম গোনার ঝামেলা নেই। দাম: সব অংশকে একই runtime-এর ভেতরে থাকতে হয়; বাইরের সাধারণ প্রোগ্রামের সাথে কথা বলতে আবার লেখাতেই ফিরতে হয়।

আধুনিক Linux জগৎ মাঝামাঝি পথ নিয়েছে: অনেক টুল এখন JSON আউটপুট দিতে পারে (ip -j addr, lsblk --json), আর jq-এর মতো টুল সেটি pipe-এর ভেতরেই কাঠামো ধরে কাজ করে — stream থাকল লেখারই, কিন্তু লেখার ভেতরে কাঠামো।

মূল উপলব্ধি: interface যত সরল, তত বেশি জিনিস তাতে জোড়া যায়, কিন্তু প্রতিটি অংশকে তত বেশি কাজ নিজে করতে হয়। “সর্বজনীন কিন্তু অগঠিত” বনাম “গঠিত কিন্তু সীমিত” — OS-এর নকশায় এই trade-off বারবার ফিরবে, যেমন file system-এর byte stream বনাম ডেটাবেসের টেবিল।

প্র ০৩Apple কেন ২০১৯ সালে ডিফল্ট shell bash থেকে zsh-এ বদলাল? আর ২০১৪ সালের “Shellshock” থেকে shell-এর নিরাপত্তা নিয়ে কী শেখার আছে?

লাইসেন্সের গল্প। ২০০৯ সালে bash ৪.০ থেকে GNU প্রজেক্ট bash-কে GPL সংস্করণ ৩ লাইসেন্সে প্রকাশ করতে থাকে। Apple এই লাইসেন্সের শর্ত নিয়ে স্বস্তিতে ছিল না, তাই macOS-এ বহু বছর ধরে GPLv2-এর শেষ সংস্করণ bash ৩.২ রেখে দেয়। পুরোনো shell মানে নতুন সুবিধা নেই, আর নিরাপত্তা-সংশোধন নিজেদেরই সামলাতে হয়। শেষে macOS Catalina (২০১৯) থেকে নতুন অ্যাকাউন্টের ডিফল্ট হয় zsh — যার লাইসেন্স MIT-ধরনের উদার, আর যা bash-এর প্রায় সব interactive সুবিধা দেয়। এটি মনে করিয়ে দেয়, OS-এর ডিফল্ট সিদ্ধান্তগুলো সবসময় শুধু প্রযুক্তি নয়, আইন আর ব্যবসাও ঠিক করে।

Shellshock. ২০১৪ সালের সেপ্টেম্বরে প্রকাশ পায়, bash-এ বহু বছর ধরে একটি গুরুতর ত্রুটি ছিল (CVE-2014-6271 এবং সংশ্লিষ্ট কয়েকটি)। bash environment variable-এর ভেতর দিয়ে function পাঠানোর সুবিধা দিত, আর সেই variable পড়ার সময় function-এর পরে লেখা বাড়তি কমান্ডও চালিয়ে দিত। সমস্যা হলো, অনেক ওয়েব সার্ভার (পুরোনো CGI পদ্ধতিতে) ব্যবহারকারীর পাঠানো HTTP header environment variable-এ বসিয়ে bash চালাত — ফলে বাইরের কেউ সার্ভারে কমান্ড চালানোর সুযোগ পেতে পারত। কয়েক দিনের মধ্যে সংশোধন আসে, আর বিশ্বজুড়ে সার্ভার আপডেটের হুড়োহুড়ি পড়ে।

রক্ষার শিক্ষা: (১) environment-ও একটি ইনপুট — বাইরের ডেটা যেখানেই ঢোকে, সেখানে বিশ্বাস করা যায় না; (২) system-এর shell আর প্যাকেজ নিয়মিত আপডেট রাখুন (Ubuntu-তে sudo apt update && sudo apt upgrade); (৩) ওয়েব অ্যাপ বা script-এ ব্যবহারকারীর ইনপুট কখনো shell-এর লাইনে জুড়ে দেবেন না, প্রোগ্রামকে argument-এর তালিকা হিসেবে দিন; (৪) সার্ভিসকে সর্বনিম্ন প্রয়োজনীয় ক্ষমতায় চালান, যাতে ত্রুটি থাকলেও ক্ষতি সীমিত থাকে (পর্ব ৪৮–৪৯)।

মূল উপলব্ধি: shell একটি সাধারণ user প্রোগ্রাম, আর সব প্রোগ্রামের মতো এতেও বাগ থাকতে পারে। কিন্তু সে সবকিছুর সংযোগস্থলে বসে বলে তার বাগের প্রভাব বিশাল — তাই “কোন ডেটা shell পর্যন্ত পৌঁছাচ্ছে” প্রশ্নটি নিরাপত্তার কেন্দ্রে।

প্র ০৪rm কেন Recycle Bin-এ পাঠায় না? ফাইল “মুছে ফেললে” ডিস্কে আসলে কী ঘটে, আর মোছা ফাইল কি সত্যিই চলে যায়?

Unix-এর rm কোনো ডেস্কটপ ফিচার নয়, এটি সরাসরি kernel-এর unlink() system call-এর পাতলা মোড়ক। আর unlink আক্ষরিক অর্থেই “সংযোগ কাটে”: ফোল্ডারের তালিকা থেকে নাম আর ফাইলের আসল তথ্যের (inode, পর্ব ৪৩) মধ্যের সংযোগটি সরিয়ে দেয়। Recycle Bin বা Trash হলো GUI-র বানানো সুবিধা — ফাইলটি আসলে মোছে না, একটি লুকানো ফোল্ডারে mv করে। Linux ডেস্কটপের ফাইল ম্যানেজারও তাই করে; কিন্তু terminal-এর rm নয়। Unix-এর জন্ম বহু-user সার্ভারে, যেখানে টুলগুলো ছোট, অনুমানযোগ্য আর script-এ ব্যবহারযোগ্য রাখাই লক্ষ্য ছিল।

মজার ব্যাপার, unlink-এর পরও ফাইলের তথ্য সাথে সাথে মোছে না। Kernel প্রতিটি inode-এর জন্য গোনে কতগুলো নাম (hard link) তাকে নির্দেশ করে আর কতগুলো process তাকে খুলে রেখেছে। দুটোই শূন্য হলে তবেই block-গুলো “খালি” তালিকায় যায়। এজন্যই কোনো প্রোগ্রাম log ফাইল খুলে রাখা অবস্থায় সেটি rm করলে ডিস্কের জায়গা খালি হয় না — সার্ভারে “ফাইল মুছলাম, তবু ডিস্ক ভরা!” রহস্যের একটি পরিচিত কারণ।

Block খালি তালিকায় গেলেও পুরোনো byte নতুন লেখা না আসা পর্যন্ত ডিস্কে পড়ে থাকতে পারে — তাই HDD থেকে কখনো কখনো recovery টুল দিয়ে কিছু উদ্ধার করা যায়। SSD-তে TRIM কমান্ড OS-কে খালি block জানিয়ে দেয়, আর ড্রাইভ সেগুলো আগেভাগে মুছে ফেলতে পারে, ফলে উদ্ধারের সম্ভাবনা অনেক কমে। উল্টো দিকে, গোপন ফাইল নিরাপদে মুছতে শুধু rm যথেষ্ট নয়; full-disk encryption-ই সবচেয়ে নির্ভরযোগ্য সুরক্ষা।

বাস্তব শিক্ষাও আছে। ২০১৫ সালের শুরুতে Linux-এর জন্য Steam-এর একটি script-এ rm -rf "$STEAMROOT/"* ধরনের লাইন ছিল; কিছু অবস্থায় variable-টি খালি হয়ে যাওয়ায় কয়েকজন ব্যবহারকারীর home ফোল্ডারের ফাইল মুছে যাওয়ার অভিযোগ ওঠে। রক্ষার উপায়: set -u (অসংজ্ঞায়িত variable-এ script থামে), ${VAR:?} (খালি হলে ত্রুটি দিয়ে থামে), মোছার আগে path যাচাই। GNU rm বহু বছর ধরে ডিফল্টভাবে /-এর ওপর recursive মোছা প্রত্যাখ্যান করে (--preserve-root), কিন্তু অন্য যেকোনো ফোল্ডারের জন্য এমন কোনো রক্ষাকবচ নেই।

মূল উপলব্ধি: “মোছা” আসলে কয়েক স্তরের ঘটনা — নাম সরানো, গোনা শূন্য হওয়া, block খালি হওয়া, তথ্য ওপরে লেখা। প্রতিটি স্তর বোঝা থাকলে ডিস্ক ভরা থাকার রহস্য, ডেটা উদ্ধার আর নিরাপদ মোছা — তিনটি প্রশ্নেরই উত্তর মেলে।

প্র ০৫Shell script দিয়ে তো অনেক কিছুই হয়। তাহলে বড় প্রোগ্রাম bash-এ না লিখে Python বা Go-তে লেখার পরামর্শ কেন দেওয়া হয়? Shell-এর সীমা কোথায়?

Shell-এর আসল শক্তি অন্য প্রোগ্রাম জোড়া লাগানো — ফাইল সরানো, টুল চালানো, ফল দেখে সিদ্ধান্ত নেওয়া। Linux-এর বুট প্রক্রিয়ার পুরোনো init script, সার্ভার deploy, CI/CD pipeline-এর ধাপ — এসব জায়গায় shell script আজও সবখানে। কয়েক লাইনে যা হয়, অন্য ভাষায় তার জন্য অনেক বেশি কোড লাগত।

কিন্তু shell-এর নকশা এসেছে interactive ব্যবহার থেকে, আর তার কিছু সিদ্ধান্ত বড় প্রোগ্রামে ফাঁদ হয়ে দাঁড়ায়:

  • সব কিছুই লেখা। variable-এ ধরন নেই; সংখ্যা, তালিকা, নাম সবই string. উদ্ধৃতিচিহ্ন ছাড়া variable স্পেসে ভেঙে যায় আর wildcard হিসেবেও বদলে যেতে পারে।
  • ভুল চুপচাপ চলে যায়। ডিফল্টভাবে কোনো কমান্ড ব্যর্থ হলেও script পরের লাইনে চলে; pipeline-এর মাঝের ব্যর্থতা লুকিয়ে থাকে — এই পর্বের runnable ব্লকেই দেখেছেন। set -e সাহায্য করে, কিন্তু এর ব্যতিক্রমের নিয়মগুলো এত সূক্ষ্ম যে অভিজ্ঞরাও ভুল করেন।
  • প্রতিটি কমান্ড একটি নতুন process. একটি লুপে লাখবার grep ডাকলে লাখবার fork আর exec — যা Python-এর একটি function call-এর চেয়ে বহুগুণ ধীর। পর্ব ১০-এ এই খরচ নিজে মেপে দেখবেন।
  • বহনযোগ্যতা। #!/bin/sh লিখে bash-এর সুবিধা ব্যবহার করলে Debian বা Ubuntu-তে বিপদ — সেখানে /bin/sh হলো ছোট, দ্রুত shell dash. macOS-এর BSD টুলের পার্থক্য তো আছেই। POSIX মানদণ্ড মানলে এই সমস্যা কমে।

Google-এর প্রকাশ্য shell style guide-এর মতো অনেক নির্দেশিকা মোটামুটি একই পরামর্শ দেয়: script যদি মোটামুটি ১০০ লাইন ছাড়ায়, বা জটিল ডেটা কাঠামো লাগে, তাহলে সাধারণ কোনো প্রোগ্রামিং ভাষায় লেখা ভালো। আর shell-এর লেখা যাচাইয়ে shellcheck নামের বিনামূল্যের টুলটি উদ্ধৃতিচিহ্ন বাদ পড়া থেকে শুরু করে অনেক সাধারণ ভুল ধরে।

মূল উপলব্ধি: shell হলো আঠা — প্রোগ্রাম জোড়ার জন্য অসাধারণ, কিন্তু নিজেই বড় কাঠামো বানানোর জন্য নয়। কোন কাজে কোন টুল, এই বিচারবোধই একজন systems ইঞ্জিনিয়ারকে আলাদা করে।

অনুশীলন

প্রথম দুটি কাগজ-কলমে করুন, তারপর নিজের terminal-এ বা উপরের runnable ব্লকে মিলিয়ে দেখুন।

  1. Path হাতে হিসাব করুন: আপনার home /home/mitu, আর আপনি আছেন /var/log-এ। নিচের কমান্ডগুলো পরপর চালালে প্রতিটির পর pwd কী দেখাবে? (সব ফোল্ডার আছে বলে ধরুন) — (ক) cd .. (খ) cd ./lib/../tmp (গ) cd ~/projects/os (ঘ) cd ../../.. (ঙ) cd -

    (ক) /var — log-এর parent. (খ) /var/tmp — /var থেকে ./ মানে এখানেই, lib-এ ঢুকে .. দিয়ে আবার /var, তারপর tmp. (গ) /home/mitu/projects/os — ~ shell বদলে দেয় home দিয়ে, বর্তমান অবস্থান এখানে অপ্রাসঙ্গিক। (ঘ) /home — তিন ধাপ ওপরে: projects → mitu → /home. (ঙ) /home/mitu/projects/os — cd - ঠিক আগের ফোল্ডারে ফেরে আর পথটি ছাপায়ও।

  2. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন কী ছাপা হবে — (ক) printf 'b\na\nb\nc\nb\n' | sort | uniq -c | sort -rn | head -n 1 (খ) echo one > f; echo two >> f; echo three > f; cat f (গ) ls /nope 2>/dev/null && echo yes || echo no (ঘ) printf 'b\na\nb\n' | uniq | wc -l

    (ক) 3 b — সাজানোর পর b তিনটি পাশাপাশি আসে; uniq -c গুনে 1 a, 3 b, 1 c; উল্টো সংখ্যা-ক্রমে প্রথম লাইন 3 b (uniq -c সংখ্যার আগে কয়েকটি স্পেস বসায়)।

    (খ) শুধু three — তৃতীয় > ফাইলটি খালি করে নতুন করে লিখেছে।

    (গ) no — ls ব্যর্থ (exit 2), ত্রুটির বার্তা /dev/null-এ গেছে, তাই &&-এর অংশ বাদ, ||-এর অংশ চলে।

    (ঘ) 3 — sort ছাড়া uniq শুধু পাশাপাশি একই লাইন মেলায়; b, a, b-তে কোনো দুটি একই লাইন পাশাপাশি নেই, তাই তিনটিই থাকে। এটিই কারণ যে uniq-এর আগে প্রায় সবসময় sort লাগে।

  3. কমান্ড লিখুন: /etc/passwd ফাইলের প্রতিটি লাইনে কোলন (:) দিয়ে আলাদা ৭টি ঘর থাকে; শেষ ঘরটি সেই user-এর login shell. (ক) কোন shell কতজন user ব্যবহার করে, বেশি থেকে কম ক্রমে দেখানোর একটি pipeline লিখুন। (খ) শুধু /bin/bash ব্যবহারকারীদের নাম (প্রথম ঘর) একটি ফাইল bash_users.txt-এ রাখুন।

    (ক) cut -d: -f7 /etc/passwd | sort | uniq -c | sort -rn — cut সপ্তম ঘর কাটে, তারপর পরিচিত গোনার জুটি। সাধারণ Ubuntu-তে সবচেয়ে বেশি আসে /usr/sbin/nologin — অনেক system সার্ভিসের নিজস্ব user থাকে, কিন্তু তাদের login করার অনুমতি নেই; এটিও একটি নিরাপত্তা-নকশা (পর্ব ৪৭)।

    (খ) grep ':/bin/bash$' /etc/passwd | cut -d: -f1 > bash_users.txt — $ মানে “লাইনের শেষে”, তাই নামের মাঝে কোথাও bash থাকলে ভুল মিল হবে না। বিকল্প: awk -F: '$7 == "/bin/bash" {print $1}' /etc/passwd. উপরের যেকোনো runnable ব্লকে লাইন দুটি বসিয়ে চালিয়ে দেখতে পারেন।

  4. পর্যবেক্ষণ — builtin চিনুন: নিজের terminal-এ চালান: type cd pwd echo ls grep আর type -a echo pwd. কোনগুলো builtin, কোনগুলো ফাইল? তারপর bash -c 'cd /tmp; pwd'; pwd চালিয়ে দুটি pwd-এর ফল তুলনা করুন। কেন আলাদা?

    cd, pwd, echo সাধারণত builtin; ls আর grep ফাইল (যেমন /usr/bin/ls; Ubuntu-তে interactive shell-এ ls alias হিসেবেও দেখাতে পারে)। type -a দেখাবে echo আর pwd-এর builtin-এর পাশাপাশি /usr/bin/echo, /usr/bin/pwd-ও আছে — এরা গতির জন্য builtin, বাধ্য হয়ে নয়।

    দ্বিতীয় পরীক্ষায় প্রথম লাইন দেখাবে /tmp, দ্বিতীয়টি আপনার আগের ফোল্ডার। bash -c একটি নতুন child shell process চালায়; সে নিজের cwd বদলে /tmp ছাপায়, তারপর শেষ হয়ে যায়। আপনার আসল shell-এর cwd কখনো ছোঁয়া হয়নি — process-এর অবস্থা প্রতিটি process-এর নিজস্ব। এটিই প্র ০১-এর উত্তরের হাতে-কলমে প্রমাণ।

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

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

ক্রমls -l | grep txt > out.txt চালাতে shell যা যা করে, সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ⌨️ পুরো লাইনটি পড়া
  2. ✂️ শব্দ আর |, > চিহ্নে ভাঙা
  3. 🔁 expansion — ~, $VAR, wildcard বদলানো
  4. 🚰 pipe() দিয়ে kernel-এ pipe তৈরি
  5. 🍴 প্রতিটি পাশের জন্য fork, fd জোড়া (redirection), তারপর exec
  6. ⏳ নিজের pipe-মুখ বন্ধ করে অপেক্ষা, শেষে $? রাখা

বর্তমান ফোল্ডার /home/rahim/shop/src. cd ../docs সফল হলে pwd কী দেখাবে?

  • /home/rahim/shop/src/docs
  • /home/rahim/shop/docs
  • /docs
  • /home/rahim/docs

bash-এ rm *.log লিখলে *.log নকশাটিকে ফাইলের নামের তালিকায় বদলায় কে?

  • rm প্রোগ্রাম নিজে
  • kernel
  • shell, কমান্ড চালানোর আগেই
  • terminal emulator

কোন কমান্ডে একটি প্রোগ্রামের সাধারণ আউটপুট আর ত্রুটির বার্তা দুটোই all.txt ফাইলে যাবে?

  • prog > all.txt 2>&1
  • prog 2>&1 > all.txt
  • prog 2> all.txt
  • prog | all.txt

bash-এ false && echo A || echo B চালালে কী ছাপা হবে?

  • A
  • A তারপর B
  • কিছুই না
  • B

একটি কমান্ড চালানোর পর echo $? দেখাল 127. সবচেয়ে সম্ভাব্য কারণ কী?

  • কমান্ডটি সফল হয়েছে
  • ফাইলটি আছে, কিন্তু চালানোর অনুমতি নেই
  • কমান্ডটি খুঁজে পাওয়া যায়নি
  • Ctrl+C দিয়ে থামানো হয়েছে

নিজের বানানো backup.sh চালাতে ./backup.sh লিখলে Permission denied আসছে। সবচেয়ে সঠিক সমাধান কোনটি?

  • chmod 777 backup.sh
  • chmod +x backup.sh
  • sudo ./backup.sh
  • শুধু backup.sh লেখা

শব্দকোষ

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

মনে রাখুন

  1. Terminal জানালা, shell কমান্ড বোঝা user প্রোগ্রাম, kernel আসল সম্পদের মালিক — shell-ও system call দিয়ে অনুরোধ করে।
  2. কমান্ড = নাম · option · argument; বাইরের প্রতিটি কমান্ড মানে fork + exec করা একটি নতুন process.
  3. Path: / দিয়ে শুরু হলে absolute, না হলে বর্তমান ফোল্ডার থেকে relative; cd builtin কারণ cwd প্রতিটি process-এর নিজস্ব।
  4. fd 0, 1, 2 = stdin, stdout, stderr; >, >>, <, 2> এগুলোর সংযোগ বদলায়, | দুটি process-কে kernel-এর pipe দিয়ে জোড়ে; wildcard মেলায় shell.
  5. ০ মানে সফল; &&, || exit status দেখে চলে; PATH-এ প্রথম মিলটিই চলে; script-এ shebang আর chmod +x — আর rm, sudo, AI-এর কমান্ড সবসময় বুঝে, যাচাই করে।

পরের পর্বে: terminal-এ আপনি যখন একটি অক্ষর টাইপ করেন, কিবোর্ড থেকে সেটি কীভাবে CPU-র নজরে আসে? OS-এর চোখে হার্ডওয়্যার — CPU, interrupt, memory hierarchy আর I/O.

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

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