پرسش و پاسخ شماره ۷۳ - آموزش اسکریپت نویسی
X
تبلیغات
رایتل

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

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

#!/bin/bash

پرسش و پاسخ شماره ۷۳

پرسش و پاسخ شماره ۷۳

چگونه می‌توانم از بسط پارامتر استفاده کنم؟ چطور زیر رشته‌ها را ایجاد کنم؟ چگونه می‌توانم نام فایل را بدون پسوند، یا فقط پسوند فایل‌ها را به دست آورم؟

بسط پارامتر یک مبحث مهم است. این صفحه شامل یک مرور کلی و فشرده برای بسط پارامتر است.

بسط پارامتر، متغیر یا پارامتر ویژه را با مقدار آن جایگزین می‌کند. درپوسته‌های Bourne گونه، از قبیل Bash، بسط پارامتر روش اصلی ارجاع به متغیرها می‌باشد. همچنین بسط پارامتر می‌تواند برای راحتی به طور همزمان عملیات متنوعی روی کمیت‌ها انجام بدهد.

اولین مجموعه از امکانات، حذف زیر رشته از ابتدا یا انتهای پارامتر را در بر می‌گیرد. این هم یک مثال از بسط پارامتر با موردی مانند نام میزبان(اجزاء جدا شده با نقطه):

 ‎‏‎‏‏پارامتر‎               نتیجه‎‏
-----------   ------------------------------
${NAME}       polish.ostrich.racing.champion
${NAME#*.}           ostrich.racing.champion
${NAME##*.}                         champion
${NAME%%.*}   polish
${NAME%.*}    polish.ostrich.racing

و این یک مثال از بسط‌های پارامتر برای نام فایل نوعی:

 ‎‏‎‏‏پارامتر‎               نتیجه
-----------   --------------------------------------------------------
${FILE}       /usr/share/java-1.4.2-sun/demo/applets/Clock/Clock.class
${FILE#*/}     usr/share/java-1.4.2-sun/demo/applets/Clock/Clock.class
${FILE##*/}                                                Clock.class
${FILE%%/*}
${FILE%/*}    /usr/share/java-1.4.2-sun/demo/applets/Clock

ممکن است برای کاربران صفحه کلید US مشاهده اینکه در صفحه کلید "#" سمت چپ علامت "%"است، برای کمک به یادآوری، مفید باشد. "#" در طرف چپ پارامتر، و "%" در طرف راست پارامتر عمل می‌کند(مترجم: منظور برقراری ارتباط این موضوع با محل علائم روی صفحه کلید در طرفین علامت $ است یعنی آن کاراکتری که سمت چپ کاراکتر $ قرار دارد بر سمت چپ پارامتر عمل می‌کند و بالعکس). glob بعد از "%" یا "%%" یا "#" یا "##" الگویی را که باید از بسط پارامتر حذف گردد مشخص می‌کند. یک یادآورانه دیگر آنست که در جمله انگلیسی "#" معمولاً قبل از عدد می‌آید(به عنوان مثال، "The #1 Bash reference site")، در حالیکه "%" معمولاً بعد از عدد می‌آید(به عنوان نمونه، "Now 5% discounted")، پس آنها در همان جهات عمل می‌کنند.

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

#  را نگهداری می‌کند key="some value" عبارت foo‎
bar=${foo#*=\"} bar=${bar%\"*}
# را در خود دارد some value این bar حالا

اینجا مثالهای بیشتری آمده است(اما لطفاً برای لیستی از تمام ویژگی‌ها، مستندات حقیقی را ببینید!). من اینها را اخیراً اضافه کردم چندان که دوباره اشخاص wiki را با افزودن پرسشهایی که این موارد به آنها پاسخ می‌دهد، تجزیه نکنند.

${string:2:1}   #  (0, 1, 2 = third) سومین کاراکتر از رشته‎
${string:1}     #  شروع از دومین کاراکتر رشته‎
                #  است ${string#?} توجه: این معادل‎
${string%?}     #  رشته با حذف آخرین کاراکترش
${string: -1}   #  آخرین کاراکتر رشته
${string:(-1)}  #  ترکیب جایگزین، آخرین کاراکتر رشته‎
                #  معنی کاملاً متفاوتی دارد، پایین را نگاه کنید string:-1 توجه کنید
${file%.mp3}    #  .mp3 نام فایل بدون پسوند
                #  for file in *.mp3; do ...بسیار سودمند برای حلقه‌ای به شکل 
${file%.*}      #  نام فایل بدون آخرین پسوند
${file%%.*}     #  نام فایل بدون تمام پسوندها
${file##*.}     #  فقط پسوند فایل، با فرض اینکه فقط یکی باشد‎ 
		#  بسط داده می‌شود ${file}وگرنه به  

مثالهای دستکاری نام فایل

در اینجا یک روش سازگار با Posix برای گرفتن نام مسیر کامل، استخراج قسمت تشکیل‌دهنده دایرکتوری، نام فایل، فقط پسوند، نام فایل بدون پسوند(بدنه)، هر جزء عددی واقع شده در انتهای بدنه(صرف نظر از ارقام واقع در وسط نام فایل)، انجام محاسبات عددی روی آن عدد(در این وضعیت افزودن یک به آن)، و دوباره به هم بستن قطعات و افزودن یک پیشوند(در اینجا _New) به نام فایل و جایگزین کردن عدد انتها با آن عدد جدید، آمده است.

FullPath=/path/to/name4afile-00809.ext # نتیجه=> #  /path/to/name4afile-00809.ext
Filename=${FullPath##*/}                             #   name4afile-00809.ext
PathPref=${FullPath%"$Filename"}                     #   /path/to/
FileStub=${Filename%.*}                              #   name4afile-00809
FileExt=${Filename#"$FileStub"}                      #   .ext
FnumPossLeading0s=${FileStub##*[![:digit:]]}         #   00809
FnumOnlyLeading0s=${FnumPossLeading0s%%[!0]*}        #   00
FileNumber=${FnumPossLeading0s#"$FnumOnlyLeading0s"} #   809
NextNumber=$(( FileNumber + 1 ))                     #   810
FileStubNoNum=${FileStub%"$FnumPossLeading0s"}       #   name4afile-
NewFullPath=${PathPref}New_${FileStubNoNum}${FnumOnlyLeading0s}${NextNumber}${FileExt}
‎#        ‏نتیجه نهایی این است===>             ‎#   /path/to/New_name4afile-00810.ext

توجه کنید که اگر‎ $FullPath ‎ برابر ‎ "SomeFilename.ext"‎ یا بعضی نام مسیرهای دیگرِ بدون یک کاراکتر ‎/ باشد، تلاش برای بدست آوردن جزء سازنده دایرکتوری نام مسیر ‎PathPref="${FullPath%/*}"‎ در بازگرداندن یک رشته تهی ناموفق خواهد شد. به طور مشابه، کوشش برای به دست آوردن پسوند با استفاده از‎ FileExt="${Filename#*.}"‎ در صورتی که ‎ $Filename‎ دارای نقطه (و بنابراین پسوند) نباشد، در بازگرداندن رشته تهی ناموفق خواهد شد.

همچنین توجه نمایید که پاک کردن صفرهای مقدم از جزء عددی ‎ $FileNumber به منظور انجام محاسبه لازم است، وگرنه به عنوان عدد اُکتال تفسیر می‌شود. به طور جایگزین، شخصی می‌تواند برای تحمیل نمودن مبنای 10 یک ‎10#‎ را به عنوان پیشوند اضافه کند. در مثال فوق، تلاش برای انجام محاسبه ‎$(( FnumPossLeading0s + 1 ))‎ منجر به یک خطا می‌گردد چون "00809" یک عدد اُکتای معتبر نیست. اگر ما به جای آن از "00777" استفاده کرده بودیم، خطایی به وجود نمی‌آمد، اما ‎$(( FnumPossLeading0s + 1 ))‎ منجر به "1000" می‌گردید(چون اُکتای ‎ 777 + 1‎ می‌شود اکتای ‎1000‎) که احتمالاً آنچه می‌خواستیم نبود. صفحه عبارت محاسباتی را ببینید.

نقل‌قولی کردن در تخصیص متغیر لازم نمی‌باشد، چون تفکیک کلمه رخ نمی‌دهد. از طرف دیگر، متغیرهایی که داخل بسط پارامتر به آنها رجوع می‌شود، لازم است نقل‌قولی بشوند(برای مثال، نقل‌قول ‎$Filename‎ در ‎PathPref=${FullPath%"$Filename"}‎ ) وگرنه هر کاراکتر * یا ‎? یا کاراکترهای دیگری از این قبیل در داخل نام فایل به طور ناصحیح بخشی از بسط پارامتر می‌شوند(برای مثال، یک ستاره اولین کاراکتر نام فایل باشد --امتحان کنید ‎FullPath="dir/*filename"‎ ).

مثال فوق در صورتی که تعداد ارقام حاصل از انجام محاسبه ‎$NextNumber‎ با تعداد ارقام عدد اولیه متفاوت باشد ناموفق می‌شود. اگر ‎$FullPath‎ به صورت "filename099" بود، آنوقت ‎$NewFullPath‎ به "New_filename0100" یعنی نام فایلی با یک رقم طولانی‌تر، تبدیل می‌گردید.

Bash 4

‎Bash 4‎ تعدادی بسط پارامتر اضافه ارائه می‌کند، تبدیل به حالت بزرگ ‎(^)‎ و تبدیل به حال کوچک حروف ‎(,)‎.

# string='hello, World!'
 ‎‏‎‏‏پارامتر‎                                نتیجه
-----------   --------------------------------------------------------
${string^}    Hello, World! # کاراکتر اول به حالت بزرگ تبدیل می‌شود
${string^^}   HELLO, WORLD! # تمام کاراکترها به حروف بزگ تبدیل می‌شوند
${string,}    hello, World! # کاراکتر اول به حرف کوچک تبدیل می‌شود
${string,,}   hello, world! # تمام کاراکترها به حالت کوچک حروف تبدیل می‌شوند

بسط پارامتر روی آرایه‌ها

آرایه‌های BASH به طور قابل ملاحظه‌ای انعطاف پذیر می‌باشند، به علت اینکه آنها با سایر بسط‌های پوسته خوب یکپارچه می‌شوند. هر بسط پارامتری که بتواند روی یک عنصر غیر آرایه‌ای یا عنصر انفرادی آرایه انجام بشود، می‌تواند به طور یکسان روی کل یک آرایه یا مجموعه‌ای از پارامترهای مکانی اِعمال گردد، به طوری که تمام عناصر بدون یک عمل اضافی ترسیم به سوی هر عنصر، در یک نوبت بسط داده می‌شوند. این عمل با بسط پارامترها به شکل @، *، ‎arrayname[@]‎ و ‎arrayname[*]‎ انجام می‌شود. این حیاتی است که این بسط های خاص به طور صحیح نقل‌قولی بشوند -تقریباً همیشه منظور نقل‌قول دوگانه است(به عنوان مثال ‎"$@"‎ یا ‎"${cmd[@]}"‎) - به طوریکه با عناصر، صرف نظر از محتوای آنها به صورت کلمات لفظی جداگانه رفتار می‌گردد. برای مثال، ‎arr=("${list[@]}" foo)‎ با تمام عناصر آرایه list به طور صحیح رفتار می‌کند.

اول بسط ها:

$ a=(alpha beta gamma)  # تخصیص آرایه اصلی از طریق تخصیص مرکب
$ echo "${a[@]#a}"      # از ابتدای هر عنصر 'a' جدا کردن
lpha beta gamma
$ echo "${a[@]%a}"      # از انتها
alph bet gamm
$ echo "${a[@]//a/f}"   # جایگزینی
flphf betf gfmmf

بسط های زیر( جایگزینی در ابتدا یا انتها) برای بخش بعدی خیلی مفید هستند:

$ echo "${a[@]/#a/f}"   # f با a جایگزینی
flpha beta gamma
$ echo "${a[@]/%a/f}"   # در انتها
alphf betf gammf

از اینها برای پیشوند یا پسوند کردن به هر عضو استفاده می‌کنیم:

$ echo "${a[@]/#/a}"    # درج نمودن در ابتدا
aalpha abeta agamma     # (برای این مورد ‎floyd-n-milan‎ با تشکر از)
$ echo "${a[@]/%/a}"    # درج در انتها
alphaa betaa gammaa

این یکی با تعویض نمودن یک رشته تهیِ ابتدا یا انتها با مقداری که می‌خواهیم درج نماییم، کار می‌کند.

سرانجام، یک مثال سریع از چگونگی امکان استفاده از اینها در اسکریپت، فرض کنید برای اضافه نمودن پیشوند تعیین شدهِ کاربر به هر عنصر:

$ PFX=inc_
$ a=("${a[@]/#/$PFX}")
$ echo "${a[@]}"
inc_alpha inc_beta inc_gamma

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

این پارامتر ویژه @ همچنین می‌تواند به عنوان یک آرایه برای مقاصد بسط‌های پارامتر به کار برود:

${@:(-2):1}             # پارامتر دوم تا آخر‎
${@: -2:1}              # ترکیب جایگزین

به هرحال، از این ‎${@:-2:1}‎ نمی‌توانید استفاده کنید(به فضای سفید توجه کنید)، زیرا با ترکیب دستوری که در بخش بعدی تشریح می‌شود تصادم می‌کند.

قابلیت حمل

پوسته Bourne اولیه(هفتمین نگارش یونیکس)فقط از یک مجموعه خیلی محدود گزینه‌های بسط پارامتر پشتیبانی می‌کرد:

${var-word}       # را "word" را به کار می‌برد وگرنه  var تعریف شده باشد، var اگر
${var+word}       # را به کار می‌برد، وگرنه هیچ "word" تعریف شده باشد  var اگر 
${var=word}       # ... و "word" را به کار می‌برد، وگرنه var تعریف شده باشد var اگر‎
                  # اختصاص می‌دهد var را به "word" همچنین‎
${var?error}      # استفاده می‌کند وگرنه var تعریف شده باشد var اگر‎
                  # را چاپ و خارج می‌شود "error"

اینها تنها بسط های کاملاً قابل حمل معتبر هستند.

پوسته‌های POSIX (به همان خوبی پوسته Korn و BASH) آنها را ارائه می‌کنند، به علاوه یک تفاوت جزئی:

${var:-word}      # به کار می‌رودword وگرنه var تعریف شده باشد و تهی نباشد var اگر‎
                  # و غیره ${var:+word} به طور مشابهی برای

POSIX و Korn(تمام نسخه‌ها) و Bash همگی ازبسط های ‎${var#word}‎ و ‎${var%word}‎ و ‎${var##word}‎ و ‎${var%%word}‎ پشتیبانی می‌کنند.

ksh88 از ‎${var/replace/with}‎ یا ‎${var//replace/all}‎ پشتیبانی نمی‌کند، اما ksh93 و Bash پشتیبانی می‌کنند.

ksh88 از بسط ذوقی با آرایه(به عنوان مثال ‎${a[@]%.gif}‎) پشتیبانی نمی‌کند، اما ksh93 و Bash پشتیبانی می‌کنند.

ksh88 ازسَبک‎ arr=(...)‎ تخصیص مرکب پشتیبانی نمی‌کند. یکی از موارد، ‎set -A arrayname -- elem1 elem2 ...‎، یا تخصیص هر عنصر جداگانه با ‎arr[0]=val1 arr[1]=val2 ...‎ را به کار می‌برد.


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