Repository navigation
Expand file tree
/
Copy pathport-kill.sh
More file actions
executable file
·132 lines (108 loc) · 3.7 KB
/
Copy pathport-kill.sh
File metadata and controls
executable file
·132 lines (108 loc) · 3.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#!/usr/bin/env bash
#
# port-kill.sh - kill whatever is listening on a TCP port.
set -uo pipefail
usage() {
cat <<'HELP'
----------------------------------------
Dimitry4Now :: port-kill.sh
----------------------------------------
USAGE
port-kill.sh [OPTIONS] PORT [PORT...]
OPTIONS
-n, --dry-run list the listeners, kill nothing
-9, --force send SIGKILL instead of SIGTERM
-h, --help show this help
Only TCP sockets in LISTEN state are matched. A browser tab open on
localhost:3000 holds a socket whose *remote* port is 3000, so a plain
`lsof -i :3000` hits the browser too - this does not.
Processes belonging to another user are listed and skipped, never killed.
HELP
}
die() {
printf '%s: %s\n' "${0##*/}" "$1" >&2
exit "${2:-2}"
}
# ---------------------------------------------------------------------------
# OPTIONS
# ---------------------------------------------------------------------------
dry_run=0
signal=TERM
while [ $# -gt 0 ]; do
case "$1" in
-n|--dry-run) dry_run=1 ;;
-9|--force) signal=KILL ;;
-h|--help) usage; exit 0 ;;
--) shift; break ;;
-*) die "unknown option: $1 (try --help)" ;;
*) break ;;
esac
shift
done
[ $# -gt 0 ] || die "missing PORT (try --help)"
for port in "$@"; do
case "$port" in
''|*[!0-9]*) die "not a port number: $port" ;;
esac
[ "$port" -ge 1 ] && [ "$port" -le 65535 ] || die "port out of range: $port"
done
command -v lsof >/dev/null 2>&1 || die "lsof not found" 1
me=$(id -un)
found=0
killed=0
# ---------------------------------------------------------------------------
# MAIN
# ---------------------------------------------------------------------------
for port in "$@"; do
# -sTCP:LISTEN is the whole point; -nP keeps lsof from resolving names and
# turning a local lookup into a DNS wait.
rows=$(lsof -nP -iTCP:"$port" -sTCP:LISTEN 2>/dev/null | tail -n +2)
if [ -z "$rows" ]; then
printf 'port %s: nothing listening\n' "$port"
continue
fi
# A server bound to both IPv4 and IPv6 shows up as two rows, same pid.
seen=" "
while read -r comm pid user _rest; do
[ -n "${pid:-}" ] || continue
case "$seen" in *" $pid "*) continue ;; esac
seen="$seen$pid "
found=$((found + 1))
if [ "$pid" -eq $$ ] || [ "$pid" -eq 1 ]; then
printf 'port %s: skipping pid %s (%s)\n' "$port" "$pid" "$comm"
continue
fi
if [ "$user" != "$me" ]; then
printf 'port %s: %s (pid %s) belongs to %s - skipped\n' \
"$port" "$comm" "$pid" "$user"
continue
fi
if [ "$dry_run" -eq 1 ]; then
printf 'port %s: would SIG%s %s (pid %s)\n' "$port" "$signal" "$comm" "$pid"
continue
fi
if ! kill -"$signal" "$pid" 2>/dev/null; then
printf 'port %s: could not signal %s (pid %s)\n' "$port" "$comm" "$pid"
continue
fi
printf 'port %s: SIG%s -> %s (pid %s)\n' "$port" "$signal" "$comm" "$pid"
killed=$((killed + 1))
# SIGTERM is a request. Give it a moment, then say so if it was ignored.
if [ "$signal" = "TERM" ]; then
waited=0
while [ "$waited" -lt 6 ] && kill -0 "$pid" 2>/dev/null; do
sleep 0.5
waited=$((waited + 1))
done
if kill -0 "$pid" 2>/dev/null; then
printf 'port %s: pid %s still alive after SIGTERM (retry with -9)\n' \
"$port" "$pid"
fi
fi
done <<EOF
$rows
EOF
done
[ "$found" -gt 0 ] || exit 1
[ "$dry_run" -eq 0 ] || printf '\nNothing was signalled.\n'
exit 0