mkdir
Create a directory, and the parents it needs
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.
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