فصل 2: شروع با یک Sha-Bang

 

برنامه‌نویسی پوسته یک جعبه گرامافون دهه 1950 است . . .

--Larry Wall

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

مثال ‎2-1‎.‏ cleanup: یک اسکریپت برای پاکسازی فایل ثبت رخداد در ‎/var/log‎

# Cleanup
# .البته به عنوان کاربر ارشد اجرا کنید‎

cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo "Log files cleaned up."

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

مثال 2-2. cleanup: یک اسکریپت clean-up بهبودیافته

#!/bin/bash
#                 Bash سرآیند بایسته برای یک اسکریپت‎

#                                   ‏، نگارش 2Cleanup‎

#               .البته به عنوان کاربر ارشد اجرا کنید‎
#         کدی را در اینجا درج کنید که اگر کاربر ارشد
#           .نباشد پیغام خطایی نمایش داده و خارج شود‎
LOG_DIR=/var/log
#.کاربرد متغیرها بهتر از درج مستقیم کمیت‌ها در کد است‎
cd $LOG_DIR

cat /dev/null > messages
cat /dev/null > wtmp


echo "Logs cleaned up."

exit #شیوه صحیح و بایسته خروج از یک اسکریپت. یک «exit» ساده
     # ‏(بدون پارامتر) وضعیت خروج فرمان  قبلی را برگشت می‌دهد.

اکنون آن کد مانند یک اسکریپت واقعی به نظر می‌رسد. اما می‌توانیم حتی پیش‌تر برویم . . .

مثال ‎2-3‎.‏ cleanup: یک نگارش ارتقا یافته و تعمیم داده شده از اسکریپت‌های فوق.

#!/bin/bash
# ‏، نگارش  3Cleanup‎

#         هشدار:‎
#    ------------‎
# .این اسکریپت برخی از ویژگی‌ها را که بعداً توضیح داده خواهند شد، به کار می‌برد‎
#   .وقتی نیمه نخست کتاب را تمام کنید مورد مبهمی در آن نباید وجود داشته باشد‎


LOG_DIR=/var/log
ROOT_UID=0     #  فقط کاربرانی با ‏‎$UID‎ برابر با ‎0‎ دارای امتیازات ‎root‎ هستند.‎
LINES=50       #                              .تعداد پیش‌فرض سطرهای ذخیره شده‎
E_XCD=86       #                         تعویض دایرکتوری نمی‌تواند انجام شود؟‎
E_NOTROOT=87   #                                 .نبودنroot خطای خروج در اثر‎


               #                        .البته به عنوان کاربر ارشد اجرا کنید
if [ "$UID" -ne "$ROOT_UID" ]
then
  echo "Must be root to run this script."
  exit $E_NOTROOT
fi  

if [ -n "$1" ]
                         # .بررسی آنکه آیا شناسه سطر فرمان حاضر است(غیر تهی)‏‎
then
  lines=$1
else  
  lines=$LINES            #  .پیش‌فرض، در صورتیکه در سطر فرمان مشخص نشده باشد‎
fi  


#        مورد زیر را به عنوان یک روش بهتر جهت Stephane Chazelas‎
#+         بررسی شناسه‌های سطر فرمان پیشنهاد می‌کند، اما این هنوز‎
#+                     برای این مرحله از آموزش قدری پیشرفته است‎
#
#    E_WRONGARGS=85      # شناسه غیر عددی (قالب نامناسب شناسه)‏ ‎
#
#    case "$1" in
#    ""      ) lines=50;;
#    *[!0-9]*) echo "Usage: `basename $0` lines-to-cleanup";
#     exit $E_WRONGARGS;;
#    *       ) lines=$1;;
#    esac
#
#           * بروید "Loops" برای سردرآوردن از تمام اینها به فصل ‎


cd $LOG_DIR

if [ `pwd` != "$LOG_DIR" ]    #   ‎if [ "$PWD" != "$LOG_DIR" ]‎ یا‎
                              #     نیست ؟ ‎/var/log‎ در دایرکتوری‎
then
  echo "Can't change to $LOG_DIR."
  exit $E_XCD
fi  #.قبل از کار با فایل بررسی مجدد آنکه در دایرکتوری صحیح باشیم

#                              :مورد بسیار کارآمدتر عبارت است از‎
#
# cd /var/log || {
#   echo "Cannot change to necessary directory." >&2
#   exit $E_XCD;
# }




tail -n $lines messages > mesg.temp  # .ذخیره آخرین قسمت پیغام فایل ثبت وقایع‎
mv mesg.temp messages                 #  .سیستم log تغییرنام آن به عنوان فایل ‎


#  cat /dev/null > messages
#*                                  .دیگر نیاز نبود، چون روش فوق مطمئن‌تر است‎

cat /dev/null > wtmp        # .دارای تاثیر یکسان هستند ‎': > wtmp'‎ و ‎'> wtmp'‎ ‎
echo "Log files cleaned up."
#          ‎/var/log‎ توجه نمایید که سایر فایل‌های ثبت وقایع موجود در دایرکتوری‎
#+                                        تحت تاثیر این اسکریپت واقع نمی‌شوند‎

exit 0
#.یک مقدار برگشتی صفر از اسکریپت در موقع خروج، موفقیت را به پوسته نشان می‌دهد‎

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

* * *

شبانگ‎(#!)‎ ‎[1]‎ در ابتدای یک اسکریپت به سیستم شما می‌گوید که این فایل یک مجموعه از فرمانها برای تغذیه شدن به مفسر فرمان اشاره شده است. ‎#!‎ در حقیقت یک magic number دو بایتی است‎[2]‎ ، یک علامت‌گذار که نوع فایل، یا در این مورد یک اسکریپت پوسته قابل اجرا را مشخص می‌کند (برای جزییات بیشتر در باره این مبحث جذاب، دستور ‎man magic‎ را تایپ کنید). بلافاصله به دنبال sha-bang یک نام مسیر است. این نام، بیانگر مسیر برنامه‌ای است که فرمان‌های داخل اسکریپت را تفسیر می‌کند، خواه یک پوسته، یک زبان برنامه‌نویسی، یا یک برنامه سودمند باشد. سپس این مفسر فرمان با شروع از بالا (سطر بعداز sha-bang)، و صرفنظر کردن از توضیحات، فرمانهای داخل اسکریپت را اجرا می‌کند. ‎[3]‎

#!/bin/sh
#!/bin/bash
#!/usr/bin/perl
#!/usr/bin/tcl
#!/bin/sed -f
#!/bin/awk -f

هر یک از سطرهای سرآیند اسکریپت در بالا یک مفسر فرمان متفاوت را فرا می‌خواند، خواه ‎/bin/sh‎ پوسته پیش‌فرض (و bash در یک سیستم لینوکس) باشد یا غیر از آن‎[4]‎. استفاده از ‎#!/bin/sh‎، که پوسته Bourne پیش‌فرض در اکثر گونه‌های تجاری یونیکس است، اسکریپت را قابل حمل به ماشین‌های غیر لینوکس می‌کند، اگر چه شما ویژگی‌های مختص Bash را از دست می‌دهید. اما اسکریپت مطابق با استاندارد sh POSIX‎[5]‎ خواهد بود.

توجه نمایید که مسیر تعیین شده در «sha-bang» باید صحیح باشد، در غیر اینصورت یک پیغام خطا -- به طور معمول ‎«Command not found.»‎ -- تنها نتیجه اجرای اسکریپت خواهد بود.‎[6]‎

در صورتیکه اسکریپت فقط متشکل از یک مجموعه فرمان‌های عمومی سیستم بدون کاربرد دستورات داخلی پوسته باشد، ‎#!‎ می‌تواند از قلم انداخته شود. مثال دوم بالا، به ‎#!‎ ابتدایی نیاز دارد، چون سطر تخصیص متغیر ‎lines=50‎ از یک ساختار مخصوص پوسته استفاده می‌کند.‎[7]‎ یک‌بار دیگر توجه نمایید که ‎#!/bin/sh‎ مفسر پوسته پیش‌فرض را که در ماشین‌های لینوکس ‎/bin/bash‎ است فراخوانی می‌کند.

Tip

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

E_WRONG_ARGS=85
script_parameters="-a -h -m -z"
#                  -a = all, -h = help, etc.

if [ $# -ne $Number_of_expected_args ]
then
  echo "Usage: `basename $0` $script_parameters"
  # .نام فایل اسکریپت است ‎`basename $0`‎ ‎
  exit $E_WRONG_ARGS
fi

شما بارها اسکریپتی خواهید نوشت که یک وظیفه بخصوص را انجام می‌دهد. اسکریپت اول این فصل یک نمونه آن است. بعدها ممکن است پیش بیاید که شما اسکریپت را برای انجام سایر وظایف مشابه تعمیم بدهید. تعویض ثابت‌های لفظی ‎("hard-wired")‎ با متغیرها، اقدامی در این جهت است، همانطور که جایگزینی قطعات کد تکرارشوند به وسیله توابع چنین است.

یادداشت‌ها

‎[1]‎

در نوشته‌ها بیشتر به صورت she-bang یا sh-bang دیده شده. این اصطلاح از الحاق نشانه‌های sharp ‎(#)‎ و bang ‎(!)‎ حاصل می‌شود.

‎[2]‎

بعضی از گرایش‌های یونیکس (آنها که مبتنی بر ‎BSD 4.2‎ هستند) به طوری که ادعا گردیده، یک ‎magic number‎ چهار بایتی می‌گیرند، که نیازمند یک جای خالی بعد از ! است -- ‎#! /bin/sh‎. به قول ‎Sven Mascheck‎ این احتمالاً یک افسانه است.

‎[3]‎

سطر ‎#!‎ در یک اسکریپت پوسته اولین چیزی خواهد بود که مفسر فرمان (sh یا bash) می‌بیند. چون این سطر با یک # شروع می‌شود، وقتی سرانجام مفسر‌فرمان اسکریپت را اجرا می‌کند به طور صحیح به عنوان یک توضیح تفسیر خواهد گردید. این سطر قبلاً به هدفش - فراخوانی مفسر فرمان - خدمت کرده است.

در واقع، اگر اسکریپت شامل یک سطر اضافی ‎#!‎ باشد، آنوقت bash آن را به عنوان یک توضیح تفسیر خواهد نمود.

#!/bin/bash

echo "Part 1 of script."
a=1

#!/bin/bash
#    .این یک شل جدید را راه‌اندازی نمی‌کند‎

echo "Part 2 of script."
echo $a   # برابر 1 باقی می‌ماند ‎$a‎ مقدار

‎[4]‎

این مورد، برخی ترفندهای جالب را میسر می‌سازد.

#!/bin/rm
# .اسکریپت حذف کننده خودش ‎

# .اسکریپت را که اجرا ‌کنید چیزی غیر از ناپدید شدن فایل آن رخ نمی‌دهد‎

WHATEVER=85

echo "This line will never print (betcha!)."

exit $WHATEVER  #    .نقشی ندارد، اسکریپت در اینجا خارج نمی‌شود‎
                # .امتحان کنید ‎echo $?‎ بعد از خاتمه اسکریپت یک‎
                #             ‎85‎ به دست خواهید آورد نه یک ‎0‎ یک‎

همچنین، شروع یک فایل README با یک ‎#!/bin/more‎ و قابل اجرا کردن آن را امتحان کنید. نتیجه یک فایل مستندسازی خود فهرست کننده است. (یک ‎here document‎ با کاربرد cat احتمالاً جایگزین مناسب‌تری است -- مثال ‎19-3‎ را ببینید).

‎[5]‎

PortableOperating System Interface‎، یک کوشش استانداردسازی سیستم‌عامل‌های UNIX-مبنا می‌باشد. مشخصات POSIX در سایت Open Group لیست گردیده است.

‎[6]‎

برای پرهیز از این احتمال، یک اسکریپت می‌تواند با سطر sha-bang به صورت ‎#!/bin/env bash‎ شروع بشود. این مورد ممکن است در آن ماشین‌های یونیکس که bash در آنها در دایرکتوری ‎/bin‎ قرار ندارد مفید باشد.

‎[7]‎

اگر Bash پوسته پیش‌فرض شما باشد، آنوقت ‎#!‎ در ابتدای یک اسکریپت ضروری نیست. به هرحال در صورت راه‌اندازی اسکریپت از یک پوسته متفاوت مانند tcsh، آنوقت شما به ‎#!‎ نیاز خواهید داشت.