الحصول على مُدخلات المستخدم في Bash

من موسوعة حسوب
اذهب إلى: تصفح، ابحث

استخدام أمر read

أمر read هو الأمر المتمم لأمريْ echo و printf، وبنيته اللغوية كالتالي:
read [options] NAME1 NAME2 ... NAMEN
يُقرأ سطر واحد من المُدخل القياسي (standard input) أو من واصف ملف (file descriptor) أُدخِل كوسيط لخيار u-، وتُعيَّن أول كلمة من السطر لأول اسم NAME1، والثانية للاسم الثاني NAME2 وهكذا، ثم تُعيَّن الكلمات المتبقية وفاصلاتها المتداخلة (intervening seperators) إلى الاسم الأخير NAMEN، أما إن كانت الكلمات المقروءة من مجرى الدخل أقل من الأسماء فتعيَّن قيم فارغة لتلك الأسماء.

تُستخدم المحارف في قيمة متغير IFS لفصل سطر المُدخل إلى كلمات أو وحدات (tokens)، انظر انقسام الكلمات في فصل التوسعات في Bash، وقد يُستخدم محرف الشرطة المائلة الخلفية لمتابعة السطر ولإزالة أي معنىً خاص للمحرف التالي. كذلك إن لم يٌدخل أي اسم فإن السطر المقروء يُعيَّن إلى متغير REPLY.

يكون رمز الإعادة (return code) لأمر read صفرًا إن لم يوجد محرف end-of-file أو يكون وقت انتظار الأمر قد انتهى (time out) أو يُدخل واصف ملف غير صالح كوسيط لخيار u-. كذلك ي-دعم أمر read الخيارات التالية:

جدول 8-2: خيارات أمر read.

الخيار المعنى
a ANAME- تُعيَّن الكلمات إلى الفهارس المتسلسلة لمتغير المصفوفة ANAME بدءًا من 0، وتُحذف كل العناصر من ANAME قبل التعيين، ويتم تجاهل بقية وسائط NAME.
d DELIM- يُستخدم أول محرف من DELIM لإنهاء سطر المدخلات بدلًا من محرف السطر الجديد (newline).
e- يُستخدم readline للحصول على السطر.
n NCHARS- يعود أمر read بعد قراءة محارف NCHARS بدلًا من انتظار سطر إدخال كامل.
p PROMPT- يعرض PROMPT بدون سطر جديد لاحق (trailing newline)، قبل محاولة قراءة أي مُدخلات، ولا يُعرض المحث إلا إن كان المُدخل قادمًا من طرفية.
r- إن استُخدِم هذا الخيار فلا تتصرف الشرطة المائلة الخلفية \ كمحرف هروب (escape character)، بل تكون جزءًا من السطر، ولا يُستخدم محرفي الشرطة المائلة الخلفية والسطر الجديد معًا لمتابعة السطر.
s- الوضع الصامت، إن أتى المُدخل من طرفية فلا تُطبع المحارف.
t TIMEOUT- هذا الخيار ينهي وقت أمر read و يجعله يعيد فشلًا إن لم يُقرأ سطر مدخلات كامل في غضون زمن TIMEOUT المحدد بالثواني. وليس لهذا الخيار تأثير إن لم يقرأ read مُدخلات من الطرفية أو من أنبوب (pipe).
u FD- يقرأ مُدخلات من واصف ملف FD.
المثال التالي هو نسخة محسنة من testleap.sh من عبارات if المتداخلة، فصل الاستخدامات المتقدمة لعبارة if الشرطية في Bash:
hsoub ~/test> cat leaptest.sh
#!/bin/bash
# سيتفقد هذا البرنامج السنة الحالية لنعرف ما إن كنا في سنة كبيسة.

echo "Type the year that you want to check (4 digits), followed by [ENTER]:"

read year

if (( ("$year" % 400) == "0" )) || (( ("$year" % 4 == "0") && ("$year" % 100 !=
"0") )); then
  echo "$year is a leap year."
else
  echo "This is not a leap year."
fi

hsoub ~/test> leaptest.sh
Type the year that you want to check (4 digits), followed by [ENTER]:
2000
2000 is a leap year.

طلب إدخال المستخدم

يبين المثال التالي كيف تستخدم المحثات لشرح ما يجب أن يُدخِله المستخدم:
hsoub ~/test> cat friends.sh
#!/bin/bash

# يحفظ هذا البرنامج دفتر عناوينك مُحدَّثًا

friends="/var/tmp/michel/friends"

echo "Hello, "$USER".  This script will register you in Hsoub's friends database."

echo -n "Enter your name and press [ENTER]: "
read name
echo -n "Enter your gender and press [ENTER]: "
read -n 1 gender
echo

grep -i "$name" "$friends"

if  [ $? == 0 ]; then
  echo "You are already registered, quitting."
  exit 1
elif [ "$gender" == "m" ]; then
  echo "You are added to Hsoub's friends list."
  exit 1
else
  echo -n "How old are you? "
  read age
  if [ $age -lt 25 ]; then
    echo -n "Which colour of hair do you have? "
    read colour
    echo "$name $age $colour" >> "$friends" 
    echo "You are added to Hsoub's friends list.  Thank you so much!"
  else
    echo "You are added to Hsoub's friends list."
    exit 1
  fi
fi

michel ~/test> cp friends.sh /var/tmp; cd /var/tmp

michel ~/test> touch friends; chmod a+w friends

michel ~/test> friends.sh
Hello, hsoub.  This script will register you in Hsoub's friends database.
Enter your name and press [ENTER]: michel
Enter your gender and press [ENTER] :m
You are added to Hsoub's friends list.

hsoub ~/test> cat friends
لاحظ أن الخرج لم يُهمل هنا، فالبرنامج يُخزن المعلومات عن الناس الذين يهتم بهم المستخدم حسوب فقط، لكنه سيخبرك دومًا أنك أُضِفتَ إلى القائمة، إلا إن كنت فيها بالفعل. يمكن لأي مستخدم الآن أن يبدأ تنفيذ البرنامج:
[anny@octarine tmp]$ friends.sh
Hello, anny.  This script will register you in Hsoub's friends database.
Enter your name and press [ENTER]: anny
Enter your gender and press [ENTER] :f
How old are you? 22
Which colour of hair do you have? black
You are added to Hsoub's friends list.
ستبدو قائمة friends بعد فترة قريبًا من هذه:
tille 24 black
anny 22 black
katya 22 blonde
maria 21 black
--output omitted--
هذا الموقف لا يمثل الحالة المثالية بما أنه بإمكان أي أحد أن يعدِّل على ملفات المستخدم حسوب (لكن لا يستطيع حذفها)، ويمكنك حل تلك المشكلة باستخدام أوضاع الدخول الخاصة على ملف البرنامج، انظر SUID و SGID في دليل مقدمة إلى لينكس.

إعادة التوجيه وواصفات الملفات

لعلك تعلم من الاستخدام البسيط للصدفة أن مدخلات أمر ما ومخرجاته قد يعاد توجيهها قبل التنفيذ باستخدام رموز خاصة تفسرها الصدفة -معامِلات إعادة التوجيه-، ويمكن استخدام إعادة التوجيه أيضًا لفتح وإغلاق الملفات لبيئة التنفيذ الحالية للصدفة.

قد تحدث إعادة التوجيه كذلك في برنامج للصدفة (shell script) ليستطيع استقبال مدخلات من ملف مثلًا أو يرسل مخرجات إليه، ويستطيع المستخدم أن يراجع ملف المخرجات فيما بعد، كما يمكن استخدام ملف المخرجات ذاك كمُدخل لبرنامج آخر.

يمكن إتمام مدخلات ملف ما ومخرجاته باستخدام ماسِكات رقمية (integer handles) تراقب كل الملفات المفتوحة لعملية ما، وتُعرف تلك القيَم الرقمية باسم واصفات الملفات (file descriptors)، وأشهر تلك الواصفات هي stdin و stdout و sterr، وأرقام واصفات الملفات لهم هي 0 و 1 و 2 على الترتيب، وتُحفظ تلك الأرقام والأجهزة التي تقابلها. كذلك يمكن لصدفة Bash أن تأخذ منافذ TCP و UDP على الأجهزة المضيفة المتصلة ببعضها (networked hosts) كواصفات ملفات أيضًا.

يوضح المثال التالي كيف تشير وصافات الملفات المحفوظة إلى أجهزة حقيقية:
hsoub ~> ls -l /dev/std*
lrwxrwxrwx  1 root    root     17 Oct  2 07:46 /dev/stderr -> ../proc/self/fd/2
lrwxrwxrwx  1 root    root     17 Oct  2 07:46 /dev/stdin -> ../proc/self/fd/0
lrwxrwxrwx  1 root    root     17 Oct  2 07:46 /dev/stdout -> ../proc/self/fd/1

hsoub ~> ls -l /proc/self/fd/[0-2]
lrwx------  1 hsoub  hsoub   64 Jan 23 12:11 /proc/self/fd/0 -> /dev/pts/6
lrwx------  1 hsoub  hsoub   64 Jan 23 12:11 /proc/self/fd/1 -> /dev/pts/6
lrwx------  1 hsoub  hsoub   64 Jan 23 12:11 /proc/self/fd/2 -> /dev/pts/6
لاحظ أن كل عملية لديها عرضها الخاص للملفات في proc/self/ إذ أنه رابط رمزي إلى <proc/<process_ID/، ربما يجب أن تنظر في info MAKEDEV و info proc للمزيد من المعلومات عن مجلدات proc/ الفرعية والطريقة التي يعالج بها نظامك واصفات الملفات القياسية لكل عملية جارية.

تُتَّبع الخطوات التالية بالترتيب عند تنفيذ أمر ما:

  • إن أُدخل الخرج القياسي لأمر سابق في الدخل القياسي للأمر الحالي عبر أنبوب فإن واصف الملف proc/<current_process_ID>/fd/0 يُحدَّث ليستهدف نفس الأنبوب المجهول كـ proc/<previous_process_ID>/fd/1.
  • إن أُدخل الخرج القياسي للأمر الحالي في الدخل القياسي للأمر التالي عبر أنبوب فإن واصف الملف proc/<current_process_ID>/fd/1 يُحدَّث ليستهدف أنبوبًا مجهولًا آخر.
  • تُعالَج إعادة توجيه الأمر الحالي من اليسار إلى اليمين.
  • إعادة توجيه "N>&M" أو "N<&M" بعد أمر ما لها نفس تأثير إنشاء أو تحديث الرابط الرمزي proc/self/fd/N مع نفس الهدف كرابط proc/self/fd/M.
  • إعادات التوجيه "N>file" و "N<file" لها تأثير إنشاء أو تحديث الرابط الرمزي proc/self/fd/N مع الملف الهدف.
  • إغلاق واصف الملف "-&<N" له تأثير حذف الرابط الرمزي proc/self/fd/N.
  • يُنفَّذ الأمر الحالي الآن فقط.
لا تحدث تغييرات كثيرة حين تنفذ برنامجًا من سطر الأوامر لأن الصدفة الفرعية ستستخدم نفس واصفات الملفات التي تستخدمها الصدفة الأم، فإن لم توجد الصدفة الأم -كما في حالة تنفيذ الأمر باستخدام أداة cron مثلًا- فإن واصفات الملفات القياسية تكون أنابيب أو أي ملفات مؤقتة ما لم تُستخدم إحدى صور إعادة التوجيه. انظر المثال التالي الذي يبين خرج برنامج at بسيط:
hsoub ~> date
Fri Jan 24 11:05:50 CET 2003

hsoub ~> at 1107
warning: commands will be executed using (in order) 
a) $SHELL b) login shell c)/bin/sh
at> ls -l /proc/self/fd/ > /var/tmp/fdtest.at
at> <EOT>
job 10 at 2003-01-24 11:07

hsoub ~> cat /var/tmp/fdtest.at
total 0
lr-x------    1 michel michel  64 Jan 24 11:07 0 -> /var/spool/at/!0000c010959eb (deleted)
l-wx------    1 michel michel  64 Jan 24 11:07 1 -> /var/tmp/fdtest.at
l-wx------    1 michel michel  64 Jan 24 11:07 2 -> /var/spool/at/spool/a0000c010959eb
lr-x------    1 michel michel  64 Jan 24 11:07 3 -> /proc/21949/fd

إعادة توجيه الأخطاء

يتضح من الأمثلة السابقة أنك تستطيع إضافة مدخلات ومخرجات إلى برنامج -انظر مُدخلات الملف ومخرجاته بالأسفل- لكن قد ينسى البعض إعادة توجيه الأخطاء - المخرجات التي قد نكون في حاجة إليها فيما بعد. ربما إن كنت محظوظًا فستُرسَل إليك الأخطاء لتعرف أسباب فشل البرنامج، أما إن لم يكن لك نصيب في ذلك الحظ فتتسبب الأخطاء في فشل برنامجك لكنك لن تستطيع تصحيحها لأنها لم تُرسل إليك أو تُلتقط في أي مكان آخر.

لاحظ أن ترتيب الأسبقية في توجيه الأخطاء مهم جدًا، فهذا الأمر مثلًا سيوجه الخرج القياسي لأمر ls إلى ملف unaccessible-in-spool في var/tmp/:
ls -l * 2> /var/tmp/unaccessible-in-spool
أما هذا فسيوجه كلًا من الدخل والخرج القياسييْن إلى ملف spoollist:
ls -l * > /var/tmp/spoollist 2>&1
والأمر التالي يوجه الخرج القياسي فقط إلى ملف الوجهة لأن الخطأ القياسي قد نُسخَ إلى الخرج القياسي قبل أن يعاد توجيه الخرج.

تُوجَّه الأخطاء عادة إلى dev/null/ عند التأكد أن المستخدم لن يحتاج إليها، ستجد مئات الأمثلة في برامج بدء التشغيل في نظامك. تسمح Bash بإعادة توجيه كل من الخرج القياسي والخطأ القياسي إلى الملف الذي يكون اسمه نتيجة توسع FILE على هذه الصورة: File <&، وهذا يكافئ بنية FILE 2>&1 < التي استُخدمت في الأمثلة السابقة. يُجمع ذلك أيضًا مع إعادة التوجيه إلى dev/null/، حين تريد تنفيذ أمر مثلًا دون النظر إلى الأخطاء الذي سيحدِثُها أو الخرج الذي سينتج عنه.

مدخلات الملف ومخرجاته

استخدام dev/fd/

يحتوي مجلد dev/fd/ على مداخل (entries) بأسماء 0 ، 1 ، 2 ، وهكذا، ويكافئ فتح ملف dev/fd/N/ تكرار واصف الملف N، وإن كان نظامك به dev/stdin/ و dev/stdout/ و dev/stderr/، فسترى أن هؤلاء يكافئون dev/fd/0 و dev/fd/1 و dev/fd/2 على الترتيب.

والاستخدام الرئيسي لملفات dev/fd/ يكون من الصدفة، وتسمح تلك الآلية للبرامج التي تستخدم وسائط اسم المسار (pathname arguments) بمعالجة الدخل القياسي والخرج القياسي كذلك بنفس أسلوب معالجة بقية أسماء المسارات. فإن لم يكن dev/fd/ متاحًا في النظام فيجب أن تجد طريقة لتخطي المشكلة، باستخدام الشرطة مثلًا - لتوضيح أن البرنامج يجب أن يقرأ من أنبوب، انظر المثال التالي:
hsoub ~> filter body.txt.gz | cat header.txt - footer.txt
This text is printed at the beginning of each print job and thanks the sysadmin
for setting us up such a great printing infrastructure.

Text to be filtered.

This text is printed at the end of each print job.
يقرأ أمر cat ملف header.txt أولًا، ثم الدخل القياسي له الذي هو خرج أمر filter، ثم ملف footer.txt في النهاية، وانتبه أن المعنى الخاص للشرطة - كوسيط في سطر الأوامر يشير إلى الدخل القياسي أو الخرج القياسي هو مفهوم خاطئ وجد طريقه إلى برامج كثيرة. قد تكون هناك مشاكل أيضًا عند تحديد شرطة - كأول وسيط بما أنها قد تُفسَّر كخيار للأمر الذي يسبقها، وحل ذلك يكون باستخدام dev/fd/ من أجل توحيد الأسلوب وتلافي التفسير الخاطئ:
hsoub ~> filter body.txt | cat header.txt /dev/fd/0 footer.txt | lp
في المثال السابق، أُدخِل كل الخرج في أنبوب إلى lp لإرساله إلى الطابعة الافتراضية.

Read و exec

تعيين واصف ملف إلى ملف

يمكن النظر إلى واصفات الملفات على أنها طريقة لتعيين قيمة رقمية إلى ملف، فيمكنك استخدام رقم واصف الملف بدلًا من استخدام اسم الملف، ويُستخدم أمر exec لاستبدال صدفة العملية الحالية أو لتغيير واصفات الملفات للصدفة الحالية، فمثلًا قد تُستخدم لتعيين واصف ملف إلى ملف.

استخدم exec fdN> file لتعيين واصف الملف N إلى file من أجل الخرج، و exec fdN< file لتعيين N إلى file من أجل الدخل. يمكن استخدام واصف الملف بعد تعيينه إلى ملف مع معامِلات إعادة توجيه الصدفة، انظر المثال التالي:
hsoub ~> exec 4> result.txt

hsoub ~> filter body.txt | cat header.txt /dev/fd/0 footer.txt >& 4

hsoub ~> cat result.txt
This text is printed at the beginning of each print job and thanks the sysadmin
for setting us up such a great printing infrastructure.

Text to be filtered.

This text is printed at the end of each print job.
واصف الملف 5
استخدام واصف الملف 5 قد يسبب مشاكل، انظر دليل Bash المتقدم لكتابة برامج الصدفة، فصل إعادة توجيه المدخلات والمخرجات، إذ ننصح بشدة بعدم استخدامه.

أمر Read في برامج الصدفة

يبين المثال التالي كيفية التبديل بين مدخلات ملف ومدخلات سطر الأوامر:
hsoub ~/testdir> cat sysnotes.sh
#!/bin/bash

# This script makes an index of important config files, puts them together in
# a backup file and allows for adding comment for each file.

CONFIG=/var/tmp/sysconfig.out
rm "$CONFIG" 2>/dev/null

echo "Output will be saved in $CONFIG."

# create fd 7 with same target as fd 0 (save stdin "value")
exec 7<&0

# update fd 0 to target file /etc/passwd
exec < /etc/passwd

# Read the first line of /etc/passwd
read rootpasswd

echo "Saving root account info..."
echo "Your root account info:" >> "$CONFIG"
echo $rootpasswd >> "$CONFIG"

# update fd 0 to target fd 7 target (old fd 0 target); delete fd 7
exec 0<&7 7<&-

echo -n "Enter comment or [ENTER] for no comment: "
read comment; echo $comment >> "$CONFIG"

echo "Saving hosts information..."

# first prepare a hosts file not containing any comments
TEMP="/var/tmp/hosts.tmp"
cat /etc/hosts | grep -v "^#" > "$TEMP"

exec 7<&0
exec < "$TEMP"

read ip1 name1 alias1
read ip2 name2 alias2

echo "Your local host configuration:" >> "$CONFIG"

echo "$ip1 $name1 $alias1" >> "$CONFIG"
echo "$ip2 $name2 $alias2" >> "$CONFIG"

exec 0<&7 7<&-

echo -n "Enter comment or [ENTER] for no comment: "
read comment; echo $comment >> "$CONFIG"
rm "$TEMP"

hsoub ~/testdir> sysnotes.sh
Output will be saved in /var/tmp/sysconfig.out.
Saving root account info...
Enter comment or [ENTER] for no comment: hint for password: blue lagoon
Saving hosts information...
Enter comment or [ENTER] for no comment: in central DNS

hsoub ~/testdir> cat /var/tmp/sysconfig.out
Your root account info:
root:x:0:0:root:/root:/bin/bash
hint for password: blue lagoon
Your local host configuration:
127.0.0.1 localhost.localdomain localhost
192.168.42.1 tintagel.kingarthur.com tintagel
in central DNS

إغلاق واصفات الملفات

من الجيد التعود على إعلاق واصف ملف عند انتهاء الحاجة إليه بما أن العمليات الفرعية ترث واصفات الملفات المفتوحة، ويمكن فعل ذلك باستخدام البنية التالية:
exec fd<&-
وفي المثال السابق فإن واصف الملف 7 -الذي عُيِّن إلى الدخل القياسي- قد أُغلِق في كل مرة احتاج المستخدم فيها للوصول إلى الجهاز الفعلي للمدخل القياسي، والذي كان لوحة المفاتيح في الغالب. انظر المثال التالي الذي يبين إعادة توجيه الخطأ القياسي فقط إلى أنبوب:
hsoub ~> cat listdirs.sh
#!/bin/bash

# يطبع هذا البرنامج الخرج القياسي دون تغيير، بينما يوجه الخطأ القياسي 
# awk للمعالجة باستخدام

INPUTDIR="$1"

# fd 6 targets fd 1 target (console out) in current shell
exec 6>&1

# fd 1 targets pipe, fd 2 targets fd 1 target (pipe),
# fd 1 targets fd 6 target (console out), fd 6 closed, execute ls
ls "$INPUTDIR"/* 2>&1 >&6 6>&- \
				# Closes fd 6 for awk, but not for ls.

| awk 'BEGIN { FS=":" } { print "YOU HAVE NO ACCESS TO" $2 }' 6>&-

# fd 6 closed for current shell
exec 6>&-

مستندات Here

قد يحتاج برنامجك إلى مخاطبة برامج أخرى تتطلب مدخلات، ويوفر مستند here طريقة لتقرأ الصدفة المدخلات من المصدر الحالي حتى إيجاد السطر الذي يحتوي نص البحث فقط (بدون مسافات فارغة تليه)، وتُستخدم كل الأسطر المقروءة حتى تلك النقطة كمدخل قياسي للأمر. والنتيجة أنك لا تحتاج إلى التواصل مع ملفات منفصلةن فكل ما عليك فعله هو استخدام محارف خاصة بالصدفة، كما سيكون مظهر البرنامج أفضل من لو استخدمت سلسلة من أوامر echo:
hsoub ~> cat startsurf.sh
#!/bin/bash

# هذا البرنامج يقدم طريقة سهلة للاختيار بين المتصفحات

echo "These are the web browsers on this system:"
 
# here بدء مستند
cat << BROWSERS
mozilla
links
lynx
konqueror
opera
netscape
BROWSERS
# here انتهاء مستند

echo -n "Which is your favorite? "
read browser

echo "Starting $browser, please wait..."
$browser &

hsoub ~> startsurf.sh
These are the web browsers on this system:
mozilla
links
lynx
konqueror
opera
netscape
Which is your favorite? opera
Starting opera, please wait...
ورغم أننا نتحدث عن "مستند" here، إلا أنه يجب أن يكون في نفس ملف البرنامج، إليك مثالًا يثبّت حزمة بشكل آلي، رغم أنك تضطر إلى تأكيد التثبيت غالبًا:
#!/bin/bash
 
# yum هذا البرنامج يثبّت الحزَم تلقائيًا باستخدام.
 
if [ $# -lt 1 ]; then
        echo "Usage: $0 package."
        exit 1
fi
 
yum install $1 << CONFIRM
y
CONFIRM
ويعمل المثال السابق بالشكل التالي أدناه، حيث يجيب البرنامج بـ y تلقائيًا حين يظهر سؤال [Is this ok [y/N:
[root@hsoub bin]# ./install.sh tuxracer
Gathering header information file(s) from server(s)
Server: Fedora Linux 2 - i386 - core
Server: Fedora Linux 2 - i386 - freshrpms
Server: JPackage 1.5 for Fedora Core 2
Server: JPackage 1.5, generic
Server: Fedora Linux 2 - i386 - updates
Finding updated packages
Downloading needed headers
Resolving dependencies
Dependencies resolved
I will do the following:
[install: tuxracer 0.61-26.i386]
Is this ok [y/N]: EnterDownloading Packages
Running test transaction:
Test transaction complete, Success!
tuxracer 100 % done 1/1
Installed:  tuxracer 0.61-26.i386
Transaction(s) Complete

انظر أيضًا

مصادر