এই post পড়ার আগে Part 2: readdir(), dirent, closedir() পড়ে নাও — সেখানে directory থেকে file list করা শেখানো হয়েছে।
Part 2 এর শেষে একটা জিনিস notice করেছিলাম — আমাদের myls চালালে output এ . আর .. দেখাচ্ছে, কিন্তু real ls দেখায় না। তখন বলেছিলাম "এটা পরে fix করব।" এই part এ সেই কাজটাই করব।
কিন্তু শুধু fix করব না — আরেকটু গভীরে যাব। কেন এই behavior? এটা কি intentional নাকি accidental? আর এই জায়গাটায় Unix এর একটা মজার ইতিহাস আছে।
Hidden Files — একটা Accidental Feature এর জন্ম
Unix এর শুরুর দিকে, ls প্রোগ্রামে . (current directory) আর .. (parent directory) দেখাত। এগুলো দেখতে annoying লাগত কারণ প্রতিটা directory list এ এরা থাকতই। তখন কেউ একজন — ধারণা করা হয় Ken Thompson বা Dennis Ritchie — এই দুটো entry skip করার জন্য একটা quick fix লিখলেন:
if (name[0] == '.') continue;
এটা লেখার কথা ছিল এরকম:
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
কিন্তু সেটা না লিখে shorter version লেখা হলো — "যদি নামের প্রথম character . হয়, skip করো।" Bug! এতে শুধু . আর .. না, যেকোনো dot দিয়ে শুরু হওয়া file skip হয়ে গেল।
কিন্তু তখন কেউ এটাকে bug মনে করল না — বরং এটাকে feature হিসেবে ব্যবহার শুরু করে দিল। .bashrc, .gitignore, .ssh — এই convention টা সেই একটা lazy shortcut থেকেই জন্ম নিয়েছে।
আজকে Unix/Linux এর hidden file মানেই dot দিয়ে শুরু — এটা কোনো official specification না, একটা accidental convention।
আমাদের সমস্যা
Part 2 এর myls এর output এ সমস্যাটা দেখি:
$ ./myls
. 32 bytes
.. 32 bytes
myls 32 bytes
myls.c 32 bytes
Real ls দিলে:
$ ls
myls myls.c
দুটো পার্থক্য:
.ও..দেখাচ্ছে — দেখানো উচিত না by default- Hidden files (যেকোনো dot file) দেখাচ্ছে —
-aflag ছাড়া দেখানো উচিত না
Fix টা simple:
if (entry->d_name[0] == '.') continue;
কিন্তু আমরা চাই real ls এর মতো behavior — ./myls -a দিলে সব দেখাবে, না দিলে hide থাকবে। এজন্য command-line argument handle করতে হবে।
argc ও argv — Program কিভাবে Argument পায়
main function এর signature টা এতদিন ignore করে এসেছি:
int main(int argc, char **argv)
এই দুটো parameter দিয়ে program তার command-line argument গুলো পায়।
argc(argument count): কতগুলো argument আছে। Program এর নিজের নামও count হয়, তাই minimum value সবসময়1।argv(argument values): সব argument এর array।argv[0]সবসময় program এর নাম।
উদাহরণ — ./myls -a /tmp run করলে:
argc = 3
argv[0] = "./myls"
argv[1] = "-a"
argv[2] = "/tmp"
Memory তে argv দেখতে কেমন?
argv আসলে একটা pointer এর pointer — char **। প্রতিটা element একটা string এর শুরুর address। শেষে NULL থাকে sentinel হিসেবে।
argv
│
▼
┌─────────┐
│ ptr 0 │───────▶ "./myls\0"
├─────────┤
│ ptr 1 │───────▶ "-a\0"
├─────────┤
│ ptr 2 │───────▶ "/tmp\0"
├─────────┤
│ NULL │
└─────────┘
Live experiment
নিজে হাতে দেখতে চাইলে এই ছোট্ট program টা লিখে run করো:
// argc_test.c
#include <stdio.h>
int main(int argc, char **argv) {
printf("argc = %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
Compile করে চালাও:
$ gcc -o argc_test argc_test.c
$ ./argc_test hello world -a
argc = 4
argv[0] = ./argc_test
argv[1] = hello
argv[2] = world
argv[3] = -a
লক্ষ্য করো — argv[0] এ program এর নাম automatically চলে এসেছে, যদিও আমরা সেটা type করিনি। এটা shell এর কাজ — program execute করার সময় নামটাও argument হিসেবে পাঠায়।
String Compare — == কেন কাজ করে না
Flag check করতে গেলে argv[1] == "-a" লিখতে ইচ্ছে করে। কিন্তু C তে এটা কাজ করবে না।
C তে string হলো character array এর pointer। == দিয়ে দুটো pointer compare করলে দেখে দুটো একই memory address কিনা — দুটো string এর content একই কিনা সেটা না।
এজন্য strcmp() ব্যবহার করতে হয়:
#include <string.h>
strcmp("hello", "hello") // returns 0 (equal)
strcmp("abc", "xyz") // returns negative (abc comes before xyz)
strcmp("xyz", "abc") // returns positive
0 মানে equal। তাই flag check করতে:
if (strcmp(argv[i], "-a") == 0) {
show_hidden = 1;
}
Updated myls.c
এখন সব মিলিয়ে updated code লিখি:
// myls.c
#include <dirent.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv) {
// -a flag আছে কিনা check করি
int show_hidden = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-a") == 0) {
show_hidden = 1;
}
}
DIR *dir_stream;
struct dirent *dir_read;
dir_stream = opendir(".");
if (dir_stream == NULL) {
perror("Error while trying to open directory");
return 1;
}
while ((dir_read = readdir(dir_stream)) != NULL) {
// show_hidden না থাকলে dot file skip করি
if (!show_hidden && dir_read->d_name[0] == '.') {
continue;
}
printf("%s\n", dir_read->d_name);
}
if (closedir(dir_stream) == -1) {
perror("Can't close the dir");
return -1;
}
return 0;
}
কোডটা ধাপে ধাপে কী করছে
1. Flag scan:
int show_hidden = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-a") == 0) show_hidden = 1;
}
i = 1 থেকে শুরু — argv[0] হলো program এর নাম, সেটা skip করি। সব argument check করি, -a পেলে show_hidden = 1 করি।
2. Hidden file filter:
if (!show_hidden && dir_read->d_name[0] == '.') {
continue;
}
দুটো condition একসাথে:
show_hiddenfalse (মানে-aদেওয়া হয়নি)- নামের প্রথম character
.
দুটোই true হলে continue — এই entry skip করে পরেরটায় যাও।
Output Comparison
Compile করে test করি:
gcc -Wall -Werror -g myls.c -o myls
তিনটা version পাশাপাশি দেখি — real ls, আমাদের ./myls, আর ./myls -a:
┌────────────────┬────────────────┬─────────────────┐
│ $ ls │ $ ./myls │ $ ./myls -a │
├────────────────┼────────────────┼─────────────────┤
│ myls │ myls │ . │
│ myls.c │ myls.c │ .. │
│ │ │ myls │
│ │ │ myls.c │
└────────────────┴────────────────┴─────────────────┘
lsআর./myls— identical। Hidden files hide হচ্ছে।./myls -a— সব দেখাচ্ছে,.ও..সহ।
Real ls এর মতো behavior!
GDB দিয়ে argv দেখা
কৌতূহল থামাতে পারছি না — GDB দিয়ে দেখি argv memory তে আসলে কী আছে:
GDB session টা দেখতে এরকম হবে:
$ gdb ./myls
(gdb) b main
Breakpoint 1 at 0x11a9: file myls.c, line 6.
(gdb) run -a
Starting program: /work/myls -a
Breakpoint 1, main (argc=2, argv=0x7fffffffe328) at myls.c:6
(gdb) p argc
$1 = 2
(gdb) p argv[0]
$2 = 0x7fffffffe5e4 "./myls"
(gdb) p argv[1]
$3 = 0x7fffffffe5eb "-a"
(gdb) p argv
$4 = (char **) 0x7fffffffe328
(gdb) p *argv
$5 = 0x7fffffffe5e4 "./myls"
Output এ দেখবে:
argc = 2argv[0] = "./myls"— program এর নামargv[1] = "-a"— আমাদের flagargvনিজে একটা memory address — কারণ এটা pointer
একটা interesting জিনিস: p argv দিলে একটা address দেখাবে, p *argv দিলে argv[0] দেখাবে। কারণ argv হলো char** — pointer এর pointer।
আজকে কী শিখলাম
| বিষয় | কী শিখলাম |
|---|---|
| Hidden files | Unix convention — dot দিয়ে শুরু হলে hidden। একটা accidental bug থেকে জন্ম নিয়েছে। |
argc | Command-line argument এর count। Program নিজেও count হয়। |
argv | Argument এর array। argv[0] = program name। |
strcmp() | String compare এর সঠিক উপায়। == দিয়ে string compare হয় না C তে। |
continue | Loop এর current iteration skip করে পরেরটায় যায়। |
| Boolean flag pattern | int show_hidden = 0 → argument loop → if (!show_hidden && ...) |
উপসংহার
একটা ছোট্ট if check আর argc/argv — এটুকু দিয়েই আমাদের myls এখন real ls এর মতো hidden files handle করতে পারছে। আর সেই accidental bug এর গল্পটা — এটা আমার কাছে reminder যে অনেক "design decision" আসলে কেউ একদিন lazy ছিল বলে।
পরের part এ argv আরো গভীরে দেখব — path argument নেওয়া, যাতে ./myls /tmp এর মতো যেকোনো directory list করা যায়।
সম্পূর্ণ সিরিজ
| Part | বিষয় |
|---|---|
| Part 1 | opendir(), DIR structure, GDB দিয়ে debug |
| Part 2 | readdir(), dirent struct, closedir() |
| Part 3 (এই post) | Hidden files, argc/argv, -a flag |
| Part 4 | argv deep dive, char**, path argument |

