Cuando abres una terminal y algo “no funciona” —un alias que desaparece, una variable de entorno que no está, un PATH que no tiene lo que pusiste— casi siempre es el mismo problema: el archivo equivocado para el contexto equivocado. Bash tiene tres archivos de inicialización principales y cada uno tiene su momento. Si no los distingues, acabas duplicando configuración, parándote a depurar cosas que “deberían funcionar”, o creando entornos inconsistentes entre tu sesión SSH y tu terminal de escritorio.
Login shell, interactive non-login shell y non-interactive shell son los tres tipos que necesitas tener claros. Un login shell es el que arranca cuando te autenticas con credenciales: una sesión SSH, una consola virtual (Ctrl+Alt+F2), o cuando haces su - (el guion es clave; sin él no es login). Una interactive non-login shell es la que te abre GNOME Terminal, Konsole, o cualquier emulador de terminal gráfico —hay terminal, hay prompt, pero no hay proceso de login porque ya te autenticaste con la sesión gráfica. Una non-interactive shell es la que ejecuta un script sin terminal: un cron job, un pipeline en CI, un bash script.sh lanzado desde otro proceso.
El diseño tiene lógica: las variables de entorno y el PATH son configuración de sesión, deben estar disponibles para todos los procesos hijos independientemente de si hay terminal o no. Los aliases y las opciones de completado son interactivos por naturaleza, no tiene sentido cargarlos en un script. Por eso la separación existe.
Equivocarte aquí tiene consecuencias concretas: pones export PATH="$HOME/.local/bin:$PATH" en .bashrc y tu PATH es correcto en el terminal de escritorio, pero cuando conectas por SSH el binario no se encuentra. O lo pones en .bash_profile pero con una configuración que no llama a .profile, y entonces el usuario que usa zsh nunca lo ve. O defines aliases en .profile y te preguntas por qué no funcionan en scripts.
El flujo de carga en bash es el siguiente:
- Login shell: lee
/etc/profile, luego busca en orden~/.bash_profile,~/.bash_login,~/.profiley ejecuta el primero que encuentre, parando ahí. - Interactive non-login: lee
/etc/bash.bashrc, luego~/.bashrc. - Non-interactive: por defecto no lee ninguno de estos (salvo que
$BASH_ENVapunte a algo, lo cual es raro).
El detalle importante en Debian: el ~/.profile que viene por defecto incluye este bloque:
if [ -n "$BASH_VERSION" ]; then
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi
Esto significa que en Debian, cuando entras por SSH (login shell → carga .profile), .profile a su vez carga .bashrc. Por eso en la práctica un login shell en Debian también carga tu .bashrc. Pero depender de esto como garantía es frágil: si alguien edita .profile y elimina ese bloque, o si usas una distribución que no lo incluye, la cadena se rompe.
La práctica estándar que funciona en todos los casos: variables de entorno y modificaciones de PATH en ~/.profile (funciona con cualquier shell POSIX, no solo bash), aliases, funciones interactivas y opciones de completado en ~/.bashrc. Si tienes algo que solo tiene sentido en bash y en login shell, entonces ~/.bash_profile — pero en ese caso lo primero que debe hacer es cargar .bashrc explícitamente.
# ─── Verificar qué tipo de shell tenemos ahora mismo ───────────────────
# ¿Es login shell?
shopt -q login_shell && echo "login shell" || echo "non-login shell"
# La variable $- contiene flags de la shell; 'i' = interactive
[[ $- == *i* ]] && echo "interactive" || echo "non-interactive"
# ─── Añadir trazas temporales para depurar qué se carga ────────────────
# Ejecuta esto una vez; después de hacer login nuevo verás el orden real
cat >> ~/.bashrc <<'EOF'
echo "[DEBUG] cargando ~/.bashrc (PID $$, padre: $PPID)" >&2
EOF
cat >> ~/.profile <<'EOF'
echo "[DEBUG] cargando ~/.profile (PID $$, padre: $PPID)" >&2
EOF
# Abre un nuevo terminal gráfico → solo verás .bashrc
# Conecta por SSH → verás .profile y luego .bashrc (por el bloque de Debian)
# Ejecuta: bash -l → fuerza login shell, verás .profile
# ─── Estructura recomendada real ────────────────────────────────────────
# ~/.profile — variables y PATH, compatible POSIX
cat > ~/.profile <<'EOF'
# Herramientas instaladas en el home del usuario
export PATH="$HOME/.local/bin:$HOME/bin:$PATH"
# Editor por defecto para cron, git commit, etc.
export EDITOR="vim"
export VISUAL="vim"
# Cargar .bashrc si estamos en bash (ya está en el default de Debian,
# pero lo ponemos explícito para que no dependa de haberlo tocado)
if [ -n "$BASH_VERSION" ] && [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
EOF
# ~/.bashrc — configuración interactiva, solo tiene sentido con terminal
cat > ~/.bashrc <<'EOF'
# Salir inmediatamente si no es interactiva (buena práctica defensiva)
[[ $- != *i* ]] && return
# Aliases
alias ll='ls -lh --color=auto'
alias grep='grep --color=auto'
alias gs='git status'
# Prompt con rama de git si estamos en un repo
PS1='\u@\h:\w$(__git_ps1 " (%s)" 2>/dev/null)\$ '
# Historial: no guardar duplicados, tamaño generoso
HISTCONTROL=ignoreboth
HISTSIZE=10000
HISTFILESIZE=20000
shopt -s histappend
EOF
# ~/.bash_profile — solo si necesitas algo específico de bash en login shell.
# En la mayoría de casos, simplemente delega a .profile y .bashrc:
cat > ~/.bash_profile <<'EOF'
# Delegar a .profile (que a su vez carga .bashrc)
[ -f "$HOME/.profile" ] && . "$HOME/.profile"
EOF
# ─── Verificar el resultado sin abrir nueva sesión ─────────────────────
# Simular login shell en la misma terminal:
bash --login -c 'echo $PATH; type ll 2>/dev/null || echo "ll no disponible"'
# Simular non-login interactive (lo que hace GNOME Terminal):
bash -i -c 'echo $PATH; type ll 2>/dev/null || echo "ll no disponible"'
El bloque [[ $- != *i* ]] && return al principio de .bashrc merece atención: cuando un script hace source ~/.bashrc accidentalmente o cuando bash arranca como non-interactive, esto corta la ejecución antes de que se cargue nada interactivo. Sin esa guarda, un script que por algún motivo cargue .bashrc podría recibir aliases que machacan comandos estándar —alias grep='grep --color=auto' dentro de un script tiene efectos impredecibles.
La redirección >&2 en las trazas de debug es deliberada: los mensajes van a stderr, así que no contaminan la salida estándar de los scripts que puedan cargar estos archivos. Una vez que termines de depurar, quitas esas líneas con sed -i '/\[DEBUG\]/d' ~/.bashrc ~/.profile.
Fíjate en el ~/.bash_profile del ejemplo: solo llama a .profile. El error clásico es escribir configuración directamente ahí sin llamar a .profile, con lo cual esa configuración nunca llega a usuarios de zsh o dash. Si ~/.bash_profile existe, bash no lee ~/.profile en una login shell — por eso hay que llamarlo explícitamente desde dentro.
El comando bash --login -c '...' del final es la forma más rápida de comprobar si algo funciona en una login shell sin abrir una sesión nueva ni desconectarte. bash -i -c '...' hace lo mismo para interactive non-login. Son tus herramientas de verificación antes de hacer commit de cambios en esos archivos.
N° 72