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

‎20.1‎- کاربرد exec

یک فرمان ‎exec <filename‎ ورودی استاندارد را به یک فایل تغییر مسیر می‌دهد. از آن نقطه‌ای که قرار دارد تمام stdin به جای منبع معمولی‌اش (معمولاً ورودی صفحه کلید است)، از آن فایل می‌آید. این تغییر مسیر، روشی برای خواندن سطر به سطر یک فایل و احتمالاً تجزیه هر سطر ورودی با sed و-یا awk فراهم می‌سازد.

مثال ‎20-1‎. تغییر مسیر دادن stdin با استفاده از exec

#!/bin/bash
# تغییر مسیر ‎stdin‎ با کاربرد ‎exec‎.


exec 6<&0          #پیوند زدن توصیف‌گر فایل شماره ‎6‎ با ‎stdin‎
                   #                  ‎stdin‎ را ذخیره می‌کند.

exec < data-file   # ‎stdin‎ با فایل ‎data-file‎ تعویض شده است.

read a1            # سطر نخست از فایل ‎data-file‎ را می‌خواند.
read a2            #  سطر دوم از فایل ‎data-file‎ را می‌خواهد.

echo
echo "Following lines read from file."
echo "-------------------------------"
echo $a1
echo $a2

echo; echo; echo

exec 0<&6 6<&-
# اکنون بازیابی ‎stdin‎ از ‎fd #6‎، جایی که ذخیره شده بود، و بستن ‎fd‎
#+ ‎شماره ‎6‎ ‏( ‎6<&-‎ ) برای آزاد کردن آن جهت استفاده سایر پردازش‌ها.
#
#                                        ‎<&6 6<&-‎ نیز کار می‌کند.

echo -n "Enter data  "
read b1  #اکنون عملیات خواندن مطابق معمول، خواندن از ‎stdin‎ عادی.
echo "Input read from stdin."
echo "----------------------"
echo "b1 = $b1"

echo

exit 0

به طور مشابهی، یک فرمان ‎exec >filename‎ خروجی استاندارد را به یک فایل هدف تغییر مسیر می‌دهد. این تمام خروجی فرمان را که به طور عادی به ‎stdout‎ می‌رفت، به آن فایل می‌فرستد.

important

‎exec N > filename‎ بر تمام اسکریپت یا پوسته جاری اثر می‌کند. اما...

‎N > filename‎ فقط بر پردازش‌هایی که به تازگی منشعب می‌شوند اثر می‌کند، نه بر تمام اسکریپ یا پوسته.

با تشکر از احمد درویش برای اشاره کردن به این مورد.


مثال ‎20-2‎. تغییر مسیر دادن stdout با استفاده از exec

#!/bin/bash
# reassign-stdout.sh

LOGFILE=logfile.txt

exec 6>&1           # ارتباط دادن توصیف‌گر فایل شماره ‎6‎ با ‎stdout‎
                    #                     ‎stdout‎ را ذخیره می‌کند.

exec > $LOGFILE     #   ‎stdout‎ با فایل ‎logfile.txt‎ تعویض گردیده.

#------------------------------------------------------------- #
#تمام خروجی فرمان‌ها در این بلوک به فایل ‎$LOGFILE‎ فرستاده می‌شوند.

echo -n "Logfile: "
date
echo "-------------------------------------"
echo

echo "Output of \"ls -al\" command"
echo
ls -al
echo; echo
echo "Output of \"df\" command"
echo
df

#------------------------------------------------------------- #

exec 1>&6 6>&-      # بازیابی ‎stdout‎ و بستن توصیف‌گر فایل شماره ‎6‎

echo
echo "== stdout now restored to default == "
echo
ls -al
echo

exit 0

مثال ‎20-3‎. تغییر مسیر دادن ‎stdin‎ و ‎stdout‎ با exec هر دو با هم در یک اسکریپت

#!/bin/bash
# upperconv.sh
#       محتوای فایل ورودی مشخص شده را به حروف بزرگ تبدیل می‌کند.

E_FILE_ACCESS=70
E_WRONG_ARGS=71

if [ ! -r "$1" ]     #آیا فایل ورودی تعیین شده قابل خواندن است؟
then
  echo "Can't read from input file!"
  echo "Usage: $0 input-file output-file"
  exit $E_FILE_ACCESS
fi                   #  حتی اگر فایل ورودی ‎($1)‎ مشخص نشده باشد،
                     #+       با همان خطا خارج خواهد شد (چرا؟).

if [ -z "$2" ]
then
  echo "Need to specify output file."
  echo "Usage: $0 input-file output-file"
  exit $E_WRONG_ARGS
fi


exec 4<&0
exec < $1                     #از فایل ورودی خواهد خواند.

exec 7>&1
exec > $2                     # در فایل خروجی خواهد نوشت.
#با فرض قابل نوشتن بودن فایل خروجی (بررسی را اضافه کنید)‏.

# --------------------------------------------------------
    cat - | tr a-z A-Z        #       تبدیل به حروف بزرگ.
#   ^^^^^                     #         از ‎stdin‎ می‌خواند.
#           ^^^^^^^^^^        #        در ‎stdout‎ می‌نویسد.
#  به هرحال، هم ‎stdin‎ و هم ‎stdout‎ تغییر مسیر داده شده‌اند.
#                 توجه نمایید که cat می‌تواند صرفنظر بشود.
# --------------------------------------------------------

exec 1>&7 7>&-                #   بازیابی ‎stout‎
exec 0<&4 4<&-                #   بازیابی ‎stdin‎

#بعد از بازگردانی، سطر زیر به طوریکه انتظار می‌رود در ‎stdout‎ چاپ می‌شود.
echo "File \"$1\" written to \"$2\" as uppercase conversion."

exit 0

تغییر مسیر ورودی-خروجی، روش هوشمندانه‌ای برای پرهیز از مشکل رنجش‌آور غیر قابل دسترس بودن متغیرها در درون یک پوسته فرعی است.

مثال ‎20-4‎. پرهیز از پوسته فرعی

#!/bin/bash
#  avoid-subshell.sh
# پیشنهاد شده توسط ‎Matthew Walker‎
Lines=0
echo
cat myfile.txt | while read line;
                 do {
                   echo $line
                   (( Lines++ ));  # مقادیر افزایش یافته این متغیر
#+                  در خارج حلقه قابل دسترس نیست. مشکل پوسته فرعی.
                 }
                 done
echo "Number of lines read = $Lines"     #‎0‎، اشتباه!
echo "------------------------"
exec 3<> myfile.txt
while read line <&3
do {
  echo "$line"
  (( Lines++ ));                   # مقادیر افزایش یافته این متغیر
#+      خارج از حلقه قابل دستیابی است. بدون پوسته فرعی، بدون مشکل.
}
done
exec 3>&-
echo "Number of lines read = $Lines"     # ‎8‎، صحیح
exit 0