类似于Dropbox的bash脚本

类似于Dropbox的bash脚本,bash,synchronization,Bash,Synchronization,我正在尝试编写一个bash脚本,该脚本同步两个文件夹,类似于Dropbox服务所做的工作,即删除最新文件夹中不存在的文件/文件夹,并复制最新的内容。 但是,我不确定如何处理路径/文件名。因此,我向您征求一些建议,也可能是关于改进脚本的建议。代码如下: #!/bin/bash out=/dev/stdout #outerr=/dev/stderr outerr="test.txt" if [ $# -lt 2 ] then echo "" >> $out echo

我正在尝试编写一个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
提前谢谢大家,


阿尔多

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

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)的变化。(编辑:我使用
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会阻止我们读取通过的第一个目录。编辑后的代码将实际用于子目录和完整路径目录


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

如果您开发此脚本是为了作业或只是为了学习如何编写bash脚本,那么Jason已经做出了非常有用的回答。另外,请忽略我文章的其余部分,因为它没有回答你的问题

如果您只需要一个用于同步两个文件夹的工具,则
rsync
适合您。 例如,我使用以下脚本将音乐同步到外部驱动器

#!/bin/sh
rsync -aE --delete /home/user/Music/  /media/user/data/my.lib/music
--delete
用于删除源文件夹树中不存在的目标文件和文件夹。还有一个选项用于在同步完成后删除这些文件(
delete after
)。您也可以使用
--dry run
选项,以便不执行任何实际更改。
-aE
用于递归到文件夹中,保留文件权限、修改时间、可执行性、链接、所有者和组


为什么不构建自己的脚本?原因有三。首先,没有理由重新发明轮子。其次,像
rsync
这样的工具经过了良好的测试和调试。第三,我们正在讨论您的文件…;-)

您是否知道
rsync
?据我所知,它不会删除源文件中不存在的目标文件夹中的文件。
--从dest dirs中删除无关文件。
。因此,最好不要删除路径中的反斜杠?我发现这样更容易。代码中存在一些逻辑错误,结果变成
cp/
,显然会失败。这是由于源列表通过管道传输到sed,而不是目标列表。因此,这些列表是不同的,但它们的逻辑是相同的。我选择这种方法是因为它消除了嵌套for循环。越简单越好,尤其是我重新分配了IFS。对不起。错误在这里
cp$source/$filesrc$dest/$filedest
。其中,这将扩展到正确的源文件,但目标文件将扩展到
/
。我想练习并拥有某种交互式文件到文件的操作选择。出于实际目的,坚持rsync会更健壮(更安全!)