Solution 1 :
Il existe à peu près quatre niveaux de portabilité pour les scripts shell (en ce qui concerne la ligne shebang) :
-
Le plus portable :utilisez un
#!/bin/sh
shebang et utilisez uniquement la syntaxe de shell de base spécifiée dans la norme POSIX. Cela devrait fonctionner sur à peu près n'importe quel système POSIX/unix/linux. (Eh bien, sauf Solaris 10 et versions antérieures qui avaient le véritable shell Bourne hérité, antérieur à POSIX donc non conforme, comme/bin/sh
.) -
Deuxième plus portable :utilisez un
#!/bin/bash
(ou#!/usr/bin/env bash
) ligne shebang et respectez les fonctionnalités de bash v3. Cela fonctionnera sur n'importe quel système qui a bash (à l'emplacement prévu). -
Troisième plus portable :utilisez un
#!/bin/bash
(ou#!/usr/bin/env bash
) ligne shebang et utilisez les fonctionnalités bash v4. Cela échouera sur tout système doté de bash v3 (par exemple, macOS, qui doit l'utiliser pour des raisons de licence). -
Le moins portable :utilisez un
#!/bin/sh
shebang et utilisez les extensions bash pour la syntaxe du shell POSIX. Cela échouera sur tout système qui a autre chose que bash pour /bin/sh (comme les versions récentes d'Ubuntu). Ne faites jamais cela; ce n'est pas seulement un problème de compatibilité, c'est tout simplement faux. Malheureusement, c'est une erreur que beaucoup de gens commettent.
Ma recommandation :utilisez la plus conservatrice des trois premières qui fournit toutes les fonctionnalités du shell dont vous avez besoin pour le script. Pour une portabilité maximale, utilisez l'option 1, mais d'après mon expérience, certaines fonctionnalités de bash (comme les tableaux) sont suffisamment utiles pour que je opte pour la 2.
La pire chose que vous puissiez faire est # 4, utiliser le mauvais shebang. Si vous ne savez pas quelles fonctionnalités sont de base POSIX et lesquelles sont des extensions bash, restez avec un bash shebang (c'est-à-dire l'option n ° 2) ou testez soigneusement le script avec un shell très basique (comme dash sur vos serveurs Ubuntu LTS). Le wiki Ubuntu a une bonne liste de bashismes à surveiller.
Il y a de très bonnes informations sur l'histoire et les différences entre les shells dans la question Unix et Linux "Qu'est-ce que cela signifie d'être compatible sh?" et la question Stackoverflow "Différence entre sh et bash".
Sachez également que le shell n'est pas la seule chose qui diffère entre les différents systèmes; si vous êtes habitué à Linux, vous êtes habitué aux commandes GNU, qui contiennent de nombreuses extensions non standard que vous ne trouverez peut-être pas sur d'autres systèmes Unix (par exemple, bsd, macOS). Malheureusement, il n'y a pas de règle simple ici, il vous suffit de connaître la plage de variation des commandes que vous utilisez.
L'une des commandes les plus désagréables en termes de portabilité est l'une des plus basiques :echo
. Chaque fois que vous l'utilisez avec des options (par exemple, echo -n
ou echo -e
), ou avec des échappements (barres obliques inverses) dans la chaîne à imprimer, différentes versions feront des choses différentes. Chaque fois que vous souhaitez imprimer une chaîne sans saut de ligne après, ou avec des échappements dans la chaîne, utilisez printf
à la place (et apprenez comment cela fonctionne -- c'est plus compliqué que echo
est). Le ps
commande est aussi un gâchis.
Une autre chose générale à surveiller est les extensions récentes/GNU de la syntaxe des options de commande :l'ancien format de commande (standard) est que la commande est suivie d'options (avec un seul tiret, et chaque option est une seule lettre), suivies de arguments de commande. Les variantes récentes (et souvent non portables) incluent des options longues (généralement introduites avec --
), permettant aux options de venir après les arguments et utilisant --
pour séparer les options des arguments.
Solution 2 :
Dans le ./configure
script qui prépare le langage TXR pour la construction, j'ai écrit le prologue suivant pour une meilleure portabilité. Le script s'amorcera même si #!/bin/sh
est un ancien Bourne Shell non conforme à POSIX. (Je construis chaque version sur une machine virtuelle Solaris 10).
#!/bin/sh
# use your own variable name instead of txr_shell;
# adjust to taste: search for your favorite shells
if test x$txr_shell = x ; then
for shell in /bin/bash /usr/bin/bash /usr/xpg4/bin/sh ; do
if test -x $shell ; then
txr_shell=$shell
break
fi
done
if test x$txr_shell = x ; then
echo "No known POSIX shell found: falling back on /bin/sh, which may not work"
txr_shell=/bin/sh
fi
export txr_shell
exec $txr_shell $0 ${@+"[email protected]"}
fi
# rest of the script here, executing in upgraded shell
L'idée ici est que nous trouvions un meilleur shell que celui sous lequel nous exécutons et réexécutons le script en utilisant ce shell. Le txr_shell
la variable d'environnement est définie, de sorte que le script réexécuté sache qu'il s'agit de l'instance récursive réexécutée.
(Dans mon script, le txr_shell
La variable est également utilisée par la suite, dans exactement deux buts :premièrement, elle est imprimée dans le cadre d'un message informatif dans la sortie du script. Deuxièmement, il est installé en tant que SHELL
variable dans le Makefile
, de sorte que make
utilisera également ce shell pour exécuter des recettes.)
Sur un système où /bin/sh
est tiret, vous pouvez voir que la logique ci-dessus trouvera /bin/bash
et réexécutez le script avec ça.
Sur un boîtier Solaris 10, le /usr/xpg4/bin/sh
se déclenchera si aucun Bash n'est trouvé.
Le prologue est écrit dans un dialecte shell conservateur, en utilisant test
pour les tests d'existence de fichier, et le ${@+"[email protected]"}
astuce pour développer des arguments répondant à certains vieux shells cassés (qui seraient simplement "[email protected]"
si nous étions dans un shell conforme POSIX).
Solution 3 :
Toutes les variantes du langage shell Bourne sont objectivement terribles par rapport aux langages de script modernes comme Perl, Python, Ruby, node.js et même (sans doute) Tcl. Si vous devez faire quoi que ce soit d'un peu compliqué, vous serez plus heureux à long terme si vous utilisez l'un des éléments ci-dessus au lieu d'un script shell.
Le seul et unique avantage que le langage shell a encore sur ces nouveaux langages est que quelque chose s'appelant /bin/sh
est garanti d'exister sur tout ce qui prétend être Unix. Cependant, ce quelque chose peut même ne pas être conforme à POSIX; de nombreux Unix propriétaires hérités ont gelé le langage implémenté par /bin/sh
et les utilitaires dans le PATH par défaut prior aux changements exigés par Unix95 (oui, Unix95, il y a vingt ans et ça continue). Il peut y avoir un ensemble d'outils Unix95, ou même POSIX.1-2001 si vous avez de la chance, dans un répertoire pas sur le PATH par défaut (par exemple /usr/xpg4/bin
) mais leur existence n'est pas garantie.
Cependant, les bases de Perl sont plus probables être présent sur une installation Unix arbitrairement sélectionnée que Bash ne l'est. (Par "les bases de Perl", je veux dire /usr/bin/perl
existe et est certain , peut-être assez ancienne, version de Perl 5, et si vous avez de la chance, l'ensemble des modules livrés avec cette version de l'interpréteur sont également disponibles.)
Par conséquent :
Si vous écrivez quelque chose qui doit marcher partout qui prétend être Unix (comme un script "configure"), vous devez utiliser #! /bin/sh
, et vous ne devez utiliser aucune extension. De nos jours, j'écrirais un shell conforme à POSIX.1-2001 dans cette circonstance, mais je serais prêt à corriger les POSIXismes si quelqu'un demandait un support pour le fer rouillé.
Mais si vous n'êtes pas écrire quelque chose qui doit fonctionner partout, alors au moment où vous êtes tenté d'utiliser n'importe quel bashisme, vous devriez vous arrêter et réécrire le tout dans un meilleur langage de script à la place. Votre futur moi vous remerciera.
(Alors quand est convient-il d'utiliser les extensions Bash ? Au premier ordre :jamais. Au deuxième ordre :uniquement pour étendre l'environnement interactif Bash - par ex. pour fournir une saisie semi-automatique intelligente et des invites sophistiquées.)