Paths and File Types
/var/log/nginx/access.log leads to an ordinary text file. /dev/null looks like a file too, but it is a special object backed by a device, not a text file at all. "Everything is a file" is a convenient simplification on Linux, but an administrator still needs to know the difference between a path, the type of object it leads to, and which file system holds it. Otherwise it becomes easy to edit the wrong path, delete an oddly-named file in a dangerous way, or try to read a special file as if it were ordinary text.
This article ties together the ideas from Filesystem Hierarchy, Hard and Symbolic Links, and Mount Concepts into a practical approach for analyzing a path. The examples assume Ubuntu Server LTS, Bash, and GNU/util-linux tools. You will learn when to use an absolute versus a relative path; how the kernel resolves a path component by component; how to diagnose a broken path with pwd, realpath, and namei; what ls, stat, and file each actually tell you; how to recognize the seven core file types; and how to safely handle names that contain spaces or start with a -.
How a Path Leads to an Object
A pathname is a string built from directory names and a final object name. In /etc/ssh/sshd_config, / is the starting point, etc and ssh are intermediate directories, and sshd_config is the final component.
The kernel searches for the next name inside each intermediate directory in turn. Simplified, the process looks like this:
- the search starts either at the root directory or at the process's current directory;
- the path is checked left to right, one directory component at a time;
.stays in the same directory,..moves to the parent;- if a symbolic link is encountered, resolution normally continues from its target;
- if a mount point is encountered, the search continues into whatever file system is mounted there;
- once the final component is found, the program performs the requested operation on that object.
This is why what looks like the same error can have different causes: the final file may be missing, an intermediate directory may not exist, a symbolic link along the way may be broken, or one of the directories may simply not be readable by the current user.
Absolute versus Relative Paths
An absolute path begins with / and is normally resolved from the root directory:
A relative path is resolved from the process's current working directory:
Both commands point at the same directory here, but the second one's result depends entirely on wherever cd happened to leave you. A script, a cron job, or a service's working directory can differ from what you see in an interactive terminal. Because of that, production scripts usually spell out important configuration and data paths in full.
Checking the current directory:
pwd shows the logical path according to Bash's own bookkeeping; pwd -P shows the physical path after resolving symbolic links. If /srv/current is a symbolic link, the first command may print that name, while the second could print something like /srv/releases/v2.
., .., ~, and - Are Not the Same Mechanism
.: the current directory, as a path component...: the parent directory, as a path component.~: not a name the kernel recognizes at all — Bash expands it to the user's home directory.cd -: Bash returns to the directory stored inOLDPWD, its previous working directory.
For example:
These two normally print the same thing. But "~" inside double quotes is not expanded:
Result:
This distinction matters when ~ is written into a configuration file: not every program performs Bash's own expansion. Unless a program's documentation says otherwise, use an absolute path.
Why ./program Is Necessary
When a command name contains no /, Bash searches for the executable across the directories listed in PATH. The current directory is usually not part of PATH. That is why a script in the current directory is run like this:
Here, . is the current directory, and the / tells Bash that an explicit path was given. Even if the file exists, missing execute permission produces a different error — permissions are covered in detail in the next module.
Turning a Path into Its Canonical Form
A path can contain redundant . and .. components, repeated / characters, and symbolic links along the way. A canonical path resolves all of that into one absolute form.
Expected result:
Useful variants:
realpath -e <path>: requires every component to actually exist.realpath -m <path>: computes the path even if some components have not been created yet.realpath -L <path>: resolves..logically before following symbolic links.realpath -P <path>: resolves symbolic links as soon as they are encountered; this is GNU's default behavior.
-L and -P can produce different results when a symbolic link and a .. appear together in the same path. Do not swap them in automation without knowing which semantics the situation actually calls for.
To see every component of a path individually, namei is convenient:
A representative result on Ubuntu Server:
f: /var/log/syslog
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxrwxr-x root syslog log
-rw-r----- syslog adm syslog
The first character shows the object's type, and the rest of the string shows its permissions. Each row is one path component. Even if the final file is readable by a user, the path will not open if one of the intermediate directories lacks the necessary access — the actual owners, groups, and result depend on the system.
Tip
namei -lx <path> marks a mount boundary with an uppercase D. On a server with complex containers, bind mounts, or a separate /var, this makes it easy to spot exactly where a path crosses into a different file system.
Practical Rules About Filenames
Linux filenames cannot contain / or a NUL byte. Many other characters are allowed, including spaces, newlines, and -, but the shell gives some of them special meaning.
Spaces and Special Characters
Quote a path whenever it is stored in a variable or passed to a command:
Without quotes, $file can split into two separate arguments. -- signals that whatever follows is not an option.
A Name That Starts with -
The following lab is safe:
Check the value before cleaning up:
Then remove only the temporary directory shown on screen:
rm -rI asks for one confirmation before a recursive delete. Without --, -report could be interpreted as an option instead of a filename.
Warning
If a variable is empty or points at the wrong directory, a recursive delete destroys data you did not intend to touch. On a production server, check the target first with printf '%q\n' "$lab" and find "$lab" -maxdepth 1 -ls. Anything that matters should have a backup or a VM snapshot before you run a recursive delete against it.
Case and Extensions
On most Linux server file systems, Report.txt and report.txt are different names. This is not a universal, kernel-wide rule, though — some file systems used for interoperating with other operating systems can be configured differently.
A .txt, .jpg, or .service extension is part of the name, not proof of the object's actual type. Linux programs commonly check a file's actual content, its magic bytes, or a specific configuration location instead of trusting the name.
Core File Types on Linux
The first character of ls -l output identifies the object's type:
| Character | Type | Typical Example |
|---|---|---|
- |
regular file | /etc/hosts |
d |
directory | /var/log |
l |
symbolic link | /etc/mtab |
b |
block device | /dev/sda or /dev/vda |
c |
character device | /dev/null |
p |
named pipe, FIFO | inter-process local stream |
s |
Unix domain socket | a service socket under /run |
What ls, stat, and file Each Tell You
These three commands do not replace each other:
ls -lgives a compact view of name, type, permissions, owner, size, and time.statgives the inode, link count, device identifier, and precise timestamps.fileguesses the object's practical format using file system type, magic data, and content tests.
ls -ld /etc/hosts /dev/null
stat -c 'type=%F inode=%i link=%h device=%D name=%n' /etc/hosts
file -h /etc/hosts
A representative result:
-rw-r--r-- 1 root root 220 Apr 15 09:30 /etc/hosts
crw-rw-rw- 1 root root 1, 3 Apr 15 09:20 /dev/null
type=regular file inode=658017 link=1 device=10301 name=/etc/hosts
/etc/hosts: ASCII text
The c in the /dev/null line marks it as a character device; 1, 3 there is not a file size — it is the device's major and minor number. Inode numbers and dates vary by system.
file -h inspects the symbolic link itself, while file -L follows the target:
Regular Files and Directories
A regular file stores bytes — text, executable code, an archive, an image, and so on. A directory is a special structure that maps names to inodes. Because of that, a directory's own "size" is not the sum of the files inside it; use du for disk usage and df for file system capacity, as covered in Disk and File System Basics.
Block and Character Devices
A block device represents a device that supports random access in fixed-size blocks, such as a disk. A character device works as a stream of bytes instead. Neither device node stores the actual data itself — it is a reference point to a kernel driver.
Danger
Do not write to block devices like /dev/sda, /dev/vda, or /dev/nvme0n1 with cat, redirection, dd, mkfs, or a text editor. Doing so can corrupt the file system and destroy data irrecoverably. lsblk -f, blkid, and stat are enough to study a device read-only; any write experiment belongs only on a separate, snapshotted VM disk.
FIFOs and Sockets
A FIFO (first in, first out) transfers a stream between two processes through a name in the file system. It does not store persistent content. If one side — a reader or a writer — is missing, opening it can block until the other side connects.
A Unix domain socket is also an inter-process communication endpoint, but it works bidirectionally through socket semantics. Reading it with cat as if it were a regular file is not a meaningful diagnostic technique.
A Safe Lab: Creating and Inspecting Types
This lab creates a regular file, a directory, a symbolic link, and a FIFO. Devices and any existing sockets are only inspected read-only.
Starting State
Create the Objects
These commands only affect the new temporary directory and do not need sudo.
Check the Result
A representative ls -l result:
drwxr-xr-x 2 user user 40 Jul 29 10:00 subdir
lrwxrwxrwx 1 user user 4 Jul 29 10:00 link -> text
-rw-r--r-- 1 user user 6 Jul 29 10:00 text
prw-r--r-- 1 user user 0 Jul 29 10:00 queue
d, l, -, and p mark the directory, symbolic link, regular file, and FIFO respectively. The 4 shown for the symbolic link's size is the length of the target string text — not the size of the file it points to.
Finding devices and sockets on the system without modifying anything:
The list of sockets varies by service and distribution. An empty find result here is not necessarily an error.
Cleanup
printf 'directory to remove: %q\n' "$lab"
find "$lab" -maxdepth 1 -ls
rm -rI -- "$lab"
test ! -e "$lab" && printf 'cleaned up\n'
Practical Scenario: "No Such File or Directory"
A service cannot find /srv/app/config/app.ini. Instead of immediately creating a file or changing permissions, walk the path one layer at a time.
First confirm exactly which environment and current directory are actually in play:
Then check each component:
If a symbolic link is involved:
Finally, see which mount the path actually falls under:
Interpretation:
- a
?in anameirow: that component was not found, or another lookup error occurred. - an error from
realpath -e: at least one component does not exist or cannot be resolved. - a relative target from
readlink: it is computed relative to the directory the link is in. - an unexpected file system from
findmnt: a previous directory's contents may be hidden underneath a mount.
This sequence is considerably safer than reaching for chmod -R on a guess — it finds exactly which part of the path is broken first.
Common Mistakes
Using a Relative Path from the Wrong Directory
config/app.ini is only correct from the one expected current directory. In automation, either set the working directory explicitly or use an absolute path.
Assuming Every Program Understands ~
~ is a Bash expansion. A YAML file, a systemd unit, or an application's own configuration parser may not turn it into $HOME automatically.
Passing a Path Without Quotes
Spaces and glob characters can split or expand an argument unexpectedly. Pass a variable as "$path", and mark the end of options with --.
Inferring a File's Type from Its Extension
backup.jpg could be an archive or an executable. stat reports the actual file system object type; file inspects the content format.
Opening a FIFO or Device as an Ordinary File
A FIFO can block; writing to a device can corrupt data. Use stat, file -h, and a purpose-built diagnostic tool instead.
Exercises
- Write three different relative paths to one existing file in your home directory. Confirm with
realpath -ethat all three resolve to the same object. - Create two files, one with a space in its name and one starting with
-. Inspect both safely withstat -- <path>, then remove them interactively. - In a temporary directory, create a regular file, a directory, a symbolic link, and a FIFO. Compare
stat -c '%F %n'output against the first character ofls -l. - Analyze
/etc/mtabwithnamei -lx,readlink, andreadlink -f. Note which part is a symbolic link and which part is a mount boundary. - Find only the regular files under
/var/logusingfind /var/log -xdev -type f. Do not hide permission errors — review them separately, and explain whysudowould or would not be appropriate before using it.
Verification criterion: for every conclusion you draw, you should be able to point to a specific character in a command's output, a stat field, or a path component that supports it.
References
- Linux man-pages:
path_resolution(7): https://man7.org/linux/man-pages/man7/path_resolution.7.html - GNU Bash Reference Manual: Tilde Expansion: https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html
- GNU Coreutils manual:
realpathinvocation: https://www.gnu.org/software/coreutils/manual/html_node/realpath-invocation.html - util-linux:
namei(1): https://man7.org/linux/man-pages/man1/namei.1.html file(1)manual page: https://man7.org/linux/man-pages/man1/file.1.html- Filesystem Hierarchy Standard 3.0: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html