fix(dev): 端口探测改为 lsof 优先,修复 dev.sh 误判"未运行"

macOS 上 /usr/local/bin/ss 由 iproute2mac 提供(netstat 包装),输出以 TAB
分隔且 -p 不产出 pid=,导致 listener_pids 恒为空:

- port_in_use 永远为假 → start 重复拉起 uvicorn 并报
  [Errno 48] Address already in use,最终 die "后端启动超时"
- status 误报"未运行",stop 定位不到监听进程

改为 lsof 优先、ss 兜底(保留 Linux 真 iproute2 的 pid= 提取),并用
([^0-9]|$) 界定端口边界以兼容空格/TAB 两种分隔。显式 if 替代
[[ ]] && ——bash 3.2 在 set -e 下会因中途失败的 && 列表直接退出。
This commit is contained in:
Simon
2026-09-30 19:37:32 +08:00
parent 9aaca12751
commit d1287799f7
+20 -8
View File
@@ -41,15 +41,27 @@ die() { echo "[dev.sh] 错误: $*" >&2; exit 1; }
# ---------- 进程探测 ----------
# 监听指定端口的 pid(无则空)
listener_pids() { # $1 port —— ss 优先(lsof 在部分环境对 socket 不可靠)
if command -v ss >/dev/null 2>&1; then
ss -tlnp 2>/dev/null | awk -v p=":$1 " '
$0 ~ p && /LISTEN/ {
if (match($0, /pid=[0-9]+/)) print substr($0, RSTART+4, RLENGTH-4)
}'
elif command -v lsof >/dev/null 2>&1; then
lsof -t -iTCP:"$1" -sTCP:LISTEN 2>/dev/null || true
# 取值顺序:lsof → ss。
# 不能把 ss 放在首位:macOS 上 /usr/local/bin/ss 常由 iproute2mac 提供(netstat 包装),
# 其输出以 TAB 分隔且 -p 不产出 pid=,会让匹配恒为空 → port_in_use 永远为假 →
# start 重复拉起进程并报 [Errno 48] Address already in use,status 也误报"未运行"。
# 真实 iproute2(Linux)的 ss 仍作为兜底,此时可从 users:(("x",pid=NNN)) 取到 pid。
listener_pids() { # $1 port
local pids=""
if command -v lsof >/dev/null 2>&1; then
pids="$(lsof -t -iTCP:"$1" -sTCP:LISTEN 2>/dev/null || true)"
fi
if [[ -z "$pids" ]] && command -v ss >/dev/null 2>&1; then
# ([^0-9]|$) 界定端口边界:兼容 ss 的空格与 TAB 两种分隔,且避免匹配到更长的端口号
pids="$(ss -tlnp 2>/dev/null | awk -v p=":$1" '
$0 ~ (p "([^0-9]|$)") && /LISTEN/ {
if (match($0, /pid=[0-9]+/)) print substr($0, RSTART+4, RLENGTH-4)
}' || true)"
fi
if [[ -n "$pids" ]]; then
printf '%s\n' "$pids"
fi
return 0
}
port_in_use() { [[ -n "$(listener_pids "$1")" ]]; }