Erro absoluto do FreeBSD

Aug 22 2020

Eu gostaria de colocar shebang #!/bin/sh -eufo pipefailno meu script. Mas há várias coisas estranhas:

  1. O script iria falhar com aquela coisa no FreeBSD, mas não quando executado no MacOS
  2. no FreeBSd, a mesma coisa funciona quando executada diretamente da linha de comando (também /bin/sh).
>>> sh -eufo pipefail -c 'echo hi'  # this works
hi

>>> cat <<EOF > script                                                                                      
#!/bin/sh -eufo pipefail
echo hi
EOF

>>> chmod +x ./script 
>>> ./script  # this doesn't work on FreeBSD but works on MacOS
Illegal option -o ./script

>>> cat ./script 
#!/bin/sh -eufo pipefail
echo hi

>>> uname -a
FreeBS 11.3-RELEASE-p7

Respostas

1 JdeBP Aug 22 2020 at 20:11

O MacOS ainda mantém o antigo comportamento do FreeBSD anterior a 2005. Em 2005, houve uma grande mudança na maneira como o kernel do FreeBSD era tratado #!no início de um arquivo executável passado execve(), para torná-lo mais alinhado com alguns outros kernels do sistema operacional, incluindo Linux e o kernel NetBSD.

O comentário no código-fonte do kernel do NetBSD tenta pintar isso como um universal:

* coletar o argumento shell. tudo depois do nome do shell
 * é passado como UM argumento; esse é o correto (histórico)
 * comportamento.
- kern/exec-script.c. NetBSD. linhas 189 e segs ..

Na verdade não é. Sven Mascheck fez alguns testes cerca de uma década atrás e existem quatro comportamentos básicos, o AT&T Unix System 5 tendo tanto a pretensão de ser um comportamento "histórico correto" quanto o 4.2BSD tem:

  • Ignore os caracteres (antes de 4.2BSD e AT&T Unix System 5).
  • Passe a string inteira em um único argumento (4.2BSD, NetBSD, Linux e FreeBSD de 2005 em diante).
  • Divida a string por espaços em branco e passe-a como vários argumentos (FreeBSD antes de 2005 e MacOS).
  • Divida a string por espaços em branco e passe apenas o primeiro argumento (AT&T Unix System 5 e Solaris)

Incluí apenas os sistemas operacionais relevantes para esta resposta entre parênteses. M. Mascheck verificou muito mais, assim como Ahmon Dancy na discussão do Relatório de Problemas do FreeBSD 16393. Veja a leitura adicional para as listas completas.

O que levou as coisas à tona no FreeBSD em 2005 foi que, ironicamente, o FreeBSD não era tão simples assim. Foi introduzida uma mudança com o objetivo de fazer as coisas escritas em livros populares sobre Perl realmente funcionarem: os argumentos eram ignorados após um caractere de comentário. Os livros recomendavam coisas como:

#! / bin / sh - # - * - perl - * -
- Larry Wall, Tom Christiansen, Jon Orwant (2000). Perl de programação: 3ª edição . O'Reilly Media. ISBN 9780596000271. p. 488.

PR 16393 em 2000 foi uma maneira de fazer o kernel lidar com scripts Perl executáveis, escritos da maneira que Larry Wall não menos disse que funcionaria. No entanto, quebrou outras coisas e não funcionou completamente.

Houve algumas idas e vindas sobre isso. Finalmente, em 2005, o mecanismo para fazer a ideia de Larry Wall et al. Funcionar foi retirado do kernel, que foi feito para se comportar de forma compatível com Linux, NetBSD e 4.2BSD (em vez de Solaris e AT&T Unix System 5) e feito a responsabilidade de sh.

O comportamento desde 2005 tem sido que o shell recebe três argumentos, o segundo argumento sendo a cauda inteira da #!linha, e invocar seu script diretamente com execve()é efetivamente o mesmo que invocar:

sh '-eufo pipefail' ./script

Deve ser bastante óbvio porque o shell Almquist (que é o que shestá no FreeBSD) está pensando que ./scripté o argumento de opção para a -oopção, e que está tratando a pipefailparte como outras opções de uma única letra coletadas atrás -(que não tem em torno de processamento ainda).

Uma alternativa também óbvia é ter set -o pipefailcomo primeiro comando no script, conforme apontado emhttps://unix.stackexchange.com/a/533418/5132para o shell Bourne Again . No entanto, ele só foi adicionado ao shell do Almquist do FreeBSD em 2019 e, portanto, só está disponível em versões muito recentes do FreeBSD. (O shell Debian Almquist ainda não foi adicionado, até 2020.)

Leitura adicional

schily Aug 23 2020 at 13:53

Mesmo que seu #!uso não seja portátil porque usa mais do que o simples:

#!/bin/sh

ou o único argumento comumente suportado como em:

#!/bin/sh -oneflag

O verdadeiro problema é que você está usando uma opção não portátil -o pipefail.

Esta é uma opção ksh93e bashnão é suportada por outros shells.

Fundo:

  • No MacOS, /bin/shé bashque foi compilado de uma maneira específica (por exemplo, para fazer as sequências de escape echofuncionarem por padrão) para torná-lo compatível com POSIX. Como o bash é compatível pipefail(veja acima), isso funciona no MacOS.

  • No FreeBSD, /bin/shé ashque não suporta pipefail.

Você tem dois caminhos possíveis:

  • Não use set -o pipefail

  • Espere dois anos e tente novamente. Isso provavelmente funcionará, porque decidimos adicionar esta opção ao próximo padrão POSIX (Edição 8) há 10 meses, consultehttps://www.austingroupbugs.net/view.php?id=789e como o próximo padrão POSIX será publicado em aprox. um ano, há uma grande chance de que o FreeBSD irá adicionar suporte para set -o pipefaila ashbreve.