Je travaille habituellement avec des serveurs Ubuntu LTS qui , de ce que je comprends symlink /bin/sh
à /bin/dash
. Beaucoup d'autres distributions que symlink /bin/sh
to /bin/bash
.
À partir de là, je comprends que si un script utilise #!/bin/sh
par dessus, il ne fonctionnera peut-être pas 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 veut une portabilité maximale de ces scripts entre les serveurs?
#!/bin/sh
et n'utilisez rien d'autre que ce que le shell d'origine a fourni.Réponses:
Il existe environ quatre niveaux de portabilité pour les scripts shell (en ce qui concerne la ligne shebang):
Plus portable: utilisez un
#!/bin/sh
shebang et utilisez uniquement la syntaxe de base du shell spécifiée dans la norme POSIX . Cela devrait fonctionner à peu près sur n'importe quel système POSIX / unix / linux. (Eh bien, à l'exception de Solaris 10 et des versions antérieures, qui contenaient le véritable shell Bourne, précédant POSIX si non conforme, comme/bin/sh
.)Deuxième plus portable: utilisez une ligne
#!/bin/bash
(ou#!/usr/bin/env bash
) shebang et respectez les fonctionnalités de bash v3. Cela fonctionnera sur tous les systèmes dotés de bash (à l'emplacement prévu).Troisième plus portable: utilisez une ligne
#!/bin/bash
(ou#!/usr/bin/env bash
) shebang, et utilisez les fonctionnalités de bash v4. Cela échouera sur tous les systèmes dotés de bash v3 (par exemple, macOS, qui doit l’utiliser pour des raisons de licence).Le moins portable: utilisez un
#!/bin/sh
shebang et des extensions bash à la syntaxe du shell POSIX. Cela échouera sur tout système ayant autre chose que bash pour / bin / sh (comme les versions récentes d'Ubuntu). Ne fais jamais ça; ce n'est pas simplement un problème de compatibilité, c'est tout simplement faux. Malheureusement, c'est une erreur commise par beaucoup de gens.Ma recommandation: utilisez le plus prudent des trois premiers qui fournit toutes les fonctionnalités du shell dont vous avez besoin pour le script. Pour une portabilité maximale, utilisez l'option n ° 1, mais d'après mon expérience, certaines fonctionnalités de bash (telles que les tableaux) sont suffisamment utiles pour que je passe à la n ° 2.
La pire chose que vous puissiez faire est la n ° 4, en utilisant le mauvais shebang. Si vous n'êtes pas sûr des fonctionnalités POSIX de base et des extensions bash, vous pouvez soit vous en tenir à un bash shebang (option n ° 2), soit tester le script de manière approfondie 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 quelques très bonnes informations sur l'histoire et les différences entre les shells dans la question Unix et Linux "Qu'est-ce que ça veut dire ê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 d’un système à l’autre; si vous êtes habitué à Linux, vous êtes habitué aux commandes GNU, qui ont beaucoup d'extensions non standard que vous pourriez ne pas trouver 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' un des plus élémentaires:
echo
. Chaque fois que vous l'utilisez avec des options (par exemple,echo -n
ouecho -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 celle-ci, ou avec des échappements dans la chaîne, utilisez-la à laprintf
place (et découvrez comment cela fonctionne - c'est plus compliqué que ceecho
n'est). Laps
commande est aussi un désordre .Les extensions récentes / GNUish de la syntaxe des options de commande constituent un autre point à surveiller: le format de commande ancien (standard) indique que la commande est suivie d'options (avec un tiret unique, chaque option étant une 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 suivre les arguments et--
permettant de séparer les options des arguments.la source
sh
), il/bin/sh
n’est donc pas garanti que ce soit le bon chemin. Le plus portable est alors de ne spécifier aucun shebang, ou d'adapter le shebang au système d'exploitation utilisé.#!/bin/sh
à#!/bin/bash
bien, le script a parfaitement fonctionné. (Pour ma défense, ce script a évolué avec le temps, passant de celui qui nécessitait uniquement un sh-isme à un comportement qui s'appuyait sur un comportement semblable à celui de bash.)ksh
comportement, pas de Bourne. La plupart des distributions Linux suivant la FHS ont/bin/sh
un lien symbolique avec le shell qu'elles sélectionnent pour assurer la compatibilité POSIX, généralementbash
oudash
.Dans le
./configure
script qui prépare le langage TXR à la construction, j’ai écrit le prologue suivant pour une meilleure portabilité. Le script s’amorce même s’il#!/bin/sh
est un ancien Bourne Shell non conforme à POSIX. (Je construis chaque version sur une machine virtuelle Solaris 10).L'idée ici est que nous trouvions un meilleur shell que celui sous lequel nous fonctionnons et que nous ré-exécutions le script à l'aide de ce shell. La
txr_shell
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, la
txr_shell
variable est également utilisé par la suite, pour exactement deux buts: d'une part , il est imprimé dans le cadre d'un message d' information dans la sortie du script En second lieu , il est installé comme.SHELL
Variable dans leMakefile
, de sorte quemake
utilisera cette shell aussi 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écutera le script avec cela.Sur une boîte Solaris 10, le
/usr/xpg4/bin/sh
sera activé si aucun Bash n’est trouvé.Le prologue est écrit dans un dialecte de shell conservateur, utilisant
test
pour les tests d’existence de fichier et le${@+"$@"}
truc pour développer les arguments s’adressant à de vieux shell cassés (ce qui serait tout simplement le"$@"
cas si nous étions dans un shell conforme à POSIX).la source
x
piratage informatique si on utilisait des citations appropriées, car les situations qui l' exigeaient englobaient des invocations de test maintenant obsolètes,-a
ou-o
combinant plusieurs tests.test x$whatever
que je suis en train de commettre ressemble à un oignon dans le vernis. Si nous ne pouvons pas faire confiance à l'ancien shell cassé pour citer, alors la dernière${@+"$@"}
tentative est vaine.Toutes les variantes du langage Bourne shell sont objectivement terribles par rapport aux langages de script modernes tels que 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 du langage shell sur ces nouveaux langages réside dans le fait qu’il est garanti que quelque chose qui s’appelle lui
/bin/sh
- même existe sur tout ce qui est censé être Unix. Cependant, il se peut que quelque chose ne soit même pas conforme à POSIX; la plupart des Unixes propriétaires hérités ont gelé le langage mis en œuvre par/bin/sh
et les utilitaires dans le PATH par défaut avant les modifications demandées par Unix95 (oui, Unix95, il y a vingt ans). Il peut y avoir un ensemble d'Unix95, ou même de POSIX.1-2001 si vous êtes chanceux, des outils dans un répertoire ne se trouvant pas sur le chemin PATH 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 avez de la chance, les modules fournis avec cette version de l'interpréteur sont également disponibles.)Par conséquent:
Si vous écrivez quelque chose qui doit fonctionner partout et qui est censé ê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 ce cas, mais je serais prêt à corriger les erreurs POSIXisms 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 serez tenté d'utiliser un quelconque Bashisme, vous devriez arrêter et réécrire le tout dans un meilleur langage de script. Votre futur moi vous en sera reconnaissant.
(Alors, quand est- il approprié d'utiliser des extensions Bash? Au premier ordre: jamais. Au second ordre: uniquement pour étendre l'environnement interactif Bash - par exemple pour fournir des commandes intelligentes pour compléter les onglets et fantaisie.)
la source
find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -
. Il ne serait pas possible de le faire fonctionner correctement sans les bashismes (read -d ""
) et les extensions GNU (find -print0
,tar --null
). Réimplémenter cette ligne dans Perl serait beaucoup plus long et plus maladroit. C'est le genre de situation lorsque vous utilisez des bashismes et d'autres extensions non-POSIX, c'est la bonne chose à faire.perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmd
style ressemble à quelque chose comme: Notez que non seulement vous n’avez besoin d’aucun basisme, vous n’avez besoin d’aucune extension GNU tar. (Si la longueur de la ligne de commande vous préoccupe ou si vous souhaitez que l'analyse et lestar --null -T
find
plus de 50 000 noms de fichiers ont été renvoyés dans mon scénario. Votre script atteindra donc probablement la limite de longueur d'argument. Une implémentation correcte qui n'utilise pas d'extensions non-POSIX devrait générer la sortie tar de manière incrémentielle, ou peut-être utiliser Archive :: Tar :: Stream dans le code Perl. Bien sûr, cela peut être fait, mais dans ce cas, le script Bash est beaucoup plus rapide à écrire et a moins de passe-partout, et donc moins de place pour les bugs.find ... -print0 |perl -0ne '... print ...' |tar c --null -T -
. Mais les questions qui se posent sont les suivantes: 1) j’utilisefind -print0
et detar --null
toute façon, alors quel est le point d’éviter le bashismeread -d ""
, qui est exactement le même genre de chose (une extension GNU pour gérer le séparateur nul qui, contrairement à Perl, peut devenir un jour POSIX )? 2) Perl est-il vraiment plus répandu que Bash? J'ai utilisé des images Docker récemment, et beaucoup d'entre elles ont Bash mais pas de Perl. Il est plus probable que de nouveaux scripts soient exécutés ici que sur les anciens Unices.