En esta página
Bash, Zsh y Fish
Los tres son shells: programas que leen tus comandos, los interpretan y piden al sistema que los ejecute. La diferencia está en cuánto te ayudan, qué sintaxis aceptan y qué tan lejos están del estándar.
La tabla que resume todo
| Bash | Zsh | Fish | |
|---|---|---|---|
| Nombre completo | Bourne Again SHell | Z shell | Friendly Interactive SHell |
| Dónde viene por defecto | casi todo Linux | macOS | algunos Linux |
| Compatible con POSIX | casi | casi | no |
| Autocompletado | básico (mejorable) | muy bueno | excelente, de fábrica |
| Sugerencias mientras escribes | no | con plugins | sí, de fábrica |
| Resaltado de sintaxis | no | con plugins | sí, de fábrica |
| Sintaxis de scripts | estándar | estándar + extras | propia |
| Configuración | ~/.bashrc | ~/.zshrc | ~/.config/fish/config.fish |
| Curva de aprendizaje | media | media | baja |
La recomendación práctica: aprende bash para scripting (es lo que vas a
encontrar en servidores, CI y ejercicios), usa zsh o fish para el día a
día si te gustan, y escribe los scripts siempre en bash o sh.
Qué es POSIX y cómo se estructura un script
POSIX no es un programa ni un shell: es un conjunto de normas que dice cómo debe comportarse un sistema operativo tipo Unix. Para lo que nos importa, define:
- qué comandos tienen que existir (
cd,echo,grep,sed,test…); - cómo se comporta el shell;
- qué sintaxis tiene el lenguaje de scripting;
- cómo se pasan variables de entorno;
- qué significa el código de salida de un comando.
Si un script dice #!/bin/sh y usa solo lo que POSIX define, tiene que correr
igual en Linux, macOS, BSD o un contenedor mínimo. Eso es lo que lo hace
portable.
La estructura de un script POSIX
Un script es un archivo de texto común. Se lee de arriba a abajo, como una receta. El orden habitual es:
1. shebang → con qué programa ejecutarlo
2. comentarios → qué hace y cómo se usa
3. configuración → opciones del shell
4. variables → datos que vas a reutilizar
5. funciones → bloques de comandos con nombre
6. programa → lo que se ejecuta de verdad
Ejemplo mínimo, con cada parte señalada:
#!/bin/sh
# compilar.sh: compila un archivo de C y avisa si falta algo.
set -e
salida="programa"
fuente="$1"
compilar() {
cc -Wall -Wextra -Werror "$1" -o "$salida"
}
if [ -z "$fuente" ]; then
echo "Uso: $0 archivo.c" >&2
exit 1
fi
compilar "$fuente"
echo "Listo: $salida"
| Línea | Qué hace |
|---|---|
#!/bin/sh | el shebang: indica que lo ejecute sh. Va en la primera línea, sin espacios antes. |
# ... | comentario: el shell lo ignora. Sirve para explicar el por qué. |
set -e | si un comando falla, el script corta. Evita seguir con datos rotos. |
salida="programa" | variable. No lleva espacios alrededor del =. |
fuente="$1" | $1 es el primer argumento que recibe el script. |
compilar() { ... } | función: un bloque de comandos con nombre. |
[ -z "$fuente" ] | prueba POSIX: verdadera si la cadena está vacía. |
>&2 | manda el mensaje al canal de errores. |
exit 1 | termina con código de error (0 significa "salió bien"). |
La gramática de una línea
Casi todo comando sigue esta forma:
comando [opciones] [argumentos] [redirecciones]
grep -rn "TODO" src/ > pendientes.txt
│ │ │ │ │
│ │ │ │ └─ redirección: guarda la salida en un archivo
│ │ │ └──────── argumento: dónde buscar
│ │ └───────────────── argumento: qué buscar
│ └─────────────────────── opciones: modifican el comportamiento
└────────────────────────────── comando: el programa que se ejecuta
Los operadores que combinan comandos son parte del lenguaje:
| Operador | Qué hace | Ejemplo |
|---|---|---|
| | conecta la salida de uno con la entrada del otro | ls | wc -l |
> | guarda la salida en un archivo (lo pisa) | echo hola > saludo.txt |
>> | agrega al final del archivo | echo otra >> saludo.txt |
< | toma la entrada desde un archivo | wc -l < lista.txt |
2> | redirige solo los errores | cmd 2> errores.txt |
; | ejecuta uno y después el otro, pase lo que pase | cd /tmp; ls |
&& | ejecuta el segundo solo si el primero salió bien | gcc main.c && ./a.out |
|| | ejecuta el segundo solo si el primero falló | cd carpeta || exit 1 |
Las piezas del lenguaje
| Pieza | Cómo se escribe | Para qué |
|---|---|---|
| variable | nombre="valor" | guardar un dato |
| usar variable | "$nombre" | leerlo (siempre entre comillas) |
| argumento | $1, $2, $# | lo que recibe el script |
| condición | if [ ... ]; then ... fi | decidir |
| bucle | for x in ...; do ... done | repetir |
| bucle condicional | while [ ... ]; do ... done | repetir mientras |
| función | nombre() { ... } | agrupar comandos y reutilizarlos |
| código de salida | $?, exit 0, exit 1 | decir si salió bien o mal |
Qué es una función (y por qué aparece en todos lados)
Una función es un bloque de comandos con un nombre. En vez de repetir las mismas cinco líneas en tres lugares, las escribes una vez y las llamas por su nombre. Eso es todo.
saludar() {
echo "Hola, $1"
}
saludar "Ana" # imprime: Hola, Ana
saludar "Luis" # imprime: Hola, Luis
saludares el nombre.$1es el primer argumento que recibe la función.- El cuerpo va entre
{y}. local nombre="$1"(en bash) hace que la variable no se escape de la función.
La misma idea existe en C, en Python y en casi todos los lenguajes: un bloque de código con nombre que puedes ejecutar cuando quieras. En los scripts sirve para no repetir y para que el script se lea como una lista de pasos.
POSIX vs bash
| Shell | ¿Es POSIX? | Consecuencia |
|---|---|---|
sh (dash en Debian/Ubuntu) | sí | mínimo común denominador |
| bash | casi (tiene extensiones) | seguro en casi todos lados |
| zsh | casi | acepta lo de bash, con variantes |
| fish | no | un script de fish no corre en bash, ni al revés |
Diferencias que te van a morder:
| Quieres | POSIX (sh) | bash / zsh |
|---|---|---|
| comparar cadenas | [ "$a" = "$b" ] | [[ "$a" == "$b" ]] |
| condición combinada | [ -f x ] && [ -r x ] | [[ -f x && -r x ]] |
| arrays | no tiene | frutas=(manzana pera) |
$(...) | sí | sí |
local | no estándar | sí |
pipefail | no | set -o pipefail |
Regla: si el script va a correr en otro lado, #!/bin/sh con sintaxis POSIX, o
#!/usr/bin/env bash si necesitas las extensiones de bash. Fish es para tu
terminal, no para scripts portables.
Bash
El que vas a encontrar en el 95 % de los servidores Linux.
echo "$BASH_VERSION"
| Archivo | Cuándo se carga |
|---|---|
~/.bashrc | cada terminal interactiva (lo más común) |
~/.bash_profile | al iniciar sesión |
~/.profile | login, si no existe .bash_profile |
Cosas que sabe hacer:
frutas=("manzana" "pera") # arrays, índice 0
echo "${frutas[0]}" # manzana
[[ -f archivo && -r archivo ]] # condiciones combinadas
for f in *.c; do echo "$f"; done
Es el shell de referencia para scripting: todo lo del documento de terminal aplica aquí.
Zsh
Nació como una mejora de bash. Es el shell por defecto de macOS desde 2019.
echo "$ZSH_VERSION"
| Archivo | Cuándo se carga |
|---|---|
~/.zshenv | siempre |
~/.zshrc | cada terminal interactiva |
~/.zprofile | al iniciar sesión |
Lo que gana sobre bash:
| Función | Ejemplo |
|---|---|
| Autocompletado potente | Tab cicla opciones con descripción |
| Globbing extendido | ls **/*.c recursivo |
| Corrección de typos | setopt correct |
| Temas y plugins | oh-my-zsh, zinit, antidote |
| Prompt a medida | powerlevel10k, starship |
Detalles que sorprenden:
- Los arrays empiezan en 1, no en 0:
${array[1]}es el primero. **/recursivo viene activado.- Con
setoptcambias comportamientos que en bash son fijos.
Instalación típica (Ubuntu/Debian):
sudo apt install zsh
chsh -s "$(which zsh)"
Cierra sesión y vuelve a entrar. Para probar sin cambiar nada: zsh.
Fish
Está diseñado para ser cómodo sin configurar nada.
echo $FISH_VERSION
fish_config # abre la configuración en el navegador
Lo que trae de fábrica:
| Función | Cómo se ve |
|---|---|
| Sugerencias | gris claro con el resto del comando anterior |
| Resaltado | verde si el comando existe, rojo si no |
| Autocompletado | con descripciones y pestañas |
| Historial compartido | entre terminales, al instante |
| Funciones con nombre | function saludo ... end |
El costo: sintaxis propia. No es POSIX.
| Quieres | Bash / Zsh | Fish |
|---|---|---|
| asignar variable | VAR="hola" | set VAR hola |
| exportar | export VAR=hola | set -gx VAR hola |
| capturar salida | $(comando) | (comando) |
| si falló | $? | $status |
| Y lógico | && | and (o && moderno) |
| función | f() { ... } | function f; ...; end |
| condición | [[ -f x ]] | test -f x |
Por eso: fish para la terminal, bash para los scripts.
Cómo cambiar de shell
cat /etc/shells # shells permitidos en el sistema
chsh -s /usr/bin/zsh # cambia tu shell por defecto
exec zsh # prueba sin cerrar la sesión
Si chsh dice que el shell no está en /etc/shells, hay que agregarlo (con
permisos de administrador) o instalar el paquete que lo provee.
Configuración que sí vale la pena
Sin importar el shell, tres cosas mejoran el día a día:
1. Alias
alias ll="ls -la"
alias g="git"
alias gs="git status"
alias ..="cd .."
2. Prompt informativo
Un buen prompt te dice en qué carpeta estás, si hay cambios sin commitear y qué
rama tienes. starship funciona en los tres shells:
# ~/.bashrc o ~/.zshrc
eval "$(starship init bash)" # o zsh
3. Historial compartido
# bash: ~/.bashrc
shopt -s histappend
export HISTSIZE=10000
export HISTFILESIZE=20000
Errores frecuentes
| Síntoma | Causa | Solución |
|---|---|---|
Edité .zshrc y no pasa nada | no recargaste | source ~/.zshrc |
command not found después de instalar | el PATH no incluye la carpeta | agregarlo al archivo de tu shell |
| El script anda en bash y no en fish | sintaxis incompatible | ejecutarlo con bash script.sh |
#!/bin/sh falla con [[ ]] | sh no soporta esa sintaxis | usar #!/usr/bin/env bash |
| El prompt tarda segundos | plugin pesado | desactivar plugins y medir |
PATH duplicado | el archivo se carga dos veces | agregar guardas o revisar .profile |
permission denied al cambiar shell | el shell no está en /etc/shells | agregarlo o instalarlo |
Qué elegir
| Situación | Elección |
|---|---|
| Vas a escribir scripts para servidores o proyectos | bash |
| Quieres comodidad sin configurar | fish |
| Quieres compatibilidad + plugins | zsh |
| No quieres pensar en nada | el que venga instalado |
No hay una respuesta correcta: hay contexto. Lo que sí es universal es saber bash, porque es el que está en la máquina cuando algo se rompe.
Siguiente parada: Vim.
Hecho con amor para Laura, por Bogdan.