OnCallReady

Lesson 1.5 · First Login · 15 min read

Making things

In plain words

Think of a stamp that makes folders. If you ask it for "Box A, inside it Box B, inside that Box C" and Box A does not exist yet, the plain stamp refuses. The -p stamp makes whatever is missing and shrugs at what is already there.

Brace expansion is like a helper who rewrites your order before the stamp sees it: "make {red,blue,green} boxes" becomes three separate orders. mkdir never sees the braces; bash has already turned ~/lab/{a,b,c} into three paths. And rm is a shredder with no bin next to it: once a file is gone, there is nothing to take back out. Relative paths mean the stamp works in whatever room you happen to be standing in.

On day one you made a lab folder, and ended up with a second copy nested inside the first - no error, no warning. And there is no trash can on a server: a wrong rm is gone for good. This lesson is creating, copying and deleting files without those surprises.

What you need to know already: 1.3 The tree (absolute vs relative paths, ~, ls).

mkdir -p and the relative-path trap

mkdir DIR (make directory) creates one directory. mkdir a/b/c fails if a does not exist yet. mkdir -p a/b/c (-p for parents) creates the whole chain, and does not complain if part of it is already there. That also makes it safe to run twice - the second run changes nothing and still succeeds.

Your day-one bug: you ran mkdir with a relative path while standing in the wrong directory, and got a second oncall-lab/labs nested inside the first. Nothing was broken and nothing warned you; the path was simply relative to where you happened to be.

Two habits that prevent it forever:

  1. pwd before any relative mkdir.
  2. Or don't be relative: start the path with ~/ or /.

Brace expansion

mkdir -p ~/lab/{systemd,processes,bash}

Before mkdir even starts, the shell rewrites {systemd,processes,bash} into three separate words: ~/lab/systemd ~/lab/processes ~/lab/bash. This is brace expansion. mkdir never sees a brace. Prove it with echo, which just prints its arguments back:

$ echo ~/lab/{a,b,c}
/home/learner/lab/a /home/learner/lab/b /home/learner/lab/c

Braces combine: {dev,prod}/{app,db} gives four paths. No spaces inside the braces - a space ends the word and the expansion does not happen.

Making files

> and >> are redirection: instead of printing to the screen, the output goes into a file. The shell does the redirecting, not the command - it opens the file as you, before the command starts. Remember that; it explains a famous trap in lesson 1.11.

Copying, moving, deleting

There is no trash. rm -rf (-f = force: never ask, ignore missing files) on the wrong path is the classic career moment. Two guards: look at what you are about to delete with ls first, and never type rm -rf $VAR/ unless you are certain the variable $VAR (a named value the shell fills in - see lesson 1.9) is set. If it is empty, that command becomes rm -rf /.

The errors you will hit, on purpose

$ mkdir a/b/c
mkdir: cannot create directory 'a/b/c': No such file or directory
$ mkdir -p a/b/c                     # fine, and fine again if re-run
$ rmdir a
rmdir: failed to remove 'a': Directory not empty
$ rm a
rm: cannot remove 'a': Is a directory
$ cp a /tmp/
cp: -r not specified; omitting directory 'a'
$ mv nothere x
mv: cannot stat 'nothere': No such file or directory

(Anything after # on a line is a comment: the shell ignores it.)

Every one of those is the tool refusing to guess. rmdir removes only empty directories, which makes it the safe choice when you expect something to be empty. "cannot stat" means "cannot look up the details of" - the file is not there.

Wildcards are expanded by the shell too

A glob (wildcard pattern) like *.log matches file names: * means "any characters", so *.log is every name ending in .log. Like braces, the shell replaces it with the matching names before the command runs.

$ ls *.log                  the shell turns *.log into the matching names
$ echo *.nothing
*.nothing                   no match: bash passes the pattern through as-is
$ rm -i *.tmp               -i (interactive) asks before each delete

Before any rm with a wildcard, run the same pattern with ls or echo and read the list. It costs one second.

Seeing what you made

$ echo hello > f.txt; echo world >> f.txt
$ cat f.txt
hello
world
$ wc -l f.txt
2 f.txt
$ head -n 1 f.txt; tail -n 1 f.txt
hello
world

What you can now do

Why it helps

Commands that are safe to run twice, like mkdir -p, are the habit behind every setup script you will write or review: rerunning it must not fail or make a mess. Brace expansion lets you create a whole layout in one command, and reading it in someone else's script stops you misjudging what a line does.

The rm guards matter most. The classic disaster stories - a script that deleted everything because a variable in its path was empty, a cleanup run from the wrong directory - are exactly the relative-path and empty-variable traps in this lesson. Checking "is every path absolute, and is every variable in it definitely set" is a real, valued review comment.

Commands in this lesson

mkdir echo rmdir rm cp mv ls cat wc head

FAQ

Is touch just "create an empty file"?

Its real job is updating a file's "last modified" time; creating the file when it does not exist is a side effect. On an existing file, touch leaves the content alone and sets the time to now. That is useful for testing rules that look at file age, and for creating placeholder files like .gitkeep. If you want to empty a file, use > file (redirect nothing into it), not touch.

Why did brace expansion not work when I added spaces?

Because a space ends a word before brace expansion happens. {a, b} is two words, {a, and b}, neither of which is a complete brace expression, so bash leaves them as they are. Write {a,b} with no spaces. Also, braces are not expanded inside quotes, and {a} with a single item stays literal: you need a comma, or a range like {1..5}.

What does echo .nothing printing .nothing mean?

When a wildcard pattern matches no files, bash passes the pattern through unchanged. So rm *.tmp in a directory without .tmp files runs rm with the literal name *.tmp and you get "cannot remove '*.tmp': No such file or directory". It is harmless there, but it is why you echo or ls a pattern first: you see exactly what the shell will hand to the command.

Is there any way to undo rm?

Not from the shell. rm removes the file's name, and the space is reused once nothing refers to the file any more. Recovery tools exist for some filesystems but are unreliable and not something to count on. The real protections are backups, version control such as git, ls or echo of the pattern before deleting, and rm -i, which asks before each file.

Why does rmdir exist if rm -r can delete directories?

rmdir only removes empty directories, and that restriction is the feature. If you believe a directory is empty, rmdir either confirms it by succeeding or refuses with "Directory not empty" and deletes nothing. rm -r will happily delete whatever turned out to be inside. When cleaning up something you expect to be empty, rmdir is a free safety check.

In an interview Junior

What does mkdir -p do, and why is it preferred in scripts?

It creates every missing parent directory, and it does not complain if the directory is already there. Plain mkdir a/b/c fails with "No such file or directory" when a is missing. With -p the command is safe to run twice: the second run changes nothing and still succeeds, which is what you want in anything that gets re-run.

Combined with brace expansion it builds a whole layout in one go: mkdir -p ~/lab/{systemd,processes,bash} - the shell expands the braces into three paths before mkdir starts (check with echo first).

The gotcha: -p will happily build the whole chain in the wrong place. A relative path is taken from wherever you are standing, which is exactly how a second labs folder ends up nested inside the first. So check pwd, or anchor the path with ~/ or /.

Also asked: What is the difference between > and >>? · How do you make an rm with a wildcard safer? · What is the difference between rm, rm -r and rmdir?

Practise this lesson in the terminal Free, in your browser - a real Ubuntu terminal to try it in, with missions that check your work.