ShellCheck: ferramenta de análise que encontra bugs em scripts shell
(shellcheck.net)- ShellCheck é uma ferramenta que permite verificar imediatamente na web erros comuns em scripts shell, mostrando os problemas do código colado na saída abaixo do editor
- Em ambiente local, pode ser instalada com
cabal,apt,dnf,pkg,brew installe outros, permitindo uso baseado em gerenciadores de pacotes - Os exemplos mostram pontos frequentemente ignorados em scripts reais, como avisos de portabilidade com shebang
sh, problemas semânticos e erros no uso de aspas - É software livre sob licença GPLv3 e também está disponível no GitHub, na Wiki e como linter integrado para os principais editores
- Pode ser usado no CodeClimate, Codacy e CodeFactor para verificação automática de repositórios GitHub, e o próprio ShellCheck é escrito em Haskell
Recursos de análise de scripts shell
- O ShellCheck é uma ferramenta de análise que encontra bugs em scripts shell
- Ao colar um script no site, é possível ver imediatamente os resultados da verificação na janela de saída abaixo do editor
- Os exemplos incluídos mostram que ele consegue detectar vários tipos de problemas
- diversos problemas escondidos em scripts comuns
- avisos de portabilidade quando o shebang é
sh - problemas semânticos de nível mais alto
- vários problemas no tratamento de aspas
Instalação, licença e integrações
- A instalação local pode ser feita com
cabal,apt,dnf,pkg,brew install - É software livre sob licença GPLv3
- A documentação pode ser consultada na ShellCheck Wiki
- Está disponível publicamente no GitHub, e o código do site também é aberto
- Já existem pacotes disponíveis para distribuições e gerenciadores de pacotes
- Pode ser usado como linter integrado nos principais editores
- Pode ser utilizado no CodeClimate, Codacy e CodeFactor para verificação automática de repositórios GitHub
- O ShellCheck é escrito em Haskell
1 comentários
Comentários do Hacker News
A dica que eu uso é a seguinte. Quase sempre coloco
-u(nounset) no shebang para transformar variáveis não declaradas em erro; uma exceção comum é quando a sintaxe"${arr[@]}"expande um array vazio e isso é tratado como unbound-n(noexec) impede a execução de comandos, então pode ser usado como uma espécie de dry-run dos pobres.-e(errexit) também é útil, mas é preciso tomar cuidado porque, na prática, só um comando “ele mesmo” que falha causa a saída; pessoalmente, prefiro evitar e costumo colocar|| fail "..."depois dos comandos"${arr[@]}"existe apenas no bash 3 ou anterior; a partir do bash 4, mesmo quando a variável realmente não está definida,[@]não gera unbound variableMesmo assim, isso continua sendo um problema porque o macOS ainda instala bash v3 por padrão e não atualiza automaticamente. É realmente absurdo que a última release do bash 3 tenha sido há 20 anos. O unbound na expansão de array vazio pode ser contornado com a expansão
${var+alter}:echo "${arr+${arr[@]}}"bash my_script.shem vez de./my_script.sh, as opções não serão aplicadasMuita gente faz isso para não precisar definir o bit de execução, e às vezes também por falta de entendimento. Por isso é comum recomendarem usar
set, comoset -euo pipefail, na primeira linha depois do shebang; isso também ajuda em casos como#!/usr/bin/env bash, em que o shebang lida mal com argumentos adicionais-uno shebang. Por que no shebang em vez deset -u?No Bash,
"${arr[@]}"parece funcionar bem. Como também foi mencionado em outro comentário, isso melhorou no Bash mais recente e parece ser problema só no<= 4.3: https://news.ycombinator.com/item?id=38397241Por exemplo,
bash -uc 'unset x; echo "=> ${x[@]}"'ebash -uc 'x=(); echo "=> ${x[@]}"'passam com valor vazio, masbash -uc 'x=(); echo "=> ${x[0]}"'gerabash: x[0]: unbound variable. O Zsh não gosta do primeiro exemplo, mas ambos deveriam suportar expansão com valor padrão, como embash -uc 'unset x; echo "=> ${x[@]:-null}"'.-efica muito confuso quando se mistura com funções, e com o tempo passei a gostar cada vez menos dele-epode ser tratado bem com-o pipefail, e isso foi incluído no POSIX desde o ano passadotrapquando havia arquivos para limpartrapé um excelente recurso de scripting, mas dá a sensação de que não se fala o suficiente sobre issoRecentemente encontrei uma vulnerabilidade de elevação de privilégio em shell script por causa de expansão aritmética. É de um tipo parecido com o descrito em https://research.nccgroup.com/2020/05/12/shell-arithmetic-ex...
Por exemplo, em
$((1 + ENV_VAR)), se você conseguir controlar$ENV_VAR, dá para injetar código. Infelizmente, o ShellCheck não detectou isso, pelo menos na configuração padrão. Mas, se você vai implementar qualquer coisa minimamente importante para segurança, em primeiro lugar não deveria usar shellShellCheck é realmente um salvador. No passado eu fiz um pequeno wrapper https://github.com/jamespwilliams/strictbash que pode ser usado no shebang do script
Ele roda o ShellCheck antes de executar o script, e se houver qualquer falha o script nem chega a rodar; além disso, ativa todos os flags do “strict mode” do bash. Referência: http://redsymbol.net/articles/unofficial-bash-strict-mode/
Este tópico já apareceu várias vezes: https://news.ycombinator.com/from?site=shellcheck.net
A última grande discussão foi em 2021, com 301 pontos e 54 comentários: https://news.ycombinator.com/item?id=27030504
Há pouco tempo, converti alguns scripts de build e deploy, além de alguns scripts bash para um único servidor de produção, para o Turtle do Haskell
Foi ótimo porque consegui reduzir bastante a duplicação, e o código resultante também ficou muito menor. https://hackage.haskell.org/package/turtle
Pelo que sei, programas em Turtle têm apenas um diretório atual, então é difícil executar trabalhos concorrentes que precisam rodar em diretórios específicos. Resolvi parte disso com locks, filas e workers, mas quando o diretório atual do Turtle foi apagado e tudo começou a falhar, ficou inviável de manter
Já o typed-process funciona melhor para workflows grandes e complexos, porque inicia processos separados e pode executá-los dentro do diretório de trabalho sem precisar dar
cd. O suporte aOverloadedStringstambém é ótimo, então normalmente dá para copiar e colar o que eu teria digitado no bash e funciona diretoTambém uso o pacote
interpolateeQuasiQuotespara deixar strings brutas mais legíveis no código-fonte, mas eles não são compatíveis comhlint, então pretendo procurar outro pacote para lidar com stringsÉ autopromoção descarada, mas tenho ShellCheck e vários linters na configuração do pre-commit com a regra de corrigir todos os avisos antes de fazer commit ou pelo menos antes de fazer merge
Só que a maior parte do shell nos meus projetos fica dentro de arquivos
.gitlab-ci.yml, o que é difícil de verificar. Então criei um wrapper para automatizar isso: https://pypi.org/project/glscpc/Com o projeto ShellCheck e um pouco de mágica, ele mostra os apontamentos do ShellCheck com números de linha quase exatos
Não uso GitLab CI, mas há vários formatos de arquivo que essencialmente embutem shell script inline, como Dockerfile, GitHub Actions e Justfile
Normalmente, mesmo que seja só por causa do ShellCheck, qualquer coisa mais complexa do que alguns comandos eu separo em um shell script à parte e faço o script inline no Dockerfile chamá-lo. Esse padrão também ajuda a evitar que o CI fique acoplado demais ao GitHub Actions
Há um exemplo de uso como hook de pre-commit para
.gitlab-ci.yml, e um exemplo de configuração está aqui: https://gitlab.com/engmark/root/-/blob/9f7d9b93c2297d0b170e5...scriptdo job correspondenteA vantagem é que tudo ainda continuaria autocontido dentro do job do gitlab-ci. Mas lidar com todas as esquisitices do shell no ambiente do runner do GitLab CI era doloroso demais, então desisti e agora estou migrando todos os jobs para scripts em Python
Também existe o bash language server: https://github.com/bash-lsp/bash-language-server/
Bom. Logo na primeira vez que rodei isso em um script de produção
/bin/sh, já aprendi algumas coisas, e mexo com esse tipo de script desde os anos 80Se ficou longo demais para escrever em Bash — e na verdade isso já indica que talvez não devesse ser feito assim — também recomendo https://github.com/bach-sh/bach
ShellCheck é excelente, mas lidar com source/import é realmente doloroso. Não é culpa do ShellCheck; é porque
shé um pesadelo# shellcheck source=./deployment/deployment-example.envseguido de. "${1}"Mas dá para entender por que isso se torna sofrido quando você tem vários subscripts shell e muitos arquivos para carregar com source