شرح Bash Globbing: البدائل وExtglob
تعلّم كيف يحوّل Bash الأنماط إلى أسماء ملفات قبل تنفيذ الأمر، مع أمثلة عملية على nullglob وdotglob وglobstar ونفي extglob وملاحظة Bash 5.2.
ما يفعله Bash globbing قبل تشغيل الأمر
يحوّل Bash globbing نمطاً مثل *.log إلى قائمة مرتبة من أسماء الملفات الموجودة فعلياً قبل بدء الأمر. لا يمرر rm *.log نمطاً إلى rm. يقرأ Bash الدليل، ويحتفظ بالأسماء المطابقة، ويرتّبها، ثم يشغّل rm مع كل اسم باعتباره وسيطاً منفصلاً. ولا يستطيع برنامج rm معرفة أن نمطاً كان مستخدماً.
ينتج معظم ما يبدو مفاجئاً في globbing عن هذه الحقيقة وحدها. تنفّذ الصدفة المطابقة مقابل الملفات الموجودة في تلك اللحظة، ولا يرى الأمر إلا القائمة النهائية. الاسم الرسمي لهذه العملية هو 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. إذا كانت هذه الصياغة جديدة عليك، يشرح كيفية التقاط استبدال الأوامر لمخرجات أمر ذلك بالتفصيل.
اعرض ما أنشأته، وتحقق من shell المستخدم، لأن ملاحظة في النهاية تعتمد على الإصدار:
ls -a
bash --versionيعمل كل ما سبق على bash 4.0 والإصدارات الأحدث. تنطبق ملاحظة globskipdots قرب النهاية على bash 5.2 والإصدارات الأحدث، وهو الإصدار الذي تأتي به إصدارات Ubuntu وDebian الحالية اعتباراً من August 2026.
الأمر المستخدم للمعاينة طوال الشرح هو printf '%s\n' PATTERN. يطبع تطابقاً واحداً في كل سطر ولا يغيّر أي شيء على القرص، لذلك يمكن تشغيله بأمان على أي نمط لست متأكداً منه.
الأنماط البديلة الثلاثة: النجمة وعلامة الاستفهام والأقواس
*يطابق أي سلسلة من المحارف، بما في ذلك السلسلة الفارغة.?يطابق محرفاً واحداً بالضبط.[...]يطابق محرفاً واحداً بالضبط من المجموعة الموجودة داخل الأقواس.
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]* ثم شغّله مرة أخرى لرؤية الفرق.
تنطبق قاعدتان على الأنماط البديلة الثلاثة. لا يطابق أي نمط بديلاً /، لذلك يبقى النمط داخل مستوى دليل واحد. كما يجب كتابة . البادئة حرفياً، لذلك يتجاوز النمط الذي يبدأ بـ* كل اسم مخفي. ولهذا السبب يترك rm * ملفات النقطة دون تغيير.
تصل نتائج Glob مرتبة وفق ترتيب التجميع في الإعدادات المحلية الحالية، وهو ترتيب نصي يقارن المحارف واحداً تلو الآخر. لذلك تُرتَّب لاحقة 10 قبل لاحقة 2، لأن المقارنة لا تتعامل مع أي منهما بوصفه رقماً. شغّل printf '%s\n' app.log.* ثم ls -v app.log.* وقارن بين الناتجين: يرتّب ls -v الأرقام المضمّنة حسب قيمتها، ويؤدي sort -V الوظيفة نفسها داخل خط أنابيب.
فئات المحارف والنطاقات
داخل الأقواس، يمكنك كتابة قائمة أو نطاق أو نفي.
[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]*النطاقات هي الجزء الذي يختلف بين الأجهزة. يُحدَّد النطاق وفق ترتيب الفرز في الإعداد المحلي الحالي، وليس وفق ASCII فقط. لذلك، يطابق [a-z] الأحرف الكبيرة أيضاً على بعض الأنظمة. يوفّر Bash خيار globasciiranges لفرض ترتيب ASCII فقط، وتفعّله الإصدارات الحديثة افتراضياً. لذلك، تحقّق من إعدادك باستخدام shopt globasciiranges بدلاً من الافتراض. إذا كان يجب على أحد البرامج النصية أن يطابق بالطريقة نفسها في كل مكان، فيمكنه تثبيت الترتيب بنفسه بتعيين LC_ALL=C في سطر مستقل قرب البداية. يعيد ذلك كل نطاق إلى ترتيب البايتات العادي.
يجب أن يكون هذا التعيين أمراً مستقلاً بحد ذاته. عند كتابته كبادئة، يضبط LC_ALL=C ls [a-z]* الإعداد المحلي لـ ls فقط. ويكون الـshell قد وسّع النمط بالفعل وفق إعداده المحلي قبل بدء ls، وهذه هي القاعدة نفسها من القسم الأول التي تظهر هنا مجدداً.
توسعة الأقواس ليست توسعة الأنماط
تبدو الأقواس مثل حرف بدل، لكنها تعمل بطريقة مختلفة تماماً. تحدث توسعة الأقواس قبل توسعة الأنماط بوقت طويل، ولا تفحص القرص مطلقاً.
echo file{1,2,3}.txt
echo {01..10}
echo {a..e}
echo {0..20..5}لا يوجد أي من هذه الملفات، ومع ذلك تحدث التوسعة. هذا هو الفرق الأساسي: يسأل النمط نظام الملفات عن الملفات المطابقة، بينما تنشئ الأقواس نصاً فقط. لذلك تُستخدم الأقواس لإنشاء العناصر بدلاً من العثور عليها.
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 يضيف أصفاراً إلى كل رقم مُنشأ حتى يصبح بالعرض نفسه.
يوضح ترتيب التوسعة أيضاً سبب حدوث خطأ شائع. يوسّع Bash الأقواس أولاً، ثم المتغيرات، ثم ينفّذ توسعة أسماء المسارات أخيراً.
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، ويتطلب حذفه لاحقاً استخدام علامات الاقتباس حتى لا توسّعه الصدفة مرة أخرى: rm -- '*.tmp'.
يغيّر خياران في الصدفة هذه القاعدة. جرّب كل خيار منهما مع نمط لا يطابق أي شيء، ثم ألغِ تفعيله.
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 على النمط الحرفي. وهذا خطأ في جميع النصوص البرمجية تقريباً التي تفعل ذلك. عند تفعيل nullglob، يُنفَّذ جسم الحلقة صفراً من المرات.
يحمل nullglob خطراً خاصاً به، لأن الأمر الذي جُرّدت جميع وسائطه منه يستمر في العمل. يصبح ls *.nope أمراً مجرداً من ls، فيعرض الدليل بأكمله. ويصبح grep -l needle *.nope أمراً من grep -l needle، فلا يجد ملفاً لقراءته، ولذلك ينتظر الإدخال القياسي ويبدو كأنه توقف عن الاستجابة. فعّل nullglob حول الحلقة التي تحتاج إليه، ثم عطّله مرة أخرى بعد ذلك.
يسلك failglob المسار الآخر: يُعدّ النمط غير المطابق خطأً، ويبلّغ bash عنه، ولا يُنفّذ الأمر إطلاقاً. وهذا إعداد مناسب للصدفة التفاعلية، لأنه يمنع تمرير نمط مكتوب خطأً إلى rm باعتباره اسماً حرفياً.
الملفات المخفية: dotglob
shopt -s dotglob
printf '%s\n' *
shopt -u dotglobنفِّذ هذه الكتلة وقارن القائمة الناتجة بـ printf '%s\n' * العادي من القسم السابق. يزيل dotglob قاعدة النقطة البادئة، لذلك يطابق * الأسماء المخفية أيضاً. يبقى الإدخالان . و.. مستبعدين مع dotglob في جميع الحالات.
من دون dotglob، تكون .* الطريقة المعتادة للوصول إلى الملفات المخفية، لكنها كانت خطرة سابقاً. في إصدارات bash الأقدم من 5.2، كان .* يطابق أيضاً . و..، لذلك كان أمر تكراري مثل chmod -R 755 .* ينتقل مباشرة إلى الدليل الأب. أضاف Bash 5.2 الخيار globskipdots، وهو مفعّل افتراضياً، ويُبقي . و.. خارج كل عملية توسعة. نفِّذ shopt globskipdots قبل الاعتماد عليه، لأن نظاماً أقدم لن يتضمنه.
globstar: المطابقة حتى جميع مستويات شجرة المجلدات
shopt -s globstar
printf '%s\n' **/*.log
printf '%s\n' **/
shopt -u globstarعند ضبط globstar، تطابق ** التي تشكّل مكوّناً كاملاً من المسار الملفات والمجلدات على أي عمق، بما في ذلك عدم النزول إلى أي مستوى. لذلك تغطي **/*.log الدليل الحالي، إضافة إلى logs/ وكل ما يوجد ضمن archive/. أما **/ مع الشرطة المائلة اللاحقة، فتطابق الأدلة فقط، ما يجعلها طريقة سريعة لعرض بنية شجرة المجلدات.
عند عدم ضبط globstar، تتصرف ** مثل * عادية وتبقى ضمن دليل واحد. شغّل النمط نفسه مع تعطيل الخيار وقارن النتيجة: إذا أعاد نمط تكراري نسخته من مكان آخر نتائج أقل بكثير مما توقعت، فغالباً يكون هذا هو السبب. تكون globstar معطّلة افتراضياً في كل إصدار من bash.
تجدر معرفة حدّ واحد: لا تتبع ** الروابط الرمزية إلى الأدلة. إذا كانت شجرة المجلدات موصولة عبر روابط رمزية، فاستخدم find -L بدلاً منها.
nocaseglob، لمطابقة الأسماء التي يختلف فيها ترميز الأحرف
shopt -s nocaseglob
printf '%s\n' *.log
shopt -u nocaseglobيجعل nocaseglob مطابقة أسماء الملفات غير حساسة لحالة الأحرف، لذلك تطابق اللاحقة ذات الأحرف الكبيرة .LOG النمط ذا الأحرف الصغيرة .log. قارن هذه القائمة بالقائمة الواردة في القسم الأول. عطّل الخيار مجدداً فوراً، لأن تركه مفعّلاً يغيّر المطابقة لكل أمر يأتي بعده في جلسة الصدفة نفسها، ومن الصعب تشخيص ذلك بعد مرور ساعة.
ينطبق nocaseglob على توسيع أسماء الملفات فقط. أما الخيار المرتبط بمطابقة الأنماط داخل عبارة case أو اختبار الأقواس المزدوجة، فهو nocasematch، ويُضبط الخياران بشكل منفصل.
extglob: الأنماط التي تعني «ليس هذا»
تكون الأنماط الموسّعة معطّلة افتراضياً. فعّلها في سطر مستقل:
shopt -s extglobتوجد خمسة أشكال، ويأخذ كل منها قائمة من الأنماط مفصولة بـ |:
?(list)يطابق تكراراً صفراً أو مرة واحدة.*(list)يطابق صفراً أو أكثر من التكرارات.+(list)يطابق مرة واحدة أو أكثر.@(list)يطابق بديلاً واحداً بالضبط من البدائل.!(list)يطابق أي شيء لا يطابق أي بديل.
يجب أن يقرأ النمط الموسّع shell فعّل الخيار فيه مسبقاً، لذلك يوضع الدفعي أدناه في script صغير يبدأ باستخدام bash -O extglob. يفعّل الخيار -O من خيارات shopt قبل أن يقرأ الـshell الجديد سطراً واحداً من الملف، ما يعني أن كل نمط فيه سيُحلَّل بصورة صحيحة. يوضّح القسم الأخير من هذا القسم سبب أهمية الترتيب.
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]) رقماً واحداً أو أكثر، لذلك يطابق لاحقة مرقّمة بأي طول. وهذا يصلح حد ? المذكور سابقاً. من المفيد تشغيل النمطين بالتتابع لرؤية الفرق.
صيغة النفي هي الحل لمطابقة «كل شيء باستثناء»، وهي سبب تفعيل extglob أساساً. وهناك أمران يسببان الالتباس.
تطابق هذه الصيغة الأدلة والملفات أيضاً، لذلك يسرد ls !(*.log) محتويات أي دليل ضمن النتيجة بدلاً من اسم الدليل. استخدم ls -d، أو المعاينة printf، لرؤية الأسماء نفسها.
كما أنها تتبع قاعدة النقطة مثل كل glob آخر، لذلك يطابق !(*.log) كل اسم ظاهر لا ينتهي بـ .log. تحتاج الأسماء المخفية إلى تفعيل dotglob أيضاً.
يشير ! في !(...) إلى نفي glob. ولا علاقة له بـ توسيع سجل الأوامر والمحرف bang، فهذه ميزة مستقلة تعمل في مرحلة مختلفة.
هناك فخ آخر، ويحدث هذا أثناء التحليل. يحلل Bash الأمر الكامل قبل تشغيل أي جزء منه، لذلك يفشل تفعيل extglob واستخدام نمط موسّع في السطر نفسه: إذ يُحلَّل النمط بينما لا يزال الخيار معطّلاً. ضع shopt -s extglob في سطر مستقل، قبل الأنماط وبالقرب من بداية script. يُحلَّل جسم الدالة عند تعريفها، لذلك يجب تفعيل الخيار قبل التعريف، لا قبل الاستدعاء. ويؤدي بدء الـ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.
لا تُعد أنماط glob تعبيرات نمطية، حتى إذا اشتركت معها في بعض الأحرف. في نمط glob، يعني * أي تسلسل من الأحرف. أما في التعبير النمطي، فيعني * صفراً أو أكثر مما سبقه، لذلك يطلب grep '*.log' شيئاً مختلفاً تماماً عما يبدو عليه.
تتبع المتغيرات ترتيب العمليات نفسه. يحدث توسيع glob بعد توسيع المتغير، لذلك يُوسَّع المتغير غير الموضوع بين علامتي اقتباس إذا كان يحتوي على نمط، وذلك وفق محتويات الدليل.
pat='*.log'
printf '%s\n' $pat
printf '%s\n' "$pat"عندما لا تضع المتغير بين علامتي اقتباس، يوسّع bash نتيجة المتغير وفق glob. وعندما تضعه بين علامتي اقتباس، يبقى النمط حرفياً. وينتج عن ذلك أمر ذو صلة يجب الاعتماد عليه: لا تُقسَّم الكلمات الناتجة عن glob مرة أخرى عند المسافات، لذلك يتعامل for f in *.log مع اسم ملف يحتوي على مسافة باعتباره اسماً واحداً، بينما يقسم for f in $(ls *.log) ذلك الاسم إلى كلمتين. نفّذ الحلقة على glob مباشرة.
عاين النمط التدميري قبل تشغيله
لا تجعل rm أول أمر تجرّبه مع نمط جديد. شغّل النمط باستخدام أمر غير ضار، واقرأ القائمة، ثم غيّر الأمر في بداية السطر فقط.
target='!(*.log)'
printf '%s\n' $target
ls -ld -- $target
rm -- $targetتمنع كتابة النمط في target مرة واحدة الأسطر الثلاثة من التباعد عن بعضها، وتُترك $target من دون علامات اقتباس عمداً، لأن ذلك ما يتيح لـbash توسيعها. يطبع printf '%s\n' اسماً واحداً في كل سطر ولا يغيّر شيئاً. يعرض ls -ld كل إدخال بحد ذاته بدلاً من سرد محتويات الأدلة، ويبيّن عمود النمط أي الإدخالات أدلة؛ وهذه هي النقطة التي يجب التحقق منها قبل أي حذف تكراري. يشرح قراءة بتات الأذونات drwxr-xr-x هذا العمود.
استدعِ السطر السابق بسهم الأعلى، وعدّل الأمر فقط، حتى يبقى النمط مطابقاً. تحدث الأخطاء غالباً عند إعادة كتابة النمط من الذاكرة.
ضع -- قبل النمط. فهو يحدد نهاية الخيارات، لذلك يُعامل اسم الملف الذي يبدأ بـ- اسماً لا خياراً.
إذا أردت تعطيل توسيع الأنماط globbing مؤقتاً في جزء من البرنامج النصي، فإن set -f يعطّله، ويعيد set +f تفعيله.
تكتسب هذه العادة أهميتها لأن rm لا يوفّر ميزة التراجع. إن استعادة الملفات المحذوفة باستخدام rm -rf بطيئة وعادةً غير مكتملة، لذلك تكلّف المعاينة أقل بكثير من البديل.
نظّف دليل التدريب عند الانتهاء. اطبع المسار واقرأه أولاً، لأن توجيه rm -rf إلى المتغير الخطأ هو بالضبط الحادث الذي يتناوله هذا القسم.
cd ~
echo "$lab"
rm -rf -- "$lab"FAQ
لماذا يعثر find . -name *.log على الملفات الخطأ؟
لأن bash يوسّع *.log قبل أن يبدأ find. تطابق الصدفة النمط مع الدليل الحالي، ثم تمرّر اسم الملف الناتج إلى find، الذي يبحث بعد ذلك عن ذلك الاسم الواحد في كل المستويات. إذا تطابق النمط مع ملفين أو أكثر، يتلقى find وسائط إضافية في موضع يتوقع فيه تعبيراً، ويعرض خطأ في صيغة الاستخدام. ضع النمط بين علامتي اقتباس كما في find . -name '*.log'، لكي ينفّذ find المطابقة بنفسه، وفق قواعده الخاصة وآلية التكرار الخاصة به.
ماذا يعني ظهور النمط كنص حرفي؟
يعني أن أياً من الملفات لم يطابقه. يترك bash النمط غير المطابق كما هو افتراضياً، ويمرّر الأحرف الأصلية إلى الأمر، الذي يتعامل معها بعد ذلك على أنها اسم ملف. اضبط shopt -s failglob لتحويل النمط غير المطابق إلى خطأ يوقف الأمر، أو اضبط shopt -s nullglob لجعله يتمدّد إلى لا شيء. عند استخدام nullglob، تحقق من أن الأمر لا يزال منطقياً من دون أي وسائط، لأن ls *.nope يصبح ls مجرداً ويعرض محتويات الدليل بالكامل.
كيف أطابق كل ملف باستثناء نمط واحد في bash؟
فعّل الأنماط الموسّعة باستخدام shopt -s extglob في سطر مستقل، ثم استخدم صيغة النفي. يطابق !(*.log) الأسماء التي لا تنتهي بـ .log، بينما يستبعد !(*.log|*.md) النمطين معاً. تبقى الملفات المخفية خارج النتيجة ما لم تضبط dotglob أيضاً، كما تُضمّن الأدلة في النتيجة. لذلك اعرض نتيجة النمط أولاً باستخدام printf '%s\n' !(*.log) قبل تمريرها إلى rm.
لماذا يتخطى * الملفات المخفية؟
يجب مطابقة النقطة في بداية الاسم حرفياً، لذلك لا يطابق * أي اسم يبدأ بها. استخدم .* للوصول إلى الأسماء المخفية، أو اضبط shopt -s dotglob لضمّها إلى كل نمط عادي. يُستبعد الإدخالان . و.. دائماً عند استخدام dotglob، كما يبقيهما bash 5.2 والإصدارات الأحدث خارج .* أيضاً من خلال الخيار globskipdots، المفعّل افتراضياً.
هل أحتاج إلى globstar لكي يعمل **؟
نعم. من دون shopt -s globstar، يتعامل bash مع ** على أنه * عادي، يطابق داخل دليل واحد ويتوقف عنده. عند تفعيل globstar، يطابق ** الذي يشكّل مكوّناً كاملاً من مسار على أي عمق، لذلك يصل **/*.log إلى الدليل الحالي وكل دليل فرعي تحته، بينما يطابق **/ بمفرده الأدلة فقط. لاحظ أن ** لا يتبع الروابط الرمزية إلى الأدلة، لذلك استخدم find -L عند التعامل مع شجرة مبنية من الروابط الرمزية.