یک شل برنامهای است که به شما امکان میدهد برای تعامل با سیستمعامل دستور وارد کنید. شلها میتوانند گرافیکی باشند؛ همان محیط میزکاری که با ماوس یا تاچپد استفاده میکنید، خودش یک شل است. ما روی رابطهای خط فرمان تمرکز میکنیم؛ جایی که دستورها را تایپ میکنید و نتایج (معمولاً) در ترمینال شما چاپ میشوند.
شلهای خط فرمان متفاوت زیادی وجود دارد. ویندوز cmd و powershell را ارائه میدهد. مک و لینوکس «شلهای یونیکس» را ارائه میدهند که تعدادشان زیاد است. شل پیشفرض مک zsh است. csh یک شل قدیمی با نحوهی نگارشی شبیه C است. bash یکی از محبوبترین شلهاست، بهویژه در لینوکس.
یکی از نخستین شلهای تاریخ، شل Bourne یعنی sh است. با گذشت زمان، این شل استانداردسازی شد تا روی هر سیستم شبهیونیکس ارائه شود. بسیاری از شلها بر پایهی شل Bourne ساخته شدهاند و اغلب قابلیتهای تعاملی یا برنامهنویسی بیشتری فراهم میکنند.
Bash بر پایهی شل Bourne است. به همین دلیل به آن «Bourne-again shell» میگویند. این شل پیشفرض بسیاری از سیستمهای لینوکس است. Bash امکانات زیادی فراهم میکند که آن را به شلی تعاملی و لذتبخش تبدیل میکند، از جمله:
این ترک دربارهی نحوهی استفاده از bash بهعنوان یک شل تعاملی نیست. بلکه دربارهی یادگیری نوشتن برنامه با bash است. تأکید بر استفاده از فقط bash برای حل مسئلهها خواهد بود، هرچند محدودیتی برای فراخوانی ابزارهای بیرونی وجود ندارد.
Bash دستورها را از ورودی خود میخواند (که معمولاً یا یک ترمینال است یا یک فایل). هر خطی از ورودی که میخواند بهعنوان یک دستور در نظر گرفته میشود، یعنی دستورالعملی که باید اجرا شود. (چند مورد پیشرفته وجود دارد، مثل دستورهایی که چند خط را در بر میگیرند، که بعداً دربارهشان صحبت میکنیم.)
Bash هر خط را به واژههایی تقسیم میکند که با نویسهی فاصله (فاصله و تب) از هم جدا میشوند. نخستین واژهی خط، اسم دستوری است که باید اجرا شود. همهی واژههای باقیمانده آرگومانهای آن دستور میشوند (گزینهها، اسم فایلها و غیره).
در مثالهای زیر، چند دستور تعاملی را نشان میدهیم که میتوانید اجرا کنید.
# یک توضیح را آغاز میکند.
هر واژهای که پس از توضیح بیاید توسط شل نادیده گرفته میشود؛ توضیح فقط برای خواندن است.$ نشاندهندهی پرامپت شل است.
اگر دستور را کپی میکنید، پرامپت را وارد نکنید.# Note: It is always a good idea to run `ls` before `rm`
# to make sure you know what you are deleting.
$ rm * # Remove all files in the current directory.
# Make sure you run this in a temp directory or you can lose files!
$ ls # List files in the current directory (no output, no files).
$ touch a b c # Create files 'a', 'b' and 'c'.
$ ls # List files again; this time the outputs: 'a', 'b' and 'c'.
a b c
rm برنامهای است که فایلهای دادهشده به آن را حذف میکند.
* یک نویسهی glob است، یعنی الگویی «عام» که با همهی فایلهای پوشهی جاری مطابقت میکند.
بعداً بیشتر دربارهی glob صحبت میکنیم.
آیا توجه کردید که بین a و b چند فاصله وجود دارد و بین b و c فقط یکی؟
میزان فضای خالی بین آرگومانها اهمیتی ندارد!
برای مثال:
$ echo This is a test.
This is a test.
$ echo This is a test.
This is a test.
echo دستوری است که آرگومانهایش را روی خروجی استاندارد چاپ میکند (که در مورد ما ترمینال است).
Bash دستور را به پنج واژه تقسیم میکند و دستور echo را با چهار آرگومان فراخوانی میکند.
فاصلههای اضافی در مثال دوم هیچ تفاوتی ایجاد نمیکنند.
echo نمیداند فاصلهها در ابتدا چگونه بین واژهها بودند.
اگر بخواهیم فضای خالی اضافی را نگه داریم، باید جمله را بهعنوان یک آرگومان واحد ارسال کنیم. میتوانیم این کار را با استفاده از گیومهها انجام دهیم:
$ echo "This is a test."
This is a test.
گیومهها هر چیزی را که داخلشان باشد در یک آرگومان واحد گروهبندی میکنند.
آرگومان همان This is a test. است، شامل همان نویسههای فاصلهی مشخص.
توجه کنید که گیومهها بخشی از آرگومان نیستند؛ bash آنها را حذف میکند و سپس آن آرگومان واحد را به echo میسپارد.
آرگومانها با فضای خالی از اسم دستور و از یکدیگر جدا میشوند. به خاطر سپردن این نکته مهم است. برای مثال، دستور زیر اشتباه است:
$ [-f file]
bash: [-f: command not found
قصد این است که وجود فایلی به اسم file بررسی شود.
منطقی است که فرض کنیم فضای خالی اطراف [ و ] اهمیتی ندارد، اما [ در واقع یک دستور است و نیاز دارد که آخرین آرگومانش ] باشد.
(بعداً دستور [ را با جزئیات بیشتری بررسی میکنیم.)
بنابراین باید [ را از -f و ] را از file جدا کنیم.
در غیر این صورت bash فکر میکند میخواهیم دستوری به اسم [-f را با یک آرگومان file] اجرا کنیم.
دستور درست همهی آرگومانها را با فضای خالی جدا میکند:
$ [ -f file ]
(افراد زیادی را میبینیم که این رفتار گیجشان میکند؛ فکر میکنند [...] «فقط نحوهی نگارش» است و میتوانند فاصلهها را حذف کنند؛ اما [ یک دستور است و فضای خالی لازم است.)
اگر اسم فایل ما شامل فضای خالی یا نویسههای خاص دیگری باشد، باید آن را هم داخل گیومه گذاشت:
$ [ -f "my file" ]
Bash چند نوع مختلف دستور را میشناسد: نامهای مستعار، توابع، دستورهای درونی، کلیدواژهها و فایلهای اجرایی. همهی اینها دستور هستند و همه به شکلی بسیار مشابه عمل میکنند.
بهطور پیشفرض نامهای مستعار فقط در شلهای تعاملی فعال هستند، نه در اسکریپتهای شل. اینجا به آنها نمیپردازیم.
تابع شامل دستورهای شل است و بسیار شبیه یک اسکریپت کوچک عمل میکند؛ حتی میتواند آرگومان بگیرد و متغیرهای محلی بسازد. وقتی تابعی فراخوانی میشود، دستورهای داخلش اجرا میشوند. توابع بعداً بهطور عمیق بررسی میشوند.
Bash چند دستور پایه دارد که درونش ساخته شدهاند، مثل: cd (تغییر پوشه)، echo (نوشتن خروجی) و غیره.
میتوان آنها را بهعنوان توابعی در نظر گرفت که از قبل فراهم شدهاند.
کلیدواژهها شبیه دستورهای درونی هستند، با این تفاوت اصلی که کلیدواژهها در واقع نحوهی نگارش خود bash هستند و ممکن است با قواعد ویژهای تجزیه شوند.
برای مثال، [ یک دستور درونی bash است، در حالی که [[ یک کلیدواژهی bash است؛ هر دو برای آزمودن انواع شرطها به کار میروند.
اما کلیدواژهی [[ قابلیتهای اضافی دارد.
برای مثال، عملگر =~ را فراهم میکند که آزمون عبارت باقاعده را انجام میدهد.
$ [[ "hello world" =~ e.*o ]] # returns a "success" status
$ [ "hello world" =~ e.*o ]
bash: [: =~: binary operator expected
عملگرهای مقایسهی رشته، یعنی < و >، مثال دیگری از این هستند که کلیدواژهها کد را متفاوت تجزیه میکنند.
داخل [[...]]، این عملگرها به هیچ مراقبت ویژهای نیاز ندارند.
$ if [[ "$USER" < "n" ]]; then
echo "$USER is in the first half of the alphabet"
fi
# => glennj is in the first half of the alphabet
اما داخل [...] باید آنها را داخل گیومه بگذارید یا اسکیپ کنید تا شل آنها را بهعنوان عملگرهای تغییرمسیر تجزیه نکند.
$ if [ "$USER" < "n" ]; then
echo "$USER is in the first half of the alphabet"
fi
# => bash: n: No such file or directory
$ if [ "$USER" \< "n" ]; then
echo "$USER is in the first half of the alphabet"
fi
# => glennj is in the first half of the alphabet
آخرین نوع دستوری که bash میتواند اجرا کند یک فایل اجرایی است.
اینها فایلهای اجرایی (برنامههایی) روی فایلسیستم رایانهی شما هستند که میتوانید اجرا کنید.
(به فایلهای اجرایی ممکن است دستورهای بیرونی یا برنامهها هم گفته شود.)
فایلهای اجرایی معمولاً فقط با تایپ اسمشان فراخوانی میشوند؛ معمولاً نیازی به مسیر کامل فایل نیست.
این کار ممکن است چون bash از یک متغیر از پیش تعریفشده استفاده میکند که به آن کمک میکند مکان فایلهای اجرایی رایج را پیدا کند.
اسم این متغیر PATH است.
مجموعهای از اسمهای پوشه است که با دونقطه از هم جدا شدهاند (مثلاً /usr/bin:/bin).
وقتی دستوری (مثلاً myprogram یا ls) بدون مسیر فایل مشخص شود (و نام مستعار، تابع، دستور درونی یا کلیدواژه نباشد)، bash پوشههای داخل PATH را جستوجو میکند.
پوشهها به ترتیب، از چپ به راست، جستوجو میشوند تا ببینند آیا فایل اجرایی با اسم دادهشده را در خود دارند یا نه.
اگر فایل اجرایی خارج از مسیر شناختهشده باشد، باید مسیر فایل آن مشخص شود.
برای فایل اجرایی myprogram در پوشهی جاری، از ./myprogram استفاده کنید؛ اگر در پوشهی /opt/somedirectory باشد، از /opt/somedirectory/myprogram استفاده کنید.
نکته: میتوان از دستور type برای گرفتن جزئیات دربارهی دستور استفاده کرد:
$ type rm
rm is hashed (/bin/rm)
# or it might return: `rm is /bin/rm`
$ type cd
cd is a shell builtin
یک اسکریپت دنبالهای از دستورهاست که در یک فایل ذخیره شدهاند. Bash فایل را میخواند و دستورها را به ترتیب پردازش میکند. فقط وقتی دستور جاری تمام شود، به دستور بعدی میرود. استثنا زمانی است که دستوری برای اجرای ناهمگام (در پسزمینه) مشخص شده باشد. هنوز زیاد نگران این مورد نباشید؛ بعداً یاد میگیریم که چطور کار میکند.
هر مثال خط فرمانی در این راهنما را میتوان در یک اسکریپت هم به کار برد.
ساختن اسکریپت آسان است. با ساختن یک فایل جدید شروع کنید و این را در خط اول بگذارید:
#!/bin/bash
به این خط دستورالعمل مفسر میگویند (به آن هشبنگ یا شبنگ هم میگویند).
مشخص میکند که وقتی فایل بهعنوان فایل اجرایی در یک دستور به کار میرود، باید از /bin/bash بهعنوان مفسر استفاده شود.
وقتی هستهی سیستمعامل یک فایل غیردودویی را اجرا میکند، خط اول فایل را میخواند.
اگر خط با #! شروع شود، هسته از آن خط برای تعیین مفسری استفاده میکند که فایل به آن سپرده میشود.
(راههای معتبر دیگری هم برای این کار وجود دارد، پایین را ببینید.)
#! باید در همان ابتدای فایل باشد، بدون هیچ فاصله یا خط خالی پیش از آن.
دستورهای اسکریپت ما در خطهای جداگانهای پایین این قرار میگیرند.
نکته: بهجای #!/bin/bash میتوانید از این استفاده کنید
#!/usr/bin/env bash
env در $PATH دنبال فایل اجراییای میگردد که اولین آرگومانش مشخص کرده است (در این مورد، «bash»).
برای توضیح مفصلتر این تکنیک و تفاوتش با #!/bin/bash ساده، این پرسش را در StackOverflow ببینید.
لطفاً گول اسکریپتها یا مثالهای اینترنتی را که از /bin/sh بهعنوان مفسر استفاده میکنند نخورید.
sh همان bash نیست!
خود Bash شلی «سازگار با sh» است، یعنی میتواند بیشتر اسکریپتهای sh را اجرا کند و بسیاری از قواعد نگارش یکسان را با آن دارد.
اما عکس آن درست نیست؛ بعضی از قابلیتهای bash در sh میشکنند یا رفتار غیرمنتظرهای ایجاد میکنند.
در واقع، sh یک شل واحد نیست.
پیادهسازیهای زیادی از شل وجود دارند که از نحوهی نگارش POSIX-sh پشتیبانی میکنند.
بسته به سیستمعاملتان، وقتی sh را اجرا میکنید ممکن است در واقع bash، ash، dash یا شل دیگری را اجرا کنید.
وقتی فایل اسکریپت ساخته شد، میتوان آن را با این کار اجرا کرد:
$ bash myscript
در این مثال، دستور bash را اجرا میکنیم و به آن میگوییم اسکریپتی را بخواند که بهعنوان آرگومان دادهایم.
وقتی این کار را اینگونه انجام میدهیم، خط #! فقط یک توضیح است.
بهطور جایگزین، میتوانیم به اسکریپتهایمان مجوز اجرا بدهیم. با این روش میتوانیم اسکریپت را مثل هر فایل اجرایی دیگری اجرا کنیم، بهجای اینکه bash را دستی فراخوانی کنیم:
$ chmod +x myscript # Mark the file as executable.
$ ./myscript # Now, myscript can be executed directly
# instead of having to specify bash.
وقتی دستور را اینگونه اجرا میکنیم، سیستمعامل از خط #! استفاده میکند تا تعیین کند کدام مفسر برای اجرای اسکریپت شما به کار رود.
محتوای این درس از Commands and Arguments در Bash Guide گرفته شده است.