lsof — List open files — and the processes holding them across all 5 shells
Equivalents in every shell
lsof -i :8080`-i :8080` lists processes with the given TCP/UDP port open — the canonical "what is listening on port X" question. `-i tcp:80` narrows to TCP; `-p 1234` lists files held by PID 1234; `+D /path` lists processes with files open under that directory (slow — walks the whole tree). `-nP` skips DNS + service-name resolution for faster output.
lsof -i :8080Same external. macOS ships `lsof` by default (Apple-maintained build, slightly older than the upstream Victor Hugo Sousa fork that Linux distros track). Most flags are identical. The macOS-specific `-X` flag enables additional output for kqueue file descriptors.
lsof -i :8080Same external. Pattern for "what is using my SSD-mounted volume": `lsof +D /Volumes/External` — note `+D` (with `+`) is recursive descent, while `-d` is for file-descriptor matching. Mixing them up is a classic typo.
Get-Process -Id (Get-NetTCPConnection -LocalPort 8080).OwningProcessNo single cmdlet covers `lsof`. Common splits: TCP/UDP sockets → `Get-NetTCPConnection` / `Get-NetUDPEndpoint` with `OwningProcess`. Open file handles → Sysinternals `handle.exe -p <pid>` (free download, not built in). Loaded modules per process → `(Get-Process -Id <pid>).Modules`. For "which process is holding this file": `handle.exe "C:\path\to\file"`.
netstat -ano | findstr :8080`netstat -ano` lists every TCP/UDP socket with its owning PID; `findstr :8080` filters to the port. Resolve the PID to a name: `tasklist /FI "PID eq 1234"`. For non-socket file handles (e.g. "who is locking this Excel file"), cmd has no native answer — install Sysinternals `handle.exe` or use Resource Monitor (GUI).
Worked examples
Find the process holding TCP port 8080
lsof -i :8080Get-NetTCPConnection -LocalPort 8080 | Select-Object OwningProcess, @{n="Process";e={(Get-Process -Id $_.OwningProcess).ProcessName}}netstat -ano | findstr :8080List every file held by a specific PID
lsof -p 1234(Get-Process -Id 1234).Modules; handle.exe -p 1234Find which process is keeping a directory busy (e.g. for unmount)
lsof +D /mnt/datahandle.exe "C:\Mount"Gotchas
- `lsof` without sudo only shows YOUR processes — root processes (typical daemons) are filtered out silently. Add `sudo` to see system-wide. The same applies to `lsof -i :<port>` against ports held by `root`-owned listeners.
- `lsof` is SLOW by default because it resolves IP addresses to DNS names and port numbers to service names. `-nP` (`-n` no-DNS, `-P` no-service-lookup) makes it 10–100× faster, especially on machines with slow / unreachable DNS. Aliasing `lsof` to `lsof -nP` is a common shell-config tweak.
- On Windows, `netstat -ano` shows PIDs but NOT the process command line — only the executable name once you cross-reference via `tasklist`. `Get-NetTCPConnection` is the cleaner PowerShell answer; pipe through `Get-Process -Id` to get cmdline / path: `Get-NetTCPConnection -LocalPort 8080 | ForEach-Object { Get-Process -Id $_.OwningProcess }`.
- Sysinternals `handle.exe` requires admin rights for full system visibility — without admin it shows handles only for processes the current user owns. The EULA-acceptance flag `-accepteula` is required on first run; CI scripts typically pre-create the registry key under `HKCU\Software\Sysinternals\Handle\EulaAccepted` to skip it.
- On macOS, modern apps using QUIC (HTTP/3) hold UDP sockets, not TCP — `lsof -i tcp:443` misses them. Use `lsof -i :443` (no protocol prefix) or `lsof -i udp:443`. iOS Simulator instances also show up as UDP listeners on random high ports — usually safe to ignore.
WSL & PowerShell Core notes
Common tasks using lsof
- Attach to the output of a running process
Read the stdout/stderr of an already-running process you did not launch — for debugging daemons, observing what a frozen-looking job is doing, and tailing logs that weren't redirected at start.
- 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.
- Find all listening ports across every network interface
List every TCP / UDP port currently accepting connections on the local host — for security audits ("what is exposed?"), firewall rule verification, troubleshooting "address already in use" errors, and inventory of running services.
- 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 the process listening on a port
Find and terminate whatever process is bound to a TCP/UDP port — the answer to "address already in use" when a previous run did not release the socket.