آموزش‌های خط فرمانی

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

آموزش‌های خط فرمانی

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

پایان یک طرح

با ترجمه BashPitfalls (یاداشت قبلی) نزدیک به ۸۰ تا ۹۰ درصد محتوای  Greg's Wiki در باره ‌Bash را ترجمه کرده‌ام که تمام آن در وبلاگ اختصاص یافته به آن در  اینجا قرار گرفنه است و با این یادداشت طرح فوق از طرف من پایان یافته قلمداد می‌گردد. باشد که جویندگان آن را بیابند و مورد استفاده قرار بدهند و امیدوارم برایشان سودمند باشد.

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


اشتباهات رایج در برنامه‌نویسی Bash


این صفحه اشتباهات رایجی را که برنامه نویسان Bash مرتکب می‌گردند نشان می‌دهد. مثالهای ذیل هر یک به نوعی معیوب هستند:

for i in $(ls *.mp3)‎

یکی از رایج‌ترین اشتباهاتی که برنامه‌نویسان BASH مرتکب می‌شوند، نوشتن حلقه‌ای مانند این است:

  •  for i in $(ls *.mp3); do     # Wrong!
        some command $i            # Wrong!
     done
    
     for i in $(ls)                         # Wrong!
     for i in `ls`                             # Wrong!
    
     for i in $(find . -type f)     # Wrong!
     for i in `find . -type f`         # Wrong!
    
     files=($(find . -type f))     # Wrong!
     for i in ${files[@]}               # Wrong!
    

هرگز بدون نقل‌قول‌ها از یک جایگزینی فرمان -- از هر نوع! -- استفاده نکنید. در اینجا دو موضوع اصلی وجود دارد: استفاده از یک بسط غیر نقل‌قولی برای تفکیک خروجی به شناسه‌ها، و تجزیه خروجی ls -- برنامه‌ای که در هر صورت هرگز خروجی‌اش نباید تجزیه شود.

چرا؟ چون وقتی یک فایل در نام خود شامل فاصله باشد، این مورد ناموفق می‌شود. چرا؟ به علت آن که خروجی جایگزینی فرمانِ ‎ $(ls *.mp3)‎ دستخوش تفکیک کلمه می‌گردد. فرض کنیم ما در دایرکتوری جاری دارای فایلی به نام ‎01 - Don't Eat the Yellow Snow.mp3‎ باشیم، حلقه for روی هر کلمه حاصل شده از نام فایل تکرار می‌شود: ‎01, -, Don't, Eat‎... وغیره.

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

همچنین شما نمی‌توانید جایگزینی را با نقل‌قول دوگانه بپوشانید:

  •  for i in "$(ls *.mp3)"; do   # Wrong!

این باعث می‌گردد با کل خروجی فرمان ls به عنوان یک کلمه منفرد رفتار شود. به جای هر نوبت تکرار با هر نام فایل، حلقه فقط یک مرتبه با تمام نام فایلهای منگنه شده با یکدیگر اجرا خواهد شد.

علاوه بر این، استفاده از ls به طور قابل فهمی غیر ضروری است. این یک فرمان خارجی است که واقعاً برای انجام این کار لازم نیست. بنابراین، روش صحیح برای انجام آن، کدام است؟

  • for i in *.mp3; do # ... بهتر! و‎ ‎‎ some command "$i" # ... شماره دو را ببینید pitfall برای اطلاعات بیشتر done

اجازه بدهید Bash لیست نام فایلها را برای شما بسط بدهد. بسط، در معرض تفکیک کلمه قرار نخواهد گرفت. با هر نام فایل که با یک glob به شکل ‎*.mp3‎ منطبق می‌شود، به عنوان یک کلمه جداگانه رفتار می‌گردد، و حلقه برای هر نام فایل یکبار تکرار خواهد شد. (اگرنیاز دارید فایلها را به طور بازگشتی پردازش کنید، UsingFind را ببینید.)

سؤال: اگر هیچ نام فایل مطابق جانشین ‎ *.mp3‎ در دایرکتوری جاری نباشد چه می‌شود؟ آنوقت حلقه for یکبار با ‎ i="*.mp3"‎ اجرا می‌شود، که رفتار مورد انتظار نمی‌باشد! چاره کار، بررسی آن است که آیا یک فایل منطبق وجود دارد:

# POSIX
for i in *.mp3; do
    [ -e "$i" ] || continue
    some command "$i"
done

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

به نقل‌قول‌های اطراف ‎$i‎ در بدنه حلقه توجه نمایید. این مورد به دومین pitfall ما منجر می‌شود:

ادامه مطلب

ترجمه کامل این صفحه در قالب pdf یا به صورت یک فایل html نیز قابل دریافت است.


کمبودهای Bash


موارد معینی وجود دارد که در آن موارد BASH خیلی مناسب نیست. برخی وظایف هستند که شما نباید در bash انجام بدهید، مگر اینکه واقعاً، به درستی مجبور باشید. اغلب بهتر است برای انجام اکثر آن وظایف زبان دیگری را انتخاب کنید.

  1. سرعت: آیا ما واقعاً باید آن را بگوییم؟ Bash کُند است. اگر سرعت یک وجه‌التزامِ با اهمیت است، آنوقت ممکن است Bash بهترین انتخاب نباشد.

  2. حساب ممیز شناور: Bash فقط حساب اعداد صحیح دارد. از ‎bc(1)‎ یا در صورتیکه به محاسبه ممیز شناور نیاز دارید از AWK استفاده کنید.

  3. ساختمان داده‌ها: Bash نه رکوردهای سَبکِ پاسکال(ساخت‌های C-شکل) دارد، نه اشاره‌گر دارد. هر تلاشی برای ایجاد ساختمان داده‌های پیشرفته(پشته‌ها، صف‌ها، لیست‌های پیوندی، درختهای دو دویی...) باید با ترفندهای فوق‌العاده قدیمی انجام بشود.

  4. مدیریت پردازش برازنده: Bash مورد قابل قیاس با ‎select(2)‎ یا ‎poll(2)‎ ندارد. راهی برای وارد شدن به یک event loop وجود ندارد. اگر به نمونه فعال شونده در نتیجه رویدادها احتیاج دارید از سایر زبانهای برنامه‌نویسی استفاده نمایید. اکثر زبانهای شئ‌گرا برای انجام این وظایف مناسب‌تر هستند.

  5. تجزیه XML و HTML: (یا مشابه). برای انجام آن به طور صحیح، به ابزارها یا کتابخانه‌های خارجی نیاز دارید. از ‏xslt, tidy, xmlstarlet, پرل، یا برخی ابزارهای مناسب دیگر استفاده کنید.

  6. داده‌های باینری: Bash روشی برای ذخیره بایت تهی در یک متغیر ندارد، بنابراین داده‌های باینری یا باید رمزنگاری(و رمزگشایی) گردند، یا در یک فایل نگهداری شوند. همچنین نمی‌توانید بایت تهی را به عنوان یک شناسه به برنامه عبور بدهید، زیرا کرنل از رشته‌های C برای آنها استفاده می‌کند. تجزیه داده‌های باینری از یک فایل نیز مشکل ناچیزی نیست. به جای آن پرل یا C را امتحان کنید.

  7. پرس و جوی بانک اطلاعاتی: موقع بازیابی یک چندتایی(1 tuple) از یک بانک اطلاعات رابطه‌ای، روشی برای Bash وجود ندارد که بفهمد کجا یک عضو tuple خاتمه می‌یابد و بعدی شروع می‌شود. به طور کلی، Bash برای هیچ نوع بازیابی داده‌ای که در یک عمل منفرد، مقادیر چندتایی داده‌ها را استخراج نماید، مناسب نیست، مگر آنکه به طور آشکار یک جداکننده معین بین فیلدها وجود داشته باشد. برای پرس و جوی بانک اطلاعاتی(SQL یا غیر از آن)، زبان دیگری را که از رابط پرس و جوی بانک اطلاعاتی پشتیبانی نماید، برگزینید.

  8. تعیین نوع متغیر: Bash مانند اکثر زبانهای اسکریپت‌نویسی، به راستی از انواع متغیر نیرومند پشتیبانی نمی‌کند. متغیرها به سستی به عنوان ساده یا آرایه(بعلاوه آرایه‌های انجمنی در ‎bash 4‎)، با پشتیبانی جزئی برای عدد صحیح طبقه‌بندی می‌شوند. اما واقعاً، همه چیز یک رشته است.

  9. اعطای مجوزها: مجاز نمودن یک اسکریپت bash برای اجرا شدن به عنوان کاربر ارشد، می‌تواند دشوار باشد. در زبانهایی مانند C، پرل، و پایتون، شما در یک نقطه معین به آسانی می‌توانید امتیازات را اعطا کنید. در bash این یک مورد ترفندگونه است، زیرا در حالیکه می‌توانید su یا sudo را اجرا کنید، اینها برنامه‌های خارجی هستند -- تمام محیط اجرایی‌تان را از دست می‌دهید.

  10. Try/catch: برخی زبانهای برنامه‌نویسی به شما اجازه می‌دهند فرمانی را در یک بلوک ‎try ... catch‎ بسته‌بندی کنید. این امر فرمان را در نوعی "sandbox" تفسیر می‌کند، جایی که در آن خطاهایی که به طور معمول موجب یک انصراف می‌شدند، گرفتار(caught) می‌شوند، و ماشه اجرای کدِ نوعی مدیریت خطا کشیده می‌شود. Bash چیزی قابل قیاس با این مورد ندارد. هر کدِ bash که شما اجرا می‌کنید، کُد واقعی است.

  11. مدیریت استثناء: بسیاری از زبانهای برنامه‌نویسی دارای مفهومی از یک «استثناء» هستند، در اصل یک رویدادی که موقع وقوع انواع معینی از خطاها، محیطِ حینِ اجرا تولید می‌کند. Bash اینها را ندارد. Bash برای مدیریت خطا مدل C را به کار می‌برد: اجرای آن را به عهده شما می‌گذارد. شما نیاز دارید که نتیجه هر فرمان حساس در اسکریپت خود را کنترل نمایید. ( و خیر، ‎set -e‎ نیز پاسخ مناسب نیست.)

  12. توابع: توابع Bash دارای چند موضوع است:

    • برگشت کمیت‌ها: توابع Bash چیزی برگشت نمی‌دهند ، آنها فقط جریانهای خروجی فراهم می‌کنند. هر روش قابل‌قبولِ به چنگ آوردن آن خروجی و یا تخصیص آن به یک متغیر یا عبور دادن آن به عنوان شناسه مستلزم یک پوسته فرعی است، که تمام تخصیص‌ها را برای حوزه بیرونی نقض می‌کند. برای شگردهای بازیابی نتایج یک تابع، پرسش و پاسخ شماره ۸۴ را ببینید، اما توجه نمایید که تمام آنها ترفند هستند و محدودیت‌های گوناگونی دارند.

    • قابلیت استفاده مجدد: شما نمی‌توانید شناسه‌ها را «با ارجاع» عبور بدهید، یا حداقل تا ‎Bash 4.3‎ نمی‌توانستید (و حتی آنجا هم مکانیسم ‎declare -n‎ دارای نواقص امنیتی جدی است). راهی برای گفتن نام متغیری به تابع که می‌خواهید تابع خروجی‌اش را در آن قرار بدهد وجود ندارد. حتی کار با آرایه‌ها بدتر از آن است -- شما نمی‌توانید نام یک آرایه را به یک تابع عبور داده و بگذارید تابع آن را به کار ببرد. بهترین کاری که می‌توانید انجام بدهید، به طور نمونه، عبور دادن هر عضو آرایه به عنوان یک شناسه جداگانه است. این به معنای آن است که کتابخانه‌های مجتمعِ توابعِ قابل استفادهِ مجدد عملی نیست، مگر به واسطه انجام عملیات آکروباتیک eval.

    • حوزه: Bash دارای یک سیستم ساده حوزه محلی است که تقریباً به حوزه پویا شباهت دارد(به عنوان مثال جاوا اسکریپت، elisp). توابع مناطق فراخواننده‌اشان را می‌بینند( مانند کلمه کلیدی "nonlocal" پایتون)، اما نمی‌توانند پارامتر مکانی فراخواننده را دستیابی کنند (مگر از طریق BASH_ARGV در صورتی که extdebug فعال بشود). معاف شدن توابع قابل استفاده مجدد از تداخل‌های namespace نمی‌تواند تضمین شود مگر برای آنکه برخوردها به قدر کفایت غیر محتمل گردند، شما به قواعد نامگذاری عجیب متوسل شوید. این مطلب مخصوصاً در صورتی یک مسئله است که اجرای توابعی مورد انتظار باشد که از قاب n-3 بر روی نام متغیرهایی عمل کند که توسط تابع قابل استفاده مجدد شما در قاب n-2 رونویسی گردیده است. Ksh93 می‌تواند از قواعد حوزه لغوی بیشتر متداول، به وسیله تعریف توابع با ترکیب دستوری ‎"function name { ... }"‎ استفاده نماید (Bash نمی‌تواند، امابه هر حال از این ترکیب پشتیبانی می‌کند).

    • Closureها 2: در Bash، توابع خودشان همیشه سراسری هستند(دارای حوزه فایل)، بنابراین closure نیستند. تعاریف تابع ممکن است تو در تو باشد، اما اینها closure نیستند، هرچند خیلی زیاد مشابه آنها هستند. توابع «گذر پذیر» نیستند(درجه اول)، و هیچ تابع بدون نامی وجود ندارد (lambdas). در حقیقت، چیزی قابل عبور نیست، مخصوصاً آرایه‌ها. Bash معناشناسی سخت‌گیرانه call-by-value را به کار می‌برد .

    • پیچیدگی‌های بسیاری بیشتری را شامل می‌گردد، پوسته‌های فرعی، توابع صادر شده، «function collapsing» (توابعی که خودشان یا سایر توابع را تعریف یا بازتعریف می‌کنند)، traps (و میراث آنها)، و روشی که توابع با ورودی خروجی استاندارد فعل و انفعال می‌کنند. نوآموز را برای عدم درک همه این موارد نیش نزنید. به طور کلی توابع پوسته f***ed(در وضعیت نامناسب) هستند.

  13. مرتب سازی: Bash نمی‌تواند گروههای داده‌ها را مرتب نماید. اگر نیاز دارید یک آرایه را مرتب کنید، یا می‌توانید الگوریتم مرتب‌سازی خودتان را در bash خالص بنویسید، یا می‌توانید مجموعه داده‌ها را مسلسل نموده آن را به sort لوله‌کشی کنید و سپس آنرا تجزیه و برگردانید. هر یک از این روشها آزاردهنده است، مخصوصاً اگر برنامه sort شما گزینه ‎-z‎ نداشته باشد.

برتر از تمام اینها، BASH برای برنامه‌های بزرگ مطلوب نیست. اگر برنامه شما می‌خواهد پاسخگوی وظایف زیادی، بویژه به طور فعل و انفعالی باشد، آنوقت شاید لازم باشد مفسر دیگری را در نظر بگیرید یا به طور کلی به سمت یک زبان قابل ترجمه بروید. اسکریپت‌های بزرگ Bash خیلی به سرعت رنجش‌آور می‌شوند زیرا Bash در بسیاری از مواردی که مفسرهای دیگر در آن سریع هستند، کند است. با کاربرد معدود روشهای غیر از توابع به منظور ایجاد ساختار در کدتان، قطعات بزرگ کدِ Bash به سرعت غیر شفاف می‌گردند. اسکریپت‌های Bash تقریباً غیر قابل تست هستند. حتی آن برنامه‌نویسان bash که بیشترین وسواس را در استعمال صحیح کلمات دارند ( و زیاد هم نیستند ) کدی می‌نویسند که وقتی تمام آن جمع می‌شود، برای نگهداری مشکل می‌گردد. Bash برای ایمنی کدی که اجازه می‌دهد باگهای آب زیر کاه واقعاً به سادگی بدون اطلاع یا هشدار در آن رسوخ کنند، تقریباً راه‌کاری ندارد. و موقعی که امور اشتباه می‌شوند(و امور اشتباه خواهند شد)، به راستی اشکالزدایی اسکریپت‌های بزرگ خیلی دشوار است.

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


CategoryShell

کمبودهای Bash (آخرین ویرایش ‎2013-06-28 20:51:14‎ توسط GreyCat)


  1. مترجم: tuple یک مورد داده‌‌ای متشکل از دو عنصر یا بیشتر است، مجموعه‌ای از مقادیر مرتبط با هم که در سیستم مدیریت رابطه‌ای در یک سطر ذخیره می‌شود، یا مجموعه‌ای از صفات مشخصه که نشانگر یک موجودیت متناظر با یک سطر از یک جدول هستند (بازگشت)

  2. مترجم: closure یک تکنیک برنامه‌نویسی است که اجازه می‌دهد متغیرهای خارج از حوزه یک تابع دستیابی گردند، در بسیاری از موقعیت‌ها یک closure هنگامی ایجاد می‌شود که تابعی داخل تابع دیگر تعریف می‌گردد، و به تابع داخلی اجازه می‌دهد که به متغیرهای تابع خارجی دسترسی داشته باشد. (بازگشت)


bashism


چگونه اسکریپت‌های bash بسازیم که در dash کار کنند

این صفحه کوششی است برای لیست کردن برخی از رایج‌ترین bashismها، یعنی ویژگی‌هایی که توسط POSIX تعریف نشده‌اند (در dash، یا ‎/bin/sh‎ متداول، کار نخواهند کرد). احتمالاً شامل تمام جزئیات نخواهد بود. همچنین توجه نمایید که ما در باره "bashism" صحبت می‌کنیم، زیرا این ویکی به طور عمده متمرکز بر bash می‌باشد، اما چون اکثر ویژگی‌های اسکریپت‌نویسی Bash از ksh مشتق گردیده است تعدادی(تقریباً همه) از این ملحقات احتمالاً با تفاوت‌هایی در جزئیات، حداقل در برخی پوسته‌های دیگر مانند ksh یا zsh نیز کار می‌کنند. POSIX تعداد بسیار کمتری از آنها را لازم دانسته است.

ادامه مطلب