گاهی اوقات زدودن سطرهای خالی واقعاً مطلوب است، یا شاید شما بدانید که همواره ورودی با سطرجدید جدا شده است، از قبیل ورودی تولید شده به طور درونی توسط اسکریپت شما. در برخی پوستهها، امکان استفاده از -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)