فصل سیزدهم - ورودی و خروجی

از Linuxreview Wiki
پرش به: ناوبری, جستجو


تا به اینجا شما با دستوراتی آشنا شدید که پیغامی را در صفحه نمایش چاپ میکنند. و احتمالا باید بدانید زمانی که یک پیغام بر روی صفحه نمایش چاپ میشود به آن خروجی (output) گفته میشود.
در این فصل شما با انواع خروجی‌های شل آشنا شده و تفاوت‌ بین آن‌ها را خواهید آموخت. این فصل همچنین ورودی‌هایی که از یک کاربر میتوان دریافت کرد را شرح میدهد.
ارتباط یک برنامهٔ تحت خط فرمان، با دنیای بیرون (از جمله کاربر و سایر برنامه‌ها) معمولاً از سه طریق پایه‌ای و استاندارد است:

  1. ورودی استاندارد یا STDIN
  2. خروجی استاندارد یا STDOUT
  3. خطای استاندارد یا STDERR

به این سه، جریان‌های استاندارد یا standard streams می‌گویند. یک برنامهٔ تحت خط فرمان، شبیه یک جعبهٔ سیاه است، که سه لوله به آن متصل است. که هر یک از این سه لوله، می‌تواند به یکی از این‌ها وصل شود:

  1. جعبهٔ ترمینال
  2. یک جعبهٔ فایل
  3. یک لوله(جریان) از برنامهٔ دیگر
  4. ناکجاآباد یا 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

علاوه بر فاصله میتوانید از نقطه گذاری، جانشینی در متغیرها، و کاراکترهای خاص در یک رشته استفاده کنید.

نکته: سه کاراکتر خاصی که میتوان در یک رشته تعبیه کرد در جدول زیر تعریف شده‌اند:

  1. ‎\n مکان نما را به ابتدای خط بعد منتقل میکند.
  2. ‎\t مشابه کاراکتر تب عمل میکند
  3. ‎\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

نکته:

  1. برای استفاده از این حالت کل رشته باید داخل کاراکتر نقل دوگانه ("") قرار بگیرد
  2. آپشن 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 یکی از موارد جدول زیر میتواند باشد:

بسته به مقدار 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

ورودی

بیشتر اسکریپت‌های یونیکس حالت تعاملی داشته و در بسیاری موارد از کاربر ورودی‌های مورد نیاز را دریافت میکنند.
در شل به سه طریق میتوان اطلاعات ورودی را دریافت کرد:

  1. هدایت ورودی از یک فایل
  2. هدایت ورودی کاربر
  3. لوله‌ها


هدایت ورودی از یک فایل

شکل کلی ساختار هدایت محتوای یک فایل به عنوان ورودی به صورت زیر است:

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 هدایت میشود

توصیف‌گرهای فایل

زمانی که با هر دستوری کار میکنید، سه فایل باز شده و همراه آن دستور قرار دارد. در شل برای هر یک از این سه فایل یک عدد صحیح در نظر گرفته شده که به آن توصیف‌گر فایل میگوند. سه فایلی که باز شده و همراه هر دستور وجود دارد به همراه توصیف‌گر مربوط به آن فایل در زیر آورده شده است:

  1. ورودی استاندارد 0 (STDIN)
  2. خروجی استاندارد 1 (STDOUT)
  3. ارور استاندارد 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/ اضافه میشود.

ابزارهای شخصی
گویش‌ها
فضاهای نام
عملکردها
گشتن
کتاب‌ها
مقاله‌ها
جعبه‌ابزار