كيف تبني مدير ملفات الإعدادات (Dotfiles) الخاص بك على لينكس من الصفر

دقائق القراءة: 12

مقدمة إلى عالم ملفات الإعدادات (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 وسائط مختلفة في سكريبت واحد).

تصور سير العمل

دعنا نتخيل كيف سيعمل سكريبتنا:

رسم بياني يوضح سير عمل سكريبت إدارة ملفات الإعدادات (dotfiles manager).

تجهيز المتطلبات الأساسية

لضمان عمل سكريبتنا بسلاسة، نحتاج إلى توفر بعض الأدوات الضرورية في نظامك:

أداة 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)

الآن، بمجرد أن يكمل المستخدم الإعداد الأولي، نحتاج إلى عرض خيارات "المدير" له.

واجهة مستخدم نصية لمدير ملفات الإعدادات (dotman) تعرض الخيارات المتاحة.

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) إذا كانت لديك أي أسئلة حول هذا الدليل أو إرسالها إلي مباشرة.

بطاقة GitHub لمستودع dotman، تعرض معلومات المشروع.

لقد أتحتُه كقالب حتى تتمكن من استخدامه لإنشاء نسختك الخاصة من 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 لعرض نص ملون في الطرفية.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *