فصل سیزدهم - ورودی و خروجی
تا به اینجا شما با دستوراتی آشنا شدید که پیغامی را در صفحه نمایش چاپ
میکنند. و احتمالا باید بدانید زمانی که یک پیغام بر روی صفحه نمایش چاپ
میشود به آن خروجی (output) گفته میشود.
در این فصل شما با انواع خروجیهای شل آشنا شده و تفاوت بین آنها را
خواهید آموخت. این فصل همچنین ورودیهایی که از یک کاربر میتوان دریافت کرد
را شرح میدهد.
ارتباط یک برنامهٔ تحت خط فرمان، با دنیای بیرون (از جمله کاربر و سایر برنامهها) معمولاً از سه طریق پایهای و استاندارد است:
- ورودی استاندارد یا STDIN
- خروجی استاندارد یا STDOUT
- خطای استاندارد یا STDERR
به این سه، جریانهای استاندارد یا standard streams میگویند. یک برنامهٔ تحت خط فرمان، شبیه یک جعبهٔ سیاه است، که سه لوله به آن متصل است. که هر یک از این سه لوله، میتواند به یکی از اینها وصل شود:
- جعبهٔ ترمینال
- یک جعبهٔ فایل
- یک لوله(جریان) از برنامهٔ دیگر
- ناکجاآباد یا dev/null/ (هر چیزی که آنجا برود نابود میشود!)
که البته میتوان هر جریان را به دو تا از اینها (مثلاً ترمینال و فایل) هم وصل کرد که در آینده بررسی خواهد شد. در حالت پیشفرض، هر سهٔ این جریانهای استاندارد (ورودی، خروجی، خطا) به ترمینال متصل میشود، مگر اینکه آن را به جای دیگر تغییرمسیر دهید (redirect کنید)
در تمام زبانهای برنامهنویسی، یک شیء شبیه شیء file pointer برای ارتباط با این سه جریان استاندارد (standard streams) وجود دارد. در لینوکس، سه فایل device زیر بطور مستقیم قابل استفاده هستند:
/dev/stdin /dev/stdout /dev/stderr
توجه: عبارت «لوله» که در اینجا بکار برده شد، فقط یک تشبیه بود، نباید آن را با مفهوم pipe در یونیکس اشتباه بگیرید. در یونیکس، stream(جریان) و pipe(لوله) شبیه هم هستند ولی یکی نیستند.
محتویات[نهفتن] |
خروجی و خطا
همان طور که در فصلهای قبل مشاهده کردید بیشتر دستورات شل دارای خروجی هستند. به عنوان مثال دستور date را در نظر بگیرید. این دستور منجر به چاپ اطلاعات به روی صفحه نمایش به فرم زیر میشود:
Thu Nov 12 16:32:35 PST 1998
نکته: زمانی که یک دستور موجب تولید خروجی در ترمینال میشود و اطلاعاتی را بر روی صفحه نمایش مینویسد، اصطلاحا گفته میشود که برنامه در "خروجی استاندارد" مینویسد. که به اختصار STDOUT گفته میشود. به عنوان مثال زمانی که شما دستور date را اجرا میکنید این دستور در خروجی استاندارد (STDOUT) مینویسد.
همچنین ممکن دستوری که اجرا میکنید منجر به چاپ یک خطا در صفحه نمایش شود. به عنوان مثال دستور زیر را در نظر بگیرید:
$ ln -s ch01.doc ch01-01.doc ln: cannot create ch01-1.doc: File Exists
در این حالت برنامه احتمالاً در خطای استاندارد مینویسد.
نکته: پیغامهای خطا معمولاً در STDERR نوشته میشوند، ولی گاهی هم ممکن است (بر خلاف استاندارد) در STDOUT نوشته شوند.
نکته: با نگاه کردن به ترمینال بعد از، یا در حال اجرای یک دستور، نمیتوانید با قطعیت تشخیص دهید که کدام قسمت از متن از طریق stdout به ترمینال رسیده، و کدام قسمت از طریق stderr. مگر آنکه یکی از این دو را به جایی متفاوت (مثل فایل، یا یک دستور دیگر) تغییر مسیر دهید.
فرستادن به خروجی یا خطای استاندارد
دستور echo
دستور echo معمولا برای چاپ رشتهها بصورت ساده و بدون تغییر خاص مورد استفاده قرار میگیرد. شکل کلی این دستور به صورت زیر است:
echo string
و برای فرستادن به خطای استاندارد:
echo string >&2
و اگر میخواهید هم به خروجی استاندارد فرستاده شود هم به خطای استاندارد:
echo string 1>&2
در اینجا string نام رشتهای است که شما قصد چاپ کردن آن را دارید.
مثال:
$ echo Hi Hi
همچنین میتوانید از فاصله برای جدا کردن رشتهها استفاده کنید:
$ echo Safeway has fresh fruit Safeway has fresh fruit
علاوه بر فاصله میتوانید از نقطه گذاری، جانشینی در متغیرها، و کاراکترهای خاص در یک رشته استفاده کنید.
نکته: سه کاراکتر خاصی که میتوان در یک رشته تعبیه کرد در جدول زیر تعریف شدهاند:
- \n مکان نما را به ابتدای خط بعد منتقل میکند.
- \t مشابه کاراکتر تب عمل میکند
- \c (پس از چاپ رشته مکان نما را به ابتدای خط بعد منتقل نمیکند. (در حالت پیش فرض پس از چاپ رشته مکان نما به ابتدای خط بعد منتقل میشود
رشته شما میتواند از نقطه گذاری در عبارت خود استفاده کند:
مثال:
$echo Do you want to install? Do you want to install? $echo ERROR: Could not find required libraries! Exiting. ERROR: Could not find required libraries! Exiting.
جانشینی در متغیرها
مثال:
echo Your home directory is $HOME Your home directory is /home/baroon
البته در این مثال از سادهترین شکل جانشینی استفاده شده است. ولی شما
میتوانید هر شکل از جانشینی در متغیرها را در رشتهی خود تعبیه کنید.
مثال: استفاده از کاراکترهای خاص
$ FRUIT_BASKET="apple orange pear" $ echo -e "Your fruit basket contains:\n$FRUIT_BASKET" Your fruit basket contains: apple orange pear
که بدون استفاده از این قابلیت معادل سه دستور زیر است:
$ FRUIT_BASKET="apple orange pear" $ echo Your fruit basket contains $ echo FRUIT_BASKET Your fruit basket contains: apple orange pear
نکته:
- برای استفاده از این حالت کل رشته باید داخل کاراکتر نقل دوگانه ("") قرار بگیرد
- آپشن e- برای استفاده از این ویژگی باید فعال باشد
استفاده از t\
مثال:
$ echo -e "Name\t\tUser Name\nSriranga\tranga\nSrivathsa\tvathsa" Name User Name Sriranga ranga Srivathsa vathsa
استفاده از c\
این آپشن موجب میشود خط بعدی در ادامهی خط جاری قرار گیرد.
مثال:
echo -e "Making directories, please wait...\t\c" for i ${DIRS_TO_MAKE} ; do mkdir -p $I ; done echo "Done."
که منجر به تولید خروجی زیر میشود:
Making directories, please wait... Done.
در صورتی که از c\ استفاده نشود خروجی بصورت زیر میشود:
Making directories, please wait... Done.
مثال
echo -e "Copying files, please wait\t\c" for i in ${FILES} ; do cp $i $DEST && echo ".\c" ; done echo -e "\tDone."
که منجر به تولید خروجی زیر میشود:
Copying files, please wait ........ Done
دستور printf
دستور printf نسخهی شل از دستور printf در زبان برنامه نویسی C است که انعطاف و قابلیتهای زیادی برای نمایش خروجی در قالبهای مخلتف دارد. شکل کلی این دستور مشابه دستور echo است. برای مثال دو دستور زیر یک خروجی را تولید میکنند:
$ echo "Is that a mango?" $ printf "Is that a mango?\n"
در واقع در این مثال تنها تفاوت عمده بین این دو دستور استفاده از n\ در انتهای عبارت در دستور printf است.
شکل کلی دستور printf به صورت زیر است:
printf format arguments
در این دستور format یک یا چند قالب برای نمایش رشته و arguments رشتههایی هستند که باید در خروجی نوشته شوند.
قالب دنباله داری که در این دستور میتوان استفاده کرد به فرم زیر است:
%[-]m.nx
در اینجا ٪ شروع قالب بندی را نشان میدهد. و x یکی از موارد جدول زیر میتواند باشد:
- s : رشته
- c : کاراکتر
- d : عدد صحیح
- x : عدد صحیح در مبنای شانزده
- o : عدد صحیح در مبنای هشت
- e : تعداد رقم نمایی
- g : تعداد رقم
- f : تعداد ممیز شناور
بسته به مقدار x، مقادیر m و n به ترتیب حداکثر و حداقل طول رشته را
تعیین میکنند. و کاراکتر - راستچین و چپچین بودن را نشان میدهد. البته به
طور پیش فرض تمامی فیلدها راست چین هستند.
دومثال زیر را در نظر بگیرید که اولی از دستور echo و دومی از دستور printf استفاده میکنند:
#!/bin/sh echo "File Name\tType" for i in *; do echo "$i\t\c" if [ -d $i ]; then echo "directory" elif [ -h $i ]; then echo "symbolic link" elif [ -f $i ]; then echo "file" else echo "unknown" fi done
این اسکریپت خروجی زیر را تولید میکند:
File Name Type RCS directory dev directory humor directory images directory index.html file
install directory
java directory
همانطور که مشاهده میکنید ستونها دارای هماهنگی نیستند و به خاطر طول
متفاوت رشتهها خروجی دارای ناهماهنگی در نمایش کلمات میباشد و هر رشته در
زیر ستون خود قرار نگرفتهاست.
اما با استفاده دستور prinft میتوان این مشکل را حل نمود.
در ابتدا شما باید نوع رشتهای که میخواهید استفاده کنید را مشخص کنید. که
در اینجا s% به خاطر رشتهای بودن متن مورد نظر است. سپس باید حداقل طول را
برای ستون اول مشخص کنید که در اینجا ۳۲ عدد مناسبی است و با 32s% تعیین
میشود. از آنجا که در ستون دوم مهم نیست چقدر کاراکترها طولانی باشند به
استفاده از s% به تنهایی بسنده میکنیم.
حال بازنویسی مثال بالا با دستور printf به صورت زیر درمیآید:
#!/bin/sh printf "%32s %s\n" "File Name" "File Type" for i in *; do printf "%32s " "$i" if [ -d "$i" ]; then echo "directory" elif [ -h "$i" ]; then echo "symbolic link" elif [ -f "$i" ]; then echo "file" else echo "unknown" fi; done
که خروجی زیر را تولید خواهد کرد:
File Name File Type RCS directory dev directory humor directory images directory index.html file install directory java directory
همانطور که مشاهده میکنید به دلیل چنیش کلمات از سمت راست، عبارت نظم دقیقی ندارد. برای حل این مشکل با استفاده از کاراکتر - چینش رشتهها در فضای خالی را از سمت چپ قرار میدهیم.
#!/bin/sh printf "%-32s %s\n" "File Name" "File Type" for i in *; do printf "%-32s " "$i" if [ -d "$i" ]; then echo "directory" elif [ -h "$i" ]; then echo "symbolic link" elif [ -f "$i" ]; then echo "file" else echo "unknown" fi; done
که منجر به تولید خروجی زیر میشود:
File Name File Type RCS directory dev directory humor directory images directory index.html file install directory java directory
تغییرمسیر به فایل
در هنگام کار در محیط شل بسیار اوقات نیاز است خروجی یک دستور را گرفته و
در یک فایل ذخیره کنید تا بتوانید به راحتی تغییرات لازم را بر روی آن
انجام دهید.
در یونیکس به فرآیند گرفتن خروجی از یک دستور و نوشتن آن در یک فایل،
redirect کردن گفته میشود که معمولاً به «تغییرمسیر» یا «هدایت» ترجمه
میشود(تغییرمسیر ترجمهٔ بهتری است، چون هدایت کردن ترجمهٔ direct است و
پیشوند re را نادیده میگیرد)
برای تغییرمسیر خروجی یک دستور به یک فایل از عملگر < استفاده میشود. شکل کلی استفاده از این عملگر به صورت زیر است:
command > ofile
و برای تغییرمسیر خطا به فایل
command 2> efile
در این دستور چنانچه فایل با نام file موجود نباشد، بوجود میآید و چنانچه وجود داشته باشد بازنویسی میشود.
اگر میخواهید، در صورت وجود فایل، به انتهای آن اضافه کند، از علامت << استفاده کنید:
command >> ofile command 2>> efile
مثال:
date > now
در این مثال خروجی دستور date بجای چاپ روی صحفه نمایش، داخل فایل now نوشته میشود.
مثال:
میتوان خروجی چند دستور را به طور همزمان هدایت کرد:
{ date; uptime; who ; } > mylog
در این مثال خروجی هر سه دستور date uptime who در فایل mylog نوشته میشوند.
نکته: توجه کنید که در این حالت در صورتی که فایل از قبل وجود داشته باشد تمام اطلاعات آن از بین رفته و اطلاعات جدید جایگزین میشوند.
تغییرمسیر به فایل و ترمینال
در شل این امکان نیز میسر شده است که علاوه هدایت خروجی یک دستور به فایل روی صفحهی آنرا نمایش نیز داد. برای این منظور از دستور tee به صورت زیر استفاده میشود:
command | tee file
مثال:
$ date | tee now
که علاوه بر نوشتن خروجی دستور date در فایل now، آنرا نمایش نیز میدهد:
Sat Nov 14 19:50:16 PST 1998
این دستور در اسکریپتهایی که نیاز دارند تا تمام خروجیها را در یک فایل log ذخیره کنند، بسیار مفید است.
اسکریپت زیر را در نظر بگیرید:
if [ "$LOGGING" != "true" ] ; then LOGGING="true" ; export LOGGING ; exec $0 | tee $LOGFILE fi
محو/نابود کردن خروجی و خطا
اگر میخواهید فقط خروجی دستور محو شود:
command > /dev/null
اگر میخواهید خطا محو شود:
command 2> /dev/null
و اگر میخواهید هر دو محو شوند (و هیچ چیزی در ترمینال نشان داده نشود)
command > /dev/null 2>&1
ورودی
بیشتر اسکریپتهای یونیکس حالت تعاملی داشته و در بسیاری موارد از کاربر ورودیهای مورد نیاز را دریافت میکنند.
در شل به سه طریق میتوان اطلاعات ورودی را دریافت کرد:
- هدایت ورودی از یک فایل
- هدایت ورودی کاربر
- لولهها
هدایت ورودی از یک فایل
شکل کلی ساختار هدایت محتوای یک فایل به عنوان ورودی به صورت زیر است:
command < file
در این ساختار محتویات file به عنوان ورودی دستور command قرار میگیرد.
مثال:
Mail ranga@soda.berkeley.edu < Final_Exam_Answers
در این دستور محتویات فایل Final_Exam_Answers به عنوان ورودی دستور Mail مورد استفاده قرار میگیرد
هدایت درجا
همانند حالت قبل است با این تفاوت که چیزی در حافظه موقت ذخیره نمیشوند. ساختار کلی این نوع از هدایت فایل به صورت زیر است:
command << delimiter document delimiter
که در اینجا document یک متن اختیاری به عنوان ورودی است. و delimiter پایان سند را مشخص میکند.
توجه کنید که در اینجا متنی که به عنوان ورودی هدایت میشود در هیچ فایلی ذخیره نمیشود.
مثال:
lpr << MYURLS http://www.csua.berkeley.edu/~ranga/ http://www.cisco.com/ http://www.marathon.org/story/ http://www.gnu.org/ MYURLS
همچنین میتوان این ساختار را با هدایت خروجی یک دستور ترکیب کرد:
command > file << delimiter document delimiter
در این ساختار ورودی دستور command از document و خروجی حاصل از آن در file نوشته میشود.
مثال:
cat > urls << MYURLS http://www.csua.berkeley.edu/~ranga/ http://www.cisco.com/ http://www.marathon.org/story/ http://www.gnu.org/ MYURLS
خواندن از ورودی
یک عمل رایج در شل دریافت ورودی از کاربر است. برای این منظور از دستور read استفاده میشود. ساختار کلی دستور read به صورت زیر است:
read name
که در آن name نام متغیری است که پاسخ کاربر در آن ذخیره میشود.
مثال:
YN=yes printf "Do you want to play a game [$YN]? " read YN : ${YN:=yes} case $YN in [yY]|[yY][eE][sS]) exec xblast ;; *) echo "Maybe later." ;; esac
در این مثال شما یک مقدار پیش فرض را برای متغیر در نظر گرفته و سپس
مقداری را که کاربر وارد کرده در یک ساختار case بررسی میکنید و متناسب با
مقداری که کاربر وارد کرده، عمل متناسب آن انجام میشود.
یکی از کاربردهای دستور read خواندن یک فایل به صورت خط به خط است. ساختار کلی این دستور به شکل زیر است:
while read LINE do : # manipulate file here done < file
مثال
while read LINE do case $LINE in *root*) echo $LINE ;; esac done < /etc/passwd
در این اسکریپت تنها آن خط از فایل passwd نشان داده میشود که حاوی عبارت root میباشد. در سیستم من خروجی به صورت زیر است:
root:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root:/sbin/nologin
معمولاً در هنگام گرفتن یک ورودی از کاربر، پیغام یا سوالی نشان داده
میشود تا کاربر بداند چه چیزی باید وارد کند، همانطور که در مثالهای بالا
میبینید از دستور echo یا printf برای نمایش این پیغام که به آن Prompt
میگویند استفاده شده است. ولی خود دستور read هم آپشنی دارد که Prompt را
به آن میدهید، مثلاً این دو خط:
printf "Do you want to play a game [$YN]? " read YN
را میتوان در یک خط خلاصه کرد:
read -p "Do you want to play a game [$YN]? " YN
لولهها یا PIPE ها
بیشتر دستورات یونیکس بطوری طراحی شدهاند با فایلها کار میکنند و
همچنین میتوانند ورودی خود را از ورودی استاندارد دریافت کنند. در شل این
امکان وجود دارد تا با استفاده از لولهها خروجی یک دستور را به عنوان
ورودی دستور دیگر مورد استفاده قرار دهید.
شکل کلی استفاده از لولهها به صورت زیر است:
command1 | command2 | ...
کاراکتر | خروجی استاندارد دستور command1 را به عنوان ورودی استاندارد دستور command2 مورد استفاده قرار میدهد و غیره... .
مثال:
tail -f /var/adm/messages | more ps -ael | grep "$UID" | more
در مثال اول خروجی دستور ... tail به عنوان ورودی دستور more هدایت میشود. و در مثال دوم خروجی دستور ps به عنوان ورودی دستور grep هدایت شده و خروجی حاصل از انجام این دستور برای نمایش به دستور more هدایت میشود
توصیفگرهای فایل
زمانی که با هر دستوری کار میکنید، سه فایل باز شده و همراه آن دستور قرار دارد. در شل برای هر یک از این سه فایل یک عدد صحیح در نظر گرفته شده که به آن توصیفگر فایل میگوند. سه فایلی که باز شده و همراه هر دستور وجود دارد به همراه توصیفگر مربوط به آن فایل در زیر آورده شده است:
- ورودی استاندارد 0 (STDIN)
- خروجی استاندارد 1 (STDOUT)
- ارور استاندارد 2 (STDERR)
عددی که همراه هر کدام از این سه فایل آوره شده است، توصیف گر مربوط به آن فایل است. در بخش قبل شما فایلها را با استفاده از هدایت کنندههای پیش فرض به ورودی یا خروجی استاندارد هدایت کردید. در این بخش شکل کامل و عمومی هدایت فایلها مورد بررسی قرار میگیرند.
فایلهای مشترک به همراه توصیفگر فایلها
در شل شما امکان هدایت فایل به تعداد دفعات دلخواه دارید، بدون اینکه چندین بار دستوری را تکرار کنید.
برای باز کردن یک فایل به منظور نوشتن در آن به تعداد دفعات دلخواه، شکل زیر از دستور exec مورد استفاده قرار میگیرد:
exec n>file exec n>>file
که در اینجا n یک عدد صحیح و file نام فایل مورد نظر برای باز شدن و نوشتن درون آن است.
و برای باز کردن یک فایل به خواندن از آن از فرم زیر استفاده میشود:
exec n<file
مثال
$ exec 4>fd4.out
این دستور فایل fd4.out را با توصیف گر چهار باز میکند.
هدایت ورودی/خروجی در حالت کلی
برای هدایت خروجی یک دستور به همراه توصیفگر آن از فرم زیر استفاده میشود:
command n> file command n>> file
به عنوان مثال میتوانید با توصیفگر 1 از هدایت خروجی استاندارد استفاده کنید:
command 1> file command 1>> file
و برای حالت ورودی فرم کلی به صورت زیر است:
command n<file
مثال زیر دریافت هدایت و دریافت یک فایل از ورودی استاندارد را نشان میدهد:
command 0<file
هدایت STDERR و STDOUT به چندین فایل
یکی از کاربردهای شل امکان هدایت خروجی و ارور استاندارد به دو فایل مجزا است. برای این منظور، از دستورات به صورت زیر میتوان استفاده کرد:
command 1> file1 2> file2
که در اینجا خروجی به file1 و ارورها به file2 منتقل میشوند.
البته معمولا توصیف گر فایل خروجی استاندارد نوشته نمیشود. بدین ترتیب دستور بالا به فرم زیر تبدیل میشود:
command > file1 2> file2
همچنین میتوان از هر کدام از عملگرهای هدایت خروجی برای نوشتن در فایل استفاده کرد:
command >> file1 2> file2 command > file1 2>> file2 command >> file1 2>> file2
مثال:
for FILE in $FILES do ln -s $FILE ./docs >> /tmp/ln.log 2> /dev/null done
که در اینجا خروجی استاندارد دستور ls در فایل tmp/ln.log/ ذخیره شده و ارورهای آن به فایل dev/null/ هدایت میشوند.
تغییرمسیر STDERR و STDOUT به یک فایل مشترک
برای اینمنظور از دستور به فرم زیر استفاده میشود:
command > file 2>&1 command >> file 2>&1
در این دستور خروجی استاندارد و ارور استاندارد در یک فایل ذخیره میشوند.
مثال:
rm -rf /tmp/my_tmp_dir > /dev/null 2>&1 ; mkdir /tmp/my_tmp_dir
در اینجا هیچ پیغام یا خطایی که حاصل از انجا دستور rm است بر روی صفحه نمایش داده نمیشود. و به پوشه dev/null/ هدایت میشود.
مثال:
rdate -s ntp.nasa.gov >> /var/log/rdate.log 2>&1
در این مثال هر خروجی یا ارور حاصل از اجرای دستور rdate به انتهای فایل var/log/rdate.log/ اضافه میشود.