[i=s] 本帖最后由 👍👍👍_fxY9f 于 2024-12-19 15:49 编辑 [/i]<br />
<br />
折腾了一天,用AI写了个脚本。用rclone挂载远程网盘,将网盘内容同步到本地。
大概流程
1 FnOS里强制重装rclone (好像FnOS自带的rclone功能不全)
2 配置rclone 挂载远程网盘(只要是rclone支持的网盘都可以挂载)
3 FnOS安装 screen ,在同步远程网盘的时候不需要一直开着shell窗口
4 通过脚本将远程网盘里的部分或者全部内容同步到本地文件夹(rclone同步的时候一不小心就会将本地路径下的文件删掉,只保留远程网盘同步的内容。输入了错误的本地路径可能会导致系统崩溃!在脚本里面尽量避免出现这种麻烦,但是操作依然有风险)
下面是脚本内容
#!/bin/bash
# 配置日志路径和大小限制
MAX_LOG_SIZE=5242880 # 5MB
LOG_FILE="/root/.config/rclone/rclone.log"
CHECK_LOG="/root/.config/rclone/check.log"
# 轮换日志文件
rotate_log() {
local log_file="$1"
if [ -f "$log_file" ] && [ $(stat --format="%s" "$log_file") -ge $MAX_LOG_SIZE ]; then
mv -f "$log_file" "$log_file.bak"
echo "日志文件 $log_file 大小超过 5MB,已将其备份为 $log_file.bak"
> "$log_file"
fi
}
# 执行 rclone同步函数
run_rclone_sync() {
local remote_path="$1"
local local_path="$2"
for attempt in {1..3}; do
rclone sync -q "$remote_path" "$local_path" \
#rclone copy -q "$remote_path" "$local_path" \
--update \
--transfers=32 \
--checkers=32 \
--multi-thread-streams=16 \
--buffer-size=128M \
--fast-list \
--contimeout=60s \
--timeout=300s \
--retries=5 \
--low-level-retries=10 \
--progress \
--stats=30s \
--create-empty-src-dirs \
--log-file="$LOG_FILE"
if [ $? -eq 0 ]; then
echo "同步已成功完成。"
break
else
echo "第 $attempt 次同步失败,重试中..."
fi
done
}
# 检查是否为文件夹
#is_remote_folder() {
# local full_remote_path="$1"
#
# # 使用 rclone lsf 列出路径下的内容
# output=$(rclone lsf "$full_remote_path" 2>/dev/null)
#
# # 判断输出内容的行数是否为1
# if [ "$(echo "$output" | wc -l)" -eq 1 ]; then
# # 判断输出的内容是否包含在原路径中
# if [[ "$output" == *"$full_remote_path"* ]]; then
# # 拼接成新的路径并检查该路径是否存在
# new_path="$full_remote_path/$output"
#
# # 检查该路径是否存在
# if rclone lsf "$new_path" >/dev/null 2>&1; then
# echo "yes" # 该路径是文件夹
# else
# echo "no" # 该路径是文件
# fi
# else
# echo "yes" # 输出内容与路径无关,认为是文件夹
# fi
# else
# echo "yes" # 如果输出不是1行内容,默认认为是文件夹
# fi
#}
is_remote_folder() {
local full_remote_path="$1"
# 使用 rclone lsf 列出路径下的内容
output=$(rclone lsf "$full_remote_path" 2>/dev/null)
# 判断路径是否为有效路径(rclone lsf 成功返回)
if [ $? -eq 0 ]; then
# 如果 lsf 命令成功执行,说明路径是一个文件夹
# 判断是否为空文件夹(没有内容)
if [ -z "$output" ]; then
echo "yes" # 空文件夹,输出 yes
else
echo "yes" # 非空文件夹,输出 yes
fi
else
# 如果 lsf 命令失败,说明路径不是文件夹
echo "no" # 不是文件夹,输出 no
fi
}
# 执行 rclone check 并仅记录本次同步失败的文件列表
run_rclone_check() {
local remote_path="$1"
local local_path="$2"
local temp_error_list=$(mktemp)
rotate_log "$CHECK_LOG"
for attempt in {1..3}; do
echo "第 $attempt 次校验中..."
rclone check "$remote_path" "$local_path" --size-only --quiet --log-file="$CHECK_LOG"
check_status=$?
if [ $check_status -eq 0 ]; then
echo "同步校验成功,无文件丢失。"
break
else
echo "第 $attempt 次校验失败,重试中..."
run_rclone_sync "$remote_path" "$local_path"
fi
done
grep -iE 'ERROR|FAILED|MISSING' "$CHECK_LOG" | awk '{print $NF}' > "$temp_error_list"
if [ -s "$temp_error_list" ]; then
echo "本次同步失败的文件列表:" >> "$LOG_FILE"
cat "$temp_error_list" >> "$LOG_FILE"
fi
rm -f "$temp_error_list"
}
# 读取远程路径
#read_remote_path() {
# while true; do
# read -e -p "请输入远程路径: " REMOTE_PATH
# if [ -n "$REMOTE_PATH" ]; then
# rclone lsf "$SELECTED_REMOTE:$REMOTE_PATH" > /dev/null 2>&1
# if [ $? -ne 0 ]; then
# echo "错误: 远程路径 '$REMOTE_PATH' 不存在。"
# else
# break
# fi
# fi
# done
#}
# 读取远程路径 增加判断远程路径为空文件夹,避免同步清空本地文件夹
read_remote_path() {
while true; do
read -e -p "请输入远程路径: " REMOTE_PATH
if [ -n "$REMOTE_PATH" ]; then
# 检查路径是否存在
rclone lsf "$SELECTED_REMOTE:$REMOTE_PATH" > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "错误: 远程路径 '$REMOTE_PATH' 不存在。"
else
# 如果路径存在,检查是否是一个空文件夹
result=$(rclone lsf "$SELECTED_REMOTE:$REMOTE_PATH")
if [ -z "$result" ]; then
echo "路径 '$REMOTE_PATH' 是一个空文件夹,会清空本地同名文件夹,请重新输入。"
else
break
fi
fi
fi
done
}
# 读取本地路径
read_local_path() {
while true; do
read -e -p "请输入本地路径: " LOCAL_PATH
# 检查输入的路径是否存在并且是一个文件
if [ -e "$LOCAL_PATH" ]; then
if [ -f "$LOCAL_PATH" ]; then
echo "错误: '$LOCAL_PATH' 是一个文件,请输入一个目录路径。"
elif [ -d "$LOCAL_PATH" ]; then
#echo "路径 '$LOCAL_PATH' 是有效的目录。"
break
fi
else
# 如果路径不存在,尝试创建该目录
echo "路径 '$LOCAL_PATH' 不存在,正在创建目录..."
mkdir -p "$LOCAL_PATH"
break
fi
done
}
# 读取 rclone 远程存储
read_remote_storage() {
REMOTE_NAMES=($(rclone listremotes | sed 's/://'))
while true; do
echo "可用的远程存储:"
for i in "${!REMOTE_NAMES[@]}"; do
echo "$((i+1)). ${REMOTE_NAMES[$i]}"
done
echo "0. 退出"
read -p "请选择远程存储 (1-${#REMOTE_NAMES[@]}, 0: 退出): " STORAGE_INDEX
if [[ "$STORAGE_INDEX" =~ ^[0-9]+$ ]]; then
if [ "$STORAGE_INDEX" -ge 1 ] && [ "$STORAGE_INDEX" -le ${#REMOTE_NAMES[@]} ]; then
SELECTED_REMOTE="${REMOTE_NAMES[$((STORAGE_INDEX-1))]}"
break
elif [ "$STORAGE_INDEX" -eq 0 ]; then
echo "退出脚本..."
exit 0
else
echo "无效的选择,请重新选择。"
fi
else
echo "无效的输入,请输入一个数字。"
fi
done
}
# 主程序逻辑
rotate_log "$LOG_FILE"
read_remote_storage
read_remote_path
read_local_path
if [[ "$LOCAL_PATH" != "/" ]]; then
LOCAL_PATH="${LOCAL_PATH%/}"
fi
if [[ "$REMOTE_PATH" != "/" ]]; then
REMOTE_PATH="${REMOTE_PATH%/}"
fi
FULL_REMOTE_PATH="$SELECTED_REMOTE:$REMOTE_PATH"
echo "输出完整远程路径 $FULL_REMOTE_PATH"
REMOTE_BASENAME=$(basename "$REMOTE_PATH")
# 如果远程路径是根目录 /
if [[ "$REMOTE_PATH" == "/" ]]; then
if [[ "$LOCAL_PATH" == "/" ]]; then
# 情况 1: 本地路径也是根目录 /,警告并强制退出
echo "警告: 远程路径和本地路径都为根目录 '/',操作已被终止以防止系统损坏。"
exit 1
else
# 情况 2: 本地路径不是根目录 /,继续执行
echo "警告: 继续执行将会同步远程路径 '/' 到本地路径 '$LOCAL_PATH',可能会删除该路径下原有的文件。"
read -p "是否继续执行?(y/n): " user_choice
if [[ "$user_choice" != "y" && "$user_choice" != "Y" ]]; then
echo "操作已取消。"
exit 0
fi
echo "远程路径为根目录 '/', 本地路径为 '$LOCAL_PATH',继续执行。"
# 执行同步和校验
run_rclone_sync "$FULL_REMOTE_PATH" "$LOCAL_PATH"
run_rclone_check "$FULL_REMOTE_PATH" "$LOCAL_PATH"
fi
# 远程路径不是根目录 /
else
is_folder=$(is_remote_folder "$FULL_REMOTE_PATH")
#可以输出对远程路径的判断如果是文件夹输出 yes 否则输出 no
#echo "$is_folder"
if [[ "$LOCAL_PATH" == "/" ]]; then
# 情况 1: 本地路径是根目录 /,判断远程路径是否为文件夹
if [[ "$is_folder" == "yes" ]]; then
REMOTE_BASENAME=$(basename "$REMOTE_PATH")
# 判断本地路径下是否已经存在该远程文件夹
if [[ ! -d "$LOCAL_PATH/$REMOTE_BASENAME" ]]; then
# 本地路径下不存在该文件夹,创建文件夹
echo "本地路径 '$LOCAL_PATH' 下不存在文件夹 '$REMOTE_BASENAME',正在创建该文件夹..."
mkdir -p "$LOCAL_PATH/$REMOTE_BASENAME"
else
echo "本地路径 '$LOCAL_PATH' 下已存在文件夹 '$REMOTE_BASENAME',跳过创建。"
fi
fi
else
# 情况 2: 本地路径不是根目录 /,判断远程路径是否为文件夹
original_local_path="$LOCAL_PATH"
if [[ "$is_folder" == "yes" ]]; then
REMOTE_BASENAME=$(basename "$REMOTE_PATH")
LOCAL_BASENAME=$(basename "$LOCAL_PATH")
# 判断本地路径是否包含远程文件夹名
if [[ "$LOCAL_BASENAME" != "$REMOTE_BASENAME" ]]; then
LOCAL_PATH="$LOCAL_PATH/$REMOTE_BASENAME"
fi
# 判断本地路径下是否已经存在该文件夹
if [[ ! -d "$LOCAL_PATH" ]]; then
echo "本地路径 '$original_local_path' 下不存在文件夹 '$REMOTE_BASENAME',正在创建该文件夹..."
mkdir -p "$LOCAL_PATH"
else
echo "本地路径 '$original_local_path' 下已存在文件夹 '$REMOTE_BASENAME',跳过创建。"
fi
fi
fi
# 无论哪种情况,都在最后执行同步和校验
run_rclone_sync "$FULL_REMOTE_PATH" "$LOCAL_PATH"
run_rclone_check "$FULL_REMOTE_PATH" "$LOCAL_PATH"
fi
储存脚本为remote-local.sh到当前文件夹(或者指定的文件夹下然后 cd 到这个文件夹下)
赋予权限 sudo chmod +x remote-local.sh
这里最好是单开一个screen
root用户直接 ./remote-local.sh
非root 用户 sudo ./remote-local.sh
(rclone会自己读取配置文件,好像是必须要root权限,或者有其他办法没研究)
脚本有交互,需要你选择需要同步的远程网盘(即使只配置了一个远程网盘)
要求输入远程网盘需要同步的目录或者文件路径
(由于网络原因,可能无法完整远程路径的查询而报错,重新输入远程路径即可)
要求输入本地存储路径
虽然脚本中添加了几处防止输入错误而覆盖本地原有文件的功能
但是 rclone还是会有覆盖本地文件的风险
比如说 远程目录 /movie下只有一部电影名为 dianying.mp4
你想同步到本地 /vol1/1000/Movie下
本地文件夹下还有很多其他的电影
当你输入
远程路径 /movie
本地路径 /vol1/1000/Movie
那么恭喜你,当你同步完之后 本地/vol1/1000/Movie文件夹下只剩一下一部 dianying.mp4
为了避免这种情况出现
远程路径你可以输入 /movie/dianying.mp4
本地路径 输入/vol1/1000/Movie
这样无论 dianying.mp4 是一个文件还是文件夹 脚本都能实现同步 而不影响本地目录/vol1/1000/Movie下其他内容(脚本自动判断远程路径是文件夹还是文件)
如果远程路径/movie下面有空子文件夹或者子文件夹下只有少数文件 而本地/vol1/1000/Movie文件夹下有同名子文件夹且比远程同名子文件夹下文件多,脚本会删除本地子文件夹下的内容
在输入路径的时候 最后带不带"/" 都不影响 ,脚本中加了去除最后"/"的功能
如果你不小心将远程路径和本地路径都输入成了"/" ,不知道rclone会不会擦除系统 (脚本中加了这种可能的判断,会中断执行)
脚本还添加了最多三次两地文件校验,防止长时间传输网络中断造成的同步失败,尽可能的将远程文件完整的同步到本地
我的是千兆移动网络,看看我的同步速度

我不是专门写程序的,轻点喷
欢迎一起交流
喜欢的拿去用
将脚本中 rclone sync 替换为 rclone copy 可以避免覆盖造成删除本地其他文件,同名文件也会被覆盖
操作有风险 同步需谨慎
如果造成你的宝贵数据丢失 本人概不负责