Creating Files and Directories (touch, mkdir)
Before you can edit, organize, or run anything on a Linux system, you first have to create it. touch and mkdir are the two commands that do exactly that at the most basic level: touch creates empty files (and updates timestamps on existing ones), and mkdir creates directories. They look almost too simple to deserve a full lesson, but their precise behavior — what actually happens on disk, how default permissions get chosen, and what their less obvious flags do — is something every Linux user eventually needs to understand exactly, especially once you start writing scripts that build directory structures automatically.
Overview / How it works
Every file and directory on a Linux filesystem is represented by an inode, a data structure that stores metadata — owner, permissions, size, timestamps, and pointers to the actual data blocks on disk — but not the file’s name. Names live in a separate structure: a directory. A directory is itself just a special kind of file whose contents are a table mapping names to inode numbers. When you list a directory with ls, you are reading that table; when you open a file by path, the kernel walks the path component by component, looking up each name in its parent directory’s table until it reaches the target inode.
When you run touch somefile on a name that does not yet exist, the shell asks the kernel to open that path with the O_CREAT flag. The kernel allocates a fresh inode, marks it as a regular file with zero bytes of data, adds an entry for the name in the current directory’s table, and sets the initial permission bits based on your umask (typically resulting in 644, i.e. rw-r--r--). If the file already exists, touch does not touch its contents at all — it simply calls a system call (utimensat) that updates two timestamps: the access time (atime) and modification time (mtime), setting both to the current moment by default. This is why touch is the standard tool for “bump this file’s timestamp without changing anything in it,” a trick used constantly in Makefiles and build scripts to force or skip rebuilds.
mkdir works similarly but creates a directory-type inode instead of a regular file: the new directory’s own table is initialized with two built-in entries, . (a self-reference) and .. (a reference to the parent directory), and an entry for the new directory’s name is added to the parent. Like touch, the default permissions come from the umask, but because directories need to be enterable, the default typically ends up as 755 (rwxr-xr-x) rather than 644 — execute permission on a directory means “allowed to access things inside it,” not “runnable.”
Syntax
touch [OPTION]... FILE...
mkdir [OPTION]... DIRECTORY...
Both commands accept one or more targets, so a single invocation can create many files or directories at once. The most useful options:
| Command | Option | Meaning |
|---|---|---|
touch |
-a |
Update only the access time, not the modification time. |
touch |
-m |
Update only the modification time, not the access time. |
touch |
-c, --no-create |
Do not create the file if it does not already exist (only update timestamps if it does). |
touch |
-d STRING |
Use a specific date/time (e.g. "2026-01-01 09:00") instead of the current time. |
touch |
-t STAMP |
Use a timestamp in [[CC]YY]MMDDhhmm[.ss] format instead of the current time. |
touch |
-r FILE |
Copy the timestamp of a reference FILE instead of using the current time. |
mkdir |
-p, --parents |
Create any missing parent directories along the path, and do not error if the target already exists. |
mkdir |
-m MODE |
Set the permission bits of the new directory explicitly (e.g. -m 700), instead of relying on the umask. |
mkdir |
-v, --verbose |
Print a line for every directory created. |
Examples
1. Create a single empty file
touch notes.txt
ls -l notes.txt
Output:
-rw-r--r-- 1 alice alice 0 Aug 4 09:41 notes.txt
notes.txt did not exist, so touch created a brand-new zero-byte regular file with default permissions 644, owned by the current user, timestamped to right now.
2. Inspect the timestamps touch actually updates
touch app.log
stat app.log
Output:
File: app.log
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: 259,2 Inode: 1181779 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 1000/alice) Gid: ( 1000/alice)
Access: 2026-08-04 09:52:11.000000000 -0400
Modify: 2026-08-04 09:52:11.000000000 -0400
Change: 2026-08-04 09:52:11.000000000 -0400
Birth: 2026-08-04 09:52:11.000000000 -0400
stat shows four separate timestamps. Access (atime) and Modify (mtime) are the two touch updates directly. Change (ctime) tracks when the inode’s metadata last changed (permissions, ownership, or content) and is updated automatically as a side effect — you cannot set it directly. Birth is when the inode was first created and never changes. If you ran touch app.log again later on this same file, only Access and Modify would move forward; Birth would stay fixed.
3. Create several files at once
touch report.md summary.md draft.md
ls -l
Output:
-rw-r--r-- 1 alice alice 0 Aug 4 09:55 draft.md
-rw-r--r-- 1 alice alice 0 Aug 4 09:55 report.md
-rw-r--r-- 1 alice alice 0 Aug 4 09:55 summary.md
All three arguments are treated as separate targets, each created independently, all with essentially the same timestamp since the calls happen milliseconds apart.
4. Build a nested project directory tree in one command
mkdir -p ~/projects/website/{css,js,images}
ls -R ~/projects/website
Output:
/home/alice/projects/website:
css images js
/home/alice/projects/website/css:
/home/alice/projects/website/images:
/home/alice/projects/website/js:
Bash expands {css,js,images} into three separate arguments before mkdir ever runs, so this is really mkdir -p ~/projects/website/css ~/projects/website/js ~/projects/website/images. Because website did not exist yet either, -p created it along the way as a parent, with no error and no need for a separate command.
5. Set directory permissions at creation time
mkdir -m 700 ~/.ssh/backup
ls -ld ~/.ssh/backup
Output:
drwx------ 2 alice alice 4096 Aug 4 10:02 /home/alice/.ssh/backup
-m 700 means rwx for the owner and nothing for group or other — useful for sensitive directories like SSH key backups, because it sets the final permissions atomically at creation instead of leaving a brief window where the directory exists with looser, umask-derived permissions before a follow-up chmod tightens them.
How it works step by step
Take mkdir -p ~/projects/website/{css,js,images} apart:
- Bash performs brace expansion first, turning one word into three:
~/projects/website/css,~/projects/website/js,~/projects/website/images. - Bash then performs tilde expansion, replacing
~with your home directory (e.g./home/alice). mkdirreceives three fully-expanded path arguments plus the-pflag.- For each path,
mkdirwalks it component by component. Ifprojectsdoesn’t exist, it’s created; ifwebsitedoesn’t exist, it’s created next; finally the leaf directory (css,js, orimages) is created. Without-p, any missing intermediate component would cause the whole call to fail with an error instead of being created automatically. - Each new directory inode gets permissions from the umask (or from
-mif given), and a.and..entry pointing to itself and its parent.
For touch app.log on an existing file, the sequence is simpler: the kernel resolves the path to an existing inode, sees the file is already there, and calls utimensat() to set atime and mtime to the current time — no data blocks, size, or content are touched at all.
Common Mistakes
Mistake 1: Using mkdir without -p on a path with missing parents
mkdir ~/projects/newapp/src
Output:
mkdir: cannot create directory '/home/alice/projects/newapp/src': No such file or directory
If ~/projects/newapp doesn’t exist yet, plain mkdir refuses to create more than the final component. Fix it by adding -p, which creates every missing parent along the way and does not error even if the target directory already exists — making it the safer default for scripts:
mkdir -p ~/projects/newapp/src
Mistake 2: Assuming touch will create missing parent directories
touch ~/projects/newapp/config/settings.conf
Output:
touch: cannot touch '/home/alice/projects/newapp/config/settings.conf': No such file or directory
touch only ever creates the final file — it never creates directories for you, even with no equivalent of -p. If the parent directory doesn’t exist, create it first with mkdir -p, then touch the file:
mkdir -p ~/projects/newapp/config
touch ~/projects/newapp/config/settings.conf
Mistake 3: Leaving a variable unquoted
dirname="Project Reports"
mkdir $dirname
Output (from ls afterward):
Project Reports
The unquoted $dirname undergoes word splitting on the space, so mkdir sees two separate arguments and creates two directories — Project and Reports — instead of the single Project Reports directory you intended. Always quote variable expansions that hold paths or filenames:
dirname="Project Reports"
mkdir "$dirname"
Best Practices
- Default to
mkdir -pin scripts, even for single-level directories — it makes the command idempotent (safe to re-run) and tolerant of missing parents, with no downside for the simple case. - Always quote variable expansions and command substitutions in
touchandmkdirarguments ("$file","$dir") to avoid word splitting and glob expansion on unexpected characters like spaces. - Use
touchto create placeholder files such as.gitkeepinside otherwise-empty directories, since Git does not track empty directories at all. - Set sensitive permissions with
mkdir -mat creation time rather than creating the directory first and runningchmodafterward — this avoids a brief window where the directory exists with looser permissions. - Use
ls -lorstatafter a scripted creation step to verify what was actually produced, especially in automated deployment or backup scripts. - Remember
touch -cif you specifically want to update timestamps on files that might not exist, without accidentally creating empty new ones.
Practice Exercises
- Using a single
mkdircommand with brace expansion, create a project skeleton at~/projects/blogcontaining three subdirectories:posts,drafts, andassets/images. - Using a single
touchcommand, create three empty files inside~/projects/blog:index.html,style.css, andscript.js. Then verify withls -lthat all three exist and are dated today. - Write a short script that creates a directory named
backup-followed by today’s date (hint:$(date +%F)) only if it doesn’t already exist, then creates an emptybackup.logfile inside it. Think about whichmkdirflag makes the “only if it doesn’t already exist” behavior automatic without anifcheck.
Summary
touchcreates a new empty file if the name doesn’t exist, or updates its access and modification timestamps if it does — it never touches file content on an existing file.mkdircreates a new directory; use-pto create nested paths in one call and to avoid errors when parent directories are missing or the target already exists.- Both commands accept multiple targets, so one call can create many files or directories at once.
- Default permissions come from the umask (commonly
644for files,755for directories); use-mwithmkdirto set exact permissions at creation time. statreveals four distinct timestamps — access, modify, change, and birth — only the first two of whichtouchupdates directly.- Always quote variable expansions holding paths to avoid word splitting turning one intended directory or file into several.
