遍历目录并将所有子目录中的文件复制到同名的新目录中

遍历目录并将所有子目录中的文件复制到同名的新目录中

我会尽力解释这一点。我有几百个目录,每个目录都包含一些其他子目录,文件分散在每个子目录中。我不需要也不想要任何子目录,只需要文件,但目录名称必须保持不变。因此基本上从:

$ source tree

├── sub1
│   └── sub1.1
│       ├── file1
│       └── file2
├── sub2
    └── sub2.1
        └── sub2.1.1
            ├── file1
            └── file2

到新目录:

├── sub1
│     ├── file1
│     └── file2
├── sub2
      ├── file1
      └── file2

真正重要的是,文件夹(此处为 sub1 和 sub2)具有相同的名称,其中的所有子文件夹均未复制,并且所有文件均复制到新文件夹中。我已经尝试解决这个问题大约 2 个小时了,但似乎无法做到。

我正在使用 bash,我知道这是可能的,但我不知道如何实现。谢谢您的帮助!

答案1

你可以通过管道连接findxargs,执行cp

find sub1/* -type f -mindepth 2 -print0 | xargs -0 cp -t /sub1

答案2

我的第一个想法是将整个目录树复制到新位置,cd然后在那里重新排列新的目录树。但是像这样的文件./sub2/foo/foo会有问题:它应该变成./sub2/foo,但这个名字被一个目录占用,在我们移动文件之前无法删除该目录。

让我们在复制的同时构建一棵新树。


解决方案bash,使用非可移植选项

#!/bin/bash

srce="/source/directory"
trgt="/target/directory"

shopt -s nullglob dotglob

mkdir -p -- "$trgt" || exit 1

# in case trgt is relative
cd -- "$trgt" || exit 1
trgt="$PWD"
cd -- "$OLDPWD" || exit 2

cd -- "$srce" || exit 2

find . -maxdepth 1 ! -type d -exec cp -t "$trgt" -- {} +

for d in ./*; do
   [ -d "$d" ] && mkdir -p -- "$trgt/$d" \
   && find "$d" ! -type d -exec cp -t "$trgt/$d" -- {} +
done

笔记:

  • 非直接存在于源目录中的目录将被直接复制到目标目录中。
  • 无论如何都会创建没有文件的子目录(如果有)。
  • 目标目录中的现有内容可能会导致代码出现错误。
  • 名称冲突(如果有)不会被处理。
  • 双破折号解释这里
  • 不可移植碎片:shoptfind -maxdepthcp -t

便携式解决方案

(我的意思是我认为它是便携式的。如果不是,请发表评论。)

#!/bin/sh

srce="/source/directory"
trgt="/target/directory"

mkdir -p -- "$trgt" || exit 1

# in case trgt is relative
cd -- "$trgt" || exit 1
trgt="$PWD"
cd -- "$OLDPWD" || exit 2

cd -- "$srce" || exit 2

find . \
   ! -type d \
   \( \( ! -path '*/*/*' -exec cp -- {} "$trgt/" \; \) \
   -o \( -path '*/*/*' -exec sh -c '
      trgt="$1"
      shift
      for f do
         sbdr="${f#./}"
         sbdr="${sbdr%%/*}"
         mkdir -p -- "$trgt/$sbdr" && cp -- "$f" "$trgt/$sbdr"
      done
   ' find-sh "$trgt" {} + \) \)

笔记:

  • 这种方法有很大不同,它不使用for循环。
  • 非直接存在于源目录中的目录将被直接复制到目标目录中。
  • 不会创建没有文件的子目录。这里两种解决方案有所不同。
  • 目标目录中的现有内容可能会导致代码出现错误。
  • mkdir -p被过度使用。如果逻辑更合理,当然可以减少 s 的数量mkdir,但我选择
  • cp使用效率很低:每个文件一个进程。
  • 名称冲突(如果有)不予处理。两种不同的解决方案可能会保留不同的冲突文件。

另一种方法是找到包含 s 的子目录find,然后在其中运行额外的finds 来查找文件。这是可行的,但很棘手。比较我的这个答案,片段“如何正确地运行对另一个查找结果的查找?”

答案3

我的解决方案有点复杂,但也许更容易理解?

> tree
.
├── filebot.txt
├── test
│   ├── dir1
│   │   ├── foo1
│   │   └── foo2
│   └── dir2
│       └── foo3
├── test2
│   ├── dir2
│   │   └── foo3
│   └── dir3
│       ├── foo1
│       └── foo2
└── test3
    ├── dir1
    │   ├── foo1
    │   └── foo3
    └── dir3
        └── foo2

命令

> mapfile -t files < <(find . -mindepth 2 -type f );\
 for FILE in ${files[*]}; do \
   mv "$FILE" $(sed -n 's/\(.\/.*\/\).*\//\1/p' <<< "$FILE");\
 done

解释

  • mapfile:创建要处理的文件数组
  • for循环:处理数组中的每个文件
  • mv/cp/rsync:每个文件的操作
    • sed:捕获基目录名称并剥离子目录。

结果

mv ./test/dir1/foo1 ./test/foo1
mv ./test/dir1/foo2 ./test/foo2
mv ./test/dir2/foo3 ./test/foo3
mv ./test2/dir2/foo3 ./test2/foo3
mv ./test2/dir3/foo1 ./test2/foo1
mv ./test2/dir3/foo2 ./test2/foo2
mv ./test3/dir1/foo1 ./test3/foo1
mv ./test3/dir1/foo3 ./test3/foo3
mv ./test3/dir3/foo2 ./test3/foo2

您可以使用以下命令清理空目录

find -type d -empty -delete

答案4

好吧,这个命令将为您提供位于指定根文件夹的子文件夹中的所有文件的列表:

find ./*/ -type f -iname "*.*"

然后,您可以使用 basename 提取文件名本身,然后使用 rsync 迭代该输出(比 cp 更好,因为它会检查其工作)。可能像这样:

rsync -aiSP /source/"${filepath}" /destination/"${basefilename}"

结构上是这样的:

for filepath in "$( find ./*/ -type f -iname "*.*" )" ; do 
    basefilename="$( basename "${filepath}" )" 
    rsync -aiSP /source/"${filepath}" /destination/"${basefilename}" 
done  

由于 find 的解析方式,该代码无法工作,但是这样的代码应该可以完成您所要求的操作。

我的主要收获是find ./*/避免根文件夹中的松散文件并仅在任何子文件夹中查找文件,并且使用 rsync 比使用 cp 效果更好。

如果您有任何疑问,请告诉我。

我想我会探索以下解决方案:

while IFS= read -r -d '' filepath; do 
    basefilename="$( basename "${filepath}" )" 
    rsync -aiSP /source/"${filepath}" /destination/"${basefilename}" 
done < <( find ./*/ -type f -iname "*.*" -print0 ) 

该代码更有可能按原样工作。尽情享受吧!

相关内容