چگونه میتوانم از متغیرهای آرایهای استفاده کنم؟
این پاسخ فرض بر این دارد که شما در درجه اول، دارای درک اساسی از اینکه آرایهها چه میباشند، هستید. اگر شما در این نوع برنامهنویسی، تازه وارد هستید،شاید بهتر باشد با توضیح راهنما شروع نمایید. صفحه کامل و دارای جزئیات بیشتری است.
محتویات
1. مقدمه
آرایههای یک بُعدی با شاخص عدد صحیح در پوسته Bash، Zsh، واکثر پوستههای متنوع Korn از جمله AT&T ksh88 یا مابعد از آن، mksh، و pdksh پیادهسازی گردیدهاند. آرایهها توسط POSIX تعریف نشدهاند و در میراث آن یا پوستههای حداقلی، مانند شل Bourne Dash در دسترس نمیباشند. پوستههای سازگار با POSIX که معمولاً در اصول اساسی آرایهها توافق دارند، ولی در جزئیات تفاوتهای عمدهای دارند. کاربران پیشرفته پوستههای متعدد، باید با تحقیق نمودن ویژگیها، اطمینان حاصل نمایند. Ksh93، Zsh، و Bash نگارش 4.0 علاوه براین دارای آرایههای انجمنی نیز میباشند. این مقاله بر آرایههای شاخصدار تمرکز مینماید، چون آنها رایجترین و سودمندترین نوع هستند.
این هم کاربرد نوعیِ نمایان کننده الگو، در آرایهای به نام host:
# Bash در پوسته # .به آرایهای با شاخصهای ترتیبی goofy و mickey, minnie اختصاص مقادیر host=(mickey minnie goofy) # \"host\"تکرار روی شاخص های for idx in \"${!host[@]}\"; do printf \'Host number %d is %s\' \"$idx\" \"${host[idx]}\" done
\"${!host[@]}\" به شاخص های آرایه host بسط مییابد، هر یک به صورت یک شناسه جداگانه. (در ادامه وارد جزئیات ترکیب دستوری--syntax-- خواهیم شد.)
آرایههای شاخصدار پراکنده هستند، و عناصر آنها میتوانند به طور نامرتب حذف و اضافه بشوند.
# Bash و ksh در پوسته # .ترکیب دستوری ساده arr[0]=0 arr[2]=2 arr[1]=1 arr[42]=\'what was the question?\' # (مترجم: با شروع از صفر، میشود سومین عضو ) \"arr\" حذف عضو شماره دو unset -v \'arr[2]\' # الحاق عناصر به طوری که با فاصله جداشدهاند در یک رشته منفرد و نمایش آن echo \"${arr[*]}\" # \"0 1 what was the question?\" :خروجی
حتی اگر گمان میکنید میتوانید تضمین کنید که هرگز حفرهای وجود نخواهد داشت، این یک تمرین مناسب برای نوشتن کُد به طریقی میباشد که بتواند آرایه پراکنده را مدیریت نماید. تنها در صورتی با آرایهها به عنوان لیست رفتار کنید که مطمئن باشید، و جلوگیری از پیچیدگی تا اندازهای باشد که این عمل را توجیه نماید.
2. بارگذاری کمیتها در یک آرایه
اختصاص یک عضو آرایه در یک نوبت، ساده و قابل حمل است:
# Bash و ksh در پوسته arr[0]=0 arr[42]=\'the answer\'
تخصیص چندگانه کمیتها به آرایه، در یک نوبت نیز امکان پذیر است، اما ترکیب دستوری آن در میان پوستهها متفاوت است. Bash فقط از ترکیب arrName=(args...) پشتیبانی میکند. ksh88 فقط از ترکیب set -A arrName -- args... پشتیبانی مینماید. ksh93، mksh، و zsh از هردو پشتیبانی میکنند. اگر از نزدیک به آن نگاه کنید، در هر دو روش تفاوتهای ظریفی بین تمام این پوستهها وجود دارد.
# Bash, ksh93, mksh, zsh در پوستههای array=(zero one two three four)
# ksh88/93, mksh, zsh در پوستههای set -A array -- zero one two three four
موقع مقدار دهی اولیه به این طریق، اولین شاخص صفر است مگر اینکه شاخص دیگری تعیین شده باشد.
در تخصیص مرکب، در داخل پرانتزها فاصله همانند کاراکتر کاما بین شناسههای یک فرمان ارزیابی میشود، به انضمام بسط پارامتر و تفکیک کلمه. هر نوع بسط یا جایگزینی میتواند به کار برود. تمام قواعد معمول نقلقول در آنها صدق میکند.
# Bash و ksh93 در پوسته oggs=(*.ogg)
در روش تخصیص ksh88 مانند، با استفاده از set، شناسهها دقیقاً مانند شناسههای معمولی فرمان هستند.
# Korn در پوسته set -A oggs -- *.ogg
# (بسط ابرو نیازمند نگارش 3.0 یا بالاتر است) Bash در homeDirs=(~{,root}) # است Bash به ترتیب دیگری رخ میدهد و این روش محصوص ksh بسط ابرو در letters=({a..z}) # در همه شلها نمیتوان با بسط-رشته حروف را به کار برد
# Korn در پوسته set -A args -- \"$@\"
2.1. بارگیری سطرها از فایل یا جریان داده
در bash نگارش 4، فرمان mapfile(که readarray نیز نامیده میشود) این موارد را انجام میدهد:
# Bash 4 در mapfile -t lines <myfile # یا mapfile -t lines < <(some command)
بخش جایگزینی پردازش و پرسش و پاسخ شماره ۲۴ را برای جزئیات بیشتر در باره ترکیت دستوری <(...) ملاحظه کنید.
فرمان mapfile سطرهای خالی و همچنین فقدان سطر جدید انتهای یک جریان ورودی را با درج آنها به عنوان یک عضو تهی آرایه اداره میکند. این مورد، موقعی که دادهها به روش دیگری( بخش بعدی را ببینید) خوانده میشوند، میتواند مشکل ساز بشود. mapfile یک سری اشکال دارد: این فرمان فقط سطر جدید را میتواند به عنوان خاتمه دهنده سطرها مدیریت کند. تمام گزینههای مورد پشتیبانی فرمان read توسط mapfile، و به طور معکوس، مدیریت نمیشوند. به عنوان مثال mapfile نمیتواند فایلهای جداشده با کاراکتر NUL بواسطه فرمان find -print0` را مدیریت کند. موقعی که mapfile در دسترس نباشد، برای تهیه المثنی آن باید خیلی سخت کار کنیم. روشهای بسیاری برای تهیه تقریباً موفق آن وجود دارد، اما به طرُق ظریفی شکست میخورند.
این مثالها رونوشتی از اکثر تواناییهای اساسی mapfile ایجاد میکنند:
# Bash, Ksh93, mksh در پوستههای while IFS= read -r; do lines+=(\"$REPLY\") done <file [[ $REPLY ]] && lines+=(\"$REPLY\")
عملگر += وقتی با پرانتزها به کار میرود، عنصری به آرایه اضافه میکند، که شماره شاخص آن برابر بالاترین شاخص موجود آرایه به اضافه یک خواهد بود.
# Korn در پوسته # .کاهش و افزایش قبل و بعد را پشتیبانی نمیکند Ksh88 i=0 while IFS= read -r; do lines[i+=1,$i]=$REPLY done <file [[ $REPLY ]] && lines[i]=$REPLY
کروشهها زمینه محاسباتی ایجاد میکنند. نتیجه عبارت، شاخص مورد استفاده برای تخصیص است.
2.1.1. مدیریت سطرهای جدید(یا فقدان آنها) در انتهای فایل
قرمان read موقعی که آخرین سطر فایل را میخواند false را باز میگرداند. این مشکلی را بیان میکند: اگر فایل شامل سطر جدید در انتها باشد، آنوقت read موقع خواندن و تخصیص سطر انتهایی غلط خواهد بود، در غیر آنصورت موقع خواندن و تخصیص آخرین سطر داده، نادرست خواهد بود. بدون یک کنترل خاص برای این حالتها، صرف نظر از منطق به کار رفته، همیشه یا آرایه حاصل با یک عضو خالی اضافی، یا با فقدان عضو پایانی خاتمه خواهد یافت.
برای واضح شدن مطلب- اکثر فایلهای متن شامل یک سطر جدید به عنوان آخرین کاراکتر فایل میباشند. سطرجدید توسط اکثر ویرایشگرهای متن به انتهای فایلها اضافه میشود، و همچنین توسطHere documentها و Here stringها نیز اضافه میشوند. اکثر اوقات، فقط موقع خواندن خروجی از لولهها و جایگزینی پردازش، یا از فایلهای متن معیوب تولید شده توسط ابزارهای معیوب یا فاقد پیکربندی مناسب، این مورد یک مسئله است. اجازه دهید به چند مثال نگاه کنیم.
این روش عناصر را با استفاده از حلقه، یک به یک میخواند.
# !به طور صحیح عمل نمیکند unset -v arr i while IFS= read -r \'arr[i++]\'; do : done < <(printf \'%s\\n\' {a..d})
متأسفانه، اگر فایل یا جریان داده شامل یک سطر جدید انتهایی باشد، یک عضو خالی به انتهای آرایه اضافه میشود، زیرا دستور read -r arr[i++] بعد از آخرین سطر شامل متن و قبل از باز گرداندن غلط، یک مرتبه اضافی اجرا میشود.
# !باز هم به طور صحیح عمل نمیکند unset -v arr i while read -r; do arr[i++]=$REPLY done < <(printf %s {a..c}$\'\\n\' d)
کروشهها زمینه محاسباتی ایجاد میکنند. داخل آنها، i++ همانطور عمل میکند که برنامهنویسان C انتظار دارند(همه غیر از ksh88).
این راهکار در حالت عکس آن ناموفق است - سطرهای خالی و ورودیهای ختم شده به سطر جدید را به طور صحیح اداره میکند، اما اگر سطر جدید انتهایی فایل یا ورودی غایب باشد، در ذخیره آخرین سطر ورودی ناموفق است. . بنابراین لازم است آن حالت را به طور خاص اداره کنیم:
# Bash, ksh93, mksh در پوسته های unset -v arr i while IFS= read -r; do arr[i++]=$REPLY done <file # .الحاق سطر داده خاتمه نیافته، در صورت موجود بودن [[ $REPLY ]] && arr[i++]=$REPLY
این مورد به «آخرین راه حل» که ما قبلاً ارائه نمودیم، بسیار نزدیک است -- هم مدیریت سطرهای خالی داخل فایل، و هم سطر انتهایی خاتمه نیافته. IFS تهی برای ممانعت فرمان read از زدودن فضای سفید احتمالی ابتدا و انتهای سطرها در صورتی که مایل به حفظ آنها باشید، به کار میرود .
یک طریق دیگر عبور از این مشکل، حذف عضو خالی پس از پایان حلقه است:
# Bash unset -v arr i while IFS= read -r \'arr[i++]\'; do : done <file # .عضو انتهایی را اگر باشد حذف میکند [[ ${arr[i-1]} ]] || unset -v \'arr[--i]\'
خواه شما ترجیح بدهید مقدار اضافه بخوانید و سپس یکی را حذف کنید، یا کمتر بخوانید و بعد یکی را اضافه کنید، یک انتخاب شخصی است.
توجه: برای آنکه کروشهها به عنوان globها تفسیر نشوند، نقلقولی نمودن \'arr[i++]\' تحویل شده به فرمان read ضروری است. این مظلب برای سایر دستورات داخلی غیر کلیدواژهای که یک نام متغیر زیرنویسدار میگیرند مانند let و unset نیز صدق میکند.