514 lines
17 KiB
Bash
514 lines
17 KiB
Bash
#!/usr/bin/env bash
|
|
# ------------------------------------------------------------------------------
|
|
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
|
|
# Protected by the BSD3 license. Please read bellow for details.
|
|
#
|
|
# * Redistribution and use in source and binary forms,
|
|
# * with or without modification, are permitted provided
|
|
# * that the following conditions are met:
|
|
# *
|
|
# * Redistributions of source code must retain the above
|
|
# * copyright notice, this list of conditions and the
|
|
# * following disclaimer.
|
|
# *
|
|
# * Redistributions in binary form must reproduce the above
|
|
# * copyright notice, this list of conditions and the following
|
|
# * disclaimer in the documentation and/or other materials
|
|
# * provided with the distribution.
|
|
# *
|
|
# * Neither the name of the copyright holder nor the names
|
|
# * of any other contributors may be used to endorse or
|
|
# * promote products derived from this software without
|
|
# * specific prior written permission.
|
|
# *
|
|
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
# * OF SUCH DAMAGE.
|
|
# ------------------------------------------------------------------------------
|
|
|
|
load_conf "processes"
|
|
|
|
# Common helpers, then the ones private to this module (helpers/processes.sh)
|
|
# shellcheck source=../helpers/common.sh
|
|
declare -F _profile_load_helpers >/dev/null || . "$(dirname -- "${BASH_SOURCE[0]}")/../helpers/common.sh"
|
|
_profile_load_helpers "${BASH_SOURCE[0]}"
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Search processes matching the given string
|
|
# Usage: ppg <string>
|
|
ppg()
|
|
{
|
|
local PARSED
|
|
|
|
PARSED=$(getopt -o h --long help -n 'ppg' -- "$@")
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
if [[ $? -ne 0 ]]; then
|
|
disp E "Invalid options, use \"ppg --help\" to display usage."
|
|
return 1
|
|
fi
|
|
|
|
local -a _pargs=()
|
|
_getopt_split _pargs "$PARSED"
|
|
set -- "${_pargs[@]}"
|
|
while true; do
|
|
case "$1" in
|
|
-h|--help)
|
|
printf "ppg: Search processes matching the given string.\n\n"
|
|
printf "Usage: ppg <string>\n\n"
|
|
printf "Options:\n"
|
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
|
return 0
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
disp E "Invalid options, use \"ppg --help\" to display usage."
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$1" ]]; then
|
|
disp E "Usage: ppg <string>"
|
|
return 1
|
|
fi
|
|
|
|
local pattern="$*"
|
|
|
|
# pgrep reads the pattern as an extended regular expression, the fallback
|
|
# below as a plain substring. Passed through ENVIRON, not awk -v: besides
|
|
# escape handling, a pattern on awk's own command line would match the awk
|
|
# process in the ps listing.
|
|
if command -v pgrep >/dev/null 2>&1; then
|
|
pgrep -af -- "$pattern"
|
|
return $?
|
|
fi
|
|
|
|
ps -ef | PPG_PATTERN="$pattern" awk '
|
|
BEGIN { pattern = ENVIRON["PPG_PATTERN"] }
|
|
NR == 1 {
|
|
print
|
|
next
|
|
}
|
|
index($0, pattern) {
|
|
print
|
|
matched = 1
|
|
}
|
|
END {
|
|
exit matched ? 0 : 1
|
|
}
|
|
'
|
|
}
|
|
export -f ppg
|
|
# ------------------------------------------------------------------------------
|
|
# List processes owned by a specific user
|
|
# Usage: ppu <username>
|
|
ppu()
|
|
{
|
|
local PARSED
|
|
|
|
PARSED=$(getopt -o h --long help -n 'ppu' -- "$@")
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
if [[ $? -ne 0 ]]; then
|
|
disp E "Invalid options, use \"ppu --help\" to display usage."
|
|
return 1
|
|
fi
|
|
|
|
local -a _pargs=()
|
|
_getopt_split _pargs "$PARSED"
|
|
set -- "${_pargs[@]}"
|
|
while true; do
|
|
case "$1" in
|
|
-h|--help)
|
|
printf "ppu: List processes owned by a specific user.\n\n"
|
|
printf "Usage: ppu <username>\n\n"
|
|
printf "Options:\n"
|
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
|
return 0
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
disp E "Invalid options, use \"ppu --help\" to display usage."
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$1" ]]; then
|
|
disp E "Usage: ppu <username>"
|
|
return 1
|
|
fi
|
|
|
|
# -u lists processes for a specific user
|
|
# -o provides a clean, standard output format
|
|
ps -u "$1" -o "${PPU_DEFAULT_FORMAT:-pid,user,%cpu,%mem,start,time,command}"
|
|
}
|
|
export -f ppu
|
|
# ------------------------------------------------------------------------------
|
|
# List processes by exact command name (no path/parameters)
|
|
# Usage: ppn <command_name>
|
|
ppn()
|
|
{
|
|
local PARSED
|
|
|
|
PARSED=$(getopt -o h --long help -n 'ppn' -- "$@")
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
if [[ $? -ne 0 ]]; then
|
|
disp E "Invalid options, use \"ppn --help\" to display usage."
|
|
return 1
|
|
fi
|
|
|
|
local -a _pargs=()
|
|
_getopt_split _pargs "$PARSED"
|
|
set -- "${_pargs[@]}"
|
|
while true; do
|
|
case "$1" in
|
|
-h|--help)
|
|
printf "ppn: List processes by exact command name (no path/parameters).\n\n"
|
|
printf "Usage: ppn <command_name>\n\n"
|
|
printf "Options:\n"
|
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
|
return 0
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
disp E "Invalid options, use \"ppn --help\" to display usage."
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$1" ]]; then
|
|
disp E "Usage: ppn <command_name>"
|
|
return 1
|
|
fi
|
|
|
|
# ps prints the PID and the command name (comm, 15 characters at most, no
|
|
# arguments); awk keeps the header and the lines whose whole name equals
|
|
# the argument. grep -w was wrong: it also matched inside the PID column,
|
|
# and in names such as "kde-bash" ("-" is not a word character).
|
|
ps -eo pid,comm | PPN_NAME="$1" awk '
|
|
BEGIN { name = ENVIRON["PPN_NAME"] }
|
|
NR == 1 { print; next }
|
|
{
|
|
line = $0
|
|
sub(/^ *[0-9]+ +/, "", line)
|
|
if (line == name) { print; found = 1 }
|
|
}
|
|
END { exit found ? 0 : 1 }
|
|
'
|
|
}
|
|
export -f ppn
|
|
# ------------------------------------------------------------------------------
|
|
# Get PID list of the given process name
|
|
# Usage: gpid <process_name [process_name2 ...]>
|
|
gpid()
|
|
{
|
|
local PARSED
|
|
|
|
PARSED=$(getopt -o h --long help -n 'gpid' -- "$@")
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
if [[ $? -ne 0 ]]; then
|
|
disp E "Invalid options, use \"gpid --help\" to display usage."
|
|
return 1
|
|
fi
|
|
|
|
local -a _pargs=()
|
|
_getopt_split _pargs "$PARSED"
|
|
set -- "${_pargs[@]}"
|
|
while true; do
|
|
case "$1" in
|
|
-h|--help)
|
|
printf "gpid: Get PID list of the given process name.\n\n"
|
|
printf "Usage: gpid <process_name [process_name2 ...]>\n\n"
|
|
printf "Options:\n"
|
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
|
return 0
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
disp E "Invalid options, use \"gpid --help\" to display usage."
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$1" ]]; then
|
|
disp E "Usage: gpid <process_name [process_name2 ...]>"
|
|
return 1
|
|
fi
|
|
local single=0
|
|
local found=0
|
|
local proc_name result
|
|
|
|
[[ $# -eq 1 ]] && single=1
|
|
|
|
for proc_name in "$@"; do
|
|
result=""
|
|
|
|
if command -v pgrep >/dev/null 2>&1; then
|
|
result=$(pgrep -d ' ' -x -- "$proc_name")
|
|
else
|
|
result=$(ps -eo pid=,comm= | GPID_NAME="$proc_name" awk '
|
|
BEGIN { proc = ENVIRON["GPID_NAME"] }
|
|
$2 == proc {
|
|
if (out != "") {
|
|
out = out " "
|
|
}
|
|
out = out $1
|
|
}
|
|
END {
|
|
print out
|
|
}
|
|
')
|
|
fi
|
|
|
|
[[ -z "$result" ]] && continue
|
|
|
|
found=1
|
|
if (( single )); then
|
|
echo "$result"
|
|
else
|
|
echo "$proc_name: $result"
|
|
fi
|
|
done
|
|
|
|
(( found )) || return 1
|
|
}
|
|
export -f gpid
|
|
# ------------------------------------------------------------------------------
|
|
# Kill all processes owned by the given users (kill user)
|
|
# Usage: ku [options] <username1 [username2 ...]>
|
|
# The users are validated first: nothing is killed if one of them is unknown.
|
|
# The processes of root and of the current user are never killed (it would take
|
|
# the session down), except in dry-run mode.
|
|
ku()
|
|
{
|
|
local dry_run=0
|
|
local -a signal_opt=() _ku_sigs=() _ku_rest=()
|
|
|
|
_proc_split_signals _ku_sigs _ku_rest "$@"
|
|
|
|
local PARSED
|
|
PARSED=$(getopt -o hns: --long help,dry-run,signal: -n 'ku' -- "${_ku_rest[@]}")
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
if [[ $? -ne 0 ]]; then
|
|
disp E "Invalid options, use \"ku --help\" to display usage."
|
|
return 1
|
|
fi
|
|
# Restore the getopt-normalised (shell-quoted) argument list
|
|
local -a _pargs=()
|
|
_getopt_split _pargs "$PARSED"
|
|
set -- "${_pargs[@]}"
|
|
while true; do
|
|
case "$1" in
|
|
-h|--help)
|
|
printf "ku: Kill all processes owned by the given users.\n\n"
|
|
printf "Usage: ku [options] <username1 [username2 ...]>\n\n"
|
|
printf "Options:\n"
|
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
|
printf "\t-n, --dry-run\t\tDisplay commands without executing them\n"
|
|
printf "\t-s, --signal SIG\tSignal to send (overrides KU_DEFAULT_SIGNAL)\n"
|
|
printf "\t --signal=SIG\tSame as above\n"
|
|
printf "\t -SIG / -NUM\t\tSignal format compatible with kill\n\n"
|
|
printf "Users are given by name or numeric ID. The processes of root and of the\n"
|
|
printf "current user are never killed (except with --dry-run).\n"
|
|
return 0
|
|
;;
|
|
-n|--dry-run)
|
|
dry_run=1
|
|
shift
|
|
;;
|
|
-s|--signal)
|
|
signal_opt=(-s "$2")
|
|
shift 2
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
disp E "Invalid options, use \"ku --help\" to display usage."
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# A kill-style signal (-9, -KILL) applies unless -s/--signal was given
|
|
if [[ ${#signal_opt[@]} -eq 0 && ${#_ku_sigs[@]} -gt 0 ]]; then
|
|
signal_opt=("${_ku_sigs[-1]}")
|
|
fi
|
|
|
|
if [[ $# -eq 0 ]]; then
|
|
disp E "Usage: ku [options] <username1 [username2 ...]>"
|
|
return 1
|
|
fi
|
|
|
|
# First pass: resolve every user to a name (killall only takes names, so
|
|
# numeric IDs are translated) and check it, before anything is killed.
|
|
local u uid uname
|
|
local -a unames=()
|
|
for u in "$@"; do
|
|
if ! uid=$(id -u -- "$u" 2>/dev/null); then
|
|
disp E "User '$u' does not exist."
|
|
return 1
|
|
fi
|
|
uname=$(id -un -- "$u")
|
|
if (( ! dry_run )) && [[ $uid -eq 0 || $uid -eq $EUID ]]; then
|
|
disp E "Safety abort: refusing to kill the processes of '$uname' (root or current user)."
|
|
return 1
|
|
fi
|
|
unames+=("$uname")
|
|
done
|
|
|
|
# Second pass: kill. The failure of one user does not skip the others.
|
|
local rc=0
|
|
local -a cmd
|
|
for uname in "${unames[@]}"; do
|
|
# killall (psmisc) preferred; fall back to pkill (procps-ng).
|
|
if command -v killall >/dev/null 2>&1; then
|
|
cmd=(killall)
|
|
if [[ ${#signal_opt[@]} -gt 0 ]]; then
|
|
cmd+=("${signal_opt[@]}")
|
|
elif [[ -n "${KU_DEFAULT_SIGNAL:-}" ]]; then
|
|
cmd+=("-${KU_DEFAULT_SIGNAL}")
|
|
fi
|
|
cmd+=(-u "$uname")
|
|
elif command -v pkill >/dev/null 2>&1; then
|
|
cmd=(pkill)
|
|
# Translate killall's -s SIGNAME form to pkill's -SIGNAME form.
|
|
if [[ ${#signal_opt[@]} -eq 2 && "${signal_opt[0]}" == "-s" ]]; then
|
|
cmd+=("-${signal_opt[1]}")
|
|
elif [[ ${#signal_opt[@]} -gt 0 ]]; then
|
|
cmd+=("${signal_opt[@]}")
|
|
elif [[ -n "${KU_DEFAULT_SIGNAL:-}" ]]; then
|
|
cmd+=("-${KU_DEFAULT_SIGNAL}")
|
|
fi
|
|
cmd+=(-u "$uname")
|
|
else
|
|
disp E "ku: neither 'killall' (psmisc) nor 'pkill' (procps) is available."
|
|
return 1
|
|
fi
|
|
|
|
if (( dry_run )); then
|
|
printf "DRY-RUN: "
|
|
printf "%q " "${cmd[@]}"
|
|
printf "\n"
|
|
else
|
|
"${cmd[@]}" || rc=1
|
|
fi
|
|
done
|
|
return "$rc"
|
|
}
|
|
export -f ku
|
|
# ------------------------------------------------------------------------------
|
|
# Kill all children of a process then the process (kill tree)
|
|
# Usage: kt [options] <pid>
|
|
kt()
|
|
{
|
|
local dry_run=0
|
|
local -a kill_args=() _kt_sigs=() _kt_rest=()
|
|
|
|
_proc_split_signals _kt_sigs _kt_rest "$@"
|
|
|
|
local PARSED
|
|
PARSED=$(getopt -o hns: --long help,dry-run,signal: -n 'kt' -- "${_kt_rest[@]}")
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
if [[ $? -ne 0 ]]; then
|
|
disp E "Invalid options, use \"kt --help\" to display usage."
|
|
return 1
|
|
fi
|
|
# Restore the getopt-normalised (shell-quoted) argument list
|
|
local -a _pargs=()
|
|
_getopt_split _pargs "$PARSED"
|
|
set -- "${_pargs[@]}"
|
|
while true; do
|
|
case "$1" in
|
|
-h|--help)
|
|
printf "kt: Kill all children of a process then the process (kill tree).\n\n"
|
|
printf "Usage: kt [options] <pid>\n\n"
|
|
printf "Options:\n"
|
|
printf "\t-h, --help\t\tDisplay this help screen\n"
|
|
printf "\t-n, --dry-run\t\tDisplay kill commands without executing them\n"
|
|
printf "\t-s, --signal SIG\tSignal to send to process tree\n"
|
|
printf "\t --signal=SIG\tSame as above\n"
|
|
printf "\t -SIG / -NUM\t\tSignal format compatible with kill\n"
|
|
return 0
|
|
;;
|
|
-n|--dry-run)
|
|
dry_run=1
|
|
shift
|
|
;;
|
|
-s|--signal)
|
|
kill_args=(-s "$2")
|
|
shift 2
|
|
;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
*)
|
|
disp E "Invalid options, use \"kt --help\" to display usage."
|
|
return 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# A kill-style signal (-9, -KILL) applies unless -s/--signal was given
|
|
if [[ ${#kill_args[@]} -eq 0 && ${#_kt_sigs[@]} -gt 0 ]]; then
|
|
kill_args=("${_kt_sigs[-1]}")
|
|
fi
|
|
|
|
if [[ $# -eq 0 ]]; then
|
|
disp E "Usage: kt [options] <pid>"
|
|
return 1
|
|
fi
|
|
if [[ $# -gt 1 ]]; then
|
|
disp E "Unexpected argument: $2. Usage: kt [options] <pid>"
|
|
return 1
|
|
fi
|
|
|
|
local parent_pid="$1"
|
|
if [[ ! $parent_pid =~ ^[0-9]+$ ]]; then
|
|
disp E "Invalid PID: '$parent_pid'."
|
|
return 1
|
|
fi
|
|
if [[ "$parent_pid" == "0" || "$parent_pid" == "1" ]]; then
|
|
disp E "Safety abort: Refusing to kill PID $parent_pid (system critical)."
|
|
return 1
|
|
fi
|
|
if [[ "$parent_pid" == "$$" || "$parent_pid" == "$BASHPID" ]]; then
|
|
disp E "Safety abort: Refusing to kill the current shell (PID $parent_pid)."
|
|
return 1
|
|
fi
|
|
if ! command -v pgrep >/dev/null 2>&1; then
|
|
disp E "kt: 'pgrep' (procps) is required to find the children."
|
|
return 1
|
|
fi
|
|
|
|
_kt_tree "$dry_run" "$parent_pid" "${kill_args[@]}"
|
|
}
|
|
export -f kt
|
|
# ------------------------------------------------------------------------------
|
|
|
|
# EOF
|