L’utilisation de linter pour PHP, JavaScript et bien d’autres langages de programmation est courante.
Les développeurs webs par exemple connaissent tous la commande « php -l », commande de lint présente directement dans le langage. Cet appel, par sa simplicité, est souvent intégré pleinement dans le cycle de développement : Il peut par exemple être intégré dans un hook de precommit git ou même être directement configuré dans l’IDE du développeur.
Mais il est beaucoup plus rare de voir de la vérification syntaxique sur des scripts shell. On peut facilement imaginer plusieurs raisons pour cela, comme par exemple une utilisation assez rare de la part des administrateurs systèmes et devops d’IDE, ceux-ci passant en général beaucoup de temps branchés directement en SSH sur des machines distantes sans interfaces graphiques et aux ressources parfois minimalistes.
Il est pourtant extrêmement simple de mettre en place un linter shell grâce à Shellcheck, et les gains sont comme pour les autres langages de programmation ou scripting non-négligeables. Étudions cela ensemble.
Shellcheck c’est quoi ?
Shellcheck, c’est un outil de vérification syntaxique pour Shell et Bash qui donne des warnings et suggestions.
Le projet est open source sous licence GPLv3, avec un repo Github créé en 2012 et amassant à cette date près de dix mille stars.
Le projet possède en outre une version en ligne synchronisée sur les derniers commits, permettant de tester directement son fonctionnement :
Shellcheck, pourquoi ?
Comme pour tous linter, l’intérêt de Shellcheck est multiple :
- Corriger les coquilles et erreurs d’inattention qui parasitent du temps de développement
- Aider lors de scripts particulièrement complexes en indiquant les parties qui ne sont plus utilisées ou peuvent maintenant être refactor différemment
- Mettre en garde contre de cas pouvant réagir différemment de ce qui est habituellement attendu
Un linter est donc tout autant utile pour le nouvel utilisateur du langage (lui montrant les erreurs courantes qu’il fait) que pour l’utilisateur confirmé (lui évitant les erreurs d’inattention et les cas très spécifiques).
Shellcheck permet une utilisation de plusieurs façons permettant de répondre à la plupart des cas d’utilisation : En utilisation interactive via la commande shell ou l’interface en ligne, en aide intégré dans une éditeur de texte, et en utilisation inline ou intégré dans une autre solution via une sortie disponible entre autre en JSON et XML.
Installation système
L’installation est des plus simples, Shellcheck étant présent sur la majorité des gestionnaires de paquets :
Installation sous Debian et Ubuntu
apt-get install shellcheck
Installation sous macOS
brew install shellcheck
Il est aussi disponible via yum, dnf, pacman, cabal, port…
Utilisation via command line
L’utilisation est on ne peut plus simple. Dans sa version la plus simple, shellcheck <file> effectue une vérification syntaxique et l’output dans la sortie standard :
Imaginons que pour une raison ou une autre vous ne puissiez corriger un warning indiqué. Deux solutions s’offrent à vous : L’ignorer directement dans le code à la ligne dédiée, ou l’ignorer entièrement dans shellcheck.
Dans notre cas, imaginons que l’erreur ligne 3 “SC1090: Can’t follow non-constant source. Use a directive to specify location.” ne soit pas corrigeable. Les deux possibilités sont donc soit la directive directement dans le code :
ou soit l’éviter globalement dans la commande en elle-même :
Intégration dans Sublime Text 3
L’intégration de Shellcheck dans Sublime passe par SublimeLinter :
- Installer Shellcheck via votre gestionnaire de paquet
- Installer Package Control s’il ne l’est pas déjà
- Ouvrir la commande Package Control dans Sublime, via cmd+shift+p pour Mac OS X, ctrl+shift+p pour Linux et Windows, choisir install > Package Control: Install Package et sélectionner SublimeLinter
- Répéter l’étape précédente, mais installer cette fois-ci SublimeLinter-shellcheck
- Redémarrer Sublime Text
Vous devriez maintenant avoir votre linter intégré :
Intégration dans vim
Bonne nouvelle, ale (Asynchronous Lint Engine) gère Shellcheck nativement !
Si vous ne l’avez pas déjà, le plus simple est encore de passer par le gestionnaire de paquet pour vim pack.
pack install w0rp/ale
Erreurs standards
Les erreurs et warnings les plus courants que Shellcheck vous ressortira :
- Variables ou glop non encadrées de guillemets
- Opération non supportée (citation dans une regex dans une comparaison =~, double && dans le scope d’une comparaison, etc)
- Utilisation d’un glob à la place d’une regex ou inversement
- Espace à côté du signe égal lors de l’assignement d’une variable
- Non vérification d’un cd
- Injection shell via find -exec
- rm -rf sauvage
- Utilisation de fonctionnalités non présentes dans la version shell définie dans le shebang (utilisation de bash dans un cadre shell, etc)
Une liste plus complète est disponible dans leur README.
Utilisation dans une CI avec Jenkins2
Chez Adfab, nous utilisons Jenkins sous sa version Blue Ocean de manière intensive pour tous nos projets. Bien qu’ils soient majoritairement web et mobiles pour nos clients, nos projets internes et surtout les projets de l’équipe DevOps sont shell-friendly : à un moment ou à un autre, une grande majorité de notre coeur applicatif interne repose sur des scripts shell.
Nous avons donc mis en place ce linter dans la chaîne de déploiement. Et cela le plus simplement du monde, vous allez voir avec comme exemple une de nos plateforme interne – une couche PHP Symfony3 pilotant des scripts Bash.
Notre Jenkinsfile est des plus standard : Une étape de build, une étape de tests, et une étape de déploiement.
L’étape de test est minimaliste : Un fichier de commandes, sobrement appelé test.sh, et des arguments pour Jenkins.
Deux lignes nous intéressent ici : Le fichier test.sh en lui-meme, et le checkstyle pattern.
Voyons tout d’abord le fichier shell :
Ce script se décompose en trois parties :
1/ De l’initialisation interne et vérification dans le cadre de notre projet.
2/ Notre appel Shellcheck
3/ Le reste de nos appels de tests, dans ce cas-ci pour de la vérification PHP.
Étudions donc l’étape 2 :
En premier lieu, nous récupérons les scripts shell à analyser, en excluant certains fichiers volontairement non parsés.
SCFILES=$(ls bin/*.sh | grep -v -e « common.sh » -e « variables.sh » | xargs)
Ensuite, la partie intéressante. Nous vérifions tout d’abord que Shellcheck est bien présent sur le système :
command -v shellcheck 2>/dev/null
Si c’est bien le cas, nous appelons la commande shellcheck, en lui fournissant trois paramètres :
- -f : Le format de sortie souhaité, dans notre cas checkstyle, géré nativement par notre Jenkins2.
- -e : Les fameuses exclusions d’erreurs globales. Vous remarquerez que certaines exclusions locales étaient déjà présentes dans notre test.sh.
- ${SCFILES} : Les scripts récupérés plus tôt à analyser.
Enfin nous envoyons la sortie de Shellcheck dans un fichier de log.
> build/checkstyle-shell.xml
Ce fichier, rappelez-vous, vous le retrouvez dans le Jenkinsfile :
checkstyle pattern: ‘build/checkstyle-php.xml, build/checkstyle-shell.xml’, unstableTotalAll: ‘0’
Nous indiquons donc le fichier de checkstype shell créé, ceux des autres langages au besoin comme ici pour le PHP, et enfin avec unstableTotalAll: ‘0’ nous indiquons à Jenkins de traiter le build comme unstable si des warnings ou erreurs ressortent de ces tests.
Et voila. Si vous vous dirigez vers votre interface Jenkins Blue Ocean, vous pouvez maintenant observer votre linter shell à l’oeuvre :
En conclusion
Shellcheck est rapide, efficace, open source et mis à jour régulièrement, et en plus de cela son intégration dans toute autre solution est l’affaire de quelques secondes. Pourquoi s’en passer ?