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

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

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

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

مستعارها

ادامه یادداشت قبل


مستعارها

مستعارها در نگاه اول ظاهراً مشابه توابع هستند، اما در بررسی عمیق‌تر، آنها رفتار کاملا متفاوتی دارند.

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

اساساً مستعارها، میانبرهایی برای استفاده درفایلهای ‎.bashrc‎ جهت آسانتر نمودن روند کارهای شما می‌باشند. آنها به طور معمول چنین به نظر می‌آیند:

    $ alias ls='ls --color=auto'

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

    $ ls /tmp

BASH چنان عمل می‌کند، که گویی تایپ نموده‌اید

    $ ls --color=auto /tmp

اگر خواسته باشید این توانایی را در یک تابع ایجاد کنید، به این شکل خواهد شد:

    $ unalias ls
    $ ls() { command ls --color=auto "$@"; }

همانند یک گروه دستور، اگر بخواهیم تمام آن را در یک سطر بنویسیم، لازم است یک سمی‌کالن (;) قبل از بستن تابع با ‎ }‎ به کار ببریم. دستور داخلی ویژه command به تابع ما می‌گوید خودش را به طور بازگشتی فراخوانی نکند، به جای آن می‌خواهیم آن فرمان ls را فراخوانی کند، که در صورت عدم وجود تابع همنام خودش آن را احضار می‌کرد.

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

انهدام ساختارها

برای از بین بردن یک تابع یا متغیراز محیط جاری پوسته خود، دستور unset را به کار ببرید.

    $ unset myfunction

برای از بین بردن مستعار، فرمان unalias را به کار ببرید.

    $ unalias rm



پایان فصل هشتم

توابع

ادامه یادداشت قبل


توابع

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

    $ sum() {
    >   echo "$1 + $2 = $(($1 + $2))"
    > }

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

    $ sum 1 4
    1 + 4 = 5

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

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

   1 #!/bin/bash
   2 sum() {
   3         echo "$1 + $2 = $(($1 + $2))"
   4 }
   5 sum "$1" "$2"

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

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

این یک مثال کمتر فاقد کیفیت:

   1 #!/bin/bash
   2 open() {
   3     case "$1" in
   4         *.mp3|*.ogg|*.wav|*.flac|*.wma) xmms "$1";;
   5         *.jpg|*.gif|*.png|*.bmp)        display "$1";;
   6         *.avi|*.mpg|*.mp4|*.wmv)        mplayer "$1";;
   7     esac
   8 }
   9 for file; do
  10     open "$file"
  11 done

در اینجا تابعی به نام open تعریف نموده‌ایم. این تابع قطعه‌ای کُد است که یک شناسه منفرد را دریافت می‌کند، و بر اساس الگوی آن شناسه، برنامه xmms یا display یا mplayer را با آن شناسه اجرا خواهد نمود. سپس، یک حلقه for با تمام پارامترهای مکانی اسکریپت تکرار می‌شود. (به خاطر داشته باشید که ‎ for file‎ معادل ‎for file in "$@"‎ می‌باشد و هر یک از آنها تکرار روی مجموعه کامل پارامترهای موقعیتی را انجام می‌دهند.) حلقه for برای هر یک از پارامترها، تابع open را فراخوانی می‌کند.

به طوری که شاید ملاحظه نموده باشید، پارامترهای تابع غیر از پارامترهای اسکریپت هستند.

همچنین، تابع ممکن است متغیرهای محلی داشته باشد، که با دستور داخلی local یا declare تعریف شده باشند. این امر شما را قادر می‌سازد بدون احتمال رونویسی متغیرهای مهم، توابع را فراخوانی کنید. برای نمونه:

    count() {
        local i
        for ((i=1; i<=$1; i++)); do echo $i; done
        echo 'Ah, ah, ah!'
    }
    for ((i=1; i<=3; i++)); do count $i; done

متغیر iمحلی به طور متمایز از متغیر i خارج از تابع اسکریپت، ذخیره می‌گردد. این باعث می‌شود هر یک از دو حلقه،بدون تداخل با شمارش‌گر دیگری عمل کند.

توابع می‌توانند خودشان را نیز به طور بازگشتی فراخوانی نمایند، اما در اینجا به آن نمی‌پردازیم. شاید بعداً!



ادامه دارد...

ارزیابی محاسباتی

ادامه یادداشت قبل


ارزیابی محاسباتی

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

اولین روش دستور let می‌باشد:

    $ unset a; a=4+5
    $ echo $a
    4+5
    $ let a=4+5
    $ echo $a
    9

اگر عبارت را نقل‌قولی کنید، می‌توانید از فاصله‌ها، پرانتزها و غیره استفاده کنید:

   $ let a='(5+2)*3'

برای لیست کامل عملگرهای معتبر، دستور help let یا مستندات را ببینید.

بعد دستور مرکب ارزیابی محاسباتی حقیقی است:

    $ ((a=(5+2)*3))

این معادل دستور let می‌باشد، اما می‌توانیم آن را به عنوان یک فرمان نیز به کار ببریم، به عنوان مثال در یک جمله if به این صورت:

    $ if (($a == 21)); then echo 'Blackjack!'; fi

عملگرهایی مانند == و < , > و غیره موجب انجام یک مقایسه در درون ارزیابی محاسباتی می‌شوند. اگر مقایسه صحیح باشد( به طور نمونه 10 > 2 در محاسبه صحیح است -- اما در رشته‌ها خیر!) ، بعد دستور مرکب با کد وضعیت 0 خارج می‌شود. اگر مقایسه غلط باشد با کد وضعیت 1 خارج می‌گردد. این امر آن را برای انجام بررسی‌ها در یک اسکریپت سودمند می‌سازد.

هرچند اگر یک دستور مرکب نباشد، یک ترکیب دستوری معتبر جایگزینی حسابی (یا یک عبارت حسابی) نیز می‌باشد:

   $ echo "There are $(($rows * $columns)) cells"

بخش درونی ‎ $((...))‎ یک مفهوم محاسباتی است، درست مانند ‎((...))، این به معنای آنست که به جای دستکاری رشته‌ها(الحاق row$، فاصله، ستاره، فاصله، و columns$)، می‌خواهیم محاسبه انجام بدهیم(عملیات چندگانه). ترکیب ‎$((...))‎ همچنین قابل حمل به شل POSIX می‌باشد، در حالیکه ‎((...)) نیست.

خوانندگانی که با زبان برنامه‌نویسی C آشنا هستند، ممکن است مایل باشند بدانند که ‎((...)) ویژگی‌های C-شکل بسیاری دارد. از میان آنها یکی عملگر سه‌گانه است:

   $ ((abs = (a >= 0) ? a : -a))

و یکی هم استفاده از مقدار صحیح به عنوان مقدار درست:

   $ if ((flag)); then echo "uh oh, our flag is up"; fi

توجه نمایید که ما متغیرها را در داخل ‎((...))‎ بدون پیشوند کردن علامت $ به آنها، استفاده کرده‌ایم. این یک ترکیب خاص کوتاه شده است که BASH در درون ارزیابی محاسباتی و عبارتهای محاسباتی اجازه می‌دهد. همچنین می‌توانیم آنرا درون یک ‎$((...))‎ در BASH به کار ببریم، اما در شل POSIX خیر.

یک مطلب نهایی وجود دارد ، که باید در باره ‎ ((flag))‎ متذکر شویم. چون درون عبارت ‎((...))‎ ‏ قالب C-مانند دارد، یک متغیر( یا عبارت) که صفر ارزیابی ‌گردد، به واسطه مفاهیم ارزیابی محاسباتی به عنوان false در نظر گرفته می‌شود. سپس، چون ارزیابی، غلط است، با کد وضعیت 1 خارج می‌شود. علاوه بر این، اگر عبارت درون ‎((...))غیر صفر باشد، به عنوان صحیح در نظر گرفته می‌شود، و نظر به اینکه، ارزیابی صحیح است با کد وضعیت صفر خارج خواهد شد. این امر به طور بالقوه بسیار گیج‌کننده است، حتی برای اهل فن، به طوری که وقت زیادی برای پرداختن به آن از شما خواهد گرفت. با وجود این، موقعی که مسائل به طریقی که برای آنها در نظر گرفته شده‌اند به کار بروند، در نهایت قابل درک خواهند بود:

    $ flag=0      # no error
    $ while read line; do
    >   if [[ $line = *err* ]]; then flag=1; fi
    > done < inputfile
    $ if ((flag)); then echo "oh no"; fi



ادامه دارد...

دستورات مرکب

فصل هشتم


دستورات مرکب

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

BASH ساختارهایی دارد که دستورات مرکب نامیده می‌شوند، عبارتی چندپهلو، که با مفاهیم متفاوتی مرتبط می‌باشد. ما قبلاً بعضی از دستورات مرکب ارائه شده در BASH را دیده‌ایم -- جمله‌های if و حلقه‌های for و حلقه‌های while و کلمه‌کلیدیهای ‎ [[‎ و case و select. دوباره آن مطالب را در اینجا تکرار نخواهیم نمود. به جای آن، دستورات مرکب دیگری را که هنوز ندیده‌ایم، بررسی خواهیم کرد: پوسته‌های فرعی، گروه‌بندی دستورات، و ارزیابی محاسباتی.

علاوه براین، به توابع و مستعارها که دستورات مرکب نیستند، اما به روش مشابهی عمل می‌کنند، نیز خواهیم پرداخت.



پوسته های فرعی

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

    $ (cd /tmp || exit 1; date > timestamp)
    $ pwd
    /home/lhunath

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

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



گروه بندی دستورات

قبلاً با این موضوع در گروه بندی جملات برخورد کرده‌ایم، با وجود آن در ضمن این فصل تکرار می‌شود.

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

دستورات گروهی می‌توانند برای اجرای دستورات چندگانه به کار رفته و یک تغییر مسیر منفردِ مؤثر بر تمام آنها داشته باشند:

    $ { echo "Starting at $(date)"; rsync -av . /backup; echo "Finishing at $(date)"; } > backup.log 2>&1

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

گروه‌های دستورات همچنین برای کوتاه کردن وظایف معین متداول، سودمند می‌باشند:

    $ [[ -f $CONFIGFILE ]] || { echo "Config file $CONFIGFILE not found" >&2; exit 1; }

این را با روایت رسمی آن مقایسه کنید:

    $ if [[ ! -f $CONFIGFILE ]]; then
    > echo "Config file $CONFIGFILE not found" >&2
    > exit 1
    > fi

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

گروه دستورات، همچنین برای تنظیم متغیرها در حالت‌های غیر معمول، به کار می‌رود:

    $ echo "cat
    > mouse
    > dog" > inputfile
    $ { read a; read b; read c; } < inputfile
    $ echo "$b"
    mouse

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

    $ read a < inputfile
    $ read b < inputfile
    $ read c < inputfile
    $ echo "$b"
    cat

ابداً آنچه ما می‌خواستیم، نیست!

اگر آنگونه که ما در اینجا نشان دادیم، گروه دستورات در یک سطر باشد، سپس باید یک سمی‌کالن قبل از بستن گروه با ‎ }‎ قرار داده شود، در غیر اینصورت، BASH گمان خواهد نمود ‎}‎ یک شناسه برای آخرین دستور گروه است. اگر گروه دستورات در چندین سطر گسترده شده باشند، آنوقت سمی‌کالن می‌تواند با کاراکتر سطر جدید جایگزین شود:

    $ {
    >  echo "Starting at $(date)"
    >  rsync -av . /backup
    >  echo "Finishing at $(date)"
    > } > backup.log 2>&1



ادامه دارد...

سایر عملگرها

ادامه یادداشت قبل


6. عملگرهای متفرقه

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

6.1. جایگزینی پردازش

پسرعموی لوله، عملگر جایگزینی پردازش است، که به دو شکل ظاهر می‌گردد: ‎<()‎ و ‎>()‎. این روش مناسبی برای به کار بردن لوله‌های با نام، بدون لزوم ایجاد فایلهای موقتی می‌باشد. هنگامی که تصور می‌کنید برای انجام مواردی نیاز به فایل موقتی دارید، ممکن است جایگزینی پردازش روش بهتری برای انجام آنها باشد.

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

در اینجا چگونگی انجام آن را در عمل می‌بینیم: موقعیتی را در نظر بگیرید که می‌خواهید تفاوت میان خروجی دو دستور را ببینید. به طور معمول، باید دو خروجی را در دو فایل قرار بدهید و با برنامه diff آنها را مقایسه کنید:

    $ head -n 1 .dictionary > file1
    $ tail -n 1 .dictionary > file2
    $ diff -y file1 file2
    Aachen                                                  | zymurgy
    $ rm file1 file2

می‌توانیم با به کاربردن عملگر جایگزینی پردازش، تمام آن را در یک سطر انجام بدهیم، و نیازی به پاکسازی دستی هم نیست:

    $ diff -y <(head -n 1 .dictionary) <(tail -n 1 .dictionary)
    Aachen                                                  | zymurgy

قسمت ‎ <(..)‎ با فایل FIFO موقتی ایجاد شده توسط bash، تعویض می‌گردد، بنابراین diff در حقیقت، چیزی به این شکل را می‌بیند:

    $ diff -y /dev/fd/63 /dev/fd/62

در اینجا می‌بینیم که وقتی ما از جایگزینی پردازش استفاده می‌کنیم، bash چگونه diff را اجرا می‌کند. دستورهای head و tail را اجرا می‌کند، خروجی‌های آنها را به ترتیب به فایلهای ‎ /dev/fd/63‎ و ‎/dev/fd/62‎ تغییرمسیر می‌دهد. سپس فرمان diff را با قبول کردن آن نام فایلها در جایی که ما عملگرهای جایگزینی پردازش را قرار داده بودیم اجرا می‌کند.

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

    $ echo diff -y <(head -n 1 .dictionary) <(tail -n 1 .dictionary)
    diff -y /dev/fd/63 /dev/fd/62

عملگر ‎>(..)‎ بسیار همانند عملگر ‎ <(..)‎ می‌باشد، اما به جای تغییرمسیر خروجی فرمان به یک فایل، یک فایل را به ورودی فرمان تغییر مسیر می‌دهد. این برای مواردی به کار می‌رود که شما دستوری را اجرا می‌نمایید که در یک فایل می‌نویسد، اما شما می‌خواهید به جای آن در دستور دیگر بنویسد:

    $ tar -cf >(ssh host tar xf -) .


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

پایان فصل هفتم