dotfiles/scripts/bin/murder

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