فصل ‎20‎- تغییر مسیر ورودی-خروجی

‎20.2‎- تغییر مسیر دادن بلوک‌های کد

بلوک‌های کد، از قبیل حلقه‌های while‏، until‏، و for، حتی بلوک‌های تست ‎if/then‎ نیز می‌توانند با تغییر مسیر stdin بهم پیوسته شوند. حتی یک تابع می‌تواند این شکل از تغییر مسیر را به کار ببرد ( مثال ‎24-11‎ را ببینید). عملگر ‎<‎ در انتهای بلوک کد این عمل را به انجام می‌رساند.

مثال ‎20-5‎. حلقه while تغییر مسیر یافته

#!/bin/bash
# redir2.sh

if [ -z "$1" ]
then
  Filename=names.data          # پیش‌فرض، در صورتیکه نام فایل تعیین نشود.
else
  Filename=$1
fi  
#            تست فوق را می‌توان با سطر زیر (جایگزینی پارامتر) تعویض نمود.
#+ Filename=${1:-names.data}

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‎. شکل جانشین برای حلقه while تغییر مسیر داده شده

#!/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‎. حلقه تغییر مسیر یافته until

#!/bin/bash
# همانند مثال قبلی، اما با حلقه «until»

if [ -z "$1" ]
then
  Filename=names.data         #پیش‌فرض، در صورتیکه نام فایل تعیین نشده باشد.
else
  Filename=$1
fi  

# while [ "$name" != Smith ]
until [ "$name" = Smith ]     #     تعویض ‎!=‎ با ‎=
do
  read name                   #     به جای ‎stdin‎ از ‎$Filename‎ می‌خواند.
  echo $name
done <"$Filename"             #‎stdin‎ را به فایل ‎$Filename‎ هدایت می‌کند.
#    ^^^^^^^^^^^^

# مانند همان نتایج به دست آمده با حلقه ‎while‎ در مثال قبلی.

exit 0

مثال ‎20-8‎. حلقه تغییر مسیر داده شده for

#!/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 [ "$name" != Smith ]‎          --  پیچیده‌تر از یک حلقه ‎while‎ است --
do
  read name                    #        به جای ‎stdin‎ از ‎$Filename‎ می‌خواند.
  echo $name
  if [ "$name" = Smith ]       #      تمام این کارهای اضافی اینجا لازم است.
  then
    break
  fi  
done <"$Filename"              #   ‎stdin‎ را به فایل ‎$Filename‎ هدایت می‌کند. 
#    ^^^^^^^^^^^^

exit 0

می‌توانیم مثال قبل را برای تغییر مسیر خروجی حلقه نیز ویرایش کنیم.

مثال ‎20-9‎. حلقه for تغییر مسیر داده شده ( ‎stdin‎ و ‎stdout‎ هر دو تغییر مسیر داده شده‌اند)

#!/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‎. تست ‎if/then‎ تغییر مسیر داده شده

#!/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‎ یک حلقه while باید شدنی باشد.

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