June 10, 2026
The Ghost in the Filesystem: How to Read and Write to Deleted Files Using File Descriptors
Every file you “delete” isn’t actually gone if a process is still holding onto its secret numeric handle. Here is how Unix handles the…
Shambhuyadav_
3 min read
- 1 Every file you "delete" isn't actually gone if a process is still holding onto its secret numeric handle. Here is how Unix handles the undead!
- 2 The Separation of Name and Data: Inodes vs. Links
- 3 Seeing is Believing: A Terminal Experiment
- 4 1. Create a "Ghost File" and Hold it Open
- 5 2. Delete the File Entirely
Every file you "delete" isn't actually gone if a process is still holding onto its secret numeric handle. Here is how Unix handles the undead!
Picture this: You are running a critical, long-standing background script or database on your server. In a moment of absolute panic or accidental oversight, someone runs an rm command on the massive log file or data layout it's writing to. The file vanishes from your directory tree. You scan the folder with ls, and it's completely gone.
Is the data destroyed? Is the process going to crash instantly?
In Unix-like systems like macOS and Linux, the answer is a fascinating no. Thanks to the architecture of File Descriptors (FDs), that file is technically still alive, humming along in the background as a "ghost file." You can still read from it, write to it, and even completely rescue its contents from the void.
Here is the under-the-hood magic of how this works, and how you can manipulate deleted files right from your terminal.
The Separation of Name and Data: Inodes vs. Links
To understand why a deleted file can still be read, we have to look at how the operating system handles storage. In systems like Linux or macOS, a file is split into two distinct parts:
- The Inode: This is the actual physical backbone of the file. It lives on your disk storage, holding the raw data blocks, permissions, and file metadata.
- The Directory Entry (The Link): This is just a human-readable pointer (like
app.log) inside a folder that references that specific Inode.
When you run rm app.log, you aren't actually erasing data blocks from your drive. You are performing an unlink operation. You are simply removing the human-readable name from the directory tree, dropping the file's "link count" to zero.
However, the operating system kernel keeps a strict rulebook. It will refuse to clear the physical Inode data blocks as long as the file's process reference count is greater than zero. If a running program opened that file before it was deleted, it still holds an active File Descriptor to it — meaning the data blocks remain entirely safe and accessible.
Seeing is Believing: A Terminal Experiment
You can prove this to yourself on your laptop right now using nothing but a standard terminal.
1. Create a "Ghost File" and Hold it Open
Open a terminal window and create a quick file. Then, use an exec command to assign an active file descriptor (3) to it. This forces the shell to keep a persistent handle on the file.
echo "This data is officially unlinked, but still alive." > ghost.txt
exec 3< ghost.txtecho "This data is officially unlinked, but still alive." > ghost.txt
exec 3< ghost.txt2. Delete the File Entirely
Open a second terminal window or tab, and delete the file you just created:
rm ghost.txtrm ghost.txtIf you check Finder, file managers, or run ls, ghost.txt is nowhere to be found. To the rest of the computer, it does not exist.
3. Read the Data via the File Descriptor
Go back to your first terminal window. Even though the filename is erased from the filesystem, your shell process still holds File Descriptor 3 hooked directly into the Inode.
Run a cat command, redirecting that specific descriptor back into it:
cat <&3cat <&3The Output:
This data is officially unlinked, but still alive.This data is officially unlinked, but still alive.The kernel happily fetches the data blocks directly from the disk because your open file descriptor gave it the direct pass code, bypassing the need for a directory name entirely.
The Dev Ops Rescue Guide: How to Recover Data from a Deleted File
This isn't just a neat party trick; it's a lifesaver for system administrators and backend developers. Imagine a production application has been writing critical state data to a file that was accidentally deleted, and you desperately need that data before the application shuts down.
As long as the application is still running, you can intercept its file descriptors to clone the data.
Step 1: Find the "Undead" File Descriptor
On macOS or Linux, you can use the lsof (List Open Files) utility to hunt down processes holding onto deleted files. Run:
lsof +L1lsof +L1(The +L1 flag specifically tells the utility to look for open files that have a hard link count of less than 1 — meaning they have been deleted from the directory structure).+L1
You will get an output showing the command name, the Process ID (PID), and the specific FD number.
Step 2: Extract the Data From the Kernel
Unix systems expose active file descriptors natively. On a Mac, you can copy the data straight out of the kernel's process tracking layer back onto your desktop:
cp /dev/fd/<FD_NUMBER> ~/Desktop/recovered_data.txtcp /dev/fd/<FD_NUMBER> ~/Desktop/recovered_data.txt(If you are running on a Linux environment, you would copy from /proc//fd/ instead).
Boom — your lost production data is safely cloned back into a normal file, and you didn't have to restart the application or dig into raw disk sectors.
When Does the Ghost Finally Vanish?
The file will remain in this limbo state indefinitely as long as the process holding the file descriptor remains active.
The moment that terminal tab is closed, the script finishes executing, or the server daemon is terminated, the application automatically drops its file descriptors. At that exact millisecond, the kernel sees that the file has 0 directory links and 0 active file descriptors.
The reference count hits absolute zero, the kernel releases the file structure, and those disk blocks are finally marked as free space — permanently erasing the data into thin air.