Google Shell Style Guide (en russe)

Préface


Quel shell utiliser


Bash seul langage de script shell pouvant être utilisé pour les fichiers exécutables.


Les scripts doivent commencer par #!/bin/bash avec un ensemble minimal d'indicateurs. Utilisez set pour définir les options du shell afin que l'appel de votre script en tant que bash <script_name> ne viole pas sa fonctionnalité.


Limiter tous les scripts shell à bash nous donne un langage shell cohérent qui est installé sur toutes nos machines.


La seule exception est si vous êtes limité par les conditions de votre programmation. Un exemple serait les packages Solaris SVR4, qui nécessitent l'utilisation du shell Bourne habituel pour n'importe quel script.


Quand utiliser Shell


Shell ne doit être utilisé que pour les petits utilitaires ou les wrappers de script simples.


Bien que le script shell ne soit pas un langage de développement, il est utilisé pour écrire divers utilitaires dans Google. Ce guide de style est plus une reconnaissance de son utilisation, plutôt qu'une proposition de l'utiliser à grande échelle.


Quelques recommandations:


  • Si vous appelez le plus souvent d'autres utilitaires et effectuez relativement peu de manipulation de données, le shell est un choix acceptable pour la tâche.
  • Si les performances sont importantes, utilisez autre chose mais pas de shell.
  • Si vous constatez que vous devez utiliser des tableaux pour plus que l'attribution de ${PIPESTATUS} , vous devez utiliser Python.
  • Si vous écrivez un script de plus de 100 lignes, vous devriez probablement l'écrire en Python. Gardez à l'esprit que les scripts se développent. Réécrivez votre script dans une autre langue plus tôt pour éviter une réécriture fastidieuse plus tard.

Fichiers shell et appel interprète


Extensions de fichiers


Les fichiers exécutables ne doivent pas avoir l'extension (fortement préférée) ou l'extension .sh . Les bibliothèques doivent avoir l'extension .sh et ne doivent pas être exécutables.


Il n'est pas nécessaire de savoir dans quelle langue le programme est écrit lors de son exécution, et le shell ne nécessite pas d'extension, nous préférons donc ne pas l'utiliser pour les fichiers exécutables.


Cependant, il est important que les bibliothèques sachent dans quelle langue elles sont écrites, et parfois il est nécessaire d'avoir des bibliothèques similaires dans différentes langues. Cela vous permet d'avoir des fichiers de bibliothèque nommés de manière identique avec des objectifs identiques, mais écrits dans des langues différentes doivent avoir un nom identique, à l'exception d'un suffixe spécifique à la langue.


SUID / SGID


SUID et SGID sont interdits sur les scripts shell.


Il y a trop de problèmes de sécurité, ce qui rend presque impossible de fournir une protection SUID / SGID suffisante. Bien que bash complique le lancement de SUID, il est toujours possible sur certaines plateformes, nous interdisons donc explicitement son utilisation.


Utilisez sudo pour un accès amélioré si vous en avez besoin.


L'environnement


STDOUT vs STDERR


Tous les messages d'erreur doivent être envoyés à STDERR .


Cela permet de séparer l'état normal des problèmes réels.


Il est recommandé d'utiliser la fonction d'affichage des messages d'erreur avec d'autres informations d'état.


 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 

Commentaires


En-tête de fichier


Démarrez chaque fichier avec une description de son contenu.


Chaque fichier doit avoir un titre dans le commentaire, y compris une brève description de son contenu. Les mentions de copyright et les informations sur l'auteur sont facultatives.


Un exemple:


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

Commentaires sur les fonctionnalités


Toute fonction qui n'est pas évidente et courte doit être commentée. Toute fonction de la bibliothèque doit être commentée quelle que soit sa longueur ou sa complexité.


Vous devez vous assurer que quelqu'un d'autre comprend comment utiliser votre programme ou comment utiliser la fonction dans votre bibliothèque en lisant simplement les commentaires (et le besoin de s'améliorer) sans lire le code.


Tous les commentaires sur les fonctionnalités doivent inclure:


  • Description de la fonction
  • Variables globales utilisées et modifiées
  • Arguments reçus
  • Renvoie des valeurs différentes des codes de sortie standard de la dernière commande.

Un exemple:


 #!/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() { ... } 

Commentaires sur la mise en œuvre


Commentez des parties complexes, non évidentes, intéressantes ou importantes de votre code.


Ceci est supposé être la pratique habituelle de commenter le code sur Google. Ne commentez pas tout. S'il y a un algorithme compliqué ou si vous faites quelque chose d'inhabituel, ajoutez un bref commentaire.


Commentaires TODO


Utilisez les commentaires TODO pour du code temporaire, à court terme ou plutôt bon, mais pas parfait.


Ceci est conforme à la convention du manuel C ++ .


Les commentaires TODO doivent inclure le mot TODO en majuscules, suivi de votre nom entre parenthèses. Un deux-points est facultatif. Il est également préférable d'indiquer le numéro de bug / ticket à côté de l'élément TODO.


Un exemple:


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

Formatage


Bien que vous deviez suivre le style déjà utilisé dans les fichiers que vous modifiez, les éléments suivants sont requis pour tout nouveau code.


Indentation


Retrait de 2 espaces. Aucun onglet.


Utilisez des lignes vides entre les blocs pour améliorer la lisibilité. L'indentation est de deux espaces. Quoi que vous fassiez, n'utilisez pas d'onglets. Pour les fichiers existants, restez fidèle à l'indentation actuelle.


Longueur de chaîne et longueur de valeur


La longueur de ligne maximale est de 80 caractères.


Si vous avez besoin d'écrire des lignes de plus de 80 caractères, cela doit être fait en utilisant le here document ou, si possible, la newline intégrée. Les valeurs littérales qui peuvent dépasser 80 caractères et ne peuvent pas être séparées sont raisonnablement autorisées, mais il est fortement recommandé de trouver un moyen de les raccourcir.


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

Pipelines


Les pipelines doivent être divisés chacun sur une seule ligne s'ils ne tiennent pas sur une seule ligne.


Si un pipeline tient sur une seule ligne, il doit l'être sur une seule ligne.


Sinon, il doit être divisé de façon à ce que chaque section soit sur une nouvelle ligne et en retrait de 2 espaces pour la section suivante. Cela fait référence à la chaîne de commande combinée à l'aide de «|» ainsi que les connexions logiques utilisant '||' et '&&'.


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

Cycles


Lieu ; do ; do et ; then ; then sur la même ligne que while , for ou if .


Les cycles dans la coque sont légèrement différents, mais nous suivons les mêmes principes qu'avec les accolades pour déclarer des fonctions. C'est ; then ; then et ; do ; do doit être sur la même ligne que if / for / while . else , les instructions doivent être sur une ligne distincte et les instructions de fermeture doivent être sur leur propre ligne, alignées verticalement avec l'instruction d'ouverture.


Un exemple:


 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 

Déclaration de cas


  • Options séparées dans 2 espaces.
  • Les options sur une seule ligne nécessitent un espace après le crochet de fermeture du modèle et avant ;; .
  • Les options longues ou multi-commandes doivent être divisées en plusieurs lignes avec un modèle, des actions et ;; sur des lignes séparées.

Les expressions correspondantes se retirent d'un niveau de case et d' esac . Les actions multilignes ont également des retraits à un niveau distinct. Il n'est pas nécessaire de mettre des expressions entre guillemets. Les modèles d'expression ne doivent pas être précédés de parenthèses ouvertes. Évitez d'utiliser &; et ;;& notation.


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

Des commandes simples peuvent être placées sur une seule ligne avec le motif et ;; tandis que l'expression reste lisible. Cela convient souvent pour gérer les options à une seule lettre. Lorsque les actions ne tiennent pas sur une seule ligne, laissez le modèle dans votre ligne, l'action suivante, puis ;; également en ligne propre. Lorsqu'il s'agit de la même ligne que pour les actions, utilisez un espace après le crochet de fermeture du modèle et un autre avant ;; .


 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 

Expansion variable


Par ordre de priorité: observez ce qui est déjà utilisé; mettez les variables entre guillemets; préférez "${var}" à "$var" , mais en tenant compte du contexte d'utilisation.


Ce sont plutôt des recommandations, car le sujet est assez controversé pour la réglementation obligatoire. Ils sont classés par ordre de priorité.


  1. Utilisez le même style que vous trouvez dans le code existant.
  2. Mettez les variables entre guillemets, voir la section Devis ci-dessous.
  3. Ne mettez pas de caractères uniques spécifiques aux paramètres shell / positionnels entre guillemets et accolades, sauf si cela est strictement nécessaire et pour éviter toute confusion profonde.
    Préférez les accolades pour toutes les autres variables.


      #    #    '' : 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" 

    Citations


    • Utilisez toujours des guillemets pour les valeurs qui contiennent des variables, des substitutions de commande, des espaces ou des métacaractères de shell, jusqu'à ce que vous deviez exposer en toute sécurité les valeurs qui ne sont pas entre guillemets.
    • Préférez les guillemets pour les valeurs qui sont des "mots" (par opposition aux paramètres de commande ou aux noms de chemin)
    • Ne citez jamais des nombres entiers.
    • Savoir comment les guillemets fonctionnent pour les modèles de correspondance dans [[ .
    • Utilisez "$@" si vous n'avez aucune raison particulière d'utiliser $* .


 # ''  ,     . # ""  ,   /. #   # "   " 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 "$#, $@") 

Caractéristiques et erreurs


Substitution de commandes


Utilisez $(command) au lieu de backticks.


Les backticks imbriqués nécessitent d'échapper les guillemets internes avec \ . Le format $ (command) ne change pas en fonction de l'imbrication et est plus facile à lire.


Un exemple:


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

Chèques, [ et [[


[[ ... ]] plus préférable que [ , test ou /usr/bin/[ .


[[ ... ]] réduit le risque d'erreur, car il n'y a pas de résolution de chemin ni de séparation de mots entre [[ et ]] , et [[ ... ]] vous permet d'utiliser une expression régulière là où [ ... ] ne l' [ ... ] pas.


 #  ,       #  `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 

Vérifier les valeurs


Utilisez des guillemets plutôt que des caractères supplémentaires dans la mesure du possible.


Bash est suffisamment intelligent pour fonctionner avec une chaîne vide dans un test. Par conséquent, le code résultant est beaucoup plus facile à lire; utilisez des vérifications pour les valeurs vides / non vides ou les valeurs vides, sans utiliser de caractères supplémentaires.


 #  : 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 

Pour éviter toute confusion sur ce que vous vérifiez, utilisez explicitement -z ou -n .


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

Expressions de substitution pour les noms de fichiers


Utilisez le chemin explicite lors de la création d'expressions génériques pour les noms de fichiers.


Étant donné que les noms de fichiers peuvent commencer par le caractère - , il est beaucoup plus sûr de ./* expression générique comme ./* au lieu de * .


 #   : # -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 doit être évité.


Eval vous permet d'étendre les variables passées en entrée, mais il peut également définir d'autres variables, sans possibilité de les vérifier.


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

Tuyaux dans While


Utilisez la substitution de commandes ou la boucle for , plutôt que des tuyaux dans while . Les variables modifiées dans la while ne se propagent pas au parent, car les commandes de boucle sont exécutées dans un sous-shell.


Un sous-shell implicite dans le tube while peut rendre le suivi des erreurs difficile.


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

Utilisez une boucle for si vous êtes sûr que l'entrée ne contiendra pas d'espaces ou de caractères spéciaux (cela n'implique généralement pas d'entrée utilisateur).


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

L'utilisation de la substitution de commandes vous permet de rediriger la sortie, mais exécute les commandes dans un sous-shell explicite, contrairement au sous-shell implicite, qui crée bash pour la 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}" 

Utilisez les boucles while où il n'est pas nécessaire de transmettre des résultats complexes au shell parent - ceci est typique lorsqu'un "parsing" plus complexe est requis. N'oubliez pas que des exemples simples sont parfois beaucoup plus faciles à résoudre à l'aide d'un outil comme awk. Il peut également être utile lorsque vous ne souhaitez pas spécifiquement modifier les variables de l'environnement parent.


 #    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 

Convention de dénomination


Noms des fonctions


Minuscule avec des traits de soulignement pour séparer les mots. Séparez les bibliothèques avec :: . Des parenthèses sont requises après le nom de la fonction. Le mot clé function est facultatif, mais s'il est utilisé, il est cohérent tout au long du projet.


Si vous écrivez des fonctions séparées, utilisez des mots en minuscules et séparés avec des traits de soulignement. Si vous écrivez un package, séparez les noms des packages par :: . Les crochets doivent être sur la même ligne que le nom de la fonction (comme dans d'autres langues sur Google), et ne pas avoir d'espace entre le nom de la fonction et le crochet.


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

Lorsque "()" vient après le nom de la fonction, le mot-clé function semble redondant, mais il améliore l'identification rapide des fonctions.


Nom de variable


Concernant les noms de fonction.


Les noms de variables pour les boucles doivent être également nommés pour toute variable sur laquelle vous itérez.


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

Noms des constantes des variables d'environnement


Tous en majuscules, séparés par des traits de soulignement, sont déclarés en haut du fichier.


Les constantes et tout ce qui est exporté vers l'environnement doivent être en majuscules.


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

Certaines choses restent constantes lors de leur première installation (par exemple, via getopts ). Ainsi, il est tout à fait normal de définir une constante via getopts ou en fonction d'une condition, mais cela doit être fait en readonly juste après cela. Notez que declare ne fonctionne pas avec les variables globales dans les fonctions, donc en readonly ou export recommandé à la place.


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

Noms des fichiers source


Minuscules, avec soulignement pour séparer les mots, si nécessaire.


Cela s'applique à la correspondance avec d'autres styles de code sur Google: maketemplate ou make_template , mais pas make-template .


Variables en lecture seule


Utilisez readonly ou declare -r pour vous assurer qu'ils sont en lecture seule.


Étant donné que les globales sont largement utilisées dans le shell, il est important de détecter les erreurs lors de leur utilisation. , , .


 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 , .


. .


principal


, main , , .


, main . , ( , ). main:


 main "$@" 

, , , main — , .




.


$? if , .


Un exemple:


 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 ).


Un exemple:


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

Conclusion


.


, Parting Words C++ .

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


All Articles