Sync bash project
This commit is contained in:
@@ -1,267 +1,492 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# 用法示例:curl -fsSL "https://pan.shumengya.top/d/scripts/login_info.sh" | bash
|
||||
#===============================================================================
|
||||
# 登录信息展示脚本 (Login Info)
|
||||
# 用途: 快速查看系统/资源/网络/Docker 等信息,适合作为 SSH 登录后提示。
|
||||
#
|
||||
# 手动运行:
|
||||
# bash login-info/login-info.sh
|
||||
#
|
||||
# 安装为登录自动展示:
|
||||
# sudo bash login-info/install_login-info.sh
|
||||
#
|
||||
# 环境变量:
|
||||
# LOGIN_INFO_MODE=summary|full # 默认 summary
|
||||
# LOGIN_INFO_COLOR=auto|always|never # 默认 auto
|
||||
#===============================================================================
|
||||
|
||||
# --- 精心选择的颜色定义 ---
|
||||
NONE='\033[00m'
|
||||
BOLD='\033[1m'
|
||||
usage() {
|
||||
local prog
|
||||
prog=${0##*/}
|
||||
|
||||
# 主题色 (可根据喜好调整)
|
||||
# 可以选择一个主色调,例如蓝色系或绿色系
|
||||
# 这里我们尝试用青色作为标题,黄色作为关键信息,白色作为次要信息
|
||||
TITLE_COLOR='\033[01;36m' # 亮青色 (Bold Cyan)
|
||||
INFO_COLOR='\033[01;33m' # 亮黄色 (Bold Yellow)
|
||||
VALUE_COLOR='\033[00;37m' # 白色 (White)
|
||||
LABEL_COLOR='\033[00;32m' # 绿色 (Green) - 用于标签
|
||||
SUBTLE_COLOR='\033[01;30m' # 深灰色/亮黑色 (Bold Black / Dark Gray) - 用于分隔符或不重要信息
|
||||
BLUE_COLOR='\033[01;34m' # 亮蓝色 (Bold Blue)
|
||||
ORANGE_COLOR='\033[01;33m' # 亮黄色 (在16色终端中常作为橙色) - 使用 INFO_COLOR 相同的代码
|
||||
cat <<EOF
|
||||
用法:
|
||||
${prog} [--summary|--full] [--no-docker] [--no-color] [--help]
|
||||
|
||||
说明:
|
||||
--summary 快速模式(默认),适合登录时自动展示
|
||||
--full 详细模式(包含 GPU/磁盘明细/软件包统计等)
|
||||
--no-docker 不显示 Docker 信息
|
||||
--no-color 禁用颜色输出
|
||||
EOF
|
||||
}
|
||||
|
||||
has_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
init_colors() {
|
||||
local color_mode=${LOGIN_INFO_COLOR:-auto}
|
||||
|
||||
# --- 设备名称和欢迎 ---
|
||||
HOSTNAME_TEXT=$(hostname) # 如果不再需要显示hostname,可以注释掉或删除这行
|
||||
case "$color_mode" in
|
||||
auto|always|never) ;;
|
||||
*) color_mode=auto ;;
|
||||
esac
|
||||
|
||||
# --- 系统概览 ---
|
||||
OS_INFO=$(lsb_release -ds 2>/dev/null || echo "N/A")
|
||||
KERNEL_VERSION=$(uname -sr 2>/dev/null || echo "N/A")
|
||||
BOOT_TIME=$(uptime -s 2>/dev/null || who -b | awk '{print $3, $4}')
|
||||
UPTIME=$(uptime -p 2>/dev/null | sed 's/up //')
|
||||
LOAD_AVG=$(uptime 2>/dev/null | awk -F'load average: ' '{print $2}')
|
||||
|
||||
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
|
||||
echo -e "${TITLE_COLOR}${BOLD}系统概览${NONE}"
|
||||
echo -e " ${LABEL_COLOR}系统名称:${NONE} ${VALUE_COLOR}$(uname -a)${NONE}"
|
||||
echo -e " ${LABEL_COLOR}操作系统:${NONE} ${VALUE_COLOR}$OS_INFO${NONE}"
|
||||
echo -e " ${LABEL_COLOR}内核版本:${NONE} ${VALUE_COLOR}$KERNEL_VERSION${NONE}"
|
||||
echo -e " ${LABEL_COLOR}启动时间:${NONE} ${VALUE_COLOR}$BOOT_TIME${NONE}"
|
||||
echo -e " ${LABEL_COLOR}运行时间:${NONE} ${VALUE_COLOR}$UPTIME${NONE}"
|
||||
echo -e " ${LABEL_COLOR}平均负载:${NONE} ${VALUE_COLOR}$LOAD_AVG${NONE}"
|
||||
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
|
||||
|
||||
|
||||
|
||||
# --- 硬件资源 ---
|
||||
# 获取 CPU 型号,处理可能的多行输出,只取第一行,并去除首尾空格
|
||||
CPU_MODEL=$(grep "model name" /proc/cpuinfo | head -n 1 | awk -F ':' '{print $2}' | sed 's/^[ \t]*//;s/[ \t]*$//' || echo "N/A")
|
||||
CPU_CORES=$(grep -c "processor" /proc/cpuinfo 2>/dev/null || echo "N/A")
|
||||
# CPU 温度 (尽可能兼容更多机型)
|
||||
CPU_TEMP=""
|
||||
# 定义获取CPU温度的函数
|
||||
get_cpu_temp() {
|
||||
local temp_c=""
|
||||
|
||||
# 1. 尝试使用 sensors 命令 (如果安装了 lm-sensors)
|
||||
if command -v sensors &> /dev/null; then
|
||||
# 尝试匹配常见的CPU温度标签
|
||||
# Package id 0: Intel/AMD 多核封装温度
|
||||
# Tctl/Tdie: AMD Ryzen
|
||||
# Core 0: Intel Core
|
||||
# cpu_thermal: 树莓派/通用
|
||||
# Composite: 部分嵌入式设备
|
||||
temp_c=$(sensors | grep -iE '^(package id 0|tctl|tdie|core 0|cpu_thermal|composite|temp1):' | head -n 1 | awk '{print $2}' | tr -d '+°C')
|
||||
if [ -n "$temp_c" ]; then
|
||||
echo "$temp_c"
|
||||
return
|
||||
local enable_color=0
|
||||
if [ "$color_mode" = "always" ]; then
|
||||
enable_color=1
|
||||
elif [ "$color_mode" = "never" ] || [ -n "${NO_COLOR:-}" ]; then
|
||||
enable_color=0
|
||||
else
|
||||
if [ -t 1 ] && [ "${TERM:-}" != "dumb" ]; then
|
||||
enable_color=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. 尝试读取 /sys/class/thermal/thermal_zone*
|
||||
# 优先查找 x86_pkg_temp (Intel) 或 k10temp (AMD) 或 cpu-thermal (ARM) 或 acpitz
|
||||
for zone in /sys/class/thermal/thermal_zone*; do
|
||||
[ -e "$zone/type" ] || continue
|
||||
type=$(cat "$zone/type")
|
||||
if [[ "$type" == "x86_pkg_temp" || "$type" == "k10temp" || "$type" == "cpu-thermal" || "$type" == "acpitz" ]]; then
|
||||
if [ -r "$zone/temp" ]; then
|
||||
temp_raw=$(cat "$zone/temp")
|
||||
# 转换为摄氏度 (除以1000)
|
||||
if [ "$temp_raw" -gt 0 ] 2>/dev/null; then
|
||||
awk "BEGIN {printf \"%.1f\", $temp_raw/1000}"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
if [ "$enable_color" -eq 1 ]; then
|
||||
C_RESET='\033[0m'
|
||||
C_BOLD='\033[1m'
|
||||
C_TITLE='\033[1;36m'
|
||||
C_INFO='\033[1;33m'
|
||||
C_VALUE='\033[0;37m'
|
||||
C_LABEL='\033[0;32m'
|
||||
C_SUBTLE='\033[1;30m'
|
||||
C_BLUE='\033[1;34m'
|
||||
C_RED='\033[0;31m'
|
||||
else
|
||||
C_RESET=''
|
||||
C_BOLD=''
|
||||
C_TITLE=''
|
||||
C_INFO=''
|
||||
C_VALUE=''
|
||||
C_LABEL=''
|
||||
C_SUBTLE=''
|
||||
C_BLUE=''
|
||||
C_RED=''
|
||||
fi
|
||||
|
||||
HR='───────────────────────────────────────────────────────────────────────────────'
|
||||
}
|
||||
|
||||
print_hr() { printf '%b%s%b\n' "${C_SUBTLE}" "${HR}" "${C_RESET}"; }
|
||||
print_title() { printf '%b%s%b\n' "${C_TITLE}${C_BOLD}" "$1" "${C_RESET}"; }
|
||||
print_kv() { printf ' %b%s:%b %b%s%b\n' "${C_LABEL}" "$1" "${C_RESET}" "${C_VALUE}" "$2" "${C_RESET}"; }
|
||||
print_kv_info() { printf ' %b%s:%b %b%s%b\n' "${C_LABEL}" "$1" "${C_RESET}" "${C_INFO}" "$2" "${C_RESET}"; }
|
||||
|
||||
format_kb() {
|
||||
local kb=${1:-}
|
||||
case "$kb" in
|
||||
''|*[!0-9]*) printf 'N/A'; return 0 ;;
|
||||
esac
|
||||
|
||||
if [ "$kb" -ge 1048576 ]; then
|
||||
awk -v v="$kb" 'BEGIN{printf "%.1fGiB", v/1048576}'
|
||||
elif [ "$kb" -ge 1024 ]; then
|
||||
awk -v v="$kb" 'BEGIN{printf "%.1fMiB", v/1024}'
|
||||
else
|
||||
printf '%sKiB' "$kb"
|
||||
fi
|
||||
}
|
||||
|
||||
get_os_pretty_name() {
|
||||
local os=''
|
||||
|
||||
if has_cmd lsb_release; then
|
||||
os=$(lsb_release -ds 2>/dev/null || true)
|
||||
os=${os#\"}
|
||||
os=${os%\"}
|
||||
fi
|
||||
|
||||
if [ -z "$os" ] && [ -r /etc/os-release ]; then
|
||||
os=$(
|
||||
awk -F= '
|
||||
$1=="PRETTY_NAME"{
|
||||
gsub(/^"/,"",$2); gsub(/"$/,"",$2);
|
||||
print $2; exit
|
||||
}' /etc/os-release 2>/dev/null || true
|
||||
)
|
||||
fi
|
||||
|
||||
printf '%s' "${os:-N/A}"
|
||||
}
|
||||
|
||||
get_boot_time() {
|
||||
local boot=''
|
||||
if has_cmd uptime; then
|
||||
boot=$(uptime -s 2>/dev/null || true)
|
||||
fi
|
||||
if [ -z "$boot" ] && has_cmd who; then
|
||||
boot=$(who -b 2>/dev/null | awk '{print $3" "$4}' || true)
|
||||
fi
|
||||
printf '%s' "${boot:-N/A}"
|
||||
}
|
||||
|
||||
get_uptime_pretty() {
|
||||
local up=''
|
||||
if has_cmd uptime; then
|
||||
up=$(uptime -p 2>/dev/null | sed 's/^up //')
|
||||
fi
|
||||
|
||||
if [ -z "$up" ] && [ -r /proc/uptime ]; then
|
||||
local seconds
|
||||
seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo 0)
|
||||
if [ "$seconds" -gt 0 ] 2>/dev/null; then
|
||||
local days hours minutes
|
||||
days=$((seconds / 86400))
|
||||
hours=$(((seconds % 86400) / 3600))
|
||||
minutes=$(((seconds % 3600) / 60))
|
||||
if [ "$days" -gt 0 ]; then
|
||||
up="${days}天 ${hours}小时 ${minutes}分钟"
|
||||
elif [ "$hours" -gt 0 ]; then
|
||||
up="${hours}小时 ${minutes}分钟"
|
||||
else
|
||||
up="${minutes}分钟"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# 3. 如果还没找到,尝试任何 thermal_zone0 (通常是主要的)
|
||||
if [ -r "/sys/class/thermal/thermal_zone0/temp" ]; then
|
||||
temp_raw=$(cat "/sys/class/thermal/thermal_zone0/temp")
|
||||
if [ "$temp_raw" -gt 0 ] 2>/dev/null; then
|
||||
awk "BEGIN {printf \"%.1f\", $temp_raw/1000}"
|
||||
return
|
||||
fi
|
||||
|
||||
printf '%s' "${up:-N/A}"
|
||||
}
|
||||
|
||||
get_load_avg() {
|
||||
local load=''
|
||||
if [ -r /proc/loadavg ]; then
|
||||
load=$(awk '{print $1" "$2" "$3}' /proc/loadavg 2>/dev/null || true)
|
||||
fi
|
||||
if [ -z "$load" ] && has_cmd uptime; then
|
||||
load=$(uptime 2>/dev/null | awk -F'load average: ' '{print $2}' || true)
|
||||
fi
|
||||
printf '%s' "${load:-N/A}"
|
||||
}
|
||||
|
||||
get_cpu_model() {
|
||||
local model=''
|
||||
if [ -r /proc/cpuinfo ]; then
|
||||
model=$(awk -F': *' '/model name/{print $2; exit}' /proc/cpuinfo 2>/dev/null || true)
|
||||
[ -n "$model" ] || model=$(awk -F': *' '/Hardware/{print $2; exit}' /proc/cpuinfo 2>/dev/null || true)
|
||||
[ -n "$model" ] || model=$(awk -F': *' '/Processor/{print $2; exit}' /proc/cpuinfo 2>/dev/null || true)
|
||||
fi
|
||||
if [ -z "$model" ] && has_cmd lscpu; then
|
||||
model=$(lscpu 2>/dev/null | awk -F': *' '/Model name:/{print $2; exit}' || true)
|
||||
fi
|
||||
printf '%s' "${model:-N/A}"
|
||||
}
|
||||
|
||||
get_cpu_cores() {
|
||||
local cores=''
|
||||
if has_cmd nproc; then
|
||||
cores=$(nproc 2>/dev/null || true)
|
||||
fi
|
||||
if [ -z "$cores" ] && [ -r /proc/cpuinfo ]; then
|
||||
cores=$(awk -F': *' '/^processor/{c++} END{print c+0}' /proc/cpuinfo 2>/dev/null || true)
|
||||
fi
|
||||
printf '%s' "${cores:-N/A}"
|
||||
}
|
||||
|
||||
get_cpu_temp_c() {
|
||||
local temp=''
|
||||
|
||||
if has_cmd sensors; then
|
||||
temp=$(
|
||||
sensors 2>/dev/null | awk '
|
||||
BEGIN{IGNORECASE=1}
|
||||
/(package id 0|tctl|tdie|cpu temperature|core 0|composite|temp1)/{
|
||||
for(i=1;i<=NF;i++){
|
||||
if($i ~ /^[+]?[0-9]+([.][0-9]+)?°C$/){
|
||||
gsub(/[+°C]/,"",$i);
|
||||
print $i; exit
|
||||
}
|
||||
}
|
||||
}' || true
|
||||
)
|
||||
fi
|
||||
|
||||
if [ -z "$temp" ]; then
|
||||
local zone
|
||||
for zone in /sys/class/thermal/thermal_zone*; do
|
||||
[ -r "$zone/temp" ] || continue
|
||||
local temp_raw
|
||||
temp_raw=$(cat "$zone/temp" 2>/dev/null || true)
|
||||
case "$temp_raw" in
|
||||
''|*[!0-9]*) continue ;;
|
||||
esac
|
||||
if [ "$temp_raw" -gt 0 ] 2>/dev/null; then
|
||||
if [ "$temp_raw" -ge 1000 ]; then
|
||||
temp=$(awk -v t="$temp_raw" 'BEGIN{printf "%.1f", t/1000}')
|
||||
else
|
||||
temp=$temp_raw
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
printf '%s' "$temp"
|
||||
}
|
||||
|
||||
section_system() {
|
||||
print_hr
|
||||
print_title "系统概览"
|
||||
print_kv "主机名" "$(hostname 2>/dev/null || echo N/A)"
|
||||
print_kv "操作系统" "$(get_os_pretty_name)"
|
||||
print_kv "内核版本" "$(uname -sr 2>/dev/null || echo N/A)"
|
||||
print_kv "系统架构" "$(uname -m 2>/dev/null || echo N/A)"
|
||||
print_kv "启动时间" "$(get_boot_time)"
|
||||
print_kv "运行时间" "$(get_uptime_pretty)"
|
||||
print_kv "平均负载" "$(get_load_avg)"
|
||||
print_kv "当前时间" "$(date '+%F %T %Z' 2>/dev/null || echo N/A)"
|
||||
print_hr
|
||||
}
|
||||
|
||||
section_resources() {
|
||||
print_title "硬件与资源"
|
||||
|
||||
local cpu_model cpu_cores cpu_temp
|
||||
cpu_model=$(get_cpu_model)
|
||||
cpu_cores=$(get_cpu_cores)
|
||||
cpu_temp=$(get_cpu_temp_c)
|
||||
|
||||
if [ -n "$cpu_temp" ]; then
|
||||
print_kv_info "CPU" "${cpu_model} (${cpu_cores} 核, ${cpu_temp}°C)"
|
||||
else
|
||||
print_kv "CPU" "${cpu_model} (${cpu_cores} 核)"
|
||||
fi
|
||||
|
||||
local mem_total_kb mem_avail_kb mem_used_percent
|
||||
mem_total_kb=$(awk '/MemTotal:/ {print $2; exit}' /proc/meminfo 2>/dev/null || true)
|
||||
mem_avail_kb=$(awk '/MemAvailable:/ {print $2; exit}' /proc/meminfo 2>/dev/null || true)
|
||||
|
||||
mem_used_percent=$(
|
||||
awk -v t="${mem_total_kb:-0}" -v a="${mem_avail_kb:-0}" '
|
||||
BEGIN{
|
||||
if(t>0 && a>0){
|
||||
printf "%.1f%%", (t-a)*100/t
|
||||
} else {
|
||||
print "N/A"
|
||||
}
|
||||
}' 2>/dev/null || true
|
||||
)
|
||||
print_kv "内存" "总计: $(format_kb "$mem_total_kb") | 已用: ${mem_used_percent} | 可用: $(format_kb "$mem_avail_kb")"
|
||||
|
||||
local disk_type disk_size disk_used disk_avail disk_percent
|
||||
disk_type=$(df -hPT / 2>/dev/null | awk 'NR==2 {print $2}' || true)
|
||||
disk_size=$(df -hPT / 2>/dev/null | awk 'NR==2 {print $3}' || true)
|
||||
disk_used=$(df -hPT / 2>/dev/null | awk 'NR==2 {print $4}' || true)
|
||||
disk_avail=$(df -hPT / 2>/dev/null | awk 'NR==2 {print $5}' || true)
|
||||
disk_percent=$(df -hPT / 2>/dev/null | awk 'NR==2 {print $6}' || true)
|
||||
if [ -n "$disk_size" ] && [ -n "$disk_percent" ]; then
|
||||
print_kv "磁盘(/)" "${disk_type:-N/A} 总:${disk_size} 已用:${disk_used} (${disk_percent}) 可用:${disk_avail}"
|
||||
else
|
||||
print_kv "磁盘(/)" "N/A"
|
||||
fi
|
||||
}
|
||||
|
||||
section_storage_full() {
|
||||
print_title "存储明细"
|
||||
if ! has_cmd df; then
|
||||
print_kv "df" "未安装"
|
||||
return 0
|
||||
fi
|
||||
|
||||
df -hPT \
|
||||
-x tmpfs -x devtmpfs -x devpts -x proc -x sysfs -x cgroup -x cgroup2 \
|
||||
-x securityfs -x pstore -x efivarfs -x autofs -x debugfs -x mqueue \
|
||||
-x hugetlbfs -x tracefs -x fusectl -x squashfs 2>/dev/null \
|
||||
| awk 'NR==1{next} {printf "%s|%s|%s|%s|%s|%s\n",$7,$2,$3,$4,$6,$1}' \
|
||||
| while IFS='|' read -r mount fs_type size used percent device; do
|
||||
case "$mount" in
|
||||
/var/lib/docker/*|/run/*|/snap/*) continue ;;
|
||||
esac
|
||||
printf ' %b%s%b (%b%s%b - %s)\n' "${C_INFO}" "$mount" "${C_RESET}" "${C_VALUE}" "$device" "${C_RESET}" "$fs_type"
|
||||
printf ' %b总:%b %b%s%b, %b已用:%b %b%s%b (%b%s%b)\n' \
|
||||
"${C_LABEL}" "${C_RESET}" "${C_BLUE}" "$size" "${C_RESET}" \
|
||||
"${C_LABEL}" "${C_RESET}" "${C_BLUE}" "$used" "${C_RESET}" \
|
||||
"${C_VALUE}" "$percent" "${C_RESET}"
|
||||
done
|
||||
}
|
||||
|
||||
section_network() {
|
||||
print_title "网络连接"
|
||||
|
||||
if has_cmd ip; then
|
||||
local default_route default_iface default_gw
|
||||
default_route=$(ip route show default 2>/dev/null | head -n 1 || true)
|
||||
if [ -n "$default_route" ]; then
|
||||
default_gw=$(printf '%s\n' "$default_route" | awk '{print $3}' || true)
|
||||
default_iface=$(printf '%s\n' "$default_route" | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}' || true)
|
||||
[ -n "$default_iface" ] && print_kv "默认路由" "dev=${default_iface} gw=${default_gw:-N/A}"
|
||||
fi
|
||||
|
||||
local has_ip=0
|
||||
local iface
|
||||
while IFS= read -r iface; do
|
||||
[ -n "$iface" ] || continue
|
||||
case "$iface" in
|
||||
lo|docker*|veth*|br-*|virbr* ) continue ;;
|
||||
esac
|
||||
|
||||
local ipv4 ipv6 mac
|
||||
ipv4=$(ip -o -4 addr show dev "$iface" scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | paste -sd' ' - || true)
|
||||
ipv6=$(ip -o -6 addr show dev "$iface" scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | paste -sd' ' - || true)
|
||||
mac=$(ip link show dev "$iface" 2>/dev/null | awk '/link\/ether/ {print $2; exit}' || true)
|
||||
|
||||
if [ -n "$ipv4" ] || [ -n "$ipv6" ]; then
|
||||
has_ip=1
|
||||
printf ' %b%s%b\n' "${C_INFO}${C_BOLD}" "$iface" "${C_RESET}"
|
||||
[ -n "$ipv4" ] && printf ' %bIPv4:%b %b%s%b\n' "${C_LABEL}" "${C_RESET}" "${C_VALUE}" "$ipv4" "${C_RESET}"
|
||||
[ -n "$ipv6" ] && printf ' %bIPv6:%b %b%s%b\n' "${C_LABEL}" "${C_RESET}" "${C_VALUE}" "$ipv6" "${C_RESET}"
|
||||
[ -n "$mac" ] && printf ' %bMAC:%b %b%s%b\n' "${C_LABEL}" "${C_RESET}" "${C_VALUE}" "$mac" "${C_RESET}"
|
||||
fi
|
||||
done < <(ip -o link show up 2>/dev/null | awk -F': ' '{print $2}' | cut -d'@' -f1)
|
||||
|
||||
if [ "$has_ip" -eq 0 ]; then
|
||||
print_kv "状态" "未检测到活动的网络连接或 IP 地址"
|
||||
fi
|
||||
else
|
||||
local ips
|
||||
ips=$(hostname -I 2>/dev/null | tr -s ' ' ' ' | sed 's/ $//' || true)
|
||||
if [ -n "$ips" ]; then
|
||||
print_kv "IP" "$ips"
|
||||
else
|
||||
print_kv "状态" "未检测到 IP(ip 命令不可用)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
CPU_TEMP_VAL=$(get_cpu_temp)
|
||||
if [ -n "$CPU_TEMP_VAL" ]; then
|
||||
CPU_TEMP="(${INFO_COLOR}${CPU_TEMP_VAL}°C${VALUE_COLOR})"
|
||||
fi
|
||||
section_docker() {
|
||||
local mode=$1
|
||||
|
||||
# 智能内存显示:小于 1GB 显示 MB,否则显示 GB (保留1位小数)
|
||||
MEM_TOTAL_MB=$(free -m | awk 'NR==2{print $2}')
|
||||
if [ "$MEM_TOTAL_MB" -ge 1000 ]; then
|
||||
MEM_TOTAL_DISPLAY=$(awk -v val="$MEM_TOTAL_MB" 'BEGIN {printf "%.1fGB", val/1024}')
|
||||
else
|
||||
MEM_TOTAL_DISPLAY="${MEM_TOTAL_MB}MB"
|
||||
fi
|
||||
if ! has_cmd docker; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
MEM_USED_PERCENT=$(free -m | awk 'NR==2{printf "%.1f%%", $3*100/$2 }')
|
||||
MEM_AVAILABLE_MB=$(free -m | awk 'NR==2{print $7"MB"}') # 'available' 通常比 'free' 更能代表实际可用内存
|
||||
if ! docker ps >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# --- GPU 检测 ---
|
||||
# 优先使用 lspci 检测 VGA, 3D, Display 控制器
|
||||
GPU_INFO=""
|
||||
if command -v lspci &> /dev/null; then
|
||||
# 获取设备列表,去除前面的地址信息,只保留设备名称
|
||||
# 典型 lspci 输出: "00:02.0 VGA compatible controller: Intel Corporation ..."
|
||||
# 我们需要截取 "Intel Corporation ..."
|
||||
# 使用 sed 处理:匹配开头到第二个冒号之前的所有内容并删除
|
||||
GPU_INFO=$(lspci | grep -iE 'vga|3d|display' | sed 's/^.*: .*[:] //')
|
||||
fi
|
||||
local running total
|
||||
running=$(docker ps -q 2>/dev/null | wc -l | awk '{print $1}' || true)
|
||||
total=$(docker ps -aq 2>/dev/null | wc -l | awk '{print $1}' || true)
|
||||
|
||||
echo -e "${TITLE_COLOR}${BOLD}硬件资源${NONE}"
|
||||
echo -e " ${LABEL_COLOR}中央处理器 (CPU):${NONE}"
|
||||
echo -e " ${VALUE_COLOR}$CPU_MODEL - ${CPU_CORES} 核心 ${CPU_TEMP}${NONE}"
|
||||
if [ -n "$GPU_INFO" ]; then
|
||||
echo -e " ${LABEL_COLOR}图形处理器 (GPU):${NONE}"
|
||||
# 处理多显卡情况,逐行显示
|
||||
echo "$GPU_INFO" | while read -r line; do
|
||||
echo -e " ${VALUE_COLOR}$line${NONE}"
|
||||
done
|
||||
fi
|
||||
echo -e " ${LABEL_COLOR}内存 (RAM):${NONE}"
|
||||
echo -e " ${VALUE_COLOR}总计: ${INFO_COLOR}$MEM_TOTAL_DISPLAY${VALUE_COLOR} | 已用: ${INFO_COLOR}$MEM_USED_PERCENT${VALUE_COLOR} | 可用: ${INFO_COLOR}$MEM_AVAILABLE_MB${NONE}"
|
||||
echo -e " ${LABEL_COLOR}存储空间:${NONE}"
|
||||
print_title "Docker 容器"
|
||||
print_kv "容器数量" "运行中: ${running:-0} / 总计: ${total:-0}"
|
||||
|
||||
# --- 修改的存储部分 (删除进度条, 修改颜色) ---
|
||||
# 使用 df -hT 获取信息,-x 排除不必要的类型 (保留 overlay)
|
||||
# 使用 awk 跳过头部 (NR>1),并按列提取 (Filesystem, Type, Size, Used, Use%, Mounted on)
|
||||
# 注意:已添加 -x overlay 以隐藏 Docker 挂载卷
|
||||
df -hT -x tmpfs -x devtmpfs -x devpts -x proc -x sysfs -x cgroup -x fusectl -x securityfs -x pstore -x efivarfs -x autofs -x overlay 2>/dev/null | awk 'NR>1 {print $1, $2, $3, $4, $6, $7}' | while read -r DEVICE TYPE SIZE USED PERCENT MOUNTPOINT; do
|
||||
if [ "$mode" = "full" ] && [ "${running:-0}" -gt 0 ] 2>/dev/null; then
|
||||
local names
|
||||
names=$(docker ps --format '{{.Names}}' 2>/dev/null | paste -sd', ' - || true)
|
||||
[ -n "$names" ] && print_kv "运行列表" "$names"
|
||||
fi
|
||||
}
|
||||
|
||||
# --- 已删除进度条的计算和绘制 ---
|
||||
section_activity() {
|
||||
local mode=$1
|
||||
local user from_ip tty last_login
|
||||
|
||||
echo -e " ${INFO_COLOR}${MOUNTPOINT}${NONE} (${VALUE_COLOR}${DEVICE} - ${TYPE}${NONE})"
|
||||
# --- 核心改动:将 总 和 已用 后面的数值改为蓝色 ---
|
||||
# LABEL_COLOR 用于标签文字 "总:" 和 "已用:"
|
||||
# BLUE_COLOR 用于数值 SIZE 和 USED
|
||||
# VALUE_COLOR 用于百分比 PERCENT 以及括号和逗号
|
||||
echo -e " ${LABEL_COLOR}总:${NONE} ${BLUE_COLOR}${SIZE}${NONE}, ${LABEL_COLOR}已用:${NONE} ${BLUE_COLOR}${USED}${NONE} (${VALUE_COLOR}${PERCENT}${NONE})"
|
||||
done
|
||||
user=${USER:-}
|
||||
[ -n "$user" ] || user=$(id -un 2>/dev/null || echo N/A)
|
||||
|
||||
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
|
||||
from_ip=''
|
||||
if [ -n "${SSH_CONNECTION:-}" ]; then
|
||||
from_ip=$(printf '%s\n' "$SSH_CONNECTION" | awk '{print $1}' || true)
|
||||
fi
|
||||
if [ -z "$from_ip" ] && has_cmd who; then
|
||||
from_ip=$(who -m 2>/dev/null | awk -F'[()]' '{print $2}' | awk '{print $1}' || true)
|
||||
fi
|
||||
|
||||
tty=$(tty 2>/dev/null || true)
|
||||
|
||||
print_title "登录与统计"
|
||||
print_kv "当前用户" "$user"
|
||||
[ -n "$from_ip" ] && print_kv "来源地址" "$from_ip"
|
||||
[ -n "$tty" ] && print_kv "终端" "$tty"
|
||||
|
||||
# --- 网络连接 ---
|
||||
echo -e "${TITLE_COLOR}${BOLD}网络连接${NONE}"
|
||||
INTERFACES_UP=$(ip -o link show up | awk -F': ' '{print $2}' | cut -d '@' -f 1) # 使用 cut 移除 @ifname 后缀
|
||||
HAS_IP=false
|
||||
if [ -n "$INTERFACES_UP" ]; then
|
||||
for IFACE in $INTERFACES_UP; do
|
||||
if [[ "$IFACE" == "lo" ]]; then continue; fi # 跳过 lo 接口
|
||||
# 跳过 Docker 相关接口
|
||||
if [[ "$IFACE" == docker* || "$IFACE" == veth* || "$IFACE" == br-* ]]; then continue; fi
|
||||
last_login=$(
|
||||
last -n 5 -w "$user" 2>/dev/null \
|
||||
| awk -v u="$user" '$1==u && $0 !~ /wtmp begins/ && $0 !~ /still logged in/ {print; exit}' \
|
||||
|| true
|
||||
)
|
||||
[ -n "$last_login" ] && print_kv "上次登录" "$last_login"
|
||||
|
||||
IP_ADDRS=$(ip -4 addr show dev "$IFACE" 2>/dev/null | grep -oP 'inet \K[\d.]+' | tr '\n' ' ')
|
||||
IP_ADDRS_V6=$(ip -6 addr show dev "$IFACE" 2>/dev/null | grep -oP 'inet6 \K[0-9a-fA-F:]+' | grep -ivE '^fe80::' | head -n1 | tr '\n' ' ') # 只显示一个全局 IPv6
|
||||
MAC_ADDR=$(ip link show dev "$IFACE" 2>/dev/null | awk '/ether/ {print $2}')
|
||||
|
||||
if [ -n "$IP_ADDRS" ] || [ -n "$IP_ADDRS_V6" ]; then
|
||||
HAS_IP=true
|
||||
IFACE_TYPE_LABEL=""
|
||||
if [[ "$IFACE" == wlan* || "$IFACE" == wlp* || "$IFACE" == ath* || "$IFACE" == ra* ]]; then
|
||||
IFACE_TYPE_LABEL="(${LABEL_COLOR}WiFi${NONE}) "
|
||||
elif [[ "$IFACE" == eth* || "$IFACE" == enp* || "$IFACE" == eno* ]]; then
|
||||
IFACE_TYPE_LABEL="(${LABEL_COLOR}有线${NONE}) "
|
||||
# 添加对虚拟接口类型的更全面判断
|
||||
elif [[ "$IFACE" == docker* || "$IFACE" == br-* || "$IFACE" == veth* || "$IFACE" == tun* || "$IFACE" == tap* || "$IFACE" == virbr* ]]; then
|
||||
IFACE_TYPE_LABEL="(${LABEL_COLOR}虚拟${NONE}) "
|
||||
# 考虑桥接接口
|
||||
elif ip link show "$IFACE" | grep -q "bridge"; then
|
||||
IFACE_TYPE_LABEL="(${LABEL_COLOR}桥接${NONE}) "
|
||||
fi
|
||||
|
||||
|
||||
echo -e " ${INFO_COLOR}${BOLD}${IFACE}${NONE} ${IFACE_TYPE_LABEL}"
|
||||
[ -n "$IP_ADDRS" ] && echo -e " ${LABEL_COLOR}IPv4:${NONE} ${VALUE_COLOR}${IP_ADDRS% }${NONE}" # % 移除末尾空格
|
||||
[ -n "$IP_ADDRS_V6" ] && echo -e " ${LABEL_COLOR}IPv6:${NONE} ${VALUE_COLOR}${IP_ADDRS_V6% }${NONE}"
|
||||
[ -n "$MAC_ADDR" ] && echo -e " ${LABEL_COLOR}MAC:${NONE} ${VALUE_COLOR}${MAC_ADDR}${NONE}"
|
||||
if [ "$mode" = "full" ]; then
|
||||
local package_count='N/A'
|
||||
if has_cmd dpkg-query; then
|
||||
package_count=$(dpkg-query -f '${Package}\n' -W 2>/dev/null | wc -l | awk '{print $1}' || true)
|
||||
print_kv "软件包数" "${package_count:-N/A} (Debian/APT)"
|
||||
elif has_cmd rpm; then
|
||||
package_count=$(rpm -qa 2>/dev/null | wc -l | awk '{print $1}' || true)
|
||||
print_kv "软件包数" "${package_count:-N/A} (RPM/Yum/Dnf)"
|
||||
elif has_cmd pacman; then
|
||||
package_count=$(pacman -Qq 2>/dev/null | wc -l | awk '{print $1}' || true)
|
||||
print_kv "软件包数" "${package_count:-N/A} (Pacman)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
section_gpu() {
|
||||
if ! has_cmd lspci; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local gpus
|
||||
gpus=$(lspci 2>/dev/null | awk -F': ' '/VGA compatible controller|3D controller|Display controller/{print $2}' || true)
|
||||
[ -n "$gpus" ] || return 0
|
||||
|
||||
print_title "图形处理器 (GPU)"
|
||||
while IFS= read -r line; do
|
||||
[ -n "$line" ] || continue
|
||||
printf ' %b%s%b\n' "${C_VALUE}" "$line" "${C_RESET}"
|
||||
done <<<"$gpus"
|
||||
}
|
||||
|
||||
main() {
|
||||
local mode=${LOGIN_INFO_MODE:-summary}
|
||||
local show_docker=1
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--summary|--fast) mode=summary ;;
|
||||
--full) mode=full ;;
|
||||
--no-docker) show_docker=0 ;;
|
||||
--no-color) LOGIN_INFO_COLOR=never ;;
|
||||
-h|--help) usage; return 0 ;;
|
||||
*) printf '错误: 未知参数: %s\n' "$1" >&2; usage; return 2 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
fi
|
||||
if ! $HAS_IP; then
|
||||
echo -e " ${VALUE_COLOR}未检测到活动的网络连接或IP地址${NONE}"
|
||||
fi
|
||||
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
|
||||
|
||||
init_colors
|
||||
|
||||
section_system
|
||||
section_resources
|
||||
|
||||
# --- Docker 容器 ---
|
||||
# 检查 docker 命令是否存在且守护进程正在运行
|
||||
if command -v docker &> /dev/null && docker ps &> /dev/null; then
|
||||
DOCKER_RUNNING_COUNT=$(docker ps -q | wc -l)
|
||||
DOCKER_TOTAL_COUNT=$(docker ps -aq | wc -l)
|
||||
|
||||
echo -e "${TITLE_COLOR}${BOLD}Docker 容器${NONE}"
|
||||
echo -e " ${LABEL_COLOR}容器数量:${NONE} ${VALUE_COLOR}运行中: ${INFO_COLOR}${DOCKER_RUNNING_COUNT}${VALUE_COLOR} / 总计: ${INFO_COLOR}${DOCKER_TOTAL_COUNT}${NONE}"
|
||||
|
||||
if [ "$DOCKER_RUNNING_COUNT" -gt 0 ]; then
|
||||
# 获取名称列表,用逗号分隔
|
||||
DOCKER_NAMES=$(docker ps --format "{{.Names}}" | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /g')
|
||||
echo -e " ${LABEL_COLOR}运行列表:${NONE} ${VALUE_COLOR}${DOCKER_NAMES}${NONE}"
|
||||
fi
|
||||
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
# --- 活动与统计 ---
|
||||
LAST_LOGIN_INFO=$(last -n 1 -wFai 2>/dev/null | head -n 1) # -a 把hostname放最后, -i 显示IP
|
||||
LAST_LOGIN_DISPLAY=""
|
||||
if [[ "$LAST_LOGIN_INFO" == *"wtmp begins"* || -z "$LAST_LOGIN_INFO" || $(echo "$LAST_LOGIN_INFO" | wc -w) -lt 5 ]]; then # 增加字段数检查
|
||||
LAST_LOGIN_DISPLAY="${VALUE_COLOR}无先前登录记录或记录格式异常${NONE}"
|
||||
else
|
||||
# --- 修改的上次登录部分 ---
|
||||
USER=$(echo "$LAST_LOGIN_INFO" | awk '{print $1}')
|
||||
# 对于 -wFai, IP/hostname 通常是第三列
|
||||
FROM_IP_OR_HOST=$(echo "$LAST_LOGIN_INFO" | awk '{print $3}')
|
||||
|
||||
# 精确提取日期和时间部分,跳过 'still' 和年份 (字段 4到7)
|
||||
LOGIN_TIME_FIELDS=$(echo "$LAST_LOGIN_INFO" | awk '{print $4, $5, $6, $7}')
|
||||
|
||||
# 尝试用date格式化,如果失败则显示原始字段
|
||||
LOGIN_TIME_FORMATTED=$(date -d "$LOGIN_TIME_FIELDS $(date +%Y)" +"%Y-%m-%d %H:%M:%S" 2>/dev/null) # 显式添加年份尝试格式化
|
||||
if [ -z "$LOGIN_TIME_FORMATTED" ]; then
|
||||
LOGIN_TIME_FORMATTED="${VALUE_COLOR}${LOGIN_TIME_FIELDS}${NONE} (raw)" # 格式化失败,显示原始字段并标记
|
||||
if [ "$mode" = "full" ]; then
|
||||
section_gpu
|
||||
section_storage_full
|
||||
fi
|
||||
|
||||
LAST_LOGIN_DISPLAY="${LABEL_COLOR}用户:${NONE} ${INFO_COLOR}$USER${NONE}, ${LABEL_COLOR}来自:${NONE} ${INFO_COLOR}$FROM_IP_OR_HOST${NONE}, ${LABEL_COLOR}时间:${NONE} ${VALUE_COLOR}$LOGIN_TIME_FORMATTED${NONE}"
|
||||
fi
|
||||
section_network
|
||||
|
||||
if [ "$show_docker" -eq 1 ]; then
|
||||
section_docker "$mode"
|
||||
fi
|
||||
|
||||
PACKAGE_COUNT=$(dpkg-query -f '${Package}\n' -W 2>/dev/null | wc -l || echo "N/A") # Debian/APT
|
||||
section_activity "$mode"
|
||||
print_hr
|
||||
|
||||
echo -e "${TITLE_COLOR}${BOLD}活动与统计${NONE}"
|
||||
echo -e " ${LABEL_COLOR}上次登录:${NONE} $LAST_LOGIN_DISPLAY"
|
||||
if [ "$mode" = "summary" ]; then
|
||||
printf '%b提示:%b 运行 %blogin-info --full%b 查看更完整信息\n' \
|
||||
"${C_SUBTLE}" "${C_RESET}" "${C_INFO}" "${C_RESET}"
|
||||
fi
|
||||
}
|
||||
|
||||
# 检查是否是 Debian/Ubuntu 系列系统,然后显示包数
|
||||
if command -v dpkg-query &> /dev/null; then
|
||||
PACKAGE_COUNT=$(dpkg-query -f '${Package}\n' -W 2>/dev/null | wc -l || echo "N/A")
|
||||
echo -e " ${LABEL_COLOR}软件包数:${NONE} ${VALUE_COLOR}$PACKAGE_COUNT (Debian/APT)${NONE}"
|
||||
elif command -v rpm &> /dev/null; then
|
||||
PACKAGE_COUNT=$(rpm -qa 2>/dev/null | wc -l || echo "N/A")
|
||||
echo -e " ${LABEL_COLOR}软件包数:${NONE} ${VALUE_COLOR}$PACKAGE_COUNT (RPM/Yum/Dnf)${NONE}"
|
||||
elif command -v pacman &> /dev/null; then
|
||||
PACKAGE_COUNT=$(pacman -Qq 2>/dev/null | wc -l || echo "N/A")
|
||||
echo -e " ${LABEL_COLOR}软件包数:${NONE} ${VALUE_COLOR}$PACKAGE_COUNT (Pacman)${NONE}"
|
||||
else
|
||||
echo -e " ${LABEL_COLOR}软件包数:${NONE} ${VALUE_COLOR}N/A (未知包管理器)${NONE}"
|
||||
fi
|
||||
|
||||
CURRENT_DATETIME=$(date +"%Y年%m月%d日 %A %H:%M:%S %Z")
|
||||
echo -e " ${LABEL_COLOR}当前登录时间:${NONE} ${VALUE_COLOR}$CURRENT_DATETIME$"
|
||||
|
||||
echo -e "${SUBTLE_COLOR}───────────────────────────────────────────────────────────────────────────────${NONE}"
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user