【问题标题】:Recursively move files of certain type and keep their directory structure递归移动特定类型的文件并保持其目录结构
【发布时间】:2012-01-10 03:19:54
【问题描述】:

我有一个目录,其中包含多个带有 movjpg 文件的子目录。

/dir/
  /subdir-a/  # contains a-1.jpg, a-2.jpg, a-1.mov
  /subdir-b/  # contains b-1.mov
  /subdir-c/  # contains c-1.jpg
  /subdir-d/  # contains d-1.mov
  ...         # more directories with the same pattern

我需要找到一种使用命令行工具(最好是在 Mac OSX 上)将所有 mov 文件移动到新位置的方法。但是,一个要求是保持目录结构,即:

/dir/
  /subdir-a/  # contains a-1.mov
  /subdir-b/  # contains b-1.mov
              # NOTE: subdir-c isn't copied because it doesn't have mov files 
  /subdir-d/  # contains d-1.mov
  ...

我熟悉find, grep, and xargs,但不知道如何解决这个问题。非常感谢您!

【问题讨论】:

  • 如果有空间,您可以cp 目录和find newdir ! -name '*.mov' -delete。它可能不是最好的解决方案,但实际上它可能会在您在这里得到更好的答案之前完成。

标签: bash shell command-line find grep


【解决方案1】:

这稍微取决于您的操作系统,尤其是您的tar 版本中的设施以及您是否有命令cpio。它还取决于您的文件名中是否有换行符(特别是);大多数人没有。

选项#1

cd /old-dir
find . -name '*.mov' -print | cpio -pvdumB /new-dir

选项 #2

find . -name '*.mov' -print | tar -c -f - -T - |
(cd /new-dir; tar -xf -)

cpio 命令具有传递(复制)模式,在其标准输入中给出文件名列表(每行一个),该模式完全符合您的要求。

tar 命令的某些版本具有从标准输入读取文件名列表的选项,每行一个;在 MacOS X 上,该选项是 -T -(其中唯一的 - 表示“标准输入”)。对于第一个tar 命令,选项-f - 表示(在使用-c 编写存档的上下文中,写入标准输出);在第二个tar 命令中,-x 选项表示-f - 表示“从标准输入读取”。

可能还有其他选择;请仔细查看tar 的手册页或帮助输出。

此过程复制文件而不是移动它们。操作的后半部分是:

find . -name '*.mov' -exec rm -f {} +

【讨论】:

  • 我们可以用find-delete选项代替-exec rm吗?
  • 你可以使用-print0/-0来处理换行问题,你应该使用-delete而不是rm -f
  • 是的,如果您的find 变体支持,您可能可以使用-delete。在第 7 版 UNIX 中它不是一个选项(实际上在 POSIX 2008 中仍然不是一个选项)。我倾向于忘记非标准选项,这样我写的东西就有最大的机会在许多系统上工作。与-print0 / -0 选项类似;我不确定tarcpio 是否支持它们;具体来说,MacOS X 10.7.2 上的 cpiotar 都不支持它。这就是为什么我将“文件名中没有换行符”警告放入答案中的原因。其他都可以。
  • 当我添加 -delete 文件在 cpio 之前被删除。如何避免?
  • 我认为您并没有因为这个答案而受到适当的喜爱(尤其是 cpio 部分)。与此相比,实际上还有 30 或 40 个其他 stackexchange 答案很糟糕 |并在此之前出现在 Google 上。对你的 SEO 标准感到羞耻!很好的答案 +1。
【解决方案2】:

ASSERT:文件中没有换行符。但是,空格是 AOK。

# TEST FIRST: CREATION OF FOLDERS
find . -type f -iname \*.mov -printf '%h\n' | sort | uniq | xargs -n 1 -d '\n' -I '{}' echo mkdir -vp "/TARGET_FOLDER_ROOT/{}"

# EXECUTE CREATION OF EMPTY TARGET FOLDERS
find . -type f -iname \*.mov -printf '%h\n' | sort | uniq | xargs -n 1 -d '\n' -I '{}' mkdir -vp "/TARGET_FOLDER_ROOT/{}"

# TEST FIRST: REVIEW FILES TO BE MOVED
find . -type f -iname \*.mov -exec echo mv {} /TARGET_FOLDER_ROOT/{} \;

# EXECUTE MOVE FILES
find . -type f -iname \*.mov -exec mv {} /TARGET_FOLDER_ROOT/{} \;

【讨论】:

  • 创建文件夹时找不到 . -type d -exec mkdir -vp /TARGET_FOLDER_ROOT/{} \;还工作吗?通常是否存在任何关于符号链接的陷阱?
【解决方案3】:

作为大文件,如果它们在同一个文件系统上,您不想复制它们,而只是在移动时复制它们的目录结构。 你可以使用这个功能:

# moves a file (or folder) preserving its folder structure (relative to source path)
# usage: move_keep_path source destination
move_keep_path () {
  # create directories up to one level up
  mkdir -p "`dirname "$2"`"
  mv "$1" "$2"
}

或者,添加对合并现有目录的支持:

# moves a file (or folder) preserving its folder structure (relative to source path)
# usage: move_keep_path source destination
move_keep_path () {
  # create directories up to one level up
  mkdir -p "`dirname "$2"`"
  if [[ -d "$1" && -d "$2" ]]; then
    # merge existing folder
    find "$1" -depth 1 | while read file; do
      # call recursively for all files inside
      mv_merge "$file" "$2/`basename "$file"`"
    done
    # remove after merge
    rmdir "$1"
  else
    # either file or non-existing folder
    mv "$1" "$2"
  fi
}

【讨论】:

  • 这可以扩展为使用通配符吗?例如move_keep_path /path/to/files/*.txt /dest
【解决方案4】:

复制以下文件更容易:

cp --parents some/folder/*/*.mov new_folder/

【讨论】:

    【解决方案5】:

    从"dir的父目录执行这个:

    find ./dir -name "*.mov" | xargs tar cif mov.tar
    

    然后 cd 到要移动文件的目录并执行:

    tar xvf /path/to/parent/directory/of"dir"/mov.tar
    

    【讨论】:

      【解决方案6】:

      如果您想将所有 mov 文件移动到名为新位置的目录,这应该可以工作 -

      find ./dir -iname '*.mov' -exec mv '{}' ./newlocation \;
      

      但是,如果您希望移动 mov 文件及其子目录,那么您可以执行以下操作 -

      第 1 步:使用 cp 将 /dir 的整个结构复制到新位置

      cp -iprv dir/ newdir
      

      第 2 步:从 newdir 中找到 jpg 文件并将其删除。

      find ./newdir -iname "*.jpg" -delete
      

      测试:

      [jaypal:~/Temp] ls -R a
      a.mov aa    b.mov
      
      a/aa:
      aaa   c.mov d.mov
      
      a/aa/aaa:
      e.mov f.mov
      [jaypal:~/Temp] mkdir d
      [jaypal:~/Temp] find ./a -iname '*.mov' -exec mv '{}' ./d \;
      [jaypal:~/Temp] ls -R d
      a.mov b.mov c.mov d.mov e.mov f.mov
      

      【讨论】:

      • 第一个命令将创建文件/newlocation/dir/path/under/olddir/file.mov(而不是/newlocation/path/under/olddir/file.mov);您需要在要从中移动文件的顶级目录中启动find 命令。
      • 感谢乔恩,我添加了几个测试,并没有真正注意到任何区别。是因为我用的例子吗?
      • 我提到的第一个命令是find 命令,而不是各种cp 命令。
      • 对不起我的坏乔恩。我用find 命令做了一个测试。它将文件移动到新目录。正如我在答案中所说,它不会移动子目录。
      • 呃……是的,我也弄错了。我最初的评论是错误的(我想当名称流式传输到cpio 时会发生什么)。所以,有一个问题;这不是我诊断的问题。我的(一半)不好。
      【解决方案7】:

      我修改了@djjeck 的功能,因为它没有按我的需要工作。下面的函数将源文件移动到目标目录,同时在源文件路径中创建所需的层次结构(参见下面的示例):

      # moves a file, creates needed levels of hierarchy in destination
      # usage: move_with_hierarchy source_file destination top_level_directory
      move_with_hierarchy () {
          path_tail=$(dirname $(realpath --relative-to="$3" "$1"))
      
          cd "$2"
          mkdir -p $path_tail
          cd - > /dev/null
      
          mv "$1" "${2}/${path_tail}"
      }
      

      示例:

      $ ls /home/sergei/tmp/dir1/dir2/bla.txt
      /home/sergei/tmp/dir1/dir2/bla.txt
      $ rm -rf tmp2
      $ mkdir tmp2
      $ move_with_hierarchy /home/sergei/tmp/dir1/dir2/bla.txt /home/sergei/tmp2 /home/sergei/tmp
      $ tree ~/tmp2
      /home/sergei/tmp2
      └── dir1
          └── dir2
              └── bla.txt
      
      2 directories, 1 file
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-01
        • 1970-01-01
        相关资源
        最近更新 更多