بررسی و شناخت سابشل (SubShell) یا زیرپوسته در بش (Bash)
سابشل ها یکی از مفاهیم پایهای در نوشتن بشاسکریپت به حساب میان که در عین اینکه خیلی کاربردی و قدرتمند هستند، میتونن باعث گیج شدن آدم موقع خوندن بشاسکریپت بشن، حالا اگر مبتدی باشیم که دیگه بدتر. سابشل ها این امکان رو میدن که محیط های اجرایی ایزوله ایجاد کنیم و دستورات رو بدون اینکه شل والدشون رو تحت تاثیر بذارن اجرا کنیم. هدفم اینه که توی این نوشته به ایجاد، رفتار و کاربرد سابشل ها بپردازم.
سابشل ها دقیقا چی هستن؟
سابشل ها که بهشون بچهشل هم میگن شل های مجزایی هستند که در دل یک شل بهوجود میان. متغیرهای محیطی رو از شل والد میگیرن ولی مستقل عمل میکنند که این موضوع به ما امکان اجرای دستورات یا بشاسکریپت به صورت رو مستقل میده. وقتی که یک سابشل ایجاد میشه در یک پروسس مجزا از شل والد اجرا میشه. این یعنی وقتی در یک سابشل متغیرهای محیطی رو تغییر میدیم یا یک تابع ایجاد میکنیم، اون متغیر یا تابع در شل والد هیچ اثری نداره و درصورت نابود شدن سابشل، اونها هم از بین میرن.
ساختن سابشل
ساخت سابشل راه های مختلفی داره که هر کدوم کاربرد خودشون رو دارن.
استفاده از پرانتز یا آکولاد
دستوراتی که داخل پرانتز هستن در یک سابشل اجرا میشوند و این روش آسون ترین و رایج ترین راه ایجاد سابشل در بش است.
# یک بچهشل ایجاد میکند
(pwd; ls; whoami)
با گذاشتن آکولاد اطراف دستورات میتونید سابشل ایجاد کنید. مثلا:
{ sleep 3; echo "Hello from subshell"; }
# یک سابشل ایجاد شده
echo "Back in parent shell"
جایگزینی دستور
جایگزینی دستور، یک سابشل ایجاد میکنه و خروجی اونو دریافت میکند و میشه اونو توی یک متغیر دیگه ریخت یا توی یک دستور دیگه ازش استفاده کرد.
# انتساب خروجی یک سابشل به یک متغیر
output=$(pwd; ls; whoami)
فراخوانی صریح زیرپوسته
خود دستور بش این امکان رو داره که باهاش یک سابشل ساخت و توش دستورات مد نظر رو اجرا کرد. بعد از سوییچ c- دستوری که قرار توی سابشل زده بشه رو وارد میکنیم.
# Execute a subshell
bash -c "ls; whoami"
ارتباط بین شلوالد و سابشل
شلوالد و سابشل باهم ارتباط سلسله مراتبی دارن. همونطور که قبلا هم اشاره کردم، سابشل متغیرهای محیطی، توابع و سایر تنظیمات را از شلوالد به ارث میبرد اما هرگونه تغییر در آنها در شلوالد اعمال نمیشود و منحصرا در سابشل که ایزوله است اعمال میشوند. مثال زیر این امر را روشن میکند.
# شلوالد
echo "Parent Shell: Value of is $pshell"
#یک سابشل ایجاد میکنیم و مقدار متغیر را تغییر میدهیم
(pshell=10; echo "Subshell: Value of pshell is $pshell")
# مقدار متغیر را در شلوالد چک میکنیم
echo "Parent Shell: Value of pshell is $pshell"
در این مثال، متغیر pshell در ابتدا مقدار دهی نشده است. داخل سابشل که توسط پرانتز ساخته شده است، مقدار متغیر pshell را ۱۰ تنظیم میکنیم و آن را پرینت میکنیم. بعد از اینکه سابشل از بین میرود، در شلوالد دوباره مقدار متغیر را بررسی میکنیم و میبینیم که تنظیم نشده است. فکر کنم این ساده ترین مثال برای به تصویر کشیدن ارتباط سابشل و شلوالد باشه.
درک این موضوع که سابشل ایزوله هستند برای توسعه بشاسکریپت اهمیت حیاتیای داره. باید بدونیم که متغیر های که میسازیم یا تغییر میدیم، بیرون از سابشل در دسترسمون نیستن حتی اگر که سابشل بخشی از یک اسکریپت بزرگ باشه.

چطور بفهمیم که سابشل ایجاد شده است
با بررسی مقدار متغیر BASH_SUBSHELL میشه فهمید که شل فعلی سابشل هست یا نه. این متغیر در سابشل مقداری غیر از ۰ دارد و در سابشل ها نشانگر سطح تو در تویی است.
echo $BASH_SUBSHELL
# در شلوالد مقدار ۰ و در سابشل مقدار غیر از ۰ پرینت میکند
یک راه دیگه هم استفاده از دستور ps با سوییچ forest– است که میشه فهمید شل، والد است یا سابشل.
bash
bash
ps --forest
اجرای این دستور همچنین سطح تو در تویی (سطح آشیانهای) را نیز مشخص میکند که در بخش بعدی میریم سراغش.
سابشل های تو در تو (آشیانهای)
سابشل ها میتونند تو در تو یا Nested باشن به این شکل که هر سابشل میتواند یک سابشل دیگر در خود ایجاد کند. هر سابشل متغیر های محیطی خود را از والد خود به ارث میبرد و مقدار متغیر BASH_SUBSHELL هر بار که یک سابشل داخل سابشل دیگه ایجاد میشه، یک عدد زیاد میشه.
echo "Parent shell: $BASH_SUBSHELL"
(
echo "Subshell 1: $BASH_SUBSHELL"
(
echo "Subshell 2: $BASH_SUBSHELL"
)
)
این هم یک مثال دیگر:
bash
bash
bash
ps --forest
در بالا ما چهار بار دستور بش را وارد کردهایم. (با احتساب والد) با دستور exit یا کلیدترکیبی Ctrl + D میتوانیم از هر سابشل خارج شویم.

آیا شلاسکریپتها در سابشل اجرا میشوند؟
جواب این سوال بله است! به صورت پیشفرض شلاسکریپها در سابشل اجرا میشوند. این به این معنی است که متغیرهای محیطی در شلوالد در دسترس نخواهند بود. با این وجود با دستور source یا نقطه (.) که اسکریپت را در شل فعلی اجرا میکند میتوان این رفتار پیشفرض را تغییر داد و اجازه داد که متغیرهای داخل اسکریپت در شلوالد بازتاب شوند.
# اسکریپت را در شل فعلی اجرا میکند
source .bashrc
البته میتوان در اسکریپتها از دستور exec استفاده کرد تا پروسس فعلی با پروسه داخل اسکریپت جایگزین شود و نتیجتا از اجرای اسکریپ در سابشل جلوگیری شود.
exec bash
exec bash
exec bash
ps --forest
استفاده از سابشلها
میتوان از سابشلها برای راهکار های خلاقانهای استفاده کرد. برای مثال از آنها برای اجرای دستوراتی در پسزمینه استفاده کرد. در ادامه به چند مثال کاربردی از سابشلها میپردازیم.
قرار دادن لیستی از پروسهها در پسزمینه
میتوان با قرار دادن لیستی از دستورات در پرانتز میتوان سابشل را در پسزمینه اجرا کرد و به شلوالد برگشت در حالی که همزمان سابشل در حال اجرا است.
# اجرای سابشل در پسزمینه
(sleep 10; echo "This ran in the background") &
پردازش همزمان (Co-processing)
پردازش همزمان شبیه اجرا در پسزمینه است با این تفاوت که در پردازش همزمان سابشل ایجاد میکنیم. پردازش همزمان در وظایفی با اجرای موازی یا ارتباط بینپروسهای به کار میرود.
# ایجاد یک پردازش همزمان که به خواب ۶۰ ثانیه فرو میرود
coproc sleep 60
پردازش موازی با سابشلها
استفاده از سابشل ها به ما این امکان را میدهد که چندین تسک را در یک لحظه و به صورت موازی پردازش کنیم. با قرار دادن هر تسک در پرانتز و جدا کردن آنها با اپراتور & میتوان سابشلهایی ایجاد کرد که به صورت موازی اجرا میشوند.
# مثال پردازش موازی
(task1.sh) & (task2.sh) & (task3.sh) &
# صبر میکند تا تمام تسکها تمام شوند
wait
در مثال بالا task1.sh و task2.sh و task3.sh در سابشل های مجزا به صورت موازی اجرا میشوند. دستور wait نیز مطمئن میشود که شل والد تا زمانی که تسکها تمام نشدهاند منتظر بماند.
این تکنیک برای تسکهای زمانبر یا تسکهایی که نیاز به قدرت پردازش زیادی دارند امکان موازی کاری و بهبود چشمگیر زمان اجرا را فراهم میکند.
چرا گاهی اوقات در بش باید از سابشلها پرهیز کرد؟
در حالی که سابشلها ویژگیها و توانایی های جذابی در اختیار ما میگذارند، شرایطی وجود دارد که احتمالا باید از استفاده از سابشلهای غیر ضروری پرهیز نمود. ایجاد سابشلها باعث به وجود آمدن پروسههای جدید میشود که ممکن از هزینه بالاسری برای ما داشته باشد، خصوصا در مواقعی که ایجاد سابشل به امری مداوم و تکرار شونده تبدیل شود. بهعلاوه تغییرات که در تنظیمات و متغیرهای محیطی در سابشلها ایجاد میکنیم، در شلوالد آنها بازتاب نمیشود و این امر ممکن است باعث رفتارهای غیرمنتظره، تناقضات و ناهمخوانی بشه. استفاده بیشازحد از سابشلها باعث پیچیده شدن اسکریپت و ناخوانایی آن برای افراد با سطح دانش کمتر میشود. همچنین ایجاد سابشل جدید مصرف منابعی از قبیل مموری و file descriptorها را در پی دارد که این امر در جاهایی که با محدودیت منابع روبرو هستیم مشکل ساز خواهد بود.
خلاصه
در این نوشته به بررسی مفهوم سابشل یا زیرپوسته در Bash پرداختیم و روش های ایجاد آنها، رفتارشان، نحوه استفاده از آنها و تکنیکهای جلوگیری از اجرای اسکریپت در سابشل را مورد بررسی قرار دادیم. در نهایت هم درباره مواقعی که باید از استفاده از سابشلها پرهیز کرد، بحث کردیم.

عالی بود
دقیقاً همونطور که گفتی یکی از دلایل اصلی که توی اسکریپتهام از سابشل استفاده میکنم اینه که به خاطر ایزوله بودنشون دیگه نگران side effectهای ناخواسته نیستم