Linting Bash
Integração Shellcheck e shfmt para garantia de qualidade de scripts bash.
Shellcheck
Instalação
# Debian/Ubuntu
apt install shellcheck
# macOS
brew install shellcheck
# A partir do código fonte
cabal update && cabal install ShellCheck
Uso Básico
# Verificar arquivo único
shellcheck script.sh
# Verificar vários arquivos
shellcheck *.sh
# Com dialeto shell específico
shellcheck --shell=bash script.sh
shellcheck --shell=sh script.sh
# Excluir regras específicas
shellcheck --exclude=SC2086 script.sh
shellcheck --exclude=SC2086,SC2046 script.sh
# Formatos de saída
shellcheck --format=gcc script.sh # Estilo GCC
shellcheck --format=json script.sh # JSON para ferramentas
shellcheck --format=diff script.sh # Diff unificado
Códigos Comuns do Shellcheck
| Código | Problema | Correção |
|---|---|---|
| SC2086 | Aspas duplas para evitar globbing/divisão | "$var" |
| SC2046 | Cite a substituição de comando | "$(cmd)" |
| SC2006 | Use $() em vez de crases | $(cmd) |
| SC2034 | Variável parece não utilizada | Remova ou exporte |
| SC2155 | Declare e atribua separadamente | Separe local var; var=$(...) |
| SC2164 | Use cd ... || exit | Trate falha do cd |
| SC2181 | Verifique status de saída diretamente | if cmd; then |
| SC2129 | Considere agrupar escritas | Use { } > arquivo |
| SC1090 | Não foi possível seguir arquivo sourceado | Use # shellcheck source=caminho |
| SC2154 | Variável referenciada mas não atribuída | Inicialize ou declare |
Diretivas do Shellcheck
# Desabilitar para a próxima linha
# shellcheck disable=SC2086
echo $var_nao_entre_aspas
# Desabilitar para o arquivo inteiro (no topo)
# shellcheck disable=SC2086,SC2046
# Especificar arquivo de origem para source
# shellcheck source=./lib/functions.sh
source "$SCRIPT_DIR/lib/functions.sh"
# Especificar dialeto shell
# shellcheck shell=bash
# Desabilitar para bloco (não suportado - use por linha)
Padrões de Diretivas Inline
# Desabilitar aviso específico com explicação
# shellcheck disable=SC2034 # Variável usada pelo script que faz source
readonly CONFIG_VERSION="1.0"
# Desabilitar múltiplos códigos
# shellcheck disable=SC2086,SC2046
resultado=$(echo $var)
# Diretiva de source para caminhos dinâmicos
# shellcheck source=/dev/null
source "${CAMINHO_DINAMICO}/config.sh"
shfmt
Instalação
# macOS
brew install shfmt
# Instalação Go
go install mvdan.cc/sh/v3/cmd/shfmt@latest
# Snap
snap install shfmt
# Download binário
# De https://github.com/mvdan/sh/releases
Uso Básico
# Formatar e imprimir na saída padrão
shfmt script.sh
# Formatar no local
shfmt -w script.sh
# Verificar formatação (código de saída 1 se não formatado)
shfmt -d script.sh
# Diretório recursivo
shfmt -w .
shfmt -w scripts/
Opções de Formatação
# Indentação
shfmt -i 2 script.sh # Indentação de 2 espaços
shfmt -i 4 script.sh # Indentação de 4 espaços
shfmt -i 0 script.sh # tabs (padrão)
# Operadores binários no início da linha
shfmt -bn script.sh
# Casos switch indentados
shfmt -ci script.sh
# Operadores de redirecionamento seguidos por espaço
shfmt -sr script.sh
# Manter alinhamento de colunas
shfmt -kp script.sh
# Chave de abertura da função em linha separada
shfmt -fn script.sh
# Combinado
shfmt -i 4 -ci -bn script.sh
Configuração (.editorconfig)
# .editorconfig
[*.sh]
indent_style = space
indent_size = 4
shell_variant = bash
binary_next_line = true
switch_case_indent = true
space_redirects = true
Exemplos de Transformação
Antes do shfmt:
if [ -f "$arquivo" ];then
echo "existe"
fi
for i in 1 2 3;do
process $i
done
Depois de shfmt -i 4 -ci:
if [ -f "$arquivo" ]; then
echo "existe"
fi
for i in 1 2 3; do
process $i
done
Integração com Pre-commit
.pre-commit-config.yaml
repos:
- repo: https://github.com/koalaman/shellcheck-precommit
rev: v0.9.0
hooks:
- id: shellcheck
args: ["--severity=warning"]
- repo: https://github.com/scop/pre-commit-shfmt
rev: v3.7.0-1
hooks:
- id: shfmt
args: ["-i", "4", "-ci", "-w"]
# Alternativa: hooks locais
- repo: local
hooks:
- id: shellcheck
name: shellcheck
entry: shellcheck
language: system
types: [shell]
args: ["--severity=warning", "-x"]
- id: shfmt
name: shfmt
entry: shfmt
language: system
types: [shell]
args: ["-i", "4", "-ci", "-w"]
Executando Pre-commit
# Instalar hooks
pre-commit install # ou: prek install
# Executar em todos os arquivos
pre-commit run --all-files # ou: prek run --all-files
# Executar hook específico
pre-commit run shellcheck --all-files
pre-commit run shfmt --all-files
# Executar em arquivos específicos
pre-commit run --files script.sh
Integração com CI/CD
GitHub Actions
name: Lint Shell
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Executar ShellCheck
uses: ludeeus/action-shellcheck@master
with:
severity: warning
- name: Verificar formatação com shfmt
uses: mvdan/github-action-shfmt@master
with:
flags: -d -i 4 -ci
GitLab CI
shellcheck:
image: koalaman/shellcheck-alpine:stable
script:
- find . -name "*.sh" -exec shellcheck {} +
shfmt:
image: mvdan/shfmt:latest
script:
- shfmt -d -i 4 -ci .
Corrigindo Problemas Comuns
SC2086: Aspas para evitar divisão
# Ruim
echo $var
# Bom
echo "$var"
printf '%s\n' "$var"
SC2155: Declare e atribua separadamente
# Ruim - mascara status de saída
local var=$(algum_comando)
# Bom
local var
var=$(algum_comando)
SC2164: Use cd || exit
# Ruim
cd "$dir"
rm -rf *
# Bom
cd "$dir" || exit 1
rm -rf *
# Ou com subshell
(cd "$dir" && rm -rf *)
SC2181: Verifique saída diretamente
# Ruim
comando
if [ $? -eq 0 ]; then
# Bom
if comando; then
SC1090/SC1091: Problemas de source
# Adicione diretiva para source dinâmico
# shellcheck source=/dev/null
source "$CAMINHO_DINAMICO/lib.sh"
# Ou especifique caminho real
# shellcheck source=./lib/functions.sh
source "$SCRIPT_DIR/lib/functions.sh"
Integração com Editores
VS Code
Instale a extensão "ShellCheck" de Timon Wong.
// settings.json
{
"shellcheck.enable": true,
"shellcheck.run": "onSave",
"shellcheck.executablePath": "shellcheck",
"editor.formatOnSave": true,
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
}
}
Vim/Neovim
" Com ALE
let g:ale_linters = {'sh': ['shellcheck']}
let g:ale_fixers = {'sh': ['shfmt']}
let g:ale_sh_shfmt_options = '-i 4 -ci'
" Com coc.nvim
" Instale a extensão coc-sh
Melhores Práticas
- Execute shellcheck cedo - integre no editor e no CI
- Corrija problemas, não suprima - desabilite apenas com boa razão
- Documente supressões - explique por que a regra está desabilitada
- Use níveis de severidade -
--severity=warningpara CI - Formatação consistente - use shfmt no pre-commit
- Versione as ferramentas - fixe versões no CI/pre-commit