این پاسخ فرض بر این دارد که شما در درجه اول، دارای درک اساسی از اینکه آرایهها چه میباشند، هستید. اگر شما در این نوع برنامهنویسی، تازه وارد هستید،شاید بهتر باشد با توضیح راهنما شروع نمایید. صفحه کامل و دارای جزئیات بیشتری است.
آرایههای یک بُعدی با شاخص عدد صحیح در پوسته 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?" :خروجی
حتی اگر گمان میکنید میتوانید تضمین کنید که هرگز حفرهای وجود نخواهد داشت، این یک تمرین مناسب برای نوشتن کُد به طریقی میباشد که بتواند آرایه پراکنده را مدیریت نماید. تنها در صورتی با آرایهها به عنوان لیست رفتار کنید که مطمئن باشید، و جلوگیری از پیچیدگی تا اندازهای باشد که این عمل را توجیه نماید.
اختصاص یک عضو آرایه در یک نوبت، ساده و قابل حمل است:
# 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 -- "$@"
در 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
کروشهها زمینه محاسباتی ایجاد میکنند. نتیجه عبارت، شاخص مورد استفاده برای تخصیص است.
قرمان 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 نیز صدق میکند.
گاهی اوقات زدودن سطرهای خالی واقعاً مطلوب است، یا شاید شما بدانید که همواره ورودی با سطرجدید جدا شده است، از قبیل ورودی تولید شده به طور درونی توسط اسکریپت شما. در برخی پوستهها، امکان استفاده از -d برای تنظیم جداکننده سطر read به null، و بعد سوء استفاده از -a یا -A(نسبت به پوسته) -- که به طور معمول برای خواندن فیلدهای یک سطر به داخل آرایه به کار میرود -- برای خواندن سطرها، وجود دارد. به طورقابل اجرا، با تمام ورودی به عنوان یک سطر رفتار میشود، و فیلدهای آن با سطرجدید جدا میشوند.
# Bash 4 در شل IFS=$'\n' read -rd '' -a lines <file
# mksh و zsh در شل IFS=$'\n' read -rd '' -A lines <file
متأسفانه، مورد فوق در ksh93 کار نمیکند، ولواینکه فرمان read در این پوسته نشانه جداکننده -d را دارد. البته، مثالهای فوق سطرهای خالی را حفظ نمیکنند، اما آنها جایگزین سریع و آسان mapfile هستند که در چند پوسته غیر-bash نیز کار میکنند. از نگارش alpha 2012-10-12 شل ksh93 برطرف گردیده است
12-10-09 +read -d '' now reads up to a NUL byte.
هرگز سطرها را با استفاده از for در حلقهها نخوانید! متکی بودن به تفکیک کلمه متغیر IFS، اگر فضای سفید جداکننده تکراری داشته باشید، باعث مسائلی میگردد، به دلیل آنکه آنها یکپارچه میشوند. به این طریق امکان حفظ نمودن سطرهای سفید به عنوان عناصر خالی آرایه وجود ندارد. حتی بدتر از این، کاراکترهای ویژه جانشینی، بدون غیرفعال کردن و فعال نمودن مجدد آن، بسط داده میشوند. هرگز از این راهکار استفاده نکنید، مسئلهساز است، روشهای عبور موقت همگی ناخوشایند هستند، و تمام مشکلات حل نمیشوند.
چون که چنین موردی به طور غیر قابل باوری، یک اشتباه رایج میباشد، مورد ذیل تقریباً بهترین بیان از این ترفند و اینکه چقدر سختتر از انجام آن به طور صحیح است، را تشریح میکند-- و باز هم نمیتواند سطرهای جدید متوالی را حفظ نماید! ناسالمیاش از اینجا ناشی میشود. برای توضیحات تفصیلی بخش سطرهای جدید را با for نخوانید را ملاحظه کنید.
# Bash # WARNING: Don't do this! evilReadLines() { [[ -e $2 ]] || return # را حفظ کند trap به سختی تلاش میکند جانشین قبلی و وضعیتهای # را تنظیم کند بازهم در زحمت است ERR یا DEBUG اما اگر فراخواننده if [[ $- != *f* ]]; then set -f local oReturn=$(trap -p RETURN) trap 'set +f; trap "${oReturn:--}" RETURN' RETURN fi local line idx IFS=$'\n' for line in ${1:+$(<"$2")}; do printf -v "${1}[idx++]" %s "$line" done #:این یک جایگزین برای حلقه فوق، همان اندازه نامساعد، گرچه اندکی سریعتر # IFS=$'\n' declare -a ${1:+"$1"'=( $(<"$2") )'} 2>/dev/null } declare -ft evilReadLines # .را از فراخوانی کننده به ارث میبرد trapها # نامها و نامفایلها را به آرایه عبور میدهد evilReadLines myArray myFile
اگر با رکوردهایی سر و کار دارید که سطرهای جدید میتوانند در آنها تعبیه شده باشد، شما در حال استفاده از یک جداکننده جایگزین از قبیل کاراکتر NUL ( \0 ) برای جدا کردن رکوردها خواهید بود. در آن وضعیت، شما نیاز به استفاده ازشناسه -d فرمان read نیز خواهید داشت:
# Bash unset -v arr i while IFS= read -rd '' 'arr[i++]'; do : done < <(find . -name '*.ugly' -print0) # or while read -rd ''; do arr[i++]=$REPLY done < <(find . -name '*.ugly' -print0) # or (bash 3.1 and up) while read -rd ''; do arr+=("$REPLY") done < <(find . -name '*.ugly' -print0)
read -d '' به Bash میگوید، تا رسیدن به یک بایت تهی به جای رسیدن به سطرجدید، خواندن را ادامه دهد. معلوم نیست این مطلب در تمام پوستهها با -d کار بکند.
به طوری که قبلاً اشاره شد، آرایهها پراکنده هستند - یعنی، تضمین نمیشود که شاخصهای عددی همجوار با کمیتها اشغال شده باشند. این موضوع، آنچه به معنی «الحاق به یک آرایه موجود» میباشد را مغشوش میکند. چند رویکرد (برای الحاق)وجود دارد.
اگر شما بالاترین شاخص شمارهگذاری شده را با نگهداری در یک متغیر ( برای مثال، اثر جانبی مقداردهی به عناصر یک آرایه در یک حلقه) پیگردی میکنید، و میتوانید تضمین کنید که مقدار آن صحیح است، میتوانید دقیقاً از آن با اطمینان از همگام باقی ماندنش استفاده کنید و ادامه دهید.
# Bash/ksh93 arr[++i]="new item"
اگر نمیخواهید یک متغیر شاخص نگهداری کنید، اما میدانید که آرایه شما پراکنده نیست، آنوقت میتوانید از تعداد عناصر برای محاسبه مبدأ الحاق استفاده کنید(پیشنهاد نمیشود):
# Bash/ksh # .اگر آرایه دارای حفره باشد(پرکنده باشد) ناموفق خواهد شد arr[${#arr[@]}]="new item"
اگر نمیدانید آیا آرایه شما پراکنده است یا خیر، اما در نظر ندارید تمام آرایه را شاخصگذاری مجدد نمایید(بسیار کم بازده)، پس میتوانید از این استفاده کنید:
# Bash arr=("${arr[@]}" "new item") # Ksh set -A arr -- "${arr[@]}" "new item"
اگر در bash نگارش 3.1 یا بالاتر هستید، میتوانید عملگر += را به کار ببرید:
# Bash 3.1, ksh93, mksh, zsh arr+=(item 'another item')
توجه: پرانتزها لازم میباشند، درست مانند موقع تخصیص یک آرایه. در غیر آن صورت شما با الحاق به ${arr[0]} که مترادف $arr است مواجه میشوید. اگر پوسته شما این نوع الحاق را پشتیبانی میکند، این روش ارجح میباشد.
برای نمونههایی ازکاربرد آرایه جهت نگهداری دستورات پیچیده پوسته، پرسش و پاسخ شماره 50 و شماره 40 را ملاحظه کنید.
${#arr[@]} یا ${#arr[*]} به تعداد عناصر آرایه بسط مییابند:
# Bash shopt -s nullglob oggs=(*.ogg) echo "There are ${#oggs[@]} Ogg files."
عناصر منفرد به وسیله شاخص بازیابی میشوند:
echo "${foo[0]} - ${bar[j+1]}"
کروشهها زمینه محاسبات میباشند. درون زمینه محاسباتی، به متغیرها، از جمله آرایهها، توسط نام میتواند رجوع بشود. برای مثال، در بسط زیر:
${arr[x[3+arr[2]]]}
شاخص arr کمیت آن عضو آرایه x که شاخص آن 3 به اضافه مقدار arr[2] است، خواهد بود.
استفاده دسته جمعی از عناصر آرایه، یکی از ویژگیهای کلیدی آرایههای پوسته است. دقیقاً همانگونه که "$@" به پارامترهای مکانی بسط مییابد، "${arr[@]}" به لیستی از کلمات، هر عضو آرایه به یک کلمه، بسط مییابد. برای مثال:
# Korn/Bash for x in "${arr[@]}"; do echo "next element is '$x'" done
حتی اگر عناصر آرایه محتوی فضای سفید باشند این کُد کار میکند. همواره به همان تعداد کلمه منجر میشود که آرایهای با آن تعداد عضو دارید.
اگر کسی حقیقتاً بخواهد از آرایه کامل رونوشت بردارد، هر عضو در یک سطر، این سادهترین شیوه است:
# Bash/ksh printf "%s\n" "${arr[@]}"
برای نسخهبرداری اندکی پیچیدهتر از آرایه، "${arr[*]}" باعث میشود عناصر با اولین کاراکتر متغیر IFS( یا در صورت برقرار نبودن متغیر IFS با یک فاصله) در میان آنها، به یکدیگر پیوسته شوند. در نتیجه وقوع آن، "$*" به همان روش پارامترهای مکانی بسط داده میشود.
# Bash arr=(x y z) IFS=/; echo "${arr[*]}"; unset IFS # prints x/y/z
متأسفانه، نمیتوانید کاراکترهای چندتایی را با کاربرد این روش مابین عناصر آرایه قرار بدهید. به جای آن باید چیزی مشابه این را به کار ببرید:
# Bash/ksh arr=(x y z) tmp=$(printf "%s<=>" "${arr[@]}") echo "${tmp%<=>}" # .اضافی را از انتها حذف میکند <=> # را چاپ میکند x<=>y<=>z
یا از بُرش آرایه، که در بخش بعد شرح داده شده، استفاده کنید.
# Bash/ksh typeset -a a=([0]=x [5]=y [10]=z) printf '%s<=>' "${a[@]::${#a[@]}-1}" printf '%s\n' "${a[@]:(-1)}"
این نیز نشان میدهد که چطور عناصر چندگانه آرایههای پراکنده میتواند در یک نوبت تخصیص داده شود. توجه نمایید استفاده از نشانه arr=([key]=value ...) در میان پوستهها متفاوت است. در ksh93، این ترکیب دستوری به طور پیشفرض به شما یک آرایه انجمنی میدهد مگر اینکه شما طور دیگری تعیین کنید، و کاربرد آن مستلزم تعیین کمیت یک شاخص معین به طور صریح است، برخلاف bash، که جایی که شاخصها از قلم افتاده باشند از شاخص قبلی شروع میکند . این مثال به طریقی نوشته شده است که با هر دو سازگار باشد.
BASH نگارش 3.0 قابلیت بازیابی کمیت شاخصهای یک آرایه را اضافه نموده:
# Bash 3.0 or higher arr=(0 1 2 3) arr[42]='what was the question?' unset 'arr[2]' echo "${! arr[@]}" # چاپ میکند 0 1 3 42 # (مترجم: درصورتیکه کد زیر محتوای عناصر آرایه را چاپ میکند نه کمیت خود شاخصها را) echo "${arr[@]}" # را چاپ میکند 0 1 2 3 what was the question?
بازیابی شاخصها برای بعضی از انواع معین وظایف بینهایت اهمیت دارد، از قبیل نگهداری آرایههای موازی با شاخصهای یکسان( روشی کم بها برای تقلید یک آرایه از structها در زبان فاقد struct است):
# Bash 3.0 or higher unset file title artist i for f in ./*.mp3; do file[i]=$f title[i]=$(mp3info -p %t "$f") artist[i++]=$(mp3info -p %a "$f") done # بعداً، روی هر فایل صوتی تکرار میکند. حتی اگر آرایه پراکنده # . باشد، این روش کار میکند، فقط به شرط آنکه حفرههای آنها یکسان باشد for i in "${!file[@]}"; do echo "${file[i]} is ${title[i]} by ${artist[i]}" done
بسطهای پارامتر Bash میتواند روی عناصر آرایه به طور دسته جمعی انجام بشود:
# Bash arr=(abc def ghi jkl) echo "${arr[@]#?}" # را چاپ میکند bc ef hi kl echo "${arr[@]/[aeiou]/}" # را چاپ میکند bc df gh jkl
بسط پارامتر نیز میتواند برای استخراج عضوهای یک آرایه به کار برود. بعضی ها این را بُرش مینامند:
# Bash echo "${arr[@]:1:3}" # (دومین عنصر) #1 سه عنصر با شروع ازعضو شماره echo "${arr[@]:(-2)}" # دوعضو انتها echo "${@:(-1)}" # آخرین پارامتر مکانی echo "${@:(-2):1}" # پارامتر مکانی دوم از انتها
به طوری که در بالا دیدیم، آرایه @(آرایهای از پارامترهای مکانی) میتواند تقریباً مانند آرایه نامگذاری شده عادی استفاده بشود. این تنها متغیر آرایهای مورد استفاده در پوستههای POSIX یا Bourne میباشد. این آرایه محدودیتهای معینی دارد : نمیتوانید به طور جداگانه عناصر منفرد را برقرار یا حذف کنید، و نمیتواند پراکنده باشد. با وجود این، بازهم انجام برخی وظایف معین در پوسته POSIX را که در غیر آن صورت مستلزم ابزارهای خارجی بودند، امکانپذیر میسازد:
# POSIX set -- *.mp3 if [ -e "$1" ]; then echo "there are $# MP3 files" else echo "there are 0 MP3 files" fi
# POSIX ... # .یک گزینه به لیست گزینههای ما که به طور پویا تولید شدهاند، اضافه میکند set -- "$@" -f "$somefile" ... foocommand "$@"
(با دستورات تولید شده به طور پویا در پرسش و پاسخ 50 با استفاده از آرایههای نام گذاری شده، مقایسه کنید.)
پرسش و پاسخهای Bash -شماره 5 (آخرین ویرایش 2012-12-01 15:17:42 توسط 148)