Basic Linux commands — exercises with paths, wildcards, an IC2 hand-in and a folder in chaos (and the space that wiped out my .c files)
One Friday afternoon I wanted to clean the temporary files out of my labs folder before uploading it. I typed rm *.tmp… or so I thought. A space slipped in between the asterisk and .tmp, I pressed Enter and the terminal only said it couldn’t find .tmp. When I ran ls, nothing was left: neither the temporary files nor my .c files. A single space. You’ll see it live in “What’s wrong here?”, with the real output, and you’ll understand why rm never even noticed there was a wildcard.
These are the basic Linux commands exercises with solutions, the end of the topic after the theory and the practice. There are four exercises in three levels (paths, wildcards, handing in a lab and a folder in chaos), three fragments with a trap, the commented solutions with their real output and a cheat sheet to keep handy in the lab.
Table of Contents
How to use this article
- Open the Fedora terminal and, for each exercise, first copy the setup block: it creates the folders and files you’ll work with.
- Try to solve it on your own. If you’ve been stuck for more than ten minutes, open the hint.
- Before looking at the solution, do the “What’s wrong here?” fragments: predict what comes out and then check it.
- Compare with the commented solutions. There isn’t a single right answer: if your command does the same, it counts.
The outputs are real, from a single run as the user sergio. On your computer you’ll see your own user and date. My Fedora is in Spanish, so its default folders are Descargas, Documentos and Escritorio. ls will show names in columns; here they appear one per line.
Before you start: wildcards in Linux
Wildcards are special characters that stand for one or more characters in file names. They save you typing files one by one:
| Wildcard | Means | Example | Matches |
|---|---|---|---|
* | Zero or more characters of any kind | *.c | main.c, practice1.c… |
? | Exactly one character | practice?.c | practice1.c, but not practice10.c |
[abc] | One of the characters in the list | [aeiou]* | Anything starting with a vowel |
[a-z], [0-9] | One from the range | *[0-9]* | Anything with a digit in the name |
And the most important thing, the one that explains almost every wildcard mistake: wildcards are expanded by bash, not by the command. When you type ls *.c, bash looks for the files that match, replaces the pattern with the list (ls main.c practice1.c) and only then runs ls. The command never sees the asterisk. Three rules follow from that:
- They work with any command that takes file names:
ls,cp,mv,rm,cat… - If the pattern matches nothing, bash leaves it as it is and the command receives a literal
*.pdf(and complains that it doesn’t exist). *doesn’t match hidden files: for those, the pattern must start with a dot (.h*).
Using a wildcard with rm without first checking what it expands to. First ls *.tmp (or echo *.tmp); if the list is what you expect, change ls to rm.
Basic level
Exercise 1: find files with paths, without moving
You have a small C project in your home folder. Set it up with this:
mkdir -p project/src/utils project/src/tests project/docs touch project/src/main.c project/src/utils/helpers.c project/src/utils/math.c touch project/src/tests/test1.c project/docs/manual.txt echo 'IC2 project' > project/docs/readme.txt
Solve these tasks without using cd, only with paths from your home folder:
- List the contents of
project/src/utils. - Show the contents of
readme.txt. - Copy
helpers.ctoproject/src/tests/and check it’s there. - List all the project’s
.cfiles with a single command and wildcards. - How many
.cfiles are there inproject/src/utils?
Expected output: in step 2 you should see IC2 project; in step 4, five files (counting the copy from step 3); in step 5, two.
cat and cp accept paths just like ls. For step 4, a * only covers one folder level: project/src/*/*.c finds what’s in utils and tests, but not main.c, which is one level higher.
Exercise 2: wildcards in action
Now a folder with files of every kind. Set it up:
mkdir wildcards cd wildcards touch agenda1.txt agenda2.txt agenda3.txt data1.csv data2.csv data3.csv touch script1.sh script2.sh image.png frame.jpg video.mp4 .hidden1 .hidden2 ls -a
The last setup command shows you what’s there, hidden files included:
$ mkdir wildcards $ cd wildcards $ touch agenda1.txt agenda2.txt agenda3.txt data1.csv data2.csv data3.csv $ touch script1.sh script2.sh image.png frame.jpg video.mp4 .hidden1 .hidden2 $ ls -a . .. .hidden1 .hidden2 agenda1.txt agenda2.txt agenda3.txt data1.csv data2.csv data3.csv frame.jpg image.png script1.sh script2.sh video.mp4
Solve with wildcards:
- List only the
.txtfiles. - List the
.txtand.csvfiles in a single command. - List the ones starting with
data. - List the ones with a digit in the name.
- List the hidden ones starting with
.h. - Copy the text files (
.txtand.csv) to a new foldertexts. - What happens with
ls *.pdf? Does it give an error or show nothing?
Expected output: step 4 gives nine files (watch out for video.mp4); step 7, an error.
You can put several patterns in the same command (ls *.txt *.csv). For step 4, remember the digit can be anywhere in the name: you need a * on each side of [0-9].
Intermediate level
Exercise 3: prepare the lab 2 hand-in
It’s Sunday night and lab 2 for IC2 is due. You have this:
mkdir -p IC2/Practices/P2/tests IC2/Hand_ins cd IC2/Practices/P2 touch main.c functions.c functions.h notes.txt draft.c ls
Here’s what it shows:
$ mkdir -p IC2/Practices/P2/tests IC2/Hand_ins $ cd IC2/Practices/P2 $ touch main.c functions.c functions.h notes.txt draft.c $ ls draft.c functions.c functions.h main.c notes.txt tests
From inside P2, do the hand-in:
- A backup of the whole lab in
Practices/P2_copy, just in case. - Rename
main.ctop2_medina.c(the lecturer wants your surname in the name; use yours). - Copy to
IC2/Hand_insonly what gets handed in:p2_medina.c,functions.candfunctions.h. - Delete
draft.cwith confirmation. - Remove the
testsfolder, which is empty. - Check how
P2,Hand_insand the backup look.
Expected output: Hand_ins with three files; P2 with functions.c, functions.h, notes.txt and p2_medina.c; and the backup, untouched, with the original main.c.
From IC2/Practices/P2, Hand_ins is two levels up: ../../Hand_ins/. cp accepts several sources and one destination at the end, which must be a folder.
Final challenge
Exercise 4: sort out the chaos
A chaos folder with everything mixed up, like anyone’s halfway through the term. Create it:
mkdir chaos
cd chaos
touch practice1.c practice2.c practice3.c exam_final.c exam_midterm.c
touch class_notes_linux.txt class_notes_c.txt class_notes_python.txt photo_class.jpg photo_lab.jpg
touch delete_me.tmp clutter.tmp dump.tmp
echo 'int main(void) { return 0; }' > practice1.c
ls
This is what it looks like:
$ mkdir chaos
$ cd chaos
$ touch practice1.c practice2.c practice3.c exam_final.c exam_midterm.c
$ touch class_notes_linux.txt class_notes_c.txt class_notes_python.txt photo_class.jpg photo_lab.jpg
$ touch delete_me.tmp clutter.tmp dump.tmp
$ echo 'int main(void) { return 0; }' > practice1.c
$ ls
class_notes_c.txt
class_notes_linux.txt
class_notes_python.txt
clutter.tmp
delete_me.tmp
dump.tmp
exam_final.c
exam_midterm.c
photo_class.jpg
photo_lab.jpg
practice1.c
practice2.c
practice3.c
Your goal is to leave it like this:
chaos/ ├── code/ │ ├── practices/ ← the practice*.c files │ └── exams/ ← the exam*.c files ├── class_notes/ ← the class_notes*.txt files ├── multimedia/ ← the .jpg photos └── (the .tmp files, deleted)
Rules:
- No moving file by file: one wildcard per group.
- The
.tmpfiles are deleted with confirmation, and you check which ones they are first. - At the end, a single command that shows the whole structure.
- Ten commands at most.
mkdir -p creates all five folders at once (code included). With mv practice*.c code/practices/ you move the three labs in one command. And ls -R shows the full tree from where you are.
What’s wrong here?
Three short fragments that look correct. Read each one, predict what will come out and only then look at the real output.
Fragment 1: cleaning up temporary files
In the cleanup folder there are two code files and two temporary ones. I want to delete the temporary files:
ls rm * .tmp ls
What’s left in the folder? Think about it before going on.
$ ls clutter.tmp dump.tmp main.c notes.txt $ rm * .tmp rm: cannot remove '.tmp': No such file or directory $ ls
Nothing is left. The space between * and .tmp turns one pattern into two arguments: * (which bash expands to all the files) and .tmp (a name that doesn’t exist). rm receives five names, deletes the four that exist and only complains about the fifth. The error message makes you think nothing happened, when everything did.
Reading rm‘s error as “nothing was deleted”. rm processes its arguments one by one: an error with one doesn’t stop it deleting the others.
The fix: no space, and look first with ls:
ls *.tmp rm *.tmp ls
The real output:
$ ls *.tmp clutter.tmp dump.tmp $ rm *.tmp $ ls main.c notes.txt
Before an rm with wildcards, type the same command with echo in front: echo rm * .tmp shows you exactly what rm will receive, without deleting anything.
Fragment 2: the backup that didn’t back up
I want a backup of the whole Practices folder before touching anything:
cp Practices Practices_copy ls
Do you have a backup? Predict and look:
$ cp Practices Practices_copy cp: -r not specified; omitting directory 'Practices' $ ls Practices
There’s no backup: ls only shows Practices. cp without -r skips folders: it prints a warning (“omitting directory”) and carries on as if nothing happened. If you don’t read the message, you think you have a backup and you don’t.
Trusting that nothing came out in red. There are no colours in the terminal to warn you: you have to read every line the command prints.
With -r (recursive) cp goes into the folder and copies everything inside:
cp -r Practices Practices_copy ls -R Practices_copy
What the terminal shows:
$ cp -r Practices Practices_copy $ ls -R Practices_copy Practices_copy: P1 Practices_copy/P1: hello.c
ls -R on the copy is the quick check: if hello.c appears inside P1, the copy is complete.
Fragment 3: the folder with a space
I want a folder called “My practices” and to go into it:
mkdir My practices ls cd My practices
How many folders are created? Can I go in?
$ mkdir My practices $ ls Descargas Documentos Escritorio My practices $ cd My practices bash: cd: too many arguments
Two folders are created, My and practices, because the space separates arguments and mkdir receives two names. And cd My practices fails with “too many arguments”: cd only accepts one destination and it got two.
Using spaces in file and folder names in Linux. They work, but they force you to use quotes in every command.
The fix: quotes (or, even better, an underscore: My_practices):
mkdir "My practices" ls cd "My practices" pwd
Result:
$ mkdir "My practices" $ ls Descargas Documentos Escritorio My practices $ cd "My practices" $ pwd /home/sergio/My practices
If you press Tab after cd My, bash completes the name with the backslash it needs: cd My\ practices. The backslash tells it that space is part of the name.
Commented solutions
Solution to exercise 1
# 1. contents of utils without leaving my folder ls project/src/utils # 2. view readme.txt: cat accepts paths just like ls cat project/docs/readme.txt # 3. copy helpers.c to tests (destination = existing folder) cp project/src/utils/helpers.c project/src/tests/ ls project/src/tests # 4. all the .c files: one * per folder level ls project/src/*.c project/src/*/*.c # ** in bash does NOT go down every level: it means the same as * ls project/**/*.c # 5. how many .c files are in utils ls project/src/utils/*.c
The real output:
$ ls project/src/utils helpers.c math.c $ cat project/docs/readme.txt IC2 project $ cp project/src/utils/helpers.c project/src/tests/ $ ls project/src/tests helpers.c test1.c $ ls project/src/*.c project/src/*/*.c project/src/main.c project/src/tests/helpers.c project/src/tests/test1.c project/src/utils/helpers.c project/src/utils/math.c $ ls project/**/*.c project/src/main.c $ ls project/src/utils/*.c project/src/utils/helpers.c project/src/utils/math.c
Why it’s done this way:
- Paths instead of
cd. Every command accepts a relative path from where you are. It’s faster and you don’t risk getting lost. cp ... project/src/tests/copies inside becausetestsis an existing folder.ls project/src/testsconfirms it: now there are two files.- Two patterns for all the
.cfiles.project/src/*.cfindsmain.candproject/src/*/*.cthe four in the subfolders: 1 + 4 = 5. **is not recursive by default. In bash,project/**/*.cmeans the same asproject/*/*.c: a single level. That’s why it only findsproject/src/main.c. For it to go down every level you have to turn onshopt -s globstar, which is off by default.- Counting by eye.
ls project/src/utils/*.cgives two lines: two files. When you get to pipes,ls ... | wc -lwill count them for you.
| Pattern | Levels it looks at | Finds |
|---|---|---|
project/src/*.c | Only src | main.c |
project/src/*/*.c | One level below src | helpers.c (×2), test1.c, math.c |
project/**/*.c | One below project (without globstar) | main.c |
Solution to exercise 2
# 1. only the .txt files ls *.txt # 2. .txt and .csv in a single command: two patterns ls *.txt *.csv # 3. the ones starting with data ls data* # 4. the ones with a digit anywhere in the name ls *[0-9]* # 5. the hidden ones starting with .h (the pattern already has the dot) ls -d .h* # 6. copy the text files to a new folder mkdir texts cp *.txt *.csv texts/ ls texts # 7. a pattern that matches nothing reaches ls as it is ls *.pdf
Here’s what really comes out:
$ ls *.txt agenda1.txt agenda2.txt agenda3.txt $ ls *.txt *.csv agenda1.txt agenda2.txt agenda3.txt data1.csv data2.csv data3.csv $ ls data* data1.csv data2.csv data3.csv $ ls *[0-9]* agenda1.txt agenda2.txt agenda3.txt data1.csv data2.csv data3.csv script1.sh script2.sh video.mp4 $ ls -d .h* .hidden1 .hidden2 $ mkdir texts $ cp *.txt *.csv texts/ $ ls texts agenda1.txt agenda2.txt agenda3.txt data1.csv data2.csv data3.csv $ ls *.pdf ls: cannot access '*.pdf': No such file or directory
Why it’s done this way:
ls *.txt *.csv: two patterns, each expanded separately, andlsreceives six names.*[0-9]*finds nine, not eight.video.mp4has a4in its extension and the pattern looks at the whole name. Count: threeagenda, threedata, twoscriptandvideo.mp4= 9.ls -d .h*: the pattern already starts with a dot, so it matches hidden files without needing-a. The-dstopslsgoing into them if they were folders.cp *.txt *.csv texts/: several sources and a destination that’s a folder. We createtextsfirst becausecpdoesn’t create folders.ls *.pdfgives an error. There’s no.pdf, so bash expands nothing andlsreceives the text*.pdfas it is. It looks for a file literally called*.pdfand doesn’t find it.
ls *.{txt,csv} also works, but braces aren’t a wildcard: bash turns them into *.txt *.csv before looking for files. If it confuses you, use the two separate patterns.
Solution to exercise 3
# 1. backup of the whole lab (a folder: -r is needed) cp -r ../P2 ../P2_copy ls .. # 2. rename main.c with my surname mv main.c p2_medina.c # 3. copy to Hand_ins only what gets handed in: several sources, one destination cp p2_medina.c functions.c functions.h ../../Hand_ins/ ls ../../Hand_ins # 4. delete the draft, asking first rm -i draft.c # 5. remove the tests folder, which is empty rmdir tests # 6. check the result ls ls ../P2_copy
When you run it:
$ cp -r ../P2 ../P2_copy $ ls .. P2 P2_copy $ mv main.c p2_medina.c $ cp p2_medina.c functions.c functions.h ../../Hand_ins/ $ ls ../../Hand_ins functions.c functions.h p2_medina.c $ rm -i draft.c rm: remove regular empty file 'draft.c'? y $ rmdir tests $ ls functions.c functions.h notes.txt p2_medina.c $ ls ../P2_copy draft.c functions.c functions.h main.c notes.txt tests
Why it’s done this way:
- The backup comes first. If something goes wrong in the next steps,
P2_copykeeps the lab as it was, withmain.canddraft.cincluded (the lastlsconfirms it). cp -r ../P2 ../P2_copy: from insideP2, the folder is named as../P2. The copy ends up as a sibling, inPractices.mvto rename because the file doesn’t need to exist twice: only its name changes.- One
cpwith three sources. The last argument (../../Hand_ins/) is the destination; everything before it is copied inside.notes.txtisn’t handed in, so it’s not there. rm -iandrmdir. They’re the two “safety net” ways to delete:rm -iasks andrmdironly works if the folder is empty.
| Step | Command | Where everything ends up |
|---|---|---|
| 1 | cp -r ../P2 ../P2_copy | Full copy (6 items) in P2_copy |
| 2 | mv main.c p2_medina.c | In P2, main.c is now called p2_medina.c |
| 3 | cp ... ../../Hand_ins/ | Hand_ins: 3 files |
| 4 | rm -i draft.c | Gone from P2 (still in the backup) |
| 5 | rmdir tests | Gone from P2 (still in the backup) |
Solution to exercise 4
# 1. the whole structure at once mkdir -p code/practices code/exams class_notes multimedia # 2-5. one wildcard per group of files mv practice*.c code/practices/ mv exam*.c code/exams/ mv class_notes*.txt class_notes/ mv *.jpg multimedia/ # 6. check before deleting, then delete asking one by one ls *.tmp rm -i *.tmp # 7. the complete result ls -R
Result:
$ mkdir -p code/practices code/exams class_notes multimedia $ mv practice*.c code/practices/ $ mv exam*.c code/exams/ $ mv class_notes*.txt class_notes/ $ mv *.jpg multimedia/ $ ls *.tmp clutter.tmp delete_me.tmp dump.tmp $ rm -i *.tmp rm: remove regular empty file 'clutter.tmp'? y rm: remove regular empty file 'delete_me.tmp'? y rm: remove regular empty file 'dump.tmp'? y $ ls -R .: class_notes code multimedia ./class_notes: class_notes_c.txt class_notes_linux.txt class_notes_python.txt ./code: exams practices ./code/exams: exam_final.c exam_midterm.c ./code/practices: practice1.c practice2.c practice3.c ./multimedia: photo_class.jpg photo_lab.jpg
That’s nine commands, one fewer than the maximum. This is where the thirteen files end up:
Why it’s done this way:
mkdir -pwith four paths creates five folders:codeappears by itself because-pcreates the intermediate ones.- One wildcard per group.
practice*.cmatches the three labs andexam*.cthe two exams. Watch out for plain*.c: it would have put the labs and the exams in the same folder. ls *.tmpbeforerm -i *.tmp. It’s the rule from fragment 1: look at what the pattern expands to before deleting.rm -iasks three times, once per file, and you answeryto all three.ls -Rwith no arguments walks the current folder (.) and all its subfolders: it’s the “final picture” the task asked for.
| Command | Files it moves or deletes | Still loose in chaos |
|---|---|---|
| (start) | — | 13 |
mv practice*.c code/practices/ | 3 | 10 |
mv exam*.c code/exams/ | 2 | 8 |
mv class_notes*.txt class_notes/ | 3 | 5 |
mv *.jpg multimedia/ | 2 | 3 |
rm -i *.tmp | 3 | 0 |
Count checked: 3 + 2 + 3 + 2 + 3 = 13.
Try it in your terminal
There’s no Python Tutor for Linux, but there’s a trick to “see” what bash does before running anything: echo. Repeat fragment 1 without deleting anything (with the cleanup folder freshly created):
echo *.tmp echo * .tmp echo rm * .tmp
What you should see:
$ echo *.tmp clutter.tmp dump.tmp $ echo * .tmp clutter.tmp dump.tmp main.c notes.txt .tmp $ echo rm * .tmp rm clutter.tmp dump.tmp main.c notes.txt .tmp
What to watch:
echo *.tmpshows the two files that match: that’s whatrm *.tmpwould receive.echo * .tmpshows the four files plus a lone.tmpat the end: the space has split the pattern in two.echo rm * .tmpprints the full command exactly as bash would run it. That’s the smoking gun.- None of the three commands deleted anything:
echoonly prints. Do this every time you’re unsure about a wildcard.
Cheat sheet: basic Linux commands
The essentials in 30 seconds: pwd tells you where you are and ls what’s there. A path with / in front starts at the root; without it, in your current folder. cp duplicates, mv moves or renames and rm deletes with no recycle bin. Folders need -r to be copied, or deleted with rm, and mkdir -p creates whole paths. Wildcards are expanded by bash: check with ls or echo before deleting.
| I want to… | Code | Result |
|---|---|---|
| Know where I am | pwd | /home/sergio |
| See everything, hidden included, in detail | ls -la | One line per item |
| See a whole tree | ls -R folder | Each folder with its contents |
| Go up one level / two levels | cd .. / cd ../.. | The folder above |
| Go home / back to the previous one | cd / cd - | ~ / the previous folder |
| Create a whole path | mkdir -p a/b/c | The three folders |
| Remove an empty folder | rmdir folder | Error if it isn’t empty |
| Create an empty file | touch file | 0 bytes |
| View a file | cat file | Its content |
| Copy into a folder | cp file folder/ | Same name, another place |
| Copy a folder | cp -r source copy | All its contents |
| Rename / move | mv old new / mv file folder/ | The original disappears |
| Delete asking first | rm -i file | rm: remove ...? |
| Delete a folder with everything | rm -r folder | No questions, no recycle bin |
| Absolute path of something | realpath file | /home/sergio/... |
| Write / append a line | echo 'x' > f / echo 'x' >> f | > overwrites, >> appends |
| Wildcard | Example | Matches |
|---|---|---|
* | ls *.c | All .c files (not hidden ones) |
? | ls practice?.c | practice1.c, not practice10.c |
[0-9] | ls *[0-9]* | Anything with a digit |
.h* | ls -d .h* | Hidden files starting with .h |
Exercise templates:
# Exercise 1: paths without moving ls project/src/*.c project/src/*/*.c # every .c file (5) # Exercise 2: several patterns, one destination cp *.txt *.csv texts/ # Exercise 3: hand-in cp -r ../P2 ../P2_copy # the backup first cp p2_medina.c functions.c functions.h ../../Hand_ins/ # Exercise 4: one wildcard per group mkdir -p code/practices code/exams class_notes multimedia mv practice*.c code/practices/ ls *.tmp && rm -i *.tmp # look before deleting
| Symptom | Likely cause |
|---|---|
No such file or directory | You’re in another folder, there’s an extra / in front or a wrong capital letter |
cp: -r not specified; omitting directory | Copying a folder without -r: nothing was copied |
rm: cannot remove 'x': Is a directory | rm without -r on a folder (use rmdir if it’s empty) |
rmdir: failed to remove 'x': Directory not empty | The folder has something in it: look with ls first |
mkdir: cannot create directory 'a/b' | The intermediate folder is missing: use mkdir -p |
cd: too many arguments | A name with spaces and no quotes |
ls: cannot access '*.pdf' | The wildcard doesn’t match any file |
Everything vanished after an rm with a wildcard | A space inside the pattern (* .tmp) |
Before you hand in, ask yourself:
- Did I run
pwdbefore creating, moving or deleting? - Did I check with
ls(orecho) what each wildcard expands to before using it withrm? - Did I copy folders with
-rand readcp‘s warnings? - Do I have a backup (or the shared folder) before any
rm -r?
That wraps up the basic commands. The next step is Linux permissions: why /etc belongs to root, what the rw-r--r-- in ls -l means and how to change it with chmod. If you skipped anything, go back to the theory or the practice.
Did this article help you? Here is how you can support SergioLearns →




What is an operating system — theory with your laptop on a study afternoon, a write() call and a CPU that never rests (and the three programs that weren’t running at the same time)