Files
script/homebrew/brew-upgrade-manager.sh
Orion 6a0fe9dded refactor(core): ♻️ 优化终端宽度检测逻辑为动态获取
通过引入 terminal_width 函数,将原本固定的终端宽度检测逻辑重构为动态获取。当未指定固定宽度时,脚本将实时读取当前终端窗口尺寸,提升了在不同交互环境下(如窗口缩放)的显示适配能力。

同时更新了配套文档,明确了命令行参数、环境变量与动态检测之间的优先级关系。
2026-05-04 02:02:23 +08:00

234 lines
6.7 KiB
Bash
Raw 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 bash
# Homebrew 智能升级脚本(强制 Cask 更新增强版 v5.3 - 移除 sudo 密码注入)
# ================== 脚本环境设置 ==================
# set -e当命令返回非零退出状态表示失败脚本会立即退出。
set -e
# set -o pipefail在管道命令中如果任何一个子命令失败整个管道即为失败。
set -o pipefail
# --- 颜色定义 (自动检测终端是否支持) ---
if [ -t 1 ]; then
GREEN='\033[1;32m'
YELLOW='\033[1;33m'
BLUE='\033[1;34m'
CYAN='\033[1;36m'
NC='\033[0m'
else
GREEN=''
YELLOW=''
BLUE=''
CYAN=''
NC=''
fi
# --- 终端宽度和打印函数 ---
DEFAULT_FALLBACK_WIDTH="130"
TERMINAL_WIDTH_OVERRIDE=""
# 解析命令行参数
while [[ $# -gt 0 ]]; do
case "$1" in
--width)
if [[ -n "$2" && "$2" =~ ^[0-9]+$ ]]; then
TERMINAL_WIDTH_OVERRIDE="$2"
shift 2
else
echo -e "${YELLOW}Error: '--width' parameter requires a valid numeric value.${NC}"
exit 1
fi
;;
--width=*)
TERMINAL_WIDTH_OVERRIDE="${1#*=}"
if ! [[ "$TERMINAL_WIDTH_OVERRIDE" =~ ^[0-9]+$ ]]; then
echo -e "${YELLOW}Error: '--width' parameter requires a valid numeric value.${NC}"
exit 1
fi
shift
;;
*)
shift
;;
esac
done
FIXED_TERMINAL_WIDTH=""
if [[ -n "$TERMINAL_WIDTH_OVERRIDE" ]]; then
FIXED_TERMINAL_WIDTH="$TERMINAL_WIDTH_OVERRIDE"
elif [[ -n "$HB_TERMINAL_WIDTH" && "$HB_TERMINAL_WIDTH" =~ ^[0-9]+$ ]]; then
FIXED_TERMINAL_WIDTH="$HB_TERMINAL_WIDTH"
fi
terminal_width() {
local width=""
if [[ -n "$FIXED_TERMINAL_WIDTH" ]]; then
printf "%s" "$FIXED_TERMINAL_WIDTH"
return
fi
if command -v stty &>/dev/null && stty size &>/dev/null; then
width=$(stty size 2>/dev/null | awk '{print $2}')
fi
if [[ -z "$width" || "$width" -le 0 ]]; then
width=$(tput cols 2>/dev/null || echo "$DEFAULT_FALLBACK_WIDTH")
fi
if [[ -z "$width" || "$width" -le 0 ]]; then
width="$DEFAULT_FALLBACK_WIDTH"
fi
printf "%s" "$width"
}
separator() { local width; width=$(terminal_width); printf '=%.0s' $(seq 1 "$width"); printf "\n"; }
print_header() { echo -e "${BLUE}$1${NC}"; }
# ================== 流程开始 ==================
separator
print_header "Step 1: Updating Homebrew repositories (brew update -v)"
brew update -v
separator
printf "\n"
separator
print_header "Step 2: Performing health check (brew doctor)"
if ! brew doctor; then
echo -e "${YELLOW}Warning: 'brew doctor' detected issues. Manual review and resolution are recommended.${NC}"
else
echo "Homebrew environment is in good health."
fi
separator
printf "\n"
separator
print_header "Step 3: Verifying brew-cu extension for GUI Apps"
if ! brew tap | grep -q "buo/cask-upgrade"; then
echo -e "${YELLOW}Extension 'brew-cu' not found. Installing now...${NC}"
brew tap buo/cask-upgrade
else
echo -e "${GREEN}Extension 'brew-cu' is already active.${NC}"
fi
separator
printf "\n"
separator
print_header "Step 4: Executing comprehensive upgrades (Formulae & Casks)"
echo -e "${NC}"
# 1. 升级命令行工具 (Formulae)
echo -e "\n${CYAN}>>> [1/2] Upgrading CLI Formulae (brew upgrade --formula)...${NC}"
brew upgrade --formula
# 2. 升级图形界面软件 (Casks)
echo -e "\n${CYAN}>>> [2/2] Upgrading GUI Casks (brew cu -yaq)...${NC}"
# 强制注入环境变量,确保终端输出依然保留 ANSI 颜色格式
export HOMEBREW_COLOR=1
# --- 终端宽度处理 ---
# 交互式终端里让子进程读取 PTY 的实时尺寸;非交互环境或固定宽度模式下提供 COLUMNS 兜底。
if [[ -n "$FIXED_TERMINAL_WIDTH" || ! -t 1 ]]; then
export COLUMNS
COLUMNS="$(terminal_width)"
else
unset COLUMNS
fi
# 使用 Python 原生 PTY 自建极简转发引擎。它只转发用户输入,不保存、不注入 sudo 密码。
python3 -c '
import pty, os, sys, select, fcntl, termios, struct, tty, signal
cmd = sys.argv[1:]
pid, fd = pty.fork()
if pid == 0:
os.execvp(cmd[0], cmd)
else:
def sync_winsize(*_):
s = struct.pack("HHHH", 0, 0, 0, 0)
try:
winsize = fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ, s)
rows, cols, xpix, ypix = struct.unpack("HHHH", winsize)
if rows > 0 and cols > 0:
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
return
except Exception:
pass
try:
fallback_cols = int(os.environ.get("COLUMNS", "130") or "130")
except ValueError:
fallback_cols = 130
try:
fallback_rows = int(os.environ.get("LINES", "40") or "40")
except ValueError:
fallback_rows = 40
fallback = struct.pack("HHHH", fallback_rows, fallback_cols, 0, 0)
fcntl.ioctl(fd, termios.TIOCSWINSZ, fallback)
sync_winsize()
signal.signal(signal.SIGWINCH, sync_winsize)
stdin_fd = sys.stdin.fileno()
stdout_fd = sys.stdout.fileno()
forward_stdin = sys.stdin.isatty()
old_stdin_attrs = None
if forward_stdin:
old_stdin_attrs = termios.tcgetattr(stdin_fd)
tty.setraw(stdin_fd)
exit_code = 0
try:
while True:
try:
watch = [fd]
if forward_stdin:
watch.append(stdin_fd)
try:
rfds, _, _ = select.select(watch, [], [], 0.1)
except InterruptedError:
continue
if fd in rfds:
data = os.read(fd, 8192)
if not data:
break
os.write(stdout_fd, data)
if forward_stdin and stdin_fd in rfds:
data = os.read(stdin_fd, 8192)
if data:
os.write(fd, data)
except OSError:
break
try:
wpid, wstatus = os.waitpid(pid, os.WNOHANG)
if wpid == pid:
if os.WIFEXITED(wstatus):
exit_code = os.WEXITSTATUS(wstatus)
else:
exit_code = 1
break
except ChildProcessError:
break
finally:
if old_stdin_attrs is not None:
termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_stdin_attrs)
sys.exit(exit_code)
' brew cu -yaq
separator
printf "\n"
separator
print_header "Step 5: Cleaning up old files and caches (brew cleanup --prune=all)"
brew cleanup --prune=all
separator
printf "\n"
echo -e "${GREEN}All operations completed!${NC}"
printf "\n"