通过引入 terminal_width 函数,将原本固定的终端宽度检测逻辑重构为动态获取。当未指定固定宽度时,脚本将实时读取当前终端窗口尺寸,提升了在不同交互环境下(如窗口缩放)的显示适配能力。 同时更新了配套文档,明确了命令行参数、环境变量与动态检测之间的优先级关系。
234 lines
6.7 KiB
Bash
234 lines
6.7 KiB
Bash
#!/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"
|