首页 > 解决方案 > 类似于 Dropbox 的 bash 脚本

问题描述

我正在尝试编写一个 bash 脚本来同步两个类似于 Dropbox 服务所做的文件夹,即删除最新文件夹中不存在的文件/文件夹并复制最新的东西。但是我不确定如何处理路径/文件名。因此,我向您征求一些建议,以及可能改进脚本的问题。这是代码:

#!/bin/bash

out=/dev/stdout
#outerr=/dev/stderr
outerr="test.txt"

if [  $# -lt 2 ]
then
    echo "" >> $out
    echo "   Not enough arguments passed to the script, try again next time." >> $out
    echo "" >> $out
    echo "   Usage: " >> $out
    echo "   bash files_sync.sh [--t] SOURCE_NEW DESTINATION_OLD " >> $out
    echo "" >> $out
    echo "   Options: --t  just prints the modifications, without taking any action" >> $out
    echo "" >> $out
    exit 1
fi

test=0

if [ $# -eq 2 ]
then 
    source=$(printf '%s\n' "$1" | sed  's/\///')
    dest=$(printf '%s\n' "$2")
    #dest=$(printf '%s\n' "$2" | sed  's/\///')
fi

if [ $# -eq 3 ]
then 
    if [ $1 == "--t" ]
    then
        echo "" >> $out
        echo "   Printing the suggested actions..." >> $out
        test=1 
        source=$(printf '%s\n' "$2" | sed  's/\///')
        dest=$(printf '%s\n' "$3")
    else
        echo "   What's your game, dude? Exiting. " >> $out
        exit 20
    fi
    #source=$(printf '%s\n' "$2" | sed  's/\///')
    #dest=$(printf '%s\n' "$3" | sed  's/\///')
fi

if [ ! -d $source ]; then 
    echo "" >> $out
    echo "   Path $source does not exists or not a folder, exiting. " >>         $out
    echo "" >> $out
    exit 2
else
    echo "" >> $out
    echo "   (SOURCE) UP-TO-DATE FOLDER: $source" >> $out
    echo "" >> $out
fi

if [ ! -d $dest ]; then 
    echo "   Path $dest does not exists or not a folder, exiting. " >>     $out
    echo "" >> $out
    exit 3
else
    echo "   (DESTINATION) TO BE SYNCHED FOLDER: $dest" >> $out
    echo "" >> $out
fi

echo -n "   Do you REALLY want to proceed? [y/N] : " >> $out
read choice
echo "" >> $out

if [ $choice == "n" ]; then
    echo "   Brilliant, bye!" >> $out
    echo "" >> $out
    exit 4
fi

if [ $choice == "y" ]
then
    echo "   Awesome! Let's get started ...."
    #FIRST CHECK IF DEST FILES ARE IN SOURCE. IF NOT DELETING THEM

    list1=`find $source -mindepth 1 -name "*" | sed 's/'"$source"'\///'`
    list2=`find $dest -mindepth 1 -name "*"`
    #list2=`find $dest -mindepth 1 -name "*" | sed 's/'"$dest"'\///'` 

    outdest="testdest.txt"
    echo "  Writing DEST files into $outdest file" >> $out
    > $outdest
    for filedest in $list2
    do
        echo $filedest >> $outdest
    done
    outsrc="testsrc.txt"
    echo "  Writing SRC files into $outsrc file" >> $out
    > $outsrc
    for filesrc in $list1
    do
        echo $filesrc >> $outsrc
    done

    outerr="testerr.txt"
    >$outerr
    echo "  Writing operations into $outerr file" >> $out

    for filedest in $list2
    do
        #if [ ! -d $filedest ]
        #then
            check="true"
            for filesrc in $list1
            do
                #if [ ! -d $filesrc ]
                #then
                    if [ $filedest == $filesrc ]
                    then
                        check="false"
                        break
                    fi
                #fi
           done
                if [ $check == "true" ]
                then 
                if [ $test -eq 1 ]
                then        
                    echo "rm $dest/$filedest"  >> $outerr
                else
                    echo " ! "; rm $dest/$filedest
                fi
            fi
    #fi
    done
    #THEN CHECK IF FILE IN SOURCE IS IN DEST. IF NOT COPY ELSE CHECK IF IT IS NEWER
    for filesrc in $list1
    do
        check="true"
        for filedest in $list2
        do              
            if [ $filedest == $filesrc ]
            then
                check="false"
                if [ $source/$filesrc -nt $dest/$filedest ] 
                then 
                    if [ -d $source/$filesrc ]
                    then
                        echo " folder here, do nothing"  >> $outerr
                    else
                        if [ $test -eq 1 ]
                        then        
                            echo "cp $source/$filesrc $dest/$filedest"  >> $outerr
                        else
                            echo " ! "; cp $source/$filesrc $dest/$filedest
                        fi

                    fi
                fi
                #break
            fi
        done
        if [ $check == "true" ]
        then
            if [ $test -eq 1 ]
            then        
                #echo -n "   +++ $source/$filesrc not existing in DEST : "  >> $outerr
                if [ -d $source/$filesrc ]
                then
                    echo "mkdir $dest/$filesrc"  >> $outerr
                else
                    echo "mv $source/$filesrc $dest/ "  >> $outerr
                fi
            else
                if [ -d $source/$filesrc ]
                then
                    echo " ! "; mkdir $source/$filesrc
                else
                    echo " ! "; mv $source/$filesrc $dest/$filedest
                fi
            fi
        fi
    done

    echo ""  >> $out
    echo '   --------- Sync COMPLETE ! -------------' >> $out
    echo ""  >> $out
   else
        echo ""  >> $out
        echo '   Not getting the answer. Exiting'  >> $out
        echo ""  >> $out
   fi

先感谢您,

阿尔多

标签: bashsynchronization

解决方案


我已将脚本的内容缩减为一个循环。我想我正在做你想做的事。我改变了所有:

if [ something ]
then

if [something ]; then. 我认为它使所有缩进更容易阅读。我删除了预删除逻辑,并在所有复制完成后添加了一个循环。所以整个循环消失了:

for filedest in $list2
    do
        #if [ ! -d $filedest ]
        #then
            check="true"
            for filesrc in $list1
            do
                #if [ ! -d $filesrc ]
                #then
                    if [ $filedest == $filesrc ]
                    then
                        check="false"
                        break
                    fi
                #fi
           done
                if [ $check == "true" ]
                then 
                if [ $test -eq 1 ]
                then        
                    echo "rm $dest/$filedest"  >> $outerr
                else
                    echo " ! "; rm $dest/$filedest #logic error here.
                fi
            fi
    #fi
    done

另一个重大变化是所有引用和内部字段分隔符(IFS)的更改。(编辑:我将 using 更改find为 glob ( *),因此无需更改 IFS)这些都是确保您可以处理包含空格的文件名所必需的。这是我最终得到的结果:

#!/bin/bash

out=/dev/stdout
#outerr=/dev/stderr
#outerr="test.txt"


function sync_dir 
{
    #This is our current subdirectory of $source and $dest.
    #Note: In the original call we do not pass an argument so $subdirs is blank.
    #This means that "$source/$subdirs"* = "$source/"* for the original call.
    local subdirs="$1"
    
    #By removing these find calls, we can just use * and avoid making changes to the IFS
    #local list1=`find "$source/$subdirs" -mindepth 1 -maxdepth 1 -name "*"`
    #local list2=`find "$dest/$subdirs" -mindepth 1 -maxdepth 1 -name "*"`
    
    #No need for find.  Let * do all the work.
    for filesrc in "$source/$subdirs"*; do 
        echo "$filesrc" >> $outsrc
        #remove path from $filesrc
        local filewithoutdir="$(basename "$filesrc")"           
        #if "$filesrc" is a directory
        if [ -d "$filesrc" ]; then
            #check to see if the destination is a directory. Make it if not...
            if [ ! -d "$dest/$subdirs$filewithoutdir" ]; then   
                if [ $test -eq 1 ]; then            
                    echo "mkdir \"$dest/$subdirs$filewithoutdir\"" >> $outerr
                else
                    echo " ! "; mkdir "$dest/$subdirs$filewithoutdir" 2>> $outerr
                fi
            fi
            #recursive call if we are dealing with a directory.
            sync_dir "$subdirs$filewithoutdir/"
        #else if not a file OR "$filesrc" is newer than the destination, copy it.
        elif [ ! -f "$dest/$subdirs$filewithoutdir" -o "$filesrc" -nt "$dest/$subdirs$filewithoutdir" ]; then 
            if [ $test -eq 1 ]; then        
                echo "cp \"$filesrc\" \"$dest/$subdirs$filewithoutdir\""  >> $outerr
            else
                echo " ! "; cp "$filesrc" "$dest/$subdirs$filewithoutdir" 2>> $outerr
            fi
        fi
    done
    #Here we loop throught the destination directory
    for filedest in "$dest/$subdirs"*; do
        echo "$filedest" >> $outdest
        local filewithoutdir="$(basename "$filedest")"
        #If the files do not exist in the source directory, delete them.
        if [ ! -e "$source/$subdirs$filewithoutdir" ]; then
            if [ $test -eq 1 ]; then        
                echo "rm -f \"$filedest\" >/dev/null 2>&1"  >> $outerr
            else
                #We allow this to fail and silence it because it may try to delete a directory.
                echo " ! "; rm -f "$filedest" >/dev/null 2>&1
            fi
            
        fi
        #if you want to remove files and directories use this, but this can be dangerous.
        #if [ ! -e $source/$filewithoutdir ]; then
        #    if [ $test -eq 1 ]; then        
        #        echo "rm -rf \"$filedest\" >/dev/null 2>&1"  >> $outerr
        #    else
        #        echo " ! "; rm -rf "$filedest" 2>> $outerr
        #    fi
        #    rm -rf "$filedest" >/dev/null 2>&1
        #fi
    done  
}

if [  $# -lt 2 ]; then
    echo "" >> $out
    echo "   Not enough arguments passed to the script, try again next time." >> $out
    echo "" >> $out
    echo "   Usage: " >> $out
    echo "   bash files_sync.sh [--t] SOURCE_NEW DESTINATION_OLD " >> $out
    echo "" >> $out
    echo "   Options: --t  just prints the modifications, without taking any action" >> $out
    echo "" >> $out
    exit 1
fi

test=0

if [ $# -eq 2 ]; then
    #This will cause an issue when processing full path like /home/user/bla
    #source=$(printf '%s\n' "$1" | sed  's/\///')
    #dest=$(printf '%s\n' "$2")
    #dest=$(printf '%s\n' "$2" | sed  's/\///')
    source="$1"
    dest="$2"
fi

if [ $# -eq 3 ]; then
    if [ $1 == "--t" ]; then
        echo "" >> $out
        echo "   Printing the suggested actions..." >> $out
        test=1 
        source="$2"
        dest="$3"
    else
        echo "   What's your game, dude? Exiting. " >> $out
        exit 20
    fi
fi

if [ ! -d $source ]; then 
    echo "" >> $out
    echo "   Path $source does not exists or not a folder, exiting. " >>         $out
    echo "" >> $out
    exit 2
else
    echo "" >> $out
    echo "   (SOURCE) UP-TO-DATE FOLDER: $source" >> $out
    echo "" >> $out
fi

if [ ! -d $dest ]; then 
    echo "   Path $dest does not exists or not a folder, exiting. " >>     $out
    echo "" >> $out
    exit 3
else
    echo "   (DESTINATION) TO BE SYNCHED FOLDER: $dest" >> $out
    echo "" >> $out
fi

echo -n "   Do you REALLY want to proceed? [y/N] : " >> $out
read choice
echo "" >> $out

if [ $choice == "n" ]; then
    echo "   Brilliant, bye!" >> $out
    echo "" >> $out
    exit 4
fi

if [ $choice == "y" ]; then
    echo "   Awesome! Let's get started ...."
    
    outdest="testdest.txt"
    echo "  Writing DEST files into $outdest file" >> $out
    > $outdest
    
    outsrc="testsrc.txt"
    echo "  Writing SRC files into $outsrc file" >> $out
    > $outsrc
    
    outerr="testerr.txt"    
    echo "  Writing operations into $outerr file" >> $out
    >$outerr
    
    #function call
    sync_dir
    
    #If we are not in test mode and errors have occured, they will be written to $outerr.
    #This test (-s) checks to see that $outerr is not zero length (implies -e).
    if [ ! $test -eq 1 -a -s $outerr ]; then
      echo "WARNING: Errors have occured during the running of this script.  See $PWD/$outerr for details" >> $out
    fi
    
    echo ""  >> $out
    echo '   --------- Sync COMPLETE ! -------------' >> $out
    echo ""  >> $out
else
    echo ""  >> $out
    echo '   Not getting the answer. Exiting'  >> $out
    echo ""  >> $out
fi

目前,这不会复制源中目录的内容。如果那是你想要的,你可以把这个东西的肉放在一个函数中。然后cd进入目标目录并递归调用该函数以复制子目录的内容。

编辑:现在实现了递归版本。我的早期版本实际上是错误的。我将 -mindepth 误读为 -maxdepth。-maxdepth 会阻止我们读取第一个目录。编辑后的代码实际上将适用于子目录和完整路径目录。

您还会注意到我在,和电话2>> $outerr的末尾添加了。这将捕获任何输出到 stderr 并将其写入. 请记住,所有这些命令都可能失败,因此至少捕获错误很重要。在测试模式下,您正在打印所有将用于. 当您不在测试模式时,您根本没有使用它,所以我用处理过程中可能发生的任何错误填充它。最后,我们检查是否报告了任何错误并发布一个小警告。如果您想安全起见,请更改为强制退出任何错误。cprmmkdir$outerr$outerr-s $outerr2>> $outerr2>> $outerr || exit 2


推荐阅读