CoursesLinux essentialsFile & directory commands

File & directory commands

ls, cd, cp, mv, rm, mkdir, touch, ln — with output.

Beginner16 min · lesson 4 of 25

Everything you do on a Linux server comes back to files. Your settings live in files. Your logs are files. Even the running programs and the hardware show up as files if you know where to look. So before you can run anything or secure anything, you need to move around the filesystem (the way Linux organizes everything into a tree of folders) the way you move around a house you grew up in: opening the right door without thinking, knowing which drawer holds what. This small set of commands is how you do that. Each one has a single job. You will run them thousands of times, and within a week they will feel like second nature.

Listing Files With ls

ls is short for 'list'. It shows you what is inside a directory, the same way sliding open a drawer shows you the folders standing in it. Type it on its own and you get a bare grid of names. The flags (short options that start with a dash) are what make it worth using. -l gives you the long format, one item per line with permissions, owner, size, and date. -a shows hidden files, the ones whose names start with a dot, which ls normally keeps out of sight. -h prints sizes the way a person reads them (K for kilobytes, M for megabytes) instead of raw bytes. -t sorts by time, newest first. You will end up typing ls -lah so often your fingers learn it on their own: long, all, human-readable.

~/secopslog — bash
$ ls
config.yml data logs server.py
$ ls -lah
total 24K drwxr-xr-x 4 deploy deploy 4.0K Jul 3 10:14 . drwxr-xr-x 6 deploy deploy 4.0K Jul 3 09:02 .. -rw-r--r-- 1 deploy deploy 512 Jul 3 10:14 config.yml drwxr-xr-x 2 deploy deploy 4.0K Jul 3 10:10 data drwxr-xr-x 2 deploy deploy 4.0K Jul 3 10:12 logs -rwxr-xr-x 1 deploy deploy 1.2K Jul 3 10:14 server.py

That first block of letters on each line is the part that matters most for security work, so read it left to right. The very first character is the type: d for a directory, - for an ordinary file, l for a link. The next nine characters are the permissions, in three groups of three: what the owner can do, then the owner's group, then everyone else on the machine. r is read, w is write, x is execute a file or enter a directory. So -rw-r--r-- means the owner can read and change the file and every other account can read it. After that come the owner, the group, the size, the date it last changed, and the name. Reading this line at a glance is most of what file security looks like day to day.

Hidden Files Are Where Secrets And Attackers Live

Here is why -a matters to a defender. Anything whose name begins with a dot is hidden from a plain ls. That is usually a convenience, since your home directory is full of dot-files holding settings you rarely touch. It is also a hiding place. Application secrets often sit in a file called .env. Your login keys sit in a folder called .ssh. And an attacker who lands on a machine will often stash tools in a dot-named file precisely because a quick ls skips over them. When you are looking at a box you do not fully trust, ls -la is the first thing you run: in the home directories, in /tmp, and in /dev/shm (a temporary in-memory folder attackers like because it leaves little behind on disk).

~/secopslog — bash
$ ls -la /home/deploy
total 28 drwxr-xr-x 4 deploy deploy 4096 Jul 3 10:22 . drwxr-xr-x 3 root root 4096 Jun 30 08:00 .. -rw------- 1 deploy deploy 220 Jun 30 08:00 .bash_history -rw-r--r-- 1 deploy deploy 3771 Jun 30 08:00 .bashrc -rw-r--r-- 1 deploy deploy 180 Jul 3 10:14 .env drwx------ 2 deploy deploy 4096 Jul 3 09:40 .ssh drwxr-xr-x 4 deploy deploy 4096 Jul 3 10:14 app

Look at the permissions on .env. The last three characters are r--, which means every account on this machine can read it. Now look at what is inside.

/home/deploy/.env
DB_HOST=10.0.2.15
DB_USER=appuser
DB_PASSWORD=S3cr3t-pw-9f3a
API_TOKEN=sk_live_7Hd2kQ9xLm

That is a database password and a live API token (a secret string that lets a program act as you) sitting in a file the whole machine can read. Fixing the permissions belongs to a later lesson, but the habit starts here: when you list a file, read its permission bits and ask who else can see it. A world-readable secret is one of the most common real findings on a running server.

Knowing Where You Are: pwd And cd

A filesystem is a tree. At the very top is one directory written as / and called the root, and everything branches down from there: /home, /var, /etc, and so on. At any moment your shell (the program that reads and runs your typed commands) is standing in one of those directories, called your working directory. pwd, short for 'print working directory', is the you-are-here dot on a map. cd, short for 'change directory', is you walking somewhere else. You can give cd two kinds of address. An absolute path starts with / and spells out the full route from the root, so it works from anywhere. A relative path is written from where you already stand: nginx means the nginx folder inside this one, and .. means one level up. Two shortcuts save you all day: cd on its own drops you home, and cd - jumps to whatever directory you were in a moment ago, like a browser's back button.

~/secopslog — bash
$ pwd /home/deploy/app cd /var/log/nginx # absolute path: starts at the root /, works from anywhere pwd /var/log/nginx cd .. # relative: go up one level pwd /var/log cd - # jump back to the previous directory (it prints where it lands) /var/log/nginx cd # no argument at all: straight to your home directory pwd /home/deploy

Making Things: mkdir, touch, rmdir

mkdir, short for 'make directory', builds an empty folder. On its own it makes one. Add -p (for 'parents') and it builds a whole chain of nested folders in a single step, staying quiet instead of complaining if part of the path already exists. touch has two jobs. If the file does not exist, it creates an empty one. If it does exist, it leaves the contents alone and sets the file's timestamp to now. rmdir removes a directory, but only when it is already empty, which makes it the cautious option (most of the time you will reach for rm -r instead, coming up next).

~/secopslog — bash
$ mkdir logs mkdir -p project/src/api # makes project, project/src, and project/src/api in one shot touch project/src/api/main.py # create an empty file ls -R project
project: src project/src: api project/src/api: main.py

That timestamp is more interesting than it looks. Every file records when it was last changed, and during an investigation those times are how you reconstruct what happened and when. Attackers know this. A common move after tampering with a file is to run touch with -d or -r to set its timestamp back to something innocent, so the changed file blends in with its neighbours instead of standing out as the one thing edited at 3 a.m. That trick is called timestomping. It is also why careful forensics does not fully trust the timestamps on screen, and cross-checks them against a separate change-time the filesystem records and touch cannot easily rewrite.

Copy, Move, Delete: cp, mv, rm

cp, short for 'copy', is a photocopier: the original stays and you get a duplicate. cp source destination copies one file. To copy a whole directory and everything inside it, add -r for 'recursive', which tells the command to walk down into every subfolder. mv, short for 'move', does two jobs with one command. Give it a file and a new name and it renames. Give it a file and a directory and it moves the file into that directory. rm, short for 'remove', deletes: rm on a file removes the file, rm -r on a directory removes the directory and everything under it. There is no recycle bin and no undo, which is why rm is the one command here to treat with real caution.

~/secopslog — bash
$ cp config.yml config.yml.bak # a quick backup before editing cp -r data data-archive # copy a whole directory tree (recursive) mv server.py app.py # rename in place mv app.py project/src/ # move into a directory rm config.yml.bak # delete one file rm -r data-archive # delete a directory and everything in it
$ ls
config.yml data logs project

mv hides a detail worth understanding, because it explains a real gotcha. When you move a file to another spot on the same disk (the same filesystem), Linux does not copy the data at all. It only rewrites the label that records where the file lives. That is why moving a 2-gigabyte file across your home directory is instant. But when the destination sits on a different filesystem (say a mounted USB drive, an external disk attached into the tree, or a separate data volume), there is nothing to relabel, so mv quietly falls back to copying every byte and then deleting the original. Same command, wildly different amount of work, and if that copy is interrupted you can be left with a partial file at the destination while the original is already gone.

Links: ln And Its Two Kinds

A link lets one file answer to more than one name, and there are two kinds. Picture a warehouse where every box sits in a numbered shelf slot, and a paper index maps names to slots. In Linux that numbered slot is called an inode (short for 'index node'), the record that holds a file's real contents and details. A name in a directory is an entry in that index pointing at an inode. A hard link is a second name pointing at the very same inode, the same box on the same shelf, so deleting one name leaves the data intact as long as another name still points at it. A symbolic link, or symlink, made with ln -s, is different. It is a small signpost that holds a path, a note reading 'the thing you want is over there'. It points at a name, not at the inode. If that name goes away, the signpost still stands but now points at nothing, which is called a dangling link.

~/secopslog — bash
$ ln -s /opt/app/config/prod.yml current.yml # current.yml is now a signpost to prod.yml ls -l current.yml
lrwxrwxrwx 1 deploy deploy 24 Jul 3 10:20 current.yml -> /opt/app/config/prod.yml

The leading l on that line is ls telling you this is a link, and the arrow shows where it points. Symlinks are all over a real system. On most machines /usr/bin/python3 is itself a symlink pointing at a specific version like python3.11, and deployment tools lean on the same trick to switch which build is live, repointing one link instead of shuffling files around. That flexibility is exactly where the security problem starts.

Two kinds of link, and why it matters
Hard link (ln)
Points at the inode
the actual data on disk
Data survives while any name remains
delete one name, the others still work
Same filesystem only
cannot span two disks
Never dangles
always backed by real data
Symbolic link (ln -s)
Points at a path (a name)
just text saying where to look
Breaks if the target is removed
becomes a dangling link
Can cross filesystems and link folders
far more flexible
Can be followed somewhere unexpected
the security risk
A hard link is another name for the same data. A symlink is a signpost holding a path.

Here is the attack a DevSecOps engineer (someone who builds, secures, and runs software) has to watch for. Suppose a program running as root writes to a predictable file such as /tmp/app.lock, and it opens that path without checking it. /tmp is world-writable, meaning any user on the box is allowed to create files there. An attacker gets in first and drops a symlink named app.lock pointing at /etc/passwd (the file that lists every user account) or some other sensitive target. When the root process writes to its lock file, it follows the signpost and clobbers a file it never meant to touch. A symlink is only a path written into a file, so whoever creates it can aim it anywhere, including at files they have no business reaching. Modern kernels ship a guard called protected_symlinks that blocks the most common version of this in sticky world-writable folders like /tmp, but it is a speed bump, not a wall. So treat any path in a shared directory as untrusted, check whether it is a symlink before you write through it, and use tooling that refuses to follow links it did not expect. This is why programs that handle files in shared folders are so careful about what they open.

rm -rf is the command that ends careers
rm -r -f deletes recursively and by force, with no prompt and no recovery. A single stray space is all it takes: type rm -rf ~/project/build with an accidental space after the tilde and you get rm -rf ~ /project/build, which erases your entire home directory before it ever reaches the folder you meant. An empty variable is every bit as dangerous. rm -rf "$DIR"/* with DIR unset becomes rm -rf /*. Modern rm refuses a bare rm -rf / thanks to a built-in guard (called --preserve-root), but the /* version walks straight past that guard, and it has wiped whole machines. Read every rm -rf out loud before you press Enter, prefer explicit paths over wildcards, never run it from a directory you did not expect to be in, and be doubly careful as root. When you are unsure, run ls on the exact path first to see what you are about to destroy.
Quick check
01You run mv bigfile.log /var/log/ and it finishes instantly, even though the file is 2 GB. The same command to /mnt/usb/ takes 40 seconds. What is going on?
Incorrect — Speed is a symptom, not the cause. The real difference is that one move stays on the same filesystem and the other crosses onto a different one.
Correct — Within one filesystem a move is a relabel and is instant; across filesystems it becomes a full copy-and-delete.
Incorrect — Within a single filesystem mv copies nothing, it only updates the directory entry, which is exactly why it is instant.
Incorrect — mv does no compression and neither does the mount. The difference is same-filesystem versus cross-filesystem.
02According to the lesson, what is the key difference between a hard link and a symbolic link (symlink)?
Incorrect — this is backwards; the symlink holds a path and the hard link points at the inode.
Incorrect — this is reversed; symlinks can cross filesystems, hard links cannot.
Incorrect — this is reversed; a hard link never dangles because it is backed by real data, but a symlink can.
Correct — the hard link shares the inode, the symlink is just a stored path to a name.
03During an investigation you find a tampered config file whose last-modified time looks perfectly normal and matches its neighbours. The lesson warns that attackers reset timestamps with touch -d or -r. What is the sound response?
Incorrect — touch can set the modification time backward, which is the whole point of timestomping.
Correct — the lesson says forensics compares against that harder-to-forge change-time.
Incorrect — a normal timestamp proves nothing, and deleting evidence destroys your investigation.
Incorrect — any user can run touch on files they own, so this offers no assurance.

When you inherit a server you did not set up, this handful of commands is your first walk-through. cd into the home directories and run ls -la to surface the dot-files. Read the permission bits and flag anything world-readable that holds a secret. Follow the symlinks in the config folders to see what current really points at. Check the timestamps in /tmp and /dev/shm for files that appeared when nobody was supposed to be working. None of it needs a special tool. It is the same ten commands you now know, used with a suspicious eye.

Try this

Work through “Links: ln And Its Two Kinds” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.

Takeaway

The trap worth remembering here: rm -rf is the command that ends careers. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.

Related