فصل ۲۴ – نوشتن شل‌اسکریپت

تا اینجا دستورهای شل را به صورت تعاملی اجرا کرده‌ایم؛ هر بار فرمانی را تایپ کرده‌ایم و نتیجه را دیده‌ایم.
شل‌اسکریپت‌ها به ما اجازه می‌دهند مجموعه‌ای از این فرمان‌ها را در یک فایل متنی ذخیره کنیم تا بعداً با یک دستور ساده اجرا شوند.
در این فصل نخستین اسکریپت‌های خود را می‌نویسیم، نحوهٔ اجرا و اشکال‌زدایی‌شان را یاد می‌گیریم و با مفاهیمی آشنا می‌شویم که در فصل‌های بعدی گسترش خواهند یافت.


اسکریپت چیست؟

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

اولین اسکریپت ما

یک اسکریپت باید فایلی متنی باشد، بنابراین از ویرایشگر دلخواه استفاده می‌کنیم. مثال‌های کتاب از nano بهره می‌برند.
فایلی به نام hello_world.sh بسازید و خطوط زیر را در آن قرار دهید:

#!/bin/bash
# hello_world.sh - نمونه‌ای ساده از یک اسکریپت bash

echo "سلام دنیا!"

خط اول شبانگ (#!) است که به سیستم می‌گوید این فایل باید با /bin/bash اجرا شود.
خط دوم یک نظر است و نادیده گرفته می‌شود. خط سوم دستور آشنای echo است.

فایل را ذخیره کنید و سپس اجازهٔ اجرا بدهید:

[me@linuxbox ~]$ chmod 755 hello_world.sh

مجوز 755 یعنی صاحب فایل حق خواندن، نوشتن و اجرا دارد و سایر کاربران فقط می‌توانند فایل را بخوانند و اجرا کنند.

اکنون می‌توانیم آن را اجرا کنیم:

[me@linuxbox ~]$ ./hello_world.sh
سلام دنیا!

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

[me@linuxbox ~]$ bash hello_world.sh

آشنایی بیشتر با شبانگ

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

#!/bin/sh              # شل استاندارد سیستم (ممکن است dash یا bash باشد)
#!/usr/bin/env python3 # اجرای اسکریپت Python با جست‌وجو در PATH
#!/usr/bin/perl        # اجرای اسکریپت Perl

اگر شبانگ حذف شود و اسکریپت را با نامش اجرا کنیم، هسته تلاش می‌کند آن را به عنوان یک برنامهٔ دودویی بارگذاری کند و شکست می‌خورد.
اما اگر اسکریپت را به صراحت به bash بدهیم (bash script.sh)، شل آن را بدون شبانگ هم اجرا خواهد کرد.

محل نگه‌داری اسکریپت‌ها

برای اینکه اسکریپت‌ها از هر جایی قابل فراخوانی باشند، باید در مسیری قرار گیرند که در متغیر محیطی PATH فهرست شده است.
بسیاری از کاربران دایرکتوری ~/bin را برای این منظور ایجاد می‌کنند:

[me@linuxbox ~]$ mkdir -p ~/bin
[me@linuxbox ~]$ mv hello_world.sh ~/bin/hello

سپس اطمینان حاصل کنید که مسیر تازه به PATH اضافه شده است. اگر در خروجی دستور زیر مسیر ~/bin را نمی‌بینید، آن را به ~/.bashrc اضافه کنید:

export PATH="$HOME/bin:$PATH"

پس از باز کردن شل جدید (یا اجرای source ~/.bashrc) می‌توانید تنها با تایپ hello اسکریپت را اجرا کنید.
گفتنی است که وجود پسوند .sh الزامی نیست؛ بسیاری از کاربران نام کوتاه‌تری بدون پسوند برای اسکریپت‌های اجرایی خود انتخاب می‌کنند.

ایجاد فایل با cat

گاهی لازم است اسکریپتی را به سرعت ایجاد کنیم. می‌توانیم از دستور cat و یک here document بهره ببریم:

[me@linuxbox ~]$ cat <<'EOF' > ~/bin/cleanup
#!/bin/bash
rm -i *.bak *.tmp
EOF
[me@linuxbox ~]$ chmod 755 ~/bin/cleanup

علامت 'EOF' تعیین می‌کند که محتوای بین دو نشانه بدون جایگزینی متغیرها نوشته شود.
این ترفند در طول فصل‌های بعدی نیز به کار می‌آید.

پروژهٔ نمونه: ساخت صفحهٔ گزارش سیستم

برای تمرین، اسکریپتی می‌نویسیم که گزارشی HTML از وضعیت سیستم تولید کند. نام فایل را sys_info_page می‌گذاریم و آن را در ~/bin ذخیره می‌کنیم.
هدف اسکریپت تولید فایل /tmp/system_info.html با محتوای پویا است.

ابتدا چارچوب کلی را می‌نویسیم:

#!/bin/bash

# sys_info_page - تولید یک صفحهٔ HTML ساده شامل اطلاعات سیستم

TITLE="گزارش وضعیت سیستم برای \$HOSTNAME"
CURRENT_TIME=$(date +"%x %r %Z")
TIME_STAMP="گزارش تولید شده در $CURRENT_TIME توسط $USER"

cat << _EOF_
<html>
<head>
    <title>$TITLE</title>
</head>
<body>
    <h1>$TITLE</h1>
    <p>$TIME_STAMP</p>
</body>
</html>
_EOF_

نکته‌های مهم:

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

cat << _EOF_
<html>
<head>
    <title>$TITLE</title>
</head>
<body>
    <h1>$TITLE</h1>
    <p>$TIME_STAMP</p>
    <h2>اطلاعات سیستم</h2>
    <pre>$(uname -mp)</pre>
    <h2>زمان راه‌اندازی</h2>
    <pre>$(uptime)</pre>
    <h2>کاربران فعال</h2>
    <pre>$(who)</pre>
    <h2>فضای دیسک</h2>
    <pre>$(df -h)</pre>
</body>
</html>
_EOF_

با اجرای اسکریپت صفحه‌ای با فرمت HTML در /tmp/system_info.html ساخته می‌شود. می‌توانید آن را با مرورگر باز کنید.

بهبود خروجی

می‌توانیم خروجی اسکریپت را مستقیماً در فایل ذخیره کنیم و در صورت موفقیت پیام مناسبی نمایش دهیم:

FILE=/tmp/system_info.html

if cat << _EOF_ > $FILE
<html>
<head>
    <title>$TITLE</title>
</head>
<body>
    <h1>$TITLE</h1>
    <p>$TIME_STAMP</p>
    <h2>اطلاعات سیستم</h2>
    <pre>$(uname -mp)</pre>
    <h2>زمان راه‌اندازی</h2>
    <pre>$(uptime)</pre>
    <h2>کاربران فعال</h2>
    <pre>$(who)</pre>
    <h2>فضای دیسک</h2>
    <pre>$(df -h)</pre>
</body>
</html>
_EOF_
then
    echo "گزارش در $FILE ذخیره شد"
else
    echo "ایجاد فایل با خطا روبه‌رو شد" >&2
    exit 1
fi

دستور cat << _EOF_ > $FILE در صورت موفقیت مقدار بازگشتی صفر دارد؛ بنابراین می‌توانیم از آن در دستور if استفاده کنیم.

قالب‌بندی و خوانایی

اشکال‌زدایی و آزمون

دو ابزار ساده در bash برای بررسی اسکریپت وجود دارد:

همچنین می‌توانید درون اسکریپت از set -x برای فعال کردن حالت ردگیری و set +x برای غیرفعال کردن آن در بخش‌های مشخص استفاده کنید.

دسترسی و ایمن‌سازی

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

انتقال و به اشتراک‌گذاری اسکریپت

اسکریپت‌ها فایل‌های متنی هستند. می‌توانید آنها را به راحتی با دوستانتان به اشتراک بگذارید، در مخازن Git قرار دهید یا در وب منتشر کنید.
در صورت استفاده در توزیع‌های مختلف، از ساختار قابل‌حمل (POSIX sh) بهره ببرید یا در مستندات بنویسید که اسکریپت مخصوص bash است.

جمع‌بندی


تمرین‌ها

  1. اسکریپت sys_info_page را گسترش دهید تا خروجی ip addr show را در بخشی جداگانه نمایش دهد.
  2. اسکریپتی بنویسید که از دایرکتوری مشخصی نسخهٔ پشتیبان می‌گیرد: ابتدا فهرست فایل‌ها را با tar آرشیو و سپس با gzip فشرده کند. نام فایل شامل تاریخ روز باشد.
  3. با استفاده از bash -x اسکریپت‌های خود را اجرا کنید و یادداشت کنید هر فرمان قبل از اجرا چگونه در خروجی نشان داده می‌شود.

مطالعهٔ بیشتر