آموزش اسکریپت نویسی

آموزش اسکریپت نویسی پوسته گنو-لینوکس

آموزش اسکریپت نویسی

آموزش اسکریپت نویسی پوسته گنو-لینوکس

تکنیک‌ها



راه و روش‌ها

1. انتخاب پوسته شما

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

BASH ممکن است برای یادگیری و نوشتن در آن آسان باشد، اما همیشه مناسب انجام کار نیست.

در مجموعه ابزارهای اساسی، تعداد بسیاری ابزار موجود است که می‌تواند به شما کمک کند. اگر شما فقط به AWK نیاز داشته باشید، نباید یک اسکریپت شل ایجاد کنید که آن را فراخوانی کند. فقط یک اسکریپت AWK ایجاد کنید. اگر به بازیابی داده از یک فایل HTMLیا XML به یک روش معتبر نیاز دارید، نیز Bash ابزار اشتباهی برای انجام آن کار است. باید به جای آن XPath/XSLT را به کار ببرید، یا یک زبانی که کتابخانه معتبری برای تجزیه XML یا HTML دارد.

اگر تصمیم گرفتید که اسکریپت پوسته آن ابزاری است که شما می‌خواهید، اول این سؤال‌ها را از خود بپرسید:

  • در یک آینده قابل پیش‌بینی، آیا ممکن است اسکریپت شما در محیطی که Bash به طور پیش‌فرض در دسترس نیست، مورد احتیاج باشد؟

    • اگر اینطور است، پس به جای آن sh را در نظر بگیرید. sh یک شل POSIX است و ویژگی‌هایش در هر پوسته موافق با استاندارد POSIX، در دسترس می‌باشد. به این واقعیت تکیه کنید که هر سیستم POSIX قادر به اجرای اسکریپت شما خواهد بود. شما باید توازنی بین لزوم قابلیت حمل و عدم استفاده از قابلیت‌های ویژه Bash برقرار کنید.

    • به خاطر داشته باشید که این راهنما شامل sh نمی‌شود! صفحه bashism پیشنهادهایی دارد، اما کامل نیست.

  • آیا اطمینان دارید که در تمام محیط‌هایی که اسکریپت را اجرا می‌کنید یا ممکن است در آینده بخواهید اجرا کنید، Bash 3.x (یا ‎ 4.x‎) در دسترس شما خواهد بود؟
    • اگر نه، باید خود را فقط به ویژگی‌های Bash 2.x محدود نمایید.

اگر سؤلات فوق انتخاب شما را محدود نمی‌کند، از تمام ویژگی‌های Bash که لازم دارید، استفاده کنید، توجه کنید که کدام نگارش Bash برای اجرای اسکریپت شما لازم است.

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

  • از اسکریپت‌های نمونه‌ای که در Web می‌بینید، بدون درک کامل عملکرد آنها دوری کنید. اسکریپت‌هایی که در Web پیدا می‌کنید اکثراً به نوعی ناقص هستند. از آنها کپی paste نکنید.
  • همواره از شبانگ صحیح استفاده کنید. اگر در حال نوشتن اسکریپت هستید، قرار دادن ‎#!/usr/bin/env bash‎ در بالای اسکریپت لازم است. از قلم انداختن این سرآیند یا استفاده از سرآیند ‎ #!/bin/shاشتباه است. در حالت اخیر، شما دیگر قادر به استفاده از ویژگیهای Bash نیستید. محدود به اسکریپت‌نویسی استاندارد POSIX می‌شوید(حتی اگر ‎/bin/sh شما یک پیوند به Bash باشد).

  • موقع نوشتن اسکریپت‌های پوسته، از دستور ‎[‎ استفاده نکنید. Bash جایگزین بسیار بهتری دارد: ‎[[‎. کلمه‌کلیدی ‎[[‎ در Bash از بسیاری جهات قابل اعتمادتر است و به هیچ وجه مزیتی برای چسبیدن به نوع عتیقه آن وجود ندارد. همچنین مقدار زیادی از ویژگی‌های فراهم شده برای ‎ [[‎ که در ‎ [‎ وجود ندارد( مانند انطباق الگو) را از دست می‌دهید.

  • وقت آنست که `...` نیز به فراموشی سپرده شود. این مورد با ساختار بسط سازگار نیست. به جای آن از ‎$(...)‎ استفاده کنید.

  • و به واسطه قدرت شگرف، "کاربرد بیشتر نقل‌قولها!" رشته‌ها و بسط پارامترهای خود را از تفکیک کلمات محافظت کنید. اگر به طور صحیح نقل‌قولی نکنید، تفکیک کلمات نوزادان شما را می‌خورد.

  • به جای استفاده از sed یا cut برای کار با رشته‌های ساده در Bash، استفاده از بسط‌ پارامترها را بیاموزید. اگر می‌خواهید پسوند نام فایل را حذف کنید، به جای ‎`echo "$filename" | sed 's/ \.[^.]*$//' `‎ یا بعضی دایناسورهای دیگر، از ‎ ${filename%.*} استفاده کنید.

  • به جای استفاده از expr برای انجام محاسبات ساده، از حساب داخلی استفاده کنید، مخصوصاً وقتی‌که فقط مقدار متغیری افزایش می‌یابد. اگر اسکریپتی می‌خوانید که ‎x=`expr $x + 1`‎ را به کار برده، این چیزی نیست که شما از آن تقلید کنید.

2. نقل‌قولی کردن

تفکیک کلمه اهریمن درون BASH است که با جدیت تلاش می‌کند تازه‌واردها یا حتی کهنه سربازانی که سپر محافظ خود را زمین می‌گذارند، را غافلگیر کند.

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

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

    $ echo Push that word             away from me.
    Push that word away from me.
    $ echo "Push that word             away from me."
    Push that word             away from me.

حال، تصور نکنید که تفکیک کلمه برای فرو ریختن فاصله‌هاست. آنچه به طور واقعی در این مثال اتفاق می‌افتد، آنست که در مثال اول، هر یک از کلمات جمله ما به عنوان یک شناسه( argument) جداگانه به echo تحویل داده می‌شود. BASH جمله ما را به کلمات تجزیه می‌کند، از فضای سفید برای تعیین آن که هر شناسه از کجا شروع و به کجا ختم می‌گردد، استفاده می‌نماید. در مثال دوم BASH وادار شده تمام رشته نقل‌قولی شده را با هم نگاه دارد. به این معنا که به شناسه‌ها تفکیک نمی‌شوند و تمام رشته به عنوان یک شناسه به echo تحویل می‌شود. دستور echo همه شناسه‌هایی که به آن داده شود را با یک فاصله مابین آنها در خروجی چاپ می‌کند. اکنون باید اساس تجزیه کلمه را متوجه شده باشید.

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

    $ sentence="Push that word             away from me."
    $ echo $sentence
    Push that word away from me.
    $ echo "$sentence"
    Push that word             away from me.

به طوری که ملاحظه می‌کنید، در دستور echo اول، سهل‌انگاری کرده و نقل‌قول‌ها را از قلم انداخته‌ایم. یک اشتباه بود. BASH جمله ما را بسط داده و سپس از تفکیک کلمه برای تجزیه نتیجه بسط به شناسه‌ها ‌جهت تحویل به echo استفاده نموده. در دومین مثال، نقل‌قول‌ها در اطراف بسط پارامتر جمله، اطمینان ایجاد می‌کند که BASH آن را به چندین شناسه پیرامون فضاهای سفید تجزیه نمی‌کند.

فقط فاصله‌ها نیستند که باید محافظت شوند. تفکیک کلمه در فاصله‌ها، tabها، سطر جدید، یا هر کاراکتر دیگری که در متغیر IFS باشد، صورت می‌گیرد. در اینجا مثال دیگری هست، که به شما نشان می‌دهد، چطور سهل‌انگاری در استفاده از نقل‌قول‌ها، موجب تجزیه نامناسبی می‌شود:

    $ echo "$(ls -al)"
    total 8
    drwxr-xr-x   4 lhunath users 1 2007-06-28 13:13 "."/
    drwxr-xr-x 102 lhunath users 9 2007-06-28 13:13 ".."/
    -rw-r--r--   1 lhunath users 0 2007-06-28 13:13 "a"
    -rw-r--r--   1 lhunath users 0 2007-06-28 13:13 "b"
    -rw-r--r--   1 lhunath users 0 2007-06-28 13:13 "c"
    drwxr-xr-x   2 lhunath users 1 2007-06-28 13:13 "d"/
    drwxr-xr-x   2 lhunath users 1 2007-06-28 13:13 "e"/
    $ echo $(ls -al)
    total 8 drwxr-xr-x 4 lhunath users 1 2007-06-28 13:13 "."/
	 drwxr-xr-x 102 lhunath users 9 2007-06-28 13:13 ".."/ -rw-r--r-- 1 lhunath users 0 2007-06-28 13:13 "a" -rw-r--r-- 1 lhunath users 0 2007-06-28 13:13 "b" -rw-r--r-- 1 lhunath users 0 2007-06-28 13:13 "c" drwxr-xr-x 2 lhunath users 1 2007-06-28 13:13 "d"/ drwxr-xr-x 2 lhunath users 1 2007-06-28 13:13 "e"/

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

    $ friends="Marcus JJ Thomas Michelangelo"
    $ for friend in $friends
    > do echo "$friend is my friend!"; done
    Marcus is my friend!
    JJ is my friend!
    Thomas is my friend!
    Michelangelo is my friend!

اما، صادقانه؟ برای چنین حالاتی باید از آرایه‌ها استفاده کنید. آرایه‌ها این فایده را دارند که بدون نیاز به جداکننده صریح، رشته‌ها را جدا می‌کنند. این به آن معنا می‌باشد، که رشته‌های شما می‌توانند شامل هر کاراکتر معتبر(غیرتهی) باشند، بدون آنکه نگران جداکننده بودن کاراکتر باشید(مانند فاصله در مثال فوق). به کار بردن آرایه در مثال فوق ما را قادر می‌کند نام فامیل دوستان را نیز اضافه کنیم:

    $ friends=( "Marcus The Rich" "JJ The Short" "Timid Thomas" "Michelangelo The Mobster" )
    $ for friend in "${friends[@]}"
    > do echo "$friend is my friend!"; done

توجه نمایید که در حلقه for قبلی، ما از ‎$friends‎ به شکلغیر نقل‌قولی استفاده کردیم. این کار BASH را برای جداکردن رشته friends به کلمات، مجاز می‌سازد. در مثال اخیر، بسط پارامتر${friends[@]}‎ را نقل‌قولی کرده‌ایم . نقل‌قولی کردن یک آرایه با شاخص سراسری @ موجب می‌شود BASH آن آرایه را به صورت یک توالی از تمام عناصر که در آن هر یک در علامت نقل‌قول پیچیده شده، بسط بدهد.

3. خوانایی

تقریباً خوانایی کد شما به همان اندازه نتایج آن اهمیت دارد.

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

  • به من اعتماد کنید وقتی می‌گویم، هیچ بخشی از کد، هرگز 100% تکمیل نیست، به استثنای، برخی کدهای خیلی کوتاه و عاری از فایده .

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

   1     friends=( "Marcus The Rich" "JJ The Short" "Timid Thomas" "Michelangelo The Mobster" )
   2 
   3     # مطالب مهمی در باره دوستانم می‌گوید‎
   4     for name in "${friends[@]}"; do
   5 
   6         #(اولین دوست من (در لیست‎
   7         if [[ $name = ${friends[0]} ]]; then
   8             echo $name was my first friend.
   9 
  10         # شروع می‌شود M دوستان من که نامشان با ‎
  11         elif [[ $name = M* ]]; then
  12             echo "$name starts with an M"
  13 
  14         # دوستان کوتاه من‎
  15         elif [[ " $name " = *" Short "* ]]; then
  16             echo "$name is a shorty."
  17 
  18         # دوستانی که زحمت بخاطر سپردن آنها را نمی‌کشم‎
  19         else
  20             echo "I kind of forgot what $name is like."
  21 
  22         fi
  23     done

تا اینکه با موردی مشابه این روبرو شوید:

   1     x=(       Marcus\ The\ Rich JJ\ The\ Short
   2       Timid\ Thomas Michelangelo\ The\ Mobster)
   3     for name in "${x[@]}"
   4       do if [ "$name" = "$x" ]; then echo $name was my first friend.
   5      elif
   6        echo $name    |   \
   7       grep -qw Short
   8         then echo $name is a shorty.
   9      elif [ "x${name:0:1}" = "xM" ]
  10          then echo $name starts   with an M; else
  11     echo I kind of forgot what $name \
  12      is like.; fi; done

و بله، می‌دانم که این مثال کمی اغراق‌آمیز است، اما من بعضی کدهای معتبری را دیده‌ام که واقعاً شباهت بسیاری به مثال اخیر دارند.

برای سلامت خودتان این چند نکته را به یاد داشته باشید:

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

  • از پوشش با کاراکتر \ اجتناب کنید. استفاده زیاد از این کاراکترِ گریز، موجب حواس‌پرتی و آشفتگی ذهنی می‌گردد. حتی در مثالهای کوچک، تقلای ذهنی بیشتری برای فهمیدن ‎a\ b\ c‎ نسبت به ‎'a b c'‎ صرف می‌شود.

  • روش تفکرتان را، یادداشت کنید، قبل از اینکه آن را فراموش کنید. ممکن است دریابید، کدی که کاملاً متعارف حس می‌شود، می‌تواند موضوع "چه جهنمی فکر می‌کردم، وقتی این را نوشتم؟" یا "تصور انجام چه کاری از این داشتم؟".

  • سازگاری از ناراحتی ذهن پیشگیری می‌کند. در شیوه نام‌گذاری استوار باشید. در استفاده از حروف بزرگ سازگار باشید. در استفاده خود از ویژگی‌های پوسته پایدار باشید. در کدنویسی، برخلاف اطاق‌خواب، خوبست ساده و قابل پیش بینی باشید.

4. بررسی‌های Bash

فرمان test که به عنوان ‎[‎ نیز شناخته شده، یک برنامه کاربردی است که به طور معمول جایی در ‎ /usr/bin‎ یا ‎/bin‎ استقرار می‌یابد و توسط برنامه‌نویس پوسته برای اجرای آزمایش‌های معینی با متغیرها و فایلها، خیلی زیاد به کار می‌رود. در تعدادی از پوسته‌ها، از جمله Bash, دستور test به صورت دستور داخلی پوسته نیز پیاده‌سازی گردیده است.

این مورد می‌تواند نتایج شگف‌انگیزی فراهم نماید، به ویژه برای آنان که شروع به اسکریپت‌نویسی پوسته می‌نمایند و تصور می‌کنند ‎ [ ]‎ بخشی از دستور زبان پوسته است.

اگر از پوسته sh استفاده می‌کنید، انتخاب کمی دارید و استفاده از test تنها راه انجام اکثر بررسی‌هایتان می‌باشد.

گرچه اگر از Bash در اسکریپت‌نویسی استفاده می‌کنید(و من فرض می‌کنم چنین است، چون در حال خواندن این راهنما هستید)، پس می‌توانید از کلید واژه ‎[[‎ نیز استفاده کنید. هر چند بازهم از خیلی جهات همچون یک فرمان رفتار می‌کند، چندین مزیت نیز نسبت به فرمان سنتی test ارائه می‌کند.

اجازه بدهید تشریح کنم که چگونه ‎ [[‎ می‌تواند با فرمان test تعویض شود، وچطور می‌تواند به شما کمک کند از برخی اشتباهات متداول در کاربرد test پرهیز نمایید:

    $ var=''
    $ [ $var = '' ] && echo True
    -bash: [: =: unary operator expected
    $ [ "$var" = '' ] && echo True
    True
    $ [[ $var = '' ]] && echo True
    True

قسمت ‎[ $var = '' ]‎ به ‎[ = '' ]‎ بسط داده می‌شود. اولین کاری که دستور test انجام می‌دهد، شمارش شناسه‌هایش می‌باشد. چون ‎[‎ را به کار برده‌ایم، باید شناسه الزامی ] در انتها را کنار بگذاریم. در مثال اول، test دو شناسه می‌بیند: = و ''. حالا می‌داند که دو شناسه دارد، اولی باید unary operator (یک عملگر که یک عملوند می‌گیرد). اما = عملگر یگانی(unary operator) نیست(یک عملگر binary است که دو عملوند نیاز دارد)، بنابراین، test نمی‌تواند کار کند.

بله، test متغیر تهی ‎$var‎ را نمی‌بیند، زیرا BASH قبل از اینکه test حتی بتواند آن را ببیند، به هیچ بسطش داده است. نتیجه اخلاقی؟ استفاده بیشتر از نقل‌قول‌ها! کاربرد نقل‌قول‌ها در قسمت، ‎[ "$var= '' ]‎ موجب بسط آن به ‎ [ "" = '' ]‎ می‌گردد و test مشکلی ندارد.

حال آنکه، ‎[[‎ می‌تواند تمام دستور را قبل از اینکه بسط داده شود، ببیند. می‌تواند ‎$var‎ را ببیند، و نه بسط ‎$var‎ را. در نتیجه، نیازی به نقل‌قولها نمی‌باشد! ‎[[‎ مطمئن‌تر است.

    $ var=
    $ [ "$var" < a ] && echo True
    -bash: a: No such file or directory
    $ [ "$var" \< a ] && echo True
    True
    $ [[ $var < a ]] && echo True
    True

در این مثال سعی نموده‌ایم یک مقایسه رشته‌ای بین یک متغیر تهی و 'a' انجام بدهیم. شگفت‌زده می‌شویم با دیدن آنکه از اولین تلاش ما True حاصل نمی‌گردد، ولواینکه تصور می‌کردیم، می‌شود. درعوض، با خطای عجیبی که دلالت بر تلاش BASH برای باز کردن فایلی به نام ‎'a'‎ می‌نماید، مواجه می‌شویم.

ما توسط تغییر مسیر فایل گَزیده شده‌ایم. چون test دقیقاً یک برنامه کاربردی است، کاراکتر ‎<‎ در دستور ما به جای عملگر مقایسه رشته‌ای برای test،  به عنوان عملگر تغییر مسیر فایل تفسیر شده است(همانطور که باید می‌شد). BASH دستور باز کردن فایل 'a' و اتصال آن به stdin برای خواندن را دریافت نموده. برای ممانعت از این مورد، لازم است, ‎ <‎ را با کاراکتر گریز پوشش دهیم، به طوری که به جای BASH برنامه test عملگر را دریافت کند. این دومین تلاش ما را تشکیل داد.

با استفاده از ‎ [[‎ می‌توانیم روی‌هم‌رفته از نابسامانی اجتناب نماییم. ‎[[‎ عملگر ‎ < را قبل از آنکه BASH آنرا برای تغییر مسیر دریافت کند، می‌بیند -- مشکل رفع می‌شود. یکبار دیگر ‎ [[‎ مطمئن‌تر است.

حتی خطرناک‌تر، استفاده از عملگر ‎ >‎ به جای عملگر ‎ <‎ مثال قبلی است. چون ‎ >‎ ماشهٔ تغییر مسیر خروجی را می‌کشد، فایلی به نام'a' ایجاد خواهد نمود. در نتیجه، هیچ پیغام خطای هشداردهنده‌ای برای ما صادر نمی‌شود که بدانیم مرتکب اشتباه شده‌ایم! به جای آن، فقط اسکریپت ما خراب می‌شود. حتی وخیم‌تر، شاید فایل مهمی را رونویسی کنیم! برای ما حدس زدن آنکه مشکل کجاست، سخت است:

    $ var=a
    $ [ "$var" > b ] && echo True || echo False
    True
    $ [[ "$var" > b ]] && echo True || echo False
    False

دو نتیجه متفاوت، شگرف. به من اعتماد کنید، وقتی می‌گویم، همیشه می‌توانید به ‎ [[‎ بیشتر از ‎ [‎ اطمینان کنید. ‎[ "$var> b ]‎ به ‎[ "a" ]‎ بسط یافته و خروجی به یک فایل جدید به نام 'b' تغییر مسیر داده می‌شود. چون ‎[ "a" ]‎ در واقع همان ‎[ -n "a" ]‎ می‌باشد و اساساً بررسی می‌شود که آیا رشته "a" غیرتهی است، نتیجه بررسی موفق است و echo True اجرا می‌شود.

با کاربرد ‎[[‎ انتظار ما که مقایسه "a" در برابر "b" است، برآورده می‌شود، و نظر به اینکه همه می‌دانیم "a" قبل از "b" مرتب می‌شود، ماشه اجرای دستور echo False کشیده می‌شود. و این چگونگی آنست که اسکریپت شما می‌تواند بدون پی‌بردن شما ناموفق بشود. هر چند که، شما یک فایل شبهه برانگیزی به نام 'b' نیز در دایرکتوری جاری خواهید داشت.

بنابراین به من باور داشته باشید، وقتی می‌گویم، ‎[[‎ مطمئن‌تر از ‎[‎ است. زیرا هر کسی به ناچار خطاهای برنامه‌نویسی را ایجاد می‌کند. افراد به طور معمول قصد ندارند، باگهایی در کدهایشان ارائه کنند. اما اتفاق می‌افتد. بنابراین مدعی نشوید که از ‎ [‎ استفاده می‌کنید و "مراقب خواهید بود که چنین اشتباهاتی مرتکب نگردید"، زیرا می‌توانم شما را مجاب کنم که مرتکب خواهید شد.

گذشته ازاین، ‎ [[‎ ویژگیهای زیر را علاوه بر ‎ [‎ ارائه می‌کند:

  • [[‎ می تواند مطابقت الگوی جانشین(glob) را انجام دهد:

    • [[ abc = a* ]]

  • [[‎ می‌تواند انطباق الگوی عبارت باقاعده(regex) را (از Bash نگارش 3.1 به بعد) انجام دهد:

    • [[ abb =~ ab+ ]]

تنها برتری test قابلیت حمل آن است.

5. هرگز این موارد را انجام ندهید

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

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

برای پاکیزگی اسکریپت‌هایتان، و به خاطر تمام افراد بشر، هرگز هیچ موردی از سطور زیر را انجام ندهید:

  • ls -l | awk '{ print $8 }'

    • هرگز تجزیه خروجی فرمان ls را انجام ندهید! خروجی فرمان ls به چند دلیل نمی‌تواند قابل اعتماد باشد.

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

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

      3. آخر از همه، اما نه کم اهمیت‌تر، قالب خروجی فرمان ‎ ls -l‎ تضمین نمی‌شود که در تمام پلاتفرم‌ها همسان باشد. برخی سیستم‌ها به طور پیش‌فرض شماره شناسایی گروه را از قلم می‌اندازند، و اثر گزینه ‎-g‎ را معکوس می‌نمایند. بعضی سیستم‌ها از دو فیلد برای زمان ویرایش و برخی از سه فیلد برای آن استفاده می‌کنند. در سیستم‌هایی که از سه فیلد استفاده می‌کنند، فیلد سوم می‌تواند سال یا یک ‎ HH:MM‎ الحاقی، نسبت به سن فایل، باشد.

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

  • if echo "$file| fgrep .txt; then
    ls *.txt | grep story

    • هرگز نام فایل ‌ها را با grep بررسی یا فیلتر نکنید! غیر از آنکه الگوی grep شما واقعاً هوشمند باشد، این کار احتمالاً قابل اطمینان نخواهد بود.

    • در نمونه اول مثال فوق، بررسی با هر دو مورد story.txt و story.txt.exe منطبق می‌گردد. اگر الگوهایی از grep ایجاد کنید که به اندازه کافی هوشمند باشند، احتمالاً آنها به قدری زشت، حجیم و ناخوانا می‌شوند، که با این وجود نباید از آنها استفاده کنید.

    • جایگزین آن globbing نامیده می‌شود( مترجم: من در این ترجمه گاهی به جای آن کلمه«جانشینی» را به کاربرده‌‌ام). Bash یک ویژگی به نام بسط نام‌مسیر دارد. این ویژگی به شما کمک می‌کند، که تمام فایلهایی که با یک الگوی معین مطابقت دارند را به شمار آورید. همچنین، می‌توانیداز globها جهت بررسی آنکه یک نام فایل، آیا با یک الگوی معین مطابقت می‌کند، (در یک دستور case یا ‎[[‎ ) استفاده نمایید .

  • cat file | grep pattern

    • برنامه cat را برای خوراندن محتویات یک فایل منفرد به یک فیلتر به کار نبرید. cat یک ابزار مورد استفاده برای الحاق محتویات چند فایل با یکدیگر است.

    • برای تغذیه محتویات فایلی به یک پردازش، احتمالاً می‌توانید نام فایل را به عنوان شناسه تحویل برنامه مورد نظر(مانند ‎grep 'pattern/my/file‎ یا ‎sed 'expression/my/file ‎و غیره) بدهید.

    • اگر مستندات برنامه هیچ راهی برای انجام این کار تعیین نکرده است، باید از تغییر مسیر استفاده کنید (‎read column1 column2 < /my/file‎ یا ‎tr ' ' '\n< /my/file ‎و غیره).

  • for line in $(<file); do

    • از حلقه for برای خواندن سطرهای یک فایل استفاده نکنیم. به جای آن حلقه while read را به کار ببریم.

  • for number in $(seq 1 10); do

    • به خاطر خدا و به خاطر تمام مقدسات، از برنامه seq برای شمارش استفاده نکنید.

    • Bash به اندازه کافی در انجام شمارش توانمند است. نیازی به یک برنامه خارجی(مخصوصاً یک برنامه تک سکویی) برای انجام محاسبه و ارسال آن به خروجی Bash جهت تفکیک کلمه، ندارید. ترکیب دستوری for را قبلاً آموخته‌اید!

    • باید در Bash نگارش 3 به بعد، از این: ‎for number in {1..10}‎، یا در ‎Bash 2‎ از این: ‎for ((i=1i<=10i++))‎ استفاده کنید.

    • اگر شما عملاً یک جریانی از اعداد که با کاراکتر سطر جدید از هم جدا شده‌اند، هنگام بررسی ورودی می‌خواهید، این مورد را در نظر بگیرید: ‎printf '%d\n' {1..10}

  • i=`expr $i + 1>`

    • expr یک عتیقه رُم باستان است. آن را به کار نبرید.

    • این برنامه در اسکریپت‌های نوشته شده برای پوسته‌هایی با امکانات بسیار محدود، به کار می‌رفت. اساساً با استفاده از آن در حال ایجاد یک پردازش جدید هستید که برنامه C دیگری برای انجام برخی محاسبات را برایتان فراخوانی نماید و نتایج را به صورت رشته به bashتحویل بدهد. Bash تمام اینها را خودش می‌تواند خیلی سریعتر، و به طور قابل اعتمادتر (بدون تبدیل عدد به ->رشته -> به عدد) و در همه حال بهتر، انجام بدهد.

    • شما در Bash باید از این استفاده کنید: ‎let i++ ‎ یا ‎((i++))

    • حتی پوسته POSIX بورن می‌تواند محاسبات را انجام بدهد: i=$(($i+1))‎. فقط فاقد عملگر ++ و دستور‎ ((...))‎ می‌باشد(فقط عبارت جایگزینی ‎ $((...))‎ را دارد).

6. اشکالزدایی

خیلی وقتها، خودتان را مستأصل می‌بینید که چرا، اسکریپت شما آنگونه عمل نمی‌کند، که شما می‌خواهید. حل این مسئله همواره، موضوع درک عمومی و شیوه‌های اشکال‌یابی است.

تشخیص مشکل

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

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


حداقل‌سازی کد اصلی

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

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

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

به مجرد اینکه، موردی که مشکل ایجاد نموده را حذف کردید، دست بکشید(یا موردی که اضافه نمودنش دوباره آن مشکل را ظاهر می‌کند)، شما کشف کرده‌اید که مشکل در کجا قرار دارد. حتی اگر به طور دقیق به آن نرسیده‌اید، حداقل دیگر به یک اسکریپت حجیم خیره نمی‌شوید، بلکه امیدوارانه، با کوتوله‌ای نه بیش از 3 تا 7 سطر، مواجه هستید.

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

    for image in $(ls -R "$imgFolder"); do
        echo "$image"
    done

اسکریپت واقعی شما به مراتب پیچیده‌تر از این خواهد بود، و درون حلقه for آن نیز طولانی‌تر خواهد بود. اما اصل مشکل این کُد است. وقتی مشکل را به این اندازه محدود کنید، ممکن است دیدن آنکه با چه مشکلی مواجه هستید، آسانتر باشد. دستور echo اجزاء نام فایلها را بیرون می‌دهد، به نظر می‌رسد تمام فضاهای سفید با کاراکتر سطرجدید تعویض گردیده‌اند. باید اینطور باشد، زیرا echo برای هرقسمت از نام فایل که منتهی به فضای سفید است، اجرا می‌شود، نه برای هر نام تصویر(در نتیجه، به نظر می‌رسد، خروجی نام فایلهایی که دارای فضای سفید می‌باشند، تفکیک شده‌اند). با این کُد کاهش یافته، دیدن آنکه مسبب واقعی، جمله for است که خروجی ls را به کلمات تفکیک نموده، آسانتر است. هرگز ls را در اسکریپتها به کار نبرید، مگر آنکه بخواهید خروجی آنرا به کاربر نمایش بدهید.

ما نمی‌توانیم glob بازگشتی به کار ببریم(مگر در bash نگارش 4)، بنابراین باید دستور find را برای به دست آوردن نام فایلها به کار بگیریم. یک راه اصلاح آن، چنین خواهد بود:

    find "$imgFolder" -print0 | while IFS= read -r -d '' image; do
        echo "$image"
    done

اکنون که مشکل را در این مثال کوچک برطرف نموده‌اید، برگشتن و ترکیب کردن آن با اسکریپ اصلی آسان است.


فعال نمودن وضعیت اشکال‌یابی BASH

اگر بازهم خطای روش‌هایتان را نمی‌بینید، شاید وضعیت اشکال‌یابی BASH برای دیدن مشکل در میان کُد به شما کمک نماید.

موقعی که BASH با گزینه ‎-x‎ اجرا می‌شود، این وضعیت فعال می‌گردد، هر دستوری را قبل از اجرا در خروجی چاپ می‌کند. همینطورهم ، بعد از هر بسط و گسترشی که انجام شده است. در نتیجه، به طور دقیق می‌توانید ببینید با اجرای هر سطر کُد، چه اتفاقی رخ می‌دهد. به نقل‌قول‌های استفاده شده خیلی دقیق توجه نمایید. BASH نقل‌قولها را برای نشان دادن آنکه دقیقاً کدام رشته به عنوان یک شناسه منفرد عبور داده شده، به کار می‌برد.

سه روش برای فعال کردن این وضعیت موجود است.

  • اجرای اسکریپت به صورت ‎ bash -x‎:

          $ bash -x ./mybrokenscript
  • ویرایش سرآیند اسکریپت:
          #!/bin/bash -x
          [.. script ..]
    
  • یا:
          #!/usr/bin/env bash
          set -x
    
  • یا اضافه نمودن ‎set -x‎ در جایی از کُد برای فعال کردن این حالت، منحصراً برای قطعه معینی از کُد:

          [..کدهای بی ارتباط..]
          set -x
          [..قطعه کد مرتبط..]
          set +x
          [..کدهای بی ارتباط..]
  • اگر ‎set -x‎ شما مقدار زیادی خروجی دارد، ویژگی دلپذیر bash نگارش 4.1 و بالاتر، متغیر BASH_XTRACEFD است. این متغیر امکان تعیین یک توصیف‌گر فایل برای هدایت خروجی اشکال‌های ‎set -x‎ به آن را فراهم می‌کند. در نسخه‌های قدیمی‌تر bash، همواره این خروجی به stderr می‌رفت، و اگر جدا کردن آن از خروجی معمولی ناممکن نبود، ولی دشوار بود. این هم یک روش دلپسند برای کاربرد آن:

        # ‎ را در یک فایل کپی می‌کند set -x ‎بخش 
        # ‎با یک نام فایل به عنوان 1$ آنرا فعال می‌کند‎
        # اگر پارامتری وجود نداشته باشد آن را غیر فعال می‌کند
        # ‎شماره  4 نباید در جای دیگری از اسکریپت استفاده شده باشد fd ‎
        setx_output()
        {
            if [[ $1 ]]; then 
               exec 4>>"$1"
               BASH_XTRACEFD=4
               set -x
            else
               set +x
               exec 4>&-
            fi
        }

اگر اسکریپت‌های پیچیده و آشفته‌ای دارید، شاید تغییر محتوی متغیر PS4 قبل از برقراری اشکال‌یابی با ‎ -x را سودمند بیابید:

      export PS4='+$BASH_SOURCE:$LINENO:$FUNCNAME: '


Step your code

اگر خروجی اشکال‌یابی از نظر شما خیلی سریع عبور می‌کند، می‌توانید کُد-مرحله‌ای را فعال کنید. کُد زیر از DEBUG دستور trap برای اطلاع به کاربر در باره دستوری که اجرا خواهد شد و انتظار برای تایید پیشرفت، استفاده می‌کند. این کُد را در محلی از اسکریپت خود که می‌خواهید مرحله‌ای بشود، قرار دهید:

    trap '(read -p "[$BASH_SOURCE:$LINENO] $BASH_COMMAND?")' DEBUG


اشکالزدای BASH

پروژه اشکالزدای Bash یک اشکال‌یاب gdb-مانند، در آدرس /http://bashdb.sourceforge.net است.

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


بازخوانی مستندات

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

نکته‌ها را حفظ کنید و تکرارهای راهنمایی این آموزش را خوب به خاطر بسپارید. اینها غالباً برای پرهیز از مشکلات در اسکریپت‌ها به شما کمک می‌کنند.

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

  •     $ tr -d '\r' < myscript > tmp && mv tmp myscript


پرسش و پاسخها و Pitfallها را بخوانید

صفحه‌های پرسش و پاسخهای رایج و دام‌های Bash تصورات غلط معمول و مشکلاتی که دیگران در اسکریپت‌های BASH با آنها روبرو شده‌اند را شرح می‌دهند. احتمال بسیار دارد، مشکل شما به شکلی در آنجا تشریح شده باشد.

برای اینکه قادر به یافتن مشکل خود در آنجا باشید، باید مسئله را به طور کاملاً واضح شناسایی کرده باشید. شما باید بدانید که در جستجوی چه چیزی می‌باشید.


از ما در IRC بپرسید

در 24 ساعت هفت روز هفته، اکثراً افرادی در کانال ‎#bash‎ حضور دارند. این کانال در شبکه freenode IRC مستقر است. برای رسیدن به ما، لازم است یک سرویس‌گیرنده IRC داشته باشید. از طریق آن به ‎irc.freenode.net‎ و ‎/join #bash ارتباط برقرار نمایید.

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

نکته دیگر، لطفاً قبل از ورود به ‎#bash‎ صفحه : XyProblem را ملاحظه نمایید.

نظرات 0 + ارسال نظر
ایمیل شما بعد از ثبت نمایش داده نخواهد شد