كيف تبني مدير ملفات الإعدادات (Dotfiles) الخاص بك على لينكس من الصفر
مقدمة إلى عالم ملفات الإعدادات (Dotfiles) وأهميتها
كمستخدم جديد لنظام التشغيل لينكس، قد تكتشف وجود مجموعة كبيرة من ملفات التكوين (configuration files) في نظامك. هذه الملفات الخاصة يطلق عليها اسم "dotfiles"، وهي ضرورية لتخصيص تجربتك مع لينكس. في هذا الدليل الشامل، سنتعلم كيفية بناء مدير لـ dotfiles وإنشاء نسخة احتياطية لهذه الملفات على منصة GitHub.
قد تتساءل: ما هي هذه الملفات .dotfiles ولماذا نحتاجها؟ ببساطة، ترتبط ملفات dotfiles عادةً ببرامج معينة مثبتة على نظامك وتستخدم لتخصيص سلوك هذه البرامج. على سبيل المثال، إذا كنت تستخدم zsh كصدفة افتراضية (default shell)، فستجد ملف .zshrc في مجلدك الرئيسي (HOME directory). إليك بعض الأمثلة الأخرى الشائعة:
.vimrc: يستخدم لتكوين محرر النصوصVIM..bashrc: متاح بشكل افتراضي، ويستخدم لتغيير إعداداتBash..bash_aliases: يستخدم عادةً لتخزين اختصارات الأوامر (command aliases) الخاصة بك..gitconfig: يخزن الإعدادات المتعلقة بنظام التحكم في الإصداراتGit..gitmessage: يستخدم لتوفير قالب رسالة التزام (commit message template) عند استخدام الأمرgit commit.
تتغير ملفات .dotfiles هذه بمرور الوقت مع بدء تخصيص لينكس وفقًا لاحتياجاتك. يعد إنشاء نسخة احتياطية لهذه الملفات أمرًا ضروريًا في حال حدوث أي خطأ ورغبت في العودة إلى حالة مستقرة سابقة. وهنا يأتي دور أنظمة التحكم في الإصدارات (VCS - Version Control Software). في هذا الدليل، سنتعلم كيفية أتمتة هذه المهمة عن طريق كتابة سكريبت شل (shell script) بسيط وتخزين ملفات dotfiles الخاصة بنا على GitHub.

الخطوات الأولية وتصور السكريبت
قبل الشروع في كتابة أي سطر من التعليمات البرمجية، دعنا نطلق اسمًا على سكريبتنا: dotman، وهو اختصار لـ (dot)file (man)ager. قبل البدء، نحتاج إلى تحديد متطلباتنا وتصميم كيفية عمل سكريبت الشل الخاص بنا.
متطلبات مدير ملفات الإعدادات (dotman)
سنجعل dotman بسيطًا وسهل الاستخدام. يجب أن يكون قادرًا على:
- العثور على ملفات
dotfilesالموجودة داخل نظامنا. - التمييز بين الملفات الموجودة في مستودع
Gitالخاص بنا وتلك الموجودة على نظامنا. - تحديث مستودع
dotfilesالخاص بنا (إما الدفع إلى المستودع البعيدremoteأو السحب منه). - أن يكون سهل الاستخدام (لا نريد 5 وسائط مختلفة في سكريبت واحد).
تصور سير العمل
دعنا نتخيل كيف سيعمل سكريبتنا:
تجهيز المتطلبات الأساسية
لضمان عمل سكريبتنا بسلاسة، نحتاج إلى توفر بعض الأدوات الضرورية في نظامك:
أداة Git
نحتاج إلى Git لأننا قد نرغب في العودة إلى إصدار سابق من ملفات dotfile الخاصة بنا. بالإضافة إلى ذلك، سنقوم بتخزين ملفات dotfiles الخاصة بنا في مضيف VCS (مثل GitHub أو GitLab أو Bitbucket أو Gittea). إذا لم يكن Git مثبتًا لديك، يمكنك مراجعة الدليل المناسب لنظامك لتعلم كيفية تثبيته.
صدفة Bash
ستكون Bash متاحة على أجهزة لينكس/يونكس/ماك أو إس الخاصة بك بشكل افتراضي. يمكنك التحقق من ذلك عن طريق التحقق من الإصدار باستخدام الأمر bash --version. يجب أن يظهر لك شيء مشابه لما يلي. لا تقلق كثيرًا بشأن الإصدار، حيث سيعمل سكريبتنا بشكل جيد مع إصدارات Bash >=3.
GNU bash, version 4.4.20(1)-release (i686-pc-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
البدء في البرمجة: بناء السكريبت خطوة بخطوة
الآن بعد أن أصبح كل شيء جاهزًا، افتح محرر النصوص أو بيئة التطوير المتكاملة المفضلة لديك.

تعريف Shebang وتغيير الأذونات
نحتاج إلى تعريف shebang للإشارة إلى أننا سنستدعي مترجمًا للتنفيذ. في بداية السكريبت، أضف هذا السطر:
#!/usr/bin/env bash
يتم تنفيذ الأمر (البرنامج) env كعملية جديدة ثم يستدعي الأمر الذي تم توفيره كوسيط (argument). في حالتنا، يتم بدء bash تلقائيًا بواسطة عملية env. من مسؤولية env العثور على مكان bash في نظامنا واستبدال مساره في السكريبت. يمكنك استبدال bash بـ python أو ruby على سبيل المثال.
الآن، قم بتغيير أذونات الملف لجعل سكريبتنا قابلاً للتنفيذ:
chmod +x dotman.sh
بناء الوظائف الأساسية
سنستخدم النمط البرمجي الوظيفي (functional style) في هذا السكريبت، أي أن كل جزء من المهمة سيكون داخل دالة (function()). دعنا نتبع المخطط الذي تصورناه أعلاه ونكتب دالتنا الأولى، init_check().
سنعتمد فقط على مدخلين من المستخدم:
DOT_DEST: موقع المستودع في نظامك المحلي.DOT_REPO: عنوانURLلمستودعdotfileالبعيد.
يجب أن تكون هذه المتغيرات موجودة داخل تكوين صدفة الشل الافتراضية الخاصة بك (مثل .bashrc). سنتعلم كيفية القيام بذلك لاحقًا في هذا الدليل.
init_check () {
# Check wether its a first time use or not
if [[ -z ${DOT_REPO} && -z ${DOT_DEST} ]]; then
# show first time setup menu
# initial_setup
else
# repo_check
# manage
fi
}
يستخدم الخيار -z للتحقق مما إذا كان المتغير مضبوطًا أم لا (أي، إذا كان متاحًا لسكريبتنا أم لا). إذا لم يكن كذلك، فسنستدعي دالتنا initial_setup(). وإلا، فسنتحقق مما إذا كان المستودع قد تم استنساخه وموجودًا داخل مجلد DOT_DEST.
دالة الإعداد الأولي (initial_setup)
الآن دعنا نبرمج دالة initial_setup:
initial_setup () {
echo -e "\n\nFirst time use, Set Up d○tman"
echo -e "....................................\n"
read -p "Enter dotfiles repository URL : " -r DOT_REPO
read -p "Where should I clone $(basename "${DOT_REPO}") (${HOME}/..): " -r DOT_DEST
DOT_DEST=${DOT_DEST:-$HOME}
if [[ -d "$HOME/$DOT_DEST" ]]; then
# clone the repo in the destination directory
if git -C "${HOME}/${DOT_DEST}" clone "${DOT_REPO}" ; then
add_env "$DOT_REPO" "$DOT_DEST"
echo -e "\ndotman successfully configured"
goodbye
else
# invalid arguments to exit, Repository Not Found
echo -e "\n$DOT_REPO Unavailable. Exiting"
exit 1
fi
else
echo -e "\n$DOT_DEST Not a Valid directory"
exit 1
fi
}
هذه الدالة بسيطة للغاية، دعنا نفهم ما يحدث فيها:
- الأمر
readهو أمر داخلي في الشل يستخدم لأخذ المدخلات من الطرفية (terminal). - الخيار
-pيحدد رسالة توجيه (prompt) قبل أخذ المدخلات. - السطر التالي بعد
readيسمى توسيع المعلمة (Parameter Expansion). إذا لم يقم المستخدم بإدخال قيمة لـDOT_DEST، فسيتم تعيين القيمة الافتراضية كـ/home/username/(إذا كانDOT_DESTغير مضبوط أو فارغ، يتم استبدال توسيع$HOME). وإلا، يتم استبدال القيمة التي أدخلها المستخدم. - الخيار
-dداخل جملةifيتحقق مما إذا كان الدليل موجودًا (أو تقنيًا، ما إذا كان الدليل الذي وفره المستخدم مسارًا صالحًا في نظامنا). - الخيار
-Cيستخدم فيgitلاستنساخ المستودع إلى مسار يحدده المستخدم.
تصدير المتغيرات البيئية (add_env)
الآن دعنا نرى كيفية تصدير المتغيرات البيئية في الدالة add_env():
add_env () {
# export environment variables
echo -e "\nExporting env variables DOT_DEST & DOT_REPO ..."
current_shell=$(basename "$SHELL")
if [[ $current_shell == "zsh" ]]; then
echo "export DOT_REPO=$1" >> "$HOME"/.zshrc
echo "export DOT_DEST=$2" >> "$HOME"/.zshrc
elif [[ $current_shell == "bash" ]]; then
# assume we have a fallback to bash
echo "export DOT_REPO=$1" >> "$HOME"/.bashrc
echo "export DOT_DEST=$2" >> "$HOME"/.bashrc
else
echo "Couldn't export DOT_REPO and DOT_DEST."
echo "Consider exporting them manually" .
exit 1
fi
echo -e "Configuration for SHELL: $current_shell has been updated."
}
- تشغيل
echo $SHELLفي الطرفية سيعطيك المسار لصدفة الشل الافتراضية الخاصة بك. - يستخدم الأمر
basenameلطباعة "اسم" الشل الخاص بنا (أي الاسم الفعلي بدون أي/بادئة).> echo $SHELL /usr/bin/zsh > basename $SHELL zsh - الأمر
exportهو أمر شائع الاستخدام: يسمح لك بتصدير المتغيرات البيئية. >>يسمى عامل إعادة التوجيه (redirection operator)، أي أن ناتج الأمرecho "export DOT_DEST=$2"يتم توجيهه (إلحاقه) إلى نهاية ملفzshrc.
دالة إدارة الخيارات (manage)
الآن، بمجرد أن يكمل المستخدم الإعداد الأولي، نحتاج إلى عرض خيارات "المدير" له.
manage () {
while :
do
echo -e "\n[1] Show diff"
echo -e "[2] Push changed dotfiles to remote"
echo -e "[3] Pull latest changes from remote"
echo -e "[4] List all dotfiles"
echo -e "[q/Q] Quit Session"
# Default choice is [1]
read -p "What do you want me to do ? [1]: " -n 1 -r USER_INPUT
# See Parameter Expansion
USER_INPUT=${USER_INPUT:-1}
case $USER_INPUT in
[1]* ) show_diff_check;;
[2]* ) dot_push;;
[3]* ) dot_pull;;
[4]* ) find_dotfiles;;
[q/Q]* ) exit ;;
* ) printf "\n%s\n" "Invalid Input, Try Again" ;;
esac
done
}
أنت على دراية بالفعل بالأمر read. الخيار -n 1 يحدد طول المدخلات المسموح بها، في حالتنا يمكن للمستخدم إدخال حرف واحد فقط من بين 1, 2, 3, 4, q, Q.
البحث عن ملفات الإعدادات (find_dotfiles)
الآن علينا العثور على جميع ملفات dotfiles في مجلد HOME الخاص بنا:
find_dotfiles () {
printf "\n"
readarray -t dotfiles < <( find "${HOME}" -maxdepth 1 -name ".*" -type f )
printf '%s\n' "${dotfiles[@]}"
}
تنقسم الدالة إلى جزأين رئيسيين:
find: يستخدم الأمرfindللبحث عن الملفات والدلائل في نظامنا. دعنا نفهم أجزاءه:- الخيار
-type fيحدد أننا نريد البحث عن الملفات العادية فقط وليس الدلائل أو ملفات الأحرف أو الكتل أو الأجهزة. - الخيار
-maxdepthيخبرfindبالنزول إلى مستوى واحد على الأكثر (عدد صحيح غير سالب) من مستويات الدلائل أسفل نقاط البداية. يمكنك البحث في الدلائل الفرعية عن طريق استبدال1بـ2, 3وما إلى ذلك. -nameيأخذ نمطًا (glob) للبحث. على سبيل المثال، يمكنك البحث عن جميع ملفات.pyباستخدام:-name ".py".
- الخيار
readarray(مرادف لـmapfile): يقرأ الأسطر من المدخلات القياسية إلى متغير المصفوفة المفهرسةdotfiles. الخيار-tيزيل أي محدد لاحق (السطر الجديد الافتراضي) من كل سطر مقروء.
ملاحظة: إذا كان لديك إصدار أقدم من Bash (<4)، فقد لا يكون readarray موجودًا كأمر داخلي. يمكننا تحقيق نفس الوظيفة باستخدام حلقة while بدلاً من ذلك:
while read -r value; do
dotfiles+=( $value )
done < <( find "${HOME}" -maxdepth 1 -name ".*" -type f )
التحقق من التغييرات (diff_check)
الآن سنقوم بإنشاء إحدى أهم الدوال في سكريبتنا، diff_check.
diff_check () {
if [[ -z $1 ]]; then
declare -ag file_arr
fi
# dotfiles in repository
readarray -t dotfiles_repo < <( find "${HOME}/${DOT_DEST}/$(basename "${DOT_REPO}")" -maxdepth 1 -name ".*" -type f )
# check length here ?
for (( i=0; i<"${#dotfiles_repo[@]}"; i++))
do
dotfile_name=$(basename "${dotfiles_repo[$i]}")
# compare the HOME version of dotfile to that of repo
diff=$(diff -u --suppress-common-lines --color=always "${dotfiles_repo[$i]}" "${HOME}/${dotfile_name}")
if [[ $diff != "" ]]; then
if [[ $1 == "show" ]]; then
printf "\n\n%s" "Running diff between ${HOME}/${dotfile_name} and "
printf "%s\n" "${dotfiles_repo[$i]}"
printf "%s\n\n" "$diff"
fi
file_arr+=( "${dotfile_name}" )
fi
done
if [[ ${#file_arr} == 0 ]]; then
echo -e "\n\nNo Changes in dotfiles."
return
fi
}
show_diff_check () {
diff_check "show"
}
هدفنا هنا هو العثور على ملفات dotfiles الموجودة بالفعل في المستودع ومقارنتها بالملفات المتاحة في مجلد HOME الخاص بنا.
- الكلمة المفتاحية
declareتسمح لنا بإنشاء متغيرات. يستخدم الخيار-aلإنشاء مصفوفات (arrays) ويخبر-gالأمرdeclareبجعل المتغيرات متاحة "عالميًا" (globally) داخل السكريبت. ${#file_arr}يعطينا طول المصفوفة.- الأمر المهم التالي هو
diffالذي يستخدم لمقارنة الملفات سطرًا بسطر. على سبيل المثال:> echo -e "abc\ndef\nghi" >> fileA.txt > echo -e "abc\nlmn\nghi" >> fileB.txt > cat fileA.txt abc def ghi > cat fileB.txt abc lmn ghi > diff -u fileA.txt fileB.txt --- fileA.txt 2020-07-17 16:24:16.138172662 +0530 +++ fileB.txt 2020-07-17 16:24:26.686075270 +0530 @@ -1,3 +1,3 @@ abc -def +lmn ghi
دالة الدفع (dot_push)
الآن مع دالة dot_push():
dot_push () {
diff_check
echo -e "\nFollowing dotfiles changed : "
for file in "${file_arr[@]}" ; do
echo "$file"
cp "${HOME}/$file" "${HOME}/${DOT_DEST}/$(basename "${DOT_REPO}")"
done
dot_repo="${HOME}/${DOT_DEST}/$(basename "${DOT_REPO}")"
git -C "$dot_repo" add -A
echo -e "Enter Commit Message (Ctrl + d to save):"
commit=$( </dev/stdin )
git -C "$dot_repo" commit -m "$commit"
# Run Git Push
git -C "$dot_repo" push
}
نقوم هنا بكتابة فوق الملفات عن طريق نسخها إلى مستودع dotfile الخاص بنا باستخدام الأمر cp.
دالة السحب (dot_pull)
وأخيرًا، دالة dot_pull():
dot_pull () {
# pull changes (if any) from the host repo
echo -e "\nPulling dotfiles ..."
dot_repo="${HOME}/${DOT_DEST}/$(basename "${DOT_REPO}")"
echo -e "\nPulling changes in $dot_repo \n"
git -C "$dot_repo" pull origin master
}
إضفاء لمسة جمالية على سكريبتنا: إضافة الألوان
حتى الآن، حققنا ما تصورناه في البداية. ولكن، هناك شيء مفقود… الألوان!

هناك العديد من الطرق للقيام بذلك، ولكن الطريقة الشائعة هي استخدام تسلسلات الهروب (escape sequences). ومع ذلك، سنستخدم أداة تسمى tput، وهي واجهة سهلة الاستخدام لإخراج الألوان وفقًا لطرفية المستخدم. إنها متاحة افتراضيًا في لينكس/ماك أو إس. إليك عرض توضيحي قصير:
- لطباعة نص بخط عريض (bold):
echo "$(tput bold) This $(tput sgr0) word is bold" - لتغيير لون الخلفية:
echo "$(tput setab 10) This text has green background $(tput sgr0) " - لتغيير لون المقدمة:
echo "$(tput setaf 10) This text has blue color $(tput sgr0) "
يمكنك أيضًا دمج السمات:
echo "$(tput smul) $(tput setaf 10) This text is underlined & green $(tput rmul) $(tput sgr0) "
دعني أترك هذه المهمة لك: أضف ألوانك المفضلة إلى السكريبت. اقرأ هذا الدليل لتعلم واستكشاف المزيد حول tput.
النتيجة النهائية وتشغيل السكريبت
آمل أنك ما زلت تتابعني حتى هذه النقطة. لقد وصلنا إلى النهاية، وأصبح لدينا الآن مدير ملفات dotfile ذو مظهر جميل وعملي.

الآن، ما عليك سوى تشغيل السكريبت (إذا لم تكن قد فعلت ذلك بالفعل) لترى كيف يعمل:
./dotman.sh
يمكنك الاطلاع على نسختي من dotman إذا كنت بحاجة إلى مرجع. لا تتردد في إنشاء أي مشكلات (issues) إذا كانت لديك أي أسئلة حول هذا الدليل أو إرسالها إلي مباشرة.
لقد أتحتُه كقالب حتى تتمكن من استخدامه لإنشاء نسختك الخاصة من dotman.
الخلاصة التقنية
لقد تعلمنا في هذا الدليل كيفية بناء أداة قوية ومخصصة لإدارة ملفات dotfiles على أنظمة لينكس. من خلال أتمتة عمليات النسخ الاحتياطي والمزامنة باستخدام Git وسكريبتات الشل، نضمن الحفاظ على إعداداتنا الشخصية وتخصيصاتنا عبر مختلف الأنظمة أو بعد إعادة التثبيت. استخدام أدوات مثل find و diff و tput يعكس قوة سطر الأوامر في لينكس ومرونته في إنشاء حلول مخصصة. هذا النهج لا يوفر الوقت فحسب، بل يعزز أيضًا فهمنا العميق لكيفية عمل نظام التشغيل والبرامج المثبتة عليه، مما يمهد الطريق لمزيد من الأتمتة والتخصيص المتقدم.
ملخص وأهم النقاط المستفادة
دعنا نلخص بعض النقاط الهامة التي تعلمناها في هذا الدليل:
- استخدم
basename /path/to/dir/file/للحصول على اسم الملف من المسار. - استخدم
git -C /path/to/clone/to clone https://repo.urlلاستنساخ المستودع إلى دليل مختلف عن دليل العمل الحالي. - يمكن استخدام
echo $SHELLلتحديد صدفة الشل الافتراضية الخاصة بك. - استخدم
findللبحث عن الملفات والمجلدات في نظام لينكس الخاص بك. - يستخدم الأمر
diffلمقارنة ملفين، وهو مشابه لـgit diff. - المصفوفات المعلنة داخل دالة لا يمكن الوصول إليها إلا داخل تلك الدالة. استخدم الخيار
-gلجعلها عالمية، على سبيل المثالdeclare -ag file_arr. - يمكن استخدام
tputلعرض نص ملون في الطرفية.