فصل ‎19‎-‏ Here Documentها

 

پسرها، حالا و اینجا.

--آلدوس هاکسلی، Island

here document یک بلوک کد خاص منظوره است. شکلی از تغییر مسیر ورودی-خروجی را برای تغذیه یک لیست فرمان به یک برنامه محاوره‌ای یا فرمان‌هایی از قبیل ftp‏، cat‏، یا ویرایشگر متن ex، به کار می‌برد.

COMMAND <<InputComesFromHERE
...
...
...
InputComesFromHERE

یک رشته حریم ، لیست فرمان‌ها را مشخص می‌کند (قاب می‌کند). علامت ویژه ‎<<‎ جلوتر از رشته حریم قرار می‌گیرد. عملکرد این علامت، تغییر مسیر دادن خروجی یک بلوک فرمان به stdin برنامه یا فرمان است. مشابه با سطر فرمان ‎interactive-program < command-file‎ است ، که در آن command-file محتوی

command #1
command #2
...
است.

که here document معادل با آن، چنین می‌شود:

interactive-program <<LimitString
command #1
command #2
...
LimitString

یک رشته حریم(LimitString‎‎) که به اندازه کافی غیر معمول است انتخاب کنید تا در هیچ جای لیست فرمان وجود نداشته‌ باشد و باعث اشتباه نشود.

توجه نمایید که گاهی اوقات here documentها می‌توانند برای نتیجه مناسب، با برنامه‌ها و فرمان‌های غیر محاوره‌ای، به طور مثال مانند wall، به کار بروند.

مثال ‎19-1‎.‏ broadcast: به هر شخص لاگین شده پیغام می‌فرستد

#!/bin/bash

wall <<zzz23EndOfMessagezzz23
E-mail your noontime orders for pizza to the system administrator.
    (Add an extra dollar for anchovy or mushroom topping.)
#       متن اضافه پیغام اینجا قرار می‌گیرد.
#توجه: ‎wall‎ سطرهای توضیح شده را چاپ می‌کند.
zzz23EndOfMessagezzz23

#  می‌توانست با  ‎wall <message-file‎ نیز
#         به طور کارآمدتری انجام بشود.
# اما، تعبیه الگوی پیغام در یک اسکریپت
#+راه حل سریع و نامنزه یکبار مصرف است.

exit

حتی نامزدهای غیر محتملی همچون ویرایشگر متن vi، می‌توانند برای here documentها مناسب باشند.

مثال ‎19-2‎.‏ dummyfile: یک فایل دو سطری بیفایده ایجاد می‌کند

#!/bin/bash

#استفاده غیر محاوره‌ای از ‎vi‎ برای ویرایش یک فایل.
#                         ‎sed‎ را شبیه‌سازی می‌کند.

E_BADARGS=85

if [ -z "$1" ]
then
  echo "Usage: `basename $0` filename"
  exit $E_BADARGS
fi

TARGETFILE=$1

#درج دو سطر در فایل، سپس ذخیره آن.
#---------آغاز ‎here document‎--------------#
vi $TARGETFILE <<x23LimitStringx23
i
This is line 1 of the example file.
This is line 2 of the example file.
^[
ZZ
x23LimitStringx23
#----------انتهای ‎here document‎-----------#

# توجه کنید که ‎^[‎ بالا یک escape لفظی است که
#+    به وسیله ‎Control-V <Esc>‎ تایپ گردیده.

# ‎Bram Moolenaar‎ اشاره می‌کند که این ممکن است به دلیل مشکلات
#+   احتمالی به وسیله اثر متقابل ترمینال، با vim کار نکند.

exit

اسکریپت فوق می‌توانست به جای vi، به طور کارآمدتری با ex پیاده‌سازی بشود. Here documentهای شامل لیستی از فرمان‌های ex به اندازه کافی رایج هستند تا رده خودشان با عنوان اسکریپت‌های ex را تشکیل بدهند.

#!/bin/bash

# تعویض تمام نمونه‌های ‎Smith‎ با ‎Jones‎
#+      در فایل‌های با پسوند نام ‎.txt‎ 

ORIGINAL=Smith
REPLACEMENT=Jones

for word in $(fgrep -l $ORIGINAL *.txt)
do
  # -------------------------------------
  ex $word <<EOF
  :%s/$ORIGINAL/$REPLACEMENT/g
  :wq
EOF
  # ‎:%s‎ فرمان جایگزینی ex است.
  #‎:wq‎ فرمان نوشتن و خروج است.
  # -------------------------------------
done

اسکریپت‌های cat مشابه «اسکریپت‌های ex» هستند.

مثال ‎19-3‎. پیغام چندسطری با استفاده از cat

#!/bin/bash

#       ‎echo‎ برای چاپ پیغام یک سطری مناسب است، اما
#+          برای بلوک‌های پیغام قدری مشکل‌آفرین است.
#یک «‎cat here document‎» بر این محدودیت غلبه می‌کند.

cat <<End-of-message
-------------------------------------
This is line 1 of the message.
This is line 2 of the message.
This is line 3 of the message.
This is line 4 of the message.
This is the last line of the message.
-------------------------------------
End-of-message

#تعویض نمودن سطر ‎7‎ فوق با
#+‎cat > $Newfile <<End-of-message‎
#+    ^^^^^^^^^^
#+خروجی را به جای ‎stdout‎ در فایل ‎$Newfile‎ می‌نویسد.

exit 0

#------------------------------------------------
#کد پایین به سبب «‎exit 0‎» بالا، بی اثر گردیده است.

#     ‎S.C.‎ اشاره می‌کند که مورد زیر نیز کار می‌کند.
echo "-------------------------------------
This is line 1 of the message.
This is line 2 of the message.
This is line 3 of the message.
This is line 4 of the message.
This is the last line of the message.
-------------------------------------"
#اما، متن نمی‌تواند شامل نقل‌قول‌های دوگانه باشد، مگر اینکه آنها معاف بشوند.

گزینه - با علامت‌گذاری رشته حریمِ یک ‎here document‎ ‏(‎<<-LimitString‎) کاراکترهای tab مقدم در خروجی را خنثی می‌کند (اما فاصله‌ها را خیر)‏. این مورد می‌تواند در تولید اسکریپت‌های خواناتر سودمند باشد.

مثال ‎19-4‎. پیغام چند سطری، با tabهای خنثی شده

#!/bin/bash
#مانند مثال قبل، اما...

# گزینه - با یک ‎here document‎ به صورت ‎<<-‎
#+ tabهای مقدم در بدنه سند را خنثی می‌کند،
#+            اما فاصله‌ها را خنثی نمی‌کند.
cat <<-ENDOFMESSAGE
	This is line 1 of the message.
	This is line 2 of the message.
	This is line 3 of the message.
	This is line 4 of the message.
	This is the last line of the message.
ENDOFMESSAGE

#خروجی اسکریپت در سمت چپ‌ همتراز خواهد شد.
#‎tab‎ مقدم در هر سطر نمایش داده نخواهد شد.

#  ۵ سطر «message» بالا، با یک ‎tab‎ شروع می‌شوند نه با فاصله.
#                    فاصله‌ها به وسیله ‎<<-‎ تاثیر نمی‌پذیرند.

#توجه نمایید که این گزینه روی tabهای «تعبیه‌شده» اثر ندارد.

exit 0

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

مثال ‎19-5‎.‏ ‎Here document‎ با پارامترهای قابل تعویض

#!/bin/bash
#          یک «‎cat here document‎» دیگر با کاربرد جایگزینی پارامتر.


#    آن را بدون پارامتر خط فرمان، به صورت ‎./scriptname‎ امتحان کنید
#  با یک پارامتر خط فرمان، به صورت ‎./scriptname Mortimer‎ بیازمایید
#و با پارامتر دو کلمه‌ای نقل‌قول شده، ‎ ./scriptname "Mortimer Jones"‎

CMDLINEPARAM=1         #       حداقل پارامتر خط فرمان مورد انتظار.

if [ $# -ge $CMDLINEPARAM ]
then
  NAME=$1              #    اگر پارامتر خط فرمان بیشتر از یک باشد،
                       #+             آنوقت فقط اولی را بر می‌دارد.
else
  NAME="John Doe"      #پیش‌فرض، در صورتیکه پارامتر خط فرمان نباشد.
fi  

RESPONDENT="the author of this fine script"  
  

cat <<Endofmessage

Hello, there, $NAME.
Greetings to you, $NAME, from $RESPONDENT.

#     این توضیح در خروجی نمایش داده می‌شود (چرا؟)‏.

Endofmessage

#توجه نمایید که سطرهای خالی در خروجی حاضر می‌شوند.
#          همان طوریکه توضیح هم نمایش داده می‌شود.

exit

این یک اسکریپت سودمند شامل یک ‎here document‎ با جایگزینی پارامتر است.

مثال ‎19-6‎. ارسال یک جفت فایل به دایرکتوری incoming در Sunsite

#!/bin/bash

# upload.sh

#   ارسال یک جفت فایل ‎(Filename.lsm, Filename.tar.gz)‎
#+ به دایرکتوری ‎incoming‎ در ‎Sunsite/UNC (ibiblio.org)‎
#   فایل ‎Filename.tar.gz‎ خود فایل tarball است. و فایل
#                  ‎Filename.lsm‎ فایل توصیف کننده است.
#‎Sunsite‎ فایل ‎"lsm"‎ را لازم دارد، وگرنه فایل را بیرون می‌اندازد.


E_ARGERROR=85

if [ -z "$1" ]
then
  echo "Usage: `basename $0` Filename-to-upload"
  exit $E_ARGERROR
fi  


Filename=`basename $1`          #جدا کردن نام مسیر از نام فایل.

Server="ibiblio.org"
Directory="/incoming/Linux"
#    نیازی نیست که اینها به صورت ثابت در متن اسکریپت وارد شوند،
#+    بلکه به جای آن می‌توانند با شناسه‌های خط فرمان تعویض بشوند.

Password="your.e-mail.address"  #      به طور مناسب تعویض کنید.

ftp -n $Server <<End-Of-Session
#                       گزینه ‎-n‎ لاگین خودکار را غیر فعال می‌کند.

user anonymous "$Password"      #     اگر این کار نکرد، آنوقت نقل‌قولی کردن
                                #‎user anonymous "$Password"‎ را امتحان کنید
binary
bell                            #           زنگ زدن بعد از هر انتقال فایل.
cd $Directory
put "$Filename.lsm"
put "$Filename.tar.gz"
bye
End-Of-Session

exit 0

نقل‌قول‌کردن یا معاف کردن «رشته حریم» در ابتدای یک ‎here document‎ جایگزینی پارامتر در بدنه آن را غیر فعال می‌کند. دلیل این آن است که نقل‌قول یا escape کردن رشته حریم به طور موثر کاراکترهای خاص $‏، `‏، و \ را escape می‌کند، و باعث می‌شود آنها به طور لفظی تفسیر بشوند. (‎Allen Halsey‎ برای اشاره کردن به این مورد، متشکرم.)

مثال ‎19-7‎. جایگزینی پارامتر خاموش شده

#!/bin/bash
#یک «‎cat here-document‎»، اما با جایگزینی پارامتر غیر فعال شده.

NAME="John Doe"
RESPONDENT="the author of this fine script"  

cat <<'Endofmessage'

Hello, there, $NAME.
Greetings to you, $NAME, from $RESPONDENT.

Endofmessage

#وقتی «رشته حریم» نقل‌قول یا معاف می‌شود، جایگزینی پارامتر صورت نمی‌گیرد.
#هر یک از موارد زیر در ابتدای ‎here document‎ همان نتیجه را خواهند داشت.
#   cat <<"Endofmessage"
#   cat <<\Endofmessage



# و به همچنین:

cat <<"SpecialCharTest"

Directory listing would follow
if limit string were not quoted.
`ls -l`

Arithmetic expansion would take place
if limit string were not quoted.
$((5 + 3))

A a single backslash would echo
if limit string were not quoted.
\\

SpecialCharTest


exit

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

مثال ‎19-8‎. یک اسکریپت که اسکریپت دیگری را تولید می‌کند

#!/bin/bash
# ‎generate-script.sh‎
# بر اساس ایده‌ای از ‎Albert Reiner‎

OUTFILE=generated.sh  # نام فایلی که باید تولید شود.


# ---------------------------------------------------------
#       ‎Here document‎ شامل بدنه اسکریپت تولید شونده.
(
cat <<'EOF'
#!/bin/bash

echo "This is a generated shell script."
#توجه کنید که چون داخل یک پوسته فرعی هستیم، متغیرهای
#+     بیرون از اسکریپت را نمی‌توانیم دستیابی نماییم.

echo "Generated file will be named: $OUTFILE"
#    سطر فوق به طوریکه معمولا انتظار می‌رود عمل نخواهد
#+  نمود، به علت اینکه بسط پارامتر غیر فعال شده است،
#            به جای آن، منجر به خروجی لفظی خواهد شد.

a=7
b=3

let "c = $a * $b"
echo "c = $c"

exit 0
EOF
) > $OUTFILE
# ---------------------------------------------------------

#   نقل‌قول کردن «رشته حریم» مانع بسط متغیر در داخل
#+   بدنه ‎here document‎ بالا می‌گردد. این مطلب بیرون
#  دادن رشته‌های لفظی در فایل خروجی را میسر می‌سازد.

if [ -f "$OUTFILE" ]
then
  chmod 755 $OUTFILE
  #                 قابل اجرا کردن فایل تولید شده.
else
  echo "Problem in creating file: \"$OUTFILE\""
fi

# این روش برای تولید برنامه‌های  C، برنامه‌های Perl،
#+برنامه‌های Python‏، Makefileها‏ و مشابه، کار می‌کند.

exit 0

تنظیم کردن متغیر از طریق خروجی یک ‎here document‎ امکان‌پذیر است. این کار، در واقع یک شکل ترفندگونه از جایگزینی فرمان است.

variable=$(cat <<SETVAR
This variable
runs over multiple lines.
SETVAR
)

echo "$variable"

یک ‎here document‎ می‌تواند برای تابعی در همان اسکریپت ورودی فراهم نماید.

مثال ‎19-9‎.‏ Here documentها و توایع

#!/bin/bash
# here-function.sh

GetPersonalData ()
{
  read firstname
  read lastname
  read address
  read city 
  read state 
  read zipcode
} #به طور یقین به نظر می‌رسد این یک تابع محاوره‌ای باشد، اما . . .


# فراهم نمودن ورودی برای تابع فوق.
GetPersonalData <<RECORD001
Bozo
Bozeman
2726 Nondescript Dr.
Bozeman
MT
21226
RECORD001


echo
echo "$firstname $lastname"
echo "$address"
echo "$city, $state $zipcode"
echo

exit 0

استفاده از : به عنوان یک فرمان قلابی پذیرنده خروجی از یک ‎here document‎ امکان‌پذیر است. این در عمل، یک ‎here document‎ «بی‌نام و نشان» تولید می‌کند.

مثال ‎19-10‎.‏ ‎Here Document‎ بی‌نام و نشان

#!/bin/bash

: <<TESTVARIABLES
${HOSTNAME?}${USER?}${MAIL?}  #اگر یکی از متغیرها تنظیم نباشد پیغام خطا چاپ می‌کند.
TESTVARIABLES

exit $?

tip

نوع دیگری از شگرد فوق، «توضیح نمودن» بلوک‌های کد را میسر می‌سازد.

مثال ‎19-11‎. توضیح نمودن یک بلوک از کد

#!/bin/bash
# commentblock.sh

: <<COMMENTBLOCK
echo "This line will not echo."
This is a comment line missing the "#" prefix.
This is another comment line missing the "#" prefix.

&*@!!++=
The above line will cause no error message,
because the Bash interpreter will ignore it.
COMMENTBLOCK

echo "Exit value of above \"COMMENTBLOCK\" is $?."   # 0
#نشان دهنده فقدان خطا.
echo


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

echo "Just before commented-out code block."
# سطرهای کد مابین دو سطر خط تیره دوگانه پایین، اجرا نخواهند گردید.
#===================================================================
: <<DEBUGXXX
for file in *
do
 cat "$file"
done
DEBUGXXX
#===================================================================
echo "Just after commented-out code block."

exit 0



####################################################################
# اما، توجه کنید که اگر یک متغیر قرار گرفته در براکت‌ها، داخل
#+بلوک توضیح شونده گنجانده شده باشد، آنوقت این مورد می‌تواند.
#                           باعث مشکلاتی بشود. به عنوان مثال:


#/!/bin/bash

  : <<COMMENTBLOCK
  echo "This line will not echo."
  &*@!!++=
  ${foo_bar_bazz?}
  $(rm -rf /tmp/foobar/)
  $(touch my_build_directory/cups/Makefile)
COMMENTBLOCK


$ sh commented-bad.sh
commented-bad.sh: line 3: foo_bar_bazz: parameter null or not set

# راه علاج آن نقل‌قول قوی 'COMMENTBLOCK' در سطر ‎3‎ فوق است.

  : <<'COMMENTBLOCK'

#‎Kurt Pfeifle‎ به خاطر اشاره کردن به این مورد، تشکر می‌کنم.

tip

باز هم یک دستکاری دیگر این ترفند جذاب، اسکریپت‌های «خود توضیحی» را ممکن می‌سازد.

مثال ‎19-12‎. یک اسکریپت خود توضیحی

#!/bin/bash
#self-document.sh:
#ویرایشی از ‎colm.sh‎

DOC_REQUEST=70

if [ "$1" = "-h"  -o "$1" = "--help" ]     #درخواست راهنمایی.
then
  echo; echo "Usage: $0 [directory-name]"; echo
  sed --silent -e '/DOCUMENTATIONXX$/,/^DOCUMENTATIONXX$/p' "$0" |
  sed -e '/DOCUMENTATIONXX$/d'; exit $DOC_REQUEST; fi


: <<DOCUMENTATIONXX
List the statistics of a specified directory in tabular format.
---------------------------------------------------------------
The command-line parameter gives the directory to be listed.
If no directory specified or directory specified cannot be read,
then list the current working directory.

DOCUMENTATIONXX

if [ -z "$1" -o ! -r "$1" ]
then
  directory=.
else
  directory="$1"
fi  

echo "Listing of "$directory":"; echo
(printf "PERMISSIONS LINKS OWNER GROUP SIZE MONTH DAY HH:MM PROG-NAME\n" \
; ls -l "$directory" | sed 1d) | column -t

exit 0

کاربرد یک اسکریپت cat روش جایگزینی برای انجام این کار است.

DOC_REQUEST=70

if [ "$1" = "-h"  -o "$1" = "--help" ]     #        درخواست راهنمایی.
then                                       #استفاده از «اسکریپت cat».
  cat <<DOCUMENTATIONXX
List the statistics of a specified directory in tabular format.
---------------------------------------------------------------
The command-line parameter gives the directory to be listed.
If no directory specified or directory specified cannot be read,
then list the current working directory.

DOCUMENTATIONXX
exit $DOC_REQUEST
fi

همچنین برای نمونه‌هایی از اسکریپت‌ خودتوضیحی مثال ‎A-28‎، مثال ‎A-40‎، مثال ‎A-41‎، و مثال ‎A-42‎ را ببینید.

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

bash$ bash -c 'lsof -a -p $$ -d0' << EOF
> EOF

lsof    1213 bozo    0r   REG    3,5    0 30386 /tmp/t1213-0-sh (deleted)


Caution

برخی برنامه های سودمند در درون یک ‎here document‎ کار نمی‌کنند.

WARNINGبستن رشته حریم، روی سطر انتهایی یک ‎here document‎، باید از مکان کاراکتر اول شروع بشود. هیچ فضای سفید مقدم نمی‌تواند وجود داشته باشد. فضای سفید بعد از رشته حریم نیز می‌تواند موجب رفتار غیر منتظره بشود. فضای سفید مانع شناسایی شدن رشته حریم می‌گردد. ‎[1]‎

#!/bin/bash

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

cat <<LimitString
echo "This is line 1 of the message inside the here document."
echo "This is line 2 of the message inside the here document."
echo "This is the final line of the message inside the here document."
     LimitString
#^^^^       رشته حریم توگذاری‌شده.
# خطا! این اسکریپت آنطور که انتظار می‌رود عمل نمی‌کند.

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

# این توضیحات خارج از ‎here document‎ هستند، و منعکس نخواهند شد.

echo "Outside the here document."

exit 0

echo "This line had better not echo."  # بعد از فرمان ‎exit‎ است.


Caution

برخی اشخاص، خیلی زیرکانه یک کاراکتر ! منفرد را به عنوان رشته حریم به کار می‌برند. اما، این لزوماً یک ایده مناسب نیست.

#این کار می‌کند.
cat <<!
Hello!
! Three more exclamations !!!
!


#اما . . .
cat <<!
Hello!
Single exclamation point follows!
!
!
#با یک پیغام خطا از کار می‌افتد.


#اما، مورد پایین کار خواهد کرد.
cat <<EOF
Hello!
Single exclamation point follows!
!
EOF
#استفاده از رشته حریم چندکاراکتری قابل اعتمادتر است.

جهت وظایفی که برای یک ‎here document‎ بیش از اندازه پیچیده هستند، استفاده از زبان اسکریپت‌نویسی expect را در نظر بگیرید، که مخصوصاً برای تغذیه نمودن ورودی به برنامه‌های محاوره‌ای طراحی شده است.

یادداشت‌ها

‎[1]‎

همچنانکه ‎Dennis Benzinger‎ توضیح می‌دهد، مگر در صورت کاربرد ‎<<-‎ برای خنثی کردن tabها.