Shell:异构目录的文件双向同步(二)

原创
2021/08/10 22:36
阅读数 59

前情概述:小马过河时遇到了 2 个障碍,小牛来解决了。

https://my.oschina.net/u/1044667/blog/5170007

小牛过河

小牛奥利把A、B盘合并后,把B盘换成了B1盘,想继续同步到B1,但出现了新的问题

  • 目录命名变了,导致上次的同步文件不能直接用了

    /run/media/bing/1234/资料100g/音频12g/有声书/nmw.mp3 变成了 /run/media/bing/1234/我的资料盘105g/音频10g/有声书/nmw.mp3

  • 发现了 bug:同步名单中存在相同文件。返查文件列表 f.txt,发现是这样的情况:

    4120  /run/media/bing/1234/我的资料盘105g/vid/Thumbs.db
    4120  /run/media/bing/1234/书/2a-1.pdf
    4120  /run/media/bing/D20D/视频20g/Thumbs.db
    4120  /run/media/bing/D20D/tx/pdf46.9MB/2a-1.pdf
    

    这 4 个文件大小相同。其中,2 个相同文件 2a-1.pdf 出现在了最后的同步结果中。

1. 文件搜索

本以为B系列盘没什么变化,但看来有些文件夹命名不同,文件倒是变化不大。

重新对比一遍耗时太长,又因为文件名基本没变化,所以还是用搜索来确定新路径,这样可以重复利用上一次的同步文件。找不到再报错吧

上次的同步格式例如:

=>/run/media/bing/D20D/资料盘/音频/有声书/
/run/media/bing/1234/资料100g/音频12g/有声书/nmw.mp3

=>/run/media/bing/1234/资料100g/vid/
/run/media/bing/D20D/视频20g/tx/21-a.mp4

可以看出,源和目的路径都可能改动。所以都靠搜吧,搜不到拉倒!

# 搜索路径
# B盘文件路径有所改变,但文件名未变
search() {
    local path=$1
    [[ -e "$path" ]] && echo "$path" || fd -1 "$(basename '$path')" "$dirB"
}
# 路径检查
assert() {
    local path=$1
    local msg=$2
    [[ ! -e "$path" ]] && echo "${msg:-!!!}  $path" && exit 2
}

说明:

  • fd -1:只返回第一个结果
  • ${msg:-!!!}msg 值为空时用 !!! 替代

新的同步函数出炉:

local dst new_dir
while read -r L; do
    case "$L" in
    -* )
        rm -f "${L#-}"
        continue ;;
    '=>'* )
        dst=$(search "${L#=>}")
        assert "$dst" "=>"
        continue ;;
    ++* )
        assert "$dst" "++"
        new_dir=${L#++}
        dst="${dst%/}/${new_dir}"
        echo mkdir -p "${dst}" && mkdir -p "${dst}"
        continue ;;
    /* )
        assert "$dst" "//"
        L=$(search "$L")
        assert "$L" && cp -rup "$L" "$dst"
        continue ;;
    '' )
        dst=''
        new_dir=''
        continue ;;
    * )
        echo "$L"
        exit 10 ;;
    esac
done < sync.txt

加了 assert,中断了也不怕,因为 copy 的时候不会重复拷贝

2. 嵌套数组(nested array

4120  /run/media/bing/1234/我的资料盘105g/vid/Thumbs.db
4120  /run/media/bing/D20D/书/2a-1.pdf
4120  /run/media/bing/D20D/视频20g/Thumbs.db
4120  /run/media/bing/D20D/tx/pdf46.9MB/2a-1.pdf

把这些值放到文件比较流程里:

  • fileSet[4120]=/vid/Thumbs.db
  • /书/2a-1.pdf 与其比较,检校值不同
  • /视频20g/Thumbs.db 与其比较,是相同文件,均从 fileStat 中删除
  • /pdf46.9MB/2a-1.pdf 与其比较,检校值不同

看得出来,2 个 2a-1 文件之间并没有比较,从而被计入不同文件!

所以,问题在于:所有相同大小的文件,仅仅和第一个文件进行了比较,而没有互相比较。

最直接的解决方法就是 fileSet[size] 应保存所有相同大小文件的路径,而不只是第一个。

比如:fileSet[size]='path1:path2:...',然后做字符串循环截取。

但如果是 python 的话,一般会用 maplist,即 fileSet[size] = ('path1', 'path2', ...)'

bash 是否支持呢?这样操作确实自然,不需要分割字符串。

研究了一下,确实可以模拟,用到了序列化和反序列化

# 数组
declare -a paths
paths+=("1234")  # 添加元素
# 序列化
declare -p paths
# 输出:typeset -a paths=( ‘1234’ )
# 反序列化
eval "typeset -a paths=( ‘1234’ )"
# 得到 paths 数组对象

有了这个特性,一切就比较方便了,新的比较函数如下:

declare -A fileSize fileStat
declare -a filePaths
local size path fpath
while read -r line; do
    size=$(cut -f1 <<< "$line")
    path=$(cut -f2- <<< "$line")
    fileStat+=([$path]="")
    # 相同大小
    if [[ ${fileSize[$size]+_} ]]; then
        eval "${fileSize[$size]}"  # 反序列化
        for fpath in "${filePaths[@]}"; do
            # 相同文件名
            if [[ $(basename "$fpath") == $(basename "$path") ]]; then
                unset fileStat[$fpath] fileStat[$path]  #去除相同文件
                continue
            fi
            # 相同检校值
            if [[ $(get_file_sum "$fpath") -eq $(get_file_sum "$path") ]]; then
                 unset fileStat[$fpath] fileStat[$path]  #去除相同文件
            fi
        done
        filePaths+=("$path")
        fileSize[$size]=$(declare -p filePaths)  # 序列化
    else
        filePaths=("$path")
        fileSize+=([$size]="$(declare -p filePaths)")
    fi
done < f.txt

至此 bug 得到了解决。

性能的烦恼!未完待续:

  • 虽然复用了第一个同步结果,但为了不有所遗漏,还是把所有B盘和A盘进行了比较同步,此时检校的耗时无法再忍,如何提高比较性能?
展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部