mkdir

Create a directory, and the parents it needs

Updated 2026-09-04

mkdir creates a directory. Left to itself it does that and nothing more: the parent has to exist already, and a name that is taken is an error rather than a no-op.

-p changes both of those at once. It builds every missing directory in the path, and it stays quiet when the target is already there. Staying quiet is why setup scripts use it, and the case it does not cover is a name held by a file, which still fails.

The mode of a new directory comes from your umask, so 0022 gives 755. -m sets it explicitly and the umask does not touch it, but only for the directory you named: any parents -p has to invent along the way still take the default.

Sample files used on this page

Every example below was run against these files. Recreate them to follow along.

site the tree the cp, mv and rm pages use as well; backups is empty, so rmdir has something it will agree to remove

site:
total 16
drwxr-xr-x 2 user user 4096 Jun 15 10:00 assets
drwxr-xr-x 2 user user 4096 Jun 15 10:00 backups
-rw-r--r-- 1 user user   36 Jun  1 09:00 index.html
lrwxrwxrwx 1 user user    9 Jun 18 09:00 latest.css -> style.css
-rw-r--r-- 1 user user   33 Jun 20 09:00 style.css

site/assets:
total 4
-rw-r--r-- 1 user user 35 Jun 10 09:00 logo.svg

site/backups:
total 0

notes.txt one line, beside site/ rather than inside it, and the file that stands in for a name already taken

Rebuild before deploying.
26 outputs, collapsed by default

Making a directory

mkdir takes one name or several and creates a directory for each. The parent has to exist, and the name must not.

Create a directory

mkdir reports && ls -d reports

The directory appears in the working directory. -d asks ls for the directory itself instead of a listing of what is inside it, which for a new one would be nothing.

Show output
reports

Create several at once

mkdir css js img && ls -d css js img

Every argument is a name to create. They come back alphabetically because ls sorts what it prints, not because mkdir did anything with the order.

Show output
css
img
js

Say what was created

mkdir -v reports

-v (--verbose) names each directory as it appears. Worth adding to a -p run, where how many directories get created is the part you cannot predict.

Show output
mkdir: created directory 'reports'

Use a name that is already taken

mkdir site; echo "exit $?"

An existing name is an error, and the exit status says so. A script that would rather not care about this wants -p, below.

Show output
mkdir: cannot create directory 'site': File exists
exit 1

Watch one failure not stop the rest

mkdir site reports; echo "exit $?"; ls -d reports

mkdir works through its arguments and reports the failure afterwards. reports was created regardless, so an exit status of 1 does not mean nothing happened.

Show output
mkdir: cannot create directory 'site': File exists
exit 1
reports

Create a directory you have no permission for

mkdir /etc/tips; echo "exit $?"

The permission being checked is write on the parent, /etc here. The directory being created does not exist yet, so it has no permissions to consult. sudo is the usual answer.

Show output
mkdir: cannot create directory '/etc/tips': Permission denied
exit 1

The two things -p does

-p (--parents) creates every missing directory in the path, and separately it stops an existing target being an error. A script usually wants both, and they fail in different ways when only one of them was the point.

Create a path whose parent is missing

mkdir site/2026/07; echo "exit $?"

Plain mkdir creates the last component and no more. site exists, site/2026 does not, so there is nowhere to put 07.

Show output
mkdir: cannot create directory 'site/2026/07': No such file or directory
exit 1

Create the whole path at once

mkdir -p site/2026/07 && find site/2026

-p invents every level it needs on the way down. find shows both of them, where ls site/2026 would show only the last.

Show output
site/2026
site/2026/07

Ask for a directory that is already there

mkdir -p site; echo "exit $?"

Nothing is printed and nothing changes. This has nothing to do with parents, and it is why the flag turns up in scripts that create a directory they may already have.

Show output
exit 0

See only what -p actually created

mkdir -pv site/2026/07

site was already there and goes unmentioned. A second run of the same line prints nothing at all.

Show output
mkdir: created directory 'site/2026'
mkdir: created directory 'site/2026/07'

Point -p at a name a file is holding

mkdir -p notes.txt; echo "exit $?"

The quiet success of -p covers an existing directory. A file of the same name is still an error, and this is the case that catches a script written as though -p cannot fail.

Show output
mkdir: cannot create directory 'notes.txt': File exists
exit 1

Create several paths from one expansion

mkdir -p logs/{2025,2026}/{q1,q2} && find logs -type d | sort

Brace expansion is the shell's, so mkdir is handed four finished paths and never sees a brace. sort is here because find walks directory order.

Show output
logs
logs/2025
logs/2025/q1
logs/2025/q2
logs/2026
logs/2026/q1
logs/2026/q2

Make sure a file's directory exists before writing to it

f=site/2026/07/report.csv; mkdir -p "$(dirname "$f")" && touch "$f" && find site/2026

The standard idiom for a script handed a path it did not choose. dirname drops the last component, -p builds whatever of the rest is missing, and touch creates the file itself. A script that then wants to work inside it needs cd in a subshell.

Show output
site/2026
site/2026/07
site/2026/07/report.csv

Create through a symlinked parent

ln -s site linked && mkdir -p linked/2026 && ls site

A symlink already pointing at a directory is followed, so the new directory lands in site itself. The target gets it, not the link.

Show output
2026
assets
backups
index.html
latest.css
style.css

The mode of a new directory

A new directory takes its mode from your umask. -m overrides that, and it reaches only the directory you named.

See the mode a new directory gets

mkdir reports && stat -c "%a %A %n" reports

%a is the mode in octal and %A is the same thing drawn the way ls draws it. The umask here is 0022; it takes the write bit away from group and other.

Show output
755 drwxr-xr-x reports

Watch the umask decide it

umask 077; mkdir tight; umask 022; mkdir loose; stat -c "%a %n" tight loose

mkdir asks for 777 every time and the umask takes bits away. Nothing here is specific to directories: the same subtraction sets the mode of every file the shell creates.

Show output
700 tight
755 loose

Set the mode explicitly

mkdir -m 700 private && stat -c "%a %A %n" private

-m (--mode) accepts what chmod accepts. Better than a chmod on the next line, because the directory never exists with the looser mode at all.

Show output
700 drwx------ private

Confirm -m ignores the umask

mkdir -m 777 open && stat -c "%a %n" open

The umask is still 0022 and would have taken the group and other write bits. -m is applied instead of it, so you get exactly what you asked for.

Show output
777 open

Use a symbolic mode

mkdir -m u=rwx,g=rx,o= team && stat -c "%a %A %n" team

The symbolic form reads better in a script somebody else has to change later. o= with nothing after it clears every bit for other.

Show output
750 drwxr-x--- team

Create a shared directory that holds its group

mkdir -m 2775 shared && stat -c "%a %A %n" shared

The leading 2 is setgid, drawn as an s in the group execute slot. Files created inside take the directory's group instead of the creating user's, so a shared directory stays usable by everybody in that group.

Show output
2775 drwxrwsr-x shared

See what -m leaves at the default

mkdir -p -m 700 outer/inner && stat -c "%a %n" outer outer/inner

outer was invented by -p and took the umask default, so a directory meant to be private is sitting inside one anybody can read. Create the parent on its own line when its mode matters.

Show output
755 outer
700 outer/inner

In a script

-p appears in almost every setup script for its silence about a directory that already exists, and there are places where leaving it off is the better choice.

Run the same line twice

mkdir -p reports/drafts; mkdir -p reports/drafts; echo "exit $?"

Idempotent, so a setup script can be re-run without a guard in front of it. Plain mkdir would need [ -d reports/drafts ] || to survive the second run.

Show output
exit 0

Stop a script on a directory that is already there

set -e; mkdir site; echo "never reached"

Under set -e the failure ends the script and the echo does not run. Where an existing directory means something has gone wrong, that is the behaviour you want, and it is the argument for leaving -p off.

Show output
mkdir: cannot create directory 'site': File exists

Use a directory as a lock

mkdir lock && echo "got the lock"; mkdir lock || echo "someone else holds it"

Creating a directory is atomic: the kernel either makes it or reports that it exists, with no gap in between for a second process to slip into. A [ -d lock ] test followed by a mkdir has exactly that gap. flock, which the crontab page uses, is tidier where it is available.

Show output
got the lock
mkdir: cannot create directory 'lock': File exists
someone else holds it

Name a directory after the current month

mkdir -p "backups/$(date +%Y-%m)" && ls backups

-p is covering two things here: backups does not exist on the first run, and the month directory does exist on every run after the first.

Show output

Your output will differ: the month is whichever one you run it in

2026-09

Removing one again

rmdir is the counterpart, and it removes only an empty directory. rm covers it against the rest of the ways to delete something; the form that belongs here is the one mirroring -p.

Remove a whole empty chain

mkdir -p tree/a/b/c && rmdir -p tree/a/b/c && ls

rmdir -p walks back up the path, removing each directory once its child has gone. It stops at the first one that still has something else in it.

Show output
notes.txt
site