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:
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:
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:
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.
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 '&&'.
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é.
- Utilisez le même style que vous trouvez dans le code existant.
- Mettez les variables entre guillemets, voir la section Devis ci-dessous.
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.
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 $*
.
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:
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.
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.
Pour éviter toute confusion sur ce que vous vérifiez, utilisez explicitement -z
ou -n
.
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 *
.
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.
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
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
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)
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.
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.
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.
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"
. .
, . , 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
, 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:
Conclusion
.
, Parting Words C++ .