فصل ۷ – دیدن دنیا از نگاه شل


مقدمه

شل هنگام اجرای دستورات ما فقط آن‌ها را «همان‌طور که تایپ کرده‌ایم» اجرا نمی‌کند.
پیش از آن‌که برنامه‌ی مورد نظر فراخوانی شود، شل رشته‌ی ورودی را بررسی می‌کند، بخش‌هایی از آن را گسترش می‌دهد، الگوها را با نام فایل‌ها تطبیق می‌دهد و متن نهایی را آماده می‌کند.
درک این فرایند باعث می‌شود بتوانیم قدرت واقعی خط فرمان را آزاد کنیم و از غافل‌گیر شدن توسط رفتارهای ظاهراً عجیب جلوگیری کنیم.

در این فصل می‌خوانیم:


آشنایی با گسترش‌ها

در مستندات bash واژه‌ی «expansion» برای مراحلی استفاده می‌شود که طی آن شل ورودی کاربر را گسترش می‌دهد و سپس نتیجه‌ی نهایی را به برنامه تحویل می‌دهد.
این فرایند معمولاً در پشت صحنه رخ می‌دهد و تا زمانی که بدانیم شل چگونه دنیا را می‌بیند، دستورات ما دقیقاً همان کاری را انجام خواهند داد که انتظار داریم.

برای مثال، وقتی می‌نویسیم:

echo *

ما واقعاً از برنامه‌ی echo نمی‌خواهیم ستاره را چاپ کند؛ در عوض bash پیش از اجرای echo، الگوی * را با همه‌ی فایل‌های موجود در پوشه‌ی جاری جایگزین می‌کند و سپس نام‌ها را به echo تحویل می‌دهد.


گسترش نام مسیر (Pathname Expansion)

گسترش نام مسیر که معمولاً «وايلدکارد» یا «الگو» هم نامیده می‌شود، برای یافتن مجموعه‌ای از فایل‌ها یا پوشه‌ها با نام‌های مشابه استفاده می‌شود.
رایج‌ترین نمادها عبارت‌اند از:

نماد معنی
* با هر رشته‌ای (حتی رشته‌ی خالی) مطابقت می‌کند.
? دقیقاً با یک کاراکتر دلخواه تطابق دارد.
[abc] با یکی از کاراکترهای موجود در براکت مطابقت می‌کند.
[a-z] با هر کاراکتر در محدوده‌ی مشخص (مثلاً a تا z) تطابق دارد.
[!abc] یا [^abc] با هر کاراکتری به جز آن‌هایی که ذکر شده‌اند مطابقت دارد.

مثال‌ها:

ls D*          # همه‌ی فایل‌هایی که با D آغاز می‌شوند.
ls /etc/pas?   # نام‌هایی مانند /etc/pass و /etc/past را امتحان می‌کند.
ls /usr/bin/[a-z]*
ls *.[ch]      # فایل‌های با پسوند c یا h
ls /etc/[!a-z]*

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

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

فایل‌هایی که با نقطه (.) آغاز می‌شوند، در لیست‌های معمول ls نمایش داده نمی‌شوند و * هم آن‌ها را شامل نمی‌شود.
برای افزودن آن‌ها باید نقطه را به طور صریح در الگو بیاوریم:

ls -a .*       # همه‌ی فایل‌های مخفی
ls .??*        # فایل‌های مخفی با دست‌کم دو کاراکتر پس از نقطه

الگوهای چندگانه و مرتب‌سازی

گسترش‌های متعدد می‌توانند در یک دستور ترکیب شوند:

ls /etc/{host,passwd,issue}*

پس از گسترش، bash آرگومان‌ها را بر اساس ترتیب الفبایی مرتب می‌کند و سپس نتیجه را به برنامه‌ی هدف تحویل می‌دهد.


گسترش موجک (Tilde Expansion)

کاراکتر ~ پیش از اجرای دستور جایگزین مسیر خانه‌ی کاربر می‌شود:

echo ~        # معمولاً /home/username یا /Users/username

اگر پس از ~ نام کاربری دیگری بیاید، مسیر خانه‌ی همان کاربر جایگزین می‌شود (مشروط به این‌که سیستم آن کاربر را بشناسد):

echo ~root    # /root

همچنین می‌توان از نگارش‌هایی مانند ~+ (پوشه‌ی جاری) و ~- (پوشه‌ی قبلی) استفاده کرد.


گسترش حسابی (Arithmetic Expansion)

bash می‌تواند عبارات عددی را ارزیابی کند. عبارت را درون $(( ... )) قرار دهید:

echo $((2 + 2))
result=$(( (5 * 3) + 7 ))

عملگرهای استاندارد ریاضی (جمع، تفریق، ضرب، تقسیم و باقیمانده) و بسیاری از عملگرهای C-مانند (افزایش، کاهش، بیتی و منطقی) پشتیبانی می‌شوند.
به طور پیش‌فرض محاسبات در مبنای ده انجام می‌شود، ولی می‌توان با نگارش base#number از مبناهای دیگر استفاده کرد:

echo $((16#FF))   # تبدیل 0xFF به دسیمال

گسترش پارامتر (Parameter Expansion)

هرگاه نام متغیری را با $ فراخوانی کنیم، bash مقدارش را جایگزین می‌کند:

name="Sara"
echo "سلام $name!"

برای جلوگیری از ابهام یا استفاده از قابلیت‌های پیشرفته باید از آکولاد {} کمک بگیریم:

echo "${name}‌جان"

در کنار استفاده‌ی معمول می‌توانیم رفتارهای پیش‌فرضی تعیین کنیم:

echo "${username:-guest}"   # اگر username تهی یا تعریف نشده باشد، guest چاپ می‌شود.
length=${#name}              # طول رشته

bash ده‌ها نگارش دیگر برای برش، جایگزینی و حذف زیررشته‌ها در اختیارمان می‌گذارد.


گسترش آکولادی (Brace Expansion)

Brace Expansion یا «گسترش آکولادی» روشی سریع برای تولید رشته‌های تکراری است.
الگو به شکل prefix{item1,item2,...}suffix نوشته می‌شود و bash آن را به همه‌ی ترکیب‌های ممکن تبدیل می‌کند:

echo Front-{A,B,C}-Back
# خروجی: Front-A-Back Front-B-Back Front-C-Back

می‌توان بازه‌های عددی یا حرفی تعریف کرد:

echo file{1..3}.txt
# file1.txt file2.txt file3.txt

echo {a..f}

حتی گام (step) هم قابل تنظیم است:

echo {0..10..2}

این ویژگی برای ایجاد پوشه‌ها یا فایل‌های چندگانه بسیار مفید است:

mkdir -p project/{src,docs,tests}

توجه کنید که برخلاف گسترش نام مسیر، brace expansion حتی اگر موردی در سیستم وجود نداشته باشد باز هم رشته‌ها را تولید می‌کند.


جانشینی فرمان (Command Substitution)

گاهی لازم است خروجی یک دستور را به عنوان ورودی دستور دیگر استفاده کنیم.
با قرار دادن فرمان درون $( ... )، bash ابتدا آن را اجرا می‌کند و سپس خروجی‌اش را جایگزین می‌کند:

files=$(ls *.txt)
echo "فایل‌های متنی: $files"

نسخه‌ی قدیمی‌تر این قابلیت استفاده از بک‌تیک `command` است، اما شکل $(command) خواناتر است و می‌تواند تو در تو شود:

echo "امروز $(date +%A) است."

نقل‌قول‌ها و کاراکتر فرار

چون گسترش‌ها بسیار قدرتمند هستند، گاهی لازم داریم آن‌ها را کنترل کنیم.
Bash سه سازوکار اصلی برای این کار دارد: بک‌اسلش (\)، نقل‌قول دوتایی ("") و نقل‌قول تک‌تایی ('').

بک‌اسلش

قرار دادن \ پیش از یک کاراکتر باعث می‌شود همان کاراکتر بدون تفسیر خاصی در نظر گرفته شود.
مثلاً برای چاپ یک ستاره‌ی واقعی:

echo \*

نقل‌قول دوتایی

هر چیزی که درون "..." قرار گیرد، از گسترش نام مسیر محافظت می‌شود؛
با این حال گسترش پارامتر، جانشینی فرمان و گسترش حسابی همچنان اجرا می‌شوند:

name="Sara"
echo "سلام، $name! امروز $(date +%F) است."

نقل‌قول تک‌تایی

قرار دادن متن داخل '...' تمام گسترش‌ها (به جز جدا شدن خود نقل‌قول‌ها) را غیرفعال می‌کند.
از این روش برای نمایش دقیق رشته‌ها استفاده کنید:

echo '$name literally stays $name'

نقل‌قول‌های درون هم

اگر لازم است درون نقل‌قول دوتایی از نقل‌قول دوتایی دیگر استفاده کنید، باید آن را با بک‌اسلش فرار دهید یا از ترکیب روش‌ها کمک بگیرید:

echo "او گفت \"سلام\" و رفت."
echo 'It'\''s done.'

خلاصه