nuss-sysinfo
Version 2.2.0 all
Kompakte Systemübersicht für Linux-Systeme
Skript importieren, reproduzierbar bauen, prüfen und nach ausdrücklicher Freigabe in apt.nuss.name veröffentlichen.
/home/nussname/web/repo-admin.nuss.name/private/builder/incoming
Version 2.2.0 all
Kompakte Systemübersicht für Linux-Systeme
nuss-sysinfo 2.2.0
nuss-sysinfo 2.2.0
7185a9d0bff1ef6097338ee59cd6afe16d1acaf31b510545bbe579f35d300b915b397cc27cd324333a8bfeb1df24a26deb9c945c9d67d666b497606267d0680e2d7631a3980a5a01e69b6858086a1e7c#!/usr/bin/env bash
# ==============================================================================
# sysinfo.sh - Kompakte Systemübersicht für Linux-Systeme
#
# Autor: Daniel Nuß
# Version: 2.2.0
# ==============================================================================
set -o pipefail
readonly SCRIPT_VERSION="2.2.0"
readonly EXTERNAL_IP_URL="https://api.ipify.org"
readonly WEATHER_URL="https://wttr.in/Hochheim%20am%20Main?format=%t,+%C&lang=de"
readonly BASHRC_BLOCK_BEGIN="# >>> sysinfo.sh - automatisch verwaltet >>>"
readonly BASHRC_BLOCK_END="# <<< sysinfo.sh - automatisch verwaltet <<<"
show_help() {
cat <<EOF
Verwendung: ${0##*/} [OPTION]
Zeigt eine kompakte Übersicht des lokalen Linux-Systems an.
Ohne Option werden die Systeminformationen ausgegeben.
Optionen:
--install Abhängigkeiten installieren und in ~/.bashrc aktivieren
--uninstall Automatischen Aufruf aus ~/.bashrc entfernen
--check Benötigte und optionale Kommandos prüfen
-h, --help Diese Hilfe anzeigen
-V, --version Skriptversion anzeigen
EOF
}
get_script_path() {
local script_path="${BASH_SOURCE[0]}"
if [[ "$script_path" != /* ]]; then
script_path="$(pwd)/$script_path"
fi
if command -v readlink >/dev/null 2>&1; then
readlink -f -- "$script_path" 2>/dev/null || printf '%s' "$script_path"
else
printf '%s' "$script_path"
fi
}
get_bashrc_path() {
local account_directory="${HOME:-}"
local bashrc_path=""
if [[ -z "$account_directory" ]]; then
printf 'FEHLER: Das Benutzerverzeichnis konnte nicht ermittelt werden.\n' >&2
return 1
fi
bashrc_path="${account_directory}/.bashrc"
# Bei einer vorhandenen Verknüpfung den Inhalt der Zieldatei bearbeiten,
# statt die Verknüpfung beim atomaren Austausch zu ersetzen.
if [[ -e "$bashrc_path" ]] && command -v readlink >/dev/null 2>&1; then
readlink -f -- "$bashrc_path" 2>/dev/null || printf '%s' "$bashrc_path"
else
printf '%s' "$bashrc_path"
fi
}
print_command_check() {
local command_name="$1"
if command -v "$command_name" >/dev/null 2>&1; then
printf ' [OK] %s\n' "$command_name"
return 0
fi
printf ' [FEHLT] %s\n' "$command_name"
return 1
}
check_dependencies() {
local command_name
local missing_required=0
local bashrc_path=""
local -a required_commands=(
apt apt-get awk chmod date df env grep ip mktemp mv readlink rm sed tr uname
)
printf '%s %s - Abhängigkeitsprüfung\n' "${0##*/}" "$SCRIPT_VERSION"
printf '\nPflichtkommandos:\n'
if [[ -n "${BASH_VERSION:-}" ]]; then
printf ' [OK] bash\n'
else
printf ' [FEHLT] bash\n'
missing_required=1
fi
for command_name in "${required_commands[@]}"; do
if ! print_command_check "$command_name"; then
missing_required=1
fi
done
if command -v curl >/dev/null 2>&1; then
printf ' [OK] HTTPS-Client: curl\n'
elif command -v wget >/dev/null 2>&1; then
printf ' [OK] HTTPS-Client: wget\n'
else
printf ' [FEHLT] HTTPS-Client: curl oder wget\n'
missing_required=1
fi
if [[ -r /etc/ssl/certs/ca-certificates.crt ]]; then
printf ' [OK] CA-Zertifikate\n'
else
printf ' [FEHLT] CA-Zertifikate\n'
missing_required=1
fi
printf '\nOptionale Erweiterungen:\n'
for command_name in pydf tput vcgencmd; do
if command -v "$command_name" >/dev/null 2>&1; then
printf ' [OK] %s\n' "$command_name"
else
printf ' [OPTION] %s nicht installiert\n' "$command_name"
fi
done
bashrc_path=$(get_bashrc_path 2>/dev/null || true)
printf '\nAnmeldeintegration:\n'
if [[ -n "$bashrc_path" && -r "$bashrc_path" ]] && \
grep -Fqx "$BASHRC_BLOCK_BEGIN" "$bashrc_path" 2>/dev/null; then
printf ' [AKTIV] %s\n' "$bashrc_path"
else
printf ' [INAKTIV] sysinfo.sh ist nicht in ~/.bashrc eingetragen\n'
fi
printf '\n'
if (( missing_required == 0 )); then
printf 'Ergebnis: Alle Pflichtkommandos sind vorhanden.\n'
return 0
fi
printf 'Ergebnis: Mindestens ein Pflichtkommando fehlt.\n'
return 1
}
collect_missing_apt_packages() {
local coreutils_missing=0
local command_name
local -a packages=()
command -v apt >/dev/null 2>&1 || packages+=(apt)
command -v awk >/dev/null 2>&1 || packages+=(mawk)
command -v grep >/dev/null 2>&1 || packages+=(grep)
command -v ip >/dev/null 2>&1 || packages+=(iproute2)
command -v sed >/dev/null 2>&1 || packages+=(sed)
if ! command -v curl >/dev/null 2>&1 && \
! command -v wget >/dev/null 2>&1; then
packages+=(curl)
fi
for command_name in chmod date df env mktemp mv readlink rm tr uname; do
if ! command -v "$command_name" >/dev/null 2>&1; then
coreutils_missing=1
break
fi
done
(( coreutils_missing == 0 )) || packages+=(coreutils)
[[ -r /etc/ssl/certs/ca-certificates.crt ]] || packages+=(ca-certificates)
if (( ${#packages[@]} > 0 )); then
printf '%s\n' "${packages[@]}"
fi
}
run_apt_get() {
if ! command -v apt-get >/dev/null 2>&1; then
printf 'FEHLER: apt-get ist nicht vorhanden; automatische Installation nicht möglich.\n' >&2
return 1
fi
if (( EUID == 0 )); then
DEBIAN_FRONTEND=noninteractive apt-get "$@"
elif command -v sudo >/dev/null 2>&1; then
sudo env DEBIAN_FRONTEND=noninteractive apt-get "$@"
else
printf 'FEHLER: Für die Paketinstallation werden root-Rechte oder sudo benötigt.\n' >&2
return 1
fi
}
install_dependencies() {
local -a packages=()
if ! command -v apt-get >/dev/null 2>&1; then
printf 'FEHLER: --install benötigt ein Debian-/Ubuntu-System mit apt-get.\n' >&2
return 1
fi
mapfile -t packages < <(collect_missing_apt_packages)
if (( ${#packages[@]} == 0 )); then
printf 'Alle benötigten APT-Pakete sind bereits vorhanden.\n'
return 0
fi
printf 'Fehlende Pakete: %s\n' "${packages[*]}"
printf 'APT-Paketlisten werden aktualisiert ...\n'
run_apt_get update || return 1
printf 'Fehlende Pakete werden installiert ...\n'
run_apt_get install -y --no-install-recommends "${packages[@]}" || return 1
}
rewrite_bashrc_without_managed_block() {
local bashrc_path="$1"
local temporary_file=""
temporary_file=$(mktemp "${bashrc_path}.sysinfo.XXXXXX") || return 1
if ! awk \
-v begin_marker="$BASHRC_BLOCK_BEGIN" \
-v end_marker="$BASHRC_BLOCK_END" '
$0 == begin_marker {
if (line_count > 0 && lines[line_count] ~ /^[[:space:]]*$/) {
delete lines[line_count]
line_count--
}
managed_block = 1
next
}
$0 == end_marker {
managed_block = 0
next
}
!managed_block {
lines[++line_count] = $0
}
END {
for (line_number = 1; line_number <= line_count; line_number++) {
print lines[line_number]
}
}
' "$bashrc_path" > "$temporary_file"; then
rm -f -- "$temporary_file"
return 1
fi
chmod --reference="$bashrc_path" "$temporary_file" 2>/dev/null || \
chmod 0644 "$temporary_file"
if ! mv -f -- "$temporary_file" "$bashrc_path"; then
rm -f -- "$temporary_file"
return 1
fi
}
install_bashrc_integration() {
local bashrc_path=""
local script_path=""
local bash_path=""
local quoted_script_path=""
local quoted_bash_path=""
bashrc_path=$(get_bashrc_path) || return 1
script_path=$(get_script_path) || return 1
bash_path=$(command -v bash) || return 1
if [[ ! -r "$script_path" ]]; then
printf 'FEHLER: Das Skript ist nicht lesbar: %s\n' "$script_path" >&2
return 1
fi
if [[ ! -e "$bashrc_path" ]]; then
: > "$bashrc_path" || return 1
chmod 0644 "$bashrc_path" || return 1
fi
rewrite_bashrc_without_managed_block "$bashrc_path" || {
printf 'FEHLER: %s konnte nicht vorbereitet werden.\n' "$bashrc_path" >&2
return 1
}
printf -v quoted_script_path '%q' "$script_path"
printf -v quoted_bash_path '%q' "$bash_path"
{
printf '\n%s\n' "$BASHRC_BLOCK_BEGIN"
printf 'if [[ $- == *i* ]] && [[ -r %s ]]; then\n' "$quoted_script_path"
printf ' %s %s\n' "$quoted_bash_path" "$quoted_script_path"
printf 'fi\n'
printf '%s\n' "$BASHRC_BLOCK_END"
} >> "$bashrc_path"
printf 'Installiert: sysinfo.sh wird bei neuen interaktiven Bash-Sitzungen ausgeführt.\n'
printf 'Eintrag: %s\n' "$bashrc_path"
printf 'Skript: %s\n' "$script_path"
}
uninstall_bashrc_integration() {
local bashrc_path=""
bashrc_path=$(get_bashrc_path) || return 1
if [[ ! -e "$bashrc_path" ]] || \
! grep -Fqx "$BASHRC_BLOCK_BEGIN" "$bashrc_path" 2>/dev/null; then
printf 'Kein verwalteter sysinfo.sh-Eintrag in %s gefunden.\n' "$bashrc_path"
return 0
fi
rewrite_bashrc_without_managed_block "$bashrc_path" || {
printf 'FEHLER: Der Eintrag konnte nicht aus %s entfernt werden.\n' "$bashrc_path" >&2
return 1
}
printf 'Entfernt: sysinfo.sh wird nicht mehr automatisch über %s gestartet.\n' \
"$bashrc_path"
}
if (( $# > 1 )); then
printf 'FEHLER: Es darf nur eine Option angegeben werden.\n\n' >&2
show_help >&2
exit 2
fi
case "${1:-}" in
"")
;;
--install|—install|–install)
install_dependencies || exit 1
if ! check_dependencies >/dev/null; then
check_dependencies
exit 1
fi
install_bashrc_integration || exit 1
check_dependencies
exit $?
;;
--uninstall|—uninstall|–uninstall)
uninstall_bashrc_integration
exit $?
;;
--check|—check|–check)
check_dependencies
exit $?
;;
-h|--help|—help|–help)
show_help
exit 0
;;
-V|--version|—version|–version)
printf '%s %s\n' "${0##*/}" "$SCRIPT_VERSION"
exit 0
;;
*)
printf 'Unbekannte Option: %s\n\n' "$1" >&2
show_help >&2
exit 2
;;
esac
# Farben nur verwenden, wenn die Ausgabe tatsächlich an ein Terminal geht.
color_green=""
color_red=""
color_white_bg=""
color_reset=""
if [[ -t 1 && -n "${TERM:-}" ]] && command -v tput >/dev/null 2>&1; then
color_green=$(tput setaf 2 2>/dev/null || true)
color_red=$(tput setaf 1 2>/dev/null || true)
color_white_bg=$(tput setab 7 2>/dev/null || true)
color_reset=$(tput sgr0 2>/dev/null || true)
fi
print_banner_line() {
local text="${1:-}"
local width=59
local text_length=${#text}
local left_padding=0
local right_padding=0
if (( text_length < width )); then
left_padding=$(( (width - text_length) / 2 ))
right_padding=$(( width - text_length - left_padding ))
fi
printf '%s%s%*s%s%*s%s\n' \
"$color_red" "$color_white_bg" \
"$left_padding" "" "$text" "$right_padding" "" \
"$color_reset"
}
print_banner() {
local line_number
for ((line_number = 0; line_number < 5; line_number++)); do
print_banner_line
done
print_banner_line "Daniel Nuß & Marcus Heße-Nuß"
print_banner_line "Am Bittelborn 23A"
print_banner_line "65239 Hochheim am Main"
for ((line_number = 0; line_number < 5; line_number++)); do
print_banner_line
done
}
get_uptime() {
local uptime_raw="0"
local uptime_seconds=0
local days=0
local hours=0
local minutes=0
local seconds=0
if [[ -r /proc/uptime ]]; then
read -r uptime_raw _ < /proc/uptime
uptime_seconds=${uptime_raw%%.*}
fi
[[ "$uptime_seconds" =~ ^[0-9]+$ ]] || uptime_seconds=0
seconds=$((uptime_seconds % 60))
minutes=$((uptime_seconds / 60 % 60))
hours=$((uptime_seconds / 3600 % 24))
days=$((uptime_seconds / 86400))
printf '%d Tage, %02dh%02dm%02ds' "$days" "$hours" "$minutes" "$seconds"
}
get_device_model() {
local model_file
for model_file in \
/proc/device-tree/model \
/sys/firmware/devicetree/base/model; do
if [[ -f "$model_file" && -r "$model_file" ]]; then
tr -d '\0' < "$model_file"
return 0
fi
done
return 1
}
get_cpu_temperature() {
local temperature=""
local raw_temperature=""
local sensor_file
if command -v vcgencmd >/dev/null 2>&1; then
temperature=$(vcgencmd measure_temp 2>/dev/null || true)
if [[ "$temperature" =~ ([0-9]+([.][0-9]+)?) ]]; then
printf '%s°C' "${BASH_REMATCH[1]}"
return 0
fi
fi
for sensor_file in /sys/class/thermal/thermal_zone*/temp; do
[[ -r "$sensor_file" ]] || continue
read -r raw_temperature < "$sensor_file"
if [[ "$raw_temperature" =~ ^[0-9]+$ ]] && \
(( raw_temperature > 0 && raw_temperature < 200000 )); then
awk -v value="$raw_temperature" 'BEGIN { printf "%.1f°C", value / 1000 }'
return 0
fi
done
return 1
}
get_memory_value() {
local field="$1"
awk -v field="$field" '$1 == field ":" { print $2; exit }' /proc/meminfo 2>/dev/null
}
get_process_count() {
local process_directory
local process_count=0
for process_directory in /proc/[0-9]*; do
[[ -d "$process_directory" ]] || continue
((process_count += 1))
done
printf '%d' "$process_count"
}
print_ipv4_addresses() {
local interface_name
local address
local found=0
if command -v ip >/dev/null 2>&1; then
while read -r interface_name address; do
[[ -n "$interface_name" && -n "$address" ]] || continue
interface_name=${interface_name%%@*}
address=${address%%/*}
printf 'IPv4 %-16s: %s\n' "$interface_name" "$address"
found=1
done < <(ip -o -4 address show scope global 2>/dev/null | awk '{ print $2, $4 }')
fi
if (( found == 0 )); then
printf 'IPv4................: nicht ermittelbar\n'
fi
}
fetch_https() {
local url="$1"
if command -v curl >/dev/null 2>&1; then
curl --ipv4 --fail --silent --show-error \
--connect-timeout 3 --max-time 5 "$url" 2>/dev/null
elif command -v wget >/dev/null 2>&1; then
wget --inet4-only --quiet --output-document=- \
--timeout=5 --tries=1 "$url" 2>/dev/null
else
return 127
fi
}
get_external_ip() {
local external_ip=""
external_ip=$(fetch_https "$EXTERNAL_IP_URL" | tr -d '\r\n' || true)
if [[ -n "$external_ip" ]]; then
printf '%s' "$external_ip"
else
printf 'nicht ermittelbar'
fi
}
get_weather() {
local weather=""
weather=$(fetch_https "$WEATHER_URL" | tr -d '\r\n' || true)
if [[ -n "$weather" ]]; then
printf '%s' "$weather"
else
printf 'nicht ermittelbar'
fi
}
get_apt_update_status() {
local apt_output=""
local update_count=0
if ! command -v apt >/dev/null 2>&1; then
printf 'nicht geprüft (APT nicht installiert)'
return 0
fi
# Absichtlich kein "apt update": Der Schnellcheck verwendet ausschließlich
# die bereits lokal vorhandenen Paketlisten und verändert das System nicht.
if ! apt_output=$(LC_ALL=C apt list --upgradable 2>/dev/null); then
printf 'Prüfung fehlgeschlagen (APT)'
return 0
fi
update_count=$(awk 'NR > 1 && NF > 0 { count++ } END { print count + 0 }' \
<<< "$apt_output")
if (( update_count > 0 )); then
printf 'JA - %d APT-Update(s) verfügbar (lokale Paketlisten)' "$update_count"
else
printf 'NEIN - laut lokalen APT-Paketlisten'
fi
}
print_disk_space() {
if command -v pydf >/dev/null 2>&1; then
pydf
else
df -hT
fi
}
uptime_text=$(get_uptime)
device_model=$(get_device_model || true)
cpu_temperature=$(get_cpu_temperature || true)
memory_total_kb=$(get_memory_value MemTotal)
memory_available_kb=$(get_memory_value MemAvailable)
load_one="-"
load_five="-"
load_fifteen="-"
if [[ -r /proc/loadavg ]]; then
read -r load_one load_five load_fifteen _ < /proc/loadavg
fi
# MemAvailable existiert seit Linux 3.14. Auf sehr alten Systemen zurückfallen.
if [[ -z "$memory_available_kb" ]]; then
memory_available_kb=$(get_memory_value MemFree)
fi
print_banner
printf '%s%s\n' "$color_green" "$(date '+%A, %e. %B %Y, %T')"
printf '%s\n' "$(uname -srmo)"
printf '%s' "$color_red"
printf 'Uptime...............: %s\n' "$uptime_text"
if [[ -n "$device_model" ]]; then
printf 'Gerätetyp............: %s\n' "$device_model"
fi
printf 'Speicher verfügbar...: %s kB / %s kB gesamt\n' \
"${memory_available_kb:-unbekannt}" "${memory_total_kb:-unbekannt}"
if [[ -n "$cpu_temperature" ]]; then
printf 'CPU-Temperatur.......: %s\n' "$cpu_temperature"
fi
printf 'Last (Durchschnitt)..: %s, %s, %s (1, 5, 15 min)\n' \
"$load_one" "$load_five" "$load_fifteen"
printf 'Laufende Prozesse....: %s\n' "$(get_process_count)"
print_ipv4_addresses
printf 'IP-Adresse extern....: %s\n' "$(get_external_ip)"
printf 'Hochheim a.M. Wetter.: %s\n' "$(get_weather)"
printf 'Freier Speicherplatz.:\n'
print_disk_space
# Gewünschte letzte sichtbare Ausgabezeile.
printf 'Paket-Updates........: %s\n' "$(get_apt_update_status)"
printf '%s' "$color_reset"
nuss-sysinfo 2.2.0
nuss-sysinfo 2.2.0
4a5eeac4057acd2c122508655379b4616d1acaf31b510545bbe579f35d300b915b397cc27cd324333a8bfeb1df24a26deb9c945c9d67d666b497606267d0680e2d7631a3980a5a01e69b6858086a1e7c#!/usr/bin/env bash
# ==============================================================================
# sysinfo.sh - Kompakte Systemübersicht für Linux-Systeme
#
# Autor: Daniel Nuß
# Version: 2.2.0
# ==============================================================================
set -o pipefail
readonly SCRIPT_VERSION="2.2.0"
readonly EXTERNAL_IP_URL="https://api.ipify.org"
readonly WEATHER_URL="https://wttr.in/Hochheim%20am%20Main?format=%t,+%C&lang=de"
readonly BASHRC_BLOCK_BEGIN="# >>> sysinfo.sh - automatisch verwaltet >>>"
readonly BASHRC_BLOCK_END="# <<< sysinfo.sh - automatisch verwaltet <<<"
show_help() {
cat <<EOF
Verwendung: ${0##*/} [OPTION]
Zeigt eine kompakte Übersicht des lokalen Linux-Systems an.
Ohne Option werden die Systeminformationen ausgegeben.
Optionen:
--install Abhängigkeiten installieren und in ~/.bashrc aktivieren
--uninstall Automatischen Aufruf aus ~/.bashrc entfernen
--check Benötigte und optionale Kommandos prüfen
-h, --help Diese Hilfe anzeigen
-V, --version Skriptversion anzeigen
EOF
}
get_script_path() {
local script_path="${BASH_SOURCE[0]}"
if [[ "$script_path" != /* ]]; then
script_path="$(pwd)/$script_path"
fi
if command -v readlink >/dev/null 2>&1; then
readlink -f -- "$script_path" 2>/dev/null || printf '%s' "$script_path"
else
printf '%s' "$script_path"
fi
}
get_bashrc_path() {
local account_directory="${HOME:-}"
local bashrc_path=""
if [[ -z "$account_directory" ]]; then
printf 'FEHLER: Das Benutzerverzeichnis konnte nicht ermittelt werden.\n' >&2
return 1
fi
bashrc_path="${account_directory}/.bashrc"
# Bei einer vorhandenen Verknüpfung den Inhalt der Zieldatei bearbeiten,
# statt die Verknüpfung beim atomaren Austausch zu ersetzen.
if [[ -e "$bashrc_path" ]] && command -v readlink >/dev/null 2>&1; then
readlink -f -- "$bashrc_path" 2>/dev/null || printf '%s' "$bashrc_path"
else
printf '%s' "$bashrc_path"
fi
}
print_command_check() {
local command_name="$1"
if command -v "$command_name" >/dev/null 2>&1; then
printf ' [OK] %s\n' "$command_name"
return 0
fi
printf ' [FEHLT] %s\n' "$command_name"
return 1
}
check_dependencies() {
local command_name
local missing_required=0
local bashrc_path=""
local -a required_commands=(
apt apt-get awk chmod date df env grep ip mktemp mv readlink rm sed tr uname
)
printf '%s %s - Abhängigkeitsprüfung\n' "${0##*/}" "$SCRIPT_VERSION"
printf '\nPflichtkommandos:\n'
if [[ -n "${BASH_VERSION:-}" ]]; then
printf ' [OK] bash\n'
else
printf ' [FEHLT] bash\n'
missing_required=1
fi
for command_name in "${required_commands[@]}"; do
if ! print_command_check "$command_name"; then
missing_required=1
fi
done
if command -v curl >/dev/null 2>&1; then
printf ' [OK] HTTPS-Client: curl\n'
elif command -v wget >/dev/null 2>&1; then
printf ' [OK] HTTPS-Client: wget\n'
else
printf ' [FEHLT] HTTPS-Client: curl oder wget\n'
missing_required=1
fi
if [[ -r /etc/ssl/certs/ca-certificates.crt ]]; then
printf ' [OK] CA-Zertifikate\n'
else
printf ' [FEHLT] CA-Zertifikate\n'
missing_required=1
fi
printf '\nOptionale Erweiterungen:\n'
for command_name in pydf tput vcgencmd; do
if command -v "$command_name" >/dev/null 2>&1; then
printf ' [OK] %s\n' "$command_name"
else
printf ' [OPTION] %s nicht installiert\n' "$command_name"
fi
done
bashrc_path=$(get_bashrc_path 2>/dev/null || true)
printf '\nAnmeldeintegration:\n'
if [[ -n "$bashrc_path" && -r "$bashrc_path" ]] && \
grep -Fqx "$BASHRC_BLOCK_BEGIN" "$bashrc_path" 2>/dev/null; then
printf ' [AKTIV] %s\n' "$bashrc_path"
else
printf ' [INAKTIV] sysinfo.sh ist nicht in ~/.bashrc eingetragen\n'
fi
printf '\n'
if (( missing_required == 0 )); then
printf 'Ergebnis: Alle Pflichtkommandos sind vorhanden.\n'
return 0
fi
printf 'Ergebnis: Mindestens ein Pflichtkommando fehlt.\n'
return 1
}
collect_missing_apt_packages() {
local coreutils_missing=0
local command_name
local -a packages=()
command -v apt >/dev/null 2>&1 || packages+=(apt)
command -v awk >/dev/null 2>&1 || packages+=(mawk)
command -v grep >/dev/null 2>&1 || packages+=(grep)
command -v ip >/dev/null 2>&1 || packages+=(iproute2)
command -v sed >/dev/null 2>&1 || packages+=(sed)
if ! command -v curl >/dev/null 2>&1 && \
! command -v wget >/dev/null 2>&1; then
packages+=(curl)
fi
for command_name in chmod date df env mktemp mv readlink rm tr uname; do
if ! command -v "$command_name" >/dev/null 2>&1; then
coreutils_missing=1
break
fi
done
(( coreutils_missing == 0 )) || packages+=(coreutils)
[[ -r /etc/ssl/certs/ca-certificates.crt ]] || packages+=(ca-certificates)
if (( ${#packages[@]} > 0 )); then
printf '%s\n' "${packages[@]}"
fi
}
run_apt_get() {
if ! command -v apt-get >/dev/null 2>&1; then
printf 'FEHLER: apt-get ist nicht vorhanden; automatische Installation nicht möglich.\n' >&2
return 1
fi
if (( EUID == 0 )); then
DEBIAN_FRONTEND=noninteractive apt-get "$@"
elif command -v sudo >/dev/null 2>&1; then
sudo env DEBIAN_FRONTEND=noninteractive apt-get "$@"
else
printf 'FEHLER: Für die Paketinstallation werden root-Rechte oder sudo benötigt.\n' >&2
return 1
fi
}
install_dependencies() {
local -a packages=()
if ! command -v apt-get >/dev/null 2>&1; then
printf 'FEHLER: --install benötigt ein Debian-/Ubuntu-System mit apt-get.\n' >&2
return 1
fi
mapfile -t packages < <(collect_missing_apt_packages)
if (( ${#packages[@]} == 0 )); then
printf 'Alle benötigten APT-Pakete sind bereits vorhanden.\n'
return 0
fi
printf 'Fehlende Pakete: %s\n' "${packages[*]}"
printf 'APT-Paketlisten werden aktualisiert ...\n'
run_apt_get update || return 1
printf 'Fehlende Pakete werden installiert ...\n'
run_apt_get install -y --no-install-recommends "${packages[@]}" || return 1
}
rewrite_bashrc_without_managed_block() {
local bashrc_path="$1"
local temporary_file=""
temporary_file=$(mktemp "${bashrc_path}.sysinfo.XXXXXX") || return 1
if ! awk \
-v begin_marker="$BASHRC_BLOCK_BEGIN" \
-v end_marker="$BASHRC_BLOCK_END" '
$0 == begin_marker {
if (line_count > 0 && lines[line_count] ~ /^[[:space:]]*$/) {
delete lines[line_count]
line_count--
}
managed_block = 1
next
}
$0 == end_marker {
managed_block = 0
next
}
!managed_block {
lines[++line_count] = $0
}
END {
for (line_number = 1; line_number <= line_count; line_number++) {
print lines[line_number]
}
}
' "$bashrc_path" > "$temporary_file"; then
rm -f -- "$temporary_file"
return 1
fi
chmod --reference="$bashrc_path" "$temporary_file" 2>/dev/null || \
chmod 0644 "$temporary_file"
if ! mv -f -- "$temporary_file" "$bashrc_path"; then
rm -f -- "$temporary_file"
return 1
fi
}
install_bashrc_integration() {
local bashrc_path=""
local script_path=""
local bash_path=""
local quoted_script_path=""
local quoted_bash_path=""
bashrc_path=$(get_bashrc_path) || return 1
script_path=$(get_script_path) || return 1
bash_path=$(command -v bash) || return 1
if [[ ! -r "$script_path" ]]; then
printf 'FEHLER: Das Skript ist nicht lesbar: %s\n' "$script_path" >&2
return 1
fi
if [[ ! -e "$bashrc_path" ]]; then
: > "$bashrc_path" || return 1
chmod 0644 "$bashrc_path" || return 1
fi
rewrite_bashrc_without_managed_block "$bashrc_path" || {
printf 'FEHLER: %s konnte nicht vorbereitet werden.\n' "$bashrc_path" >&2
return 1
}
printf -v quoted_script_path '%q' "$script_path"
printf -v quoted_bash_path '%q' "$bash_path"
{
printf '\n%s\n' "$BASHRC_BLOCK_BEGIN"
printf 'if [[ $- == *i* ]] && [[ -r %s ]]; then\n' "$quoted_script_path"
printf ' %s %s\n' "$quoted_bash_path" "$quoted_script_path"
printf 'fi\n'
printf '%s\n' "$BASHRC_BLOCK_END"
} >> "$bashrc_path"
printf 'Installiert: sysinfo.sh wird bei neuen interaktiven Bash-Sitzungen ausgeführt.\n'
printf 'Eintrag: %s\n' "$bashrc_path"
printf 'Skript: %s\n' "$script_path"
}
uninstall_bashrc_integration() {
local bashrc_path=""
bashrc_path=$(get_bashrc_path) || return 1
if [[ ! -e "$bashrc_path" ]] || \
! grep -Fqx "$BASHRC_BLOCK_BEGIN" "$bashrc_path" 2>/dev/null; then
printf 'Kein verwalteter sysinfo.sh-Eintrag in %s gefunden.\n' "$bashrc_path"
return 0
fi
rewrite_bashrc_without_managed_block "$bashrc_path" || {
printf 'FEHLER: Der Eintrag konnte nicht aus %s entfernt werden.\n' "$bashrc_path" >&2
return 1
}
printf 'Entfernt: sysinfo.sh wird nicht mehr automatisch über %s gestartet.\n' \
"$bashrc_path"
}
if (( $# > 1 )); then
printf 'FEHLER: Es darf nur eine Option angegeben werden.\n\n' >&2
show_help >&2
exit 2
fi
case "${1:-}" in
"")
;;
--install|—install|–install)
install_dependencies || exit 1
if ! check_dependencies >/dev/null; then
check_dependencies
exit 1
fi
install_bashrc_integration || exit 1
check_dependencies
exit $?
;;
--uninstall|—uninstall|–uninstall)
uninstall_bashrc_integration
exit $?
;;
--check|—check|–check)
check_dependencies
exit $?
;;
-h|--help|—help|–help)
show_help
exit 0
;;
-V|--version|—version|–version)
printf '%s %s\n' "${0##*/}" "$SCRIPT_VERSION"
exit 0
;;
*)
printf 'Unbekannte Option: %s\n\n' "$1" >&2
show_help >&2
exit 2
;;
esac
# Farben nur verwenden, wenn die Ausgabe tatsächlich an ein Terminal geht.
color_green=""
color_red=""
color_white_bg=""
color_reset=""
if [[ -t 1 && -n "${TERM:-}" ]] && command -v tput >/dev/null 2>&1; then
color_green=$(tput setaf 2 2>/dev/null || true)
color_red=$(tput setaf 1 2>/dev/null || true)
color_white_bg=$(tput setab 7 2>/dev/null || true)
color_reset=$(tput sgr0 2>/dev/null || true)
fi
print_banner_line() {
local text="${1:-}"
local width=59
local text_length=${#text}
local left_padding=0
local right_padding=0
if (( text_length < width )); then
left_padding=$(( (width - text_length) / 2 ))
right_padding=$(( width - text_length - left_padding ))
fi
printf '%s%s%*s%s%*s%s\n' \
"$color_red" "$color_white_bg" \
"$left_padding" "" "$text" "$right_padding" "" \
"$color_reset"
}
print_banner() {
local line_number
for ((line_number = 0; line_number < 5; line_number++)); do
print_banner_line
done
print_banner_line "Daniel Nuß & Marcus Heße-Nuß"
print_banner_line "Am Bittelborn 23A"
print_banner_line "65239 Hochheim am Main"
for ((line_number = 0; line_number < 5; line_number++)); do
print_banner_line
done
}
get_uptime() {
local uptime_raw="0"
local uptime_seconds=0
local days=0
local hours=0
local minutes=0
local seconds=0
if [[ -r /proc/uptime ]]; then
read -r uptime_raw _ < /proc/uptime
uptime_seconds=${uptime_raw%%.*}
fi
[[ "$uptime_seconds" =~ ^[0-9]+$ ]] || uptime_seconds=0
seconds=$((uptime_seconds % 60))
minutes=$((uptime_seconds / 60 % 60))
hours=$((uptime_seconds / 3600 % 24))
days=$((uptime_seconds / 86400))
printf '%d Tage, %02dh%02dm%02ds' "$days" "$hours" "$minutes" "$seconds"
}
get_device_model() {
local model_file
for model_file in \
/proc/device-tree/model \
/sys/firmware/devicetree/base/model; do
if [[ -f "$model_file" && -r "$model_file" ]]; then
tr -d '\0' < "$model_file"
return 0
fi
done
return 1
}
get_cpu_temperature() {
local temperature=""
local raw_temperature=""
local sensor_file
if command -v vcgencmd >/dev/null 2>&1; then
temperature=$(vcgencmd measure_temp 2>/dev/null || true)
if [[ "$temperature" =~ ([0-9]+([.][0-9]+)?) ]]; then
printf '%s°C' "${BASH_REMATCH[1]}"
return 0
fi
fi
for sensor_file in /sys/class/thermal/thermal_zone*/temp; do
[[ -r "$sensor_file" ]] || continue
read -r raw_temperature < "$sensor_file"
if [[ "$raw_temperature" =~ ^[0-9]+$ ]] && \
(( raw_temperature > 0 && raw_temperature < 200000 )); then
awk -v value="$raw_temperature" 'BEGIN { printf "%.1f°C", value / 1000 }'
return 0
fi
done
return 1
}
get_memory_value() {
local field="$1"
awk -v field="$field" '$1 == field ":" { print $2; exit }' /proc/meminfo 2>/dev/null
}
get_process_count() {
local process_directory
local process_count=0
for process_directory in /proc/[0-9]*; do
[[ -d "$process_directory" ]] || continue
((process_count += 1))
done
printf '%d' "$process_count"
}
print_ipv4_addresses() {
local interface_name
local address
local found=0
if command -v ip >/dev/null 2>&1; then
while read -r interface_name address; do
[[ -n "$interface_name" && -n "$address" ]] || continue
interface_name=${interface_name%%@*}
address=${address%%/*}
printf 'IPv4 %-16s: %s\n' "$interface_name" "$address"
found=1
done < <(ip -o -4 address show scope global 2>/dev/null | awk '{ print $2, $4 }')
fi
if (( found == 0 )); then
printf 'IPv4................: nicht ermittelbar\n'
fi
}
fetch_https() {
local url="$1"
if command -v curl >/dev/null 2>&1; then
curl --ipv4 --fail --silent --show-error \
--connect-timeout 3 --max-time 5 "$url" 2>/dev/null
elif command -v wget >/dev/null 2>&1; then
wget --inet4-only --quiet --output-document=- \
--timeout=5 --tries=1 "$url" 2>/dev/null
else
return 127
fi
}
get_external_ip() {
local external_ip=""
external_ip=$(fetch_https "$EXTERNAL_IP_URL" | tr -d '\r\n' || true)
if [[ -n "$external_ip" ]]; then
printf '%s' "$external_ip"
else
printf 'nicht ermittelbar'
fi
}
get_weather() {
local weather=""
weather=$(fetch_https "$WEATHER_URL" | tr -d '\r\n' || true)
if [[ -n "$weather" ]]; then
printf '%s' "$weather"
else
printf 'nicht ermittelbar'
fi
}
get_apt_update_status() {
local apt_output=""
local update_count=0
if ! command -v apt >/dev/null 2>&1; then
printf 'nicht geprüft (APT nicht installiert)'
return 0
fi
# Absichtlich kein "apt update": Der Schnellcheck verwendet ausschließlich
# die bereits lokal vorhandenen Paketlisten und verändert das System nicht.
if ! apt_output=$(LC_ALL=C apt list --upgradable 2>/dev/null); then
printf 'Prüfung fehlgeschlagen (APT)'
return 0
fi
update_count=$(awk 'NR > 1 && NF > 0 { count++ } END { print count + 0 }' \
<<< "$apt_output")
if (( update_count > 0 )); then
printf 'JA - %d APT-Update(s) verfügbar (lokale Paketlisten)' "$update_count"
else
printf 'NEIN - laut lokalen APT-Paketlisten'
fi
}
print_disk_space() {
if command -v pydf >/dev/null 2>&1; then
pydf
else
df -hT
fi
}
uptime_text=$(get_uptime)
device_model=$(get_device_model || true)
cpu_temperature=$(get_cpu_temperature || true)
memory_total_kb=$(get_memory_value MemTotal)
memory_available_kb=$(get_memory_value MemAvailable)
load_one="-"
load_five="-"
load_fifteen="-"
if [[ -r /proc/loadavg ]]; then
read -r load_one load_five load_fifteen _ < /proc/loadavg
fi
# MemAvailable existiert seit Linux 3.14. Auf sehr alten Systemen zurückfallen.
if [[ -z "$memory_available_kb" ]]; then
memory_available_kb=$(get_memory_value MemFree)
fi
print_banner
printf '%s%s\n' "$color_green" "$(date '+%A, %e. %B %Y, %T')"
printf '%s\n' "$(uname -srmo)"
printf '%s' "$color_red"
printf 'Uptime...............: %s\n' "$uptime_text"
if [[ -n "$device_model" ]]; then
printf 'Gerätetyp............: %s\n' "$device_model"
fi
printf 'Speicher verfügbar...: %s kB / %s kB gesamt\n' \
"${memory_available_kb:-unbekannt}" "${memory_total_kb:-unbekannt}"
if [[ -n "$cpu_temperature" ]]; then
printf 'CPU-Temperatur.......: %s\n' "$cpu_temperature"
fi
printf 'Last (Durchschnitt)..: %s, %s, %s (1, 5, 15 min)\n' \
"$load_one" "$load_five" "$load_fifteen"
printf 'Laufende Prozesse....: %s\n' "$(get_process_count)"
print_ipv4_addresses
printf 'IP-Adresse extern....: %s\n' "$(get_external_ip)"
printf 'Hochheim a.M. Wetter.: %s\n' "$(get_weather)"
printf 'Freier Speicherplatz.:\n'
print_disk_space
# Gewünschte letzte sichtbare Ausgabezeile.
printf 'Paket-Updates........: %s\n' "$(get_apt_update_status)"
printf '%s' "$color_reset"
nuss-sysinfo 2.1.0
nuss-sysinfo 2.1.0
377f57197177a7260995acde1fb70cb005068f02396352ef1849652b4b817d783ceef85ae3deffa22280a2855e47883935e764e9030345756aae8866e044eb883941f7565caad95252ddf2150d34e439#!/usr/bin/env bash
# ==============================================================================
# sysinfo.sh - Kompakte Systemübersicht für Linux-Systeme
#
# Autor: Daniel Nuß
# Version: 2.1.0
# ==============================================================================
set -o pipefail
readonly SCRIPT_VERSION="2.1.0"
readonly EXTERNAL_IP_URL="https://api.ipify.org"
readonly WEATHER_URL="https://wttr.in/Hochheim%20am%20Main?format=%t,+%C&lang=de"
show_help() {
cat <<EOF
Verwendung: ${0##*/} [OPTION]
Zeigt eine kompakte Übersicht des lokalen Linux-Systems an.
Optionen:
-h, --help Diese Hilfe anzeigen
-V, --version Skriptversion anzeigen
EOF
}
case "${1:-}" in
"")
;;
-h|--help)
show_help
exit 0
;;
-V|--version)
printf '%s %s\n' "${0##*/}" "$SCRIPT_VERSION"
exit 0
;;
*)
printf 'Unbekannte Option: %s\n\n' "$1" >&2
show_help >&2
exit 2
;;
esac
# Farben nur verwenden, wenn die Ausgabe tatsächlich an ein Terminal geht.
color_green=""
color_red=""
color_white_bg=""
color_reset=""
if [[ -t 1 && -n "${TERM:-}" ]] && command -v tput >/dev/null 2>&1; then
color_green=$(tput setaf 2 2>/dev/null || true)
color_red=$(tput setaf 1 2>/dev/null || true)
color_white_bg=$(tput setab 7 2>/dev/null || true)
color_reset=$(tput sgr0 2>/dev/null || true)
fi
print_banner_line() {
local text="${1:-}"
local width=59
local text_length=${#text}
local left_padding=0
local right_padding=0
if (( text_length < width )); then
left_padding=$(( (width - text_length) / 2 ))
right_padding=$(( width - text_length - left_padding ))
fi
printf '%s%s%*s%s%*s%s\n' \
"$color_red" "$color_white_bg" \
"$left_padding" "" "$text" "$right_padding" "" \
"$color_reset"
}
print_banner() {
local line_number
for ((line_number = 0; line_number < 5; line_number++)); do
print_banner_line
done
print_banner_line "Daniel Nuß & Marcus Heße-Nuß"
print_banner_line "Am Bittelborn 23A"
print_banner_line "65239 Hochheim am Main"
for ((line_number = 0; line_number < 5; line_number++)); do
print_banner_line
done
}
get_uptime() {
local uptime_raw="0"
local uptime_seconds=0
local days=0
local hours=0
local minutes=0
local seconds=0
if [[ -r /proc/uptime ]]; then
read -r uptime_raw _ < /proc/uptime
uptime_seconds=${uptime_raw%%.*}
fi
[[ "$uptime_seconds" =~ ^[0-9]+$ ]] || uptime_seconds=0
seconds=$((uptime_seconds % 60))
minutes=$((uptime_seconds / 60 % 60))
hours=$((uptime_seconds / 3600 % 24))
days=$((uptime_seconds / 86400))
printf '%d Tage, %02dh%02dm%02ds' "$days" "$hours" "$minutes" "$seconds"
}
get_device_model() {
local model_file
for model_file in \
/proc/device-tree/model \
/sys/firmware/devicetree/base/model; do
if [[ -f "$model_file" && -r "$model_file" ]]; then
tr -d '\0' < "$model_file"
return 0
fi
done
return 1
}
get_cpu_temperature() {
local temperature=""
local raw_temperature=""
local sensor_file
if command -v vcgencmd >/dev/null 2>&1; then
temperature=$(vcgencmd measure_temp 2>/dev/null || true)
if [[ "$temperature" =~ ([0-9]+([.][0-9]+)?) ]]; then
printf '%s°C' "${BASH_REMATCH[1]}"
return 0
fi
fi
for sensor_file in /sys/class/thermal/thermal_zone*/temp; do
[[ -r "$sensor_file" ]] || continue
read -r raw_temperature < "$sensor_file"
if [[ "$raw_temperature" =~ ^[0-9]+$ ]] && \
(( raw_temperature > 0 && raw_temperature < 200000 )); then
awk -v value="$raw_temperature" 'BEGIN { printf "%.1f°C", value / 1000 }'
return 0
fi
done
return 1
}
get_memory_value() {
local field="$1"
awk -v field="$field" '$1 == field ":" { print $2; exit }' /proc/meminfo 2>/dev/null
}
get_process_count() {
local process_directory
local process_count=0
for process_directory in /proc/[0-9]*; do
[[ -d "$process_directory" ]] || continue
((process_count += 1))
done
printf '%d' "$process_count"
}
print_ipv4_addresses() {
local interface_name
local address
local found=0
if command -v ip >/dev/null 2>&1; then
while read -r interface_name address; do
[[ -n "$interface_name" && -n "$address" ]] || continue
interface_name=${interface_name%%@*}
address=${address%%/*}
printf 'IPv4 %-16s: %s\n' "$interface_name" "$address"
found=1
done < <(ip -o -4 address show scope global 2>/dev/null | awk '{ print $2, $4 }')
fi
if (( found == 0 )); then
printf 'IPv4................: nicht ermittelbar\n'
fi
}
fetch_https() {
local url="$1"
if command -v curl >/dev/null 2>&1; then
curl --ipv4 --fail --silent --show-error \
--connect-timeout 3 --max-time 5 "$url" 2>/dev/null
elif command -v wget >/dev/null 2>&1; then
wget --inet4-only --quiet --output-document=- \
--timeout=5 --tries=1 "$url" 2>/dev/null
else
return 127
fi
}
get_external_ip() {
local external_ip=""
external_ip=$(fetch_https "$EXTERNAL_IP_URL" | tr -d '\r\n' || true)
if [[ -n "$external_ip" ]]; then
printf '%s' "$external_ip"
else
printf 'nicht ermittelbar'
fi
}
get_weather() {
local weather=""
weather=$(fetch_https "$WEATHER_URL" | tr -d '\r\n' || true)
if [[ -n "$weather" ]]; then
printf '%s' "$weather"
else
printf 'nicht ermittelbar'
fi
}
get_apt_update_status() {
local apt_output=""
local update_count=0
if ! command -v apt >/dev/null 2>&1; then
printf 'nicht geprüft (APT nicht installiert)'
return 0
fi
# Absichtlich kein "apt update": Der Schnellcheck verwendet ausschließlich
# die bereits lokal vorhandenen Paketlisten und verändert das System nicht.
if ! apt_output=$(LC_ALL=C apt list --upgradable 2>/dev/null); then
printf 'Prüfung fehlgeschlagen (APT)'
return 0
fi
update_count=$(awk 'NR > 1 && NF > 0 { count++ } END { print count + 0 }' \
<<< "$apt_output")
if (( update_count > 0 )); then
printf 'JA - %d APT-Update(s) verfügbar (lokale Paketlisten)' "$update_count"
else
printf 'NEIN - laut lokalen APT-Paketlisten'
fi
}
print_disk_space() {
if command -v pydf >/dev/null 2>&1; then
pydf
else
df -hT
fi
}
uptime_text=$(get_uptime)
device_model=$(get_device_model || true)
cpu_temperature=$(get_cpu_temperature || true)
memory_total_kb=$(get_memory_value MemTotal)
memory_available_kb=$(get_memory_value MemAvailable)
load_one="-"
load_five="-"
load_fifteen="-"
if [[ -r /proc/loadavg ]]; then
read -r load_one load_five load_fifteen _ < /proc/loadavg
fi
# MemAvailable existiert seit Linux 3.14. Auf sehr alten Systemen zurückfallen.
if [[ -z "$memory_available_kb" ]]; then
memory_available_kb=$(get_memory_value MemFree)
fi
print_banner
printf '%s%s\n' "$color_green" "$(date '+%A, %e. %B %Y, %T')"
printf '%s\n' "$(uname -srmo)"
printf '%s' "$color_red"
printf 'Uptime...............: %s\n' "$uptime_text"
if [[ -n "$device_model" ]]; then
printf 'Gerätetyp............: %s\n' "$device_model"
fi
printf 'Speicher verfügbar...: %s kB / %s kB gesamt\n' \
"${memory_available_kb:-unbekannt}" "${memory_total_kb:-unbekannt}"
if [[ -n "$cpu_temperature" ]]; then
printf 'CPU-Temperatur.......: %s\n' "$cpu_temperature"
fi
printf 'Last (Durchschnitt)..: %s, %s, %s (1, 5, 15 min)\n' \
"$load_one" "$load_five" "$load_fifteen"
printf 'Laufende Prozesse....: %s\n' "$(get_process_count)"
print_ipv4_addresses
printf 'IP-Adresse extern....: %s\n' "$(get_external_ip)"
printf 'Hochheim a.M. Wetter.: %s\n' "$(get_weather)"
printf 'Freier Speicherplatz.:\n'
print_disk_space
# Gewünschte letzte sichtbare Ausgabezeile.
printf 'Paket-Updates........: %s\n' "$(get_apt_update_status)"
printf '%s' "$color_reset"