فرمانهای داخلی

فصل ‎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   #
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
#
#
#       

# با تشکر از ‎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"   #
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‎ سطر جدید را موقوف می‌کند.
              #
              #

echo "var1 = $var1"
#

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

echo; echo

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

echo "var2 = $var2"
#

# ورود داده‌ها با اولین ‎<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
#
#       با تشکر از ‎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
#
# این مثال توسط ‎Bjon Eriksson‎ اهدا گردیده است.

###

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

echo
echo "++++++++++++++++++++++"
printf "\nAll done, last: $last\n" 
        #  
        #+            
        #   (نیازمند ‎Bash‎ نگارش ‎4.2 یا بالاتر است.)

exit 0  #                              
        #  
        # ابروهای اضافی را فرمان ‎echo‎ تامین می‌کند.

#
متغیر ‎(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"      #

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

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

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

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

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


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

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


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

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

let a++
let "t = a<7?7:11"   #
echo $t  #

exit

Caution

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

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

var=0
echo $?     #
            #

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

let var++
echo $?     #
            #


#

let var=0
echo $?     #
            # فرمان موفق بود؟، پس چرا ‎$?=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             # 
                    # 
eval echo $a        # 
                    # 
eval eval echo $a   # 
                    # 

#  با تشکر از ‎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/'`"
#
#
#

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  # 

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

#!/bin/bash
#

#
#+              

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      #

array_number=2    #
element_number=4
choose_array      #

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

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

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


#!/bin/bash
#

#        
#                                                     
#

params=$#              #               
param=1                #            

while [ "$param" -le "$params" ]
do
  echo -n "Command-line parameter "
  echo -n \$$param     #       فقط «نام» متغیرها را ارایه می‌کند.
##
                       #                                   
#  ‎\$‎ اولین ‎$‎ را معاف ‎(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‎ بنویسید:
#+

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


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

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


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

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

# مثال نوشته ‎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
#
#

#
#   به عنوان مثال به صورت، ‎"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 $-        #
               #
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
#                        
#  اسکریپت توسط ‎Dan Jacobson‎، با بازبینی از نظر سبک توسط نگارنده این سند.


set a\ b c d\ e;
#
#
OIFS=$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"             #
echo "second parameter = $second_param"           #
echo "remaining parameters = $remaining_params"   #

echo; echo

#
set -- $variable
first_param=$1
second_param=$2
echo "first parameter = $first_param"             #
echo "second parameter = $second_param"           #

#

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

first_param=$1
second_param=$2
echo "first parameter = $first_param"             #
echo "second parameter = $second_param"           #

exit 0

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

unset

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

bash$ unset PATH

bash$ echo $PATH

bash$ 

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

#!/bin/bash
#
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‎ چگونه شناسه‌های خط فرمان اسکریپت را پردازش می‌کند.
#       

#                    فراخوانی اسکریپت را با موارد زیر امتحان کنید.
#
# (‎qOption‎ می‌تواند رشته‌ای اختیاری باشد.)
#

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

# 
#                        

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

# اگر یک گزینه انتظار یک شناسه ‎("flag:")‎ داشته باشد، آنوقت آنچه 
#+         

NO_ARGS=0 
E_OPTERROR=85

if [ $# -eq "$NO_ARGS" ]    #
then
  echo "Usage: `basename $0` options (-mnopqrs)"
  exit $E_OPTERROR          #
                            #
                            # توجه: خط تیره ‎(-)‎ لازم است.
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
#
# از جلد دوم ‎"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
#

#  توجه: مجوزهای این اسکریپت را به ‎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                            # 
                                   #

یک 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       #
          #


caller

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

#!/bin/bash

function1 ()
{
             #  داخل تابع ‎function1 ()‎
  caller 0   # 
}

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

#
#
#فراخوانی شده از بخش ‎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
#


#  ایجاد حلقه ‎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"`‎ در 
#                       ترمینال و سپس فشردن کلید مورد نظر استفاده گردیده.

(برگشت)

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