124 lines
3.3 KiB
Bash
Executable file
124 lines
3.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Script Name: murder
|
|
# Description: Gracefully terminate processes with escalating signals
|
|
# Source: https://evanhahn.com/scripts-i-wrote-that-i-use-all-the-time/
|
|
# Credit: Evan Hahn - https://codeberg.org/EvanHahn/dotfiles
|
|
# Usage: murder 1234 # kill PID
|
|
# murder :8080 # kill process on port 8080
|
|
# murder firefox # kill process by name
|
|
# murder # interactive fzf picker (integrated with your k alias!)
|
|
|
|
# Signal escalation: [signal, wait_seconds]
|
|
# SIGTERM (15) -> SIGINT (2) -> SIGHUP (1) -> SIGKILL (9)
|
|
SIGNALS=("15 3" "2 3" "1 4" "9 0")
|
|
|
|
murder_pid() {
|
|
local pid=$1
|
|
|
|
if ! ps -p "$pid" > /dev/null 2>&1; then
|
|
echo "Process $pid not found" >&2
|
|
return 1
|
|
fi
|
|
|
|
local process_name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
|
|
echo "Terminating $process_name (PID: $pid)..."
|
|
|
|
for sig_wait in "${SIGNALS[@]}"; do
|
|
read -r sig wait <<< "$sig_wait"
|
|
|
|
if ! ps -p "$pid" > /dev/null 2>&1; then
|
|
echo "✓ Process terminated successfully"
|
|
return 0
|
|
fi
|
|
|
|
case $sig in
|
|
15) echo " → Sending SIGTERM (polite shutdown)..." ;;
|
|
2) echo " → Sending SIGINT (interrupt)..." ;;
|
|
1) echo " → Sending SIGHUP (hangup)..." ;;
|
|
9) echo " → Sending SIGKILL (force kill)..." ;;
|
|
esac
|
|
|
|
kill -"$sig" "$pid" 2>/dev/null || true
|
|
|
|
if [[ $wait -gt 0 ]]; then
|
|
sleep "$wait"
|
|
fi
|
|
done
|
|
|
|
if ps -p "$pid" > /dev/null 2>&1; then
|
|
echo "✗ Failed to terminate process" >&2
|
|
return 1
|
|
fi
|
|
|
|
echo "✓ Process terminated"
|
|
}
|
|
|
|
# If no arguments, use fzf to select process (like your k alias!)
|
|
if [[ $# -eq 0 ]]; then
|
|
if ! command -v fzf &>/dev/null; then
|
|
echo "Error: fzf not found. Install fzf or provide PID/name/port as argument." >&2
|
|
exit 1
|
|
fi
|
|
|
|
selected=$(ps aux | fzf --header="Select process to terminate (graceful escalation)" \
|
|
--header-lines=1 \
|
|
--preview='echo "Will attempt: SIGTERM → SIGINT → SIGHUP → SIGKILL"')
|
|
|
|
if [[ -n "$selected" ]]; then
|
|
pid=$(echo "$selected" | awk '{print $2}')
|
|
murder_pid "$pid"
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
# Parse argument: PID, :port, or process name
|
|
arg="$1"
|
|
|
|
if [[ "$arg" =~ ^[0-9]+$ ]]; then
|
|
# Argument is a PID
|
|
murder_pid "$arg"
|
|
elif [[ "$arg" =~ ^:[0-9]+$ ]]; then
|
|
# Argument is a port (e.g., :8080)
|
|
port="${arg:1}"
|
|
pid=$(lsof -ti ":$port" 2>/dev/null || true)
|
|
|
|
if [[ -z "$pid" ]]; then
|
|
echo "No process found listening on port $port" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Found process on port $port:"
|
|
ps -p "$pid" -o pid,comm,args | tail -1
|
|
murder_pid "$pid"
|
|
else
|
|
# Argument is a process name
|
|
pids=$(pgrep -f "$arg" || true)
|
|
|
|
if [[ -z "$pids" ]]; then
|
|
echo "No processes found matching: $arg" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# If multiple processes, show list and let user choose
|
|
count=$(echo "$pids" | wc -l)
|
|
if [[ $count -gt 1 ]]; then
|
|
echo "Multiple processes found matching '$arg':"
|
|
ps -p $pids -o pid,comm,args
|
|
|
|
if command -v fzf &>/dev/null; then
|
|
selected=$(ps -p $pids | fzf --header-lines=1)
|
|
if [[ -n "$selected" ]]; then
|
|
pid=$(echo "$selected" | awk '{print $1}')
|
|
murder_pid "$pid"
|
|
fi
|
|
else
|
|
echo -n "Enter PID to terminate: "
|
|
read pid
|
|
murder_pid "$pid"
|
|
fi
|
|
else
|
|
murder_pid "$pids"
|
|
fi
|
|
fi
|