Portabilidad Bash
Guía para escribir scripts portables compatibles con POSIX y comprender cuándo aprovechar las características específicas de Bash.
Selección de Shebang
Usar #!/usr/bin/env bash para Scripts Bash
#!/usr/bin/env bash
Por qué: Busca bash en el PATH, funciona en sistemas donde bash puede estar en diferentes ubicaciones.
Usar #!/bin/sh para Scripts POSIX
#!/bin/sh
Por qué: Máxima portabilidad cuando no se necesitan características de Bash. En muchos sistemas, /bin/sh es dash u otro shell POSIX.
Ruta Directa Cuando Sea Necesario
#!/bin/bash
Usar solo cuando: Los requisitos del sistema garanticen la ubicación de bash, o la política de seguridad exija rutas absolutas.
Matriz de Características POSIX vs Bash
| Característica | POSIX | Bash | Recomendación |
|---|---|---|---|
[[ ]] | No | Sí | Usar [ ] para POSIX |
(( )) | No | Sí | Usar [ ] con -eq, etc. |
| Arrays | No | Sí | Usar parámetros posicionales o archivos |
local | Parcial | Sí | Generalmente seguro |
${var,,} | No | 4+ | Usar tr para POSIX |
<<< | No | Sí | Usar `echo |
=~ regex | No | Sí | Usar grep o expr |
source | No | Sí | Usar el comando . (punto) |
function f() | No | Sí | Usar solo f() |
$'...' | No | Sí | Usar printf |
{1..10} | No | Sí | Usar seq o bucle while |
Patrones Compatibles con POSIX
Condicionales
# POSIX - usar [ ] con comillas adecuadas
if [ -f "$file" ]; then
echo "El archivo existe"
fi
# Comparación de cadenas
if [ "$var" = "valor" ]; then
echo "Coincidencia"
fi
# Comparación numérica
if [ "$num" -gt 10 ]; then
echo "Mayor"
fi
# Condiciones compuestas
if [ -f "$file" ] && [ -r "$file" ]; then
echo "Archivo legible"
fi
Conversión de Mayúsculas/Minúsculas (POSIX)
# Minúsculas
minusculas=$(echo "$cadena" | tr '[:upper:]' '[:lower:]')
# Mayúsculas
mayusculas=$(echo "$cadena" | tr '[:lower:]' '[:upper:]')
Operaciones con Subcadenas (POSIX)
# Obtener subcadena - usar expr o cut
subcadena=$(expr "$cadena" : '.\{3\}\(.\{5\}\)') # caracteres 4-8
subcadena=$(echo "$cadena" | cut -c4-8)
# Longitud de cadena
longitud=$(expr length "$cadena")
longitud=${#cadena} # Esto en realidad es POSIX
Lectura de Archivos (POSIX)
# Línea por línea
while IFS= read -r linea; do
echo "$linea"
done < "$archivo"
# Leer archivo completo (sin cat)
contenido=$(cat "$archivo") # cat es POSIX
Sustitución de Comandos
# Sintaxis moderna (preferida incluso en POSIX)
resultado=$(comando)
# Sintaxis heredada (evitar)
resultado=`comando`
# Anidada (por qué la moderna es mejor)
resultado=$(echo $(date)) # Claro
resultado=`echo \`date\`` # Pesadilla de escapes
Características Específicas de Bash Que Vale la Pena Usar
Cuando la portabilidad no es necesaria, estas características de Bash mejoran la calidad del código:
Test Extendido [[ ]]
# Coincidencia de patrones
[[ "$archivo" == *.txt ]]
# Coincidencia regex
[[ "$entrada" =~ ^[0-9]+$ ]]
# Sin preocupaciones por división de palabras
[[ -f $archivo ]] # Las comillas son opcionales (pero aún recomendadas)
# Operadores lógicos internos
[[ -f "$archivo" && -r "$archivo" ]]
Arrays
# Arrays indexados
declare -a archivos=()
archivos+=("uno.txt")
archivos+=("dos.txt")
for f in "${archivos[@]}"; do
procesar "$f"
done
# Arrays asociativos (Bash 4+)
declare -A config
config[host]="localhost"
config[port]="8080"
Expansión de Parámetros
# Valor por defecto
"${var:-predeterminado}"
# Conversión de mayúsculas/minúsculas (Bash 4+)
"${var,,}" # minúsculas
"${var^^}" # mayúsculas
# Subcadena
"${var:0:10}" # primeros 10 caracteres
"${var: -5}" # últimos 5 caracteres
# Buscar/reemplazar
"${var//antiguo/nuevo}"
Here Strings
# Bash
read -r var <<< "cadena de entrada"
# Equivalente POSIX
var=$(echo "cadena de entrada")
Sustitución de Procesos
# Bash - comparar dos salidas de comandos
diff <(sort archivo1) <(sort archivo2)
# Equivalente POSIX (con archivos temporales)
sort archivo1 > /tmp/ordenado1
sort archivo2 > /tmp/ordenado2
diff /tmp/ordenado1 /tmp/ordenado2
Detección del Tipo de Shell
# Verificar si se ejecuta en bash
if [ -n "${BASH_VERSION:-}" ]; then
echo "Ejecutando en Bash"
fi
# Verificar la versión de bash para características
if [ "${BASH_VERSINFO[0]:-0}" -ge 4 ]; then
echo "Bash 4+ disponible"
fi
# Detección genérica de shell
case "${SHELL##*/}" in
bash) echo "bash" ;;
zsh) echo "zsh" ;;
*) echo "otro" ;;
esac
Funciones de Utilidad Portables
Guía de Decisión de Portabilidad
Usar POSIX cuando:
- El script se ejecuta en sistemas minimalistas (contenedores, embebidos)
- El destino incluye dash, ash o busybox sh
- Se requiere máxima compatibilidad
- El script es parte de la inicialización del sistema
Usar Bash cuando:
- Se garantiza que los sistemas destino tienen bash
- Se necesitan arrays, arrays asociativos o regex
- Se requiere manipulación compleja de cadenas
- La claridad del código mejora significativamente
- Se necesitan características interactivas
Trampas Comunes de Portabilidad
echo vs printf
# Problemático - el comportamiento varía
echo -n "sin nueva línea"
echo -e "con\tabuladores"
# Portable
printf '%s' "sin nueva línea"
printf 'con\tabuladores\n'
Asignación de Variables
# Funciona en todas partes
var="valor"
# Puede fallar en algunos shells
var = "valor" # Los espacios alrededor de = son incorrectos
Exportar con Asignación
# POSIX - comandos separados
var="valor"
export var
# Bash/moderno - combinado (funciona en la mayoría de los lugares)
export var="valor"
Operaciones Similares a Arrays Sin Arrays
# Usar parámetros posicionales
set -- "elemento1" "elemento2" "elemento3"
for elemento in "$@"; do
echo "$elemento"
done
# O división basada en IFS
elementos="elemento1:elemento2:elemento3"
IFS=':' read -r elem1 elem2 elem3 <<EOF
$elementos
EOF