Nandishwar Singh

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

  1. A place to practise
  2. touch: empty files
  3. mkdir: directories
  4. cp: copy
  5. mv: move and rename
  6. rm: delete
  7. Common mistakes
  8. Practice
  9. 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

  1. Create jan.log, feb.log and mar.log with one command using braces.
  2. Create files day01 to day07.
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

  1. Make site/images/2026/10 in one command.
  2. Make backup/daily, backup/weekly and backup/monthly in 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:

OptionDoes
-iAsk before overwriting
-nNever overwrite
-uOnly copy if the source is newer than the copy
-vPrint each file as it's copied
-aArchive: 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

  1. Copy /etc/hostname into ~/files-lab under the name myname.txt.
  2. Copy the whole site directory to site-backup, keeping all timestamps.
  3. Copy notes.txt into projects/, 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

  1. Rename notes-backup.txt to notes.old.
  2. Move every report-*.txt file in ~/files-lab into backup/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

$ 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

  1. Delete the old-app directory, confirming once.
  2. Try to remove projects with rmdir. 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

Practice

Start fresh for these:

$ mkdir ~/practice7
$ cd ~/practice7
  1. In one command, create the directories shop/products, shop/orders and shop/customers.
Answer
$ mkdir -p shop/{products,orders,customers}
$ ls shop
customers  orders  products
  1. Create order-001.json to order-010.json inside shop/orders with 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}.

  1. Back up the whole shop directory as shop-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.

  1. Rename shop/customers to shop/clients.
Answer
$ mv shop/customers shop/clients
$ ls shop
clients  orders  products
  1. 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'
  1. 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

CommandWhat it does
touch a bCreate empty files (or update their times)
{1..5}, {a,b}, {01..10}Bash writes out the list for you
mkdir -p a/b/cMake a path, parents included
cp src dest, cp a b dir/Copy one file, several into a directory
cp -r dir destCopy a directory
cp -a src destCopy keeping everything (for backups)
cp -i, -n, -u, -vAsk, never overwrite, only newer, verbose
mv old newRename
mv file dir/Move
rm fileDelete a file
rm -r dir, rm -rI dirDelete a directory (asking once)
rmdir dirDelete an empty directory
shred -uz fileOverwrite, then delete