آموزش اسکریپت نویسی

آموزش اسکریپت نویسی پوسته گنو-لینوکس

آموزش اسکریپت نویسی

آموزش اسکریپت نویسی پوسته گنو-لینوکس

پرسش و پاسخ شماره ۱۰۰

پرسش و پاسخ شماره ۱۰۰

در bash چگونه می‌توانم رشته‌ها را دستکاری کنم؟

Bash می‌تواند با رشته‌ها عملیات انجام بدهد. عملیات رشته‌ای فراوان. این یک مقدمه برای دستکاری‌های رشته در bash و شگردهای مربوطه می‌باشد. با پرسش بسط پارامتر همپوشانی دارد، اما اطلاعاتی که در اینجا ارائه شده است، یک حالت مبتدی پسندتری دارد(امیدواریم).

ترکیب دستوری بسط پارامتر

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

fruit=apple

و می‌توانیم آن مقدار را دوباره با استفاده از بسط پارامتر بخوانیم:

$fruit

توجه نمایید، در هرحال، آن ‎$fruit‎ یک بسط می‌باشد -- یک نام، نه یک فعل -- و بنابراین به طور طبیعی ما نیاز داریم که آن را در نوعی دستور قرار بدهیم. همچنین، نتایج یک بسط پارامتر غیرنقل‌قولی به چندین کلمه تفکیک و به نام فایلها بسط داده خواهد شد، که معمولاً ما نمی‌خواهیم. بنابراین، همیشه باید بسط‌های پارامتر خودمان را نقل‌قولی کنیم مگر اینکه با یک وضعیت ویژه‌ای سروکار داشته باشیم.

از این جهت، برای دیدن مقدار پارامتر(از قبیل یک متغیر):

echo "$fruit"

# ‎printf "%s\n" "$fruit"‎ به طور کلی‌تر‎
# اما فعلاً آن را ساده نگه می‌داریم

یا، می‌توانیم این بسط‌ها را به عنوان بخشی از عبارت بزرگتری به کار ببریم:

echo "I like to eat $fruit"

اگر بخواهیم یک s در انتهای محتوای متغیر خود بگذاریم، وضع دشواری را ایجاد می‌کنیم:

echo "I like to eat $fruits"

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

echo "I like to eat ${fruit}s"

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

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

طول یک رشته

این یکی آسان است، بنابراین با اولین روش از آن خارج می‌شویم.

echo "The string <$var> is ${#var} characters long."

توجه نمایید که از ‎bash 3.0‎ به بعد، در مناطق چند بایتی در واقع کاراکترها هستند که به جای بایت‌ها یک تفاوت با اهمیت می‌باشند. اگر شما تعداد بایت‌ها را احتیاج دارید، لازم است ‎LC_ALL=C‎ را قبل از بسط ‎${#var}‎ صادر نمایید.

بررسی زیررشته‌ها

این مورد با پرسش و پاسخ شماره 41 همپوشانی دارد، اما در اینجا ما آن را تکرار می‌کنیم. برای بررسی یک زیررشته(شناخته، ثابت) و اقدام بر اساس وجود یا غیبت آن، فقط این کار را انجام دهید:

if [[ $var = *substring* ]]; then
  echo "<$var> contains <substring>"
else
  echo "<$var> does not contain <substring>"
fi

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

if [[ $var = *"$substring"* ]]; then
# باشد glob با زیررشته به عنوان رشته لفظی رفتار می‌شود، حتی اگر شامل کاراکترهای

اگر می‌خواهید با آن به عنوان الگوی glob رفتار شود، نقل‌قولها را حذف کنید:

if [[ $var = *$substring* ]]; then
# رفتار می‌شود glob با زیررشته به عنوان 

همچنین یک قابلیت عبارت منظم، در برگیرنده عملگر ‎=~‎ وجود دارد. به واسطه سازگاری با تمام نگارشهای Bash از ‎ 3.0‎ به بالا، مطمئن شوید برای قرار دادن عبارت منظم در یک متغیر -- آنرا به طور مستقیم در فرمان ‎[[‎ قرار نمی‌دهید. و همچنین آنرا نقل‌قولی نکنید -- وگرنه با آن به عنوان رشته لفظی رفتار می‌شود.

my_re='^fo+.*bar'
if [[ $var =~ $my_re ]]; then
#  رفتار می‌شود(ERE)به عنوان یک عبارت منظم توسعه‌یافته my_re با 

جایگزینی بخشی از یک رشته

یک نیاز رایج، تعویض بخشی از یک رشته با یک مورد دیگر می‌باشد. (اجازه بدهید در حال حاضر کلمات old و new را برای نامیدن این بخشها به کار ببریم.) اگر ما می‌دانیم کلمه old چیست، و کلمه new چه باید باشد، اما لزوماً نمی‌دانیم در کجای رشته ظاهر می‌شود، آنوقت می‌توانیم این کد را به کار ببریم:

$ var="She favors the bold.  That's cold."
$ echo "${var/old/new}"
She favors the bnew.  That's cold.

این کد فقط اولین مورد کلمه old را تعویض می‌کند. اگر می‌خواهیم تمام مواردی را که کلمه وجود دارد، تعویض کنیم، اولین کاراکتر/ را دوگانه می‌کنیم:

$ var="She favors the bold.  That's cold."
$ echo "${var//old/new}"
She favors the bnew.  That's cnew.

ممکن است کلمه دقیق مورد نظر برای تعویض را ندانیم. اگر نوع کلمه‌ای که در جستجوی آن هستیم را می‌توانیم با الگوی glob بیان کنیم، آنوقت هنوز در شرایط خوبی هستیم:

$ var="She favors the bold.  That's cold."
$ echo "${var//b??d/mold}"
She favors the mold.  That's cold.

همچنین می‌توانیم کلمه‌ای که در جستجوی آن می‌باشیم را به ابتدا یا انتهای رشته پیوند کنیم. به بیان دیگر، می‌توانیم به bash بگوییم که، در عوض جایی در وسط، فقط درصورتی که کلمه را در ابتدا یا انتهای رشته پیدا نماید، تعویض کند.

$ var="She favors the bold.  That's cold."
$ echo "${var/#bold/mold}"
She favors the bold.  That's cold.
$ echo "${var/#She/He}"
He favors the bold.  That's cold.
$ echo "${var/%cold/awful}"
She favors the bold.  That's cold.
$ echo "${var/%cold?/awful}"
She favors the bold.  That's awful

توجه نمایید که در اولین دستور اتفاقی رخ نمی‌دهد، زیرا bold در ابتدای رشته ظاهر نشده است و در فرمان سوم نیز به علت آنکه cold در انتهای رشته ظاهر نشده است. عملگر # الگو(کلمه ساده یا glob) را به ابتدای رشته پیوند می‌کند، و عملگر % به انتها پیوند می‌دهد. در فرمان چهارم، الگوی ‎cold?‎ با کلمه ‎cold.‎ (شامل نقطه) در انتهای رشته مطابقت می‌نماید.

حذف قسمتی از یک رشته

اگر بخواهیم، می‌توانیم از ترکیب دستوری ‎${var/old/}‎ یا ‎${var//old/}‎ برای تعویض کلمه با هیچ استفاده کنیم. این یک روش برای حذف بخشی از یک رشته است. اما چند روش دیگر وجود دارد که اغلب سودمندتر از آنست که شما بتوانید حدس بزنید.

اولین روش حذف چیزی از ابتدای یک رشته را در بر می‌گیرد. بار دیگر، بخشی که ما می‌خواهیم حذف کنیم می‌تواند رشته ثابتی باشد که پیشاپیش می‌شناسیم، یا شاید موردی باشد که باید با یک الگوی glob شرح بدهیم.

$ var="/usr/local/bin/tcpserver"
$ echo "${var##*/}"
tcpserver

عملگرهای ## به معنی «حذف بزرگترین رشته مورد انطباق از ابتدای محتوای متغیر» است. بخش ‎*/‎ الگویی است که می‌خواهیم مطابقت دهیم -- هر تعداد کاراکتری که به یک کاراکتر لفظی / ختم می‌شوند. اساساً نتیجه همانند نتیجه فرمان basename می‌باشد، با یک استثناء قابل اشاره: اگر رشته به یک(یا چند) کاراکتر/ ختم شود، فرمان basename نام آخرین جزء مسیر را باز خواهد گرداند، در حالیکه مورد فوق یک رشته تهی برمی‌گرداند. با هشیاری به کار ببرید.

اگر ما فقط از یک عملگر # استفاده کنیم، آنوقت کوتاهترین رشتهِ تطبیقِ محتمل را حذف می‌کنیم. این کار کمتر لازم می‌گردد، بنابراین در حال حاضر از مثال برای آن عبور می‌کنیم و بعداً یک مثال حقیقتاً خیلی خوب ارائه می‌کنیم.

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

$ var="/usr/local/bin/tcpserver"
$ echo "${var%/*}"
/usr/local/bin

عملگر % در اینجا به معنی «حذف کوتاهترین موردِ تطبیقِ محتمل از انتهای محتوای متغیر» می‌باشد، و ‎/*‎ الگوی glob است که با یک کاراکتر / لفظی که هر تعداد کاراکتر آن را دنبال کند، شروع می‌شود. چون ما کوتاهترین انطباق را لازم داریم، bash مجاز نمی‌شود که ‎/bin/tcpserver‎ یا هر مورد دیگری که شامل چند / می‌شود را تطبیق بدهد. فقط قسمت ‎/tcpserver‎ را حذف نموده است.

بعلاوه، عملگر%% به معنی «حذف بلندترین مورد انطباق محتمل از انتهای محتوای متغیر» می‌باشد.

اکنون بیاید مورد دشوارتری را امتحان کنیم: اگر به جای فقط آخرین قسمت، نوعی basename دوگانه -- آخرین دو قسمت یک نام مسیر -- را می‌خواستیم چه؟

$ var=/home/someuser/projects/q/quark
$ tmp=${var%/*/*}
$ echo "${var#$tmp/}"
q/quark

این یک مقدار مهارت‌آمیز است، این هم چگونگی کارکرد آن:

  • جستجوی کوتاهترین رشته تطبیق محتمل /*/* در انتهای نام مسیر. در این حالت، بر ‎/q/quark‎ باید منطبق گردد.

  • حذف آن از انتهای رشته اصلی. نتیجه این کار چیزی است که ما نمی‌خواهیم. این نتیجه را در tmp ذخیره می‌کنیم.

  • حذف آنچه نمی‌خواهیم(به اضافه یک / اضافی) از متغیر اصلی.

  • ما دو قسمت آخر نام مسیر را باقی گذاشته‌ایم.

همچنین سزاوار اشاره نمودن است، که چنانچه ما دقیقاً تشریح نموده‌ایم، الگوی مورد نظر برای حذف شدن(بعد از # یا % یا ## یا %%) نباید ثابت باشد -- می‌تواند یک جایگزینی دیگر باشد. این مورد در زندگی واقعی رایجترین حالت نیست، اما گاهی اوقات قابل استفاده است.

استخراج قسمت‌هایی از رشته‌ها

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

$ var='garbage in [42] garbage out'
$ tmp=${var##*[}
$ echo "${tmp%%]*}"
42

توجه نمایید که ما یک متغیر موقت برای نگهداری نتایج یک بسط پارامتر به کار برده‌ایم، و سپس آن نتیجه را به بسط دوم تغذیه نموده‌ایم. ما نمی‌توانیم در یک نوبت دو بسط پارامتر را در یک متغیر انجام بدهیم(واقعاً ترکیب دستوری آن را اجازه نمی‌دهد).

اگر جداکننده در هر دو نوبت یکسان است(برای نمونه، نقل‌قول دوگانه) آنوقت لازم است که اندکی محتاط باشیم:

$ var='garbage in "42" garbage out'
$ tmp=${var#*\"}
$ echo "${tmp%\"*}"
42

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

var='CONFIG  .SYS'
left=${var:0:8}
right=${var:(-3)}

در اینجا، ورودی یک نام فایل ‎MS-DOS‎ به صورت ‎"8.3"‎ است، پُر شده با فاصله برای طول کامل. اگر به دلایلی نیاز داشته باشیم آن را به دو قسمتش تفکیک کنیم، چند روش محتمل برای انجام آن داریم. می‌توانستیم نام را از محل نقطه به فیلدها تفکیک نماییم(این رویکرد را بعداً نشان می‌دهیم). یا می‌توانستیم ‎${var#*.}‎ را برای به دست آوردن پسوند(قسمت بعد از نقطه) و ‎${var%.*}‎ را برای به دست آوردن قسمت سمت چپ به کار ببریم. یا می‌توانستیم ستونها را چنانکه در اینجا نشان دادیم، بشماریم.

در مثال ‎${var:0:8}‎، عدد 0 محل شروع است(0 اولین ستون است) و عدد 8 طول قسمتی که ما می‌خواهیم. اگر طول را از قلم بیاندازیم، یا اگر طول بزرگتر از باقیمانده رشته باشد، آنوقت باقیمانده رشته را به عنوان خروجی به دست می‌آوریم. در مثال ‎${var:(-3)}‎، طول را از قلم انداخته‌ایم. ما محل شروع را ‎-3‎(منفی سه) تعیین کرده‌ایم، که یعنی سه از انتها. ما باید برای اجتناب از ناسازگاری گرامری( بعداً این مورد را بحث خواهیم نمود) از پرانتزها یا فاصله بین : و عدد منفی استفاده نماییم. همچنین می‌توانستیم در این حالت از ‎${var:8}‎ برای به دست آوردن باقیمانده رشته که از ستون 8 شروع می‌شود(که نهمین ستون است) استفاده کنیم، چون می‌دانیم که طول ثابت است، اما در بسیاری موقعیت‌ها نمی‌توانیم از قبل طول را بدانیم، و مشخص نمودن عدد منفی برای محل شروع به شما اجازه می‌دهد از مقداری کار غیر ضروری پرهیز کنید.

موقعی که به هیچ وجه جداکننده‌ای میان بخش‌هایی که می‌خواهیم وجود ندارد، شمارش ستونها حتی یک تکنیک قوی‌تر است:

var='CONFIG  SYS'
left=${var:0:8}
right=${var:8}

در اینجا ما نمی‌توانیم از ‎${var#*.}‎ یا روش مشابهی استفاده کنیم!

تفکیک یک رشته به فیلدها

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

اگر جداکننده یک کاراکتر منفرد است(یا یک کاراکتر از یک مجموعه -- تا وقتی که هرگز بیشتر از یکی نیست) آنوقت bash چندین رویکرد بادوام ارائه می‌کند. نخست، خواندن ورودی به طور مستقیم در یک آرایه است (با فرض اینکه متغیر شامل کاراکتر سطر جدید نمی‌باشد):

var=192.168.1.3
IFS=. read -r -a octets <<< "$var"

در اینجا دیگر ما به هیچ وجه در محدوده بسط پارامتر نیستسم. چندین ویژگی را با هم ترکیب کرده‌ایم:

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

  • نشانه گذاری ‎var=value command‎ به معنای آنست که ما می‌خواهیم فقط متغیر را برای اجرای این فرمان منفرد تنظیم کنیم. متغیر IFS ، وقتی read به پایان می‌رسد به همان حالتی که از قبل بود برمی‌گردد.

  • read نتایجش را در یک آرایه به نام octets قرار می‌دهد.

  • <<< "$var"‎ یعنی ما می‌خواهیم محتویات var را به عنوان ورودی استاندارد برای فرمان read استفاده کنیم.

بعد از این فرمان، نتیجه یک آرایه به نام octets می‌باشد که اولین عنصر آن (عضو شماره 0) عدد 192 است، و دومین عنصر آن(عضو شماره 1) عدد 168 است، و به همین ترتیب. اگر ما بخواهیم به جای آرایه از یک مجموعه متغیر ثابت استفاده کنیم، آن را هم می‌توانیم انجام بدهیم:

IFS=, read lastname firstname rest <<< "$name"

همچنین می‌توانیم از فیلدهایی که نمی‌خواهیم، با تخصیص آنها به متغیرهایی از قبیل، x یا junk که برای ما ارزش ندارند، یا به _ که با هر دستور رونویسی می‌شود، عبور کنیم:

while IFS=: read user x uid gid x home shell; do
 ...
done < /etc/passwd

(به جهت قابلیت حمل، بهتر است از _ اجتناب شود چون این متغیر در برخی پوسته‌ها یک متغیر فقط خواندنی می‌باشد)

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

  • در پوسته sh به همان خوبی bash کار می‌کند.

  • اندکی ساده‌تر است.

var=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
found=no
set -f
IFS=:
for dir in $var
do
  if test -x "$dir"/foo; then found=yes; fi
done
set +f; unset IFS

این مثال مشابه موردی در پرسش و پاسخ شماره 81 است. Bash برای تعیین آنکه آیا یک فرمان در PATH شما وجود دارد، روشهای مناسب‌تری ارائه می‌نماید، اما این مثال، اندیشه کلی را به طور کاملاً واضح تشریح می‌کند. نکات قابل توجه:

  • set -f‎ بسط glob را غیر فعال می‌کند. شما همیشه موقعی که بسط پارامتر غیرنقل‌قولی به کار می‌برید، باید globها را غیر فعال نمایید، مگراینکه مخصوصاً بخواهید در محتوای پارامتر globها را اجازه بدهید.

  • ما از ‎set +f‎ و ‎unset IFS‎ در انتهای کُد برای باز گرداندن پوسته به حالت پیش فرض استفاده نموده‌ایم. اگرچه، لزوماً این حالتی نیست که موقع شروع کد، پوسته در آن حالت بوده. بازگرداندن پوسته به حالت قبلی آن(احتمالاً غیر پیش‌فرض) در اکثر موقعیت‌ها آزار دهنده‌تر از آنست که ارزش آن را داشته باشد، بنابراین ما در اینجا به طور فراگیر در مورد آن بحث نمی‌کنیم.

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

اگر جداکننده فیلد شما یک رشته چندکاراکتری است، آنوقت متأسفانه bash روشهای ساده‌ای برای کار با آن ارائه نمی‌کند. به جای آن بهترین گزینهِ شما، انجام آن در awk می‌باشد.

$ cat inputfile
apple::0.75::21
banana::0.50::43
cherry::0.15::107
date::0.30::20
$ awk -F '::' '{print $1 " qty " $3 " @" $2 " = " $2*$3; total+=$2*$3} END {print "Total: " total}' inputfile
apple qty 21 @0.75 = 15.75
banana qty 43 @0.50 = 21.5
cherry qty 107 @0.15 = 16.05
date qty 20 @0.30 = 6
Total: 59.3

در awk گزینه ‎-F‎ تعیین یک جداکننده فیلد با هر طولی را برای ما میسر می‌سازد. awk همچنین محاسبه ممیز شناور، آرایه‌های انجمنی ، و تنوع گسترده‌ای از ویژگی‌های دیگری را که بسیاری از پوسته‌ها فاقد آنها هستند، مجاز می‌داند.

ادغام فیلدها با یکدیگر

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

echo "$foo$bar"

اگر ما به جای مجموعه ثابتی از متغیرها، یک آرایه داریم، آنوقت می‌توانیم آرایه را با یک کاراکتر منفرد(یا هیچ کاراکتری) میان فیلدهایش با استفاده از IFS چاپ کنیم:

$ array=(1 2 3)
$ (IFS=/; echo "${array[*]}")
1/2/3

نکات قابل توجه در اینجا:

  • نمی‌توانیم از ‎IFS=/ echo ...‎ استفاده کنیم، به علت چگونگی عملکرد تفکیک کننده.

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

  • اگر IFS برقرار نباشد، یک فاصله بین عناصر خواهیم داشت. اگر به یک رشته تهی تنظیم باشد، چیزی میان عناصر وجود نخواهد داشت.

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

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

array=(1 2 3)
first=1
for element in "${array[@]}"; do
  if ((! first)); then printf "::"; fi
  printf "%s" "$element"
  first=0
done
echo

مثال بعد، از حلقه ضمنی printf برای چاپ تمام شناسه‌های اسکریپت همراه با قلابهای زاویه‌دار در اطراف هر یک، استفاده می‌کند:

#!/bin/sh
printf "$# args:"
printf " <%s>" "$@"
echo

یک آرایه نام گذاری شده نیز می‌تواند به جای @ به کار برود(به عنوان مثال ‎"${array[@]}"‎ به تمام عناصر array بسط داده می‌شود).

اگر می‌خواهیم به جای نسخه‌برداری رشته‌ها، آنها را به یک متغیر دیگر متصل کنیم، آنوقت چند انتخاب داریم:

  • یک رشته با استفاده از ‎var="$var$newthing"‎ ‏(قابل حمل) یا ‎var+=$newthing‎ ‏( در ‏‎bash 3.1‎‏) می‌تواند در یک مرحله با یک قطعه تجمیع بشود. برای مثال:

    output=$1; shift
    while (($#)); do output+="::$1"; shift; done
  • اگر اتصال با یک فرمان منفرد printf بتواند انجام گردد، با استفاده از ‎printf -v var FORMAT FIELDS...‎ می‌تواند به یک متغیر تخصیص یابد(‎bash 3.1‎). برای مثال:

    printf -v output "%s::" "$@"
    output=${output%::}    # از بین بردن جداکننده غیرضرور از انتهای رشته
  • اگر اتصال به چند فرمان نیاز دارد، و توسعه دادن تدریجی رشته مطلوب نیست، جایگزینی فرمان می‌تواند برای تخصیص خروجی تابع به کار برود: ‎var=$(myjoinfunction)‎. این مورد می‌تواند با مقدار قابل توجهی از فرمانها نیز به کار برود:

    var=$(
      command
      command
    )
  • اشکال جایگزینی فرمان آنست که تمام سطر جدید‌های انتهایی را رها می‌کند. برای راهکار عبور موقت از این مشکل صفحه جایگزینی فرمان را ببینید.

مقادیر پیش فرض یا جایگزین

قدیمی‌ترین ویژگی‌های بسط پارامتر(هر پوسته خانواده Bourne دارای شکل اصلی این ویژگیها می‌باشد) استفاده یا اختصاص مقادیر پیش‌فرض در صورت تنظیم نبودن یک پارامتر را شامل می‌گردد. اینها نسبتاً سرراست هستند:

"${EDITOR-vi}" "$filename"

اگر متغیر EDITOR تنظیم نباشد، از vi استفاده می‌شود. گونه دیگری از این بسط وجود دارد:

"${EDITOR:-vi}" "$filename"

این بسط در صورتی از vi استفاده می‌کند که متغیر EDITOR تنظیم نباشد یا تهی باشد. سابقاً به یک ترکیب دستوری نابجایی اشاره نمودیم که نیازمند پرانتزها یا فاصله برای کارکردن بود:

var='a bunch of junk089'
value=${var:(-3)}

اگر در اینجا از ‎${var:-3}‎ استفاده می‌کردیم، به عنوان «استفاده از 3 برای پیش فرض var در صورتی که تنظیم نباشد»، تفسیر می‌گردید، زیرا ترکیب دستوری اخیر قبل از اینکه bash وجود داشته باشد، مورد استفاده بوده است. از اینرو نیاز به یک راه حل عبور از آن می‌باشد.

همچنین اگر متغیر از قبل تنظیم نگردیده باشد، می‌توانیم مقدار پیش‌فرضی را به آن تخصیص بدهیم:

: "${PATH=/usr/bin:/bin}"
: "${PATH:=/usr/bin:/bin}"

در اولی اگر PATH برقرار باشد، اتفاقی رخ نمی‌دهد. اگر تنظیم نباشد، آنوقت مقدار ‎/usr/bin:/bin‎ به آن اختصاص داده می‌شود. در دستور تخصیص دوم، اگر PATH به یک مقدار تهی نیز تنظیم شده باشد، تخصیص انجام می‌شود. چون ‎${...}‎ یک عبارت است و فرمان نیست، باید در یک فرمان به کار برود. به طور سنتی، فرمان : (که هیچ کاری انجام نمی‌دهد، و یک فرمان داخلی حتی در اکثر پوسته‌های قدیمی است) برای این منظور به کار می‌رود.

سرانجام، ما این عبارت را داریم:

${var+foo}

این یک به معنی آنست که، اگر متغیر تنظیم باشد foo به کار برود و در غیر اینصورت، هیچ . این یک بررسی شرطی بینهایت ابتدایی است ، و دو مورد استفاده اصلی دارد:

  • عبارت ‎${1+"$@"}‎ برای عبور موقت از رفتار ناموفق ‎"$@"‎ در پوسته‌های قدیمی یا دارای باگ، موقع نوشتن یک WrapperScript می‌باشد.

  • یک بررسی از قبیل ‎if test "${var+defined}"‎ می‌تواند برای تعیین آنکه آیا یک متغییر تنظیم شده است به کار برود.

  • برای عبور دادن شناسه‌های اختیاری مانند: ‎cmd ${opt_x+-x "$opt_x"} ...

تقریباً هرگز خارج از این سه زمینه استفاده نمی‌شود.

See Also

بسط پارامتر (نگارش موجز، با جدول‌های مفید).


CategoryShell

پرسش و پاسخ 100 (آخرین ‎2013-07-24 16:20:42‎ توسط 188-223-3-27)