Administración de Linux
Fundamentos del shell Bash: comandos, redirecciones, pipes, variables y configuración
Principiante10 min de lecturaActualizado
Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés
Esta guía explica qué ocurre realmente entre pulsar Intro y que un programa se ejecute: la búsqueda del comando, el entrecomillado, las expansiones y los tres flujos estándar. Aprenderás a redirigir la salida y los errores, a construir pipelines, a usar el historial y los atajos de edición que hacen rápido el shell, a gestionar las variables de entorno y el PATH, y a configurar Bash mediante .bashrc y .profile sin romper tu inicio de sesión.
Lo que aprenderás
- Explicar cómo analiza Bash una línea de comandos y cómo localiza el programa que debe ejecutar
- Redirigir la salida estándar y el error estándar a ficheros y conectar comandos con pipes
- Usar correctamente el entrecomillado y el globbing para que los espacios y caracteres especiales no den sorpresas
- Definir, exportar e inspeccionar variables del shell y de entorno, incluido PATH
- Configurar alias y entorno en el fichero de arranque adecuado y aprovechar bien las páginas man
Un shell es un programa que lee líneas de texto, decide qué programas ejecutar y con qué argumentos, los ejecuta y te muestra el resultado. Bash (el «Bourne-Again SHell») es el shell interactivo por defecto en casi todas las distribuciones de Linux y el intérprete que todavía dan por hecho la mayoría de los scripts de administración. Entender qué hace Bash con una línea de comandos antes de ejecutar nada —expansiones, entrecomillado, redirecciones— es lo que separa a quien teclea comandos de quien sabe predecir qué hará un comando.
Qué ocurre al pulsar Intro
Toma una línea como grep -i error /var/log/*.log > errors.txt. Bash la procesa por etapas:
- Divide la línea en palabras por los espacios en blanco que no están entre comillas.
- Realiza las expansiones:
~se convierte en tu directorio personal,$VARse sustituye por su valor,$(cmd)por la salida de un comando,{a,b}pora b, y los patrones glob como*.logpor los nombres de fichero que coinciden. - Prepara las redirecciones (
>,<,|) y las elimina de la lista de argumentos. - Resuelve la primera palabra: un alias, una función del shell, un builtin (
cd,echo,export…) o un programa externo encontrado recorriendo los directorios dePATH, en ese orden. - Crea un proceso hijo (fork), aplica en él las redirecciones y ejecuta el programa con las palabras restantes como argumentos.
El programa nunca ve el *, el > ni las comillas; recibe una lista de cadenas. grep no sabe que *.log era un patrón: simplemente recibe /var/log/syslog /var/log/kern.log …. Por eso el entrecomillado importa tanto, y por eso todos los programas de Linux tratan los comodines de la misma manera: ninguno lo hace, lo hace el shell.
type te dice a qué categoría pertenece un nombre, y command -v muestra la ruta de un comando externo:
$ type cd ls ll
cd is a shell builtin
ls is /usr/bin/ls
ll is aliased to `ls -alF'
$ echo "$PATH"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PATH se recorre de izquierda a derecha y gana la primera coincidencia. El directorio actual no está incluido a propósito, así que un programa de tu directorio de trabajo debe ejecutarse como ./program: eso impide que un ls malicioso dejado en /tmp secuestre tus comandos.
Anatomía de un comando
La mayoría de los comandos siguen la forma command [options] [arguments]. Las opciones existen en forma corta (-l), corta agrupada (-la = -l -a) y larga (--all); algunas reciben un valor (-n 5, --lines=5). Los argumentos suelen ser nombres de fichero. Un -- suelto marca el final de las opciones, de modo que rm -- -rf borra un fichero que se llama literalmente -rf.
El manual viene incluido. man ls abre la página de ls; dentro, /pattern busca, n salta a la siguiente coincidencia y q sale. Las páginas se agrupan en secciones numeradas —1 para comandos, 5 para formatos de fichero, 8 para administración— y el número resuelve ambigüedades: man 5 passwd es el formato de fichero y man 1 passwd, el comando. man -k keyword (o apropos) busca en los resúmenes de las páginas cuando no conoces el nombre del comando. La mayoría de los programas también muestran un resumen con --help.
Los tres flujos estándar
Todo proceso arranca con tres descriptores de fichero abiertos: la entrada estándar (fd 0), la salida estándar (fd 1) y el error estándar (fd 2). En una terminal, los tres apuntan a la terminal, y por eso la salida y los mensajes de error aparecen mezclados. La redirección los hace apuntar a otro sitio antes de que arranque el programa.
| Sintaxis | Efecto |
|---|---|
cmd > file | stdout a file, vaciándolo primero |
cmd >> file | stdout añadido al final de file |
cmd 2> file | stderr a file |
cmd > file 2>&1 o cmd &> file | ambos flujos a file |
cmd 2> /dev/null | descartar los errores |
cmd < file | stdin leído desde file |
cmd <<< "text" | stdin desde una cadena (una «here string») |
cmd1 | cmd2 | el stdout de cmd1 se convierte en el stdin de cmd2 |
Como el programa solo ve descriptores, no puede saber si está escribiendo en una terminal, en un fichero o en otro proceso. Esa decisión de diseño es la que hace posibles los pipelines: programas pequeños que leen de stdin y escriben en stdout pueden encadenarse sin límite.
# Contar las direcciones IP distintas que han accedido al servidor, las más activas primero
cut -d' ' -f1 /var/log/apache2/access.log | sort | uniq -c | sort -rn | head
tee copia su entrada a un fichero y a stdout, así que puedes guardar un registro sin dejar de ver la salida: ./build.sh 2>&1 | tee build.log. xargs convierte líneas de entrada en argumentos para otro comando: find . -name '*.tmp' -print0 | xargs -0 rm (la pareja -print0/-0 mantiene intactos los nombres de fichero con espacios).
Dos builtins completan el panorama. set -o noclobber hace que > se niegue a sobrescribir un fichero existente (>| lo fuerza). Y exec > logfile redirige la salida estándar del propio shell durante el resto de un script.
Entrecomillado y expansión
El entrecomillado controla qué expansiones se producen.
- Las comillas simples
'…'lo anulan todo.'$HOME'son literalmente los cinco caracteres$HOME. - Las comillas dobles
"…"anulan el globbing y la división en palabras, pero siguen realizando las expansiones$var,$(cmd)y$((math)). - Una barra invertida escapa únicamente el carácter que la sigue.
La consecuencia práctica: un $file sin comillas que contiene my report.txt se convierte en dos argumentos tras la división en palabras. "$file" sigue siendo uno. Acostúmbrate a poner entre comillas dobles cada expansión de variable, salvo que quieras expresamente la división.
name="Ada Lovelace"
echo Hello $name # echo recibe 3 argumentos: Hello, Ada, Lovelace
echo "Hello $name" # echo recibe 1 argumento
echo 'Hello $name' # imprime Hello $name
echo "Today is $(date +%A)"
echo "Free memory: $(( $(grep MemAvailable /proc/meminfo | tr -s ' ' | cut -d' ' -f2) / 1024 )) MB"
Los patrones glob encajan con nombres de fichero: * cualquier secuencia de caracteres, ? un carácter, [a-z] un carácter de un conjunto, [!0-9] un carácter que no está en el conjunto. shopt -s globstar activa ** para la coincidencia recursiva (**/*.java). Una peculiaridad importante: por defecto, un patrón que no coincide con nada se pasa tal cual, así que rm *.bak en un directorio sin ficheros .bak intenta borrar un fichero llamado *.bak y falla. shopt -s nullglob hace que los patrones sin coincidencias se expandan a nada, que es lo que suelen querer los scripts.
La expansión de llaves no es globbing: genera cadenas existan o no los ficheros, como en mkdir -p project/{src,test,docs}, cp config.yml{,.bak} o echo {01..12}.
Variables y entorno
Se asigna sin espacios alrededor del =: retries=3. Se lee con $retries o, de forma más segura, con ${retries}. Bash ofrece muchas variables automáticas: $? es el código de salida del último comando, $$ el PID del shell, además de $HOME, $USER, $PWD, $RANDOM y $SECONDS.
Una asignación simple crea una variable del shell, privada de este proceso. export la promueve al entorno, el bloque de cadenas NAME=value que se entrega a cada proceso hijo. env y printenv listan el entorno; set lo lista todo, incluidas las variables del shell y las funciones.
JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
export JAVA_HOME # ahora visible para los programas que lances
export PATH="$JAVA_HOME/bin:$PATH" # al principio: se encuentra antes que el java del sistema
LANG=C sort names.txt # puntual: solo para este comando
unset JAVA_HOME
La expansión de parámetros ofrece valores por defecto y transformaciones que ahorran muchísimo código:
echo "${EDITOR:-nano}" # el valor, o "nano" si no está definida o está vacía
: "${DB_HOST:?must be set}" # aborta con un mensaje si no está definida
f=/var/log/app/server.log
echo "${f##*/}" # server.log (quita el prefijo más largo que encaja con */)
echo "${f%.log}" # /var/log/app/server (quita el sufijo)
echo "${#f}" # longitud de la cadena
Código de salida y encadenamiento de comandos
Todo comando termina con un código de salida entero: 0 significa éxito y cualquier otro valor, fallo (el significado de cada valor distinto de cero lo define el programa). Bash lo expone como $? y lo usa para el control de flujo. Tres operadores encadenan comandos:
make && sudo make install # instalar solo si la compilación ha ido bien
ping -c1 db01 || echo "db01 unreachable"
cd /tmp; ls # ejecutar ambos pase lo que pase
grep devuelve 1 cuando no encuentra nada, así que grep -q pattern file && do_something es idiomático. true y false existen únicamente para producir códigos de salida.
Trabajar más rápido: historial y edición de línea
Bash guarda un historial de comandos (history, que se escribe en ~/.bash_history al salir). Recuperarlos es más rápido que volver a teclearlos:
Ctrl-Ry escribe: búsqueda incremental hacia atrás; vuelve a pulsarCtrl-Rpara coincidencias más antiguas, Intro para ejecutar, Esc para editar.!!repite el último comando (sudo !!es el uso clásico);!$es el último argumento del comando anterior.Alt-.inserta de forma interactiva el último argumento del comando anterior.
El editor de línea usa por defecto los atajos de Emacs: Ctrl-A/Ctrl-E saltan al principio/final, Ctrl-W borra la palabra anterior, Ctrl-U borra hasta el principio de la línea, Ctrl-K hasta el final, Ctrl-L limpia la pantalla y Tab completa nombres de fichero, nombres de comando y —con el paquete bash-completion— opciones y argumentos de cientos de herramientas. Pulsa Tab dos veces para ver los candidatos.
Ctrl-C interrumpe el comando en curso; Ctrl-D en un prompt vacío envía fin de fichero y cierra la sesión; Ctrl-Z suspende el trabajo en primer plano (lo vemos junto con el resto del control de trabajos en Procesos y systemd).
Alias, funciones y ficheros de arranque
Un alias es un atajo textual que se expande cuando es la primera palabra de un comando: alias ll='ls -alF', alias gs='git status'. Los alias no admiten argumentos en medio; para eso, define una función:
mkcd() { mkdir -p -- "$1" && cd -- "$1"; }
Bash lee ficheros distintos según cómo se haya iniciado, y por eso la queja «lo añadí al .bashrc y no funciona» es tan habitual.
| Tipo de shell | Ficheros que lee | Ejemplo |
|---|---|---|
| Interactivo de login | /etc/profile y después el primero que exista de ~/.bash_profile, ~/.bash_login, ~/.profile | Sesión SSH, login en consola |
| Interactivo sin login | /etc/bash.bashrc (Debian) o /etc/bashrc (RHEL) y después ~/.bashrc | Nueva ventana de terminal, bash escrito en el prompt |
| No interactiva | Ninguno, salvo que BASH_ENV esté definida | Scripts, cron, ssh host command |
La organización habitual: pon las variables de entorno (PATH, JAVA_HOME, EDITOR) en ~/.profile para que se definan una sola vez al iniciar sesión y todo lo demás las herede, y pon los alias, las funciones, la configuración del prompt y las opciones de shopt en ~/.bashrc, porque hay que redefinirlos en cada shell interactivo. El ~/.profile por defecto de Debian y Ubuntu carga ~/.bashrc, así que ambos se aplican a las sesiones SSH. La configuración común a todos los usuarios va en un fichero nuevo dentro de /etc/profile.d/.
Después de editar, aplica los cambios al shell actual con source ~/.bashrc (o . ~/.bashrc); ejecutar bash ~/.bashrc lo haría en un proceso hijo que termina enseguida, sin cambiar nada.
Obtener ayuda sin salir de la terminal
Además de man y --help: help cd documenta un builtin (la página man de Bash tiene más de 100 páginas; help va directo al grano); info coreutils contiene los manuales completos de GNU; whatis cmd muestra el resumen de una línea; y /usr/share/doc/<package>/ guarda los README, ejemplos y registros de cambios que se instalan con cada paquete. Cuando la salida de un comando es larga, pásala por un pipe a less y busca con /.
Errores comunes
Espacios alrededor del =. count = 3 ejecuta un comando llamado count con dos argumentos. Las asignaciones no llevan espacios.
Variables sin comillas que contienen espacios o globs. rm $file con file="report *.txt" borra todos los ficheros .txt. Ponla entre comillas.
Esperar que .bashrc afecte a cron, a systemd o a los scripts. Los shells no interactivos no lo leen. Define PATH y las demás variables en el script o en la unidad.
El orden de las redirecciones. cmd 2>&1 > file sigue mostrando los errores en la terminal. Pon > file primero o usa &> file.
Ejecutar con sh scripts escritos para Bash. sh script.sh usa Dash en Debian y Ubuntu y falla con [[, los arrays y local -n. Usa el shebang y chmod +x, y luego ejecuta ./script.sh.
Usar cat file | grep x. No hace daño, pero grep x file es más sencillo y permite a grep mostrar los nombres de fichero cuando recibe varios. Reserva cat para concatenar.
Sobrescribir el fichero que estás leyendo. sort names.txt > names.txt trunca el fichero antes incluso de que sort lo lea, así que se queda vacío. Escribe en un fichero temporal y renómbralo, o usa sponge de moreutils.
Puntos clave
- Bash expande la línea de comandos —variables, globs, llaves, sustitución de comandos— antes de que se ejecute el programa; los programas reciben simples cadenas como argumentos.
- El orden de búsqueda de un comando es alias, función, builtin y, por último, búsqueda en
PATH;typerevela cuál se aplica. - Los tres flujos estándar y los operadores de redirección (
>,>>,2>,2>&1,<,|) son lo que permite combinar herramientas pequeñas. - Pon entre comillas dobles las expansiones de variables y entre comillas simples el texto literal con
$o barras invertidas. exportlleva una variable al entorno de los procesos hijos; las variables no exportadas se quedan en el shell.- El entorno va en
~/.profile, y los alias y la configuración del prompt en~/.bashrc; los shells no interactivos no leen ninguno de los dos. - Un código de salida 0 significa éxito, y
&&,||y$?te permiten actuar en consecuencia.
Fuentes y lecturas recomendadas
Preguntas frecuentes
- ¿Qué diferencia hay entre una variable del shell y una variable de entorno?
- Una variable del shell solo existe dentro del proceso del shell actual. Al exportarla (export NOMBRE=valor) se copia en el bloque de entorno que se entrega a cada proceso hijo que lanza el shell. Programas como java, python o curl solo ven las variables exportadas. Las variables definidas en un script nunca son visibles para el shell padre, porque un hijo no puede modificar la memoria de su padre.
- ¿Por qué mi alias o mi cambio en el PATH funciona en una terminal pero no en cron ni en un servicio de systemd?
- Los alias y el .bashrc solo los leen los shells interactivos. Las tareas de cron y los servicios se ejecutan sin terminal y con un entorno mínimo, así que no ven ni tus alias ni lo que añadiste al PATH en .bashrc. Usa rutas absolutas en los scripts, define PATH explícitamente al principio del script o declara el entorno en la propia unidad del servicio o en la crontab.
- ¿Cuándo debo usar comillas simples y cuándo dobles?
- Las comillas simples son literales: dentro no se expande nada, así que son las adecuadas para expresiones regulares, programas de awk y cualquier cosa con $ o barras invertidas que quieras conservar. Las comillas dobles mantienen juntos los espacios pero siguen expandiendo $variables y $(comandos), que es justo lo que quieres al pasar una variable como un único argumento. Regla práctica: pon entre comillas dobles cada referencia a una variable y entre comillas simples el texto fijo con caracteres especiales.
- ¿Qué significa 2>&1 y por qué importa el orden?
- 2>&1 convierte el descriptor 2 (error estándar) en una copia de lo que apunte en ese momento el descriptor 1 (salida estándar). Las redirecciones se aplican de izquierda a derecha, así que cmd > log 2>&1 envía ambos flujos al fichero, mientras que cmd 2>&1 > log copia primero stderr a la terminal y solo después apunta stdout al fichero. Bash también acepta la forma abreviada &> log.