فصل ‎15‎- فرمانهای داخلی و Builtinها

هر builtin یک فرمان گنجانده شده در داخل مجموعه ابزار Bash است، به طور لفظی built in (توکار). این یا به دلایل افزایش کارایی است -- builtinها سریع‌تر از فرمانهای خارجی، که معمولاً به انشعاب ‎[1]‎ یک پردازش جداگانه نیاز دارند، اجرا می‌گردند -- یا به علت آنکه یک builtin خاص نیازمند دسترسی مستقیم به داخلی‌های پوسته است.

یک ‎builtin‎ ممکن است مترادفی برای یک فرمان سیستم با همان نام باشد، که Bash دوباره آن را به طور درونی پیاده‌سازی نموده. برای مثال، فرمان echo در Bash همان ‎/bin/echo‎ نیست، اگرچه رفتار آنها تقریباً یکسان است.

#!/bin/bash

echo "This line uses the \"echo\" builtin."
/bin/echo "This line uses the /bin/echo system command."

یک کلمه‌کلیدی، یک واژه، نشانه، یا عملگری ذخیره شده است. کلمه‌کلیدی‌ها برای پوسته دارای معنای ویژه‌ای هستند، و در واقع بلوک‌های ساختمان گرامر پوسته هستند. به عنوان مثال، for‏، while‏، do‏‏، و !‏ کلمه کلیدی هستند. همانند یک builtin، کد یک کلمه‌کلیدی نیز در داخل Bash جاسازی گردیده است، اما برخلاف یک builtin، یک کلمه‌کلیدی به خودی خود یک فرمان نیست، بلکه یک جزء تشکیل دهنده یک ساختار دستوری است.[2]

ورودی-خروجی

echo

یک عبارت یا متغیر را (در ‎stdout‎) چاپ می‌کند ( مثال ‎4-1‎ را ببینید).

echo Hello
echo $a

echo برای چاپ کاراکترهای ‎escape‎ شده، به یک گزینه ‎-e‎ احتیاج دارد. مثال ‎5-2‎ را ببینید.

به طور عادی، هر فرمان echo یک سطر جدید ترمینال چاپ می‌کند، اماگزینه ‎-n‎ مانع این کار می‌گردد.

یک echo می‌تواند برای تغذیه رشته به فرمانها در یک لوله به کار برود.

if echo "$VAR" | grep -q txt   # if [[ $VAR = *txt* ]]
then
  echo "$VAR contains the substring sequence \"txt\""
fi

یک echo در تلفیق با جایگزینی فرمان می‌تواند یک متغیر را تنظیم کند.

a=`echo "HELLO" | tr A-Z a-z`

همچنین مثال ‎16-22‎، مثال ‎16-3‎، مثال ‎16-47‎، و مثال ‎16-48‎ را ببینید.

آگاه باشید که ‎echo `command`‎ هر سطرجدیدی را که خروجی command تولید نماید، حذف می‌کند.

متغیر ‎$IFS‎(جداکننده داخلی فیلد) به طور معمول شامل ‎\n‎ (تعویض سطر) به عنوان یکی از کاراکترهای مجموعه فضای سفیدش می‌باشد. بنابراین Bash خروجی command را از محل تعویض سطرها به شناسه‌هایی برای echo تجزیه می‌کند. آنوقت echo این شناسه‌ها را به صورت جدا شده با یک فاصله بیرون می‌دهد.

bash$ ls -l /usr/share/apps/kjezz/sounds
total 40
-rw-r--r--    1 root     root         1407 Nov  7  2000 reflect.au
-rw-r--r--    1 root     root          362 Nov  7  2000 seconds.au



bash$ echo `ls -l /usr/share/apps/kjezz/sounds`

total 40 -rw-r--r-- 1 root root 716 Nov 7 2000 reflect.au -rw-r--r-- 1 root root ...

بنابراین، چگونه می‌توانیم یک سطرجدید را داخل یک رشته کاراکتر echo شده، جاسازی نماییم؟

#               جاسازی یک linefeed؟
echo "Why doesn't this string \n split on two lines?"
#                    .تجزیه نمی‌شود

# .بیاید مورد دیگری را امتحان کنیم

echo
	     
echo $"A line of text containing
a linefeed."
# به صورت دو سطر جدا(با ‎linefeed‎ جاسازی شده) چاپ می‌گردد.
#               اما، آیا واقعاً پیشوند متغیر ‎"$"‎ لازم است؟

echo

echo "This string splits
on two lines."
#                        خیر، به پیشوند ‎"$"‎ نیازی نیست.

echo
echo "---------------"
echo

echo -n $"Another line of text containing
a linefeed."
# به عنوان دو سطر مجزا (با ‎linefeed‎ جاسازی شده) چاپ می‌گردد.
#  حتی گزینه ‎-n‎ در اینجا در خنثی کردن تعویض سطر ناموفق است.

echo
echo
echo "---------------"
echo
echo

# .اما، مورد پایین آنطور که انتظار می‌رود عمل نمی‌کند
#         چرا عمل نمی‌کند؟ اشاره: تخصیص به یک متغیر.
string1=$"Yet another line of text containing
a linefeed (maybe)."

echo $string1
# Yet another line of text containing a linefeed (maybe).
#                                    ^
#        .تعویض سطر به فاصله تبدیل گردیده است

# با تشکر از ‎Steve Parker‎ برای توضیح این مورد.

این فرمان یک builtin پوسته است، و همان ‎/bin/echo‎ نیست، هرچند که رفتارش همانند آن است.

bash$ type -a echo
echo is a shell builtin echo is /bin/echo


printf

فرمان printf، چاپ قالب‌‌دار، یک echo ارتقاء یافته است. این فرمان نوع محدود شده‌ای از تابع کتابخانه‌ای ‎printf()‎ زبان C است، و ترکیب دستوری آن تا اندازه‌ای متفاوت است.

printf format-string ... parameter...

این نگارش builtin پوسته Bash از فرمان ‎/bin/printf‎ یا ‎/usr/bin/printf‎ است. برای پوشش عمیق‌تر صفحه man (فرمانِ سیستم) printf را ببینید.

Caution

نگارش‌های قدیمی‌تر Bash ممکن است از printf پشتیبانی نکنند.

مثال ‎15-2‎. printf در عمل

#!/bin/bash
# نمونه‌نمایشی عملیات printf

declare -r PI=3.14159265358979     # .متغیر فقط خواندنی، یعنی یک ثابت
declare -r DecimalConstant=31373

Message1="Greetings,"
Message2="Earthling."

echo

printf "Pi to 2 decimal places = %1.2f" $PI
echo
printf "Pi to 9 decimal places = %1.9f" $PI     #.حتی این به طور صحیح گرد می‌کند

printf "\n"                           # یک سطرجدید چاپ می‌کند، معادل ‎'echo'‎ است.

printf "Constant = \t%d\n" $DecimalConstant     #         یک ‎tab (\t)‎ درج می‌کند.

printf "%s %s \n" $Message1 $Message2

echo

# ==========================================#
#      شبیه‌سازی تابع ‎sprintf()‎ زبان ‎C
#.ذخیره یک متغیر با یک رشته قالب‌بندی

echo 

Pi12=$(printf "%1.12f" $PI)
echo "Pi to 12 decimal places = $Pi12"      # !خطای گرد کردن

Msg=`printf "%s %s \n" $Message1 $Message2`
echo $Msg; echo $Msg

# اتفاقاً تابع sprintf  اکنون می‌تواند به عنوان یک پیمانه (module)‏
#+           قابل بارگیری به Bash اضافه گردد، اما قابل حمل نیست.

exit 0

قالب‌بندی پیغام خطاها، یک کاربرد مفید printf است.

E_BADDIR=85

var=nonexistent_directory

error()
{
  printf "$@" >&2
  # پارامترهای مکانی داده شده را قالب‌بندی و به sterr ارسال می‌نماید.
  echo
  exit $E_BADDIR
}

cd $var || error $"Can't cd to %s." "$var"

#  با تشکر از ‎S.C.‎

همچنین مثال ‎36-17‎ را ببینید.

read

مقدار یک متغیر را از stdin «می‌خواند»، یعنی ورودی را به طور محاوره‌ای از صفحه‌کلید اخذ می‌کند. گزینه ‎-a‎ اجازه می‌دهد فرمان read متغیرهای آرایه‌ای دریافت کند ( مثال ‎27-6‎ را ببینید).

مثال ‎15-3‎. تخصیص متغیر، با استفاده از read

#!/bin/bash
#.خواندن متغیرها

echo -n "Enter the value of variable 'var1': "
#                 گزینه ‎-n‎ با ‎echo، سطرجدید را موقوف می‌کند‎.

read var1
# علامت ‎$‎ در جلوی ‎var1‎ نیست، چون متغیر در حال تنظیم شدن است.

echo "var1 = $var1"


echo

#       یک جمله واحد read می‌تواند چند متغیر را تنظیم نماید.
echo -n "Enter the values of variables 'var2' and 'var3' "
echo =n "(separated by a space or tab): "
read var2 var3
echo "var2 = $var2      var3 = $var3"
#اگر فقط یک مقدار وارد کنید، سایر متغیرها تنظیم نشده ‎(null)‎ ‏باقی خواهند ماند.

exit 0

یک read بدون یک متغیر مرتبط شده، ورودی‌اش را به متغیر اختصاصی ‎$REPLY‎ تخصیص می‌دهد.

مثال ‎15-4‎. آنچه هنگام فقدان متغیرِ فرمان read روی می‌دهد

#!/bin/bash
# read-novar.sh

echo

# ----------------------------- #
echo -n "Enter a value: "
read var
echo "\"var\" = "$var""
# .اینجا همه چیز مطابق انتظار است
# ----------------------------- #

echo

# ------------------------------------------------------------------- #
echo -n "Enter another value: "
read           #      متغیری برای read  فراهم نگردیده است، بنابراین...
               #+ ورودی برای read به متغیر پیش‌فرض ‎$REPLY‎ تخصیص یافته.
var="$REPLY"
echo "\"var\" = "$var""            #     .این معادل قطعه کد نخست است
# ------------------------------------------------------------------- #

echo
echo "========================="
echo


#                این مثال مشابه اسکریپت «‎reply.sh‎» است.
#   اگر چه، آن اسکریپت نشان می‌دهد که حتی بعد از یک read
#+ مطابق معمول همراه با یک متغیر، ‎$REPLY‎ در دسترس است.


# ================================================================= #

# .در برخی موارد، شاید مایل باشید اولین مقدار خوانده شده را رها کنید
#             در چنین حالت‌هایی، به سادگی متغیر ‎$REPLY‎ را صرفنظر کنید.

{               #                     .بلوک کد
read            #    .سطر اول، دور انداخته شده
read line2      # .سطر دوم، ذخیره شده در متغیر
  } <$0
echo "Line 2 of this script is:"
echo "$line2"   #   # read-novar.sh
echo            # سطر ‎#!/bin/bash‎ رها گردیده است.

# اسکریپت ‎soundcard-on.sh‎ را نیز ملاحظه کنید.

exit 0

به طور عادی، وارد کردن یک \ در جریان ورودی برای یک read، سطر جدید را خنثی می‌کند. گزینه ‎-r‎ باعث می‌شود \ وارد شده، به طور لفظی تفسیر گردد.

مثال ‎15-5‎. ورودی چند سطری برای read

#!/bin/bash

echo

echo "Enter a string terminated by a \\, then press <ENTER>."
echo "Then, enter a second string (no \\ this time), and again press <ENTER>."

read var1     # ‎"\"‎ موقع خواندن ‎$var1‎ سطر جدید را موقوف می‌کند.
              #     first line \
              #     second line

echo "var1 = $var1"
#     var1 = first line second line

#   برای هر سطر خاتمه یافته به وسیله یک ‎\‎ شما یک اعلان روی سطر
#+     بعدی جهت ادامه تغذیه کاراکترها به  var1 دریافت می‌کنید.

echo; echo

echo "Enter another string terminated by a \\ , then press <ENTER>."
read -r var2  # گزینه ‎-r‎ باعث می‌شود ‎\‎ به طور لفظی خوانده شود.
              #     first line \

echo "var2 = $var2"
#     var2 = first line \

# ورود داده‌ها با اولین ‎<ENTER>‎ خاتمه می‌یابد.

echo 

exit 0

فرمان read دارای تعدادی گزینه جالب است که نمایش یک اعلان و حتی خواندن ضربه‌کلیدها بدون زدن ENTER را مجاز می‌کنند.

# خواندن یک ضربه کلید بدون زدن ENTER.

read -s -n1 -p "Hit a key " keypress
echo; echo "Keypress was "\"$keypress\""."

#                             گزینه ‎-s‎ یعنی ورودی را منعکس نکند.
#           گزینه ‎-n‎ یا ‎-N‎ یعنی فقط N کاراکتر از ورودی قبول کند.
# گزینه ‎-p‎ یعنی قبل از خواندن ورودی اعلان پس از آن را نمایش بدهد.

#کاربرد این گزینه‌ها، مهارت‌آمیز است، چون لازم است که آنها به ترتیب صحیح باشند.

گزینه ‎-n‎ برای read همچنین تشخیص کلیدهای جهتی و برخی از سایر کلیدهای غیرعادی را میسر می‌کند.

مثال ‎15-6‎. تشخیص کلیدهای جهت‌نما

#!/bin/bash
# ‎arrow-detect.sh:‎ کلیدهای جهت‌نما و چندتای دیگر را تشخیص می‌دهد
#       با تشکر از ‎Sandro Magi‎، برای نشان دادن چگونگی آن به من.

# --------------------------------------------
# .کدهای کاراکتر تولید شده توسط ضربه‌کلیدها
arrowup='\[A'
arrowdown='\[B'
arrowrt='\[C'
arrowleft='\[D'
insert='\[2'
delete='\[3'
# --------------------------------------------

SUCCESS=0
OTHER=65

echo -n "Press a key...  "
# اگر کلیدی که در بالا لیست نشده، فشرده شود، ممکن است زدن ‎ENTER‎ نیز لازم باشد.
read -n3 key                       #                     .خواندن سه کاراکتر

echo -n "$key" | grep "$arrowup"   # .کنترل اینکه آیا کد کاراکتر شناخته شده
if [ "$?" -eq $SUCCESS ]
then
  echo "Up-arrow key pressed."
  exit $SUCCESS
fi

echo -n "$key" | grep "$arrowdown"
if [ "$?" -eq $SUCCESS ]
then
  echo "Down-arrow key pressed."
  exit $SUCCESS
fi

echo -n "$key" | grep "$arrowrt"
if [ "$?" -eq $SUCCESS ]
then
  echo "Right-arrow key pressed."
  exit $SUCCESS
fi

echo -n "$key" | grep "$arrowleft"
if [ "$?" -eq $SUCCESS ]
then
  echo "Left-arrow key pressed."
  exit $SUCCESS
fi

echo -n "$key" | grep "$insert"
if [ "$?" -eq $SUCCESS ]
then
  echo "\"Insert\" key pressed."
  exit $SUCCESS
fi

echo -n "$key" | grep "$delete"
if [ "$?" -eq $SUCCESS ]
then
  echo "\"Delete\" key pressed."
  exit $SUCCESS
fi


echo " Some other key pressed."

exit $OTHER

# ========================================= #

#     ‎Mark Alexander‎ یک نگارش ساده شده از
#+.اسکریپت فوق را مطرح نمود (سپاس‌گزارم!)‏
# نیاز به استفاده از grep را برطرف می‌کند.

#!/bin/bash

  uparrow=$'\x1b[A'
  downarrow=$'\x1b[B'
  leftarrow=$'\x1b[D'
  rightarrow=$'\x1b[C'

  read -s -n3 -p "Hit an arrow key: " x

  case "$x" in
  $uparrow)
     echo "You pressed up-arrow"
     ;;
  $downarrow)
     echo "You pressed down-arrow"
     ;;
  $leftarrow)
     echo "You pressed left-arrow"
     ;;
  $rightarrow)
     echo "You pressed right-arrow"
     ;;
  esac

exit $?

# ========================================= #

# ‎Antonio Macchi‎ یک پیشنهاد ساده‌تر دارد.

#!/bin/bash

while true
do
  read -sn1 a
  test "$a" == `echo -en "\e"` || continue
  read -sn1 a
  test "$a" == "[" || continue
  read -sn1 a
  case "$a" in
    A)  echo "up";;
    B)  echo "down";;
    C)  echo "right";;
    D)  echo "left";;
  esac
done

# ========================================= #

#                       :تمرین
#                    ------------
# ‎(1‎ تشخیص کلیدهای ‎Home‎‏، ‎End‎‏، ‎PgUp‎‏، و ‎PgDn‎ را اضافه کنید.


#مترجم: گرچه بعید است، اما اگر پس از تلاش کافی پاسخ را ندانستید، این را بینید.

گزینه ‎-n‎ با فرمان read کلید ENTER ‏(سطر جدید) را آشکار نخواهد نمود.

گزینه ‎-t‎ با فرمان read زمان‌بندی ورودی را ممکن می‌سازد (مثال ‎9-4‎ و مثال ‎A-41‎ را ببینید).

گزینه ‎-u‎ توصیف‌گر‌فایل برای فایل مقصد را قبول می‌کند.

فرمان read همچنین می‌تواند مقدار متغیرش را از یک فایل تغییر مسیر یافته به stdin «بخواند». اگر فایل محتوی بیش از یک سطر باشد، تنها سطر اول به متغیر تخصیص داده می‌شود. اگر read دارای بیش از یک پارامتر باشد، آنوقت به هر یک از این پارامترها یک رشته متعاقب فضای سفید مشخص‌شده تخصیص داده می‌شود. توجه کنید!

مثال ‎15-7‎. استفاده از read با تغییر مسیر فایل

#!/bin/bash

read var1 <data-file
echo "var1 = $var1"
#     ‎var1‎ به تمام سطر نخست فایل ورودی «‎data-file‎» تنظیم می‌گردد.

read var2 var3 <data-file
echo "var2 = $var2   var3 = $var3"
#               به رفتار ظاهرا نامفهوم read در اینجا توجه نمایید.
#                             ‎(1‎ به ابتدای فایل ورودی باز می‌گردد.
#   ‎(2‎ اکنون هر متغیری به جای تمام سطر، به یک رشته متناظر جدا شده
#                                    با فضای سفید تنظیم می‌شود.
#                 ‎(3‎ متغیر انتهایی باقیمانده سطر را دریافت می‌کند.
#     ‎(4‎ اگر تعداد متغیرها بیش از رشته‌های جدا شده با فضای سفید در
#         سطر اول باشد، آنوقت متغیرهای اضافی تهی باقی می‌مانند.

echo "------------------------------------------------"

#                چگونگی رفع اشکال فوق با یک حلقه:
while read line
do
  echo "$line"
done <data-file
#  با تشکر از ‎Heiner Steven‎ برای توضیح این مورد.

echo "------------------------------------------------"

#     جهت تجزیه سطر ورودی برای فرمان ‎read‎، در صورتیکه نمی‌خواهید فضای‌سفید
#              پیش‌فرض باشد، از ‎$IFS‎ ‏(جداکننده‌فیلد داخلی) استفاده نمایید.

echo "List of all users:"
OIFS=$IFS; IFS=:     # فایل ‎/etc/passwd‎ از جداکننده‌فیلد ‎:‎ استقاده می‌کند.
while read name passwd uid gid fullname ignore
do
  echo "$name ($fullname)"
done </etc/passwd    #     .تغییر مسیر ورودی-خروجی
IFS=$OIFS            #          بازیابی ‎$IFS‎ اولیه.
#           این خُرده کد نیز توسط ‎Heiner Steven‎ است.



#   تنظیم متغیر ‎$IFS‎ در داخل خود حلقه نیاز به ذخیره
#+       ‎$IFS‎ اصلی در یک متغیر موقتی را برطرف می‌کند.
# با تشکر از ‎Dim Segebart‎ برای توضیح دادن این مورد.
echo "------------------------------------------------"
echo "List of all users:"

while IFS=: read name passwd uid gid fullname ignore
do
  echo "$name ($fullname)"
done </etc/passwd       # .تغییر مسیر ورودی-خروجی

echo
echo "\$IFS still $IFS"

exit 0

لوله‌کشی خروجی به یک فرمان read، با استفاده از echo برای تنظیم متغیرها، ناموفق خواهد شد.

با این وجود، به نظر می‌رسد لوله کشی خروجی cat کار می‌کند.

cat file1 file2 |
while read line
do
echo $line
done

با این حال، به طوریکه ‎Bjön Eriksson‎ نشان می‌دهد:

مثال ‎15-8‎. مشکلات خواندن از یک لوله

#!/bin/sh
# readpipe.sh
# این مثال توسط ‎Bjon Eriksson‎ اهدا گردیده است.

### shopt -s lastpipe

last="(null)"
cat $0 |
while read line
do
    echo "{$line}"
    last=$line
done

echo
echo "++++++++++++++++++++++"
printf "\nAll done, last: $last\n" 
        #    اگر سطر 5 را از حالت توضیح خارج کنید
        #+             .خروجی سطر فوق تغییر می‌کند
        #   (نیازمند ‎Bash‎ نگارش ‎4.2 یا بالاتر است.)

exit 0  #                              .انتهای کد
        #   .خروجی (قسمتی) اسکریپت در ادامه می‌آید
        # ابروهای اضافی را فرمان ‎echo‎ تامین می‌کند.

#############################################
bash$ ./readpipe.sh
{#!/bin/sh}
{last="(null)"}
{cat $0 |}
{while read line}
{do}
{echo "{$line}"}
{last=$line}
{done}
{printf "nAll done, last: $lastn"}

All done, last: (null)
متغیر ‎(last)‎ در داخل حلقه-پوسته فرعی تنظیم می‌گردد اما مقدارش در خارج از حلقه پابرجا نمی‌ماند. #############################################

اسکریپت gendiff، که معمولاً روی اکثر توزیع‌های لینوکس در ‎/usr/bin‎ یافت می‌شود، خروجی find را به یک ساختار ‎while read‎ لوله‌کشی می‌کند.

find $1 \( -name "*$2" -o -name ".*$2" \) -print |
while read f; do
. . .


tip

paste کردن متن به داخل فیلد ورودی یک read امکان‌پذیر است (اما نه سطرهای چندتایی!). مثال ‎A-38‎ را ببینید.

سیستم فایل

cd

فرمان آشنای تعویض دایرکتوری cd، در جایی از اسکریپت‌ها مورد استفاده می‌یابد که اجرای یک فرمان نیازمند قرار داشتن در یک دایرکتوری معین است.

(cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
[‎از مثال قبلاً ذکر شده ‎Alan Cox]

گزینه ‎-P‎ ‏‎(physical)‎ نزد cd باعث صرفنظر کردن این فرمان از پیوندهای نمادین می‌گردد.

‎cd -‎ به ‎$OLDPWD‎، یعنی دایرکتوری کاری قبلی تغییر مکان می‌دهد.

Caution

فرمان cd موقع حضور دو کاراکتر // بعد از آن، آنطور که انتظار می‌رود عمل نمی‌کند.

bash$ cd //
bash$ pwd
//
البته خروجی باید / باشد. این مشکل هم در خط فرمان و هم در داخل اسکریپت وجود دارد.


pwd

چاپ دایرکتوری کاری ‎(Print Working Directory)‎. این فرمان دایرکتوری جاری کاربر (یا اسکریپت) را ارایه می‌کند ( مثال ‎15-9‎ را ببینید). اثر آن معادل خواندن مقدار متغیر درونی ‎‎$PWD‎ است.

pushd, popd, dirs

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

‎pushd dir-name‎ مسیر ‎dir-name‎ را در درون پشته دایرکتوری جای می‌دهد (در بالای پشته) و به طور همزمان دایرکتوری کاری جاری را به ‎dir-name‎ تغییر می‌دهد.

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

dirs محتویات پشته دایرکتوری را لیست می‌کند (این را با متغیر ‎$DIRSTACK‎ قیاس نمایید). یک فرمان موفق pushd یا popd به طور خودکار فرمان dirs را فراخوانی می‌کند.

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

مثال ‎15-9‎. تغییر دایرکتوری کاری جاری

#!/bin/bash

dir1=/usr/local
dir2=/var/spool

pushd $dir1
# یک dirs هم اجرا خواهد نمود (لیست کردن پشته دایرکتوری در خروجی استاندارد).
echo "Now in directory `pwd`."
       # از ‎'pwd'‎ پوشش یافته با نقل‌قول برعکس استفاده می‌کند.

       #    اکنون، مواردی را در دایرکتوری ‎dir1‎ انجام بدهید.
pushd $dir2
echo "Now in directory `pwd`."

       #        حالا، کارهایی در دایرکتوری ‎dir2‎ انجام بدهید.
echo "The top entry in the DIRSTACK array is $DIRSTACK."
popd
echo "Now back in directory `pwd`."

       #  اینک، کارهای دیگری در دایرکتوری ‎dir1‎ انجام بدهید.
popd
echo "Now back in original working directory `pwd`."

exit 0

#  چه اتفاقی رخ می‌دهد اگر ‎popd‎ نکنید، سپس اسکریپت خارج شود؟
#                     سرانجام در کدام دایرکتوری هستید؟ چرا؟

متغیرها

let

فرمان let عملیات حسابی بر روی متغیرها را انجام می‌دهد. [3] در موارد بسیاری، مانند یک نگارش کمتر پیچیدهِ expr عمل می‌کند.

مثال ‎15-10‎. انجام عملیات حساب توسط let.

#!/bin/bash

echo

let a=11                #             همانند با ‎a=11‎
let a=a+5               #   معادل با let "a = a + 5"‎
                        # (نقل‌قول‌های دوگانه و فاصله‌ها آن را خواناتر می‌سازند)
echo "11 + 5 = $a"      # 16

let "a <<= 3"           #  معادل با ‎let "a = a << 3"‎
echo "\"\$a\" (=16) left-shifted 3 places = $a"
                        # 128

let "a /= 4"            #   معادل با ‎let "a = a / 4"‎
echo "128 / 4 = $a"     # 32

let "a -= 5"            #   معادل با ‎let "a = a - 5"‎
echo "32 - 5 = $a"      # 27

let "a *=  10"          #      معادل با ‎"a = a * 10"‎
echo "27 * 10 = $a"     # 270

let "a %= 8"            #   معادل با ‎let "a = a % 8"‎
echo "270 modulo 8 = $a  (270 / 8 = 33, remainder $a)"
                        # 6


#  آیا let عملیات سبکِ ‎C‎ را اجازه می‌دهد؟ بله، درست مانند
#        ساختار پرانتزهای دوتایی ‎(( ... ))‎ اجازه می‌دهد.

let a++                 # افزایش ‎C-style‎.
echo "6++ = $a"         # 6++ = 7
let a--                 # کاهش ‎C-style‎.
echo "7-- = $a"         # 7-- = 6
# البته، ‎++a‎‏، و غیره نیز اجازه داده می‌شوند.
echo


#                                         عملگر سه‌گانه.

# توجه نمایید که ‎$a‎ برابر با ‎6‎ است، بالا را مشاهده کنید.
let "t = a<7?7:11"   #   شرط درست
echo $t  # 7

let a++
let "t = a<7?7:11"   # شرط نادرست
echo $t  #     11

exit

Caution

فرمان let در برخی مضمون‌ها می‌تواند یک وضعیت خروج شگفت‌آور برگشت بدهد.

# ‎Evgeniy Ivanov‎ توضیح می‌دهد:

var=0
echo $?     # 0
            # .همانطور که انتظار می‌رود

let var++
echo $?     # 1
            # فرمان موفق بود، پس چرا ‎$?=0‎ نیست؟؟؟
            # !غیر متعارف

let var++
echo $?     # 0
            # .مطابق انتظار


# . . . همچنان

let var=0
echo $?     # 1
            # فرمان موفق بود؟، پس چرا ‎$?=0‎ نیست؟؟؟

#     به هر حال، به طوری که ‎Jeff Gorak‎ توضیح می‌دهد،
#+      این بخشی از طراحی مخصوص برای ‎let‎ است . . .
#   «اگر آخرین شناسه صفر ارزیابی گردد، ‎let‎ مقدار ‎1‎
# رابرگشت می‌دهد، وگرنه صفر برگشت می‌دهد» ‎[help let]‎


eval

eval arg1 [arg2] ... [argN]

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

tip فرمان eval می‌تواند از خط فرمان یا داخل یک اسکریپت برای تولید کد به کار برود.

bash$ command_string="ps ax"
bash$ process="ps ax"
bash$ eval "$command_string" | grep "$process"
26973 pts/3    R+     0:00 grep --color ps ax 
26974 pts/3    R+     0:00 ps ax

هر فراخوانی eval یک ارزیابی مجدد شناسه‌هایش را تحمیل می‌کند.

a='$b'
b='$c'
c=d

echo $a             #  $b
                    #  .سطح اول
eval echo $a        #  $c
                    #  .سطح دوم
eval eval echo $a   #  d
                    #  .سطح سوم

#  با تشکر از ‎E. Choroba‎


مثال ‎15-11‎. نمایش اثر eval

#!/bin/bash
# به کار گیری ‎"eval"‎ ...

y=`eval ls -l`  # مشابه با ‎y=`ls -l`‎ است، اما چون متغیر «echo» شده
echo $y         #+         غیرنقل‌قولی است، تعویض سطرها حذف می‌شوند.
echo
echo "$y"       # وقتی متغیر نقل‌قولی شود، تعویض سطرها حفظ می‌گردند.

echo; echo

y=`eval df`     #              مشابه با ‎y=`df`‎
echo $y         #+ اما تعویض سطرها حذف شده‌اند.

#وقتی ‎LFها حفظ نشده‌اند، ممکن است تجزیه خروجی با استفاده
#+        از برنامه‌های سودمندی از قبیل ‎awk‎ آسان‌تر باشد.

echo
echo "==========================================================="
echo

eval "`seq 3 | sed -e 's/.*/echo var&=ABCDEFGHIJ/'`"
# var1=ABCDEFGHIJ
# var2=ABCDEFGHIJ
# var3=ABCDEFGHIJ

echo
echo "==========================================================="
echo


#اکنون، نشان دادن چگونگی انجام کار سودمند با ‎eval‎ . . .
#                              (با تشکر از ‎E. Choroba‎!)‏

version=3.4    # آیا می‌توانیم شماره نسخه را با یک فرمان
               #+       به قسمت اصلی و فرعی تجزیه کنیم؟
echo "version = $version"
eval major=${version/./;minor=}    #.‎ در شماره نگارش را با ‎;minor=‎ تعویض می‌کند
                                   #     در اثر جایگزینی ‎3; minor=4‎ حاصل می‌شود
                                   #+  پس ‎eval‎ روی ‎minor=4, major=3‎ عمل می‌کند.
echo Major: $major, minor: $minor  #   Major: 3, minor: 4

مثال ‎15-12‎. کاربرد eval برای انتخاب از میان متغیرها

#!/bin/bash
# arr-choice.sh

# عبور دادن شناسه‌ها به یک تابع جهت انتخاب نمودن
#+              .یک متغیر بخصوص از میان یک گروه

arr0=( 10 11 12 13 14 15 )
arr1=( 20 21 22 23 24 25 )
arr2=( 30 31 32 33 34 35 )
#       ‎0  1  2  3  4  5‎      شماره عضو (شاخص‌گذاری از صفر)‏


choose_array ()
{
  eval array_member=\${arr${array_number}[element_number]}
  #                 ^       ^^^^^^^^^^^^
  #  کاربرد ‎eval‎ برای ساختن نام یک متغیر، در 
  #+            .این مورد خاص، نام یک آرایه

  echo "Element $element_number of array $array_number is $array_member"
} # .تابع می‌تواند برای پذیرش پارامترها بازنویسی گردد

array_number=0    # .آرایه اول
element_number=3
choose_array      # 13

array_number=2    # .آرایه سوم
element_number=4
choose_array      # 34

array_number=3    # آرایه تهی (‎arr3‎ تخصیص نیافته است).
element_number=4
choose_array      # (null)

#       با تشکر از ‎Antonio Macchi‎ برای توضیح این مورد.

مثال ‎15-13‎.‏ Echo کردن پارامترهای خط فرمان


#!/bin/bash
# echo-params.sh

#         .این اسکریپت را با تعدادی پارامتر خط‌فرمان فراخوانی کنید
#                                                      :برای مثال
# sh echo-params.sh first second third fourth fifth

params=$#              #                .تعداد پارامترهای خط‌فرمان
param=1                #             .شروع از پارامتر اول خط‌فرمان

while [ "$param" -le "$params" ]
do
  echo -n "Command-line parameter "
  echo -n \$$param     #       فقط «نام» متغیرها را ارایه می‌کند.
#         ^^^          #  $1, $2, $3, etc.
                       #                                    چرا؟
#  ‎\$‎ اولین ‎$‎ را معاف ‎(eacape)‎می‌کند، پس به طور لفظی بازتاب می‌یابد
#+ و ‎$param‎  همانطور که انتظار می‌رود به محتوای ‎$param‎ بسط می‌یابد.
  echo -n " = "
  eval echo \$$param   #             .مقدار متغیر را ارایه می‌کند
# ^^^^      ^^^        #           ‎eval‎ بر «ارزیابی» ‎\$$‎ به عنوان
                       #+  .یک ارجاع متغیر غیرمستقیم تمرکز می‌کند

(( param ++ ))         #                    .پیشروی به مورد بعدی
done

exit $?

# =================================================
bash$ sh echo-params.sh first second third fourth fifth

Command-line parameter $1 = first
Command-line parameter $2 = second
Command-line parameter $3 = third
Command-line parameter $4 = fourth
Command-line parameter $5 = fifth

مثال ‎15-14‎. اجبار به یک ‎log-off‎


#!/bin/bash
#       کشتن ‎ppp‎ برای تحمیل یک ‎log-off‎ اجباری.
#                    البته برای ارتباط ‎dialup‎

# .اسکریپت باید به عنوان کاربر ارشد اجرا بشود

SERPORT=ttyS3
#   وابسته به سخت‌افزار و حتی نگارش کرنل، درگاه مودم شما
#+می‌تواند متفاوت باشد، و ‎/dev/ttyS1‎ یا ‎/dev/ttyS2‎ باشد.


killppp="eval kill -9 `ps ax | awk '/ppp/ { print $1 }'`"
#                     -------- ‎ID‎ پردازش ‎ppp‎ -------  

$killppp                     #      .اکنون این متغیر یک فرمان است


#                  .عملیات زیر باید به عنوان کاربر ارشد انجام شود

chmod 666 /dev/$SERPORT      #  بازیابی مجوزهای ‎r+w‎ یا مورد دیگری؟
#  چون انجام یک ‎SIGKILL‎ بر ‎ppp‎ مجوزهای درگاه سریال را تغییر می‌دهد،
#+                         .ما مجوزها را به حالت قبل بر می‌گردانیم

rm /var/lock/LCK..$SERPORT   #     حذف فایل قفل درگاه سریال. چرا؟

exit $?

#                              :تمرین‌ها
#                             ----------
# ‎(1‎ وادار نمودن اسکریپت به کنترل آنکه آیا کاربر ارشد آنرا احضار می‌کند.
# ‎(2‎ قبل از اقدام به کشتن پردازش، کنترلی انجام بدهید تا پردازشی که باید
#+                                     کشته شود واقعاً در حال اجرا باشد.   
#           ‎(3‎ یک نگارش جایگزین برای این اسکریپت بر اساس ‎fuser‎ بنویسید:
#+ if [ fuser -s /dev/modem ]; then . . .

مثال ‎15-15‎. یک نگارش از ‎‎rot13‎‎‎[‎۱‎]‎


#!/bin/bash
# نگارشی از ‎rot13‎ با استفاده از ‎eval‎.
#       با مثال ‎rot13.sh‎ مقایسه کنید.

setvar_rot_13()              # "rot13" scrambling
{
  local varname=$1 varvalue=$2
  eval $varname='$(echo "$varvalue" | tr a-z n-za-m)'
}


setvar_rot_13 var "foobar"   #  تخصیص ‎foobar‎ از طریق ‎rot13‎
echo $var                    # sbbone

setvar_rot_13 var "$var"     #  تخصیص ‎sbbone‎ از طریق ‎rot13‎
                             #       .برگشت به متغیر اصلی
echo $var                    # foobar

# مثال نوشته ‎Stephane Chazelas است که توسط نگارنده ویرایش گردیده‎.

exit 0

این هم یک مثال دیگر از کاربرد eval برای ارزیابی یک عبارت پیچیده، این مثال از یک نگارش قدیمی‌تر اسکریپت بازی ‎Tetris‎ نوشته YongYe است.

eval ${1}+=\"${x} ${y} \"

مثال ‎A-53‎ از eval برای تبدیل عناصر آرایه به یک لیست فرمان استفاده می‌کند.

فرمان eval در روش قدیمی‌تر ارجاع غیرمستقیم دیده می‌شود.

eval var=\$$var

tip

فرمان eval می‌تواند برای پارامتردار کردن بسط ابرو به کار برود.

Caution

فرمان eval می‌تواند دارای خطر باشد، و به طور معمول موقعی که یک جایگزین معقول وجود دارد باید از آن پرهیز نمود. یک ‎eval $COMMANDS محتویات COMMANDS را اجرا می‌کند، که ممکن است شامل موارد ناگوار حیرت‌انگیزی از قبیل ‎rm -rf *‎ باشد. اجرای یک eval روی کُد ناشناس نوشته شده توسط افراد ناشناخته، رفتاری مخاطره‌آمیز است.


set

فرمان set مقدار متغیرها/گزینه‌های داخلی اسکریپت را تغییر می‌دهد. یک مورد استفاده آن برای تغییر وضعیت گزینه نشانگرهایی است که به تعیین رفتار اسکریپت کمک می‌کنند. یک کاربرد دیگر برای آن، بازنشاندن پارامترهای مکانی که یک اسکریپت به عنوان نتیجه یک فرمان مشاهده می‌کند ‎(set `command`)‎. آنوقت اسکریپت می‌تواند فیلدهای خروجی فرمان را تجزیه کند.

مثال ‎15-16‎. استفاده از set با پارامترهای مکانی

#!/bin/bash
# ex34.sh
# Script "set-test"

# این اسکریپت را با سه پارامتر خط‌فرمان فراخوانی نمایید
#   به عنوان مثال به صورت، ‎"sh ex34.sh one two three"‎.

echo
echo "Positional parameters before  set \`uname -a\` :"
echo "Command-line argument #1 = $1"
echo "Command-line argument #2 = $2"
echo "Command-line argument #3 = $3"


set `uname -a` # پارامترهای مکانی را با خروجی فرمان ‎`uname -a`‎ تنظیم می‌کند

echo
echo +++++
echo $_        # +++++
# .نشانگرها در اسکریپت تنظیم می‌شوند
echo $-        # hB
               #  رفتار خلاف قاعده؟
echo

echo "Positional parameters after  set \`uname -a\` :"
#  ‎$1‎‏، ‎$2‎‏، ‎$3‎‏، و غیره با نتیجه فرمان ‎`uname -a`‎ مقداردهی شده‌اند.
echo "Field #1 of 'uname -a' = $1"
echo "Field #2 of 'uname -a' = $2"
echo "Field #3 of 'uname -a' = $3"
echo \#\#\#
echo $_        # ###
echo

exit 0

تفنن بیشتر با پارامترهای مکانی.

مثال ‎15-17‎. وارونه کردن پارامترهای مکانی

#!/bin/bash
#                         ‎revposparams.sh:‎ .معکوس کننده پارامترهای مکانی
#  اسکریپت توسط ‎Dan Jacobson‎، با بازبینی از نظر سبک توسط نگارنده این سند.


set a\ b c d\ e;
#     ^      ^                                         فاصله‌های معاف شده
#       ^ ^                                           فاصله‌های معاف نشده
OIFS=$IFS; IFS=:;
#              ^                    قبلی و تنظیم مقدار جدید آن ‎IFS‎ ذخیره

echo

until [ $# -eq 0 ]
do                         #.پیمایش مرحله به مرحله تمام پارامترهای مکانی
  echo "### k0 = "$k""     #            قبل
  k=$1:$k;                 #  .پیوست کردن هر پارامتر مکانی به متغیر حلقه
#     ^
  echo "### k = "$k""      #            بعد
  echo
  shift;
done

set $k                     #               .تنظیم پارامترهای مکانی جدید
echo -
echo $#                    #                    .شمارش پارامترهای مکانی
echo -
echo

for i       # حذف «‎in list‎» متغیر ‎i‎ را با پارامترهای مکانی تنظیم می‌کند.
do
  echo $i   #                      .پارامترهای مکانی جدید را نشان می‌دهد
done

IFS=$OIFS   #  بازیابی ‎IFS

#                          :پرسش
#   آیا برای اینکه اسکریپت به طور صحیح عمل نماید، تنظیم
#+     جداکننده داخلی فیلد ‎IFS‎ به یک مقدار جدید لازم است؟
#  اگر اینکار را نکنید چه اتفاقی رخ می‌دهد؟ امتحان کنید.
# و چرا در سطر ‎17‎ برای درج کردن در متغیر حلقه، ‎IFS‎ جدید‏
#+    ‏(یک کولن)به کار می‌رود؟ هدف از به کار بردن آن چیست؟

exit 0
$ ./revposparams.sh

### k0 = 
### k = a b

### k0 = a b
### k = c a b

### k0 = c a b
### k = d e c a b

-
3
-

d e
c
a b

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

bash$ set

AUTHORCOPY=/home/bozo/posts
BASH=/bin/bash
BASH_VERSION=$'2.05.8(1)-release'
...
XAUTHORITY=/home/bozo/.Xauthority
_=/etc/bashrc
variable22=abc
variable23=xzy

کاربرد set با گزینه -- به طور صریح محتویات یک متغیر را به پارامترهای مکانی تنظیم می‌کند. اگر متغیری گزینه -- را دنبال نکند، این فرمان پارامترهای مکانی را unset می‌کند.

مثال ‎15-18‎. تخصیص مجدد پارامترهای مکانی

#!/bin/bash

variable="one two three four five"

set -- $variable
#      پارامترهای مکانی را با محتویات ‎"$variable"‎ تنظیم می‌کند.

first_param=$1
second_param=$2
shift; shift     # .پارامتر مکانی اول و دوم را بیرون می‌اندازد
                                     #  ‎shift 2‎ نیز کار می‌کند.
remaining_params="$*"

echo
echo "first parameter = $first_param"             # one
echo "second parameter = $second_param"           # two
echo "remaining parameters = $remaining_params"   # three four five

echo; echo

# .یکبار دیگر
set -- $variable
first_param=$1
second_param=$2
echo "first parameter = $first_param"             # one
echo "second parameter = $second_param"           # two

# ======================================================

set --
# اگر متغیری تعیین نشده باشد پارامترهای مکانی را ‎Unset‎ می‌کند.

first_param=$1
second_param=$2
echo "first parameter = $first_param"             # (null value)
echo "second parameter = $second_param"           # (null value)

exit 0

همچنین مثال ‎11-2‎ و مثال ‎16-56‎ را ببینید.

unset

فرمان unset یک متغیر پوسته را حذف می‌کند، به طور ثمربخش آن را به null تنظیم می‌کند. توجه نمایید که این فرمان بر پارامترهای مکانی اثر نمی‌کند.

bash$ unset PATH

bash$ echo $PATH

bash$ 

مثال ‎15-19‎. ‎"unset"‎ کردن یک متغیر

#!/bin/bash
# ‎unset.sh:‎ .کردن یک متغیر ‎unset‎
variable=hello                       # .ارزش‌گذاری شده
echo "variable = $variable"

unset variable                       #   ‎unset‎ متغیر، در این مضمون خاص
                                     #+  دارای همان اثر ‎variable=‎ است
echo "(unset) variable = $variable"  #        ‎$variable‎ بدون مقدار است.

if [ -z "$variable" ]                #     .آزمودن در یک تست طول رشته
then
  echo "\$variable has zero length."
fi
exit 0

در اکثر مضمون‌ها، یک متغیر تعریف نشده و متغیری که unset شده است، هم معنی هستند. اما، ساختار جایگزینی پارامتر ‎${parameter:-default}‎ می‌تواند تفاوت میان این دو را تشخیص بدهد.
export

فرمان export‎[4]‎ متغیرها را برای تمام پردازشهای فرزند اسکریپتِ در حال اجرا یا پوسته قابل‌دسترس می‌نماید. یک مورد مهم کاربرد فرمان export، در فایلهای راه‌اندازی، برای مقداردهی و قابل دستیابی نمودن متغیرهای محیطی جهت پردازشهای بعدی کاربر است.

Caution

متاسفانه، روشی برای به عقب ‎export‎ کردن متغیرها به پردازش پدر وجود ندارد، ‎export‎ به پردازشی که اسکریپت را فراخوانی نموده یا پردازش پوسته میسر نیست.

مثال ‎15-20‎. استفاده از export برای رد کردن یک متغیر به یک اسکریپت جاسازی شده awk

#!/bin/bash

#   بازهم یک نگارش دیگر از اسکریپت «جمع‌زننده ستون»‏ ‎(col-totaler.sh)‎
#+         .که ستون مشخص شده (اعداد)‏ در یک فایل مقصد را جمع می‌کند
# این اسکریپت از محیط برای رد کردن یک متغیر اسکریپت به ‎awk‎ استفاده
#+                  می‌کند و اسکریپت ‎awk‎ را در یک متغیر قرار می‌دهد.


ARGS=2
E_WRONGARGS=85

if [ $# -ne "$ARGS" ]       # .کنترل صحت تعداد شناسه‌های خط فرمان
then
   echo "Usage: `basename $0` filename column-number"
   exit $E_WRONGARGS
fi

filename=$1
column_number=$2

#=============  تا اینجا درست مانند اسکریپت اصلی  =============#

export column_number
# ‎Export‎ شماره ستون به محیط، بنابراین برای بازیابی در دسترس است.


# ---------------------------------------------------
awkscript='{ total += $ENVIRON["column_number"] }
END { print total }'
# بله، یک متغیر می‌تواند یک اسکریپت ‎awk‎ را دریافت کند.
# ---------------------------------------------------

#      اکنون، اجرای اسکریپت ‎awk‎
awk "$awkscript" "$filename"

#  با تشکر از ‎Stephane Chazelas‎

exit 0

tip

مقداردهی و ‎export‎ متغیرها در یک عمل امکان‌پذیر است، همچون در عبارت ‎export var1=xxx‎.

به هر حال، همانطور که ‎Greg Keraunen‎ توضیح می‌دهد، در برخی وضعیت‌ها ممکن است این دارای اثری متفاوت با تنظیم یک متغیر و سپس صادر کردن(export) آن باشد.

bash$ export var=(a b); echo ${var[0]}
(a b)


bash$ var=(a b); export var; echo ${var[0]}
a


یک متغیر برای صادر شدن، ممکن است نیاز به تدبیر خاصی داشته باشد. مثال ‎M-2‎ را ببینید.


declare‏، typeset

فرمانهای declare و typeset خصوصیات متغیرها را مشخص و-یا محدود می‌کنند.

readonly

همانند ‎declare -r‎، یک متغیر را به صورت فقط‌خواندنی، در نتیجه، به عنوان یک ثابت تنظیم می‌کند. تلاش برای تغییر متغیر، همراه با یک پیغام خطا به شکست منجر می‌گردد. این مورد مشابهی برای توصیف‌کننده نوع const زبان C در پوسته است.

getopts

این ابزار قدرتمند، شناسه‌های فرمان داده شده به اسکریپت را تجزیه می‌کند. این مورد متشابه Bash با فرمان خارجی getopt و تابع کتابخانه‌ای getopt آشنا برای برنامه‌نویسان C است. این ابزار، عبور دادن و الحاقِ گزینه‌های چندتایی ‎[5]‎ و شناسه‌های بهم‌پیوسته را برای یک اسکریپت مجاز می‌سازد (برای مثال ‎scriptname -abc -e /usr/local‎).

ساختار getopts از دو متغیر ضمنی استفاده می‌کند. ‎$OPTIND‎ اشاره‌گر شناسه ‎(OPTion INDex)‎ است و ‎$OPTARG‎ شناسه (اختیاری)‏ پیوست شده به یک گزینه ‎(OPTion ARGument)‎ است. یک کولن در انتهای نام گزینه در هنگام تعریف، آن گزینه‌ را به عنوان گزینه دارای یک شناسهِ پیوست شده، مشخص می‌کند.

یک ساختار getopts معمولاً به صورت بسته‌بندی شده در یک حلقه while قرار می‌گیرد، که گزینه‌ها و شناسه‌ها را یکی یکی پردازش نموده سپس متغیر تلویحی ‎$OPTIND‎ را برای اشاره به مورد بعدی افزایش می‌دهد.

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

  2. الگوی getopts به طور ناچیزی با حلقه while استاندارد تفاوت دارد، چونکه فاقد براکت‌های شرط است.

  3. ساختار getopts یک جایگزین بسیار کارآمد برای فرمان خارجی سُنتی getopt است.


while getopts ":abcde:fg" Option
#                                                      .اعلان مقدماتی
#       ‏a،‏ b،‏ c،‏ d،‏ e،‏ f و ‎g‎ گزینه‌های (نشانگرهای)‏ مورد انتظار هستند.
#وجود :بعد از گزینه ‎e‎ نشان می‌دهد که دارای یک شناسه‌ی همراه خواهد بود.
do
  case $Option in
    a ) #  انجام کاری با متغیر ‎a‎.
    b ) #  انجام کاری با متغیر ‎b‎.
    ...
    e)  #  انجام کاری با متغیر ‎e‎، و همچنین با ‎$OPTARG‎ که
        #      شناسه عبور داده شده مرتبط با گزینه ‎e‎ است.
    ...
    g ) #                         انجام کاری با متغیر ‎g‎.
  esac
done
shift $(($OPTIND - 1))
#                           .حرکت اشاره‌گر شناسه به بعدی

#    تمام اینها اصلاً به آن پیچیدگی که وانمود می‌شود نیست.


مثال ‎15-21‎. کاربرد getopts برای خواندن گزینه‌ها-شناسه‌های داده شده به یک اسکریپت

#!/bin/bash
#  ‎ex33.sh‎: به کارگیری ‎getopts‎ و ‎OPTIND‎
# اسکریپت ویرایش شده ‎10/09/03‎ به پیشنهاد ‎Bill Gradwohl‎


# در اینجا می‌بینیم که ‎getopts‎ چگونه شناسه‌های خط فرمان اسکریپت را پردازش می‌کند.
#        .شناسه‌ها به عنوان «گزینه‌ها»‏ (نشانگرها) وشناسه‌های مرتبط تجزیه می‌گردند

#                    فراخوانی اسکریپت را با موارد زیر امتحان کنید.
#   'scriptname -mn'
#   'scriptname -oq qOption' (‎qOption‎ می‌تواند رشته‌ای اختیاری باشد.)
#   'scriptname -qXXX -r'

#   'scriptname -qr'
#+     نتیجه غیر منتظره، ‎r‎ را به عنوان شناسه گزینه ‎q‎ به کار می‌گیرد

#   'scriptname -q -r' 
#                            .نتیجه غیر منتظره، همانند مورد فوق

#   ‎'scriptname -mnop -mnop'‎                   نتیجه غیر منتظره
# (‎OPTIND‎ در بیان اینکه یک گزینه از کجا آمده قابل اطمینان نیست.)

# اگر یک گزینه انتظار یک شناسه ‎("flag:")‎ داشته باشد، آنوقت آنچه 
#+          .را که در خط فرمان بعد از آن باشد، تصاحب خواهد نمود

NO_ARGS=0 
E_OPTERROR=85

if [ $# -eq "$NO_ARGS" ]    # آیا اسکریپت بدون شناسه خط‌فرمان فراخوانی شده؟
then
  echo "Usage: `basename $0` options (-mnopqrs)"
  exit $E_OPTERROR          # .خروج و توضیح نحوه کاربرد
                            # Usage: scriptname -options
                            # توجه: خط تیره ‎(-)‎ لازم است.
fi  


while getopts ":mnopq:rs" Option
do
  case $Option in
    m     ) echo "Scenario #1: option -m-   [OPTIND=${OPTIND}]";;
    n | o ) echo "Scenario #2: option -$Option-   [OPTIND=${OPTIND}]";;
    p     ) echo "Scenario #3: option -p-   [OPTIND=${OPTIND}]";;
    q     ) echo "Scenario #4: option -q-\
                  with argument \"$OPTARG\"   [OPTIND=${OPTIND}]";;

    #         توجه کنید که گزینه ‎q‎ باید دارای یک شناسه مرتبط باشد،‏
    #+                       .وگرنه موفق نمی‌شود، حالت پیش‌فرض می‌شود
    r | s ) echo "Scenario #5: option -$Option-";;
    *     ) echo "Unimplemented option chosen.";;   # .حالت پیش‌فرض
  esac
done

shift $(($OPTIND - 1))
#  .اشاره‌گرِ شناسه را کاهش می‌دهد، بنابراین به شناسه بعدی اشاره می‌کند
# اکنون ‎$1‎ به اولین مورد غیر گزینه‌ای ارایه شده در خط‌فرمان در صورتیکه
#+                                     .وجود داشته باشد، رجوع می‌کند

exit $?


# همچنان که ‎Bill Gradwohl‎ بیان می‌کند، «ساز و کار getopts به شخص اجازه
# تعیین کردن ‎scriptname -mnop -mnop‎ را می‌دهد، اما برای تمیز دادن آنکه
#+  کدام از کجا آمده، روش قابل اطمینانی با کاربرد ‎OPTIND‎ وجود ندارد.»
#                      .به هر حال، روشهای رفع و رجوع موقت وجود دارند 

رفتار اسکریپت

source‏، . ( فرمان dot)

این فرمان، موقعی که از خط فرمان فراخوانی شده باشد، یک اسکریپت را اجرا می‌کند. داخل یک اسکریپت، یک ‎source file-name‎ فایل file-name را بارگذاری می‌کند. Source کردن یک فایل ‎(dot-command)‎ کد را به داخل اسکریپت وارد می‌کند، به اسکریپت پیوست می‌کند (مانند همان اثر رهنمود ‎#include‎ در یک برنامه C). پیامد کلی آن، همانند آن است که سطرهای کد «source شده» به طور فیزیکی در بدنه اسکریپت حضور داشته باشند. این فرمان در موقعیت‌هایی مفید است که چند اسکریپت از فایل داده‌ها یا تابع کتابخانه‌ای مشترک استفاده می‌کنند.

مثال ‎15-22‎. ‏«پیوست کردن» یک فایل داده

#!/bin/bash

#  توجه داشته باشید که این مثال باید با ‎bash‎ فراخوانی بشود،
#+           یعنی به شکل ‎bash ex38.sh‎ نه به صورت ‎sh ex38.sh‎ 

. data-file           #               .بارگذاری فایل داده
#               همان اثر ‎source data-file‎، اما قابل حمل‌تر.

#  فایل ‎data-file‎ باید در دایرکتوری کاری جاری موجود باشد،
#+                    .چون با نام خالصش به آن ارجاع می‌شود

#    .اکنون، اجازه بدهید به داده‌هایی از آن فایل رجوع کنیم

echo "variable1 (from data-file) = $variable1"
echo "variable3 (from data-file) = $variable3"

let "sum = $variable2 + $variable4"
echo "Sum of variable2 + variable4 (from data-file) = $sum"
echo "message1 (from data-file) is \"$message1\""
#                                نقل‌قولهای معاف شده
echo "message2 (from data-file) is \"$message2\""

print_message This is the message-print function in the data-file.


exit $?

فایل data-file برای مثال ‎15-22‎ فوق. باید در همان دایرکتوری حاضر باشد.

#         .این یک فایل داده‌ی بارگذاری شده به وسیله یک اسکریپت است
#       .چنین فایلهایی می‌توانند شامل متغیرها، تابع‌ها و غیره باشند
#     با یک فرمان ‎source‎ یا . از یک اسکریپت پوسته فراخوانی می‌شود.

#                       .اجازه بدهید چند متغیر را مقداردهی نماییم

variable1=23
variable2=474
variable3=5
variable4=97

message1="Greetings from *** line $LINENO *** of the data file!"
message2="Enough for now. Goodbye."

print_message ()
{                #     .هر پیغام تحویل شده به خود را بازتاب می‌دهد

  if [ -z "$1" ]
  then
    return 1     #               .خطا، در صورتیکه شناسه غایب باشد
  fi

  echo

  until [ -z "$1" ]
  do             #      .اجرا روی تک تک شناسه‌های داده شده به تابع
    echo -n "$1" # .بازتاب یکی یکی شناسه‌ها، پایمال کردن سطرجدیدها
    echo -n " "  #                        .درج فاصله در بین کلمات
    shift        #                                     .مورد بعدی
  done  

  echo

  return 0
}

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

شناسه‌ها می‌توانند (به طور اختیاری) به عنوان پارامترهای مکانی به فایل منبع شده عبور داده شوند.

source $filename $arg1 arg2

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

مثال ‎15-23‎. یک اسکریپت (بیفایده) که خودش را منبع می‌کند

#!/bin/bash
# ‎self-source.sh:‎ .می‌کند ‎(source)‎ اسکریپتی که به طور بازگشتی خودش را منبع
# از جلد دوم ‎"Stupid Script Tricks,"‎.

MAXPASSCNT=100    # .حداکثر تعداد اجراها

echo -n  "$pass_count  "
#              ،در دور اول اجرا، این فقط دو فاصله خالی بازتاب می‌دهد
#+                     چون ‎$pass_count‎ هنوز مقدار اولیه نگرفته است.

let "pass_count += 1"
#  با فرض اینکه متغیر ‎$pass_count‎ بدون داشتن مقدار اولیه، می‌تواند در
#+                                       .نوبت اول افزایش داده بشود
#       این در ‎Bash‎ و ‎pdksh‎ کار می‌کند، اما متکی به رفتار غیر قابل حمل
#+                                           .‏(و احتمالاً خطرناک) است
# بهتر است قبل از افزایش ‎$pass_count‎ مقدار اولیه ‎0‎ به آن تخصیص یابد.

while [ "$pass_count" -le $MAXPASSCNT ]
do
  . $0   # .اسکریپت به جای اینکه خودش را فراخوانی کند، خودش را منبع می‌کند
         #       ‎./$0‎ (که بازگشت صحیح خواهد بود) در اینجا کار نمی‌کند. چرا؟
done  

#     چیزی که اینجا رخ می‌دهد، در حقیقت بازگشت نیست، چون اسکریپت
#+   به طور موثر خودش را بسط می‌دهد، یعنی در هر دور از حلقه ‎while‎
#+              با هر ‎source‎ در سطر ‎20‎ یک بخش از کد تولید می‌گردد
#
#  البته، اسکریپت هر سطر دوباره منبع شده ‎#!‎ را به عنوان یک توضیح
#+             .تفسیر می‌کند، و نه به عنوان شروع یک اسکریپت جدید

echo

exit 0            # پیامد کلی این اسکریپت شمارش از ‎1‎ تا ‎100‎ است.
                  #                            .بسیار شگفت‌انگیز

#                                 :تمرین
#                               ----------
# .اسکریپتی بنویسید که از این ترفند برای انجام کار عملاً سودمندی استفاده کند

exit

به طور بدون قید و شرط اسکریپت را خاتمه می‌دهد. ‎[6]‎ فرمان exit به طور اختیاری می‌تواند یک شناسه صحیح دریافت کند، که به عنوان وضعیت خروج اسکریپت به پوسته برگشت داده می‌شود. پایان دادن حتی ساده‌ترین اسکریپت‌ها با یک ‎exit 0‎ مشخص کننده یک اجرای موفق، تکنیک مناسبی است.

اگر اسکریپتی با یک exit فاقد یک شناسه خاتمه یابد، وضعیت خروج اسکریپت، وضعیت خروج آخرین فرمان اجرا شده در اسکریپت بدون به حساب آوردن exit است. این معادل یک ‎exit $?‎ است.


یک فرمان exit همچنین می‌تواند برای خاتمه دادن به یک پوسته فرعی به کار برود.


exec

این ‎builtin‎ پوسته، پردازش جاری را با یک فرمان مشخص شده تعویض می‌کند. به طور عادی، موقعی که پوسته با یک فرمان مواجه می‌گردد، یک پردازش فرزند برای اجرای فرمان منشعب می‌کند. با استفاده از توکار exec پوسته انشعاب نمی‌زند، و فرمان exec شده، پوسته را تعویض می‌کند. بنابراین وقتی در اسکریپت استفاده شود، موقع خاتمه یافتن فرمان exec شده، یک خروج از اسکریپت را تحمیل می‌کند.‎[7]‎

مثال ‎15-24‎. اثرات exec

#!/bin/bash

exec echo "Exiting \"$0\" at line $LINENO."      # .خروج از اسکریپت در اینجا
# ‎$LINENO‎ یک متغیر داخلی ‎Bash‎ تنظیم شده به شماره سطری است که در آن قرار دارد.

# ----------------------------------
# .سطرهای زیر هرگز اجرا نمی‌شوند

echo "This echo fails to echo."

exit 99                       #     .این اسکریپت در اینجا خارج نمی‌شود
                              #  پس از خاتمه یافتن اسکریپت وضعیت خروج
                              #+   را با یک ‎echo $?‎ کنترل کنید. مشاهده
                              #          خواهید نمود که مقدارش ‎99‎ نیست.

مثال ‎15-25‎. اسکریپتی که خودش را exec می‌کند

#!/bin/bash
# self-exec.sh

#  توجه: مجوزهای این اسکریپت را به ‎555‎ یا ‎755‎ تنظیم کنید، سپس
# آن را با ‎./self-exec.sh‎ یا ‎sh ./self-exec.sh‎ فراخوانی کنید.

echo

echo "This line appears ONCE in the script, yet it keeps echoing."
echo "The PID of this instance of the script is still $$."
#     .اثبات می‌کند که یک پوسته فرعی منشعب نمی‌گردد

echo "==================== Hit Ctl-C to exit ===================="

sleep 1

exec $0            # یک نمونه دیگر از همین اسکریپت تولیدمثل
                   #+   .می‌کند، که جایگزین نمونه قبلی می‌شود

echo "This line will never echo!"  #      چرا منعکس نمی‌شود؟

exit 99                            #  !اینجا خارج نخواهد شد
                                   # !کد خروج 99 نخواهد بود

یک exec همچنین برای تخصیص مجدد توصیف‌گرهای‌فایل به کار می‌رود. برای مثال، ‎exec <zzz-file‎ ورودی استاندارد را با فایل zzz-file تعویض می‌کند.

گزینه ‎-exec‎ برای فرمان find همان builtin پوسته exec نیست.


shopt

این فرمان، تغییر وضعیت گزینه‌های پوسته را اجازه می‌دهد ( مثال ‎25-1‎ و مثال ‎25-2‎ را مشاهده نمایید). این فرمان اغلب در فایلهای راه‌اندازی Bash ظاهر می‌گردد، اما در اسکریپ‌ها نیز مورد استفاده دارد. به نگارش ‎2‎ یا جدیدتر ‎Bash‎ نیاز دارد.

shopt -s cdspell
# غلط نوشتاری کوچک در نام دایرکتوری را برای ‎cd‎ مجاز می‌کند.
#           گزینه ‎-s‎ تنظیم می‌کند، گزینه ‎-u‎ غیر فعال می‌کند.

cd /hpme  #  تایپ اشتباه ‎/home‎
pwd       # /home
          # .پوسته غلط نوشتاری را تصحیح نموده


caller

قرار دادن یک فرمان caller در داخل یک تابع، اطلاعاتی در باره فراخواننده آن تابع به stdout بازتاب می‌دهد.

#!/bin/bash

function1 ()
{
             #  داخل تابع ‎function1 ()‎
  caller 0   #  .در باره آن به من بگو
}

function1    #          سطر ‎9‎ اسکریپت

# 9 main test.sh
# ^                 .شماره سطری که تابع در آن فراخوانی شده بود
#   ^^^^            فراخوانی شده از بخش ‎main‎ اسکریپت
#        ^^^^^^^    نام اسکریپت فراخواننده

caller 0     #           .تاثیری ندارد، زیرا داخل یک تابع نیست

یک فرمان caller همچنین می‌تواند اطلاعات فراخوانی کننده را از یک اسکریپت source شده در داخل یک اسکریپت دیگر برگشت بدهد. در مقایسه با تابع، این یک «فراخوانی زیر روال» است.

شما ممکن است در اشکال‌زدایی این فرمان را سودمند بیابید.

فرمان‌ها

true

فرمانی که یک وضعیت خروج موفق (صفر) برگشت می‌دهد، اما کار دیگری انجام نمی‌دهد.

bash$ true
bash$ echo $?
0

             #  حلقه بی پایان
while true   # مستعاری برای ":"
do
   operation-1
   operation-2
   ...
   operation-n
   # .راهی برای بیرون رفتن از حلقه لازم است، یا آنکه اسکریپت هنگ خواهد شد
done


false

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

bash$ false
bash$ echo $?
1

# آزمایش فرمان false 
if false
then
  echo "false evaluates \"true\""
else
  echo "false evaluates \"false\""
fi
# false evaluates "false"


#  ایجاد حلقه ‎while false‎ (حلقه تهی)
while false
do
   #     .کد بعدی اجرا نخواهد گردید
   operation-1
   operation-2
   ...
   operation-n
   #         !هیچ اتفاقی رخ نمی‌دهد
done   


‎type [cmd]‎

مشابه فرمان خارجی which، فرمان ‎type [cmd]‎ شناسه‌اش «cmd» را تعیین هویت می‌کند. برخلاف which، فرمان ‎type در ‎Bash‎ یک builtin است. گزینه مفید ‎-a‎ با ‎type کلمه‌کلیدی‌ها و builtinها را شناسایی کرده، و فرمان‌های سیستم با همان نام‌ها را نیز مکان‌یابی می‌نماید.

bash$ type '['
[ is a shell builtin
bash$ type -a '['
[ is a shell builtin
[ is /usr/bin/[

bash$ type type
type is a shell builtin

فرمان ‎type برای بررسی وجود یک فرمان معین می‌تواند مفید باشد.


‎hash [cmds]‎

نام مسیر فرمانهای مشخص شده را ذخیره می‌کند -- در جدول hash پوسته‎[8]‎ -- به طوریکه پوسته یا اسکریپت در فراخوانی‌های بعدی آن فرمان‌ها نیازی به جستجوی ‎$PATH‎ ندارد. موقعی که ‎hash بدون شناسه فراخوانی می‌شود، فقط فرمان‌هایی را که hash شده‌اند لیست می‌کند. گزینه ‎-r‎ جدول hash را از نو تنظیم می‌کند.

bind

bind یک builtin است که عمل تخصیص یافته به کلیدها در readline ‎[9]‎ را نمایش می‌دهد یا ویرایش می‌کند.


help

خلاصه‌ی کوتاهی از نحوه کاربرد یک builtin پوسته را ارایه می‌کند. این همتای whatis است، اما برای builtinها. نمایش اطلاعات help در انتشار نگارش ‎4‎ پوسته Bash یک به‌روزرسانی بسیار ضروری را فراهم نمود.

bash$ help exit
exit: exit [n] 
    Exit the shell with a status of N.  If N is omitted, the exit status
    is that of the last command executed.

یادداشت‌ها

[1]

به طوری که ‎Nathan Coulter‎ توضیح می‌دهد، «در حالیکه منشعب کردن یک پردازش یک عمل کم هزینه است، اجرای یک برنامه جدید در پردازش فرزند به تازگی منشعب شده بالاسری بیشتری اضافه می‌کند.»

[2]

یک استثنا برای این مورد، فرمان time است که در مستندات رسمی Bash به عنوان یک کلیدواژه («کلمه رزرو شده») لیست گردیده است.

[3]

توجه نمایید که let نمی‌تواند برای تنظیم متغیرهای رشته‌ای به کار برود.

[4]

Export‏(صادر) کردن اطلاعات، معتبر ساختن آن در مضمون همگانی‌تر است. همچنین محدوده را مشاهده کنید.

[5]

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

[6]

از نظر تکنیکی، یک exit فقط پردازشی (یا پوسته‌ای) را که در آن در حال اجرا هست ،نه پردازش پدر را، خاتمه می‌دهد.

[7]

مگر اینکه exec برای دوباره تخصیص دادن توصیف‌گرهای فایل به کار برود.

[8]

Hashing یک روش ایجاد کلیدهای مراجعه به داده‌های ذخیره شده در یک جدول است. برای ایجاد کلیدها خود اقلام داده‌ها، با استفاده از تعدادی الگوریتم (روش، یا دستورالعمل) ساده ریاضی «درهم آمیخته» می‌شوند.

یک مزیت hashing سرعت آن است. یک کمبودش آن است که تصادم‌ها -- در جایی که یک کلید واحد به بیش از یک قلم داده ترسیم می‌گردد -- امکان‌پذیر هستند.

برای نمونه‌های ‎hash‎ کردن مثال ‎A-20‎ و مثال ‎A-21‎ را ببینید.

[9]

کتابخانه readline چیزی است که ‎Bash‎ در یک پوسته محاوره‌ای برای خواندن ورودی به کار می‌برد.



مترجم: پاسخ تمرین مثال ‎15-6‎ و کمی بیشتر.

#!/bin/bash

while true
do
  read -sn1 a
  test "$a" == `echo -en "\e"` || continue
  read -sn1 a
  (test "$a" == "[" || continue) || (test "$a" == "O" || continue)
  read -sn1 a
  case "$a" in
     A)  echo "up";;
     B)  echo "down";;
     C)  echo "right";;
     D)  echo "left";;
     6)  echo "pgDn";;
     5)  echo "pgUp";;
     3)  echo "delete";;
     2)  echo "insert";;
   H|1)  echo "home";;    #1 برای keypad 
   F|4)  echo "end";;     #4 برای keypad
  esac
done
# برای به دست‌آوردن کدهای فوق از اجرای دستور ‎read -sn1 a;`echo -en "\e"`‎ در 
#                       ترمینال و سپس فشردن کلید مورد نظر استفاده گردیده.

(برگشت)

مترجم: یک روش ساده رمزنگاری که در آن هر حرف با سیزدهمین حرف بعد از خودش در سری حروف الفبا تعویض می‌شود. (برگشت)