- ShellCheck est un outil qui permet de vérifier immédiatement sur le Web les erreurs courantes des scripts shell, et d’examiner les problèmes du code collé dans la sortie affichée sous l’éditeur
- En local, il peut être installé avec
cabal,apt,dnf,pkg,brew install, etc., ce qui permet une utilisation via un gestionnaire de paquets - Les exemples montrent des points souvent négligés dans les scripts réels, comme les avertissements de portabilité avec un shebang
sh, les problèmes sémantiques ou les erreurs de gestion des guillemets - C’est un logiciel libre sous licence GPLv3, également disponible sur GitHub, sur un Wiki et comme linter intégré dans les principaux éditeurs
- Il peut être utilisé pour l’analyse automatique des dépôts GitHub via CodeClimate, Codacy et CodeFactor, et ShellCheck lui-même est écrit en Haskell
Fonctionnalités d’analyse de scripts shell
- ShellCheck est un outil d’analyse pour trouver des bugs dans les scripts shell
- Si vous collez un script sur le site Web, vous pouvez voir immédiatement les résultats de l’analyse dans la fenêtre de sortie sous l’éditeur
- Les exemples fournis montrent qu’il peut détecter plusieurs types de problèmes
- divers problèmes cachés dans des scripts courants
- des avertissements de portabilité lorsque le shebang est
sh - des problèmes sémantiques de niveau plus élevé
- divers problèmes de gestion des guillemets
Installation, licence et intégration
- L’installation locale est possible avec
cabal,apt,dnf,pkg,brew install - C’est un logiciel libre sous licence GPLv3
- La documentation est disponible sur le Wiki ShellCheck
- Le projet est publié sur GitHub, et le code du site Web est également ouvert
- Des paquets pour distributions ou gestionnaires de paquets sont déjà disponibles
- Il peut être utilisé comme linter intégré dans les principaux éditeurs
- Il peut servir à l’analyse automatique des dépôts GitHub sur CodeClimate, Codacy, CodeFactor
- ShellCheck est écrit en Haskell
1 commentaires
Commentaires sur Hacker News
Voici mon astuce. Dans le shebang, j’ajoute presque toujours
-u(nounset) pour faire des variables non déclarées une erreur, et l’exception qu’on rencontre souvent est le cas où la syntaxe"${arr[@]}"étend un tableau vide et le traite comme une variable non liée-n(noexec) empêche l’exécution des commandes, donc on peut s’en servir comme d’un dry-run du pauvre.-e(errexit) est aussi utile, mais il faut faire attention au fait qu’en pratique il ne provoque l’arrêt que pour une commande « en elle-même » qui a échoué, donc personnellement je l’évite et je préfère souvent ajouter|| fail "..."après les commandes"${arr[@]}"n’existe qu’en bash 3 ou antérieur ; à partir de bash 4, même quand la variable n’est réellement pas définie,[@]ne déclenche plus d’unbound variableCela reste toutefois un problème parce que macOS installe encore bash v3 par défaut et ne le met pas à jour automatiquement. Le fait que la dernière version de bash 3 date d’il y a 20 ans est complètement dingue. On peut contourner l’unbound sur l’expansion d’un tableau vide avec l’expansion
${var+alter}:echo "${arr+${arr[@]}}"bash my_script.shau lieu de./my_script.sh, les options ne seront pas appliquéesPas mal de gens font ça pour éviter de définir le bit d’exécution, et parfois simplement par méconnaissance. C’est pourquoi on conseille souvent d’utiliser un
setcommeset -euo pipefailsur la première ligne après le shebang, ce qui aide aussi dans les cas où le shebang gère mal les arguments supplémentaires, comme avec#!/usr/bin/env bash-udans le shebang. Pourquoi dans le shebang plutôt qu’avecset -u?Dans Bash,
"${arr[@]}"semble bien fonctionner. Comme mentionné dans d’autres commentaires, c’est mieux dans les versions récentes de Bash, et cela semble n’être un problème qu’en<= 4.3: https://news.ycombinator.com/item?id=38397241Par exemple,
bash -uc 'unset x; echo "=> ${x[@]}"'etbash -uc 'x=(); echo "=> ${x[@]}"'passent avec une valeur vide, maisbash -uc 'x=(); echo "=> ${x[0]}"'donnebash: x[0]: unbound variable. Zsh n’aime pas le premier exemple, mais les deux devraient prendre en charge une expansion par défaut commebash -uc 'unset x; echo "=> ${x[@]:-null}"'.-edevient très déroutant quand on le mêle aux fonctions, donc avec le temps je l’apprécie de moins en moins-epeut être bien géré avec-o pipefail, et cela fait partie de POSIX depuis l’an derniertraplorsqu’il y avait des fichiers à nettoyertrapest une excellente fonctionnalité de scripting, mais j’ai l’impression qu’on n’en parle pas assezJ’ai récemment trouvé une vulnérabilité d’escalade de privilèges dans un shell script à cause de l’expansion arithmétique. C’était similaire au type décrit ici : https://research.nccgroup.com/2020/05/12/shell-arithmetic-ex...
Par exemple, avec
$((1 + ENV_VAR)), si on peut contrôler$ENV_VAR, on peut injecter du code. Malheureusement, ShellCheck ne l’a pas détecté, du moins avec la configuration par défaut. Mais si vous implémentez quelque chose d’un tant soit peu sensible du point de vue de la sécurité, il ne faut de toute façon pas utiliser le shellShellCheck est vraiment un sauveur. J’ai créé il y a quelque temps un petit wrapper https://github.com/jamespwilliams/strictbash qu’on peut utiliser comme shebang d’un script
Il exécute ShellCheck avant le lancement du script, et si une erreur est détectée, le script ne s’exécute pas du tout, tout en activant aussi tous les flags du « strict mode » de bash. Référence : http://redsymbol.net/articles/unofficial-bash-strict-mode/
Ce sujet est déjà revenu plusieurs fois : https://news.ycombinator.com/from?site=shellcheck.net
La dernière grande discussion date de 2021, avec 301 points et 54 commentaires : https://news.ycombinator.com/item?id=27030504
Il y a quelque temps, j’ai remplacé quelques scripts de build et de déploiement, ainsi que des scripts bash pour un unique serveur de production, par Turtle de Haskell
C’était appréciable parce que ça a fortement réduit la duplication, et le code obtenu est devenu bien plus court. https://hackage.haskell.org/package/turtle
Si je comprends bien, un programme Turtle n’a qu’un seul répertoire courant, ce qui complique l’exécution de tâches concurrentes qui doivent tourner dans des répertoires précis. J’ai pu contourner une partie du problème avec des verrous, des files d’attente et des workers, mais c’est devenu ingérable quand le répertoire courant de Turtle a été supprimé et que tout a commencé à échouer
À l’inverse, typed-process lance des processus séparés et peut les exécuter dans leur répertoire de travail sans avoir besoin de faire un
cd, ce qui convient bien aux workflows volumineux et complexes. La prise en charge deOverloadedStringsest aussi très pratique : en général, je peux copier-coller ce que j’aurais tapé dans bash et ça fonctionne tel quelJ’utilise aussi le package
interpolateetQuasiQuotespour rendre les chaînes brutes plus lisibles dans le code source, mais ce n’est pas compatible avechlint, donc je pense chercher un autre package pour gérer les chaînesAuto-promo assumée, mais j’ai mis ShellCheck et plusieurs autres linters dans ma configuration pre-commit avec comme principe de corriger tous les avertissements avant de commit, ou au moins avant de fusionner
Le problème, c’est que dans la plupart de mes projets, la majorité du shell se trouve dans des fichiers
.gitlab-ci.yml, donc c’est difficile à vérifier. J’ai donc créé un wrapper qui l’automatise : https://pypi.org/project/glscpc/En s’appuyant sur le projet ShellCheck et un peu de magie, il affiche les remarques de ShellCheck avec des numéros de ligne presque exacts
Je n’utilise pas GitLab CI, mais il existe pas mal de formats de fichiers, comme Dockerfile, GitHub Actions ou Justfile, qui intègrent essentiellement des scripts shell inline
En général, ne serait-ce que pour ShellCheck, tout ce qui est plus complexe que quelques commandes, je le sors dans un script shell séparé, puis je l’appelle depuis le script inline du Dockerfile. Ce schéma aide aussi à éviter que la CI soit trop couplée à GitHub Actions
Exemple d’utilisation : un hook pre-commit ciblant
.gitlab-ci.yml, et un exemple de configuration ici : https://gitlab.com/engmark/root/-/blob/9f7d9b93c2297d0b170e5...scriptde la tâche au moment du buildL’avantage, c’est que tout reste auto-contenu dans les jobs gitlab-ci. Mais gérer toutes les bizarreries du shell dans l’environnement des runners GitLab CI était beaucoup trop pénible, donc j’ai abandonné, et maintenant je suis en train de déplacer toutes les tâches vers des scripts Python
Il existe aussi bash language server : https://github.com/bash-lsp/bash-language-server/
Très bien. J’ai appris plusieurs choses dès la première exécution sur un script de production
/bin/sh, alors que je manipule ce genre de scripts depuis les années 80Si ça devient trop long pour être écrit en Bash, au point de ne plus vraiment le devoir, je recommande aussi https://github.com/bach-sh/bach
ShellCheck est excellent, mais la gestion de source/import est vraiment pénible. Ce n’est pas la faute de ShellCheck ; c’est juste que
shest un cauchemar# shellcheck source=./deployment/deployment-example.envpuis. "${1}"Cela dit, dès qu’on a plusieurs sous-scripts shell et beaucoup de fichiers à source, on comprend vite pourquoi c’est pénible