Bash脚本:将源目录中的目录结构复制到目标目录

3
我非常新手Bash脚本编写,为了练习,我尝试编写一个脚本,该脚本需要输入源目录和目标目录。该脚本将搜索源目录并复制其子目录结构到目标目录中(任何文件都将被忽略,只有目录本身会被复制)。源目录可以有任意数量的子目录,深度也可以不同。最好的方法是什么?我已经开始尝试编写递归函数,如果找到目录,则递归地调用该函数。然而,由于我对脚本编写的经验缺乏,我一直被卡住了。
以下是我到目前为止所做的:
#! /bin/bash

if [ ! $# -eq 2 ]; then 
    echo "ERROR: Script needs 2 arguments: $0 source/directory/ target/directory/"
    exit
fi

SOURCE_DIR=$1
TARGET_DIR=$2

function searchForDirectory {
    for FILE in ls $SOURCE_DIR/*; do #should be 'ls *current_directory*'
        if [ -d $FILE ]; then
            searchForDirectory #call function recursively onto $FILE
            #Do stuff here to create copy of this directory in target dir
        fi
    done
}

if [ ! -d $SOURCE_DIR ]; then 
    echo "ERROR: Source directory does not exist"
    exit
else
    searchForDirectory
fi

我知道这只是一个基本的函数,还需要投入更多的工作,但我只是希望在继续之前得到指导,确定这是否是正确的方法,如果是,下一步应该怎么做?如何将目录传递给我的函数? 编辑:在Ivan的帮助下,这是我的解决方案 #! /bin/bash
if [ ! $# -eq 2 ]; then 
    echo -e "\nERROR: Script needs 2 arguments:\n$0 source/directory/ target/directory/\n"
    exit
fi

function recursiveDuplication {
    for file in `ls $1`; do
        if [ -d $1/$file ] && [ ! -d $2/$file ]; then
            mkdir $2/$file
            recursiveDuplication $1/$file $2/$file
        elif [[ $1/$file == *.png ]]; then
            convert $1/$file $2/$file
        fi
    done
}

if [ ! -d $1 ]; then 
    echo -e "\nERROR: $1 does not exist\n"
    exit
elif [ -d $2 ] && [ ! -w $2 ]; then
    echo -e "\nERROR: You do not have permission to write to $2\n"
    exit
elif [ ! -d $2 ]; then
    echo -e "\nSUCCESS: $2 has been created"
    mkdir $2
fi

recursiveDuplication $1 $2

该解决方案存在两个问题:
  1. 正如Rany Albeg Wein在下面解释的那样,使用“ls”不是一个好的解决方案 - 当目录/ *.png名称中有空格时,我看到了为什么。

  2. 我也试图将源中的任何*.png文件复制到目标并在目标中将其转换为*.jpg图像。我该怎么做?我尝试使用ImageMagick的 convert image.png image.jpg 命令,但我不知道当image.png被称为$file时该怎么做?


如果您有兴趣,可以通过使用 findcpio 来完成。执行命令 find source -type d | cpio -pd target,将从 source 复制整个目录树到 target。如果以 root 用户身份执行该命令,则权限和所有权都将被保留。如果以非 root 用户身份执行,则只能保留权限。 - alvits
@KOB 新的答案已经添加,使用“gnu cpio”进行了测试并提供了安全解决方案。 - Jay jargot
3个回答

0

你可以简化很多。

$ find a -type d | xargs -I d mkdir -p copy/d

将目录a的树状结构复制到名为copy的新目录下

$ tree a
a
|-- b
|   |-- c
|   |   `-- file4
|   |-- d
|   |   `-- file4
|   `-- file3
`-- file2

3 directories, 4 files


$ tree copy
copy
`-- a
    `-- b
        |-- c
        `-- d

4 directories, 0 files

尽管原帖没有指定是否要保留权限和所有权,但您应该提到这种方法将不会保留它们。 - alvits
此外,包含特殊字符(´ " * \n)的目录名称会破坏这个吧?符号链接呢? - Jay jargot
此外,mkdir 由于 -p 的存在会尝试创建已经存在的目录,从而产生错误,对吗? - Jay jargot
@karakfa 这个解决方案无法复制目录树,请参考我的回答中的测试。会生成以下错误:xargs: unmatched double quote; by default quotes are special to xargs unless you use the -0 option - Jay jargot

0

这个解决方案已经测试过,可以处理包含特殊字符的目录名称、目录树中的循环以及损坏的符号链接。

下面是一个标准解决方案:

( cd source/ ; find . -depth -type d -printf "%p\0" | xargs -0 -I xxx mkdir -p ../destination/xxx )

还有第二种解决方案,使用gnu cpio

( cd source/ ; find . -depth -type d -printf "%p\0" | cpio --null -pd ../destination )

测试:

$ mkdir -p source/a/ba/c/d/e
$ mkdir -p source/a/bb/c/d/e
$ mkdir -p source/a/b/ca/d/e
$ ln -s source/broken/link source/a/ba/c/d/e/broken
$ (cd source/a/ba && ln -s ../../a  tree-loop)
$ mkdir -p source/z/"$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47\72\73\74\75\76\77\100\133\134\135\1336\137\140\173\174\175\176\177dir")" 
$ touch source/a/b/"$(printf "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\41\42\43\44\45\46\47\72\73\74\75\76\77\100\133\134\135\1336\137\140\173\174\175\176\177file")"
$ ls 
source
$ find source -depth
source/z/??????????????????????????????? !"#$%&':;<=>?@[\][6_`{|}~?dir
source/z
source/a/ba/tree-loop
source/a/ba/c/d/e/broken
source/a/ba/c/d/e
source/a/ba/c/d
source/a/ba/c
source/a/ba
source/a/b/ca/d/e
source/a/b/ca/d
source/a/b/ca
source/a/b/??????????????????????????????? !"#$%&':;<=>?@[\][6_`{|}~?file
source/a/b
source/a/bb/c/d/e
source/a/bb/c/d
source/a/bb/c
source/a/bb
source/a
source

$ ( cd source/ ; find . -depth -type d -printf "%p\0" | xargs -0 -I xxx mkdir -p ../destination/xxx )
$ find destination/ -depth 
destination/z/??????????????????????????????? !"#$%&':;<=>?@[\][6_`{|}~?dir
destination/z
destination/a/ba/c/d/e
destination/a/ba/c/d
destination/a/ba/c
destination/a/ba
destination/a/b/ca/d/e
destination/a/b/ca/d
destination/a/b/ca
destination/a/b
destination/a/bb/c/d/e
destination/a/bb/c/d
destination/a/bb/c
destination/a/bb
destination/a
destination/

gnu cpio 测试:

$ rm -rf destination
$ ( cd source/ ; find . -depth -type d -printf "%p\0" | cpio --null -pd ../destination )
0 blocks

$ find destination -depth
destination/z/??????????????????????????????? !"#$%&':;<=>?@[\][6_`{|}~?dir
destination/z
destination/a/ba/c/d/e
destination/a/ba/c/d
destination/a/ba/c
destination/a/ba
destination/a/b/ca/d/e
destination/a/b/ca/d
destination/a/b/ca
destination/a/b
destination/a/bb/c/d/e
destination/a/bb/c/d
destination/a/bb/c
destination/a/bb
destination/a
destination

-2
#!/bin/bash
# 1st argument - source dir, 2nd - destination

function rrr {
  for i in `ls $1`
  do
    if [ -d $1/$i ]
    then
      mkdir $2/$i
      rrr $1/$i $2/$i
    fi
  done
}


rrr $1  $2

同上述问题,翻译内容如下:Meh,这其实和问题一样。 - Ivan
不要使用 ls 输出进行任何操作。ls 是一个交互式查看目录元数据的工具。尝试使用代码解析 ls 输出是错误的。通配符(Globs)更简单且正确:for file in *.txt。阅读解析 ls - Rany Albeg Wein
@RanyAlbegWein 请看我的编辑,我已经解释了使用ls时遇到的问题。如果不使用ls,我该如何使用相同的for循环? - KOB
@KOB 请查看我之前评论中的链接。你会学习到正确解决问题的方法。 - Rany Albeg Wein
@KOB 请查看这个答案:仅复制文件夹而不是文件 - Rany Albeg Wein
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接