بلوکهای کد، از قبیل حلقههای while، until، و for، حتی بلوکهای تست if/then نیز میتوانند با تغییر مسیر stdin بهم پیوسته شوند. حتی یک تابع میتواند این شکل از تغییر مسیر را به کار ببرد ( مثال 24-11 را ببینید). عملگر < در انتهای بلوک کد این عمل را به انجام میرساند.
مثال 20-5. حلقه
#!/bin/bash # if [ -z "$1" ] then Filename=names.data #پیشفرض، در صورتیکه نام فایل تعیین نشود. else Filename=$1 fi #تست فوق را میتوان با سطر زیر (جایگزینی پارامتر) تعویض نمود. #+ count=0 echo while [ "$name" != Smith ] #چرا متغیر $name در نقلقول است؟ do read name #به جای stdin از $Filename میخواند. echo $name let "count += 1" done <"$Filename" #stdin را به $Filename تغییر مسیر میدهد. # echo; echo "$count names read"; echo exit 0 #توجه کنید که در برخی زبانهای اسکریپتنویسی پوسته قدیمیتر، #+حلقه تغییر مسیر داده شده، در یک پوسته فرعی اجرا میگردید. #بنابراین، $count مقدار ارزش گذاری شده در خارج حلقه (0) را برگشت میداد. #Bash و ksh «تا جاییکه ممکن باشد» از یک پوسته فرعی پرهیز میکنند، #+چنانکه، به عنوان نمونه، این اسکریپت به طور صحیح اجرا میگردد. #(با تشکر از Heiner Steven برای اشاره کردن به این مورد.) #به هرحال . . . #Bash گاهی اوقات «میتواند» در یک حلقه «while-read» لولهکشی شده، به صورتی #+متفاوت از حلقه «while» تغییر مسیر داده شده، یک پوسته فرعی اجرا کند. abc=hi echo -e "1\n2\n3" | while read l do abc="$l" echo $abc done echo $abc #تشکر از Bruno de Oliveira Schneider برای ثابت کردن آن با خُرده کد فوق. #و تشکر از Brian Onn برای تصحیح کردن یک حاشیهنویسی اشتباه.
مثال 20-6. شکل جانشین برای حلقه
#!/bin/bash #این اسکریپت یک شکل جانشین برای اسکریپت قبلی است. #توسط Heiner Steven به عنوان راهحل موقت در موقعیتهایی که یک تغییر مسیر #+حلقه به صورت یک پوسته فرعی اجرا میگردد، و بنابراین متغیرهای داخل حلقه #+مقادیرشان را با خاتمه یافتن حلقه حفظ نمیکنند، پیشنهاد گردیده است. if [ -z "$1" ] then Filename=names.data #پیشفرض، در صورتیکه نام فایل تعیین نشده باشد. else Filename=$1 fi exec 3<&0 #ذخیره stdin در توصیفگر فایل شماره 3 exec 0<"$Filename" #تغییر مسیر ورودی استاندارد. count=0 echo while [ "$name" != Smith ] do read name #از stdin تغییر مسیر یافته($Filename) میخواند. echo $name let "count += 1" done #به موجب سطر 19، حلقه از فایل $Filename میخواند. #نگارش اصلی این اسکریپت حلقه while را با done <"$Filename" خاتمه داده است. #تمرین: چرا این کار غیر ضروری است؟ exec 0<&3 #بازیابی stdin قدیمی. exec 3<&- #بستن fd 3 موقتی. echo; echo "$count names read"; echo exit 0
مثال 20-7. حلقه تغییر مسیر یافته
#!/bin/bash #همانند مثال قبلی، اما با حلقه «until» if [ -z "$1" ] then Filename=names.data #پیشفرض، در صورتیکه نام فایل تعیین نشده باشد. else Filename=$1 fi # until [ "$name" = Smith ] #تعویض != با = do read name #به جای stdin از $Filename میخواند. echo $name done <"$Filename" #stdin را به فایل $Filename هدایت میکند. # #مانند همان نتایج به دست آمده با حلقه while در مثال قبلی. exit 0
مثال 20-8. حلقه تغییر مسیر داده شده
#!/bin/bash if [ -z "$1" ] then Filename=names.data #پیشفرض، در صورتیکه نام فایل تعیین نشده باشد. else Filename=$1 fi line_count=`wc $Filename | awk '{ print $1 }'` #تعداد سطرها در فایل هدف. # #خیلی ساختگی و ناهنجار، با این حال نشان میدهد که تغییر #+مسیر دادن stdin در درون یک حلقه for در صورتیکه به #+اندازه کافی با استعداد باشید، امکانپذیر است. # #line_count=$(wc -l < "$Filename") خلاصهتر است. for name in `seq $line_count` #فراخوانی seq، اعداد متوالی را چاپ میکند. #-- پیچیدهتر از یک حلقه while است -- do read name #به جای stdin از $Filename میخواند. echo $name if [ "$name" = Smith ] #تمام این کارهای اضافی اینجا لازم است. then break fi done <"$Filename" #stdin را به فایل $Filename هدایت میکند. # exit 0
میتوانیم مثال قبل را برای تغییر مسیر خروجی حلقه نیز ویرایش کنیم.
مثال 20-9. حلقه
#!/bin/bash if [ -z "$1" ] then Filename=names.data #پیشفرض، در صورتیکه نام فایل مشخص نشده باشد. else Filename=$1 fi Savefile=$Filename.new #نام فایل برای ذخیره نتایج در آن. FinalName=Jonah #نام برای خاتمه یافتن read با آن. line_count=`wc $Filename | awk '{ print $1 }'` #تعداد سطرهای فایل هدف. for name in `seq $line_count` do read name echo "$name" if [ "$name" = "$FinalName" ] then break fi done < "$Filename" > "$Savefile" #stdin را به فایل $Filename تغییر مسیر، #میدهد و در فایل پشتیبان ذخیره میکند. exit 0
مثال 20-10. تست
#!/bin/bash if [ -z "$1" ] then Filename=names.data #پیشفرض، در صورتیکه نام فایل تعیین نشده باشد. else Filename=$1 fi TRUE=1 if [ "$TRUE" ] #if true و if : نیز کار میکند. then read name echo $name fi <"$Filename" # #فقط سطر اول فایل را میخواند. #یک تست «if/then» راهی برای تکرار ندارد مگر در یک حلقه تعبیه شود. exit 0
مثال 20-11. فایل داده names.data برای مثالهای فوق
Aristotle Arrhenius Belisarius Capablanca Dickens Euler Goethe Hegel Jonah Laplace Maroczy Purcell Schmidt Schopenhauer Semmelweiss Smith Steinmetz Tukhashevsky Turing Venn Warshawski Znosko-Borowski #این فایل data برای اسکریپتهای redir2.sh، redir3.sh #+redir4.sh، redir4a.sh، و redir5.sh است.
نتیجه تغییر مسیر دادن stdout یک بلوک کد، ذخیره کردن خروجی آن در یک فایل است. مثال 3-2 را ببینید.
Here documentها یک حالت خاص از بلوکهای کد تغییر مسیر یافته هستند. که در اینصورت، تغذیه خروجی یک here document به طرف stdin یک حلقه
#این مثال نوشته Albert Siersema است، و #با مجوز در اینجا استفاده شده (تشکر!). function doesOutput() #البته، یک فرمان خارجی هم میتوانست باشد. #اینجا نشان میدهیم که یک تابع هم میتوانید استفاده کنید. { ls -al *.jpg | awk '{print $5,$9}' } nr=0 #میخواهیم حلقه while قادر به دستکاری اینها باشد و بعد totalSize=0 #+از پایان یافتن حلقه while، تغییرات قابل دیدن باشد. while read fileSize fileName ; do echo "$fileName is $fileSize bytes" let nr++ totalSize=$((totalSize+fileSize)) #یا: let totalSize+=fileSize done<<EOF $(doesOutput) EOF echo "$nr files totaling $totalSize bytes"