Bloque I · Módulo 1 · Unidad 1.2
Terminal, shell, comandos y composición
Cómo interpreta una shell una línea, cómo circulan stdin, stdout y stderr, y cómo componer comandos sin perder errores ni códigos de salida.
Antes de empezar#
La shell no es una colección mágica de palabras. Es un programa que recibe texto, aplica reglas de análisis y expansión, conecta flujos, inicia otros programas y comunica cómo terminaron. Una línea corta puede contener varias operaciones distintas; entenderlas permite explicar qué ocurrió y evita corregir a ciegas.
Dependencia. La Unidad 1.1: capas, interfaces y estado. Aquí la shell se estudia como una aplicación en espacio de usuario que crea procesos y conecta interfaces del sistema operativo.
Alcance. Terminal, shell, comandos externos y builtins; argumentos y opciones; variables y expansiones; comillas; stdin, stdout y stderr; pipes; redirecciones; códigos de salida; composición condicional; type, which, --help, man y apropos.
Fuera de alcance. Scripts largos, funciones avanzadas, control de trabajos, portabilidad completa entre shells y memorización de catálogos de utilidades.
1. Terminal, shell y programa#
Una terminal proporciona una interfaz de entrada y salida. Puede ser una ventana gráfica, una consola o una sesión remota. La shell es el proceso que normalmente lee la línea, la interpreta y muestra un prompt nuevo al terminar. Un comando es la instrucción resultante; puede resolver a:
- un builtin ejecutado por la propia shell, como
cd; - una función o alias definidos en la sesión;
- un archivo ejecutable localizado mediante una ruta;
- una palabra reservada o una construcción del lenguaje de la shell.
El nombre escrito no basta para saber qué se ejecutará. Un alias, una función y un ejecutable pueden compartir nombre. type nombre pregunta a la propia shell cómo lo resolvería; command -V nombre ofrece una consulta similar. which suele buscar ejecutables en PATH, pero según la implementación puede no describir aliases, funciones o builtins con la misma precisión.
2. Cómo se forma un comando#
En una línea simple, la shell debe identificar palabras y operadores, aplicar quoting y expansiones, preparar redirecciones y ejecutar el resultado. Este detalle importa porque el programa no recibe la línea original: recibe una lista de argumentos ya construida.
printf '%s\n' -- "informe julio.txt"
El programa o builtin printf recibe tres argumentos relevantes:
- el formato
%s\n; --, usado aquí para cerrar el análisis de opciones;- una sola palabra:
informe julio.txt.
Sin comillas, el espacio separaría dos palabras. Las comillas no forman parte del argumento final; son instrucciones para la shell.
-- es una convención común para indicar que los argumentos siguientes ya no son opciones. No todos los programas están obligados a aceptarla, por lo que debe comprobarse en su documentación.
3. Comillas y expansión#
Las variables de shell almacenan texto. Al expandirlas, el contexto determina cuántos argumentos se producen y si caracteres como * pueden convertirse en nombres de archivo.
report="informe julio.txt"
printf '<%s>\n' "$report"
"$report" conserva el valor como un único argumento. En cambio, $report sin comillas queda expuesto a separación de palabras y expansión de patrones en los contextos donde esas reglas aplican.
| Forma | Qué protege | Qué permite |
|---|---|---|
‘texto’ | Conserva literalmente todos los caracteres entre comillas simples. | No expande variables ni sustituciones de comandos. |
“texto $var” | Evita separación de palabras y globbing del resultado. | Permite expansiones como $var y $(comando). |
texto\ con\ espacios | La barra protege el carácter siguiente. | Permite citar caracteres concretos sin encerrar toda la palabra. |
$var sin comillas | No conserva necesariamente una palabra. | Puede producir varias palabras y patrones de archivo. |
Las expansiones no son intercambiables:
$nameo${name}obtiene el valor de un parámetro;$(comando)sustituye la salida estándar del comando, retirando saltos de línea finales;$((expresión))realiza expansión aritmética;*.logpuede expandirse a nombres existentes mediante globbing.
4. Los tres flujos estándar#
Al iniciar un proceso, el sistema le proporciona normalmente tres descriptores:
| Descriptor | Nombre | Uso convencional |
|---|---|---|
0 |
stdin | datos de entrada |
1 |
stdout | resultado normal |
2 |
stderr | diagnóstico y errores |
La separación permite guardar datos sin mezclar mensajes de diagnóstico, o encadenar la salida útil mientras los errores siguen visibles.
entrada ──> stdin [ comando ] stdout ──> resultado
└── stderr ──> diagnóstico
Un programa decide qué escribe en cada flujo. La shell solo conecta descriptores. Que una frase contenga la palabra «error» no significa que se haya escrito en stderr, y que stderr esté vacío no garantiza éxito.
5. Redirecciones#
Una redirección cambia el origen o destino de un descriptor antes de ejecutar el comando:
comando < entrada.txt
comando > salida.txt
comando >> salida.txt
comando 2> errores.txt
> crea o trunca el archivo de destino; >> añade al final. Por eso una redirección puede destruir contenido aunque el comando parezca de lectura.
El orden se evalúa de izquierda a derecha:
comando > todo.log 2>&1
comando 2>&1 > solo-salida.log
En la primera línea, stdout apunta al archivo y después stderr se duplica hacia ese mismo destino. En la segunda, stderr se duplica primero hacia el stdout original —la terminal— y luego solo stdout cambia al archivo.
6. Pipelines#
El operador | conecta stdout del comando izquierdo con stdin del derecho:
productor | transformador | consumidor
Los procesos pueden ejecutarse a la vez. La shell crea las conexiones y cada programa ve un flujo, no una lista completa de resultados. stderr no entra en el pipe por defecto.
generar-informe 2> errores.log | grep 'WARN' > avisos.txt
Aquí:
- stdout de
generar-informealimenta agrep; - stderr de
generar-informeva aerrores.log; - stdout de
grepva aavisos.txt; - stderr de
grepconserva su destino anterior, normalmente la terminal.
7. Código de salida#
Cuando un comando termina, comunica un entero. Por convenio, 0 indica éxito y un valor distinto de cero indica que la operación no cumplió su contrato. El significado concreto de cada valor pertenece al comando.
En una shell interactiva, $? contiene el estado de la operación recién terminada:
command --option
status=$?
printf 'estado=%s\n' "$status"
Hay que capturarlo inmediatamente. Cualquier comando posterior, incluido printf, sustituye $?.
En un pipeline POSIX, el estado observado por defecto se deriva del último comando. Bash permite activar set -o pipefail para que el pipeline no parezca exitoso cuando un comando anterior falla; su resultado será el del comando no cero situado más a la derecha, o cero si todos terminan con éxito.
set -o pipefail
productor | filtro
status=$?
pipefail es estado de la shell actual. No debe activarse en un fragmento copiado sin entender qué listas o scripts dependen de la semántica anterior.
8. Componer decisiones#
Las listas && y || utilizan el código de salida:
comprobar && ejecutar
comprobar || informar
- con
&&, el segundo comando se ejecuta solo si el primero termina con cero; - con
||, se ejecuta solo si el primero termina con no cero.
Esto permite expresar una decisión corta, pero no reemplaza una estrategia de errores.
mkdir -- "$attempt_dir" &&
cp -- "$source" "$attempt_dir/input.txt"
La copia solo se intenta si el directorio pudo crearse. Aun así, faltan decisiones: qué ocurre si el destino ya existe, cómo se valida la variable, qué se registra y cómo se limpia un intento parcial.
9. Descubrir comandos y opciones#
La competencia estable no es recordar todas las banderas, sino localizar su contrato:
type printf
type -a printf
command --help
help printf
man 1 printf
apropos 'process status'
typeexplica cómo resolverá un nombre la shell;type -amuestra todas las resoluciones conocidas;helpdocumenta builtins de Bash;comando --helpsuele ofrecer una referencia breve cuando el programa la implementa;man sección nombreabre la página correspondiente instalada;aproposbusca términos en las descripciones del manual;whichpuede ayudar a localizar un ejecutable enPATH, pero no sustituye la vista de la shell.
Las secciones de man distinguen contratos con el mismo nombre. Por ejemplo, una utilidad puede estar en la sección 1 y una interfaz de programación en otra.
10. Texto humano y contratos#
Un pipeline suele transformar texto. Eso no convierte cualquier salida en una API estable. Columnas alineadas para lectura humana pueden cambiar con la versión, el ancho, el idioma o los datos.
Siempre que exista, prefiere una salida estructurada o una opción diseñada para scripting. Si debes analizar texto humano:
- limita el entorno y la versión;
- selecciona por significado, no por posición visual;
- conserva ejemplos con casos vacíos y caracteres especiales;
- detecta el fallo en lugar de producir datos plausibles;
- documenta el contrato asumido.
11. Copiar comandos con seguridad#
Una línea obtenida de documentación o de otra persona puede contener más de una operación: sustituciones, pipes, redirecciones y comandos condicionados. Antes de ejecutarla:
- separa sus operadores;
- identifica cada comando con
type; - revisa expansiones y destinos;
- consulta opciones desconocidas;
- comprueba privilegios y alcance;
- convierte primero las operaciones destructivas en observaciones;
- prepara verificación y rollback.
Evita también ejecutar directamente patrones como curl URL | sh. Descarga o inspecciona el contenido, verifica procedencia e integridad y entiende qué estado modificará.
12. Demostración completa#
Supongamos un archivo eventos.log con líneas de datos y una herramienta que puede producir diagnósticos. Queremos conservar los errores por separado, seleccionar eventos WARN y saber si toda la composición pudo completarse.
set -o pipefail
normalizar-eventos --input eventos.log 2> normalizar.err |
grep --fixed-strings 'level=WARN' > avisos.log
status=$?
printf 'pipeline=%s\n' "$status"
Podemos explicar la línea sin ejecutarla:
- la shell reconoce un pipeline, dos redirecciones y una asignación posterior;
normalizar-eventosrecibe dos argumentos después de su nombre;- su stderr se desvía a
normalizar.err; - su stdout alimenta stdin de
grep; grepbusca texto literal y su stdout se guarda enavisos.log;pipefailevita ignorar un fallo no cero del productor;status=$?conserva inmediatamente el resultado del pipeline;- todavía debemos consultar el contrato de ambos comandos para interpretar un estado no cero.
La explicación separa flujo de datos de flujo de control. Los bytes viajan por pipe y redirecciones; los estados deciden qué considera la shell un éxito.
13. Errores conceptuales frecuentes#
14. Síntesis y dominio#
Una shell convierte una línea en palabras, expansiones, redirecciones y procesos. Las comillas determinan cómo se forman los argumentos; los descriptores determinan por dónde circulan datos y diagnósticos; el estado de salida permite que otra operación decida qué hacer.
Has alcanzado el dominio esperado cuando puedes:
- distinguir terminal, shell, builtin y programa externo;
- enumerar los argumentos finales de una línea corta;
- explicar stdin, stdout y stderr de cada tramo de un pipeline;
- predecir el efecto del orden de dos redirecciones;
- conservar y razonar sobre el código de salida;
- localizar la documentación exacta de una opción;
- señalar el riesgo concreto de un comando copiado antes de ejecutarlo.
Prácticas relacionadas#
SYS-Q-102: distinguir terminal, shell, builtin, argumento y flujo.SYS-L-102: componer un pipeline sin perder stderr ni exit status.
15. Fuentes#
- GNU Bash Reference Manual
Secciones Shell Operation, Quoting, Shell Expansions, Redirections, Pipelines, Lists of Commands, Bourne Shell Builtins y Exit Status.
- The Open Group Base Specifications Issue 8 — Shell Command Language
Definición normativa de tokenización, quoting, expansión, redirecciones, pipelines, listas AND-OR y estado de salida POSIX.