Cerraste el Módulo 2 con criterio para decidir quién accede a qué. Empiezas el Módulo 3 con algo más íntimo: el sitio donde trabajas. Cada vez que abres una sesión en srv-tramontana, Bash construye a tu alrededor un entorno —un conjunto de variables, atajos, rutas de búsqueda y memoria de lo que ya hiciste— que determina cómo se comportan todos los comandos que ejecutes después. Hasta ahora lo has usado tal como venía de fábrica. A partir de aquí lo entiendes y lo moldeas.

No es un capricho estético. El 90 % de los «pero si a mí me funcionaba» de un administrador de sistemas —el script que va desde tu terminal y falla en cron, el sort que ordena distinto en tu portátil y en el servidor, el comando que existe para operador pero no para root— son problemas de entorno. Entender qué se hereda, qué no y desde qué fichero se carga cada cosa es lo que separa depurar en cinco minutos de perder una tarde.

Contenido

  1. Qué es el entorno de un proceso
  2. Variables de shell y variables de entorno
  3. La herencia hacia los procesos hijos
  4. PATH: cómo encuentra Bash un ejecutable
  5. Las otras variables que importan
  6. Expansión de variables y comillas, con precisión
  7. Sustitución de comandos
  8. Alias
  9. Shells de login, interactivos y no interactivos
  10. Personalizar el prompt PS1
  11. Historial persistente y su riesgo de seguridad
  12. El ~/.bashrc recomendado para operador

  1. Qué es el entorno de un proceso

Cuando el kernel arranca un proceso, le entrega tres cosas: los argumentos de la línea de comandos, los descriptores de archivo abiertos (que verás en 03-04) y un vector de cadenas CLAVE=valor llamado entorno. No es magia de Bash: es una estructura del sistema operativo, disponible para cualquier programa escrito en cualquier lenguaje.

Como en Linux todo es un archivo, puedes verlo directamente:

operador@srv-tramontana:~$ tr '\0' '\n' < /proc/$$/environ | head -6
SHELL=/bin/bash
PWD=/home/operador
LOGNAME=operador
HOME=/home/operador
LANG=es_ES.UTF-8
USER=operador

$$ es el PID del shell actual. El fichero environ guarda las variables separadas por bytes nulos, por eso hace falta tr para leerlas. Lo importante: ese contenido se fijó cuando el proceso arrancó. Es una fotografía, no un enlace vivo.

  1. Variables de shell y variables de entorno

Bash maneja dos conjuntos que se parecen y no son lo mismo.

Variable de shell Variable de entorno
Se crea con VAR=valor export VAR=valor
La ve el shell actual Sí Sí
La ven los procesos hijos No Sí
Se lista con set env o printenv
Uso típico trabajo temporal en la sesión configurar programas

La asignación tiene una regla que provoca errores el primer día: no puede haber espacios alrededor del =.

operador@srv-tramontana:~$ RELEASE=3.2.1
operador@srv-tramontana:~$ RELEASE = 3.2.1
RELEASE: orden no encontrada

Con espacios, Bash interpreta RELEASE como un comando y = y 3.2.1 como sus argumentos. Si el valor lleva espacios, hay que entrecomillarlo: MENSAJE="despliegue ok".

Para promover una variable ya existente a variable de entorno basta export RELEASE. Para eliminar cualquiera de las dos, unset RELEASE.

Ver una en concreto:

printenv VARIABLE imprime su valor y devuelve 1 si la variable no está en el entorno, aunque exista como variable de shell. Es la forma más rápida de comprobar si te falta un export.

  1. La herencia hacia los procesos hijos

Este es el concepto que hay que dejar cerrado, porque explica una familia entera de errores. La herencia es unidireccional y en el momento del arranque: el hijo recibe una copia del entorno del padre, y nada de lo que el hijo haga vuelve al padre.

operador@srv-tramontana:~$ export ENTORNO=produccion
operador@srv-tramontana:~$ bash -c 'echo "el hijo ve: $ENTORNO"; ENTORNO=pruebas'
el hijo ve: produccion
operador@srv-tramontana:~$ echo "el padre sigue con: $ENTORNO"
el padre sigue con: produccion

El hijo cambió su copia y murió con ella. Por eso un script no puede cambiar el directorio de trabajo de tu terminal, ni definirte una variable, salvo que lo ejecutes con source (o su sinónimo .), que no crea un proceso hijo: lee el fichero en el shell actual.

operador@srv-tramontana:~$ echo 'VERSION_APP=3.2.1' > /tmp/vars.sh
operador@srv-tramontana:~$ bash /tmp/vars.sh; echo "[$VERSION_APP]"     # hijo: se pierde
[]
operador@srv-tramontana:~$ source /tmp/vars.sh; echo "[$VERSION_APP]"   # shell actual
[3.2.1]

Los paréntesis crean un subshell, que es un hijo más y se comporta igual: ( cd /opt/tramontana/app && pwd ) imprime /opt/tramontana/app y te deja donde estabas. Ese patrón es útil de verdad: te desplazas, haces algo y vuelves solo, sin arriesgarte a olvidar un cd -.

  1. PATH: cómo encuentra Bash un ejecutable

En el Módulo 2 quedó como noción. Ahora en detalle. PATH es una lista de directorios separados por :, y Bash la recorre de izquierda a derecha, parándose en la primera coincidencia.

operador@srv-tramontana:~$ echo "$PATH"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
operador@srv-tramontana:~$ type -a python3
python3 is /usr/bin/python3

type -a muestra todas las coincidencias en orden; si hubiera dos versiones instaladas, verías cuál gana. Bash además cachea las rutas resueltas: si instalas un binario que enmascara a otro y sigue ejecutándose el antiguo, hash -r limpia esa caché.

Para añadir /home/operador/scripts, la forma correcta es anteponer o añadir sin destruir lo que ya había:

operador@srv-tramontana:~$ export PATH="$HOME/scripts:$PATH"

Fíjate en dos detalles. Primero, se reutiliza $PATH dentro del nuevo valor; escribir PATH=/home/operador/scripts a secas deja el sistema sin ls ni sudo hasta que cierres la sesión. Segundo, va entrecomillado.

Poniéndolo delante, tus scripts tienen prioridad sobre los del sistema, lo que permite sobrescribir un comando a propósito; poniéndolo detrás (PATH="$PATH:$HOME/scripts") el sistema gana siempre, que es más conservador. En Tramontana usamos el segundo orden para las cuentas administrativas.

Por qué nunca se pone . en el PATH

Incluir el directorio actual parece cómodo y es un agujero de seguridad clásico. Imagina que . está el primero y que Luis, depurando, deja en /srv/tramontana/backups/temporales/ un archivo llamado ls con contenido malicioso. Entras ahí a mirar, escribes ls, y en lugar del comando del sistema ejecutas el archivo de ese directorio con tus privilegios. Con sudo de por medio, es una escalada de privilegios completa.

La regla es absoluta: . no va en el PATH, ni al principio ni al final. Para ejecutar algo del directorio actual se escribe ./programa, explícitamente. Esos dos caracteres son la diferencia entre una decisión consciente y un accidente.

  1. Las otras variables que importan

Variable Qué contiene Nota práctica
HOME /home/operador Es lo que expande ~ y adonde va cd sin argumentos
USER / LOGNAME operador Informativas; para identidad real usa id -un
SHELL /bin/bash Es tu shell de login, no necesariamente el que corre ahora
PWD / OLDPWD directorio actual y anterior cd - usa OLDPWD
LANG / LC_ALL idioma y locale Afecta a mensajes, orden y formato de fechas
EDITOR / VISUAL editor por defecto Lo usan crontab -e, git, visudo
TERM tipo de terminal xterm-256color en local, vt100 en consolas viejas
PS1 cadena del prompt La personalizamos en el apartado 10

La locale merece un párrafo propio porque produce diferencias silenciosas entre máquinas:

operador@srv-tramontana:~$ printf 'casa\nCasa\ncal-ferrer\nCan-Ventos\n' | sort | tr '\n' ' '
cal-ferrer Can-Ventos casa Casa
operador@srv-tramontana:~$ printf 'casa\nCasa\ncal-ferrer\nCan-Ventos\n' | LC_ALL=C sort | tr '\n' ' '
Can-Ventos Casa cal-ferrer casa

Con es_ES.UTF-8, sort ignora mayúsculas y guiones y ordena «como un diccionario». Con LC_ALL=C ordena por valor de byte, y todas las mayúsculas van antes que las minúsculas. Cuando el orden tiene que ser reproducible —comparar dos listados, generar una suma de verificación— fija LC_ALL=C. Fíjate también en que la asignación va delante del comando, sin export: eso define la variable solo para esa ejecución, sin tocar tu sesión. Es el modo más limpio de probar cosas.

LC_ALL pisa a todas las demás LC_* y a LANG; por eso es la que se usa para forzar. El mismo efecto aparece en las fechas: date te dará «lun ago 18» o «Mon Aug 18» según la locale, y si un script recorta esa salida por posición, se rompe al cambiar de máquina.

  1. Expansión de variables y comillas, con precisión

El Módulo 2 dio las comillas por encima. Aquí va la regla exacta, que es de las tres o cuatro cosas más rentables de todo el curso.

Forma Se expanden variables Se hace globbing Se parte por espacios
$VAR (sin comillas) Sí Sí, sobre el resultado Sí
"$VAR" Sí No No
'$VAR' No No No

La diferencia entre las dos primeras filas rompe scripts todos los días:

operador@srv-tramontana:~$ RUTA="/home/operador/datos/informe agosto.txt"
operador@srv-tramontana:~$ touch "$RUTA"
operador@srv-tramontana:~$ ls -l $RUTA
ls: no se puede acceder a '/home/operador/datos/informe': No existe el archivo
ls: no se puede acceder a 'agosto.txt': No existe el archivo
operador@srv-tramontana:~$ ls -l "$RUTA"
-rw-r----- 1 operador operador 0 ago 18 10:12 '/home/operador/datos/informe agosto.txt'

Sin comillas, Bash parte el valor por el espacio y ls recibe dos argumentos. La regla de oro: entrecomilla siempre "$VAR", salvo que quieras deliberadamente que se parta en palabras.

Las llaves ${VAR} delimitan el nombre cuando lo que sigue podría confundirse con parte de él. Con V=3.2.1, echo "release-$V_final" imprime release- —Bash busca una variable V_final, que no existe— mientras que echo "release-${V}_final" imprime release-3.2.1_final.

Dos expansiones con valor por defecto que usarás constantemente:

operador@srv-tramontana:~$ echo "Destino: ${BACKUP_DIR:-/srv/tramontana/backups/temporales}"
Destino: /srv/tramontana/backups/temporales
operador@srv-tramontana:~$ echo "Versión: ${VERSION:?falta indicar la versión}"
bash: VERSION: falta indicar la versión
  • ${VAR:-valor} usa valor si VAR está vacía o no definida, sin asignarla. Ideal para valores por defecto.
  • ${VAR:?mensaje} aborta con ese mensaje si falta. Es la forma corta de exigir un parámetro obligatorio.

Existe también ${VAR:=valor}, que además asigna, y ${VAR:+valor}, que usa valor solo si la variable sí está definida.

  1. Sustitución de comandos

$(comando) ejecuta el comando y sustituye la expresión por su salida, quitando los saltos de línea finales.

operador@srv-tramontana:~$ ACTIVO="$(readlink /opt/tramontana/app)"
operador@srv-tramontana:~$ echo "Release activo: $ACTIVO, revisado el $(date +%F)"
Release activo: releases/3.2.1, revisado el 2026-08-18

Ya lo venías usando en la convención de copias de seguridad cp app.conf app.conf.bak-$(date +%F). Ahora sabes exactamente qué ocurre ahí.

Existe la forma antigua con acentos graves, `comando`. Usa siempre $(...): se puede anidar sin escapar nada y no se confunde visualmente con las comillas simples. Y entrecomilla el resultado, "$(...)", por la misma razón del apartado anterior.

  1. Alias

Un alias es una abreviatura que Bash sustituye al principio de un comando, antes de ejecutarlo.

operador@srv-tramontana:~$ alias ll='ls -lh --group-directories-first'
operador@srv-tramontana:~$ alias logs='cd /var/log/tramontana'
operador@srv-tramontana:~$ alias | head -3
alias ll='ls -lh --group-directories-first'
alias logs='cd /var/log/tramontana'
alias ls='ls --color=auto'

Se eliminan con unalias ll. Para ejecutar el comando original saltándote el alias, antepón una barra invertida:

operador@srv-tramontana:~$ alias rm='rm -i'
operador@srv-tramontana:~$ \rm /tmp/vars.sh

También sirven command rm y la ruta absoluta /bin/rm. Esto importa porque los alias son locales a tu sesión interactiva: no existen en un script, ni en cron, ni cuando otro usuario ejecuta lo mismo. Un alias rm='rm -i' te da una falsa sensación de red de seguridad que desaparece justo en el contexto donde más daño harías.

Los alias son para tu comodidad, no para automatizar. Si necesitas que algo se comporte igual siempre y en todas partes, eso es un script, y los scripts son el Módulo 4. En el apartado 12 verás el juego completo que usamos en Tramontana.

  1. Shells de login, interactivos y no interactivos

Aquí está el lío que hay que deshacer de una vez. Bash lee ficheros de arranque distintos según cómo se ha lanzado.

Tipo Cuándo aparece Qué lee
Login interactivo ssh operador@srv-tramontana, consola TTY, su - /etc/profile, luego el primero que exista de ~/.bash_profile, ~/.bash_login, ~/.profile
No login interactivo abrir una pestaña de terminal, escribir bash /etc/bash.bashrc y ~/.bashrc
No interactivo bash script.sh, cron, ssh servidor 'comando' Nada de lo anterior (solo $BASH_ENV si está definida)
flowchart TD
    S["Arranca bash"] --> L{"¿Es shell<br/>de login?"}
    L -->|Sí| P["/etc/profile"] --> U["~/.bash_profile<br/>o ~/.bash_login<br/>o ~/.profile<br/>(el primero que exista)"]
    U --> R["~/.profile suele incluir:<br/>source ~/.bashrc"]
    R --> B["~/.bashrc"]
    L -->|No| I{"¿Es<br/>interactivo?"}
    I -->|Sí| G["/etc/bash.bashrc"] --> B
    I -->|No| N["No lee ningún fichero<br/>de arranque"]
    B --> W["Sesión lista"]
    N --> W
    W --> X{"¿Sale un<br/>shell de login?"}
    X -->|Sí| O["~/.bash_logout"]

De ese diagrama salen tres consecuencias que resuelven casi todas las dudas:

  1. Las variables de entorno van en ~/.profile; los alias, funciones y PS1 van en ~/.bashrc. Lo primero se hereda por todos los procesos de la sesión; lo segundo solo tiene sentido cuando hay alguien tecleando.
  2. En Ubuntu, ~/.profile termina con un bloque que hace source ~/.bashrc. Por eso al conectarte por SSH ves tus alias: llegan por esa cadena, no porque el shell de login lea .bashrc por sí mismo. Si creas un ~/.bash_profile, ~/.profile deja de leerse y esa cadena se rompe.
  3. Cron no lee nada de esto, y esa es la causa raíz del fallo clásico que estudiarás en 03-07.

Para saber en qué tipo de shell estás: shopt -q login_shell && echo login te dice si es de login, y echo "$-" muestra las opciones activas, donde la i indica interactivo. Y ~/.bash_logout se ejecuta al cerrar un shell de login: sirve, por ejemplo, para limpiar la pantalla de un TTY físico con clear.

  1. Personalizar el prompt PS1

PS1 es la cadena que Bash imprime antes de cada comando. Admite secuencias de escape propias:

Secuencia Significado
\u nombre de usuario
\h / \H nombre corto / completo del host
\w / \W ruta completa (con ~) / solo el último directorio
\$ # si eres root, $ si no
\t / \d hora HH:MM:SS / fecha
\n salto de línea

El color se añade con secuencias ANSI, y hay un detalle imprescindible: todo lo que no ocupa espacio en pantalla debe ir entre \\[ y \\]. Si te lo saltas, Bash calcula mal la anchura de la línea y el historial se pinta encima del prompt al recuperar comandos largos.

operador@srv-tramontana:~$ PS1='\[\e[1;32m\]\u@\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]\$ '

En producción usamos otro color a propósito:

operador@srv-tramontana:~$ PS1='\[\e[1;37;41m\] PROD \[\e[0m\] \u@\h:\w\$ '
 PROD  operador@srv-tramontana:~$

Texto blanco sobre fondo rojo (41). En Tramontana, el prompt de srv-tramontana va en rojo y el del portátil en verde, por una razón muy poco estética: casi todos los borrados catastróficos empiezan por ejecutar en la ventana equivocada un comando pensado para la otra. Un aviso visual permanente e imposible de ignorar es más eficaz que cualquier norma escrita. La misma lógica se aplica cuando trabajas como root, donde el \$ pasa a # por sí solo.

  1. Historial persistente y su riesgo de seguridad

Bash guarda los comandos en memoria durante la sesión y los vuelca a ~/.bash_history al salir. Ese comportamiento por defecto tiene dos problemas: si abres varias terminales, la última en cerrarse sobrescribe el historial de las demás, y si la sesión muere de golpe, se pierde todo.

Ajuste Qué hace
HISTSIZE=10000 comandos guardados en memoria
HISTFILESIZE=20000 líneas conservadas en el fichero
HISTCONTROL=ignoreboth ignora duplicados consecutivos y líneas que empiezan por espacio
HISTIGNORE='ls:ll:pwd:exit:history:clear' no guarda estos comandos triviales
HISTTIMEFORMAT='%F %T ' añade fecha y hora a cada entrada
shopt -s histappend añade al fichero en vez de sobrescribirlo

ignoreboth incluye ignorespace, y eso habilita un truco práctico: si escribes un comando precedido de un espacio, no se guarda. Con HISTTIMEFORMAT puesto, history 3 te muestra las tres últimas entradas con su marca de tiempo, lo que convierte el historial en un registro de qué hiciste y cuándo.

La advertencia de seguridad

~/.bash_history es un fichero de texto plano. Todo lo que escribas en la línea de comandos acaba ahí, incluidas las credenciales:

operador@srv-tramontana:~$ mysql -u tramontana -pSecreto123 tramontana_db   # NUNCA

Esa contraseña queda escrita en tu historial, y además es visible en la tabla de procesos para cualquier usuario del sistema mientras el comando corre (lo verás con ps aux en 03-06). Dos problemas, no uno.

Qué hacer en su lugar:

  • Deja que la herramienta la pida de forma interactiva (mysql -u tramontana -p, sin pegarla).
  • Guárdala en un fichero de credenciales con permisos 600 y pásale la ruta.
  • Si ya la has escrito: bórrala del historial en memoria y del fichero.
operador@srv-tramontana:~$ history -d 512          # borra la entrada 512 en memoria
operador@srv-tramontana:~$ history -w              # reescribe ~/.bash_history

Y comprueba los permisos, que deben ser 600. La gestión seria de secretos es la lección 06-05; aquí quédate con la higiene mínima.

  1. El ~/.bashrc recomendado para operador

Reuniendo todo, este es el bloque que añadimos al final de ~/.bashrc en las cuentas administrativas de Tramontana. Aplica la convención del curso: copia de seguridad antes de editar y diff -u después.

operador@srv-tramontana:~$ cp ~/.bashrc ~/.bashrc.bak-$(date +%F)
operador@srv-tramontana:~$ nano ~/.bashrc
# --- Ajustes Tramontana ---------------------------------------------
umask 027                                    # nada para "otros"

HISTSIZE=10000
HISTFILESIZE=20000
HISTCONTROL=ignoreboth
HISTIGNORE='ls:ll:pwd:exit:history:clear'
HISTTIMEFORMAT='%F %T '
shopt -s histappend                          # no sobrescribir entre terminales
shopt -s checkwinsize cdspell

export EDITOR=nano
export PATH="$PATH:$HOME/scripts"            # nuestros scripts, al final

alias ll='ls -lh --group-directories-first'
alias la='ls -lha'
alias grep='grep --color=auto'
alias df='df -h'
alias logs='cd /var/log/tramontana'
alias rel='ls -l /opt/tramontana/releases/'

PS1='\[\e[1;37;41m\] PROD \[\e[0m\] \[\e[1;32m\]\u@\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]\$ '
# --------------------------------------------------------------------

Se activa sin cerrar la sesión con source ~/.bashrc, y se verifica el cambio con diff -u ~/.bashrc.bak-2026-08-18 ~/.bashrc. Fíjate en que umask 027 va aquí y no en ~/.profile porque nos interesa para el trabajo interactivo; para los servicios se fija en otro sitio, y eso es materia de 05-05.

Errores Comunes y Consejos

  • Espacios alrededor del =. VAR = valor no es una asignación. Es el error número uno.
  • Editar ~/.bashrc y esperar que se aplique solo. Hay que hacer source ~/.bashrc o abrir una sesión nueva.
  • Romper el PATH. Antes de tocarlo, guarda una copia: PATH_ORIG="$PATH". Si te quedas sin comandos, export PATH="$PATH_ORIG" te salva sin reconectar.
  • Poner variables de entorno en ~/.bashrc. Funciona en interactivo y desaparece en contextos no interactivos. Su sitio es ~/.profile.
  • Crear ~/.bash_profile sin saber que anula ~/.profile. Si lo creas, incluye dentro [ -f ~/.profile ] && . ~/.profile.
  • Confiar en alias rm='rm -i'. No existe en scripts ni en cron. La red de seguridad de verdad es mirar con ls antes del rm.
  • Olvidar \\[ \\] en los colores del PS1. El síntoma es un prompt que se corrompe al navegar por el historial.
  • Consejo: env -i comando arranca un programa sin ninguna variable. Es la mejor forma de reproducir lo que ve cron, y volverás a ello en 03-07. Y como set sin argumentos lista variables y funciones, filtra siempre: set | grep '^HIST'.

Ejercicios

Ejercicio 1. Demuestra en tu VM la diferencia entre variable de shell y variable de entorno. Define DESPLIEGUE=3.3.0 sin exportar, comprueba que un bash -c hijo no la ve, expórtala, comprueba que ahora sí, y explica por qué printenv daba error antes.

Ejercicio 2. Añade /home/operador/scripts al PATH de forma que no tenga prioridad sobre los comandos del sistema y que el cambio sobreviva a reconectar por SSH. Verifica que funciona sin cerrar la sesión y explica en qué fichero lo has puesto y por qué.

Ejercicio 3. Genera una lista ordenada y reproducible de las casas de /home/operador/datos/casas.txt que sea idéntica en tu portátil y en el servidor, aunque tengan locales distintas. Guarda el resultado en /home/operador/datos/casas-ordenadas.txt y justifica la decisión.

Soluciones

Solución 1.

operador@srv-tramontana:~$ DESPLIEGUE=3.3.0
operador@srv-tramontana:~$ echo "$DESPLIEGUE"
3.3.0
operador@srv-tramontana:~$ printenv DESPLIEGUE; echo "código: $?"
código: 1
operador@srv-tramontana:~$ bash -c 'echo "hijo ve: [$DESPLIEGUE]"'
hijo ve: []
operador@srv-tramontana:~$ export DESPLIEGUE
operador@srv-tramontana:~$ bash -c 'echo "hijo ve: [$DESPLIEGUE]"'
hijo ve: [3.3.0]

La variable existía desde el principio en el shell actual —por eso echo la mostraba—, pero no estaba en el vector de entorno que se copia a los hijos. printenv consulta exactamente ese vector, así que no la encontró y devolvió 1. export no crea la variable: la marca para exportación, y a partir de ahí todos los hijos nuevos la reciben. Los que ya estuvieran corriendo, no: la herencia ocurre en el momento del fork.

Solución 2. Debe ir en ~/.profile, porque es una variable de entorno y porque ~/.profile se lee en los shells de login, que es lo que abre SSH. Al final, y no al principio, para que el sistema tenga prioridad.

operador@srv-tramontana:~$ cp ~/.profile ~/.profile.bak-$(date +%F)
operador@srv-tramontana:~$ printf '\n# scripts propios de Tramontana\nexport PATH="$PATH:$HOME/scripts"\n' >> ~/.profile
operador@srv-tramontana:~$ diff -u ~/.profile.bak-$(date +%F) ~/.profile
@@ -20,3 +20,6 @@
     fi
 fi
+
+# scripts propios de Tramontana
+export PATH="$PATH:$HOME/scripts"
operador@srv-tramontana:~$ source ~/.profile
operador@srv-tramontana:~$ echo "$PATH" | tr ':' '\n' | tail -2
/usr/games
/home/operador/scripts

source aplica el cambio a la sesión actual sin reconectar. Si en lugar de eso hubiéramos escrito PATH="$HOME/scripts:$PATH", un script nuestro llamado df o tar enmascararía al del sistema, y ese tipo de sorpresa es difícil de diagnosticar. Ponerlo al final es la opción conservadora. Nótese también que las comillas simples del printf impiden que $PATH y $HOME se expandan al escribir el fichero: queremos que la expansión ocurra en cada login, no ahora.

Solución 3.

operador@srv-tramontana:~$ LC_ALL=C sort /home/operador/datos/casas.txt \
    > /home/operador/datos/casas-ordenadas.txt
operador@srv-tramontana:~$ cat /home/operador/datos/casas-ordenadas.txt
cal-ferrer
can-ventos
el-moli
la-solana
mas-figueres

La justificación es la clave del ejercicio: sin LC_ALL=C, el resultado depende de la locale de cada máquina. Con es_ES.UTF-8 el guion se ignora en la comparación y mas-figueres podría quedar en otra posición respecto a un hipotético masfigueres; con C se compara byte a byte y el resultado es idéntico en cualquier sistema. Cuando la salida va a compararse, versionarse o alimentar otro proceso, la reproducibilidad pesa más que la corrección ortográfica del orden. Y se pone delante del comando, sin export, para no alterar el resto de la sesión.

Conclusión

Has pasado de habitar el entorno del shell a diseñarlo.

  • El entorno es un vector CLAVE=valor que el kernel entrega a cada proceso; lo lees en /proc/<pid>/environ.
  • Distingues variable de shell de variable de entorno: export marca la diferencia, y la herencia es unidireccional y en el momento del arranque, por eso un script no puede cambiarte el cd salvo con source.
  • Entiendes cómo Bash busca un ejecutable recorriendo el PATH de izquierda a derecha, cómo ampliarlo sin destruirlo, y por qué . nunca va en el PATH.
  • Conoces el efecto real de la locale sobre sort y las fechas, y usas LC_ALL=C cuando necesitas resultados reproducibles.
  • Manejas con precisión "$VAR" frente a $VAR y '$VAR', las llaves ${VAR}, los valores por defecto ${VAR:-...} y ${VAR:?...}, y la sustitución de comandos $(...).
  • Tienes desenredado el lío de los ficheros de arranque: entorno en ~/.profile, alias y PS1 en ~/.bashrc, y nada de eso en un shell no interactivo.
  • Has puesto el prompt de producción en rojo por una razón operativa y configurado un historial persistente con histappend, sabiendo que las contraseñas escritas en la línea acaban en un fichero de texto plano.

La siguiente lección da el paso natural. Ya sabes describir un archivo con su ruta; ahora aprenderás a describir conjuntos de archivos y patrones de texto. En Comodines y Expresiones Regulares verás la distinción que casi nadie explica bien —que el globbing lo hace el shell antes de ejecutar el comando, mientras que las expresiones regulares las interpreta el programa que las recibe—, y esa distinción, que ahora entenderás porque ya sabes cómo Bash procesa una línea antes de lanzarla, es la que evita que grep *.log haga algo completamente distinto de lo que esperabas.

Curso de Linux: De Principiante a Administrador de Sistemas

Módulo 1: Introducción a Linux

Módulo 2: Comandos Básicos de Linux

Módulo 3: Habilidades Avanzadas en la Línea de Comandos

Módulo 4: Scripting en Shell

Módulo 5: Administración del Sistema

Módulo 6: Redes y Seguridad

Módulo 7: Temas Avanzados

Módulo 8: Proyectos Prácticos

© Copyright 2026. Todos los derechos reservados