5 points par GN⁺ 2023-11-25 | 1 commentaires | Partager sur WhatsApp
  • 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

 
GN⁺ 2023-11-25
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

    • Le problème avec "${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 variable
      Cela 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[@]}}"
    • On entend aussi souvent le conseil de ne pas mettre d’options Bash/shell dans le shebang. Si quelqu’un exécute le script en spécifiant explicitement l’interpréteur, par exemple bash my_script.sh au lieu de ./my_script.sh, les options ne seront pas appliquées
      Pas 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 set comme set -euo pipefail sur 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
    • Je me demande s’il y a une raison particulière de mettre -u dans le shebang. Pourquoi dans le shebang plutôt qu’avec set -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=38397241
      Par exemple, bash -uc 'unset x; echo "=> ${x[@]}"' et bash -uc 'x=(); echo "=> ${x[@]}"' passent avec une valeur vide, mais bash -uc 'x=(); echo "=> ${x[0]}"' donne bash: x[0]: unbound variable. Zsh n’aime pas le premier exemple, mais les deux devraient prendre en charge une expansion par défaut comme bash -uc 'unset x; echo "=> ${x[@]:-null}"'. -e devient très déroutant quand on le mêle aux fonctions, donc avec le temps je l’apprécie de moins en moins
    • Le problème de -e peut être bien géré avec -o pipefail, et cela fait partie de POSIX depuis l’an dernier
    • Quand on utilise des options qui arrêtent le script tôt, il fallait en général aussi utiliser trap lorsqu’il y avait des fichiers à nettoyer
      trap est une excellente fonctionnalité de scripting, mais j’ai l’impression qu’on n’en parle pas assez
  • J’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 shell

    • Si on veut plus de sécurité, qu’est-ce qu’il faut utiliser ?
  • ShellCheck 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/

    • C’est bien, mais ça semble surtout valable pour ses propres scripts. Sinon il faudrait déboguer et corriger en permanence tous les scripts qu’on doit exécuter
  • 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

    • J’ai essayé Turtle récemment, mais je l’ai finalement abandonné au profit de typed-process
      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 de OverloadedStrings est aussi très pratique : en général, je peux copier-coller ce que j’aurais tapé dans bash et ça fonctionne tel quel
      J’utilise aussi le package interpolate et QuasiQuotes pour rendre les chaînes brutes plus lisibles dans le code source, mais ce n’est pas compatible avec hlint, donc je pense chercher un autre package pour gérer les chaînes
  • Auto-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

    • J’aimerais bien qu’il existe un projet qui fasse ça de manière plus générale
      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
    • High five. J’ai moi aussi fait quelque chose de similaire tout récemment : https://gitlab.com/engmark/shellcheck-gitlab-ci-scripts-hook
      Exemple d’utilisation : un hook pre-commit ciblant .gitlab-ci.yml, et un exemple de configuration ici : https://gitlab.com/engmark/root/-/blob/9f7d9b93c2297d0b170e5...
    • Vraiment excellent. J’avais essayé autrefois d’aborder ce problème sous un autre angle. Je voulais ajouter un préprocesseur qui prenne des scripts shell « généraux » et les rende dans la partie script de la tâche au moment du build
      L’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 80

  • Si ç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 sh est un cauchemar

    • On peut faire quelque chose comme ça : # shellcheck source=./deployment/deployment-example.env puis . "${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