SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor

آموزش کامل Globbing و Wildcard در Bash

با نحوه عملکرد globbing و تبدیل الگوها به لیست فایل‌ها در Bash آشنا شوید. این راهنما تنظیمات nullglob، dotglob، globstar و استفاده از extglob را برای مدیریت دقیق فایل‌ها بررسی می‌کند.

عملکرد globbing در Bash پیش از اجرای دستور

عملیات globbing در Bash به این معناست که shell یک الگو مانند *.log را پیش از اجرای دستور، به لیستی مرتب‌شده از نام فایل‌های واقعی تبدیل می‌کند. rm *.log هرگز یک الگو را به rm تحویل نمی‌دهد. Bash دایرکتوری را می‌خواند، نام‌های منطبق را نگه می‌دارد، آن‌ها را مرتب می‌کند و سپس rm را با هر نام به عنوان یک آرگومان جداگانه اجرا می‌کند. برنامه rm نمی‌تواند تشخیص دهد که اصلاً الگویی در کار بوده است.

تقریباً تمام نکات غیرمنتظره درباره globbing از همین یک واقعیت ناشی می‌شود. تطبیق توسط shell و بر اساس فایل‌هایی که در آن لحظه وجود دارند انجام می‌شود و دستور فقط لیست نهایی را می‌بیند. نام رسمی این فرآیند pathname expansion است. Globbing واژه رایج‌تری است و هر دو به یک معنا هستند.

ساخت یک درخت کوچک برای تمرین

تمام مثال‌های زیر روی فایل‌های یکسانی اجرا می‌شوند. آن‌ها را در یک دایرکتوری موقت ایجاد کنید تا هیچ فایل مهمی در دسترس نباشد و پاک‌سازی تنها با یک دستور انجام شود.

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 یک دایرکتوری جدید ایجاد کرده و مسیر آن را برمی‌گرداند، و $(...) آن مسیر را در متغیر lab ذخیره می‌کند. اگر این نحو برای شما جدید است، نحوه ثبت خروجی دستور توسط command substitution آن را به‌طور کامل توضیح می‌دهد.

آنچه ساخته‌اید را مشاهده کنید و shell خود را بررسی کنید، زیرا یک نکته در پایان به نسخه آن بستگی دارد:

ls -a
bash --version

همه موارد در اینجا روی bash 4.0 و نسخه‌های بعد از آن کار می‌کنند. نکته globskipdots در نزدیکی پایان، مربوط به bash 5.2 و نسخه‌های بعد از آن است که در نسخه‌های فعلی Ubuntu و Debian تا اوت 2026 ارائه شده است.

دستور پیش‌نمایشی که در سراسر متن استفاده شده، printf '%s\n' PATTERN است. این دستور هر تطابق را در یک خط چاپ می‌کند و هیچ تغییری در دیسک ایجاد نمی‌کند، بنابراین اجرای آن روی هر الگویی که از آن مطمئن نیستید، ایمن است.

سه کاراکتر Wildcard: ستاره، علامت سوال و براکت

  • * با هر رشته‌ای از کاراکترها، حتی رشته تهی، مطابقت دارد.
  • ? دقیقاً با یک کاراکتر مطابقت دارد.
  • [...] دقیقاً با یک کاراکتر از مجموعه داخل براکت مطابقت دارد.
printf '%s\n' *.log
printf '%s\n' app.log.?
printf '%s\n' app.log.[0-9]
printf '%s\n' notes.*
printf '%s\n' *

خط دوم را با لیست فایلی که ایجاد کردید مقایسه کنید. ? تنها برای یک کاراکتر است و نه بیشتر، بنابراین یک پسوند دو رقمی در این الگو قرار نمی‌گیرد. آن را به app.log.[0-9]* گسترش دهید و دوباره اجرا کنید تا تفاوت را ببینید.

دو قانون برای هر سه Wildcard اعمال می‌شود. هیچ Wildcard با / مطابقت ندارد، بنابراین الگو در سطح یک دایرکتوری باقی می‌ماند. همچنین یک . در ابتدای نام فایل باید به صورت صریح تایپ شود، بنابراین الگویی که با * شروع می‌شود، تمام فایل‌های مخفی (hidden) را نادیده می‌گیرد. به همین دلیل است که rm * فایل‌های dotfile شما را تغییر نمی‌دهد.

نتایج Glob بر اساس ترتیب مرتب‌سازی (collation) لوکال فعلی مرتب می‌شوند که یک مرتب‌سازی متنی کاراکتر به کاراکتر است. بنابراین پسوند 10 قبل از پسوند 2 قرار می‌گیرد، زیرا مقایسه هرگز آن‌ها را به عنوان عدد نمی‌خواند. دستور printf '%s\n' app.log.* و سپس ls -v app.log.* را اجرا کنید و آن دو را مقایسه کنید: ls -v اعداد موجود در متن را بر اساس مقدار عددی‌شان مرتب می‌کند و sort -V همین کار را در یک pipeline انجام می‌دهد.

کلاس‌ها و بازه‌های کاراکتری

درون براکت‌ها می‌توانید یک لیست، یک بازه یا یک نقیض بنویسید.

  • [ch] با یک کاراکتر، یا c یا h مطابقت دارد.
  • [a-f] با یک کاراکتر در بازه a تا f مطابقت دارد.
  • [!0-9] با یک کاراکتری که رقم نباشد مطابقت دارد. [^0-9] نیز همین معنا را می‌دهد.
printf '%s\n' [dn]*
printf '%s\n' *.[A-Z]*
printf '%s\n' [A-Z]*

بازه‌ها بخشی هستند که در سیستم‌های مختلف رفتار متفاوتی دارند. یک بازه بر اساس ترتیب مرتب‌سازی (collation order) لوکال فعلی حل می‌شود، نه بر اساس ASCII ساده؛ بنابراین در برخی سیستم‌ها، [a-z] با حروف بزرگ نیز مطابقت دارد. Bash دارای یک گزینه globasciiranges است که ترتیب ASCII ساده را اجبار می‌کند و نسخه‌های جدید به‌صورت پیش‌فرض آن را فعال کرده‌اند، پس به‌جای فرض کردن، آن را با shopt globasciiranges بررسی کنید. اسکریپتی که باید در همه جا به یک شکل عمل کند، می‌تواند با قرار دادن LC_ALL=C در یک خط مستقل در ابتدای فایل، ترتیب را ثابت کند که باعث می‌شود تمام بازه‌ها به ترتیب بایت ساده بازگردند.

این انتساب باید یک دستور مستقل باشد. اگر به‌صورت پیشوند نوشته شود، LC_ALL=C ls [a-z]* لوکال را فقط برای ls تنظیم می‌کند و نه هیچ چیز دیگر؛ و shell پیش از آنکه ls شروع به کار کند، الگو را در لوکال خودش بسط داده است، که این همان قانونی است که در بخش اول ذکر شد.

گسترش آکولاد (Brace expansion) با globbing متفاوت است

آکولادها شبیه به wildcard به نظر می‌رسند اما عملکرد آن‌ها هیچ شباهتی به wildcard ندارد. گسترش آکولاد بسیار زودتر از globbing اجرا می‌شود و هرگز محتویات دیسک را بررسی نمی‌کند.

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

هیچ‌کدام از این فایل‌ها وجود ندارند، اما گسترش همچنان انجام می‌شود. تفاوت اصلی همین است: یک glob از سیستم فایل پرس‌وجو می‌کند، در حالی که آکولاد فقط متن تولید می‌کند. به همین دلیل است که آکولادها ابزار مناسبی برای ایجاد فایل‌ها هستند، نه پیدا کردن آن‌ها.

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

عبارت cp app.log{,.bak} به دو کلمه app.log و app.log.bak گسترش می‌یابد، زیرا آیتم خالی قبل از کاما، متن اصلی را بازتولید می‌کند. عبارت {01..10} صفر‌های ابتدایی خود را حفظ می‌کند، زیرا وجود صفر در ابتدای هر یک از دو نقطه انتهایی باعث می‌شود bash تمام اعداد تولیدشده را با همان عرض (padding) نمایش دهد.

ترتیب گسترش، دلیل یک خطای رایج را نیز توضیح می‌دهد. Bash ابتدا آکولادها را گسترش می‌دهد، سپس متغیرها را جایگزین می‌کند و در نهایت گسترش مسیر (pathname expansion) را انجام می‌دهد.

n=5
echo {1..$n}

گسترش آکولاد تا زمانی که $n به یک عدد تبدیل شود، به پایان رسیده است؛ بنابراین هیچ بازه‌ای ساخته نمی‌شود. از seq 1 "$n" یا یک حلقه به سبک C که به صورت for ((i=1; i<=n; i++)) نوشته می‌شود، استفاده کنید.

وقتی یک الگو با هیچ‌چیز مطابقت ندارد چه اتفاقی می‌افتد

این اولین حالت شکست است و همه یک‌بار در آن گرفتار می‌شوند. به‌صورت پیش‌فرض، bash الگوی مطابقت‌نیافته را کاملاً دست‌نخورده باقی می‌گذارد. کلمه دقیقاً همان‌طور که تایپ کرده‌اید به دستور می‌رسد، بنابراین دستور کاراکترهای *، .، t، m و p را دریافت می‌کند و سعی می‌کند با آن‌ها به‌عنوان یک نام فایل رفتار کند.

printf '%s\n' *.tmp

این رفتار با printf بی‌خطر است. اما با دستوری که فایل ایجاد می‌کند، بی‌خطر نیست. دستور touch *.tmp در دایرکتوری که هیچ فایل .tmp در آن وجود ندارد، فایلی ایجاد می‌کند که نام آن دقیقاً *.tmp است و حذف آن در ادامه نیاز به استفاده از کوتیشن دارد تا shell دوباره آن را expand نکند: rm -- '*.tmp'.

دو گزینه shell این قانون را تغییر می‌دهند. هر کدام را روی الگویی که با هیچ‌چیز مطابقت ندارد امتحان کنید و سپس آن را unset کنید.

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

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

گزینه nullglob باعث می‌شود الگوی مطابقت‌نیافته به هیچ‌چیز تبدیل شود، بنابراین دستور با آرگومان‌های کمتری نسبت به آنچه نوشته‌اید اجرا می‌شود. این همان چیزی است که در حلقه‌ها به آن نیاز دارید. وقتی nullglob غیرفعال باشد، for f in *.tmp بدنه خود را یک‌بار اجرا می‌کند در حالی که f حاوی الگوی literal است؛ این یک باگ در تقریباً تمام اسکریپت‌هایی است که چنین کاری انجام می‌دهند. با فعال بودن nullglob، بدنه صفر بار اجرا می‌شود.

گزینه nullglob خطرات خاص خود را دارد، زیرا دستوری که تمام آرگومان‌هایش حذف شده باشد همچنان اجرا می‌شود. دستور ls *.nope به یک ls خالی تبدیل می‌شود که کل دایرکتوری را لیست می‌کند. دستور grep -l needle *.nope به grep -l needle تبدیل می‌شود که فایلی برای خواندن ندارد، بنابراین منتظر ورودی استاندارد (standard input) می‌ماند و به نظر می‌رسد که سیستم قفل کرده است. گزینه nullglob را فقط در اطراف حلقه‌ای که به آن نیاز دارد فعال کنید و پس از آن دوباره غیرفعال نمایید.

گزینه failglob مسیر دیگری را در پیش می‌گیرد: الگوی مطابقت‌نیافته یک خطا محسوب می‌شود، bash آن را گزارش می‌دهد و دستور اصلاً اجرا نمی‌شود. این تنظیم برای یک shell تعاملی مناسب است، زیرا از ارسال الگوی اشتباه‌تایپ‌شده به rm به‌عنوان یک نام literal جلوگیری می‌کند.

فایل‌های مخفی: dotglob

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

این بلوک را اجرا کنید و خروجی لیست‌شده را با printf '%s\n' * ساده‌ای که پیش‌تر دیدید مقایسه کنید. dotglob قانون نقطهٔ ابتدایی را حذف می‌کند، بنابراین * با نام‌های مخفی نیز مطابقت پیدا می‌کند. ورودی‌های . و .. تحت dotglob همیشه مستثنی باقی می‌مانند.

بدون dotglob، استفاده از .* روش معمول برای دسترسی به فایل‌های مخفی است و در گذشته خطرناک بود. در نسخه‌های bash پیش از 5.2، عبارت .* با . و .. نیز مطابقت داشت؛ بنابراین یک دستور بازگشتی (recursive) مانند chmod -R 755 .* مستقیماً وارد دایرکتوری والد می‌شد. نسخه 5.2 از bash گزینه globskipdots را اضافه کرد که به‌صورت پیش‌فرض فعال است و . و .. را از تمام بسط‌ها (expansion) خارج نگه می‌دارد. پیش از تکیه بر این قابلیت، shopt globskipdots را اجرا کنید، زیرا سیستم‌های قدیمی‌تر فاقد آن هستند.

globstar: تطبیق در تمام سطوح درخت دایرکتوری

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

با فعال بودن globstar، یک ** که یک جزء کامل از مسیر را تشکیل می‌دهد، با فایل‌ها و دایرکتوری‌ها در هر عمقی مطابقت پیدا می‌کند؛ این شامل عمق صفر نیز می‌شود. بنابراین **/*.log دایرکتوری فعلی، به علاوه logs/ و هر چیزی که در زیر archive/ قرار دارد را پوشش می‌دهد. یک **/ که با اسلش (/) ختم می‌شود، فقط با دایرکتوری‌ها مطابقت دارد که روشی سریع برای مشاهده ساختار یک درخت دایرکتوری است.

بدون تنظیم globstar، عبارت ** مانند یک * معمولی عمل کرده و فقط در یک دایرکتوری باقی می‌ماند. همان الگو را با غیرفعال بودن این گزینه اجرا کنید و مقایسه کنید: اگر یک الگوی بازگشتی (recursive) که از جای دیگری کپی کرده‌اید نتایج بسیار کمتری از حد انتظار برمی‌گرداند، معمولاً دلیل آن همین است. گزینه globstar به‌صورت پیش‌فرض در تمام نسخه‌های bash غیرفعال است.

یک محدودیت که باید از آن آگاه باشید: ** از لینک‌های نمادین (symbolic links) به دایرکتوری‌ها پیروی نمی‌کند. درختی که با استفاده از symlinkها به هم متصل شده است، به جای آن به find -L نیاز دارد.

nocaseglob، برای نام‌هایی که در بزرگی و کوچکی حروف تفاوت دارند

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

nocaseglob باعث می‌شود تطبیق نام فایل‌ها به بزرگی و کوچکی حروف حساس نباشد، بنابراین یک پسوند .LOG با حروف بزرگ، با الگوی .log با حروف کوچک مطابقت پیدا می‌کند. این لیست را با لیست بخش اول مقایسه کنید. بلافاصله این گزینه را دوباره خاموش کنید، زیرا روشن ماندن آن، تطبیق را برای تمام دستورات بعدی در آن shell تغییر می‌دهد و این موضوعی است که عیب‌یابی آن پس از یک ساعت بسیار گیج‌کننده خواهد بود.

nocaseglob فقط برای بسط نام فایل (filename expansion) اعمال می‌شود. گزینه مرتبط برای تطبیق الگو در داخل دستور case یا تست‌های double bracket، گزینه nocasematch است و این دو به صورت جداگانه تنظیم می‌شوند.

extglob: الگوهایی که می‌توانند بگویند «این نه»

الگوهای توسعه‌یافته (Extended patterns) به‌صورت پیش‌فرض غیرفعال هستند. آن‌ها را در یک خط جداگانه فعال کنید:

shopt -s extglob

پنج قالب وجود دارد که هر کدام فهرستی از الگوها را می‌پذیرند که با | از هم جدا شده‌اند:

  • ?(list) با صفر یا یک تکرار مطابقت دارد.
  • *(list) با صفر یا تعداد بیشتری تکرار مطابقت دارد.
  • +(list) با یک یا تعداد بیشتری تکرار مطابقت دارد.
  • @(list) دقیقاً با یکی از گزینه‌ها مطابقت دارد.
  • !(list) با هر چیزی که با هیچ‌کدام از گزینه‌ها مطابقت نداشته باشد، همخوانی دارد.

یک الگوی توسعه‌یافته باید توسط shellای خوانده شود که قبلاً این گزینه را فعال کرده است، بنابراین دسته‌دستور زیر در یک اسکریپت کوچک قرار می‌گیرد که با bash -O extglob اجرا می‌شود. پرچم -O یک گزینه shopt را پیش از آنکه shell جدید حتی یک خط از فایل را بخواند تنظیم می‌کند، که به این معنی است که هر الگو در آن به‌درستی تجزیه (parse) می‌شود. پاراگراف آخر این بخش دلیل اهمیت ترتیب را توضیح می‌دهد.

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]) به معنی یک یا چند رقم است، بنابراین پسوند شماره‌دار با هر طولی را پوشش می‌دهد. این راهکار برای محدودیت ? از بخش‌های قبلی است و ارزش دارد که هر دو الگو را پشت سر هم اجرا کنید تا تفاوت را ببینید.

قالب نفی (negation)، پاسخ به «همه چیز به‌جز» است و دلیل اصلی فعال‌سازی extglob همین است. دو نکته در مورد آن وجود دارد که ممکن است کاربران را غافلگیر کند.

این قالب هم با دایرکتوری‌ها و هم با فایل‌ها مطابقت دارد، بنابراین ls !(*.log) محتویات هر دایرکتوری موجود در نتیجه را فهرست می‌کند، نه نام خود دایرکتوری را. از ls -d یا پیش‌نمایش printf استفاده کنید تا خود نام‌ها را ببینید.

این قالب همچنین مانند هر glob دیگری از قانون نقطه پیروی می‌کند، بنابراین !(*.log) تمام نام‌های قابل مشاهده‌ای را که به .log ختم نمی‌شوند پوشش می‌دهد. برای نام‌های مخفی، باید dotglob نیز تنظیم شده باشد.

عبارت ! در !(...) همان نفی در glob است. این عبارت هیچ ارتباطی با گسترش تاریخچه و کاراکتر bang ندارد، که یک قابلیت مجزا است و در مرحلهٔ متفاوتی اجرا می‌شود.

یک تلهٔ دیگر وجود دارد که در زمان تجزیه (parse) رخ می‌دهد. Bash یک دستور کامل را پیش از اجرای هر بخشی از آن تجزیه می‌کند، بنابراین فعال‌سازی extglob و استفاده از یک الگوی توسعه‌یافته در یک خط واحد با شکست مواجه می‌شود: الگو در حالی تجزیه می‌شود که گزینه هنوز غیرفعال است. دستور shopt -s extglob را در خطی جداگانه، پیش از الگوها و در نزدیکی ابتدای اسکریپت قرار دهید. بدنهٔ یک تابع زمانی تجزیه می‌شود که تابع تعریف می‌شود، بنابراین گزینه باید پیش از تعریف تابع فعال باشد، نه پیش از فراخوانی آن. شروع shell با bash -O extglob، همان‌طور که در دسته‌دستور بالا انجام شده، ترتیب را پیش از آغاز هرگونه تجزیه مشخص می‌کند.

پوسته الگو را بسط می‌دهد، نه دستور

این دومین حالت شکست است و توضیح‌دهندهٔ دسته‌ای از باگ‌هاست.

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

خط اول به bash اجازه می‌دهد تا تطبیق را انجام دهد و grep لیستی از نام فایل‌ها را دریافت می‌کند. خط دوم الگو را در کوتیشن قرار می‌دهد، بنابراین find پنج کاراکتر *.log را دریافت کرده و تطبیق را خودش، در تمام سطوح زیر نقطه شروع، انجام می‌دهد. خط سوم باگ است: bash ابتدا الگو را فقط در دایرکتوری جاری بسط می‌دهد، بنابراین به find گفته می‌شود که به دنبال یک نام خاص بگردد. در دایرکتوری با یک تطبیق، دستور بی‌سروصدا به دنبال مورد اشتباه می‌گردد. با دو یا چند تطبیق، find خطای استفاده گزارش می‌دهد، زیرا نام‌های اضافی در جایی قرار می‌گیرند که دستور انتظار یک عبارت را دارد.

قانون کوتاه است. اگر الگو برای دستور است، آن را در کوتیشن قرار دهید. اگر برای پوسته است، آن را بدون کوتیشن رها کنید. همین تفکیک برای الگوهای --exclude در tar و rsync، و فیلترهای --include در grep نیز حاکم است.

گلاب‌ها (Globs) عبارات منظم (regular expressions) نیستند، حتی اگر کاراکترهای مشترکی داشته باشند. در یک گلاب، * به معنای هر رشته‌ای از کاراکترهاست. در یک عبارت منظم، * به معنای صفر یا بیشتر از چیزی است که پیش از آن آمده، بنابراین grep '*.log' چیزی کاملاً متفاوت از آنچه به نظر می‌رسد را درخواست می‌کند.

متغیرها از همین ترتیب عملیات پیروی می‌کنند. گلابینگ پس از بسط متغیر اجرا می‌شود، بنابراین یک متغیر بدون کوتیشن که حاوی یک الگو است، در دایرکتوری بسط داده می‌شود.

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

بدون کوتیشن، bash نتیجه متغیر را گلاب می‌کند. با کوتیشن، الگو به صورت تحت‌اللفظی باقی می‌ماند. نتیجه مرتبطی وجود دارد که باید به آن تکیه کنید: کلمات تولید شده توسط گلابینگ هرگز دوباره بر اساس فاصله (space) تقسیم نمی‌شوند، بنابراین for f in *.log یک نام فایل حاوی فاصله را به عنوان یک نام واحد مدیریت می‌کند، در حالی که for f in $(ls *.log) آن نام را به دو کلمه می‌شکند. مستقیماً روی گلاب حلقه بزنید.

پیش‌نمایش یک الگوی مخرب پیش از اجرا

هرگز اجازه ندهید rm اولین دستوری باشد که یک الگوی جدید را با آن امتحان می‌کنید. الگو را با چیزی بی‌خطر اجرا کنید، لیست را بخوانید و سپس فقط دستور ابتدای خط را تغییر دهید.

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

نوشتن الگو در target باعث می‌شود سه خط از هم جدا نشوند و $target عمداً بدون کوتیشن باقی می‌ماند، زیرا این همان چیزی است که به bash اجازه می‌دهد آن را بسط (expand) دهد. printf '%s\n' هر نام را در یک خط چاپ می‌کند و هیچ تغییری ایجاد نمی‌کند. ls -ld هر ورودی را به تنهایی نمایش می‌دهد و به جای لیست کردن محتویات دایرکتوری‌ها، خود آن‌ها را نشان می‌دهد؛ ستون mode مشخص می‌کند کدام ورودی‌ها دایرکتوری هستند، که این همان چیزی است که باید پیش از هر حذف بازگشتی (recursive delete) بررسی شود. خواندن بیت‌های دسترسی drwxr-xr-x این ستون را پوشش می‌دهد.

خط قبلی را با کلید جهت‌نمای بالا فراخوانی کنید و فقط دستور را ویرایش کنید تا الگو دقیقاً یکسان باقی بماند. تایپ مجدد الگو از روی حافظه، جایی است که اشتباهات رخ می‌دهند.

-- را پیش از الگو قرار دهید. این دستور پایان گزینه‌ها را مشخص می‌کند، بنابراین نام فایلی که با - شروع می‌شود، به عنوان یک نام در نظر گرفته می‌شود و نه یک پرچم (flag).

اگر می‌خواهید globbing را برای بخشی از یک اسکریپت غیرفعال کنید، set -f آن را غیرفعال و set +f دوباره فعال می‌کند.

این عادت اهمیت دارد زیرا rm قابلیت بازگشت (undo) ندارد. بازیابی فایل‌های حذف‌شده با rm -rf فرآیندی کند و معمولاً ناقص است، بنابراین هزینه یک پیش‌نمایش بسیار کمتر از جایگزین آن است.

پس از پایان کار، دایرکتوری تمرینی را پاکسازی کنید. ابتدا مسیر را چاپ کرده و آن را بخوانید، زیرا rm -rf که به متغیر اشتباه اشاره کند، دقیقاً همان حادثه‌ای است که این بخش درباره آن صحبت می‌کند.

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

FAQ

چرا find . -name *.log فایل‌های اشتباهی را پیدا می‌کند؟

زیرا bash عبارت *.log را پیش از آنکه find اصلاً شروع به کار کند، بسط می‌دهد. شل الگو را با دایرکتوری جاری تطبیق می‌دهد و نام فایل حاصل را به find می‌فرستد؛ سپس این دستور در هر عمقی از مسیر، فقط همان یک نام را جستجو می‌کند. اگر دو یا چند فایل با الگو مطابقت داشته باشند، find آرگومان‌های اضافی دریافت می‌کند که در انتظار یک عبارت (expression) است و خطای استفاده (usage error) گزارش می‌دهد. الگو را داخل find . -name '*.log' قرار دهید تا find خودش عمل تطبیق را با قوانین و بازگشت (recursion) اختصاصی خود انجام دهد.

وقتی الگوی من به صورت متن معمولی ظاهر می‌شود، به چه معناست؟

به این معناست که هیچ موردی پیدا نشده است. به‌طور پیش‌فرض، bash الگوی تطبیق‌نیافته را دست‌نخورده باقی می‌گذارد و کاراکترهای خام را به دستور می‌فرستد که آن دستور نیز آن‌ها را به عنوان نام فایل در نظر می‌گیرد. گزینه shopt -s failglob را تنظیم کنید تا الگوی تطبیق‌نیافته به خطایی تبدیل شود که دستور را متوقف می‌کند، یا از shopt -s nullglob استفاده کنید تا الگو به هیچ تبدیل شود. در حالت nullglob، بررسی کنید که آیا دستور بدون هیچ آرگومانی همچنان منطقی است یا خیر، زیرا ls *.nope به یک ls خالی تبدیل شده و کل دایرکتوری را لیست می‌کند.

چگونه در bash همه فایل‌ها را به جز یک الگو انتخاب کنم؟

الگوهای توسعه‌یافته را با دستور shopt -s extglob در یک خط مجزا فعال کنید و سپس از فرم نقیض استفاده کنید. !(*.log) نام‌هایی را که به .log ختم نمی‌شوند مطابقت می‌دهد و !(*.log|*.md) هر دو را مستثنی می‌کند. فایل‌های مخفی در نتیجه نهایی ظاهر نمی‌شوند مگر اینکه dotglob نیز تنظیم شده باشد؛ همچنین دایرکتوری‌ها نیز شامل می‌شوند، بنابراین پیش از دادن الگو به rm، آن را با printf '%s\n' !(*.log) پیش‌نمایش کنید.

چرا * فایل‌های مخفی را نادیده می‌گیرد؟

نقطه ابتدایی باید دقیقاً مطابقت داده شود، بنابراین * هرگز با نامی که با نقطه شروع می‌شود تطبیق نمی‌یابد. برای دسترسی به نام‌های مخفی از .* استفاده کنید یا shopt -s dotglob را تنظیم کنید تا آن‌ها را در هر الگوی معمولی ادغام کند. ورودی‌های . و .. تحت dotglob همیشه مستثنی هستند و bash نسخه 5.2 و بالاتر، آن‌ها را از طریق گزینه globskipdots که به‌طور پیش‌فرض فعال است، از .* نیز دور نگه می‌دارد.

آیا برای کارکرد ** به globstar نیاز دارم؟

بله. بدون shopt -s globstar، bash عبارت ** را به عنوان یک * معمولی در نظر می‌گیرد که فقط در یک دایرکتوری جستجو کرده و در همان‌جا متوقف می‌شود. با فعال بودن globstar، یک ** که یک جزء کامل از مسیر را تشکیل می‌دهد، در هر عمقی مطابقت می‌یابد؛ بنابراین **/*.log به دایرکتوری جاری و تمام زیردایرکتوری‌های آن می‌رسد و **/ به تنهایی فقط دایرکتوری‌ها را مطابقت می‌دهد. توجه داشته باشید که ** از لینک‌های نمادین (symbolic links) به دایرکتوری‌ها پیروی نمی‌کند، بنابراین برای درختی که از symlinkها ساخته شده، از find -L استفاده کنید.

#bash#globbing#shell#linux#scripting