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

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

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

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

محیط

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


2. محیط

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

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

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

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

متغیرهای محیط همچنین می‌توانند به طور بینهایت آسانی در حین کار تنظیم گردند( آسان‌تر از آنکه اگر همان اطلاعات در یک فایل ذخیره شده باشند). موقعی که در Bash دستوری را اجرا می‌کنید، گزینه‌ای دارید، برای تعیین یک تغییر موقتی محیط که فقط در طول مدت اجرای آن فرمان مؤثر است. این با قرار دادن عبارت VAR=value جلوی آن فرمان انجام می‌شود. در اینجا یک مثال آورده‌ایم:

    $ ls /tpm
    ls: no se puede acceder a /tpm: No existe el fichero o el directorio
    $ LANG=C ls /tpm
    ls: cannot access /tpm: No such file or directory

محیط موقتی LANG=C باعث نمی‌شود که منطقه کاربر برای مورد دیگری غیر از آن فرمانی که بعد از آن تایپ گردیده است، تغیر نماید.

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

    if [[ $DISPLAY ]]; then
        xterm -e top
    else
        top
    fi

این مثال، در صورتی که متغیر محیطی DISPLAY تنظیم شده باشد( و تهی نباشد )دستور ‎ xterm -e top را اجرا می‌کند، در غیر آن صورت، دستور top را اجرامی‌نماید.

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

    export MYVAR=something

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

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


  • تکرار مفید:
    در اسکریپت‌های خود از نامهای تماماً با حروف بزرگ برای متغیرها استفاده نکنید. برای پرهیز از تصادمات، حروف کوچک یا ترکیبی از کوچک و بزرگ به کار ببرید .



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

ورودی و خروجی

فصل هفتم


ورودی و خروجی


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

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

  • شناسه‌های خط فرمان(که در پارامترهای مکانی قرارگرفته‌اند)

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

خروجی به هر اطلاعاتی که برنامه شما ارائه می‌کند(یا می‌نویسد) اشاره می‌کند. خروجی یک اسکریپت Bash نیز می‌تواند به چندین محل مختلف برود:

  • فایلها
  • هر چیز دیگر که یک توصیف‌گر فایل می‌تواند به آن اشاره کند
  • شناسه‌های خط فرمانی برای سایر برنامه‌ها
  • متغیرهای محیطی که به سایر برنامه‌ها ارسال می‌شوند

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


1. شناسه‌های خط فرمان

برای بسیاری از اسکریپت‌ها، اولین(یا تنها) ورودی که به آن توجه می‌نماییم، شناسه‌هایی می‌باشند که اسکریپت در خط فرمان دریافت نموده است. به طوری که در فصل پارامترها دیدیم، تعدادی پارامتر ویژه معتبر برای هر اسکریپت وجود دارد که، محتوی این شناسه‌ها هستند. اینها پارامترهای مکانی نام دارند. این پارامترها یک آرایه خیلی ساده از رشته‌ها می‌باشند که با اعداد شاخص‌گذاری شده‌اند(در حقیقت، در شل POSIX ، تنها آرایه موجود در شل هستند). به اولین پارامتر مکانی با ‎$1‎ رجوع می‌شود، به دومین، با ‎$2‎، و به همین ترتیب، پس از نهمین پارامتر، باید از ابروها برای رجوع به آنها استفاده گردد: ‎${10}, ${11}‎، و غیره. اما در عمل، خیلی به ندرت ممکن است مجبور به استفاده از این ترکیب بشوید، به علت آنکه، روشهای بهتری برای کار با آنها به عنوان یک گروه، موجود است.

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

یک روش دیگر کارکردن با پارامترهای مکانی، دور انداختن هر یک پس از استفاده است. یک دستور داخلی ویژه‌ای به نام shift وجود دارد، که برای این منظور به کار می‌رود. موقعی که شما فرمان shift را صادر می‌کنید، اولین پارامتر مکانی (‎$1‎) از بین می‌رود. دومین پارامتر می‌شود ‎$1‎، سومی می‌شود ‎$2‎، و به همیت ترتیب تا پایان خط. بنابراین، اگر مایل باشید، می‌توانید حلقه‌ای بنویسید که استفاده از ‎$1‎ را چندین بار ادامه دهد.

در اسکریپت‌های حقیقی، ترکیبی از این تکنیک‌ها به کار می‌رود. یک حلقه برای پردازش ‎$1‎ مادامی‌که با یک علامت - شروع می‌شود آن را به عنوان گزینه تعبیر می‌کند و سپس وقتی همه گزینه‌ها به کنار رفتند، هر چیز باقیمانده (در ‎"$@"‎) نام فایلی است که می‌خواهیم پردازش کنیم.

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


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



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

آرایه های انجمنی

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


آرایه‌های انجمنی

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

پس از انتشار BASHنگارش 4، دیگر بهانه‌ای برای استفاده از متغیر غیر مستقیم( یا بدتر از آن، eval) برای این منظور نیست. اکنون شما می‌توانید آرایه‌های انجمنی خوش‌ساخت را به کار ببرید.

برای ایجاد یک آرایه انجمنی، باید آرایه به صورت( declare -A) تعریف شود. این برای هماهنگی با تعریف آرایه‌های استاندارد شاخص گذاری شده است. در اینجا چگونگی انجام آن، آمده است:

   $ declare -A fullNames
   $ fullNames=( ["lhunath"]="Maarten Billemont" ["greycat"]="Greg Wooledge" )
   $ echo "Current user is: $USER.  Full name: ${fullNames[$USER]}."
   Current user is: lhunath.  Full name: Maarten Billemont.

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

    $ for user in "${!fullNames[@]}"
    > do echo "User: $user, full name: ${fullNames[$user]}."; done
    User: lhunath, full name: Maarten Billemont.
    User: greycat, full name: Greg Wooledge.

در اینجا دو مورد یادآوری: اول، ترتیب بازیابی کلیدها از یک آرایه انجمنی، با کاربرد ترکیب دستوری ‎${!array[@]}‎ غیرقابل پیش‌بینی است، و لزوماً به همان ترتیب که شما اعضاء را اختصاص داده‌اید، یا هر نوع ذخیره مرتب دیگر نمی‌باشد.

دوم، وقتی از پارامترها به عنوان کلید آرایه انجمنی استفاده می‌کنید، نمی‌توانید از علامت $ صرف‌نظر کنید. با آرایه‌ها شاخص‌دار معمولی،قسمت [...] در حقیقت یک مفهوم محاسباتی است( در آنجا به راستی، می‌توانید بدون یک علامت‌گذاری صریح ‎$((...))‎ محاسبه انجام دهید(. در یک زمینه محاسباتی، یک نام به هیچ وجه نمی‌تواند عدد معتبری باشد، و بنابراین BASH فرض می‌کند، آن یک پارامتر است که شما می‌خواهید از محتوای آن استفاده کنید. این مورد در آرایه‌های انجمنی صدق نمی‌کند، چون در اینجا یک نام نیز به خوبی می‌تواند یک کلید معتبر آرایه انجمنی باشد.

اجازه دهید با مثال تشریح کنیم:

    $ indexedArray=( "one" "two" )
    $ declare -A associativeArray=( ["foo"]="bar" ["alpha"]="omega" )
    $ index=0 key="foo"
    $ echo "${indexedArray[$index]}"
    one
    $ echo "${indexedArray[index]}"
    one
    $ echo "${indexedArray[index + 1]}"
    two
    $ echo "${associativeArray[$key]}"
    bar
    $ echo "${associativeArray[key]}"
    $
    $ echo "${associativeArray[key + 1]}"
    $

به طوری که می‌توانید ملاحظه کنید، هم ‎ $index‎ و هم index به خوبی با آرایه‌های معمولی کار می‌کنند. هر دو، عدد 0 ارزیابی می‌شوند. حتی می‌توانید برای اضافه کردن 1 به آن و به دست آوردن مقدار دوم، با آن محاسبه کنید. آنچه که با آرایه‌های انجمنی نمی‌تواند انجام شود. در اینجا، لازم است ‎$key‎ به کار برود، آن دیگری کار نمی‌کند.


پایان فصل ششم


استفاده از آرایه‌ها

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


استفاده از آرایه‌ها

استفاده از مزیت عناصر آرایه‌ها به راستی آسان است. به علت آنکه یک آرایه وسیله مطمئن ذخیره است، ما به سادگی می‌توانیم یک حلقه for را برای تکرار روی عناصر آن، به کار ببریم:

 $ for file in "$>{myfiles[@]}"; do
 >     cp "$file" /backups/
 > done

به ترکیب دستوری استفاده شده برای بسط آرایه در اینجا توجه نمایید. ما شکل نقل‌قولی به کار برده‌ایم: ‎ "${myfiles[@]}"‎. سپس Bash این ترکیب را با هر عنصر منفرد در آرایه، تعویض می‌نماید، نقل‌قولی صحیح.

دو مثال زیر نتیجه یکسان دارند:

 $ names=("Bob" "Peter" "$USER" "Big Bad John")
 $ for name in "${names[@]}"; do echo "$name"; done

 $ for name in "Bob" "Peter" "$USER" "Big Bad John"; do echo "$name"; done

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

به خاطر داشته باشید، بسط ‎ ${arrayname[@]}‎ را به طور صحیح نقل‌قولی نمایید. در غیر اینصورت، تمام مزایای استفاده از آرایه را از دست می‌دهید: رها کردن شناسه‌های غیر نقل‌قولی، به معنی آنست که به Bash برای تفکیک آنها به قطعات و جداسازی دوباره آنها تأییدیه می‌دهید.

مثال فوق آرایه را در یک ساختار حلقه for بسط می‌دهد. اما می‌توانید آرایه را در هرجایی که بخواهیدعناصر آن را به عنوان شناسه قرار دهید، بسط بدهید، در یک فرمان cp :

 myfiles=(db.sql home.tbz2 etc.tbz2)
 cp "${myfiles[@]}" /backups/

این مثال، دستور cp را، با تعویض عبارت ‎ "${myfiles[@]}"‎ با همه نام فایل‌های موجود در آرایه myfiles اجرا می‌نماید، نقل‌قول شده صحیح. پس از انجام بسط، Bash به طور مؤثر دستور زیر را اجرا می‌کند:

 cp "db.sql" "home.tbz2" "etc.tbz2" /backups/

فرمان cp فایلها را به دایرکتوری /backups/ شما کپی خواهد نمود.

همچنین می‌توانیدعناصر منفرد آرایه را با ارجاع به شماره عضویت آنها(که index یا شاخص نام دارد)، بسط بدهید. به خاطر داشته باشید، که به طور پیش‌فرض،آرایه‌ها zero-based می‌باشند، یعنی شماره شاخص اولین عضو آنها صفر می‌باشد:

 $ echo "The first name is: ${names[0]}"
 $ echo "The second name is: ${names[1]}"

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

روش دیگری نیز برای بسط تمام عناصر آرایه وجود دارد، که به شکل ‎ "${arrayname[*]}"‎ می‌باشد. این شکل فقط برای تبدیل آرایه به یک رشته منفردکه تمام عناصر آرایه در آن باهم متصل گردیده‌اند، مفید می‌باشد. مقصود اصلی در این روش ارائه خروجی آرایه به اشخاص می‌باشد:

 $ names=("Bob" "Peter" "$USER" "Big Bad John")
 $ echo "Today's contestants are: ${names[*]}"
 Today's contestants are: Bob Peter lhunath Big Bad John

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

به خاطر داشته باشید، هنوز هم به دقت نقل‌قولی نمایید! اگر ‎ ${arrayname[*]}‎ را نقل‌قولی نکنید، یکبار دیگر تفکیک کلمه Bash موجب بریدن آن به تکه‌ها می‌گردد.

می‌توانید متغیر IFS را با ‎ ${arrayname[*]}‎ ترکیب کنید، که نشان بدهید از چه کاراکتری برای جدا کردن عناصر آرایه از یکدیگر، موقعی که آنها را در یک رشته منفرد ادغام می‌کنید، استفاده شود. برای مثال، وقتی می‌خواهید نامها با کاراکتر کاما از هم جدا شوند، به راحتی به صورت زیر انجام می‌گردد:

 $ names=("Bob" "Peter" "$USER" "Big Bad John")
 $ ( IFS=,; echo "Today's contestants are: ${names[*]}" )
 Today's contestants are: Bob,Peter,lhunath,Big Bad John

توجه نمایید که در این مثال، چگونه جمله‎ IFS=,; echo ...‎ را با قرار دادن بین ( و ) در یک Subshell یا پوسته فرعی اجرا نمودیم. چنین کردیم زیرا نمی‌خواهیم مقدار پیش‌فرض متغیر IFS در پوسته اصلی را تغییر بدهیم. موقعی که پوسته فرعی خارج می‌شود، متغیر IFS بازهم مقدار پیش‌فرض را دارد، و دیگر کاما نمی‌باشد. این اهمیت دارد، به دلیل آنکه متغیر IFS برای موارد بسیاری استفاده می‌شود، و تغییر مقدار آن به چیزی غیر از مقدار پیش‌فرض، رفتار غریبی را که انتظار آن را ندارید، موجب خواهد شد!

افسوس، بسط "${array[*]}" فقط کاراکتر اول از متغیر IFS را برای بهم پیوستن عناصر با یکدیگر به کار می‌گیرد. اگر در مثال قبل، می‌خواستیم نامها را با یک کاما و یک فاصله از یکدیگر جدا کنیم، می‌باید برخی تکنیک‌های دیگر را به کار می‌بردیم( به عنوان مثال، یک حلقه for ).

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

 $ names=("Bob" "Peter" "$USER" "Big Bad John")
 $ printf "%s\n" "${names[@]}"
 Bob
 Peter
 lhunath
 Big Bad John

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

 $ printf "%s\0" "${myarray[@]}" > myfile

یک نکته پایانی: شما می‌توانید تعداد عناصر یک آرایه را با استفاده از ‎ ${#array[@]}‎ به دست آورید.

 $ array=(a b c)
 $ echo ${#array[@]}
 3


  • تکرار مفید:
    همیشه بسط آرایه‌ها را به طور صحیح نقل‌قولی کنید، درست همانطور که بسط پارامترهای معمولی را نقل‌قولی می‌کنید .
    از ‎ ${#myarray[@]}‎ برای بسط تمام عناصر آرایه استفاده کنید و ‎ ${#myarray[*]}‎ را فقط موقعی که می‌خواهید همه عناصر آرایه را در یک رشته منفرد با یکدیگر متصل کنید، به کار ببرید.


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

ایجاد آرایه ها

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


ایجاد آرایه‌ها

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

ساده‌ترین راه برای ایجاد یک آرایه ساده با داده، استفاده از ترکیب ‎=()‎ می‌باشد:

    $ names=("Bob" "Peter" "$USER" "Big Bad John")

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

    $ names=([0]="Bob" [1]="Peter" [20]="$USER" [21]="Big Bad John")
    # or...
    $ names[0]="Bob"

توجه نمایید که بین شاخص 1و 20 در این مثال یک شکاف وجود دارد. یک آرایه باحفره‌هایی در آن آرایه پراکنده نامیده می‌شود. Bash این امر را اجازه می‌دهد واغلب می‌تواند کاملاً سودمند باشد.

اگر می‌خواهید یک آرایه را با نام فایل‌ها پر کنید، ممکن است احتمالاً بخواهید از Globs استفاده کنید:

    $ photos=(~/"My Photos"/*.jpg)

توجه نمایید که در اینجا بخش My Photos را نقل‌قول کرده‌ایم زیرا شامل یک فاصله است. اگر این کار را نمی‌کردیم، Bash آن را به صورت ‎ photos=('~/My'  'Photos/' *.jpg ) ‎  تفکیک می‌نمود، که به وضوح آنچه ما می‌خواهیم نبود. همچنین توجه نمایید که ما فقط بخش شامل فاصله را نقل‌قولی کردیم. به این دلیل چنین است که ما نمی‌توانیم ~ یا * را نقل‌قولی کنیم، اگر چنین کنیم، آنهاکاراکترهای لفطی می‌شوند و Bash دیگر با آنها همچون کاراکترهای خاص رفتار نمی‌کند.

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

    $ files=$(ls)    # BAD, BAD, BAD!
    $ files=($(ls))  # STILL BAD!

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

روش صحیح انجام آن این است:

    $ files=(*)      # Good!

این جمله یک آرایه به ما می‌دهد که در آن هر نام فایل یک عنصر جداگانه است. کامل!

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

گاهی اوقات می‌خواهیم یک آرایه از یک رشته یا خروجی یک فرمان تشکیل بدهیم. خروجی فرمانها رشته هستند: برای نمونه، اجرای یک فرمان find نام فایل‌ها را به شمار می‌آورد و آنها را با یک کاراکتر سطر جدید(قرار دادن هر نام فایل در یک سطر جداگانه) از هم جدا می‌کند. بنابراین برای تفکیک یک رشته بزرگ به داخل یک آرایه، لازم است به Bash بگوییم هر عضو کجا به انتها می‌رسد. (تذکر، این یک مثال بد است، چون نام فایل می‌تواند شامل یک سطر جدید باشد،بنابراین جدا کردن آنها با سطر جدید نمی‌تواند ایمن باشد! اما مثال زیر را نگاه کنید.)

آنچه برای شکستن یک رشته به کار می‌رود محتوای متغیر IFS می‌باشد:

    $ IFS=. read -a ip_elements ‎<<<‎ "127.0.0.1"

در اینجا از متغیر IFS با محتوای . برای بریدن آدرس IP داده شده به عناصر آرایه از جایی که . وجود دارد، نتیجه یک آرایه با عناصر، 127 و 0 و 0 و 1 است.

(دستور داخلی read و عملگر ‎<<<‎ به طورمفصل‌تری درفصل ورودی و خروجی. پوشش داده می‌شود)

می‌توانستیم همین کار را با دستور find انجام بدهیم، در صورتی‌که متغیر IFS را به کاراکتر سطر جدید تنظیم می‌کردیم. اما موقعی که شخصی فایلی دارای کاراکتر سطر جدید ایجاد نماید( به طور اتفاقی یا بدخواهانه)، اسکریپت ما کار نخواهد کرد.

بنابراین، آیا روشی برای دریافت لیستی از عناصر از یک برنامه خارجی ( مانند find) در یک آرایه Bash وجود دارد؟ به طور کلی، پاسخ بلی است، به شرط آنکه راه قابل اطمینانی برای جداسازی عناصر موجود باشد.

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

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

نه نام‌های فایل می‌توانند شامل بایت تهی باشند( چون آنها توسط یونیکس همانند رشته‌های C تکمیل شده‌اند )، و نه اکثریت وسیع اقلام قابل خواندن برای انسان که شاید ما بخواهیم در یک اسکریپت ذخیره کنیم(ازقبیل نام افراد، آدرس‌های IP، و غیره ). این موضوع NUL را یک نامزد عالی برای جداسازی عناصر در یک جریان ، می‌سازد. به طور کلی اغلب، دستوری که می‌خواهید خروجی آن را بخوانید، یک گزینه‌ای خواهد داشت، که خروجی‌اش را به صورت جدا شده با بایت تهی، به جای سطر جدید یا کاراکتر دیگری، ایجاد می‌کند.

دستور find ( در GNU و BSD, در هر حال) گزینه ‎ -print0‎ را دارد، که ما در این مثال استفاده خواهیم نمود:

    files=()
    while read -r -d $'\0'; do
        files+=("$REPLY")
    done < <(find /foo -print0)

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

سطر اول ‎files=()‎ یک آرایه خالی به نام files ایجاد می‌کند.

ما از یک حلقه while استفاده می‌کنیم که هر مرتبه یک دستور read را اجرا می‌کند. فرمان read از گزینه ‎ -d  $'\ 0'‎ استفاده می‌کند، به آن معنا که به جای خواندن یک سطر در هر دفعه(تا رسیدن به یک کاراکتر سطر جدید)، تا رسیدن به بایت NUL می‌خوانیم ‎(\0)‎. همچنین از گزینه ‎ -r‎ برای جلوگیری از رفتار ویژه با کاراکتر\ استفاده می‌کند.

وقتی read مقداری از داده ها را می‌خواند و با یک بایت تهی مواجه می‌شود، بدنه حلقه while اجرا می‌گردد. ما آنچه را خوانده‌ایم( که در متغیر REPLY قرار دارد) در آرایه قرار می‌دهیم.

برای انجام این کار، ما از ترکیب ‎+=()‎ استفاده می‌کنیم. این ترکیب دستوری یک یا چند عنصر را به انتهای آرایه ما اضافه می‌کند.

و سرانجام، ترکیب دستوری ‎< <(..)‎ که ترکیبی از یک تغیرمسیر فایل ‎ (<)‎ و جایگزینی پردازش ‎(<(..))‎ می‌باشد. در حال حاضر صرف نظر از جزئیات تکنیکی، به سادگی می‌گوییم این چگونگی ارسال خروجی فرمان find به درون حلقه while ما می‌باشد.

همان طور که قبلاً بیان گردید، فرمان find خود با یک گزینه ‎ -print0‎ به کار رفته، که به او بگوید،نام فایلهایی که می‌یابد را، با یک بایت تهی تفکیک کند.


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


  • در مستندات گنو: Arrays



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