Initial commit: wer - replacement for w/who on Debian 13

Lists logged-in sessions (local and ssh), one entry per terminal,
including multiplexed ssh connections (ControlMaster). Needed because
Debian 13 no longer populates utmp, so w/who show nothing.

- Sessions and origin via systemd-logind (loginctl)
- Terminals detected via session leaders and cgroup mapping
- sudo shells (own pty) shown indented below their terminal
- BEFEHL column shows the current foreground process
- Sessions without a terminal (scp, sftp, ssh -N) are listed too
This commit is contained in:
2026-09-20 14:51:29 +02:00
parent d50149bf19
commit 9b78a12d97
Executable
+103
View File
@@ -0,0 +1,103 @@
#!/bin/bash
# wer - ein Eintrag pro Terminal (lokal, ssh, auch bei ControlMaster/Multiplexing)
# Ersatz für 'w'/'who' unter Debian 13
# - sudo-Shells (eigenes Pseudo-Terminal) erscheinen eingerückt unter ihrem Terminal
# - Spalte BEFEHL: aktuell im Vordergrund laufender Befehl (leer = Shell wartet am Prompt)
declare -A from seen elev
rows=()
get_from() { # Herkunft einer logind-Sitzung (mit Cache)
local sid=$1 k v remote=no host=
[[ -n ${from[$sid]} ]] && return
while IFS== read -r k v; do
case $k in Remote) remote=$v ;; RemoteHost) host=$v ;; esac
done < <(loginctl show-session "$sid" -p Remote -p RemoteHost 2>/dev/null </dev/null)
if [[ $remote == yes ]]; then from[$sid]=${host:-remote}; else from[$sid]=lokal; fi
}
session_of_pid() {
sed -n 's#.*/session-\([A-Za-z0-9]\+\)\.scope.*#\1#p' "/proc/$1/cgroup" 2>/dev/null | head -1
}
# alle Prozesse mit Terminal: pid ppid sid tty etimes pgid tpgid comm
procs=$(ps -eo pid=,ppid=,sid=,tty=,etimes=,pgid=,tpgid=,comm= -ww </dev/null | awk '$4!="?"')
now=$(date +%s)
mypg=$(ps -o pgid= -p $$ 2>/dev/null </dev/null | tr -d ' ') # eigene Gruppe (wer selbst) ausblenden
# Vordergrundbefehl eines Terminals: $1=tty $2=PID der Shell (wird übersprungen)
fgcmd() {
local pid ppid sess t et pg tpg comm a res=
while read -r pid ppid sess t et pg tpg comm; do
[[ $t == "$1" && $pg == "$tpg" && $pg != "$mypg" && $pid != "$2" ]] || continue
a=$(ps -o args= -p "$pid" -ww 2>/dev/null </dev/null)
[[ -n $a ]] && res+="${res:+ | }$a"
done <<<"$procs"
printf '%s' "${res:0:70}"
}
# ---- Durchgang 1: Terminals einsammeln, sudo-Terminals dem äußeren zuordnen ----
for tty in $(awk '{print $4}' <<<"$procs" | sort -uV); do
lsid= leader= lcomm= lppid= first= oldest=0
while read -r pid ppid sess t et pg tpg comm; do
[[ -z $first ]] && first=$comm
[[ -z $lsid ]] && lsid=$(session_of_pid "$pid")
(( et > oldest )) && oldest=$et
if [[ $pid == "$sess" ]]; then leader=$pid; lcomm=$comm; lppid=$ppid; fi
done < <(awk -v t="$tty" '$4==t' <<<"$procs")
[[ -n $lsid ]] || continue # kein Benutzer-Login
seit=$(date -d "@$(( now - oldest ))" '+%F %H:%M')
if [[ -n $lppid ]]; then
pcomm=$(ps -o comm= -p "$lppid" 2>/dev/null </dev/null)
if [[ $pcomm == sudo ]]; then
# Pseudo-Terminal von sudo -> unter dem äußeren Terminal anzeigen
outer=$(ps -o tty= -p "$lppid" 2>/dev/null </dev/null | tr -d ' ')
suser=$(ps -o user= -p "$leader" 2>/dev/null </dev/null | tr -d ' ')
read -r cpid child < <(ps -o pid=,comm= --ppid "$leader" 2>/dev/null </dev/null \
| grep -v ' sudo$' | head -1)
[[ -n $child ]] || { child=$lcomm; cpid=$leader; }
cmd=$(fgcmd "$tty" "$cpid")
[[ -n $outer ]] && elev[$outer]+="$suser|$tty|$seit|$child|$cmd"$'\n'
continue
fi
[[ $pcomm =~ ^(tmux|screen|SCREEN) ]] && continue
fi
user=$(stat -c %U "/dev/$tty" 2>/dev/null)
get_from "$lsid"
seen[$lsid]=1
cmd=$(fgcmd "$tty" "$leader")
rows+=("$lsid|${user:-?}|$tty|${from[$lsid]}|$seit|${lcomm:-$first}|$cmd")
done
# Spaltenbreite für VON (IPv6-Adressen sind lang)
vw=6
for r in "${rows[@]}"; do
IFS='|' read -r _ _ _ v _ <<<"$r"
(( ${#v} > vw )) && vw=${#v}
done
fmt="%-8s %-12s %-9s %-${vw}s %-16s %-9s %s\n"
# ---- Durchgang 2: Ausgabe ----
printf "$fmt" SESSION BENUTZER TTY VON SEIT PROZESS BEFEHL
for r in "${rows[@]}"; do
IFS='|' read -r lsid user tty von seit proc cmd <<<"$r"
printf "$fmt" "$lsid" "$user" "$tty" "$von" "$seit" "$proc" "$cmd"
while IFS='|' read -r su st ss sc sb; do
[[ -n $su ]] && printf "$fmt" "" '\_ '"$su" "$st" "sudo" "$ss" "$sc" "$sb"
done <<<"${elev[$tty]}"
done
# Sitzungen ohne Terminal (scp, sftp, ssh -N ...) ebenfalls anzeigen
while read -r id _ user _; do
[[ -n ${seen[$id]} ]] && continue
[[ $(loginctl show-session "$id" -p Class --value 2>/dev/null </dev/null) == user ]] || continue
get_from "$id"
since=$(loginctl show-session "$id" -p Timestamp --value </dev/null)
printf "$fmt" "$id" "$user" "-" "${from[$id]}" \
"$(date -d "$since" '+%F %H:%M' 2>/dev/null)" "-" "(kein Terminal)"
done < <(loginctl list-sessions --no-legend --no-pager </dev/null)