Garantindo a Portabilidade de Scripts Bash em Diferentes Sistemas
Escreva scripts Bash portáveis que lidam com diferenças entre GNU, BSD e BusyBox em ambientes Linux, macOS e CI.
Garantindo a Portabilidade de Scripts Bash em Diferentes Sistemas
Escrever scripts Bash que funcionam no seu laptop, em um servidor Linux e em um executor de CI é mais difícil do que parece. A portabilidade de scripts Bash geralmente quebra em pequenas diferenças: uma flag sed -i que funciona no Linux, mas falha no macOS, uma opção date que existe apenas no GNU coreutils, ou um script que assume que /bin/bash é a versão que você testou.
A dificuldade central é que o Bash é apenas parte do ambiente. O Linux normalmente vem com utilitários GNU. O macOS vem com utilitários do tipo BSD. Contêineres baseados em BusyBox podem fornecer implementações menores com menos opções. Seu script precisa ser claro sobre o que ele requer.
Este guia foca em scripts Bash, não estritamente em scripts POSIX sh. Se você precisar de verdadeira portabilidade /bin/sh, evite completamente a sintaxe exclusiva do Bash e teste com shells como dash.
Comece com um Contrato de Shell Claro
Use um shebang que corresponda à sua intenção. Se o script requer Bash, diga:
#!/usr/bin/env bash
/usr/bin/env localiza o Bash através do $PATH, o que é útil quando os usuários instalam um Bash mais novo fora do /bin. Se seus hosts de produção exigem um caminho de interpretador fixo, documente e aplique esse caminho.
O modo estrito captura muitos erros cedo, mas não é mágico:
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
Essas opções ajudam, com ressalvas:
-e: Sai quando muitos comandos simples retornam um status diferente de zero.-u: Trata variáveis não definidas como erros.pipefail: Faz um pipeline falhar se qualquer comando no pipeline falhar.
Lide com falhas esperadas explicitamente:
if ! grep -q "ready" "$log_file"; then
echo "O serviço ainda não está pronto"
fi
Conheça Sua Versão do Bash
Não dependa acidentalmente de um recurso do Bash que seus sistemas alvo não possuem. O macOS historicamente vem com um Bash mais antigo em /bin/bash, enquanto muitas distribuições Linux vêm com versões mais novas.
Recursos a serem usados com cuidado incluem:
- Arrays associativos.
- Globbing avançado como
**. - Substituição de processo como
<(command). - Comportamento mais novo de expansão de parâmetros.
Se você precisar de uma versão mínima do Bash, verifique perto do topo:
if (( BASH_VERSINFO[0] < 4 )); then
echo "Este script requer Bash 4 ou mais novo." >&2
exit 1
fi
Lidando com Diferenças entre GNU, BSD e BusyBox
Os maiores problemas de portabilidade geralmente vêm de comandos externos, não do próprio Bash.
sed -i
O GNU sed aceita -i sem uma extensão de backup. O BSD sed no macOS requer um argumento de extensão após -i, mesmo que essa extensão seja uma string vazia.
file="data.txt"
pattern="s/error/success/g"
case "$(uname -s)" in
Darwin)
sed -i '' "$pattern" "$file"
;;
*)
sed -i "$pattern" "$file"
;;
esac
Para scripts críticos, um padrão mais seguro é escrever em um arquivo temporário e depois movê-lo para o lugar. Isso evita depender do comportamento de edição in-place.
date
A matemática de datas é diferente entre os sistemas:
| Objetivo | GNU date |
BSD date no macOS |
|---|---|---|
| 30 dias atrás | date -d "30 days ago" +%Y%m%d |
date -v-30d +%Y%m%d |
Se seu script precisa de matemática de datas complexa, use uma dependência consistente como Python, ou exija GNU coreutils no macOS e chame gdate explicitamente. Não assuma silenciosamente que date -d existe.
grep, find e xargs
Use opções amplamente suportadas quando possível:
- Use
grep -Eem vez de depender deegrep. - Evite
grep -Pa menos que você verifique se o GNU grep tem suporte a PCRE. - Tenha cuidado com predicados
findque diferem entre implementações GNU e BSD. - Prefira pipelines delimitados por nulo para nomes de arquivos quando suportado:
find "$root" -type f -name '*.log' -print0 | xargs -0 rm -f
Gerencie Dependências e Caminhos
Use $PATH para a busca normal de comandos, mas verifique as ferramentas necessárias antes de fazer o trabalho:
check_dependency() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "Erro: comando necessário '$1' não encontrado." >&2
exit 1
fi
}
check_dependency jq
check_dependency curl
Prefira command -v em vez de which porque é um built-in do shell no Bash e se comporta de forma mais previsível em scripts.
Coloque variáveis entre aspas, a menos que você queira intencionalmente a divisão de palavras:
cp "$source_file" "$target_dir/"
Isso é importante para caminhos como Project Files/report.txt, e também protege contra a expansão de curingas em entradas inesperadas.
Use Arquivos Temporários com Segurança
Use mktemp para trabalho temporário. Um padrão simples e portátil é criar um diretório temporário e colocar arquivos dentro dele:
tmp_dir=$(mktemp -d)
trap 'rm -rf "$tmp_dir"' EXIT
tmp_file="$tmp_dir/output.txt"
some_command > "$tmp_file"
O trap com aspas simples mantém $tmp_dir de ser expandido até que o trap seja executado. Como a variável ainda está no escopo, a limpeza remove o diretório correto.
Cuidado com Finais de Linha e Case do Sistema de Arquivos
Scripts editados no Windows podem usar finais de linha CRLF. Um sintoma comum é:
/usr/bin/env: bash\r: No such file or directory
Configure seu editor para salvar scripts de shell com finais de linha LF, ou execute dos2unix no seu processo de build.
Lembre-se também que a maioria dos sistemas de arquivos Linux é sensível a maiúsculas/minúsculas por padrão, enquanto as configurações padrão do APFS no macOS geralmente são insensíveis a maiúsculas/minúsculas. Se seu script escreve Config.yml e depois lê config.yml, pode funcionar no seu Mac e falhar no Linux.
Teste nos Sistemas que Você Suporta
A melhor verificação de portabilidade é uma pequena matriz de teste:
- Linux com utilitários GNU.
- macOS com utilitários BSD.
- Contêineres mínimos se seu script for executado em ambientes Alpine ou BusyBox.
Execute o ShellCheck também. Ele não vai pegar todos os problemas de plataforma, mas pega muitos padrões de citação, variáveis não definidas e comandos frágeis antes que seus usuários o façam.
Conclusão
A portabilidade de scripts Bash vem de tornar suas suposições explícitas. Escolha o shell, verifique dependências, coloque variáveis entre aspas, evite flags exclusivas do GNU a menos que você as exija, e teste nos mesmos sistemas operacionais que seus usuários executam. Uma pequena matriz de CI com Linux e macOS pega a maioria dos bugs de portabilidade antes que sua automação chegue à produção.