- ShellCheck es una herramienta para revisar al instante errores comunes en scripts de shell desde la web, y permite ver los problemas del código pegado en la salida debajo del editor
- En entornos locales puede instalarse con
cabal,apt,dnf,pkg,brew install, entre otros, lo que permite usarlo mediante gestores de paquetes - Los ejemplos muestran puntos que suelen pasarse por alto en scripts reales, como advertencias de portabilidad con shebang
sh, problemas semánticos y errores en el manejo de comillas - Es software libre con licencia GPLv3 y también está disponible en GitHub, la Wiki y como linter integrado para los principales editores
- Puede usarse en CodeClimate, Codacy y CodeFactor para revisar automáticamente repositorios de GitHub, y ShellCheck en sí está escrito en Haskell
Funciones de análisis de scripts de shell
- ShellCheck es una herramienta de análisis para encontrar errores en scripts de shell
- Si pegas un script en el sitio web, puedes ver de inmediato los resultados del análisis en la ventana de salida debajo del editor
- Los ejemplos incluidos muestran que puede detectar varios tipos de problemas
- diversos problemas ocultos en scripts comunes
- advertencias de portabilidad que aparecen cuando el shebang es
sh - problemas semánticos de nivel más alto
- varios problemas con el manejo de comillas
Instalación, licencia e integración
- La instalación local puede hacerse con
cabal,apt,dnf,pkg,brew install - Es software libre con licencia GPLv3
- La documentación puede consultarse en la Wiki de ShellCheck
- Está disponible públicamente en GitHub, y también se publica el código del sitio web
- Ya existen paquetes disponibles para distribuciones o gestores de paquetes
- Puede usarse como linter integrado en los principales editores
- Puede utilizarse en CodeClimate, Codacy, CodeFactor para la revisión automática de repositorios de GitHub
- ShellCheck está escrito en Haskell
1 comentarios
Opiniones de Hacker News
El consejo que uso es este. Casi siempre conviene poner
-u(nounset) en el shebang para que las variables no declaradas sean un error, y la excepción que se encuentra con frecuencia es cuando la sintaxis"${arr[@]}"para expandir un arreglo vacío se trata como unbound.-n(noexec) impide ejecutar comandos, así que puede usarse como una especie de dry-run de pobres.-e(errexit) también es útil, pero hay que tener cuidado porque en la práctica solo provoca la salida ante un comando que falló “por sí mismo”; personalmente prefiero evitarlo y suelo agregar|| fail "..."después de los comandos."${arr[@]}"existe solo en bash 3 o anterior; desde bash 4,[@]no lanza unbound variable incluso cuando la variable realmente no está definida.Aun así, sigue siendo un problema porque macOS todavía instala bash v3 por defecto y tampoco lo actualiza automáticamente. Que la última versión de bash 3 haya salido hace 20 años es una locura. El unbound al expandir arreglos vacíos puede esquivarse con la expansión
${var+alter}:echo "${arr+${arr[@]}}"bash my_script.shen lugar de./my_script.sh, esas opciones no se aplican.Mucha gente lo hace para no tener que configurar el bit de ejecución y, a veces, también por falta de comprensión. Por eso es común recomendar usar
set, comoset -euo pipefail, en la primera línea después del shebang; además ayuda en casos donde el shebang maneja mal argumentos adicionales, como#!/usr/bin/env bash.-uen el shebang. ¿Por qué el shebang en lugar deset -u?En Bash,
"${arr[@]}"parece funcionar bien. Como se mencionó en otros comentarios, en Bash moderno mejoró aún más y parece ser un problema solo en<= 4.3: https://news.ycombinator.com/item?id=38397241Por ejemplo,
bash -uc 'unset x; echo "=> ${x[@]}"'ybash -uc 'x=(); echo "=> ${x[@]}"'pasan con un valor vacío, perobash -uc 'x=(); echo "=> ${x[0]}"'dejabash: x[0]: unbound variable. A Zsh no le gusta el primer ejemplo, pero ambos deberían soportar expansión con valor por defecto, comobash -uc 'unset x; echo "=> ${x[@]:-null}"'. Con los años,-eme gusta cada vez menos porque se vuelve muy confuso cuando se combina con funciones.-ese pueden manejar bien con-o pipefail, y eso forma parte de POSIX desde el año pasado.trapsi había archivos que limpiar.trapes una gran función de scripting, pero siento que no se habla lo suficiente de ella.Hace poco encontré una vulnerabilidad de escalada de privilegios en un shell script por culpa de la expansión aritmética. Es un tipo similar al descrito en https://research.nccgroup.com/2020/05/12/shell-arithmetic-ex....
Por ejemplo,
$((1 + ENV_VAR))permite inyección de código si se puede controlar$ENV_VAR. Lamentablemente, ShellCheck no lo detectó, al menos con la configuración por defecto. Pero si estás implementando algo aunque sea un poco importante para la seguridad, para empezar no deberías usar shell.ShellCheck es realmente un salvavidas. Hace un tiempo hice un pequeño wrapper, https://github.com/jamespwilliams/strictbash, que puede usarse como shebang de un script.
Ejecuta ShellCheck antes de correr el script y, si hay fallas, directamente no lo ejecuta; además configura todas las flags de “strict mode” de bash. Referencia: http://redsymbol.net/articles/unofficial-bash-strict-mode/.
Este tema ya apareció varias veces: https://news.ycombinator.com/from?site=shellcheck.net
La última discusión grande fue en 2021, con 301 puntos y 54 comentarios: https://news.ycombinator.com/item?id=27030504
Hace poco reemplacé los scripts de build y despliegue, y algunos scripts bash para un único servidor de producción, por Turtle de Haskell
Me gustó porque pude reducir mucho la duplicación, y el código resultante quedó bastante más corto. https://hackage.haskell.org/package/turtle
Hasta donde sé, los programas de Turtle tienen un solo directorio actual, así que se complica cuando hay que ejecutar tareas concurrentes que deben correr en directorios específicos. Lo resolví en parte con locks, colas y workers, pero cuando el directorio actual de Turtle se borró y empezó a fallar, se volvió inmanejable
En cambio, typed-process lanza procesos separados y puede ejecutarlos dentro del directorio de trabajo sin necesidad de hacer
cd, así que encaja bien en flujos de trabajo grandes y complejos. También tiene buen soporte paraOverloadedStrings, así que normalmente basta con copiar y pegar lo que habrías escrito en bash y funciona tal cualTambién uso el paquete
interpolateyQuasiQuotespara que los strings crudos se vean mejor en el código fuente, pero no es compatible conhlint, así que pienso buscar otro paquete para manejar stringsEs autopromoción descarada, pero tengo ShellCheck y varios linters configurados en pre-commit con el principio de arreglar todas las advertencias antes de commitear o, al menos, antes de mergear
Pero la mayoría del shell de mis proyectos está dentro de archivos
.gitlab-ci.yml, así que es difícil revisarlo. Por eso hice un wrapper que lo procesa automáticamente: https://pypi.org/project/glscpc/Usando el proyecto ShellCheck y un poco de magia, muestra los hallazgos de ShellCheck con números de línea casi exactos
No uso GitLab CI, pero uso bastantes formatos de archivo que esencialmente incrustan shell scripts en línea, como Dockerfile, GitHub Actions y Justfile
Por lo general, aunque sea solo por ShellCheck, saco cualquier cosa más compleja que unos pocos comandos a un shell script separado y hago que lo llamen desde el script en línea del Dockerfile. Este patrón también ayuda a que el CI no quede demasiado atado a GitHub Actions
El ejemplo de uso es un hook de pre-commit para
.gitlab-ci.yml, y el ejemplo de configuración está aquí: https://gitlab.com/engmark/root/-/blob/9f7d9b93c2297d0b170e5...scriptde ese jobLa ventaja es que todo seguiría quedando autocontenido dentro del job de gitlab-ci. Pero lidiar con todas las rarezas del shell en el entorno del runner de GitLab CI era demasiado doloroso, así que lo dejé, y ahora estoy moviendo todos los jobs a scripts de Python
También existe bash language server: https://github.com/bash-lsp/bash-language-server/
Muy bueno. En el primer script de producción en
/bin/shque ejecuté, aprendí varias cosas de inmediato, y eso que vengo tocando este tipo de scripts desde los 80Si se volvió demasiado largo para escribirlo en Bash, al punto de que en realidad no deberías estar haciéndolo así, también recomiendo https://github.com/bach-sh/bach
ShellCheck es excelente, pero el manejo de source/import es realmente doloroso. No es culpa de ShellCheck, sino de que
shes una pesadilla# shellcheck source=./deployment/deployment-example.envy luego usar. "${1}"Pero si tienes muchos sub-scripts de shell y muchos archivos para hacer source, se entiende por qué se vuelve doloroso