اموزش گام به گام تحت عامل لینوکس جهت کدنویسی برای انالیز کردن داده های اقلیمی
با سلام
تحت عامل لینوکس وکدنویسی درآن در دو سه سال اخیر به دلیل مطالعات شبیه سازی عناصر اقلیمی از طریق مدل های دینامیکی Reg cm استفاده فراوانی در مطالعات دینامیکی گرمایش جهانی داشته است تحت عامل لینوکس و برنامه نویسی در آن جهت انجام این پروژه ها امری ضروری است در این بخش به بیان آرایه های لینوکس می پردازیم
آرایه ها .
بدون تردید رشتهها پر استفادهترین نوع پارامترها میباشند. اما آنها همچنین، بد رفتارترین نوع پارامترها هستند. اهمیت دارد که به خاطر بسپاریم، یک رشته فقط یک عنصر را نگاه میدارد. به عنوان نمونه، گرفتن خروجی یک فرمان، و قرار دادن آن در پارامتر رشتهای بدین معنا میباشد که پارامتر فقط یک رشته از کاراکترها میباشد، صرفنظر از اینکه آیا آن رشته نام بیست فایل، بیست عدد، یا نام بیست نفر را نمایندگی میکند.
و همینطور است که همیشه وقتی اقلام چندگانه را در یک رشته منفرد قرار میدهید، باید این اقلام را به طریقی از یکدیگر جدا کنید. ما، به عنوان انسان معمولاً میتوانیم وقتی به یک رشته نگاه میکنیم نام فایلهای مختلف را کشف کنیم. ما فرض میکنیم که، شاید، هر سطر در یک رشته نام یک فایل را نشان میدهد، یا هر کلمه نام یک فایل را نمایندگی میکند. در حالیکه این پنداشت قابل درک است، همچنین به طور ذاتی معیوب است. هر نام فایل منفرد میتواند شامل هر کاراکتری باشد که ممکن است شما بخواهید برای جداکردن نام فایلها در یک رشته استفاده کنید. به این معنی که از نظر تکنیکی گفته نمیشود که نام اولین فایل در کجای یک رشته به پایان میرسد، زیراکاراکتری وجود ندارد که بتواند بگوید: «من به پایان نام فایل اشاره میکنم» چون آن کاراکتر خودش میتواند بخشی از نام فایل باشد.
غالباً، اشخاص این اشتباه را مرتکب میگردند:
# این در حالت کلی کار نمیکند
$ files=$(ls ~/*.jpg); cp $files /backups/
در حالیکه احتمالاً این میتواند ایده بهتری باشد( استفاده از آرایهها که در بخش بعد شرح داده میشوند):
# این در حالت کلی کار میکند
$ files=(~/*.jpg); cp “${files[@]}” /backups/
تلاش اولی در پشتیبانگیری از فایلهای دایرکتوری جاری معیوب است. ما خروجی دستور ls را در یک رشته به نام files قرار دادهایم و سپس از بسط پارامتر $files به صورت غیرنقلقولی برای بریدن آن رشته به شناسهها(بر مبنای تفکیک کلمه) استفاده کردهایم. به طوری که قبلاً اشاره شد، تفکیک کلمه و شناسه، یک رشته را از جایی که فضای سفید وجود دارد به قطعاتی برش میدهد. استناد به بسط فوق به این معنی است که فرض کردهایم در نام فایلهای ما هیچ فضای سفیدی نیست، اگر باشد نام فایل به دو نیمه یا بیشتر بریده میشود. فرجام: نامساعد.
تنها روش مطمئن نشان دادن عناصر چندگانه رشته در Bash از طریق استفاده از آرایهها میباشد. آرایه نوعی متغیر است که رشتهها را با اعداد ترسیم میکند. این اساساً به معنای آن است که یک لیست شماره گذاری شده از رشتهها را نگهداری میکند. چون هر یک از این رشتهها یک هویت(عنصر) جداگانه است، میتواند بدون خطر هر کاراکتری، حتی فضای سفید را در خود داشته باشد.
برای بهترین نتیجه و کمترین دردسر، به خاطر بسپارید که اگر لیستی ازعناصر دارید، همیشه باید آنها را در یک آرایه قرار دهید.
بر خلاف برخی زبانهای برنامهنویسی، Bash لیستها، رکوردها و غیره را ارائه نکرده است. فقط آرایهها و آرایههای انجمنی( که در نگارش ۴ از Bash جدید است).
آرایهها: یک آرایه لیست شماره گذاری شده رشتهها است: رشتهها را با اعداد صحیح مرتبط میکند .
——————————————————————————–
ایجاد آرایهها
چند روش موجود است که میتوانید آرایهها را ایجاد نموده یا با دادهها پر کنید. یک روش صحیح منفرد وجود ندارد: روشی که شما نیاز خواهید داشت بستگی به آن دارد که دادهها کدامند و از کجا میآیند.
سادهترین راه برای ایجاد یک آرایه ساده با داده، استفاده از ترکیب =() میباشد:
$ names=(“Bob” “Peter” “$USER” “Big Bad John”)
این ترکیب دستوری(syntax) برای ایجاد آرایههایی با دادههای ایستا یا مجموعهای از پارامترهای رشتهای معلوم، عالی است، اما قابلیت انعطاف بسیار کمی برای افزودن مقادیر زیاد عناصر آرایه، در اختیار میگذارد. اگر انعطاف پذیری بیشتری میخواهید، میتوانید از شاخصهای صریح استفاده کنید:
$ names=([0]=”Bob” [1]=”Peter” [20]=”$USER” [21]=”Big Bad John”)
# or…
$ names[0]=”Bob”
توجه نمایید که بین شاخص ۱و ۲۰ در این مثال یک شکاف وجود دارد. یک آرایه باحفرههایی در آن آرایه پراکنده نامیده میشود. 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 داده شده به عناصر آرایه از جایی که . وجود دارد، نتیجه یک آرایه با عناصر، ۱۲۷ و ۰ و ۰ و ۱ است.
(دستور داخلی read و عملگر <<< به طورمفصلتری درفصل ورودی و خروجی. پوشش داده میشود)
میتوانستیم همین کار را با دستور find انجام بدهیم، در صورتیکه متغیر IFS را به کاراکتر سطر جدید تنظیم میکردیم. اما موقعی که شخصی فایلی دارای کاراکتر سطر جدید ایجاد نماید( به طور اتفاقی یا بدخواهانه)، اسکریپت ما کار نخواهد کرد.
بنابراین، آیا روشی برای دریافت لیستی از عناصر از یک برنامه خارجی ( مانند find) در یک آرایه Bash وجود دارد؟ به طور کلی، پاسخ بلی است، به شرط آنکه راه قابل اطمینانی برای جداسازی عناصر موجود باشد.
در یک حالت خاص از نام فایلها، پاسخ این مشکل، بایتهای تهی(NUL) است. یک بایت تهی، بایتی است که همه بیتهای آن صفر است: ۰۰۰۰۰۰۰۰٫ رشتههای Bash نمیتوانند شامل بایتهای تهی باشند، به عنوان یک محصول زبان برنامهنویسی "C" : در زبان C بایت تهی برای علامت گذاری انتهای رشته به کاررفته است. از این جهت Bash که به زبان C نوشته شده و از رشتههای بومی C استفاده میکند، این رفتار را به ارث میبرد.
یک جریان داده( مانند خروجی یک فرمان، یا یک فایل ) میتواند شامل بایت تهی باشد. جریانها مانند رشتهها هستند، با سه تفاوت عمده: آنها به صورت ترتیبی خوانده میشوند(به طور معمول نمیتوانید با پرش از روی آنها عبور کنید)، آنها یک سویه میباشند( شما میتوانید از آنها بخوانید یا در آنها بنویسید، اما به طور نوعی هر دو با هم میسر نیست )، و آنها میتوانند شامل بایتهای تهی باشند.
نه نامهای فایل میتوانند شامل بایت تهی باشند( چون آنها توسط یونیکس همانند رشتههای C تکمیل شدهاند )، و نه اکثریت وسیع اقلام قابل خواندن برای انسان که شاید ما بخواهیم در یک اسکریپت ذخیره کنیم(ازقبیل نام افراد، آدرسهای IP، و غیره ). این موضوع NUL را یک نامزد عالی برای جداسازی عناصر در یک جریان ، میسازد. به طور کلی اغلب، دستوری که میخواهید خروجی آن را بخوانید، یک گزینهای خواهد داشت، که خروجیاش را به صورت جدا شده با بایت تهی، به جای سطر جدید یا کاراکتر دیگری، ایجاد میکند.
دستور find ( در GNU و BSD, در هر حال) گزینه -print0 را دارد، که ما در این مثال استفاده خواهیم نمود:
files=()
while read -r -d $''; do
files+=("$REPLY")
done < <(find /foo -print0)
این یک روش مطمئن تفکیک خروجی یک فرمان به رشتهها میباشد. به طور قابل فهمی، ابتدا کمی به هم پیچیده و گیج کننده به نظر میرسد. لذا، بیایید کمی آن را باز کنیم:
سطر اول files=() یک آرایه خالی به نام files ایجاد میکند.
ما از یک حلقه while استفاده میکنیم که هر مرتبه یک دستور read را اجرا میکند. فرمان read از گزینه -d $'\ 0' استفاده میکند، به آن معنا که به جای خواندن یک سطر در هر دفعه(تا رسیدن به یک کاراکتر سطر جدید)، تا رسیدن به بایت NUL میخوانیم (). همچنین از گزینه -r برای جلوگیری از رفتار ویژه با کاراکتر\ استفاده میکند.
وقتی read مقداری از داده ها را میخواند و با یک بایت تهی مواجه میشود، بدنه حلقه while اجرا میگردد. ما آنچه را خواندهایم( که در متغیر REPLY قرار دارد) در آرایه قرار میدهیم.
برای انجام این کار، ما از ترکیب +=() استفاده میکنیم. این ترکیب دستوری یک یا چند عنصر را به انتهای آرایه ما اضافه میکند.
و سرانجام، ترکیب دستوری < <(..) که ترکیبی از یک تغیرمسیر فایل (<) و جایگزینی پردازش ({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” “${myarray[@]}” > myfile
یک نکته پایانی: شما میتوانید تعداد عناصر یک آرایه را با استفاده از ${#array[@]} به دست آورید.
$ array=(a b c)
$ echo ${#array[@]}
۳
——————————————————————————–
تکرار مفید:
همیشه بسط آرایهها را به طور صحیح نقلقولی کنید، درست همانطور که بسط پارامترهای معمولی را نقلقولی میکنید .
از ${#myarray[@]} برای بسط تمام عناصر آرایه استفاده کنید و ${#myarray[*]} را فقط موقعی که میخواهید همه عناصر آرایه را در یک رشته منفرد با یکدیگر متصل کنید، به کار ببرید.
——————————————————————————–
آرایههای انجمنی
تا همین اواخر، BASH فقط از اعداد( به طور دقیقتر، اعداد صحیح مثبت ) میتوانست برای شاخص آرایهها استفاده کند. به این معنی که نمیتوانستید یک رشته را با دیگری ترجمه یا ترسیم کنید . این به عنوان یک کمبود احساس میشد. اشخاصی به منظور آدرسدهی به یک موضوع، سوءمصرف از متغیرهای غیرمستقیم را آغاز کردند .
پس از انتشار BASHنگارش ۴، دیگر بهانهای برای استفاده از متغیر غیر مستقیم( یا بدتر از آن، 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]}”