في رحلتك لتعلم النظام واستخدامه، قد مررت على الأرجح ذات مرة بتثبيت نظامٍ عبر الطرفيَّة، أو استخدام أداة تعرض لك مربعات وقوائم، كل ما يتطلب منك إدخال مدخل نصي، أو التحريك بالأسهم نزولًا وصعودًا والضغط على زر مسافة (space) لتحديد شيءٍ ما، كهذه الصورة:

أو كهذه:
وتساءلت كيف برمجها؟ وماذا تُسمَّى؟ وهل مِن المُمكِن برمجتها بنفسك؟
نعم، مِن المُمكِن برمجة برامج مشابهة طبق الأصل، فهذه ما تسمَّى بواجهةِ مستخدمٍ مبنيةٍ على النَصّ (text-based user interface) تُختصر بثلاثةِ حروفٍ «TUI»، وموضوعنا اليوم حول استخدام برمجيَّة «whiptail» التي تُمكِنُنا مِن برمجة برامج واجهة مستخدم مبنية على النَصّ بيسر وسهولة.
ما هو whiptail
هو برمجيَّة تُمكّن المُبَرمِج مِن عرضِ مربَّعات حوارٍ (dialog boxes) في الطرفيَّة مِن داخلِ نصوصٍ برمجيَّة بلغة باش (bash) لأيَّ غرض يُرِيده المُبَرمِج، وتُعتَبر صديقة للمستخدمِ لسهولة فهمها، ولبساطتها الجذابة المُغرية، ومريحَة للمُبَرمِج أيضًا إذ تجعَله يتنبَّئ بمدخلاتِ المستخدم بسطورٍ قليلة وبطريقةِ عرض جميلة.
يوجد برامج شبهة به تخصُّ واجهة المستخدم الرسومية (GUI) تَعرض مربَّعات حوار رسومية من داخل نصوص برمجية بلغة باش، مثل xdialog لعرض نوافذ X11، وzenity لعرض نوافذ GTK (يعرف أيضًا zenity باسم gdialog).
هناك برامج أخرى شبيهة لعمل whiptail مثل dialog الذي يُعتَبر نسخة مشابهة له، ولديه خيارات متنوعة، ومقال اليوم تستطيع تطبيقه على dialog أيضًا بنفس الخيارات.
قبل البدء في استخدامه يجب أن تكون عارِفًا بلغة باش، وأن تثبَّت whiptail عبر مدير الحُزم الخاصّ بتوزيعك، فإن لَمْ تجده فثبَّت dialog، وأنشِئ اسمًا مستعارًا له باسمِ whiptail، وتابِع المقال:
alias whiptail="dialog"
![]() |
---|
صورة لصفحة دليل whiptail |
مربع المعلومة (info box)
قد تودُّ عرض رسالة تُخبِر المستخدم بفعل شيءٍ وثُمَّ الخروج، كأخباره بأن يُشغِّل الملف كجذر (root)، ولهذا الغرض سنستخدم أبسط مربَّعات الحوار في whiptail مربَّع المعلومة (info box)، يَعرضُ هذا المربَّع معلومة للمستخدمِ، مربَّع به نصٌّ ما. صياغة الأمر:
whiptail --infobox text height width
- whiptail
البرنامج المسؤول عن عرضِ مربَّع الحوارات. - (–infobox)
الخيار الذي نُرِيدهُ مِن البرنامج. - text
نصُّ الرسالة، إن كانت أكثر مِن كَلِمة فاكتبها بين علامات تنصيص.
مثلًا نُرِيدُ طِباعة رسالة فحواها: “الرجاء تشغيلي كجذر”، عندما يُشغَّلُ كمستخدمٍ عاديٍ. - height
إرتفاع مُربَّعنا، فالمربَّع الذي نُنشِئهُ نُحدِّدُ طوله. - width
عرض مُربَّعنا، حالهُ كحالِ الطول، نُحدِّدهُ أيضًا.
بَعدَ فِهم الأمر سنكتبُ ملفًا بسيطًا لتطبيقِ ما تعلمناه (استخدم أيَّ محرر نصوص يُعجبك) بالمحتويات الآتية:
#!/bin/bash
if ! [[ $(id -u) -eq 0 ]]; then
whiptail --infobox "Run me as root" 10 97
exit 0
fi
النَصُّ السابق بسيطٌ جدًا، عِبارة شَرطيَّة تتحققُ من المستخدم الذي شغَّل الملف هل هو مستخدم جذر أم لا؟
البعض قد لا يعمل لديه الأمر بشكلٍ صحيحٍ وهذا خلل بِمُربَّعِ المعلومة لأنه لا يعملُ في بعض الطرفيَّات لذا تحتاج إلى إضافة TERM=ansi قَبْل whiptail، فيكون الأمر:
TERM=ansi whiptail --infobox "Run me as root" 10 97

إضافة عنوان لأي مربع حوار
هل ترى المربَّع الذي ظَهَر؟ هل تودُّ إضافة عنوان في أعلاه؟
لإضافة عنوان نستخدِم الخيار (title–) لنخبره بأننا نُرِيدُ إضَافة عنوان، ثُمَّ نتبُعه بنصِّ العنوان المراد عرضه:
whiptail --title "INFOBOX" --infobox "This is a INFOBOX" 10 97

تذكَّر بأن لكل مربَّع حوار عنوان في أعلاه، فخيار إضَافة العنوان تستطيع استخدامه مع بقيّة الأمثلة التي سترد بعد قليل، فلن أعيد شرحه مرة أخرى. |
مربع الرسالة (message box)
مربَّع الرِسالة يُشبِه إلى حدٍ كبير مربَّع المعلومة، باختلافٍ بسيطٍ بينهُما، وهو أن مربَّع الرِسالة (message box) ينتظرُ المستخدمُ ليضغط زر الموافقة (كما سنراه بعد قليل)، بينما مربَّع المعلومة لا يَنتظرُ المستخدم ليضغط. صيغته:
whiptail --msgbox text height weight
الأمور بسيطة سهلة الآن، فلقد عرفنا النمط المستخدم في مربَّعات الحوار، text يرمز إلى النص المراد عرضه، height طول المربَّع (الارتفاع)، وwidght عرض المربَّع، وكليهُما يقبلان عددان صحيحان.
دعنا نفترض بأنَّ لدينا ملفٌ نصّي حدِّثَ النِظام وهناك إصدار نواة جديد، والمستخدم وصل (mount) بعض الأجهزة أو ما زال يعمل، فأنت لا تريد أن تُطفئ النِظام فجأة، فهنا يأتي مربَّع الرِسالة ليحل لك المشكلة:
#!/bin/bash
if ! [[ $(id -u) -eq 0 ]]; then
whiptail --infobox "Run me as root" 10 97
exit 0
fi
whiptail --title "message box" --msgbox "When you press enter I will reboot your system" 10 97

تغيير كلمة ok في الزر
لم تروق لك كلمة ok في الزر وتريد تغييرها إلى done مثلًا؟
تستطيع تغييرها عبر الخيار (ok-button–) متبوعًا بالنصِّ المرادِ تغييره إليه:
whiptail --title "Message box" --ok-button "Done" --msgbox "Hello World!" 8 97

تذكَّر أن أيّ مربَّع حوار يمتلك زرار موافقة (ok button) تستطيع تغييره بالخيار السابق.
مربع نعم/لا (Yes/No box)
كما يوحي اسمه، مربَّع الحوار نعم/لا (Yes/No box)، يُعطِيك القُدرة على اِختِيار فعل شيء أو رفضه. مثالنا السابق الذي يُحدَّثُ النِظام، وثُمَّ يُعيدُ تَشغيله ماذا لو سألنا المُستخدم عن رأيه بإعادة التشغيل بدل إعادة تشغيله بغتَةً؟
في هذه الحالة سنستخدم مربَّع الحوار نعم/لا، وصيغته:
whiptail --yesno text height weight
لا داعي لشرحه صحيح؟ النمط معروف وسهل الملاحظة. نعود لمثالنا السابق، سنسأل المستخدم سؤال بسيط، وسنخبره بكل لباقة واحترام، هل تفضل يا سيدي الموقَّر إعادة تشغيل الجهاز أم لا؟
whiptail --title "YES/NO BOX" --yesno "Did you shutdown the computer?" 8 97

الأمر السابق يُعيد رمز حالة خروج (exit status code) والذي سيكون صفر إن ضغط المستخدم على زر القبول، ولو ضغط على زر الرفض سيرجع العدد 1، أما لو ضغط المستخدم على زر ESC للهرب من البرنامج فإنه سيرجع العدد 255، وبمعرفتك هذه المعلومة تستطيع إدخال العبارات الشرطيَّة. لا تقلق سترى مثال بعد قليل.
الآن انظر في الصورة التي في الأعلى، المؤشر (cursor) على الزر نعم، إن أردت أن يكون المؤشر على الزر لا، فالخيار defaultno– يفعلُ ذلِكَ:
whiptail --title "YES/NO BOX" --defaultno --yesno "Did you shutdown the computer?" 8 97

تغيير نصي yes وno
إن أردت تبديل كَلِمة yes بشيء آخر، مثل: “نعم، أريد إطفاء الحاسوب”، وكَلِمة no بشيءٍ آخر مثل: “لا، لا أريد إطفاءه” فسنستخدم الخيار (yes-button–) متبوعًا بالنصِّ الجديد، وبنفس الخطوة أيضًا مع no، نستخدم الخيار (no-button–) متبوعًا بالنص الجديد:
whiptail --title "YES/NO BOX" --yes-button "Yes, I am a member" --no-button "No, I am not a member" --yesno "Are you a member of the aosus community?" 8 97

مربع الإدخال (input box)
مربَّع الإدخال، يطلبُ مِن المُستخدم إدخال قيمة ما، صيغته:
whiptail --inputbox text height width [init]
القيمة الأخيرة اختيارية، تستطيع تركها فارغة، عند وضع نصّ مكان تلك القيمة فإن مربع الإدخال حين يظهر ستكون تلك القيمة في مربع الإدخال افتراضيًا.
عند تثبيتي لتوزيعة VoidLinux وكتابة اسم المستخدم، ظهر مربَّع الإدخال بقيمةٍ افتراضيَّةٍ كانت «void»، فتستطيع تغييرها أو الضغط على زر الموافقة لاختياره وإكمال التثبيت.
مثال لمربَّع الإدخال:
whiptail --title "username" --inputbox "enter your username: " 7 87 MrNarsus

تغيير زر الإلغاء
تعلمنا سابقًا تغيير نصّ زر الموافقة باستخدام ok-button–، ولتغيير زر الإلغاء (cancel button) نستخدم الخيار cancel-button– متبوعًا بالنصِّ الجديد:
whiptail --title "username of system" --cancel-button "Back" --inputbox "Enter Your username: " 7 87 Mr-Narsus

وجب التنبيه بشأن مربَّع الإدخال، فهو لا يُرسِل المدخل إلى الخرج القياسي (stdout) بل يطبعه إلى الخطأ القياسي (stderr)، وهذا لن يسمح لك بتخزين القيمة في مُتغيَّر (variable)، ولحل هذه المشكلة سنعكس العملية وسنجلعه يُعيدُ توجيه الخطأ القياسي إلى الخرج القياسي:
3>&1 1>&2 2>&3
- أنشأنا واصف ملف (file descriptor) ثالث يُشير إلى الدخل القياسي، الذي يمتلك واصف الملف 1.
- أعدنا توجيه الخرج القياسي إلى الخطأ القياسي الذي يمتلك واصف الملف 2.
- أعدنا توجيه الخطأ القياسي إلى واصف الملف 3، الذي يُشِير بذاته إلى الدخل القياسي.
الآن دعنا نجرب تخزين مدخل إلى مُتغيَّرٍ ثُمَّ طبعه:
#!/bin/bash
name=$(whiptail --title "Your Name" --ok-button "Done" --cancel-button "Exit" --inputbox "Enter Your Name: " 8 85 3>&1 1>&2 2>&3)
whiptail --title "Welcome message" --ok-button "Exit" --msgbox "Hello Sir $name" 10 83


إخفاء زر الإلغاء
لإخفاء زر الإلغاء نستخدم الخيار nocancel–:
whiptail --title "Your Name" --ok-button "Done" --nocancel --inputbox "Enter Your Name: " 8 85

إن أردت تكبير الأزرار فاستخدم اللاحقة fb– التي تجعل الزر يظهر بمظهر أكبر.
مربع النص (text box)
يَسمحُ لك بعرض محتويات ملف ما، كأنه مستعرض ملفات لكِنه بدائيّ جدًا. صيغته:--textbox filename height weight
مثال:
whiptail --title "simple files viewer" --ok-button "Back" --textbox filename.txt 8 82

عند عرض محتويات ملف ما، والملف به سطور كثيرة فلن تظهر كُلَّها، ولن تستطيع النزول لأسفل أو الصعود لأعلى، لذا تحتاج إلى اللاحقة scrolltext–:
whiptail --title "simple files viewer" --ok-button "Back" --scrolltext --textbox filename.txt 8 82

مربع كلمة السر (password box)
يَسمحُ لك باستقبال كلمة سرٍ مِن المستخدمِ، عِندما يكتُبها لن تظهر له، ما سيظهر نجمات. صيغته:
--passwordbox text height width [init]
لقد مرَّ معنا هذا النمط مِن قَبل في مربَّع الإدخال. النصُّ ثُمَّ الطول -الارتفاع- والعرض، ثُمَّ قيمة افتراضيّة تودُّ أن تظهر عند ظهور مربَّع الإدخال وللمستخدمِ الحُرَّيَّة في تغييرها أو تركها.
دعنا ننشِئ ملف بسيط، وندمج ما تعلمناه منذ البداية حتى الآن. سالم يُرِيدُ مِنك صُنع أداة بسيطة تمكّنه من إنشاء مستخدم وتحديد كلمة مرور له، بالقدر الذي يريده، ثُمَّ تستعرض له اسم المستخدم في الملف passwd:
#!/bin/bash
if ! [[ $(id -u) -eq 0 ]]; then
whiptail --infobox "Run me as root" 10 97
exit 0
fi
function add_user() {
whiptail --title "Add user to the system" --ok-button "Continue" --msgbox "Hello Sir $(whoami)\
I will help you to create a user, and set a password for it, and set a shell. " 10 97
username=$(whiptail --title "USERNAME" --ok-button "Continue" --cancel-button "Exit" --inputbox "Enter Your username: " 8 85 3>&1 1>&2 2>&3)
password=$(whiptail --title "PASSWORD" --ok-button "Create user" --cancel-button "Back" --inputbox "Enter Your password: " 8 85 3>&1 1>&2 2>&3)
useradd -m -G sudo -s /bin/bash -p $(echo $password | openssl passwd -1 -stdin) $username
grep $username /etc/passwd > /tmp/out.txt
whiptail --title "simple file viewer" --ok-button "Done" --textbox out.txt 8 82
}
while :; do
add_user
if $(whiptail --title "Add User Program" --yesno "Do you want to create another user? " 8 97); then
add_user
else
break
fi;done
القوائم
القائمة (menu)
صَممت أداة رائعة، لديها خيارات كثيرة، تود عرض قائمة بالخيارات التي تحتويها أداتك ليختار المستخدم خيار واحد مِن تلك الخيارات، لحسن الحظ قدَّم لك whiptail الحل على شكل خيار اسمه القائمة menu–. صيغته:
--menu text height width <menu height> tag1 item1
النصُّ، ثُمَّ الطول والعرض الخاص بالمربَّع المعروض، ثُمَّ طول القائمة، ثم الخيار الممثل بكَلِمة «tag1»، ثُمَّ وصف للخيار وهو ممثل بكلمة «item1».
سنُنشِئ ملف بسيط يجعل المستخدم يختار نظامه من قائمة متواجد بها عدة أنظمة لكي يُحدِّث نظامه:
whiptail --title "Update Your System" --menu "Choose a system from the list: " 25 78 15 "1" "Debian" "2" "ArchLinux" "3" "VoidLinux"

إن كان لديك قائمة طويلة وتريد تحقيق استفادة كُلية من حجم الطرفيَّة المفتوحة فتستطيع استخدام:
eval $(resize)
whiptail --title "Update Your System" --menu "Choose a system from the list: " $LINES $COLUMNS $(( $LINES - 8 )) "1" "Debian" "2" "ArchLinux" "3" "VoidLinux"

إن أردت أن يكون المؤشر على أحد الخيارات عندما يعمل فاللاحقة default-item تفعل ذلك، اكتب اللاحقة ثُمَّ العنصر الذي تودُّ أن يكون المؤشر عليه عندما تظهر القائمة:
whiptail --title "Update Your System" --default-item "1" --menu "Choose a system from the list: " $LINES $COLUMNS $(( $LINES - 8 )) "1" "Debian" "2" "ArchLinux" "3" "VoidLinux"

وتستطيع أيضًا إخفاء الخيارات وإظهار الوصف فقط عبر اللاحقة notags–:
whiptail --title "Update Your System" --notags --menu "Choose a system from the list: " $LINES $COLUMNS $(( $LINES - 8 )) "1" "Debian" "2" "ArchLinux" "3" "VoidLinux"

قائمة التحقق (check list)
سيتطور برنامجُك، وستريد إضافة خيارات أخرى ولن يكون من المفيد وضعها في تلك القائمة السابق ذكرها، مثلًا: تعرض قائمة لمستخدم لِيُفعَّل بعض المميزات في البرنامج، وهنا تأتي فائدة قائمة التحقق التي تُمكّن المستخدم مِن تحديد أكثر مِن خيار واحد. صيغة الأمر:
--checklist text height width <list height> <tag1> <item1> <status1>
النصُّ وارتفاع وعرض المربَّع، وارتفاع القائمة، وعنصر القائمة ثُمَّ وصف له، أما العنصر الأخير الممثل بِكَلِمة «status1» الذي يُشِير إلى حالة العنصر عند عرضه، هل تريده مُفعَّل افتراضيًّا أم لا؟
إن أردته مفعَّل فضع كلمة on أما أن أردت العكس فضع كلمة off. مثال:
whiptail --title "Check List" --checklist "Choose a package you want to install: " $LINES $COLUMNS $(( $LINES - 8 )) "firefox" "web browser" off "vim" "text editor" on "XFCE" "desktop environment" off "DWM" "window manager" on

إخفاء وصف الخيار
إن أردت إخفاء وصف خيار ما، وأردت إظهار الخيار فقط، فتستطيع استخدام اللاحقة noitem–.
قائمة الراديو/المذياع (radio list)
تُشبِه أختها السابقة كثيرًا إلا أن الاختلاف بينهُما أن الأولى -أي قائمة التحقق- تسمح لك باختيار أكثر مِن خيار في القائمة، أمَّا قائمة الراديو/المذياع تسمحُ بتحديد خيار واحد فقط.
أظن أن اسمها جاء بسبب أن المذياع تستطيع تحديد قناة واحدة للاستماع إليها في المرة الواحدة.
صيغتها تمامًا كأختها:
--radiolist text height width <list height> tag1 item1 status1
مثال:
whiptail --title "Radio list" --radiolist "Choose a editor you want to install: " 28 98 18 "1" "vim" on "2" "nano" off "3" "emacs" off

مقياس التقدم (gauge)
يعرضُ شريط تقدم بسيط، ويقبل المدخلات مِن الدخل القياسي (stdin). صيغته:--gauge text height width [percent]
النصُّ وارتفاع وعرض المربَّع، ثُمَّ القيمة التي سيبدأ منها المقياس بالعد. مثال:
for ((i = 0 ; i <= 60 ; i+=1)); do
sleep 1
echo $i
done | whiptail --gauge "Wait until the minute is up and I will turn off the device" 6 50 0
تستطيع وضع المربَّع في أعلى الزاوية اليسرى مِن الطرفيَّة عبر اللاحقة topleft–.
عند تحديد مربَّع حوار ما سيبقى هناك متسع في الطرفيَّة (ذلك الذي باللون الأزرق) فتستطيع إضافة عنوان هناك قد يحتوي على اسم برنامجك أو شيء آخر تريده، واللاحقة المسؤولة عن ذلك هي backtitle– متبوعًا بالنصِّ المرادِ طبعه.
الخاتمة
وها أنا أخطُّ الخطوط الأخيرة لهذا المقال، ولعلَّي وفِّقتُ في شرحهِ ولفتت انتباهُك إلى برْمَجةٍ كهذه، فهذه المقالة بذرة بسيطة لتعريفك بطريقة مختلفة للبرمجة لمحبين لغة باش.
في نهاية الأمر لا يسعني سوى شكرك لحسن قراءتك، وإني لبشر أصيب وأخطئ، فإن وفِّقتُ في طرحِ الموضوع فبتوفيق من اللّٰه عز وجل وإن أخفقت فمن نفسي والشَيطان، والسلام عليكم ورحمة اللّٰه تعالى وبركاته. دمتم بخير.
هذا الموضوع فائز بجائزة أسس للكتابة
هذا الموضوع أحد المواضيع الفائزة بجائزة أسس للكتابة، الجائزة الاولى في العالم العربي للتحفيز الكتابة عن البرمجيات الحرة.
تفاصيل أكثر عن الجائزة

المواضيع الفائزة لشهر يونيو 2022


Mr.Narsus
كُلَّما أَدَّبَني الدّهرُ أَراني نَقصَ عَقلي
وَإِذا ما اِزدَدتُ عِلمًا زادَني عِلمًا بِجَهلي