ESLint ya analiza scripts de Bash, POSIX sh y mksh con @eslint/shell, un plugin oficial publicado el 6 de noviembre de 2026. Lo probamos frente a ShellCheck con el mismo script: coinciden en casi todo, cada uno detecta algo que el otro no, y encontramos un comportamiento no documentado que conviene conocer antes de llevarlo a CI.
yoDEV Tested · 11 de noviembre de 2026
@eslint/shell 0.1.2 con ESLint 10.14.0 frente a ShellCheck 0.11.0 en Linux (Node 22), sobre un script de deploy de 16 líneas. Los resultados que reportamos como nuestros salen de esa prueba.
¿Qué es ESLint y qué añade @eslint/shell?
ESLint es el linter estándar del ecosistema JavaScript, y desde julio de 2024 su equipo trabaja para convertirlo en un linter de propósito general para cualquier lenguaje. Ya tenía soporte oficial para JSON, Markdown y CSS; @eslint/shell añade los scripts de shell.
El plugin tiene tres piezas:
- Un parser construido sobre
mvdan/sh, el mismo que usashfmt. Convierte el script en un árbol sintáctico estilo ESTree, así que si alguna vez escribiste una regla personalizada de ESLint, ya sabes escribir una para shell. Puedes explorar ese árbol en el ESLint Code Explorer (explorer.eslint.org). - Diez reglas que el propio proyecto describe como inspiradas en ShellCheck. El README asocia cada regla con el código de ShellCheck que replica.
- Una configuración
shell/recommendedque activa las diez.
La licencia es Apache-2.0 y el código está en github.com/eslint/shell. Lo firma Nicholas C. Zakas, creador de ESLint y miembro de su Technical Steering Committee. Al momento de publicar esta nota va por la versión 0.1.2, anterior a la 1.0, así que los nombres de las reglas y su comportamiento pueden cambiar.
¿Cómo analizar un script de Bash con ESLint?
Necesitas Node.js 20.19 o superior, 22.13 o superior, o 24 en adelante. El README indica que el plugin está probado con ESLint v10.
npm install -D eslint @eslint/shell
Después añade la configuración recomendada a eslint.config.js:
import { defineConfig } from "eslint/config";
import shell from "@eslint/shell";
export default defineConfig([
{
plugins: { shell },
extends: ["shell/recommended"],
},
]);
shell/recommended se aplica a los archivos **/*.sh y **/*.bash y los interpreta como Bash. Ejecuta npx eslint . y tus scripts se analizan junto con tu JavaScript, en el mismo paso de CI y con la misma integración de editor que ya mantienes. Si buscabas un bash linter o un shell script linter que no sea una herramienta más que instalar, es esto.
Si prefieres elegir las reglas una por una, el anuncio muestra la 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? Cómo analizar scripts POSIX en lugar de Bash
Define language junto a la configuración recomendada:
export default defineConfig([
{
plugins: { shell },
extends: ["shell/recommended"],
language: "shell/posix",
},
]);
Los tres dialectos son shell/bash (el predeterminado), shell/posix y shell/mksh.
Dos cosas que vimos en la prueba:
- El shebang no elige el dialecto; lo elige la configuración. Un archivo que empieza con
#!/bin/shy usa arrays y[[ ]]de Bash pasó sin errores con la configuración predeterminada. Si tu repositorio mezcla scripts de Bash y desh, sepáralos con patronesfiles. - En modo POSIX, un bashismo es un error de parseo, no una regla. El ejemplo con arrays se detuvo en
Parsing error: … arrays are a bash/mksh featurey el resto del archivo no se analizó. Los bashismos se corrigen de uno en uno.
¿Qué reglas de ShellCheck incluye @eslint/shell?
Estas son las diez, con el código de ShellCheck que cada una replica según el README:
| Regla | ShellCheck | Qué detecta | Corrección automática | Recommended |
|---|---|---|---|---|
no-backticks |
SC2006 | Backticks antiguos en lugar de $(...) |
Sí | error |
no-expansions-in-single-quotes |
SC2016 | Variables entre comillas simples que no se expanden | No | warn |
no-ls-iteration |
SC2045 | Recorrer la salida de ls |
No | error |
no-unquoted-expansions |
SC2086 / SC2046 | Expansiones sin comillas sujetas a word splitting y globbing | Sí | error |
no-unused-vars |
SC2034 | Variables asignadas que nunca se usan | No | error |
no-useless-cat |
SC2002 | cat archivo | comando |
No | error |
no-useless-echo |
SC2116 | $(echo ...) |
No | error |
no-variables-in-printf-format |
SC2059 | Variables en el formato de printf |
No | error |
require-cd-guard |
SC2164 | cd sin manejar el fallo |
No (sugerencias) | error |
require-read-r |
SC2162 | read sin -r |
Sí | error |
ShellCheck tiene cientos de comprobaciones; esto son diez. Están bien elegidas: las comillas, los cd que fallan y el read -r explican buena parte de los errores reales en scripts. Pero es un subconjunto, no una reimplementación, y el proyecto no dice lo contrario.
¿ESLint o ShellCheck? Qué detectó cada uno en el mismo script bash
Escribimos un script de “deploy” de 16 líneas con un ejemplo de cada problema y algunos extra: un [ -z $TARGET ] sin comillas, un eval "$TARGET" y un glob pasado directamente a ls. Después ejecutamos las dos herramientas.
| Problema en el script | @eslint/shell 0.1.2 | ShellCheck 0.11.0 |
|---|---|---|
for f in $(ls *.log): recorrer ls |
||
…misma línea, $(ls ...) sin comillas |
— | |
…misma línea, glob que puede expandirse a -opciones |
— | |
cd /opt/app sin || exit |
||
| Backticks | ||
rm $files sin comillas |
||
cat config.txt | grep PORT |
— (no lo reportó en nuestra prueba) | |
printf "$TARGET\n" |
||
read line sin -r |
||
'$HOME' entre comillas simples |
||
x=$(echo hola) |
||
[ -z $TARGET ] sin comillas |
||
| Tres variables sin usar | ||
eval "$TARGET" |
— | — |
Ninguna de las dos marcó el eval, y ninguna dice hacerlo.
La fila importante es la de las variables sin usar.
¿Por qué un eval desactiva no-unused-vars?
shell/no-unused-vars funciona: en un archivo de tres líneas con foo=1 reporta foo. Pero en nuestro script de prueba no reportó nada, mientras ShellCheck marcó las tres variables sin usar.
Lo aislamos. Un solo eval en cualquier parte del archivo silencia no-unused-vars en todo el archivo. Un script de tres líneas, foo=1 seguido de eval "$1", termina con código 0. Si quitas el eval del script original, vuelven las tres advertencias.
La lógica se puede defender: eval puede referirse a cualquier variable con un nombre construido en tiempo de ejecución, así que el linter no puede demostrar que una variable no se usa. ShellCheck adopta la posición contraria y reporta igual. Pero al momento de publicar esta nota, con la versión 0.1.2, el README no documenta este comportamiento y la salida no avisa de que la regla se desactivó. Si tus scripts usan eval, algo habitual en scripts de deploy y de bootstrap, ESLint no está revisando variables sin usar en esos archivos.
¿Puede corregir el script automáticamente?
Tres reglas se corrigen solas: no-backticks, no-unquoted-expansions y require-read-r. Al ejecutar npx eslint --fix sobre nuestro script:
- convirtió
files=`find . -name "*.tmp"`enfiles=$(find . -name "*.tmp") - añadió comillas en
rm "$files"y en[ -z "$TARGET" ] - convirtió
read lineenread -r line
No tocó nada que requiera una decisión humana, como el bucle sobre ls, la protección del cd o el formato de printf. Es lo correcto.
¿Cómo desactivar una regla en un script bash?
Los comentarios estándar de ESLint funcionan, escritos como comentarios de shell:
# eslint-disable-next-line shell/no-unused-vars
foo=1
bar=2 # eslint-disable-line shell/no-unused-vars
Para una variable que se define en un archivo y se lee en otro, como un valor de configuración que carga un script con source, no-unused-vars acepta una lista allowed:
rules: {
"shell/no-unused-vars": ["error", { allowed: ["CONFIG"] }],
},
Probamos las dos opciones.
¿Conviene reemplazar ShellCheck por ESLint?
Si tu equipo ya ejecuta ESLint en CI y tus scripts son el pegamento habitual (deploy, setup, utilidades de CI), @eslint/shell te da las comprobaciones que detectan la mayoría de los errores reales sin una segunda herramienta que instalar, fijar y configurar. La corrección automática de comillas es una ventaja real.
Si el shell es una parte importante de tu código, o tus scripts usan eval, mantén ShellCheck. Cubre muchas más comprobaciones y en nuestra prueba detectó cosas que ESLint no. No entran en conflicto: usar los dos es una configuración razonable mientras el plugin no llegue a la 1.0.
El equipo de ESLint recibe ideas de reglas en github.com/eslint/shell/issues, y al ser una versión 0.1, la lista de reglas es lo que más probablemente crezca.
¿Y tú? ¿Qué error en un script de shell te ha costado más caro en producción?
- ShellCheck
- ESLint con @eslint/shell
- No uso ningún linter para shell
- Prefiero otra opción (cuéntanos cuál)
Comenta abajo o, si este artículo te llegó por correo, responde directamente al email: tu respuesta se publica aquí.
Relacionado: @shadcn/lint: el linter que obliga a tu agente de IA a respetar tu design system en Tailwind CSS