Bash Globbing: wildcard, nullglob ও extglob বুঝুন
Bash command চলার আগে wildcard কীভাবে filename-এ প্রসারিত হয় তা জানুন। nullglob, dotglob, globstar এবং extglob-এর negation ব্যবহারের বাস্তব উদাহরণ দেখুন।
আপনার কমান্ড চালানোর আগে Bash globbing কী করে
Bash globbing হলো shell-এর এমন একটি প্রক্রিয়া, যেখানে কমান্ড শুরু হওয়ার আগে *.log-এর মতো একটি pattern-কে বাস্তব filename-এর sorted list-এ রূপান্তর করা হয়। rm *.log কখনো rm-এর কাছে pattern পাঠায় না। Bash directory পড়ে, pattern-এর সঙ্গে মেলা নামগুলো রাখে, সেগুলো sort করে, তারপর প্রতিটি নামকে আলাদা argument হিসেবে দিয়ে rm চালায়। কোনো pattern ব্যবহৃত হয়েছিল কি না, rm program তা বুঝতে পারে না।
Globbing নিয়ে প্রায় সব অপ্রত্যাশিত আচরণের কারণ এই একটি বিষয়। Matching shell নিজেই করে, সেই মুহূর্তে বিদ্যমান file-এর বিরুদ্ধে। Command শুধু সম্পূর্ণ list-টি পায়। এর formal নাম pathname expansion। দৈনন্দিন ব্যবহারে globbing বলা হয়, এবং দুটি শব্দের অর্থ একই।
অনুশীলনের জন্য একটি ছোট tree তৈরি করুন
নিচের প্রতিটি উদাহরণ একই ফাইলগুলোর ওপর চলে। এগুলো একটি temporary directory-তে তৈরি করুন, যাতে আপনার গুরুত্বপূর্ণ কোনো ফাইল কাছাকাছি না থাকে এবং cleanup একটি command-এই করা যায়।
lab=$(mktemp -d)
cd "$lab"
mkdir -p logs archive/2025 archive/2026
touch app.log app.log.1 app.log.2 app.log.10 error.log debug.LOG
touch notes.txt notes.md README 'weekly report.log'
touch .env .apprc
touch logs/nginx.log logs/nginx.log.1
touch archive/2025/dec.log archive/2026/jan.logmktemp -d একটি নতুন directory তৈরি করে এবং তার path ফেরত দেয়। $(...) সেই path-টি lab variable-এ সংরক্ষণ করে। এই syntax আপনার কাছে নতুন হলে, command substitution কীভাবে একটি command-এর output সংরক্ষণ করে নিবন্ধে এর পূর্ণ ব্যাখ্যা আছে।
আপনি কী তৈরি করেছেন তা দেখুন। আপনার shell-ও পরীক্ষা করুন, কারণ শেষে একটি নোট shell-এর version-এর ওপর নির্ভর করে:
ls -a
bash --versionএখানে সবকিছু bash 4.0 এবং পরবর্তী version-এ কাজ করে। শেষের দিকে থাকা globskipdots নোটটি bash 5.2 এবং পরবর্তী version-এর ক্ষেত্রে প্রযোজ্য। August 2026 অনুযায়ী বর্তমান Ubuntu ও Debian release-এ এই version-ই সরবরাহ করা হয়।
পুরো টিউটোরিয়ালে ব্যবহৃত preview command হলো printf '%s\n' PATTERN। এটি প্রতি লাইনে একটি match দেখায় এবং disk-এর কোনো পরিবর্তন করে না। তাই কোনো pattern সম্পর্কে নিশ্চিত না হলেও এটি চালানো নিরাপদ।
তিনটি wildcard: star, question mark এবং brackets
*খালি string-সহ যেকোনো সংখ্যক character-এর ধারার সঙ্গে মেলে।?ঠিক একটি character-এর সঙ্গে মেলে।[...]brackets-এর ভেতরের set থেকে নেওয়া ঠিক একটি character-এর সঙ্গে মেলে।
printf '%s\n' *.log
printf '%s\n' app.log.?
printf '%s\n' app.log.[0-9]
printf '%s\n' notes.*
printf '%s\n' *দ্বিতীয় লাইনটি আপনার তৈরি করা file list-এর সঙ্গে তুলনা করুন। ? একটি এবং কেবল একটি character বোঝায়। তাই দুই অঙ্কের suffix ওই pattern-এর সঙ্গে মেলে না। এটিকে app.log.[0-9]*-এ বিস্তৃত করে আবার চালান এবং পার্থক্যটি দেখুন।
তিনটি wildcard-এর ক্ষেত্রেই দুটি নিয়ম প্রযোজ্য। কোনো wildcard কখনো /-এর সঙ্গে মেলে না। তাই একটি pattern এক directory level-এর মধ্যেই থাকে। আর শুরুতে থাকা . আক্ষরিকভাবে লিখতে হয়। তাই * দিয়ে শুরু হওয়া pattern সব hidden name বাদ দেয়। এই দ্বিতীয় নিয়মের কারণেই rm * আপনার dotfile-গুলো অক্ষত রাখে।
Glob-এর ফলাফল sorted অবস্থায় আসে। এতে বর্তমান locale-এর collation order ব্যবহৃত হয়, যা character ধরে text sort করে। তাই 10 suffix, 2 suffix-এর আগে sort হয়, কারণ তুলনার সময় কোনোটিকেই number হিসেবে পড়া হয় না। printf '%s\n' app.log.* চালানোর পর ls -v app.log.* চালান এবং দুটির তুলনা করুন: ls -v embedded number-এর value অনুযায়ী sort করে, আর pipeline-এ একই কাজ করে sort -V।
অক্ষর-শ্রেণি এবং range
ব্র্যাকেটের ভিতরে একটি তালিকা, একটি range অথবা একটি negation লিখতে পারেন।
[ch]একটি অক্ষরের সঙ্গে মেলে, যা হয়cঅথবাh।[a-f]a থেকে f পর্যন্ত range-এর একটি অক্ষরের সঙ্গে মেলে।[!0-9]digit নয় এমন একটি অক্ষরের সঙ্গে মেলে।[^0-9]-এর অর্থও একই।
printf '%s\n' [dn]*
printf '%s\n' *.[A-Z]*
printf '%s\n' [A-Z]*Range-ই বিভিন্ন মেশিনে ফল ভিন্ন হওয়ার প্রধান কারণ। একটি range সরাসরি ASCII অনুসারে নয়, বর্তমান locale-এর collation order অনুসারে নির্ধারিত হয়। তাই কিছু সিস্টেমে [a-z] uppercase অক্ষরের সঙ্গেও মেলে। Bash-এ একটি globasciiranges option আছে, যা plain ASCII order বাধ্যতামূলক করে। সাম্প্রতিক build-গুলোতে এটি default হিসেবে সক্রিয় থাকে। তাই অনুমান না করে shopt globasciiranges দিয়ে আপনার সিস্টেমের মান পরীক্ষা করুন। কোনো script-কে সব জায়গায় একইভাবে match করতে হলে নিজেই order নির্ধারণ করতে পারেন। এজন্য উপরের দিকে, আলাদা একটি line-এ LC_ALL=C সেট করুন। এতে প্রতিটি range আবার plain byte order ব্যবহার করবে।
এই assignment-টি নিজস্ব command হিসেবে লিখতে হবে। Prefix হিসেবে লিখলে LC_ALL=C ls [a-z]* শুধু ls-এর জন্য locale সেট করে। অন্য কোনো কিছুর জন্য নয়। ls শুরু হওয়ার আগেই shell নিজের locale ব্যবহার করে pattern expand করে ফেলেছে। প্রথম section-এর নিয়মটি এখানেও প্রযোজ্য।
ব্রেস এক্সপ্যানশন globbing নয়
ব্রেস দেখতে wildcard-এর মতো হলেও এর আচরণ সম্পূর্ণ ভিন্ন। ব্রেস এক্সপ্যানশন globbing-এর অনেক আগে চলে এবং এটি কখনো disk পরীক্ষা করে না।
echo file{1,2,3}.txt
echo {01..10}
echo {a..e}
echo {0..20..5}এই ফাইলগুলোর একটিও নেই, তবু expansion ঘটে। এটাই মূল পার্থক্য: glob filesystem-কে একটি প্রশ্ন করে, আর brace শুধু text তৈরি করে। তাই কোনো কিছু খুঁজে বের করার বদলে তৈরি করতে braces ব্যবহার করাই উপযুক্ত।
mkdir -p site/{css,js,img}
cp app.log{,.bak}
touch report-{2025,2026}-{01,02}.csvcp app.log{,.bak} দুটি word-এ expand হয়: app.log এবং app.log.bak। কারণ comma-এর আগে থাকা empty item মূল text-টি আবার তৈরি করে। {01..10}-এ zero padding বজায় থাকে, কারণ যেকোনো endpoint-এর শুরুতে zero থাকলে bash সব generated number-কে একই width-এ pad করে।
Expansion order-ও একটি সাধারণ failure-এর কারণ ব্যাখ্যা করে। Bash প্রথমে braces expand করে, তারপর variables expand করে, এবং শেষে pathname expansion চালায়।
n=5
echo {1..$n}$n একটি number-এ পরিণত হওয়ার আগেই brace expansion শেষ হয়ে যায়। তাই কোনো range তৈরি হয় না। seq 1 "$n" ব্যবহার করুন, অথবা for ((i=1; i<=n; i++)) হিসেবে লেখা C-style loop ব্যবহার করুন।
কোনো pattern-এর সঙ্গে কিছুই match না করলে কী হয়
এটি প্রথম ধরনের ব্যর্থতা, এবং একবার না একবার সবারই এতে ভুল হয়। ডিফল্টভাবে bash কোনো unmatched pattern অপরিবর্তিত রাখে। আপনি যেভাবে লিখেছেন, word-টি ঠিক সেভাবেই command-এ পৌঁছায়। ফলে command *, ., t, m এবং p অক্ষরগুলো পায় এবং সেগুলোকে filename হিসেবে ব্যবহার করার চেষ্টা করে।
printf '%s\n' *.tmpprintf-এর ক্ষেত্রে এটি ক্ষতিকর নয়। কিন্তু কোনো কিছু তৈরি করে এমন command-এর ক্ষেত্রে এটি ক্ষতিকর। .tmp file নেই এমন directory-তে touch *.tmp চালালে filename হিসেবে আক্ষরিকভাবে *.tmp তৈরি হয়। পরে এটি মুছতে quoting ব্যবহার করতে হয়, যাতে shell এটি আবার expand না করে: rm -- '*.tmp'।
দুটি shell option এই নিয়ম পরিবর্তন করে। কোনো কিছুর সঙ্গে match করে না এমন pattern দিয়ে প্রতিটি option একবার পরীক্ষা করুন। তারপর option unset করুন।
shopt -s nullglob
printf '%s\n' *.tmp
shopt -u nullglob
shopt -s failglob
printf '%s\n' *.tmp
shopt -u failglobnullglob unmatched pattern-কে সম্পূর্ণ খালি করে দেয়। ফলে command-টি আপনার লেখা argument-এর চেয়ে কম argument নিয়ে চলে। Loop-এর ক্ষেত্রে এটিই সাধারণত প্রত্যাশিত আচরণ। nullglob বন্ধ থাকলে for f in *.tmp body একবার চালায় এবং f-এ literal pattern থাকে। এটি এমন প্রায় প্রতিটি script-এ bug তৈরি করে যেখানে এই পদ্ধতি ব্যবহার করা হয়। nullglob চালু থাকলে body শূন্যবার চলে।
nullglob-এর নিজস্ব ঝুঁকি আছে, কারণ সব argument বাদ গেলেও command চালু হয়। ls *.nope একটি bare ls-এ পরিণত হয়, যা পুরো directory তালিকাভুক্ত করে। grep -l needle *.nope, grep -l needle-এ পরিণত হয়। পড়ার মতো কোনো file না থাকায় এটি standard input-এর জন্য অপেক্ষা করে এবং দেখে মনে হয় যেন আটকে গেছে। যে loop-এর জন্য nullglob দরকার, তার আশেপাশে এটি চালু করুন। কাজ শেষ হলে আবার বন্ধ করুন।
failglob ভিন্ন পদ্ধতি ব্যবহার করে। কোনো pattern match না করলে এটি error হিসেবে গণ্য হয়। bash error জানায় এবং command একেবারেই চালায় না। Interactive shell-এর জন্য এটি ভালো setting, কারণ ভুল লেখা pattern-কে literal name হিসেবে rm-এর কাছে পাঠানো থেকে এটি বাধা দেয়।
লুকানো ফাইল: dotglob
shopt -s dotglob
printf '%s\n' *
shopt -u dotglobএই block-টি চালিয়ে আগের সাধারণ printf '%s\n' *-এর listing-এর সঙ্গে তুলনা করুন। dotglob শুরুর dot rule সরিয়ে দেয়, তাই * লুকানো নামগুলোকেও মেলায়। . এবং .. entry dotglob চালু থাকলেও সব সময় বাদ থাকে।
dotglob ছাড়া লুকানো ফাইলে পৌঁছানোর প্রচলিত উপায় হলো .*, এবং আগে এটি বিপজ্জনক ছিল। 5.2-এর আগের bash সংস্করণে .*, . এবং ..-কেও মেলাত। তাই chmod -R 755 .*-এর মতো recursive command সরাসরি parent directory-তে ঢুকে যেত। Bash 5.2-তে globskipdots option যোগ করা হয়েছে এবং এটি default হিসেবে enabled থাকে। এই option প্রতিটি expansion থেকে . এবং .. বাদ রাখে। এর ওপর নির্ভর করার আগে shopt globskipdots চালান, কারণ পুরোনো সিস্টেমে এটি থাকবে না।
globstar: গাছের সব স্তর পর্যন্ত মিল খোঁজা
shopt -s globstar
printf '%s\n' **/*.log
printf '%s\n' **/
shopt -u globstarglobstar সেট থাকলে, সম্পূর্ণ path component হিসেবে থাকা ** শূন্য স্তরসহ যেকোনো গভীরতার file ও directory-এর সঙ্গে মিলে। তাই **/*.log বর্তমান directory, logs/ এবং archive/-এর নিচের সবকিছুকেও অন্তর্ভুক্ত করে। শেষে slash থাকা **/ শুধু directory-এর সঙ্গে মেলে। তাই গাছের কাঠামো দ্রুত দেখার জন্য এটি কার্যকর।
globstar সেট না থাকলে ** সাধারণ *-এর মতো আচরণ করে এবং একটি directory-তেই সীমাবদ্ধ থাকে। option বন্ধ রেখে একই pattern চালিয়ে তুলনা করুন। অন্য কোথাও থেকে copy করা recursive pattern প্রত্যাশার তুলনায় অনেক কম ফল দিলে সাধারণত কারণ এটাই। প্রতিটি bash-এ globstar ডিফল্টভাবে বন্ধ থাকে।
আরেকটি সীমাবদ্ধতা জানা দরকার: ** directory-এর symbolic link অনুসরণ করে না। symbolic link দিয়ে যুক্ত করা tree-এর জন্য এর পরিবর্তে find -L প্রয়োজন।
nocaseglob, যেসব নামের uppercase/lowercase এক নয় সেগুলোর জন্য
shopt -s nocaseglob
printf '%s\n' *.log
shopt -u nocaseglobnocaseglob filename matching-এ case উপেক্ষা করে। তাই uppercase .LOG suffix, lowercase .log pattern-এর সঙ্গে মিলে যায়। এই listing-টি প্রথম section-এর listing-এর সঙ্গে তুলনা করুন। option-টি সঙ্গে সঙ্গে আবার বন্ধ করুন। কারণ এটি চালু থাকলে ওই shell-এ পরবর্তী প্রতিটি command-এর matching পরিবর্তিত হয়, এবং এক ঘণ্টা পরে সেটি debug করা বিভ্রান্তিকর হতে পারে।
nocaseglob শুধু filename expansion-এর ক্ষেত্রে প্রযোজ্য। case statement-এর ভেতরে বা double bracket test-এ pattern matching-এর জন্য সংশ্লিষ্ট option হলো nocasematch। এই দুটি option আলাদাভাবে set করতে হয়।
extglob: “এটি নয়” বলা যায় এমন pattern
Extended pattern ডিফল্টভাবে বন্ধ থাকে। এটি আলাদা একটি লাইনে চালু করুন:
shopt -s extglobএগুলোর 5টি রূপ আছে। প্রতিটি রূপ | দিয়ে আলাদা করা pattern-এর তালিকা গ্রহণ করে:
?(list)শূন্য বা 1টি occurrence মেলায়।*(list)শূন্য বা তার বেশি occurrence মেলায়।+(list)1টি বা তার বেশি occurrence মেলায়।@(list)বিকল্পগুলোর মধ্যে ঠিক 1টিকে মেলায়।!(list)কোনো বিকল্পের সঙ্গে মেলে না—এমন যেকোনো কিছুকে মেলায়।
Extended pattern এমন shell-এ পড়াতে হবে যেখানে option আগে থেকেই চালু আছে। তাই নিচের batch-টি bash -O extglob দিয়ে চালু করা একটি ছোট script-এ রাখা হয়েছে। নতুন shell ফাইলের একটি লাইন পড়ার আগেই -O flag একটি shopt option চালু করে। ফলে ফাইলের প্রতিটি pattern সঠিকভাবে parse হয়। এই section-এর শেষ paragraph-এ ordering গুরুত্বপূর্ণ হওয়ার কারণটি ব্যাখ্যা করা হয়েছে।
cat > patterns.sh <<'EOF'
printf '%s\n' @(app|error).log
printf '%s\n' app.log.+([0-9])
printf '%s\n' *.@(md|txt)
printf '%s\n' !(*.log)
printf '%s\n' !(*.log|*.md|README)
EOF
bash -O extglob patterns.sh+([0-9]) এক বা একাধিক digit বোঝায়। তাই এটি যেকোনো দৈর্ঘ্যের numbered suffix মেলাতে পারে। আগের ? limit-এর সমাধান এটাই। পার্থক্যটি দেখতে দুটি pattern পরপর চালানো ভালো।
“সবকিছু বাদে” মেলানোর জন্য negation form ব্যবহার করা হয়। extglob চালু করার প্রধান কারণও এটি। এই form নিয়ে দুটি বিষয় ব্যবহারকারীদের অবাক করে।
এটি file-এর পাশাপাশি directory-ও মেলায়। তাই ls !(*.log) ব্যবহার করলে ফলাফলে directory name-এর বদলে প্রতিটি directory-এর contents তালিকাভুক্ত হয়। নিজস্ব নাম দেখতে ls -d ব্যবহার করুন। বিকল্প হিসেবে printf preview-ও ব্যবহার করতে পারেন।
অন্য glob-এর মতো এটিও dot rule মেনে চলে। তাই !(*.log), .log দিয়ে শেষ হয় না—এমন প্রতিটি দৃশ্যমান name মেলায়। Hidden name মেলাতে dotglob-ও সেট করতে হবে।
!(...)-এর ! হলো glob negation। history expansion এবং bang character-এর সঙ্গে এর কোনো সম্পর্ক নেই। এটি ভিন্ন একটি feature, যা ভিন্ন পর্যায়ে কাজ করে।
আরও একটি সমস্যা আছে, এবং এটি parse time-এ ঘটে। Bash কোনো command-এর অংশ চালানোর আগে পুরো command parse করে। তাই একই লাইনে extglob চালু করে extended pattern ব্যবহার করলে ব্যর্থ হয়: option তখনও বন্ধ থাকা অবস্থায় pattern parse করা হয়। shopt -s extglob-কে আলাদা একটি লাইনে, pattern-এর আগে এবং script-এর শুরুর দিকে রাখুন। Function define করার সময় তার body parse করা হয়। তাই function call-এর আগে নয়, definition-এর আগেই option চালু থাকতে হবে। আগের batch-এর মতো bash -O extglob দিয়ে shell চালু করলে parsing শুরু হওয়ার আগেই ordering নির্ধারিত হয়।
Shell pattern-টি প্রসারিত করে, command নয়
এটি দ্বিতীয় ধরনের ব্যর্থতা। এই ব্যর্থতা একই শ্রেণির অনেক bug-এর কারণ ব্যাখ্যা করে।
grep -l needle *.log
find . -name '*.log'
find . -name *.logপ্রথম লাইনে bash matching করে, এবং grep একটি filename-এর তালিকা পায়। দ্বিতীয় লাইনে pattern-টি quote করা হয়েছে। তাই find পাঁচটি অক্ষর *.log হিসেবে পায় এবং starting point-এর নিচে প্রতিটি depth-এ নিজে matching করে। তৃতীয় লাইনটিই bug। bash প্রথমে শুধু current directory-র বিপরীতে pattern-টি প্রসারিত করে। ফলে find-কে একটি নির্দিষ্ট নাম খুঁজতে বলা হয়। কোনো directory-তে একটি মাত্র match থাকলে এটি নীরবে ভুল জিনিস খোঁজে। দুই বা তার বেশি match থাকলে find usage error দেখায়। কারণ অতিরিক্ত নামগুলো expression-এর প্রত্যাশিত স্থানে চলে আসে।
নিয়মটি সংক্ষিপ্ত। Pattern-টি command-এর জন্য হলে সেটি quote করুন। Pattern-টি shell-এর জন্য হলে quote ছাড়া রাখুন। একই বিভাজন tar ও rsync-এর --exclude pattern এবং grep-এর --include filter-এর ক্ষেত্রেও প্রযোজ্য।
Glob regular expression নয়, যদিও উভয়টিতে একই ধরনের কিছু character থাকে। Glob-এ * যেকোনো দৈর্ঘ্যের character sequence বোঝায়। Regular expression-এ * এর আগের উপাদানটির শূন্য বা তার বেশি পুনরাবৃত্তি বোঝায়। তাই grep '*.log' দেখতে যেমন মনে হয়, তার চেয়ে সম্পূর্ণ ভিন্ন কিছু খোঁজে।
Variable-ও একই ক্রম অনুসরণ করে। Variable expansion-এর পরে globbing চলে। তাই pattern ধারণ করা কোনো unquoted variable directory-র বিপরীতে প্রসারিত হয়।
pat='*.log'
printf '%s\n' $pat
printf '%s\n' "$pat"Quote না করলে bash variable-এর ফলাফলের ওপর glob প্রয়োগ করে। Quote করলে pattern-টি literal থাকে। এর সঙ্গে সম্পর্কিত আরেকটি ফলের ওপর নির্ভর করতে পারেন: globbing থেকে তৈরি word-গুলো space অনুযায়ী আর split হয় না। তাই for f in *.log space থাকা filename-কে একটি নাম হিসেবে পরিচালনা করে, কিন্তু for f in $(ls *.log) সেই নামটিকে দুটি word-এ ভাগ করে। Glob-টি সরাসরি loop করুন।
ধ্বংসাত্মক pattern চালানোর আগে এর ফলাফল দেখুন
নতুন কোনো pattern প্রথমে rm দিয়ে চালাবেন না। আগে harmless কোনো command দিয়ে pattern-টি চালান, তালিকাটি পড়ুন, তারপর লাইনের শুরুতে থাকা command-টি ছাড়া আর কিছু পরিবর্তন করবেন না।
target='!(*.log)'
printf '%s\n' $target
ls -ld -- $target
rm -- $targetএকবার pattern-টি target-এ লিখলে তিনটি লাইন আলাদা হয়ে যাওয়ার ঝুঁকি থাকে না। $target ইচ্ছাকৃতভাবে উদ্ধৃতিচিহ্ন ছাড়া রাখা হয়েছে, কারণ এভাবেই bash এটিকে expand করতে পারে। printf '%s\n' প্রতি লাইনে একটি করে নাম দেখায় এবং কোনো কিছু পরিবর্তন করে না। ls -ld প্রতিটি entry নিজে দেখায়; directory-এর ভেতরের contents তালিকাভুক্ত করে না। কোন entry directory, তা mode column-এ দেখা যায়। recursive delete করার আগে এই বিষয়টিই পরীক্ষা করতে হবে। drwxr-xr-x permission bit পড়া-তে এই column সম্পর্কে ব্যাখ্যা আছে।
up arrow দিয়ে আগের লাইনটি ফিরিয়ে আনুন এবং শুধু command-টি সম্পাদনা করুন, যাতে pattern অপরিবর্তিত থাকে। স্মৃতি থেকে pattern আবার টাইপ করলেই সাধারণত ভুল ঢুকে পড়ে।
pattern-এর আগে -- বসান। এটি options-এর সমাপ্তি চিহ্নিত করে। ফলে - দিয়ে শুরু হওয়া filename-কে flag নয়, নাম হিসেবে গণ্য করা হয়।
কোনো script-এর একটি অংশে globbing বন্ধ রাখতে চাইলে set -f এটি বন্ধ করে এবং set +f আবার চালু করে।
এই অভ্যাস গুরুত্বপূর্ণ, কারণ rm-এর কোনো undo নেই। rm -rf দিয়ে মুছে ফেলা file পুনরুদ্ধার করা ধীর এবং সাধারণত অসম্পূর্ণ। তাই preview করার খরচ বিকল্পটির তুলনায় অনেক কম।
কাজ শেষ হলে practice directory পরিষ্কার করুন। path প্রিন্ট করে আগে পড়ে নিন, কারণ ভুল variable-এ rm -rf নির্দেশ করাই এই section-এ আলোচিত দুর্ঘটনা।
cd ~
echo "$lab"
rm -rf -- "$lab"FAQ
কেন find . -name *.log ভুল ফাইল খুঁজে পায়?
কারণ find শুরু হওয়ার আগেই bash *.log expand করে। Shell বর্তমান directory-এর সঙ্গে pattern মিলিয়ে পাওয়া filename find-এ পাঠায়। এরপর find প্রতিটি depth-এ শুধু ওই একটি নাম খোঁজে। দুই বা তার বেশি ফাইল মিললে find এমন অতিরিক্ত argument পায়, যেখানে তার expression প্রত্যাশিত ছিল। তখন usage error দেখায়। Pattern-টি find . -name '*.log' হিসেবে quote করুন। এতে find নিজস্ব নিয়ম ও recursion ব্যবহার করে matching করবে।
Pattern-টি literal text হিসেবে দেখা গেলে এর অর্থ কী?
এর অর্থ কোনো কিছু match করেনি। ডিফল্টভাবে bash unmatched pattern অপরিবর্তিত রাখে এবং raw character-গুলো command-এ পাঠায়। এরপর command সেগুলোকে filename হিসেবে গণ্য করে। Unmatched pattern-কে command বন্ধ করা error-এ রূপান্তর করতে shopt -s failglob সেট করুন। অথবা pattern-টি সম্পূর্ণ খালি করতে shopt -s nullglob সেট করুন। nullglob ব্যবহার করলে কোনো argument না থাকলেও command-টি অর্থপূর্ণ কি না যাচাই করুন। কারণ তখন ls *.nope একটি bare ls-এ পরিণত হয় এবং পুরো directory তালিকাভুক্ত করে।
bash-এ একটি pattern ছাড়া সব ফাইল কীভাবে match করব?
নিজস্ব লাইনে shopt -s extglob দিয়ে extended pattern চালু করুন। এরপর negation form ব্যবহার করুন। !(*.log) এমন নাম match করে যেগুলো .log দিয়ে শেষ হয় না। !(*.log|*.md) উভয় pattern বাদ দেয়। dotglob সেট না করা থাকলে hidden file ফলাফলে থাকবে না। Directory-ও অন্তর্ভুক্ত হয়। তাই rm-এ দেওয়ার আগে printf '%s\n' !(*.log) দিয়ে pattern-এর ফলাফল দেখুন।
* কেন hidden file বাদ দেয়?
শুরুতে থাকা dot-কে literal হিসেবে match করতে হয়। তাই * কখনো dot দিয়ে শুরু হওয়া নাম match করে না। Hidden name-এ পৌঁছাতে .* ব্যবহার করুন। অথবা প্রতিটি সাধারণ pattern-এর মধ্যে hidden name অন্তর্ভুক্ত করতে shopt -s dotglob সেট করুন। . এবং .. entry সব সময় dotglob-এর অধীনে বাদ থাকে। bash 5.2 এবং পরবর্তী সংস্করণে globskipdots option-এর মাধ্যমে .* থেকেও এগুলো বাদ থাকে। এই option ডিফল্টভাবে চালু থাকে।
** কাজ করার জন্য কি globstar প্রয়োজন?
হ্যাঁ। shopt -s globstar ছাড়া bash **-কে সাধারণ * হিসেবে গণ্য করে। এটি একটি directory-এর ভেতরে match করে এবং সেখানেই থেমে যায়। globstar চালু থাকলে পুরো path component গঠন করা ** যেকোনো depth-এ match করতে পারে। ফলে **/*.log বর্তমান directory এবং তার নিচের সব subdirectory-তে পৌঁছায়। আর **/ একা শুধু directory match করে। মনে রাখুন, ** directory-এর symbolic link অনুসরণ করে না। তাই symlink দিয়ে তৈরি tree-এর জন্য find -L ব্যবহার করুন।