پسرها، حالا و اینجا. --آلدوس هاکسلی، 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 <<"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 $?
نوع دیگری از شگرد فوق، «توضیح نمودن» بلوکهای کد را میسر میسازد. |
مثال 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 به خاطر اشاره کردن به این مورد، تشکر میکنم.
باز هم یک دستکاری دیگر این ترفند جذاب، اسکریپتهای «خود توضیحی» را ممکن میسازد. |
مثال 19-12. یک اسکریپت خود توضیحی
#!/bin/bash # #ویرایشی از 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) |
برخی برنامه های سودمند در درون یک here document کار نمیکنند. |
بستن رشته حریم، روی سطر انتهایی یک 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 # |
جهت وظایفی که برای یک here document بیش از اندازه پیچیده هستند، استفاده از زبان اسکریپتنویسی expect را در نظر بگیرید، که مخصوصاً برای تغذیه نمودن ورودی به برنامههای محاورهای طراحی شده است.
[1] | همچنانکه Dennis Benzinger توضیح میدهد، مگر در صورت کاربرد <<- برای خنثی کردن tabها. |