Find a process by its current working directory
Locate which PIDs have a specific directory as their CWD — for unmounting a busy filesystem, debugging "device busy" errors, or auditing what is running out of a particular path.
How to find a process by its current working directory in each shell
lsof +D /path/to/dir 2>/dev/null`+D PATH` recursively lists every process with ANY file open under PATH (cwd, open file, mmap). For STRICTLY cwd: `lsof -d cwd | grep /path`. To list cwd of a SPECIFIC PID: `lsof -p 12345 -a -d cwd`. The `2>/dev/null` hides permission-denied warnings for files you can't inspect. SLOW on large trees — `+D` walks every file; pair with a tight path.
lsof +D /path/to/dir 2>/dev/nulllsof +D /path/to/dir 2>/dev/nullGet-CimInstance Win32_Process | Select ProcessId, Name, ExecutablePath, CommandLineWindows does NOT expose per-process CWD via standard cmdlets — `Get-Process` and `Win32_Process` don't include it. `ExecutablePath` is the binary path, NOT the working directory. The only built-in way: **SysInternals `handle.exe`** (`handle.exe -p PID` or `handle.exe \path\to\dir`) — lists all file handles + cwd. For pwsh-native: `Get-CimInstance Win32_Process -Filter "ProcessId = 12345"` shows command line; the actual CWD is reachable only via NtQueryInformationProcess (.NET P/Invoke). Practically: install handle.exe.
handle.exe -p 12345SysInternals utility — not bundled with Windows; download from `learn.microsoft.com/sysinternals/downloads/handle`. First run prompts EULA accept (`-accepteula` to bypass). `handle.exe \path\to\dir` lists every PID with handles under that directory. For the reverse "which PID is locking this file?" question, `handle.exe -u FILE` shows the user-friendly answer.
Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.
Gotchas & notes
- **Linux `/proc/PID/cwd` is a SYMLINK to the directory** — read with `ls -l /proc/PID/cwd` or `readlink /proc/PID/cwd`. Scan all PIDs in one shot: `for pid in /proc/[0-9]*; do echo "$pid: $(readlink $pid/cwd 2>/dev/null)"; done | grep /target/path`. Cleaner with awk: `find /proc -maxdepth 2 -name cwd -lname "/target/path*" -printf "%h\n" 2>/dev/null` — uses `find`'s `-lname` to match symlink TARGET directly. Note: `/proc/PID/cwd` requires read access to that proc directory; non-root sees only own processes (unless `ptrace_scope=0`).
- **`lsof +D` vs `lsof -d cwd` — different questions**: `+D /path` answers "what processes have ANY OPEN FILE under /path?" (covers cwd, open file descriptors, mmaped libs). `-d cwd` answers "what is the CWD of each process?" — for filtering to a path, `lsof -d cwd | awk '$NF ~ /\/target\//'`. For UNMOUNT scenarios you want `+D` (anything blocking the unmount); for "is anything still running OUT of this dir" you want `-d cwd`. There's also `fuser -m /mount-point` (mount-specific) — lists every PID with ANY reference into the mounted filesystem (cwd, open file, mmap, exec). `fuser -mk` adds "kill them all" — useful for forced unmount during shutdown scripts.
- **Why CWD matters for unmount**: `umount /mnt/data` returns "target is busy" if ANY process has /mnt/data (or anything below it) as cwd, as an open file, or mmaped. The kernel cannot unmount a busy filesystem. `lsof +D /mnt/data` + `fuser -m /mnt/data` together list every process to either kill or `cd` out of the way. For VAGRANT/Docker mounts that won't release: `lsof -nP +D /vagrant 2>/dev/null | grep -v COMMAND` — `n` no name resolution, `P` no port resolution (FASTER on slow DNS). Use `lsof -t +D /path` for PID-only output suitable for piping to `kill`.
- **Windows: SysInternals handle.exe is the answer** — there is no `proc` filesystem on Windows; CWD is stored in the EPROCESS struct, accessible via undocumented NtQueryInformationProcess. `handle.exe` wraps this. Alternatives: Process Explorer (`procexp.exe`) GUI — right-click process → Properties → Image tab → "Current directory". For PowerShell scripting, install `handle.exe` and call it: `handle.exe -nobanner -p $pid | Select-String "Directory"` extracts the cwd line. There is no first-party-cmdlet equivalent in 2026.
- **macOS: lsof works the same, no /proc**: `lsof +D /path` works identically. There is no `/proc/PID/cwd` (macOS has no procfs). To list every process's cwd: `lsof -d cwd -F0nt` produces NUL-delimited records (PID, NAME, then the cwd path). For "what's in /Volumes/USBDrive": `lsof +D /Volumes/USBDrive` — find the offending PID before clicking eject. macOS's eject failure dialog is famously unhelpful; `lsof` is the actual answer. SIP restrictions don't apply to lsof for unprivileged file checks; running as the file owner OR root gets you full visibility.
Related commands
Related tasks
- Find a process ID by name— Look up the PID(s) of a running process given its executable name — the lookup that precedes most kill / signal / inspect operations.
- Find which process is listening on a port— Identify the PID and process name bound to a local TCP/UDP port — for debugging "address already in use" errors, auditing what's exposed, and killing a zombie server holding port 3000.
- Kill a process by name— Terminate every running process whose executable matches a given name.
- Show the process tree— Render the parent/child relationships between running processes — useful for tracing rogue children, debugging fork bombs, and understanding shell-spawned subprocess chains.