Módulo 2: Trabajar con archivos y texto
2. Crear archivos y carpetas: mkdir y touch
Descripción
Al terminar esta lección vas a poder crear un árbol completo de carpetas anidadas con un solo comando usando mkdir -p, generar varios archivos o carpetas de golpe con expansión de llaves y rangos, y crear o actualizar archivos con touch entendiendo qué hace realmente por debajo — no lo que parece que hace. También vas a nombrar todo lo que crees con un criterio que te va a evitar dolores de cabeza más adelante, cuando ese nombre tenga que viajar por un script, una URL o la terminal de otra persona.
Esto no es un ejercicio de organización prolija: es la tarea con la que arranca casi cualquier trabajo real. Cuando te sumas a un proyecto nuevo o arrancas uno desde cero, la primera media hora suele ser armar el esqueleto de carpetas que el equipo espera (src/, tests/, docs/) y algunos archivos placeholder — un README, un archivo de configuración vacío que vas a llenar después. Hacerlo carpeta por carpeta, clic por clic, es lento y no queda registrado en ningún lado. Hacerlo con un comando reproducible es la diferencia entre "tardé veinte minutos" y "tardé cinco segundos, y además lo puedo pegar en un script para el próximo proyecto". Y vas a entender por qué herramientas de automatización como make revisan la fecha de un archivo para decidir si conviene reconstruir algo — un detalle de touch que vas a poder usar a tu favor o vas a tener que depurar el día que algo no se actualiza como esperabas.
Conexión con el módulo: la lección anterior trazó el mapa del módulo y te advirtió que a partir de ahora sí se puede romper algo. Esta es la primera lección donde construyes: mkdir y touch son las herramientas con las que vas a levantar toda la estructura que las lecciones siguientes van a reorganizar, leer y buscar.
mkdir: construir una dirección completa antes de que exista
Cuando una cuadrilla levanta un edificio, no puede apoyar el tercer piso en el aire: necesita que el primero y el segundo ya estén de pie. Si alguien pide directamente "construyan el tercer piso" sin que exista nada debajo, la respuesta lógica es negarse — no hay dónde apoyarlo. Y si alguien pide un piso que ya está construido, tampoco tiene sentido frenar toda la obra para reclamarlo: se sigue trabajando.
mkdir (make directory) tiene exactamente esa primera limitación por defecto: solo crea el último nivel de la ruta que le pidas, y exige que todo lo anterior ya exista. Compruébalo:
mkdir orders/2026/july
Qué esperar (en macOS/BSD):
mkdir: orders/2026/july: No such file or directory
En Linux/GNU vas a ver una redacción distinta pero la misma causa: mkdir: cannot create directory 'orders/2026/july': No such file or directory. En ambos casos el problema es idéntico: mkdir intentó crear únicamente july, y ni siquiera orders/2026 existía todavía para apoyarlo.
La opción -p (parents) resuelve exactamente esto, y de dos formas a la vez: construye automáticamente cada nivel que falte en el camino, y si el nivel final ya existe, no lo trata como error — sigue de largo sin quejarse.
Ejemplo trabajado: el mismo comando, con -p
mkdir -p orders/2026/july
ls -R orders
Qué esperar:
orders:
2026
orders/2026:
july
orders/2026/july:
Las tres carpetas se crearon en un solo comando, y la última (july) está vacía porque acaba de nacer. Ahora corre exactamente el mismo comando otra vez, sin haber borrado nada:
mkdir -p orders/2026/july
Qué esperar: nada. mkdir no imprime ni una línea — silencio es éxito en la terminal, igual que vas a ver con otros comandos de este módulo. Sin -p, ese mismo segundo intento habría fallado con mkdir: orders/2026/july: File exists. Con -p, un directorio que ya existe simplemente no es un problema: es el comportamiento que quieres cuando un script se corre más de una vez y no debería romperse la segunda.
Expansión de llaves: crear varias rutas de un solo golpe
Imagina que además necesitas tres carpetas hermanas para el mismo proyecto: src, tests y docs. Podrías escribir tres comandos separados, o incluso uno solo con tres rutas completas — mkdir -p project/src project/tests project/docs funciona perfectamente, y para tres carpetas simples es una opción perfectamente razonable. Pero la terminal (bash y zsh, las dos shells que vas a encontrar en la práctica) tiene una forma de decir "esto de acá, pero con estas variaciones" sin repetir el prefijo común: la expansión de llaves.
{a,b,c} no es una función de mkdir — es un mecanismo de la shell que ocurre antes de que el comando reciba sus argumentos. La shell ve project/{src,tests,docs}, la expande a tres palabras completas (project/src, project/tests, project/docs), y recién entonces se las entrega a mkdir como si las hubieras escrito tú mismo, una por una. Por eso funciona con cualquier comando, no solo con mkdir — es una característica del intérprete de comandos, no del programa que la recibe.
La ventaja real aparece cuando la estructura se anida, porque ahí repetir el prefijo a mano se vuelve tedioso y aumenta el margen de error de tipeo:
Ejemplo trabajado: el esqueleto de un proyecto nuevo, de un solo comando
mkdir -p expense-tracker/{src/{models,views,controllers},tests,docs}
ls -R expense-tracker
Qué esperar:
expense-tracker:
docs src tests
expense-tracker/src:
controllers models views
expense-tracker/tests:
expense-tracker/docs:
La llave interior ({models,views,controllers}) se resuelve primero y produce tres rutas dentro de src; esas tres se combinan con la llave exterior junto con tests y docs, y -p construye todos los niveles intermedios que hacían falta — incluyendo expense-tracker mismo, que tampoco existía antes de correr el comando. Seis carpetas, tres niveles de profundidad, un solo comando.
touch: tocar una fecha, no crear contenido
Piensa en la hoja de registro de un laboratorio compartido: cada vez que alguien entra, anota la hora al lado de su nombre. Si tu nombre ya estaba en la lista, la anotación solo actualiza la hora — nadie reescribe tu historial ni te asigna una fila nueva. Pero si es tu primera vez y tu nombre todavía no aparece en ningún lado, anotar la hora también implica, de paso, agregar tu nombre a la lista por primera vez, sin ninguna actividad previa registrada.
Eso es literalmente lo que hace touch: su trabajo real es actualizar la marca de tiempo de modificación (y de acceso) de un archivo a ahora mismo. Si el archivo ya existe, el contenido no se toca ni un byte — cambia solo la fecha. Si el archivo no existe todavía, actualizar esa fecha requiere que exista algo sobre lo cual escribirla, así que touch lo crea primero — vacío, con cero bytes. Por eso mucha gente cree que "crear archivos" es la función principal de touch, cuando en realidad crear es un efecto secundario de su verdadero trabajo: estampar un instante en el tiempo.
Ejemplo trabajado: la fecha cambia, el contenido no
touch expense-tracker/README.md
ls -l expense-tracker/README.md
Qué esperar (los nombres de usuario y grupo van a variar según tu máquina):
-rw-r--r-- 1 ana staff 0 Jul 21 10:14 expense-tracker/README.md
Cero bytes: el archivo nació vacío, con los permisos que le da el umask de tu sistema (eso lo vas a ver en detalle en el módulo de permisos). Ahora, sin escribir nada adentro, corre touch sobre ese mismo archivo otra vez, un rato después:
touch expense-tracker/README.md
ls -l expense-tracker/README.md
Qué esperar:
-rw-r--r-- 1 ana staff 0 Jul 21 10:37 expense-tracker/README.md
El tamaño sigue siendo 0, los permisos son los mismos — lo único que cambió es la columna de la hora. Si ese archivo ya tuviera cien líneas escritas por otra persona, el resultado sería idéntico en ese sentido: touch no altera ni una letra del contenido, sin importar cuánto pese el archivo. Solo mueve la marca de tiempo hacia adelante.
touch también acepta expansión de llaves con rangos, útil para crear varios archivos placeholder de una vez:
touch expense-tracker/tests/test-{01..03}.py
ls expense-tracker/tests
Qué esperar:
test-01.py test-02.py test-03.py
El cero inicial en {01..03} le indica a la shell que rellene todos los números al mismo ancho (funciona así en zsh, la shell por defecto en macOS desde hace varios años, y en cualquier bash moderno). Sin el cero — {1..3} — el resultado sería 1 2 3, sin relleno.
Convenciones de nombre que te ahorran dolor después
Todo lo que acabas de crear tiene un nombre, y ese nombre vive más tiempo del que crees: va a terminar dentro de un script, en una URL, en un comando que corre otra persona con otro idioma configurado en su sistema. Un nombre como Informe Final (v2).txt funciona sin problema mientras lo abras haciendo doble clic en tu propia máquina — y se convierte en un problema real en cuanto ese archivo tiene que viajar: los espacios rompen comandos que no los esperan, los acentos pueden mostrarse mal según la codificación del sistema que los reciba, y los paréntesis son caracteres que la shell interpreta de forma especial si no los proteges.
La convención que evita casi todo esto se llama kebab-case: solo minúsculas, palabras separadas por un guion simple, como las piezas ensartadas en el pincho de un kebab — informe-final-v2.txt en vez de Informe Final (v2).txt. El conjunto de caracteres verdaderamente portable entre sistemas es letras sin acentos, números, guion, guion bajo y punto — es literalmente el estándar que define POSIX para nombres de archivo seguros en cualquier sistema Unix. Adoptar esa disciplina cuando tú creas el archivo es gratis: no cuesta nada escribir expense-tracker en vez de Expense Tracker.
El problema real aparece cuando heredas un archivo con un nombre así, creado por alguien más (o por ti mismo, en otra época, arrastrando y soltando en el Finder). No lo vas a renombrar todavía — eso es exactamente lo que vas a aprender en la próxima lección con mv — pero sí necesitas poder referenciarlo sin que la shell lo destroce. Supón que heredaste esta carpeta con un archivo que alguien nombró en Windows:
ls
Qué esperar:
Informe Final.txt
Quieres actualizar su fecha de modificación, y escribes lo primero que se te ocurre:
touch Informe Final.txt
ls
Qué esperar:
Final.txt Informe Informe Final.txt
Nada de lo que querías pasó. La shell partió tu comando en el espacio sin preguntarte, así que touch recibió dos argumentos distintos: Informe y Final.txt. Como ninguno de los dos existía, touch hizo exactamente lo que ya sabes que hace con un nombre que no existe: los creó vacíos. El archivo real, Informe Final.txt, ni siquiera fue tocado — su fecha sigue siendo la original.
La forma correcta es proteger el nombre completo con comillas dobles, para que la shell lo trate como una sola palabra:
touch "Informe Final.txt"
ls -l "Informe Final.txt"
Qué esperar: solo la fecha de Informe Final.txt cambia; no aparece ningún archivo nuevo. El backslash antes de cada espacio (Informe\ Final.txt) logra exactamente lo mismo, carácter por carácter — y si en la terminal real empiezas a escribir touch Inf y presionas la tecla Tab, el autocompletado va a insertar el nombre completo ya escapado por ti, sin que tengas que acordarte de las comillas. Esa es la técnica más confiable de las tres: dejar que la terminal escape por ti en vez de hacerlo a mano.
Errores comunes
Pensar que la última palabra de una ruta para mkdir puede ser un archivo. Corres mkdir -p src/utils/helpers.py pensando que vas a terminar con un archivo helpers.py dentro de src/utils/ — y lo que obtienes es una carpeta llamada literalmente helpers.py, no un archivo. Si después intentas touch src/utils/helpers.py, el comando no falla, pero tampoco arregla nada: como ya existe algo con ese nombre (aunque sea una carpeta), touch solo le actualiza la marca de tiempo. Por qué pasa: mkdir no tiene forma de saber que la última palabra "parecía" un archivo por tener una extensión — para mkdir, cada argumento que recibe es un directorio a crear, sin excepción, tenga punto en el nombre o no. Cómo se detecta: ls -l muestra una d al principio de los permisos donde esperabas ver un archivo normal (-). Cómo se corrige: crear con mkdir -p solo hasta el último nivel que sí es carpeta, y usar touch aparte para el archivo final — mkdir -p src/utils && touch src/utils/helpers.py.
Dejar un espacio después de la coma dentro de las llaves. mkdir -p project/{src, tests, docs} (con espacio después de cada coma) no crea tres carpetas limpias — crea entradas con nombres rotos que incluyen literalmente las llaves y la coma. Por qué pasa: la shell separa las palabras por espacios antes de intentar expandir las llaves, así que un espacio sin comillas ya cortó {src, de tests, y de docs} en tres argumentos distintos. Cuando la shell busca la llave de cierre para expandir cada uno, ya no la encuentra en la misma palabra — y en vez de fallar con un error, deja el texto tal cual, literal. Cómo se detecta: un ls inmediatamente después muestra nombres con llaves y comas sueltas en vez de las carpetas limpias que esperabas. Cómo se corrige: nunca dejar espacio después de una coma dentro de las llaves — pegado, {src,tests,docs}.
Creer que tocar un archivo existente "no hace nada" porque el contenido queda igual. Parece un comando inofensivo porque no cambia ni un byte del contenido — pero si tienes un Makefile u otra herramienta de automatización que decide si reconstruir algo comparando fechas de modificación, tocar el archivo puede disparar una reconstrucción innecesaria (o, al revés, resolver un build que estaba atascado precisamente porque la fecha no cambiaba). Por qué pasa: touch actualiza la marca de tiempo aunque el contenido sea bit a bit idéntico, y muchas herramientas de build comparan fechas, no contenido. Cómo se detecta: ls -l antes y después muestra la fecha cambiada aunque el tamaño en bytes sea exactamente el mismo. Cómo se corrige: si de verdad quieres dejar un archivo intacto —contenido y fecha—, no lo toques; si quieres forzar una reconstrucción, touch es la herramienta correcta, el punto es hacerlo a propósito y no por accidente al andar probando comandos.
Ejercicios
Ejercicio 1. Necesitas armar este esqueleto de proyecto en un solo comando:
blog-api/
├── src/
│ ├── routes/
│ └── models/
├── tests/
└── docs/
Escribe el comando mkdir con expansión de llaves anidada que lo crea de una vez, asumiendo que ni siquiera blog-api/ existe todavía.
Ver solución
mkdir -p blog-api/{src/{routes,models},tests,docs}
Por qué funciona: la llave interior {routes,models} se expande primero y produce dos rutas dentro de src; la llave exterior las combina con tests y docs al mismo nivel que src. -p se encarga de crear blog-api/ y blog-api/src/ aunque no existieran antes — sin -p, el comando fallaría en el primer nivel que faltara.
Ejercicio 2. Corres esta secuencia completa, en este orden, sin borrar nada entre medio:
touch archive/report-{1..3}.csv
ls archive
touch archive/report-{1..3}.csv
ls archive
¿El segundo ls muestra archivos duplicados o distintos de los del primero? ¿Qué cambió realmente entre una ejecución y la otra?
Ver solución
Los dos ls muestran exactamente los mismos tres nombres: report-1.csv, report-2.csv y report-3.csv. No hay duplicados porque touch sobre un archivo que ya existe no crea uno nuevo — actualiza su marca de tiempo. Lo único que cambió entre la primera y la segunda ejecución es la fecha de modificación de esos tres archivos, invisible en un ls simple (haría falta ls -l para verla) pero real: la segunda pasada dejó los tres archivos con una marca de tiempo más reciente que la primera.
Ejercicio 3. Heredaste una carpeta llamada Reporte Año 2025 (con espacio y con tilde). Sin renombrarla todavía, quieres crear dentro de ella un archivo nuevo, siguiendo la convención de esta lección: summary-2025.md. Escribe el comando completo.
Ver solución
touch "Reporte Año 2025/summary-2025.md"
Por qué funciona: las comillas envuelven toda la ruta, incluida la parte heredada con el espacio, así que la shell la trata como una sola palabra en vez de partirla en Reporte, Año y 2025/summary-2025.md. El archivo nuevo que tú creas, en cambio, ya sigue kebab-case desde el primer momento — no hereda el problema, lo contiene.
Resumen y siguiente paso
Antes de seguir, deberías poder crear un árbol de carpetas anidadas con un solo mkdir -p, explicar por qué -p no falla si algo ya existe, usar expansión de llaves y rangos para no repetir un prefijo a mano, y predecir cuándo touch va a crear un archivo nuevo y cuándo solo le va a actualizar la fecha sin tocar el contenido.
Ya sabes crear estructura completa de un solo golpe. Lo que todavía no sabes es reorganizarla, renombrarla ni destruirla con criterio — y ahí es exactamente donde entras en el terreno donde un descuido sí tiene consecuencias reales. cp, mv y rm son las herramientas de la próxima lección, y la primera vez que la terminal deja de ser inofensiva.
Recursos
- GNU Coreutils Manual — mkdir invocation — referencia oficial de
mkdir, incluida la opción-p/--parents. - GNU Coreutils Manual — touch invocation — referencia oficial de
touch, el comportamiento por defecto de creación y la opción-c/--no-create. - GNU Bash Manual — Brace Expansion — especificación exacta de la expansión de llaves, incluidas las secuencias tipo
{1..5}. - FreeBSD General Commands Manual — mkdir(1) — versión BSD del comando, la base de
mkdiren macOS. - The Open Group Base Specifications — 3.282 Portable Filename Character Set — el estándar POSIX que define el conjunto de caracteres seguro para nombres de archivo entre sistemas.