طراحی بالا به پایین (top-down design) در اسکریپتنویسی
همانطور که برنامهها بزرگتر و پیچیدهتر میشوند، طراحی، کدنویسی و نگهداری آن نیز دشوارتر میشود. درست مثل هر پروژه بزرگ دیگری ایده خوبی است که وظایف بزرگ و پیچیده را به یکسری وظایف ساده و کوچک تر تقسیم کنیم.
فرض کنید که میخواهیم یک وظیفه روزمره مثل رفتن به سوپرمارکت و خرید مواد غذایی را توصیف کنیم میتوانیم کل پروسه را به صورت وظایف زیر توصیف کنیم :
1- وارد ماشین میشویم.
2- به سمت سوپر مارکت رانندگی میکنیم.
3- ماشین را پارک میکنیم.
4- وارد فروشگاه میشویم.
5- مواد غذایی را خریداری میکنیم.
6- به ماشین بازمیگردیم.
7- به سمت خانه رانندگی می کنیم.
8- ماشین را پارک میکنیم.
9- وارد خانه میشویم.
هر چند میتوانیم هر وظیفه را با جزئیات بیشتری توضیح دهیم و به این منظور هر وظیفه را به چند زیر وظیفه دیگر بسط میدهیم مثلا وظیفه پارک کردن ماشین را میتوان به گام های زیر تقسیم کرد:
1- پیدا کردن فضای پارک
2- رانندگی ماشین به فضای پارک.
3- خاموش کردن موتور ماشین.
4- کشیدن ترمز و از ماشین خارج شدن.
5- قفل کردن ماشین.
به تبع آن میتوان زیر وظیفه خاموش کردن موتور ماشین را به دو گام خاموش کردن موتور و برداشتن سوییچ تقسیم کرد و .. و همینطور پروسه ها را شکست تا این وظیفه کلی رفتن به سوپر مارکت به صورت کامل تعریف شود.
این پروسه شناسایی گامهای سطح بالا و توسعه افزایشی جزئیات نمایش گامها را طراحی از بالا به پایین مینامند. این تکنیک به ما اجازه میدهد تا وظایف پیچیده را به وظایف کوچک و ساده تر تبدیل کنیم طراحی از بالا به پایین (Top Down Design) یک متد رایج طراحی برنامه ها و گزینهای است که بویژه برای برنامهنویسی در شل (Shell) مناسب است.
در این درس ما از طراحی از بالا به پایین استفاده میکنیم تا هر چه بیشتر اسکریپت ایجاد گزارش خود را توسعه دهیم.
توابع شل (Shell Functions)
اسکریپت ما هم اکنون گامهای زیر را برای ایجاد سند HTML دنبال میکند:
1- باز کردن صفحه.
2- باز کردن هدر صفحه.
3- تنظیم عنوان صفحه.
4- بستن هدر صفحه.
5- باز کردن بدنه صفحه.
6- خروجی تگ H1 صفحه.
7- خروجی برچسب زمانی.
8- بستن بدنه صفحه.
9- بستن صفحه.
برای مرحله بعدی توسعه ما برخی وظایف را بین گامهای هفت و هشت اضافه خواهیم کرد شامل:
- آپتایم و بارگذاری سیستم: این گزینه مقدار زمانی از آخرین خاموش کردن یا ریبوت و متوسط تمداد وظایف اخیر در حال اجرا بر روی پردازشگر در وقفههای زمانی مختلف است.
- فضای دیسک: کل فضای مورد استفاده بر روی دیوایسهای ذخیرهسازی سیستم.
- فضای خانگی: میزان فضای ذخیرهسازی که توسط هر کاربر مورد استفاده قرار می گیرد
اگر که ما فرمانی برای هر کدام از این وظایف داشتیم بایستی آنها را به سادگی به اسکریپت خود اضافه میکردیم:
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMP="Generated $CURRENT_TIME, by $USER"
cat << _EOF_
<HTML>
<HEAD>
<TITLE>$TITLE</TITLE>
</HEAD>
<BODY>
<H1>$TITLE</H1>
<P>$TIME_STAMP</P>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</BODY>
</HTML>
_EOF_
ما میتوانیم این فرمانهای اضافی را به دو شیوه ایجاد کنیم. میتوانیم این سه فرمان اضافی را در فایلهای اسکربیت جداگانه بنویسیم و آنها را درون پوشهای که درون مسیر PATH ما هستند قرار دهیم. یا اینکه میتوانیم این اسکریپتها به عنوان توابع
شل (Shall Function) درون برنامه خود قرار دهیم.
همانگونه که قبلا گفتیم توابع شل یکسری مینیاسکریپت هستند که درون دیگر اسکریپتهای ما قرار دارند و میتوانند به عنوان برنامههای خودمختار عمل کنند. توابع شل دارای دو شکل ترکیبی هستند. اولین شکل آنها به صورت زیر است:
function name {
commands
return
}
که در این شکل name نام تابع ماست و commands یکسری از فرمانهایی است که درون تابع قرار داده شده اند. شکل دوم آنها به صورت زیر است:
name () {
commands
return
}
هر دو شکل با هم برابر هستند و ممکن است به جای یکدیگر استفاده شوند. در زیر یک اسکریپتی را میبینیم که استفاده از یک
تابع شل (Shell Function) را نمایش میدهد:
1 #!/bin/bash
2
3 # Shell function demo
4
5 function funct {
6 echo "Step 2"
7 return
8 }
9
10 # Main program starts here
11
12 echo "Step 1"
13 funct
14 echo "Step 3"
زمانی که شل (Shadi) اسکریپت را میخواند خطوط ابتدایی را رد میکند تا به اجرای برنامه اصلی در خط ۱۲ برسد. در خط ۱۳ تابع funct فراخوانی میشود و شل (Shell) تابع را درست مثل هر فرمانی دیگر اجرا میکنند. سپس کنترل برنامه به خط ٦ باز میگردد echo موجود در گام دوم اجرا می شود. سپس خط ۷ اجرا میشود که دستور return هست. دستور return تابع را پایان میدهد و کنترل برنامه را به خط ١٤ باز میگرداند و در نهایت آخرین Echo که گام سوم است اجرا می شود. توجه داشته باشید برای اینکه فراخوانی یک تابع انجام شود تابع بایستی قبل از فراخوانی تعریف شود یعنی در ابتدای اسکریپت. خوب اکنون که نحوه تعریف توابع را آموختیم کمی توابع ساده را به اسکریپت خود اضافه میکنیم:
#!/bin/bash
# Program to output a system information page
TITLE="System Information Report For $HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMP="Generated $CURRENT_TIME, by $USER"
report_uptime () {
return
}
report_disk_space () {
return
}
report_home_space () {
return
}
cat << _EOF_
<HTML>
<HEAD>
<TITLE>$TITLE</TITLE>
</HEAD>
<BODY>
<H1>$TITLE</H1>
<P>$TIME_STAMP</P>
$(report_uptime)
$(report_disk_space)
$(report_home_space)
</BODY>
</HTML>
_EOF_
نامهای توابع شل (Shell Function همان قوانین متغیرها را شامل میشود یک تابع بایستی حداقل دارای یک فرمان باشد که ما
در اینجا فقط فرمان return (که تابع را پایان میدهد) را قرار دادهایم.
متغیرهای محلی (Local Variables)
در اسکریپتی که تاکنون نوشتیم همه متغیرها شامل ثابتها متغیرهای سراسری بودند. متغیرهای سراسری موجودیت خود را در طول برنامه حفظ میکنند. این ویژگی برای بسیاری از وظایف مناسب است. ولی گاهی میتواند استفاده از توابع شل را پیچیده کند. درون توابع شل (Shell Functions) . اغلب خوب است که متغیرهای محلی را داشته باشیم. متغیرهای محلی فقط درون توایمی که درون آن تعریف شده اند قابل دسترسی هستند و پس از آنکه تابع بسته شد موجودیت آنها نیز از بین میرود.
داشتن متغیرهای محلی به برنامهنویس این اجازه را میدهد تا متغیرها را با اسامی که هم اکنون در برنامه موجود هستند و استفاده شدهاند نامگذاری کنند چه درون اسکریپت و چه به صورت سراسری درون دیگر توابع شل بدون اینکه نگران این باشد. که نام متغیرها با هم تلاقی و تضاد پیدا کند. این یک مثال از اسکریپتی است که نحوه تعریف و استفاده از متغیرهای محلی را توضیح میدهد:
#!/bin/bash
# local-vars: script to demonstrate local variables
foo=0 # global variable foo
funct_1 () {
local foo # variable foo local to funct_1
foo=1
echo "funct_1: foo = $foo"
}
funct_2 () {
local foo # variable foo local to funct_2
foo=2
echo "funct_2: foo = $foo"
}
echo "global: foo = $foo"
funct_1
echo "global: foo = $foo"
funct_2
echo "global: foo = $foo"
همانگونه که میبینیم، متغیرهای محلی با نام متغیر و کلمه local (به معنی محلی) تعریف شدهاند. این باعث میشود متغیری ایجاد شود که برای تایمی که درون آن تعریف شده لوکال هست. زمانی که اسکریپت خارج از تابع شل هست. متغیر دیگر موجود نیست. زمانی که ما این اسکریپت را اجرا میکنیم نتایج را مشاهده میکنیم:
[me@linuxbox ~]$ local-vars global: foo = 0 funct_1: foo = 1 global: foo = 0 funct_2: foo = 2 global: foo = 0
میبینیم که اختصاص مقادیر به متغیر محلی foo درون توابع شل هیچ تاثیری بر روی مقدار foo تعریف شده در خارج از این توابع ندارد. این ویژگی به توابع شل (Shell Functions) اجازه میدهد تا به نحوی نوشته شوند که مستقل از دیگر اسکرییتها پدیدار شوند. این ویژگی بسیار ارزشمند است چون که به یک بخش از برنامه کمک میکند تا از تضاد و ناسازگاری با دیگر برنامهها جلوگیری کند. همچنین این ویژگی به توابع شل (Shell Functions) اجازه میدهد تا به نحوی نوشته شوند که پرتابل باشند. به این صورت است که میتوان آنها را از یک اسکریپت کپی کرد و به اسکریپتی دیگر انتقال داد.
اسکریپتها را در حالت اجرا نگه داریم
در حالیکه برنامه خود را توسعه میدهیم ایده خوبی است که برنامه خود را در یک وضعیت اجرا نگه داریم. با انجام این کار و تست متناوب، میتوانیم خطاهای موجود در فرایند توسعه را تشخیص دهیم. این کار دیباگ مشکلات را بسیار آسانتر میکند. برای مثال، اگر که ما برنامه را اجرا کنیم و یک تغییر جزئی ایجاد کنیم. دوباره برنامه را اجرا کنیم و مشکل را پیدا کنیم کاملا مشخص است که تغییر اخیر ما منبع مشکل بوده است. با اضافه کردن توابع خالی که در زبان برنامهنویسی (stub خوانده میشوند) میتوانیم جریان منطقی برنامه خود را در مرحله کنونی تصدیق کنیم. زمانی که یک stub ایجاد میکنیم. ایده خوبی است که چیزی را قرار دهیم که برای برنامهنویس فیدیکی ارسال کند که نشان دهد جریان منطقی به پیش رفته است. اگر که به خروجی اسکریپت خود نگاهی بیندازیم. میبینیم که برخی خطوط خالی پس از برچسب زمانی وجود دارد ولی نمیتوانیم از دلیل بوجود آمدن آنها اطمینان پیدا کنیم:
[me@linuxbox ~]$ sys_info_page
<HTML>
<HEAD>
<TITLE>System Information Report For twin2</TITLE>
</HEAD>
<BODY>
<H1>System Information Report For linuxbox</H1>
<P>Generated 03/19/2012 04:02:10 PM EDT, by me</P>
</BODY>
</HTML>
میتوانیم توابع را تغییر دهیم تا فیدبکی ارسال کنند:
report_uptime () {
echo "Function report_uptime executed."
return
}
report_disk_space () {
echo "Function report_disk_space executed."
return
}
report_home_space () {
echo "Function report_home_space executed."
return
}
و سپس دوباره اسکریپت را اجرا کنیم:
[me@linuxbox ~]$ sys_info_page
<HTML>
<HEAD>
<TITLE>System Information Report For linuxbox</TITLE>
</HEAD>
<BODY>
<H1>System Information Report For linuxbox</H1>
<P>Generated 03/20/2012 05:17:26 AM EDT, by me</P>
Function report_uptime executed.
Function report_disk_space executed.
Function report_home_space executed.
</BODY>
</HTML>
آهان. حالا شد. همانگونه که میبینیم در حقیقت این فضای خالی مربوط به توابعی بود که بدون مقدار اجرا میشدند. حالا که توابع ما به درستی کار میکنند. زمانی آن رسیده که کمی تابع حقیقی بنویسیم. اولین تابع ما report_uptime میباشد:
report_uptime () {
cat <<- _EOF_
<H2>System Uptime</H2>
<PRE>$(uptime)</PRE>
EOF_
return
}
کاملا مشخص است. از یک here document به منظور خروجی یک بخش هدر و خروجی فرمان uptime که در بین تگ Pre قرار گرفته استفاده کردیم. به همین صورت تابع report_disk_space را به صورت زیر نمایش میدهیم:
report_disk_space () {
cat <<- _EOF_
<H2>Disk Space Utilization</H2>
<PRE>$(df -h)</PRE>
_EOF_
return
}
این تابع از فرمان df -h که برای تشخیص میزان فضای موجود در دیسک استفاده کرد. در نهایت تابع report_home_space، را به صورت زیر ایجاد میکنیم:
report_home_space () {
cat <<- _EOF_
<H2>Home Space Utilization</H2>
<PRE>$(du -sh /home/*)</PRE>
_EOF_
return
}
در اینجا از دفرمان du به همراه گزینههای -sh برای انجام این وظیفه استفاده کردیم. هر چند یک راهکار کامل برای این مشکل نیست. در حالیکه این دستور بر روی برخی سیستمها مثل اوبونتو کار میکند ولی بر روی دیگر سیستمها کار نمیکند. دلیل آن هم این است که بسیاری از سیستمها مجوزهای دایرکتوریهای خانگی را به صورتی تنظیم میکنند تا مانع از خواندن آنها توسط دیگران (World) شوند که یک راهکار امنیتی مناسب به شمار میرود. بر روی این سیستم ها تابع report_home_space فقط در صورتی کار میکنند که اسکریپت ما با مجوزهای کاربر ارشد اجرا شود. یک راهکار بهتر این است که اسکریپت را به گونه ای تنظیم کنیم تا بر طبق مجوزهای کاربر رفتار کنند. به این موضوع در دروس بعدی میپردازیم.
درباره فرشید نوتاش حقیقت
همیشه نیازمند یک منبع آموزشی فارسی در حوزه نرمافزارهای آزاد/ متنباز و سیستمعامل گنو/لینوکس بودم. از این رو این رسالت رو برای خودم تعریف کردم تا رسانه «محتوای باز» رو بوجود بیارم.
نوشتههای بیشتر از فرشید نوتاش حقیقتاین سایت از اکیسمت برای کاهش جفنگ استفاده میکند. درباره چگونگی پردازش دادههای دیدگاه خود بیشتر بدانید.
دیدگاهتان را بنویسید