【问题标题】:Search all dirs and subdirs in current dir for files that match list of file extensions. Copy those files to new dir maintaining file structure在当前目录中的所有目录和子目录中搜索与文件扩展名列表匹配的文件。将这些文件复制到维护文件结构的新目录
【发布时间】:2020-04-16 21:47:05
【问题描述】:

我有一个包含一长串文件扩展名的文本文件:

.sln
.csproj
.cs
.xaml
.cshtml
.javasln
.project
.java
... (etc)

我有三个项目目录,每个目录都有很多子目录。

我可以通过以下方式获取所有文件和路径的列表:find . -type f -printf "%p\n"

./DirectoryA/src/main/resources/static/resources/build/home.ini
./DirectoryA/src/main/resources/static/resources/images/spring-pivotal-logo.png
./DirectoryB/src/main/resources/db/hsqldb/data.sql
./DirectoryC/src/main/resources/project/schema.project

我想遍历这些,如果文件扩展名与我列表中的一个匹配,请将其复制到 myCopyDirectory,同时保持其目录结构。

也就是说,如果./DirectoryC/src/main/resources/project/schema.project 文件扩展名匹配.project(就像它一样)作为我的文本文件中的扩展名之一......将其复制到一个新目录,如./myCopyDirectory 为:./myCopyDirectory/DirectoryC/src/main/resources/project/schema.project

所以我需要一个 for 或 while 循环,请原谅我糟糕的伪代码,但这是我的愿景

场景 1:动态复制文件

for FILE in `find . -type f -printf "%p\n"`; do if [ ${FILE##*.} in extensions.txt ]; then mkdir -p ./myCopyDirectory/DirectoryC/src/main/resources/project/ && cp ./DirectoryC/src/main/resources/project/schema.project ./myCopyDirectory/DirectoryC/src/main/resources/project/schema.project

场景 2:构建匹配的文件列表并复制它们

for FILE in `find . -type f -printf "%p\n"`; do if [ ${FILE##*.} in extensions.txt ]; then echo $FILE >> listOfFiles.txt

for FILE in `cat listOfFiles.txt`; do filename="${FILE##*/}" && dir="${FILE:0:${#FILE} - ${#filename}}" && if [ -e ./myCopyDirectory/$dir ]; then mkdir -p ./myCopyDirectory/$dir && cp $FILE ./myCopyDirectory

场景 3:只需将 cp -R 所有三个目录都指向 ./myCopyDirectory 并删除所有与 extensions.txt 文件中的扩展名不匹配的文件

请原谅我糟糕的伪代码。我只是想完成这项工作,而且有点超出我的深度。我可以创建一个 PERL 或 Python 脚本来执行此操作,但这似乎没有必要。

【问题讨论】:

标签: linux bash shell awk sed


【解决方案1】:

您实际上不需要 for 循环;事实上,您可以利用find-exec 选项将复杂的shell 命令传递给它来处理“目录保留”复制过程。

以下单行应该可以工作(解释如下)。

find root1 -regex '.*\.\(ext1\|ext2\)$' -exec sh -c 'dir=${1%/*}; dir=${dir/root1/root2}; file=${1##*/}; mkdir -p $dir && cp $1 $dir/$file' _ {} \;

我已经通过创建以下示例目录树对其进行了测试,

$ mkdir root1
$ mkdir root1/sub
$ mkdir root1/sub/dir
$ touch root1/a.ext1 root1/a.ext2 root1/a.ext3 root1/sub/a.ext1 root1/sub/a.ext2 root1/sub/a.ext3 root1/sub/dir/a.ext1 root1/sub/dir/a.ext2 root1/sub/dir/a.ext3
$ tree root1/
root1/
├── a.ext1
├── a.ext2
├── a.ext3
└── sub
    ├── a.ext1
    ├── a.ext2
    ├── a.ext3
    └── dir
        ├── a.ext1
        ├── a.ext2
        └── a.ext3

2 directories, 9 files

然后在上面执行命令并检查结果

$ find root1 -regex '.*\.\(ext1\|ext2\)$' -exec sh -c 'dir=${1%/*}; dir=${dir/root1/root2}; file=${1##*/}; mkdir -p $dir && cp $1 $dir/$file' _ {} \;
$ tree root2
root2
├── a.ext1
├── a.ext2
└── sub
    ├── a.ext1
    ├── a.ext2
    └── dir
        ├── a.ext1
        └── a.ext2

2 directories, 6 files
  • -regex 选项用于查找扩展名为 ext1ext2 的文件;
  • -exec 选项用于对找到的每个文件执行以下shell 命令;
  • 此命令通过-c 选项传递命令字符串,然后是参数0{} 的虚拟占位符_,这是find 找到的文件的名称,对于参数1
  • shell 命令字符串
    • 提取每个文件的目录dir,方法是删除最后一个/ 以及$1 后面的任何内容(已通过{}),
    • 然后通过将root2 替换为root1 来更改它;
    • 同样,它提取文件名file
    • 最后,它使用mkdir 创建新的目录结构,并复制其中的文件。

我没有包含 -type f 选项,但如果您确实有扩展名等于您正在查找的文件夹名称之一的文件夹名称,则可以。

【讨论】:

    【解决方案2】:

    这似乎运作良好。我感谢所有帮助过的人。拜托,欢迎改进和建议!再次感谢。

    find ./myDirToSearch -type f -regex ".*\.\(sln\|csproj\|cs\|xaml\|cshtml\|javasln\|project\|java\)" -exec cp --parents \{\} ./myCopyDir \;
    

    【讨论】:

    • 为什么是\{\} 而不是更好的'{}'?我不知道--parents
    【解决方案3】:

    您可以尝试findwhile read loop 以及一些shell 功能。

    #!/usr/bin/env bash
    
    shopt -s extglob
    
    ##: If bash is lower that v4, one alternative is.
    ##: while read -r lines; do extensions+=("${lines#*.}"); done < file_with_extension.txt
    
    ##: This assumes that the file_with_extensions.txt is in the same
    ##: directory as the files/directory that you're going to process, 
    ##: change the correct path e.g. /path/to/file_with_extension.txt
    
    mapfile -t extensions < file_with_extension.txt
    
    ##: Add as much directory you need.
    Dirs=(
      ./DirectoryC/src/main/resources/project/
      ./DirectoryB/src/main/resources/db/hsqldb/
      ./DirectoryA/src/main/resources/static/resources/images
      ./DirectoryA/src/main/resources/static/resources/build
      /AnotherDirectory/From/another/Path
      /A/Not/So/distant/Directory/From/Far/Far/Away
      /One/Directory/To/Rule/Em/All
    )
    
    ext=$(IFS='|'; printf '%s' "*.@(${extensions[*]#*.})" )
    
    dest=./myCopyDirectory
    
    while IFS= read -d '' -r files ; do
      if [[ $files = $ext ]]; then
         echo mkdir -p "$dest/${files%/*}" && echo cp -v "${files}" "$dest/${files%/*}"
      fi
    done < <(find "${Dirs[@]}" -type f -print0)
    

    • 如果您认为输出正确,请删除echo

    • 文件和路径名中的空格、制表符和换行符应该是安全的,这是其他帖子关于此问题的唯一优势。

    • 缺点?它需要bash4+,因为mapfile(有关解决方法,请参阅脚本的评论),它不是oneliner :-)


    一个示例模拟。

    mkdir -p /tmp/testing123 && cd /tmp/testing123
    
    mkdir -p ./DirectoryC/src/main/resources/project/
    mkdir -p ./DirectoryB/src/main/resources/db/hsqldb/
    mkdir -p ./DirectoryA/src/main/resources/static/resources/images
    mkdir -p ./DirectoryA/src/main/resources/static/resources/build/
    
    touch ./DirectoryC/src/main/resources/project/schema.project
    touch ./DirectoryB/src/main/resources/db/hsqldb/data.sql
    touch ./DirectoryA/src/main/resources/static/resources/images/spring-pivotal-logo.png
    touch ./DirectoryA/src/main/resources/static/resources/build/home.ini
    

    确保上面的scriptfiles_with_extensions.txt 与您当前的密码/密码在同一目录中。

    运行脚本。

    ./myscript
    

    输出

    'DirectoryA/src/main/resources/static/resources/images/spring-pivotal-logo.png' -> './myCopyDirectory/DirectoryA/src/main/resources/static/resources/images/spring-pivotal-logo.png'
    'DirectoryA/src/main/resources/static/resources/build/home.ini' -> './myCopyDirectory/DirectoryA/src/main/resources/static/resources/build/home.ini'
    'DirectoryB/src/main/resources/db/hsqldb/data.sql' -> './myCopyDirectory/DirectoryB/src/main/resources/db/hsqldb/data.sql'
    'DirectoryC/src/main/resources/project/schema.project' -> './myCopyDirectory/DirectoryC/src/main/resources/project/schema.project'
    

    查看myCopyDirectory的目录/文件

    find myCopyDirectory/ -type f
    

    输出

    myCopyDirectory/DirectoryC/src/main/resources/project/schema.project
    myCopyDirectory/DirectoryB/src/main/resources/db/hsqldb/data.sql
    myCopyDirectory/DirectoryA/src/main/resources/static/resources/images/spring-pivotal-logo.png
    myCopyDirectory/DirectoryA/src/main/resources/static/resources/build/home.ini
    

    让我们分解一下。

    • shopt -s extglob 启用 shell 功能,以便 [[ ]] 中的测试可以工作。

    • mapfile -t extensions &lt; file_with_extension.txt 将文件中的文件扩展名保存到名为extensions的数组中

    • ext=$(IFS='|'; printf '%s' "*.@(${extensions[*]#*.})" )extglob 结构中使用IFS 的值格式化名为extension 的数组,了解[[ ]] 测试内部。 #*. 删除数组中每个元素/条目的前导 . 点。

    • dest=./myCopyDirectory 将前导目录结构保存在名为dest的变量中

    • while IFS= read -d '' -r files 默认情况下,read 去除前导和尾随空格,因此需要IFS=(这是默认值)来禁用该功能。 -d ''null 分隔输入是安全的,-r 对于有反斜杠的输入是安全的。

    • [[ $files = $ext ]] 如果来自find 的文件与列表中的扩展名匹配,列表中已转换为数组并转换为extglob 可以理解的格式。

    • mkdir -p "$dest/${files%/*}" 创建前导目录结构加上匹配文件的目录结构。 -p为您省去了很多麻烦和错误,见mkdir --helpinfo mkdirman mkdir

    • cp -v "${files}" "$dest/${files%/*}" 将匹配的文件复制(-v 是详细的)到具有所需结构的新创建目录中。 ${files%/*} 从 find 的输出中去除尾随 /,因为 / 不允许出现在 file name 中(至少对于我正在使用的文件系统),可以保证你只是从文件名中删除路径。

    • &lt; &lt;(find "${Dir[@]}" -type f -print0)&lt;() 称为进程替换。 "${Dirs[@]}" 将扩展到所有元素,因为它是一个数组。 -type f 将确保您只对普通文件感兴趣,而不对目录等感兴趣。 -print0 输出 null 分隔结构。

    【讨论】:

      【解决方案4】:

      这可能对你有用(GNU 并行和查找):

      find . -type f |
      parallel --rpl '{d} s:.*?/::;s:/[^/]*$::' \
        'mkdir -p myCopyDirectory/{1d} && \
         [ {1} = {1.}{2} ] && \
         cp -v {1} myCopyDirectory/{1d}/{1/}' :::: - :::: ../fileExts.txt
      

      使用 find 命令仅打印当前目录内/下的文件。

      将生成的文件字符串作为参数 1 传递到并行命令中。

      并行定义一个名为{d}的替换字符串,这会从输入字符串中去除顶层目录和文件名。

      使用上述字符串在当前目录中创建一个目录(使用-p 选项强制创建中间目录)。

      根据参数 2 所需的文件扩展名测试当前文件(将这些扩展名保存在当前文件上方或任何地方的目录中的文本文件中)。

      如果扩展名匹配,则使用详细选项将当前文件 cp 到创建的备份中,以便查看复制的文件(这可能会被删除以进行静默运行)。

      出于测试目的,请同时使用--dryrun 选项来查看生成的命令,一旦获得批准,就可以删除该选项。

      【讨论】:

        【解决方案5】:

        此解决方案从文件中读取find 命令的文件扩展名:“./extensions.txt”。

        mapfile -t < ./extensions.txt; exts=$(IFS='|'; printf '%s' "${MAPFILE[*]#*.}" ); exts=`echo $exts | sed 's/|/\\\|/'`; find ./myDirToSearch -type f -regex ".*\.\($exts\)" -exec cp --parents {} ./myCopyDir \;
        

        相同,但分成多行以便于阅读:

        mapfile -t < ./extensions.txt; \
        exts=$(IFS='|'; printf '%s' "${MAPFILE[*]#*.}" ); \
        exts=`echo $exts | sed 's/|/\\\|/'`; \
        find ./myDirToSearch -type f -regex ".*\.\($exts\)" -exec cp --parents {} ./myCopyDir \;
        

        或者可以使用 awk 代替 bash 来设置“exts”:

        exts=`awk 'BEGIN { FS = "." } \
            { exts[NR] = $2 } \
            END { i = 0; \
                for (key in exts) { \
                    printf "%s", exts[key]; \
                    if (++i != NR) { printf "\\\|" } \
                 } \
                 printf "\n" \
            }' ./extensions.txt`; \
        find ./myDirToSearch -type f -regex ".*\.\($exts\)" -exec cp --parents {} ./myCopyDir \;
        

        或者对于我们在 Mac 上的用户(注意 find-E 选项以及删除正则表达式和 exts bash 变量中的转义字符):

        exts=`awk 'BEGIN { FS = "." } { exts[NR] = $2 } END { i = 0; for (key in exts) { printf "%s", exts[key]; if (++i != NR) { printf "|" } } printf "\n" }' ./extensions.txt`; find -E ./myDirToSearch -type f -regex ".*\.($exts)" -exec bash -c 'dir=`dirname {}`; dir=./myCopyDir/$dir; mkdir -p $dir; cp {} $dir' \;
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-01-25
          • 2011-09-09
          • 2011-03-07
          • 1970-01-01
          • 1970-01-01
          • 2022-01-21
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多