فصل ‎12‎- جایگزینی فرمان

جایگزینی فرمان، خروجی یک فرمان ‎[1]‎ یا حتی چندین فرمان را دوباره تخصیص می‌دهد، یعنی خروجی فرمان را بدون کم و کاست به یک مضمون دیگر متصل می‌کند. ‎[2]‎

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

script_name=`basename $0`
echo "The name of this script is $script_name."

خروجی فرمان می‌تواند به عنوان شناسه‌های یک فرمان دیگر، برای تنظیم یک متغیر، و حتی برای تولید لیست شناسه‌ها در یک حلقه for به کار برود.

rm `cat filename`         # ‎"filename"‎ شامل لیستی از فایلها جهت حذف شدن است.
#
#        ‎S. C.‎ اشاره می‌کند که ممکن است به خطای ‎"arg list too long"‎ منجر بشود.
#				         ‎xargs rm -- < filename‎ مناسب‌تر است. 
# ( ‎--‎ آن مواردی را پوشش می‌دهد که در آنها ‎"filename"‎ با یک ‎"-"‎ شروع می‌شود )

textfile_listing=`ls *.txt`
#                  متغیر شامل نام تمام فایلهای ‎*.txt‎ در دایرکتوری جاری می‌شود.
echo $textfile_listing

textfile_listing2=$(ls *.txt)                #  .شکل دیگری از جایگزینی فرمان
echo $textfile_listing2                      #                   .همان نتیجه

#    یک مشکل احتمالی با قرار دادن لیستی از فایلها در یک رشته منفرد آن است که
#                            .شاید به طور مخفیانه یک سطر جدید در آن وارد شود
#
#.روش مطمئن‌تر برای تخصیص لیستی از فایلها به یک پارامتر، استفاده از آرایه است
#     shopt -s nullglob     # .اگر منطبق نگردد، نام فایل به هیچ بسط داده شود
#     textfile_listing=( *.txt )
#
#  با تشکر از ‎S.C.‎

جایگزینی فرمان یک پوسته فرعی احضار می‌کند.


Caution

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

COMMAND `echo a b`     #  ‎2‎ شناسه: a و b

COMMAND "`echo a b`"   #  ‎1‎ شناسه: ‎"a b"‎

COMMAND `echo`         # بدون شناسه

COMMAND "`echo`"       # یک شناسه تهی

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

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

# cd "`pwd`"  # .این مورد باید همیشه کار کند
#                                    ... اما

mkdir 'dir with trailing newline
'

cd 'dir with trailing newline
'

cd "`pwd`"                   #  :پیغام خطای
# bash: cd: /tmp/file with trailing newline: No such file or directory

cd "$PWD"                    # .به خوبی کار می‌کند



old_tty_setting=$(stty -g)   #              .ذخیره تنظیمات قبلی ترمینال
echo "Hit a key "
stty -icanon -echo           #  لغو کردن وضعیت ‎"canonical"‎ برای ترمینال.
                             #    همچنین غیر فعال کردن تنظیم «محلی» echo
key=$(dd bs=1 count=1 2> /dev/null)    #استفاده از dd جهت ضبط ضربه‌کلید.
stty "$old_tty_setting"      #                    .بازیابی تنظیمات قبلی
echo "You hit ${#key} key."  
#      ‎${#variable}‎ مساوی تعداد کاراکترها در ‎$variable‎ است.
#
# با زدن هر کلیدی غیر از اینتر، خروجی ‎"You hit 1 key."‎ است.
#               با زدن اینتر، خروجی‌اش ‎"You hit 0 key."‎ است.
#                 .سطر جدید در جایگزینی فرمان خورده می‌شود
#قطعه کد توسط ‎Stéphane Chazelas‎.


Caution

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

dir_listing=`ls -l`
echo $dir_listing          # نقل‌قول نشده

# .یک لیست دایرکتوری مرتب شده دلپذیر مورد انتظار است
#          :در حالیکه، آنچه به دست می‌آورید، چنین است

# total 3 -rw-rw-r-- 1 bozo bozo 30 May 13 17:15 1.txt -rw-rw-r-- 1 bozo
# bozo 51 May 15 20:57 t2.sh -rwxr-xr-x 1 bozo bozo 217 Mar 5 21:13 wi.sh

#                     .سطرهای جدید ناپدید گردیده‌اند


echo "$dir_listing"        # نقل‌قولی شده
# total 3 
# -rw-rw-r--    1 bozo       30 May 13 17:15 1.txt
# -rw-rw-r--    1 bozo       51 May 15 20:57 t2.sh
# -rwxr-xr-x    1 bozo      217 Mar  5 21:13 wi.sh

جایگزینی فرمان حتی تنظیم یک متغیر به محتوای یک فایل را با استفاده از تغییر مسیر یا فرمان cat جایز می‌کند.

variable1=`<file1`        #   تنظیم متغیر ‎variable1‎ به محتویات فایل ‎file1‎
variable2=`cat file2`     #  تنظیم متغیر ‎variable2‎ به محتویات فایل ‎file2‎
                          #    به هرحال، این یک پردازش جدید منشعب می‌کند,
                          #+ .پس این کد آهسته‌تر از نگارش فوق اجرا می‌شود

             #  ،توجه کنید که ممکن است متغیرها شامل فضای سفید تعبیه شده
             #+      .یا حتی (بسیار ناخوشایند)، کاراکترهای کنترلی باشند

             #              .به طور صریح تخصیص دادن یک متغیر ضروری نیست
echo "` <$0`"          # .خود اسکریپت را به خروجی استاندارد منعکس می‌کند

#  برگزیده از فایل سیستمی ‎/etc/rc.d/rc.sysinit‎ (در یک سیستم لینوکس ردهت)


if [ -f /fsckoptions ]; then
        fsckoptions=`cat /fsckoptions`
...
fi
#
#
if [ -e "/proc/ide/${disk[$device]}/media" ] ; then
             hdmedia=`cat /proc/ide/${disk[$device]}/media`
...
fi
#
#
if [ ! -n "`uname -r | grep -- "-"`" ]; then
       ktag="`cat /proc/version`"
...
fi
#
#
if [ $usb = "1" ]; then
    sleep 5
    mouseoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=02"`
    kbdoutput=`cat /proc/bus/usb/devices 2>/dev/null|grep -E "^I.*Cls=03.*Prot=01"`
...
fi

Caution

یک متغیر را به محتوای یک فایل متن طولانی تنظیم نکنید، مگر اینکه دلیل بسیار خوبی برای انجام آن داشته باشید. متغیر را به محتویات یک فایل binary تنظیم نکنید، حتی به عنوان یک شوخی.

مثال ‎12-1‎. ترفندهای اسکریپت نابخردانه

#!/bin/bash
# ‎stupid-script-tricks.sh:‎ در منزل و نزد خویشان اسکریپت را امتحان نکنید
# از جلد اول ‎"Stupid Script Tricks"‎ .

exit 99  ###     .اگر جرات دارید این سطر را به صورت توضیح درآورید

dangerous_variable=`cat /boot/vmlinuz`   # .فشرده خود هسته لینوکس

echo "string-length of \$dangerous_variable = ${#dangerous_variable}"
#       طول رشته ‎$dangerous_variable‎ مساوی ‎794151‎ است.
#                         (کرنل‌های جدید بزرگتر هستند.)
# با ‎'wc -c /boot/vmlinuz'‎ همان تعداد را ارایه نمی‌کند.

# echo "$dangerous_variable"
# .این را امتحان نکنید! اسکریپت را هنگ خواهد نمود


# .من از وجود برنامه‌ مفیدی برای تنظیم متغیر به محتویات فایل باینری بی‌خبرم

exit 0

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

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

مثال ‎12-2‎. تولید یک متغیر از یک حلقه

#!/bin/bash
# ‎csubloop.sh:‎ تنظیم یک متغیر به خروجی یک حلقه

variable1=`for i in 1 2 3 4 5
do
  echo -n "$i"                        #   فرمان ‎'echo'‎ در اینجا برای
done`                                 #+  جایگزینی فرمان حیاتی است

echo "variable1 = $variable1"         #         variable1 = 12345


i=0
variable2=`while [ "$i" -lt 10 ]
do
  echo -n "$i"                        #      دوباره، ‎'echo'‎ ضروری.
  let "i += 1"                        #                   .افزایش
done`

echo "variable2 = $variable2"         #    variable2 = 0123456789

#     .نشان می‌دهد که تعبیه یک حلقه در داخل یک اعلان متغیر ممکن است

exit 0


نوع ‎$(...)‎ جایگزینی فرمان، نقل‌قولهای برعکس برای جایگزینی فرمان را از اعتبار انداخته است.

output=$(sed -n /"$1"/p $file)   #		   از مثال ‎"grp.sh"‎
	      
#        		     .تنظیم یک متغیر به محتویات یک فایل متن
File_contents1=$(cat $file1)      
File_contents2=$(<$file2)        # ‎Bash‎ این مورد را نیز اجازه می‌دهد.

نوع ‎$(...)‎ جایگزینی فرمان، با backslash دوتایی به روشی غیر از ‎`...`‎ رفتار می‌کند.

bash$ echo `echo \\`


bash$ echo $(echo \\)
\

شکل ‎$(...)‎ جایگزینی فرمان، تودرتویی را اجازه می‌دهد. ‎[3]‎

word_count=$( wc -w $(echo * | awk '{print $8}') )

یا برای موردی تا اندازه‌ای پیچیده‌تر . . .

مثال ‎12-3‎. یافتن مقلوبی‌ها

#!/bin/bash
# agram2.sh
# .مثال جایگزینی فرمان تودرتو

#     از برنامه سودمند ‎"anagram"‎ که قسمتی از بسته
#+  لیست کلمه ‎"yawl"‎ نگارنده است، استفاده می‌کند.
#  http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
#  http://bash.deta.in/yawl-0.3.2.tar.gz

E_NOARGS=86
E_BADARG=87
MINLEN=7

if [ -z "$1" ]
then
  echo "Usage $0 LETTERSET"
  exit $E_NOARGS         # .اسکریپت یک شناسه خط فرمان لازم دارد
elif [ ${#1} -lt $MINLEN ]
then
  echo "Argument must have at least $MINLEN letters."
  exit $E_BADARG
fi



FILTER='.......'         # .حداقل باید دارای ۷ حرف باشد
#       1234567
Anagrams=( $(echo $(anagram $1 | grep $FILTER) ) )
#          ‎$(     $(‎ جایگزینی فرمان تودرتو   ‎) )‎
#        ‎(‎               مقداردهی آرایه          ‎)‎

echo
echo "${#Anagrams[*]}  7+ letter anagrams found"
echo
echo ${Anagrams[0]}      # مقلوبی اول
echo ${Anagrams[1]}      # مقلوبی دوم
                         # و غیره

# echo "${Anagrams[*]}"  #      برای لیست تمام شناسه‌ها در یک سطر واحد

#   .برای روشن شدن آن که اینجا چه پیش می‌آید، به فصل آرایه‌ها نگاه کنید

# همچنین برای یک تمرین در مورد کشف مقلوبی، اسکریپت ‎agram.sh‎ را ببینید.

exit $?

مثالهای جایگزینی فرمان در اسکریپت‌های پوسته:

  1. مثال ‎11-8‎

  2. مثال ‎11-27‎

  3. مثال ‎9-16‎

  4. مثال ‎16-3‎

  1. مثال ‎16-22‎

  2. مثال ‎16-17‎

  3. مثال ‎16-54‎

  4. مثال ‎11-14‎

  1. مثال ‎11-11‎

  2. مثال ‎16-32‎

  3. مثال ‎20-8‎

  4. مثال ‎A-16‎

  1. مثال ‎29-3‎

  2. مثال ‎16-47‎

  3. مثال ‎16-48‎

  4. مثال ‎16-49‎

یادداشت‌ها

[1]

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

[2]

در یک مفهوم از نظر تکنیکی صحیح‌تر، جایگزینی فرمان stdout یک فرمان را استخراج کرده، سپس آن را با استفاده از عملگر = به یک متغیر تخصیص می‌دهد.

[3]

در واقع، تودرتویی با نقل‌قولهای برعکس نیز ممکن است، اما همانطورکه ‎John Default‎ اشاره می‌کند، فقط با معاف کردن (escaping) نقل‌قولهای برعکس درونی.

word_count=` wc -w \`echo * | awk '{print $8}'\` `