-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmurder.sh
More file actions
executable file
·348 lines (291 loc) · 8.93 KB
/
murder.sh
File metadata and controls
executable file
·348 lines (291 loc) · 8.93 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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#!/usr/bin/env bash
set -euo pipefail
usage() {
local cmd
cmd=$(basename "$0")
cat << EOF
Usage: $cmd [OPTIONS] TARGET
Kill processes gracefully using escalating signals.
This script terminates processes using an escalating signal strategy:
SIGTERM (15) with 1s wait, SIGINT (2) with 3s wait, SIGHUP (1) with 4s wait,
and finally SIGKILL (9). When killing by name or port, it shows matching
processes and asks for confirmation before terminating each one (unless -f
is used). For safety, it refuses to kill root-owned processes unless the
--allow-root flag is explicitly provided.
ARGUMENTS:
TARGET Process identifier - can be:
- PID (e.g., 1234)
- Name (e.g., node)
- Port (e.g., :8080 or 8080)
OPTIONS:
-h, --help Show this help message and exit
-f, --force, -y, --yes Skip confirmation prompts
-r, --allow-root Allow killing root-owned processes
PREREQUISITES:
- Standard Unix utilities: ps, kill, lsof (for port-based killing)
EXAMPLES:
$cmd 1234 Kill process with PID 1234
$cmd node Kill all processes named 'node'
$cmd :8080 Kill process listening on port 8080
$cmd -f python Kill all python processes without confirmation
$cmd -y node Kill all node processes without confirmation
$cmd -r 1234 Kill process 1234 even if owned by root
$cmd --allow-root node Kill all node processes including root-owned
$cmd --help Show this help
EXIT CODES:
0 Successfully killed target process(es)
1 Error occurred or no processes found
EOF
}
check_dependencies() {
local required_deps=(
# keep-sorted start
"kill"
"ps"
# keep-sorted end
)
local missing_deps=()
for dep in "${required_deps[@]}"; do
if ! command -v "$dep" &> /dev/null; then
missing_deps+=("$dep")
fi
done
if [[ ${#missing_deps[@]} -gt 0 ]]; then
echo "Error: Missing required dependencies: ${missing_deps[*]}"
exit 1
fi
}
is_process_alive() {
local pid=$1
kill -0 "$pid" 2>/dev/null
}
is_root_owned() {
local pid=$1
local owner
owner=$(ps -o user= -p "$pid" 2>/dev/null | tr -d '[:space:]')
[[ "$owner" == "root" ]]
}
kill_with_escalation() {
local pid=$1
local signals=(15 2 1 9)
local waits=(1 3 4 0)
local signal_names=("TERM" "INT" "HUP" "KILL")
for i in "${!signals[@]}"; do
if ! is_process_alive "$pid"; then
return 0
fi
local sig=${signals[$i]}
local wait_time=${waits[$i]}
local sig_name=${signal_names[$i]}
echo " Sending SIG${sig_name} (${sig}) to PID ${pid}..."
kill -"$sig" "$pid" 2>/dev/null || true
if [[ $wait_time -gt 0 ]]; then
sleep "$wait_time"
fi
done
if is_process_alive "$pid"; then
echo " Failed to kill PID ${pid}"
return 1
fi
echo " Successfully killed PID ${pid}"
return 0
}
kill_by_pid() {
local pid=$1
local allow_root=${2:-false}
if [[ ! "$pid" =~ ^[0-9]+$ ]]; then
echo "Error: Invalid PID: $pid"
return 1
fi
if ! is_process_alive "$pid"; then
echo "Error: Process $pid does not exist or already terminated"
return 1
fi
# Prevent self-termination
if [[ "$pid" -eq $$ ]]; then
echo "Error: Cannot kill self (PID $$)"
return 1
fi
# Check for root ownership
if [[ "$allow_root" != "true" ]] && is_root_owned "$pid"; then
echo "Error: Process $pid is owned by root. Use --allow-root to override"
return 1
fi
echo "Killing process $pid..."
kill_with_escalation "$pid"
}
kill_by_name() {
local name=$1
local force=$2
local allow_root=$3
local pids
local killed=0
# Get PIDs matching the name, excluding this script's process
# Using ps with grep for compatibility across systems (pgrep behavior varies)
# shellcheck disable=SC2009
pids=$(ps -eo pid,comm | grep -i "$name" | grep -v "^[[:space:]]*$$[[:space:]]" | awk '{print $1}' || true)
if [[ -z "$pids" ]]; then
echo "No processes found matching: $name"
return 1
fi
echo "Found processes matching '$name':"
ps -p "${pids//$'\n'/,}" -o pid,ppid,user,comm,args 2>/dev/null || true
echo
while IFS= read -r pid; do
if [[ -z "$pid" ]]; then
continue
fi
# Skip self
if [[ "$pid" -eq $$ ]]; then
continue
fi
# Check for root ownership
if [[ "$allow_root" != "true" ]] && is_root_owned "$pid"; then
echo " Skipped PID $pid (owned by root, use --allow-root to override)"
continue
fi
local cmd
cmd=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
if [[ "$force" != "true" ]]; then
echo -n "Kill PID $pid ($cmd)? [y/N] "
read -r response </dev/tty
case "$response" in
[yY]|[yY][eE][sS]|[yY][aA][sS])
kill_with_escalation "$pid" && killed=$((killed + 1))
;;
*)
echo " Skipped PID $pid"
;;
esac
else
kill_with_escalation "$pid" && killed=$((killed + 1))
fi
done <<< "$pids"
if [[ $killed -eq 0 ]]; then
echo "No processes were killed"
return 1
fi
echo "Killed $killed process(es)"
return 0
}
kill_by_port() {
local port=$1
local force=$2
local allow_root=$3
# Remove leading colon if present
port=${port#:}
if [[ ! "$port" =~ ^[0-9]+$ ]]; then
echo "Error: Invalid port number: $port"
return 1
fi
if ! command -v lsof &> /dev/null; then
echo "Error: 'lsof' is required for port-based killing but not found"
return 1
fi
local pids
pids=$(lsof -ti ":$port" 2>/dev/null || true)
if [[ -z "$pids" ]]; then
echo "No processes found listening on port $port"
return 1
fi
echo "Found processes listening on port $port:"
ps -p "${pids//$'\n'/,}" -o pid,ppid,user,comm,args 2>/dev/null || true
echo
local killed=0
while IFS= read -r pid; do
if [[ -z "$pid" ]]; then
continue
fi
# Skip self
if [[ "$pid" -eq $$ ]]; then
continue
fi
# Check for root ownership
if [[ "$allow_root" != "true" ]] && is_root_owned "$pid"; then
echo " Skipped PID $pid (owned by root, use --allow-root to override)"
continue
fi
local cmd
cmd=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
if [[ "$force" != "true" ]]; then
echo -n "Kill PID $pid ($cmd) on port $port? [y/N] "
read -r response </dev/tty
case "$response" in
[yY]|[yY][eE][sS]|[yY][aA][sS])
kill_with_escalation "$pid" && killed=$((killed + 1))
;;
*)
echo " Skipped PID $pid"
;;
esac
else
kill_with_escalation "$pid" && killed=$((killed + 1))
fi
done <<< "$pids"
if [[ $killed -eq 0 ]]; then
echo "No processes were killed"
return 1
fi
echo "Killed $killed process(es)"
return 0
}
main() {
local force=false
local allow_root=false
local target=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
exit 0
;;
-f|-y|--force|--yes)
force=true
shift
;;
-r|--allow-root)
allow_root=true
shift
;;
-*)
echo "Error: Unknown option: $1"
usage
exit 1
;;
*)
if [[ -z "$target" ]]; then
target=$1
else
echo "Error: Multiple targets specified"
usage
exit 1
fi
shift
;;
esac
done
if [[ -z "$target" ]]; then
echo "Error: TARGET is required"
usage
exit 1
fi
check_dependencies
# Determine target type and dispatch
if [[ "$target" =~ ^:?[0-9]+$ ]] && [[ "$target" =~ : ]]; then
# Port (starts with colon)
kill_by_port "$target" "$force" "$allow_root"
elif [[ "$target" =~ ^[0-9]+$ ]]; then
# Could be PID or port - check if process exists
if is_process_alive "$target"; then
kill_by_pid "$target" "$allow_root"
else
# Try as port
kill_by_port "$target" "$force" "$allow_root"
fi
else
# Name
kill_by_name "$target" "$force" "$allow_root"
fi
}
main "$@"