Home / Learn Linux / Lesson 7
Creating, copying, moving and deleting files
touch, mkdir, cp, mv and rm, the flags that matter, and how not to delete the wrong thing.
- Lesson
- 7 of 30
- Time
- About 40 minutes
On this page
- A place to practise
- touch: empty files
- mkdir: directories
- cp: copy
- mv: move and rename
- rm: delete
- Common mistakes
- Practice
- Cheat sheet
A place to practise
Everything in this lesson happens in ~/files-lab, so nothing you break matters. Go there first:
ubuntu@linux:~$ mkdir ~/files-lab ubuntu@linux:~$ cd ~/files-lab
touch: empty files
You met touch in lesson 5 as the command that updates timestamps. When the file doesn't exist yet, it creates an empty one, and that's what it's mostly used for. Give it as many names as you like:
$ touch notes.txt todo.txt $ ls -l total 0 -rw-rw-r-- 1 ubuntu ubuntu 0 Oct 21 11:36 notes.txt -rw-rw-r-- 1 ubuntu ubuntu 0 Oct 21 11:36 todo.txt
Bash can write lists of names for you with braces. {1..5} expands to 1 2 3 4 5, and {a,b,c} to a b c. Bash expands them before the command runs, so touch simply receives five names:
$ echo report-{1..5}.txt report-1.txt report-2.txt report-3.txt report-4.txt report-5.txt $ touch report-{1..5}.txt $ ls notes.txt report-2.txt report-4.txt todo.txt report-1.txt report-3.txt report-5.txt
Try it
- Create
jan.log,feb.logandmar.logwith one command using braces. - Create files
day01today07.
Answer
$ touch {jan,feb,mar}.log $ touch day0{1..7} $ ls day01 day04 day07 mar.log report-2.txt report-5.txt day02 day05 feb.log notes.txt report-3.txt todo.txt day03 day06 jan.log report-1.txt report-4.txt
mkdir: directories
mkdir makes a directory. To make a whole path at once, including parents that don't exist yet, add -p. Without it, mkdir refuses:
$ mkdir projects $ mkdir projects/web/css mkdir: No such file or directory $ mkdir -p projects/web/css
-p also doesn't complain if the directory already exists, which makes it safe in scripts. Braces make whole layouts in one line, and -v prints what was made:
$ mkdir -pv app/{src,docs,tests} mkdir: created directory 'app' mkdir: created directory 'app/src' mkdir: created directory 'app/docs' mkdir: created directory 'app/tests' $ tree app app ├── docs ├── src └── tests 4 directories, 0 files
Try it
- Make
site/images/2026/10in one command. - Make
backup/daily,backup/weeklyandbackup/monthlyin one command.
Answer
$ mkdir -p site/images/2026/10 $ mkdir -p backup/{daily,weekly,monthly} $ tree site backup site └── images └── 2026 └── 10 backup ├── daily ├── monthly └── weekly 8 directories, 0 files
cp: copy
cp source destination copies a file. If the destination is an existing directory, the copy goes inside it with the same name:
$ echo "first draft" > notes.txt $ cp notes.txt notes-backup.txt $ cp notes.txt projects/ $ ls notes* projects/ notes-backup.txt notes.txt projects/: notes.txt web
To copy several files into a directory, list them all and put the directory last. Wildcards work too:
$ cp report-1.txt report-2.txt backup/ $ cp report-*.txt backup/daily/ $ ls backup backup/daily backup: daily monthly report-1.txt report-2.txt weekly backup/daily: report-1.txt report-2.txt report-3.txt report-4.txt report-5.txt
Copying directories
cp won't copy a directory unless you add -r (recursive), which copies everything inside it too:
$ cp app app-copy cp: -r not specified; omitting directory 'app' $ cp -r app app-copy $ tree app-copy app-copy ├── docs ├── src └── tests 4 directories, 0 files
Watch out for one thing with -r. If the destination doesn't exist, cp -r app app-copy creates app-copy as an exact copy. If it already exists, the copy goes inside it, as app-copy/app. Run the same command twice and you get a nested copy:
$ cp -r app app-copy $ ls app-copy app docs src tests
Don't overwrite by accident
cp overwrites an existing file without asking. These options protect you:
| Option | Does |
|---|---|
-i | Ask before overwriting |
-n | Never overwrite |
-u | Only copy if the source is newer than the copy |
-v | Print each file as it's copied |
-a | Archive: copy everything, keeping times, permissions and links. The right choice for backups |
$ echo "second draft" > notes.txt $ cp -i notes.txt notes-backup.txt cp: overwrite 'notes-backup.txt'? n $ cat notes-backup.txt first draft
Answering n kept the old backup. A plain cp would have replaced it.
A normal copy gets a new modify time. -a keeps the original's, which matters for backups:
$ touch -d 2026-01-01 old.txt $ cp old.txt copy-plain.txt $ cp -a old.txt copy-archive.txt $ ls -l old.txt copy-plain.txt copy-archive.txt -rw-rw-r-- 1 ubuntu ubuntu 0 Jan 1 2026 copy-archive.txt -rw-rw-r-- 1 ubuntu ubuntu 0 Oct 21 11:36 copy-plain.txt -rw-rw-r-- 1 ubuntu ubuntu 0 Jan 1 2026 old.txt
Try it
- Copy
/etc/hostnameinto~/files-labunder the namemyname.txt. - Copy the whole
sitedirectory tosite-backup, keeping all timestamps. - Copy
notes.txtintoprojects/, but only if it's newer than the copy already there, and show what happened.
Answer
$ cp /etc/hostname myname.txt $ cp -a site site-backup $ cp -uv notes.txt projects/ 'notes.txt' -> 'projects/notes.txt'
notes.txt was changed after the first copy, so -u copies it again and -v reports it.
mv: move and rename
mv moves a file, and moving it to a new name in the same directory is how you rename. Linux has no separate rename command for single files:
$ mv todo.txt tasks.txt $ mv tasks.txt projects/ $ mv {jan,feb,mar}.log backup/monthly/ $ ls projects backup/monthly backup/monthly: feb.log jan.log mar.log projects: notes.txt tasks.txt web
Unlike cp, mv moves directories without -r, because it only changes where the name points. Within the same disk, moving a 50 GB directory is instant:
$ mv app-copy old-app $ ls -d old-app old-app
mv overwrites without asking too, and it takes the same -i, -n and -v options as cp. Use -i when you're moving files into a directory that might already have files with those names.
Try it
- Rename
notes-backup.txttonotes.old. - Move every
report-*.txtfile in~/files-labintobackup/weekly, printing each move.
Answer
$ mv notes-backup.txt notes.old $ mv -v report-*.txt backup/weekly/ renamed 'report-1.txt' -> 'backup/weekly/report-1.txt' renamed 'report-2.txt' -> 'backup/weekly/report-2.txt' renamed 'report-3.txt' -> 'backup/weekly/report-3.txt' renamed 'report-4.txt' -> 'backup/weekly/report-4.txt' renamed 'report-5.txt' -> 'backup/weekly/report-5.txt'
rm: delete
rm deletes files. There's no recycle bin on the command line: deleted means gone.
$ rm notes.old $ rm day0* $ ls app copy-archive.txt myname.txt old-app projects site-backup backup copy-plain.txt notes.txt old.txt site
To delete a directory and everything inside it, add -r. rmdir removes a directory only if it's empty, which makes it a safe way to tidy up:
$ rmdir site rmdir: failed to remove 'site': Directory not empty $ rm -r site-backup $ mkdir empty $ rmdir empty $ ls app copy-archive.txt myname.txt old-app projects backup copy-plain.txt notes.txt old.txt site
rmdir site refused because site still had things in it, so nothing was lost.
Deleting safely
- **
-iasks about every file.**-I(capital) asks once if you're deleting more than three files or deleting recursively.rm -rI diris a good habit. - **
-fmeans force.** It never asks and doesn't complain about missing files.rm -rfis the most dangerous command you'll type, because nothing stops it. - **Check wildcards with
lsfirst.** Beforerm *.log, runls *.logand look at what matches. A stray space inrm * .logdeletes everything in the directory, then complains that.logdoesn't exist. - **Never
rm -rfwith a path built from a variable in a script** unless you've checked the variable isn't empty. If it is,rm -rf "$DIR/"becomesrm -rf /. Modernrmrefuses to delete/itself, but/*would still go through.
$ rm -rI backup rm: remove 1 argument recursively? n $ ls -d backup backup
shred: when deleted has to mean unreadable
rm removes the file's name, but its data stays on the disk until something overwrites it, and recovery tools can sometimes find it. shred overwrites the contents with random data first. -u deletes the file afterwards, -z adds a final pass of zeros and -v shows the passes:
$ echo "card 1234 5678" > secret.txt $ shred -uzv secret.txt shred: secret.txt: pass 1/4 (random)... shred: secret.txt: pass 2/4 (random)... shred: secret.txt: pass 3/4 (random)... shred: secret.txt: pass 4/4 (000000)... shred: secret.txt: removing shred: secret.txt: renamed to 0000000000 shred: 0000000000: renamed to 000000000 shred: 000000000: renamed to 00000000 shred: 00000000: renamed to 0000000 shred: 0000000: renamed to 000000 shred: 000000: renamed to 00000 shred: 00000: renamed to 0000 shred: 0000: renamed to 000 shred: 000: renamed to 00 shred: 00: renamed to 0 shred: secret.txt: removed $ ls secret.txt ls: cannot access 'secret.txt': No such file or directory
shred is only reliable on ordinary hard disks. On SSDs, on journaling filesystems like ext4 in some modes, and on VMs, copies of old data can survive elsewhere on the disk, so for truly sensitive data, encrypt the whole disk instead.
Try it
- Delete the
old-appdirectory, confirming once. - Try to remove
projectswithrmdir. What happens, and why?
Answer
$ rm -rI old-app rm: remove 1 argument recursively? y $ rmdir projects rmdir: failed to remove 'projects': Directory not empty
rmdir only removes empty directories, and projects still has files in it.
Common mistakes
- **
cp -rinto an existing directory.** You getdest/sourceinstead of a copy atdest. Check withlswhether the destination exists first. - Overwriting without noticing.
cpandmvdon't ask. Use-ior-nwhen it matters. - Spaces around wildcards.
rm * .loginstead ofrm *.logdeletes everything. Alwayslsthe pattern first. - **
rm -rfas a habit.** Userm -r, and add-I. Save-ffor scripts where you've checked the path. - Expecting a trash can. There isn't one. Copy before you experiment:
cp -a important important.bak.
Practice
Start fresh for these:
$ mkdir ~/practice7 $ cd ~/practice7
- In one command, create the directories
shop/products,shop/ordersandshop/customers.
Answer
$ mkdir -p shop/{products,orders,customers} $ ls shop customers orders products
- Create
order-001.jsontoorder-010.jsoninsideshop/orderswith one command.
Answer
$ touch shop/orders/order-0{01..10}.json $ ls shop/orders order-001.json order-004.json order-007.json order-010.json order-002.json order-005.json order-008.json order-003.json order-006.json order-009.json
Bash keeps the leading zeros when the range is written with them: {01..10}.
- Back up the whole
shopdirectory asshop-2026-10-21(use today's date with$(date +%F)), keeping all timestamps.
Answer
$ cp -a shop shop-$(date +%F) $ ls shop shop-2026-10-21
%F is the same as %Y-%m-%d.
- Rename
shop/customerstoshop/clients.
Answer
$ mv shop/customers shop/clients $ ls shop clients orders products
- Move orders 001 to 005 into a new directory
shop/archive, printing each move.
Answer
$ mkdir shop/archive $ mv -v shop/orders/order-00{1..5}.json shop/archive/ renamed 'shop/orders/order-001.json' -> 'shop/archive/order-001.json' renamed 'shop/orders/order-002.json' -> 'shop/archive/order-002.json' renamed 'shop/orders/order-003.json' -> 'shop/archive/order-003.json' renamed 'shop/orders/order-004.json' -> 'shop/archive/order-004.json' renamed 'shop/orders/order-005.json' -> 'shop/archive/order-005.json'
- Delete the backup directory from task 3, but check what you're about to delete first.
Answer
$ ls -d shop-* shop-2026-10-21 $ rm -rI shop-* rm: remove 1 argument recursively? y $ ls shop
Cheat sheet
| Command | What it does |
|---|---|
touch a b | Create empty files (or update their times) |
{1..5}, {a,b}, {01..10} | Bash writes out the list for you |
mkdir -p a/b/c | Make a path, parents included |
cp src dest, cp a b dir/ | Copy one file, several into a directory |
cp -r dir dest | Copy a directory |
cp -a src dest | Copy keeping everything (for backups) |
cp -i, -n, -u, -v | Ask, never overwrite, only newer, verbose |
mv old new | Rename |
mv file dir/ | Move |
rm file | Delete a file |
rm -r dir, rm -rI dir | Delete a directory (asking once) |
rmdir dir | Delete an empty directory |
shred -uz file | Overwrite, then delete |
← Reading files: cat, less, head and tailPipes and redirection →