Journal Shebang #!/usr/bin/env sh : testé et approuvé

Posté par  . Licence CC By‑SA.
Étiquettes :
26
2
mai
2019

Je prends grand soin à éviter les bashismes pour que mes scripts puissent tourner sur n’importe quel système, quelque soit le shell présent, pourvu qu’il soit compatible POSIX.

Mes scripts shell son préfixés avec le shebang #!/usr/bin/env sh et jusqu’à maintenant, je le faisais purement pour la bonne pratique, en me disant que je pourrais très bien utiliser #!/bin/sh, et mon environnement d’exécution se débrouillera toujours pour trouver un shell compatible POSIX pour exécuter mon script. Et si le système ne respecte pas la hiérarchie standard, j’aurai un problème dans tous les cas puisqu’on spécifie de toute façon /usr/bin/env dans l’autre shebang. Or, quel système, en pratique, fournirait /usr/bin/env et pas /bin/sh ? D’ailleurs, il me semblait plus probable de tomber sur un système qui a un /bin/sh et pas un /usr/bin/env, la description de /usr/bin étant « Binaires exécutables qui ne sont pas déjà présents dans /bin et donc pas indispensables à un système minimaliste » (d’ailleurs, pourquoi env est dans /usr/bin et pas dans /bin ?). Ce qui ressemble effectivement à un point négatif à l'utilisation de #!/usr/bin/env sh.

Les systèmes que j’ai l’habitude d’utiliser utilisent dash comme shell par défaut. Tout va bien dans le meilleur des mondes, c’est un shell minimaliste qui à priori ne propose que très peu d’ajouts par rapport à la norme POSIX. Ce qui veut dire que si un script tourne chez moi, il devrait tourner partout pourvu que ses dépendances soient installées.

Et là, j’utilise un système qui utilise bash pour /bin/sh. Je m’en rends vite compte, parce que pour vérifier qu’une construction n’est pas un bashisme, je lance sh et je la colle dedans.
Sauf que là, les bashismes passaient (aka Comment ça, $EPOCHREALTIME fonctionne dans sh alors que c’est une fonctionnalité du tout nouveau Bash 5 ?). Ce qui veut aussi dire que mes scripts avec le shebang #!/usr/bin/env sh se mettent à fonctionner même s’ils contiennent des bashismes (et bon, si je me mets à avoir besoin des bashismes, peut-être qu’il est temps d’utiliser un autre langage de script installé par défaut sur la plupart des systèmes).

Que faire ? Je ne vais pas changer le shell par défaut du système, parce que c’est certainement un bon moyen de chercher les ennuis voire casser le démarrage (il doit y avoir des scripts plein partout se déclarant avec /bin/sh ou /usr/bin/env sh et qui contiennent en fait des bashismes).

Grâce à l’utilisation de #!/usr/bin/env sh, la solution est simple : installer dash ou n’importe quel autre shell limité à la norme POSIX, créer un lien $HOME/bin/sh pointant vers ce shell et avoir $HOME/bin dans mon $PATH, et paf ça fonctionne (initialement, l’idée de faire ce lien dans /usr/local/bin m’a chatouillé l’esprit deux secondes, mais à la troisième seconde je me suis dit que ça avait un bon potentiel pour semer le même chaos que celui qui est décrit dans le paragraphe précédent).

Ouf, ma vie n’a pas changé, je vais pouvoir continuer à écrire mes scripts en toute tranquillité indépendamment du shell par défaut du système que j’utilise. N’avais-je pas utilisé #!/usr/bin/env sh, j’étais bon pour changer tous mes shebangs. Un bon gros sed sur tout mon $HOME. #!/usr/bin/env sh, c’est bon, mangez-en.

  • # Branlette intellectuelle

    Posté par  . Évalué à 5.

    Tout est dans le titre.

    • [^] # Re: Branlette intellectuelle

      Posté par  . Évalué à 6.

      Assumée :-)

      Cela dit, j'ai déjà écrit des scripts pour le shell d'Android et avoir l'habitude d'écrire en POSIX a permis de le faire sans se sentir limité par l'absence de bash.

    • [^] # Re: Branlette intellectuelle

      Posté par  . Évalué à 7.

      Bein non, désolé…. De mon point de vue, la démarche est pro et propre.

      Beaucoup devrait s'en inspirer, ça éviterait souvent des "chez moi ça marche ©" et autre "ah mais non, là y a pas bash, je vais devoir tout réécrire … à moins que tu me déploies bash sur les 123 serveur AIX".

  • # Ksh

    Posté par  (site web personnel) . Évalué à 8. Dernière modification le 02 mai 2019 à 17:09.

    J'ai l'impression d'être en 2005 d'un coup.

    Pas que c'était si lointain, mais parce qu'il y avait dans mon entourage des évangélistes du Ksh, du Sh, mais jamais du Bash.

    Pourquoi bash se fait basher? La compatibilité Posix? c'est vraiment ça le truc?

    • [^] # Re: Ksh

      Posté par  . Évalué à 3.

      C’est de bon ton de basher le truc en cours de règne ! Rares sont les présidents populaires. En tout cas bash n’est détrôné que marginalement.

      De mémoire Dash est utilisé notamment pour les perfs comme shell de démarrage, mais avec systemd comme il y a moins de scripts à l’init ça a sans doute perdu de son intérêt.

    • [^] # Re: Ksh

      Posté par  . Évalué à 5.

      Je n'ai rien contre bash perso, j'ai juste une préférence pour zsh comme shell interactif et du coup j'ai tendance à l'utiliser pour mes scripts perso.

      • [^] # Re: Ksh

        Posté par  (site web personnel) . Évalué à 8. Dernière modification le 03 mai 2019 à 13:01.

        bash, zsh, si Debian a basculé sur dash il y a des années pour les scripts systèmes (le compte root a toujours bash comme shell), ce n'est pas juste pour les beaux yeux. dash est plus rapide et a moins de fonctionnalités donc un angle d'attaque plus faible. Il y a d'ailleurs eu un bogue sur bash qui a touché de manière critique toutes les distributions sauf celles basées sur dash comme Debian et ses dérivées.

        Bref, il ne faut pas opposer le shell du terminal du shell utilisé dans certains scripts.

        • [^] # Re: Ksh

          Posté par  . Évalué à 2.

          Te-ai pas le même usage que debian donc j'utilise pas les même outils que debian.

          • [^] # Re: Ksh

            Posté par  (site web personnel) . Évalué à 2.

            Toi oui mais raphj semble vouloir faire aussi des script système donc chacun ses usages. J'ai un chercheur chez moi qui te dirait de tout faire en Python puisque tu ne fait pas du système ! La diversité à du bien ;-)

  • # Nixos

    Posté par  (site web personnel) . Évalué à 6.

    Or, quel système, en pratique, fournirait /usr/bin/env et pas /bin/sh

    J'allais te dire nixos, mais en fait non ;)

    guillaume@paddle:~]$ ls -alh /bin
    total 20K
    drwxr-xr-x 1 root root   4 Apr 30 14:36 .
    drwxr-xr-x 1 root root 110 Feb 28 16:29 ..
    lrwxrwxrwx 1 root root  75 Apr 30 14:36 sh -> /nix/store/jx8g6cjv2y5rs69ym08b0pj3ngxvnhhg-bash-interactive-4.4-p23/bin/sh
    
    [guillaume@paddle:~]$ ls -alh /usr/bin
    total 4.0K
    drwxr-xr-x 1 root root  6 Apr 30 14:36 .
    drwxr-xr-x 1 root root  6 Jul  4  2018 ..
    lrwxrwxrwx 1 root root 66 Apr 30 14:36 env -> /nix/store/baylddnb83lh45v3fz15ddhbpxbdb7m7-coreutils-8.31/bin/env
    
  • # Solaris

    Posté par  . Évalué à 4.

    je pourrais très bien utiliser #!/bin/sh, et mon environnement d’exécution se débrouillera toujours pour trouver un shell compatible POSIX pour exécuter mon script.

    Rien n’est complètement garanti.

    La dernière fois que j’ai essayé openindiana, il y a quelques années seulement, il conservait encore comme /bin/sh un ksh antédiluvien et notoirement buggé, paraît‐il pour garantir la compatibilité à des logiciels qui en dépendent étroitement…

    Et ce n’est pas que ce shell fasse des choses en plus par rapport à la norme POSIX, c’est qu’il en fait moins.

    « Le fascisme c’est la gangrène, à Santiago comme à Paris. » — Renaud, Hexagone

  • # POSIX

    Posté par  . Évalué à 4.

    Ce qui veut dire que si un script tourne chez moi, il devrait tourner partout pourvu que ses dépendances soient installées.

    Tu pense aussi à positionner la variable POXILY_CORRECT par exemple ?

    Perso, je me fou de POSIX, faut juste que mes scripts soient cohérents. Si j'utilise une fonctionnalité de bash, je déclare bien bash, par exemple.

    J'aime bien zsh et il me permet d'avoir assez peu de dépendances dans mes scripts, je trouve ça cool.

    • [^] # Re: POSIX

      Posté par  . Évalué à 2.

      Non, par contre j'essaie d'utiliser les fonctionnalités POSIX des outils. Peut-être que je devrais pour être parfaitement cohérent !
      Il m'est déjà arrivé d'avoir un problème dans un de mes scripts qui fonctionnait sous GNU/Linux et pas sous macOS à cause d'une option prise en charge par l'un et pas par l'autre.

    • [^] # Re: POSIX

      Posté par  . Évalué à 1.

      J'aime bien zsh et il me permet d'avoir assez peu de dépendances dans mes scripts, je trouve ça cool.

      Ça m'intéresse, tu as des exemples ?

      • [^] # Re: POSIX

        Posté par  . Évalué à 2.

        Une partie peu aussi de faire avec d'autres, mais pêle-mêle :

        • find peut être remplacé par des globbings étendu
        • sed/cut par des substitutions
        • file possède un équivalent en zsh tu te fournis directement un dictionnaire donc pas besoin de faire du parsing
        • xargs peut-être remplacer par zargs
        • tu as des modules réseaux qui peuvent remplacer curl/wget

        Bien sûr ce ne sont pas des remplaçant parfait et identiques, mais dans bien des cas ils me conviennent voir sont plus confortables

        • [^] # Re: POSIX

          Posté par  (site web personnel) . Évalué à 6.

          Moi qui croyais que le Linuxiens ne juraient que par "une fonctionnalité, un outil, ne pas faire des couteaux suisses compliqués et on pipe tout", tout s'en va… Bientôt fera un système d'init dans le même style tout imbriqué et on l'appellera systemd.

          --> []

          • [^] # Re: POSIX

            Posté par  (site web personnel) . Évalué à 10.

            Moi qui croyais que le Linuxiens ne juraient que par "une fonctionnalité, un outil, […]

            Les unixiens, pas les linuxiens. Je commence à croire que ce sont deux mondes qui s'éloignent de plus en plus.

            Bientôt fera un système d'init dans le même style tout imbriqué et on l'appellera systemd.

            il ne faut pas s'arrêter là !
            Pourquoi se contenter du système d'init ? pourquoi ne pas y ajouter plein d'autre trucs comme les logs, le hostname, un bus système ou que sais-je encore ?

            • [^] # Re: POSIX

              Posté par  . Évalué à 0.

              Windowsd ?

            • [^] # Re: POSIX

              Posté par  . Évalué à 9.

              Pire, on pourrait intégrer à un bête éditeur de texte comme emacs tout plein de fonctionnalités comme calculatrice, client mail, lecteur de news, client IRC, lecteur audio, lecteur pdf et navigateur web. Ça serait tellement perturbant pour les utilisateurs qu'on y rajouterait un psy virtuel.

              • [^] # Re: POSIX

                Posté par  . Évalué à 7.

                On pourrait aussi ajouter un firewall à Libreoffice

                -->[]

            • [^] # Re: POSIX

              Posté par  . Évalué à 3.

              C'est une règle, il faut savoir ce qu'elle signifie pour comprendre quand s'en servir ou quand elle est moins pertinente. Parser la sortie de ls ou file peut être une gageure par exemple (et assez fragile).

        • [^] # Re: POSIX

          Posté par  . Évalué à 3.

          Une partie peu aussi de faire avec d'autres, mais pêle-mêle :
          […]
          sed/cut par des substitutions

          Bash fait aussi de la substitution… après, je connais pas les capacités de zsh en la matière, je peux donc pas te dire si c'est du même niveau.

          • [^] # Re: POSIX

            Posté par  . Évalué à 2.

            Oui oui c'est pas une spécifité de zsh, laid zsh est plus sympa. Pour enchaîner des substitutions tu n'es pas contraint de créer des variables intermédiaires.

  • # autres langages ?

    Posté par  . Évalué à 3.

    Pour ma part, quand j'ai un script un peu balaise à écrire (traitement de données via json et utilisation de libs externes bien pratiques), j'hésite souvent entre node.js et python.
    Je suis hors sujet ? 😀

    • [^] # Re: autres langages ?

      Posté par  . Évalué à 1.

      et pourquoi pas scratch !

      non je rigole

    • [^] # Re: autres langages ?

      Posté par  . Évalué à 2. Dernière modification le 03 mai 2019 à 10:51.

      Bonjour,

      C'est d'autant plus vrai avec python, perl, etc… En effet, si tu prends Python, entre Python2, Python3, virtualenv, pyenv, conda, les installs spécifiques avec un prefix différent, environment modules…. Bref, il me parait très important pour les développeurs de respecter cette règle élémentaire décrit dans cet article.
      Il m'arrive malheureusement régulièrement de faire un sed massif sur tous les scripts python pour corriger ça en vérifiant la version de python nécessaire au préalable.

      Cdlmt

    • [^] # Re: autres langages ?

      Posté par  . Évalué à 3.

      Pour le traitement des données (et c'est ce que je fais en ce moment), je me rends compte que l'idée de créer une chaîne de traitements (pipeline) avec un script shell et des pipes reliant des bouts de traitement indépendant les uns avec les autres me séduit de plus en plus.

      On a un script shell qui orchestre le traitement. On évite les fichiers intermédiaires sauf peut-être pour certains traitements lourds, et chaque brique est codée dans un langage adapté. Les briques très simples peuvent être des fonctions du shell.

      Par exemple, j'ai eu besoin de faire une transposition d'un tableau en CVS. Une réponse sur stack overflow suggère l'utilisation de Ruby, langage que je ne maîtrise pas du tout, mais ça fait exactement le job que je veux. Du coup, j'ai adapté la réponse pour que ça utilise des points virgules plutôt que des virgules et ma brique de traitement pour ça est :

      transpose_csv_semicolon() {
          ruby -rcsv -e 'puts CSV.parse(STDIN, { headers: false, col_sep: ";"}).transpose.map {|i| i.to_csv({col_sep: ";"})}'
      }
      

      Ensuite, dans ma chaîne de traitement :

      < "res.csv" select_results | make_csv | transpose_csv_semicolon | interpret_results > "formated.csv"
      

      Cette manière de traiter les données peut être (bien) plus lente qu'un traitement monolithique spécialisé, mais le script sera peu lancé dans sa vie et l'opération lourde dans mon cas est la création des données, de toute façon. Les enjeux ici sont :

      • rapide à écrire
      • facile à comprendre
      • [^] # Re: autres langages ?

        Posté par  (site web personnel) . Évalué à 3.

        Entièrement d'accord avec toi. C'est ainsi que je fait de plus en plus. Cela permet d'avoir des petites briques qu'on teste bien de manière indépendante et qu'on comprends. Si la vitesse n'est pas un critère, c'est vraiment intéressant.

        Deux cas que j'utilise, un bien et un mauvais :

        • si un script doit faire certaines commandes importante (type rm ou autre), j'ai tendance à le couper en deux. Le premier fait des écho des commandes mais ne fait rien et je l'utilise au final comme cela
         ./mon-script | bash

        C'est assez pratique pour éviter les grosses conneries !

        • les anciennes de bash n'avais pas les tableaux associatifs. Si on commence à utiliser les variables vectorisées (en gros la variable stockée dans la chaîne d'une autre variable, il est temps de passer à Perl ou Python ou de mettre à jour son bash ;-) Extrait partiel d'une de mes tentatives dont l'objectif était de gérer un cache des groupes des personnes dans un traitement afin de gagner du temps… À éviter !
         eval "export ${var_user}='${string}'" # "
         ...
         var_user="user_$(echo ${user} | tr '-' '_')"
         users="${users} ${!var_user}"
        • [^] # Re: autres langages ?

          Posté par  (site web personnel) . Évalué à 3.

          Suite à ton exemple d’utilisation d’eval, je ne résiste pas au plaisir malsain de partager ce bout de code :

          # get archive-specific value for a given variable name, or use default value
          # USAGE: use_archive_specific_value $var_name
          use_archive_specific_value() {
              [ -n "$ARCHIVE" ] || return 0
              testvar "$ARCHIVE" 'ARCHIVE' || liberror 'ARCHIVE' 'use_archive_specific_value'
              local name_real
              name_real="$1"
              local name
              name="${name_real}_${ARCHIVE#ARCHIVE_}"
              local value
              while [ "$name" != "$name_real" ]; do
                  value="$(get_value "$name")"
                  if [ -n "$value" ]; then
                      export ${name_real?}="$value"
                      return 0
                  fi
                  name="${name%_*}"
              done
          }
          
          # get package-specific value for a given variable name, or use default value
          # USAGE: use_package_specific_value $var_name
          use_package_specific_value() {
              [ -n "$PKG" ] || return 0
              testvar "$PKG" 'PKG' || liberror 'PKG' 'use_package_specific_value'
              local name_real
              name_real="$1"
              local name
              name="${name_real}_${PKG#PKG_}"
              local value
              while [ "$name" != "$name_real" ]; do
                  value="$(get_value "$name")"
                  if [ -n "$value" ]; then
                      export ${name_real?}="$value"
                      return 0
                  fi
                  name="${name%_*}"
              done
          }

          PS : Je sais que local n’est pas POSIX, je l’utilise quand même parce que dans les faits tous les interpréteurs que j’ai testé savent l’utiliser et qu’il est trop pratique pour s’en passer sans méchamment compliquer beaucoup de choses.

  • # posix

    Posté par  . Évalué à -2.

    • [^] # Re: posix

      Posté par  . Évalué à 7.

      Donc tu as une dépendance explicite à bash, autant utiliser ses fonctionnalités.

      • [^] # Re: posix

        Posté par  (site web personnel) . Évalué à 1. Dernière modification le 03 mai 2019 à 12:03.

        en même temps, shebang ne supporte pas les arguments..

        Question puisque l'on est vendredi, que tout est permis

        j'ai testé csh, ksh, bash, aucun ne râle lorsque je fais :
        $ false && truc

        ou

        #!/bin/sh
        
        test=truc
        if [ $test = bidule ]; then
                machin
        fi

        Je me rappelle que sous certaines versions de Solaris 8 c'était impossible…

        • [^] # Re: posix

          Posté par  (site web personnel) . Évalué à 4.

          en même temps, shebang ne supporte pas les arguments..

          Tu peux très bien passer un argument ce qui est la cas ici. Sinon l'astuce avec /usr/bin/env ne fonctionnerait pas !

          • [^] # Re: posix

            Posté par  (site web personnel) . Évalué à 1.

            j'ai lu cela il y a bien longtemps peut-être un peu rapidement : multiple-arguments-in-shebang

            pour passer des arguments à mes scripts Groovy, je fais un truc immonde :

            #!/bin/bash
            package extract
            
            REM=$/
            # bash stuff here
            export FOOBAR=Hello
            groovy/groovy-2.4.0-beta-3/bin/groovy -cp ./Desktop/jdbc\ driver/pvjdbc2.jar:./Desktop/jdbc\ driver/jpscs.jar "$0" $@
            exit $?
            /$
            // groovy stuff here

            normalement je ne passe jamais d'arguments pour mes scripts Groovy, car les libs se trouve en général sur Maven, mais là, c'est une lib qui n'est plus maintenu depuis 2008, je suis obligé de déclarer le classpath.

        • [^] # Re: posix

          Posté par  . Évalué à 2.

          Depuis une dizaine d'années au moins ça fonctionne. Ça permet d'utiliser env ou awk -F

          • [^] # Re: posix

            Posté par  . Évalué à 3.

            C'est un peu plus subtil, il ne supporte qu'un argument.

            • [^] # Re: posix

              Posté par  (site web personnel) . Évalué à 2.

              Depuis 2018 il supporte sous linux l'option -S comme sous FreeBSD. Mais ça fait pas 10 ans c'est vrai.

              • [^] # Re: posix

                Posté par  . Évalué à 2.

                En effet, mais utiliser l'option -S introduira une dépendance à certaines versions de env dès la première ligne. C'est dommage car la portabilité est réduite dès le tout début du fichier et empêche de mettre des conditions pour supporter différents systèmes.

                • [^] # Re: posix

                  Posté par  . Évalué à 1.

                  OSEF? Si le problème de portabilité se résume au shebang, on en parlerait pas :)

                  • [^] # Re: posix

                    Posté par  . Évalué à 2.

                    Au contraire, c'est d'autant plus grave sur la première ligne car il n'y a pas la possibilité d'adapter le comportement en fonction du système.

    • [^] # Re: posix

      Posté par  . Évalué à 1.

      Ça a l'air de se comporter comme quand on appelle bash avec le nom de programme sh chez moi.
      Entre autres, echo $EPOCHREALTIME fonctionne.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.