تجزیه کننده پوسته، قبل از اجرای نهایی فرمانهای ما، چند عمل روی آنها انجام میدهد. درک اینکه فرمان اولیه شما چگونه توسط پوسته تغییر شکل داده می شود، در نوشتن اسکریپتهای قدرتمند از بالاترین اهمیت برخوردار است. نقل از صفحههای man پوسته bash:
ترتیب انجام بسطها عبارت است از: بسط ابرو، بسط مَد، پارامتر، متغیر، و بسط حسابی و جایگزینی دستور(به ترتیب از چپ به راست)، تفکیک کلمه، و بسط نام مسیر.
برای اطلاعات اضافی در مورد تفکیک کلمه و کار با شناسهها در Bash، صفحه شناسهها را مطالعه نمایید.
البته، این صفحه بر تفکیک کلمه متمرکز خواهد شد. قبل از اینکه وارد جزئیات فنی بشویم، اجازه دهید یک اسکریپت کمکی بنویسیم، که چگونگی عبور دادن شناسهها توسط پوسته را به ما نشان خواهد داد:
#!/bin/sh printf "%d args:" $# printf " <%s>" "$@" echo
اگر شما فایلی به نام args با محتویات فوق ایجاد کنید، و با فرمان chmod +x args آن را قابل اجرا نمایید، و آن را در یکی از دایرکتوریهای لیست شده در خروجی echo "$PATH" قرار بدهید، آنوقت میتوانید فرمان زیر را اجرا نموده و خروجی پایین را داشته باشید:
griffon:~$ args hello world "how are you?" 3 args: <hello> <world> <how are you?>
نتیجه نهایی اکثر فرمانهای پوسته، اجرای برنامههایی با مجموعهای از شناسههای تعیین شده(مانند متغیرهای برقرار شده محیط، توصیفگرهای فایل باز و غیره)، است. تفکیک کلمه قسمتی از فرآیندی است، که تعیین میکند، هر یک از آن شناسهها چه خواهند بود -- پس از تفکیک کلمه و بسط نام مسیر، هر کلمه نتیجه شده، یک شناسه میشود برای برنامهای که پوسته اجرا میکند. برنامه کمکی ما در بالا، لیست شناسهها را آنطور که شل ساخته است، دریافت نموده و به ما نشان میدهد.
تفکیک کلمه روی نتایج تقریباً تمام بسطهای غیر نقلقولی انجام می شود. نتیجه بسط بر اساس کاراکترهای متغیر IFS به کلمات جداگانه شکسته میشود. اگر متغیر IFS برقرار نباشد، سپس این امر آنطور انجام میشود، که اگر IFS محتوی فاصله، tab، و سطرجدید باشد. برای مثال:
griffon:~$ var="This is a variable" griffon:~$ args $var 4 args: <This> <is> <a> <variable>
یک مثال با کاربرد IFS:
griffon:~$ log=/var/log/qmail/current IFS=/ griffon:~$ args $log 5 args: <> <var> <log> <qmail> <current> griffon:~$ unset IFS
یک مثال با جایگزینی فرمان:
griffon:/music/Yello$ ls -l total 2864 -rw-r--r-- 1 greg greg 2919154 2001-05-23 00:48 Yello - Oh Yeah.mp3 griffon:/music/Yello$ args $(ls) 4 args: <Yello> <-> <Oh> <Yeah.mp3>
همچنانکه میتوانید در بالا مشاهده نمایید، به طور معمول ما نمیخواهیم وقتی با نام فایل مواجه هستیم، تفکیک کلمه رخ بدهد. (برای بحث در باره این موضوع ویژه، بخش تلهها را ملاحظه کنید.)
نقلقول دوگانه یک عبارت، غیر از موارد خاص "$@" و "${array[@]}" ، تفکیک کلمه را موقوف مینماید، :
griffon:~$ var="This is a variable"; args "$var" 1 args: <This is a variable> griffon:~$ array=(testing, testing, "1 2 3"); args "${array[@]}" 3 args: <testing,> <testing,> <1 2 3>
"$@" باعث میشود هر پارامتر مکانی به یک کلمه جداگانه بسط داده شود، همارز آرایهای آن نیز باعث میشود، هرعنصر آرایه به یک کلمه جداگانه بسط داده شود.
قواعد خیلی پیچیدهای در ارتباط با کاراکترهای فضای سفید IFS وجود دارد. بار دیگر نقلقول صفحه man :
اگرIFS برقرار نباشد(unset)، یا دقیقاً مقدار پیشفرض <space><tab><newline> را داشته باشد، آن وقت هر توالی از کاراکترهای IFS برای تفکیک کلمات به کار میرود. اگر IFS مقدار غیر پیشفرض داشته باشد، آنوقت مادامیکه کاراکتر فضای سفید در محتوای متغیر IFS هست (یک کاراکتر فضای سفید IFS)، فضاهای سفید متوالی فاصله و tab در ابتدا و انتهای کلمات صرفنظر میگردند. هر کاراکتری در IFS که فضای سفید IFS نیست، همراه با هر کاراکتر فضای سفید IFS مجاور، فیلد را جدا میکند. رشتهای از کاراکترهای فضای سفید IFS نیز به عنوان جداکننده رفتار میکند. اگر محتوای IFS تهی باشد، تفکیک کلمه رخ نمیدهد.
ما نمیخواهیم آن قواعد را به طورعمقی در اینجا بشکافیم، به استثنای توجه دادن به قسمت مربوط به رشته کاراکترهای غیر فضای سفید. اگر IFS محتوی کاراکترهای غیر فضای سفید باشد، در آن حالت کلمات تهی میتوانند تولید بشوند:
griffon:~$ getent passwd sshd sshd:x:100:65534::/var/run/sshd:/usr/sbin/nologin griffon:~$ IFS=:; args $(getent passwd sshd) 7 args: <sshd> <x> <100> <65534> <> </var/run/sshd> </usr/sbin/nologin> griffon:~$ unset IFS
در یکی از مثالهای قبلی ما نیز کلمه تهی دیگری تولید شده بود، جایی که IFS به / تنظیم شد. خوانندگان هشیار توجه کردهاند، از اینرو، به طریقی که با کاراکترهای فضای سفید IFS عمل میشود، آن کاراکترهای غیرفضای سفید IFS در ابتدا و انتهای بسطها صرفنظر نمیشوند.
وقتی IFS شامل یک فضای سفید باشد(یا به هیچوجه مقدار نداشته باشد)، کاراکترهای فضای سفید IFS یگانهسازی میشوند. برای مثال، چندین فاصله در یک سطر همان اثر را دارند که یک فاصله منفرد دارد. سطرهای جدید نیز برای این منظور همچون فضای سفید محسوب میشوند، که وقتی مبادرت به بار گیری یک آرایه با سطرهای ورودی، میشود، پیامدهای مهمی دارد.
سرانجام، ما توجه میدهیم که بسط نام مسیر بعد از تفکیک کلمه به وقوع میپیوندد، و میتواند نتایج تکاندهندهای ایجاد کند.
griffon:~$ getent passwd qmaild qmaild:*:994:998::/var/qmail:/sbin/nologin griffon:~$ IFS=:; args $(getent passwd qmaild) 737 args: <qmaild> <00INDEX.lsof> <03> <037_ftpd.patch> ... griffon:~$ unset IFS
کلمه *، که توسط تفکیک کلمه پوسته تولید شده، سپس به عنوان یک glob بسط داده شده است، منجر به صدها کلمه جدید و مهیج گردیده است. این اگر به طور غیرمنتظره رخ بدهد، میتواند مصیبتبار باشد. مانند اکثر ویژگیهای خطرناک پوسته، حفظ گردیده است، زیرا «همیشه به همان طریق عمل میکند». در حقیقت، اگر شما دقیق باشید، میتواند به طور پیوسته استفاده بشود:
griffon:/music/Yello$ files='*.mp3 *.ogg' griffon:/music/Yello$ args $files 2 args: <Yello - Oh Yeah.mp3> <*.ogg>
بسط نام مسیر میتواند با دستور set -f غیر فعال شود، اگرچه این میتواند منجر به غافلگیری و مغشوش سازی شود.
تفکیک کلمه در بسطهای داخل کلیدواژههای Bash از قبیل [[ ... ]] و case انجام نمیشود.
foo=$bar
bar=$(a command)
logfile=$logdir/foo-$(date +%Y%m%d)
PATH=/usr/local/bin:$PATH ./myscript
در هر یک از این دو حالت، به هرحال نقلقولی کردن، چیزی را تجزیه نمیکند. بنابراین اگر مردد هستید، نقلقول کنید!
موقعی که از فرمان read استفاده میکنید، تفکیک کلمه بر ورودی اِعمال میشود، اما فقط موقعی که چند نام متغیر فراهم شده باشد، یا موقعی که read -a به کار رفته باشد(برای استقرار در یک آرایه). در اینجا نقلقول بی ربط است، اگرچه این رفتار، با حذف فضای سفید از IFS میتواند غیر فعال شود.