lsof 是 macOS 查看监听端口与进程 ID 最直接有效的方式,无需安装,支持 TCP/UDP 与 LISTEN/ESTABLISHED 状态区分;推荐用 lsof -i -P -n | grep LISTEN 全局扫描,或 lsof -iTCP -sTCP:LISTEN 精确筛选,lsof -ti :3000 可快速提取 PID。

lsof 是 macOS 查看监听端口与进程 ID 最直接有效的方式,不需要额外安装,结果包含完整进程上下文,且能区分 TCP/UDP、LISTEN/ESTABLISHED 等状态。其他工具如 netstat 或 ss 要么功能受限(macOS 版 netstat 不支持 -p),要么需手动装包(ss 依赖 iproute2mac),不推荐作为首选。
用 lsof -i -P -n | grep LISTEN 列出全部监听端口
这条命令是排查端口冲突或服务启动失败时最常用的全局扫描方式:
-
-i表示只显示网络连接(含端口) -
-P禁用端口号转服务名(如跳过把 8080 映射成 http-alt),避免延迟和歧义 -
-n禁用 DNS 反向解析(跳过把 127.0.0.1 转成 localhost),提升响应速度并确保地址格式统一 -
grep LISTEN过滤出真正处于监听状态的条目,排除已建立的连接(ESTABLISHED)或等待关闭的状态(TIME_WAIT)
执行后你会看到类似这样的输出:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME node 1234 user 25u IPv6 0xXXXXXXXXXXXX 0t0 TCP *:3000 (LISTEN) nginx 5678 root 30u IPv4 0xXXXXXXXXXXXX 0t0 TCP *:80 (LISTEN)
注意 NAME 列末尾的 (LISTEN) 和前面的 *:3000 ——星号表示监听所有接口,数字是端口号,PID 列就是你要找的进程 ID。
用 lsof -iTCP -sTCP:LISTEN 精确筛选 TCP 监听进程
默认 lsof -i 会混入 UDP 连接,而很多开发场景(如本地 Web 服务)只关心 TCP 监听。加协议和状态限定能避免干扰:
-
-iTCP限定只查 TCP 协议(同理可用-iUDP) -
-sTCP:LISTEN明确指定 TCP 状态为 LISTEN,比grep LISTEN更可靠——某些非标准实现可能不带括号,但状态字段一定存在 - 组合使用可避免漏掉绑定在具体 IP(如
127.0.0.1:3000)而非通配符(*:3000)上的服务
例如检查是否真有服务占了 3000 端口:
sudo lsof -iTCP -sTCP:LISTEN | grep ':3000'
输出中若出现 127.0.0.1:3000 或 *:3000,说明该端口确实被监听;没输出则空闲。
为什么不用 netstat 直接查 PID
macOS 自带的 netstat 不支持 -p 参数,所以即使你运行 sudo netstat -tuln,也看不到 PID/Program name 列:
- 它只能告诉你
*.3000在监听,但不知道是谁干的 - 想补全 PID,还得切回
lsof -i :3000,徒增步骤 -
netstat -anvp tcp | grep LISTEN输出里看似有 PID 字段,但那是 macOS 的“伪实现”——实际是空列或填充占位符,不可信
换句话说:netstat 只适合验证“有没有监听”,不能替代 lsof 回答“谁在监听”。把它当辅助交叉验证工具还行,别指望靠它定位进程。
用 lsof -ti :端口号 快速提取 PID 值
当你已经知道端口号,只想拿到 PID 数字用于后续 kill,用 -t(terse 模式)和 -i(网络)组合最干净:
-
lsof -ti :3000会直接输出一行纯数字,比如1234 - 如果端口未被占用,命令无任何输出(不是报错,是静默)
- 搭配
kill -9 $(lsof -ti :3000)可一键终止,但注意:必须加sudo才能杀其他用户进程,即sudo kill -9 $(lsof -ti :3000)
这个写法常被写进脚本,但手动执行时建议先跑一遍 lsof -i :3000 确认进程名,避免误杀系统服务(比如不小心干掉 launchd 或 mDNSResponder)。
真正容易被忽略的是:监听地址的细节。比如 127.0.0.1:3000 和 *:3000 都算监听,但前者只接受本机请求,后者接受任意来源。有些服务(如 React 开发服务器)默认只绑 localhost,这时用 curl http://localhost:3000 能通,但用本机 IP 访问就失败——这不是端口没被占,而是监听范围限制。查端口时务必看清 NAME 列的具体地址。

















