# shellcheck shell=bash ################################################################################ # 硬链接处理 ################################################################################ # 判断源与目标是否为同一 inode(硬链接已存在)。 # 获取文件 inode(兼容 GNU/BSD/BusyBox stat)。 # 依次尝试:GNU stat -c '%i' -> BSD stat -f '%i' -> ls -i -> find -printf。 get_file_inode() { local filepath="$1" local inode inode=$(stat -c '%i' "$filepath" 2>/dev/null) [[ -n "$inode" ]] && { echo "$inode" return 0 } inode=$(stat -f '%i' "$filepath" 2>/dev/null) [[ -n "$inode" ]] && { echo "$inode" return 0 } # ls -i 解析 inode 字段(BusyBox 可靠,stat/find -printf 可能不可用) # shellcheck disable=SC2012 inode=$(ls -i "$filepath" 2>/dev/null | awk '{print $1}') [[ -n "$inode" ]] && { echo "$inode" return 0 } # GNU find 兜底:-printf 解析 inode find "$filepath" -maxdepth 0 -printf '%i' 2>/dev/null } same_inode() { local src="$1" dst="$2" [[ ! -e "$dst" ]] && return 1 local src_inode dst_inode src_inode=$(get_file_inode "$src") dst_inode=$(get_file_inode "$dst") [[ -z "$src_inode" || -z "$dst_inode" ]] && return 1 [[ "$src_inode" == "$dst_inode" ]] } # 创建硬链接;干运行模式下仅打印;目标已存在时直接替换(不备份)。 # 注:不做 .bak 备份——遗留的 .bak_* 文件会被 Jellyfin 当作媒体扫描,干扰刮削。 hardlink_or_dryrun() { local src="$1" dst="$2" if $DRY_RUN; then _log 信息 "干运行:硬链接 '$src' -> '$dst'" return 0 fi if same_inode "$src" "$dst"; then _log 跳过 "硬链接已存在,inode: $(stat -c '%i' "$src" 2>/dev/null)" return 0 fi # 目标已存在:直接替换(先删旧目标再建硬链接) if [[ -e "$dst" ]]; then # 安全保护:目标是目录(如误传根目录)时拒绝替换,避免误操作真实目录 if [[ -d "$dst" ]]; then _log 错误 "目标是目录,拒绝替换: $dst" return 1 fi _log 信息 "目标已存在,直接替换: $dst" rm -f "$dst" fi if ln "$src" "$dst"; then _log 成功 "硬链接已创建" return 0 else _log 错误 "硬链接创建失败" return 1 fi } ################################################################################ # 错误处理 ################################################################################ # 全局 ERR trap:记录出错行号后退出。 _handle_error() { local line_num="$1" _log 错误 "脚本在第 $line_num 行出错(错误码:$?)" exit 1 }