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).
  • mkdir receives three fully-expanded path arguments plus the -p flag.
  • For each path, mkdir walks it component by component. If projects doesn’t exist, it’s created; if website doesn’t exist, it’s created next; finally the leaf directory (css, js, or images) 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 -m if 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 -p in 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 touch and mkdir arguments ("$file", "$dir") to avoid word splitting and glob expansion on unexpected characters like spaces.
  • Use touch to create placeholder files such as .gitkeep inside otherwise-empty directories, since Git does not track empty directories at all.
  • Set sensitive permissions with mkdir -m at creation time rather than creating the directory first and running chmod afterward — this avoids a brief window where the directory exists with looser permissions.
  • Use ls -l or stat after a scripted creation step to verify what was actually produced, especially in automated deployment or backup scripts.
  • Remember touch -c if you specifically want to update timestamps on files that might not exist, without accidentally creating empty new ones.

Practice Exercises

  1. Using a single mkdir command with brace expansion, create a project skeleton at ~/projects/blog containing three subdirectories: posts, drafts, and assets/images.
  2. Using a single touch command, create three empty files inside ~/projects/blog: index.html, style.css, and script.js. Then verify with ls -l that all three exist and are dated today.
  3. 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 empty backup.log file inside it. Think about which mkdir flag makes the “only if it doesn’t already exist” behavior automatic without an if check.

Summary

  • touch creates 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.
  • mkdir creates a new directory; use -p to 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 644 for files, 755 for directories); use -m with mkdir to set exact permissions at creation time.
  • stat reveals four distinct timestamps — access, modify, change, and birth — only the first two of which touch updates directly.
  • Always quote variable expansions holding paths to avoid word splitting turning one intended directory or file into several.