SSD Nodes Learn 🎉 VPS $5.50/ماہ سے
تعلیمی Matt Connorتحریر: Matt Connor

Bash Globbing: Wildcards اور Extglob کی مکمل وضاحت

Bash globbing میں shell command سے پہلے wildcards کو filenames میں بدلتا ہے۔ nullglob، dotglob، globstar اور Extglob negation کے عملی فرق سمجھیں۔

کمانڈ چلنے سے پہلے Bash globbing کیا کرتا ہے

Bash globbing سے مراد shell کا کسی pattern، مثلاً *.log، کو کمانڈ شروع ہونے سے پہلے موجود حقیقی filenames کی sorted فہرست میں تبدیل کرنا ہے۔ rm *.log، rm کو کبھی pattern نہیں دیتا۔ Bash directory پڑھتا ہے، matching names رکھتا ہے، انہیں sort کرتا ہے، اور پھر rm کو ہر name الگ argument کے طور پر چلاتا ہے۔ rm program کو یہ معلوم نہیں ہو سکتا کہ اس میں کوئی pattern شامل تھا۔

Globbing سے متعلق تقریباً تمام حیرت انگیز نتائج اسی ایک حقیقت سے پیدا ہوتے ہیں۔ Matching shell اس وقت موجود files کے خلاف کرتا ہے، اور command کو صرف مکمل فہرست نظر آتی ہے۔ اس عمل کا رسمی نام pathname expansion ہے۔ روزمرہ استعمال میں اسے globbing کہا جاتا ہے، اور دونوں سے ایک ہی عمل مراد ہے۔

مشق کے لیے ایک چھوٹا سا درخت بنائیں

ذیل کی ہر مثال انہی فائلوں پر چلتی ہے۔ انہیں ایک عارضی 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.log

mktemp -d ایک نئی directory بناتا ہے اور اس کا path واپس کرتا ہے، جبکہ $(...) اس path کو lab variable میں محفوظ کرتا ہے۔ اگر یہ syntax آپ کے لیے نیا ہے تو دیکھیں کہ command substitution کسی command کا output کیسے محفوظ کرتی ہے؛ اس میں مکمل وضاحت موجود ہے۔

جو کچھ آپ نے بنایا ہے اسے دیکھیں، اور اپنا shell بھی چیک کریں، کیونکہ آخر میں دیا گیا ایک نوٹ version پر منحصر ہے:

ls -a
bash --version

یہاں موجود ہر چیز bash 4.0 اور اس کے بعد کے versions پر کام کرتی ہے۔ آخر کے قریب موجود globskipdots نوٹ bash 5.2 اور اس کے بعد کے versions پر لاگو ہوتا ہے، جو August 2026 تک موجودہ Ubuntu اور Debian releases میں شامل ہیں۔

پورے متن میں استعمال ہونے والی preview command printf '%s\n' PATTERN ہے۔ یہ ہر match کو الگ line پر print کرتی ہے اور disk پر کوئی تبدیلی نہیں کرتی، اس لیے کسی بھی ایسے pattern کے ساتھ چلانا محفوظ ہے جس کے بارے میں آپ یقین نہ رکھتے ہوں۔

تین wildcards: star، question mark اور brackets

  • * حروف کے کسی بھی مسلسل سلسلے سے match کرتا ہے، خواہ وہ خالی ہی کیوں نہ ہو۔
  • ? ٹھیک ایک character سے match کرتا ہے۔
  • [...] brackets کے اندر موجود set میں سے ٹھیک ایک character سے match کرتا ہے۔
printf '%s\n' *.log
printf '%s\n' app.log.?
printf '%s\n' app.log.[0-9]
printf '%s\n' notes.*
printf '%s\n' *

دوسری line کا اس file list سے موازنہ کریں جو آپ نے بنائی ہے۔ ? صرف ایک character کی نمائندگی کرتا ہے، اس سے زیادہ کی نہیں، اس لیے دو digits والا suffix اس pattern میں شامل نہیں ہوتا۔ اسے app.log.[0-9]* تک وسیع کریں اور فرق دیکھنے کے لیے اسے دوبارہ چلائیں۔

تینوں wildcards پر دو قواعد لاگو ہوتے ہیں۔ کوئی بھی wildcard کبھی / سے match نہیں کرتا، اس لیے pattern ایک ہی directory level کے اندر رہتا ہے۔ نیز ابتدائی . کو literal طور پر type کرنا ضروری ہے، اس لیے * سے شروع ہونے والا pattern ہر hidden name کو چھوڑ دیتا ہے۔ اسی دوسرے قاعدے کی وجہ سے rm * آپ کی dotfiles کو نظرانداز کرتا ہے۔

Glob کے نتائج موجودہ locale کے collation order کے مطابق sorted آتے ہیں۔ یہ character-by-character text sort ہوتا ہے۔ اس لیے 10 کا suffix 2 کے suffix سے پہلے sort ہوتا ہے، کیونکہ comparison دونوں میں سے کسی کو بھی number کے طور پر نہیں پڑھتا۔ printf '%s\n' app.log.* چلائیں، پھر ls -v app.log.* چلائیں، اور دونوں کا موازنہ کریں: ls -v embedded numbers کو ان کی value کے مطابق sort کرتا ہے، جبکہ sort -V pipeline میں یہی کام کرتا ہے۔

حروفی مجموعے اور حدود

قوسین کے اندر آپ فہرست، حد یا نفی لکھ سکتے ہیں۔

  • [ch] ایک حرف سے match کرتا ہے، جو c یا h ہو سکتا ہے۔
  • [a-f]، a سے f تک کی حد میں موجود ایک حرف سے match کرتا ہے۔
  • [!0-9] ایسے ایک حرف سے match کرتا ہے جو digit نہیں ہے۔ [^0-9] بھی یہی معنی رکھتا ہے۔
printf '%s\n' [dn]*
printf '%s\n' *.[A-Z]*
printf '%s\n' [A-Z]*

حدود وہ حصہ ہیں جو مختلف machines کے درمیان مختلف نتائج دے سکتا ہے۔ حد کا تعین plain ASCII کے بجائے موجودہ locale کی collation order سے ہوتا ہے، اس لیے کچھ systems پر [a-z] uppercase letters سے بھی match کرتا ہے۔ Bash میں globasciiranges option موجود ہے جو plain ASCII order نافذ کرتا ہے، اور حالیہ builds میں یہ default طور پر enabled ہوتا ہے۔ اس لیے اندازہ لگانے کے بجائے shopt globasciiranges سے اپنی setting چیک کریں۔ ایسی script جسے ہر جگہ یکساں طریقے سے match کرنا ہو، وہ اپنی order کو خود مقرر کر سکتی ہے۔ اس کے لیے LC_ALL=C کو file کے شروع کے قریب الگ line پر set کریں۔ اس سے ہر range دوبارہ plain byte order استعمال کرے گی۔

یہ assignment اپنے طور پر ایک command ہونی چاہیے۔ Prefix کے طور پر لکھنے سے LC_ALL=C ls [a-z]*، ls اور صرف اسی command کے لیے locale set کرتا ہے۔ Shell نے pattern کو اپنے locale میں پہلے ہی expand کر لیا ہوتا ہے، اس سے پہلے کہ ls شروع ہو۔ یہی پہلے section کا اصول دوبارہ لاگو ہوتا ہے۔

Brace expansion، globbing نہیں

Braces wildcard جیسے دکھائی دیتے ہیں، لیکن ان کا عمل بالکل مختلف ہوتا ہے۔ Brace expansion، globbing سے بہت پہلے چلتی ہے اور disk کو کبھی نہیں دیکھتی۔

echo file{1,2,3}.txt
echo {01..10}
echo {a..e}
echo {0..20..5}

ان میں سے کوئی بھی file موجود نہیں، پھر بھی expansion ہو جاتی ہے۔ یہی بنیادی فرق ہے: glob filesystem سے سوال کرتا ہے، جبکہ brace صرف text بناتا ہے۔ اسی لیے چیزیں تلاش کرنے کے بجائے بنانے کے لیے braces موزوں ہیں۔

mkdir -p site/{css,js,img}
cp app.log{,.bak}
touch report-{2025,2026}-{01,02}.csv

cp app.log{,.bak} دو words میں expand ہوتا ہے: app.log اور app.log.bak، کیونکہ comma سے پہلے موجود خالی item اصل text کو دوبارہ شامل کرتا ہے۔ {01..10} اپنی zero padding برقرار رکھتا ہے، کیونکہ دونوں endpoints میں سے کسی ایک کے شروع میں zero ہونے پر bash تمام generated numbers کو یکساں width تک pad کرتا ہے۔

Expansion order ایک عام failure کی وجہ بھی واضح کرتا ہے۔ Bash پہلے braces expand کرتا ہے، پھر variables، اور آخر میں pathname expansion کرتا ہے۔

n=5
echo {1..$n}

جب $n number بنتا ہے، اس وقت تک brace expansion مکمل ہو چکی ہوتی ہے، اس لیے کوئی range بنتی ہی نہیں۔ seq 1 "$n" استعمال کریں، یا C-style loop لکھیں، جیسے for ((i=1; i<=n; i++))۔

جب pattern کسی فائل سے match نہ ہو

یہ ناکامی کی پہلی صورت ہے، اور ایک نہ ایک بار سب کو اس کا سامنا ہوتا ہے۔ پہلے سے طے شدہ طور پر bash unmatched pattern کو بالکل اسی طرح چھوڑ دیتا ہے۔ یہ لفظ command تک عین اسی شکل میں پہنچتا ہے جس شکل میں آپ نے اسے لکھا تھا۔ اس لیے command کو *، .، t، m اور p حروف موصول ہوتے ہیں، اور وہ انہیں filename سمجھنے کی کوشش کرتی ہے۔

printf '%s\n' *.tmp

printf کے ساتھ یہ بے ضرر ہے۔ لیکن ایسی command کے ساتھ بے ضرر نہیں جو چیزیں بناتی ہو۔ ایسے directory میں touch *.tmp چلانے سے جہاں کوئی .tmp فائل موجود نہ ہو، literally *.tmp نام کی file بن جاتی ہے۔ بعد میں اسے delete کرنے کے لیے quoting ضروری ہے، تاکہ shell اسے دوبارہ expand نہ کرے: rm -- '*.tmp'۔

دو shell options اس اصول کو تبدیل کرتے ہیں۔ ہر option کو ایسے pattern کے ساتھ آزمائیں جو کسی چیز سے match نہ ہو، پھر اسے unset کر دیں۔

shopt -s nullglob
printf '%s\n' *.tmp
shopt -u nullglob

shopt -s failglob
printf '%s\n' *.tmp
shopt -u failglob

nullglob unmatched pattern کو مکمل طور پر خالی کر دیتا ہے۔ اس طرح command ان arguments سے کم arguments کے ساتھ چلتی ہے جتنے آپ نے لکھے تھے۔ loop کے اندر یہی مطلوب ہوتا ہے۔ nullglob بند ہونے پر for f in *.tmp اپنا body ایک بار چلاتا ہے اور f میں literal pattern موجود ہوتا ہے۔ تقریباً ہر ایسی script میں یہ ایک bug ہے۔ nullglob فعال ہونے پر body صفر بار چلتا ہے۔

nullglob کا اپنا خطرہ ہے، کیونکہ تمام arguments ختم ہو جانے کے باوجود command چلتی رہتی ہے۔ ls *.nope ایک سادہ ls بن جاتا ہے، جو پورا directory list کرتا ہے۔ grep -l needle *.nope ایک سادہ grep -l needle بن جاتا ہے۔ اسے پڑھنے کے لیے کوئی file نہیں ہوتی، اس لیے یہ standard input کا انتظار کرتا ہے اور ایسا لگتا ہے جیسے یہ freeze ہو گیا ہو۔ جس loop کو یہ option درکار ہو، اس کے گرد nullglob فعال کریں، اور loop کے بعد اسے دوبارہ غیر فعال کر دیں۔

failglob دوسرا طریقہ اختیار کرتا ہے: unmatched pattern ایک error بن جاتا ہے، bash اس کی اطلاع دیتا ہے، اور command بالکل نہیں چلتی۔ interactive shell کے لیے یہ اچھی setting ہے، کیونکہ یہ غلط لکھے گئے pattern کو literal name کے طور پر rm کے حوالے ہونے سے روک دیتی ہے۔

چھپی ہوئی فائلیں: dotglob

shopt -s dotglob
printf '%s\n' *
shopt -u dotglob

اس block کو چلائیں اور listing کا پہلے دیے گئے سادہ printf '%s\n' * سے موازنہ کریں۔ dotglob ابتدائی dot rule ختم کرتا ہے، اس لیے * چھپے ہوئے ناموں سے بھی match کرتا ہے۔ . اور .. entries dotglob کے تحت ہمیشہ خارج رہتی ہیں۔

dotglob کے بغیر چھپی ہوئی فائلوں تک پہنچنے کا معمول کا طریقہ .* ہے، اور پہلے یہ خطرناک ہوا کرتا تھا۔ 5.2 سے پہلے کے bash میں .*، . اور .. سے بھی match کرتا تھا، اس لیے chmod -R 755 .* جیسی recursive command براہِ راست parent directory میں چلی جاتی تھی۔ Bash 5.2 نے globskipdots option شامل کیا، جو default طور پر enabled ہے اور ہر expansion سے . اور .. کو خارج رکھتا ہے۔ اس پر انحصار کرنے سے پہلے shopt globskipdots چلائیں، کیونکہ پرانے سسٹم میں یہ option موجود نہیں ہوگا۔

globstar: پورے درخت میں ہر سطح سے مطابقت

shopt -s globstar
printf '%s\n' **/*.log
printf '%s\n' **/
shopt -u globstar

globstar فعال ہونے پر، پورا path component بننے والا ** کسی بھی گہرائی پر موجود files اور directories سے match کرتا ہے، جس میں صفر سطح نیچے بھی شامل ہے۔ اس لیے **/*.log موجودہ directory کے ساتھ ساتھ logs/ اور archive/ کے اندر موجود ہر چیز کو بھی شامل کرتا ہے۔ آخر میں slash والا **/ صرف directories سے match کرتا ہے، اس لیے یہ tree کی ساخت دیکھنے کا تیز طریقہ ہے۔

globstar فعال نہ ہو تو ** عام * کی طرح کام کرتا ہے اور ایک ہی directory تک محدود رہتا ہے۔ اسی pattern کو option بند کرکے چلائیں اور فرق دیکھیں۔ اگر کہیں اور سے copy کیا ہوا recursive pattern آپ کی توقع سے بہت کم نتائج دے، تو عموماً وجہ یہی ہوتی ہے۔ ہر bash میں globstar بطور default بند ہوتا ہے۔

ایک اہم حد یہ ہے کہ ** directories کی طرف جانے والے symbolic links کو follow نہیں کرتا۔ اگر symlinks سے جڑا ہوا tree ہو تو اس کے بجائے find -L استعمال کریں۔

nocaseglob، ان ناموں کے لیے جن کے حروف کی حالت مختلف ہو

shopt -s nocaseglob
printf '%s\n' *.log
shopt -u nocaseglob

nocaseglob فائل ناموں کی matching میں حروف کی حالت کو نظرانداز کرتا ہے، اس لیے uppercase .LOG suffix، lowercase .log pattern سے match ہو جاتا ہے۔ اس listing کا موازنہ پہلے section والی listing سے کریں۔ اس option کو فوراً دوبارہ بند کر دیں، کیونکہ فعال رہنے کی صورت میں یہ اسی shell میں اس کے بعد چلنے والی ہر command کی matching تبدیل کر دیتا ہے، اور ایک گھنٹے بعد اس مسئلے کی debugging کرنا دشوار ہوتا ہے۔

nocaseglob صرف filename expansion پر لاگو ہوتا ہے۔ case statement یا double bracket test کے اندر pattern matching کے لیے متعلقہ option nocasematch ہے، اور دونوں کو الگ الگ set کیا جاتا ہے۔

extglob: وہ patterns جو کہہ سکتے ہیں کہ "یہ نہیں"

Extended patterns پہلے سے بند ہوتے ہیں۔ انہیں اپنی الگ سطر پر فعال کریں:

shopt -s extglob

اس کی پانچ شکلیں ہیں، اور ہر شکل | سے الگ کیے گئے patterns کی فہرست لیتی ہے:

  • ?(list) صفر یا ایک بار وقوع کو match کرتا ہے۔
  • *(list) صفر یا اس سے زیادہ وقوع کو match کرتا ہے۔
  • +(list) ایک یا اس سے زیادہ وقوع کو match کرتا ہے۔
  • @(list) متبادل patterns میں سے بالکل ایک کو match کرتا ہے۔
  • !(list) ایسی ہر چیز کو match کرتا ہے جو کسی بھی متبادل pattern سے match نہ ہو۔

Extended pattern کو ایسے shell سے پڑھا جانا چاہیے جس میں یہ option پہلے سے فعال ہو۔ اس لیے ذیل کا batch ایک چھوٹی script میں رکھا جاتا ہے، جسے bash -O extglob کے ساتھ شروع کیا جاتا ہے۔ -O flag نئی shell کے file کی ایک سطر پڑھنے سے پہلے shopt option set کرتا ہے۔ اس کا مطلب ہے کہ اس میں موجود ہر pattern درست طور پر parse ہوتا ہے۔ اس section کا آخری پیراگراف بتاتا ہے کہ 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]) ایک یا اس سے زیادہ digits کو ظاہر کرتا ہے، اس لیے یہ کسی بھی length کے numbered suffix کو cover کرتا ہے۔ یہی پہلے بیان کردہ ? limit کا حل ہے۔ دونوں patterns کو مسلسل چلا کر فرق دیکھنا مفید ہے۔

Negation form "ہر چیز سوائے" کے لیے استعمال ہوتی ہے، اور extglob فعال کرنے کی بنیادی وجہ بھی یہی ہے۔ اس کے بارے میں دو باتیں لوگوں کو عموماً حیران کرتی ہیں۔

یہ files کے ساتھ directories کو بھی match کرتی ہے۔ اس لیے ls !(*.log) result میں directory name کے بجائے اس directory کا content list کرتا ہے۔ خود names دیکھنے کے لیے ls -d یا printf preview استعمال کریں۔

یہ ہر دوسرے glob کی طرح dot rule کی پابندی بھی کرتی ہے۔ اس لیے !(*.log) ہر visible name کو cover کرتا ہے جو .log پر ختم نہ ہو۔ Hidden names کے لیے dotglob بھی set کرنا ہوگا۔

!(...) میں موجود ! glob negation ہے۔ اس کا history expansion اور bang character سے کوئی تعلق نہیں۔ وہ الگ feature ہے جو مختلف stage پر چلتا ہے۔

ایک اور trap parse time پر پیش آتا ہے۔ Bash کسی command کے کسی حصے کو چلانے سے پہلے پوری command parse کرتا ہے۔ اس لیے اسی سطر پر extglob فعال کرنا اور extended pattern استعمال کرنا ناکام ہوتا ہے، کیونکہ option کے فعال ہونے سے پہلے pattern parse ہو جاتا ہے۔ shopt -s extglob کو اپنی الگ سطر پر، patterns سے پہلے اور script کے اوپر والے حصے کے قریب رکھیں۔ Function body کو function define کرتے وقت parse کیا جاتا ہے، اس لیے option کو call سے پہلے نہیں بلکہ definition سے پہلے فعال ہونا چاہیے۔ shell کو bash -O extglob کے ساتھ شروع کرنے سے، جیسا کہ اوپر والا batch کرتا ہے، parsing شروع ہونے سے پہلے ordering طے ہو جاتی ہے۔

شیل pattern کو expand کرتی ہے، command نہیں

یہ failure mode کی دوسری قسم ہے، اور bugs کے ایک پورے سلسلے کی وضاحت کرتی ہے۔

grep -l needle *.log
find . -name '*.log'
find . -name *.log

پہلی line matching کا کام bash کو کرنے دیتی ہے، اور grep کو filenames کی فہرست ملتی ہے۔ دوسری line pattern کو quote کرتی ہے، اس لیے find کو *.log کے پانچ characters ملتے ہیں اور وہ starting point کے نیچے ہر depth پر خود matching کرتا ہے۔ تیسری line bug ہے: bash پہلے pattern کو صرف current directory کے خلاف expand کرتی ہے، اس لیے find کو ایک مخصوص name تلاش کرنے کا کہا جاتا ہے۔ ایسی directory میں جہاں صرف ایک match ہو، یہ خاموشی سے غلط چیز تلاش کرتا ہے۔ دو یا اس سے زیادہ matches ہونے پر find usage error دیتا ہے، کیونکہ اضافی names اس جگہ پہنچتے ہیں جہاں وہ expression کی توقع کرتا ہے۔

اصول مختصر ہے۔ اگر pattern command کے لیے ہے تو اسے quote کریں۔ اگر pattern shell کے لیے ہے تو اسے bare چھوڑیں۔ یہی تقسیم --exclude میں tar اور rsync کے patterns، اور grep میں --include filters پر بھی لاگو ہوتی ہے۔

Globs regular expressions بھی نہیں ہیں، اگرچہ دونوں میں کچھ characters مشترک ہوتے ہیں۔ Glob میں * characters کی کسی بھی مسلسل تعداد کو ظاہر کرتا ہے۔ Regular expression میں * اس سے پہلے آنے والی چیز کی صفر یا زیادہ تکرار کو ظاہر کرتا ہے، اس لیے grep '*.log' ایسی چیز تلاش کرتا ہے جو بظاہر اس pattern سے بالکل مختلف ہے۔

Variables بھی operations کی یہی ترتیب follow کرتے ہیں۔ Globbing variable expansion کے بعد چلتی ہے، اس لیے pattern رکھنے والا unquoted variable directory کے خلاف expand ہو جاتا ہے۔

pat='*.log'
printf '%s\n' $pat
printf '%s\n' "$pat"

Unquoted صورت میں bash variable کے result پر globbing کرتی ہے۔ Quoted صورت میں pattern literal رہتا ہے۔ اس کا ایک متعلقہ نتیجہ ہے جس پر آپ اعتماد کر سکتے ہیں: globbing سے بننے والے words کو spaces کی بنیاد پر دوبارہ split نہیں کیا جاتا۔ اس لیے for f in *.log space پر مشتمل filename کو ایک name کے طور پر handle کرتا ہے، جبکہ for f in $(ls *.log) اس name کو دو words میں توڑ دیتا ہے۔ Glob پر براہ راست loop چلائیں۔

تباہ کن pattern چلانے سے پہلے اس کا پیش نظارہ کریں

نئے pattern کے ساتھ کبھی بھی rm کو پہلی command نہ بنائیں۔ پہلے pattern کو کسی بے ضرر command کے ساتھ چلائیں، فہرست پڑھیں، پھر صرف لائن کے شروع میں موجود command تبدیل کریں۔

target='!(*.log)'
printf '%s\n' $target
ls -ld -- $target
rm -- $target

pattern کو ایک بار target میں لکھنے سے تینوں لائنیں ایک دوسرے سے مختلف نہیں ہوتیں، اور $target کو جان بوجھ کر quotes کے بغیر رکھا گیا ہے، کیونکہ اسی سے bash اسے expand کرتا ہے۔ printf '%s\n' ہر نام کو الگ لائن میں print کرتا ہے اور کسی چیز کو تبدیل نہیں کرتا۔ ls -ld ہر entry کو خود دکھاتا ہے، directories کے contents کی فہرست نہیں بناتا، اور mode column سے معلوم ہوتا ہے کہ کون سی entries directories ہیں۔ ہر recursive delete سے پہلے یہی چیز check کرنی چاہیے۔ drwxr-xr-x permission bits پڑھنا میں اس column کی وضاحت ہے۔

پچھلی لائن کو up arrow سے دوبارہ لائیں اور صرف command میں ترمیم کریں، تاکہ pattern بالکل وہی رہے۔ pattern کو یاد سے دوبارہ لکھنے کے دوران ہی عموماً غلطی ہو جاتی ہے۔

pattern سے پہلے -- لکھیں۔ یہ options کے اختتام کی نشاندہی کرتا ہے، اس لیے - سے شروع ہونے والا filename flag کے بجائے نام سمجھا جاتا ہے۔

اگر script کے کسی حصے کے لیے globbing بند کرنا ہو تو set -f اسے disable کرتا ہے اور set +f دوبارہ enable کرتا ہے۔

یہ عادت اس لیے اہم ہے کہ rm میں undo کی سہولت نہیں ہوتی۔ rm -rf سے حذف شدہ files بحال کرنا سست اور عموماً نامکمل عمل ہے، اس لیے preview کرنا متبادل نقصان کے مقابلے میں بہت کم مہنگا ہے۔

کام مکمل ہونے پر practice directory صاف کر دیں۔ path کو print کریں اور پہلے اسے پڑھیں، کیونکہ غلط variable کی طرف اشارہ کرتا ہوا rm -rf بالکل وہی حادثہ ہے جس کے بارے میں یہ section ہے۔

cd ~
echo "$lab"
rm -rf -- "$lab"

FAQ

find . -name *.log کو غلط فائلیں کیوں ملتی ہیں؟

کیونکہ bash، find کے شروع ہونے سے پہلے ہی *.log کو expand کر دیتا ہے۔ shell موجودہ directory کے خلاف pattern match کرتا ہے اور حاصل ہونے والی filename کو find کو دے دیتا ہے۔ اس کے بعد find ہر depth پر صرف اسی نام کو تلاش کرتا ہے۔ اگر دو یا زیادہ فائلیں match ہوں تو find کو وہاں اضافی arguments ملتے ہیں جہاں اسے expression درکار ہوتی ہے، اور وہ usage error ظاہر کرتا ہے۔ pattern کو find . -name '*.log' کے طور پر quote کریں تاکہ matching خود find کرے، اور اپنی rules اور اپنی recursion استعمال کرے۔

جب میرا pattern literal text کے طور پر ظاہر ہو تو اس کا کیا مطلب ہے؟

اس کا مطلب ہے کہ کوئی چیز match نہیں ہوئی۔ بطور default bash unmatched pattern کو تبدیل نہیں کرتا اور raw characters کو command کے حوالے کر دیتا ہے، جو انہیں filename سمجھتی ہے۔ shopt -s failglob set کریں تاکہ unmatched pattern ایک error میں تبدیل ہو جائے اور command رک جائے، یا shopt -s nullglob set کریں تاکہ یہ خالی expand ہو۔ nullglob کے تحت یہ بھی دیکھیں کہ command مکمل طور پر arguments کے بغیر درست رہتی ہے، کیونکہ ls *.nope ایک bare ls بن جاتا ہے اور پوری directory کی فہرست دکھاتا ہے۔

bash میں ایک pattern کے سوا ہر فائل سے کیسے match کروں؟

اپنی الگ line پر shopt -s extglob کے ذریعے extended patterns فعال کریں، پھر negation form استعمال کریں۔ !(*.log) ان names سے match کرتا ہے جو .log پر ختم نہیں ہوتے، جبکہ !(*.log|*.md) دونوں کو خارج کرتا ہے۔ Hidden files نتیجے میں شامل نہیں ہوں گی، جب تک dotglob بھی set نہ ہو۔ Directories بھی شامل ہوتی ہیں، اس لیے pattern کو printf '%s\n' !(*.log) کے ذریعے preview کریں، پھر اسے rm کو دیں۔

* hidden files کو کیوں چھوڑ دیتا ہے؟

ابتدائی dot کو literal طور پر match کرنا ضروری ہے، اس لیے * کبھی ایسے name سے match نہیں کرتا جو dot سے شروع ہو۔ Hidden names تک پہنچنے کے لیے .* استعمال کریں، یا shopt -s dotglob set کریں تاکہ انہیں ہر ordinary pattern میں شامل کیا جائے۔ . اور .. entries dotglob کے تحت ہمیشہ خارج رہتی ہیں۔ bash 5.2 اور بعد کے versions میں globskipdots option کے ذریعے انہیں .* سے بھی خارج رکھا جاتا ہے، اور یہ option بطور default فعال ہوتا ہے۔

کیا ** کے کام کرنے کے لیے globstar درکار ہے؟

ہاں۔ shopt -s globstar کے بغیر bash، ** کو ایک ordinary * سمجھتا ہے، جو ایک directory کے اندر match کرتا ہے اور وہیں رک جاتا ہے۔ globstar فعال ہونے پر، پورا path component بنانے والا ** کسی بھی depth پر match کرتا ہے۔ اس لیے **/*.log موجودہ directory اور اس کے نیچے موجود ہر subdirectory تک پہنچتا ہے، جبکہ اکیلا **/ صرف directories سے match کرتا ہے۔ یاد رکھیں کہ ** directories کی symbolic links کو follow نہیں کرتا۔ اس لیے symlinks سے بنی tree کے لیے find -L استعمال کریں۔

#bash#globbing#shell#linux#scripting