دليل نمط Shell من Google (بالروسية)

مقدمة


التي شل لاستخدامها


Bash لغة البرمجة النصية الوحيدة التي يمكن استخدامها للملفات القابلة للتنفيذ.


يجب أن تبدأ البرامج النصية بـ #!/bin/bash مع الحد الأدنى من العلامات. استخدم set لتعيين خيارات shell بحيث لا يؤدي انتهاك النص البرمجي باسم bash <script_name> انتهاك وظائفه.


يمنحنا تحديد جميع النصوص البرمجية لـ bash لغة غلاف ثابتة يتم تثبيتها على جميع أجهزتنا.


الاستثناء الوحيد هو إذا كنت مقيدًا بشروط ما تقوم بالبرمجة له. أحد الأمثلة على ذلك حزم Solaris SVR4 ، التي تتطلب استخدام غلاف بورن المعتاد لأي نص برمجي.


متى تستخدم شل


يجب استخدام Shell فقط للمرافق الصغيرة أو أغلفة البرامج النصية البسيطة.


على الرغم من أن برمجة shell ليست لغة تطوير ، إلا أنها تستخدم لكتابة العديد من الأدوات المساعدة في جميع أنحاء Google. يعد دليل الأسلوب هذا أكثر اعترافًا باستخدامه ، وليس مقترحًا لاستخدامه على نطاق واسع.


بعض التوصيات:


  • إذا كنت تستخدم أدوات مساعدة أخرى في كثير من الأحيان وقمت بمعالجة بيانات قليلة نسبيًا ، فإن shell هو خيار مقبول لهذه المهمة.
  • إذا كان الأداء مهمًا ، فاستخدم شيئًا آخر غير shell.
  • إذا وجدت أنك بحاجة إلى استخدام المصفوفات لأكثر من تعيين ${PIPESTATUS} ، فيجب عليك استخدام Python.
  • إذا كنت تكتب نصًا أطول من 100 سطر ، فمن المحتمل أن تكتبه في Python. ضع في اعتبارك أن النصوص البرمجية تنمو. أعد كتابة النص البرمجي بلغة أخرى في وقت سابق لتجنب إعادة الكتابة التي تستغرق وقتًا طويلاً.

ملفات شل واستدعاء المترجم


ملحقات الملفات


يجب ألا تحتوي الملفات القابلة للتنفيذ على الامتداد (المفضل بشدة) أو الامتداد .sh . يجب أن يكون .sh الامتداد .sh ويجب ألا تكون قابلة للتنفيذ.


ليست هناك حاجة لمعرفة اللغة التي يكتبها البرنامج أثناء تنفيذه ، ولا يتطلب shell امتدادًا ، لذلك نفضل عدم استخدامه للملفات القابلة للتنفيذ.


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


SUID / SGID


يحظر SUID و SGID على نصوص الغلاف.


هناك الكثير من مشكلات الأمان ، مما يجعل من المستحيل تقريبًا توفير حماية كافية من SUID / SGID. على الرغم من أن باش يعقد إطلاق SUID ، إلا أنه لا يزال ممكنًا على بعض الأنظمة الأساسية ، لذا فإننا نحظر استخدامه صراحة.


استخدم sudo للوصول المحسن إذا كنت في حاجة إليها.


البيئة


STDOUT مقابل STDERR


يجب إرسال جميع رسائل الخطأ إلى STDERR .


هذا يساعد على فصل الحالة الطبيعية عن المشاكل الفعلية.


يوصى باستخدام وظيفة عرض رسائل الخطأ مع معلومات الحالة الأخرى.


 err() { echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $@" >&2 } if ! do_something; then err "Unable to do_something" exit "${E_DID_NOTHING}" fi 

التعليقات


رأس الملف


ابدأ كل ملف بوصف محتوياته.


يجب أن يكون لكل ملف عنوان من التعليق ، بما في ذلك وصف موجز لمحتوياته. إشعار حقوق النشر ومعلومات المؤلف اختيارية.


مثال:


 #!/bin/bash # # Perform hot backups of Oracle databases. 

تعليقات الميزة


يجب التعليق على أي وظيفة غير واضحة وقصيرة. يجب التعليق على أي وظيفة في المكتبة بغض النظر عن طولها أو تعقيدها.


تحتاج إلى التأكد من أن شخصًا آخر يفهم كيفية استخدام برنامجك أو كيفية استخدام الوظيفة في مكتبتك بمجرد قراءة التعليقات (والحاجة إلى التحسين الذاتي) دون قراءة التعليمات البرمجية.


يجب أن تتضمن جميع تعليقات الميزات ما يلي:


  • وصف الوظيفة
  • المتغيرات العالمية المستخدمة والمعدلة
  • تم تلقي الحجج
  • إرجاع القيم التي تختلف عن رموز الإنهاء القياسية في الأمر الأخير.

مثال:


 #!/bin/bash # # Perform hot backups of Oracle databases. export PATH='/usr/xpg4/bin:/usr/bin:/opt/csw/bin:/opt/goog/bin' ######################################## # Cleanup files from the backup dir # Globals: # BACKUP_DIR # ORACLE_SID # Arguments: # None # Returns: # None ######################################## cleanup() { ... } 

تعليقات التنفيذ


قم بالتعليق على الأجزاء المعقدة أو غير الواضحة أو المثيرة للاهتمام أو المهمة من التعليمات البرمجية.


يُفترض أن تكون هذه ممارسة شائعة للتعليق على التعليمات البرمجية على Google. لا تعلق على كل شيء. إذا كانت هناك خوارزمية معقدة أو كنت تفعل شيئًا غير عادي ، فأضف تعليقًا قصيرًا.


تعليقات TODO


استخدم تعليقات TODO لرمز مؤقت أو قصير المدى أو جيد جدًا ، ولكنه ليس مثاليًا.


يتوافق هذا مع الاصطلاح الموجود في دليل C ++ .


يجب أن تتضمن تعليقات TODO كلمة TODO بأحرف كبيرة ، متبوعة باسمك بين قوسين. القولون اختياري. من الأفضل أيضًا الإشارة إلى رقم الخطأ / التذكرة بجوار عنصر TODO.


مثال:


 # TODO(mrmonkey): Handle the unlikely edge cases (bug ####) 

التنسيق


على الرغم من أنه يجب عليك اتباع النمط المستخدم بالفعل في الملفات التي تقوم بتحريرها ، إلا أن التالي مطلوب لأي رمز جديد.


المسافة البادئة


مسافة بادئة 2 مسافات. لا علامات تبويب.


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


طول السلسلة وطول القيمة


يبلغ الحد الأقصى لطول الخط 80 حرفًا.


إذا كنت بحاجة إلى كتابة أسطر أطول من 80 حرفًا ، فيجب القيام بذلك باستخدام here document أو ، إذا أمكن ، newline مضمن. يُسمح بشكل معقول بالقيم الحرفية التي يمكن أن تكون أطول من 80 حرفًا ولا يمكن فصلها ، ولكن يُنصح بشدة أن تجد طريقة لجعلها أقصر.


 #  'here document's cat <<END; I am an exceptionally long string. END #  newlines   long_string="I am an exceptionally long string." 

خطوط الأنابيب


يجب تقسيم خطوط الأنابيب كل على سطر واحد إذا لم تتناسب مع سطر واحد.


إذا كان خط الأنابيب يناسب خط واحد ، فيجب أن يكون على خط واحد.


إذا لم يكن الأمر كذلك ، فيجب تقسيمه بحيث يكون كل قسم على خط جديد ويوضع مسافة بادئة بمسافتين للقسم التالي. يشير هذا إلى سلسلة الأوامر مجتمعة باستخدام '|' بالإضافة إلى الاتصالات المنطقية باستخدام '||' و "&&".


 #      command1 | command2 #   command1 \ | command2 \ | command3 \ | command4 

دورات


مكان ; do و ; then على نفس الخط مثل ، أو if .


تختلف الدورات في الغلاف قليلاً ، لكننا نتبع نفس المبادئ كما هو الحال مع الأقواس المتعرجة عند الإعلان عن الوظائف. هذا هو ; then ; then و ; do يجب أن يكون على نفس السطر كما if / for / while . else يجب أن يكون في سطر منفصل ، ويجب أن تكون العبارات الختامية في السطر الخاص بها ، ومحاذاة رأسيًا مع العبارة الافتتاحية.


مثال:


 for dir in ${dirs_to_cleanup}; do if [[ -d "${dir}/${ORACLE_SID}" ]]; then log_date "Cleaning up old files in ${dir}/${ORACLE_SID}" rm "${dir}/${ORACLE_SID}/"* if [[ "$?" -ne 0 ]]; then error_message fi else mkdir -p "${dir}/${ORACLE_SID}" if [[ "$?" -ne 0 ]]; then error_message fi fi done 

بيان الحالة


  • خيارات منفصلة في مسافتين.
  • تتطلب خيارات الخط الواحد مسافة بعد قوس إغلاق القالب وقبله ;; .
  • يجب تقسيم خيارات الأوامر الطويلة أو المتعددة إلى عدة أسطر مع قالب وإجراءات و ;; على خطوط منفصلة.

تتراجع التعبيرات المقابلة عن مستوى واحد من case و esac . تحتوي الإجراءات متعددة الخطوط أيضًا على مسافات بادئة على مستوى منفصل. ليست هناك حاجة لوضع تعبيرات بين علامتي اقتباس. يجب ألا تكون أنماط التعبير مسبوقة بأقواس مفتوحة. تجنب استخدام &; و ;;& تدوين.


 case "${expression}" in a) variable="..." some_command "${variable}" "${other_expr}" ... ;; absolute) actions="relative" another_command "${actions}" "${other_expr}" ... ;; *) error "Unexpected expression '${expression}'" ;; esac 

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


 verbose='false' aflag='' bflag='' files='' while getopts 'abf:v' flag; do case "${flag}" in a) aflag='true' ;; b) bflag='true' ;; f) files="${OPTARG}" ;; v) verbose='true' ;; *) error "Unexpected option ${flag}" ;; esac done 

توسع متغير


مرتبة حسب الأولوية: لاحظ ما هو مستخدم بالفعل ؛ إرفاق المتغيرات في علامات الاقتباس ؛ تفضل "${var}" أكثر من "$var" ، ولكن مع التركيز على سياق الاستخدام.


هذه توصيات بالأحرى ، لأن الموضوع مثير للجدل تمامًا للتنظيم الإلزامي. يتم سردها في ترتيب الأولويات.


  1. استخدم نفس النمط الذي تجده في التعليمات البرمجية الموجودة.
  2. ضع المتغيرات بين علامتي اقتباس ، انظر قسم عرض الأسعار أدناه.
  3. لا تضع أحرف مفردة خاصة بالمعلمات الصدفة / الموضعية في علامات الاقتباس والأقواس ما لم يكن ذلك ضروريًا تمامًا وتجنب الارتباك العميق.
    تفضل الأقواس المتعرجة لجميع المتغيرات الأخرى.


      #    #    '' : echo "Positional: $1" "$5" "$3" echo "Specials: !=$!, -=$-, _=$_. ?=$?, #=$# *=$* @=$@ \$=$$ ..." #   : echo "many parameters: ${10}" #    : # Output is "a0b0c0" set -- abc echo "${1}0${2}0${3}0" #     : echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}" while read f; do echo "file=${f}" done < <(ls -l /tmp) #    #   ,    , #     ,   shell echo a=$avar "b=$bvar" "PID=${$}" "${1}" #  : #    "${1}0${2}0${3}0",   "${10}${20}${30} set -- abc echo "$10$20$30" 

    اقتباسات


    • استخدم دائمًا علامات الاقتباس للقيم التي تحتوي على متغيرات ، أو استبدالات أوامر ، أو مسافات أو أحرف أولية ، حتى تحتاج إلى عرض القيم بأمان لا بين علامات اقتباس.
    • تفضيل علامات الاقتباس للقيم "الكلمات" (على عكس معلمات الأمر أو أسماء المسارات)
    • لا تستشهد أبداً بالأعداد الصحيحة.
    • اعرف كيف تعمل علامات الاقتباس لأنماط المطابقة في [[ .
    • استخدم "$@" إذا لم يكن لديك سبب محدد لاستخدام $* .


 # ''  ,     . # ""  ,   /. #   # "   " flag="$(some_command and its args "$@" 'quoted separately')" # "  " echo "${flag}" # "      " value=32 # "    ",      number="$(generate_number)" # "   ",    readonly USE_INTEGER='true' # "    - shell" echo 'Hello stranger, and well met. Earn lots of $$$' echo "Process $$: Done making \$\$\$." # "    " # ( ,  $1  ) grep -li Hugo /dev/null "$1" #    # "   ,    ": ccs     git send-email --to "${reviewers}" ${ccs:+"--cc" "${ccs}"} #   : $1    #       . grep -cP '([Ss]pecial|\|?characters*)$' ${1:+"$1"} #   , # "$@"   ,  # $*    # # * $*  $@   ,   #      ; # * "$@"     ,   #       ; #     ,      #   # * "$*"    ,    #     () , #        # ( 'man bash'  nit-grits ;-) set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$*"; echo "$#, $@") set -- 1 "2 two" "3 three tres"; echo $# ; set -- "$@"; echo "$#, $@") 

الميزات والأخطاء


استبدال الأمر


استخدم $(command) بدلاً من backticks.


تتطلب backticks المتداخلة الهروب من علامات الاقتباس الداخلية مع \ . لا يتغير تنسيق $ (command) اعتمادًا على التداخل ويسهل قراءته.


مثال:


 #  : var="$(command "$(command1)")" #  : var="`command \`command1\``" 

الشيكات ، [ و [[


[[ ... ]] أفضل من [ ، test or /usr/bin/[ .


[[ ... ]] يقلل من احتمال الخطأ ، لأنه لا يوجد تحليل مسار أو فصل كلمة بين [[ و ]] ، و [[ ... ]] يسمح لك باستخدام تعبير عادي حيث [ ... ] ليس كذلك.


 #  ,       #  `alnum`,     . #  ,       #  .  ,  # E14   https://tiswww.case.edu/php/chet/bash/FAQ if [[ "filename" =~ ^[[:alnum:]]+name ]]; then echo "Match" fi #     "f*" (    ) if [[ "filename" == "f*" ]]; then echo "Match" fi #    "too many arguments",   f*   #     if [ "filename" == f* ]; then echo "Match" fi 

تحقق من القيم


استخدم علامات الاقتباس بدلاً من الأحرف الزائدة حيثما أمكن.


Bash ذكي بما يكفي للعمل مع سلسلة فارغة في الاختبار. لذلك ، من السهل جدًا قراءة التعليمات البرمجية الناتجة ؛ استخدم عمليات التحقق من القيم الفارغة / غير الفارغة أو القيم الفارغة ، دون استخدام أحرف إضافية.


 #  : if [[ "${my_var}" = "some_string" ]]; then do_something fi # -z (   ),  -n (    ): #      if [[ -z "${my_var}" ]]; then do_something fi #   ( ),   : if [[ "${my_var}" = "" ]]; then do_something fi #   : if [[ "${my_var}X" = "some_stringX" ]]; then do_something fi 

لتجنب الارتباك حول ما تقوم بفحصه ، استخدم -z أو -n بشكل صريح.


 #   if [[ -n "${my_var}" ]]; then do_something fi #  ,    ,  ${my_var} #     . if [[ "${my_var}" ]]; then do_something fi 

تعبيرات الاستبدال لأسماء الملفات


استخدم المسار الصريح عند إنشاء تعبيرات حرف البدل لأسماء الملفات.


نظرًا لأن أسماء الملفات يمكن أن تبدأ بالحرف - ، فمن الأكثر أمانًا أن ./* تعبير حرف البدل على أنه ./* بدلاً من * .


 #   : # -f -r somedir somefile #        force psa@bilby$ rm -v * removed directory: `somedir' removed `somefile' #   : psa@bilby$ rm -v ./* removed `./-f' removed `./-r' rm: cannot remove `./somedir': Is a directory removed `./somefile' 

تقييم


يجب تجنب eval .


يسمح لك Eval بتوسيع المتغيرات التي تم تمريرها في الإدخال ، ولكن يمكنه أيضًا تعيين متغيرات أخرى ، دون إمكانية التحقق منها.


 #   ? #   ?   ? eval $(set_my_variables) #  ,         ? variable="$(eval some_function)" 

الأنابيب في حين


استخدم استبدال الأمر أو حلقة for بدلاً من الأنابيب في while . المتغيرات المتغيرة في while لا تنتشر إلى الأصل ، لأن أوامر الحلقة تنفذ في غلاف فرعي.


يمكن أن يؤدي وجود غلاف فرعي ضمني في الأنبوب أثناء التشغيل إلى صعوبة تتبع الأخطاء.


 last_line='NULL' your_command | while read line; do last_line="${line}" done #   'NULL' echo "${last_line}" 

استخدم حلقة for إذا كنت متأكدًا من أن الإدخال لن يحتوي على مسافات أو أحرف خاصة (لا يعني هذا عادةً إدخال المستخدم).


 total=0 #  ,       . for value in $(command); do total+="${value}" done 

يسمح لك استخدام استبدال الأوامر بإعادة توجيه الإخراج ، ولكنه ينفذ الأوامر في غلاف فرعي صريح ، على عكس الغلاف الفرعي الضمني ، الذي ينشئ bash while .


 total=0 last_file= while read count filename; do total+="${count}" last_file="${filename}" done < <(your_command | uniq -c) #         # . echo "Total = ${total}" echo "Last one = ${last_file}" 

استخدم while الحلقات حيث لا توجد حاجة لتمرير النتائج المعقدة إلى القشرة الأم - وهذا أمر نموذجي عند الحاجة إلى "تحليل" أكثر تعقيدًا. تذكر أن الأمثلة البسيطة أسهل أحيانًا لحلها باستخدام أداة مثل awk. يمكن أن يكون مفيدًا أيضًا عندما لا تريد على وجه التحديد تعديل متغيرات البيئة الرئيسية.


 #    awk: # awk '$3 == "nfs" { print $2 " maps to " $1 }' /proc/mounts cat /proc/mounts | while read src dest type opts rest; do if [[ ${type} == "nfs" ]]; then echo "NFS ${dest} maps to ${src}" fi done 

اصطلاح التسمية


أسماء الدوال


أحرف صغيرة مع شرطات سفلية لفصل الكلمات. مكتبات منفصلة مع :: . الأقواس مطلوبة بعد اسم الوظيفة. الكلمة الأساسية الوظيفية اختيارية ، ولكن إذا تم استخدامها ، فهي متسقة طوال المشروع.


إذا قمت بكتابة وظائف منفصلة ، فاستخدم كلمات صغيرة وكتابة منفصلة بشرطة سفلية. إذا كنت تكتب حزمة ، افصل بين أسماء الحزم بـ :: . يجب أن تكون الأقواس على نفس سطر اسم الوظيفة (كما هو الحال في اللغات الأخرى على Google) ، ولا تحتوي على مسافة بين اسم الوظيفة والقوس.


 #   my_func() { ... } #   mypackage::my_func() { ... } 

عندما تأتي "()" بعد اسم الوظيفة ، تبدو الكلمة الرئيسية للوظيفة زائدة عن الحاجة ، ولكنها تحسن التعرف السريع على الوظائف.


اسم متغير


فيما يتعلق بأسماء الوظائف.


يجب تسمية الأسماء المتغيرة للحلقات بالتساوي لأي متغير تقوم بتكراره.


 for zone in ${zones}; do something_with "${zone}" done 

أسماء ثابتة متغيرة البيئة


يتم الإعلان عن جميع الأحرف الكبيرة ، مفصولة بشرطة سفلية ، في الجزء العلوي من الملف.


يجب أن تكون الثوابت وكل ما يتم تصديره إلى البيئة في حالة كبيرة.


 #  readonly PATH_TO_FILES='/some/path' # ,   declare -xr ORACLE_SID='PROD' 

تظل بعض الأشياء ثابتة عند تثبيتها لأول مرة (على سبيل المثال ، عبر getopts ). وبالتالي ، من الطبيعي جدًا تعيين ثابت من خلال getopts أو بناءً على شرط ، ولكن يجب أن يتم readonly بعد ذلك مباشرة. لاحظ أن declare لا يعمل مع المتغيرات العامة داخل الوظائف ، لذا يُنصح readonly أو export بدلاً من ذلك.


 VERBOSE='false' while getopts 'v' flag; do case "${flag}" in v) VERBOSE='true' ;; esac done readonly VERBOSE 

أسماء ملفات المصدر


أحرف صغيرة ، مع تسطير لفصل الكلمات ، إذا لزم الأمر.


ينطبق هذا على مطابقة الأنماط الأخرى من التعليمات البرمجية على Google: maketemplate أو make_template ، ولكن ليس make-template .


قراءة المتغيرات فقط


استخدم readonly أو declare -r للتأكد من أنها للقراءة فقط.


shell, . , , .


 zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)" if [[ -z "${zip_version}" ]]; then error_message else readonly zip_version fi 


, , local . .


, , local . , .


, ; local exit code .


 my_func2() { local name="$1" #      : local my_var my_var="$(my_func)" || return #   : $?  exit code  'local',   my_func local my_var="$(my_func)" [[ $? -eq 0 ]] || return ... } 


. .


, . , set , .


. .


الرئيسية


, main , , .


, main . , ( , ). main:


 main "$@" 

, , , main — , .




.


$? if , .


:


 if ! mv "${file_list}" "${dest_dir}/" ; then echo "Unable to move ${file_list} to ${dest_dir}" >&2 exit "${E_BAD_MOVE}" fi #  mv "${file_list}" "${dest_dir}/" if [[ "$?" -ne 0 ]]; then echo "Unable to move ${file_list} to ${dest_dir}" >&2 exit "${E_BAD_MOVE}" fi Bash    `PIPESTATUS`,         .           ,   : ```bash tar -cf - ./* | ( cd "${dir}" && tar -xf - ) if [[ "${PIPESTATUS[0]}" -ne 0 || "${PIPESTATUS[1]}" -ne 0 ]]; then echo "Unable to tar files to ${dir}" >&2 fi 

, PIPESTATUS , - , , PIPESTATUS ( , [ PIPESTATUS ).


 tar -cf - ./* | ( cd "${DIR}" && tar -xf - ) return_codes=(${PIPESTATUS[*]}) if [[ "${return_codes[0]}" -ne 0 ]]; then do_something fi if [[ "${return_codes[1]}" -ne 0 ]]; then do_something_else fi 


shell , .


, bash, ( , sed ).


:


 #  : addition=$((${X} + ${Y})) substitution="${string/#foo/bar}" #  : addition="$(expr ${X} + ${Y})" substitution="$(echo "${string}" | sed -e 's/^foo/bar/')" 

الخلاصة


.


, Parting Words C++ .

Source: https://habr.com/ru/post/ar413155/


All Articles