پوسته‌های فرعی

فصل ‎21‎- پوسته‌های فرعی

اجرای یک اسکریپت پوسته، پردازش جدیدی راه‌اندازی می‌کند، یک پوسته فرعی.

پوسته فرعی یک نمونه جداگانه از پردازشگر فرمان -- پوسته‌ای که در کنسول یا در پنجره xterm یک اعلان به شما می‌دهد -- است. درست همانطور که فرمان‌های شما در اعلان خط فرمان تفسیر می‌شوند، به طور مشابهی یک اسکریپت پردازش دسته‌ای لیستی از فرمان‌ها را انجام می‌دهد. هر اسکریپت پوسته در حال اجرا، در عمل یک پردازش فرعی (پردازش فرزند) از پوسته پدر است.

یک اسکریپت پوسته خودش می‌تواند پردازش‌های فرعی راه‌اندازی نماید. این پوسته‌های فرعی، اجازه انجام پردازش‌های موازی، یعنی اجرای موثر چند وظیفه فرعی همزمان را به اسکریپت می‌دهند.

#!/bin/bash
#

(
#     داخل پرانتزها، و بنابراین یک پوسته فرعی . . .
while [ 1 ]                   #       حلقه بی‌پایان.
do
  echo "Subshell running . . ."
done
)

#          اسکریپت برای همیشه، یا حداقل تا موقعی که
#+       به وسیله ‎Ctl-C‎ خاتمه بیابد، اجرا خواهد شد.

exit $?  #پایان اسکریپت (اما هرگز به اینجا نمی‌رسد).

اکنون، اسکریپت را اجرا کنید:
bash$ sh subshell-test.sh

و ضمن اینکه اسکریپت در حال اجرا است، از یک ‎xterm‎ دیگر:

bash$ ps -ef | grep subshell-test.sh
UID       PID   PPID  C STIME TTY      TIME     CMD
500       2698  2502  0 14:26 pts/4    00:00:00 sh subshell-test.sh
500       2699  2698 21 14:26 pts/4    00:00:24 sh subshell-test.sh
          ^^^^
تحلیل:
‎PID 2698‎ یعنی اسکریپت، ‎PID 2699‎ یعنی پوسته فرعی را راه‌اندازی نموده است.
توجه: سطر ‎"UID ..."‎ به وسیله فرمان ‎grep‎ فیلتر خواهد گردید، اما در اینجا به منظور روشنگری آن را نمایش داده‌ایم.

به طور کلی، یک فرمان خارجی در اسکریپت یک پردازش فرعی منشعب می‌سازد ‎[1]‎ در حالیکه یک ‎Bash builtin‎ چنین نمی‌کند. به این علت، فرمان‌های داخلی به طور سریع‌تری اجرا می‌گردند و از معادل‌های خارجی‌شان منابع سیستم کمتری مصرف می‌کنند.

لیست فرمان داخل پرانتزها

(‎ command1; command2; command3; ...‎ )

یک لیست فرمان جاسازی شده در میان پرانتزها به صورت یک پوسته فرعی اجرا می‌گردد.

متغیرهای یک پوسته فرعی در خارج از بلوک کد پوسته فرعی قابل رویت نیستند. آنها در پردازش پدر، یعنی پوسته‌ای که پوسته فرعی را راه‌اندازی نموده، قابل دستیابی نیستند. در واقع این متغیرها برای پردازش فرزند محلی هستند.

مثال ‎21-1‎. حوزه متغیر در پوسته فرعی

#!/bin/bash
#subshell.sh

echo
echo "We are outside the subshell."
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
#           ‎Bash‎ نگارش ‎3‎‏، متغیر جدید  ‎$BASH_SUBSHELL‎ را اضافه کرد.
echo; echo

outer_variable=Outer
global_variable=
#      تعریف متغیر سراسری برای «ذخیره‌سازی» مقدار متغیر پوسته فرعی.

(
echo "We are inside the subshell."
echo "Subshell level INSIDE subshell = $BASH_SUBSHELL"
inner_variable=Inner

echo "From inside subshell, \"inner_variable\" = $inner_variable"
echo "From inside subshell, \"outer\" = $outer_variable"

global_variable="$inner_variable"   #    آیا «بیرون بردن» متغیر از
                                    #+  پوسته فرعی را ممکن می‌سازد؟
)

echo; echo
echo "We are outside the subshell."
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
echo

if [ -z "$inner_variable" ]
then
  echo "inner_variable undefined in main body of shell"
else
  echo "inner_variable defined in main body of shell"
fi

echo "From main body of shell, \"inner_variable\" = $inner_variable"
#    ‎$inner_variable‎ به صورت تهی (ارزش‌گذاری نشده) نمایش خواهد یافت
#    +زیرا متغیرهای تعریف شده در پوسته فرعی «متغیرهای محلی» هستند.
#                            آیا راه علاجی برای این مطلب وجود دارد؟
echo "global_variable = "$global_variable""  # چرا این عمل نمی‌کند؟

echo

# ==================================================================

#    به طور اضافی ...

echo "-----------------"; echo

var=41                                              #متغیر همگانی.

( let "var+=1"; echo "\$var INSIDE subshell = $var" )  #        42

echo "\$var OUTSIDE subshell = $var"                   #        41

#عملیات متغیر در داخل یک پوسته فرعی، حتی با یک متغیر «همگانی»، روی
#+                    مقدار آن در خارج از پوسته فرعی تاثیر نمی‌کند!

exit 0

#                            پرسش:
#                    -------------------
#   بعد از خروج از یک پوسته فرعی، راهی جهت دوباره وارد شدن به همان
#+    پوسته فرعی واقعی جهت ویرایش یا دسترسی متغیرهای آن وجود دارد؟

همچنین ‎$BASHPID‎ و مثال ‎34-2‎ را ببینید.


در حالیکه متغیر داخلی ‎$BASH_SUBSHELL‎ نشان‌دهنده سطح درونی یک پوسته فرعی است، متغیر ‎$SHLVL‎ در داخل یک پوسته فرعی تغییری نشان نمی‌دهد.

echo " \$BASH_SUBSHELL outside subshell       = $BASH_SUBSHELL"           # 0
  ( echo " \$BASH_SUBSHELL inside subshell        = $BASH_SUBSHELL" )     # 1
  ( ( echo " \$BASH_SUBSHELL inside nested subshell = $BASH_SUBSHELL" ) ) # 2
#                           *** تودرتو ***                        

echo

echo " \$SHLVL outside subshell = $SHLVL"       # 3
( echo " \$SHLVL inside subshell  = $SHLVL" )   # ‎3‎ (بدون تغییر!)

تغییرات دایرکتوری انجام شده در پوسته فرعی به پوسته پدر منتقل نمی‌گردد.

مثال ‎21-2‎. لیست پروفایل‌ کاربران

#!/bin/bash
#   چاپ پروفایل همه کاربران.

#این اسکریپت به وسیله ‎Heiner Steven‎ نوشته شده و توسط نگارنده ویرایش گردیده.

FILE=.bashrc      # فایل محتوی پروفایل کاربر در اسکریپت اولیه ‎.profile‎ بود.

for home in `awk -F: '{print $6}' /etc/passwd`
do
  [ -d "$home" ] || continue    #اگر دایرکتوری خانگی نیست به مورد بعدی برو.
  [ -r "$home" ] || continue    #   اگر قابل خواندن نیست به مورد بعدی برود.
  (cd $home; [ -e $FILE ] && less $FILE)
done

#وقتی اسکریپت خاتمه می‌یابد، نیاز به تعویض دایرکتوری به دایرکتوری اولیه نیست
#+                        به دلیل اینکه ‎cd $home‎ در پوسته فرعی صورت می‌گیرد.

exit 0

پوسته فرعی می‌تواند جهت تنظیم یک «محیط اختصاصی» برای گروهی از فرمان‌ها به کار برود.

COMMAND1
COMMAND2
COMMAND3
(
  IFS=:
  PATH=/bin
  unset TERMINFO
  set -C
  shift 5
  COMMAND4
  COMMAND5
  exit 3              #  فقط از پوسته فرعی خارج می‌شود!
)
#پوسته پدر تحت تاثیر قرار نگرفته، و محیط محافظت می‌شود.
COMMAND6
COMMAND7

همچنانکه در اینجا دیده می‌شود، فرمان exit فقط پوسته فرعی را که در آن اجرا می‌شود، نه پوسته پدر یا اسکریپت را، خاتمه می‌دهد.

یک کاربرد برای چنین «محیط اختصاصی»، بررسی نمودن آن است که آیا یک متغیر تعریف شده است.

if (set -u; : $variable) 2> /dev/null
then
  echo "Variable is set."
fi       # متغیر در اسکریپت جاری تنظیم شده است، یا یک متغیر داخلی ‎Bash‎
         #+                   است، یا در محیط حاضر است (صادر شده است).
مترجم: در اینجا ‎set -u‎ باعث می‌گردد پوسته غیرمحاوره‌ای هنگام اقدام به بسط متغیری که برقرار
 نیست یک پیغام خطا در stderr نوشته و بلافاصله خارج گردد. به دنبال آن ‎: $variable‎  قرار دارد و 
می‌دانیم که پوسته قبل از اجرای فرمان، پارامتر آن را بسط می‌دهد و در اینجا به علت تنظیم قبلی 
اگر متغیر برقرار نباشد وضعیت خروج از پوسته فرعی ناموفق و اگر برقرار باشد موفق خواهد بود.
‎set -u‎ معادل ‎set -o nounset‎ است.




#هم می‌توانست نوشته شود  ‎[[ ${variable-x} != x || ${variable-y} != y ]]‎
# یا                    ‎[[ ${variable-x} != x$variable ]]‎
# یا                              ‎[[ ${variable+x} = x ]]‎
# یا                             ‎[[ ${variable-x} != x ]]‎

یک کاربرد دیگر کنترل وجود یک فایل قفل است:

if (set -C; : > lock_file) 2> /dev/null
then
  :    #‎lock_file‎ وجود ندارد: هیچ کاربری در حال اجرای اسکریپت نیست
else
  echo "Another user is already running that script."
exit 65
fi
مترجم: تنظیم گزینه C (بزرگ) برای پوسته مانع از رونویسی شدن فایل موجود به وسیله عملگر
تغییر مسیر ‎>‎ پوسته می‌گردد. پس از آن ‎:  > lock_file‎ قرار دارد. این به معنای آن است که فایل
نام‌برده باز شود و نتیجه فرمان (که در اینجا هیچ است) در آن نوشته شود. اگر فایل موجود نباشد
ایجاد می‌شود و بدون انجام عملی بقیه کار ادامه می‌یابد و اگر موجود باشد به علت تنظیم گزینه C
رونویسی آن منع گردیده بنابراین با وضعیت خروج ناموفق از پوسته فرعی خارج گردیده و پس از
نمایش پیغام تعیین شده، از اسکریپت نیز خارج می‌شود.
‎set -C‎ معادل  ‎set -o noclobber‎‎ است. 






# قطعه کد نوشته ‎Stéphane Chazelas‎ با
#+ویرایش ‎Paulo Marcel Coelho Aragao‎.

+

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

مثال ‎21-3‎. اجرای پردازش‌های موازی در پوسته‌های فرعی

	(cat list1 list2 list3 | sort | uniq > list123) &
	(cat list4 list5 list6 | sort | uniq > list456) &

	# هر دو مجموعه لیست‌ها را به طور همزمان ادغام و مرتب می‌کند.
	#           اجرا در پس‌زمینه، اجرای موازی را تضمین می‌نماید.
	#
	#                                         همان کار به صورت
	# 
	# 
	
	wait   #تا خاتمه یافتن پوسته‌های فرعی، فرمان بعد اجرا نشود.
	
	diff list123 list456

تغییر مسیر ورودی-خروجی به یک پوسته فرعی، از عملگر لوله | همچون در ‎ls -al | (command)‎ استفاده می‌کند.


یک بلوک کد در میان براکت‌های کمانی، پوسته فرعی راه‌اندازی نمی‌کند.

‎{ command1; command2; command3; . . . commandN; }‎

var1=23
echo "$var1"   # 23

{ var1=76; }
echo "$var1"   # 76

یادداشت‌ها

‎[1]‎

یک فرمان خارجی احضار شده با یک exec به طور معمول پردازش فرعی-پوسته فرعی منشعب نمی‌کند.