web-dev-qa-db-fra.com

#! / bin / sh vs #! / bin / bash pour une portabilité maximale

Je travaille généralement avec des serveurs Ubuntu LTS qui d'après ce que je comprends symlink /bin/sh à /bin/dash. Beaucoup d'autres distributions via symlink /bin/sh à /bin/bash.

De cela, je comprends que si un script utilise #!/bin/sh en plus il peut ne pas fonctionner de la même manière sur tous les serveurs?

Existe-t-il une pratique suggérée sur le shell à utiliser pour les scripts lorsque l'on souhaite une portabilité maximale de ces scripts entre les serveurs?

37
cherouvim

Il existe environ quatre niveaux de portabilité pour les scripts Shell (en ce qui concerne la ligne Shebang):

  1. Le plus portable: utilisez un #!/bin/sh Shebang et utilisez uniquement la syntaxe de base du Shell spécifiée dans le standard POSIX . Cela devrait fonctionner sur à peu près n'importe quel système POSIX/unix/linux. (Eh bien, sauf Solaris 10 et les versions antérieures qui avaient le véritable héritage Bourne Shell, antérieurs à POSIX si non conformes, comme /bin/sh.)

  2. 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).

  3. Troisième plus portable: utilisez un #!/bin/bash (ou #!/usr/bin/env bash) Shebang line, et utilisez les fonctionnalités de bash v4. Cela échouera sur tout système disposant de bash v3 (par exemple, macOS, qui doit l'utiliser pour des raisons de licence).

  4. Le moins portable: utilisez un #!/bin/sh Shebang et utilisez les extensions bash de 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 fais jamais ça; ce n'est pas seulement un problème de compatibilité, c'est tout simplement faux. Malheureusement, c'est une erreur que beaucoup de gens font.

Ma recommandation: utilisez la plus conservatrice des trois premières qui fournit toutes les fonctionnalités 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 j'aille avec la # 2.

La pire chose que vous puissiez faire est # 4, en utilisant le mauvais Shebang. Si vous ne savez pas quelles sont les fonctionnalités POSIX de base et quelles sont les extensions bash, restez avec un Shebang bash (c'est-à-dire l'option # 2), ou testez le script à fond avec un Shell très basique (comme un tiret sur vos serveurs Ubuntu LTS). Le wiki Ubuntu a un bonne liste de bashismes à surveiller .

Il y a de très bonnes informations sur l'historique et les différences entre les shells dans la question Unix et Linux "Que signifie ê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 ont beaucoup d'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 méchantes en termes de portabilité est l'une des plus élémentaires: 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 elle, ou avec des échappements dans la chaîne, utilisez plutôt printf (et découvrez comment cela fonctionne - c'est plus compliqué que echo). La commande ps est aussi un gâchis .

Une autre chose générale à surveiller est les extensions récentes/GNUish 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), suivie 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.

66
Gordon Davisson

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 sera bootstrap lui-même même si #!/bin/sh est un ancien Bourne Shell non conforme à POSIX. (Je construis chaque version sur une VM 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 ${@+"$@"}
fi

# rest of the script here, executing in upgraded Shell

L'idée ici est que nous trouvons un meilleur shell que celui sous lequel nous fonctionnons et que nous réexécutons le script à l'aide de 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, pour exactement deux raisons: 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 variable Shell dans Makefile, de sorte que make utilisera également ce shell pour exécuter des recettes.)

Sur un système où /bin/sh est un tiret, vous pouvez voir que la logique ci-dessus trouvera /bin/bash et réexécutez le script avec cela.

Sur une boîte Solaris 10, le /usr/xpg4/bin/sh démarrera si aucun Bash n'est trouvé.

Le prologue est écrit dans un dialecte Shell conservateur, utilisant test pour les tests d'existence de fichier, et le ${@+"$@"} astuce pour étendre les arguments en fonction de certains vieux obus cassés (ce qui serait simplement "$@" si nous étions dans un Shell conforme POSIX).

5
Kaz

Toutes les variantes du langage Bourne Shell 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 quelque chose, même 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 pour exister sur tout ce qui prétend être Unix. Cependant, ce quelque chose peut même ne pas être conforme à POSIX; la plupart des Unixes propriétaires hérités ont gelé le langage implémenté par /bin/sh et les utilitaires du CHEMIN par défaut antérieurs aux changements demandés par Unix95 (oui, Unix95, il y a vingt ans et plus). Il peut y avoir un ensemble d'Unix95, ou même POSIX.1-2001 si vous êtes chanceux, des outils dans un répertoire pas sur le CHEMIN par défaut (par exemple /usr/xpg4/bin) mais leur existence n'est pas garantie.

Cependant, les bases de Perl sont plus susceptibles d'être présentes sur une installation Unix choisie arbitrairement que Bash. (Par "les bases de Perl", je veux dire /usr/bin/Perl existe et est une version , peut-être assez ancienne, de Perl 5, et si vous êtes chanceux, l'ensemble des modules fournis avec cette version de l'interprète est également disponible.)

Donc:

Si vous écrivez quelque chose qui doit fonctionner 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 Shell conforme à POSIX.1-2001 dans cette circonstance, mais je serais prêt à corriger les POSIXismes si quelqu'un demandait de l'aide pour le fer rouillé.

Mais si vous n'écrivez pas quelque chose qui doit fonctionner partout, alors au moment où vous êtes tenté d'utiliser n'importe quel bashisme, vous devez vous arrêter et réécrire le chose entière dans un meilleur langage de script à la place. Votre futur moi vous remerciera.

(Donc quand est approprié d'utiliser des extensions Bash? Au premier ordre: jamais. Au deuxième ordre: seulement pour étendre l'environnement interactif Bash - par exemple pour fournir fin de tabulation intelligente et invites fantaisistes.)

2
zwol