#! / bin / sh vs #! / bin / bash para máxima portabilidade

36

Eu costumo trabalhar com servidores Ubuntu LTS que pelo que entendi link simbólico /bin/shpara /bin/dash. Um monte de outras distros embora link simbólico /bin/shpara /bin/bash.

Pelo que entendi, se um script usa #!/bin/shno topo, ele pode não ser executado da mesma maneira em todos os servidores?

Existe uma prática sugerida sobre qual shell usar para scripts quando se deseja portabilidade máxima desses scripts entre servidores?

cherouvim
fonte
Existem pequenas diferenças entre as várias conchas. Se a portabilidade é a mais importante para você, use #!/bin/she não use nada além do que o shell original forneceu.
Thorbjørn Ravn Andersen

Respostas:

65

Existem aproximadamente quatro níveis de portabilidade para scripts de shell (no que diz respeito à linha shebang):

  1. Mais portáteis: use um #!/bin/shshebang e use apenas a sintaxe básica do shell especificada no padrão POSIX . Isso deve funcionar em praticamente qualquer sistema POSIX / unix / linux. (Bem, exceto o Solaris 10 e versões anteriores que possuíam o shell Bourne legado real, anterior ao POSIX de forma não compatível, como /bin/sh.)

  2. Segundo mais portátil: use uma #!/bin/bash(ou #!/usr/bin/env bash) linha shebang e atenha-se aos recursos do bash v3. Isso funcionará em qualquer sistema que tenha o bash (no local esperado).

  3. Terceiro mais portátil: use uma #!/bin/bash(ou #!/usr/bin/env bash) linha shebang e use os recursos do bash v4. Isso falhará em qualquer sistema que tenha o bash v3 (por exemplo, macOS, que precisa usá-lo por motivos de licença).

  4. Menos portátil: use um #!/bin/shshebang e use extensões bash na sintaxe do shell POSIX. Isso falhará em qualquer sistema que tenha algo diferente de bash para / bin / sh (como as versões recentes do Ubuntu). Nunca faça isso; não é apenas um problema de compatibilidade, é simplesmente errado. Infelizmente, é um erro que muitas pessoas cometem.

Minha recomendação: use o mais conservador dos três primeiros que fornece todos os recursos de shell necessários para o script. Para máxima portabilidade, use a opção nº 1, mas, na minha experiência, alguns recursos do bash (como matrizes) são úteis o suficiente para que eu vá com a nº 2.

A pior coisa que você pode fazer é # 4, usando o shebang errado. Se você não tiver certeza de quais recursos são POSIX básicos e quais são as extensões do bash, use um shebang do bash (por exemplo, opção 2) ou teste o script completamente com um shell muito básico (como traço nos servidores Ubuntu LTS). O wiki do Ubuntu tem uma boa lista de bashismos a serem observados .

Há algumas informações muito boas sobre o histórico e as diferenças entre os shells na pergunta Unix e Linux "O que significa ser compatível com sh?" e a pergunta Stackoverflow "Diferença entre sh e bash" .

Além disso, esteja ciente de que o shell não é a única coisa que difere entre diferentes sistemas; se você está acostumado ao linux, está acostumado aos comandos GNU, que possuem muitas extensões fora do padrão que você pode não encontrar em outros sistemas unix (por exemplo, bsd, macOS). Infelizmente, não há uma regra simples aqui, você apenas precisa conhecer o intervalo de variação dos comandos que está usando.

Um dos comandos piores em termos de portabilidade é uma das mais básicas: echo. Sempre que você o usar com qualquer opção (por exemplo, echo -nou echo -e), ou com qualquer escape (barra invertida) na string a ser impressa, versões diferentes farão coisas diferentes. Sempre que desejar imprimir uma sequência sem avanço de linha ou com escapes na sequência, use-o printf(e aprenda como funciona - é mais complicado do que echoé). O pscomando também é uma bagunça .

Outra coisa geral a se observar são as extensões recentes / GNUish para comando da sintaxe da opção: formato de comando antigo (padrão) é que o comando é seguido por opções (com um único traço e cada opção com uma única letra), seguido por argumentos de comando. As variantes recentes (e geralmente não portáteis) incluem opções longas (geralmente introduzidas com --), permitindo que as opções sejam apresentadas após os argumentos e usando --para separar as opções dos argumentos.

Gordon Davisson
fonte
12
A quarta opção é simplesmente uma ideia errada. Por favor não use.
Pabouk
3
@pabouk Concordo completamente, por isso editei minha resposta para deixar isso mais claro.
Gordon Davisson
1
Sua primeira declaração é um pouco enganadora. O padrão POSIX não especifica nada sobre o shebang de fora para dizer que o uso leva a um comportamento não especificado. Além disso, o POSIX não especifica onde o shell posix deve estar localizado, apenas seu nome ( sh), portanto, /bin/shnão é garantido que seja o caminho certo. O mais portátil é, então, não especificar nenhum shebang ou adaptar o shebang ao sistema operacional usado.
Jlliagre 30/07
2
Caí na quarta posição com um script meu muito recentemente e não conseguia entender por que não estava funcionando; afinal, os mesmos comandos funcionaram solidamente e fizeram exatamente o que eu queria que eles fizessem quando os experimentei diretamente no shell. Assim que mudei #!/bin/shpara #!/bin/bash, o script funcionou perfeitamente. (Para minha defesa, que roteiro tinha evoluído ao longo do tempo a partir de um que realmente só precisava sh-ismos, para uma que contou com bash-como o comportamento.)
um CVn
2
@ MichaelKjörling O shell (verdadeiro) Bourne quase nunca é fornecido com distribuições Linux e, de qualquer maneira, não é compatível com POSIX. O shell padrão POSIX foi criado a partir do kshcomportamento, não do Bourne. O que a maioria das distribuições Linux após o FHS faz é ter /bin/shum link simbólico para o shell que elas selecionam para fornecer compatibilidade com POSIX, normalmente bashou dash.
Jlliagre
5

No ./configurescript que prepara a linguagem TXR para a construção, escrevi o seguinte prólogo para melhor portabilidade. O script será auto-inicializado, mesmo que #!/bin/shseja um Bourne Shell antigo não compatível com POSIX. (Eu construo todas as versões em uma Solaris 10 VM).

#!/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

A idéia aqui é encontrar um shell melhor do que o que estamos executando e executar novamente o script usando esse shell. A txr_shellvariável de ambiente está definida, para que o script reexecutado saiba que é a instância recursiva reexecutada.

(No meu script, a txr_shellvariável também é usada posteriormente, com exatamente dois propósitos: em primeiro lugar, é impressa como parte de uma mensagem informativa na saída do script. Em segundo lugar, é instalada como a SHELLvariável no Makefile, para makeusar isso shell também para executar receitas.)

Em um sistema onde /bin/shestá o traço, você pode ver que a lógica acima encontrará /bin/bashe reexecutará o script com isso.

Em uma caixa Solaris 10, a entrada /usr/xpg4/bin/shserá iniciada se nenhum Bash for encontrado.

O prólogo é escrito em um dialeto conservador de shell, usando testpara testes de existência de arquivo, e o ${@+"$@"}truque para expandir argumentos que atendem a alguns shells antigos quebrados (o que seria simplesmente "$@"se estivéssemos em um shell em conformidade com POSIX).

Kaz
fonte
Não seria necessário o xhackery se citações adequadas estivessem em uso, uma vez que as situações que exigiam esse idioma envolvem invocações de teste agora obsoletas -aou -ocombinação de vários testes.
Charles Duffy
@CharlesDuffy De fato; o test x$whateverque eu estou cometendo lá parece uma cebola no verniz. Se não podemos confiar no antigo shell quebrado para fazer a citação, a ${@+"$@"}tentativa final é inútil.
Kaz
2

Todas as variações da linguagem shell Bourne são objetivamente terríveis em comparação com as linguagens de script modernas, como Perl, Python, Ruby, node.js e até Tcl (sem dúvida). Se você precisar fazer algo um pouco complicado, ficará mais feliz a longo prazo se usar um dos itens acima em vez de um shell script.

A única vantagem que a linguagem shell ainda tem sobre as linguagens mais recentes é que algo que se chama /bin/shé garantido que existe em qualquer coisa que pretenda ser Unix. No entanto, esse item pode até não ser compatível com POSIX; muitos dos Unixes proprietários herdados congelaram o idioma implementado pelo /bin/she pelos utilitários no PATH padrão antes das alterações exigidas pelo Unix95 (sim, Unix95, vinte anos atrás e contando). Pode haver um conjunto de ferramentas Unix95 ou POSIX.1-2001, se você tiver sorte, em um diretório que não esteja no PATH padrão (por exemplo /usr/xpg4/bin), mas não há garantia de que elas existem.

No entanto, é mais provável que o básico do Perl esteja presente em uma instalação Unix selecionada arbitrariamente do que o Bash. (Por "princípios básicos do Perl", quero dizer, /usr/bin/perlexiste e é uma versão do Perl 5, possivelmente bastante antiga, e se você tiver sorte, o conjunto de módulos fornecidos com essa versão do interpretador também estará disponível.)

Assim sendo:

Se você está escrevendo algo que tem que funcionar em qualquer lugar que pretenda ser Unix (como um script "configure"), você precisa usá #! /bin/sh-lo e não usar nenhuma extensão. Atualmente, eu escreveria um shell compatível com POSIX.1-2001 nessa circunstância, mas estaria preparado para corrigir os POSIXismos se alguém pedisse suporte para ferro enferrujado.

Mas se você não estiver escrevendo algo que funcione em qualquer lugar, no momento em que for tentado a usar qualquer bashismo, pare e reescreva tudo em uma linguagem de script melhor. Seu futuro eu agradecerá.

(Então, quando é apropriado usar extensões Bash? Na primeira ordem: nunca. Na segunda ordem: apenas para estender o ambiente interativo da Bash - por exemplo, para fornecer guias inteligentes e solicitações sofisticadas).

zwol
fonte
Outro dia, tive que escrever um roteiro que fizesse algo assim find ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -. Não seria possível fazê-lo funcionar corretamente sem bashisms ( read -d "") e extensões GNU ( find -print0, tar --null). Reimplementar essa linha no Perl seria muito mais longo e desajeitado. Esse é o tipo de situação em que usar bashisms e outras extensões não POSIX é a coisa certa a fazer.
michau 5/07
@michau Pode não se qualificar mais como uma linha, dependendo do que se passa nessas elipses, mas acho que você não está aceitando a "reescrita de tudo em uma linguagem de script melhor" tão literal quanto eu pretendia. Meu jeito é algo como perl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmdObserve que não apenas você não precisa de basismos, como também não precisa de extensões tar do GNU. (Se o comprimento da linha de comando for uma preocupação ou você desejar que a verificação e o tar sejam executados em paralelo, será necessário tar --null -T).
zwol 05/07
O problema é que findretornou mais de 50.000 nomes de arquivos no meu cenário, portanto é provável que seu script atinja o limite de tamanho do argumento. Uma implementação correta que não use extensões não-POSIX teria que criar a saída do tar incrementalmente, ou talvez usar Archive :: Tar :: Stream no código Perl. Obviamente, isso pode ser feito, mas, neste caso, o script Bash é muito mais rápido de escrever e tem menos clichê e, portanto, menos espaço para erros.
michau 6/07
BTW, eu poderia usar Perl em vez de Bash de uma forma rápida e concisa: find ... -print0 |perl -0ne '... print ...' |tar c --null -T -. Mas as perguntas são: 1) Estou usando find -print0e de tar --nullqualquer maneira, então qual é o objetivo de evitar o basismo read -d "", que é exatamente o mesmo tipo de coisa (uma extensão GNU para manipular separador nulo que, diferentemente do Perl, pode se tornar algo POSIX algum dia )? 2) Perl é realmente mais difundido que Bash? Eu tenho usado imagens do Docker recentemente, e muitas delas têm Bash, mas não Perl. É provável que novos scripts sejam executados lá, do que nos antigos Unices.
michau 6/07