We closed Module 1 promising that "the real work with the tools" started here, and this is the first piece of it. Creating, copying, moving and deleting files is the most frequent operation for anyone who administers a system, and also the most dangerous: Linux has no recycle bin, and a mistyped rm asks for no confirmation and leaves no trace. In this lesson you will learn to manipulate files and directories precisely on srv-veloz-01, to protect your work with instant backups, and to recognize the traps that turn an apparently harmless command into a production data loss.

Contents

  1. Creating directories with mkdir and mkdir -p
  2. Creating and touching files with touch
  3. Copying with cp: the options that matter
  4. Moving and renaming with mv
  5. Deleting with rm and rmdir: the point of no return
  6. Problematic names: spaces and leading dashes
  7. Quick backups before touching anything
  8. Hands-on case: organizing ~/veloz-ops and archiving the day's CSV

  1. Creating directories with mkdir and mkdir -p

mkdir (make directory) creates directories, one or several per invocation. The problem shows up with nested structures:

mkdir /srv/veloz/data/archive/2026/08
mkdir: cannot create directory '...': No such file or directory

mkdir only creates the last element of the path and assumes the parents already exist. The -p option (parents) creates every missing intermediate directory:

mkdir -pv ~/veloz-ops/logs/2026/08
mkdir: created directory '/home/joan/veloz-ops/logs/2026'
mkdir: created directory '/home/joan/veloz-ops/logs/2026/08'

-v only mentions what it actually created: ~/veloz-ops/logs already existed from lesson 01-02.

-p has a second virtue, even more important in scripts: it does not fail if the directory already exists. Without -p, running mkdir reports twice returns an error and a non-zero exit code; with -p, it returns 0 silently. It is an idempotent operation: running it once or a hundred times produces the same result. That is why every serious script uses mkdir -p. There is also -m MODE to set the permissions at creation time, a topic we will tackle in 02-03.

  1. Creating and touching files with touch

touch has two uses that are worth keeping apart: if the file does not exist, it creates it empty; if it already exists, it updates its access and modification timestamps without touching the content.

touch ~/veloz-ops/bin/daily-report.sh
ls -l ~/veloz-ops/bin/daily-report.sh
-rw-rw-r-- 1 joan joan 0 Aug  3 11:02 /home/joan/veloz-ops/bin/daily-report.sh

Size 0: an empty file, ready to edit. The second use is what gives the command its name and serves to mark milestones: many backup processes decide what to copy by comparing timestamps, and a touch puts the file back in scope. Useful options: -c (do not create if it does not exist), -d "2026-07-01 09:00" (set a specific date) and -r REF (copy the timestamps of another file).

touch does not create files with content. For that you will use an editor or, from 02-04 onward, redirection.

  1. Copying with cp: the options that matter

The syntax is cp SOURCE DESTINATION, but the behavior changes depending on whether the destination exists and is a directory:

cp shipments.csv shipments-copy.csv    # destination = file name → renamed copy
cp shipments.csv /tmp/                 # destination = directory → /tmp/shipments.csv
cp a.csv b.csv c.csv /tmp/backup/      # several sources → the destination MUST be a directory

If the destination is a file that already exists, cp overwrites it without warning. This is the day's first serious trap.

Option Name What it does
-r / -R recursive Copies directories with all their contents
-a archive Copies "as is": recursive + preserves metadata + symbolic links
-i interactive Asks before overwriting
-n no-clobber Never overwrites, and never asks
-v verbose Shows every file copied
-u update Copies only if the source is newer, or the destination does not exist

Without -r, copying a directory fails with cp: -r not specified; omitting directory '...'.

What -a preserves exactly

-a is equivalent to -dR --preserve=all, and that expansion explains its value. It preserves:

  • Mode (permissions): an executable script stays executable in the copy.
  • Owner and group, if you have the privileges to assign them.
  • Timestamps: the copy keeps the original date, not today's.
  • Symbolic links: it copies them as links instead of following them and duplicating data.

Compare that with plain -r, which creates new files with the current date and permissions filtered through your umask (02-03). For archiving or backing up, -a is almost always the right choice: you want a faithful replica, not a "freshly made" version.

cp -av ~/veloz-ops /tmp/veloz-ops-snapshot
'/home/joan/veloz-ops' -> '/tmp/veloz-ops-snapshot'
'/home/joan/veloz-ops/bin/daily-report.sh' -> '/tmp/veloz-ops-snapshot/bin/daily-report.sh'

The existing-destination trap

With cp, what decides the outcome is not the trailing slash on the source, but whether the destination exists: if /tmp/target does not exist, cp -a source /tmp/target creates the copy under that name; if it does exist, it creates /tmp/target/source inside it. (In rsync, which you will see in 07-03, the trailing slash on the source does change the meaning; mixing up the two behaviors is a classic.) The way not to get it wrong is to check first with ls -d /tmp/target.

  1. Moving and renaming with mv

mv moves files. And renames files. And they are exactly the same operation: in Unix, a file's name is nothing more than an entry in a directory pointing at its data. Moving within the same filesystem consists of deleting that entry from the source directory and creating it in the destination one; if the directory is the same and only the text of the entry changes, we call it "renaming".

mv report.txt report-2026-08-03.txt        # rename
mv report-2026-08-03.txt ~/veloz-ops/logs/ # move
mv draft.md ~/veloz-ops/logs/final.md      # move and rename at once

Practical consequence: moving within the same disk is instantaneous, even for 50 GB, because no data is copied. Moving between different disks does involve copying and deleting, and that is why it takes time.

Just like cp, mv overwrites without asking. -i asks before overwriting; -n never overwrites and never asks; -v shows what it is doing.

mv -nv shipments.csv /srv/veloz/data/archive/

In scripts, -n is preferable to -i: a script should not sit waiting for an answer nobody is going to type.

  1. Deleting with rm and rmdir: the point of no return

Read this section twice. rm is the command that has destroyed the most data in the history of Unix.

rm file.txt                 # deletes a file
rm -r directory/            # deletes a directory and everything in it
rm -i *.tmp                 # asks about each one
rm -f file                  # force: no questions, no complaints if it does not exist
rmdir empty-directory       # deletes a directory ONLY if it is empty

There is no recycle bin. rm unlinks the name and the space becomes available for reuse. Recovery is not a user-level operation: it requires forensic tools, unmounting the filesystem, and even then it usually fails. Always work on the assumption that what you delete is gone.

rmdir is the safe counterpart: since it only deletes empty directories, it cannot destroy content by accident. Use it when your intent is "clean up something that should already be empty": if it fails, something unexpected was in there, and that is valuable information.

The traps of mistyped paths

rm -rf /srv/veloz/data /archive   # ← one space too many: deletes data AND /archive
rm -rf ~/veloz-ops /logs          # ← the same mistake in disguise
rm -rf $DIR/*                     # ← if DIR is empty, this is rm -rf /*

An accidental space turns one argument into two, and rm deletes both without blinking. The third case is the reason why in Module 3 you will learn to quote variables and give them default values.

Habits that protect you, applying what you learned in 01-05:

ls -la /srv/veloz/data/archive/2026/07        # 1. look at what is there first
echo rm -rf /srv/veloz/data/archive/2026/07   # 2. print without running and re-read it
rm -rf /srv/veloz/data/archive/2026/07        # 3. run it only if it was right

And two more rules in production: never use rm -rf with sudo without listing first, and prefer moving to a quarantine directory when the operation is not trivially reversible.

  1. Problematic names: spaces and leading dashes

The shell splits arguments on spaces, so report august.txt is seen as two arguments. The solution is quoting or escaping:

mv "report august.txt" report-august.txt
mv report\ august.txt report-august.txt

Worse is a file whose name starts with a dash:

rm -weird-file.txt
rm: invalid option -- 'w'

rm interprets the name as bundled options. There are two standard solutions:

rm -- -weird-file.txt     # -- marks the end of the options
rm ./-weird-file.txt      # with ./ in front it no longer starts with a dash

The -- separator is accepted by almost every GNU program and means: "whatever comes after this is arguments, not options". It will also save you when a search pattern starts with a dash.

About accents and ñ: the filesystem accepts them, but if those names end up in a URL, in a portable script or on a Windows system they will give you trouble. For the Veloz Envíos toolkit we adopt a strict convention: lowercase, no accents, dashes instead of spaces.

  1. Quick backups before touching anything

Before editing a configuration file in production, make yourself this copy. It costs a second and saves entire afternoons:

cp -a ~/veloz-ops/etc/veloz-ops.conf ~/veloz-ops/etc/veloz-ops.conf.bak

# If you need several successive copies, date the name (command substitution, 03-06)
cp -a veloz-ops.conf "veloz-ops.conf.$(date +%F).bak"   # → veloz-ops.conf.2026-08-03.bak

-a is deliberate: the copy keeps the permissions and the original date, so it serves as a reference for "how this used to be". This is a local copy: it protects you from your own editing mistakes, not from a disk failure. The real Veloz Envíos backup, with tar and rsync, arrives in 05-01 and 07-03.

  1. Hands-on case: organizing ~/veloz-ops and archiving the day's CSV

# 1. Complete the toolkit structure
mkdir -pv ~/veloz-ops/{bin,lib,etc,logs,tmp}

# 2. Create the configuration file and its backup
touch ~/veloz-ops/etc/veloz-ops.conf
cp -a ~/veloz-ops/etc/veloz-ops.conf ~/veloz-ops/etc/veloz-ops.conf.bak

# 3. Prepare the data archive by year and month
sudo mkdir -pv /srv/veloz/data/archive/2026/08

# 4. Archive a snapshot of the day's CSV, preserving metadata
sudo cp -av /srv/veloz/data/shipments.csv \
            /srv/veloz/data/archive/2026/08/shipments-2026-08-03.csv

The {bin,lib,...} syntax is brace expansion and we will study it in 02-05; for now just remember that it creates the five directories in one pass. The trailing backslash in step 4 lets the command continue on the next line.

Notice the design decision: we copy, we do not move. /srv/veloz/data/shipments.csv is the live file that the API keeps writing to; moving it would break the application. The archive is fed with dated copies, and the name shipments-2026-08-03.csv is deliberately sortable alphabetically, which here means sortable chronologically.

ls -lh /srv/veloz/data/archive/2026/08/
total 24K
-rw-r--r-- 1 root root 21K Aug  3 09:14 shipments-2026-08-03.csv

The time shown, 09:14, is that of the original file, not of the copy: that is -a doing its job.

Common Mistakes and Tips

  • Believing that cp and mv warn before overwriting. They do not. Interactively you can define alias cp='cp -i' and alias mv='mv -i' in your ~/.bashrc (02-06), but do not depend on them: aliases are not expanded in scripts, and getting used to the safety net makes you drop your guard on someone else's machine.
  • Using -r when you meant -a. Copying ~/veloz-ops with -r leaves the scripts with today's date and, in some cases, without the execute bit.
  • Forgetting that rm -r does not ask. rm -ri directory/ asks step by step; it is slow, but in production that slowness is a virtue.
  • Typing the path with one space too many. Before any recursive deletion, put echo in front and read the result.
  • Inferring cp's destination from the trailing slash. Check with ls -d whether it exists.
  • Filling the disk with .bak files. Quick copies are for the moment; clean up the ones that are no longer useful or you will end up debugging a df at 100 %.

Exercises

Exercise 1 — Set up the monthly archive. Prepare the July 2026 archive and place a copy of the current data inside it without touching the live file: (1) create /srv/veloz/data/archive/2026/07 in a single command even though the parents are missing; (2) copy shipments.csv there as shipments-2026-07-31.csv, preserving permissions and timestamps and showing what it does; (3) check that the original is still in place; (4) explain why -a is preferable to a plain copy.

Exercise 2 — Clean up a naming disaster. A badly written script has left the files report august.txt, -tmp.log and summary.txt in ~/veloz-ops/tmp. Rename the first one to report-august.txt; delete -tmp.log, reasoning about why rm -tmp.log fails; and move summary.txt to ~/veloz-ops/logs/ without overwriting anything if one with that name is already there.

Exercise 3 — Audit a dangerous command. A colleague hands you this to "clean up the old archive" and asks you to review it before running it as root:

sudo rm -rf /srv/veloz/data/archive /2025

What does it actually do? What was the intent? Write a safe sequence that achieves the real intent.

Solutions

Solution to Exercise 1

sudo mkdir -p /srv/veloz/data/archive/2026/07
sudo cp -av /srv/veloz/data/shipments.csv \
            /srv/veloz/data/archive/2026/07/shipments-2026-07-31.csv
ls -l /srv/veloz/data/shipments.csv

-p is essential because two intermediate levels are missing. -a is preferable because an archive must be a faithful replica: if the copy got today's date we would lose the information about when the data was generated, which is exactly what an archive exists to preserve. It also keeps the permissions, avoiding a future audit finding unexplained differences.

Solution to Exercise 2

cd ~/veloz-ops/tmp
mv "report august.txt" report-august.txt
rm -- -tmp.log
mv -n summary.txt ~/veloz-ops/logs/

rm -tmp.log fails because the shell hands the string -tmp.log to rm and rm interprets it as bundled options (-t, -m, -p...). The -- separator tells it not to expect any more options; rm ./-tmp.log works just as well. In the last step, -n guarantees that a pre-existing summary.txt is not lost and, unlike -i, does not block execution waiting for an answer.

Solution to Exercise 3

  1. What it actually does: the space before /2025 turns the command into two arguments. It recursively deletes all of /srv/veloz/data/archive —the entire archive, every year— and additionally tries to delete the /2025 directory at the root. With sudo and -f, without a single question or error message.

  2. The intent was to delete the 2025 subdirectory: /srv/veloz/data/archive/2025.

  3. Safe sequence:

ls -la /srv/veloz/data/archive/2025          # 1. confirm path and contents
du -sh /srv/veloz/data/archive/2025          # 2. does the size match expectations?
echo sudo rm -rf /srv/veloz/data/archive/2025 # 3. print without running and re-read it

# 4. Preferable alternative in production: quarantine instead of deletion
sudo mkdir -p /srv/veloz/quarantine
sudo mv -n /srv/veloz/data/archive/2025 /srv/veloz/quarantine/

The quarantine is the key improvement: it turns an irreversible operation into a reversible one. After a few weeks with no incidents, the final deletion can be done calmly. When you learn find and tar in 05-01 this flow will be automated, but the judgment will stay the same.

Conclusion

You now have basic filesystem manipulation down: you create complete structures with mkdir -p, generate files with touch, copy faithfully using cp -a, understand that moving and renaming are the same operation and —most importantly— you have internalized that rm does not forgive. You know how to handle names with spaces and leading dashes using quotes and --, you have the habit of the .bak copy before editing anything, and you have left ~/veloz-ops and the /srv/veloz/data archive organized for the rest of the course.

You are still handling files one at a time or by full name; selecting them by patterns arrives in 02-05 with wildcards, and complex searches in 05-01 with find.

In the next lesson, 02-02, we stop moving files and start reading what is inside them. You will learn to explore /var/log/veloz/app.log live, to search for errors with grep, to extract columns from shipments.csv and to sort and count results. This is the moment when Veloz Envíos' data starts answering questions.

Bash Programming Course

Module 1: Introduction to Bash

Module 2: Basic Bash Commands

Module 3: Scripting Fundamentals

Module 4: Intermediate Scripting

Module 5: Advanced Scripting Techniques

Module 6: Working with External Tools

Module 7: Automation and Scheduling

Module 8: Best Practices and Optimization

Module 9: Real-World Projects

© Copyright 2026. All rights reserved