O ESLint já analisa scripts de Bash, POSIX sh e mksh com o @eslint/shell, um plugin oficial publicado em 6 de novembro de 2026. Nós o testamos contra o ShellCheck com o mesmo script: eles concordam em quase tudo, cada um detecta algo que o outro não detecta, e encontramos um comportamento não documentado que vale conhecer antes de levá-lo para o CI.
yoDEV Tested · 11 de novembro de 2026
@eslint/shell 0.1.2 com ESLint 10.14.0 contra ShellCheck 0.11.0 no Linux (Node 22), sobre um script de deploy de 16 linhas. Os resultados que reportamos como nossos saem desse teste.
O que é o ESLint e o que o @eslint/shell acrescenta?
O ESLint é o linter padrão do ecossistema JavaScript, e desde julho de 2024 sua equipe trabalha para transformá-lo em um linter de propósito geral para qualquer linguagem. Ele já tinha suporte oficial para JSON, Markdown e CSS; o @eslint/shell acrescenta os scripts de shell.
O plugin tem três peças:
- Um parser construído sobre o
mvdan/sh, o mesmo usado peloshfmt. Ele converte o script em uma árvore sintática no estilo ESTree, então, se você já escreveu uma regra personalizada do ESLint, já sabe escrever uma para shell. Você pode explorar essa árvore no ESLint Code Explorer (explorer.eslint.org). - Dez regras que o próprio projeto descreve como inspiradas no ShellCheck. O README associa cada regra ao código do ShellCheck que ela replica.
- Uma configuração
shell/recommendedque ativa as dez.
A licença é Apache-2.0 e o código está em github.com/eslint/shell. Quem assina é Nicholas C. Zakas, criador do ESLint e membro do seu Technical Steering Committee. No momento da publicação desta nota, ele está na versão 0.1.2, anterior à 1.0, então os nomes das regras e seu comportamento podem mudar.
Como analisar um script de Bash com o ESLint?
Você precisa do Node.js 20.19 ou superior, 22.13 ou superior, ou 24 em diante. O README indica que o plugin é testado com o ESLint v10.
npm install -D eslint @eslint/shell
Depois, adicione a configuração recomendada ao eslint.config.js:
import { defineConfig } from "eslint/config";
import shell from "@eslint/shell";
export default defineConfig([
{
plugins: { shell },
extends: ["shell/recommended"],
},
]);
O shell/recommended se aplica aos arquivos **/*.sh e **/*.bash e os interpreta como Bash. Execute npx eslint . e seus scripts são analisados junto com o seu JavaScript, na mesma etapa de CI e com a mesma integração de editor que você já mantém. Se você procurava um bash linter ou um shell script linter que não seja mais uma ferramenta para instalar, é isto.
Se você prefere escolher as regras uma a uma, o anúncio mostra a forma manual:
import { defineConfig } from "eslint/config";
import shell from "@eslint/shell";
export default defineConfig([
{
files: ["**/*.sh"],
plugins: { shell },
language: "shell/posix",
rules: {
"shell/no-backticks": "error",
"shell/no-unquoted-expansions": "error",
},
},
]);
Bash vs sh? Como analisar scripts POSIX em vez de Bash
Defina language junto com a configuração recomendada:
export default defineConfig([
{
plugins: { shell },
extends: ["shell/recommended"],
language: "shell/posix",
},
]);
Os três dialetos são shell/bash (o padrão), shell/posix e shell/mksh.
Duas coisas que vimos no teste:
- O shebang não escolhe o dialeto; quem escolhe é a configuração. Um arquivo que começa com
#!/bin/she usa arrays e[[ ]]do Bash passou sem erros com a configuração padrão. Se o seu repositório mistura scripts de Bash e desh, separe-os com padrõesfiles. - No modo POSIX, um bashismo é um erro de parsing, não uma regra. O exemplo com arrays parou em
Parsing error: … arrays are a bash/mksh featuree o restante do arquivo não foi analisado. Os bashismos são corrigidos um de cada vez.
Quais regras do ShellCheck o @eslint/shell inclui?
Estas são as dez, com o código do ShellCheck que cada uma replica segundo o README:
| Regra | ShellCheck | O que detecta | Correção automática | Recommended |
|---|---|---|---|---|
no-backticks |
SC2006 | Backticks antigos em vez de $(...) |
Sim | error |
no-expansions-in-single-quotes |
SC2016 | Variáveis entre aspas simples que não se expandem | Não | warn |
no-ls-iteration |
SC2045 | Percorrer a saída do ls |
Não | error |
no-unquoted-expansions |
SC2086 / SC2046 | Expansões sem aspas sujeitas a word splitting e globbing | Sim | error |
no-unused-vars |
SC2034 | Variáveis atribuídas que nunca são usadas | Não | error |
no-useless-cat |
SC2002 | cat arquivo | comando |
Não | error |
no-useless-echo |
SC2116 | $(echo ...) |
Não | error |
no-variables-in-printf-format |
SC2059 | Variáveis no formato do printf |
Não | error |
require-cd-guard |
SC2164 | cd sem tratar a falha |
Não (sugestões) | error |
require-read-r |
SC2162 | read sem -r |
Sim | error |
O ShellCheck tem centenas de verificações; aqui são dez. Elas foram bem escolhidas: as aspas, os cd que falham e o read -r explicam boa parte dos erros reais em scripts. Mas é um subconjunto, não uma reimplementação, e o projeto não diz o contrário.
ESLint ou ShellCheck? O que cada um detectou no mesmo script bash
Escrevemos um script de “deploy” de 16 linhas com um exemplo de cada problema e alguns extras: um [ -z $TARGET ] sem aspas, um eval "$TARGET" e um glob passado diretamente ao ls. Depois executamos as duas ferramentas.
| Problema no script | @eslint/shell 0.1.2 | ShellCheck 0.11.0 |
|---|---|---|
for f in $(ls *.log): percorrer ls |
||
…mesma linha, $(ls ...) sem aspas |
— | |
…mesma linha, glob que pode se expandir para -opções |
— | |
cd /opt/app sem || exit |
||
| Backticks | ||
rm $files sem aspas |
||
cat config.txt | grep PORT |
— (não reportou no nosso teste) | |
printf "$TARGET\n" |
||
read line sem -r |
||
'$HOME' entre aspas simples |
||
x=$(echo hola) |
||
[ -z $TARGET ] sem aspas |
||
| Três variáveis não usadas | ||
eval "$TARGET" |
— | — |
Nenhuma das duas marcou o eval, e nenhuma diz fazer isso.
A linha importante é a das variáveis não usadas.
Por que um eval desativa o no-unused-vars?
O shell/no-unused-vars funciona: em um arquivo de três linhas com foo=1, ele reporta foo. Mas no nosso script de teste não reportou nada, enquanto o ShellCheck marcou as três variáveis não usadas.
Isolamos o problema. Um único eval em qualquer parte do arquivo silencia o no-unused-vars no arquivo inteiro. Um script de três linhas, foo=1 seguido de eval "$1", termina com código 0. Se você tirar o eval do script original, as três advertências voltam.
A lógica é defensável: o eval pode se referir a qualquer variável com um nome construído em tempo de execução, então o linter não consegue demonstrar que uma variável não é usada. O ShellCheck adota a posição contrária e reporta mesmo assim. Mas, no momento da publicação desta nota, com a versão 0.1.2, o README não documenta esse comportamento e a saída não avisa que a regra foi desativada. Se os seus scripts usam eval, algo comum em scripts de deploy e de bootstrap, o ESLint não está verificando variáveis não usadas nesses arquivos.
Ele consegue corrigir o script automaticamente?
Três regras se corrigem sozinhas: no-backticks, no-unquoted-expansions e require-read-r. Ao executar npx eslint --fix sobre o nosso script, ele:
- converteu
files=`find . -name "*.tmp"`emfiles=$(find . -name "*.tmp") - adicionou aspas em
rm "$files"e em[ -z "$TARGET" ] - converteu
read lineemread -r line
Não mexeu em nada que exija uma decisão humana, como o loop sobre o ls, a proteção do cd ou o formato do printf. É o correto.
Como desativar uma regra em um script bash?
Os comentários padrão do ESLint funcionam, escritos como comentários de shell:
# eslint-disable-next-line shell/no-unused-vars
foo=1
bar=2 # eslint-disable-line shell/no-unused-vars
Para uma variável que é definida em um arquivo e lida em outro, como um valor de configuração carregado por um script com source, o no-unused-vars aceita uma lista allowed:
rules: {
"shell/no-unused-vars": ["error", { allowed: ["CONFIG"] }],
},
Testamos as duas opções.
Vale a pena substituir o ShellCheck pelo ESLint?
Se a sua equipe já executa o ESLint no CI e os seus scripts são a cola de sempre (deploy, setup, utilitários de CI), o @eslint/shell oferece as verificações que detectam a maioria dos erros reais sem uma segunda ferramenta para instalar, fixar e configurar. A correção automática de aspas é uma vantagem real.
Se o shell é uma parte importante do seu código, ou se os seus scripts usam eval, mantenha o ShellCheck. Ele cobre muito mais verificações e, no nosso teste, detectou coisas que o ESLint não detectou. Eles não entram em conflito: usar os dois é uma configuração razoável enquanto o plugin não chegar à 1.0.
A equipe do ESLint recebe ideias de regras em github.com/eslint/shell/issues e, por ser uma versão 0.1, a lista de regras é o que tem mais chance de crescer.
E você? Qual erro em um script de shell já saiu mais caro para você em produção?
- ShellCheck
- ESLint com @eslint/shell
- Não uso nenhum linter para shell
- Prefiro outra opção (conte qual)
Comente abaixo ou, se este artigo chegou até você por e-mail, responda diretamente ao e-mail: sua resposta é publicada aqui.
Relacionado: @shadcn/lint: el linter que obliga a tu agente de IA a respetar tu design system en Tailwind CSS