Files
2025-09-23 12:33:50 +08:00

235 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
systeminfo.py - 将你给的 Bash 信息展示脚本等效地实现为 Python 版本。
依赖仅使用系统命令ip、df、free、uptime、last、df 等),在没有命令时会尽量降级显示。
此脚本会收集并以彩色终端输出显示系统信息,包括:
- 系统概览(主机名、操作系统、内核、运行时间、负载)
- 硬件资源CPU 型号与核数、CPU 温度、内存、磁盘分区)
- 网络连接活动接口、IPv4/IPv6、MAC
- 活动与统计(最近登录、已安装软件包数量、当前时间)
实现细节:尽量使用标准系统工具获取信息;当某些工具不可用时,会尝试替代命令或回退为默认值。
"""
import subprocess
import shutil
import os
import re
from datetime import datetime
from typing import List
# -------------------- 颜色 --------------------
NONE = '\033[00m'
BOLD = '\033[1m'
TITLE_COLOR = '\033[01;36m' # 亮青色
INFO_COLOR = '\033[01;33m' # 亮黄色
VALUE_COLOR = '\033[00;37m' # 白色
LABEL_COLOR = '\033[00;32m' # 绿色
SUBTLE_COLOR = '\033[01;30m' # 深灰色
BLUE_COLOR = '\033[01;34m' # 亮蓝色
ORANGE_COLOR = '\033[01;33m' # 同 INFO_COLOR用于 16 色终端
# -------------------- 运行本地系统命令 --------------------
def run_cmd(cmd: str) -> str:
"""运行一个 shell 命令并返回其标准输出(字符串)。错误时返回空字符串。"""
try:
out = subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL, text=True)
return out.strip()
except Exception:
return ""
def has_cmd(name: str) -> bool:
"""检查命令是否存在于 PATH 中。"""
return shutil.which(name) is not None
# -------------------- ASCII 艺术头部 --------------------
def print_header() -> None:
"""打印带颜色的 ASCII 艺术化标题和分隔线。"""
print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}")
print(f"{ORANGE_COLOR} _____ _ _ {NONE}")
print(f"{ORANGE_COLOR} | __ \\ | | (_) {NONE}")
print(f"{ORANGE_COLOR} | | | | ___ | |__ _ __ _ _ __ {NONE}")
print(f"{ORANGE_COLOR} | | | | / _ \\| '_ \\ | | / _` || '_ \\ {NONE}")
print(f"{ORANGE_COLOR} | |__| || __/| |_) || || (_| || | | |{NONE}")
print(f"{ORANGE_COLOR} |_____/ \\___||_.__/ |_| \\__,_||_| |_|{NONE}")
print()
print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}")
# -------------------- 系统概览 --------------------
def system_overview() -> None:
"""收集并打印系统概览信息。"""
print(f"{TITLE_COLOR}{BOLD}系统概览{NONE}")
hostname = run_cmd("hostname") or "N/A"
os_info = run_cmd("lsb_release -ds") or run_cmd("cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2 | tr -d '\"'") or "N/A"
kernel = run_cmd("uname -sr") or "N/A"
uptime = run_cmd("uptime -p").replace("up ", "") if has_cmd("uptime") else "N/A"
loadavg = run_cmd("uptime | awk -F'load average: ' '{print $2}'") or "N/A"
print(f" {LABEL_COLOR}系统名称:{NONE} {VALUE_COLOR}{hostname}{NONE}")
print(f" {LABEL_COLOR}操作系统:{NONE} {VALUE_COLOR}{os_info}{NONE}")
print(f" {LABEL_COLOR}内核版本:{NONE} {VALUE_COLOR}{kernel}{NONE}")
print(f" {LABEL_COLOR}运行时间:{NONE} {VALUE_COLOR}{uptime}{NONE}")
print(f" {LABEL_COLOR}平均负载:{NONE} {VALUE_COLOR}{loadavg}{NONE}")
print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}")
# -------------------- 硬件资源 --------------------
def hardware_resources() -> None:
"""收集并打印硬件资源信息CPU、内存、存储等"""
print(f"{TITLE_COLOR}{BOLD}硬件资源{NONE}")
# CPU 核心数与型号
cpu_model = "N/A"
cores: int | str = "N/A"
if os.path.exists("/proc/cpuinfo"):
try:
with open("/proc/cpuinfo", "r", encoding="utf-8", errors="ignore") as f:
data = f.read()
m = re.search(r"model name\s+:\s+(.+)", data)
if m:
cpu_model = m.group(1).strip()
cores = len(re.findall(r"^processor\s+:", data, flags=re.M))
except Exception:
pass
else:
cpu_model = run_cmd("uname -p") or cpu_model
cores = run_cmd("nproc") or cores
# CPU 温度(可选,通过 sensors 命令)
cpu_temp = ""
if has_cmd("sensors"):
sensors_out = run_cmd("sensors")
m = re.search(r"(\+\d+\.\d+°C)", sensors_out)
if m:
cpu_temp = f"({INFO_COLOR}{m.group(1)}{VALUE_COLOR})"
# 内存
mem_total = run_cmd("free -g | awk 'NR==2{print $2\"GB\"}'") or "N/A"
mem_used_pct = run_cmd("free -m | awk 'NR==2{printf \"%.1f%%\", $3*100/$2 }'") or "N/A"
mem_avail = run_cmd("free -m | awk 'NR==2{print $7\"MB\"}'") or "N/A"
print(f" {LABEL_COLOR}中央处理器 (CPU):{NONE}")
cores_str = str(cores)
print(f" {VALUE_COLOR}{cpu_model} - {cores_str} 核心 {cpu_temp}{NONE}")
print(f" {LABEL_COLOR}内存 (RAM):{NONE}")
print(f" {VALUE_COLOR}总计: {INFO_COLOR}{mem_total}{VALUE_COLOR} | 已用: {INFO_COLOR}{mem_used_pct}{VALUE_COLOR} | 可用: {INFO_COLOR}{mem_avail}{NONE}")
# 存储
print(f" {LABEL_COLOR}存储空间:{NONE}")
if has_cmd("df"):
df_cmd = "df -hT -x tmpfs -x devtmpfs -x devpts -x proc -x sysfs -x cgroup -x fusectl -x securityfs -x pstore -x efivarfs -x autofs 2>/dev/null | awk 'NR>1 {print $1, $2, $3, $4, $6, $7}'"
df_out = run_cmd(df_cmd)
if df_out:
for line in df_out.splitlines():
parts = line.split(None, 5)
if len(parts) == 6:
device, ftype, size, used, percent, mount = parts
print(f" {INFO_COLOR}{mount}{NONE} ({VALUE_COLOR}{device} - {ftype}{NONE})")
print(f" {LABEL_COLOR}总:{NONE} {BLUE_COLOR}{size}{NONE}, {LABEL_COLOR}已用:{NONE} {BLUE_COLOR}{used}{NONE} ({VALUE_COLOR}{percent}{NONE})")
else:
print(f" {VALUE_COLOR}无法读取磁盘信息{NONE}")
else:
print(f" {VALUE_COLOR}df 命令不可用{NONE}")
print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}")
# -------------------- 网络相关信息 --------------------
def network_info() -> None:
"""收集并打印网络接口与 IP 信息。"""
print(f"{TITLE_COLOR}{BOLD}网络连接{NONE}")
interfaces: List[str] = []
ip_out = run_cmd("ip -o link show up")
if ip_out:
for line in ip_out.splitlines():
m = re.match(r"\d+:\s+([^:@]+)", line)
if m:
interfaces.append(m.group(1))
else:
try:
interfaces = [i for i in os.listdir("/sys/class/net")]
except Exception:
interfaces = []
has_ip = False
for iface in interfaces:
if iface == "lo":
continue
ip_addrs = run_cmd(f"ip -4 addr show dev {iface} 2>/dev/null | grep -oP 'inet \\K[\\d.]+' | tr '\\n' ' '").strip()
ip6 = run_cmd(f"ip -6 addr show dev {iface} 2>/dev/null | grep -oP 'inet6 \\K[0-9a-fA-F:]+' | grep -ivE '^fe80::' | head -n1 | tr '\\n' ' '").strip()
mac = run_cmd(f"ip link show dev {iface} 2>/dev/null | awk '/ether/ {{print $2}}'").strip()
if ip_addrs or ip6:
has_ip = True
iface_label = ""
if re.match(r'^(wlan|wlp|ath|ra)', iface):
iface_label = f"({LABEL_COLOR}WiFi{NONE}) "
elif re.match(r'^(eth|enp|eno)', iface):
iface_label = f"({LABEL_COLOR}有线{NONE}) "
elif re.match(r'^(docker|br-|veth|tun|tap|virbr)', iface):
iface_label = f"({LABEL_COLOR}虚拟{NONE}) "
else:
br = run_cmd(f"ip link show {iface} | grep -i bridge || true")
if br:
iface_label = f"({LABEL_COLOR}桥接{NONE}) "
print(f" {INFO_COLOR}{BOLD}{iface}{NONE} {iface_label}")
if ip_addrs:
print(f" {LABEL_COLOR}IPv4:{NONE} {VALUE_COLOR}{ip_addrs.strip()}{NONE}")
if ip6:
print(f" {LABEL_COLOR}IPv6:{NONE} {VALUE_COLOR}{ip6.strip()}{NONE}")
if mac:
print(f" {LABEL_COLOR}MAC:{NONE} {VALUE_COLOR}{mac}{NONE}")
if not has_ip:
print(f" {VALUE_COLOR}未检测到活动的网络连接或IP地址{NONE}")
print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}")
# -------------------- 活动与统计 --------------------
def activity_stats() -> None:
"""收集并打印用户活动与系统统计信息(最近登录、包数量等)。"""
print(f"{TITLE_COLOR}{BOLD}活动与统计{NONE}")
last_login_info = run_cmd("last -n 1 -wFai 2>/dev/null | head -n 1")
last_login_display = ""
if not last_login_info or "wtmp begins" in last_login_info or len(last_login_info.split()) < 5:
last_login_display = f"{VALUE_COLOR}无先前登录记录或记录格式异常{NONE}"
else:
parts = last_login_info.split()
user = parts[0] if len(parts) > 0 else "N/A"
from_host = parts[2] if len(parts) > 2 else "N/A"
time_fields = " ".join(parts[3:7]) if len(parts) >= 7 else " ".join(parts[3:])
try:
formatted = datetime.strptime(time_fields + " " + str(datetime.now().year), "%b %d %H:%M %Y").strftime("%Y-%m-%d %H:%M:%S")
except Exception:
formatted = f"{time_fields} (raw)"
last_login_display = f"{LABEL_COLOR}用户:{NONE} {INFO_COLOR}{user}{NONE}, {LABEL_COLOR}来自:{NONE} {INFO_COLOR}{from_host}{NONE}, {LABEL_COLOR}时间:{NONE} {VALUE_COLOR}{formatted}{NONE}"
package_count = "N/A"
package_label = ""
if has_cmd("dpkg-query"):
package_count = run_cmd("dpkg-query -f '${Package}\\n' -W 2>/dev/null | wc -l") or "N/A"
package_label = "(Debian/APT)"
elif has_cmd("rpm"):
package_count = run_cmd("rpm -qa 2>/dev/null | wc -l") or "N/A"
package_label = "(RPM/Yum/Dnf)"
elif has_cmd("pacman"):
package_count = run_cmd("pacman -Qq 2>/dev/null | wc -l") or "N/A"
package_label = "(Pacman)"
else:
package_label = "(未知包管理器)"
current_dt = datetime.now().strftime("%Y年%m月%d%A %H:%M:%S")
print(f" {LABEL_COLOR}上次登录:{NONE} {last_login_display}")
print(f" {LABEL_COLOR}软件包数:{NONE} {VALUE_COLOR}{package_count} {package_label}{NONE}")
print(f" {LABEL_COLOR}当前登录时间:{NONE} {VALUE_COLOR}{current_dt}{NONE}")
print(f"{SUBTLE_COLOR}─────────────────────────────────────────────────────────────{NONE}")
# -------------------- 主程序 --------------------
def main() -> None:
print_header()
system_overview()
hardware_resources()
network_info()
activity_stats()
if __name__ == "__main__":
main()