هناك نصيحة سيسمعها مبرمجو bash الجدد مرارًا وتكرارًا:
ضع متغيراتك بين علامات التنصيص!
لماذا ذلك؟
عندما تُوسّع متغيرًا (مثل $name) فأنت تريد في الغالب استخدام قيمة المتغير كسلسلة نصية واحدة (أو عدد).
لا تريد أن تُعالَج القيمة بشكل غير متوقع على أنها سلسلتان نصيتان منفصلتان أو أكثر.
التنصيص هو الآلية الأساسية لضمان معاملة قطعة نص ككلمة واحدة. هناك نوعان رئيسان من علامات التنصيص.
تسمح علامات التنصيص المزدوجة بتوسيع المعاملات والتوسيع الحسابي واستبدال الأوامر داخلها.
echo "This is bash version $BASH_VERSION"
يمكنك تضمين علامة تنصيص مزدوجة حرفية داخل سلسلة نصية محاطة بعلامات تنصيص مزدوجة إذا سبقتها بشرطة مائلة عكسية.
echo "She said \"Let me teach you about bash.\""
علامات التنصيص المفردة لا تسمح بأي توسيع داخلها.
echo 'This is bash version $BASH_VERSION'
# ...^..................................^
# prints exactly: This is bash version $BASH_VERSION
من غير الممكن تضمين علامة تنصيص مفردة داخل سلسلة نصية محاطة بعلامات تنصيص مفردة.
ما المشكلة بالضبط في echo $name؟
عندما تحلّل الصدَفة سطرًا من الكود، فإنها تُجري عدة أنواع من التوسيعات بالترتيب. اثنان من التوسيعات الأخيرة هما تقسيم الكلمات و_توسيع أسماء الملفات_.
أي نتائج ناتجة عن توسيع المعاملات أو استبدال الأوامر أو التوسيع الحسابي لم تحدث داخل علامات تنصيص مزدوجة تكون مؤهّلة "لتقسيم الكلمات".
تأخذ الصدَفة النتيجة الموسَّعة، وتقسّمها عند أي تسلسل من المحارف موجود في قيمة $IFS.
افتراضيًا، تكون هذه هي المسافة ومسافة الجدولة والسطر الجديد.
يمكننا رؤية ذلك في العمل باستخدام حلقة في الصدَفة:
sentence="The quick brown fox jumps."
i=0
for word in "$sentence"; do echo "$((++i)) $word"; done
# ..........^.........^
فيكون الناتج:
1 The quick brown fox jumps.
لكن بدون علامات تنصيص:
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
فيكون الناتج:
1 The
2 quick
3 brown
4 fox
5 jumps.
لنرَ ذلك بقيمة مختلفة لـ $IFS:
csv="first,second,third"
IFS=","
for word in $csv; do echo "$word"; done
فيكون الناتج:
first
second
third
أحد الأساليب لتعطيل تقسيم الكلمات هو ضبط $IFS على سلسلة نصية فارغة
sentence="The quick brown fox jumps."
IFS= # or explicitly, IFS=""
i=0
for word in $sentence; do echo "$((++i)) $word"; done
# .........^.........^
فيكون الناتج:
1 The quick brown fox jumps.
لكن لا يُنصح بترك المتغيرات بدون علامات تنصيص إلا إذا عطّلت أيضًا توسيع أسماء الملفات. سنرى كيفية القيام بذلك أدناه.
تُستخدم أنماط glob للتعبير عن نمط موجز لمطابقة مجموعة من الملفات.
على سبيل المثال، قد تُطابق جميع ملفات Markdown في مجلد باستخدام *.md.
ظهرت أنماط glob منذ اختراع Unix.
محارف البدل في glob هي:
يطابق * صفرًا أو أكثر من أي محرف،
يطابق ? محرفًا واحدًا بالضبط من أي نوع،
يطابق [...] محرفًا واحدًا بالضبط من مجموعة المحارف المدرجة بين القوسين.
[abc] محرفًا واحدًا بالضبط يكون إما a أو b أو c.
^ اختياري)
[][] مجموعة محارف تطابق إما قوس إغلاق أو قوس فتح.[[\]]
[x-y] محرفًا واحدًا بالضبط يقع في النطاق من x إلى y
^ اختياري) أو آخر محرف قبل قوس الإغلاق.
[-xy] و[xy-] محرفًا واحدًا بالضبط يكون - أو x أو y
[x\-y]
[0-9A-Fa-f] رقمًا سداسيًا عشريًا[^abc] محرفًا واحدًا بالضبط ليس a أو b أو c،[^x-y] محرفًا واحدًا بالضبط ليس في النطاق من x إلى y
[[:character_class:]] محرفًا واحدًا ينتمي إلى "character_class" المسمّاة
lower -- الحروف الصغيرةupper -- الحروف الكبيرةalpha -- الحروفdigit -- الأرقامalnum -- الحروف والأرقامspace -- المسافات البيضاءblank -- المسافات البيضاء الأفقية (مسافة ومسافة جدولة)xdigit -- الأرقام السداسية عشريةword -- المحارف المسموح بها في معرّف (حروف وأرقام وشرطة سفلية)punct -- علامات الترقيمcntrl -- محارف التحكمgraph -- المحارف "المرئية"print -- المحارف المرئية إضافة إلى المسافة[[:alpha:]] من [[:lower:][:upper:]]
[[:alnum:]] من [[:alpha:][:digit:]]
[[:word:]] من [[:alnum:]_]
[[:word:]] و[[:punct:]]
[[:graph:]] من [[:alnum:][:punct:]]
[[:print:]] من [[:graph:] ] -- المسافة وحدها (ثماني 040) وليس أي مسافة بيضاء أخرى.يمكنك التكرار على قائمة ملفات باستخدام حلقة for
for file in *.csv; do
do_something_with "$file"
done
لاحظ أن المتغير "$file" موضوع بين علامات تنصيص.
قد يُطابق نمط glob ملفات تحتوي أسماؤها على مسافات.
نحتاج إلى تنصيص المتغير حتى يستقبل الأمر do_something_with وسيطًا واحدًا بالضبط، وهو اسم الملف.
أنماط glob ليست تعبيرات نمطية.
.
?
.*
*
.* يعني "مطابقة اسم ملف حيث يكون المحرف الأول نقطة حرفية تتبعها صفر أو أكثر من أي محارف"..+
?*
توفّر Bash أنماطًا موسّعة أقرب إلى التعبيرات النمطية.
هناك أوقات تريد فيها منع توسيع أسماء الملفات. التعامل مع إدخال المستخدم أحد هذه الأوقات. خذ بعين الاعتبار مقتطف الكود هذا
read -p "Enter something: " user_input
echo $user_input
إذا أدخل المستخدم نجمة (*)، فما الذي سيُخرَج؟
قائمة الملفات في المجلد الحالي.
هذا على الأرجح ليس ما تريد فعله.
مثال آخر هو تمرير الأنماط كوسائط إلى برامج تتوقع أنماطًا.
غالبًا ما يُستخدم برنامج tr لمعالجة النصوص. لتحويل قيمة متغير إلى حروف صغيرة، يمكنك فعل هذا
var="My Puppet"
echo "$var" | tr '[:upper:]' '[:lower:]'
فيكون الناتج:
my puppet
من الأخطاء الشائعة نسيان تنصيص وسائط tr: tr [:upper:] [:lower:]
لماذا يُعدّ ذلك مشكلة؟
ستُوسَّع أنماط glob غير الموضوعة بين علامات تنصيص إلى قائمة الملفات المطابقة.
افترض أن مستخدم سكربتك لديه ملفات باسم p وu وw في المجلد الحالي:
touch p u w
var="My Puppet"
echo "$var" | tr [:upper:] [:lower:]
يؤدي هذا إلى خطأ!
يطابق النمط [:upper:] غير الموضوع بين علامات تنصيص أيًا من : أو u أو p أو e أو r.
يطابق النمط [:lower:] غير الموضوع بين علامات تنصيص أيًا من : أو l أو o أو w أو e أو r.
تُطابِق Bash الملفين p وu مع النمط الأول، والملف w مع النمط الثاني، ويُستدعى tr هكذا
echo "$var" | tr p u w
وهذا عدد خاطئ من الوسائط بالنسبة إلى tr.
يمكنك استخدام الأمر set لتعطيل توسيع أسماء الملفات.
echo * # prints a list of files
set -f # disable filename expansion
echo * # prints a literal asterisk
set +f # enable filename expansion
هناك عدة إعدادات للأمر المضمّن shopt تتحكم في كيفية عمل توسيع أسماء الملفات.
وهناك إعدادان مثيران للاهتمام:
shopt -s nocaseglob -- إجراء مطابقة غير حساسة لحالة الأحرفshopt -s extglob -- تمكين الأنماط الموسّعةshopt -s nullglob -- إذا لم تطابق أي ملفات، استبدل النمط بلا شيء.
السلوك الافتراضي هو ترك النمط في مكانه كسلسلة نصية حرفية.