En la lección anterior aprendiste a moverte por srv-veloz-01 y quedó una pregunta en el aire: por qué cd no puede ser un programa externo. Esa pregunta es la puerta de entrada a esta lección. Hasta ahora has usado el shell; ahora vas a entender qué ocurre exactamente entre que pulsas Enter y aparece el resultado. Este conocimiento no es teórico: explica por qué una variable definida en una tubería desaparece, por qué which a veces miente, por qué tu script "no encuentra" un comando que tú sí ves, y por qué source y ejecutar un fichero no son lo mismo. Son precisamente los errores que más tiempo hacen perder a quien programa en Bash sin este modelo mental.
Contenido
- Qué ocurre cuando pulsas Enter
- Los tipos de comando y cómo distinguirlos
- El
PATHy el orden de búsqueda - Procesos padre e hijo:
forkyexec - Subshells y por qué las variables no sobreviven
- Entorno frente a variables de shell
$?: el código de salida- Poniéndolo todo junto en Veloz Envíos
- Qué ocurre cuando pulsas Enter
Escribes ls -lh /var/log/veloz y pulsas Enter. Lo que parece instantáneo son en realidad seis fases bien definidas.
graph TD
A["Pulsas Enter"] --> B["1. Lectura y troceado<br/>divide la línea en palabras"]
B --> C["2. Expansiones<br/>~, $VAR, *, $(...), aritmética"]
C --> D["3. Resolución del comando<br/>alias → keyword → función → builtin → PATH"]
D --> E{"¿Es interno?"}
E -->|"Sí: builtin/función"| F["Ejecuta dentro del propio Bash"]
E -->|"No: ejecutable externo"| G["4. fork(): crea proceso hijo"]
G --> H["5. exec(): el hijo se convierte<br/>en el programa"]
H --> I["Bash espera con wait()"]
F --> J["6. Código de salida en $?"]
I --> J
J --> K["Nuevo prompt"]
Veamos cada fase.
Fase 1: lectura y troceado
Bash lee la línea completa y la divide en palabras usando espacios, tabuladores y saltos de línea como separadores. También identifica los operadores especiales (|, >, &&, ;). De ls -lh /var/log/veloz salen tres palabras.
Aquí ya aparece la primera fuente de errores. Si un nombre de fichero contiene espacios, el troceado lo parte en dos:
Bash entiende dos argumentos, no uno. Por eso las comillas son importantes: ls "mi informe.txt". Todo el detalle está en 03-06.
Fase 2: expansiones
Bash reescribe la línea antes de ejecutar nada. Sustituye:
| Expansión | Ejemplo | Se convierte en |
|---|---|---|
| Llaves | log-{a,b}.txt |
log-a.txt log-b.txt |
| Virgulilla | ~/veloz-ops |
/home/joan/veloz-ops |
| Parámetros | $HOME |
/home/joan |
| Comandos | $(date +%F) |
2026-08-03 |
| Aritmética | $((2 + 3)) |
5 |
| Rutas (globbing) | *.log |
acceso.log app.log |
Este punto es crucial y sorprende a mucha gente: el comando nunca ve los asteriscos. Cuando escribes ls *.log, quien expande el patrón es Bash, y ls recibe ya la lista de nombres. Puedes comprobarlo:
echo no sabe nada de comodines: recibió tres argumentos ya resueltos. El detalle completo de las expansiones se trata en 02-05 y 03-06; aquí basta con que sepas que ocurren antes.
Fase 3: resolución del comando
Bash toma la primera palabra y busca en un orden estricto qué es. Lo veremos en la sección 2, porque es donde se concentran las sorpresas.
Fase 4 y 5: fork y exec
Si el comando resulta ser un ejecutable externo, Bash no puede "convertirse" en él (dejaría de existir tu shell). Hace dos cosas:
fork(): crea una copia de sí mismo, un proceso hijo.exec(): el hijo reemplaza su propia imagen en memoria por la del programa a ejecutar.
El padre (tu Bash) se queda esperando con wait() a que el hijo termine. Por eso el prompt no vuelve hasta que el comando acaba, salvo que lo lances en segundo plano con &.
Fase 6: código de salida
Cuando el hijo muere, devuelve un número entre 0 y 255. Bash lo guarda en $?. Es la base de todo el control de errores en scripting, y lo veremos en la sección 7.
- Los tipos de comando y cómo distinguirlos
No todo lo que escribes en el prompt es un programa. Bash reconoce cinco categorías, y las busca en este orden exacto:
| Orden | Tipo | Qué es | Ejemplos |
|---|---|---|---|
| 1 | Alias | Sustitución de texto definida por ti | ll, ops |
| 2 | Keyword | Palabra reservada de la sintaxis de Bash | if, for, while, function, [[ |
| 3 | Función | Bloque de código que has definido | Tus funciones de lib/ |
| 4 | Builtin | Comando integrado en el propio Bash | cd, echo, export, source, type |
| 5 | Ejecutable externo | Un fichero encontrado en el PATH |
ls, grep, awk, date |
El orden importa. Si defines una función llamada ls, se usará en lugar del /usr/bin/ls. Y si además defines un alias ls, el alias gana a la función.
2.1 type: la herramienta fiable
type es un builtin de Bash y responde exactamente lo que Bash haría.
cd es una orden interna del shell ls es un alias de «ls --color=auto» if es una palabra clave del shell echo es una orden interna del shell
La opción -a muestra todas las coincidencias, en orden de prioridad. Es reveladora:
Existen dos echo: el builtin de Bash y el programa /usr/bin/echo de coreutils. Gana el builtin, siempre. Y no son idénticos: sus opciones difieren ligeramente entre sistemas, lo que provoca scripts que funcionan en una máquina y no en otra. Por eso en scripting profesional se prefiere printf, que es mucho más predecible (lo veremos en el Módulo 3).
Otro ejemplo muy ilustrativo con nuestro PATH de veloz-ops:
Aparecen dos rutas porque en los sistemas modernos /bin es un enlace simbólico a /usr/bin: es el mismo fichero visto de dos formas.
2.2 command -v: la versión para scripts
command -v imprime la ruta (o la definición) y devuelve un código de salida que indica si existe o no. Es la forma estándar y portable de comprobar disponibilidad en un script:
if command -v jq > /dev/null 2>&1; then
echo "jq disponible"
else
echo "Falta jq: instala con sudo apt install jq"
fiEste patrón lo usaremos de verdad en el Módulo 6, cuando veloz-ops necesite jq para hablar con la API.
2.3 which: por qué no debes fiarte
Observa: which cd no imprime nada. Y ahí está el problema. which es un programa externo (en muchos sistemas, un script) que se limita a recorrer el PATH buscando ficheros. No sabe nada de builtins, funciones ni keywords, porque no puede ver el interior de tu shell.
| Herramienta | Tipo | Ve builtins | Ve funciones | Ve alias | Portable |
|---|---|---|---|---|---|
type |
Builtin de Bash | Sí | Sí | Sí | Bash/ksh/zsh |
command -v |
Builtin POSIX | Sí | Sí | Sí | Sí, POSIX |
which |
Programa externo | No | No | Depende | Inconsistente |
La conclusión práctica: usa type -a para investigar de forma interactiva y command -v dentro de los scripts. Evita which. Un caso real de confusión: un compañero se queja de que time no existe porque which time no devuelve nada; en realidad time es una keyword de Bash, y type time lo aclara al instante.
- El
PATH y el orden de búsqueda
PATH y el orden de búsquedaCuando Bash llega al paso 5 (ejecutable externo), recorre los directorios de PATH de izquierda a derecha y ejecuta la primera coincidencia.
Puntos importantes:
- La búsqueda se detiene en la primera coincidencia. Si hubiera un
lsen~/veloz-ops/bin, se ejecutaría ese y no el del sistema. - El orden lo decides tú. Poner tu directorio delante da prioridad a tus herramientas; ponerlo detrás es más seguro.
- Bash cachea las rutas encontradas para no repetir la búsqueda. Si instalas un programa nuevo y Bash sigue diciendo que no existe, limpia la caché:
O consulta qué tiene memorizado:
Este es un problema real y frustrante: acabas de mover un script de sitio, lo ejecutas y Bash intenta lanzarlo desde la ruta antigua. hash -r lo resuelve en un segundo.
3.1 Por qué ./script.sh y no script.sh
Si estás en ~/veloz-ops/bin y escribes informe.sh, Bash buscará en el PATH. Como el directorio actual no está en el PATH (y por seguridad no debe estarlo, según vimos en 01-02), no lo encontrará:
La solución es indicar una ruta explícita: ./informe.sh. En cuanto una palabra contiene una barra /, Bash deja de buscar en el PATH y la trata como ruta directa.
Con nuestra configuración de veloz-ops hay un matiz agradable: como añadimos ~/veloz-ops/bin al PATH, los scripts que pongamos ahí sí se podrán invocar por su nombre desde cualquier directorio, igual que ls. Es exactamente el efecto que buscábamos al montar el toolkit.
- Procesos padre e hijo:
fork y exec
fork y execCada proceso en Linux tiene un identificador (PID) y un padre (PPID). Tu shell también.
$$ es el PID del shell actual y $PPID el de su padre (normalmente el emulador de terminal o sshd).
Comprobemos la relación padre-hijo en directo:
El hijo tiene un PID distinto y su padre es tu shell. Esa es la mecánica del fork.
Cuando el hijo termina, todo lo que hizo en su propia memoria desaparece: variables, directorio actual, funciones. Solo sobrevive lo que escribió en disco o envió por su salida estándar. Este principio explica la mitad de los "misterios" de Bash.
4.1 La excepción: exec sin fork
Si usas exec explícitamente, Bash no crea un hijo: se reemplaza a sí mismo por el programa.
Se muestra la fecha y la terminal se cierra, porque tu shell ya no existe: se convirtió en date, y date terminó. Es una herramienta especializada, muy usada en los entrypoint.sh de contenedores Docker para que el proceso de la aplicación herede el PID 1 y reciba correctamente las señales de parada. No la uses a la ligera.
- Subshells y por qué las variables no sobreviven
Un subshell es un proceso hijo que también es un Bash. Se crean en más situaciones de las que la gente sospecha.
| Construcción | ¿Crea subshell? | Ejemplo |
|---|---|---|
( comandos ) |
Sí | (cd /tmp; ls) |
| Tubería ` | ` | Sí, para cada lado (en Bash por defecto) |
Sustitución $( ) |
Sí | hoy=$(date +%F) |
Segundo plano & |
Sí | tarea & |
{ comandos; } |
No, agrupa en el shell actual | { cd /tmp; ls; } |
source fichero |
No | source ~/.bashrc |
./script.sh |
Sí | Ejecuta un fichero |
5.1 El experimento clave
contador=0
echo "Antes: $contador"
( contador=99; echo "Dentro del subshell: $contador" )
echo "Después: $contador"Los paréntesis crearon un proceso hijo con una copia de las variables. Ese hijo modificó su copia y murió. El padre nunca se enteró. No es un fallo: es cómo funcionan los procesos en Unix. La información fluye del padre al hijo, nunca al revés.
Compara con las llaves:
Las llaves solo agrupan; no hay proceso nuevo. (Atención a la sintaxis: las llaves necesitan espacios interiores y un ; antes de la de cierre.)
5.2 El caso que arruina scripts de verdad
Este es el error clásico, y lo planteamos con datos de Veloz Envíos. Queremos contar cuántas líneas de app.log son errores:
errores=0
grep 'ERROR' /var/log/veloz/app.log | while read -r linea; do
errores=$((errores + 1))
done
echo "Errores encontrados: $errores"Cero, pese a que sí hay errores en el fichero. ¿Por qué? Porque el lado derecho de la tubería se ejecuta en un subshell. El bucle incrementó correctamente su propia copia de errores hasta, digamos, 47; después el subshell terminó y la copia se evaporó. El echo final lee la variable del shell padre, que sigue valiendo 0.
Hay tres soluciones, y conviene conocerlas todas:
# Solución 1: redirección de entrada en lugar de tubería
errores=0
while read -r linea; do
errores=$((errores + 1))
done < <(grep 'ERROR' /var/log/veloz/app.log)
echo "Errores encontrados: $errores"La construcción < <(...) se llama sustitución de procesos: el grep se ejecuta aparte, pero el bucle while corre en el shell principal, así que la variable sobrevive. Se estudia a fondo en 05-05.
# Solución 2: activar la opción lastpipe (solo Bash, requiere job control desactivado)
shopt -s lastpipe
errores=0
grep 'ERROR' /var/log/veloz/app.log | while read -r linea; do
errores=$((errores + 1))
done
echo "Errores encontrados: $errores"Con lastpipe, el último comando de la tubería se ejecuta en el shell actual. Funciona en scripts, pero no en sesiones interactivas normales.
# Solución 3: no usar el shell para contar
errores=$(grep -c 'ERROR' /var/log/veloz/app.log)
echo "Errores encontrados: $errores"Esta es la mejor: grep -c cuenta por sí mismo y la sustitución de comandos captura su salida. Cuando existe una herramienta que ya hace el trabajo, no lo hagas con un bucle. Es más rápido, más corto y no tiene el problema del subshell.
5.3 El mismo principio explica source
Ahora se entiende del todo lo que vimos en 01-02:
./config.sh # ejecuta en un subshell
echo "$CIUDAD" # vacío
source config.sh # ejecuta en el shell actual
echo "$CIUDAD" # ValenciaY por eso cd tiene que ser un builtin: si fuera un programa externo, se ejecutaría en un hijo, cambiaría el directorio de ese hijo y moriría. Tu directorio no se movería un milímetro. Aquella pregunta pendiente de la lección anterior queda resuelta.
- Entorno frente a variables de shell
Hay dos clases de variables, y la diferencia es exactamente la de la sección anterior: qué se hereda.
- Variable de shell: existe solo en el shell actual. No se pasa a los hijos.
- Variable de entorno: se copia al entorno de cada proceso hijo.
local_var="solo aquí"
export global_var="viaja a los hijos"
bash -c 'echo "local_var=[$local_var] global_var=[$global_var]"'El hijo no vio local_var porque nunca se exportó.
6.1 Herramientas para inspeccionar
| Comando | Qué muestra |
|---|---|
env |
Variables de entorno (las exportadas) |
printenv |
Igual que env; admite printenv NOMBRE |
set |
Todas las variables y funciones del shell actual |
declare -p |
Variables con su tipo y atributos |
export -p |
Solo las exportadas, con la sintaxis declare -x |
/home/joan /home/joan/veloz-ops/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin joan
Variables de entorno que te encontrarás siempre:
| Variable | Contenido |
|---|---|
HOME |
Tu carpeta personal |
USER |
Tu nombre de usuario |
PATH |
Directorios de búsqueda de comandos |
PWD |
Directorio actual |
OLDPWD |
Directorio anterior (lo que usa cd -) |
SHELL |
Shell de login configurado |
LANG |
Idioma y codificación |
TERM |
Tipo de terminal |
EDITOR |
Editor preferido |
6.2 Ejecutar con un entorno modificado
Puedes definir una variable solo para un comando, sin tocar tu shell:
VELOZ_ENV=pruebas bash -c 'echo "Entorno: $VELOZ_ENV"'
echo "En mi shell sigue siendo: [$VELOZ_ENV]"Esta sintaxis —asignación delante del comando, sin ;— es limpísima y muy usada en la práctica, por ejemplo para forzar un idioma en la salida de un comando:
Con LC_ALL=C la fecha sale en inglés. Es un truco importante en scripts: si tu script analiza la salida de un comando, fuerza LC_ALL=C para que no dependa del idioma del servidor. Un grep que busca "ago" fallaría en una máquina configurada en inglés.
Y para ejecutar algo con un entorno completamente limpio:
env -i borra todo el entorno. Es la mejor forma de simular las condiciones de cron, que ejecuta con un entorno mínimo. Si tu script funciona así, funcionará en cron. Lo aplicaremos en 07-01.
$?: el código de salida
$?: el código de salidaTodo comando devuelve al terminar un número entre 0 y 255:
- 0 significa éxito.
- Cualquier otro valor significa error.
Es al revés de lo que sugiere la intuición, pero tiene sentido: hay una única forma de acertar y muchas de fallar, así que cada valor distinto de cero puede identificar un tipo de fallo.
ls /var/log/veloz > /dev/null
echo "Código: $?"
ls /directorio/inexistente 2> /dev/null
echo "Código: $?"Códigos con significado convencional:
| Código | Significado |
|---|---|
| 0 | Éxito |
| 1 | Error genérico |
| 2 | Uso incorrecto del comando (argumentos mal) |
| 126 | El fichero existe pero no es ejecutable |
| 127 | Comando no encontrado |
| 130 | Interrumpido con Ctrl+C (128 + señal 2) |
| 137 | Terminado con kill -9 (128 + señal 9) |
Comprobémoslos:
Un detalle que causa errores sutiles: $? se sobrescribe con cada comando, incluido el echo que la muestra.
La segunda lectura devuelve 0 porque es el resultado del echo anterior, que funcionó bien. Si necesitas el valor más de una vez, guárdalo inmediatamente:
Un caso especial que merece atención: en una tubería, $? es el código del último comando, no del primero.
Aunque grep falló, wc -l funcionó, así que la tubería declara éxito. Es una fuente de errores silenciosos en scripts. Se resuelve con set -o pipefail o consultando el array PIPESTATUS, temas de la lección 05-03.
Los códigos de salida son la base de &&, ||, if y del control de errores en general. Aquí solo era un primer contacto; el uso completo llega en 03-03 y 03-04.
- Poniéndolo todo junto en Veloz Envíos
Un escenario realista que combina todo lo anterior. Un compañero te dice: "He escrito un script veloz-estado que cuenta los errores del log, pero devuelve siempre 0 y encima cuando lo lanzo desde cron dice que no encuentra el comando."
Diagnostiquemos paso a paso.
Paso 1: ¿qué es realmente veloz-estado?
Existe y está en el PATH. Pero cron no lee tu ~/.bashrc (lo vimos en 01-02), así que su PATH no incluye ~/veloz-ops/bin. Simulemos el entorno de cron:
Reproducido. La solución: usar la ruta absoluta en el crontab, o definir el PATH dentro del propio script.
Paso 2: ¿por qué cuenta 0?
El script contiene esto:
total=0
grep 'ERROR' /var/log/veloz/app.log | while read -r linea; do
total=$((total + 1))
done
echo "Total de errores: $total"Ya sabes el diagnóstico: el while corre en un subshell y total no sobrevive. La corrección:
Paso 3: ¿y si el fichero no existe?
Si el log no existe, grep devuelve 2 y total queda vacío. Guardar el código permite reaccionar. En 03-04 aprenderás a convertir eso en una condición, y en 05-03 a hacerlo de forma robusta.
Paso 4: verificación final del entorno
echo "PID del shell: $$"
echo "Comando resuelto: $(command -v veloz-estado)"
echo "Directorio: $PWD"
VELOZ_ENV=produccion veloz-estado
echo "Salida del script: $?"Este pequeño ritual —saber qué se va a ejecutar, con qué entorno y qué devolvió— es exactamente lo que distingue a alguien que depura con método de alguien que prueba cosas al azar.
Errores Comunes y Consejos
- Usar
whichpara comprobar si un comando existe. No ve builtins ni funciones y su comportamiento varía entre sistemas. Usacommand -ven scripts ytype -ade forma interactiva. - Esperar que una variable modificada dentro de una tubería sobreviva. El lado derecho de
|es un subshell. Usa< <(...),lastpipe, o mejor aún, una herramienta que ya haga el cálculo (grep -c,wc -l,awk). - Ejecutar un fichero de configuración con
./config.shesperando que defina variables. Necesitassource config.sh. - Olvidar
exporty extrañarse de que el script hijo no vea la variable. Sinexport, la variable no sale del shell actual. - Leer
$?demasiado tarde. Cualquier comando intermedio la sobrescribe, incluido unecho. Guárdala en cuanto la necesites. - Suponer que una tubería falla si falla el primer comando.
$?refleja el último. Usaset -o pipefail(05-03). - Modificar un script, moverlo y que Bash siga ejecutando la versión antigua. Es la caché de rutas:
hash -r. - Dar por hecho que el entorno de cron es el tuyo. No lo es. Prueba tus scripts con
env -iantes de programarlos. - Consejo: cuando algo se comporte de forma inexplicable, hazte estas tres preguntas por orden: ¿qué es realmente este comando (
type -a)?, ¿se está ejecutando en un subshell?, ¿qué variables hay en el entorno (env)?. Cubren la mayoría de los casos. - Consejo:
bash -x script.shmuestra cada línea tras las expansiones, antes de ejecutarla. Es la forma más rápida de ver el proceso de la sección 1 en acción. Lo formalizaremos en 05-03.
Ejercicios
Ejercicio 1: Clasificar comandos
Sin ejecutar nada, predice qué tipo de comando es cada uno (alias, keyword, función, builtin o externo) y luego verifica con type -a:
cdgrep[[exportforawk
Explica además por qué which cd no devuelve nada.
Ejercicio 2: El contador que no cuenta
Este script pretende contar cuántos envíos con estado incidencia hay en /srv/veloz/datos/envios.csv, pero siempre imprime 0:
#!/usr/bin/env bash
incidencias=0
cat /srv/veloz/datos/envios.csv | while IFS=',' read -r id fecha ciudad repartidor estado importe; do
if [ "$estado" = "incidencia" ]; then
incidencias=$((incidencias + 1))
fi
done
echo "Incidencias: $incidencias"- Explica exactamente por qué falla.
- Corrígelo de dos formas distintas.
- Indica cuál de las dos preferirías en
veloz-opsy por qué.
Ejercicio 3: Entorno y códigos de salida
Responde ejecutando lo necesario:
- Define una variable
CIUDAD="Bilbao"sin exportar y comprueba si unbash -chijo la ve. Repite exportándola. - Ejecuta un comando que devuelva el código 127 y otro que devuelva el 2, y muestra ambos códigos.
- Ejecuta
grep 'ERROR' /fichero/que/no/existe | wc -ly explica por qué$?vale 0. - Comprueba si
jqestá instalado en tu sistema usandocommand -vdentro de unif.
Soluciones
Solución al Ejercicio 1
cd es una orden interna del shell grep es /usr/bin/grep [[ es una palabra clave del shell export es una orden interna del shell for es una palabra clave del shell awk es /usr/bin/awk
| Comando | Tipo | Razón |
|---|---|---|
cd |
Builtin | Debe cambiar el directorio del shell actual |
grep |
Externo | Programa independiente en /usr/bin |
[[ |
Keyword | Parte de la sintaxis; Bash la analiza de forma especial (por eso no hay división de palabras dentro) |
export |
Builtin | Modifica el entorno del shell actual |
for |
Keyword | Estructura de control del lenguaje |
awk |
Externo | Es un lenguaje completo con su propio intérprete |
which cd no devuelve nada porque which es un programa externo que solo recorre los directorios del PATH buscando ficheros. cd no es un fichero: es código dentro del propio Bash. Un proceso externo no puede ver los builtins de su padre.
Solución al Ejercicio 2
-
Por qué falla: el bucle
whileestá al lado derecho de una tubería, así que Bash lo ejecuta en un subshell. Ese subshell recibe una copia deincidencias, la incrementa correctamente y muere al terminar el bucle. El shell principal nunca ve el cambio, y elechofinal lee su propia variable, que sigue valiendo 0. Es exactamente el problema de la sección 5.2. -
Corrección A: sustitución de procesos (elimina la tubería, el bucle corre en el shell principal):
#!/usr/bin/env bash
incidencias=0
while IFS=',' read -r id fecha ciudad repartidor estado importe; do
if [ "$estado" = "incidencia" ]; then
incidencias=$((incidencias + 1))
fi
done < /srv/veloz/datos/envios.csv
echo "Incidencias: $incidencias"Nótese que aquí ni siquiera hace falta < <(...): como solo queríamos leer un fichero, basta con una redirección directa < fichero. El cat original era innecesario (es el llamado uso inútil de cat).
Corrección B: delegar el conteo en una herramienta:
#!/usr/bin/env bash
incidencias=$(grep -c ',incidencia,' /srv/veloz/datos/envios.csv)
echo "Incidencias: $incidencias"O de forma más precisa, comprobando exactamente el quinto campo con awk (Módulo 6):
- Cuál preferir: la B, con
awk. Motivos: es una sola línea, no tiene el problema del subshell, y es mucho más rápida en ficheros grandes. Un buclewhile readen Bash procesa unas pocas miles de líneas por segundo;awkprocesa cientos de miles. Con unenvios.csvde 11 MB la diferencia es de segundos frente a milisegundos. La regla general de Bash: el shell es para orquestar, no para procesar datos línea a línea. Lo veremos formalizado en 08-02.
Solución al Ejercicio 3
# 1
CIUDAD="Bilbao"
bash -c 'echo "Sin export: [$CIUDAD]"'
export CIUDAD
bash -c 'echo "Con export: [$CIUDAD]"'Sin export, CIUDAD es una variable de shell y no forma parte del entorno que hereda el hijo. Con export, pasa a formar parte del entorno y viaja en la copia.
# 2
comando_inexistente_veloz
echo "Código A: $?"
ls --opcion-invalida 2> /dev/null
echo "Código B: $?"127 es el código convencional para "comando no encontrado" y 2 para "uso incorrecto".
$? vale 0 porque en una tubería refleja el estado del último comando. grep falló con código 2, pero wc -l recibió una entrada vacía, contó cero líneas y terminó correctamente. Para detectar el fallo real:
El array PIPESTATUS guarda el código de cada elemento. La alternativa habitual en scripts es set -o pipefail, que hace que la tubería devuelva el primer código distinto de cero (05-03).
# 4
if command -v jq > /dev/null 2>&1; then
echo "jq está instalado en $(command -v jq)"
else
echo "jq NO está instalado"
fiLa redirección > /dev/null 2>&1 descarta tanto la salida normal como los errores: solo nos interesa el código de salida de command -v, no su texto. Las redirecciones se estudian en 02-04.
Conclusión
Ya tienes el modelo mental que sostiene todo lo que viene. Sabes que Bash trocea la línea, la expande, resuelve el comando en un orden estricto (alias → keyword → función → builtin → PATH) y, si es externo, hace fork y exec para ejecutarlo en un hijo. Entiendes por qué type -a es fiable y which no, cómo funciona la caché del PATH, y —lo más rentable de todo— por qué las variables no sobreviven a un subshell, lo que explica de una vez el comportamiento de las tuberías, los paréntesis, source y el propio cd. Y has tenido tu primer contacto con $?, la pieza sobre la que se construirá todo el control de errores del curso.
Con esto cierras el conocimiento fundamental del shell. Queda una habilidad transversal antes de pasar a los comandos: saber resolver dudas por tu cuenta. Ningún profesional recuerda todas las opciones de find ni el formato exacto de un crontab; lo que sabe es dónde mirarlo en diez segundos. En la siguiente lección, Encontrar Ayuda: man, help y --help, aprenderás a leer páginas de manual, a distinguir cuándo usar man y cuándo help, y a verificar comandos peligrosos antes de ejecutarlos en srv-veloz-01.
Curso de Programación en Bash
Módulo 1: Introducción a Bash
- ¿Qué es Bash?
- Configurando tu Entorno
- Navegación Básica en la Línea de Comandos
- Entendiendo el Shell
- Encontrar Ayuda: man, help y --help
Módulo 2: Comandos Básicos de Bash
- Operaciones con Archivos y Directorios
- Comandos de Procesamiento de Texto
- Permisos y Propiedad de Archivos
- Redirección y Tuberías
- Comodines y Expansión de Rutas
- Historial y Atajos de Teclado
Módulo 3: Fundamentos de Scripting
- Creando y Ejecutando un Script
- Variables y Constantes
- Operadores Básicos
- Sentencias Condicionales
- Argumentos y Entrada del Usuario
- Comillas, Expansión y Sustitución
Módulo 4: Scripting Intermedio
- Bucles en Bash
- Funciones en Bash
- Arrays y Arrays Asociativos
- Manipulación de Cadenas
- La Sentencia case y los Menús Interactivos
- Aritmética y Cálculos Numéricos
Módulo 5: Técnicas Avanzadas de Scripting
- Operaciones Avanzadas con Archivos
- Gestión de Procesos
- Manejo de Errores y Depuración
- Expresiones Regulares
- Entrada/Salida Avanzada: Descriptores y Here-Documents
- Scripts Modulares y Librerías Reutilizables
Módulo 6: Trabajando con Herramientas Externas
Módulo 7: Automatización y Programación
- Trabajos Cron
- Automatizando Tareas
- Scripts de Respaldo y Restauración
- Monitoreo y Registro
- Servicios y Temporizadores con systemd
- Automatización Remota con SSH
Módulo 8: Mejores Prácticas y Optimización
- Escribiendo Código Legible
- Optimizando Scripts en Bash
- Consideraciones de Seguridad
- Control de Versiones con Git
- Análisis Estático con ShellCheck y shfmt
- Pruebas Automatizadas con Bats
- Portabilidad: POSIX sh frente a Bashismos
