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
- Creating directories with
mkdirandmkdir -p - Creating and touching files with
touch - Copying with
cp: the options that matter - Moving and renaming with
mv - Deleting with
rmandrmdir: the point of no return - Problematic names: spaces and leading dashes
- Quick backups before touching anything
- Hands-on case: organizing
~/veloz-opsand archiving the day's CSV
- Creating directories with
mkdir and mkdir -p
mkdir and mkdir -pmkdir (make directory) creates directories, one or several per invocation. The problem shows up with nested structures:
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: 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.
- Creating and touching files with
touch
touchtouch 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.
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.
- Copying with
cp: the options that matter
cp: the options that matterThe 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 directoryIf 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.
'/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.
- Moving and renaming with
mv
mvmv 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 oncePractical 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.
In scripts, -n is preferable to -i: a script should not sit waiting for an answer nobody is going to type.
- Deleting with
rm and rmdir: the point of no return
rm and rmdir: the point of no returnRead 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 emptyThere 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 rightAnd 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.
- 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:
Worse is a file whose name starts with a dash:
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 dashThe -- 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.
- 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.
- Hands-on case: organizing
~/veloz-ops and archiving the day's CSV
~/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.csvThe {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.
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
cpandmvwarn before overwriting. They do not. Interactively you can definealias cp='cp -i'andalias 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
-rwhen you meant-a. Copying~/veloz-opswith-rleaves the scripts with today's date and, in some cases, without the execute bit. - Forgetting that
rm -rdoes 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
echoin front and read the result. - Inferring
cp's destination from the trailing slash. Check withls -dwhether it exists. - Filling the disk with
.bakfiles. Quick copies are for the moment; clean up the ones that are no longer useful or you will end up debugging adfat 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:
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
-
What it actually does: the space before
/2025turns 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/2025directory at the root. Withsudoand-f, without a single question or error message. -
The intent was to delete the 2025 subdirectory:
/srv/veloz/data/archive/2025. -
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
- What Is Bash?
- Setting Up Your Environment
- Basic Command Line Navigation
- Understanding the Shell
- Finding Help: man, help and --help
Module 2: Basic Bash Commands
- File and Directory Operations
- Text Processing Commands
- File Permissions and Ownership
- Redirection and Piping
- Wildcards and Path Expansion
- History and Keyboard Shortcuts
Module 3: Scripting Fundamentals
- Creating and Running a Script
- Variables and Constants
- Basic Operators
- Conditional Statements
- Arguments and User Input
- Quoting, Expansion and Substitution
Module 4: Intermediate Scripting
- Loops in Bash
- Functions in Bash
- Arrays and Associative Arrays
- String Manipulation
- The case Statement and Interactive Menus
- Arithmetic and Numeric Calculations
Module 5: Advanced Scripting Techniques
- Advanced File Operations
- Process Management
- Error Handling and Debugging
- Regular Expressions
- Advanced I/O: Descriptors and Here-Documents
- Modular Scripts and Reusable Libraries
Module 6: Working with External Tools
Module 7: Automation and Scheduling
- Cron Jobs
- Automating Tasks
- Backup and Restore Scripts
- Monitoring and Logging
- Services and Timers with systemd
- Remote Automation with SSH
Module 8: Best Practices and Optimization
- Writing Readable Code
- Optimizing Bash Scripts
- Security Considerations
- Version Control with Git
- Static Analysis with ShellCheck and shfmt
- Automated Testing with Bats
- Portability: POSIX sh versus Bashisms
